Fitting Powder Diffraction data

Contents

Fitting Powder Diffraction data#

This tutorial guides you through the Rietveld refinement of crystal structures using simulated powder diffraction data. It consists of two parts:

  • Introduction: A simple reference fit using silicon (Si) crystal structure.

  • Exercise: A more complex fit using Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO) crystal structure.

πŸ› οΈ Import Library#

We start by importing the necessary library for the analysis. In this tutorial, we use the EasyDiffraction library, which offers tools for analyzing and refining powder diffraction data.

This tutorial is self-contained and designed for hands-on learning. However, if you’re interested in exploring more advanced features or learning about additional capabilities of the EasyDiffraction library, please refer to the official documentation: https://docs.easydiffraction.org/lib/tutorials/

Depending on your requirements, you may choose to import only specific classes. However, for the sake of simplicity in this tutorial, we will import the entire library.

We also import a utility module that contains some helper functions for fetching data.

import easydiffraction as ed
import utils
⚠️ 'pycrysfml' module not found. This calculation engine will not be available.
βœ… 'cryspy' calculation engine is successfully imported.
βœ… 'pdffit' calculation engine is successfully imported.

πŸ“˜ Introduction: Simple Reference Fit – Si#

Before diving into the more complex fitting exercise with the Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO) crystal structure, let’s start with a simpler example using the silicon (Si) crystal structure. This will help us understand the basic concepts and steps involved in fitting a crystal structure using powder diffraction data.

For this part of the tutorial, we will use the powder diffraction data from the previous tutorial, simulated using the Si crystal structure.

πŸ“¦ Create a Project – β€˜reference’#

In EasyDiffraction, a project serves as a container for all information related to the analysis of a specific experiment or set of experiments. It enables you to organize your data, experiments, sample models, and fitting parameters in a structured manner. You can think of it as a folder containing all the essential details about your analysis. The project also allows us to visualize both the measured and calculated diffraction patterns, among other things.

project_1 = ed.Project(name='reference')

You can set the title and description of the project to provide context and information about the analysis being performed. This is useful for documentation purposes and helps others (or yourself in the future) understand the purpose of the project at a glance.

project_1.info.title = 'Reference Silicon Fit'
project_1.info.description = 'Fitting simulated powder diffraction pattern of Si.'

πŸ”¬ Create an Experiment#

Now we will create an experiment within the project. An experiment represents a specific diffraction measurement performed on a specific sample using a particular instrument. It contains details about the measured data, instrument parameters, and other relevant information.

In this case, the experiment is defined as a powder diffraction measurement using time-of-flight neutrons. The measured data is loaded from a file containing the reduced diffraction pattern of Si from the data reduction tutorial.

si_xye_path = '../4-reduction/reduced_Si.xye'

⚠️ If you did not complete the powder diffraction data reduction yesterday, you can use some pre-prepared data by uncommenting and running the cell below:

#si_xye_path = utils.fetch_data('/4-reduction/reduced_Si.xye')

Now we can create the experiment and load the measured data.

project_1.experiments.add(
    name='sim_si',
    sample_form='powder',
    beam_mode='time-of-flight',
    radiation_probe='neutron',
    data_path=si_xye_path,
)
Loading measured data from ASCII file
/home/runner/work/dmsc-school/dmsc-school/4-reduction/reduced_Si.xye

Data loaded successfully
Experiment πŸ”¬ 'sim_si'. Number of data points: 900

Inspect Measured Data#

After creating the experiment, we can examine the measured data. The measured data consists of a diffraction pattern having time-of-flight (TOF) values and corresponding intensities. The TOF values are given in microseconds (ΞΌs), and the intensities are in arbitrary units.

The data is stored in XYE format, a simple text format containing three columns: TOF, intensity, and intensity error (if available).

The plot_meas method of the project enables us to visualize the measured diffraction pattern.

Before plotting, we set the plotting engine to β€˜plotly’, which provides interactive visualizations.

project_1.plotter.engine = 'plotly'
Current plotter changed to
plotly
project_1.plot_meas(expt_name='sim_si')

If you zoom in on the highest TOF peak (around 120,000 ΞΌs), you will notice that it has a broad and unusual shape. This distortion, along with some more effects on the low TOF peaks, is a result of the simplified data reduction process. Obtaining a more accurate diffraction pattern would require a more advanced data reduction, which is beyond the scope of this tutorial. Therefore, we will simply exclude both the low and high TOF regions from the analysis by adding an excluded regions to the experiment.

project_1.experiments['sim_si'].excluded_regions.add(minimum=0, maximum=55000)
project_1.experiments['sim_si'].excluded_regions.add(minimum=105500, maximum=200000)

To visualize the effect of excluding the high TOF region, we can plot the measured data again. The excluded region will be omitted from the plot and is not used in the fitting process.

project_1.plot_meas(expt_name='sim_si')

Set Instrument Parameters#

After the experiment is created and measured data are loaded, we need to set the instrument parameters.

In this type of experiment, the instrument parameters define how the measured data is converted between d-spacing and time-of-flight (TOF) during the data reduction process as well as the angular position of the detector. So, we put values based on those from the reduction. These values can be found in the header of the corresponding .XYE file. Their names are two_theta and DIFC, which stand for the two-theta angle and the linear conversion factor from d-spacing to TOF, respectively.

You can set them manually, but it is more convenient to use the get_value_from_xye_header function from the EasyDiffraction library.

