Skip to content

Commit c16d358

Browse files
committed
Fix changing r-grid issue in refine
1 parent f49f787 commit c16d358

File tree

4 files changed

+31
-181
lines changed

4 files changed

+31
-181
lines changed

docs/source/morphpy.rst

Lines changed: 1 addition & 140 deletions
Original file line numberDiff line numberDiff line change
@@ -193,12 +193,6 @@ funcy: tuple (function, dict)
193193
funcx: tuple (function, dict)
194194
Apply a function to the x-axis of the (two-column) data.
195195

196-
This morph works fundamentally differently from the other grid morphs
197-
(e.g. stretch and squeeze) as it directly modifies the grid of the
198-
morph function.
199-
The other morphs maintain the original grid and apply the morphs by interpolating
200-
the function ***.
201-
202196
This morph applies the function funcx[0] with parameters given in funcx[1].
203197
The function funcx[0] take in as parameters both the abscissa and ordinate
204198
(i.e. take in at least two inputs with as many additional parameters as needed).
@@ -403,137 +397,4 @@ how much the
403397
MorphFuncxy:
404398
^^^^^^^^^^^^
405399
The ``MorphFuncxy`` morph allows users to apply a custom Python function
406-
to a dataset that modifies both the ``x`` and ``y`` column values.
407-
This is equivalent to applying a ``MorphFuncx`` and ``MorphFuncy``
408-
simultaneously.
409-
410-
This morph is useful when you want to apply operations that modify both
411-
the grid and function value. A PDF-specific example includes computing
412-
PDFs from 1D diffraction data (see paragraph at the end of this section).
413-
414-
For this tutorial, we will go through two examples. One simple one
415-
involving shifting a function in the ``x`` and ``y`` directions, and
416-
another involving a Fourier transform.
417-
418-
1. Let's start by taking a simple ``sine`` function.
419-
420-
.. code-block:: python
421-
422-
import numpy as np
423-
morph_x = np.linspace(0, 10, 101)
424-
morph_y = np.sin(morph_x)
425-
morph_table = np.array([morph_x, morph_y]).T
426-
427-
2. Then, let our target function be that same ``sine`` function shifted
428-
to the right by ``0.3`` and up by ``0.7``.
429-
430-
.. code-block:: python
431-
432-
target_x = morph_x + 0.3
433-
target_y = morph_y + 0.7
434-
target_table = np.array([target_x, target_y]).T
435-
436-
3. While we could use the ``hshift`` and ``vshift`` morphs,
437-
this would require us to refine over two separate morph
438-
operations. We can instead perform these morphs simultaneously
439-
by defining a function:
440-
441-
.. code-block:: python
442-
443-
def shift(x, y, hshift, vshift):
444-
return x + hshift, y + vshift
445-
446-
4. Now, let's try finding the optimal shift parameters using the ``MorphFuncxy`` morph.
447-
We can try an initial guess of ``hshift=0.0`` and ``vshift=0.0``.
448-
449-
.. code-block:: python
450-
451-
from diffpy.morph.morphpy import morph_arrays
452-
initial_guesses = {"hshift": 0.0, "vshift": 0.0}
453-
info, table = morph_arrays(morph_table, target_table, funcxy=(shift, initial_guesses))
454-
455-
5. Finally, to see the refined ``hshift`` and ``vshift`` parameters, we extract them from ``info``.
456-
457-
.. code-block:: python
458-
459-
print(f"Refined hshift: {info["funcxy"]["hshift"]}")
460-
print(f"Refined vshift: {info["funcxy"]["vshift"]}")
461-
462-
Now for an example involving a Fourier transform.
463-
464-
1. Let's say you measured a signal of the form :math:`f(x)=\exp\{\cos(\pi x)\}`.
465-
Unfortunately, your measurement was taken against a noisy sinusoidal
466-
background of the form :math:`n(x)=A\sin(Bx)`, where ``A``, ``B`` are unknown.
467-
For our example, let's say (unknown to us) that ``A=2`` and ``B=1.7``.
468-
469-
.. code-block:: python
470-
471-
import numpy as np
472-
n = 201
473-
dx = 0.01
474-
measured_x = np.linspace(0, 2, n)
475-
476-
def signal(x):
477-
return np.exp(np.cos(np.pi * x))
478-
479-
def noise(x, A, B):
480-
return A * np.sin(B * x)
481-
482-
measured_f = signal(measured_x) + noise(measured_x, 2, 1.7)
483-
morph_table = np.array([measured_x, measured_f]).T
484-
485-
2. Your colleague remembers they previously computed the Fourier transform
486-
of the function and has sent that to you.
487-
488-
.. code-block:: python
489-
490-
# We only consider the region where the grid is positive for simplicity
491-
target_x = np.fft.fftfreq(n, dx)[:n//2]
492-
target_f = np.real(np.fft.fft(signal(measured_x))[:n//2])
493-
target_table = np.array([target_x, target_f]).T
494-
495-
3. We can now write a noise subtraction function that takes in our measured
496-
signal and guesses for parameters ``A``, ``B``, and computes the Fourier
497-
transform post-noise-subtraction.
498-
499-
.. code-block:: python
500-
501-
def noise_subtracted_ft(x, y, A, B):
502-
n = 201
503-
dx = 0.01
504-
background_subtracted_y = y - noise(x, A, B)
505-
506-
ft_x = np.fft.fftfreq(n, dx)[:n//2]
507-
ft_f = np.real(np.fft.fft(background_subtracted_y)[:n//2])
508-
509-
return ft_x, ft_f
510-
511-
4. Finally, we can provide initial guesses of ``A=0`` and ``B=1`` to the
512-
``MorphFuncxy`` morph and see what refined values we get.
513-
514-
.. code-block:: python
515-
516-
from diffpy.morph.morphpy import morph_arrays
517-
initial_guesses = {"A": 0, "B": 1}
518-
info, table = morph_arrays(morph_table, target_table, funcxy=(background_subtracted_ft, initial_guesses))
519-
520-
5. Print these values to see if they match with the true values of
521-
of ``A=2.0`` and ``B=1.7``!
522-
523-
.. code-block:: python
524-
525-
print(f"Refined A: {info["funcxy"]["A"]}")
526-
print(f"Refined B: {info["funcxy"]["B"]}")
527-
528-
You can also use this morph to help find optimal parameters
529-
(e.g. ``rpoly``, ``qmin``, ``qmax``, ``bgscale``) for computing
530-
PDFs of materials with known structures.
531-
One does this by setting the ``MorphFuncxy`` function to a PDF
532-
computing function such as
533-
`PDFgetx3 <https://www.diffpy.org/products/pdfgetx.html>`_.
534-
The input (morphed) 1D function should be the 1D diffraction data
535-
one wishes to compute the PDF of and the target 1D function
536-
can be the PDF of a target material with similar geometry.
537-
More information about this will be released in the ``diffpy.morph``
538-
manuscript, and we plan to integrate this feature automatically into
539-
``PDFgetx3`` soon.
400+
to a dataset, ***.

src/diffpy/morph/morphs/morphfuncx.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ class MorphFuncx(Morph):
1111
General morph function that applies a user-supplied function to the
1212
x-coordinates of morph data to make it align with a target.
1313
14-
Notice: the morph should maintain the monotonicity of the grid.
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]).
1518
1619
Configuration Variables
1720
-----------------------
1821
function: callable
1922
The user-supplied function that applies a transformation to the
20-
y-coordinates of the data.
23+
x-coordinates of the data.
2124
2225
parameters: dict
2326
A dictionary of parameters to pass to the function.
@@ -29,27 +32,31 @@ class MorphFuncx(Morph):
2932
transformed according to the user-specified function and parameters
3033
The morphed data is returned on the same grid as the unmorphed data
3134
32-
Example (FIX)
33-
-------------
34-
Import the funcy morph function:
35+
Example
36+
-------
37+
Import the funcx morph function:
3538
36-
>>> from diffpy.morph.morphs.morphfuncy import MorphFuncy
39+
>>> from diffpy.morph.morphs.morphfuncx import MorphFuncx
3740
3841
Define or import the user-supplied transformation function:
3942
40-
>>> def sine_function(x, y, amplitude, frequency):
41-
>>> return amplitude * np.sin(frequency * x) * y
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.
4249
4350
Provide initial guess for parameters:
4451
45-
>>> parameters = {'amplitude': 2, 'frequency': 2}
52+
>>> parameters = {'scale': 1, 'rate': 1}
4653
4754
Run the funcy morph given input morph array (x_morph, y_morph)and target
4855
array (x_target, y_target):
4956
50-
>>> morph = MorphFuncy()
51-
>>> morph.function = sine_function
52-
>>> morph.funcy = parameters
57+
>>> morph = MorphFuncx()
58+
>>> morph.funcx_function = exp_function
59+
>>> morph.funcx = parameters
5360
>>> x_morph_out, y_morph_out, x_target_out, y_target_out =
5461
... morph.morph(x_morph, y_morph, x_target, y_target)
5562
@@ -63,7 +70,7 @@ class MorphFuncx(Morph):
6370
"""
6471

6572
# Define input output types
66-
summary = "Apply a Python function to the y-axis data"
73+
summary = "Apply a Python function to the x-axis data"
6774
xinlabel = LABEL_RA
6875
yinlabel = LABEL_GR
6976
xoutlabel = LABEL_RA

src/diffpy/morph/morphs/morphrgrid.py

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -51,39 +51,26 @@ class MorphRGrid(Morph):
5151
youtlabel = LABEL_GR
5252
parnames = ["rmin", "rmax", "rstep"]
5353

54-
# Define rmin rmax holders for adaptive x-grid refinement
55-
# Without these, the program r-grid can only decrease in interval size
56-
rmin_origin = None
57-
rmax_origin = None
58-
rstep_origin = None
59-
6054
def morph(self, x_morph, y_morph, x_target, y_target):
6155
"""Resample arrays onto specified grid."""
62-
if self.rmin is not None:
63-
self.rmin_origin = self.rmin
64-
if self.rmax is not None:
65-
self.rmax_origin = self.rmax
66-
if self.rstep is not None:
67-
self.rstep_origin = self.rstep
68-
6956
Morph.morph(self, x_morph, y_morph, x_target, y_target)
70-
rmininc = max(min(self.x_target_in), min(self.x_morph_in))
71-
r_step_target = (max(self.x_target_in) - min(self.x_target_in)) / (
57+
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]) / (
7259
len(self.x_target_in) - 1
7360
)
74-
r_step_morph = (max(self.x_morph_in) - min(self.x_morph_in)) / (
61+
r_step_morph = (self.x_morph_in[-1] - self.x_morph_in[0]) / (
7562
len(self.x_morph_in) - 1
7663
)
7764
rstepinc = max(r_step_target, r_step_morph)
7865
rmaxinc = min(
79-
max(self.x_target_in) + r_step_target,
80-
max(self.x_morph_in) + r_step_morph,
66+
self.x_target_in[-1] + r_step_target,
67+
self.x_morph_in[-1] + r_step_morph,
8168
)
82-
if self.rmin_origin is None or self.rmin_origin < rmininc:
69+
if self.rmin is None or self.rmin < rmininc:
8370
self.rmin = rmininc
84-
if self.rmax_origin is None or self.rmax_origin > rmaxinc:
71+
if self.rmax is None or self.rmax > rmaxinc:
8572
self.rmax = rmaxinc
86-
if self.rstep_origin is None or self.rstep_origin < rstepinc:
73+
if self.rstep is None or self.rstep < rstepinc:
8774
self.rstep = rstepinc
8875
# roundoff tolerance for selecting bounds on arrays.
8976
epsilon = self.rstep / 2

src/diffpy/morph/refine.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,12 @@ def _residual(self, pvals):
9191
else:
9292
# Padding
9393
if len(rvec) < self.res_length:
94-
diff_length = self.res_length - len(rvec)
9594
rvec = list(rvec)
96-
rvec.extend([0] * diff_length)
95+
rvec.extend([0] * (self.res_length - len(rvec)))
9796
rvec = array(rvec)
9897
# Removal
99-
# For removal, pass the average RMS
100-
# This is fast and easy to compute
101-
# For sufficiently functions, this approximation becomes exact
10298
elif len(rvec) > self.res_length:
103-
avg_rms = sum(rvec**2) / len(rvec)
104-
rvec = array([avg_rms for _ in range(self.res_length)])
99+
pass
105100

106101
return rvec
107102

0 commit comments

Comments
 (0)