dislocation_SDVPN - Methodology and code
Python imports
[1]:
# Standard library imports
from pathlib import Path
import shutil
import datetime
from copy import deepcopy
from math import floor
from typing import Optional, Tuple, Union
import random
# http://www.numpy.org/
import numpy as np
# https://ipython.org/
from IPython.display import display, Markdown
# https://github.com/usnistgov/atomman
import atomman as am
import atomman.lammps as lmp
import atomman.unitconvert as uc
from atomman.tools import filltemplate
import matplotlib.pyplot as plt
# https://github.com/usnistgov/iprPy
import iprPy
from iprPy.tools import read_calc_file
print('Notebook last executed on', datetime.date.today(), 'using iprPy version', iprPy.__version__)
Notebook last executed on 2023-07-31 using iprPy version 0.11.6
1. Load calculation and view description
1.1. Load the calculation
[2]:
# Load the calculation being demoed
calculation = iprPy.load_calculation('dislocation_SDVPN')
1.2. Display calculation description and theory
[3]:
# Display main docs and theory
display(Markdown(calculation.maindoc))
display(Markdown(calculation.theorydoc))
dislocation_SDVPN calculation style
Lucas M. Hale, lucas.hale@nist.gov, Materials Science and Engineering Division, NIST.
Introduction
The dislocation_SDVPN calculation style predicts a dislocation’s planar spreading using the semidiscrete variational Peierls-Nabarro method. The solution finds the disregistry (difference in displacement above and below the slip plane) that minimizes the dislocation’s energy. The energy term consists of two primary components: an elastic component due to the dislocation interacting with itself, and a misfit component arising from the formation of a generalized stacking fault along the dislocation’s spreading.
Version notes
2018-09-25: Notebook added
2019-07-30: Notebook setup and parameters changed.
2020-09-22: Notebook updated to reflect changes in the calculation method due to updates in atomman’s Volterra class solution generators. Setup and parameter definitions streamlined.
2022-03-11: Notebook updated to reflect version 0.11.
Additional dependencies
Disclaimers
The calculation method solves the problem using a 2D generalized stacking fault energy map. Better results may be possible by measuring a full 3D map, but that would require adding a new calculation for the 3D variation.
The implemented method is suited for dislocations with planar spreading. It is not suited for dislocations that spread on multiple atomic planes, like the a/2<111> bcc screw dislocation.
While the solution is taken at discrete points that (typically) correspond to atomic sites, the underlying method is still a continuum solution that does not fully account for the atomic nature of the dislocation.
Method and Theory
This calculation method is a wrapper around the atomman.defect.SDVPN class. More details on the method and theory can be found in the associated tutorial within the atomman documentation.
2. Define calculation functions and generate files
This section defines the calculation functions and associated resource files exactly as they exist inside the iprPy package. This allows for the code used to be directly visible and modifiable by anyone looking to see how it works.
2.1. sdvpn()
This is the primary function for the calculation. The version of this function built in iprPy can be accessed by calling the calc() method of an object of the associated calculation class.
[4]:
def sdvpn(ucell: am.System,
C: am.ElasticConstants,
burgers: Union[list, np.ndarray],
ξ_uvw: Union[list, np.ndarray],
slip_hkl: Union[list, np.ndarray],
gamma: am.defect.GammaSurface,
m: Union[list, np.ndarray] = [0,1,0],
n: Union[list, np.ndarray] = [0,0,1],
cutofflongrange: float = uc.set_in_units(1000, 'angstrom'),
tau: np.ndarray = np.zeros((3,3)),
alpha: list = [0.0],
beta: np.ndarray = np.zeros((3,3)),
cdiffelastic: bool = False,
cdiffsurface: bool = True,
cdiffstress: bool = False,
fullstress: bool = True,
halfwidth: float = uc.set_in_units(1, 'angstrom'),
normalizedisreg: bool = True,
xnum: Optional[int] = None,
xmax: Optional[float] = None,
xstep: Optional[float] = None,
xscale: bool = False,
min_method: str = 'Powell',
min_options: dict = {},
min_cycles: int = 10) -> dict:
"""
Solves a Peierls-Nabarro dislocation model.
Parameters
----------
ucell : atomman.System
The unit cell to use as the seed for the dislocation system. Note that
only box information is used and not atomic positions.
C : atomman.ElasticConstants
The elastic constants associated with the bulk crystal structure
for ucell.
burgers : array-like object
The dislocation's Burgers vector given as a Miller or
Miller-Bravais vector relative to ucell.
ξ_uvw : array-like object
The dislocation's line direction given as a Miller or
Miller-Bravais vector relative to ucell.
slip_hkl : array-like object
The dislocation's slip plane given as a Miller or Miller-Bravais
plane relative to ucell.
m : array-like object, optional
The m unit vector for the dislocation solution. m, n, and ξ
(dislocation line) should be right-hand orthogonal. Default value
is [0,1,0] (y-axis).
n : array-like object, optional
The n unit vector for the dislocation solution. m, n, and ξ
(dislocation line) should be right-hand orthogonal. Default value
is [0,0,1] (z-axis). n is normal to the dislocation slip plane.
cutofflongrange : float, optional
The cutoff distance to use for computing the long-range energy.
Default value is 1000 angstroms.
tau : numpy.ndarray, optional
A (3,3) array giving the stress tensor to apply to the system
using the stress energy term. Only the xy, yy, and yz components
are used. Default value is all zeros.
alpha : list of float, optional
The alpha coefficient(s) used by the nonlocal energy term. Default
value is [0.0].
beta : numpy.ndarray, optional
The (3,3) array of beta coefficient(s) used by the surface energy
term. Default value is all zeros.
cdiffelastic : bool, optional
Flag indicating if the dislocation density for the elastic energy
component is computed with central difference (True) or simply
neighboring values (False). Default value is False.
cdiffsurface : bool, optional
Flag indicating if the dislocation density for the surface energy
component is computed with central difference (True) or simply
neighboring values (False). Default value is True.
cdiffstress : bool, optional
Flag indicating if the dislocation density for the stress energy
component is computed with central difference (True) or simply
neighboring values (False). Only matters if fullstress is True.
Default value is False.
fullstress : bool, optional
Flag indicating which stress energy algorithm to use. Default
value is True.
halfwidth : float, optional
A dislocation halfwidth guess to use for generating the initial
disregistry guess. Does not have to be accurate, but the better the
guess the fewer minimization steps will likely be needed. Default
value is 1 Angstrom.
normalizedisreg : bool, optional
If True, the initial disregistry guess will be scaled such that it
will have a value of 0 at the minimum x and a value of burgers at the
maximum x. Default value is True. Note: the disregistry of end points
are fixed, thus True is usually preferential.
xnum : int, optional
The number of x value points to use for the solution. Two of xnum,
xmax, and xstep must be given.
xmax : float, optional
The maximum value of x to use. Note that the minimum x value will be
-xmax, thus the range of x will be twice xmax. Two of xnum, xmax, and
xstep must be given.
xstep : float, optional
The delta x value to use, i.e. the step size between the x values used.
Two of xnum, xmax, and xstep must be given.
xscale : bool, optional
Flag indicating if xmax and/or xstep values are to be taken as absolute
or relative to ucell's a lattice parameter. Default value is False,
i.e. the x parameters are absolute and not scaled.
min_method : str, optional
The scipy.optimize.minimize method to use. Default value is
'Powell'.
min_options : dict, optional
Any options to pass on to scipy.optimize.minimize. Default value
is {}.
min_cycles : int, optional
The number of minimization runs to perform on the system. Restarting
after obtaining a solution can help further refine to the best pathway.
Default value is 10.
Returns
-------
dict
Dictionary of results consisting of keys:
- **'SDVPN_solution'** (*atomman.defect.SDVPN*) - The SDVPN solution
object at the end of the run.
- **'minimization_energies'** (*list*) - The total energy values
measured after each minimization cycle.
- **'disregistry_profiles'** (*list*) - The disregistry profiles
obtained after each minimization cycle.
"""
# Solve Volterra dislocation
volterra = am.defect.solve_volterra_dislocation(C, burgers, ξ_uvw=ξ_uvw,
slip_hkl=slip_hkl, box=ucell.box,
m=m, n=n)
# Generate SDVPN object
pnsolution = am.defect.SDVPN(volterra=volterra, gamma=gamma,
tau=tau, alpha=alpha, beta=beta,
cutofflongrange=cutofflongrange,
fullstress=fullstress, cdiffelastic=cdiffelastic,
cdiffsurface=cdiffsurface, cdiffstress=cdiffstress,
min_method=min_method, min_options=min_options)
# Scale xmax and xstep by alat
if xscale is True:
if xmax is not None:
xmax *= ucell.box.a
if xstep is not None:
xstep *= ucell.box.a
# Generate initial disregistry guess
x, idisreg = am.defect.pn_arctan_disregistry(xmax=xmax, xstep=xstep, xnum=xnum,
burgers=pnsolution.burgers,
halfwidth=halfwidth,
normalize=normalizedisreg)
# Set up loop parameters
cycle = 0
disregistries = [idisreg]
minimization_energies = [pnsolution.total_energy(x, idisreg)]
# Run minimization for min_cycles
pnsolution.x = x
pnsolution.disregistry = idisreg
while cycle < min_cycles:
cycle += 1
pnsolution.solve()
disregistries.append(pnsolution.disregistry)
minimization_energies.append(pnsolution.total_energy())
# Initialize results dict
results_dict = {}
results_dict['SDVPN_solution'] = pnsolution
results_dict['minimization_energies'] = minimization_energies
results_dict['disregistry_profiles'] = disregistries
return results_dict
3. Specify input parameters
3.1. Initial unit cell system
ucell is an atomman.System representing a fundamental unit cell of the system (required). Here, this is generated using the load parameters and symbols.
[5]:
# Create ucell by loading prototype record
ucell = am.load('crystal', potential_LAMMPS_id='1999--Mishin-Y--Ni--LAMMPS--ipr1',
family='A1--Cu--fcc')
print(ucell)
avect = [ 3.520, 0.000, 0.000]
bvect = [ 0.000, 3.520, 0.000]
cvect = [ 0.000, 0.000, 3.520]
origin = [ 0.000, 0.000, 0.000]
natoms = 4
natypes = 1
symbols = ('Ni',)
pbc = [ True True True]
per-atom properties = ['atype', 'pos']
id atype pos[0] pos[1] pos[2]
0 1 0.000 0.000 0.000
1 1 0.000 1.760 1.760
2 1 1.760 0.000 1.760
3 1 1.760 1.760 0.000
3.2. Elastic constants
C_dict is a dictionary containing the unique elastic constants for the potential and crystal structure defined above.
C is an atomman.ElasticConstants object built from C_dict.
[6]:
C_dict = {}
C_dict['C11'] = uc.set_in_units(247.86, 'GPa')
C_dict['C12'] = uc.set_in_units(147.83, 'GPa')
C_dict['C44'] = uc.set_in_units(124.84, 'GPa')
# -------------- Derived parameters -------------- #
# Build ElasticConstants object from C_dict terms
C = am.ElasticConstants(**C_dict)
3.3. Defect parameters
gammasurface_file gives the path to a data model file containing 2D gamma surface results. This can be a calc_stacking_fault_map_2D record.
burgers is the crystallographic Miller Burgers vector for the dislocation.
ξ_uvw is the Miller [uvw] line vector direction for the dislocation. The angle between burgers and ξ_uvw determines the dislocation’s character
slip_hkl is the Miller (hkl) slip plane for the dislocation.
m is the Cartesian vector of the final system that the dislocation solution’s m vector (in-plane, perpendicular to ξ) should align with. Limited to being parallel to one of the three Cartesian axes.
n is the Cartesian vector of the final system that the dislocation solution’s n vector (slip plane normal) should align with. Limited to being parallel to one of the three Cartesian axes.
gamma is a GammaSurface object. Here, it is loaded from gammasurface_file. Note that gamma and volterra must be for the same plane.
volterra is a VolterraDislocation object for the dislocation type of interest. Here, it is created based on the elastic constants, unit cell and dislocation parameters above. Note that gamma and volterra must be for the same plane.
[7]:
gammasurface_file = '../stacking_fault_map_2D/gamma.json'
gamma = am.defect.GammaSurface(model=gammasurface_file)
# fcc a/2 <110>{111} dislocations
burgers = 0.5 * np.array([ 1., -1., 0.])
slip_hkl = [1, 1, 1]
# Line direction determines dislocation character
ξ_uvw = [ 1, 1,-2] # 90 degree edge
#ξ_uvw = [ 1, 0,-1] # 60 degree mixed
#ξ_uvw = [ 1,-2, 1] # 30 degree mixed
#ξ_uvw = [ 1,-1, 0] # 0 degree screw
# Best choice for m + n as it works for non-cubic systems
m = [0,1,0]
n = [0,0,1]
3.4. Calculation-specific parameters
xmax: The maximum value of the x-coordinates to use for the points where the disregistry is evaluated. The solution is centered around x=0, therefore this also corresponds to the minimum value of x used. The set of x-coordinates used is fully defined by giving at least two of xmax, xstep and xnum.
xstep: The step size (delta x) value between the x-coordinates used to evaluate the disregistry. The set of x-coordinates used is fully defined by giving at least two of xmax, xstep and xnum.
xnum: The total number of x-coordinates at which to evaluate the disregistry. The set of x-coordinates used is fully defined by giving at least two of xmax, xstep and xnum.
min_method: The scipy.optimize.minimize method style to use when solving for the disregistry. Default value is ‘Powell’, which seems to do decently well for this problem.
min_options: Allows for the specification of the options dictionary used by scipy.optimize.minimize. This is given as “key value key value…”.
min_cycles: Specifies the number of times to run the minimization in succession. The minimization algorithms used by the underlying scipy code often benefit from restarting and rerunning the minimized configuration to achive a better fit. Default value is 10.
cutofflongrange: The radial cutoff (in distance units) to use for the long-range elastic energy. The long-range elastic energy is configuration-independent, so this value changes the dislocation’s energy but not the computed disregistry profile. Default value is 1000 Angstroms.
tau_xy: Shear stress (in units of pressure) to apply to the system. Default value is 0 GPa.
tau_yy: Normal stress (in units of pressure) to apply to the system. Default value is 0 GPa.
tau_yz: Shear stress (in units of pressure) to apply to the system. Default value is 0 GPa.
alpha: Coefficient(s) (in pressure/length units) of the non-local energy correction term to use. Default value is 0.0, meaning this correction is not applied.
beta_xx, beta_yy, beta_zz, beta_xy, beta_xz, beta_yz: Components of the surface energy coefficient tensor (in units pressure-length) to use. Default value is 0.0 GPa-Angstrom for all, meaning this correction is not applied.
cdiffelastic, cdiffsurface, cdiffstress: Booleans indicating how the dislocation density (derivative of disregistry) is computed within the elastic, surface and stress terms, respectively. If True, central difference is used, otherwise only the change between the current and previous points is used. Default values are True for cdiffsurface, and False for the other two.
halfwidth: The arctan disregistry halfwidth (in length units) to use for creating the initial disregistry guess.
normalizedisreg: Boolean indicating how the disregistry profile is handled. If True (default), the disregistry is scaled such that the minimum x value has a disregistry of 0 and the maximum x value has a disregistry equal to the dislocation’s Burgers vector. Note that the disregistry for these endpoints is fixed, so if you use False the initial disregistry should be close to the final solution.
fullstress: Boolean indicating which of two stress formulas to use. True uses the original full formulation, while False uses a newer, simpler representation. Default value is True.
[8]:
xmax = None
xstep = 1/5
xnum = 200
xscale = True
min_method = 'Powell'
min_options = {}
min_options['disp'] = True # will display convergence info
min_options['xtol'] = 1e-6 # smaller convergence tolerance than default
min_options['ftol'] = 1e-6 # smaller convergence tolerance than default
#min_options['maxiter'] = 2 # for testing purposes
min_cycles = 10
cutofflongrange = uc.set_in_units(1000, 'angstrom')
halfwidth = uc.set_in_units(5, 'angstrom')
4. Run calculation and view results
4.1. Run calculation
All primary calculation method functions take a series of inputs and return a dictionary of outputs.
[9]:
results_dict = sdvpn(ucell, C, burgers, ξ_uvw, slip_hkl, gamma,
m = m,
n = n,
cutofflongrange = cutofflongrange,
#tau = tau,
#alpha = alpha,
#beta = beta,
#cdiffelastic = cdiffelastic,
#cdiffsurface = cdiffsurface,
#cdiffstress = cdiffstress,
#fullstress = fullstress,
halfwidth = halfwidth,
#normalizedisreg = normalizedisreg,
xnum = xnum,
xstep = xstep,
xmax = xmax,
xscale = xscale,
min_method = min_method,
min_options = min_options,
min_cycles = min_cycles)
Optimization terminated successfully.
Current function value: 4.905357
Iterations: 23
Function evaluations: 144536
Optimization terminated successfully.
Current function value: 4.905351
Iterations: 2
Function evaluations: 15730
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8122
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8557
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8431
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8693
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8577
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8549
Optimization terminated successfully.
Current function value: 4.905350
Iterations: 1
Function evaluations: 8574
Optimization terminated successfully.
Current function value: 4.905349
Iterations: 1
Function evaluations: 8460
4.2. Report results
Values returned in the results_dict:
‘SDVPN_solution’ (atomman.defect.SDVPN) - The SDVPN solution object at the end of the run.
‘minimization_energies’ (list) - The total energy values measured after each minimization cycle.
‘disregistry_profiles’ (list) - The disregistry profiles obtained after each minimization cycle.
[10]:
length_unit = 'Å'
energyperlength_unit = 'eV/Å'
energyperarea_unit = 'eV/Å^2'
[11]:
results_dict['SDVPN_solution'].disregistry_plot(length_unit=length_unit)
plt.show()