project_1.experiments['sim_si'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(si_xye_path, 'two_theta')
project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(si_xye_path, 'DIFC')

Every parameters is an object, which has different attributes, such as value, free, etc. To display the parameter of interest, you can simply print the parameter object. For example, to display the linear conversion factor from d-spacing to TOF, which is the calib_d_to_tof_linear parameter, you can do the following:

project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear
Parameter: sim_si.instr.d_to_tof_linear = 61441.822051197305 Β΅s/β„«

The value attribute represents the current value of the parameter as a float. You can access it directly by using the value attribute of the parameter. This is useful when you want to use the parameter value in calculations or when you want to assign it to another parameter. For example, to get only the value of the same parameter as floating point number, but not the whole object, you can do the following:

project_1.experiments['sim_si'].instrument.calib_d_to_tof_linear.value
61441.822051197305

Note that to set the value of the parameter, you can simply assign a new value to the parameter object without using the value attribute, as we did above.

Set Peak Profile Parameters#

The next set of parameters is needed to define the peak profile used in the fitting process. The peak profile describes the shape of the diffraction peaks. They include parameters for the broadening and asymmetry of the peaks.

Here, we use a pseudo-Voigt peak profile function with Ikeda-Carpenter asymmetry, which is a common choice for neutron powder diffraction data.

The values are typically determined experimentally on the same instrument and under the same configuration as the data being analyzed based on measurements of a standard sample. We consider this Si sample as a standard reference. Therefore, we will set the initial values of the peak profile parameters based on the values obtained from another simulation and refine them during the fitting process. The refined parameters will be used as a starting point for the more complex fit in the next part of the tutorial.

project_1.experiments['sim_si'].peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
project_1.experiments['sim_si'].peak.broad_gauss_sigma_0 = 69498
project_1.experiments['sim_si'].peak.broad_gauss_sigma_1 = -55578
project_1.experiments['sim_si'].peak.broad_gauss_sigma_2 = 14560
project_1.experiments['sim_si'].peak.broad_mix_beta_0 = 0.0019
project_1.experiments['sim_si'].peak.broad_mix_beta_1 = 0.0137
project_1.experiments['sim_si'].peak.asym_alpha_0 = -0.0055
project_1.experiments['sim_si'].peak.asym_alpha_1 = 0.0147
Peak profile type for experiment 'sim_si' changed to
pseudo-voigt * ikeda-carpenter

Set Background#

The background of the diffraction pattern represents the portion of the pattern that is not related to the crystal structure of the sample. It’s rather representing the noise and other sources of scattering that can affect the measured intensities. This includes contributions from the instrument, the sample holder, the sample environment, and other sources of incoherent scattering.

The background can be modeled in various ways. In this example, we will use a simple line segment background, which is a common approach for powder diffraction data. The background intensity at any point is defined by linear interpolation between neighboring points. The background points are selected to span the range of the diffraction pattern while avoiding the peaks.

We will add several background points at specific TOF values (in ΞΌs) and corresponding intensity values. These points are chosen to represent the background level in the diffraction pattern free from any peaks.

The background points are added using the add method of the background object. The x parameter represents the TOF value, and the y parameter represents the intensity value at that TOF.

Let’s set all the background points at a constant value of 0.01, which can be roughly determined by the eye, and we will refine them later during the fitting process.

project_1.experiments['sim_si'].background_type = 'line-segment'
project_1.experiments['sim_si'].background.add(x=50000, y=0.01)
project_1.experiments['sim_si'].background.add(x=60000, y=0.01)
project_1.experiments['sim_si'].background.add(x=70000, y=0.01)
project_1.experiments['sim_si'].background.add(x=80000, y=0.01)
project_1.experiments['sim_si'].background.add(x=90000, y=0.01)
project_1.experiments['sim_si'].background.add(x=100000, y=0.01)
project_1.experiments['sim_si'].background.add(x=110000, y=0.01)
Background type for experiment 'sim_si' changed to
line-segment

🧩 Create a Sample Model – Si#

After setting up the experiment, we need to create a sample model that describes the crystal structure of the sample being analyzed.

In this case, we will create a sample model for silicon (Si) with a cubic crystal structure. The sample model contains information about the space group, lattice parameters, atomic positions of the atoms in the unit cell, atom types, occupancies and atomic displacement parameters. The sample model is essential for the fitting process, as it is used to calculate the expected diffraction pattern.

EasyDiffraction refines the crystal structure of the sample, but does not solve it. Therefore, we need a good starting point with reasonable structural parameters.

Here, we define the Si structure as a cubic structure. As this is a cubic structure, we only need to define the single lattice parameter, which is the length of the unit cell edge. The Si crystal structure has a single atom in the unit cell, which is located at the origin (0, 0, 0) of the unit cell. The symmetry of this site is defined by the Wyckoff letter β€˜a’. The atomic displacement parameter defines the thermal vibrations of the atoms in the unit cell and is presented as an isotropic parameter (B_iso).

Sometimes, the initial crystal structure parameters can be obtained from one of the crystallographic databases, like for example the Crystallography Open Database (COD). In this case, we use the COD entry for silicon as a reference for the initial crystal structure model: https://www.crystallography.net/cod/4507226.html

Usually, the crystal structure parameters are provided in a CIF file format, which is a standard format for crystallographic data. An example of a CIF file for silicon is shown below. The CIF file contains the space group information, unit cell parameters, and atomic positions.

data_si

_space_group.name_H-M_alt  "F d -3 m"
_space_group.IT_coordinate_system_code  2

_cell.length_a      5.43
_cell.length_b      5.43
_cell.length_c      5.43
_cell.angle_alpha  90.0
_cell.angle_beta   90.0
_cell.angle_gamma  90.0

loop_
_atom_site.label
_atom_site.type_symbol
_atom_site.fract_x
_atom_site.fract_y
_atom_site.fract_z
_atom_site.wyckoff_letter
_atom_site.occupancy
_atom_site.ADP_type
_atom_site.B_iso_or_equiv
Si Si   0 0 0   a  1.0   Biso 0.89

As with adding the experiment in the previous step, we will create a default sample model and then modify its parameters to match the Si structure.

Add Sample Model#

project_1.sample_models.add(name='si')

Set Space Group#

project_1.sample_models['si'].space_group.name_h_m = 'F d -3 m'
project_1.sample_models['si'].space_group.it_coordinate_system_code = '2'

Set Lattice Parameters#

project_1.sample_models['si'].cell.length_a = 5.43

Set Atom Sites#

project_1.sample_models['si'].atom_sites.add(
    label='Si',
    type_symbol='Si',
    fract_x=0,
    fract_y=0,
    fract_z=0,
    wyckoff_letter='a',
    b_iso=0.89,
)

πŸ”— Assign Sample Model to Experiment#

Now we need to assign, or link, this sample model to the experiment created above. This linked crystallographic phase will be used to calculate the expected diffraction pattern based on the crystal structure defined in the sample model.

project_1.experiments['sim_si'].linked_phases.add(id='si', scale=1.0)

πŸš€ Analyze and Fit the Data#

After setting up the experiment and sample model, we can now analyze the measured diffraction pattern and perform the fit.

The fitting process involves comparing the measured diffraction pattern with the calculated diffraction pattern based on the sample model and instrument parameters. The goal is to adjust the parameters of the sample model and the experiment to minimize the difference between the measured and calculated diffraction patterns. This is done by refining the parameters of the sample model and the instrument settings to achieve a better fit.

Set Fit Parameters#

To perform the fit, we need to specify the refinement parameters. These are the parameters that will be adjusted during the fitting process to minimize the difference between the measured and calculated diffraction patterns. This is done by setting the free attribute of the corresponding parameters to True.

We will refine the scale factor of the Si phase, the intensities of the background points as well as the peak profile parameters. The structure parameters of the Si phase will not be refined, as this sample is considered a reference sample with known parameters.

project_1.experiments['sim_si'].linked_phases['si'].scale.free = True

for line_segment in project_1.experiments['sim_si'].background:
    line_segment.y.free = True

project_1.experiments['sim_si'].peak.broad_gauss_sigma_0.free = True
project_1.experiments['sim_si'].peak.broad_gauss_sigma_1.free = True
project_1.experiments['sim_si'].peak.broad_gauss_sigma_2.free = True
project_1.experiments['sim_si'].peak.broad_mix_beta_0.free = True
project_1.experiments['sim_si'].peak.broad_mix_beta_1.free = True
project_1.experiments['sim_si'].peak.asym_alpha_0.free = True
project_1.experiments['sim_si'].peak.asym_alpha_1.free = True

Show Free Parameters#

We can check which parameters are free to be refined by calling the show_free_params method of the analysis object of the project.

project_1.analysis.show_free_params()
Free parameters for both sample models (🧩 data blocks) and experiments (πŸ”¬ data blocks)
datablock category entry parameter value uncertainty min max units
1
sim_si
background
50000
y
0.0100
2
sim_si
background
60000
y
0.0100
3
sim_si
background
70000
y
0.0100
4
sim_si
background
80000
y
0.0100
5
sim_si
background
90000
y
0.0100
6
sim_si
background
100000
y
0.0100
7
sim_si
background
110000
y
0.0100
8
sim_si
linked_phases
si
scale
1.0000
9
sim_si
peak
asym_alpha_0
-0.0055
10
sim_si
peak
asym_alpha_1
0.0147
11
sim_si
peak
gauss_sigma_0
69498.0000
Β΅sΒ²
12
sim_si
peak
gauss_sigma_1
-55578.0000
Β΅s/β„«
13
sim_si
peak
gauss_sigma_2
14560.0000
Β΅sΒ²/β„«Β²
14
sim_si
peak
mix_beta_0
0.0019
deg
15
sim_si
peak
mix_beta_1
0.0137
deg

Visualize Diffraction Patterns#

Before performing the fit, we can visually compare the measured diffraction pattern with the calculated diffraction pattern based on the initial parameters of the sample model and the instrument. This provides an indication of how well the initial parameters match the measured data. The plot_meas_vs_calc method of the project allows this comparison.

project_1.plot_meas_vs_calc(expt_name='sim_si')

Run Fitting#

We can now perform the fit using the fit method of the analysis object of the project.

project_1.analysis.fit()
Using experiment πŸ”¬ 'sim_si' for 'single' fitting
πŸš€ Starting fit process with 'lmfit (leastsq)'...
πŸ“ˆ Goodness-of-fit (reduced χ²) change:
iteration χ² improvement [%]
1
33.06
19
3.30
90.0% ↓
35
1.91
42.2% ↓
100
1.90
πŸ† Best goodness-of-fit (reduced χ²) is 1.90 at iteration 99
βœ… Fitting complete.

Fit results
βœ… Success: True
⏱️ Fitting time: 0.98 seconds
πŸ“ Goodness-of-fit (reduced χ²): 1.90
πŸ“ R-factor (Rf): 6.03%
πŸ“ R-factor squared (RfΒ²): 4.74%
πŸ“ Weighted R-factor (wR): 4.61%
πŸ“ˆ Fitted parameters:
datablock category entry parameter start fitted uncertainty units change
1
sim_si
background
50000
y
0.0100
-0.0231
0.0072
330.74 % ↓
2
sim_si
background
60000
y
0.0100
0.0116
0.0020
16.11 % ↑
3
sim_si
background
70000
y
0.0100
0.0200
0.0014
100.41 % ↑
4
sim_si
background
80000
y
0.0100
0.0222
0.0013
121.90 % ↑
5
sim_si
background
90000
y
0.0100
0.0237
0.0009
136.87 % ↑
6
sim_si
background
100000
y
0.0100
0.0250
0.0015
150.37 % ↑
7
sim_si
background
110000
y
0.0100
0.0167
0.0036
67.45 % ↑
8
sim_si
linked_phases
si
scale
1.0000
1.4523
0.0064
45.23 % ↑
9
sim_si
peak
asym_alpha_0
-0.0055
-0.0053
0.0004
3.12 % ↓
10
sim_si
peak
asym_alpha_1
0.0147
0.0138
0.0006
5.95 % ↓
11
sim_si
peak
gauss_sigma_0
69498.0000
102749.7431
6391.1239
Β΅sΒ²
47.85 % ↑
12
sim_si
peak
gauss_sigma_1
-55578.0000
-88098.2338
7733.6846
Β΅s/β„«
58.51 % ↑
13
sim_si
peak
gauss_sigma_2
14560.0000
22223.0057
2164.9299
Β΅sΒ²/β„«Β²
52.63 % ↑
14
sim_si
peak
mix_beta_0
0.0019
0.0012
0.0001
deg
37.82 % ↓
15
sim_si
peak
mix_beta_1
0.0137
0.0162
0.0007
deg
18.47 % ↑

Check Fit Results#

You can see that the agreement between the measured and calculated diffraction patterns is now much improved and that the intensities of the calculated peaks align much better with the measured peaks. To check the quality of the fit numerically, we can look at the goodness-of-fit χ² value and the reliability R-factors. The χ² value is a measure of how well the calculated diffraction pattern matches the measured pattern, and it is calculated as the sum of the squared differences between the measured and calculated intensities, divided by the number of data points. Ideally, the χ² value should be close to 1, indicating a good fit.

Visualize Fit Results#

After the fit is completed, we can plot the comparison between the measured and calculated diffraction patterns again to see how well the fit improved the agreement between the two. The calculated diffraction pattern is now based on the refined parameters.

project_1.plot_meas_vs_calc(expt_name='sim_si')

TOF vs d-spacing#

The diffraction pattern is typically analyzed and plotted in the time-of-flight (TOF) axis, which represents the time it takes for neutrons to travel from the sample to the detector. However, it is sometimes more convenient to visualize the diffraction pattern in the d-spacing axis, which represents the distance between planes in the crystal lattice. The d-spacing can be calculated from the TOF values using the instrument parameters. The plot_meas_vs_calc method of the project allows us to plot the measured and calculated diffraction patterns in the d-spacing axis by setting the d_spacing parameter to True.

project_1.plot_meas_vs_calc(expt_name='sim_si', d_spacing=True)

As you can see, the calculated diffraction pattern now matches the measured pattern much more closely. Typically, additional parameters are included in the refinement process to further improve the fit. However, we will stop here, as the goal of this part of the tutorial is to demonstrate that the data reduction and fitting process function correctly. The fit is not perfect, but it is sufficient to show that the fitting process works and that the parameters are being adjusted appropriately. The next part of the tutorial will be more advanced and will involve fitting a more complex crystal structure: Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO).

