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,178 @@
1
+ """Tests for PTSymmetricEnsemble and the real-eigenvalue-fraction
2
+ statistic.
3
+
4
+ The pseudo-Hermiticity relation P H P = H^dagger (P the fixed +-1
5
+ signature matrix) was verified numerically to machine precision during
6
+ development for both beta=1 and beta=2 before this construction was
7
+ trusted -- see module docstring in
8
+ ``physicskit.rmt/ensembles/pt_symmetric.py``.
9
+ """
10
+
11
+ import numpy as np
12
+ import pytest
13
+ from cache_utils import cached_sample
14
+
15
+ import physicskit.rmt as rmt
16
+
17
+
18
+ @pytest.mark.parametrize("beta", [1, 2])
19
+ def test_pseudo_hermiticity_holds_exactly(beta):
20
+ p, q = 8, 6
21
+ ens = rmt.ensembles.PTSymmetricEnsemble(p=p, q=q, g=0.7, beta=beta, seed=0)
22
+ h = ens._sample_matrix(np.random.default_rng(0))
23
+ signature = np.diag(np.concatenate([np.ones(p), -np.ones(q)]))
24
+ residual = signature @ h @ signature - h.conj().T
25
+ assert np.abs(residual).max() < 1e-10
26
+
27
+
28
+ @pytest.mark.parametrize("beta", [1, 2])
29
+ def test_zero_coupling_gives_fully_real_spectrum(beta):
30
+ ens = rmt.ensembles.PTSymmetricEnsemble(p=10, q=10, g=0.0, beta=beta, seed=1)
31
+ spectrum = cached_sample(ens, n_samples=10)
32
+ frac = rmt.stats.real_eigenvalue_fraction(spectrum.eigenvalues)
33
+ np.testing.assert_allclose(frac, 1.0, atol=1e-6)
34
+
35
+
36
+ @pytest.mark.parametrize("beta", [1, 2])
37
+ def test_real_fraction_decreases_as_coupling_grows(beta):
38
+ # The PT-symmetry-breaking transition: increasing coupling g should
39
+ # drive the spectrum from fully real toward mostly complex.
40
+ fracs = []
41
+ for g in [0.0, 0.3, 1.0, 3.0]:
42
+ ens = rmt.ensembles.PTSymmetricEnsemble(p=15, q=15, g=g, beta=beta, seed=2)
43
+ spectrum = cached_sample(ens, n_samples=20)
44
+ fracs.append(rmt.stats.real_eigenvalue_fraction(spectrum.eigenvalues).mean())
45
+ assert fracs == sorted(fracs, reverse=True)
46
+ assert fracs[0] == pytest.approx(1.0, abs=1e-6)
47
+ assert fracs[-1] < 0.3
48
+
49
+
50
+ def test_eigenvalues_are_real_or_conjugate_pairs():
51
+ # Pseudo-Hermiticity forces the eigenvalue SET to be closed under
52
+ # complex conjugation: every non-real eigenvalue has a partner at
53
+ # its complex conjugate (matched here via nearest-neighbor distance,
54
+ # not a naive sort, since floating-point noise can break exact ties
55
+ # between a conjugate pair's real parts).
56
+ ens = rmt.ensembles.PTSymmetricEnsemble(p=10, q=10, g=1.0, beta=2, seed=3)
57
+ spectrum = ens.sample(n_samples=1)
58
+ eigs = spectrum.eigenvalues[0]
59
+ non_real = eigs[np.abs(eigs.imag) > 1e-6]
60
+ for lam in non_real:
61
+ nearest_dist = np.min(np.abs(eigs - np.conj(lam)))
62
+ assert nearest_dist < 1e-8
63
+
64
+
65
+ def test_invalid_parameters_rejected():
66
+ with pytest.raises(ValueError):
67
+ rmt.ensembles.PTSymmetricEnsemble(p=5, q=5, g=-1.0, seed=0)
68
+ with pytest.raises(ValueError):
69
+ rmt.ensembles.PTSymmetricEnsemble(p=5, q=5, g=1.0, beta=4, seed=0)
70
+
71
+
72
+ def test_reproducibility():
73
+ ens_a = rmt.ensembles.PTSymmetricEnsemble(p=6, q=6, g=0.5, seed=42)
74
+ ens_b = rmt.ensembles.PTSymmetricEnsemble(p=6, q=6, g=0.5, seed=42)
75
+ spec_a = ens_a.sample(n_samples=3)
76
+ spec_b = ens_b.sample(n_samples=3)
77
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
78
+
79
+
80
+ # --- exceptional-point detection ---
81
+
82
+
83
+ def test_find_exceptional_points_finds_real_count_drops():
84
+ eps = rmt.stats.find_exceptional_points(p=6, q=6, beta=2, seed=0, g_max=3.0, n_grid=3000)
85
+ assert len(eps) > 0
86
+ assert np.all(eps > 0.0)
87
+ assert np.all(eps < 3.0)
88
+
89
+
90
+ def test_eigenvector_condition_number_spikes_near_exceptional_point():
91
+ # The defining structural signature distinguishing a genuine
92
+ # exceptional point (eigenvector coalescence) from an incidental
93
+ # level crossing (eigenvectors stay independent): the right-
94
+ # eigenvector matrix's condition number should grow sharply as g
95
+ # approaches a detected transition.
96
+ eps = rmt.stats.find_exceptional_points(p=6, q=6, beta=2, seed=0, g_max=3.0, n_grid=3000)
97
+ g_star = eps[0]
98
+ cond_far = rmt.stats.eigenvector_condition_number(6, 6, 2, 0, g=max(g_star - 0.01, 0.0))
99
+ cond_near = rmt.stats.eigenvector_condition_number(6, 6, 2, 0, g=g_star + 5e-4)
100
+ assert cond_near > 2 * cond_far
101
+
102
+
103
+ @pytest.mark.slow
104
+ def test_mean_exceptional_point_count_grows_with_system_size():
105
+ # Ensemble-level statistic: mean EP count over many independent
106
+ # realizations should grow with system size, roughly linearly in
107
+ # min(p, q) -- verified during development (p=q=4,8,12 gave ~4.2,
108
+ # 8.7, 13.1 over g in [0, 2]).
109
+ mean_small, err_small = rmt.stats.mean_exceptional_point_count(p=4, q=4, beta=2, g_max=2.0, n_realizations=8, n_grid=500, seed=0)
110
+ mean_large, err_large = rmt.stats.mean_exceptional_point_count(p=12, q=12, beta=2, g_max=2.0, n_realizations=8, n_grid=500, seed=1)
111
+ assert mean_small > 0
112
+ assert mean_large > 2 * mean_small
113
+ assert err_small >= 0 and err_large >= 0
114
+
115
+
116
+ def test_fixed_realization_reconstruction_only_rescales_coupling_block():
117
+ # The trick find_exceptional_points/eigenvector_condition_number rely
118
+ # on: rebuilding the ensemble with a different g but the same seed
119
+ # reproduces the same A, D blocks, only rescaling the coupling block.
120
+ from physicskit.rmt.stats.pt_symmetric import _fixed_realization_matrix
121
+
122
+ p, q = 5, 5
123
+ h1 = _fixed_realization_matrix(p, q, 2, 42, g=0.1)
124
+ h2 = _fixed_realization_matrix(p, q, 2, 42, g=0.5)
125
+ np.testing.assert_allclose(h1[:p, :p], h2[:p, :p])
126
+ np.testing.assert_allclose(h1[p:, p:], h2[p:, p:])
127
+ np.testing.assert_allclose(h1[:p, p:] / 0.1, h2[:p, p:] / 0.5)
128
+
129
+
130
+ # --- intermediate (semi-Poisson) spacing statistics ---
131
+
132
+
133
+ def test_semi_poisson_is_normalized_with_unit_mean_spacing():
134
+ from scipy.integrate import quad
135
+
136
+ norm, _ = quad(rmt.stats.semi_poisson_pdf, 0, np.inf)
137
+ mean, _ = quad(lambda s: s * rmt.stats.semi_poisson_pdf(np.array([s]))[0], 0, np.inf)
138
+ assert norm == pytest.approx(1.0, abs=1e-8)
139
+ assert mean == pytest.approx(1.0, abs=1e-8)
140
+
141
+
142
+ def test_semi_poisson_cdf_is_derivative_consistent_with_pdf():
143
+ s = np.linspace(0.01, 5.0, 50)
144
+ h = 1e-6
145
+ finite_diff = (rmt.stats.semi_poisson_cdf(s + h) - rmt.stats.semi_poisson_cdf(s - h)) / (2 * h)
146
+ np.testing.assert_allclose(finite_diff, rmt.stats.semi_poisson_pdf(s), atol=1e-4)
147
+
148
+
149
+ def test_real_axis_spacings_matches_semi_poisson_better_than_poisson_or_goe():
150
+ # Verified during development: at small-to-moderate coupling g, the
151
+ # surviving real eigenvalues' spacing statistic is closest to
152
+ # semi-Poisson, not pure Poisson or pure GOE -- the "intermediate
153
+ # statistics" signature the design plan calls for.
154
+ ens = rmt.ensembles.PTSymmetricEnsemble(p=25, q=25, g=0.3, beta=2, seed=0)
155
+ spectrum = cached_sample(ens, n_samples=200)
156
+ spacings = rmt.stats.real_axis_spacings(spectrum.eigenvalues)
157
+
158
+ from scipy.stats import kstest
159
+
160
+ ks_semi_poisson = kstest(spacings, rmt.stats.semi_poisson_cdf).statistic
161
+ ks_poisson = kstest(spacings, lambda s: 1.0 - np.exp(-s)).statistic
162
+ ks_goe = kstest(spacings, lambda s: rmt.stats.wigner_surmise_cdf(s, beta=1)).statistic
163
+
164
+ assert ks_semi_poisson < ks_poisson
165
+ assert ks_semi_poisson < ks_goe
166
+
167
+
168
+ def test_real_axis_spacings_skips_samples_with_too_few_real_eigenvalues():
169
+ # All-complex sample (no real eigenvalues at all) should not crash
170
+ # and should be skipped, not contribute spurious spacings.
171
+ eigenvalues = np.array(
172
+ [
173
+ [1.0 + 2.0j, 1.0 - 2.0j, 3.0 + 1.0j, 3.0 - 1.0j],
174
+ [0.0 + 0j, 1.0 + 0j, 2.0 + 0j, 3.0 + 0j],
175
+ ]
176
+ )
177
+ spacings = rmt.stats.real_axis_spacings(eigenvalues)
178
+ np.testing.assert_allclose(spacings, [1.0, 1.0, 1.0])
@@ -0,0 +1,19 @@
1
+ """Coverage for physicskit.rmt.utils.random_state.as_generator: the
2
+ existing-Generator passthrough branch (every ensemble test seeds with a
3
+ plain int, so this was never exercised)."""
4
+
5
+ from __future__ import annotations
6
+
7
+ import numpy as np
8
+
9
+ from physicskit.rmt.utils.random_state import as_generator
10
+
11
+
12
+ def test_as_generator_returns_an_existing_generator_unchanged():
13
+ rng = np.random.default_rng(42)
14
+ assert as_generator(rng) is rng
15
+
16
+
17
+ def test_as_generator_coerces_an_int_seed():
18
+ rng = as_generator(7)
19
+ assert isinstance(rng, np.random.Generator)
@@ -0,0 +1,112 @@
1
+ """Tests for the real-Ginibre real-eigenvalue-count statistic and
2
+ benchmark.
3
+
4
+ The asymptotic formula sqrt(2n/pi) + 1/2 was fit against, and confirmed
5
+ by, direct Monte Carlo real-eigenvalue counts at n = 2..128 during
6
+ development (see ``physicskit.rmt.stats.real_ginibre`` module docstring) -- it
7
+ is NOT the exact Edelman-Kostlan-Shub finite-n closed form, so tests use
8
+ a loose tolerance and larger n rather than expecting near-exact
9
+ agreement.
10
+ """
11
+
12
+ import numpy as np
13
+ import pytest
14
+ from cache_utils import cached_sample
15
+
16
+ import physicskit.rmt as rmt
17
+
18
+
19
+ def test_real_eigenvalue_count_empirical_on_known_matrix():
20
+ # A diagonal (all-real-eigenvalue) "spectrum": every eigenvalue
21
+ # should be counted as real.
22
+ eigenvalues = np.array([[1.0 + 0j, 2.0 + 0j, -3.0 + 0j]])
23
+ counts = rmt.stats.real_eigenvalue_count_empirical(eigenvalues)
24
+ np.testing.assert_array_equal(counts, [3])
25
+
26
+
27
+ def test_real_eigenvalue_count_empirical_mixed():
28
+ eigenvalues = np.array([[1.0 + 0j, 2.0 + 3.0j, 2.0 - 3.0j, -1.0 + 0j]])
29
+ counts = rmt.stats.real_eigenvalue_count_empirical(eigenvalues)
30
+ np.testing.assert_array_equal(counts, [2])
31
+
32
+
33
+ def test_asymptotic_matches_n2_exact_value():
34
+ # n=2 has a well-known exact closed form: E[N_real] = sqrt(2)
35
+ # (P(both eigenvalues real) = 1/sqrt(2)). The asymptotic formula is
36
+ # not expected to be exact this low, but should be in the right
37
+ # ballpark.
38
+ theory = rmt.stats.real_eigenvalue_count_asymptotic(2)
39
+ assert theory == pytest.approx(np.sqrt(2), abs=0.3)
40
+
41
+
42
+ def test_asymptotic_matches_monte_carlo_at_moderate_n():
43
+ n = 64
44
+ ens = rmt.ensembles.GinOE(n=n, seed=0)
45
+ spectrum = cached_sample(ens, n_samples=2000)
46
+ counts = rmt.stats.real_eigenvalue_count_empirical(spectrum.eigenvalues)
47
+ empirical_mean = counts.mean()
48
+ theory = rmt.stats.real_eigenvalue_count_asymptotic(n)
49
+ assert empirical_mean == pytest.approx(theory, rel=0.1)
50
+
51
+
52
+ def test_real_ginibre_eigenvalue_count_benchmark():
53
+ n = 100
54
+ ens = rmt.ensembles.GinOE(n=n, seed=1)
55
+ spectrum = cached_sample(ens, n_samples=2000)
56
+ benchmark = rmt.validation.RealGinibreEigenvalueCount()
57
+ result = benchmark.validate(spectrum)
58
+ assert result.n == n
59
+ assert result.relative_error < 0.1
60
+
61
+
62
+ def test_mean_real_count_grows_like_sqrt_n():
63
+ # The qualitative signature: E[N_real] should grow much slower than
64
+ # n itself (like sqrt(n)), unlike a generic extensive quantity.
65
+ small = rmt.stats.real_eigenvalue_count_asymptotic(25)
66
+ large = rmt.stats.real_eigenvalue_count_asymptotic(100)
67
+ # n quadruples (25->100); sqrt(n) doubles, so the count should
68
+ # roughly double too, not quadruple.
69
+ assert 1.5 < large / small < 2.5
70
+
71
+
72
+ # --- real-eigenvalue density (not just count) ---
73
+
74
+
75
+ def test_density_asymptotic_is_zero_outside_unit_disk():
76
+ n = 100
77
+ x = np.array([-1.5, -1.01, 1.01, 1.5])
78
+ density = rmt.stats.real_eigenvalue_density_asymptotic(x, n)
79
+ np.testing.assert_array_equal(density, 0.0)
80
+
81
+
82
+ def test_density_asymptotic_integrates_to_the_count():
83
+ # The uniform-box density's total integral (height * width 2) should
84
+ # equal the already-verified total count formula.
85
+ n = 200
86
+ x = np.linspace(-1.0, 1.0, 20001)
87
+ density = rmt.stats.real_eigenvalue_density_asymptotic(x, n)
88
+ integral = np.trapezoid(density, x)
89
+ assert integral == pytest.approx(rmt.stats.real_eigenvalue_count_asymptotic(n), rel=1e-3)
90
+
91
+
92
+ def test_density_empirical_matches_asymptotic_in_the_bulk():
93
+ n = 300
94
+ ens = rmt.ensembles.GinOE(n=n, seed=0)
95
+ spectrum = cached_sample(ens, n_samples=2000)
96
+ centers, density = rmt.stats.real_eigenvalue_density_empirical(spectrum.eigenvalues, n)
97
+ theory = rmt.stats.real_eigenvalue_density_asymptotic(centers, n)
98
+
99
+ bulk = np.abs(centers) < 0.8
100
+ assert density[bulk].mean() == pytest.approx(theory[bulk].mean(), rel=0.15)
101
+
102
+
103
+ def test_density_empirical_drops_near_the_edge():
104
+ # Qualitative edge-cutoff check: density well outside the unit disk
105
+ # (after rescaling by sqrt(n)) should be much smaller than in the bulk.
106
+ n = 300
107
+ ens = rmt.ensembles.GinOE(n=n, seed=0)
108
+ spectrum = cached_sample(ens, n_samples=2000)
109
+ centers, density = rmt.stats.real_eigenvalue_density_empirical(spectrum.eigenvalues, n, x_max=1.3)
110
+ bulk_density = density[np.abs(centers) < 0.5].mean()
111
+ edge_density = density[np.abs(centers) > 1.15].mean()
112
+ assert edge_density < 0.1 * bulk_density
@@ -0,0 +1,167 @@
1
+ """Tests for spectral rigidity (number variance) and universality
2
+ testing.
3
+
4
+ The GUE number variance formula is *exact* (derived directly from the
5
+ already-validated sine kernel, not an approximation), so it's tested
6
+ against a tight tolerance. The GOE formula is confirmed by two
7
+ independent secondary sources; the GSE formula follows the same
8
+ commonly-cited pattern but was verified only empirically here (against
9
+ Monte Carlo GSE) before being trusted -- both are large-L asymptotic,
10
+ so tested with a looser tolerance and only at moderately large L.
11
+ """
12
+
13
+ import numpy as np
14
+ import pytest
15
+ from cache_utils import cached_sample
16
+
17
+ import physicskit.rmt as rmt
18
+
19
+
20
+ def test_poisson_number_variance_is_exactly_l():
21
+ l_values = [1, 5, 10, 50]
22
+ np.testing.assert_allclose(rmt.stats.number_variance_poisson(l_values), l_values)
23
+
24
+
25
+ def test_gue_number_variance_much_smaller_than_poisson():
26
+ # The defining qualitative signature of spectral rigidity: GUE's
27
+ # number variance grows logarithmically, Poisson's linearly, so at
28
+ # any reasonably large L, GUE's variance is far smaller.
29
+ l_values = [10, 50, 100]
30
+ gue = rmt.stats.number_variance_gue_exact(l_values)
31
+ poisson = rmt.stats.number_variance_poisson(l_values)
32
+ assert np.all(gue < poisson / 3)
33
+
34
+
35
+ def test_gue_exact_matches_empirical_window_counting():
36
+ ens = rmt.ensembles.GUE(n=4000, seed=1)
37
+ spectrum = cached_sample(ens, n_samples=15)
38
+ l_values = [2, 5, 10, 20]
39
+ theory = rmt.stats.number_variance_gue_exact(l_values)
40
+ empirical = rmt.stats.number_variance_empirical(spectrum, rmt.stats.semicircle_cdf, l_values, n_windows=200, seed=0)
41
+ np.testing.assert_allclose(theory, empirical, atol=0.05)
42
+
43
+
44
+ def test_goe_asymptotic_matches_empirical_at_large_l():
45
+ ens = rmt.ensembles.GOE(n=4000, seed=2)
46
+ spectrum = cached_sample(ens, n_samples=15)
47
+ l_values = [5, 10, 20]
48
+ theory = rmt.stats.number_variance_goe_asymptotic(l_values)
49
+ empirical = rmt.stats.number_variance_empirical(spectrum, rmt.stats.semicircle_cdf, l_values, n_windows=300, seed=0)
50
+ np.testing.assert_allclose(theory, empirical, atol=0.06)
51
+
52
+
53
+ def test_gse_asymptotic_matches_empirical_at_large_l():
54
+ ens = rmt.ensembles.GSE(n=4000, seed=3)
55
+ spectrum = cached_sample(ens, n_samples=15)
56
+ l_values = [5, 10, 20]
57
+ theory = rmt.stats.number_variance_gse_asymptotic(l_values)
58
+ empirical = rmt.stats.number_variance_empirical(spectrum, rmt.stats.semicircle_cdf, l_values, n_windows=300, seed=0)
59
+ np.testing.assert_allclose(theory, empirical, atol=0.03)
60
+
61
+
62
+ def test_number_variance_theory_dispatch():
63
+ l_values = [10]
64
+ np.testing.assert_allclose(
65
+ rmt.stats.number_variance_theory(l_values, beta=1),
66
+ rmt.stats.number_variance_goe_asymptotic(l_values),
67
+ )
68
+ np.testing.assert_allclose(
69
+ rmt.stats.number_variance_theory(l_values, beta=2),
70
+ rmt.stats.number_variance_gue_exact(l_values),
71
+ )
72
+ np.testing.assert_allclose(
73
+ rmt.stats.number_variance_theory(l_values, beta=4),
74
+ rmt.stats.number_variance_gse_asymptotic(l_values),
75
+ )
76
+ with pytest.raises(ValueError):
77
+ rmt.stats.number_variance_theory(l_values, beta=3)
78
+
79
+
80
+ # --- spectral rigidity (Delta_3) ---
81
+
82
+
83
+ def test_poisson_delta3_is_exactly_l_over_15():
84
+ l_values = [3, 15, 30, 60]
85
+ np.testing.assert_allclose(rmt.stats.spectral_rigidity_poisson(l_values), np.array(l_values) / 15.0)
86
+
87
+
88
+ def test_poisson_delta3_empirical_matches_exact():
89
+ ens = rmt.ensembles.PoissonEnsemble(n=4000, seed=21)
90
+ spectrum = cached_sample(ens, n_samples=8)
91
+ l_values = [5, 10, 20, 40]
92
+
93
+ def identity_cdf(x):
94
+ return x / spectrum.n
95
+
96
+ empirical = rmt.stats.spectral_rigidity_empirical(spectrum, identity_cdf, l_values, n_windows=300, seed=0)
97
+ theory = rmt.stats.spectral_rigidity_poisson(l_values)
98
+ np.testing.assert_allclose(theory, empirical, rtol=0.08)
99
+
100
+
101
+ @pytest.mark.slow
102
+ def test_gue_delta3_theory_matches_empirical():
103
+ ens = rmt.ensembles.GUE(n=150, seed=22)
104
+ spectrum = cached_sample(ens, n_samples=20)
105
+ l_values = [3, 5, 8, 12]
106
+ theory = rmt.stats.spectral_rigidity_theory(l_values, beta=2)
107
+ empirical = rmt.stats.spectral_rigidity_empirical(spectrum, rmt.stats.semicircle_cdf, l_values, n_windows=200, seed=0)
108
+ np.testing.assert_allclose(theory, empirical, rtol=0.05)
109
+
110
+
111
+ @pytest.mark.slow
112
+ def test_goe_delta3_theory_matches_empirical_at_large_l():
113
+ ens = rmt.ensembles.GOE(n=200, seed=23)
114
+ spectrum = cached_sample(ens, n_samples=20)
115
+ l_values = [8, 12, 16, 20]
116
+ theory = rmt.stats.spectral_rigidity_theory(l_values, beta=1)
117
+ empirical = rmt.stats.spectral_rigidity_empirical(spectrum, rmt.stats.semicircle_cdf, l_values, n_windows=250, seed=0)
118
+ np.testing.assert_allclose(theory, empirical, rtol=0.1)
119
+
120
+
121
+ @pytest.mark.slow
122
+ def test_gue_delta3_much_smaller_than_poisson():
123
+ l_values = [10, 50, 100]
124
+ gue = rmt.stats.spectral_rigidity_theory(l_values, beta=2)
125
+ poisson = rmt.stats.spectral_rigidity_poisson(l_values)
126
+ assert np.all(gue < poisson / 3)
127
+
128
+
129
+ # --- universality ---
130
+
131
+
132
+ @pytest.mark.slow
133
+ @pytest.mark.parametrize("beta", [1, 2])
134
+ def test_universality_holds_across_entry_distributions(beta):
135
+ result = rmt.validation.check_universality(n=300, beta=beta, n_samples=10, seed=5)
136
+ assert set(result.per_distribution.keys()) == set(rmt.validation.DEFAULT_ENTRY_DISTRIBUTIONS.keys())
137
+ assert result.max_ks_statistic < 0.035
138
+
139
+
140
+ @pytest.mark.parametrize("beta", [1, 2])
141
+ def test_general_wigner_ensemble_matches_semicircle_directly(beta):
142
+ ens = rmt.ensembles.GeneralWignerEnsemble(n=1000, entry_sampler=rmt.ensembles.rademacher, beta=beta, seed=7)
143
+ spectrum = cached_sample(ens, n_samples=20)
144
+ benchmark = rmt.validation.WignerSemicircle()
145
+ result = benchmark.validate(spectrum, seed=7)
146
+ assert result.ks_statistic < 0.01
147
+
148
+
149
+ def test_general_wigner_ensemble_rejects_beta_4():
150
+ with pytest.raises(ValueError):
151
+ rmt.ensembles.GeneralWignerEnsemble(n=100, entry_sampler=rmt.ensembles.rademacher, beta=4, seed=0)
152
+
153
+
154
+ def test_asymmetric_exponential_entries_still_converge():
155
+ # The strongest universality stress test in the default set: a
156
+ # skewed, unbounded entry distribution should still give the
157
+ # symmetric semicircle law.
158
+ ens = rmt.ensembles.GeneralWignerEnsemble(
159
+ n=1000,
160
+ entry_sampler=rmt.ensembles.exponential_centered_unit_variance,
161
+ beta=2,
162
+ seed=11,
163
+ )
164
+ spectrum = cached_sample(ens, n_samples=20)
165
+ benchmark = rmt.validation.WignerSemicircle()
166
+ result = benchmark.validate(spectrum, seed=11)
167
+ assert result.ks_statistic < 0.01
@@ -0,0 +1,132 @@
1
+ """Tests for the single ring theorem and NonHermitianWishartEnsemble.
2
+
3
+ The exact Marchenko-Pastur moment identities E[X] = 1 and
4
+ E[1/X] = 1/(1-gamma) (gamma < 1), which give the theoretical ring radii
5
+ r_out=1, r_in=sqrt(1-gamma), were verified numerically against direct
6
+ Monte Carlo before being used here (see
7
+ ``physicskit.rmt.stats.single_ring`` and ``physicskit.rmt.ensembles.single_ring`` module
8
+ docstrings). Like every other limiting-law benchmark in this package,
9
+ the single ring theorem is an N -> infinity result, so tests check that
10
+ KS distance to the theoretical annulus SHRINKS with increasing N, not
11
+ that it passes a fixed small-N threshold (confirmed during development:
12
+ KS went from 0.046 at n=150 to 0.015 at n=1200 for a fixed gamma).
13
+ """
14
+
15
+ import numpy as np
16
+ import pytest
17
+ from cache_utils import cached_sample
18
+ from scipy.stats import linregress
19
+
20
+ import physicskit.rmt as rmt
21
+
22
+
23
+ def test_wishart_moment_identities():
24
+ # E[X] = 1 and E[1/X] = 1/(1-gamma) for the Marchenko-Pastur
25
+ # distribution -- the exact facts the theoretical ring radii rest
26
+ # on. Checked directly here against a fresh Monte Carlo sample,
27
+ # independent of the ensemble/benchmark machinery.
28
+ from physicskit.rmt.utils.tridiagonal import sample_laguerre_beta_eigenvalues
29
+
30
+ rng = np.random.default_rng(0)
31
+ n, m = 500, 1000
32
+ gamma = n / m
33
+ eigs = sample_laguerre_beta_eigenvalues(m, n, 2.0, rng)
34
+ assert eigs.mean() == pytest.approx(1.0, rel=0.02)
35
+ assert np.mean(1.0 / eigs) == pytest.approx(1.0 / (1.0 - gamma), rel=0.02)
36
+
37
+
38
+ def test_single_ring_radii_from_uniform_singular_values():
39
+ # Constant singular values s=1 for everything -> a genuine circle
40
+ # (r_in = r_out = 1), the trivial sanity check of the raw formula.
41
+ r_in, r_out = rmt.stats.single_ring_radii(np.ones(50))
42
+ assert r_in == pytest.approx(1.0)
43
+ assert r_out == pytest.approx(1.0)
44
+
45
+
46
+ def test_single_ring_radii_matches_wishart_theory():
47
+ gamma = 0.4
48
+ r_in, r_out = rmt.stats.single_ring_radii_wishart_theory(gamma)
49
+ assert r_out == 1.0
50
+ assert r_in == pytest.approx(np.sqrt(0.6))
51
+
52
+
53
+ def test_single_ring_radii_rejects_invalid_gamma():
54
+ with pytest.raises(ValueError):
55
+ rmt.stats.single_ring_radii_wishart_theory(1.0)
56
+ with pytest.raises(ValueError):
57
+ rmt.stats.single_ring_radii_wishart_theory(0.0)
58
+
59
+
60
+ def test_nonhermitian_wishart_rejects_gamma_geq_1():
61
+ with pytest.raises(ValueError):
62
+ rmt.ensembles.NonHermitianWishartEnsemble(n=100, m=100, seed=0)
63
+
64
+
65
+ def test_nonhermitian_wishart_rejects_nonpositive_beta():
66
+ with pytest.raises(ValueError):
67
+ rmt.ensembles.NonHermitianWishartEnsemble(n=10, m=20, beta=0.0, seed=0)
68
+
69
+
70
+ def test_single_ring_ensemble_and_nonhermitian_wishart_fresh_samples_are_finite():
71
+ # Fresh (uncached) direct .sample() calls, to actually exercise
72
+ # _sample_eigenvalues/natural_scale rather than risk a cache hit.
73
+ ens1 = rmt.ensembles.SingleRingEnsemble(n=6, singular_value_sampler=lambda rng, n: np.ones(n), seed=321)
74
+ spectrum1 = ens1.sample(n_samples=2)
75
+ assert np.all(np.isfinite(spectrum1.eigenvalues))
76
+ assert ens1.natural_scale() == 1.0
77
+
78
+ ens2 = rmt.ensembles.NonHermitianWishartEnsemble(n=6, m=12, seed=321)
79
+ spectrum2 = ens2.sample(n_samples=2)
80
+ assert np.all(np.isfinite(spectrum2.eigenvalues))
81
+ assert ens2.natural_scale() == 1.0
82
+
83
+
84
+ def test_nonhermitian_wishart_eigenvalue_radii_stay_within_ring_edges():
85
+ n, m = 400, 1000
86
+ ens = rmt.ensembles.NonHermitianWishartEnsemble(n=n, m=m, seed=1)
87
+ spectrum = cached_sample(ens, n_samples=5)
88
+ r_in, r_out = rmt.stats.single_ring_radii_wishart_theory(ens.gamma)
89
+ radii = np.abs(spectrum.eigenvalues.ravel())
90
+ # A modest margin for finite-n boundary fluctuation (the theorem is
91
+ # an n -> infinity statement about the support, not a hard bound at
92
+ # finite n).
93
+ assert radii.min() > r_in - 0.15
94
+ assert radii.max() < r_out + 0.15
95
+
96
+
97
+ def test_single_ring_theorem_convergence_shrinks_with_n():
98
+ gamma_fixed = 0.5
99
+ n_values = [100, 300, 900]
100
+ r_in, r_out = rmt.stats.single_ring_radii_wishart_theory(gamma_fixed)
101
+ benchmark = rmt.validation.SingleRingTheorem(r_in=r_in, r_out=r_out)
102
+
103
+ results = []
104
+ for n in n_values:
105
+ m = int(n / gamma_fixed)
106
+ ens = rmt.ensembles.NonHermitianWishartEnsemble(n=n, m=m, seed=5)
107
+ spectrum = cached_sample(ens, n_samples=max(2, 3000 // n))
108
+ results.append(benchmark.validate(spectrum, seed=5))
109
+
110
+ ks_stats = np.array([r.ks_statistic for r in results])
111
+ slope = linregress(np.log(n_values), np.log(ks_stats)).slope
112
+ assert slope < -0.2, f"expected shrinking KS distance, slope={slope}"
113
+
114
+
115
+ def test_single_ring_theorem_rejects_invalid_radii():
116
+ with pytest.raises(ValueError):
117
+ rmt.validation.SingleRingTheorem(r_in=1.0, r_out=0.5)
118
+
119
+
120
+ def test_balanced_gamma_one_collapses_to_filled_disk():
121
+ # gamma -> 1 (e.g. via a plain SingleRingEnsemble with Wishart-type
122
+ # singular values at m=n) should give r_in ~ 0, consistent with the
123
+ # ordinary (Ginibre) circular law rather than a genuine ring.
124
+ from physicskit.rmt.utils.tridiagonal import sample_laguerre_beta_eigenvalues
125
+
126
+ def wishart_singular_values(rng, n):
127
+ return np.sqrt(sample_laguerre_beta_eigenvalues(n, n, 2.0, rng))
128
+
129
+ ens = rmt.ensembles.SingleRingEnsemble(n=300, singular_value_sampler=wishart_singular_values, seed=2)
130
+ spectrum = cached_sample(ens, n_samples=5)
131
+ radii = np.abs(spectrum.eigenvalues.ravel())
132
+ assert radii.min() < 0.1 # close to 0, not bounded away like a genuine ring