physicskit 0.1.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 (484) hide show
  1. physicskit/__init__.py +51 -0
  2. physicskit/astro/__init__.py +66 -0
  3. physicskit/astro/cosmic_web.py +395 -0
  4. physicskit/astro/galactic_dynamics.py +137 -0
  5. physicskit/astro/nbody.py +208 -0
  6. physicskit/astro/orbital_mechanics.py +220 -0
  7. physicskit/astro/stellar_dynamo.py +628 -0
  8. physicskit/astro/stellar_structure.py +198 -0
  9. physicskit/astro/tests/__init__.py +0 -0
  10. physicskit/astro/tests/conftest.py +7 -0
  11. physicskit/astro/tests/test_cosmic_web.py +159 -0
  12. physicskit/astro/tests/test_galactic_dynamics.py +46 -0
  13. physicskit/astro/tests/test_nbody.py +81 -0
  14. physicskit/astro/tests/test_orbital_mechanics.py +73 -0
  15. physicskit/astro/tests/test_stellar_dynamo.py +198 -0
  16. physicskit/astro/tests/test_stellar_structure.py +52 -0
  17. physicskit/astro/tests/test_visualizers.py +159 -0
  18. physicskit/astro/visualizers.py +628 -0
  19. physicskit/chaos/__init__.py +55 -0
  20. physicskit/chaos/core/__init__.py +3 -0
  21. physicskit/chaos/core/base_system.py +342 -0
  22. physicskit/chaos/core/integrators.py +40 -0
  23. physicskit/chaos/exceptions.py +24 -0
  24. physicskit/chaos/quantum/__init__.py +38 -0
  25. physicskit/chaos/quantum/billiards.py +274 -0
  26. physicskit/chaos/quantum/husimi.py +85 -0
  27. physicskit/chaos/quantum/maps.py +366 -0
  28. physicskit/chaos/systems/__init__.py +38 -0
  29. physicskit/chaos/systems/billiards.py +1131 -0
  30. physicskit/chaos/systems/continuous.py +1276 -0
  31. physicskit/chaos/systems/maps.py +557 -0
  32. physicskit/chaos/tests/__init__.py +0 -0
  33. physicskit/chaos/tests/test_basins.py +76 -0
  34. physicskit/chaos/tests/test_bifurcation.py +132 -0
  35. physicskit/chaos/tests/test_billiards.py +115 -0
  36. physicskit/chaos/tests/test_continuous.py +224 -0
  37. physicskit/chaos/tests/test_dimension.py +69 -0
  38. physicskit/chaos/tests/test_divergence.py +90 -0
  39. physicskit/chaos/tests/test_dynamic_plots.py +269 -0
  40. physicskit/chaos/tests/test_integrators.py +96 -0
  41. physicskit/chaos/tests/test_io.py +41 -0
  42. physicskit/chaos/tests/test_maps.py +110 -0
  43. physicskit/chaos/tests/test_metrics.py +115 -0
  44. physicskit/chaos/tests/test_phase_space.py +73 -0
  45. physicskit/chaos/tests/test_properties.py +116 -0
  46. physicskit/chaos/tests/test_quantum_billiards.py +94 -0
  47. physicskit/chaos/tests/test_quantum_maps.py +144 -0
  48. physicskit/chaos/tests/test_recurrence.py +51 -0
  49. physicskit/chaos/tests/test_repr_and_api_consistency.py +134 -0
  50. physicskit/chaos/tests/test_section.py +49 -0
  51. physicskit/chaos/tests/test_spectral.py +48 -0
  52. physicskit/chaos/tests/test_streamlit_app.py +45 -0
  53. physicskit/chaos/tests/test_timeseries.py +131 -0
  54. physicskit/chaos/tests/test_viewer3d.py +41 -0
  55. physicskit/chaos/utils/__init__.py +46 -0
  56. physicskit/chaos/utils/dimension.py +139 -0
  57. physicskit/chaos/utils/io.py +95 -0
  58. physicskit/chaos/utils/metrics.py +304 -0
  59. physicskit/chaos/utils/recurrence.py +122 -0
  60. physicskit/chaos/utils/spectral.py +69 -0
  61. physicskit/chaos/utils/timeseries.py +303 -0
  62. physicskit/chaos/visualizers/__init__.py +85 -0
  63. physicskit/chaos/visualizers/basins.py +185 -0
  64. physicskit/chaos/visualizers/bifurcation.py +273 -0
  65. physicskit/chaos/visualizers/divergence.py +374 -0
  66. physicskit/chaos/visualizers/dynamic_plots.py +1372 -0
  67. physicskit/chaos/visualizers/phase_space.py +133 -0
  68. physicskit/chaos/visualizers/quantum.py +299 -0
  69. physicskit/chaos/visualizers/recurrence.py +91 -0
  70. physicskit/chaos/visualizers/section.py +161 -0
  71. physicskit/chaos/visualizers/theme.py +31 -0
  72. physicskit/chaos/visualizers/viewer3d.py +82 -0
  73. physicskit/classical/__init__.py +55 -0
  74. physicskit/classical/core/__init__.py +17 -0
  75. physicskit/classical/core/base_system.py +510 -0
  76. physicskit/classical/core/integrators.py +397 -0
  77. physicskit/classical/systems/__init__.py +32 -0
  78. physicskit/classical/systems/chains.py +334 -0
  79. physicskit/classical/systems/hamiltonian.py +215 -0
  80. physicskit/classical/systems/lagrangian.py +308 -0
  81. physicskit/classical/systems/newtonian.py +536 -0
  82. physicskit/classical/systems/rotations.py +614 -0
  83. physicskit/classical/tests/__init__.py +0 -0
  84. physicskit/classical/tests/conftest.py +7 -0
  85. physicskit/classical/tests/test_base_system.py +113 -0
  86. physicskit/classical/tests/test_chains.py +84 -0
  87. physicskit/classical/tests/test_conservation.py +244 -0
  88. physicskit/classical/tests/test_conservation_utils.py +47 -0
  89. physicskit/classical/tests/test_hamiltonian.py +44 -0
  90. physicskit/classical/tests/test_integrators.py +123 -0
  91. physicskit/classical/tests/test_lagrangian.py +69 -0
  92. physicskit/classical/tests/test_newtonian.py +45 -0
  93. physicskit/classical/tests/test_rotations.py +160 -0
  94. physicskit/classical/tests/test_stepsize.py +149 -0
  95. physicskit/classical/tests/test_symbolic.py +125 -0
  96. physicskit/classical/tests/test_visualizers.py +172 -0
  97. physicskit/classical/utils/__init__.py +20 -0
  98. physicskit/classical/utils/conservation.py +126 -0
  99. physicskit/classical/utils/stepsize.py +187 -0
  100. physicskit/classical/utils/symbolic.py +235 -0
  101. physicskit/classical/visualizers/__init__.py +25 -0
  102. physicskit/classical/visualizers/animations.py +508 -0
  103. physicskit/classical/visualizers/interactive.py +113 -0
  104. physicskit/classical/visualizers/modal_analysis.py +97 -0
  105. physicskit/classical/visualizers/phase_space.py +214 -0
  106. physicskit/condensed/__init__.py +122 -0
  107. physicskit/condensed/anderson_localization.py +155 -0
  108. physicskit/condensed/correlated.py +284 -0
  109. physicskit/condensed/ginzburg_landau.py +220 -0
  110. physicskit/condensed/landau_levels.py +246 -0
  111. physicskit/condensed/laughlin.py +229 -0
  112. physicskit/condensed/models.py +627 -0
  113. physicskit/condensed/tests/__init__.py +0 -0
  114. physicskit/condensed/tests/test_condensed.py +518 -0
  115. physicskit/condensed/tests/test_visualizers.py +87 -0
  116. physicskit/condensed/tight_binding.py +445 -0
  117. physicskit/condensed/topological_insulator_3d.py +178 -0
  118. physicskit/condensed/topology.py +217 -0
  119. physicskit/condensed/visualizers.py +296 -0
  120. physicskit/condensed/weyl.py +180 -0
  121. physicskit/constants.py +184 -0
  122. physicskit/fields/__init__.py +111 -0
  123. physicskit/fields/electrodynamics.py +671 -0
  124. physicskit/fields/quantum_fields.py +510 -0
  125. physicskit/fields/solitons.py +527 -0
  126. physicskit/fields/tests/__init__.py +0 -0
  127. physicskit/fields/tests/conftest.py +7 -0
  128. physicskit/fields/tests/test_field_animations.py +154 -0
  129. physicskit/fields/tests/test_fields.py +439 -0
  130. physicskit/fields/tests/test_visualizers.py +101 -0
  131. physicskit/fields/visualizers.py +503 -0
  132. physicskit/fluids/__init__.py +134 -0
  133. physicskit/fluids/core/__init__.py +23 -0
  134. physicskit/fluids/core/grid.py +165 -0
  135. physicskit/fluids/core/timestepping.py +231 -0
  136. physicskit/fluids/exceptions.py +26 -0
  137. physicskit/fluids/systems/__init__.py +70 -0
  138. physicskit/fluids/systems/compressible_flow.py +281 -0
  139. physicskit/fluids/systems/instabilities.py +343 -0
  140. physicskit/fluids/systems/navier_stokes.py +221 -0
  141. physicskit/fluids/systems/potential_flow.py +440 -0
  142. physicskit/fluids/systems/viscous_flow.py +425 -0
  143. physicskit/fluids/systems/vortex_dynamics.py +304 -0
  144. physicskit/fluids/tests/__init__.py +0 -0
  145. physicskit/fluids/tests/conftest.py +7 -0
  146. physicskit/fluids/tests/test_compressible_flow.py +66 -0
  147. physicskit/fluids/tests/test_instabilities.py +115 -0
  148. physicskit/fluids/tests/test_navier_stokes.py +67 -0
  149. physicskit/fluids/tests/test_potential_flow.py +92 -0
  150. physicskit/fluids/tests/test_static_visualizers.py +91 -0
  151. physicskit/fluids/tests/test_utils.py +100 -0
  152. physicskit/fluids/tests/test_viscous_flow.py +110 -0
  153. physicskit/fluids/tests/test_visualizers.py +34 -0
  154. physicskit/fluids/tests/test_vortex_dynamics.py +88 -0
  155. physicskit/fluids/utils/__init__.py +18 -0
  156. physicskit/fluids/utils/dimensionless.py +239 -0
  157. physicskit/fluids/utils/spectral_analysis.py +142 -0
  158. physicskit/fluids/visualizers/__init__.py +14 -0
  159. physicskit/fluids/visualizers/compressible.py +39 -0
  160. physicskit/fluids/visualizers/flow_fields.py +252 -0
  161. physicskit/fluids/visualizers/potential_flow.py +51 -0
  162. physicskit/fluids/visualizers/spectra.py +62 -0
  163. physicskit/fluids/visualizers/theme.py +38 -0
  164. physicskit/integrators/__init__.py +52 -0
  165. physicskit/integrators/adaptive.py +199 -0
  166. physicskit/integrators/fixed_step.py +360 -0
  167. physicskit/integrators/tests/__init__.py +0 -0
  168. physicskit/integrators/tests/test_integrators.py +101 -0
  169. physicskit/optics/__init__.py +124 -0
  170. physicskit/optics/_compat.py +5 -0
  171. physicskit/optics/gaussian.py +382 -0
  172. physicskit/optics/quantum_optics.py +428 -0
  173. physicskit/optics/ray.py +469 -0
  174. physicskit/optics/tests/__init__.py +0 -0
  175. physicskit/optics/tests/test_diffraction_animation.py +38 -0
  176. physicskit/optics/tests/test_gaussian.py +170 -0
  177. physicskit/optics/tests/test_quantum_optics.py +147 -0
  178. physicskit/optics/tests/test_ray.py +177 -0
  179. physicskit/optics/tests/test_visualizers.py +58 -0
  180. physicskit/optics/tests/test_wave.py +146 -0
  181. physicskit/optics/visualizers.py +264 -0
  182. physicskit/optics/wave.py +365 -0
  183. physicskit/particle/__init__.py +152 -0
  184. physicskit/particle/collider.py +382 -0
  185. physicskit/particle/confinement.py +125 -0
  186. physicskit/particle/decays.py +357 -0
  187. physicskit/particle/electroweak.py +300 -0
  188. physicskit/particle/kinematics.py +271 -0
  189. physicskit/particle/neutrinos.py +84 -0
  190. physicskit/particle/nuclear.py +100 -0
  191. physicskit/particle/scattering.py +159 -0
  192. physicskit/particle/tests/__init__.py +0 -0
  193. physicskit/particle/tests/conftest.py +7 -0
  194. physicskit/particle/tests/test_collider.py +119 -0
  195. physicskit/particle/tests/test_confinement.py +42 -0
  196. physicskit/particle/tests/test_decays.py +112 -0
  197. physicskit/particle/tests/test_electroweak.py +97 -0
  198. physicskit/particle/tests/test_kinematics.py +138 -0
  199. physicskit/particle/tests/test_neutrinos.py +36 -0
  200. physicskit/particle/tests/test_nuclear.py +45 -0
  201. physicskit/particle/tests/test_scattering.py +64 -0
  202. physicskit/particle/tests/test_visualizers.py +214 -0
  203. physicskit/particle/visualizers/__init__.py +38 -0
  204. physicskit/particle/visualizers/animations.py +705 -0
  205. physicskit/particle/visualizers/static.py +101 -0
  206. physicskit/plasma/__init__.py +176 -0
  207. physicskit/plasma/acceleration.py +152 -0
  208. physicskit/plasma/instabilities.py +417 -0
  209. physicskit/plasma/kinetic.py +588 -0
  210. physicskit/plasma/mhd.py +463 -0
  211. physicskit/plasma/single_particle.py +562 -0
  212. physicskit/plasma/tests/__init__.py +0 -0
  213. physicskit/plasma/tests/conftest.py +7 -0
  214. physicskit/plasma/tests/test_new_time_domain_features.py +222 -0
  215. physicskit/plasma/tests/test_plasma.py +232 -0
  216. physicskit/plasma/tests/test_visualizers.py +79 -0
  217. physicskit/plasma/turbulence.py +219 -0
  218. physicskit/plasma/visualizers.py +868 -0
  219. physicskit/plasma/waves.py +549 -0
  220. physicskit/quantum/__init__.py +180 -0
  221. physicskit/quantum/_compat.py +20 -0
  222. physicskit/quantum/chapters/__init__.py +0 -0
  223. physicskit/quantum/chapters/entanglement.py +489 -0
  224. physicskit/quantum/chapters/harmonic_spin.py +517 -0
  225. physicskit/quantum/chapters/hydrogen_am.py +292 -0
  226. physicskit/quantum/chapters/perturbation.py +350 -0
  227. physicskit/quantum/chapters/potentials.py +997 -0
  228. physicskit/quantum/chapters/spin.py +306 -0
  229. physicskit/quantum/chapters/wave_packets.py +541 -0
  230. physicskit/quantum/core/__init__.py +0 -0
  231. physicskit/quantum/core/eigensolvers.py +311 -0
  232. physicskit/quantum/core/operators.py +296 -0
  233. physicskit/quantum/core/solvers.py +271 -0
  234. physicskit/quantum/tests/__init__.py +0 -0
  235. physicskit/quantum/tests/conftest.py +7 -0
  236. physicskit/quantum/tests/test_animations.py +225 -0
  237. physicskit/quantum/tests/test_compat.py +43 -0
  238. physicskit/quantum/tests/test_eigensolvers.py +24 -0
  239. physicskit/quantum/tests/test_entanglement.py +127 -0
  240. physicskit/quantum/tests/test_harmonic_spin.py +140 -0
  241. physicskit/quantum/tests/test_hydrogen_am.py +48 -0
  242. physicskit/quantum/tests/test_measure.py +110 -0
  243. physicskit/quantum/tests/test_operators.py +105 -0
  244. physicskit/quantum/tests/test_perturbation.py +112 -0
  245. physicskit/quantum/tests/test_physics_checks.py +131 -0
  246. physicskit/quantum/tests/test_potentials.py +191 -0
  247. physicskit/quantum/tests/test_solvers.py +70 -0
  248. physicskit/quantum/tests/test_spin.py +22 -0
  249. physicskit/quantum/tests/test_unitarity.py +84 -0
  250. physicskit/quantum/tests/test_wave_packets.py +53 -0
  251. physicskit/quantum/utils/__init__.py +0 -0
  252. physicskit/quantum/utils/measure.py +253 -0
  253. physicskit/quantum/visualizers/__init__.py +0 -0
  254. physicskit/quantum/visualizers/bloch_sphere.py +205 -0
  255. physicskit/quantum/visualizers/entanglement.py +58 -0
  256. physicskit/quantum/visualizers/orbitals.py +187 -0
  257. physicskit/quantum/visualizers/phase_space.py +179 -0
  258. physicskit/quantum/visualizers/wavefunctions.py +230 -0
  259. physicskit/relativity/__init__.py +61 -0
  260. physicskit/relativity/chapters/__init__.py +33 -0
  261. physicskit/relativity/chapters/cosmology.py +190 -0
  262. physicskit/relativity/chapters/gw_merger.py +399 -0
  263. physicskit/relativity/chapters/kerr.py +371 -0
  264. physicskit/relativity/chapters/lensing.py +196 -0
  265. physicskit/relativity/chapters/neutron_star.py +149 -0
  266. physicskit/relativity/chapters/schwarzschild.py +412 -0
  267. physicskit/relativity/chapters/timekeeping.py +133 -0
  268. physicskit/relativity/core/__init__.py +12 -0
  269. physicskit/relativity/core/geodesics.py +216 -0
  270. physicskit/relativity/core/kerr_raytracer.py +259 -0
  271. physicskit/relativity/core/raytracer.py +241 -0
  272. physicskit/relativity/core/tensors.py +468 -0
  273. physicskit/relativity/tests/__init__.py +0 -0
  274. physicskit/relativity/tests/test_conservations.py +54 -0
  275. physicskit/relativity/tests/test_constants.py +34 -0
  276. physicskit/relativity/tests/test_cosmology.py +61 -0
  277. physicskit/relativity/tests/test_gw_merger.py +117 -0
  278. physicskit/relativity/tests/test_kerr.py +116 -0
  279. physicskit/relativity/tests/test_kerr_raytracer.py +103 -0
  280. physicskit/relativity/tests/test_lensing.py +75 -0
  281. physicskit/relativity/tests/test_neutron_star.py +48 -0
  282. physicskit/relativity/tests/test_raytracer.py +67 -0
  283. physicskit/relativity/tests/test_schwarzschild.py +126 -0
  284. physicskit/relativity/tests/test_shadow_render.py +81 -0
  285. physicskit/relativity/tests/test_tensors.py +86 -0
  286. physicskit/relativity/tests/test_timekeeping.py +51 -0
  287. physicskit/relativity/tests/test_visualizers_misc.py +115 -0
  288. physicskit/relativity/utils/__init__.py +1 -0
  289. physicskit/relativity/utils/constants.py +232 -0
  290. physicskit/relativity/visualizers/__init__.py +46 -0
  291. physicskit/relativity/visualizers/interactive.py +127 -0
  292. physicskit/relativity/visualizers/shadow_render.py +367 -0
  293. physicskit/relativity/visualizers/spacetime_3d.py +92 -0
  294. physicskit/relativity/visualizers/spacetime_diagrams.py +198 -0
  295. physicskit/relativity/visualizers/wave_plots.py +185 -0
  296. physicskit/rmt/__init__.py +8 -0
  297. physicskit/rmt/ensembles/__init__.py +108 -0
  298. physicskit/rmt/ensembles/banded.py +114 -0
  299. physicskit/rmt/ensembles/base.py +135 -0
  300. physicskit/rmt/ensembles/bdg.py +218 -0
  301. physicskit/rmt/ensembles/chiral.py +308 -0
  302. physicskit/rmt/ensembles/circular.py +104 -0
  303. physicskit/rmt/ensembles/crossover.py +82 -0
  304. physicskit/rmt/ensembles/density_matrix.py +148 -0
  305. physicskit/rmt/ensembles/effective_hamiltonian.py +230 -0
  306. physicskit/rmt/ensembles/embedded.py +207 -0
  307. physicskit/rmt/ensembles/gaussian.py +153 -0
  308. physicskit/rmt/ensembles/ginibre.py +110 -0
  309. physicskit/rmt/ensembles/girko.py +190 -0
  310. physicskit/rmt/ensembles/graph_laplacian.py +92 -0
  311. physicskit/rmt/ensembles/haar.py +57 -0
  312. physicskit/rmt/ensembles/jacobi.py +182 -0
  313. physicskit/rmt/ensembles/poisson.py +77 -0
  314. physicskit/rmt/ensembles/polynomial.py +139 -0
  315. physicskit/rmt/ensembles/pt_symmetric.py +120 -0
  316. physicskit/rmt/ensembles/single_ring.py +170 -0
  317. physicskit/rmt/ensembles/sparse.py +95 -0
  318. physicskit/rmt/ensembles/syk.py +170 -0
  319. physicskit/rmt/ensembles/truncated_unitary.py +80 -0
  320. physicskit/rmt/ensembles/universality.py +92 -0
  321. physicskit/rmt/ensembles/wishart.py +88 -0
  322. physicskit/rmt/py.typed +0 -0
  323. physicskit/rmt/spectrum.py +82 -0
  324. physicskit/rmt/stats/__init__.py +160 -0
  325. physicskit/rmt/stats/characteristic_polynomial.py +77 -0
  326. physicskit/rmt/stats/circular_law.py +54 -0
  327. physicskit/rmt/stats/correlations.py +162 -0
  328. physicskit/rmt/stats/density.py +70 -0
  329. physicskit/rmt/stats/entanglement.py +36 -0
  330. physicskit/rmt/stats/free_probability.py +202 -0
  331. physicskit/rmt/stats/localization.py +338 -0
  332. physicskit/rmt/stats/marchenko_pastur.py +55 -0
  333. physicskit/rmt/stats/pt_symmetric.py +265 -0
  334. physicskit/rmt/stats/ratios.py +110 -0
  335. physicskit/rmt/stats/real_ginibre.py +134 -0
  336. physicskit/rmt/stats/rigidity.py +272 -0
  337. physicskit/rmt/stats/single_ring.py +95 -0
  338. physicskit/rmt/stats/spacing.py +150 -0
  339. physicskit/rmt/stats/tracy_widom.py +175 -0
  340. physicskit/rmt/stats/truncated_unitary.py +40 -0
  341. physicskit/rmt/stats/unfolding.py +37 -0
  342. physicskit/rmt/stats/wachter.py +67 -0
  343. physicskit/rmt/tests/__init__.py +0 -0
  344. physicskit/rmt/tests/cache_utils.py +107 -0
  345. physicskit/rmt/tests/conftest.py +8 -0
  346. physicskit/rmt/tests/test_banded.py +86 -0
  347. physicskit/rmt/tests/test_base.py +29 -0
  348. physicskit/rmt/tests/test_bdg.py +133 -0
  349. physicskit/rmt/tests/test_characteristic_polynomial.py +50 -0
  350. physicskit/rmt/tests/test_chiral.py +273 -0
  351. physicskit/rmt/tests/test_circular.py +225 -0
  352. physicskit/rmt/tests/test_crossover.py +72 -0
  353. physicskit/rmt/tests/test_density.py +23 -0
  354. physicskit/rmt/tests/test_density_matrix.py +181 -0
  355. physicskit/rmt/tests/test_effective_hamiltonian.py +140 -0
  356. physicskit/rmt/tests/test_embedded.py +169 -0
  357. physicskit/rmt/tests/test_free_probability.py +81 -0
  358. physicskit/rmt/tests/test_gaussian_ensembles.py +22 -0
  359. physicskit/rmt/tests/test_gaussian_semicircle.py +127 -0
  360. physicskit/rmt/tests/test_ginibre_circular_law.py +131 -0
  361. physicskit/rmt/tests/test_girko.py +178 -0
  362. physicskit/rmt/tests/test_graph_laplacian.py +102 -0
  363. physicskit/rmt/tests/test_haar.py +118 -0
  364. physicskit/rmt/tests/test_jacobi_wachter.py +136 -0
  365. physicskit/rmt/tests/test_localization.py +240 -0
  366. physicskit/rmt/tests/test_poisson.py +149 -0
  367. physicskit/rmt/tests/test_polynomial.py +107 -0
  368. physicskit/rmt/tests/test_pt_symmetric.py +178 -0
  369. physicskit/rmt/tests/test_random_state.py +19 -0
  370. physicskit/rmt/tests/test_real_ginibre.py +112 -0
  371. physicskit/rmt/tests/test_rigidity_universality.py +167 -0
  372. physicskit/rmt/tests/test_single_ring.py +132 -0
  373. physicskit/rmt/tests/test_spacing_and_ratios.py +122 -0
  374. physicskit/rmt/tests/test_sparse.py +71 -0
  375. physicskit/rmt/tests/test_spectrum.py +27 -0
  376. physicskit/rmt/tests/test_syk.py +136 -0
  377. physicskit/rmt/tests/test_tracy_widom.py +113 -0
  378. physicskit/rmt/tests/test_tridiagonal.py +41 -0
  379. physicskit/rmt/tests/test_truncated_unitary.py +80 -0
  380. physicskit/rmt/tests/test_validation_base.py +25 -0
  381. physicskit/rmt/tests/test_validation_misc.py +67 -0
  382. physicskit/rmt/tests/test_wishart_marchenko_pastur.py +120 -0
  383. physicskit/rmt/utils/__init__.py +0 -0
  384. physicskit/rmt/utils/haar.py +128 -0
  385. physicskit/rmt/utils/random_state.py +24 -0
  386. physicskit/rmt/utils/tridiagonal.py +167 -0
  387. physicskit/rmt/validation/__init__.py +36 -0
  388. physicskit/rmt/validation/base.py +126 -0
  389. physicskit/rmt/validation/circular_law.py +80 -0
  390. physicskit/rmt/validation/marchenko_pastur.py +53 -0
  391. physicskit/rmt/validation/ratio_distribution.py +53 -0
  392. physicskit/rmt/validation/real_ginibre.py +60 -0
  393. physicskit/rmt/validation/sine_kernel.py +57 -0
  394. physicskit/rmt/validation/single_ring.py +68 -0
  395. physicskit/rmt/validation/tracy_widom.py +89 -0
  396. physicskit/rmt/validation/universality.py +100 -0
  397. physicskit/rmt/validation/wachter.py +52 -0
  398. physicskit/rmt/validation/wigner_semicircle.py +40 -0
  399. physicskit/rmt/validation/wigner_surmise.py +58 -0
  400. physicskit/semiclassical/__init__.py +94 -0
  401. physicskit/semiclassical/_compat.py +5 -0
  402. physicskit/semiclassical/core/__init__.py +41 -0
  403. physicskit/semiclassical/core/gutzwiller.py +215 -0
  404. physicskit/semiclassical/core/path_integral.py +594 -0
  405. physicskit/semiclassical/core/propagators.py +579 -0
  406. physicskit/semiclassical/core/wkb.py +308 -0
  407. physicskit/semiclassical/systems/__init__.py +13 -0
  408. physicskit/semiclassical/systems/scarring.py +278 -0
  409. physicskit/semiclassical/tests/__init__.py +0 -0
  410. physicskit/semiclassical/tests/test_gutzwiller.py +35 -0
  411. physicskit/semiclassical/tests/test_path_integral.py +194 -0
  412. physicskit/semiclassical/tests/test_propagators.py +82 -0
  413. physicskit/semiclassical/tests/test_scarring.py +43 -0
  414. physicskit/semiclassical/tests/test_visualizers.py +89 -0
  415. physicskit/semiclassical/tests/test_wkb.py +30 -0
  416. physicskit/semiclassical/visualizers/__init__.py +17 -0
  417. physicskit/semiclassical/visualizers/gutzwiller.py +55 -0
  418. physicskit/semiclassical/visualizers/path_integral.py +195 -0
  419. physicskit/semiclassical/visualizers/propagators.py +89 -0
  420. physicskit/semiclassical/visualizers/scarring.py +146 -0
  421. physicskit/semiclassical/visualizers/wkb.py +55 -0
  422. physicskit/statphys/__init__.py +80 -0
  423. physicskit/statphys/chapters/__init__.py +43 -0
  424. physicskit/statphys/chapters/ehrenfest_urn.py +112 -0
  425. physicskit/statphys/chapters/ising_lattice.py +431 -0
  426. physicskit/statphys/chapters/kpz_growth.py +129 -0
  427. physicskit/statphys/chapters/molecular_dynamics.py +258 -0
  428. physicskit/statphys/chapters/nonequilibrium_work.py +146 -0
  429. physicskit/statphys/chapters/percolation.py +305 -0
  430. physicskit/statphys/chapters/random_walk.py +142 -0
  431. physicskit/statphys/chapters/renormalization.py +148 -0
  432. physicskit/statphys/chapters/sandpile.py +109 -0
  433. physicskit/statphys/chapters/spin_glass.py +275 -0
  434. physicskit/statphys/core/__init__.py +10 -0
  435. physicskit/statphys/core/kpz_engine.py +53 -0
  436. physicskit/statphys/core/md_engine.py +157 -0
  437. physicskit/statphys/core/monte_carlo.py +462 -0
  438. physicskit/statphys/core/random_walk_engine.py +68 -0
  439. physicskit/statphys/core/sandpile_engine.py +52 -0
  440. physicskit/statphys/tests/__init__.py +0 -0
  441. physicskit/statphys/tests/test_dynamics.py +66 -0
  442. physicskit/statphys/tests/test_ehrenfest_urn.py +44 -0
  443. physicskit/statphys/tests/test_finite_size_scaling.py +62 -0
  444. physicskit/statphys/tests/test_interactive.py +33 -0
  445. physicskit/statphys/tests/test_ising.py +68 -0
  446. physicskit/statphys/tests/test_kpz.py +55 -0
  447. physicskit/statphys/tests/test_landau_theory.py +49 -0
  448. physicskit/statphys/tests/test_md_engine.py +31 -0
  449. physicskit/statphys/tests/test_molecular_dynamics.py +72 -0
  450. physicskit/statphys/tests/test_nonequilibrium_work.py +33 -0
  451. physicskit/statphys/tests/test_partition_function.py +26 -0
  452. physicskit/statphys/tests/test_percolation.py +116 -0
  453. physicskit/statphys/tests/test_potts_model.py +46 -0
  454. physicskit/statphys/tests/test_random_walk.py +50 -0
  455. physicskit/statphys/tests/test_renormalization.py +44 -0
  456. physicskit/statphys/tests/test_sandpile.py +44 -0
  457. physicskit/statphys/tests/test_spin_glass.py +73 -0
  458. physicskit/statphys/tests/test_thermodynamics.py +121 -0
  459. physicskit/statphys/tests/test_xy_model.py +45 -0
  460. physicskit/statphys/utils/__init__.py +1 -0
  461. physicskit/statphys/utils/dynamics.py +100 -0
  462. physicskit/statphys/utils/finite_size_scaling.py +171 -0
  463. physicskit/statphys/utils/landau_theory.py +151 -0
  464. physicskit/statphys/utils/partition_function.py +109 -0
  465. physicskit/statphys/utils/thermodynamics.py +356 -0
  466. physicskit/statphys/visualizers/__init__.py +87 -0
  467. physicskit/statphys/visualizers/interactive.py +161 -0
  468. physicskit/statphys/visualizers/jarzynski_render.py +44 -0
  469. physicskit/statphys/visualizers/kpz_render.py +65 -0
  470. physicskit/statphys/visualizers/lattice_render.py +212 -0
  471. physicskit/statphys/visualizers/particle_render.py +121 -0
  472. physicskit/statphys/visualizers/random_walk_render.py +106 -0
  473. physicskit/statphys/visualizers/rg_render.py +45 -0
  474. physicskit/statphys/visualizers/sandpile_render.py +65 -0
  475. physicskit/statphys/visualizers/spin_glass_render.py +40 -0
  476. physicskit/statphys/visualizers/urn_render.py +38 -0
  477. physicskit/statphys/visualizers/vortex_render.py +84 -0
  478. physicskit/tests/__init__.py +0 -0
  479. physicskit/tests/test_constants.py +38 -0
  480. physicskit-0.1.0.dist-info/METADATA +196 -0
  481. physicskit-0.1.0.dist-info/RECORD +484 -0
  482. physicskit-0.1.0.dist-info/WHEEL +5 -0
  483. physicskit-0.1.0.dist-info/licenses/LICENSE +21 -0
  484. physicskit-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,997 @@
