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,272 @@
1
+ """Number variance Sigma^2(L): a long-range spectral rigidity statistic,
2
+ more discriminating than nearest-neighbor spacing alone at distinguishing
3
+ correlated (GOE/GUE/GSE) from uncorrelated (Poisson) level sequences.
4
+
5
+ References
6
+ ----------
7
+ F. J. Dyson, M. L. Mehta, "Statistical Theory of the Energy Levels of
8
+ Complex Systems. IV", J. Math. Phys. 4 (1963) 701 -- the original
9
+ number variance formulas.
10
+ M. L. Mehta, *Random Matrices* (3rd ed.), Academic Press, 2004.
11
+
12
+ Sigma^2(L) is the variance of the number of (unfolded) eigenvalues found
13
+ in a randomly placed interval of length L. For an uncorrelated (Poisson)
14
+ sequence, Sigma^2(L) = L exactly. Level repulsion in GOE/GUE/GSE makes
15
+ the spectrum far more "rigid": Sigma^2(L) grows only logarithmically,
16
+
17
+ Sigma^2_beta(L) = (2 / (beta * pi^2)) * ln(L) + K_beta + O(1/L)
18
+
19
+ (Dyson-Mehta 1963) -- the leading log-coefficient's 1/beta dependence is
20
+ generic across the Gaussian ensembles; the additive constant K_beta
21
+ differs by symmetry class.
22
+
23
+ Two levels of precision are implemented:
24
+
25
+ - **beta=2 (GUE): exact**, via direct numerical integration of the
26
+ bulk two-point correlation function already validated for CUE in
27
+ ``physicskit.rmt.stats.correlations`` (spacing/rigidity statistics are
28
+ universal in beta across the Gaussian and circular families, so the
29
+ same sine-kernel Y_2(r) = (sin(pi*r)/(pi*r))^2 applies here)::
30
+
31
+ Sigma^2(L) = L - 2 * integral_0^L (L - r) * Y_2(r) dr
32
+
33
+ This is not merely asymptotic -- it holds at any L within the bulk
34
+ sine-kernel limit, and was verified numerically during development
35
+ against a Monte Carlo GUE window-counting estimate.
36
+
37
+ - **beta=1 (GOE) and beta=4 (GSE): large-L asymptotic formulas**,
38
+ since deriving beta=1,4 analogues of the sine-kernel integral would
39
+ require the fuller Pfaffian point-process machinery deferred
40
+ elsewhere in this package (see ``physicskit.rmt.stats.correlations``). The
41
+ GOE formula is confirmed by two independent secondary sources; the
42
+ GSE formula follows the same commonly-cited structural pattern and
43
+ was verified numerically against Monte Carlo GSE window-counting
44
+ before being included here (both confirmed to agree with simulation
45
+ at L = 5, 10, 20 to within a few percent -- see
46
+ ``tests/test_rigidity.py``).
47
+
48
+ Also implemented here: the companion Dyson-Mehta Delta_3(L) statistic
49
+ (least-squares deviation of the staircase counting function from a
50
+ locally fit straight line), via ``spectral_rigidity_empirical`` (a
51
+ direct, formula-free estimator valid for any point process) and
52
+ ``spectral_rigidity_theory`` (the exact integral relation to Sigma^2(L),
53
+ inheriting the same beta=2-exact / beta=1,4-asymptotic precision).
54
+ """
55
+
56
+ from __future__ import annotations
57
+
58
+ from collections.abc import Callable
59
+
60
+ import numpy as np
61
+ from scipy.integrate import quad
62
+
63
+ from ..spectrum import Spectrum
64
+
65
+ _GAMMA_E = 0.5772156649015329 # Euler-Mascheroni constant
66
+
67
+
68
+ def number_variance_empirical(
69
+ spectrum: Spectrum,
70
+ cdf_func: Callable[[np.ndarray], np.ndarray],
71
+ l_values: np.ndarray,
72
+ n_windows: int = 200,
73
+ seed: int | np.random.Generator | None = None,
74
+ ) -> np.ndarray:
75
+ """Estimate Sigma^2(L) for each L in ``l_values`` by counting
76
+ unfolded eigenvalues in many randomly placed windows.
77
+
78
+ Parameters
79
+ ----------
80
+ spectrum : physicskit.rmt.spectrum.Spectrum
81
+ cdf_func : callable
82
+ Theoretical CDF used for unfolding (e.g.
83
+ ``physicskit.rmt.stats.density.semicircle_cdf``).
84
+ l_values : sequence of float
85
+ n_windows : int
86
+ Random window placements per sample.
87
+ seed : int or None
88
+
89
+ Returns
90
+ -------
91
+ numpy.ndarray
92
+ Sigma^2(L) estimates, one per entry in ``l_values``.
93
+ """
94
+ rng = np.random.default_rng(seed)
95
+ results = []
96
+ for length in l_values:
97
+ all_counts = []
98
+ for row in spectrum.rescaled:
99
+ n = len(row)
100
+ unfolded = n * cdf_func(row)
101
+ lo, hi = unfolded[0], unfolded[-1]
102
+ if hi - lo < 3 * length:
103
+ continue # window plus margin doesn't fit; skip this sample at this L
104
+ starts = rng.uniform(lo, hi - length, size=n_windows)
105
+ counts = np.array([np.sum((unfolded >= s) & (unfolded < s + length)) for s in starts])
106
+ all_counts.append(counts)
107
+ results.append(np.var(np.concatenate(all_counts)))
108
+ return np.array(results)
109
+
110
+
111
+ def number_variance_poisson(l_values: np.ndarray) -> np.ndarray:
112
+ """Exact number variance for an uncorrelated (Poisson) sequence:
113
+ Sigma^2(L) = L. The "soft spectrum" baseline that GOE/GUE/GSE's
114
+ logarithmic rigidity is contrasted against."""
115
+ return np.asarray(l_values, dtype=float)
116
+
117
+
118
+ def _sine_kernel_y2(r: np.ndarray) -> np.ndarray:
119
+ r = np.asarray(r, dtype=float)
120
+ out = np.ones_like(r)
121
+ mask = np.abs(r) > 1e-10
122
+ out[mask] = (np.sin(np.pi * r[mask]) / (np.pi * r[mask])) ** 2
123
+ return out
124
+
125
+
126
+ def number_variance_gue_exact(l_values: np.ndarray) -> np.ndarray:
127
+ """Exact bulk-sine-kernel number variance (beta=2 / GUE / CUE):
128
+ Sigma^2(L) = L - 2 * integral_0^L (L-r) * Y_2(r) dr.
129
+ """
130
+ l_values = np.atleast_1d(np.asarray(l_values, dtype=float))
131
+ out = np.empty_like(l_values)
132
+ for i, length in enumerate(l_values):
133
+ # limit=200 (vs scipy's default 50): the oscillatory sine-kernel
134
+ # integrand at large L otherwise triggers a subdivision-limit
135
+ # warning even though the result is already converged (verified
136
+ # numerically -- the value is identical to 1e-7 either way, this
137
+ # just silences a spurious warning rather than fixing an
138
+ # accuracy problem).
139
+ val, _ = quad(lambda r, length=length: (length - r) * _sine_kernel_y2(r), 0, length, limit=200)
140
+ out[i] = length - 2.0 * val
141
+ return out
142
+
143
+
144
+ def number_variance_goe_asymptotic(l_values: np.ndarray) -> np.ndarray:
145
+ """Large-L asymptotic number variance for beta=1 (GOE):
146
+ Sigma^2(L) = (2/pi^2) * [ln(2*pi*L) + gamma_E + 1 - pi^2/8].
147
+ """
148
+ l_values = np.asarray(l_values, dtype=float)
149
+ return (2.0 / np.pi**2) * (np.log(2 * np.pi * l_values) + _GAMMA_E + 1.0 - np.pi**2 / 8.0)
150
+
151
+
152
+ def number_variance_gse_asymptotic(l_values: np.ndarray) -> np.ndarray:
153
+ """Large-L asymptotic number variance for beta=4 (GSE):
154
+ Sigma^2(L) = (1/(2*pi^2)) * [ln(4*pi*L) + gamma_E + 1 + pi^2/8].
155
+ """
156
+ l_values = np.asarray(l_values, dtype=float)
157
+ return (1.0 / (2 * np.pi**2)) * (np.log(4 * np.pi * l_values) + _GAMMA_E + 1.0 + np.pi**2 / 8.0)
158
+
159
+
160
+ def number_variance_theory(l_values: np.ndarray, beta: float) -> np.ndarray:
161
+ """Dispatch to the appropriate theoretical Sigma^2(L) by beta:
162
+ exact for beta=2, large-L asymptotic for beta=1, 4."""
163
+ if beta == 1:
164
+ return number_variance_goe_asymptotic(l_values)
165
+ if beta == 2:
166
+ return number_variance_gue_exact(l_values)
167
+ if beta == 4:
168
+ return number_variance_gse_asymptotic(l_values)
169
+ raise ValueError(f"Only beta in (1, 2, 4) have implemented formulas here, got {beta}")
170
+
171
+
172
+ def spectral_rigidity_empirical(
173
+ spectrum: Spectrum,
174
+ cdf_func: Callable[[np.ndarray], np.ndarray],
175
+ l_values: np.ndarray,
176
+ n_windows: int = 200,
177
+ n_grid_per_window: int = 1500,
178
+ seed: int | np.random.Generator | None = None,
179
+ ) -> np.ndarray:
180
+ """Estimate the Dyson-Mehta Delta_3(L) statistic for each L in
181
+ ``l_values``, directly from its definition: the mean-square deviation
182
+ of the (unfolded) staircase counting function N(xi) from its best-fit
183
+ line, over many randomly placed windows of length L.
184
+
185
+ Delta_3(L) = (1/L) * min_{A,B} <integral_x^{x+L} (N(xi) - A*xi - B)^2 dxi>
186
+
187
+ Unlike ``number_variance_theory``, no closed-form kernel is assumed
188
+ here -- each window's line fit is solved numerically (least squares
189
+ against N(xi) sampled on a fine grid), so this estimator is valid for
190
+ ANY point process, not just the classical beta-ensembles. Verified
191
+ against the exact Poisson result (``spectral_rigidity_poisson``,
192
+ Delta_3(L) = L/15) and against ``spectral_rigidity_theory`` at
193
+ beta=2 (GUE) during development -- both matched to within Monte Carlo
194
+ noise (a few percent) -- see ``tests/test_rigidity.py``.
195
+
196
+ Parameters
197
+ ----------
198
+ spectrum : physicskit.rmt.spectrum.Spectrum
199
+ cdf_func : callable
200
+ Theoretical CDF used for unfolding (e.g.
201
+ ``physicskit.rmt.stats.density.semicircle_cdf``).
202
+ l_values : numpy.ndarray
203
+ n_windows : int
204
+ Random window placements per sample.
205
+ n_grid_per_window : int
206
+ Grid resolution used to approximate the line-fit integral within
207
+ each window.
208
+ seed : int, numpy.random.Generator, or None, optional
209
+
210
+ Returns
211
+ -------
212
+ numpy.ndarray
213
+ Delta_3(L) estimates, one per entry in ``l_values``.
214
+ """
215
+ rng = np.random.default_rng(seed)
216
+ results = []
217
+ for length in l_values:
218
+ vals = []
219
+ for row in spectrum.rescaled:
220
+ n = len(row)
221
+ unfolded = n * cdf_func(row)
222
+ lo, hi = unfolded[0], unfolded[-1]
223
+ if hi - lo < 3 * length:
224
+ continue
225
+ starts = rng.uniform(lo, hi - length, size=n_windows)
226
+ for start in starts:
227
+ grid = np.linspace(start, start + length, n_grid_per_window)
228
+ counts = np.searchsorted(unfolded, grid, side="right").astype(float)
229
+ design = np.vstack([grid, np.ones_like(grid)]).T
230
+ coeffs, *_ = np.linalg.lstsq(design, counts, rcond=None)
231
+ residual = counts - design @ coeffs
232
+ vals.append(np.mean(residual**2))
233
+ results.append(np.mean(vals))
234
+ return np.array(results)
235
+
236
+
237
+ def spectral_rigidity_poisson(l_values: np.ndarray) -> np.ndarray:
238
+ """Exact Dyson-Mehta Delta_3(L) for an uncorrelated (Poisson)
239
+ sequence: Delta_3(L) = L/15 -- verified numerically here against
240
+ ``spectral_rigidity_empirical`` on ``PoissonEnsemble`` samples (see
241
+ ``tests/test_rigidity.py``), the same "soft spectrum" baseline
242
+ ``number_variance_poisson`` provides for Sigma^2(L)."""
243
+ return np.asarray(l_values, dtype=float) / 15.0
244
+
245
+
246
+ def spectral_rigidity_theory(l_values: np.ndarray, beta: float) -> np.ndarray:
247
+ """Theoretical Delta_3(L) at Dyson index beta (1, 2, or 4), via its
248
+ exact integral relation to the number variance:
249
+
250
+ Delta_3(L) = (2/L^4) * integral_0^L (L^3 - 2*L^2*r + r^3) * Sigma^2(r) dr
251
+
252
+ (Dyson-Mehta 1963; also e.g. Guhr, Mueller-Groeling, Weidenmueller,
253
+ "Random-matrix theories in quantum physics", Phys. Rep. 299 (1998)
254
+ 189, Eq. (2.14).) Sigma^2 is dispatched via
255
+ ``number_variance_theory``, so this inherits the same precision:
256
+ exact at beta=2, large-L asymptotic at beta=1, 4. Verified numerically
257
+ against ``spectral_rigidity_empirical`` during development: beta=2
258
+ matched Monte Carlo GUE to within ~1%, beta=1 matched Monte Carlo GOE
259
+ to within the same few-percent tolerance already documented for
260
+ ``number_variance_goe_asymptotic`` -- see ``tests/test_rigidity.py``.
261
+ """
262
+ l_values = np.atleast_1d(np.asarray(l_values, dtype=float))
263
+ out = np.empty_like(l_values)
264
+ for i, length in enumerate(l_values):
265
+
266
+ def integrand(r: float, length: float = length) -> float:
267
+ sigma2 = number_variance_theory(np.array([r]), beta)[0]
268
+ return (length**3 - 2.0 * length**2 * r + r**3) * sigma2
269
+
270
+ val, _ = quad(integrand, 0, length, limit=200)
271
+ out[i] = 2.0 / length**4 * val
272
+ return out
@@ -0,0 +1,95 @@
1
+ """The single ring theorem's radii formula (see
2
+ ``physicskit.rmt.ensembles.single_ring`` for the full construction and
3
+ references): a bi-unitarily-invariant non-Hermitian ensemble's
4
+ eigenvalues fill the annulus ``r_in <= |z| <= r_out``, with
5
+
6
+ r_out = sqrt(<s^2>) r_in = 1 / sqrt(<1/s^2>)
7
+
8
+ for singular values s.
9
+ """
10
+
11
+ import numpy as np
12
+
13
+
14
+ def single_ring_radii(singular_values: np.ndarray) -> tuple[float, float]:
15
+ """Theoretical single-ring radii (r_in, r_out) from a set of
16
+ singular values (or samples from the limiting singular-value
17
+ distribution).
18
+
19
+ Parameters
20
+ ----------
21
+ singular_values : numpy.ndarray
22
+
23
+ Returns
24
+ -------
25
+ r_in : float
26
+ r_out : float
27
+ """
28
+ singular_values = np.asarray(singular_values, dtype=float)
29
+ r_out = np.sqrt(np.mean(singular_values**2))
30
+ r_in = 1.0 / np.sqrt(np.mean(1.0 / singular_values**2))
31
+ return float(r_in), float(r_out)
32
+
33
+
34
+ def single_ring_radii_wishart_theory(gamma: float) -> tuple[float, float]:
35
+ """Exact single-ring radii for ``NonHermitianWishartEnsemble`` at
36
+ aspect ratio gamma = n/m < 1: r_out = 1, r_in = sqrt(1 - gamma), from
37
+ the Marchenko-Pastur distribution's exact moments E[X] = 1 and
38
+ E[1/X] = 1/(1-gamma) -- verified numerically during development (see
39
+ ``tests/test_single_ring.py``) before being used here.
40
+
41
+ Parameters
42
+ ----------
43
+ gamma : float
44
+ Aspect ratio n/m, must be in (0, 1).
45
+
46
+ Returns
47
+ -------
48
+ r_in : float
49
+ r_out : float
50
+ """
51
+ if not 0.0 < gamma < 1.0:
52
+ raise ValueError(f"gamma must be in (0, 1), got {gamma}")
53
+ return float(np.sqrt(1.0 - gamma)), 1.0
54
+
55
+
56
+ def annulus_radial_cdf(r: np.ndarray, r_in: float, r_out: float) -> np.ndarray:
57
+ """Radial marginal CDF of the uniform distribution on the annulus
58
+ ``r_in <= |z| <= r_out``: F(r) = (r^2 - r_in^2) / (r_out^2 - r_in^2),
59
+ the direct generalization of
60
+ ``physicskit.rmt.stats.circular_law.circular_law_radial_cdf`` (its r_in=0,
61
+ r_out=1 special case).
62
+
63
+ Parameters
64
+ ----------
65
+ r : numpy.ndarray
66
+ r_in : float
67
+ r_out : float
68
+
69
+ Returns
70
+ -------
71
+ numpy.ndarray
72
+ """
73
+ r = np.clip(np.asarray(r, dtype=float), r_in, r_out)
74
+ return (r**2 - r_in**2) / (r_out**2 - r_in**2)
75
+
76
+
77
+ def annulus_radial_pdf(r: np.ndarray, r_in: float, r_out: float) -> np.ndarray:
78
+ """Radial marginal density of the uniform distribution on the
79
+ annulus ``r_in <= |z| <= r_out``: f(r) = 2r / (r_out^2 - r_in^2).
80
+
81
+ Parameters
82
+ ----------
83
+ r : numpy.ndarray
84
+ r_in : float
85
+ r_out : float
86
+
87
+ Returns
88
+ -------
89
+ numpy.ndarray
90
+ """
91
+ r = np.asarray(r, dtype=float)
92
+ out = np.zeros_like(r)
93
+ mask = (r >= r_in) & (r <= r_out)
94
+ out[mask] = 2.0 * r[mask] / (r_out**2 - r_in**2)
95
+ return out
@@ -0,0 +1,150 @@
1
+ """Nearest-neighbor spacing distribution P(s), and the (generalized)
2
+ Wigner surmise it is validated against.
3
+
4
+ References
5
+ ----------
6
+ E. P. Wigner -- the original surmise, for GOE (beta=1).
7
+ M. L. Mehta, *Random Matrices* (3rd ed.), Academic Press, 2004 -- the
8
+ GUE/GSE surmises and their derivation from the 2x2 ensemble.
9
+
10
+ The beta-dependent closed form used here::
11
+
12
+ P_beta(s) = a(beta) * s**beta * exp(-b(beta) * s**2)
13
+
14
+ with a, b fixed by normalization (integral P ds = 1) and unit mean
15
+ spacing (integral s*P ds = 1), reduces exactly to the classical
16
+ GOE/GUE/GSE surmises at beta = 1, 2, 4 and is the standard two-parameter
17
+ generalization to continuum beta (see e.g. G. Livan, M. Novaes, P. Vivo,
18
+ "Introduction to Random Matrices: Theory and Practice", Springer, 2018,
19
+ Sec. 2.4). It is a surmise -- exact for the 2x2 ensemble, an excellent
20
+ but not exact approximation to the true N-level spacing distribution --
21
+ which is why it is validated here as a benchmark rather than assumed.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ from collections.abc import Callable
27
+
28
+ import numpy as np
29
+ from scipy.special import gamma, gammainc, gammaincinv
30
+
31
+ from ..spectrum import Spectrum
32
+ from .unfolding import unfold_by_cdf
33
+
34
+
35
+ def nearest_neighbor_spacings(
36
+ spectrum: Spectrum,
37
+ cdf_func: Callable[[np.ndarray], np.ndarray],
38
+ edge_trim: float = 0.1,
39
+ ) -> np.ndarray:
40
+ """Unfolded nearest-neighbor spacings, pooled across every sample in
41
+ a Spectrum.
42
+
43
+ Parameters
44
+ ----------
45
+ spectrum : physicskit.rmt.spectrum.Spectrum
46
+ cdf_func : callable
47
+ Theoretical CDF used for unfolding (e.g.
48
+ ``physicskit.rmt.stats.density.semicircle_cdf`` for the Gaussian
49
+ ensembles).
50
+ edge_trim : float
51
+ Fraction of eigenvalues discarded from each edge *after*
52
+ unfolding, before taking spacings. The mean spectral density --
53
+ and therefore unfolding accuracy -- is least reliable near the
54
+ spectrum edges (soft-edge Tracy-Widom effects live exactly
55
+ there), so edge eigenvalues bias the bulk spacing statistic if
56
+ included. Trimming must happen after unfolding, not before:
57
+ unfolding uses the total eigenvalue count N as the scale factor,
58
+ so trimming first would unfold against the wrong N and bias the
59
+ mean spacing away from 1.
60
+
61
+ Returns
62
+ -------
63
+ numpy.ndarray
64
+ Pooled unfolded spacings from all samples.
65
+ """
66
+ all_spacings = []
67
+ for row in spectrum.rescaled:
68
+ n = len(row)
69
+ unfolded = unfold_by_cdf(row, cdf_func)
70
+ lo = int(edge_trim * n)
71
+ hi = n - lo
72
+ if hi - lo < 2:
73
+ continue
74
+ all_spacings.append(np.diff(unfolded[lo:hi]))
75
+ return np.concatenate(all_spacings)
76
+
77
+
78
+ def circular_spacings(spectrum: Spectrum) -> np.ndarray:
79
+ """Unfolded nearest-neighbor spacings for a circular ensemble
80
+ (COE/CUE/CSE), including the wraparound spacing between the largest
81
+ and smallest phase.
82
+
83
+ Unlike the Gaussian/Wishart ensembles, a circular ensemble has no
84
+ spectrum edge (it lives on a circle) and no unfolding-accuracy
85
+ concerns (``spectrum.rescaled`` is already exactly unfolded, see
86
+ ``physicskit.rmt.ensembles.circular``), so every spacing is used -- no edge
87
+ trimming, and one extra "wraparound" spacing per sample compared to
88
+ the linear-spectrum case.
89
+ """
90
+ all_spacings = []
91
+ for row in spectrum.rescaled:
92
+ n = len(row)
93
+ row_sorted = np.sort(row)
94
+ spacings = np.diff(row_sorted)
95
+ wraparound = (row_sorted[0] + n) - row_sorted[-1]
96
+ all_spacings.append(np.concatenate([spacings, [wraparound]]))
97
+ return np.concatenate(all_spacings)
98
+
99
+
100
+ def wigner_surmise_params(beta: float) -> tuple[float, float]:
101
+ """(a, b) for P_beta(s) = a * s**beta * exp(-b * s**2), normalized to
102
+ integral P(s) ds = 1 and integral s * P(s) ds = 1 (unit mean spacing).
103
+
104
+ Derivation: writing the two normalization integrals in terms of
105
+ Gamma functions and eliminating a gives
106
+ b = [Gamma((beta+2)/2) / Gamma((beta+1)/2)]**2
107
+ a = 2 * b**((beta+1)/2) / Gamma((beta+1)/2)
108
+ Verified numerically against the classical closed forms at
109
+ beta=1 (a=pi/2, b=pi/4), beta=2 (a=32/pi^2, b=4/pi), and
110
+ beta=4 (a=2**18/(3**6 pi**3), b=64/(9 pi)).
111
+ """
112
+ b = (gamma((beta + 2) / 2) / gamma((beta + 1) / 2)) ** 2
113
+ a = 2 * b ** ((beta + 1) / 2) / gamma((beta + 1) / 2)
114
+ return a, b
115
+
116
+
117
+ def wigner_surmise_pdf(s: np.ndarray, beta: float) -> np.ndarray:
118
+ """Generalized Wigner surmise density at spacing(s) s."""
119
+ a, b = wigner_surmise_params(beta)
120
+ s = np.asarray(s, dtype=float)
121
+ out = np.zeros_like(s)
122
+ mask = s >= 0
123
+ out[mask] = a * s[mask] ** beta * np.exp(-b * s[mask] ** 2)
124
+ return out
125
+
126
+
127
+ def wigner_surmise_cdf(s: np.ndarray, beta: float) -> np.ndarray:
128
+ """Generalized Wigner surmise CDF.
129
+
130
+ Closed form: substituting u = t^2 in the defining integral reduces it
131
+ to the regularized lower incomplete gamma function,
132
+ F(s) = P((beta+1)/2, b*s^2)
133
+ (verified numerically against direct quadrature of the PDF).
134
+ """
135
+ _, b = wigner_surmise_params(beta)
136
+ s = np.asarray(s, dtype=float)
137
+ out = np.zeros_like(s)
138
+ mask = s >= 0
139
+ out[mask] = gammainc((beta + 1) / 2, b * s[mask] ** 2)
140
+ return out
141
+
142
+
143
+ def wigner_surmise_samples(size: int | tuple[int, ...], beta: float, rng: np.random.Generator) -> np.ndarray:
144
+ """Exact samples from the generalized Wigner surmise via inverse-CDF
145
+ sampling (the CDF's closed form as a regularized incomplete gamma
146
+ function has a matching closed-form inverse, ``gammaincinv``, so no
147
+ rejection sampling is needed)."""
148
+ _, b = wigner_surmise_params(beta)
149
+ u = rng.uniform(0.0, 1.0, size=size)
150
+ return np.sqrt(gammaincinv((beta + 1) / 2, u) / b)
@@ -0,0 +1,175 @@
1
+ """The Tracy-Widom distributions F_1, F_2, F_4: limiting law of the
2
+ largest eigenvalue at the soft edge of the Gaussian ensembles.
3
+
4
+ References
5
+ ----------
6
+ C. A. Tracy, H. Widom, "Level-spacing distributions and the Airy
7
+ kernel", Commun. Math. Phys. 159 (1994) 151 -- F_2 (GUE, beta=2).
8
+ C. A. Tracy, H. Widom, "On orthogonal and symplectic matrix ensembles",
9
+ Commun. Math. Phys. 177 (1996) 727 -- F_1 (GOE, beta=1) and F_4
10
+ (GSE, beta=4).
11
+
12
+ Construction
13
+ ------------
14
+ All three distributions are built from q(x), the Hastings-McLeod
15
+ solution of the Painleve II equation
16
+
17
+ q''(x) = x*q(x) + 2*q(x)^3, q(x) ~ Ai(x) as x -> +infinity
18
+
19
+ (the unique solution with this decaying tail; other solutions of the
20
+ same ODE blow up). q is obtained numerically by integrating *backward*
21
+ from a large positive x0 (where q(x0) = Ai(x0), q'(x0) = Ai'(x0) to
22
+ machine precision) down to a negative x_min -- forward integration from
23
+ -infinity is unstable, since nearby non-Hastings-McLeod solutions
24
+ diverge exponentially in that direction.
25
+
26
+ Numerical domain note: backward integration itself becomes unstable
27
+ (round-off eventually kicks the trajectory onto a diverging branch) for
28
+ x below about -9 to -10. This implementation restricts to [-6, 6] by
29
+ default, verified to match the known asymptotic q(x) ~ sqrt(-x/2) to
30
+ better than 0.2% at x=-6, and F_2/F_1/F_4 are already extremely close to
31
+ 0 there (the left tail decays like ``exp(-|s|^3/12)``), so truncating the
32
+ domain has negligible effect on the distributions actually used.
33
+
34
+ Given q, the three distributions are:
35
+
36
+ F_2(s) = exp(-integral_s^inf (x-s) q(x)^2 dx)
37
+ F_1(s) = sqrt(F_2(s)) * exp(-(1/2) * integral_s^inf q(x) dx)
38
+ F_4(s) = sqrt(F_2(s)) * cosh((1/2) * integral_s^inf q(x) dx)
39
+
40
+ Edge-scaling note (important, and the one detail here that is NOT
41
+ standard textbook boilerplate): for a Gaussian beta-ensemble normalized
42
+ to semicircle support [-2, 2] (as ``physicskit.rmt.ensembles.gaussian`` uses),
43
+ the largest eigenvalue converges as
44
+
45
+ n^(2/3) * (lambda_max - 2) -> F_1 (GOE, beta=1)
46
+ n^(2/3) * (lambda_max - 2) -> F_2 (GUE, beta=2)
47
+ (2n)^(2/3) * (lambda_max - 2) -> F_4 (GSE, beta=4)
48
+
49
+ The GSE case's extra factor of 2 in the effective scale is easy to get
50
+ wrong from memory (and was NOT assumed here) -- it was determined by
51
+ directly comparing candidate scalings against Monte Carlo largest-
52
+ eigenvalue samples from the already-validated ``GSE`` ensemble, via a
53
+ KS test, rather than taken from a half-remembered formula. See the
54
+ design/development notes and ``tests/test_tracy_widom.py``.
55
+ """
56
+
57
+ from __future__ import annotations
58
+
59
+ from dataclasses import dataclass
60
+
61
+ import numpy as np
62
+ from scipy.integrate import cumulative_trapezoid, solve_ivp
63
+ from scipy.special import airy
64
+
65
+ from ..spectrum import Spectrum
66
+
67
+
68
+ @dataclass
69
+ class _PainleveIISolution:
70
+ grid: np.ndarray
71
+ F1: np.ndarray
72
+ F2: np.ndarray
73
+ F4: np.ndarray
74
+
75
+
76
+ _CACHE: dict[tuple[float, float, int], _PainleveIISolution] = {}
77
+
78
+
79
+ def _solve(x_max: float = 6.0, x_min: float = -6.0, n_grid: int = 20000) -> _PainleveIISolution:
80
+ """Solve Painleve II once and build F1/F2/F4 on a grid. Cached by
81
+ (x_max, x_min, n_grid) since this is a one-time cost reused by every
82
+ subsequent CDF/PDF/sampling call."""
83
+ key = (x_max, x_min, n_grid)
84
+ if key in _CACHE:
85
+ return _CACHE[key]
86
+
87
+ def rhs(x: float, y: tuple[float, float]) -> list[float]:
88
+ q, qp = y
89
+ return [qp, x * q + 2.0 * q**3]
90
+
91
+ ai, aip, _, _ = airy(x_max)
92
+ sol = solve_ivp(
93
+ rhs,
94
+ [x_max, x_min],
95
+ [ai, aip],
96
+ dense_output=True,
97
+ rtol=1e-12,
98
+ atol=1e-14,
99
+ max_step=0.005,
100
+ )
101
+ if not sol.success:
102
+ raise RuntimeError(f"Painleve II integration failed: {sol.message}")
103
+
104
+ grid = np.linspace(x_min, x_max, n_grid)
105
+ q_vals = sol.sol(grid)[0]
106
+
107
+ # Reverse cumulative integrals (needed since every quantity here is
108
+ # defined as an integral from s to +infinity, i.e. against the grid's
109
+ # natural ascending order).
110
+ xs_rev = grid[::-1]
111
+ q2_rev = (q_vals**2)[::-1]
112
+ q_rev = q_vals[::-1]
113
+
114
+ a_s = -cumulative_trapezoid(q2_rev, xs_rev, initial=0.0)[::-1] # int_s^inf q^2 dx
115
+ b_s = -cumulative_trapezoid(xs_rev * q2_rev, xs_rev, initial=0.0)[::-1] # int_s^inf x q^2 dx
116
+ i_s = -cumulative_trapezoid(q_rev, xs_rev, initial=0.0)[::-1] # int_s^inf q dx
117
+
118
+ r_s = b_s - grid * a_s
119
+ f2 = np.exp(-r_s)
120
+ f2_clipped = np.clip(f2, 0.0, 1.0)
121
+ f1 = np.sqrt(f2_clipped) * np.exp(-0.5 * i_s)
122
+ f4 = np.sqrt(f2_clipped) * np.cosh(0.5 * i_s)
123
+
124
+ result = _PainleveIISolution(grid=grid, F1=f1, F2=np.clip(f2, 0.0, 1.0), F4=np.clip(f4, 0.0, 1.0))
125
+ _CACHE[key] = result
126
+ return result
127
+
128
+
129
+ # beta -> which precomputed curve, and the edge-scaling exponent base
130
+ # (n for beta=1,2; 2*n for beta=4 -- see module docstring).
131
+ _BETA_CURVE = {1: "F1", 2: "F2", 4: "F4"}
132
+ _BETA_SCALE_MULTIPLIER = {1: 1.0, 2: 1.0, 4: 2.0}
133
+
134
+
135
+ def largest_eigenvalues(spectrum: Spectrum) -> np.ndarray:
136
+ """The largest (rescaled) eigenvalue from each sample in a Spectrum
137
+ -- the raw statistic that Tracy-Widom-type soft-edge laws govern."""
138
+ return spectrum.rescaled.max(axis=1)
139
+
140
+
141
+ def tracy_widom_cdf(s: np.ndarray, beta: int) -> np.ndarray:
142
+ """CDF of the Tracy-Widom distribution at Dyson index beta (1, 2, or 4)."""
143
+ if beta not in _BETA_CURVE:
144
+ raise ValueError(
145
+ f"Only beta in {{1, 2, 4}} have closed-form Tracy-Widom laws "
146
+ f"(F_1, F_2, F_4); beta={beta} is not supported. The general-beta "
147
+ f"soft-edge law exists (Ramirez-Rider-Virag stochastic Airy "
148
+ f"operator) but has no elementary closed form."
149
+ )
150
+ sol = _solve()
151
+ curve = getattr(sol, _BETA_CURVE[beta])
152
+ s = np.asarray(s, dtype=float)
153
+ return np.interp(s, sol.grid, curve, left=0.0, right=1.0)
154
+
155
+
156
+ def tracy_widom_edge_scale(n: int, beta: int) -> float:
157
+ """Factor by which (lambda_max - 2) should be multiplied to compare
158
+ against the beta-indexed Tracy-Widom CDF: n^(2/3) for beta=1, 2;
159
+ (2n)^(2/3) for beta=4 (see module docstring for how the beta=4 factor
160
+ was determined)."""
161
+ if beta not in _BETA_SCALE_MULTIPLIER:
162
+ raise ValueError(f"Only beta in {{1, 2, 4}} are supported, got {beta}")
163
+ return (_BETA_SCALE_MULTIPLIER[beta] * n) ** (2.0 / 3.0)
164
+
165
+
166
+ def tracy_widom_rvs(size: int | tuple[int, ...], beta: int, rng: np.random.Generator) -> np.ndarray:
167
+ """Sample from the Tracy-Widom distribution via inverse-CDF
168
+ interpolation on the same grid used for the CDF."""
169
+ if beta not in _BETA_CURVE:
170
+ raise ValueError(f"Only beta in {{1, 2, 4}} are supported, got {beta}")
171
+ sol = _solve()
172
+ curve = getattr(sol, _BETA_CURVE[beta])
173
+ # curve is monotonically increasing in practice; interpolate its inverse
174
+ u = rng.uniform(curve[0], curve[-1], size=size)
175
+ return np.interp(u, curve, sol.grid)