Strain measurement and analysis ================================ Understanding strain and its effects ---------------------------------------- When materials experience forces, their internal atomic arrangements deform. This deformation, called strain (:math:`\varepsilon`), reveals crucial information about a material's structure and behavior. Consider bending a metal beam: some parts stretch (:math:`\varepsilon > 0`), others compress (:math:`\varepsilon < 0`), creating a complex pattern of internal deformation. At the atomic scale, these deformations appear as subtle changes in the spacing (:math:`d`) and arrangement of atoms within the crystal lattice. What is the main principle behind 4D-STEM strain measurement? .. math:: \varepsilon = \frac{d_{\text{strained}} - d_{\text{ref}}}{d_{\text{ref}}} = \frac{g_{\text{ref}} - g_{\text{meas}}}{g_{\text{meas}}} = \frac{\partial u}{\partial x} Mathematical framework ----------------------- To describe these deformations precisely, we use the strain tensor :math:`\boldsymbol{\varepsilon}`: .. math:: \boldsymbol{\varepsilon} = \begin{bmatrix} \varepsilon_{xx} & \varepsilon_{xy} \\ \varepsilon_{yx} & \varepsilon_{yy} \end{bmatrix} Each element of this tensor represents a distinct type of deformation. Below is a visualization illustrating how each strain component affects a grid of points, simulating the atomic lattice: .. plot:: :context: reset :alt: Visualization of strain tensor components effects :align: center """ Create an intuitive visualization of how different strain tensor components affect a grid of points. Each subplot shows the effect of one strain component (:math:`\varepsilon_{xx}`, :math:`\varepsilon_{xy}`, :math:`\varepsilon_{yx}`, :math:`\varepsilon_{yy}`) on a grid. """ import numpy as np import matplotlib.pyplot as plt from matplotlib.patches import Arrow def create_grid(n=5): """Create a grid of points""" x = np.linspace(-1, 1, n) y = np.linspace(-1, 1, n) return np.meshgrid(x, y) def apply_strain(X, Y, strain_type, magnitude=0.3): """Apply specified strain to grid points""" if strain_type == 'exx': return X + magnitude * X, Y elif strain_type == 'eyy': return X, Y + magnitude * Y elif strain_type == 'exy': return X + magnitude * Y, Y elif strain_type == 'eyx': return X, Y + magnitude * X return X, Y def plot_strain_component(): """Create visualization of different strain components""" fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 12)) axs = [(ax1, 'exx', ':math:`\\varepsilon_{xx}`: Normal strain (x)'), (ax2, 'exy', ':math:`\\varepsilon_{xy}`: Shear strain'), (ax3, 'eyx', ':math:`\\varepsilon_{yx}`: Shear strain'), (ax4, 'eyy', ':math:`\\varepsilon_{yy}`: Normal strain (y)')] # Original grid for reference X, Y = create_grid() for ax, strain_type, title in axs: # Plot original grid (gray) ax.plot(X, Y, 'k-', alpha=0.2, linewidth=1) ax.plot(X.T, Y.T, 'k-', alpha=0.2, linewidth=1) ax.scatter(X, Y, c='gray', alpha=0.2, s=50) # Apply and plot strained grid (blue) X_strained, Y_strained = apply_strain(X, Y, strain_type) ax.plot(X_strained, Y_strained, 'b-', linewidth=1) ax.plot(X_strained.T, Y_strained.T, 'b-', linewidth=1) ax.scatter(X_strained, Y_strained, c='blue', s=50) # Add arrows to show movement of key points if strain_type == 'exx': # Show horizontal stretching ax.arrow(1, 0, 0.3, 0, head_width=0.1, head_length=0.1, fc='r', ec='r') elif strain_type == 'eyy': # Show vertical stretching ax.arrow(0, 1, 0, 0.3, head_width=0.1, head_length=0.1, fc='r', ec='r') elif strain_type in ['exy', 'eyx']: # Show shearing motion if strain_type == 'exy': ax.arrow(0, 1, 0.3, 0, head_width=0.1, head_length=0.1, fc='r', ec='r') else: ax.arrow(1, 0, 0, 0.3, head_width=0.1, head_length=0.1, fc='r', ec='r') ax.set_title(title) ax.set_xlim(-1.5, 1.5) ax.set_ylim(-1.5, 1.5) ax.set_aspect('equal') ax.grid(True, alpha=0.3) ax.set_xlabel(r'$x$') ax.set_ylabel(r'$y$') plt.tight_layout() return fig plot_strain_component() How do you then find the strain tensor from these spot shifts? We can derive this through several key steps: First, let's define the displacement gradient tensor :math:`\mathbf{D}`. When a crystal deforms, each point moves by a displacement vector :math:`\mathbf{u}(\mathbf{r})`. The displacement gradient tensor describes how this displacement changes with position: .. math:: \mathbf{D} = \begin{pmatrix} D_{xx} & D_{xy} \\ D_{yx} & D_{yy} \end{pmatrix} = \begin{pmatrix} \frac{\partial u_x}{\partial x} & \frac{\partial u_x}{\partial y} \\ \frac{\partial u_y}{\partial x} & \frac{\partial u_y}{\partial y} \end{pmatrix} where :math:`u_x` and :math:`u_y` are the displacement components in the x and y directions. The displacement gradient tensor :math:`\mathbf{D}` contains both deformation (strain) and rotation: .. math:: \mathbf{D} = \underbrace{\frac{1}{2}(\mathbf{D} + \mathbf{D}^T)}_{\text{strain }\boldsymbol{\varepsilon}} + \underbrace{\frac{1}{2}(\mathbf{D} - \mathbf{D}^T)}_{\text{rotation }\boldsymbol{\omega}} The symmetric part :math:`\boldsymbol{\varepsilon}` represents true strain (changes in lengths and angles), while the antisymmetric part :math:`\boldsymbol{\omega}` represents rigid-body rotation. In strain analysis, we're usually interested in the strain part :math:`\boldsymbol{\varepsilon}`, since rotation doesn't cause internal deformation. How can general deformation can be decomposed into pure strain and rotation? .. plot:: ../source/plots/strain_decomp.py :include-source: false :alt: Decomposition of deformation into strain and rotation :align: center :caption: Decomposition of displacement gradient tensor into pure strain (changes in lengths/angles) and pure rotation. .. dropdown:: Why do we use :math:`\frac{1}{2}(\mathbf{D} + \mathbf{D}^T)` for strain and :math:`\frac{1}{2}(\mathbf{D} - \mathbf{D}^T)` for rotation? :icon: info :color: info Think about what strain and rotation mean physically: 1. **Strain** (stretching/compression): - Should be the same whether you measure from left-to-right or right-to-left - Should be the same whether you measure from top-to-bottom or bottom-to-top - This means: swapping any two points should give the same deformation - Mathematically: :math:`D_{xy} = D_{yx}` (symmetry) 2. **Rotation**: - Moving from left-to-right should be opposite to moving from right-to-left - Moving from top-to-bottom should be opposite to moving from bottom-to-top - This means: swapping two points should reverse the movement - Mathematically: :math:`D_{xy} = -D_{yx}` (antisymmetry) Let's see why :math:`\frac{1}{2}(\mathbf{D} + \mathbf{D}^T)` gives us strain. For any matrix: .. math:: \mathbf{D} = \begin{pmatrix} D_{xx} & D_{xy} \\ D_{yx} & D_{yy} \end{pmatrix} The symmetric part :math:`\boldsymbol{\varepsilon}` is: .. math:: \boldsymbol{\varepsilon} = \frac{1}{2}(\mathbf{D} + \mathbf{D}^T) = \frac{1}{2}\begin{pmatrix} 2D_{xx} & D_{xy} + D_{yx} \\ D_{xy} + D_{yx} & 2D_{yy} \end{pmatrix} The antisymmetric part :math:`\boldsymbol{\omega}` is: .. math:: \boldsymbol{\omega} = \frac{1}{2}(\mathbf{D} - \mathbf{D}^T) = \frac{1}{2}\begin{pmatrix} 0 & D_{xy} - D_{yx} \\ D_{yx} - D_{xy} & 0 \end{pmatrix} Now, look what happens when we add D and D^T: **In strain (symmetric) parts**: - Points moving left-to-right: :math:`D_{xy}` - Points moving right-to-left: :math:`D_{yx}` - When we add and divide by 2: :math:`\frac{D_{xy} + D_{yx}}{2}` - This averages the deformation in both directions → pure strain! **In rotation (antisymmetric) parts**: - Points rotating clockwise: :math:`D_{xy}` - Points rotating counterclockwise: :math:`-D_{yx}` - When we subtract and divide by 2: :math:`\frac{D_{xy} - D_{yx}}{2}` - This captures the difference in directions → pure rotation! The factor of :math:`\frac{1}{2}` appears because we're averaging the effects in both directions. **Simple example**: If a point moves: - Right by 2 units (:math:`D_{xy} = 2`) - Up by 1 unit (:math:`D_{yx} = 1`) Then: - Strain = :math:`\frac{2 + 1}{2} = 1.5` (average deformation) - Rotation = :math:`\frac{2 - 1}{2} = 0.5` (difference indicating rotation) The total movement (D) is perfectly split into these two components! How do diffraction spot shifts relate to strain? 1. **Real space deformation** This displacement gradient tensor :math:`\mathbf{D}` relates deformed lattice vectors to reference vectors: .. math:: \mathbf{a}_i = (\mathbf{I} + \mathbf{D})\mathbf{a}_i^{\text{ref}} where :math:`\mathbf{I}` is the identity matrix. 2. **Reciprocal lattice relationship** The real and reciprocal lattice vectors are connected by: .. math:: \mathbf{g}_i \cdot \mathbf{a}_j = 2\pi\delta_{ij} 3. **Apply deformation** Substituting the deformed lattice vectors: .. math:: \mathbf{g}_i \cdot (\mathbf{I} + \mathbf{D})\mathbf{a}_j = 2\pi\delta_{ij} In the reference state: .. math:: \mathbf{g}_i^{\text{ref}} \cdot \mathbf{a}_j^{\text{ref}} = 2\pi\delta_{ij} Therefore: .. math:: \mathbf{g}_i \cdot (\mathbf{I} + \mathbf{D}) = \mathbf{g}_i^{\text{ref}} 4. **Solve for reciprocal vectors** Multiply both sides by :math:`(\mathbf{I} + \mathbf{D})^{-1}`: .. math:: \mathbf{g}_i = \mathbf{g}_i^{\text{ref}}(\mathbf{I} + \mathbf{D})^{-1} 5. **Linearize for small strains** For the small deformations typical in 4D-STEM: .. math:: (\mathbf{I} + \mathbf{D})^{-1} \approx (\mathbf{I} - \mathbf{D}) Thus: .. math:: \boxed{\mathbf{g}_i = (\mathbf{I} - \mathbf{D}^T)\mathbf{g}_i^{\text{ref}}} .. dropdown:: Why is inverse approximation :math:`(\mathbf{I} + \mathbf{D})^{-1} \approx (\mathbf{I} - \mathbf{D})` true for small strains? :icon: info :color: info Think about a simple scalar example first. For any :math:`|x| < 1`: .. math:: \frac{1}{1+x} = 1 - x + x^2 - x^3 + ... This is because (1+x)(1-x+x²-x³+...) = 1. The same idea works for matrices: .. math:: (\mathbf{I} + \mathbf{D})^{-1} = \mathbf{I} - \mathbf{D} + \mathbf{D}^2 - \mathbf{D}^3 + ... In 4D-STEM, strain is typically tiny (0.1-1%), so if we plug in a number: For 1% strain (:math:`\|\mathbf{D}\| \approx 0.01`): - First order: :math:`\|\mathbf{D}\| \approx 10^{-2}` - Second order: :math:`\|\mathbf{D}^2\| \approx 10^{-4}` (100× smaller) - Third order: :math:`\|\mathbf{D}^3\| \approx 10^{-6}` (10000× smaller) Given these rapidly decreasing magnitudes, we can confidently approximate: .. math:: (\mathbf{I} + \mathbf{D})^{-1} \approx \mathbf{I} - \mathbf{D} The error from this approximation is on the order of :math:`\|\mathbf{D}^2\| \approx 10^{-4}`, which is negligible for strain analysis. What's the step-by-step process in 4D-STEM strain measurement? 1. **Collect reference pattern** Choose an unstrained region and identify two strong diffraction spots that correspond to reciprocal lattice vectors: .. math:: \mathbf{g}_1^{\text{ref}} &= \begin{pmatrix} 2.0 \\ 0.0 \end{pmatrix} \text{ nm}^{-1} \\ \mathbf{g}_2^{\text{ref}} &= \begin{pmatrix} 0.0 \\ 2.0 \end{pmatrix} \text{ nm}^{-1} 2. **Measure strained spots** For each scan position, record the diffraction spot positions in the strained state: .. math:: \mathbf{g}_1^{\text{meas}} &= \begin{pmatrix} 1.98 \\ 0.02 \end{pmatrix} \text{ nm}^{-1} \\ \mathbf{g}_2^{\text{meas}} &= \begin{pmatrix} -0.02 \\ 2.02 \end{pmatrix} \text{ nm}^{-1} 3. **Calculate spot displacements** Find the difference between measured and reference positions (:math:`\Delta\mathbf{g} = \mathbf{g}^{\text{meas}} - \mathbf{g}^{\text{ref}}`): .. math:: \Delta\mathbf{g}_1 &= \begin{pmatrix} -0.02 \\ 0.02 \end{pmatrix} \text{ nm}^{-1} \\ \Delta\mathbf{g}_2 &= \begin{pmatrix} -0.02 \\ 0.02 \end{pmatrix} \text{ nm}^{-1} 4. **Derive the linear system** To solve for :math:`\mathbf{D}`, let's derive how the spot shifts relate to the displacement gradient. For any measured spot :math:`\mathbf{g}^{\text{meas}}`: .. math:: \mathbf{g}^{\text{meas}} = (\mathbf{I} - \mathbf{D}^T)\mathbf{g}^{\text{ref}} Writing this out in components for a single spot: .. math:: \begin{pmatrix} g_x^{\text{meas}} \\ g_y^{\text{meas}} \end{pmatrix} = \begin{pmatrix} 1-D_{xx} & -D_{yx} \\ -D_{xy} & 1-D_{yy} \end{pmatrix} \begin{pmatrix} g_x^{\text{ref}} \\ g_y^{\text{ref}} \end{pmatrix} Multiply this out: .. math:: g_x^{\text{meas}} &= (1-D_{xx})g_x^{\text{ref}} - D_{yx}g_y^{\text{ref}} \\ g_y^{\text{meas}} &= -D_{xy}g_x^{\text{ref}} + (1-D_{yy})g_y^{\text{ref}} Rearranging to get spot shifts :math:`\Delta g = g^{\text{meas}} - g^{\text{ref}}`: .. math:: \Delta g_x &= -D_{xx}g_x^{\text{ref}} - D_{yx}g_y^{\text{ref}} \\ \Delta g_y &= -D_{xy}g_x^{\text{ref}} - D_{yy}g_y^{\text{ref}} For two spots (:math:`\mathbf{g}_1` and :math:`\mathbf{g}_2`), this gives us four equations: .. math:: \begin{pmatrix} \Delta g_{1x} \\ \Delta g_{1y} \\ \Delta g_{2x} \\ \Delta g_{2y} \end{pmatrix} = \begin{pmatrix} -g_{1x}^{\text{ref}} & 0 & -g_{1y}^{\text{ref}} & 0 \\ 0 & -g_{1x}^{\text{ref}} & 0 & -g_{1y}^{\text{ref}} \\ -g_{2x}^{\text{ref}} & 0 & -g_{2y}^{\text{ref}} & 0 \\ 0 & -g_{2x}^{\text{ref}} & 0 & -g_{2y}^{\text{ref}} \end{pmatrix} \begin{pmatrix} D_{xx} \\ D_{xy} \\ D_{yx} \\ D_{yy} \end{pmatrix} 5. **Solve for displacement gradient** Invert the system to find the displacement gradient tensor :math:`\mathbf{D}`: .. math:: \mathbf{D} = \begin{pmatrix} D_{xx} & D_{xy} \\ D_{yx} & D_{yy} \end{pmatrix} = \begin{pmatrix} 0.01 & -0.01 \\ -0.01 & -0.01 \end{pmatrix} 6. **Calculate strain tensor** The strain tensor :math:`\boldsymbol{\varepsilon}` is the symmetric part of :math:`\mathbf{D}`: .. math:: \boldsymbol{\varepsilon} = \frac{1}{2}(\mathbf{D} + \mathbf{D}^T) = \begin{pmatrix} 0.01 & -0.01 \\ -0.01 & -0.01 \end{pmatrix} This gives us: - Normal strain :math:`\varepsilon_{xx} = 0.01` (1% tensile strain in x) - Normal strain :math:`\varepsilon_{yy} = -0.01` (1% compressive strain in y) - Shear strain :math:`\varepsilon_{xy} = \varepsilon_{yx} = -0.01` (1% shear)