1
+ r"""Quantum boxes and potential wells beyond the flat infinite box.
2
+
3
+ Covers asymmetric/step wells, the V-shaped 'quantum bouncer', the symmetric
4
+ double well (tunneling doublets), the finite square well (bound + continuum
5
+ states), and 2D quantum boxes (rectangle, circular dot, triangular/stadium
6
+ billiards -- the latter bridging to ``physicskit.chaos`` via quantum scars).
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+ import numpy as np
14
+ from scipy.special import airy, jn_zeros, jv
15
+
16
+ from .._compat import trapz
17
+ from ..core.eigensolvers import (
18
+ EigenResult,
19
+ NumerovSolver,
20
+ asymmetric_step_well,
21
+ double_well,
22
+ linear_gravitational_well,
23
+ )
24
+ from ..core.solvers import SplitOperatorSolver1D
25
+ from .wave_packets import free_gaussian_wavepacket
26
+
27
+ __all__ = [
28
+ "asymmetric_well_states",
29
+ "gravitational_bouncer_states",
30
+ "airy_bouncer_energies",
31
+ "airy_wavefunction",
32
+ "DoubleWellSimulator",
33
+ "DoubleWellResult",
34
+ "FiniteSquareWell",
35
+ "ScatteringResult",
36
+ "RectangularBox2D",
37
+ "Box2DEigenstate",
38
+ "CircularBox2D",
39
+ "CircularEigenstate",
40
+ "StadiumBilliard2D",
41
+ ]
42
+
43
+
44
+ # --- Asymmetric / step wells ------------------------------------------------
45
+
46
+
47
+ def asymmetric_well_states(
48
+ width: float = 2.0, V_left: float = 40.0, V_right: float = 15.0, x_extent: float = 6.0, n_points: int = 1200, n_states: int = 6
49
+ ) -> EigenResult:
50
+ """Bound states of a well with unequal wall heights.
51
+
52
+ The wavefunctions decay at different rates into the left vs. right
53
+ forbidden regions (asymmetric evanescent tails), unlike the symmetric
54
+ finite well.
55
+
56
+ Parameters
57
+ ----------
58
+ width : float, default=2.0
59
+ Full width of the zero-potential region.
60
+ V_left : float, default=40.0
61
+ Wall height on the left side.
62
+ V_right : float, default=15.0
63
+ Wall height on the right side.
64
+ x_extent : float, default=6.0
65
+ Half-width of the numerical domain, ``x in [-x_extent, x_extent]``.
66
+ n_points : int, default=1200
67
+ Number of grid points.
68
+ n_states : int, default=6
69
+ Number of bound states to compute.
70
+
71
+ Returns
72
+ -------
73
+ physicskit.quantum.core.eigensolvers.EigenResult
74
+ The energies and wavefunctions.
75
+ """
76
+ x = np.linspace(-x_extent, x_extent, n_points)
77
+ V = asymmetric_step_well(width, V_left, V_right)
78
+ return NumerovSolver(x, V).solve(n_states)
79
+
80
+
81
+ # --- V-shaped (linear, gravitational) well ---------------------------------
82
+
83
+
84
+ def gravitational_bouncer_states(alpha: float = 1.0, x_extent: float = 15.0, n_points: int = 2000, n_states: int = 8) -> EigenResult:
85
+ r"""The 'quantum bouncer': a particle falling under uniform gravity.
86
+
87
+ Solves for :math:`V(x) = \alpha x` (:math:`x>0`) with a hard floor at
88
+ :math:`x=0`. The grid's lower boundary supplies the floor (:math:`\psi`
89
+ is forced to vanish there), so the domain spans only
90
+ :math:`x \in [0, x_\text{extent}]`. Eigenstates are Airy functions
91
+ :math:`\mathrm{Ai}(z)`; the energies returned here should closely match
92
+ :func:`airy_bouncer_energies`.
93
+
94
+ Parameters
95
+ ----------
96
+ alpha : float, default=1.0
97
+ Slope of the linear potential (e.g. :math:`mg` for gravity).
98
+ x_extent : float, default=15.0
99
+ Extent of the numerical domain, ``x in [0, x_extent]``.
100
+ n_points : int, default=2000
101
+ Number of grid points.
102
+ n_states : int, default=8
103
+ Number of bound states to compute.
104
+
105
+ Returns
106
+ -------
107
+ physicskit.quantum.core.eigensolvers.EigenResult
108
+ The energies and wavefunctions.
109
+ """
110
+ x = np.linspace(0.0, x_extent, n_points)
111
+ V = linear_gravitational_well(alpha)
112
+ return NumerovSolver(x, V).solve(n_states)
113
+
114
+
115
+ def airy_bouncer_energies(alpha: float = 1.0, n_states: int = 8, hbar: float = 1.0, m: float = 1.0) -> np.ndarray:
116
+ r"""Analytic bound-state energies for the half-line gravitational bouncer.
117
+
118
+ For :math:`V(x) = \alpha x` (:math:`x>0`, infinite floor at :math:`x=0`),
119
+
120
+ .. math::
121
+
122
+ E_n = -\alpha \left(\frac{\hbar^2}{2 m \alpha^2}\right)^{1/3} a_n,
123
+
124
+ where :math:`a_n < 0` are the zeros of the Airy function,
125
+ :math:`\mathrm{Ai}(a_n) = 0`.
126
+
127
+ Parameters
128
+ ----------
129
+ alpha : float, default=1.0
130
+ Slope of the linear potential.
131
+ n_states : int, default=8
132
+ Number of energy levels to return.
133
+ hbar : float, default=1.0
134
+ Value of :math:`\hbar` to use.
135
+ m : float, default=1.0
136
+ Particle mass.
137
+
138
+ Returns
139
+ -------
140
+ numpy.ndarray
141
+ The lowest ``n_states`` analytic energies, ascending.
142
+ """
143
+ from scipy.special import ai_zeros
144
+
145
+ a_zeros, _, _, _ = ai_zeros(n_states)
146
+ scale = (hbar**2 * alpha**2 / (2 * m)) ** (1 / 3)
147
+ return -a_zeros * scale
148
+
149
+
150
+ def airy_wavefunction(x: np.ndarray, alpha: float = 1.0, n: int = 0, hbar: float = 1.0, m: float = 1.0) -> np.ndarray:
151
+ r"""Normalized Airy-function eigenstate for the half-line bouncer.
152
+
153
+ Parameters
154
+ ----------
155
+ x : numpy.ndarray
156
+ Grid to evaluate the wavefunction on (values at :math:`x<0` are
157
+ forced to zero).
158
+ alpha : float, default=1.0
159
+ Slope of the linear potential :math:`V(x) = \alpha x`.
160
+ n : int, default=0
161
+ Quantum number (0 = ground state).
162
+ hbar : float, default=1.0
163
+ Value of :math:`\hbar` to use.
164
+ m : float, default=1.0
165
+ Particle mass.
166
+
167
+ Returns
168
+ -------
169
+ numpy.ndarray
170
+ The normalized wavefunction :math:`\psi_n(x)`.
171
+ """
172
+ from scipy.special import ai_zeros
173
+
174
+ a_zeros, _, _, _ = ai_zeros(n + 1)
175
+ a_n = a_zeros[n]
176
+ length = (hbar**2 / (2 * m * alpha)) ** (1 / 3)
177
+ # z(x) = x/length + a_n, so that z(0) = a_n (a zero of Ai), satisfying
178
+ # the psi(0)=0 hard-floor boundary condition; x/length - a_n would put
179
+ # z(0) = -a_n > 0, the wrong (decaying) branch, and psi(0) != 0.
180
+ Ai, _, _, _ = airy(x / length + a_n)
181
+ psi = np.where(x >= 0, Ai, 0.0)
182
+ norm = np.sqrt(trapz(psi**2, x))
183
+ return psi / norm
184
+
185
+
186
+ # --- Double well: tunneling doublets ---------------------------------------
187
+
188
+
189
+ @dataclass
190
+ class DoubleWellResult:
191
+ """Container for the output of :meth:`DoubleWellSimulator.solve`."""
192
+
193
+ x: np.ndarray
194
+ """numpy.ndarray: The spatial grid."""
195
+
196
+ energies: np.ndarray
197
+ r"""numpy.ndarray: Eigenenergies :math:`E_n`, ascending."""
198
+
199
+ wavefunctions: np.ndarray
200
+ """numpy.ndarray: Normalized eigenfunctions, shape ``(n_states, len(x))``."""
201
+
202
+ splitting: float
203
+ r"""float: The symmetric/antisymmetric doublet gap :math:`\Delta E = E_1 - E_0`."""
204
+
205
+ tunneling_period: float
206
+ r"""float: The tunneling oscillation period :math:`T = 2\pi\hbar/\Delta E`."""
207
+
208
+
209
+ class DoubleWellSimulator:
210
+ r"""Symmetric double well :math:`V(x) = \lambda (x^2 - a^2)^2`.
211
+
212
+ The two lowest eigenstates form a nearly degenerate symmetric/
213
+ antisymmetric doublet whose splitting :math:`\Delta E = E_1 - E_0`
214
+ sets the period :math:`T=2\pi\hbar/\Delta E` of left-right tunneling
215
+ oscillations of a wavepacket initially localized in one well.
216
+
217
+ Parameters
218
+ ----------
219
+ lam : float, default=0.5
220
+ Overall strength :math:`\lambda` of the quartic potential.
221
+ a : float, default=2.5
222
+ Half-separation of the two minima.
223
+ x_extent : float, default=6.0
224
+ Half-width of the numerical domain, ``x in [-x_extent, x_extent]``.
225
+ n_points : int, default=1600
226
+ Number of grid points.
227
+ hbar : float, default=1.0
228
+ Value of :math:`\hbar` to use.
229
+ m : float, default=1.0
230
+ Particle mass.
231
+ """
232
+
233
+ def __init__(self, lam: float = 0.5, a: float = 2.5, x_extent: float = 6.0, n_points: int = 1600, hbar: float = 1.0, m: float = 1.0):
234
+ self.lam = lam
235
+ self.a = a
236
+ self.hbar = hbar
237
+ self.m = m
238
+ self.x = np.linspace(-x_extent, x_extent, n_points)
239
+ self.V = double_well(lam, a)
240
+ self._solver = NumerovSolver(self.x, self.V, hbar=hbar, m=m)
241
+
242
+ def solve(self, n_states: int = 6) -> DoubleWellResult:
243
+ """Compute the eigenstates and the tunneling splitting/period.
244
+
245
+ Parameters
246
+ ----------
247
+ n_states : int, default=6
248
+ Number of bound states to compute (at least 2, for the doublet).
249
+
250
+ Returns
251
+ -------
252
+ DoubleWellResult
253
+ """
254
+ res = self._solver.solve(n_states)
255
+ splitting = res.energies[1] - res.energies[0]
256
+ period = 2 * np.pi * self.hbar / splitting
257
+ return DoubleWellResult(res.x, res.energies, res.wavefunctions, splitting, period)
258
+
259
+ def localized_state(self, result: DoubleWellResult, side: str = "left") -> np.ndarray:
260
+ r"""Build a state localized in one well from the doublet.
261
+
262
+ .. math::
263
+
264
+ \psi(x) = \frac{\psi_0(x) \pm \psi_1(x)}{\sqrt 2},
265
+
266
+ with ``+`` for the left well and ``-`` for the right well.
267
+
268
+ Parameters
269
+ ----------
270
+ result : DoubleWellResult
271
+ Output of :meth:`solve` (must include at least 2 states).
272
+ side : {'left', 'right'}, default='left'
273
+ Which well to localize the state in.
274
+
275
+ Returns
276
+ -------
277
+ numpy.ndarray
278
+ The localized wavefunction at :math:`t=0`.
279
+ """
280
+ psi0, psi1 = result.wavefunctions[0], result.wavefunctions[1]
281
+ sign = 1.0 if side == "left" else -1.0
282
+ psi = (psi0 + sign * psi1) / np.sqrt(2)
283
+ return psi
284
+
285
+ def tunneling_wavefunction(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
286
+ r"""Time-dependent wavefunction of a state localized on ``side``.
287
+
288
+ Evolves :math:`\psi(x,0) = (\psi_0 \pm \psi_1)/\sqrt2` under the two
289
+ doublet energies; :math:`\lvert\psi(x,t)\rvert^2`
290
+ (:meth:`tunneling_oscillation`) periodically tunnels to the other
291
+ well with period :math:`T=2\pi\hbar/\Delta E`. Shaped for
292
+ :func:`~physicskit.quantum.visualizers.wavefunctions.animate_density`.
293
+
294
+ Parameters
295
+ ----------
296
+ result : DoubleWellResult
297
+ Output of :meth:`solve`.
298
+ t : numpy.ndarray
299
+ Times to evaluate at.
300
+ side : {'left', 'right'}, default='left'
301
+ Which well the state starts localized in.
302
+
303
+ Returns
304
+ -------
305
+ numpy.ndarray
306
+ Complex-valued :math:`\psi(x,t)`, shape ``(len(t), len(x))``.
307
+ """
308
+ psi0, psi1 = result.wavefunctions[0], result.wavefunctions[1]
309
+ E0, E1 = result.energies[0], result.energies[1]
310
+ sign = 1.0 if side == "left" else -1.0
311
+ t = np.asarray(t)
312
+ phase0 = np.exp(-1j * E0 * t / self.hbar)[:, None]
313
+ phase1 = np.exp(-1j * E1 * t / self.hbar)[:, None]
314
+ return (psi0[None, :] * phase0 + sign * psi1[None, :] * phase1) / np.sqrt(2)
315
+
316
+ def tunneling_oscillation(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
317
+ r"""Time-dependent probability density of a state localized on ``side``.
318
+
319
+ See :meth:`tunneling_wavefunction`.
320
+
321
+ Parameters
322
+ ----------
323
+ result : DoubleWellResult
324
+ Output of :meth:`solve`.
325
+ t : numpy.ndarray
326
+ Times to evaluate at.
327
+ side : {'left', 'right'}, default='left'
328
+ Which well the state starts localized in.
329
+
330
+ Returns
331
+ -------
332
+ numpy.ndarray
333
+ :math:`\lvert\psi(x,t)\rvert^2`, shape ``(len(t), len(x))``.
334
+ """
335
+ return np.abs(self.tunneling_wavefunction(result, t, side=side)) ** 2
336
+
337
+ def left_well_probability(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
338
+ r"""Probability of finding the particle in the left well (:math:`x<0`) vs. time.
339
+
340
+ .. math::
341
+
342
+ P(t) = \int_{-\infty}^0 \lvert\psi(x,t)\rvert^2\, dx,
343
+
344
+ oscillating with the tunneling period :math:`T=2\pi\hbar/\Delta E`.
345
+
346
+ Parameters
347
+ ----------
348
+ result : DoubleWellResult
349
+ Output of :meth:`solve`.
350
+ t : numpy.ndarray
351
+ Times to evaluate at.
352
+ side : {'left', 'right'}, default='left'
353
+ Which well the state starts localized in.
354
+
355
+ Returns
356
+ -------
357
+ numpy.ndarray
358
+ :math:`P(t)`, shape ``(len(t),)``.
359
+ """
360
+ density_t = self.tunneling_oscillation(result, t, side=side)
361
+ mask = self.x < 0
362
+ return trapz(density_t[:, mask], self.x[mask], axis=1)
363
+
364
+
365
+ # --- Finite square well: bound states + continuum scattering ---------------
366
+
367
+
368
+ @dataclass
369
+ class ScatteringResult:
370
+ """Container for the output of :meth:`FiniteSquareWell.scattering`."""
371
+
372
+ E: float
373
+ """float: Incident energy."""
374
+
375
+ k1: float
376
+ """float: Wavenumber outside the well."""
377
+
378
+ k2: complex
379
+ """complex: Wavenumber inside the well/barrier. Purely imaginary in a
380
+ classically forbidden interior (sub-barrier tunneling), real otherwise."""
381
+
382
+ T: float
383
+ """float: Transmission probability."""
384
+
385
+ R: float
386
+ r"""float: Reflection probability (:math:`R = 1 - T`)."""
387
+
388
+
389
+ class FiniteSquareWell:
390
+ r"""A finite square well of depth :math:`V_0` and width :math:`a`,
391
+
392
+ .. math::
393
+
394
+ V(x) = \begin{cases} -V_0 & \lvert x\rvert < a/2 \\ 0 & \text{otherwise} \end{cases}.
395
+
396
+ Provides bound states (:math:`E<0`) via the Numerov solver and exact
397
+ analytic transmission/reflection coefficients for scattering states
398
+ (:math:`E>0`).
399
+
400
+ Parameters
401
+ ----------
402
+ V0 : float, default=20.0
403
+ Well depth (:math:`V_0 > 0`; the floor sits at :math:`-V_0`).
404
+ width : float, default=2.0
405
+ Full width :math:`a` of the well.
406
+ hbar : float, default=1.0
407
+ Value of :math:`\hbar` to use.
408
+ m : float, default=1.0
409
+ Particle mass.
410
+ """
411
+
412
+ def __init__(self, V0: float = 20.0, width: float = 2.0, hbar: float = 1.0, m: float = 1.0):
413
+ self.V0 = V0
414
+ self.width = width
415
+ self.hbar = hbar
416
+ self.m = m
417
+
418
+ def potential(self, x: np.ndarray) -> np.ndarray:
419
+ r"""Evaluate :math:`V(x)` (a barrier if ``V0 < 0``).
420
+
421
+ Parameters
422
+ ----------
423
+ x : numpy.ndarray
424
+
425
+ Returns
426
+ -------
427
+ numpy.ndarray
428
+ """
429
+ return np.where(np.abs(x) <= self.width / 2, -self.V0, 0.0)
430
+
431
+ def bound_states(self, x_extent: float = 6.0, n_points: int = 1600, n_states: int = 6) -> EigenResult:
432
+ """Compute the bound states (:math:`E<0`).
433
+
434
+ Parameters
435
+ ----------
436
+ x_extent : float, default=6.0
437
+ Half-width of the numerical domain.
438
+ n_points : int, default=1600
439
+ Number of grid points.
440
+ n_states : int, default=6
441
+ Number of Numerov eigenstates to request (some may be discarded
442
+ if they lie above the well rim; see Notes).
443
+
444
+ Returns
445
+ -------
446
+ physicskit.quantum.core.eigensolvers.EigenResult
447
+ Only the states with :math:`E<0` are kept -- the solver also
448
+ returns box-quantized "continuum" states above the well rim,
449
+ which are artifacts of the finite numerical domain and are
450
+ filtered out here.
451
+ """
452
+ x = np.linspace(-x_extent, x_extent, n_points)
453
+ res = NumerovSolver(x, self.potential, hbar=self.hbar, m=self.m).solve(n_states)
454
+ bound_mask = res.energies < 0
455
+ return EigenResult(res.x, res.energies[bound_mask], res.wavefunctions[bound_mask])
456
+
457
+ def scattering(self, E: float) -> ScatteringResult:
458
+ r"""Exact 1D transmission/reflection coefficients for :math:`E>0`.
459
+
460
+ .. math::
461
+
462
+ T(E) = \left[1 + \frac{V_0^2 \sin^2(k_2 a)}{4 E (E+V_0)}\right]^{-1},
463
+ \qquad R = 1 - T,
464
+
465
+ with :math:`k_1=\sqrt{2mE}/\hbar` outside the well and
466
+ :math:`k_2=\sqrt{2m(E+V_0)}/\hbar` inside it. :math:`T(E)=1`
467
+ exactly whenever :math:`k_2 a` is a multiple of :math:`\pi`
468
+ (the Ramsauer-Townsend resonances).
469
+
470
+ The same formula analytically continues correctly into a
471
+ classically forbidden interior (:math:`E+V_0<0`, e.g. a *barrier*
472
+ built by passing a negative ``V0``): :math:`k_2` becomes purely
473
+ imaginary, :math:`\sin` becomes :math:`i\sinh` of a real argument,
474
+ and :math:`T` reduces to the standard sub-barrier tunneling
475
+ formula. This method evaluates :math:`k_2` with complex arithmetic
476
+ so that regime is handled automatically.
477
+
478
+ Parameters
479
+ ----------
480
+ E : float
481
+ Incident kinetic energy (:math:`E>0`).
482
+
483
+ Returns
484
+ -------
485
+ ScatteringResult
486
+ """
487
+ hbar, m, V0, a = self.hbar, self.m, self.V0, self.width
488
+ k1 = np.sqrt(2 * m * E) / hbar
489
+ k2 = np.sqrt(2 * m * (E + V0) + 0j) / hbar
490
+ T = 1.0 / (1.0 + (V0**2 * np.sin(k2 * a) ** 2) / (4 * E * (E + V0)))
491
+ T = float(np.real_if_close(T))
492
+ R = 1.0 - T
493
+ return ScatteringResult(E=E, k1=k1, k2=k2, T=T, R=R)
494
+
495
+ def transmission_spectrum(self, E_values: np.ndarray) -> np.ndarray:
496
+ """Transmission probability :math:`T(E)` over an array of energies.
497
+
498
+ Parameters
499
+ ----------
500
+ E_values : numpy.ndarray
501
+ Incident energies (:math:`E>0`).
502
+
503
+ Returns
504
+ -------
505
+ numpy.ndarray
506
+ :math:`T(E)` for each value in ``E_values``.
507
+ """
508
+ return np.array([self.scattering(E).T for E in E_values])
509
+
510
+ def evanescent_wavefunction(self, result: EigenResult, n: int = 0) -> np.ndarray:
511
+ r"""The :math:`n`-th bound-state wavefunction.
512
+
513
+ Its exponential tails outside :math:`\lvert x\rvert < a/2` are
514
+ evanescent by construction of the Numerov solver.
515
+
516
+ Parameters
517
+ ----------
518
+ result : physicskit.quantum.core.eigensolvers.EigenResult
519
+ Output of :meth:`bound_states`.
520
+ n : int, default=0
521
+ Which bound state to return (0 = ground state).
522
+
523
+ Returns
524
+ -------
525
+ numpy.ndarray
526
+ """
527
+ return result.wavefunctions[n]
528
+
529
+ def wavepacket_scattering(
530
+ self,
531
+ x_extent: float = 25.0,
532
+ n_points: int = 2048,
533
+ x0: float = -10.0,
534
+ sigma0: float = 1.5,
535
+ k0: float | None = None,
536
+ dt: float = 2e-4,
537
+ n_steps: int = 6000,
538
+ save_every: int = 30,
539
+ ):
540
+ r"""Propagate a Gaussian wavepacket incident on this well/barrier.
541
+
542
+ A single FFT split-operator run
543
+ (:class:`~physicskit.quantum.core.solvers.SplitOperatorSolver1D`) shared
544
+ by the tunnelling (``V0 < 0``, a barrier) and the resonant-scattering
545
+ (``V0 > 0``, a well) demos: the packet splits into a reflected and a
546
+ transmitted piece as it crosses :math:`\lvert x\rvert < \text{width}/2`.
547
+
548
+ Parameters
549
+ ----------
550
+ x_extent : float, default=25.0
551
+ Half-width of the numerical domain.
552
+ n_points : int, default=2048
553
+ Number of grid points.
554
+ x0 : float, default=-10.0
555
+ Initial center of the packet (upstream of the well/barrier).
556
+ sigma0 : float, default=1.5
557
+ Initial packet width.
558
+ k0 : float or None, optional
559
+ Central wavenumber; defaults to a value giving mean kinetic
560
+ energy comparable to :math:`\lvert V_0\rvert`, so both partial
561
+ reflection and partial transmission occur.
562
+ dt : float, default=2e-4
563
+ Time step.
564
+ n_steps : int, default=6000
565
+ Total number of time steps.
566
+ save_every : int, default=30
567
+ Save a snapshot every this many steps.
568
+
569
+ Returns
570
+ -------
571
+ x : numpy.ndarray
572
+ The spatial grid.
573
+ frames : numpy.ndarray
574
+ Complex wavefunction snapshots, shape ``(n_saved, len(x))``.
575
+ times : numpy.ndarray
576
+ The time of each snapshot.
577
+ """
578
+ if k0 is None:
579
+ k0 = np.sqrt(2 * self.m * max(abs(self.V0), 1.0)) / self.hbar
580
+
581
+ x = np.linspace(-x_extent, x_extent, n_points)
582
+ psi0 = free_gaussian_wavepacket(x, x0, sigma0, k0)
583
+ solver = SplitOperatorSolver1D(x, self.potential, hbar=self.hbar, m=self.m, dt=dt)
584
+ frames, times = solver.propagate(psi0, n_steps, save_every=save_every)
585
+ return x, frames, times
586
+
587
+
588
+ # --- 2D rectangular box ------------------------------------------------------
589
+
590
+
591
+ @dataclass
592
+ class Box2DEigenstate:
593
+ """One eigenstate of :class:`RectangularBox2D`."""
594
+
595
+ nx: int
596
+ r"""int: Quantum number along :math:`x` (:math:`\ge 1`)."""
597
+
598
+ ny: int
599
+ r"""int: Quantum number along :math:`y` (:math:`\ge 1`)."""
600
+
601
+ energy: float
602
+ r"""float: The eigenenergy :math:`E_{n_x,n_y}`."""
603
+
604
+ def psi(self, X: np.ndarray, Y: np.ndarray, Lx: float, Ly: float) -> np.ndarray:
605
+ r"""Evaluate the eigenfunction on a grid.
606
+
607
+ .. math::
608
+
609
+ \psi_{n_x,n_y}(x,y) = \sqrt{\frac{4}{L_x L_y}}
610
+ \sin\!\left(\frac{n_x \pi x}{L_x}\right)
611
+ \sin\!\left(\frac{n_y \pi y}{L_y}\right).
612
+
613
+ Parameters
614
+ ----------
615
+ X, Y : numpy.ndarray
616
+ Coordinate meshgrid (e.g. from :meth:`RectangularBox2D.grid`).
617
+ Lx, Ly : float
618
+ Box dimensions.
619
+
620
+ Returns
621
+ -------
622
+ numpy.ndarray
623
+ :math:`\psi_{n_x,n_y}(x,y)` on the grid.
624
+ """
625
+ return np.sqrt(4 / (Lx * Ly)) * np.sin(self.nx * np.pi * X / Lx) * np.sin(self.ny * np.pi * Y / Ly)
626
+
627
+
628
+ class RectangularBox2D:
629
+ r"""Infinite 2D box of size :math:`L_x \times L_y`.
630
+
631
+ .. math::
632
+
633
+ E_{n_x,n_y} = \frac{\pi^2\hbar^2}{2m}
634
+ \left(\frac{n_x^2}{L_x^2} + \frac{n_y^2}{L_y^2}\right).
635
+
636
+ Rational ratios :math:`L_x/L_y` (e.g. the square box :math:`1{:}1`)
637
+ produce accidental degeneracies between distinct :math:`(n_x,n_y)` pairs.
638
+
639
+ Parameters
640
+ ----------
641
+ Lx, Ly : float, default=1.0
642
+ Box dimensions.
643
+ hbar : float, default=1.0
644
+ Value of :math:`\hbar` to use.
645
+ m : float, default=1.0
646
+ Particle mass.
647
+ """
648
+
649
+ def __init__(self, Lx: float = 1.0, Ly: float = 1.0, hbar: float = 1.0, m: float = 1.0):
650
+ self.Lx, self.Ly, self.hbar, self.m = Lx, Ly, hbar, m
651
+
652
+ def energy(self, nx: int, ny: int) -> float:
653
+ r"""Eigenenergy :math:`E_{n_x,n_y}`.
654
+
655
+ Parameters
656
+ ----------
657
+ nx, ny : int
658
+ Quantum numbers (each :math:`\ge 1`).
659
+
660
+ Returns
661
+ -------
662
+ float
663
+ """
664
+ return (np.pi**2 * self.hbar**2 / (2 * self.m)) * ((nx / self.Lx) ** 2 + (ny / self.Ly) ** 2)
665
+
666
+ def spectrum(self, n_max: int = 6) -> list:
667
+ """All eigenstates with :math:`1 \\le n_x, n_y \\le n_\\text{max}`, sorted by energy.
668
+
669
+ Parameters
670
+ ----------
671
+ n_max : int, default=6
672
+ Maximum quantum number along each axis.
673
+
674
+ Returns
675
+ -------
676
+ list of Box2DEigenstate
677
+ """
678
+ states = [Box2DEigenstate(nx, ny, self.energy(nx, ny)) for nx in range(1, n_max + 1) for ny in range(1, n_max + 1)]
679
+ return sorted(states, key=lambda s: s.energy)
680
+
681
+ def degeneracies(self, n_max: int = 6, tol: float = 1e-9) -> dict:
682
+ """Group states by (rounded) energy to reveal degenerate levels.
683
+
684
+ Parameters
685
+ ----------
686
+ n_max : int, default=6
687
+ Maximum quantum number along each axis.
688
+ tol : float, default=1e-9
689
+ Energy values within ``tol`` are treated as degenerate.
690
+
691
+ Returns
692
+ -------
693
+ dict
694
+ Maps each degenerate energy to its list of ``(nx, ny)`` pairs;
695
+ non-degenerate levels are omitted.
696
+ """
697
+ groups: dict = {}
698
+ for s in self.spectrum(n_max):
699
+ key = round(s.energy / tol) * tol
700
+ groups.setdefault(key, []).append((s.nx, s.ny))
701
+ return {E: pairs for E, pairs in groups.items() if len(pairs) > 1}
702
+
703
+ def grid(self, n_points: int = 200):
704
+ """A Cartesian meshgrid spanning the box.
705
+
706
+ Parameters
707
+ ----------
708
+ n_points : int, default=200
709
+ Number of points along each axis.
710
+
711
+ Returns
712
+ -------
713
+ X, Y : numpy.ndarray
714
+ Meshgrid arrays (``indexing='ij'``).
715
+ """
716
+ x = np.linspace(0, self.Lx, n_points)
717
+ y = np.linspace(0, self.Ly, n_points)
718
+ return np.meshgrid(x, y, indexing="ij")
719
+
720
+
721
+ # --- 2D circular quantum dot -------------------------------------------------
722
+
723
+
724
+ @dataclass
725
+ class CircularEigenstate:
726
+ """One eigenstate of :class:`CircularBox2D`."""
727
+
728
+ m: int
729
+ """int: Angular momentum quantum number."""
730
+
731
+ n: int
732
+ r"""int: Radial quantum number (the state uses the :math:`n`-th positive
733
+ zero of :math:`J_{\lvert m\rvert}`)."""
734
+
735
+ k: float
736
+ r"""float: Wavenumber :math:`k_{mn} = z_{mn}/R`."""
737
+
738
+ energy: float
739
+ r"""float: The eigenenergy :math:`E_{mn} = \hbar^2 k_{mn}^2 / 2m`."""
740
+
741
+ def psi(self, r: np.ndarray, phi: np.ndarray, R: float) -> np.ndarray:
742
+ r"""Evaluate the eigenfunction on a polar grid.
743
+
744
+ .. math::
745
+
746
+ \psi_{mn}(r,\phi) = N_{mn}\, J_m(k_{mn} r)\, e^{i m \phi}.
747
+
748
+ Parameters
749
+ ----------
750
+ r, phi : numpy.ndarray
751
+ Polar coordinate meshgrid (e.g. from :meth:`CircularBox2D.polar_grid`).
752
+ R : float
753
+ Dot radius (used to normalize :math:`N_{mn}`).
754
+
755
+ Returns
756
+ -------
757
+ numpy.ndarray
758
+ Complex-valued :math:`\psi_{mn}(r,\phi)`.
759
+ """
760
+ radial = jv(self.m, self.k * r)
761
+ norm = 1.0 / np.sqrt(np.pi * R**2 * jv(self.m + 1, self.k * R) ** 2)
762
+ return norm * radial * np.exp(1j * self.m * phi)
763
+
764
+
765
+ class CircularBox2D:
766
+ r"""Circular infinite well (quantum dot) of radius :math:`R`.
767
+
768
+ Eigenstates are Bessel functions
769
+ :math:`\psi_{mn}(r,\phi) \propto J_m(k_{mn} r)\, e^{im\phi}`, vanishing
770
+ at :math:`r=R`, showing nodal rings (radial quantum number :math:`n`)
771
+ and angular momentum quantization (:math:`m`).
772
+
773
+ Parameters
774
+ ----------
775
+ R : float, default=1.0
776
+ Dot radius.
777
+ hbar : float, default=1.0
778
+ Value of :math:`\hbar` to use.
779
+ m_mass : float, default=1.0
780
+ Particle mass (named to avoid clashing with the angular momentum
781
+ quantum number ``m``).
782
+ """
783
+
784
+ def __init__(self, R: float = 1.0, hbar: float = 1.0, m_mass: float = 1.0):
785
+ self.R = R
786
+ self.hbar = hbar
787
+ self.m_mass = m_mass
788
+
789
+ def eigenstate(self, m: int, n: int) -> CircularEigenstate:
790
+ r"""Build the :math:`(m,n)` eigenstate.
791
+
792
+ Parameters
793
+ ----------
794
+ m : int
795
+ Angular momentum quantum number.
796
+ n : int
797
+ Radial index (:math:`n=1,2,3,\dots`); the state uses the
798
+ :math:`n`-th positive zero of the order-:math:`\lvert m\rvert`
799
+ Bessel function.
800
+
801
+ Returns
802
+ -------
803
+ CircularEigenstate
804
+ """
805
+ zero = jn_zeros(abs(m), n)[-1]
806
+ k = zero / self.R
807
+ energy = self.hbar**2 * k**2 / (2 * self.m_mass)
808
+ return CircularEigenstate(m, n, k, energy)
809
+
810
+ def spectrum(self, m_max: int = 4, n_max: int = 4) -> list:
811
+ """All eigenstates with :math:`\\lvert m\\rvert \\le m_\\text{max}`,
812
+ :math:`1 \\le n \\le n_\\text{max}`, sorted by energy.
813
+
814
+ Parameters
815
+ ----------
816
+ m_max : int, default=4
817
+ Maximum :math:`\\lvert m \\rvert`.
818
+ n_max : int, default=4
819
+ Maximum radial index.
820
+
821
+ Returns
822
+ -------
823
+ list of CircularEigenstate
824
+ """
825
+ states = [self.eigenstate(m, n) for m in range(-m_max, m_max + 1) for n in range(1, n_max + 1)]
826
+ return sorted(states, key=lambda s: s.energy)
827
+
828
+ def polar_grid(self, n_r: int = 150, n_phi: int = 150):
829
+ """A polar meshgrid spanning the dot.
830
+
831
+ Parameters
832
+ ----------
833
+ n_r : int, default=150
834
+ Number of radial points.
835
+ n_phi : int, default=150
836
+ Number of angular points.
837
+
838
+ Returns
839
+ -------
840
+ r, phi : numpy.ndarray
841
+ Meshgrid arrays (``indexing='ij'``).
842
+ """
843
+ r = np.linspace(0, self.R, n_r)
844
+ phi = np.linspace(0, 2 * np.pi, n_phi)
845
+ return np.meshgrid(r, phi, indexing="ij")
846
+
847
+
848
+ # --- Triangular / stadium billiard (quantum scars, bridge to physicskit.chaos) -----
849
+
850
+
851
+ class StadiumBilliard2D:
852
+ """Bunimovich stadium billiard: a rectangle of length ``L`` capped by two
853
+ semicircles of radius ``R``.
854
+
855
+ The classical dynamics is chaotic, and the quantum eigenstates of this
856
+ box can exhibit *scars* -- probability density concentrated along
857
+ unstable classical periodic orbits -- making this the natural bridge
858
+ between ``physicskit.quantum`` and ``physicskit.chaos``.
859
+
860
+ Eigenstates are obtained by direct diagonalization of the 2D
861
+ finite-difference Laplacian on a grid, with the wavefunction forced to
862
+ zero outside the stadium boundary (infinite-wall billiard).
863
+
864
+ Parameters
865
+ ----------
866
+ L : float, default=1.0
867
+ Length of the central rectangular section.
868
+ R : float, default=0.5
869
+ Radius of the semicircular end-caps (also the half-height).
870
+ hbar : float, default=1.0
871
+ Value of :math:`\\hbar` to use.
872
+ m : float, default=1.0
873
+ Particle mass.
874
+ """
875
+
876
+ def __init__(self, L: float = 1.0, R: float = 0.5, hbar: float = 1.0, m: float = 1.0):
877
+ self.L = L
878
+ self.R = R
879
+ self.hbar = hbar
880
+ self.m = m
881
+
882
+ def mask(self, X: np.ndarray, Y: np.ndarray) -> np.ndarray:
883
+ """Boolean mask, ``True`` inside the stadium.
884
+
885
+ The stadium is a central :math:`L \\times 2R` rectangle with two
886
+ semicircular end-caps of radius :math:`R`.
887
+
888
+ Parameters
889
+ ----------
890
+ X, Y : numpy.ndarray
891
+ Coordinate meshgrid (e.g. from :meth:`grid`).
892
+
893
+ Returns
894
+ -------
895
+ numpy.ndarray of bool
896
+ """
897
+ inside_rect = (np.abs(X) <= self.L / 2) & (np.abs(Y) <= self.R)
898
+ left_cap = (X < -self.L / 2) & ((X + self.L / 2) ** 2 + Y**2 <= self.R**2)
899
+ right_cap = (X > self.L / 2) & ((X - self.L / 2) ** 2 + Y**2 <= self.R**2)
900
+ return inside_rect | left_cap | right_cap
901
+
902
+ def grid(self, n_points: int = 120):
903
+ """A Cartesian meshgrid enclosing the stadium.
904
+
905
+ Parameters
906
+ ----------
907
+ n_points : int, default=120
908
+ Number of points along the (longer) x-axis.
909
+
910
+ Returns
911
+ -------
912
+ (X, Y) : tuple of numpy.ndarray
913
+ Meshgrid arrays (``indexing='ij'``).
914
+ x, y : numpy.ndarray
915
+ The 1D coordinate arrays used to build the meshgrid.
916
+ """
917
+ half_x = self.L / 2 + self.R
918
+ half_y = self.R * 1.05
919
+ x = np.linspace(-half_x, half_x, n_points)
920
+ dx = x[1] - x[0]
921
+ # Force dy == dx (an isotropic grid): with mismatched spacing the
922
+ # semicircular caps are staircase-approximated as ellipses rather
923
+ # than circles, which was causing non-monotonic, resolution-
924
+ # dependent errors in the eigenvalues.
925
+ n_y = max(int(round(2 * half_y / dx)) + 1, 20)
926
+ y = np.linspace(-half_y, half_y, n_y)
927
+ return np.meshgrid(x, y, indexing="ij"), x, y
928
+
929
+ def solve(self, n_points: int = 160, n_states: int = 6):
930
+ r"""Diagonalize :math:`-\hbar^2/2m\,\nabla^2` on the masked grid
931
+ (Dirichlet boundary).
932
+
933
+ The curved end-caps are approximated by a "staircased" boundary
934
+ (grid points are simply included or excluded by :meth:`mask`),
935
+ which converges only as :math:`O(h)`: low resolutions give
936
+ eigenvalues with a few percent of resolution-dependent error and
937
+ visibly blocky eigenstate edges. :meth:`grid` keeps the grid
938
+ isotropic (:math:`dx=dy`) so the caps are at least staircased as
939
+ circles rather than ellipses; beyond that, accuracy is improved
940
+ simply by raising ``n_points`` -- the sparse eigensolver used here
941
+ stays fast (well under a second) even at ``n_points=260``.
942
+
943
+ Parameters
944
+ ----------
945
+ n_points : int, default=160
946
+ Number of grid points along the x-axis (see :meth:`grid`).
947
+ n_states : int, default=6
948
+ Number of lowest-energy eigenstates to compute.
949
+
950
+ Returns
951
+ -------
952
+ energies : numpy.ndarray
953
+ Eigenenergies, ascending, shape ``(n_states,)``.
954
+ wavefunctions : numpy.ndarray
955
+ Normalized eigenfunctions, shape ``(n_states, *X.shape)``, zero
956
+ outside the stadium.
957
+ X, Y : numpy.ndarray
958
+ The coordinate meshgrid.
959
+ mask : numpy.ndarray of bool
960
+ ``True`` inside the stadium (see :meth:`mask`).
961
+ """
962
+ (X, Y), x, y = self.grid(n_points)
963
+ inside = self.mask(X, Y)
964
+ idx = -np.ones(X.shape, dtype=int)
965
+ interior_points = np.argwhere(inside)
966
+ for order, (i, j) in enumerate(interior_points):
967
+ idx[i, j] = order
968
+ n_interior = interior_points.shape[0]
969
+
970
+ hx = x[1] - x[0]
971
+ hy = y[1] - y[0]
972
+ from scipy.sparse import lil_matrix
973
+ from scipy.sparse.linalg import eigsh
974
+
975
+ H = lil_matrix((n_interior, n_interior))
976
+ coeff = self.hbar**2 / (2 * self.m)
977
+ for order, (i, j) in enumerate(interior_points):
978
+ H[order, order] = coeff * (2 / hx**2 + 2 / hy**2)
979
+ for di, dj, h in ((1, 0, hx), (-1, 0, hx), (0, 1, hy), (0, -1, hy)):
980
+ ni, nj = i + di, j + dj
981
+ if 0 <= ni < X.shape[0] and 0 <= nj < X.shape[1] and idx[ni, nj] >= 0:
982
+ H[order, idx[ni, nj]] = -coeff / h**2
983
+
984
+ energies, vecs = eigsh(H.tocsr(), k=n_states, which="SM")
985
+ order = np.argsort(energies)
986
+ energies = energies[order]
987
+ vecs = vecs[:, order]
988
+
989
+ wavefunctions = np.zeros((n_states, *X.shape))
990
+ for k in range(n_states):
991
+ psi = np.zeros(X.shape)
992
+ for order_idx, (i, j) in enumerate(interior_points):
993
+ psi[i, j] = vecs[order_idx, k]
994
+ norm = np.sqrt(trapz(trapz(psi**2, y, axis=1), x))
995
+ wavefunctions[k] = psi / norm
996
+
997
+ return energies, wavefunctions, X, Y, inside