⚒️ | An automated framework for fusing materials imaging simulations into experiments. |
---|
Grain boundary structure initialization from atomic-resolution HAADF STEM imaging (CdTe [110]-[110] tilt grain boundary)
STM structure validation from simulated images of partial charge densities volumes from DFT-simulation (Cu2O(111))
The ingrained toolkit was used with first principles modeling to help pin down the structure of hydrogenated borophene. For more details, refer the full article, featured on cover of Science!
If you are interested in using this tool in your research, please send an email to [email protected] and you will be granted access to the full repository.
The following are a collection of examples containing structures and record of the optimization progress. Refer to the completed_runs folder. Some of the steps mentioned - particularly those referencing a run.py file - refer to functionality that exists only in the full version. Please contact us to gain further access.
Here, we outline the contents of the example given for a CdTe [110]-[110] tilt grain boundary structure
(1) Experimental target image (HAADF149.dm3).
(2) Configuration file (config.json) to specify parameters and settings for bicrystal crystal construction.
{
"slab_1":{
"chemical_formula":"CdTe",
"space_group":"F-43m",
"uvw_project":[1,1,0],
"uvw_upward":[-1,1,0],
"tilt_angle":8,
"max_dimension":40,
"flip_species":false
},
"slab_2":{
"chemical_formula":"CdTe",
"space_group":"F-43m",
"uvw_project":[1,1,0],
"uvw_upward":[0,0,1],
"tilt_angle":0,
"max_dimension":40,
"flip_species":false
},
"constraints":{
"min_width":30,
"max_width":60,
"min_depth":8,
"max_depth":20,
"interface_1_width": 0,
"interface_2_width": 0,
"collision_removal":[true,true],
"pixel_size":0.16388347372412682
},
"structure_file": "bicrystal.POSCAR.vasp" (OPTIONAL - include only if bicrystal structure already exists!)
}
Summary of configuration parameters:
slab parameters
- chemical_formula: An element or compound describing the composition of the bulk.
- space_group: The symmetry group of the configuration
- uvw_project: The direction of projection (screen to viewer) as a lattice vector, ua + vb + wc
- uvw_upward: The upward direction is a lattice vector ua + vb + wc (must be normal to uvw_project).
- tilt_ang: The CCW rotation around 'uvw_project' (applied after uvw_upward is set)
- max_dimension: The maximum edge length along any dimension (Å)
- flip_species: Only applies for compounds containing two chemical elements, will swap chemical identities for all elements
construction parameters
- min/max width: bounds on width in imaging plane (Å)
- min/max depth: bounds on depth along imaging axis (Å)
- interface_1_width: spacing between max position of bottom grain and min position of top grain (Å)
- interface_2_width: spacing between max position of top grain and min position of bottom grain (Å)
- collision_removal: remove atoms closer than 1Å in distance within a in volume around [interface_1,interface_2]
- pixel_size: pixel size of experimental image (check
iop.image_open(image_file)["Experiment Pixel Size"]
)
optional
- structure_file: bypass the construction and initialize
Bicrystal()
object with existing structure
(3) Python script to execute steps of the ingrained workflow (run.py).
import sys
sys.path.append('../../../../')
import matplotlib.pyplot as plt
import ingrained.image_ops as iop
from ingrained.structure import Bicrystal
from ingrained.optimize import CongruityBuilder
# Read image data
image_data = iop.image_open('HAADF149.dm3')
# Constrain optimization to clean region of image by cropping
exp_img = image_data['Pixels'][0:470,0:470]
# View the image before proceeding with optimization
plt.imshow(exp_img, cmap='gray'); plt.axis('off'); plt.show();
# Initialize a Bicrystal object and save the constructed bicrystal structure
bicrystal = Bicrystal(filename='config.json', write_poscar=True);
# Initialize a ConguityBuilder with bicrystal and experimental image
congruity = CongruityBuilder(sim_obj=bicrystal, exp_img=exp_img);
# Input parameters to optimize for an image simulation:
pix_size = image_data["Experiment Pixel Size"]
interface_width = 0.00
defocus = 1.50
x_shear = 0.00
y_shear = 0.00
x_stretch = 0.00
y_stretch = 0.00
crop_height = 289
crop_width = 161
sim_params = [pix_size, interface_width, defocus, x_shear, y_shear, x_stretch, y_stretch, crop_height, crop_width]
# Find correspondence (supports 'Powell' and 'COBYLA' methods from scipy.optimize.minimize)
congruity.find_correspondence(objective='taxicab_ssim', optimizer='Powell', initial_solution=sim_params, search_mode="gb")
Summary of optimization parameters:
- pix_size: real-space pixel size (Å).
- interface_width: spacing between max position of bottom grain and miniumum position of top grain (Å)
- defocus: controls degree to which edges blur in microscopy image (Å)
- x_shear: fractional amt shear in x (+ to the right)
- y_shear: fractional amt shear in y (+ up direction)
- x_stretch: fractional amt stretch (+) or compression (-) in x
- y_stretch: fractional amt stretch (+) or compression (-) in y
- crop_height: final (cropped) image height in pixels
- crop_width: final (cropped) image width in pixels
Here, we outline the contents of the example given for a Cu2O(111) surface
(1) Experimental target image (cropped) (03h_Cu2O_111_034_fwd_z_plane.txt).
(2) Configuration file (PARCHG) which provides the partial charge densities from a DFT-simulation.
This file will need to be downloaded, as it is not stored in the repo.
bash parchg_download.sh
(3) Python script to execute steps of the ingrained workflow (run.py).
import sys
sys.path.append('../../../../')
import numpy as np
import ingrained.image_ops as iop
import matplotlib.pyplot as plt
from ingrained.structure import PartialCharge
from ingrained.optimize import CongruityBuilder
# Read image data
image_data = iop.image_open('03h_Cu2O_111_034_fwd_z_plane.txt')
# Constrain optimization to clean region of image by cropping
exp_img = image_data['Pixels'][220:370,140:290]
# Display the experimental STM image
plt.imshow(exp_img,cmap='hot'); plt.axis('off'); plt.show();
# Initialize a PartialCharge object with the path to the PARCHG file
parchg = PartialCharge(filename='PARCHG_with_Cu_cus_modelA_P1_stm1.5');
# Initialize a ConguityBuilder with PARCHG and experimental image
congruity = CongruityBuilder(sim_obj=parchg, exp_img=exp_img);
# Input parameters to optimize for an image simulation:
z_below = 2
z_above = 12
r_val = 0.27
r_tol = 0.24
x_shear = 0.00
y_shear = 0.00
x_stretch = 0.00
y_stretch = 0.00
rotation = 110
pix_size = 0.27
sigma = 0
crop_height = 101
crop_width = 101
sim_params = [z_below, z_above, r_val, r_tol, x_shear, y_shear, x_stretch, y_stretch, rotation, pix_size, sigma, crop_height, crop_width]
# Find correspondence (supports 'Powell' and 'COBYLA' methods from scipy.optimize.minimize)
congruity.find_correspondence(objective='taxicab_ssim', optimizer='Powell', initial_solution=sim_params, search_mode="stm")
Summary of optimization parameters:
- z_below: thickness or depth from top in (Å)
- z_above: distance above the surface to consider (Å)
- r_val: isosurface charge density plane
- r_tol: tolerance to consider while determining isosurface
- x_shear: fractional amt shear in x (+ to the right)
- y_shear: fractional amt shear in y (+ to the right)
- x_stretch: fractional amt stretch (+) or compression (-) in x
- y_stretch: fractional amt stretch (+) or compression (-) in y
- rotation: image rotation angle (in degrees) (+ is CCW)
- pix_size: real-space pixel size (Å).
- sigma: std. deviation for gaussian kernel used in postprocessing
- crop_height: final (cropped) image height in pixels
- crop_width: final (cropped) image width in pixels
(4) Python script for final relaxation of z_above
parameter (relax_z_above.py)
Run only after find_correspondence
has completed. Currently, z_above
acts as a free variable
once it exceeds the top of the charge surface, and optimization may make this value arbitarily high when the best solution
is at the top of the surface. This function ensures that z_above
represents a physically meaningful distance
(Å) above the surface.
Execution of the sequence outlined in run.py will produce:
- bicrystal.POSCAR.vasp - (Ex. 1 only) a POSCAR of the newly constructed bicrystal
- strain_info.txt - (Ex. 1 only) record of the amount of strain in each bicrystal grain (given as % along width and depth)
- progress.txt - record of the optimization solution and the respective figure-of-merit (FOM) at each optimization step.
Additional tools are included to view and write optimization progress to a movie
- print_frames.py - writes specified optimization steps (frames) to custom image panels (.png)
- make_movie.sh - wrapper around FFmpeg to create a movie from the sequence of panels created in print_frames.py
Make adjustments to the frame_selection
argument passed to print_frames.py to customize the output, or just choose "all" to print all steps of the optimization. With the frames printed, make a movie outlining the progress by simply running:
bash make_movie.sh
If you find EXSCLAIM! useful, please encourage its development by citing the following paper in your research:
Schwenker, E., Kolluru, V. S. C., Spreadbury, T., Guo, J., Hu, X., Dravid, V., Klie, R., Guest, J., Chan, M.K.Y, Ingrained: an automated framework for fusing materials imaging simulations into experiments. **in preparation** (2021)
This material is based upon work supported by Laboratory Directed Research and Development (LDRD) funding from Argonne National Laboratory, provided by the Director, Office of Science, of the U.S. Department of Energy under Contract No. DE-AC02-06CH11357
This work was performed at the Center for Nanoscale Materials, a U.S. Department of Energy Office of Science User Facility, and supported by the U.S. Department of Energy, Office of Science, under Contract No. DE-AC02-06CH11357.
We gratefully acknowledge the computing resources provided on Bebop, a high-performance computing cluster operated by the Laboratory Computing Resource Center at Argonne National Laboratory.