TEMSIM
Usage
The package is made of several programs which can be run (for example in the case of Silicon with incident beam in the direction) with the following commands :
cat Si110A.in | temsim/atompotcat Si100_mulslice.in | temsim/mulslicecat Si100_autoslic.in | temsim/autoslic
where :
| Program | Input | Output | Description |
|---|---|---|---|
| atompot | slice.dat slice.in | slice.tif | computes the projected potential |
| mulslice | slice.tif name.in | name_mulslice.tif | periodic multislice simulation |
| autoslic | coords.xyz name.in | name_autoslic.tif | non periodic multislice simulation |
Python interface
A simple Python interface can generate the input decks (*.in), run temsim and postprocess the results.
Type python3 -c "from multislice import Multislice; help(Multislice)" for command line help. The doc is built with : make multislice_doc.
datafile generation
The .xyz coordinate file can be automatically generated from a structure file (pdb,cif,built-in,... imported with crystals library) using make_xyz. The orientation is controlled by providing the incident beam direction n in lattice coordinates :
make_xyz(name='dat/Lithium',compound='Li',n=[1,1,1],fmt='%.4f')
| BCC[001] | BCC[110] | BCC[111] |
|---|---|---|
![]() |
![]() |
![]() |
deck generation
The following example generates the decks for an autoslic simulation located in relative path Silicon/Si110 which would contain the coordinates file *.xyz. The simulation is setup with transverse unit cells repeated times along beam direction (which results in a total thickness ), a sampling and a slice thickness .
from multislice import Multislice
multi=Multislice('Silicon/Si110',mulslice=False,
repeat=[6,6,40],slice_thick=1.3575,NxNy=[512,512])
running a simulation
The simulation can then be run(remotely if ssh provided) and monitored using the generated input decks or directly through the interface with :
multi.run(ssh='')
multi.check_simu_state(ssh='')
multi.print_log()
post process
Once the simulation is over, post-process can be performed. Plotting the beam intensities as function of thickness and displaying the diffraction pattern as :
multi.beam_vs_thickness(bOpt='f') #force re postprocess
multi.pattern() #diffraction pattern
utilities
The library scattering can be used to compute the scattering factor and structure factor. This can be useful for analysing the results. Here for example the structure factor of Silicon is calculated as :
from scattering.structure_factor import *
from crystals import Crystal
N,sym = 4,False
#
# get info for Silicon from crystals library
Si,Z = Crystal.from_database('Si'),14
lat_vec = Si.reciprocal_vectors # get reciprocal lattice vectors
pattern = np.array([list(a.coords_fractional)+[a.atomic_number] for a in crys.atoms])
hkl,Fhkl = structure_factor3D(pattern,lat_vec,hklMax=N,sym=sym) # compute structure factor up to 4th order in each direction
Shkl = np.abs(Fhkl)**2 # Compute intensities
plot_structure3D(hkl,Shkl) # plot in 3D
atompot
Computes the projected potential of a slice.
Input deck
Contains the parameters :
#pot.in
srta.dat # structure file (for SrTiO3 here)
srtapot.tif # output image file(to feed to multislice)
512 512 # real space pixel Nx,Ny
8 8 1 # replicate unit cell
n # no thermal displacement
Slice file
where the files describing the slices srta.dat and srtb.dat read :
################# Slices for SrTiO3 ###################
#######################################################
# Si110A.dat
3.9051 3.9051 1.9525 #lattice constants ax,bx and slice thickness cz
0 #no symmetry operation
38 # Strontium Z=38
1.0000 0.0000 0.0000 # corner of the cell
8 # Oxygen
1.0000 0.5000 0.5000 # centre of the cell
#######################################################
# strb.dat
3.9051 3.9051 1.9525 #lattice constants ax,bx and slice thickness cz
0 #no symmetry operation
22 #1 Titanium atom Z=22
1.0000 0.5000 0.5000 # centre of the cell
8 # 2 Oxygen atom Z=8
1.0000 0.5000 0.0000 # edge of the cell
1.0000 0.0000 0.5000 # edge of the cell
Variables
iz: atomic numbernsx,nsy: cell to replicatefeatom: atomic scattering factor of atomizfrom Gaussian+Lorentzian fits at ,scamp: scattering amplitude structure factor due to atomsiz,cpix: projected potential of the slice
Code
//atompot.cpp
int main()
//get parameters
k2max = 1.0 / max(2*ax/nx,2*by/ny)^2;
rx2 = (1.0/ax)^2;ry2 = (1.0/by)^2;
scale = ( ((double)nx)*((double)ny) ) /(ax*by);
for( i=0; i<=n_atom_types; i++)
ns = sscanf( cline, "%d", &iz);
while( ReadLine( fp, cline, NCMAX, filein ) > 2)
ns = sscanf( cline, "%f %f %f %f", &occ[j], &x[j], &y[j], &wobble[j];
natom++;
//projected potential contribution
for( iy=0; iy<=iymid; iy++)
ky2 = ky[iy]*ky[iy]*ry2;
for( ix=0; ix<nx; ix++)
if(k2<k2max)
k2 = kx[ix] * kx[ix]*rx2 + ky2;
fe = scale * featom( iz, k2 );
scamp( kx[ix], ky[iy], &scampr, &scampi ) ;
cpix(ix,iy) += scampr*fe;
cpix.ifft(); // inverse fourier transform
//atompot.cpp
void scamp( float kx, float ky, double scampr, double scampi )
scalex = pi2*kx / ((double)nsx);scaley = pi2*ky / ((double)nsy);
for( i=0; i<natom; i++)
phasexr=phasexi=0;
w1 = pi2 * kx * x[i];
for( ixc=0; ixc<nsx; ixc++){
w = w1 + ixc * scalex;
phasexr += cos(w);
phasexi += sin(w);
w1 = pi2 * ky * y[i];
for( iyc=0; iyc<nsy; iyc++)
phaseyr=phaseyi=0;
w = w1 + iyc * scaley;
phaseyr += cos(w);
phaseyi += sin(w);
scampr += ( phasexr * phaseyr - phasexi * phaseyi ) * occ[i];
//slice.cpp
double featom( int Z, double k2 )
{
if( feTableRead == 0 ) nfe = ReadfeTable();//fparams,
sum = 0.0;
//Lorentzians
sum += fparams[Z][i]/( k2 + fparams[Z][i+1] );
//Gaussians
for( i=2 * nl; i<2 * (nl+ng); i+=2 )
sum += fparams[Z][i] * exp( - k2 * fparams[Z][i+1] );
return( sum );
}
mulslice
Input deck
Si110_mulslice.in contains multislice simulation parameters :
#mul.in
12(ab) # stacking sequence
srtapot.tif # projected potential file a
srtbpot.tif # projected potential file b
srtrmul.tif # out binary file diffraction pattern
n # include partial coherence
n # resume from previous run
400 # incident beam energy in keV
0 0 # crystal tilt
0 0 # beam tilt
n # record as a function of thickness
variables
wave: Transverse wave functiontrans[j]: Transmission function of layer ,prop[j]: Fourier transform of the propagator at layer ,vz[j]: Projected potential of layer ,cz[j]: Thickness of layer ,
Code
parsing parameter
ns = scanf("%s", filein[ilayer] );
lstart = askYN("Do you want to start from previous result");
if (lstart) ns = scanf("%s", filestart);
else ns = scanf("%g", &v0);
/* calculate relativistic factor and electron wavelength */
mm0 = 1.0F + v0/511.0F;
wavlen = (float) wavelength( v0 );
scale = wavlen * mm0;
myFile.read( filein[ilayer])
vz=scale*myFile(ix, iy);
trans[ilayer].re(ix,iy) = (float) cos(vz);
trans[ilayer].im(ix,iy) = (float) sin(vz);
/* calculate the total specimen thickness and echo */
cztot += cz[ layer[islice] ];
/* calculate spatial frequencies and positions for future use */
rx = 1.0F/ax;rx2= rx*rx;
ry = 1.0F/by;ry2= ry*ry;
ixmid = nx/2;iymid = ny/2;
/*initialize wavefunction if resuming*/
if ( lstart == 1 ) {
for( ix=0; ix<nx; ix++) for( iy=0; iy<ny; iy++) {
wave0r[ix][iy] = myFile(ix,iy);
wave0i[ix][iy] = myFile(ix+nx,iy);
wave.re(ix,iy) = wave0r[ix][iy];
wave.im(ix,iy) = wave0i[ix][iy];
}
else
// add incident beam tilt
qx = btiltx / wavlen;qy = btilty / wavlen;
wave.re(ix,iy) = (float) cos( t );
wave.im(ix,iy) = (float) sin( t );
}
/* bandwidth limit the transmission function for anti-aliasing*/
const float BW= (2.0F/3.0F);
k2max = min(nx/(2.0F*ax),ny/(2.0F*by))
k2max *= BW * k2max;
/*set to zero outside sampling circle*/
trans[ilayer].fft();
for( ilayer=0; ilayer<nlayer; ilayer++) {
for k2=kx+ky; if (k2 > k2max) trans[ilayer].re=trans[ilayer].im=0;
trans[ilayer].ifft();
}
/* calculate propagator function*/
scale = pi * cz[ilayer];
for( ix=0; ix<nx; ix++) {
t = scale*( kx2[ix] * wavlen);
propxr[ilayer][ix] = (float) cos(t);
propxi[ilayer][ix] = (float) -sin(t);
}
for( iy=0; iy<ny; iy++) {
t = scale*( ky2[iy] * wavlen);
propyr[ilayer][iy] = (float) cos(t);
propyi[ilayer][iy] = (float) -sin(t);
}
propagate
Propagation is performed as :
// mulslice.cpp
/* ------------------- coherent method --------------------*/
for( islice=0; islice<nslice; islice++ ){
ilayer=layer[islice];
wave = wave*trans[ilayer]; // transmit
wave.fft();
propagate(wave,
propxr[ilayer], propxi[ilayer],propyr[ilayer],propyi[ilayer],
kx2, ky2, k2max, nx, ny );
wave.ifft();
// transverse intensity integral
sum = abs(wave)^2.sum()/(nx * ny)
printf("layer = %c, integrated intensity = %f\n",cname[ilayer], sum );
}
autoslic
The program autoslic performs multislice simulation on non periodic structures.
Coordinates file
Input deck
Code
Parsing simulation setup
//autosliccmd.cpp
int main()
autoslic aslice();
cfpix pix,wave0,beams;
aslice.lbeams = askYN("Do you want to record the (real,imag) value of selected beams vs. thickness");
mm0 = 1.0F + v0/511.0F;
wavlen = (float) wavelength( v0 );
natom = ReadXYZcoord( filein.c_str(), ncellx, ncelly, ncellz,
&ax, &by, &cz, &Znum, &x, &y, &z, &occ, &wobble,description );
aslice.calculate(pix,wave0,natom,beams);
if (lbeams)
fp1.open(filebeam);
fp1 << " (h,k) = " << hbeam[ib] << endl;
fp1 << islice << beams.re(ib,islice) << beams.im(ib,islice) << endl;
Running the multislice simulation
//autoslic.cpp
void autoslic::calculate(cfpix &pix,cfpix &wave0, cfpix &depthpix,
natom, cfpix &beams,hbeam,kbeam)
scale=1/(nx*ny);
while (zlice<zmax)
trlayer(trans);
wave *= trans;
wave.fft(); //reciprocal space
if( (lbeams== 1) ) {
beams.re(ib,islice-1) = scale*wave.re(hbeam[ib],kbeam[ib] );
beams.im(ib,islice-1) = scale*wave.im(hbeam[ib],kbeam[ib] );
}
propagate(wave,propxr,propyr,propxi,propyi);
wave.ifft();
/*save intermediate intensities*/
if (lcross){
depthpix = wave.re*wave.re+wave.im*wave.im;
}
/*Return exit wave*/
pix = wave;
Transmission function from projected potential
void autoslic::trlayer( const float x[], const float y[], const float occ[],const int Znum[], const int natom, const float ax, const float by,const float kev, cfpix &trans, const int nx, const int ny,const floatkx2[], const float ky2[],double *phirms, int *nbeams, const float k2max)
scale = sigma(kev)/1000.0;scalex=ax/nx;scaley=by/ny;//sigma rad/V-A
//pragma omp parallel
for( i=0; i<natom; i++)
for( ix=nx1; ix<=nx2; ix++)
rx = x[i] - ((double)ix) * scalex;
for( iy=ny1; iy<=ny2; iy++)
ry = y[i] - ((double)iy) * scaley;
rsq = rx**2 + ry**2;
vz += occ[i] * vzatomLUT( Znum[i], rsq );
trans = exp(1J*scale*vz);
/* bandwidth limit the transmission function */
trans.fft();
trans[k2<k2max] = 0;
trans.ifft();
Real space atomic projected potential
Computing projected atomic potential from cubic spline interpolation using look up table coefficients.
splinx,spliny: Data projected atomic potential for spline interpolationsplinb,splinc,splind: spline coefficients
//slicelib.cpp
double vzatomLUT(int Z, double rsq)
/* generate a set of logarithmic r values */
if (!spline_init)
dlnr = log(RMAX/RMIN)/(NRMAX-1);
for( i=0; i<NRMAX; i++)
splinx[i] = (RMIN * exp( i * dlnr ) )^2;
/* generate the spline coefficients from analytical projected potential expression */
if (!nspline[iz]) //if this atomic number has not been called before do it once and for all
for( i=0; i<NRMAX; i++)
spliny[iz][i] = vzatom( Z, sqrt(splinx[i]));
splinh( splinx, spliny[iz], splinb[iz],splinc[iz], splind[iz], NRMAX);
/* now that everything is set up find the scattering factor by interpolation in the table */
vz = seval( splinx, spliny[iz], splinb[iz],
splinc[iz], splind[iz], nspline[iz], rsq );
double vzatom( int Z, double r )
const double al=150.4121417, ag=266.5985798;
nfe = ReadfeTable(fparams);
x = 2.0*pi*r;
for( i=0; i<2*nl; i+=2 )
suml += fparams[Z][i]* bessk0( x*sqrt(fparams[Z][i+1]) );
x = (pi*r)**2;
for( i=2*nl; i<2*(nl+ng); i+=2 )
sumg += fparams[Z][i]*exp(-x/fparams[Z][i+1]) / fparams[Z][i+1];
return( al*suml + ag*sumg );
//Compute
propagator
/* multiplied by the propagator function */
//autoslic.cpp
void calculate()
scale = pi*deltaz
tctx = (float) (2.0 * tan(ctiltx));
tcty = (float) (2.0 * tan(ctiltx));
propxr[ix] = cos(scale*kx2*wavlen - kx[ix]*tctx)
propxi[ix] = sin(scale*kx2*wavlen - kx[ix]*tctx)
propyr[ix] = cos(scale*ky2*wavlen - ky[iy]*tcty)
propyi[ix] = sin(scale*ky2*wavlen - ky[iy]*tcty)
//slicelib.cpp
void propagate(cfpix &wave,float* propxr, float* propxi, float* propyr, float* propyi,float* kx2, float* ky2, float k2max, int nx, int ny )
for( ix=0; ix<nx; ix++)
if( kx2[ix] < k2max )
pxr = propxr[ix];
pxi = propxi[ix];
for( iy=0; iy<ny; iy++)
if( (kx2[ix] + ky2[iy]) < k2max )
pyr = propyr[iy];
pyi = propyi[iy];
wr = wave.re(ix,iy); // real
wi = wave.im(ix,iy); // imag
tr = wr*pyr - wi*pyi;
ti = wr*pyi + wi*pyr;
wave.re(ix,iy) = tr*pxr - ti*pxi;
wave.im(ix,iy) = tr*pxi + ti*pxr;
Other programs
- image formation effects (defocus,aberration,...) :
temsmim/image - STEM(scanning => incident probe) :
temsim/stemlic - STEM non periodic :
temsim/autostem
walltime
| Kirkland | time per slice | time per slice per core |
|---|---|---|
![]() |



