MSE 104L laboratory 2 exercises

Authors: Enze Chen (University of California, Berkeley)

Note

This is an interactive exercise, so you will want to click the and open the notebook in DataHub (or Colab for non-UCB students).

This notebook contains a series of exercises to help you process your data from Lab 2. It doesn’t answer all of the discussion questions in the lab procedures, but it will help you create some figures that can supplement the narrative of your lab report.

Contents

The exercises in this notebook include:

  1. Plotting refresher

  2. Calculating lattice constants

  3. Nelson-Riley plots

Import Python packages

It can be pretty helpful to import all required packages at the top of the notebook. We’ve imported a few for you, and we’ll let you figure out what else you might need. Don’t forget the plot styling! ⭐

import numpy as np
import pandas as pd
# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

Plot a spectra as a sanity check

Back to top

Since you’re working with more powder XRD spectra in this lab, it’s a good idea to just visualize the spectra of each sample as a reference. This is also good practice to remind yourself of the commands from the previous lab. You should be quite proficient at this by now!

Remember the general steps are:

  1. Upload your data to JupyterHub, this time in the lab2 folder.

  2. Write code below to process and plot the data.

    • You can now use pandas! You’ll probably start with df = pd.read_csv('sample_N.txt', names=['something', 'sensible']).

    • Call fig, ax = plt.subplots() to get things started.

    • Use ax.plot() and ax.set() to plot and style things.

  3. Save the figure and download it to your computer.

    • Recall this is fig.savefig('my_figure.png', dpi=300, bbox_inches='tight').

