Skip to content

Commit 82221f4

Browse files
authored
Merge branch 'main' into pdfmorphpy
2 parents f8d4eee + d1c2695 commit 82221f4

File tree

1 file changed

+348
-0
lines changed

1 file changed

+348
-0
lines changed

src/diffpy/morph/pdfplot.py

Lines changed: 348 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,348 @@
1+
#!/usr/bin/env python
2+
##############################################################################
3+
#
4+
# diffpy.morph by DANSE Diffraction group
5+
# Simon J. L. Billinge
6+
# (c) 2008 Trustees of the Columbia University
7+
# in the City of New York. All rights reserved.
8+
#
9+
# File coded by: Chris Farrow
10+
#
11+
# See AUTHORS.txt for a list of people who contributed.
12+
# See LICENSE.txt for license information.
13+
#
14+
##############################################################################
15+
"""Collection of plotting functions for PDFs."""
16+
17+
import matplotlib.pyplot as plt
18+
import numpy
19+
from bg_mpl_stylesheets.styles import all_styles
20+
21+
plt.style.use(all_styles["bg-style"])
22+
23+
24+
# FIXME - make this return the figure object in the future, so several views
25+
# can be composed.
26+
def plotPDFs(pairlist, labels=None, offset="auto", rmin=None, rmax=None):
27+
"""Plots several PDFs on top of one another.
28+
29+
Parameters
30+
----------
31+
pairlist
32+
Iterable of (r, gr) pairs to plot.
33+
labels
34+
Iterable of names for the pairs. If this is not the same length as
35+
the pairlist, a legend will not be shown (default []).
36+
offset
37+
Offset to place between plots. PDFs will be sequentially shifted in
38+
the y-direction by the offset. If offset is 'auto' (default), the
39+
optimal offset will be determined automatically.
40+
rmin
41+
The minimum r-value to plot. If this is None (default), the lower
42+
bound of the PDF is not altered.
43+
rmax
44+
The maximum r-value to plot. If this is None (default), the upper
45+
bound of the PDF is not altered.
46+
"""
47+
if labels is None:
48+
labels = []
49+
if offset == "auto":
50+
offset = _find_offset(pairlist)
51+
52+
gap = len(pairlist) - len(labels)
53+
labels = list(labels)
54+
labels.extend([""] * gap)
55+
56+
for idx, pair in enumerate(pairlist):
57+
r, gr = pair
58+
plt.plot(r, gr + idx * offset, label=labels[idx])
59+
plt.xlim(rmin, rmax)
60+
61+
if gap == 0:
62+
plt.legend(loc=0)
63+
64+
plt.legend()
65+
plt.xlabel(r"$r (\mathrm{\AA})$")
66+
plt.ylabel(r"$G (\mathrm{\AA}^{-1})$")
67+
plt.show()
68+
return
69+
70+
71+
def comparePDFs(
72+
pairlist,
73+
labels=None,
74+
rmin=None,
75+
rmax=None,
76+
show=True,
77+
maglim=None,
78+
mag=5,
79+
rw=None,
80+
legend=True,
81+
l_width=1.5,
82+
):
83+
"""Plot two PDFs on top of each other and difference curve.
84+
85+
The second PDF will be shown as blue circles below and the first as a red
86+
line. The difference curve will be in green and offset for clarity.
87+
88+
Parameters
89+
----------
90+
pairlist
91+
Iterable of (r, gr) pairs to plot
92+
labels
93+
Iterable of names for the pairs. If this is not the same length as
94+
the pairlist, a legend will not be shown (default []).
95+
rmin
96+
The minimum r-value to plot. If this is None (default), the lower
97+
bound of the PDF is not altered.
98+
rmax
99+
The maximum r-value to plot. If this is None (default), the upper
100+
bound of the PDF is not altered.
101+
show
102+
Show the plot (default True)
103+
maglim
104+
Point after which to magnify the signal by mag. If None (default), no
105+
magnification will take place.
106+
mag
107+
Magnification factor (default 5)
108+
rw
109+
Rw value to display on the plot, if any.
110+
legend
111+
Display the legend (default True).
112+
"""
113+
if labels is None:
114+
labels = [2]
115+
labeldata = None
116+
labelfit = None
117+
else:
118+
labeldata = labels[1]
119+
labelfit = labels[0]
120+
rfit, grfit = pairlist[0]
121+
rdat, grdat = pairlist[1]
122+
123+
# View min and max
124+
rvmin = max(rfit[0], rdat[0])
125+
rvmin = rmin or rvmin
126+
rvmax = min(rfit[-1], rdat[-1])
127+
rvmax = rmax or rvmax
128+
129+
gap = 2 - len(labels)
130+
labels = list(labels)
131+
labels.extend([""] * gap)
132+
133+
# Put gr1 on the same grid as rdat
134+
gtemp = numpy.interp(rdat, rfit, grfit)
135+
136+
# Calculate the difference
137+
diff = grdat - gtemp
138+
139+
# Put rw in the label
140+
labeldiff = "difference" if len(labels) < 3 else labels[2]
141+
if rw is not None:
142+
labeldiff += " (Rw = %.3f)" % rw
143+
144+
# Magnify if necessary
145+
if maglim is not None:
146+
grfit = grfit.copy()
147+
grfit[rfit > maglim] *= mag
148+
sel = rdat > maglim
149+
grdat = grdat.copy()
150+
grdat[sel] *= mag
151+
diff[sel] *= mag
152+
gtemp[sel] *= mag
153+
154+
# Determine the offset for the difference curve.
155+
sel = numpy.logical_and(rdat <= rvmax, rdat >= rvmin)
156+
ymin = min(min(grdat[sel]), min(gtemp[sel]))
157+
ymax = max(diff[sel])
158+
offset = -1.1 * (ymax - ymin)
159+
160+
# Scale the x-limit based on the r-extent of the signal. This gives a nice
161+
# density of PDF peaks.
162+
rlim = rvmax - rvmin
163+
scale = rlim / 25.0
164+
# Set a reasonable minimum of .8 and maximum of 1
165+
scale = min(1, max(scale, 0.8))
166+
figsize = [13.5, 4.5]
167+
figsize[0] *= scale
168+
fig = plt.figure(1, figsize=figsize)
169+
# Get the margins based on the figure size
170+
lm = 0.12 / scale
171+
bm = 0.20 / scale
172+
rm = 0.02 / scale
173+
tm = 0.15 / scale
174+
axes = plt.Axes(fig, [lm, bm, 1 - lm - rm, 1 - bm - tm])
175+
fig.add_axes(axes)
176+
plt.minorticks_on()
177+
178+
plt.plot(rdat, grdat, linewidth=l_width, label=labeldata)
179+
plt.plot(rfit, grfit, linewidth=l_width, label=labelfit)
180+
plt.plot(rdat, offset * numpy.ones_like(diff), linewidth=3, color="black")
181+
182+
diff += offset
183+
plt.plot(rdat, diff, linewidth=l_width, label=labeldiff)
184+
185+
if maglim is not None:
186+
# Add a line for the magnification cutoff
187+
plt.axvline(
188+
maglim,
189+
0,
190+
1,
191+
linestyle="--",
192+
color="black",
193+
linewidth=1.5,
194+
dashes=(14, 7),
195+
)
196+
# FIXME - look for a place to put the maglim
197+
xpos = (rvmax * 0.85 + maglim) / 2 / (rvmax - rvmin)
198+
if xpos <= 0.9:
199+
plt.figtext(xpos, 0.7, "x%.1f" % mag, backgroundcolor="w")
200+
201+
# Get a tight view
202+
plt.xlim(rvmin, rvmax)
203+
ymin = min(diff[sel])
204+
ymax = max(max(grdat[sel]), max(gtemp[sel]))
205+
yspan = ymax - ymin
206+
# Give a small border to the plot
207+
gap = 0.05 * yspan
208+
ymin -= gap
209+
ymax += gap
210+
plt.ylim(ymin, ymax)
211+
212+
# Make labels and legends
213+
plt.xlabel(r"r ($\mathrm{\AA})$")
214+
plt.ylabel(r"G $(\mathrm{\AA}^{-1})$")
215+
if legend:
216+
plt.legend(
217+
bbox_to_anchor=(0.005, 1.02, 0.99, 0.10),
218+
loc=3,
219+
ncol=3,
220+
mode="expand",
221+
borderaxespad=0,
222+
)
223+
if show:
224+
plt.show()
225+
226+
return
227+
228+
229+
def plot_param(target_labels, param_list, param_name=None, field=None):
230+
"""
231+
Plot Rw values for multiple morphs.
232+
233+
Parameters
234+
----------
235+
target_labels: list
236+
Names (or field if --sort-by given) of each file acting as target for
237+
the morph.
238+
param_list: list
239+
Contains the values of some parameter corresponding to each file.
240+
param_name: str
241+
Name of the parameter.
242+
field: list or None
243+
When not None and entries in field are numerical, a line chart of Rw
244+
versus field is made.
245+
When None (default) or values are non-numerical, it plots a bar chart
246+
of Rw values per file.
247+
"""
248+
249+
# ensure all entries in target_labels are distinct for plotting
250+
unique_labels = set()
251+
for idx in range(len(target_labels)):
252+
item = target_labels[idx]
253+
# if repeat found, add additional label
254+
if item in unique_labels:
255+
counter = 1
256+
new_name = f"{item} ({counter})"
257+
while new_name in unique_labels:
258+
counter += 1
259+
new_name = f"{item} ({counter})"
260+
item = new_name
261+
target_labels[idx] = item
262+
unique_labels.update({item})
263+
264+
# Check if numerical field
265+
numerical = True
266+
if field is None:
267+
numerical = False
268+
else:
269+
for item in target_labels:
270+
if type(item) is not float:
271+
numerical = False
272+
273+
if numerical:
274+
# Plot the parameter against a numerical field
275+
plt.plot(target_labels, param_list, linestyle="-", marker="o")
276+
if param_name is not None:
277+
plt.ylabel(rf"{param_name}")
278+
plt.xlabel(rf"{field}")
279+
plt.minorticks_on()
280+
281+
# Create bar chart for each file
282+
else:
283+
# Ensure file names do not crowd
284+
bar_size = 1 # FIXME: depends on resolution
285+
max_len = bar_size
286+
for item in target_labels:
287+
max_len = max(max_len, len(item))
288+
angle = numpy.arccos(bar_size / max_len)
289+
angle *= 180 / numpy.pi # Convert to degrees
290+
plt.xticks(rotation=angle)
291+
292+
# Plot Rw for each file
293+
plt.bar(target_labels, param_list)
294+
if param_name is not None:
295+
plt.ylabel(rf"{param_name}")
296+
if field is None:
297+
plt.xlabel(r"Target File")
298+
else:
299+
plt.xlabel(rf"{field}")
300+
301+
# Show plot
302+
plt.tight_layout()
303+
plt.show()
304+
305+
return
306+
307+
308+
def truncatePDFs(r, gr, rmin=None, rmax=None):
309+
"""Truncate a PDF to specified bounds.
310+
311+
Parameters
312+
----------
313+
r
314+
r-values of the PDF.
315+
gr
316+
PDF g(r) values.
317+
rmin
318+
The minimum r-value. If this is None (default), the lower bound of
319+
the PDF is not altered.
320+
rmax
321+
The maximum r-value. If this is None (default), the upper bound of
322+
the PDF is not altered.
323+
324+
Returns
325+
-------
326+
r, gr
327+
Returns the truncated r, gr.
328+
"""
329+
330+
if rmin is not None:
331+
sel = r >= rmin
332+
gr = gr[sel]
333+
r = r[sel]
334+
if rmax is not None:
335+
sel = r <= rmax
336+
gr = gr[sel]
337+
r = r[sel]
338+
339+
return r, gr
340+
341+
342+
def _find_offset(pairlist):
343+
"""Find an optimal offset between PDFs."""
344+
maxlist = [max(p[1]) for p in pairlist]
345+
minlist = [min(p[1]) for p in pairlist]
346+
difflist = numpy.subtract(maxlist[:-1], minlist[1:])
347+
offset = 1.1 * max(difflist)
348+
return offset

0 commit comments

Comments
 (0)