chmpy 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- chmpy/__init__.py +15 -0
- chmpy/cmd/__init__.py +0 -0
- chmpy/cmd/convert.py +55 -0
- chmpy/core/__init__.py +4 -0
- chmpy/core/dimer.py +136 -0
- chmpy/core/eeq.py +238 -0
- chmpy/core/element.py +503 -0
- chmpy/core/molecule.py +1290 -0
- chmpy/core/wolf.py +52 -0
- chmpy/crystal/__init__.py +22 -0
- chmpy/crystal/asymmetric_unit.py +85 -0
- chmpy/crystal/atomic_graph.py +453 -0
- chmpy/crystal/crystal.py +1824 -0
- chmpy/crystal/crystal_state.py +314 -0
- chmpy/crystal/dimer_index.py +1260 -0
- chmpy/crystal/eeq_pbc.py +196 -0
- chmpy/crystal/fingerprint.py +203 -0
- chmpy/crystal/force_field.py +325 -0
- chmpy/crystal/io.py +538 -0
- chmpy/crystal/neighbor_list.py +338 -0
- chmpy/crystal/orbit.py +594 -0
- chmpy/crystal/pair_graph.py +600 -0
- chmpy/crystal/point_group.py +322 -0
- chmpy/crystal/sgdata.json +1 -0
- chmpy/crystal/shape_descriptors.py +290 -0
- chmpy/crystal/site_symmetry.py +178 -0
- chmpy/crystal/space_group.py +391 -0
- chmpy/crystal/space_group_table.py +276 -0
- chmpy/crystal/subgroup.py +1204 -0
- chmpy/crystal/surface.py +319 -0
- chmpy/crystal/symmetric_graph.py +597 -0
- chmpy/crystal/symmetry_finder.py +198 -0
- chmpy/crystal/symmetry_operation.py +496 -0
- chmpy/crystal/unit_cell.py +637 -0
- chmpy/crystal/wulff.py +285 -0
- chmpy/descriptors/__init__.py +25 -0
- chmpy/descriptors/symmetry_function_ani1.py +294 -0
- chmpy/exe/__init__.py +22 -0
- chmpy/exe/exe.py +217 -0
- chmpy/exe/gaussian.py +108 -0
- chmpy/exe/gulp.py +148 -0
- chmpy/exe/raspa.py +217 -0
- chmpy/exe/tonto.py +110 -0
- chmpy/exe/xtb.py +144 -0
- chmpy/ext/__init__.py +0 -0
- chmpy/ext/ase.py +146 -0
- chmpy/ext/charges.py +63 -0
- chmpy/ext/cosmo.py +105 -0
- chmpy/ext/crystal.py +67 -0
- chmpy/ext/cx.py +152 -0
- chmpy/ext/elastic_tensor.py +385 -0
- chmpy/ext/excitations.py +61 -0
- chmpy/ext/solvation_parameters.py +181 -0
- chmpy/ext/traj.py +27 -0
- chmpy/ext/vasp.py +78 -0
- chmpy/ff/__init__.py +34 -0
- chmpy/ff/lj_params.json +1444 -0
- chmpy/ff/params.py +373 -0
- chmpy/fmt/__init__.py +0 -0
- chmpy/fmt/aims.py +750 -0
- chmpy/fmt/ascii.py +57 -0
- chmpy/fmt/ase.py +0 -0
- chmpy/fmt/cif.py +328 -0
- chmpy/fmt/crystal17.py +95 -0
- chmpy/fmt/cube.py +102 -0
- chmpy/fmt/fchk.py +64 -0
- chmpy/fmt/gaussian_log.py +147 -0
- chmpy/fmt/gen.py +68 -0
- chmpy/fmt/gmf.py +34 -0
- chmpy/fmt/grd.py +26 -0
- chmpy/fmt/gulp.py +273 -0
- chmpy/fmt/mol2.py +118 -0
- chmpy/fmt/nwchem.py +30 -0
- chmpy/fmt/pdb.py +307 -0
- chmpy/fmt/raspa.py +347 -0
- chmpy/fmt/sdf.py +279 -0
- chmpy/fmt/shelx.py +159 -0
- chmpy/fmt/smiles.py +124 -0
- chmpy/fmt/tmol.py +69 -0
- chmpy/fmt/vasp.py +33 -0
- chmpy/fmt/xtb.py +82 -0
- chmpy/fmt/xyz_file.py +118 -0
- chmpy/graph/__init__.py +123 -0
- chmpy/graph/adjacency.py +285 -0
- chmpy/graph/aromaticity.py +462 -0
- chmpy/graph/bond_orders.py +399 -0
- chmpy/graph/canonicalization.py +367 -0
- chmpy/graph/formal_charges.py +285 -0
- chmpy/graph/rings.py +408 -0
- chmpy/graph/smiles_writer.py +530 -0
- chmpy/graph/stereochemistry.py +440 -0
- chmpy/graph/substructure.py +514 -0
- chmpy/interpolate/__init__.py +3 -0
- chmpy/interpolate/_backends.py +84 -0
- chmpy/interpolate/_radii.py +170 -0
- chmpy/interpolate/density.py +130 -0
- chmpy/interpolate/lerp.py +29 -0
- chmpy/interpolate/thakkar_interp.npz +0 -0
- chmpy/ints/__init__.py +0 -0
- chmpy/ints/lebedev.py +32 -0
- chmpy/ints/lebedev_grids.npz +0 -0
- chmpy/ints/solvation.py +76 -0
- chmpy/mc/__init__.py +3 -0
- chmpy/mc/_mc.py +110 -0
- chmpy/mc/_mc_numpy.py +316 -0
- chmpy/mc/lookup_tables.py +90 -0
- chmpy/opt/__init__.py +6 -0
- chmpy/opt/gulp.py +136 -0
- chmpy/opt/xtb.py +257 -0
- chmpy/sampling/__init__.py +50 -0
- chmpy/sampling/_lds.py +42 -0
- chmpy/sampling/_sobol.py +45 -0
- chmpy/shape/__init__.py +13 -0
- chmpy/shape/_invariants.py +173 -0
- chmpy/shape/_sht.py +324 -0
- chmpy/shape/assoc_legendre.py +56 -0
- chmpy/shape/convex_hull.py +74 -0
- chmpy/shape/reconstruct.py +111 -0
- chmpy/shape/shape_descriptors.py +228 -0
- chmpy/shape/sht.py +606 -0
- chmpy/shape/spherical_harmonics.py +109 -0
- chmpy/surface.py +142 -0
- chmpy/templates/__init__.py +42 -0
- chmpy/templates/crystal17.jinja2 +21 -0
- chmpy/templates/gaussian_scf.jinja2 +14 -0
- chmpy/templates/gulp.jinja2 +30 -0
- chmpy/templates/nwchem_input.jinja2 +19 -0
- chmpy/templates/tmol.jinja2 +18 -0
- chmpy/templates/tonto_pair_energy.jinja2 +23 -0
- chmpy/tests/__init__.py +9 -0
- chmpy/tests/core/__init__.py +0 -0
- chmpy/tests/core/test_eeq.py +168 -0
- chmpy/tests/core/test_element.py +69 -0
- chmpy/tests/core/test_molecule.py +86 -0
- chmpy/tests/crystal/__init__.py +0 -0
- chmpy/tests/crystal/test_asymmetric_unit.py +180 -0
- chmpy/tests/crystal/test_atomic_graph.py +278 -0
- chmpy/tests/crystal/test_crystal.py +263 -0
- chmpy/tests/crystal/test_crystal_state.py +273 -0
- chmpy/tests/crystal/test_dimer_index.py +557 -0
- chmpy/tests/crystal/test_eeq_pbc.py +92 -0
- chmpy/tests/crystal/test_fingerprint.py +149 -0
- chmpy/tests/crystal/test_neighbor_list.py +237 -0
- chmpy/tests/crystal/test_orbit.py +345 -0
- chmpy/tests/crystal/test_pair_graph.py +320 -0
- chmpy/tests/crystal/test_site_symmetry.py +178 -0
- chmpy/tests/crystal/test_space_group.py +181 -0
- chmpy/tests/crystal/test_space_group_table.py +204 -0
- chmpy/tests/crystal/test_subgroup.py +735 -0
- chmpy/tests/crystal/test_symmetric_graph.py +452 -0
- chmpy/tests/crystal/test_symmetry_finder.py +247 -0
- chmpy/tests/crystal/test_unit_cell.py +68 -0
- chmpy/tests/crystal/test_wulff.py +64 -0
- chmpy/tests/exe/__init__.py +0 -0
- chmpy/tests/exe/test_raspa.py +168 -0
- chmpy/tests/ext/__init__.py +0 -0
- chmpy/tests/ext/test_eem.py +18 -0
- chmpy/tests/ext/test_elastic_tensor.py +137 -0
- chmpy/tests/ff/__init__.py +0 -0
- chmpy/tests/ff/test_params.py +143 -0
- chmpy/tests/fmt/__init__.py +0 -0
- chmpy/tests/fmt/test_aims.py +534 -0
- chmpy/tests/fmt/test_gen.py +22 -0
- chmpy/tests/fmt/test_pdb.py +123 -0
- chmpy/tests/fmt/test_raspa.py +135 -0
- chmpy/tests/fmt/test_smiles.py +23 -0
- chmpy/tests/graph/__init__.py +1 -0
- chmpy/tests/graph/test_adjacency.py +217 -0
- chmpy/tests/graph/test_aromaticity.py +338 -0
- chmpy/tests/graph/test_bond_orders.py +297 -0
- chmpy/tests/graph/test_canonicalization.py +331 -0
- chmpy/tests/graph/test_integration.py +151 -0
- chmpy/tests/graph/test_rings.py +315 -0
- chmpy/tests/graph/test_smiles_writer.py +277 -0
- chmpy/tests/graph/test_stereochemistry.py +283 -0
- chmpy/tests/graph/test_substructure.py +300 -0
- chmpy/tests/promolecule/__init__.py +0 -0
- chmpy/tests/promolecule/test_density.py +99 -0
- chmpy/tests/promolecule/test_surface.py +25 -0
- chmpy/tests/sampling/test_quasirandom.py +37 -0
- chmpy/tests/shape/__init__.py +0 -0
- chmpy/tests/shape/test_shape_descriptors.py +36 -0
- chmpy/tests/shape/test_sht.py +310 -0
- chmpy/tests/shape/test_spherical_harmonics.py +52 -0
- chmpy/tests/test_files/DB09563.sdf +160 -0
- chmpy/tests/test_files/HXACAN01.pdb +47 -0
- chmpy/tests/test_files/acetic_acid.cif +31 -0
- chmpy/tests/test_files/acetic_acid.res +20 -0
- chmpy/tests/test_files/example.gen +82 -0
- chmpy/tests/test_files/iceII.cif +61 -0
- chmpy/tests/test_files/r3c_example.cif +106 -0
- chmpy/tests/test_files/water.xyz +6 -0
- chmpy/util/__init__.py +3 -0
- chmpy/util/color.py +54 -0
- chmpy/util/dict.py +69 -0
- chmpy/util/exe.py +40 -0
- chmpy/util/mesh.py +92 -0
- chmpy/util/num.py +230 -0
- chmpy/util/path.py +92 -0
- chmpy/util/text.py +118 -0
- chmpy/util/unit.py +82 -0
- chmpy/util/util.py +0 -0
- chmpy-1.2.0.dist-info/METADATA +153 -0
- chmpy-1.2.0.dist-info/RECORD +207 -0
- chmpy-1.2.0.dist-info/WHEEL +5 -0
- chmpy-1.2.0.dist-info/licenses/LICENSE.txt +49 -0
- chmpy-1.2.0.dist-info/top_level.txt +1 -0
chmpy/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from . import surface
|
|
2
|
+
from .core import Element, Molecule
|
|
3
|
+
from .crystal import Crystal, SpaceGroup, UnitCell
|
|
4
|
+
from .interpolate import PromoleculeDensity, StockholderWeight
|
|
5
|
+
|
|
6
|
+
__all__ = [
|
|
7
|
+
"Crystal",
|
|
8
|
+
"Element",
|
|
9
|
+
"Molecule",
|
|
10
|
+
"PromoleculeDensity",
|
|
11
|
+
"SpaceGroup",
|
|
12
|
+
"StockholderWeight",
|
|
13
|
+
"UnitCell",
|
|
14
|
+
"surface",
|
|
15
|
+
]
|
chmpy/cmd/__init__.py
ADDED
|
File without changes
|
chmpy/cmd/convert.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from chmpy import Crystal, Molecule
|
|
6
|
+
|
|
7
|
+
LOG = logging.getLogger("chmpy-convert")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def main():
|
|
11
|
+
import argparse
|
|
12
|
+
|
|
13
|
+
parser = argparse.ArgumentParser()
|
|
14
|
+
parser.add_argument("input")
|
|
15
|
+
parser.add_argument("-o", "--output", required=True)
|
|
16
|
+
parser.add_argument("-if", "--input-format", default="file_ext")
|
|
17
|
+
parser.add_argument("-of", "--output-format", default="file_ext")
|
|
18
|
+
parser.add_argument("--log-level", default="INFO")
|
|
19
|
+
args = parser.parse_args()
|
|
20
|
+
logging.basicConfig(level=args.log_level)
|
|
21
|
+
|
|
22
|
+
Path(args.input)
|
|
23
|
+
Path(args.output)
|
|
24
|
+
|
|
25
|
+
in_kwargs = {}
|
|
26
|
+
if args.input_format != "file_ext":
|
|
27
|
+
in_kwargs["fmt"] = args.input_format
|
|
28
|
+
out_kwargs = {}
|
|
29
|
+
if args.output_format != "file_ext":
|
|
30
|
+
out_kwargs["fmt"] = args.output_format
|
|
31
|
+
|
|
32
|
+
x = None
|
|
33
|
+
for cls in Molecule, Crystal:
|
|
34
|
+
try:
|
|
35
|
+
x = cls.load(args.input, **in_kwargs)
|
|
36
|
+
break
|
|
37
|
+
except KeyError:
|
|
38
|
+
pass
|
|
39
|
+
else:
|
|
40
|
+
LOG.error("Could not delegate parser for '%s'", args.input)
|
|
41
|
+
sys.exit(1)
|
|
42
|
+
|
|
43
|
+
LOG.debug("Loaded %s from %s", x, args.input)
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
x.save(args.output, **out_kwargs)
|
|
47
|
+
except KeyError as e:
|
|
48
|
+
LOG.error("No such writer available (%s) for file '%s'", e, args.output)
|
|
49
|
+
sys.exit(1)
|
|
50
|
+
|
|
51
|
+
LOG.debug("Saved %s to %s", x, args.output)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
if __name__ == "__main__":
|
|
55
|
+
main()
|
chmpy/core/__init__.py
ADDED
chmpy/core/dimer.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Module for pairs of molecules, handling symmetry relations and more."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from chmpy.core import Molecule
|
|
8
|
+
|
|
9
|
+
LOG = logging.getLogger(__name__)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Dimer:
|
|
13
|
+
"""Storage class for symmetry information about a dimers.
|
|
14
|
+
|
|
15
|
+
Dimers are two molecules that may or may not be symmetry related.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
mol_a (Molecule):
|
|
19
|
+
one of the molecules in the pair (symmetry unique)
|
|
20
|
+
mol_b (Molecule): the neighbouring molecule (may be symmetry
|
|
21
|
+
related to mol_a)
|
|
22
|
+
separation (float, optional): set the separation of the molecules
|
|
23
|
+
(otherwise it will be calculated)
|
|
24
|
+
transform_ab (np.ndarray, optional): specify the transform from mol_a
|
|
25
|
+
to mol_b (otherwise it will be calculated)
|
|
26
|
+
frac_shift (np.ndarray, optional): specify the offset in fractions
|
|
27
|
+
of a unit cell, which combined with transform_ab will yield mol_b
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
seitz_b = None
|
|
31
|
+
symm_str = None
|
|
32
|
+
crystal_transform = False
|
|
33
|
+
|
|
34
|
+
def __init__(
|
|
35
|
+
self, mol_a, mol_b, separation=None, transform_ab=None, frac_shift=None
|
|
36
|
+
):
|
|
37
|
+
"""Initialize a Dimer."""
|
|
38
|
+
self.a = mol_a
|
|
39
|
+
self.b = mol_b
|
|
40
|
+
self.a_idx = self.a.properties.get("asym_mol_idx", 0)
|
|
41
|
+
self.b_idx = self.b.properties.get("asym_mol_idx", 0)
|
|
42
|
+
self.frac_shift = frac_shift
|
|
43
|
+
if "generator_symop" in self.a.properties:
|
|
44
|
+
self.symop_a = self.a.properties["generator_symop"]
|
|
45
|
+
|
|
46
|
+
if "generator_symop" in self.b.properties:
|
|
47
|
+
self.symop_b = self.b.properties["generator_symop"]
|
|
48
|
+
|
|
49
|
+
if separation is not None:
|
|
50
|
+
self.separation = separation
|
|
51
|
+
else:
|
|
52
|
+
self.separation = mol_a.distance_to(mol_b)
|
|
53
|
+
if transform_ab == "calculate":
|
|
54
|
+
self.calculate_transform()
|
|
55
|
+
else:
|
|
56
|
+
self.transform_ab = transform_ab
|
|
57
|
+
self.closest_separation = self.a.distance_to(self.b, method="nearest_atom")
|
|
58
|
+
self.centroid_separation = self.a.distance_to(self.b, method="centroid")
|
|
59
|
+
self.com_separation = self.a.distance_to(self.b, method="center_of_mass")
|
|
60
|
+
|
|
61
|
+
def calculate_transform(self):
|
|
62
|
+
"""Calculate the transform (if any) from mol_a to mol_b."""
|
|
63
|
+
from chmpy.util.num import kabsch_rotation_matrix
|
|
64
|
+
|
|
65
|
+
if len(self.a) != len(self.b):
|
|
66
|
+
self.transform_ab = None
|
|
67
|
+
return
|
|
68
|
+
|
|
69
|
+
if not np.all(self.a.atomic_numbers == self.b.atomic_numbers):
|
|
70
|
+
self.transform_ab = None
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
v_a = self.a.centroid
|
|
74
|
+
v_b = self.b.centroid
|
|
75
|
+
v_ab = v_b - v_a
|
|
76
|
+
pos_a = self.a.positions - v_a
|
|
77
|
+
pos_b = self.b.positions - v_b
|
|
78
|
+
R = kabsch_rotation_matrix(pos_b, pos_a)
|
|
79
|
+
self.transform_ab = (R, v_ab)
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
self.frac_shift is not None
|
|
83
|
+
and self.symop_a is not None
|
|
84
|
+
and self.symop_b is not None
|
|
85
|
+
):
|
|
86
|
+
self.crystal_transform = True
|
|
87
|
+
from chmpy.crystal.symmetry_operation import (
|
|
88
|
+
SymmetryOperation,
|
|
89
|
+
encode_symm_str,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
s_b = SymmetryOperation.from_integer_code(self.symop_b[0])
|
|
93
|
+
t_ab = np.zeros((4, 4))
|
|
94
|
+
t_ab[:3, 3] = self.frac_shift
|
|
95
|
+
self.seitz_b = s_b.seitz_matrix.copy()
|
|
96
|
+
self.seitz_b[:3, 3] += self.frac_shift
|
|
97
|
+
self.symm_str = encode_symm_str(self.seitz_b[:3, :3], self.seitz_b[:3, 3])
|
|
98
|
+
return self.transform_ab
|
|
99
|
+
|
|
100
|
+
def supermolecule(self):
|
|
101
|
+
return Molecule.from_arrays(
|
|
102
|
+
np.hstack((self.a.atomic_numbers, self.b.atomic_numbers)),
|
|
103
|
+
np.vstack((self.a.positions, self.b.positions)),
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
def scale_separation(self, scale_factor):
|
|
107
|
+
v_a = self.a.centroid
|
|
108
|
+
v_b = self.b.centroid
|
|
109
|
+
v_ab = v_b - v_a
|
|
110
|
+
self.b.positions -= v_ab
|
|
111
|
+
v_ab *= scale_factor
|
|
112
|
+
self.b.positions += v_ab
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def separations(self):
|
|
116
|
+
"The closest atom, centroid-centroid, and center of mass -"
|
|
117
|
+
"center of mass separations of mol_a and mol_b."
|
|
118
|
+
return np.array(
|
|
119
|
+
(self.closest_separation, self.centroid_separation, self.com_separation)
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def __eq__(self, other):
|
|
123
|
+
"""Return true if all separations are identical."""
|
|
124
|
+
return np.allclose(self.separations, other.separations)
|
|
125
|
+
|
|
126
|
+
def transform_string(self):
|
|
127
|
+
"""The transform from mol_a to mol_b as a string (e.g. x,-y,z)."""
|
|
128
|
+
if self.transform_ab is None:
|
|
129
|
+
return "none"
|
|
130
|
+
if self.crystal_transform:
|
|
131
|
+
return self.symm_str
|
|
132
|
+
return str(self.transform_ab)
|
|
133
|
+
|
|
134
|
+
def __repr__(self):
|
|
135
|
+
"""Represent the Dimer for a REPL or similar."""
|
|
136
|
+
return f"<Dimer: d={self.separation:.2f} symm={self.transform_string()}>"
|
chmpy/core/eeq.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.spatial.distance import pdist
|
|
3
|
+
from scipy.special import erf
|
|
4
|
+
|
|
5
|
+
from chmpy.util.unit import ANGSTROM_TO_BOHR
|
|
6
|
+
|
|
7
|
+
# Constants for EEQ method
|
|
8
|
+
# Copied from the C++ implementation (eeq.cpp)
|
|
9
|
+
# Maximum element number in the periodic table for which parameters are defined
|
|
10
|
+
MAX_ELEM = 87
|
|
11
|
+
|
|
12
|
+
# fmt: off
|
|
13
|
+
# Electronegativity parameters
|
|
14
|
+
# Stored as numpy arrays for efficient computation
|
|
15
|
+
CHI = np.array([
|
|
16
|
+
-1.0, 1.23695041, 1.26590957, 0.54341808, 0.99666991, 1.26691604,
|
|
17
|
+
1.40028282, 1.55819364, 1.56866440, 1.57540015, 1.15056627, 0.55936220,
|
|
18
|
+
0.72373742, 1.12910844, 1.12306840, 1.52672442, 1.40768172, 1.48154584,
|
|
19
|
+
1.31062963, 0.40374140, 0.75442607, 0.76482096, 0.98457281, 0.96702598,
|
|
20
|
+
1.05266584, 0.93274875, 1.04025281, 0.92738624, 1.07419210, 1.07900668,
|
|
21
|
+
1.04712861, 1.15018618, 1.15388455, 1.36313743, 1.36485106, 1.39801837,
|
|
22
|
+
1.18695346, 0.36273870, 0.58797255, 0.71961946, 0.96158233, 0.89585296,
|
|
23
|
+
0.81360499, 1.00794665, 0.92613682, 1.09152285, 1.14907070, 1.13508911,
|
|
24
|
+
1.08853785, 1.11005982, 1.12452195, 1.21642129, 1.36507125, 1.40340000,
|
|
25
|
+
1.16653482, 0.34125098, 0.58884173, 0.68441115, 0.56999999, 0.56999999,
|
|
26
|
+
0.56999999, 0.56999999, 0.56999999, 0.56999999, 0.56999999, 0.56999999,
|
|
27
|
+
0.56999999, 0.56999999, 0.56999999, 0.56999999, 0.56999999, 0.56999999,
|
|
28
|
+
0.87936784, 1.02761808, 0.93297476, 1.10172128, 0.97350071, 1.16695666,
|
|
29
|
+
1.23997927, 1.18464453, 1.14191734, 1.12334192, 1.01485321, 1.12950808,
|
|
30
|
+
1.30804834, 1.33689961, 1.27465977
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
# Hardness parameters
|
|
34
|
+
ETA = np.array([
|
|
35
|
+
-1.0, -0.35015861, 1.04121227, 0.09281243, 0.09412380,
|
|
36
|
+
0.26629137, 0.19408787, 0.05317918, 0.03151644, 0.32275132,
|
|
37
|
+
1.30996037, 0.24206510, 0.04147733, 0.11634126, 0.13155266,
|
|
38
|
+
0.15350650, 0.15250997, 0.17523529, 0.28774450, 0.42937314,
|
|
39
|
+
0.01896455, 0.07179178, -0.01121381, -0.03093370, 0.02716319,
|
|
40
|
+
-0.01843812, -0.15270393, -0.09192645, -0.13418723, -0.09861139,
|
|
41
|
+
0.18338109, 0.08299615, 0.11370033, 0.19005278, 0.10980677,
|
|
42
|
+
0.12327841, 0.25345554, 0.58615231, 0.16093861, 0.04548530,
|
|
43
|
+
-0.02478645, 0.01909943, 0.01402541, -0.03595279, 0.01137752,
|
|
44
|
+
-0.03697213, 0.08009416, 0.02274892, 0.12801822, -0.02078702,
|
|
45
|
+
0.05284319, 0.07581190, 0.09663758, 0.09547417, 0.07803344,
|
|
46
|
+
0.64913257, 0.15348654, 0.05054344, 0.11000000, 0.11000000,
|
|
47
|
+
0.11000000, 0.11000000, 0.11000000, 0.11000000, 0.11000000,
|
|
48
|
+
0.11000000, 0.11000000, 0.11000000, 0.11000000, 0.11000000,
|
|
49
|
+
0.11000000, 0.11000000, -0.02786741, 0.01057858, -0.03892226,
|
|
50
|
+
-0.04574364, -0.03874080, -0.03782372, -0.07046855, 0.09546597,
|
|
51
|
+
0.21953269, 0.02522348, 0.15263050, 0.08042611, 0.01878626,
|
|
52
|
+
0.08715453, 0.10500484
|
|
53
|
+
])
|
|
54
|
+
|
|
55
|
+
# CN scaling constant
|
|
56
|
+
KCN_PARAM = np.array([
|
|
57
|
+
-1.0, 0.04916110, 0.10937243, -0.12349591, -0.02665108,
|
|
58
|
+
-0.02631658, 0.06005196, 0.09279548, 0.11689703, 0.15704746,
|
|
59
|
+
0.07987901, -0.10002962, -0.07712863, -0.02170561, -0.04964052,
|
|
60
|
+
0.14250599, 0.07126660, 0.13682750, 0.14877121, -0.10219289,
|
|
61
|
+
-0.08979338, -0.08273597, -0.01754829, -0.02765460, -0.02558926,
|
|
62
|
+
-0.08010286, -0.04163215, -0.09369631, -0.03774117, -0.05759708,
|
|
63
|
+
0.02431998, -0.01056270, -0.02692862, 0.07657769, 0.06561608,
|
|
64
|
+
0.08006749, 0.14139200, -0.05351029, -0.06701705, -0.07377246,
|
|
65
|
+
-0.02927768, -0.03867291, -0.06929825, -0.04485293, -0.04800824,
|
|
66
|
+
-0.01484022, 0.07917502, 0.06619243, 0.02434095, -0.01505548,
|
|
67
|
+
-0.03030768, 0.01418235, 0.08953411, 0.08967527, 0.07277771,
|
|
68
|
+
-0.02129476, -0.06188828, -0.06568203, -0.11000000, -0.11000000,
|
|
69
|
+
-0.11000000, -0.11000000, -0.11000000, -0.11000000, -0.11000000,
|
|
70
|
+
-0.11000000, -0.11000000, -0.11000000, -0.11000000, -0.11000000,
|
|
71
|
+
-0.11000000, -0.11000000, -0.03585873, -0.03132400, -0.05902379,
|
|
72
|
+
-0.02827592, -0.07606260, -0.02123839, 0.03814822, 0.02146834,
|
|
73
|
+
0.01580538, -0.00894298, -0.05864876, -0.01817842, 0.07721851,
|
|
74
|
+
0.07936083, 0.05849285
|
|
75
|
+
])
|
|
76
|
+
|
|
77
|
+
# Charge widths
|
|
78
|
+
WIDTH = np.array([
|
|
79
|
+
-1.0, 0.55159092, 0.66205886, 0.90529132, 1.51710827, 2.86070364,
|
|
80
|
+
1.88862966, 1.32250290, 1.23166285, 1.77503721, 1.11955204, 1.28263182,
|
|
81
|
+
1.22344336, 1.70936266, 1.54075036, 1.38200579, 2.18849322, 1.36779065,
|
|
82
|
+
1.27039703, 1.64466502, 1.58859404, 1.65357953, 1.50021521, 1.30104175,
|
|
83
|
+
1.46301827, 1.32928147, 1.02766713, 1.02291377, 0.94343886, 1.14881311,
|
|
84
|
+
1.47080755, 1.76901636, 1.98724061, 2.41244711, 2.26739524, 2.95378999,
|
|
85
|
+
1.20807752, 1.65941046, 1.62733880, 1.61344972, 1.63220728, 1.60899928,
|
|
86
|
+
1.43501286, 1.54559205, 1.32663678, 1.37644152, 1.36051851, 1.23395526,
|
|
87
|
+
1.65734544, 1.53895240, 1.97542736, 1.97636542, 2.05432381, 3.80138135,
|
|
88
|
+
1.43893803, 1.75505957, 1.59815118, 1.76401732, 1.63999999, 1.63999999,
|
|
89
|
+
1.63999999, 1.63999999, 1.63999999, 1.63999999, 1.63999999, 1.63999999,
|
|
90
|
+
1.63999999, 1.63999999, 1.63999999, 1.63999999, 1.63999999, 1.63999999,
|
|
91
|
+
1.47055223, 1.81127084, 1.40189963, 1.54015481, 1.33721475, 1.57165422,
|
|
92
|
+
1.04815857, 1.78342098, 2.79106396, 1.78160840, 2.47588882, 2.37670734,
|
|
93
|
+
1.76613217, 2.66172302, 2.82773085
|
|
94
|
+
])
|
|
95
|
+
|
|
96
|
+
# covalent radii (taken from Pyykko and Atsumi, Chem. Eur. J. 15, 2009, 188-197)
|
|
97
|
+
# Values for metals decreased by 10%
|
|
98
|
+
COVALENT = np.array([
|
|
99
|
+
-1.0, 0.32, 0.46, 1.20, 0.94, 0.77, 0.75, 0.71, 0.63, 0.64, 0.67, 1.40,
|
|
100
|
+
1.25, 1.13, 1.04, 1.10, 1.02, 0.99, 0.96, 1.76, 1.54, 1.33, 1.22, 1.21,
|
|
101
|
+
1.10, 1.07, 1.04, 1.00, 0.99, 1.01, 1.09, 1.12, 1.09, 1.15, 1.10, 1.14,
|
|
102
|
+
1.17, 1.89, 1.67, 1.47, 1.39, 1.32, 1.24, 1.15, 1.13, 1.13, 1.08, 1.15,
|
|
103
|
+
1.23, 1.28, 1.26, 1.26, 1.23, 1.32, 1.31, 2.09, 1.76, 1.62, 1.47, 1.58,
|
|
104
|
+
1.57, 1.56, 1.55, 1.51, 1.52, 1.51, 1.50, 1.49, 1.49, 1.48, 1.53, 1.46,
|
|
105
|
+
1.37, 1.31, 1.23, 1.18, 1.16, 1.11, 1.12, 1.13, 1.32, 1.30, 1.30, 1.36,
|
|
106
|
+
1.31, 1.38, 1.42, 2.01, 1.81, 1.67, 1.58, 1.52, 1.53, 1.54, 1.55, 1.49,
|
|
107
|
+
1.49, 1.51, 1.51, 1.48, 1.50, 1.56, 1.58, 1.45, 1.41, 1.34, 1.29, 1.27,
|
|
108
|
+
1.21, 1.16, 1.15, 1.09, 1.22, 1.36, 1.43, 1.46, 1.58, 1.48, 1.57
|
|
109
|
+
])
|
|
110
|
+
|
|
111
|
+
COVALENT_D3 = 4.0 / 3.0 * COVALENT
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# fmt: on
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def calculate_coordination_numbers(atomic_numbers, positions):
|
|
118
|
+
"""
|
|
119
|
+
Calculate coordination numbers for all atoms in a molecule or crystal.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
atomic_numbers (np.ndarray): Array of atomic numbers for each atom
|
|
123
|
+
positions (np.ndarray): Array of atomic positions (shape: N x 3)
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
np.ndarray: Array of coordination numbers for each atom
|
|
127
|
+
"""
|
|
128
|
+
N = len(atomic_numbers)
|
|
129
|
+
kcn_value = 7.5 # Constant from the C++ implementation
|
|
130
|
+
|
|
131
|
+
cn = np.zeros(N)
|
|
132
|
+
cutoff = 25.0 # Cutoff distance in Angstroms squared
|
|
133
|
+
|
|
134
|
+
dists = pdist(positions) * ANGSTROM_TO_BOHR
|
|
135
|
+
|
|
136
|
+
i, j = np.triu_indices(N, k=1)
|
|
137
|
+
|
|
138
|
+
mask = dists <= cutoff
|
|
139
|
+
dists = dists[mask]
|
|
140
|
+
i_filt, j_filt = i[mask], j[mask]
|
|
141
|
+
|
|
142
|
+
rc = (
|
|
143
|
+
COVALENT_D3[atomic_numbers[i_filt]] + COVALENT_D3[atomic_numbers[j_filt]]
|
|
144
|
+
) * ANGSTROM_TO_BOHR
|
|
145
|
+
|
|
146
|
+
counts = 0.5 * (1.0 + erf(-kcn_value * (dists / rc - 1.0)))
|
|
147
|
+
|
|
148
|
+
np.add.at(cn, i_filt, counts)
|
|
149
|
+
np.add.at(cn, j_filt, counts)
|
|
150
|
+
|
|
151
|
+
return cn
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def build_a_matrix(atomic_numbers, positions):
|
|
155
|
+
"""
|
|
156
|
+
Build the A matrix for EEQ charge calculation.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
atomic_numbers (np.ndarray): Array of atomic numbers for each atom
|
|
160
|
+
positions (np.ndarray): Array of atomic positions (shape: N x 3)
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
np.ndarray: A matrix for the EEQ calculation
|
|
164
|
+
"""
|
|
165
|
+
N = len(atomic_numbers)
|
|
166
|
+
sqrt_pi_fac = np.sqrt(2.0 / np.pi)
|
|
167
|
+
A = np.zeros((N + 1, N + 1))
|
|
168
|
+
|
|
169
|
+
positions_bohr = positions * ANGSTROM_TO_BOHR
|
|
170
|
+
|
|
171
|
+
r = pdist(positions_bohr)
|
|
172
|
+
r2 = r**2
|
|
173
|
+
|
|
174
|
+
widths = WIDTH[atomic_numbers]
|
|
175
|
+
|
|
176
|
+
i, j = np.triu_indices(N, k=1)
|
|
177
|
+
|
|
178
|
+
ri_squared = widths[i] ** 2
|
|
179
|
+
rj_squared = widths[j] ** 2
|
|
180
|
+
gamma = 1.0 / (ri_squared + rj_squared)
|
|
181
|
+
|
|
182
|
+
values = erf(np.sqrt(r2 * gamma)) / np.sqrt(r2)
|
|
183
|
+
|
|
184
|
+
A_temp = np.zeros((N, N))
|
|
185
|
+
A_temp[i, j] = values
|
|
186
|
+
A_temp[j, i] = values # Mirror values (symmetric matrix)
|
|
187
|
+
|
|
188
|
+
A[:N, :N] = A_temp
|
|
189
|
+
|
|
190
|
+
diagonal_values = ETA[atomic_numbers] + sqrt_pi_fac / WIDTH[atomic_numbers]
|
|
191
|
+
np.fill_diagonal(A[:N, :N], diagonal_values)
|
|
192
|
+
|
|
193
|
+
A[N, :N] = 1.0
|
|
194
|
+
A[:N, N] = 1.0
|
|
195
|
+
A[N, N] = 0.0
|
|
196
|
+
|
|
197
|
+
return A
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def build_x_vector(atomic_numbers, cn, charge=0.0):
|
|
201
|
+
"""
|
|
202
|
+
Build the X vector for EEQ charge calculation.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
atomic_numbers (np.ndarray): Array of atomic numbers for each atom
|
|
206
|
+
cn (np.ndarray): Array of coordination numbers for each atom
|
|
207
|
+
charge (float): Total charge of the system
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
np.ndarray: X vector for the EEQ calculation
|
|
211
|
+
"""
|
|
212
|
+
N = atomic_numbers.shape[0]
|
|
213
|
+
eps = 1e-14 # Avoid singularity with 0
|
|
214
|
+
X = np.empty(N + 1)
|
|
215
|
+
X[:N] = -CHI[atomic_numbers] + cn * KCN_PARAM[atomic_numbers] / np.sqrt(cn + eps)
|
|
216
|
+
X[N] = charge
|
|
217
|
+
return X
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def calculate_eeq_charges(atomic_numbers, positions, charge=0.0):
|
|
221
|
+
"""
|
|
222
|
+
Calculate EEQ partial charges for a set of atoms.
|
|
223
|
+
|
|
224
|
+
Args:
|
|
225
|
+
atomic_numbers (np.ndarray): Array of atomic numbers for each atom
|
|
226
|
+
positions (np.ndarray): Array of atomic positions (shape: N x 3)
|
|
227
|
+
charge (float): Total charge of the system
|
|
228
|
+
|
|
229
|
+
Returns:
|
|
230
|
+
np.ndarray: Array of partial charges for each atom
|
|
231
|
+
"""
|
|
232
|
+
# Calculate coordination numbers
|
|
233
|
+
cn = calculate_coordination_numbers(atomic_numbers, positions)
|
|
234
|
+
A = build_a_matrix(atomic_numbers, positions)
|
|
235
|
+
X = build_x_vector(atomic_numbers, cn, charge)
|
|
236
|
+
|
|
237
|
+
Q = np.linalg.solve(A, X)
|
|
238
|
+
return Q[:-1]
|