Module wsp_tools.lorentzSim
Expand source code
# wsp-tools is TEM data analysis and simulation tools developed by WSP as a grad student in the McMorran Lab.
# Copyright (C) 2021 William S. Parker
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from . import constants as _
import numpy as np
from deprecated import deprecated
__all__ = ['abphase2d']
@deprecated(version='1.2.0', reason='You should now use wsp_tools.ltem.ab_phase.')
def abphase2d(mx, my, mz, Lx=1e-6, Ly=1e-6, p=np.array([0,0,1]), t=60e-9):
"""DEPRECATED. You should instead use `wsp_tools.ltem.ab_phase.`
Calculates the Aharonov-Bohm phase acquired by an electron passing through a 2d
magnetization.
**Parameters**
* **mx** : _ndarray_ <br />
The x-component of magnetization. Should be a 2-dimensional array.
* **my** : _ndarray_ <br />
The y-component of magnetization. Should be a 2-dimensional array.
* **mz** : _ndarray_ <br />
The z-component of magnetization. Should be a 2-dimensional array.
* **Lx** : _number, optional_ <br />
The x-length of the area calculated. (i.e., Lx = xmax - xmin). <br />
Default is `Lx = 1e-6`.
* **Ly** : _number, optional_ <br />
The y-length of the area calculated. (i.e., Ly = ymax - ymin). <br />
Default is `Ly = 1e-6`.
* **p** : _ndarray, optional_ <br />
A unit vector in the direction of electron propagation. Should be a 1darray with length 3. <br />
Default is `p = np.array([0,0,1])` (the electron is propagating in the z-direction).
* **t** : _number, optional_ <br />
The thickness of the 2d magnetization. <br />
Default is `t = 60e-9`.
**Returns**
* **phi** : _ndarray_
The phase acquired by an electron passing through the specified magnetization.
Output is a 2d array of the same shape as mx, my, and mz.
"""
Mx = np.fft.fft2(mx)
My = np.fft.fft2(my)
Mz = np.fft.fft2(mz)
M = np.array([Mx, My, Mz])
SI = np.fft.fftfreq(M[0].shape[1],Lx/M[0].shape[1])
SJ = np.fft.fftfreq(M[0].shape[0],Ly/M[0].shape[0])
si, sj = np.meshgrid(SI, SJ)
s = np.array([si,sj,0*si])
s_mag = np.sqrt(np.einsum('ijk,ijk->jk',s,s))
s_mag[s_mag == 0] = np.max(s_mag)/s_mag.shape[0]/1000000
sig = np.nan_to_num(s/s_mag, nan=0,posinf=0,neginf=0)
Gts = 1/(np.einsum('i,ijk->jk',p,sig)**2 + p[2]**2) \
* np.sinc(t*np.einsum('i,ijk->jk',p,sig)/p[2])
weights = np.zeros_like(M[0], dtype=complex)
pxpxM = np.cross(p, np.cross(p, M, axisb=0))
sigxz = np.cross(sig, np.array([0,0,1]), axisa=0)
d = np.einsum('ijk,ijk->ij', sigxz, pxpxM)
weights = 1j * np.nan_to_num(t/s_mag, nan=0,neginf=0,posinf=0) * Gts * d
phi = 2*_.e/_.hbar/_.c * np.fft.ifft2(weights)
return(phi.real)
@deprecated(version='1.2.0', reason='You should now use wsp_tools.ltem.img_from_phase')
def propagate(cphase, dx = 1, dy = 1, defocus=0, wavelength=1.97e-12):
"""Calculates the Lorentz image given a specific phase and defocus.
This takes into account the microscope and phase transfer functions.
**Parameters**
* **x** : _ndarray_ <br />
The x coordinates of the input complex phase. Dimension should be 2.
* **y** : _ndarray_ <br />
The y coordinates of the input complex phase. Dimension should be 2.
* **cphase**: _complex ndarray_ <br />
The complex phase to propagate. Dimension should be two. May be complex
to allow for attenuation through the sample.
* **defocus** : _number, optional_ <br />
The defocus at which the phase was acquired. <br />
Default is `defocus = 0`.
* **wavelength** : _number, optional_ <br />
The wavelength of the electron. Scales the phase transfer function and the reciprocal coordinates. <br />
Default is `wavelength = 1.97e-12` (the relativistic wavelength of a 300kV electron).
* **focal_length** : _number, optional_ <br />
The focal length of the lens, which scales the reciprocal coordinates. <br />
Default is `focal_length = 1`.
**Returns**
* **psi_out** : _complex ndarray_ <br />
The transverse complex amplitude in the image plane. Output has the same
shape as x, y, and cphase.
"""
U = np.fft.fftfreq(cphase.shape[1], dx)
V = np.fft.fftfreq(cphase.shape[0], dy)
qx, qy = np.meshgrid(U, V)
psi_0 = np.exp(1j * cphase)
psi_q = np.fft.fft2(psi_0)
psi_out = np.fft.ifft2(psi_q * T(qx, qy, defocus, wavelength))
return(psi_out)
def T(qx, qy, defocus, wavelength):
"""Utility function for propagate(). Microscope transfer function.
"""
out = aperture(qx, qy) * np.exp(-1j * chi(qx, qy, defocus, wavelength))
return(out)
def chi(qx, qy, defocus, wavelength):
"""Utility function for propagate(). Phase transfer function.
"""
return(_.pi * wavelength * defocus * (qx**2 + qy**2))
def aperture(qx, qy):
"""Utility function for propagate(). Circular aperture.
"""
out = np.zeros_like(qx)
out[np.sqrt(qx**2 + qy**2) < np.max(np.sqrt(qx**2 + qy**2))] = 1
return(out)
@deprecated(version='1.2.0', reason='You should now use wsp_tools.ltem.jchessmodel')
def jchessmodel(x, y, z=0, **kwargs):
"""Calculates the magnetization of a hopfion based on Jordan Chess' model.
**Parameters**
* **x** : _number, ndarray_ <br />
The x-coordinates over which to calculate magnetization.
* **y** : _number, ndarray_ <br />
The y-coordinates over which to calculate magnetization.
* **z** : _number, ndarray, optional_ <br />
The z-coordinates over which to calculate magnetization. Note, if z is an
ndarray, then x, y, and z should have the same shape rather than relying
on array broadcasting. <br />
Default is `z = 0`.
* **aa**, **ba**, **ca** : _number, optional_ <br />
In this model, the thickness of the domain wall is set by a
Gaussian function, defined as `aa * exp(-ba * z**2) + ca`. <br />
Defaults are `aa = 5`, `ba = 5`, `ca = 0`.
* **ak**, **bk**, **ck** : _number, optional_ <br />
In this model, the thickness of the core is set by a Gaussian function,
defined as `ak * exp(-bk * z**2) + ck`. <br />
Defaults are `ak = 5e7`, `bk = -50`, `ck = 0`.
* **bg**, **cg** : _number, optional_ <br />
In this model, the helicity varies as a function of z, given
as `pi / 2 * tanh( bg * z ) + cg`. <br />
Defaults are `bg = 5e7`, `cg = pi/2`.
* **n** : _number, optional_ <br />
The skyrmion number. <br />
Default is `n = 1`.
**Returns**
* **mx** : _ndarray_ <br />
The x-component of magnetization. Shape will be the same as x and y.
* **my** : _ndarray_ <br />
The y-component of magnetization. Shape will be the same as x and y.
* **mz** : _ndarray_ <br />
The z-component of magnetization. Shape will be the same as x and y.
"""
p = { 'aa':5, 'ba':5, 'ca':0,
'ak':5e7, 'bk':-5e1, 'ck':0,
'bg':5e7, 'cg':_.pi/2, 'n': 1}
for key in kwargs.keys():
if not key in p.keys(): return("Error: {:} is not a kwarg.".format(key))
p.update(kwargs)
r, phi = np.sqrt(x**2+y**2), np.arctan2(y,x)
alpha_z = p['aa'] * np.exp(-p['ba'] * z**2) + p['ca']
k_z = p['ak'] * np.exp(-p['bk'] * z**2) + p['ck']
gamma_z = _.pi / 2 * np.tanh(p['bg'] * z) + p['cg']
Theta_rz = 2 * np.arctan2((k_z * r)**alpha_z, 1)
mx = np.cos(p['n']*phi%(2*_.pi)-gamma_z) * np.sin(Theta_rz)
my = np.sin(p['n']*phi%(2*_.pi)-gamma_z) * np.sin(Theta_rz)
mz = np.cos(Theta_rz)
return(mx, my, mz)
Functions
def abphase2d(mx, my, mz, Lx=1e-06, Ly=1e-06, p=array([0, 0, 1]), t=6e-08)
-
DEPRECATED. You should instead use
ab_phase().
Calculates the Aharonov-Bohm phase acquired by an electron passing through a 2d magnetization.
Parameters
-
mx : ndarray
The x-component of magnetization. Should be a 2-dimensional array. -
my : ndarray
The y-component of magnetization. Should be a 2-dimensional array. -
mz : ndarray
The z-component of magnetization. Should be a 2-dimensional array. -
Lx : number, optional
The x-length of the area calculated. (i.e., Lx = xmax - xmin).
Default isLx = 1e-6
. -
Ly : number, optional
The y-length of the area calculated. (i.e., Ly = ymax - ymin).
Default isLy = 1e-6
. -
p : ndarray, optional
A unit vector in the direction of electron propagation. Should be a 1darray with length 3.
Default isp = np.array([0,0,1])
(the electron is propagating in the z-direction). -
t : number, optional
The thickness of the 2d magnetization.
Default ist = 60e-9
.
Returns
- phi : ndarray The phase acquired by an electron passing through the specified magnetization. Output is a 2d array of the same shape as mx, my, and mz.
Expand source code
@deprecated(version='1.2.0', reason='You should now use wsp_tools.ltem.ab_phase.') def abphase2d(mx, my, mz, Lx=1e-6, Ly=1e-6, p=np.array([0,0,1]), t=60e-9): """DEPRECATED. You should instead use `wsp_tools.ltem.ab_phase.` Calculates the Aharonov-Bohm phase acquired by an electron passing through a 2d magnetization. **Parameters** * **mx** : _ndarray_ <br /> The x-component of magnetization. Should be a 2-dimensional array. * **my** : _ndarray_ <br /> The y-component of magnetization. Should be a 2-dimensional array. * **mz** : _ndarray_ <br /> The z-component of magnetization. Should be a 2-dimensional array. * **Lx** : _number, optional_ <br /> The x-length of the area calculated. (i.e., Lx = xmax - xmin). <br /> Default is `Lx = 1e-6`. * **Ly** : _number, optional_ <br /> The y-length of the area calculated. (i.e., Ly = ymax - ymin). <br /> Default is `Ly = 1e-6`. * **p** : _ndarray, optional_ <br /> A unit vector in the direction of electron propagation. Should be a 1darray with length 3. <br /> Default is `p = np.array([0,0,1])` (the electron is propagating in the z-direction). * **t** : _number, optional_ <br /> The thickness of the 2d magnetization. <br /> Default is `t = 60e-9`. **Returns** * **phi** : _ndarray_ The phase acquired by an electron passing through the specified magnetization. Output is a 2d array of the same shape as mx, my, and mz. """ Mx = np.fft.fft2(mx) My = np.fft.fft2(my) Mz = np.fft.fft2(mz) M = np.array([Mx, My, Mz]) SI = np.fft.fftfreq(M[0].shape[1],Lx/M[0].shape[1]) SJ = np.fft.fftfreq(M[0].shape[0],Ly/M[0].shape[0]) si, sj = np.meshgrid(SI, SJ) s = np.array([si,sj,0*si]) s_mag = np.sqrt(np.einsum('ijk,ijk->jk',s,s)) s_mag[s_mag == 0] = np.max(s_mag)/s_mag.shape[0]/1000000 sig = np.nan_to_num(s/s_mag, nan=0,posinf=0,neginf=0) Gts = 1/(np.einsum('i,ijk->jk',p,sig)**2 + p[2]**2) \ * np.sinc(t*np.einsum('i,ijk->jk',p,sig)/p[2]) weights = np.zeros_like(M[0], dtype=complex) pxpxM = np.cross(p, np.cross(p, M, axisb=0)) sigxz = np.cross(sig, np.array([0,0,1]), axisa=0) d = np.einsum('ijk,ijk->ij', sigxz, pxpxM) weights = 1j * np.nan_to_num(t/s_mag, nan=0,neginf=0,posinf=0) * Gts * d phi = 2*_.e/_.hbar/_.c * np.fft.ifft2(weights) return(phi.real)
-