Skip to content

Commit ed382bb

Browse files
committed
doc/tutorial: adding a tutorial for MorphFuncy and proper docstring
1 parent 87f1bff commit ed382bb

File tree

3 files changed

+166
-89
lines changed

3 files changed

+166
-89
lines changed

doc/source/quickstart.rst

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,67 @@ There is also support for morphing from a nanoparticle to a bulk. When
397397
applying the inverse morphs, it is recommended to set ``--rmax=psize``
398398
where ``psize`` is the longest diameter of the nanoparticle.
399399

400+
MorphFuncy: Applying custom functions
401+
-------------------------------------
402+
403+
The ``MorphFuncy`` morph allows users to apply a custom Python function
404+
to the y-axis values of a dataset, enabling flexible and user-defined
405+
transformations.
406+
407+
In this tutorial, we walk through how to use ``MorphFuncy`` with an example
408+
transformation. Unlike other morphs that can be run from the command line,
409+
``MorphFuncy`` requires a Python function and is therefore intended to be used
410+
within the Python API.
411+
412+
1. Import the necessary modules into your Python script ::
413+
414+
from diffpy.morph.morph_api import morph, morph_default_config
415+
import numpy as np
416+
417+
2. Define a custom Python function to apply a transformation to the data.
418+
For this example, we will use a simple linear transformation that
419+
scales the input and applies an offset ::
420+
421+
def linear_function(x, y, scale, offset):
422+
return (scale * x) * y + offset
423+
424+
3. In this example, we use a sine function for the morph data and generate
425+
the target data by applying the linear transformation with known scale
426+
and offset to it ::
427+
428+
x_morph = np.linspace(0, 10, 101)
429+
y_morph = np.sin(x_morph)
430+
x_target = x_morph.copy()
431+
y_target = np.sin(x_target) * 20 * x_target + 0.8
432+
433+
4. Set up the configuration dictionary. This includes both the
434+
transformation parameters (our initial guess) and the transformation
435+
function itself ::
436+
437+
cfg = morph_default_config(funcy={"scale": 1.2, "offset": 0.1})
438+
cfg["function"] = linear_function
439+
440+
5. Run the morph using the API function ``morph(...)``. This will apply the
441+
user-defined function and refine the parameters to best align the morph data
442+
with the target data ::
443+
444+
morph_rv = morph(x_morph, y_morph, x_target, y_target, **cfg)
445+
446+
6. Extract the morphed output and the fitted parameters from the result ::
447+
448+
morphed_cfg = morph_rv["morphed_config"]
449+
x_morph_out, y_morph_out, x_target_out, y_target_out = morph_rv["morph_chain"].xyallout
450+
451+
fitted_parameters = morphed_cfg["funcy"]
452+
print("Fitted scale:", fitted_parameters["scale"])
453+
print("Fitted offset:", fitted_parameters["offset"])
454+
455+
As you can see, the fitted scale and offset values match the ones used
456+
to generate the target (scale=20 & offset=0.8). This example shows how
457+
``MorphFuncy`` can be used to fit and apply custom transformations. Now
458+
it's your turn to experiment with other custom functions that may be useful
459+
for analyzing your data.
460+
400461
Bug Reports
401462
===========
402463

src/diffpy/morph/morphs/morphfuncy.py

Lines changed: 56 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,61 @@
1+
"""class MorphFuncy -- apply a user-supplied python function to the y-axis."""
2+
13
from diffpy.morph.morphs.morph import LABEL_GR, LABEL_RA, Morph
24

35

46
class MorphFuncy(Morph):
5-
"""Apply the user-supplied Python function to the y-coordinates of the
6-
morph data"""
7+
"""General morph function that applies a user-supplied function to the
8+
y-coordinates of morph data to make it align with a target.
9+
10+
Configuration Variables
11+
-----------------------
12+
function: callable
13+
The user-supplied function that applies a transformation to the
14+
y-coordinates of the data.
15+
16+
parameters: dict
17+
A dictionary of parameters to pass to the function.
18+
These parameters are unpacked using **kwargs.
19+
20+
Returns
21+
-------
22+
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
23+
where the target values remain the same and the morph data is
24+
transformed according to the user-specified function and parameters
25+
The morphed data is returned on the same grid as the unmorphed data
26+
27+
Example
28+
-------
29+
Import the funcy morph function:
30+
31+
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy
32+
33+
Define or import the user-supplied transformation function:
34+
35+
>>> def sine_function(x, y, amplitude, frequency):
36+
>>> return amplitude * np.sin(frequency * x) * y
37+
38+
Provide initial guess for parameters:
39+
40+
>>> parameters = {'amplitude': 2, 'frequency': 2}
41+
42+
Run the funcy morph given input morph array (x_morph, y_morph)and target
43+
array (x_target, y_target):
44+
45+
>>> morph = MorphFuncy()
46+
>>> morph.function = sine_function
47+
>>> morph.funcy = parameters
48+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
49+
... morph.morph(x_morph, y_morph, x_target, y_target)
50+
51+
To access parameters from the morph instance:
52+
53+
>>> x_morph_in = morph.x_morph_in
54+
>>> y_morph_in = morph.y_morph_in
55+
>>> x_target_in = morph.x_target_in
56+
>>> y_target_in = morph.y_target_in
57+
>>> parameters_out = morph.funcy
58+
"""
759