πŸ’ͺ Exercise: Complex Fit – LBCO#

Now that you have a basic understanding of the fitting process, we will undertake a more complex fit of the Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO) crystal structure using simulated powder diffraction data from the previous tutorial.

You can use the same approach as in the previous part of the tutorial, but this time we will refine a more complex crystal structure LBCO with multiple atoms in the unit cell.

πŸ“¦ Exercise 1: Create a Project#

Create a new project for the LBCO fit.

Hint:

You can use the same approach as in the previous part of the tutorial, but this time we will create a new project for the LBCO fit.

Solution:

Hide code cell source

project_2 = ed.Project(name='main')
project_2.info.title = 'La0.5Ba0.5CoO3 Fit'
project_2.info.description = 'Fitting simulated powder diffraction pattern of La0.5Ba0.5CoO3.'

πŸ”¬ Exercise 2: Define an Experiment#

Exercise 2.1: Create an Experiment#

Create an experiment within the new project and load the reduced diffraction pattern for LBCO.

Hint:

You can use the same approach as in the previous part of the tutorial, but this time you need to use the data file for LBCO.

Solution:

Hide code cell source

lbco_xye_path = '../4-reduction/reduced_LBCO.xye'

# ⚠️ **If you did not complete the powder diffraction data reduction yesterday**,
# you can use some pre-prepared data by uncommenting the following line:
#lbco_xye_path = utils.fetch_data('/4-reduction/reduced_LBCO.xye')

