Charge Density

Here code examples for charge and spin densities are given..

The ‘read_ECHG’ method

This method is defined in the crystal_io.Properties_output class, which read formatted output files generated by CRYSTAL and return to the electronics.ChargeDensity class. It requires the Crgra2006 (fort.25) file.

The standard screen output is required to identify the indices of corresponding 2D data maps. Otherwise the code only reads the first 2D data map.

Read an ‘ECHG’ calculation and return to a electronics.ChargeDensity class. The data attribute is a nX*nY*nspin array.

NOTE

Though ‘PATO’ is specified in the file ‘dens_grapheneMV’, by default method only the charge densities of the bonded system is read. For charge density differences, use method='substract'. If the charge density by overlapping atomic densities are wanted (very rare), a separate calculation with ‘PATO+ECHG’ only is needed (see ‘dens_grapheneMV_PATO’ files).

[2]:
from CRYSTALpytools.crystal_io import Properties_output

chg = Properties_output('dens_grapheneMV.out').read_ECHG('dens_grapheneMV.f25')
print('Spin: {:d}'.format(chg.spin))
print('Base vectors (point A, B, C):')
print(chg.base)
print('Data dimensionality:')
print(chg.data.shape)
Spin: 2
Base vectors (point A, B, C):
[[12.7064 -7.4408  0.    ]
 [ 0.      0.      0.    ]
 [-0.0907 14.7244  0.    ]]
Data dimensionality:
(100, 100, 2)

With the method input the user can substract the difference of charge between inputs of arbitrary length. Data is substracted from the first entry based on the following entries.

Since the ‘spin’ dimension is somewhat ‘meaningless’, the returned object has no spin dimension.

For multiple entries, make sure the charge (spin) density is the first (second) 2D grid data (‘-%-MAPN’) in the fort.25 files because the code does not check the output in this case. Warnings is generated.

[3]:
dchg = Properties_output().read_ECHG('dens_grapheneMV.f25',
                                     'dens_grapheneMV_PATO.f25',
                                     method='substract')
print('Spin: {:d}'.format(dchg.spin))
print('Data dimensionality:')
print(dchg.data.shape)
Spin: 1
Data dimensionality:
(100, 100, 1)
/tmp/ipykernel_21724/2111153245.py:1: UserWarning: Properties output file not found: Only the first 1 (2) density map(s) will be read for spin=1(2).
  dchg = Properties_output().read_ECHG('dens_grapheneMV.f25',
/home/huanyu/apps/anaconda3/envs/crystal_py3.9/lib/python3.9/site-packages/CRYSTALpytools/electronics.py:1160: UserWarning: Properties output file not found: Only the first 1 (2) density map(s) will be read for spin=1(2).
  obj = Properties_output().read_ECHG(i, method='normal')

A safer way is to generate 2 ChargeDensity objects (see the next section) for 2 files separately and call the substract method.

[4]:
from CRYSTALpytools.electronics import ChargeDensity

dchg = Properties_output('dens_grapheneMV.out').read_ECHG('dens_grapheneMV.f25')
pato = Properties_output('dens_grapheneMV_PATO.out').read_ECHG('dens_grapheneMV_PATO.f25')
dchg = dchg.substract(pato)

print('Spin: {:d}'.format(dchg.spin))
print('Data dimensionality:')
print(dchg.data.shape)
Spin: 1
Data dimensionality:
(100, 100, 1)

The ‘substract’ method also allows for a single fort.25 file including both ‘normal’ and ‘PATO’ charge densities. The screen output is required, otherwise the code only reads the first 2D map data.

[5]:
dchg = Properties_output('dens_grapheneMV.out').read_ECHG('dens_grapheneMV.f25', method='substract')
print('Spin: {:d}'.format(dchg.spin))
print('Data dimensionality:')
print(dchg.data.shape)
Spin: 1
Data dimensionality:
(100, 100, 1)

‘electronics.ChargeDensity’ class

The electronics.ChargeDensity class has object-oriented methods for data analysis and plotting. It is designed to be applicable to systems with 1 to 3D though by July 2024, 3D plotting is still under developing.

Instantiatation

The classmethod from_file is a wrapper of I/O functions introduced above. For spin-polarized systems, the user can also save the data in \(\alpha\) and \(\beta\) states rather than charge density (\(\alpha+\beta\)) and spin density (\(\alpha-\beta\)).

[6]:
from CRYSTALpytools.electronics import ChargeDensity

chg = ChargeDensity.from_file('dens_grapheneMV.f25',
                              output='dens_grapheneMV.out', method='alpha_beta')

Quick plotting

The plot_2D() method helps to visualize the charge / spin densities.

Plot them together with default setups (color-filled contour plot):

[7]:
from CRYSTALpytools.electronics import ChargeDensity

fig = ChargeDensity.from_file('dens_grapheneMV.f25',
                              output='dens_grapheneMV.out').plot_2D()
../../_images/examples_charge_density_charge_density_11_0.png

Not nice. Change the settings of levels for plot levels and range, and figsize for figure size.

[8]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 100, 100))
spinlevel = np.linspace(-0.05, 0.05, 100)

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
fig = chg.plot_2D(levels=[chglevel, spinlevel], figsize=[8, 4])
../../_images/examples_charge_density_charge_density_13_0.png