860
# Define input output types
961
summary = "Apply a Python function to the y-axis data"
@@ -14,53 +66,8 @@ class MorphFuncy(Morph):
1466
parnames = ["funcy"]
1567

1668
def morph(self, x_morph, y_morph, x_target, y_target):
17-
"""General morph function that applies a user-supplied function to the
18-
y-coordinates of morph data to make it align with a target.
19-
20-
Configuration Variables
21-
-----------------------
22-
function: callable
23-
The user-supplied function that applies a transformation to the
24-
y-coordinates of the data.
25-
26-
parameters: dict
27-
A dictionary of parameters to pass to the function.
28-
These parameters are unpacked using **kwargs.
29-
30-
Returns
31-
-------
32-
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
33-
where the target values remain the same and the morph data is
34-
transformed according to the user-specified function and parameters
35-
The morphed data is returned on the same grid as the unmorphed data
36-
37-
Example
38-
-------
39-
Import the funcy morph function:
40-
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy
41-
42-
Define or import the user-supplied transformation function:
43-
>>> def sine_function(x, y, amplitude, frequency):
44-
>>> return amplitude * np.sin(frequency * x) * y
45-
46-
Provide initial guess for parameters:
47-
>>> parameters = {'amplitude': 2, 'frequency': 2}
48-
49-
Run the funcy morph given input morph array (x_morph, y_morph)
50-
and target array (x_target, y_target):
51-
>>> morph = MorphFuncy()
52-
>>> morph.function = sine_function
53-
>>> morph.funcy = parameters
54-
>>> x_morph_out, y_morph_out, x_target_out, y_target_out = morph.morph(
55-
... x_morph, y_morph, x_target, y_target)
56-
57-
To access parameters from the morph instance:
58-
>>> x_morph_in = morph.x_morph_in
59-
>>> y_morph_in = morph.y_morph_in
60-
>>> x_target_in = morph.x_target_in
61-
>>> y_target_in = morph.y_target_in
62-
>>> parameters_out = morph.funcy
63-
"""
69+
"""Apply the user-supplied Python function to the y-coordinates of the
70+
morph data"""
6471
Morph.morph(self, x_morph, y_morph, x_target, y_target)
6572

6673
self.y_morph_out = self.function(

src/diffpy/morph/morphs/morphsqueeze.py

Lines changed: 49 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"""class MorphSqueeze -- Apply a polynomial to squeeze the morph function."""
2+
13
import numpy as np
24
from numpy.polynomial import Polynomial
35
from scipy.interpolate import CubicSpline
@@ -6,8 +8,51 @@
68

79

810
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+
"""Squeeze the morph function.
12+
13+
This applies a polynomial to squeeze the morph non-linearly.
14+
15+
Configuration Variables
16+
-----------------------
17+
squeeze : Dictionary
18+
The polynomial coefficients {a0, a1, ..., an} for the squeeze
19+
function where the polynomial would be of the form
20+
a0 + a1*x + a2*x^2 and so on. The order of the polynomial is
21+
determined by the length of the dictionary.
22+
23+
Returns
24+
-------
25+
A tuple (x_morph_out, y_morph_out, x_target_out, y_target_out)
26+
where the target values remain the same and the morph data is
27+
shifted according to the squeeze. The morphed data is returned on
28+
the same grid as the unmorphed data.
29+
30+
Example
31+
-------
32+
Import the squeeze morph function:
33+
34+
>>> from diffpy.morph.morphs.morphsqueeze import MorphSqueeze
35+
36+
Provide initial guess for squeezing coefficients:
37+
38+
>>> squeeze_coeff = {"a0":0.1, "a1":-0.01, "a2":0.005}
39+
40+
Run the squeeze morph given input morph array (x_morph, y_morph) and target
41+
array (x_target, y_target):
42+
43+
>>> morph = MorphSqueeze()
44+
>>> morph.squeeze = squeeze_coeff
45+
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
46+
... morph(x_morph, y_morph, x_target, y_target)
47+
48+
To access parameters from the morph instance:
49+
50+
>>> x_morph_in = morph.x_morph_in
51+
>>> y_morph_in = morph.y_morph_in
52+
>>> x_target_in = morph.x_target_in
53+
>>> y_target_in = morph.y_target_in
54+
>>> squeeze_coeff_out = morph.squeeze
55+
"""
1156

1257
# Define input output types
1358
summary = "Squeeze morph by polynomial shift"
@@ -22,44 +67,8 @@ class MorphSqueeze(Morph):
2267
extrap_index_high = None
2368

2469
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 : Dictionary
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 dictionary.
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 = {"a0":0.1, "a1":-0.01, "a2":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-
"""
70+
"""Apply a polynomial to squeeze the morph function. The morphed
71+
data is returned on the same grid as the unmorphed data."""
6372
Morph.morph(self, x_morph, y_morph, x_target, y_target)
6473

6574
coeffs = [self.squeeze[f"a{i}"] for i in range(len(self.squeeze))]

0 commit comments

Comments
 (0)