Source code for chemtools.denstools.densbased
# -*- coding: utf-8 -*-
# ChemTools is a collection of interpretive chemical tools for
# analyzing outputs of the quantum chemistry calculations.
#
# Copyright (C) 2016-2019 The ChemTools Development Team
#
# This file is part of ChemTools.
#
# ChemTools 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.
#
# ChemTools 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 <http://www.gnu.org/licenses/>
#
# --
# pragma pylint: disable=invalid-name
"""Density-Based Local Reactivity Tools."""
import numpy as np
__all__ = ['DensTool', 'DensGradTool', 'DensGradLapTool', 'DensGradLapKedTool']
[docs]class DensTool(object):
"""Local descriptive tools based on density."""
def __init__(self, dens):
r"""Initialize class.
Parameters
----------
dens : np.ndarray
Electron density evaluated on a set of grid points, :math:`\rho(\mathbf{r})`.
"""
if dens.ndim != 1:
raise ValueError('Argument dens should be a 1-dimensional array.')
self._dens = dens
@property
def density(self):
r"""Electron density :math:`\rho\left(\mathbf{r}\right)`."""
return self._dens
@property
def shannon_information(self):
r"""Shannon information defined as :math:`\rho(r) \ln \rho(r)`."""
# TODO: masking might be needed
value = self.density * np.log(self.density)
return value
@property
def ked_thomas_fermi(self):
r"""Thomas-Fermi kinetic energy density.
.. math::
\tau_\text{TF} \left(\mathbf{r}\right) = \tfrac{3}{10} \left(6 \pi^2 \right)^{2/3}
\left(\frac{\rho\left(\mathbf{r}\right)}{2}\right)^{5/3}
"""
# compute Thomas-Fermi kinetic energy
prefactor = 0.3 * (3.0 * np.pi**2.0)**(2.0 / 3.0)
kinetic = prefactor * self.density ** (5.0 / 3.0)
return kinetic
[docs]class DensGradTool(DensTool):
"""Local descriptive tools based on density & gradient."""
def __init__(self, dens, grad):
r"""Initialize class.
Parameters
----------
dens : np.ndarray
Electron density evaluated on a set of grid points, :math:`\rho(\mathbf{r})`.
grad : np.ndarray
Gradient vector of electron density evaluated on a set of grid points,
:math:`\nabla \rho(\mathbf{r})`.
"""
super(DensGradTool, self).__init__(dens)
if grad.shape != (dens.size, 3):
raise ValueError('Argument grad should be of {0} shape.'.format((dens.shape, 3)))
self._grad = grad
@property
def gradient(self):
r"""Gradient of electron density :math:`\nabla \rho\left(\mathbf{r}\right)`.
This is the first-order partial derivatives of electron density w.r.t. coordinate
:math:`\mathbf{r} = \left(x\mathbf{i}, y\mathbf{j}, z\mathbf{k}\right)`,
.. math::
\nabla\rho\left(\mathbf{r}\right) =
\left(\frac{\partial}{\partial x}\mathbf{i}, \frac{\partial}{\partial y}\mathbf{j},
\frac{\partial}{\partial z}\mathbf{k}\right) \rho\left(\mathbf{r}\right)
"""
return self._grad
@property
def gradient_norm(self):
r"""Norm of the gradient of electron density.
.. math::
\lvert \nabla \rho\left(\mathbf{r}\right) \rvert = \sqrt{
\left(\frac{\partial\rho\left(\mathbf{r}\right)}{\partial x}\right)^2 +
\left(\frac{\partial\rho\left(\mathbf{r}\right)}{\partial y}\right)^2 +
\left(\frac{\partial\rho\left(\mathbf{r}\right)}{\partial z}\right)^2 }
"""
norm = np.linalg.norm(self.gradient, axis=1)
return norm
@property
def reduced_density_gradient(self):
r"""Reduced density gradient.
.. math::
s\left(\mathbf{r}\right) = \frac{1}{2\left(3\pi ^2 \right)^{1/3}}
\frac{\lvert \nabla\rho\left(\mathbf{r}\right) \rvert}{\rho\left(\mathbf{r}\right)^{4/3}}
"""
# Mask density values less than 1.0d-30 to avoid diving by zero
mdens = np.ma.masked_less(self.density, 1.0e-30)
mdens.filled(1.0e-30)
# Compute reduced density gradient
prefactor = 0.5 / (3.0 * np.pi**2)**(1.0 / 3.0)
rdg = prefactor * self.gradient_norm / mdens**(4.0 / 3.0)
return rdg
@property
def ked_weizsacker(self):
r"""Weizsacker kinetic energy density.
.. math::
\tau_\text{W} \left(\mathbf{r}\right) = \tfrac{1}{8}
\frac{\lvert \nabla\rho\left(\mathbf{r}\right) \rvert^2}{\rho\left(\mathbf{r}\right)}
"""
# mask density values less than 1.0d-30 to avoid diving by zero
mdens = np.ma.masked_less(self.density, 1.0e-30)
mdens.filled(1.0e-30)
# compute Weizsacker kinetic energy
kinetic = self.gradient_norm**2.0 / (8.0 * mdens)
return kinetic
[docs]class DensGradLapTool(DensGradTool):
"""Local descriptive tools based on density, gradient & Laplacian."""
def __init__(self, dens, grad, lap):
r"""Initialize class.
Parameters
----------
dens : np.ndarray
Electron density evaluated on a set of grid points, :math:`\rho(\mathbf{r})`.
grad : np.ndarray
Gradient vector of electron density evaluated on a set of grid points,
:math:`\nabla \rho(\mathbf{r})`.
lap : np.ndarray
Laplacian of electron density evaluated on a set of grid points,
:math:`\nabla^2 \rho(\mathbf{r})`.
"""
super(DensGradLapTool, self).__init__(dens, grad)
if lap.shape != dens.shape:
raise ValueError('Argument lap should be of {0} shape.'.format(dens.shape))
self._lap = lap
@property
def laplacian(self):
r"""Laplacian of electron density :math:`\nabla ^2 \rho\left(\mathbf{r}\right)`.
This is defined as the trace of Hessian matrix of electron density which is equal to
the sum of its :math:`\left(\lambda_1, \lambda_2, \lambda_3\right)` eigen-values:
.. math::
\nabla^2 \rho\left(\mathbf{r}\right) = \nabla\cdot\nabla\rho\left(\mathbf{r}\right) =
\frac{\partial^2\rho\left(\mathbf{r}\right)}{\partial x^2} +
\frac{\partial^2\rho\left(\mathbf{r}\right)}{\partial y^2} +
\frac{\partial^2\rho\left(\mathbf{r}\right)}{\partial z^2} =
\lambda_1 + \lambda_2 + \lambda_3
"""
return self._lap
@property
def ked_gradient_expansion(self):
r"""Gradient expansion approximation of kinetic energy density.
.. math::
\tau_\text{GEA} \left(\mathbf{r}\right) =
\tau_\text{TF} \left(\mathbf{r}\right) +
\tfrac{1}{9} \tau_\text{W} \left(\mathbf{r}\right) +
\tfrac{1}{6} \nabla^2 \rho\left(\mathbf{r}\right)
This is a special case of :func:`ked_gradient_expansion_general` with
:math:`a=\tfrac{1}{9}` and :math:`b=\tfrac{1}{6}`.
"""
return self.ked_gradient_expansion_general(1. / 9., 1. / 6.)
@property
def ked_gradient_expansion_empirical(self):
r"""Empirical gradient expansion approximation of kinetic energy density.
.. math::
\tau_\text{empGEA} \left(\mathbf{r}\right) =
\tau_\text{TF} \left(\mathbf{r}\right) +
\tfrac{1}{5} \tau_\text{W} \left(\mathbf{r}\right) +
\tfrac{1}{6} \nabla^2 \rho\left(\mathbf{r}\right)
This is a special case of :func:`ked_gradient_expansion_general` with
:math:`a=\tfrac{1}{5}` and :math:`b=\tfrac{1}{6}`.
"""
return self.ked_gradient_expansion_general(1. / 5., 1. / 6.)
[docs] def ked_gradient_expansion_general(self, a, b):
r"""General gradient expansion approximation of kinetic energy density.
.. math::
\tau_\text{genGEA} \left(\mathbf{r}\right) =
\tau_\text{TF} \left(\mathbf{r}\right) +
a \, \tau_\text{W} \left(\mathbf{r}\right) + b \, \nabla^2 \rho\left(\mathbf{r}\right)
Parameters
----------
a : float
Value of parameter :math:`a`.
b : float
Value of parameter :math:`b`.
"""
return self.ked_thomas_fermi + a * self.ked_weizsacker + b * self.laplacian
[docs]class DensGradLapKedTool(DensGradLapTool):
"""Local descriptive tools based on density, gradient, Laplacian & kinetic energy density."""
def __init__(self, dens, grad, lap, ked):
r"""Initialize class.
Parameters
----------
dens : np.ndarray
Electron density evaluated on a set of grid points, :math:`\rho(\mathbf{r})`.
grad : np.ndarray
Gradient vector of electron density evaluated on a set of grid points,
:math:`\nabla \rho(\mathbf{r})`.
lap : np.ndarray
Laplacian of electron density evaluated on a set of grid points,
:math:`\nabla^2 \rho(\mathbf{r})`.
ked : np.ndarray
Positive-definite or Lagrangian kinetic energy density evaluated on a set of grid
points; :math:`\tau_\text{PD} (\mathbf{r})` or :math:`G(\mathbf{r})`.
"""
super(DensGradLapKedTool, self).__init__(dens, grad, lap)
if lap.shape != ked.shape:
raise ValueError('Argument ked should be of {0} shape.'.format(dens.shape))
self._ked = ked
@property
def ked_positive_definite(self):
r"""Positive definite or Lagrangian kinetic energy density, :math:`G(\mathbf{r})`.
.. math::
\tau_\text{PD} \left(\mathbf{r}\right) =
\tfrac{1}{2} \sum_i^N n_i \rvert \nabla \phi_i \left(\mathbf{r}\right) \lvert^2
"""
return self._ked
@property
def ked_hamiltonian(self):
r"""Hamiltonian kinetic energy density denoted by :math:`K(\mathbf{r})`.
.. math::
\tau_\text{ham} \left(\mathbf{r}\right) =
\tau_\text{PD} \left(\mathbf{r}\right) -
\tfrac{1}{4} \nabla^2 \rho\left(\mathbf{r}\right)
This is a special case of :func:`ked_general` with :math:`a=0`.
"""
return self.ked_general(a=0.)
[docs] def ked_general(self, a):
r"""Compute general(ish) kinetic energy density.
.. math::
\tau_\text{G} \left(\mathbf{r}, \alpha\right) =
\tau_\text{PD} \left(\mathbf{r}\right) +
\tfrac{1}{4} (a - 1) \nabla^2 \rho\left(\mathbf{r}\right)
Parameters
----------
a : float
Value of parameter :math:`a`.
"""
return self.ked_positive_definite + self.laplacian * (a - 1) / 4.