project_2.experiments.add(
    name='sim_lbco',
    sample_form='powder',
    beam_mode='time-of-flight',
    radiation_probe='neutron',
    data_path=lbco_xye_path,
)
Loading measured data from ASCII file
/home/runner/work/dmsc-school/dmsc-school/4-reduction/reduced_LBCO.xye

Data loaded successfully
Experiment πŸ”¬ 'sim_lbco'. Number of data points: 900

Exercise 2.1: Inspect Measured Data#

Check the measured data of the LBCO experiment. Are there any peaks with the shape similar to those excluded in the Si fit? If so, exclude them from this analysis as well.

Hint:

You can use the plot_meas method of the project to visualize the measured diffraction pattern. You can also use the excluded_regions attribute of the experiment to exclude specific regions from the analysis as we did in the previous part of the tutorial.

Solution:

Hide code cell source

project_2.plotter.engine = 'plotly'
project_2.plot_meas(expt_name='sim_lbco')

project_2.experiments['sim_lbco'].excluded_regions.add(minimum=0, maximum=55000)
project_2.experiments['sim_lbco'].excluded_regions.add(minimum=105500, maximum=200000)

project_2.plot_meas(expt_name='sim_lbco')
Current plotter changed to
plotly

Exercise 2.2: Set Instrument Parameters#

Set the instrument parameters for the LBCO experiment.

Hint:

Use the values from the data reduction process for the LBCO and follow the same approach as in the previous part of the tutorial.

Solution:

Hide code cell source

project_2.experiments['sim_lbco'].instrument.setup_twotheta_bank = ed.get_value_from_xye_header(lbco_xye_path, 'two_theta')
project_2.experiments['sim_lbco'].instrument.calib_d_to_tof_linear = ed.get_value_from_xye_header(lbco_xye_path, 'DIFC')

Exercise 2.3: Set Peak Profile Parameters#

Set the peak profile parameters for the LBCO experiment.

Hint:

Use the values from the previous part of the tutorial. You can either manually copy the values from the Si fit or use the value attribute of the parameters from the Si experiment to set the initial values for the LBCO experiment. This will help us to have a good starting point for the fit.

Solution:

Hide code cell source

project_2.peak_profile_type = 'pseudo-voigt * ikeda-carpenter'
project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0 = project_1.experiments['sim_si'].peak.broad_gauss_sigma_0.value
project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1 = project_1.experiments['sim_si'].peak.broad_gauss_sigma_1.value
project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2 = project_1.experiments['sim_si'].peak.broad_gauss_sigma_2.value
project_2.experiments['sim_lbco'].peak.broad_mix_beta_0 = project_1.experiments['sim_si'].peak.broad_mix_beta_0.value
project_2.experiments['sim_lbco'].peak.broad_mix_beta_1 = project_1.experiments['sim_si'].peak.broad_mix_beta_1.value
project_2.experiments['sim_lbco'].peak.asym_alpha_0 = project_1.experiments['sim_si'].peak.asym_alpha_0.value
project_2.experiments['sim_lbco'].peak.asym_alpha_1 = project_1.experiments['sim_si'].peak.asym_alpha_1.value

