Coverage for cclib/method/hirshfeld.py : 96%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2#
3# Copyright (c) 2020, the cclib development team
4#
5# This file is part of cclib (http://cclib.github.io) and is distributed under
6# the terms of the BSD 3-Clause License.
8"""Calculation of Hirshfeld charges based on data parsed by cclib."""
9import copy
10import random
11import numpy
12import logging
13import math
14import os
15import sys
17from cclib.method.calculationmethod import Method
18from cclib.method.stockholder import Stockholder
19from cclib.method.volume import electrondensity_spin
20from cclib.parser.utils import find_package
22from typing import List
25class MissingInputError(Exception):
26 pass
29class ConvergenceError(Exception):
30 pass
33class Hirshfeld(Stockholder):
34 """Hirshfeld charges."""
36 # All of these are required for DDEC6 charges.
37 required_attrs = ("homos", "mocoeffs", "nbasis", "gbasis")
39 def __init__(
40 self,
41 data,
42 volume,
43 proatom_path=None,
44 progress=None,
45 loglevel=logging.INFO,
46 logname="Log",
47 ):
48 """ Initialize Hirshfeld object.
49 Inputs are:
50 data -- ccData object that describe target molecule.
51 volume -- Volume object that describe target Cartesian grid.
52 proatom_path -- path to proatom densities
53 (directory containing atoms.h5 in horton or c2_001_001_000_400_075.txt in chargemol)
54 """
55 super(Hirshfeld, self).__init__(data, volume, proatom_path, progress, loglevel, logname)
57 def __str__(self):
58 """Return a string representation of the object."""
59 return "Hirshfeld charges of {}".format(self.data)
61 def __repr__(self):
62 """Return a representation of the object."""
63 return "Hirshfeld({})".format(self.data)
65 def _check_required_attributes(self):
66 super(Hirshfeld, self)._check_required_attributes()
68 def _cartesian_dist(self, pt1, pt2):
69 """Small utility function that calculates Euclidian distance between two points.
71 Arguments pt1 and pt2 are NumPy arrays representing points in Cartesian coordinates.
72 """
73 return numpy.sqrt(numpy.dot(pt1 - pt2, pt1 - pt2))
75 def _read_proatom(
76 self, directory, atom_num, charge # type = str # type = int # type = float
77 ):
78 return super(Hirshfeld, self)._read_proatom(directory, atom_num, charge)
80 def calculate(self):
81 """Calculate Hirshfeld charges."""
82 super(Hirshfeld, self).calculate()
84 # Generator object to iterate over the grid.
85 ngridx, ngridy, ngridz = self.charge_density.data.shape
86 indices = (
87 (i, x, y, z)
88 for i in range(self.data.natom)
89 for x in range(ngridx)
90 for y in range(ngridy)
91 for z in range(ngridz)
92 )
93 grid_shape = (self.data.natom, ngridx, ngridy, ngridz)
95 stockholder_w = numpy.zeros(grid_shape)
96 self.closest_r_index = numpy.zeros(grid_shape, dtype=int)
98 for atomi, xindex, yindex, zindex in indices:
99 # Distance of the grid from atom grid.
100 dist_r = self._cartesian_dist(
101 self.data.atomcoords[-1][atomi],
102 self.charge_density.coordinates([xindex, yindex, zindex]),
103 )
104 self.closest_r_index[atomi][xindex][yindex][zindex] = numpy.abs(
105 self.radial_grid_r[atomi] - dist_r
106 ).argmin()
108 # stockholder_w is radial proatom density projected on Cartesian grid
109 stockholder_w[atomi][xindex][yindex][zindex] = self.proatom_density[atomi][
110 self.closest_r_index[atomi][xindex][yindex][zindex]
111 ]
113 # Equation 3 in doi:10.1007/BF01113058
114 # stockholder_bigW represents the denominator in this equation
115 # \sum{eta_chi(r_chi)}_chi
116 # where eta is proatom density, chi is index of atoms in the molecule, and r_chi is
117 # radial distance from center of atom chi.
118 stockholder_bigW = numpy.sum(stockholder_w, axis=0)
120 self.fragcharges = numpy.zeros((self.data.natom))
121 self.logger.info("Creating fragcharges: array[1]")
123 for atomi in range(self.data.natom):
124 # Equation 1 in doi:10.1007/BF01113058
125 # The weights supplied for integrate function correspond to W_A in the equation.
126 # Q_A = Z_A - \integral(W_A(r) * q_A(r) dr)
127 # where Q_A is Hirshfeld charges, W_A is weights determined on each grid and on each
128 # atom, and q_A is total charge densities on the Cartesian grid.
129 self.fragcharges[atomi] = self.data.atomnos[atomi] - self.charge_density.integrate(
130 weights=(stockholder_w[atomi] / stockholder_bigW)
131 )
133 return True