MultiOptPy 1.20.2__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 (246) hide show
  1. multioptpy/Calculator/__init__.py +0 -0
  2. multioptpy/Calculator/ase_calculation_tools.py +424 -0
  3. multioptpy/Calculator/ase_tools/__init__.py +0 -0
  4. multioptpy/Calculator/ase_tools/fairchem.py +28 -0
  5. multioptpy/Calculator/ase_tools/gamess.py +19 -0
  6. multioptpy/Calculator/ase_tools/gaussian.py +165 -0
  7. multioptpy/Calculator/ase_tools/mace.py +28 -0
  8. multioptpy/Calculator/ase_tools/mopac.py +19 -0
  9. multioptpy/Calculator/ase_tools/nwchem.py +31 -0
  10. multioptpy/Calculator/ase_tools/orca.py +22 -0
  11. multioptpy/Calculator/ase_tools/pygfn0.py +37 -0
  12. multioptpy/Calculator/dxtb_calculation_tools.py +344 -0
  13. multioptpy/Calculator/emt_calculation_tools.py +458 -0
  14. multioptpy/Calculator/gpaw_calculation_tools.py +183 -0
  15. multioptpy/Calculator/lj_calculation_tools.py +314 -0
  16. multioptpy/Calculator/psi4_calculation_tools.py +334 -0
  17. multioptpy/Calculator/pwscf_calculation_tools.py +189 -0
  18. multioptpy/Calculator/pyscf_calculation_tools.py +327 -0
  19. multioptpy/Calculator/sqm1_calculation_tools.py +611 -0
  20. multioptpy/Calculator/sqm2_calculation_tools.py +376 -0
  21. multioptpy/Calculator/tblite_calculation_tools.py +352 -0
  22. multioptpy/Calculator/tersoff_calculation_tools.py +818 -0
  23. multioptpy/Constraint/__init__.py +0 -0
  24. multioptpy/Constraint/constraint_condition.py +834 -0
  25. multioptpy/Coordinate/__init__.py +0 -0
  26. multioptpy/Coordinate/polar_coordinate.py +199 -0
  27. multioptpy/Coordinate/redundant_coordinate.py +638 -0
  28. multioptpy/IRC/__init__.py +0 -0
  29. multioptpy/IRC/converge_criteria.py +28 -0
  30. multioptpy/IRC/dvv.py +544 -0
  31. multioptpy/IRC/euler.py +439 -0
  32. multioptpy/IRC/hpc.py +564 -0
  33. multioptpy/IRC/lqa.py +540 -0
  34. multioptpy/IRC/modekill.py +662 -0
  35. multioptpy/IRC/rk4.py +579 -0
  36. multioptpy/Interpolation/__init__.py +0 -0
  37. multioptpy/Interpolation/adaptive_interpolation.py +283 -0
  38. multioptpy/Interpolation/binomial_interpolation.py +179 -0
  39. multioptpy/Interpolation/geodesic_interpolation.py +785 -0
  40. multioptpy/Interpolation/interpolation.py +156 -0
  41. multioptpy/Interpolation/linear_interpolation.py +473 -0
  42. multioptpy/Interpolation/savitzky_golay_interpolation.py +252 -0
  43. multioptpy/Interpolation/spline_interpolation.py +353 -0
  44. multioptpy/MD/__init__.py +0 -0
  45. multioptpy/MD/thermostat.py +185 -0
  46. multioptpy/MEP/__init__.py +0 -0
  47. multioptpy/MEP/pathopt_bneb_force.py +443 -0
  48. multioptpy/MEP/pathopt_dmf_force.py +448 -0
  49. multioptpy/MEP/pathopt_dneb_force.py +130 -0
  50. multioptpy/MEP/pathopt_ewbneb_force.py +207 -0
  51. multioptpy/MEP/pathopt_gpneb_force.py +512 -0
  52. multioptpy/MEP/pathopt_lup_force.py +113 -0
  53. multioptpy/MEP/pathopt_neb_force.py +225 -0
  54. multioptpy/MEP/pathopt_nesb_force.py +205 -0
  55. multioptpy/MEP/pathopt_om_force.py +153 -0
  56. multioptpy/MEP/pathopt_qsm_force.py +174 -0
  57. multioptpy/MEP/pathopt_qsmv2_force.py +304 -0
  58. multioptpy/ModelFunction/__init__.py +7 -0
  59. multioptpy/ModelFunction/avoiding_model_function.py +29 -0
  60. multioptpy/ModelFunction/binary_image_ts_search_model_function.py +47 -0
  61. multioptpy/ModelFunction/conical_model_function.py +26 -0
  62. multioptpy/ModelFunction/opt_meci.py +50 -0
  63. multioptpy/ModelFunction/opt_mesx.py +47 -0
  64. multioptpy/ModelFunction/opt_mesx_2.py +49 -0
  65. multioptpy/ModelFunction/seam_model_function.py +27 -0
  66. multioptpy/ModelHessian/__init__.py +0 -0
  67. multioptpy/ModelHessian/approx_hessian.py +147 -0
  68. multioptpy/ModelHessian/calc_params.py +227 -0
  69. multioptpy/ModelHessian/fischer.py +236 -0
  70. multioptpy/ModelHessian/fischerd3.py +360 -0
  71. multioptpy/ModelHessian/fischerd4.py +398 -0
  72. multioptpy/ModelHessian/gfn0xtb.py +633 -0
  73. multioptpy/ModelHessian/gfnff.py +709 -0
  74. multioptpy/ModelHessian/lindh.py +165 -0
  75. multioptpy/ModelHessian/lindh2007d2.py +707 -0
  76. multioptpy/ModelHessian/lindh2007d3.py +822 -0
  77. multioptpy/ModelHessian/lindh2007d4.py +1030 -0
  78. multioptpy/ModelHessian/morse.py +106 -0
  79. multioptpy/ModelHessian/schlegel.py +144 -0
  80. multioptpy/ModelHessian/schlegeld3.py +322 -0
  81. multioptpy/ModelHessian/schlegeld4.py +559 -0
  82. multioptpy/ModelHessian/shortrange.py +346 -0
  83. multioptpy/ModelHessian/swartd2.py +496 -0
  84. multioptpy/ModelHessian/swartd3.py +706 -0
  85. multioptpy/ModelHessian/swartd4.py +918 -0
  86. multioptpy/ModelHessian/tshess.py +40 -0
  87. multioptpy/Optimizer/QHAdam.py +61 -0
  88. multioptpy/Optimizer/__init__.py +0 -0
  89. multioptpy/Optimizer/abc_fire.py +83 -0
  90. multioptpy/Optimizer/adabelief.py +58 -0
  91. multioptpy/Optimizer/adabound.py +68 -0
  92. multioptpy/Optimizer/adadelta.py +65 -0
  93. multioptpy/Optimizer/adaderivative.py +56 -0
  94. multioptpy/Optimizer/adadiff.py +68 -0
  95. multioptpy/Optimizer/adafactor.py +70 -0
  96. multioptpy/Optimizer/adam.py +65 -0
  97. multioptpy/Optimizer/adamax.py +62 -0
  98. multioptpy/Optimizer/adamod.py +83 -0
  99. multioptpy/Optimizer/adamw.py +65 -0
  100. multioptpy/Optimizer/adiis.py +523 -0
  101. multioptpy/Optimizer/afire_neb.py +282 -0
  102. multioptpy/Optimizer/block_hessian_update.py +709 -0
  103. multioptpy/Optimizer/c2diis.py +491 -0
  104. multioptpy/Optimizer/component_wise_scaling.py +405 -0
  105. multioptpy/Optimizer/conjugate_gradient.py +82 -0
  106. multioptpy/Optimizer/conjugate_gradient_neb.py +345 -0
  107. multioptpy/Optimizer/coordinate_locking.py +405 -0
  108. multioptpy/Optimizer/dic_rsirfo.py +1015 -0
  109. multioptpy/Optimizer/ediis.py +417 -0
  110. multioptpy/Optimizer/eve.py +76 -0
  111. multioptpy/Optimizer/fastadabelief.py +61 -0
  112. multioptpy/Optimizer/fire.py +77 -0
  113. multioptpy/Optimizer/fire2.py +249 -0
  114. multioptpy/Optimizer/fire_neb.py +92 -0
  115. multioptpy/Optimizer/gan_step.py +486 -0
  116. multioptpy/Optimizer/gdiis.py +609 -0
  117. multioptpy/Optimizer/gediis.py +203 -0
  118. multioptpy/Optimizer/geodesic_step.py +433 -0
  119. multioptpy/Optimizer/gpmin.py +633 -0
  120. multioptpy/Optimizer/gpr_step.py +364 -0
  121. multioptpy/Optimizer/gradientdescent.py +78 -0
  122. multioptpy/Optimizer/gradientdescent_neb.py +52 -0
  123. multioptpy/Optimizer/hessian_update.py +433 -0
  124. multioptpy/Optimizer/hybrid_rfo.py +998 -0
  125. multioptpy/Optimizer/kdiis.py +625 -0
  126. multioptpy/Optimizer/lars.py +21 -0
  127. multioptpy/Optimizer/lbfgs.py +253 -0
  128. multioptpy/Optimizer/lbfgs_neb.py +355 -0
  129. multioptpy/Optimizer/linesearch.py +236 -0
  130. multioptpy/Optimizer/lookahead.py +40 -0
  131. multioptpy/Optimizer/nadam.py +64 -0
  132. multioptpy/Optimizer/newton.py +200 -0
  133. multioptpy/Optimizer/prodigy.py +70 -0
  134. multioptpy/Optimizer/purtubation.py +16 -0
  135. multioptpy/Optimizer/quickmin_neb.py +245 -0
  136. multioptpy/Optimizer/radam.py +75 -0
  137. multioptpy/Optimizer/rfo_neb.py +302 -0
  138. multioptpy/Optimizer/ric_rfo.py +842 -0
  139. multioptpy/Optimizer/rl_step.py +627 -0
  140. multioptpy/Optimizer/rmspropgrave.py +65 -0
  141. multioptpy/Optimizer/rsirfo.py +1647 -0
  142. multioptpy/Optimizer/rsprfo.py +1056 -0
  143. multioptpy/Optimizer/sadam.py +60 -0
  144. multioptpy/Optimizer/samsgrad.py +63 -0
  145. multioptpy/Optimizer/tr_lbfgs.py +678 -0
  146. multioptpy/Optimizer/trim.py +273 -0
  147. multioptpy/Optimizer/trust_radius.py +207 -0
  148. multioptpy/Optimizer/trust_radius_neb.py +121 -0
  149. multioptpy/Optimizer/yogi.py +60 -0
  150. multioptpy/OtherMethod/__init__.py +0 -0
  151. multioptpy/OtherMethod/addf.py +1150 -0
  152. multioptpy/OtherMethod/dimer.py +895 -0
  153. multioptpy/OtherMethod/elastic_image_pair.py +629 -0
  154. multioptpy/OtherMethod/modelfunction.py +456 -0
  155. multioptpy/OtherMethod/newton_traj.py +454 -0
  156. multioptpy/OtherMethod/twopshs.py +1095 -0
  157. multioptpy/PESAnalyzer/__init__.py +0 -0
  158. multioptpy/PESAnalyzer/calc_irc_curvature.py +125 -0
  159. multioptpy/PESAnalyzer/cmds_analysis.py +152 -0
  160. multioptpy/PESAnalyzer/koopman_analysis.py +268 -0
  161. multioptpy/PESAnalyzer/pca_analysis.py +314 -0
  162. multioptpy/Parameters/__init__.py +0 -0
  163. multioptpy/Parameters/atomic_mass.py +20 -0
  164. multioptpy/Parameters/atomic_number.py +22 -0
  165. multioptpy/Parameters/covalent_radii.py +44 -0
  166. multioptpy/Parameters/d2.py +61 -0
  167. multioptpy/Parameters/d3.py +63 -0
  168. multioptpy/Parameters/d4.py +103 -0
  169. multioptpy/Parameters/dreiding.py +34 -0
  170. multioptpy/Parameters/gfn0xtb_param.py +137 -0
  171. multioptpy/Parameters/gfnff_param.py +315 -0
  172. multioptpy/Parameters/gnb.py +104 -0
  173. multioptpy/Parameters/parameter.py +22 -0
  174. multioptpy/Parameters/uff.py +72 -0
  175. multioptpy/Parameters/unit_values.py +20 -0
  176. multioptpy/Potential/AFIR_potential.py +55 -0
  177. multioptpy/Potential/LJ_repulsive_potential.py +345 -0
  178. multioptpy/Potential/__init__.py +0 -0
  179. multioptpy/Potential/anharmonic_keep_potential.py +28 -0
  180. multioptpy/Potential/asym_elllipsoidal_potential.py +718 -0
  181. multioptpy/Potential/electrostatic_potential.py +69 -0
  182. multioptpy/Potential/flux_potential.py +30 -0
  183. multioptpy/Potential/gaussian_potential.py +101 -0
  184. multioptpy/Potential/idpp.py +516 -0
  185. multioptpy/Potential/keep_angle_potential.py +146 -0
  186. multioptpy/Potential/keep_dihedral_angle_potential.py +105 -0
  187. multioptpy/Potential/keep_outofplain_angle_potential.py +70 -0
  188. multioptpy/Potential/keep_potential.py +99 -0
  189. multioptpy/Potential/mechano_force_potential.py +74 -0
  190. multioptpy/Potential/nanoreactor_potential.py +52 -0
  191. multioptpy/Potential/potential.py +896 -0
  192. multioptpy/Potential/spacer_model_potential.py +221 -0
  193. multioptpy/Potential/switching_potential.py +258 -0
  194. multioptpy/Potential/universal_potential.py +34 -0
  195. multioptpy/Potential/value_range_potential.py +36 -0
  196. multioptpy/Potential/void_point_potential.py +25 -0
  197. multioptpy/SQM/__init__.py +0 -0
  198. multioptpy/SQM/sqm1/__init__.py +0 -0
  199. multioptpy/SQM/sqm1/sqm1_core.py +1792 -0
  200. multioptpy/SQM/sqm2/__init__.py +0 -0
  201. multioptpy/SQM/sqm2/calc_tools.py +95 -0
  202. multioptpy/SQM/sqm2/sqm2_basis.py +850 -0
  203. multioptpy/SQM/sqm2/sqm2_bond.py +119 -0
  204. multioptpy/SQM/sqm2/sqm2_core.py +303 -0
  205. multioptpy/SQM/sqm2/sqm2_data.py +1229 -0
  206. multioptpy/SQM/sqm2/sqm2_disp.py +65 -0
  207. multioptpy/SQM/sqm2/sqm2_eeq.py +243 -0
  208. multioptpy/SQM/sqm2/sqm2_overlapint.py +704 -0
  209. multioptpy/SQM/sqm2/sqm2_qm.py +578 -0
  210. multioptpy/SQM/sqm2/sqm2_rep.py +66 -0
  211. multioptpy/SQM/sqm2/sqm2_srb.py +70 -0
  212. multioptpy/Thermo/__init__.py +0 -0
  213. multioptpy/Thermo/normal_mode_analyzer.py +865 -0
  214. multioptpy/Utils/__init__.py +0 -0
  215. multioptpy/Utils/bond_connectivity.py +264 -0
  216. multioptpy/Utils/calc_tools.py +884 -0
  217. multioptpy/Utils/oniom.py +96 -0
  218. multioptpy/Utils/pbc.py +48 -0
  219. multioptpy/Utils/riemann_curvature.py +208 -0
  220. multioptpy/Utils/symmetry_analyzer.py +482 -0
  221. multioptpy/Visualization/__init__.py +0 -0
  222. multioptpy/Visualization/visualization.py +156 -0
  223. multioptpy/WFAnalyzer/MO_analysis.py +104 -0
  224. multioptpy/WFAnalyzer/__init__.py +0 -0
  225. multioptpy/Wrapper/__init__.py +0 -0
  226. multioptpy/Wrapper/autots.py +1239 -0
  227. multioptpy/Wrapper/ieip_wrapper.py +93 -0
  228. multioptpy/Wrapper/md_wrapper.py +92 -0
  229. multioptpy/Wrapper/neb_wrapper.py +94 -0
  230. multioptpy/Wrapper/optimize_wrapper.py +76 -0
  231. multioptpy/__init__.py +5 -0
  232. multioptpy/entrypoints.py +916 -0
  233. multioptpy/fileio.py +660 -0
  234. multioptpy/ieip.py +340 -0
  235. multioptpy/interface.py +1086 -0
  236. multioptpy/irc.py +529 -0
  237. multioptpy/moleculardynamics.py +432 -0
  238. multioptpy/neb.py +1267 -0
  239. multioptpy/optimization.py +1553 -0
  240. multioptpy/optimizer.py +709 -0
  241. multioptpy-1.20.2.dist-info/METADATA +438 -0
  242. multioptpy-1.20.2.dist-info/RECORD +246 -0
  243. multioptpy-1.20.2.dist-info/WHEEL +5 -0
  244. multioptpy-1.20.2.dist-info/entry_points.txt +9 -0
  245. multioptpy-1.20.2.dist-info/licenses/LICENSE +674 -0
  246. multioptpy-1.20.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,850 @@