Exercise 2.4: Set Background#

Set the background points for the LBCO experiment. What would you suggest as the initial intensity value for the background points?

Hint:

Use the same approach as in the previous part of the tutorial, but this time you need to set the background points for the LBCO experiment. You can zoom in on the measured diffraction pattern to determine the approximate background level.

Solution:

Hide code cell source

project_2.experiments['sim_lbco'].background_type = 'line-segment'
project_2.experiments['sim_lbco'].background.add(x=50000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=60000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=70000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=80000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=90000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=100000, y=0.2)
project_2.experiments['sim_lbco'].background.add(x=110000, y=0.2)
Background type for experiment 'sim_lbco' changed to
line-segment

🧩 Exercise 3: Define a Sample Model – LBCO#

The LBSO structure is not as simple as the Si model, as it contains multiple atoms in the unit cell. It is not in COD, so we give you the structural parameters in CIF format to create the sample model.

Note that those parameters are not necessarily the most accurate ones, but they are a good starting point for the fit. The aim of the study is to refine the LBCO lattice parameters.

data_lbco

_space_group.name_H-M_alt  "P m -3 m"
_space_group.IT_coordinate_system_code  1

_cell.length_a      3.89
_cell.length_b      3.89
_cell.length_c      3.89
_cell.angle_alpha  90.0
_cell.angle_beta   90.0
_cell.angle_gamma  90.0

loop_
_atom_site.label
_atom_site.type_symbol
_atom_site.fract_x
_atom_site.fract_y
_atom_site.fract_z
_atom_site.wyckoff_letter
_atom_site.occupancy
_atom_site.ADP_type
_atom_site.B_iso_or_equiv
La La   0.0 0.0 0.0   a   0.5   Biso 0.95
Ba Ba   0.0 0.0 0.0   a   0.5   Biso 0.95
Co Co   0.5 0.5 0.5   b   1.0   Biso 0.80
O  O    0.0 0.5 0.5   c   1.0   Biso 1.66

Exercise 3.1: Create Sample Model#

Add a sample model for LBCO to the project. The sample model parameters will be set in the next exercises.

Hint:

You can use the same approach as in the previous part of the tutorial, but this time you need to use the model name corresponding to the LBCO structure, e.g. β€˜lbco’.

Solution:

Hide code cell source

project_2.sample_models.add(name='lbco')

Exercise 3.2: Set Space Group#

Set the space group for the LBCO sample model.

Hint:

Use the space group name and IT coordinate system code from the CIF data.

Solution:

Hide code cell source

project_2.sample_models['lbco'].space_group.name_h_m = 'P m -3 m'
project_2.sample_models['lbco'].space_group.it_coordinate_system_code = '1'

Exercise 3.3: Set Lattice Parameters#

Set the lattice parameters for the LBCO sample model.

Hint:

Use the lattice parameters from the CIF data.

Solution:

Hide code cell source

project_2.sample_models['lbco'].cell.length_a = 3.88

Exercise 3.4: Set Atom Sites#

Set the atom sites for the LBCO sample model.

Hint:

Use the atom sites from the CIF data. You can use the add method of the atom_sites attribute of the sample model to add the atom sites. Note that the occupancy of the La and Ba atoms is 0.5 and those atoms are located in the same position (0, 0, 0) in the unit cell. This means that an extra attribute occupancy needs to be set for those atoms.

Solution:

Hide code cell source

project_2.sample_models['lbco'].atom_sites.add(
    label='La',
    type_symbol='La',
    fract_x=0,
    fract_y=0,
    fract_z=0,
    wyckoff_letter='a',
    b_iso=0.95,
    occupancy=0.5,
)
project_2.sample_models['lbco'].atom_sites.add(
    label='Ba',
    type_symbol='Ba',
    fract_x=0,
    fract_y=0,
    fract_z=0,
    wyckoff_letter='a',
    b_iso=0.95,
    occupancy=0.5,
)
project_2.sample_models['lbco'].atom_sites.add(
    label='Co',
    type_symbol='Co',
    fract_x=0.5,
    fract_y=0.5,
    fract_z=0.5,
    wyckoff_letter='b',
    b_iso=0.80,
)
project_2.sample_models['lbco'].atom_sites.add(
    label='O',
    type_symbol='O',
    fract_x=0,
    fract_y=0.5,
    fract_z=0.5,
    wyckoff_letter='c',
    b_iso=1.66,
)

πŸ”— Exercise 4: Assign Sample Model to Experiment#

Now assign the LBCO sample model to the experiment created above.

Hint:

Use the linked_phases attribute of the experiment to link the sample model.

Solution:

Hide code cell source

project_2.experiments['sim_lbco'].linked_phases.add(id='lbco', scale=1.0)

πŸš€ Exercise 5: Analyze and Fit the Data#

Exercise 5.1: Set Fit Parameters#

Select the initial set of parameters to be refined during the fitting process.

Hint:

You can start with the scale factor and the background points, as in the Si fit, but this time you will refine the LBCO phase related parameters.

Solution:

Hide code cell source

project_2.experiments['sim_lbco'].linked_phases['lbco'].scale.free = True

for line_segment in project_2.experiments['sim_lbco'].background:
    line_segment.y.free = True

Exercise 5.2: Run Fitting#

Visualize the measured and calculated diffraction patterns before fitting and then run the fitting process.

Hint:

Use the plot_meas_vs_calc method of the project to visualize the measured and calculated diffraction patterns before fitting. Then, use the fit method of the analysis object of the project to perform the fitting process.

Solution:

Hide code cell source

project_2.plot_meas_vs_calc(expt_name='sim_lbco')

