Skip to content

Commit 00419b4

Browse files
committed
Fix changing r-grid issue in refine
1 parent 1dd4d6d commit 00419b4

File tree

5 files changed

+220
-37
lines changed

5 files changed

+220
-37
lines changed

docs/source/morphpy.rst

Lines changed: 171 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -106,19 +106,26 @@ exclude: list of str
106106
Exclude a manipulations from refinement by name
107107
(e.g. exclude=["scale", "stretch"] excludes the scale and stretch morphs).
108108
scale: float
109-
Apply scale factor. This multiplies the function ordinate by scale.
109+
Apply scale factor.
110+
111+
This multiplies the function ordinate by scale.
110112
stretch: float
111-
Stretch function grid by a fraction stretch. Specifically, this multiplies the function grid by 1+stretch.
113+
Stretch function grid by a fraction stretch.
114+
115+
This multiplies the function grid by 1+stretch.
112116
squeeze: list of float
113117
Squeeze function grid given a polynomial
114-
p(x) = squeeze[0]+squeeze[1]*x+...+squeeze[n]*x^n. n is dependent on the number
118+
p(x) = squeeze[0]+squeeze[1]*x+...+squeeze[n]*x^n.
119+
120+
n is dependent on the number
115121
of values in the user-inputted comma-separated list.
116122
The morph transforms the function grid from x to x+p(x).
117123
When this parameter is given, hshift is disabled.
118124
When n>1, stretch is disabled.
119125
smear: float
120-
Smear the peaks with a Gaussian of width smear. This
121-
is done by convolving the function with a Gaussian
126+
Smear the peaks with a Gaussian of width smear.
127+
128+
This is done by convolving the function with a Gaussian
122129
with standard deviation smear. If both smear and
123130
smear_pdf are used, only smear_pdf will be
124131
applied.
@@ -129,6 +136,7 @@ smear_pdf: float
129136
applied.
130137
slope: float
131138
Slope of the baseline used in converting from PDF to RDF.
139+
132140
This is used with the option smear_pdf. The slope will
133141
be estimated if not provided.
134142
hshift: float
@@ -139,57 +147,119 @@ qdamp: float
139147
Dampen PDF by a factor qdamp.
140148
radius: float
141149
Apply characteristic function of sphere with radius
142-
given by parameter radius. If pradius is also specified, instead apply
150+
given by parameter radius.
151+
152+
If pradius is also specified, instead apply
143153
characteristic function of spheroid with equatorial
144154
radius radius and polar radius pradius.
145155
pradius: float
146156
Apply characteristic function of spheroid with
147157
equatorial radius given by above parameter radius and polar radius pradius.
158+
148159
If only pradius is specified, instead apply
149160
characteristic function of sphere with radius pradius.
150161
iradius: float
151162
Apply inverse characteristic function of sphere with
152-
radius iradius. If ipradius is also specified, instead
163+
radius iradius.
164+
165+
If ipradius is also specified, instead
153166
apply inverse characteristic function of spheroid with
154167
equatorial radius iradius and polar radius ipradius.
155168
ipradius: float
156169
Apply inverse characteristic function of spheroid with
157170
equatorial radius iradius and polar radius ipradius.
171+
158172
If only ipradius is specified, instead apply inverse
159173
characteristic function of sphere with radius ipradius.
160174
funcy: tuple (function, dict)
175+
Apply a function to the y-axis of the (two-column) data.
176+
161177
This morph applies the function funcy[0] with parameters given in funcy[1].
162-
The function funcy[0] must be a function of both the abscissa and ordinate
163-
(e.g. take in at least two inputs with as many additional parameters as needed).
178+
The function funcy[0] take in as parameters both the abscissa and ordinate
179+
(i.e. take in at least two inputs with as many additional parameters as needed).
180+
The y-axis values of the data are then replaced by the return value of funcy[0].
181+
164182
For example, let's start with a two-column table with abscissa x and ordinate y.
165183
let us say we want to apply the function ::
166184

167185
def linear(x, y, a, b, c):
168186
return a * x + b * y + c
169187

