Tutorial 1: Automated Refinement (with BGMN)#
Dara provides a Python-based wrapper to the refinement software, BGMN, which implements a robust optimization algorithm that can refine automatically in most cases. This tutorial will show you how to interact with BGMN software and how to submit, adjust, and visualize your refinements.
You can download this tutorial project from here.
%pip install ipywidgets nbformat
from pathlib import Path
from dara.refine import do_refinement_no_saving
data = Path("tutorial_data")
cif_paths = list(data.glob("*.cif")) # include all the cif files in the data folder
pattern_fn = "CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.xy"
Basic Refinement#
A one-line refinement with the default settings.
Do refinement#
By running do_refinement_no_saving, the refinement will be performed and the results
will be printed out. There will be no BGMN refinement output folder saved on the disk.
The only two things you will need to feed into the system:
the path to the pattern. Currently, Dara only supports the
xy,xrdml,rawformats.a list of CIF file paths. The CIF will be used as the reference structure for the refinement.
refinement = do_refinement_no_saving(data / pattern_fn, cif_paths)
2026-01-03 20:24:50,677 WARNING dara.bgmn_worker BGMN executable not found. Downloading BGMN.
0%| | 0.00/1.59M [00:00<?, ?iB/s]
99%|█████████▊| 1.57M/1.59M [00:00<00:00, 6.41MiB/s]
100%|██████████| 1.59M/1.59M [00:00<00:00, 6.33MiB/s]
Visualization#
You can call visualize to visualize the refinement results. The observed, calculated, and difference patterns will be plotted.
refinement.visualize()
Save the refinement plot#
Optionally, if you want to share the plot with others, you can save the plot by calling write_image or write_html in the plotly.Figure object returned by .visualize(). The plot will be saved on the disk.
refinement.visualize().write_html("tutorial_refinement.html") # output the interactive html file to the disk
refinement.visualize().write_image("tutorial_refinement.png") # output the png image to the disk
Extracting information from the refinement#
After finishing refinement, you can read information from the RefinementResult object. The object contains the following attributes:
lst_data: information about phases, metrics of refinement (from the .lst file in BGMN)peak_data: the simulated peaks in the calculated patternplot_data:x(two-theta),y_obs,y_calc,y_bkg, contribution from each phase. This is mainly used for visualization.
For example, you can get Rwp from lst_data
f"The refinement has Rwp = {refinement.lst_data.rwp} %"
'The refinement has Rwp = 5.24 %'
You can also get the information about the lattice, weight fraction for the phase.
Some values are in a tuple, with first value as the value and the second value as the error.
phase_name = "CaNi(PO3)4_15_sym"
phase_result = refinement.lst_data.phases_results[phase_name]
gewicht = phase_result.gewicht
lattice_a = phase_result.a
lattice_b = phase_result.b
lattice_c = phase_result.c
lattice_alpha = phase_result.alpha
lattice_beta = phase_result.beta
lattice_gamma = phase_result.gamma
print(f"The lattice parameters of the phase {phase_name} are:\n" \
f" a = {lattice_a} nm, b = {lattice_b} nm, c = {lattice_c} nm,\n" \
f" alpha = {lattice_alpha}, beta = {lattice_beta}, gamma = {lattice_gamma}\n")
print(f"The weight fraction of the phase {phase_name} is ({gewicht[0]} ± {gewicht[1]}) %")
The lattice parameters of the phase CaNi(PO3)4_15_sym are:
a = (1.20707, 0.0001) nm, b = (0.869244, 9.2e-05) nm, c = (0.97907, 0.00011) nm,
alpha = None, beta = (117.9527, 0.0055), gamma = None
The weight fraction of the phase CaNi(PO3)4_15_sym is (0.2162 ± 0.0019) %
Peak data is stored in a pandas DataFrame.
refinement.peak_data
| 2theta | intensity | b1 | b2 | h | k | l | phase | phase_idx | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 37.419910 | 367.178051 | 0.004434 | 6.435354e-10 | 1 | 1 | 1 | NiO_225_sym | 0 |
| 1 | 43.435091 | 559.693988 | 0.004434 | 1.139670e-09 | 2 | 0 | 0 | NiO_225_sym | 0 |
| 2 | 62.965868 | 271.241200 | 0.004434 | 4.532117e-09 | 2 | 2 | 0 | NiO_225_sym | 0 |
| 3 | 75.453773 | 95.990765 | 0.004434 | 8.554955e-09 | 3 | 1 | 1 | NiO_225_sym | 0 |
| 4 | 79.430774 | 70.716772 | 0.004434 | 1.017677e-08 | 2 | 2 | 2 | NiO_225_sym | 0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 481 | 99.288509 | 0.046459 | 0.004889 | 1.651996e-08 | 2 | 8 | 2 | CaNi(PO3)4_15_sym | 1 |
| 482 | 102.264363 | 8.212658 | 0.004889 | 1.800964e-08 | 5 | 1 | 6 | CaNi(PO3)4_15_sym | 1 |
| 483 | 99.476618 | 11.579483 | 0.004889 | 1.661263e-08 | 11 | 1 | -1 | CaNi(PO3)4_15_sym | 1 |
| 484 | 100.286022 | 7.091383 | 0.004889 | 1.701459e-08 | 8 | 4 | 2 | CaNi(PO3)4_15_sym | 1 |
| 485 | 102.421640 | 1.774795 | 0.004889 | 1.809025e-08 | 3 | 5 | -8 | CaNi(PO3)4_15_sym | 1 |
486 rows × 9 columns
Export the refined structure#
You can also export the refined structure as a pymatgen.Structure object.
structure = refinement.export_structure("CaNi(PO3)4_15_sym")
print(structure.to("tutorial_refinement_refined_CaNi(PO3)4_15_sym.cif", symprec=1e-3))
# generated using pymatgen
data_CaNi(PO3)4
_symmetry_space_group_name_H-M C2/c
_cell_length_a 12.07070000
_cell_length_b 8.69244000
_cell_length_c 9.79070000
_cell_angle_alpha 90.00000000
_cell_angle_beta 117.95270000
_cell_angle_gamma 90.00000000
_symmetry_Int_Tables_number 15
_chemical_formula_structural CaNi(PO3)4
_chemical_formula_sum 'Ca4 Ni4 P16 O48'
_cell_volume 907.43028822
_cell_formula_units_Z 4
loop_
_symmetry_equiv_pos_site_id
_symmetry_equiv_pos_as_xyz
1 'x, y, z'
2 '-x, -y, -z'
3 '-x, y, -z+1/2'
4 'x, -y, z+1/2'
5 'x+1/2, y+1/2, z'
6 '-x+1/2, -y+1/2, -z'
7 '-x+1/2, y+1/2, -z+1/2'
8 'x+1/2, -y+1/2, z+1/2'
loop_
_atom_site_type_symbol
_atom_site_label
_atom_site_symmetry_multiplicity
_atom_site_fract_x
_atom_site_fract_y
_atom_site_fract_z
_atom_site_occupancy
Ca Ca0 4 0.00000000 0.04710000 0.25000000 1.0
Ni Ni1 4 0.25000000 0.25000000 0.50000000 1.0
P P2 8 0.00320000 0.72360000 0.47450000 1.0
P P3 8 0.18860000 0.49180000 0.19450000 1.0
O O4 8 0.03010000 0.86190000 0.41040000 1.0
O O5 8 0.06150000 0.25470000 0.42910000 1.0
O O6 8 0.07200000 0.60250000 0.15680000 1.0
O O7 8 0.13280000 0.36240000 0.06680000 1.0
O O8 8 0.21690000 0.08020000 0.33440000 1.0
O O9 8 0.22930000 0.42260000 0.34390000 1.0
Refining with customized phase parameters#
The refinement with default setting looks good. But can it be better?
Dara supports the basic refinement parameters in BGMN / Profex. You can adjust the refinement parameters by passing the parameters to the do_refinement_no_saving function.
Common parameters include:
lattice_range: you can (and need to) specify the range that the lattice parameters can vary. Usually, it can be a small range, like 0.01 ~ 0.05. It is applied to all lattice parameters (a, b, c, alpha, beta, gamma).b1: controls the width of the peak.b1describes the average particle size in the XRD sample. The larger theb1, the broader the peak. Usually, it is constraint to a small range, like from 0 to 0.005. If the fittedb1is too large, you will see the peaks go too broad. In this case, your simulated pattern will look like an amorphous material that can be easily fit into the background.k1: also controls the width of the peak.k1describes the width of the particle size distribution in the sample. The larger thek1, the smaller the distribution is. Usually, it can be constrained to 0 ~ 1.k2: describes the microstrain in the sample. The larger thek2, the larger the microstrain. Usually, it can be a fixed value, like 0.gewicht: means “weight” in German. it contains the information of scale factor. However, in BGMN, it can also be used to specify the preferred orientation you would like to use in the refinement. By specifying the preferred orientation, you can vary the intensity of a set of reflections in the pattern, which can help you fit your pattern better. BGMN is able to decide which reflection to adjust automatically. You only need to specify how strong the preferred orientation is. Usually, it can beSPHAR0(none),SPHAR2(two preferred orientation parameters), orSPHAR4(four preferred orientation parameters), … (up toSPHAR10). The larger thegewicht, the stronger the preferred orientation is. But it can cause overfitting as well.
Input parameter format#
In Dara, all the phase parameters are passed as a dictionary arg called phase_params. The key is the parameter name, and the value is the parameter value. Dara supports three types of values:
fixed. This is a string. The parameter will be fixed to the default value (usually 0).(initial value)_(min value)^(max value). This is a string. The parameter will be allowed to vary in the refinement between initial value to the min value to the max value. The min value begins with_, and the max value begins with^.Other values. It can be a string or a number. For example, setting
gewichttoSPHAR2means that preferred orientation is modeled with theSPHAR2settings; settinglattice_rangeto 0.05 means that the lattice parameters can vary up to 5%.. See the BGMN / Profex manual for more information on these settings.
If you want to allow 5% variation in lattice parameters, then use the following
settings. b1: (started from 0, min = 0, max = 0.005), k1: (started from 0, min = 0,
max = 1), k2: fixed to 0, and gewicht: SPHAR2. This corresponds to a
phase_params dict of:
phase_params = {
"lattice_range": 0.05,
"b1": "0_0^0.005",
"k1": "0_0^1",
"k2": "fixed",
"gewicht": "SPHAR2"
}
refinement = do_refinement_no_saving(data / pattern_fn, cif_paths, phase_params=phase_params)
refinement.visualize()
Now you can see the Rwp of the refinement is slightly lower, indicating a better fit.
Specify different parameters for different phases#
In the previous example, the refinement option is applied to all phases. But you can also specify different parameters for different phases. To do so, you will need to pass a special RefinementPhase object to the phases parameter.
from dara import RefinementPhase
phases = [RefinementPhase.make(cif_path) for cif_path in cif_paths]
for phase in phases:
# use a smaller lattice range for each phase
phase.params["lattice_range"] = 0.01
refinement = do_refinement_no_saving(
data / pattern_fn,
phases=phases,
# if one parameter is both specified in phase_params and in the RefinementPhase object, the value in RefinementPhase will be used.
phase_params={
"lattice_range": 0.05, # <- this will be ignored because it has already been set in the eahc RefinementPhase object
"b1": "0_0^0.005",
"k1": "0_0^1",
"k2": "fixed",
"gewicht": "SPHAR2"
}
)
Refining with different instrument profiles, angle range, wavelength, etc.#
If you would like to do refinement in a different instrument profiles or angle range, you can specify it in the refinement function as well.
instrument_name: the instrument profile you would like to use. The default instrument profile used is Aeris-fds-Pixcel1d-Medipix3. You can find the available instrument profiles in thedara/data/BGMN-Templates/Devicesfolder.wavelength: the wavelength you would like to use in the refinement. It can be two types:a number: the wavelength in nm. It is useful when you analyzing the data from a synchrotron.
a string: the element symbol. It represents the target material in the X-ray tube. BGMN can automatically find the distribution of the wavelength for the given metal. Currently, it supports sources of [“Cu”, “Co”, “Cr”, “Fe”, “Mo”]
wmin,wmax: the angle range you would like to use in the refinement. It is set inrefinement_params.
refinement = do_refinement_no_saving(
data / pattern_fn,
cif_paths,
instrument_profile="Aeris-fds-Pixcel1d-Medipix3",
wavelength="Cu",
refinement_params={
"wmin": 20, # set the minimum two-thera in the refinement to be 20 deg.
"wmax": 50 # set the maximum two-theta in the refinement to be 50 deg.
}
)
refinement.visualize()
Save the project file to a folder on the disk#
Usually, you don’t have to directly interact with the BGMN input/output files.
However, if you would like to save the project directory for later use, you can use the
do_refinement function instead.
The refinement files will be saved in the path specified by working_dir. Other than
that, do_refinement and do_refinement_no_saving share the same parameters and
output.
Feel free to modify the refinement project file yourself or by loading with the Profex software.
from dara import do_refinement
refinement = do_refinement(data / pattern_fn, cif_paths, working_dir="tutorial_refinement")
refinement_folder = Path("tutorial_refinement")
# show all the files in the folder
for file in refinement_folder.glob("*"):
print(">", file.name)
> NiO_225_sym.str
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.xy
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.lst
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.par
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.sav
> CaNi(PO3)4_800_240_Ca(OH)2_(NH4)2HPO4_NiO.dia
> Aeris-fds-Pixcel1d-Medipix3.geq
> CaNi(PO3)4_15_sym.str