[12]:
results_dict['SDVPN_solution'].E_gsf_surface_plot(length_unit=length_unit, energyperarea_unit=energyperarea_unit)
plt.show()

[13]:
results_dict['SDVPN_solution'].E_gsf_vs_x_plot(length_unit='nm', energyperarea_unit='mJ/m^2')
plt.show()

[14]:
print('Total dislocation energy per minimization run (in %s):' % energyperlength_unit)
for i, E_total in enumerate(results_dict['minimization_energies']):
print(i, uc.get_in_units(E_total, energyperlength_unit))
Total dislocation energy per minimization run (in eV/Å):
0 5.34498311386077
1 4.905357047466611
2 4.905350557433071
3 4.905350357314638
4 4.905350212931164
5 4.905350079897351
6 4.905349948696088
7 4.90534981756126
8 4.905349686309184
9 4.905349555257531
10 4.905349425045194
4.3. Save/load results
The full SDVPN results can be extracted and saved to either a JSON or XML file using the model() method of the SDVPN solution class. This content can then be loaded into an SDVPN for future analysis or further runs.
NOTE: The model saves all input parameters so the calculation can be restarted from any point. However, it only saves the current disregistry and not any previous disregistries before minimizations.
[15]:
# Build the model
model = results_dict['SDVPN_solution'].model(include_gamma=True)
# Save as json
with open('sdvpn_run.json', 'w', encoding='UTF-8') as f:
model.json(fp=f, indent=4, ensure_ascii=False)
[16]:
# Load the saved model
pnsolution = am.defect.SDVPN(model='sdvpn_run.json')
# Show that the plot is the same
pnsolution.E_gsf_surface_plot(length_unit=length_unit, energyperarea_unit=energyperarea_unit)
plt.show()
