This repository contains scripts for generating forward simulations of X-ray scattering including GIWAXS (Grazing Incidence Wide-Angle X-ray Scattering) data. The simulations are created using structure .xyz or .pdb files and produce 3D voxel grids of scattering intensity values, which can then be used to generate 2D detector images at various geometries.
If you find this code useful for your research please consider citing it:
- Most simulations can be ran on personal computer, but depending on simulation size and resolution high performance computers may be needed
- Python >= 3.8
- numpy
- matplotlib
- fabio
- scipy
- xraydb
Forward simulations are created through primarily through: simulate_GIWAXS.py. This script is intended to be run in the command line with a single argument pointing to the configuration file (ex: python simulate_GIWAXS.py --config /path/to/config_file.txt). Details of this script and the configuration file format are described below. Note that whenever a directory is input as a string please do not include the trailing /:
This script takes a .pdb (preferred) or .xyz structure file and generates a sample "detector image" in units of Å-1. This detector image represents the X-ray scattering that would be observed for the given structure with defined size sampled over the specified structure orientations. Outputs are linearly and log scaled .png along with .npy files containing the detector image and the reciprocal space values for row and column id as qz and qxy respectively. With example config values this runs in <1min on M2 macbook air.
The script runs through the following steps:
- Projecting material coordinates onto the y-z plane.
- Assigning each material coordinate a complex atomic scattering factor "f".
- Optionally applying edge smoothing the 2D grid of "f" values to prevent termination ripples.
- Taking the norm squared of 2D FFT on the "f" values, converting axes to q-space in Å-1, re-centering, and saving.
- Rotating the material coordinates by some calculated delta phi and repeating steps 1-4 until 180 degrees rotation is achieved
- Taking each detector slice and binning it into an evenly spaced 3D I(qx,qy,qz) voxel grid based on rotation. Bins are averaged at the end
- Cropping reciprocal space voxel grid to relevant q-values.
- Initializing a new detector plane size, resolution, and orientation.
- Intersecting detector pixels with scattering intensity voxel grid.
- Storing detector intensities at that orientation.
- Rotating the detector and repeating step 3 for all orientations.
- Summing final detector image of all orientations.
Optionally comparing to experimental data by: 14. Fitting a scale factor and flat background intensity for simulated data 15. Plotting overlayed linecuts between simulated and experimental data
- An example configuration file is in
/config_templates/simulate_GIWAXS_config.txt
Arguments to define the structure for scattering simulation input_filepath=(string) optionally a path to a.xyzor.pdbfile for I(q) voxelgrid from single fileinput_folder=(string) optionally a path to a folder of.pdbfiles for average I(q) from many filesfiletype=(string) must be identified if specifying input_folder (ex: .pdb).x_size=(positive float) desired slab size along x-axis in Å.y_size=(positive float) desired slab size along y-axis in Å.z_size=(positive float) desired slab size along z-axis in Å.a=(positive float) input file box side length in Å, only needed for.xyzfiles.b=(positive float) input file box side length in Å, only needed for.xyzfiles.c=(positive float) input file box side length in Å, only needed for.xyzfiles.alpha=(positive float) input file interior angle in degrees, only needed for.xyzfiles.beta=(positive float) input file interior angle in degrees, only needed for.xyzfiles.gamma=(positive float) input file interior angle in degrees, only needed for.xyzfiles.
Arguments to define the creation of reciprocal space voxel gridr_voxel_size=(positive float) side length dimension of square real-space voxels in Å.q_voxel_size=(positive float) side length dimension of square reciprocal-space voxels in Å-1.max_q=(positive float) determines the q-value to which the iq voxel grid is cropped.energy=(positive float) X-ray energy in eV for simulation of f' and f" scattering factorsfill_bkg=(boolean) flag to fill the padded real-space (needed to reach desired q_voxel_size) with average electron density. (simulate aggregate suspended in electron density matched matrix)smooth=(positve float) sigma value used to create smooth transition from slab electron density to padded electron density. 0 for no smoothing
Arguments to define the sampling of reciprocal space voxel grid with a "detector" planenum_pixels=(positive integer) number of pixels along each detector axis.angle_init_val1=(float) 1st initializing detector rotation in degrees aboutangle_init_ax1.angle_init_val2=(float) 2nd initializing detector rotation in degrees aboutangle_init_ax2.angle_init_val3=(float) 3rd initializing detector rotation in degrees aboutangle_init_ax3.angle_init_ax1=(string) rotation axis for 1st initializing rotation; set to none for no rotation.angle_init_ax2=(string) rotation axis for 2nd initializing rotation; set to none for no rotation.angle_init_ax3=(string) rotation axis for 3rd initializing rotation; set to none for no rotation.psi_start=(float) starting value in degrees for psi.psi_end=(float) ending value in degrees for psi.psi_num=(positive integer) number of linearly spaced psi steps.psi_weights_path=(string) optional path to.npyfile that holds 1D list of weights for each psiphi_start=(float) starting value in degrees for phi.phi_end=(float) ending value in degrees for phi.phi_num=(positive integer) number of linearly spaced phi steps.phi_weights_path=(string) optional path to.npyfile that holds 1D list of weights for each phitheta_start=(float) starting value in degrees for theta.theta_end=(float) ending value in degrees for theta.theta_num=(positive integer) number of linearly spaced theta steps.theta_weights_path=(string) optional path to.npyfile that holds 1D list of weights for each thetamirror=(boolean) a flag to mirror final detector image about vertical and horizontal axes.save_folder=(string) optional path to output directory; if not defined,os.get_cwd()is used.
Optional experimental comparison arguments:mask_path= (string) optional path to mask file (.npy) same size as experimental detector image. pixels==1 are maskedimg_path= (string) optional path to experimental q-converted dector image file (.tif)qxy_path= (string) optional path to qxy axis values in Å-1 for image file (.txt)qz_path= (string) optional path to qz axis values in Å-1 for image file (.txt)fit_scale_offset= (boolean) optional flag to optimize the scale and constant background of simulated data to experimental data
r_voxel_sizeandq_voxel_sizedetermine your q-uncertainty and q-resolution respectively. Choosing a smallr_voxel_sizeand smallq_voxel_sizerequires very large arrays that will utilize more memory and slow the simulation. Reasonable values for PC use are in example config filesaff_num_qscan determine how accurate your f0(q) values are. Appreciable differences in polymer scattering patterns have been found between using 1 and 5. Note that computation time increases linearly withaff_num_qsso it is recommended not to exceed 10.- Rotation axes are defined as psi, phi, and theta for rotation about detector normal, vertical, and horizontal axes, respectively.
- The detector begins with the vertical axis pointing along positive qz, the horizontal axis along positive qy, and the normal axis along positive qx.
- Use “init” rotations to set up your detector such that psi and phi will capture the disorder you desire. Phi is usually used for fiber texture and psi for orientational disorder.
- Visualization tools are available as jupyter notebooks in
./test_notebooksto better understand these manipulations. - For in-plane isotropy (common with spun coat films), only ¼ of the total rotation space needs to be probed as the GIWAXS detector plane is mirrored about the horizontal and vertical axis after summing.
- For example, if you are trying to match an experimental sample with spun coat texture and ±15° tilting about the polymer backbone axis, then you may define
psi_start,psi_end,psi_num= (0, 15, 16) andphi_start,phi_end,phi_num= (0, 179, 180). - weights paths for rotations should sum to 1 and be same length as
psi/phi/theta_num. It can be made from a pole figure (carefully consider if any correction such as sin(Chi) should be used!). If no path is specified even weighting is applied to all angles
This script takes a .xyz periodic unit cell and propagates it to a desired orthorhombic slab size.
Configuration file parameters:
An example configuration file is in /config_templates/slabmaker_config.txt
input_filepath=(string) path to.xyzor.pdbfile containing periodic celloutput_filepath=(string) directory where you would like.xyzslab saved (optional).gen_name=(string) samegen_nameused invoxelgridmaker.py.x_size=(float) size in Å of slab along x-axis.y_size=(float) size in Å of slab along y-axis.z_size=(float) size in Å of slab along z-axis.a=(float) cell side length in Å.b=(float) cell side length in Å.c=(float) cell side length in Å.alpha=(float) cell interior angle in degrees.beta=(float) cell interior angle in degrees.gamma=(float) cell interior angle in degrees.
This script takes a .xyz or .pdb structure file and converts it into a 3D voxel grid of scattering intensity values with axes in units of Å-1. With example config values this runs in <5min on M2 macbook air. The script runs through the following steps:
- Projecting material coordinates onto the y-z plane.
- Assigning each material coordinate a complex atomic scattering factor "f".
- Optionally windowing the 2D grid of "f" values to prevent termination ripples.
- Taking the norm squared of 2D FFT on the "f" values, converting axes to q-space in Å-1, re-centering, and saving.
- Rotating the material coordinates by some calculated delta phi and repeating steps 1-4 until 180 degrees rotation
- Taking each detector slice and binning it into an evenly spaced 3D I(qx,qy,qz) voxel grid based on rotation. Bins are averaged at the end
- Cropping reciprocal space voxel grid to relevant q-values and saving them for later use.
Configuration file parameters:
An example configuration file is in /config_templates/voxelgridmaker_highmem_config.txt
input_filepath=(string) optionally a path to a.xyzfile for I(q) voxelgrid from single fileinput_folder=(string) optionally a path to a folder of.xyzor.pdbfiles for average I(q) from many filesgen_name=(string) a short sample name used to create directories and output files.r_voxel_size=(positive float) side length dimension of square real-space voxels in Å.q_voxel_size=(positive float) side length dimension of square reciprocal-space voxels in Å-1.aff_num_qs=(positive integer) number of q bins to evaluate atomic scattering factor f0(q).energy=(positive float) X-ray energy in eV for simulation of f' and f" scattering factorsmax_q=(positive float) determines the q-value to which the iq voxel grid is cropped.output_dir=(string) optional path to output directory; if not defined,os.get_cwd()is used.num_cpus=(positive integer) number of cpu cores to utilize for multiprocessingfill_bkg=(boolean) flag to fill the padded real-space (needed to reach desired q_voxel_size) with average electron densitysmooth=(positve float) sigma value used to create smooth transition from slab electron density to padded electron density. 0 for no smoothingfix_dc_offset=(boolean) flag to check for and fix any bright streak along q-axes that result from "dc offset"scratch_folder=(string) path to a scratch directory for storing temporary orientation frames deleted during cleanup. Do not include trailing/. Default os.getcwd()
Tips:
r_voxel_sizeandq_voxel_sizedetermine your q-uncertainty and q-resolution respectively. Choosing a smallr_voxel_sizeand smallq_voxel_sizerequires very large arrays that will utilize more memory and slow the simulation. Reasonable values for PC use are in example config filesaff_num_qscan determine how accurate your f0(q) values are. Appreciable differences in polymer scattering patterns have been found between using 1 and 5. Note that computation time increases linearly withaff_num_qsso it is recommended not to exceed 10.- if using tukey windowing the slabs described by the
.xyzfile should be orthorhombic (slabmaker.py can do this for you)
This script loads the iq reciprocal space voxel grid and associated axes generated by voxelgridmaker.py and uses them to populate scattering intensity on a 2D detector plane at various geometries. These geometries are summed to produce a final “det_sum” as the simulated GIWAXS. With example config values this runs in ~30s on M2 macbook air. The steps are:
- Initializing detector plane size, resolution, and orientation.
- Intersecting detector pixels with scattering intensity voxels.
- Saving detector intensities at that orientation.
- Rotating the detector and repeating step 3 for all orientations.
- Summing final detector image of all orientations.
Configuration file parameters:
An example configuration file is in /config_templates/detectormaker_config.txt
iq_output_folder=(string) output fromvoxelgridmaker.py(form./name_output_files).gen_name=(string) samegen_nameused invoxelgridmaker.py.max_q=(positive float) maximum q-value on detector must be ≤ max_q used to make iq file.num_pixels=(positive integer) number of pixels along each detector axis.angle_init_val1=(float) 1st initializing detector rotation in degrees aboutangle_init_ax1.angle_init_val2=(float) 2nd initializing detector rotation in degrees aboutangle_init_ax2.angle_init_val3=(float) 3rd initializing detector rotation in degrees aboutangle_init_ax3.angle_init_ax1=(string) rotation axis for 1st initializing rotation; set to none for no rotation.angle_init_ax2=(string) rotation axis for 2nd initializing rotation; set to none for no rotation.angle_init_ax3=(string) rotation axis for 3rd initializing rotation; set to none for no rotation.psi_start=(float) starting value in degrees for psi.psi_end=(float) ending value in degrees for psi.psi_num=(positive integer) number of linearly spaced psi steps.phi_start=(float) starting value in degrees for phi.phi_end=(float) ending value in degrees for phi.phi_num=(positive integer) number of linearly spaced phi steps.theta_start=(float) starting value in degrees for theta.theta_end=(float) ending value in degrees for theta.theta_num=(positive integer) number of linearly spaced theta steps.mirror=(boolean) a flag to mirror final detector image about vertical and horizontal axes. Omit flag for False (writingmirror=Falseis still interpreted as True)cleanup=(boolean) a flag to automatically delete single orientation frames after averaging (can range 1-100s of gb). Omit flag for False (writingcleanup=Falseis still interpreted as True)num_cpus=(positive integer) number of cpu cores to utilize for multiprocessingscratch_folder=(string) path to a scratch directory for storing temporary orientation frames deleted during cleanup. Do not include trailing/. Default os.getcwd()
Tips:
- Rotation axes are defined as psi, phi, and theta for rotation about detector normal, vertical, and horizontal axes, respectively.
- The detector begins with the vertical axis pointing along positive qz, the horizontal axis along positive qy, and the normal axis along positive qx.
- Use “init” rotations to set up your detector such that psi and phi will capture the disorder you desire. Phi is usually used for fiber texture and psi for orientational disorder.
- Visualization tools are available as jupyter notebooks in
./test_notebooksto better understand these manipulations. - For fiber texture, only ¼ of the total rotation space needs to be probed as the GIWAXS detector plane is mirrored about the horizontal and vertical axis after summing.
- For example, if you are trying to match an experimental sample with fiber texture and ±15° tilting about the backbone axis, then you may define
psi_start,psi_end,psi_num= (0, 15, 16) andphi_start,phi_end,phi_num= (0, 179, 180). - If you do not want mirroring you will manually have to comment out code, better solution will be added soon
- Add capability for polarization effects
- Time=low, complexity=low
- Add memory requirement estimator tool
- Time=medium, complexity=low
- Convert plotting functions from notebook to script
- Time=medium, complexity=low
- Check for and remove duplicated atomic positions in slabmaker
- Time=medium, complexity=medium
- Optional GUI
- Time=high, complexity=medium
- Convert to classes
- Time=high, complexity=low
- Supress termination ripples in voxelgridmaker.py
- Time=medium, complexity=high
- progress bars!
- Time=low, complexity=low