In this example, the plot is non-orthogonal. The user can get orthogonal plot by setting rectangle=True. The cmode moves the non-orthogonal part on the right to the left hand side, i.e., similar to the ‘RECTANGU’ keyword of ‘MAPNET’ (see CRYSTAL manual).

[9]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 100, 100))
spinlevel = np.linspace(-0.05, 0.05, 100)

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
fig = chg.plot_2D(levels=[chglevel, spinlevel], figsize=[8, 6], rectangle=True)
../../_images/examples_charge_density_charge_density_15_0.png

The monovacancy is off-center after rectangle plotting. The user can modify periodicity by setting a_range and b_range. Both of them uses fractional coordinates of plot base vector. If they are used with rectangle=True, that refers to the old, non-orthogonal base vectors. The origin of plotting axes are always 0. To avoid influences of periodic boundaries, supercell sizes, i.e., a_range[1]-a_range[0] and b_range[1]-b_range[0] must be integers.

By setting lineplot=True and colorplot=False, contour lines is plotted. If both are ‘True’, contour lines are overlapped with color plots (not looking good with this example). Solid lines for positive values and 0 (doubled width). Dotted lines for negative values.

[10]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 10, 10))
spinlevel = np.linspace(-0.05, 0.05, 10)

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
fig = chg.plot_2D(levels=[chglevel, spinlevel], figsize=[8, 6], lineplot=True, colorplot=True,
                  a_range=[-0.3, 0.7], b_range=[-0.1, 0.9], rectangle=True)
../../_images/examples_charge_density_charge_density_17_0.png

It is also possible to get color-coded lines for contourplots with lineplot=True and colorplot=False. Positive values are denoted by solid red lines and negative by dotted blue lines. 0 is denoted by the solid black lines with doubled width.

[11]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 10, 10))
spinlevel = np.linspace(-0.05, 0.05, 10)

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
fig = chg.plot_2D(levels=[chglevel, spinlevel], figsize=[8, 6], lineplot=True, colorplot=False,
                  a_range=[-0.3, 0.7], b_range=[-0.1, 0.9], rectangle=True)
../../_images/examples_charge_density_charge_density_19_0.png

With a_range and b_range, one can also plot supercells. Set edgeplot=True to mark unit cell boundaries, which is not influanced by ‘range’ or ‘rectangle’ options.

[12]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 100, 100))
spinlevel = np.linspace(-0.05, 0.05, 100)

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
fig = chg.plot_2D(levels=[chglevel, spinlevel], figsize=[8, 6],
                  a_range=[-0.5, 1.5], b_range=[-0.5, 1.5],
                  rectangle=True, edgeplot=True)
../../_images/examples_charge_density_charge_density_21_0.png

Analysis

As has illustrated above, the sustract() method helps to generate difference maps. The alpha_beta() method splits \(\alpha\) and \(\beta\) densities into the original charge and spin densities.

[13]:
from CRYSTALpytools.electronics import ChargeDensity
import numpy as np

chglevel = np.log(np.linspace(1, 10, 100)) * 0.53**3 # roughly convert AA^-3 to Bohr^-3

chg = ChargeDensity.from_file('dens_grapheneMV.f25', method='alpha_beta',
                              output='dens_grapheneMV.out')
fig = chg.plot_2D(unit='a.u.', levels=[chglevel, chglevel], figsize=[8, 6],
                  cbar_label=[r'$\rho_{\alpha}$', r'$\rho_{\beta}$'],
                  rectangle=True, add_title=False)
