forked from diffpy/diffpy.morph
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpdfmorph_api.py
273 lines (243 loc) · 8.79 KB
/
pdfmorph_api.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
#!/usr/bin/env python
##############################################################################
#
# diffpy.pdfmorph 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.pdfmorph import morph_helpers, morphs
from diffpy.pdfmorph import refine as ref
from diffpy.pdfmorph 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)
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
def pdfmorph(
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.pdfmorph.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.pdfmorph.pdfmorph_api import pdfmorph, morph_default_config, plot_morph
morph_cfg = morph_default_config(scale=1.01)
morph_rv_dict = pdfmorph(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
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.pdfmorph.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