Skip to content

Commit 323cb00

Browse files
authored
Merge pull request #92 from cadenmyers13/srfit_examples
examples: Add `diffpy.srfit` examples
2 parents 06dec82 + 1eb0de5 commit 323cb00

File tree

29 files changed

+17632
-1
lines changed

29 files changed

+17632
-1
lines changed

.codespell/ignore_words.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,6 @@ mater
66

77
;; Frobenius norm used in np.linalg.norm
88
fro
9+
10+
;; variable used in docs/examples/srfit_ex/debyemodelII.py
11+
highT

.github/ISSUE_TEMPLATE/new_pack_proposal.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ assignees: ""
1616
- [ ] Create a requirements file by running `touch requirements/packs/<your-pack-name>.txt`. List all dependencies for your pack.
1717
- [ ] (OPTIONAL) Create a directory to place your examples, if applicable, by running `mkdir docs/examples/<your-pack-name>/<your-example-name>`.
1818
- [ ] (OPTIONAL) Copy your example scripts and data under the directory you just made. If you have multiple examples, house them under their own separate directory (i.e. `.../<your-pack-name>/example1` and `.../<your-pack-name>/example2`).
19+
- [ ] (OPTIONAL) Make a file under `docs/source/tutorial/` call `<your-pack-name>.rst` with `cp docs/source/tutorial/core.rst docs/source/tutorial/<your-pack-name>.rst`. Add your examples to the file in the same format as listed.
1920
- [ ] List your pack and its dependencies under `docs/source/available-packs.rst` using the same format as the other packs. In this same file, add a description of your pack (1-2 sentences is recommended).
2021
- [ ] Give yourself credit by listing your name and contributors to the pack you've created!
2122

.github/ISSUE_TEMPLATE/release_checklist.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ labels: "release"
66
assignees: ""
77
---
88

9-
### Run diffpy.cmi example scripts
9+
### checklist for diffpy.cmi
1010

1111
- [ ] Manually trigger the `validate-examples.yml` workflow to run all example scripts.
12+
- [ ] Make sure available examples, packs, and profiles are listed in the docs. Check this by installing locally with `pip install .`, building the docs with `cmi install docs && cd docs && make html && open build/html/index.html`, running `cmi info`, and comparing the output to what is listed under "Examples" in the docs.
1213

