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,136 @@
1
+ """Tests for JOE, JUE and the Wachter distribution benchmark.
2
+
3
+ The Wachter formula and its parametrization (a=m1/n, b=m2/n, inverse
4
+ aspect ratios) were NOT taken from memory -- an initial guess at the
5
+ formula (with a,b as the more familiar n/m aspect ratios) gave support
6
+ edges outside [0, 1], which is impossible for this ensemble, immediately
7
+ flagging the error. The correct formula and parametrization were found
8
+ via a targeted search and then verified numerically against both real
9
+ (beta=1) and complex (beta=2) simulations before being used here -- see
10
+ ``physicskit.rmt/stats/wachter.py`` and the design notes.
11
+ """
12
+
13
+ import numpy as np
14
+ import pytest
15
+ from cache_utils import cached_sample
16
+ from scipy.stats import linregress
17
+
18
+ import physicskit.rmt as rmt
19
+
20
+ JACOBI = [
21
+ ("JOE", rmt.ensembles.JOE, 1),
22
+ ("JUE", rmt.ensembles.JUE, 2),
23
+ ("JSE", rmt.ensembles.JSE, 4),
24
+ ]
25
+
26
+
27
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
28
+ def test_beta_is_correct(name, cls, beta):
29
+ ens = cls(n=50, m1=150, m2=200, seed=0)
30
+ assert ens.beta == beta
31
+
32
+
33
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
34
+ def test_a_b_properties(name, cls, beta):
35
+ ens = cls(n=100, m1=200, m2=300, seed=0)
36
+ assert ens.a == pytest.approx(2.0)
37
+ assert ens.b == pytest.approx(3.0)
38
+
39
+
40
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
41
+ def test_eigenvalues_in_unit_interval(name, cls, beta):
42
+ ens = cls(n=200, m1=400, m2=600, seed=1)
43
+ spectrum = cached_sample(ens, n_samples=10)
44
+ assert np.all(spectrum.eigenvalues >= -1e-9)
45
+ assert np.all(spectrum.eigenvalues <= 1.0 + 1e-9)
46
+
47
+
48
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
49
+ def test_reproducibility(name, cls, beta):
50
+ ens_a = cls(n=80, m1=160, m2=240, seed=42)
51
+ ens_b = cls(n=80, m1=160, m2=240, seed=42)
52
+ spec_a = ens_a.sample(n_samples=3)
53
+ spec_b = ens_b.sample(n_samples=3)
54
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
55
+
56
+
57
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
58
+ def test_support_matches_wachter_edges(name, cls, beta):
59
+ n, m1, m2 = 2000, 4000, 6000
60
+ ens = cls(n=n, m1=m1, m2=m2, seed=2)
61
+ spectrum = cached_sample(ens, n_samples=1)
62
+ lo, hi = rmt.stats.wachter_support(m1 / n, m2 / n)
63
+ eigs = spectrum.eigenvalues.ravel()
64
+ assert eigs.min() > lo - 0.05
65
+ assert eigs.max() < hi + 0.05
66
+ assert eigs.min() < lo + 0.08
67
+ assert eigs.max() > hi - 0.08
68
+
69
+
70
+ @pytest.mark.parametrize("name,cls,beta", JACOBI)
71
+ def test_wachter_convergence_shrinks_with_n(name, cls, beta):
72
+ # Looped manually (rather than via benchmark.convergence_curve) so
73
+ # each N's sample can be disk-cached individually.
74
+ m1_ratio, m2_ratio = 2.0, 3.0 # fixed inverse aspect ratios as n scales
75
+ n_values = [50, 150, 450]
76
+
77
+ benchmark = rmt.validation.Wachter(a=m1_ratio, b=m2_ratio)
78
+ results = []
79
+ for n in n_values:
80
+ ens = cls(n=n, m1=int(n * m1_ratio), m2=int(n * m2_ratio), seed=9)
81
+ spectrum = cached_sample(ens, n_samples=20)
82
+ results.append(benchmark.validate(spectrum, seed=9))
83
+
84
+ ks_stats = np.array([r.ks_statistic for r in results])
85
+ slope = linregress(np.log(n_values), np.log(ks_stats)).slope
86
+ assert slope < -0.3, f"{name}: expected shrinking KS distance, slope={slope}"
87
+ assert ks_stats[-1] < 0.01
88
+
89
+
90
+ def test_invalid_parameters_rejected():
91
+ with pytest.raises(ValueError):
92
+ rmt.ensembles.JOE(n=100, m1=50, m2=200, seed=0) # m1 < n
93
+ with pytest.raises(ValueError):
94
+ rmt.ensembles.JacobiBetaEnsemble(n=100, m1=200, m2=200, beta=3, seed=0)
95
+ with pytest.raises(ValueError):
96
+ rmt.validation.Wachter(a=0.5, b=2.0) # a < 1 invalid
97
+
98
+
99
+ def test_jse_eigenvalue_count_is_n_not_2n():
100
+ # JSE's quaternion embedding internally builds 2n x 2n matrices; only
101
+ # the n distinct (post-Kramers-deduplication) eigenvalues should be
102
+ # returned, matching GSE/CSE/EffGSE's convention -- see module
103
+ # docstring in physicskit.rmt/ensembles/jacobi.py.
104
+ n = 40
105
+ ens = rmt.ensembles.JSE(n=n, m1=80, m2=120, seed=0)
106
+ spectrum = ens.sample(n_samples=2)
107
+ assert spectrum.eigenvalues.shape[1] == n
108
+
109
+
110
+ def test_wachter_pdf_zero_outside_support():
111
+ a, b = 2.0, 3.0
112
+ lo, hi = rmt.stats.wachter_support(a, b)
113
+ assert rmt.stats.wachter_pdf(np.array([lo - 0.1, hi + 0.1]), a, b)[0] == 0.0
114
+ assert rmt.stats.wachter_pdf(np.array([lo - 0.1, hi + 0.1]), a, b)[1] == 0.0
115
+
116
+
117
+ def test_wachter_pdf_scalar_integrand_is_zero_outside_support():
118
+ """quad() integrates _wachter_pdf_scalar directly (not the vectorized
119
+ wachter_pdf above) to build wachter_cdf; it needs the same
120
+ zero-outside-support guard."""
121
+ from physicskit.rmt.stats.wachter import _wachter_pdf_scalar
122
+
123
+ a, b = 2.0, 3.0
124
+ lo, hi = rmt.stats.wachter_support(a, b)
125
+ assert _wachter_pdf_scalar(lo - 0.1, a, b, lo, hi) == 0.0
126
+ assert _wachter_pdf_scalar(hi + 0.1, a, b, lo, hi) == 0.0
127
+
128
+
129
+ def test_wachter_cdf_is_zero_below_and_one_above_support_and_monotonic():
130
+ a, b = 2.0, 3.0
131
+ lo, hi = rmt.stats.wachter_support(a, b)
132
+ x = np.linspace(lo - 0.1, hi + 0.1, 20)
133
+ cdf = rmt.stats.wachter_cdf(x, a, b)
134
+ assert cdf[0] == 0.0
135
+ assert cdf[-1] == 1.0
136
+ assert np.all(np.diff(cdf) >= -1e-12)
@@ -0,0 +1,240 @@
1
+ """Tests for eigenvector-based localization statistics (inverse
2
+ participation ratio) and the ``sample(return_eigenvectors=True)`` API.
3
+
4
+ ``ipr_theory``'s Dirichlet-moment formula was verified during
5
+ development against direct Haar-random-vector Monte Carlo at beta=1, 2,
6
+ 4 before being trusted (see ``physicskit.rmt.stats.localization``). The
7
+ tridiagonal-model ensembles (GOE/GUE/GSE via the fast
8
+ Dumitriu-Edelman sampler) do NOT get eigenvector support from that
9
+ formula for free: their raw eigenvectors were checked during development
10
+ and found to be more localized than a true Haar vector (IPR ~2-3x too
11
+ large, growing with n) -- only a genuinely dense diagonalization gives
12
+ Haar-distributed eigenvectors, which is why
13
+ ``HermiteBetaEnsemble._sample_eigenvalues_and_vectors`` uses a separate
14
+ dense construction for beta in (1, 2, 4). GSE (beta=4) eigenvectors are
15
+ naturally 2n-dimensional complex vectors (n quaternionic "sites"), so
16
+ they need the dedicated ``inverse_participation_ratio_quaternionic``
17
+ (folding pairs of components first) rather than the plain
18
+ ``inverse_participation_ratio`` -- verified during development that the
19
+ folded IPR matches ``ipr_theory(n, beta=4)`` to four significant figures.
20
+ """
21
+
22
+ import numpy as np
23
+ import pytest
24
+
25
+ import physicskit.rmt as rmt
26
+
27
+
28
+ def test_ensemble_without_eigenvector_support_raises():
29
+ ens = rmt.ensembles.LOE(n=20, m=25, seed=0)
30
+ with pytest.raises(NotImplementedError):
31
+ ens.sample(return_eigenvectors=True)
32
+
33
+
34
+ def test_gse_eigenvectors_are_normalized_and_shaped_2n_by_n():
35
+ n = 20
36
+ ens = rmt.ensembles.GSE(n=n, seed=0)
37
+ spectrum = ens.sample(n_samples=2, return_eigenvectors=True)
38
+ assert spectrum.eigenvectors is not None
39
+ assert spectrum.eigenvalues.shape == (2, n)
40
+ assert spectrum.eigenvectors.shape == (2, 2 * n, n)
41
+ norms = np.sum(np.abs(spectrum.eigenvectors) ** 2, axis=1)
42
+ np.testing.assert_allclose(norms, 1.0, atol=1e-8)
43
+
44
+
45
+ def test_gse_quaternionic_ipr_matches_haar_theory():
46
+ n = 200
47
+ ens = rmt.ensembles.GSE(n=n, seed=1)
48
+ spectrum = ens.sample(n_samples=20, return_eigenvectors=True)
49
+ ipr = rmt.stats.inverse_participation_ratio_quaternionic(spectrum.eigenvectors)
50
+ theory = rmt.stats.ipr_theory(n, beta=4)
51
+ assert ipr.mean() == pytest.approx(theory, rel=0.15)
52
+
53
+
54
+ def test_naive_ipr_on_gse_eigenvectors_does_not_match_theory():
55
+ # Documents the pitfall: calling the plain (non-quaternionic) IPR
56
+ # function directly on GSE eigenvectors treats all 2n complex
57
+ # components as independent sites, which does NOT match
58
+ # ipr_theory(n, beta=4) -- inverse_participation_ratio_quaternionic
59
+ # must be used instead (see module docstring).
60
+ n = 200
61
+ ens = rmt.ensembles.GSE(n=n, seed=1)
62
+ spectrum = ens.sample(n_samples=20, return_eigenvectors=True)
63
+ naive_ipr = rmt.stats.inverse_participation_ratio(spectrum.eigenvectors)
64
+ theory = rmt.stats.ipr_theory(n, beta=4)
65
+ assert naive_ipr.mean() != pytest.approx(theory, rel=0.15)
66
+
67
+
68
+ @pytest.mark.parametrize("cls", [rmt.ensembles.GOE, rmt.ensembles.GUE])
69
+ def test_dense_gaussian_ensemble_eigenvectors_are_normalized(cls):
70
+ ens = cls(n=30, seed=0)
71
+ spectrum = ens.sample(n_samples=2, return_eigenvectors=True)
72
+ assert spectrum.eigenvectors is not None
73
+ assert spectrum.eigenvectors.shape == (2, 30, 30)
74
+ norms = np.sum(np.abs(spectrum.eigenvectors) ** 2, axis=1)
75
+ np.testing.assert_allclose(norms, 1.0, atol=1e-8)
76
+
77
+
78
+ @pytest.mark.parametrize("cls,beta", [(rmt.ensembles.GOE, 1), (rmt.ensembles.GUE, 2)])
79
+ def test_dense_gaussian_ensemble_ipr_matches_haar_theory(cls, beta):
80
+ n = 250
81
+ ens = cls(n=n, seed=1)
82
+ spectrum = ens.sample(n_samples=30, return_eigenvectors=True)
83
+ ipr = rmt.stats.inverse_participation_ratio(spectrum.eigenvectors)
84
+ theory = rmt.stats.ipr_theory(n, beta)
85
+ assert ipr.mean() == pytest.approx(theory, rel=0.15)
86
+
87
+
88
+ def test_ipr_theory_matches_direct_haar_vector_simulation():
89
+ # Independent check of the closed-form formula itself, bypassing any
90
+ # ensemble machinery: a Haar-random (Gaussian, then normalized) real
91
+ # vector's mean IPR should match ipr_theory(n, beta=1) directly.
92
+ rng = np.random.default_rng(7)
93
+ n = 100
94
+ x = rng.standard_normal((20_000, n))
95
+ x /= np.linalg.norm(x, axis=1, keepdims=True)
96
+ empirical = np.mean(np.sum(x**4, axis=1))
97
+ assert empirical == pytest.approx(rmt.stats.ipr_theory(n, beta=1), rel=0.05)
98
+
99
+
100
+ def test_participation_ratio_is_reciprocal_of_ipr():
101
+ rng = np.random.default_rng(3)
102
+ vecs = rng.standard_normal((5, 10, 4))
103
+ ipr = rmt.stats.inverse_participation_ratio(vecs)
104
+ pr = rmt.stats.participation_ratio(vecs)
105
+ np.testing.assert_allclose(pr, 1.0 / ipr)
106
+
107
+
108
+ def test_pbrm_localization_transition_via_ipr():
109
+ # The defining physics of PowerLawBandedEnsemble (see its module
110
+ # docstring): small b (narrow band) -> localized eigenvectors (IPR
111
+ # much larger than the delocalized/GOE-like baseline); large b
112
+ # (effectively unbanded) -> delocalized eigenvectors, IPR approaching
113
+ # the Haar-vector theory value.
114
+ n = 300
115
+ localized = rmt.ensembles.PowerLawBandedEnsemble(n=n, b=0.5, alpha=2.0, seed=10)
116
+ delocalized = rmt.ensembles.PowerLawBandedEnsemble(n=n, b=50.0, alpha=2.0, seed=11)
117
+
118
+ # cache_utils.cached_sample never requests eigenvectors, so sample directly.
119
+ spec_localized = localized.sample(n_samples=10, return_eigenvectors=True)
120
+ spec_delocalized = delocalized.sample(n_samples=10, return_eigenvectors=True)
121
+
122
+ ipr_localized = rmt.stats.inverse_participation_ratio(spec_localized.eigenvectors).mean()
123
+ ipr_delocalized = rmt.stats.inverse_participation_ratio(spec_delocalized.eigenvectors).mean()
124
+ theory_delocalized = rmt.stats.ipr_theory(n, beta=1)
125
+
126
+ assert ipr_localized > 10 * ipr_delocalized
127
+ assert ipr_delocalized == pytest.approx(theory_delocalized, rel=0.3)
128
+
129
+
130
+ def test_eigenvectors_absent_by_default():
131
+ ens = rmt.ensembles.GOE(n=20, seed=0)
132
+ spectrum = ens.sample(n_samples=2)
133
+ assert spectrum.eigenvectors is None
134
+
135
+
136
+ # --- multifractal dimension spectrum ---
137
+
138
+
139
+ def test_generalized_ipr_matches_plain_ipr_at_q_2():
140
+ rng = np.random.default_rng(4)
141
+ vecs = rng.standard_normal((5, 10, 4))
142
+ np.testing.assert_allclose(rmt.stats.generalized_ipr(vecs, q=2.0), rmt.stats.inverse_participation_ratio(vecs))
143
+
144
+
145
+ def test_generalized_ipr_rejects_q_equals_1():
146
+ rng = np.random.default_rng(4)
147
+ vecs = rng.standard_normal((5, 4))
148
+ with pytest.raises(ValueError):
149
+ rmt.stats.generalized_ipr(vecs, q=1.0)
150
+
151
+
152
+ def test_multifractal_dimension_is_one_for_delocalized_pbrm():
153
+ # Large, fixed band-width b -> effectively GOE-like/delocalized, so
154
+ # D_2 should be close to 1 (E[I_2] ~ 1/n exactly).
155
+ def factory(n, seed):
156
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
157
+
158
+ d_2, r_squared = rmt.stats.multifractal_dimension(factory, n_values=[100, 200, 400, 800], q=2.0, n_samples=10, seed=0)
159
+ assert d_2 == pytest.approx(1.0, abs=0.1)
160
+ assert r_squared > 0.99
161
+
162
+
163
+ def test_multifractal_dimension_is_zero_for_localized_pbrm():
164
+ # Small, fixed band-width b -> strongly localized, so E[I_2] stays
165
+ # roughly n-independent, giving D_2 close to 0.
166
+ def factory(n, seed):
167
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=0.3, alpha=2.0, seed=seed)
168
+
169
+ d_2, _ = rmt.stats.multifractal_dimension(factory, n_values=[100, 200, 400, 800], q=2.0, n_samples=10, seed=1)
170
+ assert d_2 == pytest.approx(0.0, abs=0.2)
171
+
172
+
173
+ # --- f(alpha) singularity spectrum ---
174
+
175
+
176
+ def test_mass_exponent_is_exactly_zero_at_q_1():
177
+ def factory(n, seed):
178
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
179
+
180
+ tau_1, r_squared = rmt.stats.mass_exponent(factory, n_values=[100, 200, 400], q=1.0, n_samples=5, seed=0)
181
+ assert tau_1 == 0.0
182
+ assert r_squared == 1.0
183
+
184
+
185
+ def test_multifractal_dimension_rejects_q_1():
186
+ def factory(n, seed):
187
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
188
+
189
+ with pytest.raises(ValueError):
190
+ rmt.stats.multifractal_dimension(factory, n_values=[100, 200], q=1.0, n_samples=5, seed=0)
191
+
192
+
193
+ @pytest.mark.slow
194
+ def test_mass_exponent_matches_multifractal_dimension_relation():
195
+ def factory(n, seed):
196
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
197
+
198
+ n_values = [50, 100, 200, 400]
199
+ tau_2, _ = rmt.stats.mass_exponent(factory, n_values, q=2.0, n_samples=10, seed=0)
200
+ d_2, _ = rmt.stats.multifractal_dimension(factory, n_values, q=2.0, n_samples=10, seed=0)
201
+ assert tau_2 / (2.0 - 1.0) == pytest.approx(d_2)
202
+
203
+
204
+ @pytest.mark.slow
205
+ def test_singularity_spectrum_is_a_single_point_for_delocalized_system():
206
+ # Fully delocalized (D_q=1 for all q) means tau(q)=q-1 exactly, so
207
+ # alpha(q)=1 and f(alpha)=1 for every q -- the degenerate
208
+ # "monofractal" limit of the singularity spectrum.
209
+ def factory(n, seed):
210
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
211
+
212
+ alpha, f_alpha = rmt.stats.singularity_spectrum(
213
+ factory,
214
+ n_values=[100, 200, 400, 800],
215
+ q_values=np.array([0.5, 1.0, 1.5, 2.0]),
216
+ n_samples=10,
217
+ seed=0,
218
+ )
219
+ np.testing.assert_allclose(alpha, 1.0, atol=0.15)
220
+ np.testing.assert_allclose(f_alpha, 1.0, atol=0.15)
221
+
222
+
223
+ @pytest.mark.slow
224
+ def test_singularity_spectrum_is_positive_and_concave_at_small_q_for_critical_pbrm():
225
+ # At PBRM's multifractal critical point, the moderate-|q| portion of
226
+ # f(alpha) should be positive (a genuine fractal dimension) and
227
+ # concave (f(alpha) decreasing in |q-1|) -- see module/function
228
+ # docstring for why larger |q| is not asserted here (harder
229
+ # convergence, possible legitimate negative-dimension branch).
230
+ def factory(n, seed):
231
+ return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
232
+
233
+ q_values = np.array([0.5, 1.0, 1.5])
234
+ _, f_alpha = rmt.stats.singularity_spectrum(factory, n_values=[75, 150, 300, 600], q_values=q_values, n_samples=12, seed=2)
235
+ assert np.all(f_alpha > 0.0)
236
+ # Standard multifractal shape: f(alpha(q)) decreases monotonically as
237
+ # q increases (alpha(q) itself decreases with q, with the global max
238
+ # of f at q=0 -- not asserted here since q=0 needs even larger n for
239
+ # reliable convergence); over this q range it should be decreasing.
240
+ assert f_alpha[0] > f_alpha[1] > f_alpha[2]
@@ -0,0 +1,149 @@
1
+ """Tests for the Poisson ensemble -- the integrable-system (Berry-Tabor)
2
+ null model that RMT level-repulsion statistics are always contrasted
3
+ against.
4
+
5
+ Unlike GOE/GUE/GSE, whose spacing and ratio distributions are only
6
+ approximated by a surmise (exact for a reduced few-level model), the
7
+ Poisson process's spacing and ratio laws are exactly known in closed
8
+ form -- so tests here check against exact theory throughout, not a
9
+ surmise. The ratio-statistic law (2/(1+r)**2 for the min/max-normalized
10
+ r used by ``physicskit.rmt.stats.ratios.ratio_statistics``) is derived directly
11
+ in the module docstring from the ratio of two i.i.d. Exponential(1)
12
+ variables; ``test_ratio_statistic_matches_derived_exact_formula`` checks
13
+ that derivation numerically before it is used anywhere else.
14
+ """
15
+
16
+ import numpy as np
17
+ import pytest
18
+ from cache_utils import cached_sample
19
+ from scipy.stats import expon, kstest
20
+
21
+ import physicskit.rmt as rmt
22
+
23
+
24
+ def test_beta_is_none():
25
+ ens = rmt.ensembles.PoissonEnsemble(n=30, seed=0)
26
+ assert ens.beta is None
27
+
28
+
29
+ def test_shape_and_reproducibility():
30
+ ens_a = rmt.ensembles.PoissonEnsemble(n=50, seed=42)
31
+ ens_b = rmt.ensembles.PoissonEnsemble(n=50, seed=42)
32
+ spec_a = ens_a.sample(n_samples=5)
33
+ spec_b = ens_b.sample(n_samples=5)
34
+ assert spec_a.eigenvalues.shape == (5, 50)
35
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
36
+
37
+
38
+ def test_levels_are_strictly_increasing():
39
+ # Partial sums of strictly positive Exponential draws.
40
+ ens = rmt.ensembles.PoissonEnsemble(n=100, seed=1)
41
+ spectrum = cached_sample(ens, n_samples=5)
42
+ for row in spectrum.eigenvalues:
43
+ assert np.all(np.diff(row) > 0)
44
+
45
+
46
+ def test_mean_spacing_is_one():
47
+ ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=2)
48
+ spectrum = cached_sample(ens, n_samples=10)
49
+ spacings = np.concatenate([np.diff(row) for row in spectrum.eigenvalues])
50
+ assert spacings.mean() == pytest.approx(1.0, abs=0.03)
51
+
52
+
53
+ def test_spacing_distribution_matches_exact_exponential_law():
54
+ # No unfolding needed (see module docstring) -- raw spacings are
55
+ # already Exponential(1) by construction, checked directly against
56
+ # theory rather than a surmise.
57
+ ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=3)
58
+ spectrum = cached_sample(ens, n_samples=20)
59
+ spacings = np.concatenate([np.diff(row) for row in spectrum.eigenvalues])
60
+ ks = kstest(spacings, expon(scale=1.0).cdf)
61
+ assert ks.statistic < 0.01
62
+
63
+
64
+ def test_ratio_statistic_matches_derived_exact_formula():
65
+ # Verify 2/(1+r)**2 (module docstring derivation) directly against
66
+ # Monte Carlo ratios of independent Exponential(1) pairs, before
67
+ # trusting it against the ensemble itself.
68
+ rng = np.random.default_rng(4)
69
+ x = rng.exponential(1.0, size=2_000_000)
70
+ y = rng.exponential(1.0, size=2_000_000)
71
+ r = np.minimum(x, y) / np.maximum(x, y)
72
+
73
+ def poisson_ratio_cdf(r):
74
+ return 2.0 * r / (1.0 + r)
75
+
76
+ ks = kstest(r, poisson_ratio_cdf)
77
+ assert ks.statistic < 0.002
78
+
79
+
80
+ def test_ensemble_ratio_statistic_matches_exact_poisson_law():
81
+ ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=5)
82
+ spectrum = cached_sample(ens, n_samples=20)
83
+ ratios = rmt.stats.ratio_statistics(spectrum)
84
+
85
+ def poisson_ratio_cdf(r):
86
+ return 2.0 * r / (1.0 + r)
87
+
88
+ ks = kstest(ratios, poisson_ratio_cdf)
89
+ assert ks.statistic < 0.01
90
+
91
+
92
+ def test_number_variance_grows_linearly_like_poisson_theory():
93
+ # No unfolding needed -- raw levels already have unit mean density,
94
+ # so window-counting is done directly rather than via
95
+ # ``number_variance_empirical`` (which expects a fixed-shape
96
+ # theoretical CDF to unfold against, the wrong abstraction for an
97
+ # unbounded, already-uniform-density process -- see module
98
+ # docstring).
99
+ ens = rmt.ensembles.PoissonEnsemble(n=4000, seed=6)
100
+ spectrum = cached_sample(ens, n_samples=15)
101
+ l_values = [2, 5, 10, 20]
102
+ rng = np.random.default_rng(7)
103
+
104
+ empirical = []
105
+ for length in l_values:
106
+ counts = []
107
+ for row in spectrum.eigenvalues:
108
+ lo, hi = row[0], row[-1]
109
+ starts = rng.uniform(lo, hi - length, size=200)
110
+ counts.extend(np.sum((row >= s) & (row < s + length)) for s in starts)
111
+ empirical.append(np.var(counts))
112
+ empirical = np.array(empirical)
113
+
114
+ theory = rmt.stats.number_variance_poisson(l_values)
115
+ np.testing.assert_allclose(empirical, theory, atol=0.6)
116
+
117
+
118
+ def test_poisson_number_variance_is_much_larger_than_sampled_gue():
119
+ # The defining qualitative contrast this whole ensemble exists to
120
+ # provide: GUE's number variance grows logarithmically (level
121
+ # repulsion/rigidity), Poisson's grows linearly -- checked here
122
+ # against an actually-sampled GUE spectrum, not just GUE's theory
123
+ # curve (already covered by test_rigidity_universality.py).
124
+ l_values = [10, 20]
125
+ poisson_theory = rmt.stats.number_variance_poisson(l_values)
126
+
127
+ gue_ens = rmt.ensembles.GUE(n=4000, seed=8)
128
+ gue_spectrum = cached_sample(gue_ens, n_samples=15)
129
+ gue_empirical = rmt.stats.number_variance_empirical(
130
+ gue_spectrum,
131
+ rmt.stats.semicircle_cdf,
132
+ l_values,
133
+ n_windows=200,
134
+ seed=9,
135
+ )
136
+ assert np.all(gue_empirical < poisson_theory / 3)
137
+
138
+
139
+ def test_generalized_wigner_surmise_at_beta_zero_is_not_poisson():
140
+ # Documents the important distinction from the module docstring: the
141
+ # Gaussian-tailed generalized Wigner surmise does NOT reduce to the
142
+ # exponential Poisson spacing law at beta=0 -- it gives a
143
+ # half-Gaussian instead. Concretely, both densities are normalized
144
+ # (integrate to 1) but disagree sharply at s=0: Poisson's exact
145
+ # density there is exp(0)=1, the beta=0 surmise's is 2/pi.
146
+ poisson_density_at_zero = 1.0
147
+ surmise_density_at_zero = rmt.stats.wigner_surmise_pdf(0.0, beta=0)
148
+ assert surmise_density_at_zero == pytest.approx(2.0 / np.pi)
149
+ assert abs(surmise_density_at_zero - poisson_density_at_zero) > 0.3
@@ -0,0 +1,107 @@
1
+ """Tests for the polynomial/biorthogonal ensemble (products of Ginibre
2
+ matrices).
3
+
4
+ L=1 is checked directly against the already-validated
5
+ ``MarchenkoPastur`` benchmark (reused, not reimplemented -- it's the
6
+ exact same theoretical object). L >= 2 has no simple closed-form
7
+ density implemented here, so it is validated against the one thing that
8
+ *is* known in exact closed form for general L: the Fuss-Catalan moments
9
+ (Penson-Zyczkowski 2011), themselves checked against Monte Carlo before
10
+ being trusted (see ``test_fuss_catalan_moment_matches_monte_carlo``).
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
+ from physicskit.rmt.ensembles.polynomial import fuss_catalan_moment
19
+
20
+
21
+ def test_default_num_factors_is_two():
22
+ ens = rmt.ensembles.PolynomialEnsemble(n=30, seed=0)
23
+ assert ens.num_factors == 2
24
+
25
+
26
+ def test_beta_1_real_factors_produce_finite_eigenvalues():
27
+ ens = rmt.ensembles.PolynomialEnsemble(n=8, num_factors=2, beta=1, seed=7)
28
+ spectrum = ens.sample(n_samples=2)
29
+ assert np.all(np.isfinite(spectrum.eigenvalues))
30
+
31
+
32
+ def test_eigenvalues_are_nonnegative():
33
+ ens = rmt.ensembles.PolynomialEnsemble(n=50, num_factors=3, seed=1)
34
+ spectrum = cached_sample(ens, n_samples=5)
35
+ assert np.all(spectrum.eigenvalues >= -1e-8)
36
+
37
+
38
+ def test_reproducibility():
39
+ ens_a = rmt.ensembles.PolynomialEnsemble(n=40, num_factors=2, seed=42)
40
+ ens_b = rmt.ensembles.PolynomialEnsemble(n=40, num_factors=2, seed=42)
41
+ spec_a = ens_a.sample(n_samples=3)
42
+ spec_b = ens_b.sample(n_samples=3)
43
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
44
+
45
+
46
+ def test_invalid_parameters_rejected():
47
+ with pytest.raises(ValueError):
48
+ rmt.ensembles.PolynomialEnsemble(n=10, num_factors=0, seed=0)
49
+ with pytest.raises(ValueError):
50
+ rmt.ensembles.PolynomialEnsemble(n=10, beta=4, seed=0)
51
+
52
+
53
+ @pytest.mark.parametrize("beta", [1, 2])
54
+ def test_num_factors_one_matches_marchenko_pastur_exactly(beta):
55
+ # L=1 is, EXACTLY (not approximately), the classical Wishart/LUE-LOE
56
+ # ensemble -- reusing the already-validated MarchenkoPastur
57
+ # benchmark directly, rather than a separate implementation, is
58
+ # itself part of the point (see module docstring).
59
+ n = 2000
60
+ ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=1, beta=beta, seed=2)
61
+ spectrum = cached_sample(ens, n_samples=15)
62
+ benchmark = rmt.validation.MarchenkoPastur(gamma=1.0)
63
+ result = benchmark.validate(spectrum, seed=2)
64
+ assert result.ks_statistic < 0.02
65
+
66
+
67
+ def test_fuss_catalan_moment_matches_monte_carlo():
68
+ # Verify the exact moment formula itself (not assumed from the
69
+ # literature citation) via a large, independent Monte Carlo
70
+ # generalized-Ginibre-product simulation, for L=1..4.
71
+ rng = np.random.default_rng(3)
72
+ for num_factors in [1, 2, 3, 4]:
73
+ n = 400
74
+ x = np.eye(n, dtype=complex)
75
+ for _ in range(num_factors):
76
+ factor = (rng.standard_normal((n, n)) + 1j * rng.standard_normal((n, n))) / np.sqrt(2.0)
77
+ x = x @ factor
78
+ eigs = np.linalg.eigvalsh(x.conj().T @ x) / n**num_factors
79
+ for k in [1, 2]:
80
+ empirical = np.mean(eigs**k)
81
+ theory = fuss_catalan_moment(k, num_factors)
82
+ assert empirical == pytest.approx(theory, rel=0.1)
83
+
84
+
85
+ @pytest.mark.parametrize("num_factors", [1, 2, 3, 4])
86
+ def test_ensemble_moments_match_fuss_catalan(num_factors):
87
+ n = 300
88
+ ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=num_factors, beta=2, seed=4)
89
+ spectrum = cached_sample(ens, n_samples=10)
90
+ eigs = spectrum.rescaled.ravel()
91
+ for k in [1, 2]:
92
+ empirical = np.mean(eigs**k)
93
+ theory = fuss_catalan_moment(k, num_factors)
94
+ assert empirical == pytest.approx(theory, rel=0.1)
95
+
96
+
97
+ @pytest.mark.parametrize("num_factors", [1, 2, 3])
98
+ def test_spectrum_stays_near_fuss_catalan_support_edge(num_factors):
99
+ # Largest rescaled eigenvalue should approach, but not meaningfully
100
+ # exceed, the exact Fuss-Catalan support edge (L+1)**(L+1) / L**L.
101
+ n = 400
102
+ ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=num_factors, beta=2, seed=5)
103
+ spectrum = cached_sample(ens, n_samples=5)
104
+ edge = (num_factors + 1) ** (num_factors + 1) / num_factors**num_factors
105
+ max_eig = spectrum.rescaled.max()
106
+ assert max_eig < edge * 1.1
107
+ assert max_eig > edge * 0.7