{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. Working with larger data sets\n", "\n", "This workbook uses a large sample dataset in order to more realistically simulate the set up of the analysis process from start to finish. The dataset is not included directly with this code because of its size but can be downloaded from Zenodo - https://zenodo.org/record/3630511#.XjHhJGj7SUl\n", "\n", "We assume that you have downloaded the data, unpacked it and placed it in the *example_data* folder before running this notebook.\n", "\n", "The sample analysed is a Titanium-64 Alloy (https://en.wikipedia.org/wiki/Ti-6Al-4V). The energy of the synchrotron beam was 89 KeV, corresponding to a wavelength of ~ 0.14 Å.\n", "\n", "The spectra are sampled at a rate of 10 Hz. There are approximately 5500 frames, corresponding to an experiment approximately 9 minutes long.\n", "\n", "The experiment begins at room temperature, heats for approximately 200 seconds, is held at constant temperature for about 100 seconds. The material being analysed then undergoes hot tensile deformation for approximately 10 seconds, at around 340 seconds from the start of the experiment. After this the sample is then cooled to room temperature. We expect the crystal structure of the material to be primarily hexagonal ($\\alpha$-phase) at room temperature, primarily BCC ($\\beta$-phase) at high temperature and we expect some structural changes as a result of the deformation.\n", "\n", "We could follow the evolution of the cubic and hexagonal crystals in the material by fitting only a single peak for each. However, this would only give us information for a small subsection of grains within the material. Here, we want to consider as many different lattice plane reflections as possible to see how grains with different crystallographic orientations behave - their micromechanical response and their change in orientation over time.\n", "\n", "We follow the beta structure by fitting peaks corresponding to the cubic BCC Miller indices: 110, 200, 211, 220 and 310.\n", "\n", "We follow the alpha structure by fitting peaks corresponding to the hexagonal HCP indices: 10-10, 0002, 10-11, 10-12, 11-20, 10-13, 11-22, 20-21, 0004 and 20-22.\n", "\n", "## 4.1. Calculating peak angles\n", "\n", "Bragg's law gives the angle of scattering from a crystal lattice $(\\theta)$ as a function of the radiation wavelength planes $(\\lambda)$:\n", "\n", "$$ \\lambda = 2d \\sin (\\theta) $$\n", "\n", "We have incoming radiation $(\\theta)$ of wavelength 0.1393 Å.\n", "\n", "The Ti lattice constants for the beta phase are $a_\\beta$ = 3.282 Å and for the alpha phase they are $a_\\alpha$ = 2.95 Å, $c_\\alpha$ = 4.686 Å.\n", "\n", "### 4.1.1. Cubic crystal symmetry\n", "\n", "For a cubic system the relation between lattice spacing and the lattice constant $(a)$ is:\n", "\n", "$$ \\frac{1}{d^2} = \\frac{h^2 + k^2 + l^2}{a^2} $$\n", "\n", "Combining this relation with Bragg's law gives the relation between the scattering angle, radiation wavelength and lattice constant:\n", "\n", "$$ \\sin^2(\\theta) = \\frac{\\lambda^2}{4a^2}(h^2 + k^2 + l^2) $$\n", "\n", "For BCC the symmetry causes systematic absences in odd numbered hkl planes so we expect to see the 110, 200, 211, 220 and 310 peaks.\n", "\n", "The approximate $2\\theta$ scattering angles in order of increasing two theta angle are then:\n", "\n", "* 110: 3.44°\n", "* 200: 4.87°\n", "* 211: 5.96°\n", "* 220: 6.88°\n", "* 310: 7.70°\n", "\n", "### 4.1.2. Hexagonal crystal symmetry\n", "\n", "Using the Bravais-Miller system of notation for the hexagonal crystal symmetry (h, k, i, l) the relation between the lattice spacings and the lattice constants is:\n", "\n", "$$ d = \\frac{a}{\\sqrt{\\frac{4}{3}(h^2 + k^2 + hk) + \\frac{a^2}{c^2}l^2)}} $$\n", "\n", "Combining this with Bragg's law gives the relation between the scattering angle, radiation wavelength and lattice constants:\n", "\n", "$$ \\theta = \\sin^{-1} \\left( \\frac{\\lambda \\sqrt{\\frac{4}{3}(h^2 + k^2 + hk) + \\frac{a^2}{c^2}l^2)}}{2a} \\right) $$\n", "\n", "Iterating through the combinations of indices gives $2\\theta$ angles of:\n", "\n", "* 10-10: 3.12°\n", "* 0002: 3.41°\n", "* 10-11: 3.56°\n", "* 10-12: 4.62°\n", "* 11-20: 5.41°\n", "* 10-13: 5.99°\n", "* 20-20: 6.25°\n", "* 11-22: 6.40°\n", "* 20-21: 6.48°\n", "* 0004: 6.82°\n", "* 20-22: 7.12°\n", "\n", "\n", "## 4.2 Finding the peaks\n", "\n", "First lets take a look the diffraction pattern and see how it looks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%matplotlib inline" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Sometimes setting the figsize in the first cell doesnt work so this call is in a second cell.\n", "import matplotlib\n", "matplotlib.rcParams['figure.figsize'] = [8, 6]\n", "\n", "from xrdfit.spectrum_fitting import PeakParams, FitSpectrum, FitExperiment\n", "\n", "spectral_data = FitSpectrum('../example_data/example_data_large/adc_065_TI64_NDload_900C_15mms_00001.dat', 90)\n", "spectral_data.plot_polar()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now plot a single spectrum" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Zoom in a bit." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1, x_range=(2.5, 10.5))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This looks reasonable. Since this is the first spectrum at the start of the experiment we expect high intensity hexagonal peaks and weak beta peaks (due to the high alpha volume fraction at room temperature). Using our calculations from above we can zoom into a section at a time and assign the peaks." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1, x_range=(3, 4), show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These peaks are in the right position for the 10-10, 0002, a weak 110 and 10-11. We should fit 0002, 110 and 10-11 as a triplet due to their proximity.\n", "\n", "The 110 position is a little higher than our calculated 2-theta value. This is due to alloying additions - inclusion of Vanadium in the beta phase. Therefore, our experimentally measured unit cell size is around $a_\\beta$ = 3.18 Å at room temperature. We can then modify our original estimates of the peak positions to:\n", "\n", "* 110: 3.55°\n", "* 200: 5.02°\n", "* 211: 6.15°\n", "* 220: 7.10°\n", "* 310: 7.94°\n", "\n", "We can quickly plot the peak params to check they are OK, then do the fits and check the fit results." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((3.02, 3.27), '(10-10)'),\n", " PeakParams((3.3, 3.75), ['(0002)', '(110)', '(10-11)'], [(3.4, 3.44), (3.52, 3.56), (3.57, 3.61)])]\n", "\n", "spectral_data.plot_peak_params(peak_params, 1, show_points=True, label_angle=60)\n", "\n", "spectral_data.fit_peaks(peak_params, 1)\n", "spectral_data.plot_fit('(10-10)')\n", "spectral_data.plot_fit('(0002)')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "That looks reasonable. Now on to the next section of the spectrum." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1, x_range=(4.4, 5.8), show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "These peaks are in the right positions for 10-12, 200 and 11-20. These areas are all distinct enough to be fitted as singlets. Again lets quickly plot the fits to check they are OK." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((4.54, 4.8), '(10-12)'),\n", " PeakParams((4.9, 5.10), '(200)'),\n", " PeakParams((5.35, 5.6), '(11-20)')]\n", "\n", "spectral_data.fit_peaks(peak_params, 1)\n", "for fit in spectral_data.fitted_peaks:\n", " fit.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now moving up to the next section of the spectrum" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1, x_range=(5.75, 7.5), show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first peak is the hexagonal 10-13. Going with our revised estimates for the cubic structures the small peak at 6.1° must be the 211. The next four strong peaks then correspond to 20-20, 11-22, 20-21 and 0004 in ascending order. Then, there is a weak peak for the cubic 220 and alpha 20-22 is at about 7.2°. We probably dont need the fit for the 20-22 peak as this is a secondary (weaker) reflection of the 10-11 and we have got enough hexagonal peaks at this point. However in order to get the 220 peak we still need to include the 20-22 as the peaks are so close that we wont be able to reliably separate the 220 as a singlet.\n", "\n", "With the proximity of these peaks, they would probably be good to fit as a doublet, singlet, doublet and singlet and then doublet." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((5.9, 6.25), ['(10-13)', '(211)'], [(6.00, 6.05), (6.10, 6.15)]),\n", " PeakParams((6.21, 6.4), '(20-20)'),\n", " PeakParams((6.37, 6.71), ['(11-22)', '(20-21)'], [(6.43, 6.47), (6.52, 6.56)]),\n", " PeakParams((6.75, 6.95), '(0004)', [(6.82, 6.87)]),\n", " PeakParams((6.95, 7.35), ['(220)', '(20-22)'], [(7.05, 7.12), (7.16, 7.20)])]\n", "\n", "spectral_data.plot_peak_params(peak_params, 1, show_points=True, label_angle=45)\n", "\n", "spectral_data.fit_peaks(peak_params, 1)\n", "for fit in spectral_data.fitted_peaks:\n", " fit.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It would be good to try and get another peak for the cubic phase - we should see if we can find the 310 peak." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "spectral_data.plot(1, x_range=(7.3, 8.1), show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The first peak is probably 10-14, we will ignore this as we have enough hexagonal peaks and just try to get the very small 310 peak." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((7.75, 8.05), '(310)')]\n", "\n", "spectral_data.fit_peaks(peak_params, 1)\n", "for fit in spectral_data.fitted_peaks:\n", " fit.plot()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's not a great fit because the peak intensity is so low, but when the percentage of beta phase increases later in the experiment the fit should be better.\n", "\n", "Lets put all these peak params together and do a time fit.\n", "\n", "## 4.3 Time fitting\n", "\n", "### 4.3.1. Initial fit attempt\n", "\n", "Lets start by fitting these peaks in every 100th frame to check the fits are OK before fitting the full data set.\n", "\n", "In this case we choose to reuse the fits when running the fits. Reusing the fits can cause problems if there is a significant difference between frames, but can be faster if the fits are similar from frame to frame. It is best to try with and without and see which is quicker!\n", "\n", "In this case, reusing the fits is quicker because some of the triplet fits have close peaks with very different magnitudes so it is hard for the parameter guessing algorithm to guess good initial parameters to get the inflection point between them." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "frame_time = 0.1\n", "file_string = '../example_data/example_data_large/adc_065_TI64_NDload_900C_15mms_{:05d}.dat'\n", "first_cake_angle = 90\n", "cakes_to_fit = 1" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((3.02, 3.27), '(10-10)'),\n", " PeakParams((3.30, 3.75), ['(0002)', '(110)', '(10-11)'], [(3.4, 3.44), (3.52, 3.56), (3.57, 3.61)]),\n", " PeakParams((4.54, 4.80), '(10-12)'),\n", " PeakParams((4.90, 5.10), '(200)'),\n", " PeakParams((5.35, 5.60), '(11-20)'),\n", " PeakParams((5.90, 6.25), ['(10-13)', '(211)'], [(6.00, 6.05), (6.10, 6.15)]),\n", " PeakParams((6.21, 6.40), '(20-20)'),\n", " PeakParams((6.37, 6.71), ['(11-22)', '(20-21)'], [(6.43, 6.47), (6.52, 6.56)]),\n", " PeakParams((6.75, 6.95), '(0004)', [(6.82, 6.87)]),\n", " PeakParams((6.95, 7.35), ['(220)', '(20-22)'], [(7.05, 7.12), (7.16, 7.20)]),\n", " PeakParams((7.75, 8.05), '(310)')\n", " ]\n", "max_frame = 5657\n", "merge_cakes = False\n", "frames_to_fit = range(1, max_frame, 100)\n", "experiment = FitExperiment(frame_time, file_string,first_cake_angle, cakes_to_fit, peak_params, merge_cakes, frames_to_fit)\n", "\n", "experiment.run_analysis(reuse_fits=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You may have noticed that the progress bar does not fill linearly. For some time steps the fitting stalls a little. This is because the initial parameters are closer to the final parameters at some time points compared to others. If the fit is complex or some of the maxima intensities are low, many iterations of the fitting minimiser may required to get a good fit. This isn't necessarily a problem, but excessive stalling can be indicative of poor initial parameters.\n", "\n", "After the fitting algorithm completes, if there are any peak fits which required more than 500 minimisation steps, they are highlighted in a report. These points should be checked in particular to see if there was a problem with the fitting. If you find that this report flags too many false positives, you can increase the number of minimisation steps that triggers the report by providing the `evaluation_threshold` parameter to the `run_analysis` function.\n", "\n", "In this case there are 5 peaks which the fitting has had trouble with at some point in the fitting process. The \"(0002) (110) (10-11)\", \"(20-20)\" and \"(11-22) (20-21)\" peaks only have 1 or 2 slow fits each so they are probably nothing to worry about. However, the \"(10-13) (211)\" and \"(220) (20-22)\" peaks have multiple slow fits - these are probably worth checking.\n", "\n", "### 4.3.2 Refining the \"(10-13) (211)\" peak\n", "\n", "Lets look at the (10-13) (211) peak first. First we can look at the fit parameters to see if there is anything unusual." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.plot_fit_parameter(\"(10-13) (211)\", \"(10-13)_center\", show_points=True)\n", "experiment.plot_fit_parameter(\"(10-13) (211)\", \"(10-13)_height\", show_points=True)\n", "experiment.plot_fit_parameter(\"(10-13) (211)\", \"(211)_center\", show_points=True)\n", "experiment.plot_fit_parameter(\"(10-13) (211)\", \"(211)_height\", show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There is definitely something wrong here. The error bars are all over the place and the parameters dont change smoothly over time. \n", "\n", "If there is no error estimate available for a parameter, the data point on the graph is shown as a triangle. The lack of an error estimate may indicate a problem with the fit, although this may not always be the case. The error estimate is made by inverting the curvature matrix of the fit. This will not work if changing the parameter does not significantly affect the fitting result or if changing the parameter results in an evaluation error.\n", "\n", "Nevertheless, in this case there is not a smooth trend and the fits for both maxima look poor, so there is probably something wrong with the fits. We can plot the fits directly to see what they look like." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.plot_fits(peak_names=[\"(10-13) (211)\"], num_time_steps=20)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that although the 211 peak is present at the beginning of the experiment it disappears entirely on heating and doesnt reappear later in the experiment. The doublet fit is still trying to fit two peaks however, even though there is only one and this breaks the fit. The code centers the maxima label on the fitted center and you can see how the centers drift into each other when there is no peak to fit.\n", "\n", "In this case it is probably better to ignore the 211 peak entirely as is disappears very quickly and we can't get much information out of it. Since the 211 peak is so small, the 10-13 peak can still be accurately fit on its own as a singlet.\n", "\n", "If we wanted to try and get some information from the 211 peak, it would be best to do this as a separate FitExperiment for just this peak and a limited subset of the total frames.\n", "\n", "### 4.3.3 Refining the \"(220) (20-22)\" peak\n", "\n", "Lets plot the parameters for the (220) (20-22) peak first: " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.plot_fit_parameter(\"(220) (20-22)\", \"(220)_center\", show_points=True)\n", "experiment.plot_fit_parameter(\"(220) (20-22)\", \"(220)_height\", show_points=True)\n", "experiment.plot_fit_parameter(\"(220) (20-22)\", \"(20-22)_center\", show_points=True)\n", "experiment.plot_fit_parameter(\"(220) (20-22)\", \"(20-22)_height\", show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The 220 peak looks fine but it is the 20-22 fit that is struggling. Lets plot the fits to see why." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.plot_fits(peak_names=[\"(220) (20-22)\"], num_time_steps = 30)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are a few things going on here. From frame 5 to 15 there is a small peak which appears to the left of the fit, this likely confuses the baseline slightly. We can't exclude this however because bringing the peak bounds in would result in one of 220 or 20-22 falling off the edge of the fit later in the experiment when they get further apart.\n", "\n", "From frame 20 to 35 the 20-22 peak disappears almost completely. This is OK and the peak is picked up again when it reappears about frame 35 but we should note the the errors on these parameters for the 20-22 maxima are likely to be very large.\n", "\n", "Also at various points in the fit the intensities of the peaks are very low compared to the level of the background noise. The fitting will struggle a little when the peak height is not far above the noise. This is to be expected and just requires treating the resultant parameters with care.\n", "\n", "There is nothing as such we can do to improve these fits here. Perhaps if these peaks were important they could be fitted separately in a new `FitExperiment` instance, reducing the number of time steps and adjusting the starting parameters. This might seem a little awkward, but `xrdfit` is not really designed to provide high quality fits for peaks with a low signal to noise - it is more designed for high throughput analysis of clear peaks.\n", "\n", "### 4.3.4. Implementing the refinements\n", "\n", "Lets remove the 211 peak and try the fit again." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "peak_params = [PeakParams((3.02, 3.27), '(10-10)'),\n", " PeakParams((3.30, 3.75), ['(0002)', '(110)', '(10-11)'], [(3.4, 3.44), (3.52, 3.56), (3.57, 3.61)]),\n", " PeakParams((4.54, 4.80), '(10-12)'),\n", " PeakParams((4.90, 5.10), '(200)'),\n", " PeakParams((5.35, 5.60), '(11-20)'),\n", " PeakParams((5.90, 6.15), '(10-13)', [(6.00, 6.05)]),\n", " PeakParams((6.21, 6.40), '(20-20)'),\n", " PeakParams((6.37, 6.71), ['(11-22)', '(20-21)'], [(6.43, 6.47), (6.52, 6.56)]),\n", " PeakParams((6.75, 6.95), '(0004)', [(6.82, 6.87)]),\n", " PeakParams((6.95, 7.35), ['(220)', '(20-22)'], [(7.05, 7.12), (7.16, 7.20)]),\n", " PeakParams((7.75, 8.05), '(310)')\n", " ]\n", "\n", "\n", "max_frame = 5657\n", "frames_to_fit = range(1, max_frame, 100)\n", "experiment = FitExperiment(frame_time, file_string, first_cake_angle, cakes_to_fit, peak_params, merge_cakes, frames_to_fit)\n", "\n", "experiment.run_analysis(reuse_fits=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The (10-13) peak is now reporting no fits with many iterations. Notice that the analysis also runs about 50% quicker when just this one poor fit was improved.\n", "\n", "For a more detailed report which specifies which peaks took the longest to fit you can run the fit report again in detailed mode. This can be useful if the fitting is too slow and you want to identify which peak in particular is taking all of the processing time. Each time `FitExperiment.run_analysis` is run, it generates a `FitReport`. This can be printed with the `print` method." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.fit_report.print(detailed=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Fits which take many iterations to converge like the \"(220) (20-22)\" peak will take more time. Also the more maxima in a peak, the longer the fit will tend to take. This is because the fit has more parameters and so a greater parameter search space to explore.\n", "\n", "For the purposes of this tutorial, we now say that we are happy with these fits and proceed do some science with the fits.\n", "\n", "## 4.4. Doing some science\n", "\n", "The whole point of the fitting is to use the fitting parameters as part of an analysis to do some science. Here we will show some brief examples of how an analysis might be done. This is not a complete and thorough analysis of this dataset, but serves to show an example of how the fits can be used.\n", "\n", "First, we should refit the peaks at a higher frequency. The above fits took on the order of 1 minute to analyse every 100th frame. Running 100 times as many fits will likely take on the order of 100 minutes. This isn't an age, but long enough to make the analysis non-interactive. We should look to a way to increase the fit speed.\n", "\n", "We are not so interested in having high temporal resolution data for the majority of the experiment, mainly just the section corresponding to material deformation from about 310 to 340 seconds. Since the *frames_to_fit* parameter can take a non-uniform sequence, we can generate a subset of analysis frames which focuses on the region we are interested on." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "max_frame = 5657\n", "# Range objects can't be appended added so convert them to a list.\n", "initial_steps = list(range(1, 3101, 100))\n", "deformation_steps = list(range(3101, 3401, 2))\n", "end_steps = list(range(3401, max_frame, 100))\n", "frames_to_fit = initial_steps + deformation_steps + end_steps\n", "\n", "peak_params = [PeakParams((3.02, 3.27), '(10-10)'),\n", " PeakParams((3.30, 3.75), ['(0002)', '(110)', '(10-11)'], [(3.4, 3.44), (3.52, 3.56), (3.57, 3.61)]),\n", " PeakParams((4.54, 4.80), '(10-12)'),\n", " PeakParams((4.90, 5.10), '(200)'),\n", " PeakParams((5.35, 5.60), '(11-20)'),\n", " PeakParams((5.90, 6.15), '(10-13)', [(6.00, 6.05)]),\n", " PeakParams((6.21, 6.40), '(20-20)'),\n", " PeakParams((6.37, 6.71), ['(11-22)', '(20-21)'], [(6.43, 6.47), (6.52, 6.56)]),\n", " PeakParams((6.75, 6.95), '(0004)', [(6.82, 6.87)]),\n", " PeakParams((6.95, 7.35), ['(220)', '(20-22)'], [(7.05, 7.12), (7.16, 7.20)]),\n", " PeakParams((7.75, 8.05), '(310)')\n", " ]\n", "\n", "experiment = FitExperiment(frame_time, file_string, first_cake_angle, cakes_to_fit, peak_params, merge_cakes, frames_to_fit)\n", "\n", "experiment.run_analysis(reuse_fits=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This now takes on the order of 3 minutes (although the analysis time will depend on processor speed) - this allows a much more interactive data analysis, we can quickly check if the result is OK, modify it if necessary and then move on. An analysis can now take an hour instead of a day. Limiting the amount of data analysed can have a big effect on the analysis speed.\n", "\n", "We notice in this case that the \"(200) (20-22)\" fit is a bit slow and perhaps 0004 is worth looking at too. In reality we would want to look at these fits carefully to see what is going on, but for the purpose of this example we will move on to a simple data analysis.\n", "\n", "As a next stage we should read in some supplementary experimental data. This is found in the \"example_data/instrument_data/065_ETMT_analogue_output.dat\" file. This data file contains direct voltages recorded from the mechanical testing rig during the experiment. The sensor data was recorded with the spectra recorded on the SXRD detector, at the same rate (10 Hz). The data columns correspond to frame number, temperature, imposed load and resultant sample deformation. Since the data is in voltage, we must scale them using the calibration from the sensors to get meaningful values.\n", "\n", "The scaling for the temperature data is 150°C V$^{-1}$, the imposed load data is 25 N V$^{-1}$ and the resultant deformation (change in position) is scaled as 0.5 mm V$^{-1}$" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "instrument_data = np.loadtxt(\"../example_data/instrument_data/065_ETMT_data.txt\", skiprows=6)\n", "load = instrument_data[:, 1] * 25\n", "temperature = instrument_data[:, 2] * 150\n", "deformation = instrument_data[:, 3] * 0.5\n", "\n", "# Convert frame number to time - data is collected at 10 Hz\n", "instrument_data[:, 0] = instrument_data[:, 0] / 10\n", "\n", "plt.plot(instrument_data[:, 0], temperature)\n", "plt.xlabel(\"Time (s)\")\n", "plt.ylabel(\"Temperature (°C)\")\n", "plt.show()\n", "\n", "plt.plot(instrument_data[:, 0], load)\n", "plt.xlabel(\"Time (s)\")\n", "plt.ylabel(\"Load (N)\")\n", "plt.show()\n", "\n", "plt.plot(instrument_data[:, 0], deformation)\n", "plt.xlabel(\"Time (s)\")\n", "plt.ylabel(\"Sample deformation (mm)\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "From this data we can see the sequence of the experiment, there is a heating stage, a steady state at high temperature, a deformation and a cooling stage.\n", "\n", "We can now plot our fitted peak data along with the experimental data to see how the crystal structure changed during the experiment. Let's try and get the ratio of hexagonal to cubic crystals (alpha to beta phase). We can use the (10-11) maxima to represent the alpha phase and the (110) maxima to represent the beta phase." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "experiment.plot_fit_parameter(\"(10-11)\", \"(10-11)_center\", show_points=True)\n", "experiment.plot_fit_parameter(\"(10-11)\", \"(10-11)_amplitude\", show_points=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can see that the angle of the 10-11 peak shifted down with increasing temperature (due to the increase in lattice spacing with thermal energy) and decreased in amplitude - getting quite small during the high temperature section of the experiment." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "print(experiment.fit_parameters('(110)'))\n", "experiment.plot_fit_parameter('(110)', \"(110)_center\", show_points=True) \n", "experiment.plot_fit_parameter('(110)', \"(110)_amplitude\", show_points=True) " ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The 110 peak shows matching trends, the peak position shifts down a little and the amplitude greatly increases. Here, we see the hexagonal $(\\alpha)$ structure transforming to the cubic $(\\beta)$ structure during heating.\n", "\n", "To a first order approximation we could calculate the ratio of the $\\alpha$ to $\\beta$ phase by considering the ratio of peak amplitudes. This is not quite correct because an analysis of the phase fraction also needs to account for the decrease in peak intensity with increasing two theta angle. For a full analysis of the phase fraction changes, we would also need to take an average from multiple peaks and from different directions across our detector, since here we are only sampling a very small subsection of data (grains with a single crystallographic orientation in a single direction). But, this will give us a rough idea.\n", "\n", "Here, we take the **fraction of alpha phase** as the ratio of alpha (10-11) / beta (110) in the transverse direction" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "alpha_amplitude = experiment.get_fit_parameter(\"(10-11)\", \"(10-11)_amplitude\")\n", "beta_amplitude = experiment.get_fit_parameter(\"(110)\", \"(110)_amplitude\")\n", "percentage_alpha = alpha_amplitude[:, 1] / (alpha_amplitude[:, 1] + beta_amplitude[:, 1])\n", "\n", "import matplotlib.pyplot as plt\n", "plt.plot(alpha_amplitude[:, 0], percentage_alpha, \"-x\")\n", "plt.ylabel(\"Fraction of alpha phase\")\n", "plt.xlabel(\"Time (s)\")\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now lets put this phase ratio together with the instrument data to see the correlation." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "fig, ax1 = plt.subplots()\n", "ax1.plot(alpha_amplitude[:, 0], percentage_alpha, \"-x\")\n", "ax1.set_ylabel(\"Fraction of alpha phase\")\n", "ax1.set_xlabel(\"Time (s)\")\n", "\n", "ax2 = ax1.twinx() # instantiate a second axes that shares the same x-axis\n", "\n", "ax2.set_ylabel('Temperature (°C)')\n", "ax2.plot(instrument_data[:, 0], temperature, color=\"green\")\n", "ax2.tick_params(axis='y')\n", "\n", "fig.tight_layout() \n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As expected, the change in crystal structure is strongly related to the temperature. We also want to know about the deformation though as we can see this distinct from the temperature change between 300 and 350 seconds. We can plot the deformation, temperature and crystal ratio on the same plot to see them all together:\n", "\n", "(This dual axis plot is taken from the example at: https://matplotlib.org/examples/pylab_examples/multiple_yaxis_with_spines.html)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def make_patch_spines_invisible(ax):\n", " ax.set_frame_on(True)\n", " ax.patch.set_visible(False)\n", " for sp in ax.spines.values():\n", " sp.set_visible(False)\n", "\n", "fig, host = plt.subplots()\n", "fig.subplots_adjust(right=1)\n", "\n", "par1 = host.twinx()\n", "par2 = host.twinx()\n", "\n", "par2.spines[\"right\"].set_position((\"axes\", 1.2))\n", "make_patch_spines_invisible(par2)\n", "par2.spines[\"right\"].set_visible(True)\n", "\n", "p1, = host.plot(alpha_amplitude[:, 0], percentage_alpha, \"-x\", label=\"Fraction of alpha phase\")\n", "p2, = par1.plot(instrument_data[:, 0], temperature, \"-\", label=\"Temperature\", color=\"green\")\n", "p3, = par2.plot(instrument_data[:, 0], deformation, \"-\", label=\"Deformation\", color=\"red\")\n", "\n", "host.set_xlim(250, 400)\n", "host.set_ylim(0, 0.6)\n", "par1.set_ylim(500, 1000)\n", "\n", "host.set_xlabel(\"Time (s)\")\n", "host.set_ylabel(\"Fraction of alpha phase\")\n", "par1.set_ylabel(\"Temperature (°C)\")\n", "par2.set_ylabel(\"Deformation (mm)\")\n", "host.yaxis.label.set_color(p1.get_color())\n", "par1.yaxis.label.set_color(p2.get_color())\n", "par2.yaxis.label.set_color(p3.get_color())\n", "\n", "tkw = dict(size=4, width=1.5)\n", "host.tick_params(axis='y', colors=p1.get_color(), **tkw)\n", "par1.tick_params(axis='y', colors=p2.get_color(), **tkw)\n", "par2.tick_params(axis='y', colors=p3.get_color(), **tkw)\n", "host.tick_params(axis='x', **tkw)\n", "\n", "host.axvline(x=316, ymin=0, ymax=1, lw=1, ls=\"--\", color=\"k\")\n", "host.axvline(x=336, ymin=0, ymax=1, lw=1, ls=\"--\", color=\"k\")\n", "\n", "lines = [p1, p2, p3]\n", "\n", "host.legend(lines, [l.get_label() for l in lines], bbox_to_anchor=(1.6, 1.0))\n", "\n", "plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The start and end of the deformation are marked by the dashed lines and we can see that during the deformation there appears to be a decrease in the alpha phase fraction (grains with 10-11 in the transverse direction).\n", "\n", "Analysing the changes in position and amplitude of the peaks can therefore tell us a lot about the micromechanical and transformational behaviours of the different crystallogrpahic structures within our sample.\n", "\n", "We conclude the analysis here since this workbook isn't meant to be about the science as such, however this shows how the fits can be used as part of a more realistic analysis." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.8" }, "pycharm": { "stem_cell": { "cell_type": "raw", "metadata": { "collapsed": false }, "source": [] } } }, "nbformat": 4, "nbformat_minor": 2 }