project_2.analysis.fit()
Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'lmfit (leastsq)'...
πŸ“ˆ Goodness-of-fit (reduced χ²) change:
iteration χ² improvement [%]
1
115.31
12
68.57
40.5% ↓
22
68.57
πŸ† Best goodness-of-fit (reduced χ²) is 68.57 at iteration 12
βœ… Fitting complete.
Fit results
βœ… Success: True
⏱️ Fitting time: 0.19 seconds
πŸ“ Goodness-of-fit (reduced χ²): 68.57
πŸ“ R-factor (Rf): 37.94%
πŸ“ R-factor squared (RfΒ²): 64.36%
πŸ“ Weighted R-factor (wR): 65.88%
πŸ“ˆ Fitted parameters:
datablock category entry parameter start fitted uncertainty units change
1
sim_lbco
background
50000
y
0.2000
0.2907
0.1814
45.36 % ↑
2
sim_lbco
background
60000
y
0.2000
0.2612
0.0367
30.58 % ↑
3
sim_lbco
background
70000
y
0.2000
0.2795
0.0247
39.76 % ↑
4
sim_lbco
background
80000
y
0.2000
0.2308
0.0188
15.39 % ↑
5
sim_lbco
background
90000
y
0.2000
0.2640
0.0137
31.98 % ↑
6
sim_lbco
background
100000
y
0.2000
0.2551
0.0159
27.57 % ↑
7
sim_lbco
background
110000
y
0.2000
0.2028
0.0569
1.38 % ↑
8
sim_lbco
linked_phases
lbco
scale
1.0000
2.6483
0.1348
164.83 % ↑

Exercise 5.3: Find the Misfit in the Fit#

Visualize the measured and calculated diffraction patterns after the fit. As you can see, the fit shows noticeable discrepancies. If you zoom in on different regions of the pattern, you will observe that all the calculated peaks are shifted to the left.

What could be the reason for the misfit?

Hint:

Consider the following options:

  1. The conversion parameters from TOF to d-spacing are not correct.

  2. The lattice parameters of the LBCO phase are not correct.

  3. The peak profile parameters are not correct.

  4. The background points are not correct.

Solution:

  1. ❌ The conversion parameters from TOF to d-spacing were set based on the data reduction step. While they are specific to each dataset and thus differ from those used for the Si data, the full reduction workflow has already been validated with the Si fit. Therefore, they are not the cause of the misfit in this case.

  2. βœ… The lattice parameters of the LBCO phase were set based on the CIF data, which is a good starting point, but they are not necessarily as accurate as needed for the fit. The lattice parameters may need to be refined.

  3. ❌ The peak profile parameters do not change the position of the peaks, but rather their shape.

  4. ❌ The background points affect the background level, but not the peak positions.

Hide code cell source

project_2.plot_meas_vs_calc(expt_name='sim_lbco')

Exercise 5.4: Refine the LBCO Lattice Parameter#

To improve the fit, refine the lattice parameter of the LBCO phase.

Hint:

To achieve this, we will set the free attribute of the length_a parameter of the LBCO cell to True.

Solution:

Hide code cell source

project_2.sample_models['lbco'].cell.length_a.free = True

project_2.analysis.fit()

project_2.plot_meas_vs_calc(expt_name='sim_lbco')
Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'lmfit (leastsq)'...
πŸ“ˆ Goodness-of-fit (reduced χ²) change:
iteration χ² improvement [%]
1
68.73
13
10.57
84.6% ↓
23
4.61
56.4% ↓
33
4.40
4.5% ↓
65
4.40
πŸ† Best goodness-of-fit (reduced χ²) is 4.40 at iteration 53
βœ… Fitting complete.

Fit results
βœ… Success: True
⏱️ Fitting time: 0.54 seconds
πŸ“ Goodness-of-fit (reduced χ²): 4.40
πŸ“ R-factor (Rf): 6.88%
πŸ“ R-factor squared (RfΒ²): 6.50%
πŸ“ Weighted R-factor (wR): 5.83%
πŸ“ˆ Fitted parameters:
datablock category entry parameter start fitted uncertainty units change
1
lbco
cell
length_a
3.8800
3.8920
0.0001
Γ…
0.31 % ↑
2
sim_lbco
background
50000
y
0.2907
0.2522
0.0459
13.25 % ↓
3
sim_lbco
background
60000
y
0.2612
0.2500
0.0093
4.27 % ↓
4
sim_lbco
background
70000
y
0.2795
0.2560
0.0063
8.41 % ↓
5
sim_lbco
background
80000
y
0.2308
0.2355
0.0048
2.03 % ↑
6
sim_lbco
background
90000
y
0.2640
0.2449
0.0035
7.22 % ↓
7
sim_lbco
background
100000
y
0.2551
0.2416
0.0040
5.32 % ↓
8
sim_lbco
background
110000
y
0.2028
0.2021
0.0144
0.32 % ↓
9
sim_lbco
linked_phases
lbco
scale
2.6483
4.8622
0.0441
83.60 % ↑

One of the main goals of this study was to refine the lattice parameter of the LBCO phase. As shown in the updated fit results, the overall fit has improved significantly, even though the change in cell length is less than 1% of the initial value. This demonstrates how even a small adjustment to the lattice parameter can have a substantial impact on the quality of the fit.

Exercise 5.5: Visualize the Fit Results in d-spacing#

Plot measured vs calculated diffraction patterns in d-spacing instead of TOF.

Hint:

Use the plot_meas_vs_calc method of the project and set the d_spacing parameter to True.

Solution:

Hide code cell source

project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True)

Exercise 5.6: Refine the Peak Profile Parameters#

As you can see, the fit is now relatively good and the peak positions are much closer to the measured data.

The peak profile parameters were not refined, and their starting values were set based on the previous fit of the Si standard sample. Although these starting values are reasonable and provide a good starting point for the fit, they are not necessarily optimal for the LBCO phase. This can be seen while inspecting the individual peaks in the diffraction pattern. For example, the calculated curve does not perfectly describe the peak at about 1.38 Γ…, as can be seen below:

project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True, x_min=1.35, x_max=1.40)

The peak profile parameters are determined based on both the instrument and the sample characteristics, so they can vary when analyzing different samples on the same instrument. Therefore, it is better to refine them as well.

Select the peak profile parameters to be refined during the fitting process.

Hint:

