Skip to content

Commit 6fdc0af

Browse files
authored
Morphfuncxy (#239)
* Add morphfuncxy (scrappy) * Modifications * Add morph funcx (scrappy) * Add funcxy, funcx * Fix docstrings for morphfuncx,y * Update funcx * funcx docstring * News * Fix changing r-grid issue in refine * Add padding as well * Move epsilon * Morph rgrid change * Merge * RMS averaging * Final testing suite * Finish documentation * Doc formatting check
1 parent af80472 commit 6fdc0af

18 files changed

+942
-98
lines changed

docs/source/morphpy.rst

Lines changed: 310 additions & 19 deletions
Large diffs are not rendered by default.

news/morphfuncxy.rst

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
**Added:**
2+
3+
* morphfuncx added: apply a function to the grid of your morphed function; this function should maintain the monotonic increasing nature of the grid
4+
* morphfuncxy added: apply a general function which can modify both the ordinate and abscissa; useful when applying fourier transform or integration functions
5+
6+
**Changed:**
7+
8+
* <news item>
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_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def morph(
209209
refpars.append("baselineslope")
210210
elif k == "funcy":
211211
morph_inst = morph_cls()
212-
morph_inst.function = rv_cfg.get("function", None)
212+
morph_inst.function = rv_cfg.get("funcy_function", None)
213213
if morph_inst.function is None:
214214
raise ValueError(
215215
"Must provide a 'function' when using 'parameters'"

src/diffpy/morph/morph_io.py

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -79,32 +79,41 @@ def single_morph_output(
7979
rw_pos + idx, (f"squeeze a{idx}", sq_dict[f"a{idx}"])
8080
)
8181
mr_copy = dict(morph_results_list)
82-
funcy_function = None
83-
if "function" in mr_copy:
84-
funcy_function = mr_copy.pop("function")
85-
print(funcy_function)
86-
if "funcy" in mr_copy:
87-
fy_dict = mr_copy.pop("funcy")
88-
rw_pos = list(mr_copy.keys()).index("Rw")
89-
morph_results_list = list(mr_copy.items())
90-
for idx, key in enumerate(fy_dict):
91-
morph_results_list.insert(
92-
rw_pos + idx, (f"funcy {key}", fy_dict[key])
93-
)
94-
mr_copy = dict(morph_results_list)
82+
83+
# Handle special inputs (functional remove)
84+
func_dicts = {
85+
"funcxy": [None, None],
86+
"funcx": [None, None],
87+
"funcy": [None, None],
88+
}
89+
for func in func_dicts.keys():
90+
if f"{func}_function" in mr_copy:
91+
func_dicts[func][0] = mr_copy.pop(f"{func}_function")
92+
if func in mr_copy:
93+
func_dicts[func][1] = mr_copy.pop(func)
94+
rw_pos = list(mr_copy.keys()).index("Rw")
95+
morph_results_list = list(mr_copy.items())
96+
for idx, key in enumerate(func_dicts[func][1]):
97+
morph_results_list.insert(
98+
rw_pos + idx, (f"{func} {key}", func_dicts[func][1][key])
99+
)
100+
mr_copy = dict(morph_results_list)
101+
95102
# Normal inputs
96103
morphs_out += "\n".join(
97104
f"# {key} = {mr_copy[key]:.6f}" for key in mr_copy.keys()
98105
)
99-
# Special inputs (functional)
100-
if funcy_function is not None:
101-
morphs_in += '# funcy function =\n"""\n'
102-
f_code, _ = inspect.getsourcelines(funcy_function)
103-
n_leading = len(f_code[0]) - len(f_code[0].lstrip())
104-
for idx, f_line in enumerate(f_code):
105-
f_code[idx] = f_line[n_leading:]
106-
morphs_in += "".join(f_code)
107-
morphs_in += '"""\n'
106+
107+
# Handle special inputs (functional add)
108+
for func in func_dicts.keys():
109+
if func_dicts[func][0] is not None:
110+
morphs_in += f'# {func} function =\n"""\n'
111+
f_code, _ = inspect.getsourcelines(func_dicts[func][0])
112+
n_leading = len(f_code[0]) - len(f_code[0].lstrip())
113+
for idx, f_line in enumerate(f_code):
114+
f_code[idx] = f_line[n_leading:]
115+
morphs_in += "".join(f_code)
116+
morphs_in += '"""\n'
108117

109118
# Printing to terminal
110119
if stdout_flag:

src/diffpy/morph/morphapp.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -520,12 +520,26 @@ def single_morph(
520520

521521
# Python-Specific Morphs
522522
if pymorphs is not None:
523-
# funcy value is a tuple (function,{param_dict})
523+
# funcxy/funcx/funcy value is a tuple (function,{param_dict})
524+
if "funcxy" in pymorphs:
525+
mfxy_function = pymorphs["funcxy"][0]
526+
mfxy_params = pymorphs["funcxy"][1]
527+
chain.append(morphs.MorphFuncxy())
528+
config["funcxy_function"] = mfxy_function
529+
config["funcxy"] = mfxy_params
530+
refpars.append("funcxy")
531+
if "funcx" in pymorphs:
532+
mfx_function = pymorphs["funcx"][0]
533+
mfx_params = pymorphs["funcx"][1]
534+
chain.append(morphs.MorphFuncx())
535+
config["funcx_function"] = mfx_function
536+
config["funcx"] = mfx_params
537+
refpars.append("funcx")
524538
if "funcy" in pymorphs:
525539
mfy_function = pymorphs["funcy"][0]
526540
mfy_params = pymorphs["funcy"][1]
527541
chain.append(morphs.MorphFuncy())
528-
config["function"] = mfy_function
542+
config["funcy_function"] = mfy_function
529543
config["funcy"] = mfy_params
530544
refpars.append("funcy")
531545

@@ -692,6 +706,9 @@ def single_morph(
692706

693707
# FOR FUTURE MAINTAINERS
694708
# Any new morph should have their input morph parameters updated here
709+
# You should also update the IO in morph_io
710+
# if you think there requires special handling
711+
695712
# Input morph parameters
696713
morph_inputs = {
697714
"scale": scale_in,
@@ -705,11 +722,25 @@ def single_morph(
705722
for idx, _ in enumerate(squeeze_dict):
706723
morph_inputs.update({f"squeeze a{idx}": squeeze_dict[f"a{idx}"]})
707724
if pymorphs is not None:
725+
if "funcxy" in pymorphs:
726+
for funcxy_param in pymorphs["funcxy"][1].keys():
727+
morph_inputs.update(
728+
{
729+
f"funcxy {funcxy_param}": pymorphs["funcxy"][1][
730+
funcxy_param
731+
]
732+
}
733+
)
708734
if "funcy" in pymorphs:
709735
for funcy_param in pymorphs["funcy"][1].keys():
710736
morph_inputs.update(
711737
{f"funcy {funcy_param}": pymorphs["funcy"][1][funcy_param]}
712738
)
739+
if "funcx" in pymorphs:
740+
for funcy_param in pymorphs["funcx"][1].keys():
741+
morph_inputs.update(
742+
{f"funcx {funcy_param}": pymorphs["funcx"][1][funcy_param]}
743+
)
713744

714745
# Output morph parameters
715746
morph_results = dict(config.items())

src/diffpy/morph/morphpy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def get_args(parser, params, kwargs):
2626

2727
def __get_morph_opts__(parser, scale, stretch, smear, plot, **kwargs):
2828
# Check for Python-specific options
29-
python_morphs = ["funcy"]
29+
python_morphs = ["funcy", "funcx", "funcxy"]
3030
pymorphs = {}
3131
for pmorph in python_morphs:
3232
if pmorph in kwargs:

src/diffpy/morph/morphs/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
from diffpy.morph.morphs.morph import Morph # noqa: F401
1919
from diffpy.morph.morphs.morphchain import MorphChain # noqa: F401
20+
from diffpy.morph.morphs.morphfuncx import MorphFuncx
21+
from diffpy.morph.morphs.morphfuncxy import MorphFuncxy
2022
from diffpy.morph.morphs.morphfuncy import MorphFuncy
2123
from diffpy.morph.morphs.morphishape import MorphISphere, MorphISpheroid
2224
from diffpy.morph.morphs.morphresolution import MorphResolutionDamping
@@ -42,6 +44,8 @@
4244
MorphShift,
4345
MorphSqueeze,
4446
MorphFuncy,
47+
MorphFuncx,
48+
MorphFuncxy,
4549
]
4650

4751
# End of file

src/diffpy/morph/morphs/morphfuncx.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Class MorphFuncx -- apply a user-supplied python function to the
2+
x-axis."""
3+
4+
from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph
5+
6+
7+
class MorphFuncx(Morph):
8+
"""Apply a custom function to the x-axis (grid) of the morph
9+
function.
10+
11+
General morph function that applies a user-supplied function to the
12+
x-coordinates of morph data to make it align with a target.
13+
14+
Notice: the function provided must preserve the monotonic
15+
increase of the grid.
16+
I.e. the function f applied on the grid x must ensure for all
17+
indices i<j, f(x[i]) < f(x[j]).
18+
19+
Configuration Variables
20+
-----------------------
21+
function: callable
22+
The user-supplied function that applies a transformation to the
23+
x-coordinates of the data.
24+
25+
parameters: dict
26+
A dictionary of parameters to pass to the function.
27+
28+
Returns
29+
-------
30+
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
31+
where the target values remain the same and the morph data is
32+
transformed according to the user-specified function and parameters
33+
The morphed data is returned on the same grid as the unmorphed data
34+
35+
Example
36+
-------
37+
Import the funcx morph function:
38+
39+
>>> from diffpy.morph.morphs.morphfuncx import MorphFuncx
40+
41+
Define or import the user-supplied transformation function:
42+
43+
>>> import numpy as np
44+
>>> def exp_function(x, y, scale, rate):
45+
>>> return abs(scale) * np.exp(rate * x)
46+
47+
Note that this transformation is monotonic increasing, so will preserve
48+
the monotonic increasing nature of the provided grid.
49+
50+
Provide initial guess for parameters:
51+
52+
>>> parameters = {'scale': 1, 'rate': 1}
53+
54+
Run the funcy morph given input morph array (x_morph, y_morph)and target
55+
array (x_target, y_target):
56+
57+
>>> morph = MorphFuncx()
58+
>>> morph.funcx_function = exp_function
59+
>>> morph.funcx = parameters
60+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
61+
... morph.morph(x_morph, y_morph, x_target, y_target)
62+
63+
To access parameters from the morph instance:
64+
65+
>>> x_morph_in = morph.x_morph_in
66+
>>> y_morph_in = morph.y_morph_in
67+
>>> x_target_in = morph.x_target_in
68+
>>> y_target_in = morph.y_target_in
69+
>>> parameters_out = morph.funcx
70+
"""
71+
72+
# Define input output types
73+
summary = "Apply a Python function to the x-axis data"
74+
xinlabel = LABEL_RA
75+
yinlabel = LABEL_GR
76+
xoutlabel = LABEL_RA
77+
youtlabel = LABEL_GR
78+
parnames = ["funcx_function", "funcx"]
79+
80+
def morph(self, x_morph, y_morph, x_target, y_target):
81+
"""Apply the user-supplied Python function to the x-coordinates
82+
of the morph data."""
83+
Morph.morph(self, x_morph, y_morph, x_target, y_target)
84+
self.x_morph_out = self.funcx_function(
85+
self.x_morph_in, self.y_morph_in, **self.funcx
86+
)
87+
return self.xyallout
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
"""Class MorphFuncxy -- apply a user-supplied python function to both
2+
the x and y axes."""
3+
4+
from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph
5+
6+
7+
class MorphFuncxy(Morph):
8+
"""Apply a custom function to the morph function.
9+
10+
General morph function that applies a user-supplied function to the
11+
morph data to make it align with a target.
12+
13+
This function may modify both the grid (x-axis) and function (y-axis)
14+
of the morph data.
15+
16+
The user-provided function must return a two-column 1D function.
17+
18+
Configuration Variables
19+
-----------------------
20+
function: callable
21+
The user-supplied function that applies a transformation to the
22+
grid (x-axis) and morph function (y-axis).
23+
24+
parameters: dict
25+
A dictionary of parameters to pass to the function.
26+
27+
Returns
28+
-------
29+
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
30+
where the target values remain the same and the morph data is
31+
transformed according to the user-specified function and parameters
32+
The morphed data is returned on the same grid as the unmorphed data
33+
34+
Example (EDIT)
35+
-------
36+
Import the funcxy morph function:
37+
38+
>>> from diffpy.morph.morphs.morphfuncxy import MorphFuncxy
39+
40+
Define or import the user-supplied transformation function:
41+
42+
>>> import numpy as np
43+
>>> def shift_function(x, y, hshift, vshift):
44+
>>> return x + hshift, y + vshift
45+
46+
Provide initial guess for parameters:
47+
48+
>>> parameters = {'hshift': 1, 'vshift': 1}
49+
50+
Run the funcy morph given input morph array (x_morph, y_morph)and target
51+
array (x_target, y_target):
52+
53+
>>> morph = MorphFuncxy()
54+
>>> morph.function = shift_function
55+
>>> morph.funcy = parameters
56+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
57+
... morph.morph(x_morph, y_morph, x_target, y_target)
58+
59+
To access parameters from the morph instance:
60+
61+
>>> x_morph_in = morph.x_morph_in
62+
>>> y_morph_in = morph.y_morph_in
63+
>>> x_target_in = morph.x_target_in
64+
>>> y_target_in = morph.y_target_in
65+
>>> parameters_out = morph.funcxy
66+
"""
67+
68+
# Define input output types
69+
summary = (
70+
"Apply a Python function to the data (y-axis) and data grid (x-axis)"
71+
)
72+
xinlabel = LABEL_RA
73+
yinlabel = LABEL_GR
74+
xoutlabel = LABEL_RA
75+
youtlabel = LABEL_GR
76+
parnames = ["funcxy_function", "funcxy"]
77+
78+
def morph(self, x_morph, y_morph, x_target, y_target):
79+
"""Apply the user-supplied Python function to the y-coordinates
80+
of the morph data."""
81+
Morph.morph(self, x_morph, y_morph, x_target, y_target)
82+
self.x_morph_out, self.y_morph_out = self.funcxy_function(
83+
self.x_morph_in, self.y_morph_in, **self.funcxy
84+
)
85+
return self.xyallout

src/diffpy/morph/morphs/morphfuncy.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ class MorphFuncy(Morph):
3434
3535
Define or import the user-supplied transformation function:
3636
37+
>>> import numpy as np
3738
>>> def sine_function(x, y, amplitude, frequency):
3839
>>> return amplitude * np.sin(frequency * x) * y
3940
@@ -45,7 +46,7 @@ class MorphFuncy(Morph):
4546
array (x_target, y_target):
4647
4748
>>> morph = MorphFuncy()
48-
>>> morph.function = sine_function
49+
>>> morph.funcy_function = sine_function
4950
>>> morph.funcy = parameters
5051
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
5152
... morph.morph(x_morph, y_morph, x_target, y_target)
@@ -65,13 +66,13 @@ class MorphFuncy(Morph):
6566
yinlabel = LABEL_GR
6667
xoutlabel = LABEL_RA
6768
youtlabel = LABEL_GR
68-
parnames = ["function", "funcy"]
69+
parnames = ["funcy_function", "funcy"]
6970

7071
def morph(self, x_morph, y_morph, x_target, y_target):
7172
"""Apply the user-supplied Python function to the y-coordinates
7273
of the morph data."""
7374
Morph.morph(self, x_morph, y_morph, x_target, y_target)
74-
self.y_morph_out = self.function(
75+
self.y_morph_out = self.funcy_function(
7576
self.x_morph_in, self.y_morph_in, **self.funcy
7677
)
7778
return self.xyallout

0 commit comments

Comments
 (0)