Once a sequential fit has been performed, any function of the fit parameters from the fit and their estimated uncertainty (s.u.) can be computed by defining one or more "pseudo-variables". Note that the covariance matrix from the fit is used to provide appropriate treatment of the correlation in parameters with respect to uncertainty estimation. In addition, parametric equations in terms of sample parameters such as temperature and pressure, etc. can be fit to either the parameters from the fit or the pseudo-variables.
This tutorial assumes that the steps in the Sequential refinement of multiple datasets tutorial have already been performed. If you have not done so already, start GSAS-II. Some of the pictures in this tutorial are from a Mac OSX machine; these illustrate the differences in menu location from Windows/Linux machines.
1. Select the File/Open Project menu item to load file named SeqTut.gpx you created in the previous tutorial.
2. Select the Sequential results item in the data tree. The sequential refinement results data table is displayed, as below.
Pseudo-variables allow computation of an arbitrarily defined function of combinations of the variables that are displayed in the sequential refinement table. Any valid Python language expression can be used to define this. While one can plot a, b and c axis lengths by clicking on the appropriate column of the table, to compare them one might want to scale them. Alternately, as will be done here, we will plot the ratio of b and c.
1. Click on the Pseudo Vars/Add menu item to create a new pseudo-variable.
2. A window opens to define a formula
In this window, type a Python expression, "b/c", but note that the variables c and b here are arbitrary labels. After entering the formula, press the Validate button or wait a few seconds and the validation will be tested automatically. Note that if you pause too long, while typing, an error message may be displayed as the incomplete formula is found to be invalid. If so, make corrections or keep typing until the entire expression is entered. The error message will sometimes show where error is encountered, though not in this case.
When the expression is complete, a new table appears where the arbitrary c and b labels will be assigned.
3. Assign the label for the numerator by clicking on the menu button next to label b; this will bring up a list of variable types, from this select Phase to bring up a list of phase variables, from that list select variable 0::b, which is the b lattice parameter for phase 0 (CuCr2O4). Repeat this process to select assign to label c variable 0::b.
As each variable is set, the window shows the value for each variable from the first refinement and when the expression can be evaluated, the value for the expression, evaluated for that first refinement. Only when the expression can be evaluated can the OK button be pressed.
Press OK to close the window and add the pseudo-variable. Note that a new column appears at the end of the sequential refinement data table.
Also note that positioning the mouse above any cell in the table and waiting for a few seconds causes the s.u. to be shown as a tooltip.
Double clicking on the b/c column will cause the pseudo-variable to be plotted:
Press the "s" key inside the plot to select Temperature as the x-axis.
The plot will then appear as below:
In the next few steps we will fit increasingly sophisticated equations of state to different parameters. As a simple example, we can double-click on 0::Vol, to plot the unit volume for the first phase vs. T, and fit this to a straight line.
1. Click on the Parametric Fit/Add equation menu item, which opens a window similar to what was used before to create a pseudo-variable, except that here an equation is defined.
We will want to fit the unit cell volume to an equation of form y = mx+b, where y will be the volume and x temperature and m and b will be best fit.
2. Click the ↕ button to select a dependent variable for "y". From the phase list, select the unit cell volume for the CuCr2O4 phase, select 0::Vol and then press OK.
3. Now we enter the equation using Python syntax, which is simply "m*x + b", note that the use of spaces and the choice of labels is arbitrary. If one were to accidentally leave out the "*" and typed "mx" then that would be considered a label, if "m x" were typed, an error message would be displayed.
Then press Validate or wait a few seconds and a window changes, as above, allowing assignment of the three new labels (m, x, and b).
4. Assign b and m as refinable “free” parameters: Click on the ↕ button next to b and select Free. Then repeat this for m.
It is not necessary to change the default variable names, the initial values, or the refine flag.
5. To set x as the temperature, click on the ↕ button next to x, select Global, and then select Temperature from the list and click on OK.
The window then changes, showing the values of the dependent variable and the expression for the first refinement, as below. Optionally, the values for m and b could be changed manually and the displayed value will be updated when Validate is pressed or after a short delay.
6. Press the OK button to save this parametric fit. The b and m values are then refined and the console shows the results:
0::Vol = m*x +b
x = Temperature
b = 561.7722(26)
m = 0.014833(14)
Also, the plot shows the actual values and the fit. Note that while the values are close to a straight line, the deviations are huge compared to the estimated uncertainty values for the unit cell volume (shown as very small error bars, unless the plot is magnified). If a linear relationship had been defined as a constraint (as can be done with other software packages) the unit cell parameters would have been forced onto this line with no obvious indication that this was not a correct result.
Here we will look at how more sophisticated Python capabilities can be used to fit more complex behavior.
1. Optionally Double-click on the 0::c column. Note the changes in the c unit cell parameter as a function of temperature, as shown below.
2. We will now fit c as a function of form 1/(1 + exp[k(t-t0)]). Select the Parametric Fit/Add equation menu item (as before). For the dependent variable, click on Phase and then select 0::c.
3. We then enter expression
b + c / (1+math.exp(k*(t-t0)))
Note that use of spaces here is arbitrary, but all parentheses are required. Also note that the exponential function [exp(x) or ex] is supplied in Python in the math and numpy modules as math.exp(), numpy.exp(). Either of these can be used as well as the convenience abbreviation of np.exp(). Further, there are also a small number of common functions that are defined without the prefix for convenience [these include pi, exp(), sqrt(), sind(), cosd(), tand() where d indicates an argument in degrees.]
4. Assign free variables to b, c, k and t0, similar as to what was done before. Note that the variable name for the b label was set automatically to b_1. If the same variable name (b) was used again here, the same value would be used to fit both equations, which is certainly not what we want here. Finally, since this equation is too complex to fit without some reasonable starting values, specify an approximate value, 120, for the vicinity of where the c lattice parameter starts to change (t0) and non-zero values for variables c and k.
5. Optionally, press the Fit button to see if the equation can be refined with starting values of zero for the other three parameters. A fit is obtained and is plotted.
6. Press OK to add this equation to the parametric fits and both equations are refit (and if the label for b had not been changed, applying the constraint that b must be the same for both equations.)
7. We will now try a more complex equation for the same data, where we set the value to b for temperatures less than t0 and a critical phenomenon formulism [(t-t0)-a] for temperatures above t0. To do this we use the Python syntax "x1 if testexp else x2" which is x1 where the test expression (testexp here) is True and x2 otherwise. Thus we can use an expression such as:
b if t <= t0 else b + c * (1-(t-t0)**d)
This may be easier to read with the inclusion of extra parentheses (that have no effect on the execution):
b if (t <= t0) else (b + c * (1-(t-t0)**d))
As before, add an equation (or edit an existing one), set the dependent variable to 0::c, enter the expression and assign the free and fixed variables, as before. Set the exponent and the t0 values to initial values of -0.025 and 120, respectively, and turn off their refinement flags. Then press Fit.
Then turn refinement of t0 back on and press OK. The resulting fit shows the trends quite well:
One can fit a function to a pseudo variable, just as to a direct variable. If we look at the plot shown in Step 2, it appears there are two regions. We can treat the lower range as a power-law growth, Tk, and the upper region as linear (m*T + b).
1. As before, add an equation (or edit an existing one). For the dependent variable, select Global to bring up that selection of variables and select b/c and press OK.
2. Then enter the equation as
(o1 + (a1*T)**k) if T < 120 else (o2 + a2*T)
3. Assign the free and fixed variables, as shown below. Note that the exponent (k) and scaling factor (a1), cannot start as zero.
4. Press the Fit button and note that all the free variables are adjusted
Also, that the plotted result is close to the values from the refinement.
This completes this tutorial on use of Pseudo Variables and parametric fitting.
The parametric refinement section of GSAS-II is intended to be quite flexible, but for very complex fitting, a user may wish to define a custom Python function. Such a function can be defined in a separate module that will be accessed with a parametric equation (or pseudo variable).
As an example of that, if we create a file called fittest.py and locate that file in any directory in the Python path (most simply, in the directory where the project file, SeqTut.gpx, is located) and place a routine fitfxn in that module:
# file fittest.pydef fitfxn(T,o1,o2,a1,a2,k):if T < 120:return o1 + (a1*T)**kelse:return o2 + a2*T
The function fittest.fitfxn can then be used to duplicate the previous example:
Note that module fittest is automatically located and loaded, provided it is found in the path. No changes to the standard distributed GSAS-II code are needed. Care to avoid use of a module name already found in GSAS-II or Python is wise.