../../_images/examples_charge_density_charge_density_23_0.png

Conversion to XSF

The to_xsf() method helps to write 2D/3D charge/spin densities into the XCrySDen XSF format for visualization and analysis. Such file format is accepted by many popular modelling software such as XCrySDen, VMD and VESTA.

NOTE

For 3D data grid, if CUBE file(s) are read without output, the XSF file output has the 3D ‘CRYSTAL’ periodicity. As far as the authors have been aware of, this only causes Segmentation Fault of XCrySDen 1.6.2 when dealing with low dimensional systems. Available solution includes:

  1. including output file

  2. using other software such as VESTA

  3. changing the keyword manually.

Convert the same system into XSF format with 2D data grid.

[14]:
from CRYSTALpytools.electronics import ChargeDensity

chg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out')
chg.to_xsf('dens_grapheneMV.xsf')
/home/huanyu/apps/anaconda3/envs/crystal_py3.9/lib/python3.9/site-packages/CRYSTALpytools/electronics.py:1497: UserWarning: File 'dens_grapheneMV.xsf' exists! It will be overwritten.
  xsf.write(filename, geomonly=False, grid_name='alpha+beta')

Visualize spin densities in XCrySDen.

128ffab2be2f49efafb4de186536057e

Get charge density difference before and after CO adsoprtion onto the MgO \((001)\) 2 layers slab.

[15]:
from CRYSTALpytools.electronics import ChargeDensity

dchg = ChargeDensity.from_file('dens_MgOCO.cube',
                               'dens_MgO.cube',
                               'dens_CO.cube',
                               output='dens_MgOCO.out',
                               method='substract')
dchg.to_xsf('dens_dMgOCO.xsf')

Visualize charge density difference in VESTA. The cell has been expanded 3 times in both directions.

d2af8960326048d09dc4963e5a6bf1b4

The ‘plot.plot_ECHG()’ function

The plot.plot_ECHG() function enables a higher-level of plotting. It accepts extendable length of ‘fort.25’ files or ChargeDensity objects.

Single-system charge / spin density.

By default the charge / spin densities of a single sysmtem is plotted, as the example showed above.

Multi-system charge / spin densities

plot_ECHG() can read entries of arbitrary length and return to the matplotlib Figure object with subplots. For comparison, the uniform scale can be used by setting levels.

[16]:
from CRYSTALpytools.plot import plot_ECHG
import numpy as np

chglevel = np.log(np.linspace(1, 10, 100))
files = ['dens_grapheneMV.f25', 'dens_grapheneMV_PATO.f25']

figs = plot_ECHG(*files, option='charge', levels=chglevel,
                 rectangle=True, figsize=[8, 6])
/home/huanyu/apps/anaconda3/envs/crystal_py3.9/lib/python3.9/site-packages/CRYSTALpytools/electronics.py:1140: UserWarning: Properties output file not found: Only the first 1 (2) density map(s) will be read for spin=1(2).
  cls = Properties_output(output).read_ECHG(*files, method=method)
../../_images/examples_charge_density_charge_density_30_1.png

With option='diff', the user can quickly plot charge difference maps. It returns to non spin-polarized solution (obj.spin=1) as only charge density difference is considered.

[17]:
from CRYSTALpytools.plot import plot_ECHG
import numpy as np

chglevel = np.linspace(-0.5, 1, 100)
files = ['dens_grapheneMV.f25', 'dens_grapheneMV_PATO.f25']

figs = plot_ECHG(*files, option='diff', levels=chglevel, rectangle=True,
                 a_range=[0, 2], b_range=[0, 2], edgeplot=True, figsize=[6, 6],
                 add_title=False, cbar_label=r'$\Delta\rho$ ($|e|/\AA^{-3}$)')
../../_images/examples_charge_density_charge_density_32_0.png

If the object is generated with ‘substract’ method, plot it with ‘charge’ or ‘both’ will get charge density differences.

[18]:
from CRYSTALpytools.electronics import ChargeDensity
from CRYSTALpytools.plot import plot_ECHG
import numpy as np

chglevel = np.linspace(-0.5, 0.5, 100)
dchg = ChargeDensity.from_file('dens_grapheneMV.f25', output='dens_grapheneMV.out',
                               method='substract')
fig = plot_ECHG(dchg, option='charge', levels=chglevel, figsize=[6, 6])
../../_images/examples_charge_density_charge_density_34_0.png

For more details, please refer to the API documentations.