170-
This function takes in both the abscissa and ordinate on top of three additional
171-
parameters a, b, and c. To use the funcy parameter with initial guesses
172-
a=1.0, b=2.0, c=3.0, we would pass ``funcy=(linear, {a: 1.0, b: 2.0, c: 3.0})``.
173-
For an example use-case, see the Python-Specific Morphs section below.
188+
This example function above takes in both the abscissa and ordinate on top of
189+
three additional parameters a, b, and c.
190+
To use the funcy parameter with parameter values a=1.0, b=2.0, and c=3.0,
191+
we would pass ``funcy=(linear, {"a": 1.0, "b": 2.0, "c": 3.0})``.
192+
For an explicit example, see the Python-Specific Morphs section below.
193+
funcx: tuple (function, dict)
194+
Apply a function to the x-axis of the (two-column) data.
195+
196+
This morph applies the function funcx[0] with parameters given in funcx[1].
197+
The function funcx[0] take in as parameters both the abscissa and ordinate
198+
(i.e. take in at least two inputs with as many additional parameters as needed).
199+
The x-axis values of the data are then replaced by the return value of funcx[0].
200+
Note that diffpy.morph requires the x-axis be monotonic increasing
201+
(i.e. for i < j, x[i] < x[j]): as such,
202+
if funcx[0] is not a monotonic increasing function of the provided x-axis data,
203+
the error ``x must be a strictly increasing sequence`` will be thrown.
174204

