Skip to content

Commit 461231b

Browse files
committed
add parallel refinement examples
1 parent 1784d8d commit 461231b

1 file changed

Lines changed: 179 additions & 0 deletions

File tree

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/usr/bin/env python
2+
########################################################################
3+
#
4+
# diffpy.srfit by DANSE Diffraction group
5+
# Simon J. L. Billinge
6+
# (c) 2009 The Trustees of 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_DANSE.txt for license information.
13+
#
14+
########################################################################
15+
16+
"""Example of a PDF refinement using diffpy.structure and PDFGenerator.
17+
18+
This is example of fitting the fcc nickel structure to measured PDF
19+
data. The purpose of this example is to demonstrate and describe the
20+
classes in configuration options involved with setting up a fit in this
21+
way. The main benefit of using SrFit for PDF refinement is the
22+
flexibility of modifying the PDF profile function for specific needs,
23+
adding restraints to a fit and the ability to simultaneously refine a
24+
structure to PDF data and data from other sources. This example
25+
demonstrates only the basic configuration.
26+
"""
27+
28+
import multiprocessing as mp
29+
from pathlib import Path
30+
31+
from scipy.optimize import leastsq
32+
33+
from diffpy.srfit.fitbase import (
34+
FitContribution,
35+
FitRecipe,
36+
FitResults,
37+
Profile,
38+
)
39+
from diffpy.srfit.pdf import PDFGenerator, PDFParser
40+
from diffpy.structure import Structure
41+
42+
43+
def make_recipe(ciffile, datname):
44+
"""Create a fitting recipe for crystalline PDF data."""
45+
# The Profile
46+
# This will be used to store the observed and calculated PDF profile.
47+
profile = Profile()
48+
49+
# Load data and add it to the Profile. Unlike in other examples, we use a
50+
# class (PDFParser) to help us load the data. This class will read the data
51+
# and relevant metadata from a two- to four-column data file generated
52+
# with PDFGetX2 or PDFGetN. The metadata will be passed to the PDFGenerator
53+
# when they are associated in the FitContribution, which saves some
54+
# configuration steps.
55+
parser = PDFParser()
56+
parser.parse_file(datname)
57+
profile.load_parsed_data(parser)
58+
profile.set_calculation_range(xmax=20)
59+
60+
# The ProfileGenerator
61+
# The PDFGenerator is for configuring and calculating a PDF profile. Here,
62+
# we want to refine a Structure object from diffpy.structure. We tell the
63+
# PDFGenerator that with the 'setStructure' method. All other configuration
64+
# options will be inferred from the metadata that is read by the PDFParser.
65+
# In particular, this will set the scattering type (x-ray or neutron), the
66+
# Qmax value, as well as initial values for the non-structural Parameters.
67+
generator = PDFGenerator("G")
68+
stru = Structure()
69+
stru.read(ciffile)
70+
generator.setStructure(stru)
71+
72+
# The FitContribution
73+
# Here we associate the Profile and ProfileGenerator, as has been done
74+
# before.
75+
contribution = FitContribution("nickel")
76+
contribution.add_profile_generator(generator)
77+
contribution.set_profile(profile, xname="r")
78+
79+
# Make the FitRecipe and add the FitContribution.
80+
recipe = FitRecipe()
81+
recipe.add_contribution(contribution)
82+
83+
# Configure the fit variables
84+
85+
# The PDFGenerator class holds the ParameterSet associated with the
86+
# Structure passed above in a data member named "phase". (We could have
87+
# given the ParameterSet a name other than "phase" when we added it to the
88+
# PDFGenerator.) The ParameterSet in this case is a StructureParameterSet,
89+
# the documentation for which is found in the
90+
# diffpy.srfit.structure.diffpystructure module.
91+
phase = generator.phase
92+
93+
# We start by constraining the phase to the known space group. We could do
94+
# this by hand, but there is a method in diffpy.srfit.structure named
95+
# 'constrain_as_space_group' for this purpose. The constraints will by
96+
# default be applied to the sites, the lattice and to the ADPs.
97+
# See the method documentation for more details.
98+
# The 'constrain_as_space_group' method may create new
99+
# Parameters, which it returns in a SpaceGroupParameters object.
100+
from diffpy.srfit.structure import constrain_as_space_group
101+
102+
sgpars = constrain_as_space_group(phase, "Fm-3m")
103+
104+
# The SpaceGroupParameters object returned by
105+
# 'constrain_as_space_group' holds the free Parameters allowed by
106+
# the space group constraints. Once a structure is constrained,
107+
# we need (should) only use the Parameters
108+
# provided in the SpaceGroupParameters, as the relevant structure
109+
# Parameters are constrained to these.
110+
#
111+
# We know that the space group does not allow for any free sites because
112+
# each atom is on a special position. There is one free (cubic) lattice
113+
# parameter and one free (isotropic) ADP. We can access these Parameters in
114+
# the xyzpars, latpars, and adppars members of the SpaceGroupParameters
115+
# object.
116+
for par in sgpars.latpars:
117+
recipe.add_variable(par)
118+
for par in sgpars.adppars:
119+
recipe.add_variable(par, 0.005)
120+
121+
# We now select non-structural parameters to refine.
122+
# This controls the scaling of the PDF.
123+
recipe.add_variable(generator.scale, 1)
124+
# This is a peak-damping resolution term.
125+
recipe.add_variable(generator.qdamp, 0.01)
126+
# This is a vibrational correlation term that sharpens peaks at low-r.
127+
recipe.add_variable(generator.delta2, 5)
128+
129+
# Give the recipe away so it can be used!
130+
return recipe
131+
132+
133+
def refine_recipe(recipe):
134+
"""Helper function."""
135+
leastsq(recipe.residual, recipe.get_values())
136+
return recipe
137+
138+
139+
if __name__ == "__main__":
140+
141+
# Make the data and the recipe
142+
ciffile = str(Path(__file__).parent / "data/ni.cif")
143+
data = Path(__file__).parent / "data/ni-q27r100-neutron.gr"
144+
145+
# sanity check
146+
print("==== Rw before refinements ====")
147+
recipe = make_recipe(ciffile, data)
148+
recipe.clear_fit_hooks()
149+
res = FitResults(recipe)
150+
print(res.rw)
151+
# Make the recipe
152+
recipe_list = []
153+
for i in range(5):
154+
recipe = make_recipe(ciffile, data)
155+
recipe.clear_fit_hooks()
156+
recipe_list.append(recipe)
157+
158+
# Optimize
159+
print("==== Rw: Sequential refinements ====")
160+
for recipe in recipe_list:
161+
recipe = refine_recipe(recipe)
162+
res = FitResults(recipe)
163+
print(res.rw)
164+
165+
# Make the recipe
166+
recipe_list = []
167+
for i in range(5):
168+
recipe = make_recipe(ciffile, data)
169+
recipe.clear_fit_hooks()
170+
recipe_list.append(recipe)
171+
172+
# Optimize
173+
print("==== Rw: Parallel refinements ====")
174+
n_process = 4
175+
with mp.Pool(n_process) as p:
176+
rv = p.map(refine_recipe, recipe_list)
177+
for recipe in rv:
178+
res = FitResults(recipe)
179+
print(res.rw)

0 commit comments

Comments
 (0)