You can set the free attribute of the peak profile parameters to True to allow the fitting process to adjust them. You can use the same approach as in the previous part of the tutorial, but this time you will refine the peak profile parameters of the LBCO phase.

Solution:

Hide code cell source

project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_0.free = True
project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_1.free = True
project_2.experiments['sim_lbco'].peak.broad_gauss_sigma_2.free = True
project_2.experiments['sim_lbco'].peak.broad_mix_beta_0.free = True
project_2.experiments['sim_lbco'].peak.broad_mix_beta_1.free = True
project_2.experiments['sim_lbco'].peak.asym_alpha_0.free = True
project_2.experiments['sim_lbco'].peak.asym_alpha_1.free = True

project_2.analysis.fit()

project_2.plot_meas_vs_calc(expt_name='sim_lbco', d_spacing=True, x_min=1.35, x_max=1.40)
Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'lmfit (leastsq)'...
πŸ“ˆ Goodness-of-fit (reduced χ²) change:
iteration χ² improvement [%]
1
4.47
20
4.03
9.9% ↓
361
4.01
πŸ† Best goodness-of-fit (reduced χ²) is 4.01 at iteration 360
βœ… Fitting complete.

Fit results
βœ… Success: True
⏱️ Fitting time: 2.95 seconds
πŸ“ Goodness-of-fit (reduced χ²): 4.01
πŸ“ R-factor (Rf): 5.55%
πŸ“ R-factor squared (RfΒ²): 4.56%
πŸ“ Weighted R-factor (wR): 3.75%
πŸ“ˆ Fitted parameters:
datablock category entry parameter start fitted uncertainty units change
1
lbco
cell
length_a
3.8920
3.8914
0.0002
Γ…
0.01 % ↓
2
sim_lbco
background
50000
y
0.2522
0.2524
0.0439
0.08 % ↑
3
sim_lbco
background
60000
y
0.2500
0.2528
0.0089
1.11 % ↑
4
sim_lbco
background
70000
y
0.2560
0.2556
0.0061
0.16 % ↓
5
sim_lbco
background
80000
y
0.2355
0.2397
0.0047
1.80 % ↑
6
sim_lbco
background
90000
y
0.2449
0.2448
0.0033
0.04 % ↓
7
sim_lbco
background
100000
y
0.2416
0.2425
0.0039
0.39 % ↑
8
sim_lbco
background
110000
y
0.2021
0.2012
0.0138
0.44 % ↓
9
sim_lbco
linked_phases
lbco
scale
4.8622
4.8194
0.0436
0.88 % ↓
10
sim_lbco
peak
asym_alpha_0
-0.0053
-0.0037
0.0015
31.14 % ↓
11
sim_lbco
peak
asym_alpha_1
0.0138
0.0120
0.0020
13.01 % ↓
12
sim_lbco
peak
gauss_sigma_0
102749.7431
99308.3371
14798.5392
Β΅sΒ²
3.35 % ↓
13
sim_lbco
peak
gauss_sigma_1
-88098.2338
-97015.5977
17997.2478
Β΅s/β„«
10.12 % ↑
14
sim_lbco
peak
gauss_sigma_2
22223.0057
27079.4986
5528.1987
Β΅sΒ²/β„«Β²
21.85 % ↑
15
sim_lbco
peak
mix_beta_0
0.0012
0.0027
0.0005
deg
130.10 % ↑
16
sim_lbco
peak
mix_beta_1
0.0162
0.0105
0.0018
deg
35.37 % ↓

Exercise 5.7: Find Undefined Features#

After refining the lattice parameter and the peak profile parameters, the fit is significantly improved, but inspect the diffraction pattern again. Are you noticing anything undefined?

Hint:

While the fit is now significantly better, there are still some unexplained peaks in the diffraction pattern. These peaks are not accounted for by the LBCO phase. For example, if you zoom in on the region around 1.6 Γ… (or 95,000 ΞΌs), you will notice that the rightmost peak is not explained by the LBCO phase at all.

Solution:

Hide code cell source

project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=1.53, x_max=1.7, d_spacing=True)

Exercise 5.8: Identify the Cause of the Unexplained Peaks#

Analyze the residual peaks that remain after refining the LBCO phase and the peak-profile parameters. Based on their positions and characteristics, decide which potential cause best explains the misfit.

Hint:

Consider the following options:

  1. The LBCO phase is not correctly modeled.

  2. The LBCO phase is not the only phase present in the sample.

  3. The data reduction process introduced artifacts.

  4. The studied sample is not LBCO, but rather a different phase.

Solution:

  1. ❌ In principle, this could be the case, as sometimes the presence of extra peaks in the diffraction pattern can indicate lower symmetry than the one used in the model, or that the model is not complete. However, in this case, the LBCO phase is correctly modeled based on the CIF data.

  2. βœ… The unexplained peaks are due to the presence of an impurity phase in the sample, which is not included in the current model.

  3. ❌ The data reduction process is not likely to introduce such specific peaks, as it is tested and verified in the previous part of the tutorial.

  4. ❌ This could also be the case in real experiments, but in this case, we know that the sample is LBCO, as it was simulated based on the CIF data.

Exercise 5.9: Identify the impurity phase#

Use the positions of the unexplained peaks to identify the most likely secondary phase present in the sample.

Hint:

Check the positions of the unexplained peaks in the diffraction pattern. Compare them with the known diffraction patterns in the introduction section of the tutorial.

Solution:

The unexplained peaks are likely due to the presence of a small amount of Si in the LBCO sample. In real experiments, it might happen, e.g., because the sample holder was not cleaned properly after the Si experiment.

You can visalize both the patterns of the Si and LBCO phases to confirm this hypothesis.

Hide code cell source

project_1.plot_meas_vs_calc(expt_name='sim_si', x_min=1, x_max=1.7, d_spacing=True)
project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=1, x_max=1.7, d_spacing=True)

Exercise 5.10: Create a Second Sample Model – Si as Impurity#

Create a second sample model for the Si phase, which is the impurity phase identified in the previous step. Link this sample model to the LBCO experiment. Visualize the measured diffraction pattern and the calculated diffraction pattern. Check if the Si phase is contributing to the calculated diffraction pattern.

