#!/usr/bin/env python
##############################################################################
#
# diffpy.morph by DANSE Diffraction group
# Simon J. L. Billinge
# (c) 2018 Trustees of the Columbia University
# in the City of New York. All rights reserved.
#
# File coded by: Timothy Liu
#
# See AUTHORS.txt for a list of people who contributed.
# See LICENSE.txt for license information.
#
##############################################################################
import sys
if sys.version_info.major < 3:
# old import for py2.7
from collections import Iterable
else:
from collections.abc import Iterable
import matplotlib.pyplot as plt
from diffpy.morph import morph_helpers, morphs
from diffpy.morph import refine as ref
from diffpy.morph import tools
# map of operation dict
# TODO: include morphing on psize
_morph_step_dict = dict(
scale=morphs.MorphScale,
stretch=morphs.MorphStretch,
smear=[
morph_helpers.TransformXtalPDFtoRDF,
morphs.MorphSmear,
morph_helpers.TransformXtalRDFtoPDF,
],
qdamp=morphs.MorphResolutionDamping,
)
_default_config = dict(
scale=None, stretch=None, smear=None, baselineslope=None, qdamp=None
)
[docs]
def morph_default_config(**kwargs):
"""function to generate default morph configuration
Parameters
----------
kwargs
extra keyword arguments passed to the default morph config
Returns
-------
morph_default_config: dict
A dictionary of morph configuration
Examples
--------
morph_cfg = morph_default_config(scale=1.01)
"""
rv = dict(_default_config)
# protect against foreign keys
for k in kwargs.keys():
if k not in rv:
e = "operation: %s is not currently supported!" % k
raise ValueError(e)
rv.update(**kwargs)
return rv
[docs]
def morph(
x_morph,
y_morph,
x_target,
y_target,
rmin=None,
rmax=None,
rstep=None,
pearson=False,
add_pearson=False,
fixed_operations=None,
refine=True,
verbose=False,
**kwargs,
):
"""function to perform PDF morphing.
Parameters
----------
x_morph: numpy.array
An array of morphed x values, i.e., those will be manipulated by
morphing.
y_morph: numpy.array
An array of morphed y values, i.e., those will be manipulated by
morphing.
x_target: numpy.array
An array of target x values, i.e., those will be kept constant by
morphing.
y_morph: numpy.array
An array of target y values, i.e., those will be kept constant by
morphing.
rmin: float, optional
A value to specify lower r-limit of morph operations.
rmax: float, optional
A value to specify upper r-limit of morph operations.
rstep: float, optional
A value to specify rstep of morph operations.
pearson: Bool, optional
Option to include Pearson coefficient as a minimizing target
during morphing. Default to False.
add_pearson: Bool, optional
Option to include **both** Pearson coefficient and Rw as
minimizing targets during morphing. Default to False.
fixed_operations: list, optional
A list of string specifying operations will be keep fixed during
morphing. Default is None.
refine: bool, optional
Option to execute the minimization step in morphing. If False,
the morphing will be applied with parameter values specified in
`morph_config`. Default to True.
verbose: bool, optional
Option to print full result after morph. Default to False.
kwargs: dict, optional
A dictionary with morph parameters as keys and initial
values of morph parameters as values. Currently supported morph
parparameters are:
- 'scale'
- 'stretch'
- 'smear'
- 'baselineslope'
- 'qdamp'
Returns
-------
morph_rv_dict: dict
A dictionary contains following key-value pairs:
- morph_chain: diffpy.morph.morphs.morphchain.MorphChain
The instance of processed morph chain.
Calling
``x_morph, y_morph, x_target, y_target = morph_chain.xyallout``
will conveniently return morphed data and reference data
- morphed_cfg: dict
A dictionary of refined morphing parameters
- rw: float
The agreement factor between morphed data and reference
data
- pcc: float
The pearson correlation coefficient between morphed
data and referenced data
Examples
--------
# morphing (x_morph, y_morph) pair to (x_target, y_target) pair with
scaling from diffpy.morph.morph_api import morph,
morph_default_config, plot_morph
morph_cfg = morph_default_config(scale=1.01)
morph_rv_dict = morph(x_morph, y_morph, x_target, y_target, **morph_cfg)
# plot morhing result
plot_morph(morph_rv_dict['morph_chain'])
# print morphing parameters, pearson correlation coefficient, Rw
print(morph_rv_dict['morphed_cfg'])
print(morph_rv_dict['pcc'])
print(morph_rv_dict['rw'])
"""
refpars = []
# input config
rv_cfg = dict(kwargs)
# configure morph operations
active_morphs = [
k
for k, v in rv_cfg.items()
if (v is not None) and k in _morph_step_dict
]
rv_cfg["rmin"] = rmin
rv_cfg["rmax"] = rmax
rv_cfg["rstep"] = rstep
# configure smear, guess baselineslope when it is not provided
if rv_cfg.get("smear") is not None and rv_cfg.get("baselineslope") is None:
rv_cfg["baselineslope"] = -0.5
# config dict defines initial guess of parameters
chain = morphs.MorphChain(rv_cfg)
# rgrid
chain.append(morphs.MorphRGrid())
# configure morph chain
for k in active_morphs:
morph_cls = _morph_step_dict[k]
if k == "smear":
[chain.append(el()) for el in morph_cls]
refpars.append("baselineslope")
else:
chain.append(morph_cls())
refpars.append(k)
# exclude fixed options
if fixed_operations:
if not isinstance(fixed_operations, Iterable):
fixed_operations = [fixed_operations]
for opt in fixed_operations:
refpars.remove(opt)
# define refiner
refiner = ref.Refiner(chain, x_morph, y_morph, x_target, y_target)
if pearson:
refiner.residual = refiner._pearson
if add_pearson:
refiner.residual = refiner._add_pearson
# execute morphing
if refpars and refine:
# This works better when we adjust scale and smear first.
if "smear" in refpars:
rptemp = ["smear"]
if "scale" in refpars:
rptemp.append("scale")
refiner.refine(*rptemp)
# Refine all params
refiner.refine(*refpars)
else:
# no operation if refine=False or refpars is empty list
chain(x_morph, y_morph, x_target, y_target)
# summary
rw = tools.getRw(chain)
pcc = tools.get_pearson(chain)
# restore rgrid
chain[0] = morphs.Morph()
chain(x_morph, y_morph, x_target, y_target)
# print output
if verbose:
if fixed_operations:
print("== INFO: Following steps are fixed during morphing ==:\n")
print("\n".join(fixed_operations))
print("== INFO: Refined morph parameters ==:\n")
output = "\n".join(
["# %s = %f" % (k, v) for k, v in rv_cfg.items() if v is not None]
)
output += "\n# Rw = %f" % rw
output += "\n# Pearson = %f" % pcc
print(output)
rv_dict = dict(morph_chain=chain, morphed_config=rv_cfg, rw=rw, pcc=pcc)
return rv_dict
[docs]
def plot_morph(chain, ax=None, **kwargs):
"""Plot the morphed PDF and the target PDF of a morphing operation.
Open a new figure unless a specific axis is provided for plotting.
Parameters
----------
chain: diffpy.morph.morphs.morphchain.MorphChain
An instance of processed morph chain.
ax: matplotlib.axes.Axes, optional
An instance of Axes class to plot the morphing result.
If ax is None, instances of new Figure and Axes will be created.
Default to None.
kwargs:
Additional keyword arguments will be passed to ``ax.plot(...**kwargs)``
Returns
-------
l_list: list
A list of ``matplotlib.lines.Line2D`` objects representing the
plotted data.
"""
if ax is None:
fig, ax = plt.subplots()
rfit, grfit = chain.xy_morph_out
rdat, grdat = chain.xy_target_out
l_list = ax.plot(rfit, grfit, label="morph", **kwargs)
l_list += ax.plot(rdat, grdat, label="target", **kwargs)
ax.set_xlim([chain.config["rmin"], chain.config["rmax"]])
ax.legend()
ax.set_xlabel(r"r ($\mathrm{\AA}$)")
ax.set_ylabel(r"G ($\mathrm{\AA}^{-2}$)")
return l_list