205+
For example, let's start with a two-column table with abscissa x and ordinate y.
206+
let us say we want to apply the function ::
207+
208+
def exponential(x, y, amp, decay):
209+
return abs(amp) * (1 - 2**(-decay * x))
210+
211+
This example function above takes in both the abscissa and ordinate on top of
212+
three additional parameters amp and decay.
213+
(Even though the ordinate is not used in the function,
214+
it is still required that the function take in both acscissa and ordinate.)
215+
To use the funcx parameter with parameter values amp=1.0 and decay=2.0,
216+
we would pass ``funcx=(exponential, {"amp": 1.0, "decay:: 2.0})``.
217+
For an explicit example, see the Python-Specific Morphs section below.
218+
funcxy: tuple (function, dict)
219+
Apply a function the (two-column) data.
220+
221+
This morph applies the function funcxy[0] with parameters given in funcxy[1].
222+
The function funcxy[0] take in as parameters both the abscissa and ordinate
223+
(i.e. take in at least two inputs with as many additional parameters as needed).
224+
The two columns of the data are then replaced by the two return values of funcxy[0].
225+
226+
For example, let's start with a two-column table with abscissa x and ordinate y.
227+
let us say we want to apply the function ::
228+
229+
def shift(x, y, hshift, vshift):
230+
return x + hshift, y + vshift
231+
232+
This example function above takes in both the abscissa and ordinate on top of
233+
two additional parameters hshift and vshift.
234+
To use the funcy parameter with parameter values hshift=1.0 and vshift=2.0,
235+
we would pass ``funcy=(shift, {"hshift": 1.0, "vshift": 1.0})``.
236+
For an example use-case, see the Python-Specific Morphs section below.
175237

176238
Python-Specific Morphs
177239
======================
178240

179241
Some morphs in ``diffpy.morph`` are supported only in Python. Here, we detail
180242
how they are used and how to call them.
181243

182-
MorphFuncy: Applying custom functions
244+
MorphFunc: Applying custom functions
183245
-------------------------------------
184246

247+
In these tutorial, we walk through how to use the ``MorphFunc`` morphs
248+
(``MorphFuncy``, ``MorphFuncx``, ``MorphFuncxy``)
249+
with some example transformations.
250+
251+
Unlike other morphs that can be run from the command line,
252+
``MorphFunc`` moprhs require a Python function and is therefore
253+
intended to be used through Python scripting.
254+
255+
MorphFuncy:
256+
^^^^^^^^^^^
257+
185258
The ``MorphFuncy`` morph allows users to apply a custom Python function
186259
to the y-axis values of a dataset, enabling flexible and user-defined
187260
transformations.
188261

189-
In this tutorial, we walk through how to use ``MorphFuncy`` with an example
190-
transformation. Unlike other morphs that can be run from the command line,
191-
``MorphFuncy`` requires a Python function and is therefore intended to be used
192-
through Python scripting.
262+
Let's try out this morph!
193263

194264
1. Import the necessary modules into your Python script:
195265

@@ -230,7 +300,7 @@ through Python scripting.
230300

231301
.. code-block:: python
232302
233-
morph_params, morph_table = morph_arrays(np.array([x_morph, y_morph]).T,np.array([x_target, y_target]).T,
303+
morph_params, morph_table = morph_arrays(np.array([x_morph, y_morph]).T, np.array([x_target, y_target]).T,
234304
funcy=(linear_function,{'scale': 1.2, 'offset': 0.1}))
235305
236306
5. Extract the fitted parameters from the result:
@@ -246,3 +316,85 @@ to generate the target (scale=20 & offset=0.8). This example shows how
246316
``MorphFuncy`` can be used to fit and apply custom transformations. Now
247317
it's your turn to experiment with other custom functions that may be useful
248318
for analyzing your data.
319+
320+
MorphFuncx:
321+
^^^^^^^^^^^
322+
323+
The ``MorphFuncx`` morph allows users to apply a custom Python function
324+
to the x-axis values of a dataset, similar to the ``MorphFuncy`` morph.
325+
326+
One caveat to this morph is that the x-axis values must remain monotonic
327+
increasing, so it is possible to run into errors when applying this morph.
328+
For example, if your initial grid is ``[-1, 0, 1]``, and your function is
329+
``lambda x, y: x**2``, the grid after the function is applied will be
330+
``[1, 0, 1]``, which is no longer monotonic increasing.
331+
In this case, the error ``x must be a strictly increasing sequence``
332+
will be thrown.
333+
334+
Let's try out this morph!
335+
336+
1. Import the necessary modules into your Python script:
337+
338+
.. code-block:: python
339+
340+
from diffpy.morph.morphpy import morph_arrays
341+
import numpy as np
342+
343+
2. Define a custom Python function to apply a transformation to the data.
344+
The function must take ``x`` and ``y`` (1D arrays of the same length)
345+
along with named parameters, and return a transformed ``x`` array of the
346+
same length. Recall that this function must maintain the monotonic
347+
increasing nature of the ``x`` array.
348+
349+
For this example, we will use a simple exponential function transformation that
350+
greatly modifies the input:
351+
352+
.. code-block:: python
353+
354+
def exp_function(x, y, scale, rate):
355+
return np.abs(scale) * np.exp(np.abs(rate) * x)
356+
357+
Notice that, though the function only uses the ``x`` input,
358+
the function signature takes in both ``x`` and ``y``.
359+
360+
3. Like in the previous example, we will use a sine function for the morph
361+
data and generate the target data by applying the decay transfomration
362+
with a known scale and rate:
363+
364+
.. code-block:: python
365+
366+
x_morph = np.linspace(0, 10, 1001)
367+
y_morph = np.sin(x_morph)
368+
x_target = x_target = 20 * np.exp(0.8 * x_morph)
369+
y_target = y_morph.copy()
370+
371+
4. Setup and run the morph using the ``morph_arrays(...)``.
372+
``morph_arrays`` expects the morph and target data as **2D arrays** in
373+
*two-column* format ``[[x0, y0], [x1, y1], ...]``. This will apply
374+
the user-defined function and refine the parameters to best align the
375+
morph data with the target data. This includes both the transformation
376+
parameters (our initial guess) and the transformation function itself:
377+
378+
.. code-block:: python
379+
380+
morph_params, morph_table = morph_arrays(np.array([x_morph, y_morph]).T, np.array([x_target, y_target]).T,
381+
funcx=(decay_function, {'scale': 1.2, 'rate': 1.0}))
382+
383+
5. Extract the fitted parameters from the result:
384+
385+
.. code-block:: python
386+
387+
fitted_params = morph_params["funcx"]
388+
print(f"Fitted scale: {fitted_params['scale']}")
389+
print(f"Fitted rate: {fitted_params['rate']}")
390+
391+
Again, we should see that the fitted scale and offset values match the ones used
392+
to generate the target (scale=20 & rate=0.8).
393+
394+
For fun, you can plot the original function to the morphed function to see
395+
how much the
396+
397+
MorphFuncxy:
398+
^^^^^^^^^^^^
399+
The ``MorphFuncxy`` morph allows users to apply a custom Python function
400+
to a dataset, ***.

src/diffpy/morph/morphs/morphfuncx.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ class MorphFuncx(Morph):
4141
Define or import the user-supplied transformation function:
4242
4343
>>> import numpy as np
44-
>>> def exp_function(x, y, amplitude, decay):
45-
>>> return abs(amplitude) * (1 - np.exp(-abs(decay) * x))
44+
>>> def exp_function(x, y, scale, rate):
45+
>>> return abs(scale) * np.exp(rate * x)
4646
4747
Note that this transformation is monotonic increasing, so will preserve
4848
the monotonic increasing nature of the provided grid.
4949
5050
Provide initial guess for parameters:
5151
52-
>>> parameters = {'amplitude': 1, 'frequency': 1}
52+
>>> parameters = {'scale': 1, 'rate': 1}
5353
5454
Run the funcy morph given input morph array (x_morph, y_morph)and target
5555
array (x_target, y_target):

src/diffpy/morph/morphs/morphfuncxy.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55

66

77
class MorphFuncxy(Morph):
8-
"""Apply a custom function to the y-axis of the morph function.
8+
"""Apply a custom function to the morph function.
99
1010
General morph function that applies a user-supplied function to the
11-
y-coordinates of morph data to make it align with a target.
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.
1217
1318
Configuration Variables
1419
-----------------------
1520
function: callable
1621
The user-supplied function that applies a transformation to the
17-
y-coordinates of the data.
22+
grid (x-axis) and morph function (y-axis).
1823
1924
parameters: dict
2025
A dictionary of parameters to pass to the function.
@@ -28,24 +33,25 @@ class MorphFuncxy(Morph):
2833
2934
Example (EDIT)
3035
-------
31-
Import the funcy morph function:
36+
Import the funcxy morph function:
3237
3338
>>> from diffpy.morph.morphs.morphfuncxy import MorphFuncxy
3439
3540
Define or import the user-supplied transformation function:
3641
37-
>>> def sine_function(x, y, amplitude, frequency):
38-
>>> return amplitude * np.sin(frequency * x) * y
42+
>>> import numpy as np
43+
>>> def shift_function(x, y, hshift, vshift):
44+
>>> return x + hshift, y + vshift
3945
4046
Provide initial guess for parameters:
4147
42-
>>> parameters = {'amplitude': 2, 'frequency': 2}
48+
>>> parameters = {'hshift': 1, 'vshift': 1}
4349
4450
Run the funcy morph given input morph array (x_morph, y_morph)and target
4551
array (x_target, y_target):
4652
47-
>>> morph = MorphFuncy()
48-
>>> morph.function = sine_function
53+
>>> morph = MorphFuncxy()
54+
>>> morph.function = shift_function
4955
>>> morph.funcy = parameters
5056
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
5157
... morph.morph(x_morph, y_morph, x_target, y_target)
@@ -60,7 +66,9 @@ class MorphFuncxy(Morph):
6066
"""
6167

6268
# Define input output types
63-
summary = "Apply a Python function to the y-axis data"
69+
summary = (
70+
"Apply a Python function to the data (y-axis) and data grid (x-axis)"
71+
)
6472
xinlabel = LABEL_RA
6573
yinlabel = LABEL_GR
6674
xoutlabel = LABEL_RA

src/diffpy/morph/morphs/morphrgrid.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,12 @@ def morph(self, x_morph, y_morph, x_target, y_target):
5555
"""Resample arrays onto specified grid."""
5656
Morph.morph(self, x_morph, y_morph, x_target, y_target)
5757
rmininc = max(self.x_target_in[0], self.x_morph_in[0])
58-
r_step_target = self.x_target_in[1] - self.x_target_in[0]
59-
r_step_morph = self.x_morph_in[1] - self.x_morph_in[0]
58+
r_step_target = (self.x_target_in[-1] - self.x_target_in[0]) / (
59+
len(self.x_target_in) - 1
60+
)
61+
r_step_morph = (self.x_morph_in[-1] - self.x_morph_in[0]) / (
62+
len(self.x_morph_in) - 1
63+
)
6064
rstepinc = max(r_step_target, r_step_morph)
6165
rmaxinc = min(
6266
self.x_target_in[-1] + r_step_target,

0 commit comments

Comments
 (0)