If you don’t remember what you did in lab 1, you can always reference your completed notebooks (assuming you did them) by going to JupyterHub (https://datahub.berkeley.edu) and opening them 📁 > mse104l > lab1. Copy+paste previous code is definitely a thing! It’s also a huge advantage of programming over manual manipulation.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #
For your cubic crystal structures, you don't have to use a programmatic solution to solve for the lattice constants. But realize that this means you'll have to calculate the lattice constant (a) corresponding to each peak by hand, and that gets repetitive. Often times, if we find ourselves doing the same set of structured tasks over and over again, it's a good case to turn to a programmatic solution to automate things. Also, you'll thank us when you get to Lab 3!

Lattice constant calculations

Back to top

In this course, you can expect to be given an XRD spectrum of a cubic material and deduce the crystal structure from the peak positions. This is a tedious exercise by hand because we have to compute each peak individually, so we’ll try to speed things up and automate the calculations using Python. This will take some time initially, but save you time in the long run. 😉

Arguably the most important equation is Bragg’s law, given by

\[ n\lambda = 2d \sin(\theta) \tag{1} \]

where \(n\) is the order (typically \(1\)), \(\lambda\) is the wavelength, \(d\) is the interplanar spacing, and \(\theta\) is the Bragg angle. We’ll be manipulating this equation to calculate lattice constants.

1. Inputs / experimental data

Start by typing in your known values collected from the experiment:

  • Wavelength: A float corresponding to \(\lambda\) of your X-ray source, likely Cu-K\(\alpha\).

  • Angles: Values are \(2\theta\) in degrees. The angles should be stored in a list in the form

angles = [1.23, 4.56, 7.89]  

for however many \(2\theta\) peak values you’ve measured (maybe 3 to 5, or so).

Note: Unlike the spectra plots, we only need the angles corresponding to the peak positions, not all angles collected by the diffractometer. 😅

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #
wavelength = None
angles = []

2. Creating our DataFrame

Now it’s time to construct our pandas DataFrame using the dict version of the constructor. Recall that a dictionary takes the form:

my_dict = {'key1':val1, 'key2':val2, ...}

We’ll need one column (key:value pair) for the wavelength and another for angles. Once you’ve constructed the DataFrame, the df variable on the last line will display it.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

df  # you should see two columns with your data! Always good to show the df

3. Numerical calculations

Note that if we assume \(n=1\) in Bragg’s law, then we have three unknowns. In our case, we know the wavelength and angle, so we can find the interplanar spacing. We will try to do this in a very principled fashion. Modular solutions are very important when programming.

TODO: Before continuing, rewrite Bragg's law to solve for the interplanar spacing.

3.1 Find \(\theta\)

First, we need to get the \(\theta\) values. We can do this by creating a new column in the DataFrame whose entries are computed by dividing the existing column of \(2\theta\) values by 2. Also, we strongly recommend that you convert the angles to radians using the np.deg2rad() function on the result. ⭐

So, the syntax will look something like this:

df['new_column_name'] = np.deg2rad(df['column_of_2theta_angles'] / 2)

Of course, please use better names. After you’ve done this, display the DataFrame like we did above to confirm you did it correctly.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

3.2 Take the sine

Next, we’ll compute the sine of the \(\theta\) values using np.sin(), whose argument inside the parentheses must be in radians—good thing we converted it earlier! Add another column to your DataFrame with the sine of the angles.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

3.3 Calculate the interplanar spacing, \(d\)

Now you have all the pieces in place for calculating \(d\). What’s awesome about pandas is that you can perform element-wise division of two columns by use of the division operator, /.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

3.4 Take the ratio of \(d^2\) values

At this point, you know to take the ratio between the first distance and each distance measured; however, there’s a small “hack” to this. 😎

If you do it naively, you’ll get numbers like 1.000, 1.414, 1.732, and the like. Unless you know your square root approximations, this can be tricky to decipher. We prefer to square the distances first, and then take the ratio. We’ve started it below, so you have to fill in the second half.

The resulting column should have values that are close to integers or multiples of a simple fraction like \(\frac{1}{3}\).

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #
# square the distances
df['d^2'] = df['d'] ** 2

# take the ratio - finish this one-liner


# show the df
df

3.5 Figure out the crystal structure and index the peaks

OK! At this point, the ratios should be enough for you to deduce which crystal structure you have on your hands. This will allow you to index the peaks accordingly. You can probably do this part by hand.

Nelson-Riley plots

Back to top

Recall that the Nelson-Riley function is used for cubic systems to get a more accurate measurement of the lattice constant. The Nelson-Riley function is

\[ \text{NR} = \frac{\cos^{2} \theta}{\sin \theta} + \frac{\cos^2 \theta}{\theta} \tag{2} \]

You can probably sense where this is going. If you’ve done the above calculations with the pandas DataFrame, it’s quite easy to add another column of Nelson-Riley values! Only one line of number crunching with Equation (2). If you already did the previous parts and come back to this section later, you can easily rerun everything (Menu bar: Cell > Run All) and pick up where you left off.

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

Recall that for a cubic system the interplanar spacing between \((hkl)\) planes is given by (Table 4.1.2 in Krishnan):

\[ \frac{1}{d_{hkl}} = \frac{\sqrt{h^2 + k^2 + l^2}}{a} \stackrel{\text{def}}{=} \frac{s_{hkl}}{a} \implies a = s_{hkl} \cdot d_{hkl}\]

In order to get the cubic lattice constants for our Nelson-Riley plots, we need to first include \(s\), which we can hard code below. Based on the structure (SC, BCC, FCC), index the planes accordingly to get \(hkl\) values to calculate \(s\). To clarify what we mean, we’ve left in the code structure below, which you’ll have to again modify and fill in the calculation to get \(a\). In the JupyterHub, we’ve also included a PNG of our final data table (xrd_dataframe.png, using our fake data) so you have a sense of what we were intending—and if you did something else, that’s OK!

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #
s = np.sqrt([3, 4, 8, 11, 12, 16, 19])   # example only—you may want to change this!
df['s'] = s    # create a column

df   # add in one line of multiplication above to calculate a!

Now you’re all set to make your NR plot! Note how by doing calculations in the DataFrame, we obtained all NR and \(a\) values at once.

  1. ⭐ First plot the individual data points using a method like ax.scatter(). The s parameter changes the marker size.

  2. Then compute a line of best fit using np.polyfit() and add it to the plot.

  3. ⭐ You may want to ax.set() the \(x\)-axis limits to include \(x=0\) as a guide to the eye. 👀

  4. Of course, with np.polyfit(), you can directly obtain the value of interest and print() it out!

# -------------   WRITE YOUR CODE IN THE SPACE BELOW   ---------- #

Conclusion

Back to top

This concludes the programming exercises for Lab 2. Congratulations! We hope you’re proud of the plots that you generated and we wish you luck with the lab writeup. 📝