1314
### PyPI/GitHub rc-release preparation checklist:
1415

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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+
"""Example of fitting the Debye model to experimental Debye-Waller
16+
factors.
17+
18+
In this example, we build a fit recipe that uses an external function that can
19+
simulate a atomic displacement parameters using the Debye model. This serves as
20+
an example of how to utilize python functions in a fit and extend them with
21+
SrFit without writing additional code. This example also demonstrates guide a
22+
refinement with restraints.
23+
24+
Instructions
25+
26+
Run the example and then read the 'make_recipe' code to see how to use a python
27+
function as a profile generator.
28+
29+
Extensions
30+
31+
- Don't hardcode the mass of lead in the fitting equation. Turn it into a
32+
Parameter and refine it. There are two ways to do this. Try to figure out
33+
both.
34+
"""
35+
36+
import numpy
37+
38+
from diffpy.cmi.fit_tools import optimize_recipe, plot_results
39+
from diffpy.srfit.fitbase import (
40+
FitContribution,
41+
FitRecipe,
42+
FitResults,
43+
Profile,
44+
)
45+
46+
# The data
47+
data = """\
48+
015.0 0.00334 0.00013
49+
050.0 0.00508 0.00022
50+
100.0 0.00830 0.00040
51+
150.0 0.01252 0.00071
52+
200.0 0.01792 0.00100
53+
250.0 0.02304 0.00120
54+
300.0 0.02737 0.00140
55+
350.0 0.03085 0.00160
56+
400.0 0.03484 0.00190
57+
450.0 0.03774 0.00220
58+
500.0 0.03946 0.00250
59+
"""
60+
61+
######
62+
# Example Code
63+
64+
65+
def make_recipe():
66+
"""Make the recipe for the fit.
67+
68+
The instructions for what we want to refine, and how to refine it
69+
will be defined within a FitRecipe instance. The job of a FitRecipe
70+
is to collect and associate all the data, the fitting equations,
71+
fitting variables, constraints and restrations. We will demonstrate
72+
each of these within the code.
73+
74+
Data is held within a Profile object. The Profile is simply a
75+
container that holds the data, and the theoretical profile once it
76+
has been calculated.
77+
78+
Data is associated with a fitting equation within a FitContribution.
79+
The FitContribution defines the equation and parameters that will be
80+
adjusted to fit the data. The fitting equation can be defined within
81+
a function or optionally within the ProfileGenerator class. We won't
82+
need the ProfileGenerator class in this example since the signature
83+
of the fitting equation (the 'debye' function defined below) is so
84+
simple. The FitContribution also defines the residual function to
85+
optimize for the data/equation pair. This can be modified, but we
86+
won't do that here.
87+
"""
88+
89+
# The Profile
90+
# Create a Profile to hold the experimental and calculated signal.
91+
profile = Profile()
92+
93+
# Load data and add it to the profile. It is our responsibility to get our
94+
# data into the profile.
95+
xydy = numpy.array(data.split(), dtype=float).reshape(-1, 3)
96+
x, y, dy = xydy.T
97+
profile.setObservedProfile(x, y, dy)
98+
99+
# The FitContribution
100+
# The FitContribution associates the profile with the Debye function.
101+
contribution = FitContribution("pb")
102+
# Tell the contribution about the Profile. We will need to use the
103+
# independent variable (the temperature) from the data to calculate the
104+
# theoretical signal, so give it an informative name ('T') that we can use
105+
# later.
106+
contribution.setProfile(profile, xname="T")
107+
108+
# We now need to create the fitting equation. We tell the FitContribution
109+
# to use the 'debye' function defined below. The 'registerFunction' method
110+
# will let us do this. Since we haven't told it otherwise,
111+
# 'registerFunction' will extract the name of the function ('debye') and
112+
# the names of the arguments ('T', 'm', 'thetaD'). These arguments will
113+
# become Parameters of the FitContribution. Since we named the x-variable
114+
# 'T' above, the 'T' in the 'debye' equation will refer to this x-variable
115+
# whenever it is used.
116+
contribution.registerFunction(debye)
117+
118+
# Now we can create the fitting equation. We want to extend the 'debye'
119+
# equation by adding a vertical offset. We could wrap 'debye' in a new
120+
# function with an offset, and register that instead of 'debye', but what
121+
# we do here is easier.
122+
#
123+
# When we set the fitting equation, we do not need to specify the
124+
# Parameters to the 'debye' function since the FitContribution already
125+
# knows what they are. If we choose to specify the arguments, we can make
126+
# adjustments to their input values. We wish to have the thetaD value in
127+
# the debye equation to be positive, so we specify the input as abs(thetaD)
128+
# in the equation below. Furthermore, we know 'm', the mass of lead, so we
129+
# can specify that as well.
130+
contribution.setEquation("debye(T, 207.2, abs(thetaD)) + offset")
131+
132+
# The FitRecipe
133+
# The FitRecipe lets us define what we want to fit. It is where we can
134+
# create variables, constraints and restraints. If we had multiple profiles
135+
# to fit simultaneously, the contribution from each could be added to the
136+
# recipe.
137+
recipe = FitRecipe()
138+
recipe.addContribution(contribution)
139+
140+
# Specify which Parameters we want to refine.
141+
142+
# Vary the offset
143+
recipe.addVar(contribution.offset, 0)
144+
# We also vary the Debye temperature.
145+
recipe.addVar(contribution.thetaD, 100)
146+
147+
# We would like to 'suggest' that the offset should remain positive. This
148+
# is somethine that we know about the system that might help the refinement
149+
# converge to a physically reasonable result. We will do this with a soft
150+
# constraint, or restraint. Here we restrain the offset variable to between
151+
# 0 and infinity. We tell the recipe that we want to scale the penalty for
152+
# breaking the restraint by the point-average chi^2 value so that the
153+
# restraint is roughly as significant as any other data point throughout
154+
# the fit.
155+
recipe.restrain(recipe.offset, lb=0, scaled=True)
156+
157+
# We're done setting up the recipe. We can now do other things with it.
158+
return recipe
159+
160+
161+
def main():
162+
"""The workflow of creating, running and inspecting a fit."""
163+
164+
# Create the recipe
165+
recipe = make_recipe()
166+
167+
# Refine using the optimizer of your choice
168+
optimize_recipe(recipe)
169+
170+
# Get the results.
171+
res = FitResults(recipe)
172+
173+
# Print the results
174+
res.printResults()
175+
176+
# Plot the results
177+
x = recipe.pb.profile.x
178+
yobs = recipe.pb.profile.y
179+
ycalc = recipe.pb.profile.ycalc
180+
181+
plot_results(x, yobs, ycalc)
182+
183+
return
184+
185+
186+
# Functions required for calculation of Debye curve. Feel free to skip these,
187+
# as we treat them as if existing in some external library that we cannot
188+
# modify.
189+
190+
191+
def debye(T, m, thetaD):
192+
"""A wrapped version of 'adps' that can handle an array of
193+
T-values."""
194+
y = numpy.array([adps(m, thetaD, x) for x in T])
195+
return y
196+
197+
198+
def adps(m, thetaD, T):
199+
"""Calculates atomic displacement factors within the Debye model.
200+
201+
<u^2> = (3h^2/4 pi^2 m kB thetaD)(phi(thetaD/T)/(ThetaD/T) + 1/4)
202+
203+
arguments:
204+
m -- float -- mass of the ion in atomic mass units (e.g., C = 12)
205+
thetaD -- float -- Debye Temperature
206+
T -- float -- temperature.
207+
208+
return:
209+
Uiso -- float -- the thermal factor from the Debye recipe at temp T
210+
"""
211+
h = 6.6260755e-34 # Planck's constant. J.s of m^2.kg/s
212+
kB = 1.3806503e-23 # Boltzmann's constant. J/K
213+
amu = 1.66053886e-27 # Atomic mass unit. kg
214+
215+
def __phi(x):
216+
"""Evaluates the phi integral needed in Debye calculation.
217+
218+
phi(x) = (1/x) int_0^x xi/(exp(xi)-1) dxi
219+
220+
arguments:
221+
x -- float -- value of thetaD (Debye temperature)/T
222+
223+
returns:
224+
phi -- float -- value of the phi function
225+
"""
226+
227+
def __debyeKernel(xi):
228+
"""Function needed by debye calculators."""
229+
y = xi / (numpy.exp(xi) - 1)
230+
return y
231+
232+
import scipy.integrate
233+
234+
int = scipy.integrate.quad(__debyeKernel, 0, x)
235+
phi = (1 / x) * int[0]
236+
237+
return phi
238+
239+
m = m * amu
240+
u2 = (3 * h**2 / (4 * numpy.pi**2 * m * kB * thetaD)) * (
241+
__phi(thetaD / T) / (thetaD / T) + 1.0 / 4.0
242+
)
243+
244+
return u2 * 1e20
245+
246+
247+
if __name__ == "__main__":
248+
249+
main()
250+
251+
# End of file

0 commit comments

Comments
 (0)