Skip to content

Commit 29d7a99

Browse files
committed
Fix changing r-grid issue in refine
1 parent 54328b9 commit 29d7a99

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
@@ -105,19 +105,26 @@ apply: bool
105105
exclude: str
106106
Exclude a manipulation from refinement by name.
107107
scale: float
108-
Apply scale factor. This multiplies the function ordinate by scale.
108+
Apply scale factor.
109+
110+
This multiplies the function ordinate by scale.
109111
stretch: float
110-
Stretch function grid by a fraction stretch. Specifically, this multiplies the function grid by 1+stretch.
112+
Stretch function grid by a fraction stretch.
113+
114+
This multiplies the function grid by 1+stretch.
111115
squeeze: list of float
112116
Squeeze function grid given a polynomial
113-
p(x) = squeeze[0]+squeeze[1]*x+...+squeeze[n]*x^n. n is dependent on the number
117+
p(x) = squeeze[0]+squeeze[1]*x+...+squeeze[n]*x^n.
118+
119+
n is dependent on the number
114120
of values in the user-inputted comma-separated list.
115121
The morph transforms the function grid from x to x+p(x).
116122
When this parameter is given, hshift is disabled.
117123
When n>1, stretch is disabled.
118124
smear: float
119-
Smear the peaks with a Gaussian of width smear. This
120-
is done by convolving the function with a Gaussian
125+
Smear the peaks with a Gaussian of width smear.
126+
127+
This is done by convolving the function with a Gaussian
121128
with standard deviation smear. If both smear and
122129
smear_pdf are used, only smear_pdf will be
123130
applied.
@@ -128,6 +135,7 @@ smear_pdf: float
128135
applied.
129136
slope: float
130137
Slope of the baseline used in converting from PDF to RDF.
138+
131139
This is used with the option smear_pdf. The slope will
132140
be estimated if not provided.
133141
hshift: float
@@ -138,57 +146,119 @@ qdamp: float
138146
Dampen PDF by a factor qdamp.
139147
radius: float
140148
Apply characteristic function of sphere with radius
141-
given by parameter radius. If pradius is also specified, instead apply
149+
given by parameter radius.
150+
151+
If pradius is also specified, instead apply
142152
characteristic function of spheroid with equatorial
143153
radius radius and polar radius pradius.
144154
pradius: float
145155
Apply characteristic function of spheroid with
146156
equatorial radius given by above parameter radius and polar radius pradius.
157+
147158
If only pradius is specified, instead apply
148159
characteristic function of sphere with radius pradius.
149160
iradius: float
150161
Apply inverse characteristic function of sphere with
151-
radius iradius. If ipradius is also specified, instead
162+
radius iradius.
163+
164+
If ipradius is also specified, instead
152165
apply inverse characteristic function of spheroid with
153166
equatorial radius iradius and polar radius ipradius.
154167
ipradius: float
155168
Apply inverse characteristic function of spheroid with
156169
equatorial radius iradius and polar radius ipradius.
170+
157171
If only ipradius is specified, instead apply inverse
158172
characteristic function of sphere with radius ipradius.
159173
funcy: tuple (function, dict)
174+
Apply a function to the y-axis of the (two-column) data.
175+
160176
This morph applies the function funcy[0] with parameters given in funcy[1].
161-
The function funcy[0] must be a function of both the abscissa and ordinate
162-
(e.g. take in at least two inputs with as many additional parameters as needed).
177+
The function funcy[0] take in as parameters both the abscissa and ordinate
178+
(i.e. take in at least two inputs with as many additional parameters as needed).
179+
The y-axis values of the data are then replaced by the return value of funcy[0].
180+
163181
For example, let's start with a two-column table with abscissa x and ordinate y.
164182
let us say we want to apply the function ::
165183

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

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

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

175237
Python-Specific Morphs
176238
======================
177239

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

181-
MorphFuncy: Applying custom functions
243+
MorphFunc: Applying custom functions
182244
-------------------------------------
183245

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

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

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

@@ -229,7 +299,7 @@ through Python scripting.
229299

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

0 commit comments

Comments
 (0)