Hint:

You can use the same approach as in the previous part of the tutorial, but this time you need to create a sample model for Si and link it to the LBCO experiment. You can use the plot_meas_vs_calc method of the project to visualize the patterns.

Solution:

Hide code cell source

# Set Space Group
project_2.sample_models.add(name='si')
project_2.sample_models['si'].space_group.name_h_m = 'F d -3 m'
project_2.sample_models['si'].space_group.it_coordinate_system_code = '2'

# Set Lattice Parameters
project_2.sample_models['si'].cell.length_a = 5.43

# Set Atom Sites
project_2.sample_models['si'].atom_sites.add(
    label='Si',
    type_symbol='Si',
    fract_x=0,
    fract_y=0,
    fract_z=0,
    wyckoff_letter='a',
    b_iso=0.89,
)

# Assign Sample Model to Experiment
project_2.experiments['sim_lbco'].linked_phases.add(id='si', scale=1.0)

# Visualize Measured and Calculated patterns
project_2.plot_meas_vs_calc(expt_name='sim_lbco')

Exercise 5.11: Refine the Scale of the Si Phase#

As you can see in the plot above, the calculated pattern is now the sum of both phases. However, the intensities of the Si peaks in the calculated pattern are much too high. Therefore, you need to refine the scale factor of the Si phase. Also, plot the measured and calculated diffraction patterns again to see how the fit improves after refining the scale factor of the Si phase.

Hint:

Set the free attribute of the scale parameter of the Si phase to True to allow the fitting process to adjust the scale factor. After that, you can run the fitting process again.

Solution:

Hide code cell source

# Allow the fitting process to adjust the scale factor of the Si phase.
project_2.experiments['sim_lbco'].linked_phases['si'].scale.free = True

# Now we can perform the fit with both phases included.
project_2.analysis.fit()

# Let's plot the measured diffraction pattern and the calculated diffraction
# pattern both for the full range and for a zoomed-in region around the previously
# unexplained peak near 95,000 ΞΌs. The calculated pattern will be the sum of
# the two phases.
project_2.plot_meas_vs_calc(expt_name='sim_lbco')
project_2.plot_meas_vs_calc(expt_name='sim_lbco', x_min=88000, x_max=101000)
Using experiment πŸ”¬ 'sim_lbco' for 'single' fitting
πŸš€ Starting fit process with 'lmfit (leastsq)'...
πŸ“ˆ Goodness-of-fit (reduced χ²) change:
iteration χ² improvement [%]
1
2327.78
21
1.34
99.9% ↓
39
1.32
1.8% ↓
364
1.31
πŸ† Best goodness-of-fit (reduced χ²) is 1.31 at iteration 363
βœ… Fitting complete.
Fit results
βœ… Success: True
⏱️ Fitting time: 5.03 seconds
πŸ“ Goodness-of-fit (reduced χ²): 1.31
πŸ“ R-factor (Rf): 4.37%
πŸ“ R-factor squared (RfΒ²): 3.76%
πŸ“ Weighted R-factor (wR): 3.24%
πŸ“ˆ Fitted parameters:
datablock category entry parameter start fitted uncertainty units change
1
lbco
cell
length_a
3.8914
3.8913
0.0001
Γ…
0.00 % ↓
2
sim_lbco
background
50000
y
0.2524
0.2556
0.0251
1.28 % ↑
3
sim_lbco
background
60000
y
0.2528
0.2452
0.0051
3.02 % ↓
4
sim_lbco
background
70000
y
0.2556
0.2496
0.0034
2.34 % ↓
5
sim_lbco
background
80000
y
0.2397
0.2375
0.0027
0.92 % ↓
6
sim_lbco
background
90000
y
0.2448
0.2415
0.0019
1.33 % ↓
7
sim_lbco
background
100000
y
0.2425
0.2256
0.0023
6.97 % ↓
8
sim_lbco
background
110000
y
0.2012
0.2322
0.0079
15.39 % ↑
9
sim_lbco
linked_phases
lbco
scale
4.8194
4.8530
0.0252
0.70 % ↑
10
sim_lbco
linked_phases
si
scale
1.0000
0.0358
0.0012
96.42 % ↓
11
sim_lbco
peak
asym_alpha_0
-0.0037
-0.0080
0.0008
118.16 % ↑
12
sim_lbco
peak
asym_alpha_1
0.0120
0.0183
0.0012
52.41 % ↑
13
sim_lbco
peak
gauss_sigma_0
99308.3371
91516.6773
8624.1448
Β΅sΒ²
7.85 % ↓
14
sim_lbco
peak
gauss_sigma_1
-97015.5977
-74213.1364
10718.7411
Β΅s/β„«
23.50 % ↓
15
sim_lbco
peak
gauss_sigma_2
27079.4986
17716.2826
3226.1768
Β΅sΒ²/β„«Β²
34.58 % ↓
16
sim_lbco
peak
mix_beta_0
0.0027
0.0013
0.0003
deg
53.96 % ↓
17
sim_lbco
peak
mix_beta_1
0.0105
0.0163
0.0011
deg
55.04 % ↑

All previously unexplained peaks are now accounted for in the pattern, and the fit is improved. Some discrepancies in the peak intensities remain, but further improvements would require more advanced data reduction and analysis, which are beyond the scope of this tutorial.

Final Remarks#

In this part of the tutorial, you learned how to use EasyDiffraction to refine lattice parameters of a more complex crystal structure, Laβ‚€.β‚…Baβ‚€.β‚…CoO₃ (LBCO). In real experiments, you might also refine additional parameters, such as atomic positions, occupancies, and atomic displacement factors, to achieve an even better fit. For our purposes, we’ll stop here, as the goal was to give you a starting point for analyzing more complex crystal structures with EasyDiffraction.

🎁 Bonus#

You’ve now completed the diffraction data analysis part of the DMSC Summer School. To keep learning and exploring more features of the EasyDiffraction library, visit the official tutorials page, where you’ll find more examples: https://docs.easydiffraction.org/lib/tutorials/