1
+ import numpy as np
2
+
3
+
4
+ from multioptpy.SQM.sqm2.calc_tools import factorial2, dfactorial
5
+
6
+ class BasisSet:
7
+ def __init__(self, element_list, param):
8
+ self.element_list = element_list
9
+ self.param = param
10
+
11
+ # --- Pre-calculate atom properties ---
12
+ # These properties depend only on the element_list,
13
+ # so they can be set once.
14
+ self.nshells_list = []
15
+ self.ang_shells_list = []
16
+ self.principal_qn_list = []
17
+ self.slater_exponents_list = []
18
+ self.self_energy_list = []
19
+ self.kCN_list = []
20
+ self.en_shell_list = []
21
+ self.atomic_radius_list = []
22
+ self.ref_occ_list = []
23
+
24
+ for atn in self.element_list:
25
+ self.nshells_list.append(param.nShell[atn])
26
+ self.ang_shells_list.append(param.angShell[atn])
27
+ self.principal_qn_list.append(param.principalQuantumNumber[atn])
28
+ self.slater_exponents_list.append(param.slaterExponent[atn])
29
+ self.self_energy_list.append(param.selfEnergy[atn])
30
+ self.kCN_list.append(param.kCN[atn])
31
+ self.en_shell_list.append(param.electronegativity[atn])
32
+ self.atomic_radius_list.append(param.atomicRad[atn])
33
+ self.ref_occ_list.append(param.referenceOcc[atn])
34
+
35
+ self.is_tm_list, self.is_g11_element_list = self.check_transition_metals()
36
+
37
+ # --- Run the main basis setup ---
38
+ self._set_basis(param)
39
+ return
40
+
41
+ def _dim_basis(self, param):
42
+ """
43
+ Calculates the total number of shells (nshell), AOs (nao), and CGFs (nbf).
44
+ Corresponds to Fortran subroutine: dim_basis
45
+ """
46
+ n_atoms = len(self.element_list)
47
+ nao = 0
48
+ nbf = 0
49
+ nshell = 0
50
+
51
+ for i in range(n_atoms):
52
+ atn = self.element_list[i] # 0-indexed
53
+ k = 0
54
+
55
+ # Directly reference nShell and angShell from param
56
+ n_shell_for_atom = param.nShell[atn]
57
+ ang_shell_for_atom = param.angShell[atn]
58
+
59
+ for j in range(n_shell_for_atom):
60
+ l = ang_shell_for_atom[j]
61
+ k += 1
62
+ nshell += 1
63
+ if l == 0: # s
64
+ nbf += 1
65
+ nao += 1
66
+ elif l == 1: # p
67
+ nbf += 3
68
+ nao += 3
69
+ elif l == 2: # d
70
+ nbf += 6 # 6 Cartesian CGFs
71
+ nao += 5 # 5 spherical AOs
72
+ elif l == 3: # f
73
+ nbf += 10 # 10 Cartesian CGFs
74
+ nao += 7 # 7 spherical AOs
75
+ elif l == 4: # g
76
+ nbf += 15
77
+ nao += 9
78
+ if k == 0:
79
+ raise Exception(f"No basis found for atom {i} Z={atn+1}")
80
+
81
+ return nshell, nao, nbf
82
+
83
+ def _get_max_shells_per_atom(self):
84
+ """
85
+ Returns the maximum number of shells for any atom in the system.
86
+ Used for allocating caoshell/saoshell arrays.
87
+ """
88
+ max_shells = 0
89
+ for i in range(len(self.element_list)):
90
+ n_shells = self.nshells_list[i]
91
+ if n_shells > max_shells:
92
+ max_shells = n_shells
93
+ return max_shells
94
+
95
+ def _set_basis(self, param):
96
+ """
97
+ Main basis set setup routine.
98
+ Corresponds to Fortran subroutine: newBasisset
99
+ """
100
+ n_atoms = len(self.element_list)
101
+
102
+ self.basis = {}
103
+
104
+ # --- Global Counts (calculated by _dim_basis) ---
105
+ n_shell_total, n_ao_total, n_cgf_total = self._dim_basis(param)
106
+ max_shells_per_atom = self._get_max_shells_per_atom()
107
+
108
+ self.basis["number_of_atoms"] = n_atoms
109
+ self.basis["number_of_cgf"] = n_cgf_total # Corresponds to: nbf
110
+ self.basis["number_of_ao"] = n_ao_total # Corresponds to: nao
111
+ self.basis["number_of_shells"] = n_shell_total # Corresponds to: nshell
112
+ self.basis["total_number_of_primitives"] = 0 # Final 'ipr' value
113
+
114
+ # --- Per-Atom Indices ---
115
+ # Corresponds to Fortran: shells(2,n), fila(2,n), fila2(2,n)
116
+ self.basis["atom_shells_map"] = np.zeros((n_atoms, 2), dtype=int) # [iat, 0]=start, [iat, 1]=end
117
+ self.basis["atom_cgf_map"] = np.zeros((n_atoms, 2), dtype=int) # [iat, 0]=start, [iat, 1]=end (fila)
118
+ self.basis["atom_ao_map"] = np.zeros((n_atoms, 2), dtype=int) # [iat, 0]=start, [iat, 1]=end (fila2)
119
+
120
+ # --- Per-Atom Per-Shell Mappings (NEW - corresponds to caoshell/saoshell) ---
121
+ # caoshell(m, iat) = CGF index at start of shell m for atom iat
122
+ # saoshell(m, iat) = AO index at start of shell m for atom iat
123
+ # Using shape (n_atoms, max_shells_per_atom) with -1 for unused slots
124
+ self.basis["atom_shell_cgf_offset"] = np.full((n_atoms, max_shells_per_atom), -1, dtype=int) # caoshell
125
+ self.basis["atom_shell_ao_offset"] = np.full((n_atoms, max_shells_per_atom), -1, dtype=int) # saoshell
126
+
127
+ # --- Per-Shell Data (Lists) ---
128
+ self.basis["shell_amqn_list"] = [] # lsh(ish)
129
+ self.basis["shell_atom_list"] = [] # ash(ish)
130
+ self.basis["shell_cgf_map"] = [] # sh2bf(1:2, ish) -> [start, count]
131
+ self.basis["shell_ao_map"] = [] # sh2ao(1:2, ish) -> [start, count]
132
+ self.basis["shell_level_list"] = [] # level(ish)
133
+ self.basis["shell_zeta_list"] = [] # zeta(ish)
134
+ self.basis["shell_valence_flag_list"] = [] # valsh(ish)
135
+ self.basis["shell_min_alpha_list"] = [] # minalp(ish)
136
+
137
+ # --- Per-CGF (Contracted Gaussian Function / Cartesian BF) Data (Lists) ---
138
+ self.basis["cgf_primitive_start_idx_list"] = [] # primcount(ibf)
139
+ self.basis["cgf_valence_flag_list"] = [] # valao(ibf)
140
+ self.basis["cgf_atom_list"] = [] # aoat(ibf)
141
+ self.basis["cgf_type_id_list"] = [] # lao(ibf)
142
+ self.basis["cgf_primitive_count_list"] = [] # nprim(ibf)
143
+ self.basis["cgf_energy_list"] = [] # hdiag(ibf)
144
+
145
+ # --- Per-AO (Spherical Atomic Orbital) Data (Lists) ---
146
+ self.basis["ao_valence_flag_list"] = [] # valao2(iao)
147
+ self.basis["ao_atom_list"] = [] # aoat2(iao)
148
+ self.basis["ao_type_id_list"] = [] # lao2(iao)
149
+ self.basis["ao_energy_list"] = [] # hdiag2(iao)
150
+ self.basis["ao_zeta_list"] = [] # aoexp(iao)
151
+ self.basis["ao_shell_list"] = [] # ao2sh(iao)
152
+
153
+ # --- Per-PGF (Primitive Gaussian Function) Data (Lists) ---
154
+ self.basis["primitive_alpha_list"] = [] # alp(ipr)
155
+ self.basis["primitive_coeff_list"] = [] # cont(ipr)
156
+
157
+ # --- Transformation factors for d/f functions ---
158
+ # Corresponds to Fortran trafo arrays in set_d_function and set_f_function
159
+ d_trafo = { 5: 1.0, 6: 1.0, 7: 1.0, 8: np.sqrt(3.0), 9: np.sqrt(3.0), 10: np.sqrt(3.0) }
160
+ f_trafo = { 11: 1.0, 12: 1.0, 13: 1.0, 14: np.sqrt(5.0), 15: np.sqrt(5.0),
161
+ 16: np.sqrt(5.0), 17: np.sqrt(5.0), 18: np.sqrt(5.0), 19: np.sqrt(5.0),
162
+ 20: np.sqrt(15.0) }
163
+
164
+ # --- Global 0-indexed Counters ---
165
+ ibf = 0 # CGF counter
166
+ iao = 0 # AO counter
167
+ ipr = 0 # Primitive counter
168
+ ish = 0 # Shell counter
169
+
170
+ # === Main Loop (Corresponds to: atoms: do iat=1,n) ===
171
+ for iat in range(n_atoms):
172
+ atn = self.element_list[iat] # 0-indexed atomic number
173
+ ati = atn + 1 # 1-indexed atomic number (for H/He checks)
174
+
175
+ # These are held within the iat loop and used for diffuse s processing
176
+ nprim_s_val = 0
177
+ alpha_s_val = np.array([])
178
+ coeff_s_val = np.array([])
179
+
180
+ # Set Per-Atom Start Indices
181
+ # Corresponds to: basis%shells(1,iat)=ish+1, basis%fila(1,iat)=ibf+1, basis%fila2(1,iat)=iao+1
182
+ self.basis["atom_shells_map"][iat, 0] = ish
183
+ self.basis["atom_cgf_map"][iat, 0] = ibf
184
+ self.basis["atom_ao_map"][iat, 0] = iao
185
+
186
+ # Corresponds to: shells: do m=1,xtbData%nShell(ati)
187
+ nshell_i = self.nshells_list[iat]
188
+ for m in range(nshell_i):
189
+ # --- Get Shell Parameters ---
190
+ l = self.ang_shells_list[iat][m]
191
+ npq = self.principal_qn_list[iat][m]
192
+ level = self.self_energy_list[iat][m]
193
+ zeta = self.slater_exponents_list[iat][m]
194
+
195
+ valao = self.ref_occ_list[iat][m] # The valence flag
196
+ is_valence = (valao != 0)
197
+
198
+ current_nprim_or_R = self.get_number_of_primitives(atn, l, npq, is_valence=is_valence)
199
+
200
+ ibf_start_shell = ibf
201
+ iao_start_shell = iao
202
+
203
+ # --- Store Per-Atom Per-Shell Offsets (caoshell/saoshell) ---
204
+ self.basis["atom_shell_cgf_offset"][iat, m] = ibf
205
+ self.basis["atom_shell_ao_offset"][iat, m] = iao
206
+
207
+ # --- Store Per-Shell Data (at index ish) ---
208
+ self.basis["shell_amqn_list"].append(l)
209
+ self.basis["shell_atom_list"].append(iat)
210
+ self.basis["shell_cgf_map"].append([ibf_start_shell, 0]) # [start, count]
211
+ self.basis["shell_ao_map"].append([iao_start_shell, 0]) # [start, count]
212
+ self.basis["shell_level_list"].append(level)
213
+ self.basis["shell_zeta_list"].append(zeta)
214
+ self.basis["shell_valence_flag_list"].append(valao)
215
+
216
+ min_alpha_for_shell = np.inf
217
+
218
+ # === Process Shells by Type ===
219
+
220
+ # --- H-He s (Valence) ---
221
+ if l == 0 and ati <= 2 and is_valence:
222
+
223
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
224
+ min_alpha_for_shell = np.min(alpha)
225
+
226
+ nprim_s_val = current_nprim_or_R
227
+ alpha_s_val = alpha.copy()
228
+ coeff_s_val = coeff.copy()
229
+
230
+ # Add CGF (1)
231
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
232
+ self.basis["cgf_valence_flag_list"].append(valao)
233
+ self.basis["cgf_atom_list"].append(iat)
234
+ self.basis["cgf_type_id_list"].append(1) # 1=s
235
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
236
+ self.basis["cgf_energy_list"].append(level)
237
+ ibf += 1
238
+
239
+ # Add Primitives
240
+ for p in range(current_nprim_or_R):
241
+ self.basis["primitive_alpha_list"].append(alpha[p])
242
+ self.basis["primitive_coeff_list"].append(coeff[p])
243
+ ipr += 1
244
+
245
+ # Add AO (1)
246
+ self.basis["ao_valence_flag_list"].append(valao)
247
+ self.basis["ao_atom_list"].append(iat)
248
+ self.basis["ao_type_id_list"].append(1) # 1=s
249
+ self.basis["ao_energy_list"].append(level)
250
+ self.basis["ao_zeta_list"].append(zeta)
251
+ self.basis["ao_shell_list"].append(ish)
252
+ iao += 1
253
+
254
+ # --- H-He s (Diffuse) ---
255
+ elif l == 0 and ati <= 2 and not is_valence:
256
+
257
+ alpha_R, coeff_R = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
258
+
259
+ # Get overlap with valence s
260
+ ss = self.calc_overlap_int_for_diffuse_func(0, alpha_s_val, alpha_R, coeff_s_val, coeff_R)
261
+ min_alpha_for_shell = min(np.min(alpha_s_val), np.min(alpha_R))
262
+
263
+ nprim_total = current_nprim_or_R + nprim_s_val
264
+
265
+ # Add CGF (1)
266
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
267
+ self.basis["cgf_valence_flag_list"].append(valao)
268
+ self.basis["cgf_atom_list"].append(iat)
269
+ self.basis["cgf_type_id_list"].append(1) # 1=s
270
+ self.basis["cgf_primitive_count_list"].append(nprim_total)
271
+ self.basis["cgf_energy_list"].append(level)
272
+ ibf += 1
273
+
274
+ # Add Primitives (Diffuse part first, then valence orthogonalized)
275
+ all_alphas = []
276
+ all_coeffs = []
277
+ prim_start_idx_for_this_cgf = ipr
278
+
279
+ for p in range(current_nprim_or_R):
280
+ alpha_p = alpha_R[p]
281
+ coeff_p = coeff_R[p]
282
+ self.basis["primitive_alpha_list"].append(alpha_p)
283
+ self.basis["primitive_coeff_list"].append(coeff_p)
284
+ all_alphas.append(alpha_p)
285
+ all_coeffs.append(coeff_p)
286
+ ipr += 1
287
+
288
+ # Add Primitives (Valence part, orthogonalized)
289
+ for p in range(nprim_s_val):
290
+ alpha_p = alpha_s_val[p]
291
+ coeff_p = -ss * coeff_s_val[p]
292
+ self.basis["primitive_alpha_list"].append(alpha_p)
293
+ self.basis["primitive_coeff_list"].append(coeff_p)
294
+ all_alphas.append(alpha_p)
295
+ all_coeffs.append(coeff_p)
296
+ ipr += 1
297
+
298
+ # Renormalize the new CGF
299
+ all_alphas = np.array(all_alphas)
300
+ all_coeffs = np.array(all_coeffs)
301
+ ss_norm = self.calc_overlap_int_for_diffuse_func(0, all_alphas, all_alphas, all_coeffs, all_coeffs)
302
+ norm_factor = 1.0 / np.sqrt(ss_norm)
303
+
304
+ # Update the coefficients in the global list
305
+ for p in range(nprim_total):
306
+ self.basis["primitive_coeff_list"][prim_start_idx_for_this_cgf + p] *= norm_factor
307
+
308
+ # Add AO (1)
309
+ self.basis["ao_valence_flag_list"].append(valao)
310
+ self.basis["ao_atom_list"].append(iat)
311
+ self.basis["ao_type_id_list"].append(1) # 1=s
312
+ self.basis["ao_energy_list"].append(level)
313
+ self.basis["ao_zeta_list"].append(zeta)
314
+ self.basis["ao_shell_list"].append(ish)
315
+ iao += 1
316
+
317
+ # --- H-He p (Polarization) ---
318
+ elif l == 1 and ati <= 2:
319
+ valao_p_pol = -valao
320
+
321
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
322
+ min_alpha_for_shell = np.min(alpha)
323
+
324
+ for j_type in [2, 3, 4]: # px, py, pz
325
+ # Add CGF (1)
326
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
327
+ self.basis["cgf_valence_flag_list"].append(valao_p_pol)
328
+ self.basis["cgf_atom_list"].append(iat)
329
+ self.basis["cgf_type_id_list"].append(j_type)
330
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
331
+ self.basis["cgf_energy_list"].append(level)
332
+ ibf += 1
333
+
334
+ # Add Primitives
335
+ for p in range(current_nprim_or_R):
336
+ self.basis["primitive_alpha_list"].append(alpha[p])
337
+ self.basis["primitive_coeff_list"].append(coeff[p])
338
+ ipr += 1
339
+
340
+ # Add AO (1)
341
+ self.basis["ao_valence_flag_list"].append(valao_p_pol)
342
+ self.basis["ao_atom_list"].append(iat)
343
+ self.basis["ao_type_id_list"].append(j_type)
344
+ self.basis["ao_energy_list"].append(level)
345
+ self.basis["ao_zeta_list"].append(zeta)
346
+ self.basis["ao_shell_list"].append(ish)
347
+ iao += 1
348
+
349
+ # --- General s (Valence) ---
350
+ elif l == 0 and ati > 2 and is_valence:
351
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
352
+ min_alpha_for_shell = np.min(alpha)
353
+
354
+ # Store for use by diffuse shell
355
+ nprim_s_val = current_nprim_or_R
356
+ alpha_s_val = alpha.copy()
357
+ coeff_s_val = coeff.copy()
358
+
359
+ # Add CGF (1)
360
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
361
+ self.basis["cgf_valence_flag_list"].append(valao)
362
+ self.basis["cgf_atom_list"].append(iat)
363
+ self.basis["cgf_type_id_list"].append(1) # 1=s
364
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
365
+ self.basis["cgf_energy_list"].append(level)
366
+ ibf += 1
367
+
368
+ # Add Primitives
369
+ for p in range(current_nprim_or_R):
370
+ self.basis["primitive_alpha_list"].append(alpha[p])
371
+ self.basis["primitive_coeff_list"].append(coeff[p])
372
+ ipr += 1
373
+
374
+ # Add AO (1)
375
+ self.basis["ao_valence_flag_list"].append(valao)
376
+ self.basis["ao_atom_list"].append(iat)
377
+ self.basis["ao_type_id_list"].append(1) # 1=s
378
+ self.basis["ao_energy_list"].append(level)
379
+ self.basis["ao_zeta_list"].append(zeta)
380
+ self.basis["ao_shell_list"].append(ish)
381
+ iao += 1
382
+
383
+ # --- General p ---
384
+ elif l == 1 and ati > 2:
385
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
386
+ min_alpha_for_shell = np.min(alpha)
387
+
388
+ for j_type in [2, 3, 4]: # px, py, pz
389
+ # Add CGF (1)
390
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
391
+ self.basis["cgf_valence_flag_list"].append(valao)
392
+ self.basis["cgf_atom_list"].append(iat)
393
+ self.basis["cgf_type_id_list"].append(j_type)
394
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
395
+ self.basis["cgf_energy_list"].append(level)
396
+ ibf += 1
397
+
398
+ # Add Primitives
399
+ for p in range(current_nprim_or_R):
400
+ self.basis["primitive_alpha_list"].append(alpha[p])
401
+ self.basis["primitive_coeff_list"].append(coeff[p])
402
+ ipr += 1
403
+
404
+ # Add AO (1)
405
+ self.basis["ao_valence_flag_list"].append(valao)
406
+ self.basis["ao_atom_list"].append(iat)
407
+ self.basis["ao_type_id_list"].append(j_type)
408
+ self.basis["ao_energy_list"].append(level)
409
+ self.basis["ao_zeta_list"].append(zeta)
410
+ self.basis["ao_shell_list"].append(ish)
411
+ iao += 1
412
+
413
+ # --- General s (Diffuse) ---
414
+ elif l == 0 and ati > 2 and not is_valence:
415
+ alpha_R, coeff_R = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
416
+ # Get overlap with valence s
417
+ ss = self.calc_overlap_int_for_diffuse_func(0, alpha_s_val, alpha_R, coeff_s_val, coeff_R)
418
+ min_alpha_for_shell = min(np.min(alpha_s_val), np.min(alpha_R))
419
+
420
+ nprim_total = current_nprim_or_R + nprim_s_val
421
+
422
+ # Add CGF (1)
423
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
424
+ self.basis["cgf_valence_flag_list"].append(valao)
425
+ self.basis["cgf_atom_list"].append(iat)
426
+ self.basis["cgf_type_id_list"].append(1) # 1=s
427
+ self.basis["cgf_primitive_count_list"].append(nprim_total)
428
+ self.basis["cgf_energy_list"].append(level)
429
+ ibf += 1
430
+
431
+ # Add Primitives (Diffuse part)
432
+ all_alphas = []
433
+ all_coeffs = []
434
+ prim_start_idx_for_this_cgf = ipr
435
+
436
+ for p in range(current_nprim_or_R):
437
+ alpha_p = alpha_R[p]
438
+ coeff_p = coeff_R[p]
439
+ self.basis["primitive_alpha_list"].append(alpha_p)
440
+ self.basis["primitive_coeff_list"].append(coeff_p)
441
+ all_alphas.append(alpha_p)
442
+ all_coeffs.append(coeff_p)
443
+ ipr += 1
444
+
445
+ # Add Primitives (Valence part, orthogonalized)
446
+ for p in range(nprim_s_val):
447
+ alpha_p = alpha_s_val[p]
448
+ coeff_p = -ss * coeff_s_val[p]
449
+ self.basis["primitive_alpha_list"].append(alpha_p)
450
+ self.basis["primitive_coeff_list"].append(coeff_p)
451
+ all_alphas.append(alpha_p)
452
+ all_coeffs.append(coeff_p)
453
+ ipr += 1
454
+
455
+ # Renormalize the new CGF
456
+ all_alphas = np.array(all_alphas)
457
+ all_coeffs = np.array(all_coeffs)
458
+ ss_norm = self.calc_overlap_int_for_diffuse_func(0, all_alphas, all_alphas, all_coeffs, all_coeffs)
459
+ norm_factor = 1.0 / np.sqrt(ss_norm)
460
+
461
+ # Update the coefficients in the global list
462
+ for p in range(nprim_total):
463
+ self.basis["primitive_coeff_list"][prim_start_idx_for_this_cgf + p] *= norm_factor
464
+
465
+ # Add AO (1)
466
+ self.basis["ao_valence_flag_list"].append(valao)
467
+ self.basis["ao_atom_list"].append(iat)
468
+ self.basis["ao_type_id_list"].append(1) # 1=s
469
+ self.basis["ao_energy_list"].append(level)
470
+ self.basis["ao_zeta_list"].append(zeta)
471
+ self.basis["ao_shell_list"].append(ish)
472
+ iao += 1
473
+
474
+ # --- d functions ---
475
+ elif l == 2:
476
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
477
+ min_alpha_for_shell = np.min(alpha)
478
+
479
+ for j_type in range(5, 11): # 5..10 (6 CGFs: dxx, dyy, dzz, dxy, dxz, dyz)
480
+ # Add CGF (1)
481
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
482
+ self.basis["cgf_valence_flag_list"].append(valao)
483
+ self.basis["cgf_atom_list"].append(iat)
484
+ self.basis["cgf_type_id_list"].append(j_type)
485
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
486
+ self.basis["cgf_energy_list"].append(level)
487
+ ibf += 1
488
+
489
+ # Add Primitives (with transformation factor)
490
+ trafo = d_trafo[j_type]
491
+ for p in range(current_nprim_or_R):
492
+ self.basis["primitive_alpha_list"].append(alpha[p])
493
+ self.basis["primitive_coeff_list"].append(coeff[p] * trafo)
494
+ ipr += 1
495
+
496
+ # Add AO (5 spherical AOs for 6 Cartesian CGFs)
497
+ # Skip for j_type=5 (dxx) - Fortran: if (j .eq.5) cycle
498
+ if j_type != 5:
499
+ ao_type_id = j_type - 1 # AO IDs: 4,5,6,7,8,9 for j_type 6,7,8,9,10
500
+ self.basis["ao_valence_flag_list"].append(valao)
501
+ self.basis["ao_atom_list"].append(iat)
502
+ self.basis["ao_type_id_list"].append(ao_type_id)
503
+ self.basis["ao_energy_list"].append(level)
504
+ self.basis["ao_zeta_list"].append(zeta)
505
+ self.basis["ao_shell_list"].append(ish)
506
+ iao += 1
507
+
508
+ # --- f functions ---
509
+ elif l == 3:
510
+ # Fortran hardcodes valao=1 for f functions
511
+ valao_f = 1
512
+
513
+ alpha, coeff = self.slater2gauss(current_nprim_or_R, npq, l, zeta, param, need_normalization=True)
514
+ min_alpha_for_shell = np.min(alpha)
515
+
516
+ for j_type in range(11, 21): # 11..20 (10 Cartesian CGFs)
517
+ # Add CGF (1)
518
+ self.basis["cgf_primitive_start_idx_list"].append(ipr)
519
+ self.basis["cgf_valence_flag_list"].append(valao_f)
520
+ self.basis["cgf_atom_list"].append(iat)
521
+ self.basis["cgf_type_id_list"].append(j_type)
522
+ self.basis["cgf_primitive_count_list"].append(current_nprim_or_R)
523
+ self.basis["cgf_energy_list"].append(level)
524
+ ibf += 1
525
+
526
+ # Add Primitives (with transformation factor)
527
+ trafo = f_trafo[j_type]
528
+ for p in range(current_nprim_or_R):
529
+ self.basis["primitive_alpha_list"].append(alpha[p])
530
+ self.basis["primitive_coeff_list"].append(coeff[p] * trafo)
531
+ ipr += 1
532
+
533
+ # Add AO (7 spherical AOs for 10 Cartesian CGFs)
534
+ # Skip for j_type 11,12,13 (fxxx, fyyy, fzzz)
535
+ # Fortran: if (j.ge.11 .and.j.le.13) cycle
536
+ if j_type > 13:
537
+ ao_type_id = j_type - 3 # AO IDs: 11,12,13,14,15,16,17 for j_type 14..20
538
+ self.basis["ao_valence_flag_list"].append(valao_f)
539
+ self.basis["ao_atom_list"].append(iat)
540
+ self.basis["ao_type_id_list"].append(ao_type_id)
541
+ self.basis["ao_energy_list"].append(level)
542
+ self.basis["ao_zeta_list"].append(zeta)
543
+ self.basis["ao_shell_list"].append(ish)
544
+ iao += 1
545
+
546
+ # --- End of shell type processing ---
547
+
548
+ # Store min alpha for this shell
549
+ self.basis["shell_min_alpha_list"].append(min_alpha_for_shell)
550
+
551
+ # --- Set Per-Shell Counts ---
552
+ self.basis["shell_cgf_map"][ish][1] = ibf - ibf_start_shell
553
+ self.basis["shell_ao_map"][ish][1] = iao - iao_start_shell
554
+
555
+ # --- Increment global shell counter ---
556
+ ish += 1
557
+
558
+ # --- Set Per-Atom End Indices ---
559
+ self.basis["atom_shells_map"][iat, 1] = ish
560
+ self.basis["atom_cgf_map"][iat, 1] = ibf
561
+ self.basis["atom_ao_map"][iat, 1] = iao
562
+
563
+ # --- Finalize Global Counts ---
564
+ self.basis["total_number_of_primitives"] = ipr
565
+
566
+ # --- Final Sanity Checks (corresponds to Fortran ok check) ---
567
+ all_alphas_positive = all(a > 0.0 for a in self.basis["primitive_alpha_list"])
568
+ if n_cgf_total != ibf:
569
+ print(f"Warning: CGF count mismatch. Calculated={n_cgf_total}, Found={ibf}")
570
+ if n_ao_total != iao:
571
+ print(f"Warning: AO count mismatch.Calculated={n_ao_total}, Found={iao}")
572
+ if n_shell_total != ish:
573
+ print(f"Warning: Shell count mismatch. Calculated={n_shell_total}, Found={ish}")
574
+ if not all_alphas_positive:
575
+ print("Warning: Some primitive exponents are non-positive!")
576
+
577
+ return
578
+
579
+
580
+ def get_number_of_primitives(self, element, angmn, pqn, is_valence):
581
+ """
582
+ Returns the number of primitive Gaussians for STO-nG expansion.
583
+
584
+ This function determines nprim based on element type and orbital type.
585
+ Note: In the original Fortran, this would be read from xtbData%hamiltonian%numberOfPrimitives.
586
+ Here we use hardcoded rules matching typical GFN0 behavior.
587
+ """
588
+ atom_num = element + 1 # Convert 0-indexed to 1-indexed atomic number
589
+
590
+ if atom_num <= 2: # H, He
591
+ if is_valence:
592
+ n = 3 # STO-3G
593
+ else:
594
+ n = 2 # STO-2G (thisprimR for diffuse)
595
+ else:
596
+ if angmn == 0: # s orbital
597
+ if pqn > 5:
598
+ n = 6 # STO-6G
599
+ else:
600
+ n = 4 # STO-4G
601
+ elif angmn == 1: # p orbital
602
+ if pqn > 5:
603
+ n = 6 # STO-6G
604
+ else:
605
+ n = 3 # STO-3G
606
+ elif angmn == 2 or angmn == 3: # d orbital or f orbital
607
+ n = 4 # STO-4G
608
+ else:
609
+ raise ValueError(f"Error in get_number_of_primitives: angmn={angmn} > 3")
610
+
611
+ return n
612
+
613
+
614
+ def check_transition_metals(self):
615
+ """
616
+ Identifies transition metals and Group 11 elements in the element list.
617
+ """
618
+ is_tm_list = []
619
+ is_g11_element_list = []
620
+
621
+ g11_z_numbers = {29, 47, 79, 111} # Cu, Ag, Au, Rg
622
+
623
+ for atn in self.element_list:
624
+ z = atn + 1
625
+
626
+ if z in g11_z_numbers:
627
+ is_g11_element_list.append(True)
628
+ is_tm_list.append(False)
629
+ elif (z >= 21 and z <= 30) or \
630
+ (z >= 39 and z <= 48) or \
631
+ (z >= 57 and z <= 80) or \
632
+ (z >= 89 and z <= 112):
633
+ is_tm_list.append(True)
634
+ is_g11_element_list.append(False)
635
+ else:
636
+ is_tm_list.append(False)
637
+ is_g11_element_list.append(False)
638
+
639
+ return is_tm_list, is_g11_element_list
640
+
641
+ def set_valence_orbitals(self):
642
+ """
643
+ Determines which shells are valence vs diffuse based on angular momentum.
644
+ NOTE: This method is not called by _set_basis.
645
+ It may be used elsewhere for analysis.
646
+ """
647
+ self.valence_orbitals_list = []
648
+ for i in range(len(self.element_list)):
649
+ seen_ang_mom = set()
650
+ for shell_idx in range(self.nshells_list[i]):
651
+ ang = self.ang_shells_list[i][shell_idx]
652
+ if ang not in seen_ang_mom:
653
+ self.valence_orbitals_list.append(True)
654
+ seen_ang_mom.add(ang)
655
+ else:
656
+ self.valence_orbitals_list.append(False)
657
+
658
+ return self.valence_orbitals_list
659
+
660
+ def slater2gauss(self, nprim, pqn, angmn, slater_exp, param, need_normalization=True):
661
+ """
662
+ Converts Slater-type orbital to a sum of Gaussian primitives (STO-nG).
663
+ Corresponds to Fortran subroutine: slaterToGauss
664
+
665
+ Parameters:
666
+ nprim: Number of primitive Gaussians
667
+ pqn: Principal quantum number
668
+ angmn: Angular momentum quantum number (0=s, 1=p, 2=d, 3=f)
669
+ slater_exp: Slater exponent (zeta)
670
+ param: Parameter object containing STO-nG coefficients
671
+ need_normalization: Whether to normalize the primitives
672
+
673
+ Returns:
674
+ alpha: Array of Gaussian exponents
675
+ coeff: Array of contraction coefficients
676
+ """
677
+ alpha = np.zeros(nprim)
678
+ coeff = np.zeros(nprim)
679
+
680
+ if pqn < angmn + 1:
681
+ raise ValueError(f"Error in slater2gauss: pqn ({pqn}) < angmn + 1 ({angmn + 1})")
682
+
683
+ if slater_exp <= 0.0:
684
+ raise ValueError(f"Error in slater2gauss: non-positive slater exponent ({slater_exp})")
685
+
686
+ # Determine index into parameter tables based on orbital type
687
+ tmp_num = -1
688
+ if angmn == 0: # s orbital
689
+ tmp_num = pqn - 1
690
+ elif angmn == 1: # p orbital
691
+ tmp_num = 4 + pqn - 1
692
+ elif angmn == 2: # d orbital
693
+ tmp_num = 7 + pqn - 1
694
+ elif angmn == 3: # f orbital
695
+ tmp_num = 9 + pqn - 1
696
+ elif angmn == 4: # g orbital
697
+ tmp_num = 10 + pqn - 1
698
+ else:
699
+ raise ValueError(f"Error in slater2gauss: angmn ({angmn}) > 4")
700
+
701
+ slater_exp_sq = slater_exp ** 2.0
702
+
703
+ if nprim == 1:
704
+ alpha[0] = param.pAlpha1[tmp_num] * slater_exp_sq
705
+ coeff[0] = 1.0
706
+ elif nprim == 2:
707
+ alpha = param.pAlpha2[tmp_num, :].copy() * slater_exp_sq
708
+ coeff = param.pCoeff2[tmp_num, :].copy()
709
+ elif nprim == 3:
710
+ alpha = param.pAlpha3[tmp_num, :].copy() * slater_exp_sq
711
+ coeff = param.pCoeff3[tmp_num, :].copy()
712
+ elif nprim == 4:
713
+ alpha = param.pAlpha4[tmp_num, :].copy() * slater_exp_sq
714
+ coeff = param.pCoeff4[tmp_num, :].copy()
715
+ elif nprim == 5:
716
+ alpha = param.pAlpha5[tmp_num, :].copy() * slater_exp_sq
717
+ coeff = param.pCoeff5[tmp_num, :].copy()
718
+ elif nprim == 6:
719
+ if pqn == 6:
720
+ if angmn == 0:
721
+ alpha = param.pAlpha6s[:].copy() * slater_exp_sq
722
+ coeff = param.pCoeff6s[:].copy()
723
+ elif angmn == 1:
724
+ alpha = param.pAlpha6p[:].copy() * slater_exp_sq
725
+ coeff = param.pCoeff6p[:].copy()
726
+ else:
727
+ raise ValueError(f"Error in slater2gauss: invalid angmn ({angmn}) for pqn=6 with nprim=6")
728
+ else:
729
+ alpha = param.pAlpha6[tmp_num, :].copy() * slater_exp_sq
730
+ coeff = param.pCoeff6[tmp_num, :].copy()
731
+ else:
732
+ raise ValueError(f"Error in slater2gauss: invalid number of primitives ({nprim})")
733
+
734
+ if need_normalization:
735
+ # Normalize primitives
736
+ # Formula: coeff = coeff * (2/pi * alpha)^(3/4) * (4*alpha)^(l/2) / sqrt((2l-1)! !)
737
+ # where (2l-1)!! is the double factorial
738
+ top = 2.0 / np.pi
739
+ # dfactorial(angmn + 1) should give (2*angmn - 1)!!
740
+ # For l=0: (2*0-1)!! = (-1)!! = 1
741
+ # For l=1: (2*1-1)!! = 1!! = 1
742
+ # For l=2: (2*2-1)!! = 3!! = 3
743
+ # For l=3: (2*3-1)!! = 5!! = 15
744
+ coeff = coeff * (top * alpha) ** 0.75 * np.sqrt(4.0 * alpha) ** angmn / np.sqrt(dfactorial(angmn + 1))
745
+
746
+ return alpha, coeff
747
+
748
+
749
+ def calc_overlap_int_for_diffuse_func(self, angmn, alpha_A, alpha_B, coeff_A, coeff_B):
750
+ """
751
+ Calculates the overlap integral <A|B> for two CGFs on the same center.
752
+ Corresponds to Fortran subroutine: atovlp
753
+
754
+ Parameters:
755
+ angmn: Angular momentum quantum number (0=s, 1=p)
756
+ alpha_A, alpha_B: Primitive exponent arrays
757
+ coeff_A, coeff_B: Contraction coefficient arrays
758
+
759
+ Returns:
760
+ overlap_int: The overlap integral value
761
+ """
762
+ overlap_int = 0.0
763
+
764
+ n_prim_A = len(coeff_A)
765
+ n_prim_B = len(coeff_B)
766
+
767
+ if n_prim_A == 0 or n_prim_B == 0:
768
+ print("Warning: calc_overlap_int_for_diffuse_func called with empty primitive list.")
769
+ return 0.0
770
+
771
+ for ii in range(n_prim_A):
772
+ for jj in range(n_prim_B):
773
+ ab = 1.0 / (alpha_A[ii] + alpha_B[jj])
774
+ s00 = (np.pi * ab) ** 1.5
775
+
776
+ sss = 0.0
777
+ if angmn == 0: # s-s overlap
778
+ sss = s00
779
+ elif angmn == 1: # p-p overlap (same component)
780
+ ab05 = ab * 0.5
781
+ sss = s00 * ab05
782
+
783
+ overlap_int += sss * coeff_A[ii] * coeff_B[jj]
784
+
785
+ return overlap_int
786
+
787
+
788
+ # === Accessor methods for convenient data retrieval ===
789
+
790
+ def get_cgf_offset_for_atom_shell(self, iat, m):
791
+ """
792
+ Returns the CGF index at the start of shell m for atom iat.
793
+ Corresponds to Fortran: caoshell(m, iat)
794
+ """
795
+ return self.basis["atom_shell_cgf_offset"][iat, m]
796
+
797
+ def get_ao_offset_for_atom_shell(self, iat, m):
798
+ """
799
+ Returns the AO index at the start of shell m for atom iat.
800
+ Corresponds to Fortran: saoshell(m, iat)
801
+ """
802
+ return self.basis["atom_shell_ao_offset"][iat, m]
803
+
804
+ def get_shell_range_for_atom(self, iat):
805
+ """
806
+ Returns (start_shell, end_shell) for atom iat.
807
+ Corresponds to Fortran: shells(1:2, iat)
808
+ """
809
+ return (self.basis["atom_shells_map"][iat, 0],
810
+ self.basis["atom_shells_map"][iat, 1])
811
+
812
+ def get_cgf_range_for_atom(self, iat):
813
+ """
814
+ Returns (start_cgf, end_cgf) for atom iat.
815
+ Corresponds to Fortran: fila(1:2, iat)
816
+ """
817
+ return (self.basis["atom_cgf_map"][iat, 0],
818
+ self.basis["atom_cgf_map"][iat, 1])
819
+
820
+ def get_ao_range_for_atom(self, iat):
821
+ """
822
+ Returns (start_ao, end_ao) for atom iat.
823
+ Corresponds to Fortran: fila2(1:2, iat)
824
+ """
825
+ return (self.basis["atom_ao_map"][iat, 0],
826
+ self.basis["atom_ao_map"][iat, 1])
827
+
828
+ def get_cgf_range_for_shell(self, ish):
829
+ """
830
+ Returns (start_cgf, count) for shell ish.
831
+ Corresponds to Fortran: sh2bf(1:2, ish)
832
+ """
833
+ return tuple(self.basis["shell_cgf_map"][ish])
834
+
835
+ def get_ao_range_for_shell(self, ish):
836
+ """
837
+ Returns (start_ao, count) for shell ish.
838
+ Corresponds to Fortran: sh2ao(1:2, ish)
839
+ """
840
+ return tuple(self.basis["shell_ao_map"][ish])
841
+
842
+ def get_primitives_for_cgf(self, ibf):
843
+ """
844
+ Returns (start_prim_idx, nprim, alphas, coeffs) for CGF ibf.
845
+ """
846
+ start_idx = self.basis["cgf_primitive_start_idx_list"][ibf]
847
+ nprim = self.basis["cgf_primitive_count_list"][ibf]
848
+ alphas = self.basis["primitive_alpha_list"][start_idx:start_idx + nprim]
849
+ coeffs = self.basis["primitive_coeff_list"][start_idx:start_idx + nprim]
850
+ return start_idx, nprim, alphas, coeffs