@@ -403,4 +403,127 @@ how much the
403
403
MorphFuncxy:
404
404
^^^^^^^^^^^^
405
405
The ``MorphFuncxy `` morph allows users to apply a custom Python function
406
- to a dataset, ***.
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
+ .. code-block :: python
420
+
421
+ import numpy as np
422
+ morph_x = np.linspace(0 , 10 , 101 )
423
+ morph_y = np.sin(morph_x)
424
+ morph_table = np.array([morph_x, morph_y]).T
425
+
426
+ 2. Then, let our target function be that same ``sine `` function shifted
427
+ to the right by ``0.3 `` and up by ``0.7 ``:
428
+ .. code-block :: python
429
+
430
+ target_x = morph_x + 0.3
431
+ target_y = morph_y + 0.7
432
+ target_table = np.array([target_x, target_y]).T
433
+
434
+ 3. While we could use the ``hshift `` and ``vshift `` morphs,
435
+ this would require us to refine over two separate morph
436
+ operations. We can instead perform these morphs simultaneously
437
+ by defining a function:
438
+ .. code-block :: python
439
+
440
+ def shift (x , y , hshift , vshift ):
441
+ return x + hshift, y + vshift
442
+
443
+ 4. Now, let's try finding the optimal shift parameters using the ``MorphFuncxy `` morph.
444
+ We can try an initial guess of ``hshift=0.0 `` and ``vshift=0.0 ``:
445
+ .. code-block :: python
446
+
447
+ from diffpy.morph.morphpy import morph_arrays
448
+ initial_guesses = {" hshift" : 0.0 , " vshift" : 0.0 }
449
+ info, table = morph_arrays(morph_table, target_table, funcxy = (shift, initial_guesses))
450
+
451
+ 5. Finally, to see the refined ``hshift `` and ``vshift `` parameters, we extract them from ``info ``:
452
+ .. code-block :: python
453
+
454
+ print (f " Refined hshift: { info[" funcxy" ][" hshift" ]} " )
455
+ print (f " Refined vshift: { info[" funcxy" ][" vshift" ]} " )
456
+
457
+ Now for an example involving a Fourier transform.
458
+
459
+ 1. Let's say you measured a signal of the form :math: `f(x)=\exp \{\cos (\pi x)\}`.
460
+ Unfortunately, your measurement was taken against a noisy sinusoidal
461
+ background of the form :math: `n(x)=A\sin (Bx)`, where ``A,B `` are unknown.
462
+ For our example, let's say (unknown to us) that ``A=2 `` and ``B=1.7 ``.
463
+ .. code-block :: python
464
+
465
+ import numpy as np
466
+ n = 201
467
+ dx = 0.01
468
+ measured_x = np.linspace(0 , 2 , n)
469
+
470
+ def signal (x ):
471
+ return np.exp(np.cos(np.pi * x))
472
+
473
+ def noise (x , A , B ):
474
+ return A * np.sin(B * x)
475
+
476
+ measured_f = signal(measured_x) + noise(measured_x, 2 , 1.7 )
477
+ morph_table = np.array([measured_x, measured_f]).T
478
+
479
+ 2. Your colleague remembers they previously computed the Fourier transform
480
+ of the function and has sent that to you.
481
+ .. code-block :: python
482
+
483
+ # We only consider the region where the grid is positive for simplicity
484
+ target_x = np.fft.fftfreq(n, dx)[:n// 2 ]
485
+ target_f = np.real(np.fft.fft(signal(measured_x))[:n// 2 ])
486
+ target_table = np.array([target_x, target_f]).T
487
+
488
+ 3. We can now write a noise subtraction function that takes in our measured
489
+ signal and guesses for parameters ``A,B ``, and computes the Fourier
490
+ transform post-noise-subtraction.
491
+ .. code-block :: python
492
+
493
+ def noise_subtracted_ft (x , y , A , B ):
494
+ n = 201
495
+ dx = 0.01
496
+ background_subtracted_y = y - noise(x, A, B)
497
+
498
+ ft_x = np.fft.fftfreq(n, dx)[:n// 2 ]
499
+ ft_f = np.real(np.fft.fft(background_subtracted_y)[:n// 2 ])
500
+
501
+ return ft_x, ft_f
502
+
503
+ 4. Finally, we can provide initial guesses of ``A=0 `` and ``B=1 `` to the
504
+ ``MorphFuncxy `` morph and see what refined values we get.
505
+ .. code-block :: python
506
+
507
+ from diffpy.morph.morphpy import morph_arrays
508
+ initial_guesses = {" A" : 0 , " B" : 1 }
509
+ info, table = morph_arrays(morph_table, target_table, funcxy = (background_subtracted_ft, initial_guesses))
510
+
511
+ 5. Print these values to see if they match with the true values of
512
+ of ``A=2.0 `` and ``B=1.7 ``!
513
+ .. code-block :: python
514
+
515
+ print (f " Refined A: { info[" funcxy" ][" A" ]} " )
516
+ print (f " Refined B: { info[" funcxy" ][" B" ]} " )
517
+
518
+ You can also use this morph to help find optimal parameters
519
+ (e.g. ``rpoly ``, ``qmin ``, ``qmax ``, ``bgscale ``) for computing
520
+ PDFs of materials with known structures.
521
+ One does this by setting the ``MorphFuncxy `` function to a PDF
522
+ computing function such as
523
+ ```PDFgetx3 `` <https://www.diffpy.org/products/pdfgetx.html>`_.
524
+ The input (morphed) 1D function should be the 1D diffraction data
525
+ one wishes to compute the PDF of and the target 1D function
526
+ can be the PDF of a target material with similar geometry.
527
+ More information about this will be released in the ``diffpy.morph ``
528
+ manuscript, and we plan to integrate this feature automatically into
529
+ ``PDFgetx3 `` soon.
0 commit comments