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.
Files changed (207) hide show
  1. chmpy/__init__.py +15 -0
  2. chmpy/cmd/__init__.py +0 -0
  3. chmpy/cmd/convert.py +55 -0
  4. chmpy/core/__init__.py +4 -0
  5. chmpy/core/dimer.py +136 -0
  6. chmpy/core/eeq.py +238 -0
  7. chmpy/core/element.py +503 -0
  8. chmpy/core/molecule.py +1290 -0
  9. chmpy/core/wolf.py +52 -0
  10. chmpy/crystal/__init__.py +22 -0
  11. chmpy/crystal/asymmetric_unit.py +85 -0
  12. chmpy/crystal/atomic_graph.py +453 -0
  13. chmpy/crystal/crystal.py +1824 -0
  14. chmpy/crystal/crystal_state.py +314 -0
  15. chmpy/crystal/dimer_index.py +1260 -0
  16. chmpy/crystal/eeq_pbc.py +196 -0
  17. chmpy/crystal/fingerprint.py +203 -0
  18. chmpy/crystal/force_field.py +325 -0
  19. chmpy/crystal/io.py +538 -0
  20. chmpy/crystal/neighbor_list.py +338 -0
  21. chmpy/crystal/orbit.py +594 -0
  22. chmpy/crystal/pair_graph.py +600 -0
  23. chmpy/crystal/point_group.py +322 -0
  24. chmpy/crystal/sgdata.json +1 -0
  25. chmpy/crystal/shape_descriptors.py +290 -0
  26. chmpy/crystal/site_symmetry.py +178 -0
  27. chmpy/crystal/space_group.py +391 -0
  28. chmpy/crystal/space_group_table.py +276 -0
  29. chmpy/crystal/subgroup.py +1204 -0
  30. chmpy/crystal/surface.py +319 -0
  31. chmpy/crystal/symmetric_graph.py +597 -0
  32. chmpy/crystal/symmetry_finder.py +198 -0
  33. chmpy/crystal/symmetry_operation.py +496 -0
  34. chmpy/crystal/unit_cell.py +637 -0
  35. chmpy/crystal/wulff.py +285 -0
  36. chmpy/descriptors/__init__.py +25 -0
  37. chmpy/descriptors/symmetry_function_ani1.py +294 -0
  38. chmpy/exe/__init__.py +22 -0
  39. chmpy/exe/exe.py +217 -0
  40. chmpy/exe/gaussian.py +108 -0
  41. chmpy/exe/gulp.py +148 -0
  42. chmpy/exe/raspa.py +217 -0
  43. chmpy/exe/tonto.py +110 -0
  44. chmpy/exe/xtb.py +144 -0
  45. chmpy/ext/__init__.py +0 -0
  46. chmpy/ext/ase.py +146 -0
  47. chmpy/ext/charges.py +63 -0
  48. chmpy/ext/cosmo.py +105 -0
  49. chmpy/ext/crystal.py +67 -0
  50. chmpy/ext/cx.py +152 -0
  51. chmpy/ext/elastic_tensor.py +385 -0
  52. chmpy/ext/excitations.py +61 -0
  53. chmpy/ext/solvation_parameters.py +181 -0
  54. chmpy/ext/traj.py +27 -0
  55. chmpy/ext/vasp.py +78 -0
  56. chmpy/ff/__init__.py +34 -0
  57. chmpy/ff/lj_params.json +1444 -0
  58. chmpy/ff/params.py +373 -0
  59. chmpy/fmt/__init__.py +0 -0
  60. chmpy/fmt/aims.py +750 -0
  61. chmpy/fmt/ascii.py +57 -0
  62. chmpy/fmt/ase.py +0 -0
  63. chmpy/fmt/cif.py +328 -0
  64. chmpy/fmt/crystal17.py +95 -0
  65. chmpy/fmt/cube.py +102 -0
  66. chmpy/fmt/fchk.py +64 -0
  67. chmpy/fmt/gaussian_log.py +147 -0
  68. chmpy/fmt/gen.py +68 -0
  69. chmpy/fmt/gmf.py +34 -0
  70. chmpy/fmt/grd.py +26 -0
  71. chmpy/fmt/gulp.py +273 -0
  72. chmpy/fmt/mol2.py +118 -0
  73. chmpy/fmt/nwchem.py +30 -0
  74. chmpy/fmt/pdb.py +307 -0
  75. chmpy/fmt/raspa.py +347 -0
  76. chmpy/fmt/sdf.py +279 -0
  77. chmpy/fmt/shelx.py +159 -0
  78. chmpy/fmt/smiles.py +124 -0
  79. chmpy/fmt/tmol.py +69 -0
  80. chmpy/fmt/vasp.py +33 -0
  81. chmpy/fmt/xtb.py +82 -0
  82. chmpy/fmt/xyz_file.py +118 -0
  83. chmpy/graph/__init__.py +123 -0
  84. chmpy/graph/adjacency.py +285 -0
  85. chmpy/graph/aromaticity.py +462 -0
  86. chmpy/graph/bond_orders.py +399 -0
  87. chmpy/graph/canonicalization.py +367 -0
  88. chmpy/graph/formal_charges.py +285 -0
  89. chmpy/graph/rings.py +408 -0
  90. chmpy/graph/smiles_writer.py +530 -0
  91. chmpy/graph/stereochemistry.py +440 -0
  92. chmpy/graph/substructure.py +514 -0
  93. chmpy/interpolate/__init__.py +3 -0
  94. chmpy/interpolate/_backends.py +84 -0
  95. chmpy/interpolate/_radii.py +170 -0
  96. chmpy/interpolate/density.py +130 -0
  97. chmpy/interpolate/lerp.py +29 -0
  98. chmpy/interpolate/thakkar_interp.npz +0 -0
  99. chmpy/ints/__init__.py +0 -0
  100. chmpy/ints/lebedev.py +32 -0
  101. chmpy/ints/lebedev_grids.npz +0 -0
  102. chmpy/ints/solvation.py +76 -0
  103. chmpy/mc/__init__.py +3 -0
  104. chmpy/mc/_mc.py +110 -0
  105. chmpy/mc/_mc_numpy.py +316 -0
  106. chmpy/mc/lookup_tables.py +90 -0
  107. chmpy/opt/__init__.py +6 -0
  108. chmpy/opt/gulp.py +136 -0
  109. chmpy/opt/xtb.py +257 -0
  110. chmpy/sampling/__init__.py +50 -0
  111. chmpy/sampling/_lds.py +42 -0
  112. chmpy/sampling/_sobol.py +45 -0
  113. chmpy/shape/__init__.py +13 -0
  114. chmpy/shape/_invariants.py +173 -0
  115. chmpy/shape/_sht.py +324 -0
  116. chmpy/shape/assoc_legendre.py +56 -0
  117. chmpy/shape/convex_hull.py +74 -0
  118. chmpy/shape/reconstruct.py +111 -0
  119. chmpy/shape/shape_descriptors.py +228 -0
  120. chmpy/shape/sht.py +606 -0
  121. chmpy/shape/spherical_harmonics.py +109 -0
  122. chmpy/surface.py +142 -0
  123. chmpy/templates/__init__.py +42 -0
  124. chmpy/templates/crystal17.jinja2 +21 -0
  125. chmpy/templates/gaussian_scf.jinja2 +14 -0
  126. chmpy/templates/gulp.jinja2 +30 -0
  127. chmpy/templates/nwchem_input.jinja2 +19 -0
  128. chmpy/templates/tmol.jinja2 +18 -0
  129. chmpy/templates/tonto_pair_energy.jinja2 +23 -0
  130. chmpy/tests/__init__.py +9 -0
  131. chmpy/tests/core/__init__.py +0 -0
  132. chmpy/tests/core/test_eeq.py +168 -0
  133. chmpy/tests/core/test_element.py +69 -0
  134. chmpy/tests/core/test_molecule.py +86 -0
  135. chmpy/tests/crystal/__init__.py +0 -0
  136. chmpy/tests/crystal/test_asymmetric_unit.py +180 -0
  137. chmpy/tests/crystal/test_atomic_graph.py +278 -0
  138. chmpy/tests/crystal/test_crystal.py +263 -0
  139. chmpy/tests/crystal/test_crystal_state.py +273 -0
  140. chmpy/tests/crystal/test_dimer_index.py +557 -0
  141. chmpy/tests/crystal/test_eeq_pbc.py +92 -0
  142. chmpy/tests/crystal/test_fingerprint.py +149 -0
  143. chmpy/tests/crystal/test_neighbor_list.py +237 -0
  144. chmpy/tests/crystal/test_orbit.py +345 -0
  145. chmpy/tests/crystal/test_pair_graph.py +320 -0
  146. chmpy/tests/crystal/test_site_symmetry.py +178 -0
  147. chmpy/tests/crystal/test_space_group.py +181 -0
  148. chmpy/tests/crystal/test_space_group_table.py +204 -0
  149. chmpy/tests/crystal/test_subgroup.py +735 -0
  150. chmpy/tests/crystal/test_symmetric_graph.py +452 -0
  151. chmpy/tests/crystal/test_symmetry_finder.py +247 -0
  152. chmpy/tests/crystal/test_unit_cell.py +68 -0
  153. chmpy/tests/crystal/test_wulff.py +64 -0
  154. chmpy/tests/exe/__init__.py +0 -0
  155. chmpy/tests/exe/test_raspa.py +168 -0
  156. chmpy/tests/ext/__init__.py +0 -0
  157. chmpy/tests/ext/test_eem.py +18 -0
  158. chmpy/tests/ext/test_elastic_tensor.py +137 -0
  159. chmpy/tests/ff/__init__.py +0 -0
  160. chmpy/tests/ff/test_params.py +143 -0
  161. chmpy/tests/fmt/__init__.py +0 -0
  162. chmpy/tests/fmt/test_aims.py +534 -0
  163. chmpy/tests/fmt/test_gen.py +22 -0
  164. chmpy/tests/fmt/test_pdb.py +123 -0
  165. chmpy/tests/fmt/test_raspa.py +135 -0
  166. chmpy/tests/fmt/test_smiles.py +23 -0
  167. chmpy/tests/graph/__init__.py +1 -0
  168. chmpy/tests/graph/test_adjacency.py +217 -0
  169. chmpy/tests/graph/test_aromaticity.py +338 -0
  170. chmpy/tests/graph/test_bond_orders.py +297 -0
  171. chmpy/tests/graph/test_canonicalization.py +331 -0
  172. chmpy/tests/graph/test_integration.py +151 -0
  173. chmpy/tests/graph/test_rings.py +315 -0
  174. chmpy/tests/graph/test_smiles_writer.py +277 -0
  175. chmpy/tests/graph/test_stereochemistry.py +283 -0
  176. chmpy/tests/graph/test_substructure.py +300 -0
  177. chmpy/tests/promolecule/__init__.py +0 -0
  178. chmpy/tests/promolecule/test_density.py +99 -0
  179. chmpy/tests/promolecule/test_surface.py +25 -0
  180. chmpy/tests/sampling/test_quasirandom.py +37 -0
  181. chmpy/tests/shape/__init__.py +0 -0
  182. chmpy/tests/shape/test_shape_descriptors.py +36 -0
  183. chmpy/tests/shape/test_sht.py +310 -0
  184. chmpy/tests/shape/test_spherical_harmonics.py +52 -0
  185. chmpy/tests/test_files/DB09563.sdf +160 -0
  186. chmpy/tests/test_files/HXACAN01.pdb +47 -0
  187. chmpy/tests/test_files/acetic_acid.cif +31 -0
  188. chmpy/tests/test_files/acetic_acid.res +20 -0
  189. chmpy/tests/test_files/example.gen +82 -0
  190. chmpy/tests/test_files/iceII.cif +61 -0
  191. chmpy/tests/test_files/r3c_example.cif +106 -0
  192. chmpy/tests/test_files/water.xyz +6 -0
  193. chmpy/util/__init__.py +3 -0
  194. chmpy/util/color.py +54 -0
  195. chmpy/util/dict.py +69 -0
  196. chmpy/util/exe.py +40 -0
  197. chmpy/util/mesh.py +92 -0
  198. chmpy/util/num.py +230 -0
  199. chmpy/util/path.py +92 -0
  200. chmpy/util/text.py +118 -0
  201. chmpy/util/unit.py +82 -0
  202. chmpy/util/util.py +0 -0
  203. chmpy-1.2.0.dist-info/METADATA +153 -0
  204. chmpy-1.2.0.dist-info/RECORD +207 -0
  205. chmpy-1.2.0.dist-info/WHEEL +5 -0
  206. chmpy-1.2.0.dist-info/licenses/LICENSE.txt +49 -0
  207. 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
@@ -0,0 +1,4 @@
1
+ from .element import Element
2
+ from .molecule import Molecule
3
+
4
+ __all__ = ["Element", "Molecule"]
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]