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,127 @@
1
+ """Tests for the Gaussian ensembles (GOE/GUE/GSE) and the Wigner semicircle
2
+ benchmark.
3
+
4
+ Per the design plan (see ``physicskit.rmt.validation.base``), we do NOT gate on a
5
+ single p > 0.05 KS test at one fixed N -- KS power grows with N, so that
6
+ criterion becomes *less* likely to pass as N grows, which is backwards for
7
+ demonstrating asymptotic convergence. Instead we assert that the distance
8
+ to the theoretical semicircle law shrinks as N increases.
9
+ """
10
+
11
+ import numpy as np
12
+ import pytest
13
+ from cache_utils import cached_sample
14
+ from scipy.stats import linregress
15
+
16
+ import physicskit.rmt as rmt
17
+
18
+ CLASSICAL_ENSEMBLES = [
19
+ ("GOE", rmt.ensembles.GOE, 1.0),
20
+ ("GUE", rmt.ensembles.GUE, 2.0),
21
+ ("GSE", rmt.ensembles.GSE, 4.0),
22
+ ]
23
+
24
+
25
+ @pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_ENSEMBLES)
26
+ def test_beta_is_correct(name, cls, expected_beta):
27
+ ens = cls(n=50, seed=0)
28
+ assert ens.beta == expected_beta
29
+
30
+
31
+ @pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_ENSEMBLES)
32
+ def test_sample_shape_and_reproducibility(name, cls, expected_beta):
33
+ ens_a = cls(n=200, seed=123)
34
+ ens_b = cls(n=200, seed=123)
35
+ spec_a = ens_a.sample(n_samples=5)
36
+ spec_b = ens_b.sample(n_samples=5)
37
+
38
+ assert spec_a.eigenvalues.shape == (5, 200)
39
+ # same seed -> identical draws
40
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
41
+
42
+
43
+ @pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_ENSEMBLES)
44
+ def test_eigenvalues_are_sorted_and_real(name, cls, expected_beta):
45
+ ens = cls(n=100, seed=1)
46
+ spectrum = cached_sample(ens, n_samples=3)
47
+ for row in spectrum.eigenvalues:
48
+ assert np.all(np.isfinite(row))
49
+ assert np.all(np.diff(row) >= 0) # eigh_tridiagonal returns ascending order
50
+
51
+
52
+ @pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_ENSEMBLES)
53
+ def test_rescaled_support_approaches_semicircle_radius(name, cls, expected_beta):
54
+ # At large n, essentially all rescaled eigenvalues should fall within
55
+ # [-2, 2] (+ small finite-size overshoot), and the extremes should be
56
+ # close to +/-2 (Tracy-Widom edge fluctuations are O(n^{-2/3}), much
57
+ # smaller than the bulk scale checked here).
58
+ ens = cls(n=3000, seed=2)
59
+ spectrum = cached_sample(ens, n_samples=1)
60
+ rescaled = spectrum.rescaled.ravel()
61
+ assert rescaled.max() < 2.5
62
+ assert rescaled.min() > -2.5
63
+ assert rescaled.max() > 1.8
64
+ assert rescaled.min() < -1.8
65
+
66
+
67
+ @pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_ENSEMBLES)
68
+ def test_semicircle_convergence_rate_decreases_with_n(name, cls, expected_beta):
69
+ # NOTE on methodology: eigenvalues within a single sampled matrix are
70
+ # NOT independent (level repulsion correlates them), so the
71
+ # distance-to-theory at any *single* N is a noisy statistic -- a
72
+ # strict pairwise decrease across only 2-3 N values is not a
73
+ # statistically robust thing to assert (it can fail by chance even
74
+ # when the ensemble is implemented correctly). Instead, fit the
75
+ # log-log trend across five N values and require a clearly negative
76
+ # slope; this is the same "convergence curve, not a single p > 0.05
77
+ # gate" philosophy from the design plan, made robust to per-point noise.
78
+ #
79
+ # Looped manually here (rather than via benchmark.convergence_curve)
80
+ # so each N's sample can be disk-cached individually.
81
+ benchmark = rmt.validation.WignerSemicircle()
82
+ n_values = [100, 300, 900, 2700, 8100]
83
+ results = []
84
+ for n in n_values:
85
+ ens = cls(n=n, seed=42)
86
+ spectrum = cached_sample(ens, n_samples=30)
87
+ results.append(benchmark.validate(spectrum, seed=42))
88
+
89
+ ks_stats = np.array([r.ks_statistic for r in results])
90
+ wasserstein = np.array([r.wasserstein_distance for r in results])
91
+
92
+ ks_slope = linregress(np.log(n_values), np.log(ks_stats)).slope
93
+ # KS convergence is the clean, low-noise signal here: theory predicts
94
+ # roughly -1/2 to -1; require a clearly negative trend well short of
95
+ # that range to keep the test robust to run-to-run noise.
96
+ assert ks_slope < -0.3, f"{name}: expected shrinking KS distance, slope={ks_slope}"
97
+
98
+ # Wasserstein distance (vs a large fixed reference sample) is a noisier
99
+ # signal at this sample budget -- use it as a coarser sanity check
100
+ # (final smaller than initial) rather than a strict per-step trend.
101
+ assert wasserstein[-1] < wasserstein[0]
102
+
103
+ # By the largest N tested, agreement should already be tight in
104
+ # absolute terms. The Wasserstein floor here is set by residual
105
+ # Monte Carlo noise (correlated eigenvalues within a sample reduce
106
+ # the effective sample size below n * n_samples), not by any
107
+ # remaining bias -- verified empirically to sit around 0.002-0.003
108
+ # at n=8100 with n_samples=30 for GOE/GUE/GSE.
109
+ assert ks_stats[-1] < 0.001
110
+ assert wasserstein[-1] < 0.005
111
+
112
+
113
+ def test_continuum_beta_also_matches_semicircle():
114
+ # beta need not be 1, 2, or 4 -- the semicircle law (and this
115
+ # construction) holds for any beta > 0.
116
+ ens = rmt.ensembles.HermiteBetaEnsemble(n=2000, beta=1.7, seed=3)
117
+ spectrum = cached_sample(ens, n_samples=25)
118
+ benchmark = rmt.validation.WignerSemicircle()
119
+ result = benchmark.validate(spectrum, seed=3)
120
+ assert result.ks_statistic < 0.01
121
+
122
+
123
+ def test_invalid_beta_rejected():
124
+ with pytest.raises(ValueError):
125
+ rmt.ensembles.HermiteBetaEnsemble(n=100, beta=0, seed=0)
126
+ with pytest.raises(ValueError):
127
+ rmt.ensembles.HermiteBetaEnsemble(n=100, beta=-1, seed=0)
@@ -0,0 +1,131 @@
1
+ """Tests for GinOE, GinUE, GinSE and the circular law benchmark.
2
+
3
+ Circular law convergence is known to be slower than the Hermitian
4
+ ensembles' semicircle/Marchenko-Pastur convergence (non-Hermitian
5
+ matrices have weaker eigenvalue rigidity) -- confirmed during
6
+ development (log-log KS slope ~ -0.37 vs ~ -0.85 to -0.97 for the
7
+ semicircle law at comparable N). Thresholds here are calibrated to that
8
+ genuinely slower rate, not loosened arbitrarily.
9
+ """
10
+
11
+ import numpy as np
12
+ import pytest
13
+ from cache_utils import cached_sample
14
+ from scipy.stats import linregress
15
+
16
+ import physicskit.rmt as rmt
17
+
18
+ GINIBRE = [
19
+ ("GinOE", rmt.ensembles.GinOE, 1),
20
+ ("GinUE", rmt.ensembles.GinUE, 2),
21
+ ("GinSE", rmt.ensembles.GinSE, 4),
22
+ ]
23
+
24
+
25
+ @pytest.mark.parametrize("name,cls,beta", GINIBRE)
26
+ def test_beta_is_correct(name, cls, beta):
27
+ ens = cls(n=30, seed=0)
28
+ assert ens.beta == beta
29
+
30
+
31
+ @pytest.mark.parametrize("name,cls,beta", GINIBRE)
32
+ def test_eigenvalues_are_complex(name, cls, beta):
33
+ ens = cls(n=50, seed=1)
34
+ spectrum = cached_sample(ens, n_samples=3)
35
+ assert np.iscomplexobj(spectrum.eigenvalues)
36
+
37
+
38
+ def test_ginse_produces_2n_eigenvalues():
39
+ # Quaternion Ginibre: n x n quaternion matrix -> 2n complex
40
+ # eigenvalues per sample (genuinely distinct conjugate pairs, NOT
41
+ # deduplicated -- see ensembles/ginibre.py docstring for why this
42
+ # differs from GSE/CSE's true degeneracy).
43
+ ens = rmt.ensembles.GinSE(n=40, seed=2)
44
+ spectrum = cached_sample(ens, n_samples=5)
45
+ assert spectrum.eigenvalues.shape == (5, 80)
46
+
47
+
48
+ def test_ginse_eigenvalues_form_conjugate_pairs_but_are_distinct():
49
+ ens = rmt.ensembles.GinSE(n=30, seed=3)
50
+ spectrum = cached_sample(ens, n_samples=1)
51
+ eigs = spectrum.eigenvalues[0]
52
+ # sort by real then imaginary part; conjugate pairs should be
53
+ # adjacent, with matching real parts and opposite imaginary parts
54
+ order = np.lexsort((eigs.imag, eigs.real))
55
+ sorted_eigs = eigs[order]
56
+ # Rough structural check: for each non-real eigenvalue, its conjugate
57
+ # should also be present in the spectrum.
58
+ non_real = sorted_eigs[np.abs(sorted_eigs.imag) > 1e-6]
59
+ conjugates_present = 0
60
+ for z in non_real:
61
+ if np.any(np.abs(sorted_eigs - np.conj(z)) < 1e-8):
62
+ conjugates_present += 1
63
+ assert conjugates_present == len(non_real)
64
+ # And distinct: no exact duplicate values (unlike GSE/CSE degeneracy)
65
+ assert len(np.unique(np.round(eigs, 10))) == len(eigs)
66
+
67
+
68
+ @pytest.mark.parametrize("name,cls,beta", GINIBRE)
69
+ def test_reproducibility(name, cls, beta):
70
+ ens_a = cls(n=60, seed=42)
71
+ ens_b = cls(n=60, seed=42)
72
+ spec_a = ens_a.sample(n_samples=3)
73
+ spec_b = ens_b.sample(n_samples=3)
74
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
75
+
76
+
77
+ @pytest.mark.parametrize("name,cls,beta", GINIBRE)
78
+ def test_radii_approach_unit_disk_at_large_n(name, cls, beta):
79
+ # NOTE: GinSE embeds an n x n quaternion matrix as a 2n x 2n complex
80
+ # matrix, so its dense eigensolve cost is ~8x that of GinOE/GinUE at
81
+ # the same n -- keep n modest here rather than reusing a size tuned
82
+ # for the real/complex cases.
83
+ n = 400
84
+ ens = cls(n=n, seed=4)
85
+ spectrum = cached_sample(ens, n_samples=1)
86
+ radii = np.abs(spectrum.rescaled.ravel())
87
+ assert radii.max() < 1.35
88
+ assert radii.mean() == pytest.approx(2.0 / 3.0, abs=0.06) # E[r] for uniform disk = 2/3
89
+
90
+
91
+ @pytest.mark.parametrize("name,cls,beta", GINIBRE)
92
+ def test_circular_law_convergence_shrinks_with_n(name, cls, beta):
93
+ # Looped manually (rather than via benchmark.convergence_curve) so
94
+ # each N's sample can be disk-cached individually -- this is the
95
+ # single slowest test in the suite (GinSE's dense 2n x 2n eigensolve
96
+ # dominates), so caching it matters most here.
97
+ benchmark = rmt.validation.CircularLaw()
98
+ n_values = [40, 90, 200, 400]
99
+ results = []
100
+ for n in n_values:
101
+ ens = cls(n=n, seed=17)
102
+ spectrum = cached_sample(ens, n_samples=12)
103
+ results.append(benchmark.validate(spectrum, seed=17))
104
+
105
+ ks_stats = np.array([r.ks_statistic for r in results])
106
+ slope = linregress(np.log(n_values), np.log(ks_stats)).slope
107
+ # Genuinely shallower than the Hermitian ensembles' convergence
108
+ # (see module docstring) -- require a real negative trend, not the
109
+ # steeper threshold used for the semicircle/MP tests.
110
+ assert slope < -0.1, f"{name}: expected shrinking KS distance, slope={slope}"
111
+ assert ks_stats[-1] < 0.04
112
+
113
+
114
+ def test_real_ginibre_has_real_eigenvalues_edelman_kostlan_shub():
115
+ # Edelman, Kostlan, Shub (1994): E[# real eigenvalues] ~ sqrt(2n/pi).
116
+ # Checked as an order-of-magnitude sanity check, not a tight
117
+ # quantitative match (this is an expectation over the ensemble, and
118
+ # we only average over a modest number of samples here).
119
+ n = 500
120
+ ens = rmt.ensembles.GinOE(n=n, seed=5)
121
+ spectrum = cached_sample(ens, n_samples=15)
122
+ real_counts = np.sum(np.abs(spectrum.eigenvalues.imag) < 1e-8, axis=1)
123
+ expected = np.sqrt(2 * n / np.pi)
124
+ assert real_counts.mean() == pytest.approx(expected, rel=0.5)
125
+
126
+
127
+ def test_complex_ginibre_has_no_real_eigenvalues_generically():
128
+ ens = rmt.ensembles.GinUE(n=200, seed=6)
129
+ spectrum = cached_sample(ens, n_samples=5)
130
+ real_fraction = np.mean(np.abs(spectrum.eigenvalues.imag) < 1e-8)
131
+ assert real_fraction < 0.02 # essentially none, unlike GinOE
@@ -0,0 +1,178 @@
1
+ """Tests for the Girko ensembles (IIDEnsemble, GirkoElliptic) --
2
+ universality generalizations of the Ginibre ensembles: the circular and
3
+ elliptic laws hold for any i.i.d. entry distribution with the right
4
+ first two moments, not just Gaussian.
5
+
6
+ IIDEnsemble is validated against the SAME ``CircularLaw`` benchmark
7
+ already used for Ginibre in ``tests/test_ginibre_circular_law.py`` --
8
+ that reuse is itself the point: Girko's theorem says non-Gaussian iid
9
+ matrices obey the identical limiting law, so no new theoretical machinery
10
+ is needed. GirkoElliptic has no existing benchmark (the general 2-D
11
+ elliptic density isn't wired into the KS/Wasserstein ``Benchmark`` base
12
+ class), so it is checked via its known exact structural limits
13
+ (rho=+-1) and the known ellipse semi-axes (1+rho, 1-rho) at general rho.
14
+ """
15
+
16
+ import numpy as np
17
+ import pytest
18
+ from cache_utils import cached_sample
19
+ from scipy.stats import linregress
20
+
21
+ import physicskit.rmt as rmt
22
+
23
+
24
+ def test_iid_ensemble_beta_is_none():
25
+ ens = rmt.ensembles.IIDEnsemble(n=30, entry_sampler=rmt.ensembles.rademacher, seed=0)
26
+ assert ens.beta is None
27
+
28
+
29
+ def test_iid_ensemble_real_entries_branch_produces_finite_eigenvalues():
30
+ # A fresh (uncached) direct .sample() call, to actually exercise the
31
+ # complex_entries=False code path rather than risk a disk-cache hit
32
+ # from a previous run with these same parameters.
33
+ ens = rmt.ensembles.IIDEnsemble(n=8, entry_sampler=rmt.ensembles.rademacher, complex_entries=False, seed=99)
34
+ spectrum = ens.sample(n_samples=2)
35
+ assert np.all(np.isfinite(spectrum.eigenvalues))
36
+
37
+
38
+ @pytest.mark.parametrize(
39
+ "name,entry_sampler,complex_entries",
40
+ [
41
+ ("uniform-complex", rmt.ensembles.uniform_unit_variance, True),
42
+ ("rademacher-complex", rmt.ensembles.rademacher, True),
43
+ ("exponential-real", rmt.ensembles.exponential_centered_unit_variance, False),
44
+ ],
45
+ )
46
+ def test_iid_ensemble_eigenvalues_are_complex(name, entry_sampler, complex_entries):
47
+ ens = rmt.ensembles.IIDEnsemble(
48
+ n=50,
49
+ entry_sampler=entry_sampler,
50
+ complex_entries=complex_entries,
51
+ seed=1,
52
+ )
53
+ spectrum = cached_sample(ens, n_samples=5)
54
+ assert np.iscomplexobj(spectrum.eigenvalues)
55
+ assert spectrum.eigenvalues.shape == (5, 50)
56
+
57
+
58
+ def test_iid_ensemble_reproducibility():
59
+ ens_a = rmt.ensembles.IIDEnsemble(n=40, entry_sampler=rmt.ensembles.rademacher, seed=42)
60
+ ens_b = rmt.ensembles.IIDEnsemble(n=40, entry_sampler=rmt.ensembles.rademacher, seed=42)
61
+ spec_a = ens_a.sample(n_samples=3)
62
+ spec_b = ens_b.sample(n_samples=3)
63
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
64
+
65
+
66
+ @pytest.mark.parametrize(
67
+ "name,entry_sampler,complex_entries,seed",
68
+ [
69
+ ("uniform", rmt.ensembles.uniform_unit_variance, True, 10),
70
+ ("rademacher", rmt.ensembles.rademacher, True, 11),
71
+ ("exponential", rmt.ensembles.exponential_centered_unit_variance, True, 12),
72
+ ],
73
+ )
74
+ def test_circular_law_convergence_shrinks_with_n_non_gaussian(name, entry_sampler, complex_entries, seed):
75
+ # Same benchmark, same convergence-rate philosophy, and (per the
76
+ # module docstring) the same expected shallower log-log slope as
77
+ # Ginibre's own non-Hermitian convergence -- as this is meant to be
78
+ # the identical limiting law reached from a non-Gaussian starting
79
+ # point, not a distinct, looser standard.
80
+ benchmark = rmt.validation.CircularLaw()
81
+ n_values = [40, 90, 200, 400]
82
+ results = []
83
+ for n in n_values:
84
+ ens = rmt.ensembles.IIDEnsemble(
85
+ n=n,
86
+ entry_sampler=entry_sampler,
87
+ complex_entries=complex_entries,
88
+ seed=seed,
89
+ )
90
+ spectrum = cached_sample(ens, n_samples=12)
91
+ results.append(benchmark.validate(spectrum, seed=seed))
92
+
93
+ ks_stats = np.array([r.ks_statistic for r in results])
94
+ slope = linregress(np.log(n_values), np.log(ks_stats)).slope
95
+ assert slope < -0.1, f"{name}: expected shrinking KS distance, slope={slope}"
96
+ assert ks_stats[-1] < 0.04
97
+
98
+
99
+ def test_girko_elliptic_rho_out_of_range_raises():
100
+ with pytest.raises(ValueError):
101
+ rmt.ensembles.GirkoElliptic(n=10, rho=1.5, seed=0)
102
+
103
+
104
+ def test_girko_elliptic_beta_is_none():
105
+ ens = rmt.ensembles.GirkoElliptic(n=20, rho=0.5, seed=0)
106
+ assert ens.beta is None
107
+
108
+
109
+ def test_girko_elliptic_reproducibility():
110
+ ens_a = rmt.ensembles.GirkoElliptic(n=40, rho=0.3, seed=42)
111
+ ens_b = rmt.ensembles.GirkoElliptic(n=40, rho=0.3, seed=42)
112
+ spec_a = ens_a.sample(n_samples=3)
113
+ spec_b = ens_b.sample(n_samples=3)
114
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
115
+
116
+
117
+ def test_girko_elliptic_rho_zero_matches_circular_law():
118
+ # rho=0 -> plain i.i.d. real entries -> same limiting law as
119
+ # IIDEnsemble(complex_entries=False) / GinOE (circular law).
120
+ benchmark = rmt.validation.CircularLaw()
121
+ ens = rmt.ensembles.GirkoElliptic(n=300, rho=0.0, seed=2)
122
+ spectrum = cached_sample(ens, n_samples=15)
123
+ result = benchmark.validate(spectrum, seed=2)
124
+ assert result.ks_statistic < 0.05
125
+
126
+
127
+ def test_girko_elliptic_rho_plus_one_gives_exactly_real_eigenvalues():
128
+ # rho=1 -> X exactly symmetric -> eigenvalues exactly real (Wigner).
129
+ ens = rmt.ensembles.GirkoElliptic(n=60, rho=1.0, seed=3)
130
+ spectrum = cached_sample(ens, n_samples=5)
131
+ assert np.abs(spectrum.eigenvalues.imag).max() < 1e-8
132
+
133
+
134
+ def test_girko_elliptic_rho_minus_one_gives_exactly_imaginary_eigenvalues():
135
+ # rho=-1 -> X exactly antisymmetric -> eigenvalues exactly purely
136
+ # imaginary (real antisymmetric matrices have eigenvalues 0 or
137
+ # imaginary conjugate pairs).
138
+ ens = rmt.ensembles.GirkoElliptic(n=60, rho=-1.0, seed=4)
139
+ spectrum = cached_sample(ens, n_samples=5)
140
+ assert np.abs(spectrum.eigenvalues.real).max() < 1e-8
141
+
142
+
143
+ def test_correlated_pair_matrix_has_correct_variance_and_correlation():
144
+ # Direct, un-cached check of the raw construction's second moments
145
+ # (not just the downstream eigenvalue law) against the values
146
+ # asserted in the module docstring, pooled over many independent
147
+ # matrices for statistical accuracy.
148
+ from physicskit.rmt.ensembles.girko import _correlated_pair_matrix, standard_normal
149
+
150
+ rng = np.random.default_rng(5)
151
+ n = 8
152
+ rho = 0.6
153
+ upper_vals, lower_vals = [], []
154
+ for _ in range(4000):
155
+ x = _correlated_pair_matrix(n, rho, standard_normal, rng)
156
+ upper_vals.append(x[0, 1])
157
+ lower_vals.append(x[1, 0])
158
+ upper_vals = np.array(upper_vals)
159
+ lower_vals = np.array(lower_vals)
160
+ assert upper_vals.var() == pytest.approx(1.0, abs=0.1)
161
+ assert lower_vals.var() == pytest.approx(1.0, abs=0.1)
162
+ corr = np.corrcoef(upper_vals, lower_vals)[0, 1]
163
+ assert corr == pytest.approx(rho, abs=0.05)
164
+
165
+
166
+ @pytest.mark.parametrize("rho", [-0.5, 0.0, 0.5, 0.8])
167
+ def test_girko_elliptic_semi_axes_bound_the_spectrum_at_large_n(rho):
168
+ # Eigenvalues should (asymptotically) stay within the ellipse of
169
+ # semi-axes (1+rho, 1-rho); allow a modest finite-n margin rather
170
+ # than a tight asymptotic bound.
171
+ n = 800
172
+ ens = rmt.ensembles.GirkoElliptic(n=n, rho=rho, seed=6)
173
+ spectrum = cached_sample(ens, n_samples=3)
174
+ z = spectrum.rescaled.ravel()
175
+ a, b = 1.0 + rho, max(1.0 - rho, 1e-6)
176
+ elliptic_radius = np.sqrt((z.real / a) ** 2 + (z.imag / b) ** 2)
177
+ frac_outside = np.mean(elliptic_radius > 1.15)
178
+ assert frac_outside < 0.03, f"rho={rho}: {frac_outside:.3f} of mass outside the ellipse"
@@ -0,0 +1,102 @@
1
+ """Tests for GraphLaplacianEnsemble (Erdos-Renyi combinatorial and
2
+ normalized Laplacians)."""
3
+
4
+ import numpy as np
5
+ import pytest
6
+ from cache_utils import cached_sample
7
+
8
+ import physicskit.rmt as rmt
9
+
10
+
11
+ def test_beta_is_none():
12
+ ens = rmt.ensembles.GraphLaplacianEnsemble(n=30, p=0.3, seed=0)
13
+ assert ens.beta is None
14
+
15
+
16
+ def test_invalid_p_rejected():
17
+ with pytest.raises(ValueError):
18
+ rmt.ensembles.GraphLaplacianEnsemble(n=10, p=0.0, seed=0)
19
+ with pytest.raises(ValueError):
20
+ rmt.ensembles.GraphLaplacianEnsemble(n=10, p=1.5, seed=0)
21
+
22
+
23
+ def test_normalized_laplacian_fresh_sample_produces_finite_eigenvalues():
24
+ # A fresh (uncached) direct .sample() call, to actually exercise the
25
+ # normalized=True code path rather than risk a disk-cache hit from a
26
+ # previous run with these same parameters.
27
+ ens = rmt.ensembles.GraphLaplacianEnsemble(n=15, p=0.5, normalized=True, seed=123)
28
+ spectrum = ens.sample(n_samples=2)
29
+ assert np.all(np.isfinite(spectrum.eigenvalues))
30
+
31
+
32
+ def test_reproducibility():
33
+ ens_a = rmt.ensembles.GraphLaplacianEnsemble(n=40, p=0.2, seed=42)
34
+ ens_b = rmt.ensembles.GraphLaplacianEnsemble(n=40, p=0.2, seed=42)
35
+ spec_a = ens_a.sample(n_samples=3)
36
+ spec_b = ens_b.sample(n_samples=3)
37
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
38
+
39
+
40
+ def test_combinatorial_laplacian_is_positive_semidefinite_with_zero_mode():
41
+ # L = D - A is always PSD with smallest eigenvalue exactly 0 (the
42
+ # constant vector is always in its kernel, connected or not).
43
+ ens = rmt.ensembles.GraphLaplacianEnsemble(n=50, p=0.3, seed=1)
44
+ spectrum = cached_sample(ens, n_samples=10)
45
+ assert np.all(spectrum.eigenvalues >= -1e-8)
46
+ assert np.abs(spectrum.eigenvalues.min(axis=1)).max() < 1e-8
47
+
48
+
49
+ def test_normalized_laplacian_eigenvalues_bounded_in_zero_two():
50
+ # Exact graph-independent bound (Chung 1997), checked directly.
51
+ ens = rmt.ensembles.GraphLaplacianEnsemble(n=60, p=0.4, normalized=True, seed=2)
52
+ spectrum = cached_sample(ens, n_samples=10)
53
+ assert np.all(spectrum.eigenvalues >= -1e-8)
54
+ assert np.all(spectrum.eigenvalues <= 2.0 + 1e-8)
55
+
56
+
57
+ def test_normalized_laplacian_handles_isolated_vertices():
58
+ # Very small p on a modest n makes isolated vertices likely; should
59
+ # not raise (division-by-zero for degree-0 rows is handled by
60
+ # convention -- see module docstring) and eigenvalues must stay
61
+ # finite and within [0, 2].
62
+ ens = rmt.ensembles.GraphLaplacianEnsemble(n=30, p=0.02, normalized=True, seed=3)
63
+ spectrum = cached_sample(ens, n_samples=10)
64
+ assert np.all(np.isfinite(spectrum.eigenvalues))
65
+ assert np.all(spectrum.eigenvalues >= -1e-8)
66
+ assert np.all(spectrum.eigenvalues <= 2.0 + 1e-8)
67
+
68
+
69
+ def test_combinatorial_laplacian_zero_multiplicity_matches_component_count():
70
+ # Well-known spectral graph theory fact: the multiplicity of the
71
+ # zero eigenvalue equals the number of connected components. Force
72
+ # a disconnected graph (very small p, small n) and check directly
73
+ # via a graph-free connectivity computation (BFS/union-find on the
74
+ # same adjacency matrix used to build L).
75
+ from physicskit.rmt.ensembles.graph_laplacian import _erdos_renyi_adjacency
76
+
77
+ rng = np.random.default_rng(4)
78
+ n, p = 25, 0.03
79
+ a = _erdos_renyi_adjacency(n, p, rng)
80
+ degree = a.sum(axis=1)
81
+ laplacian = np.diag(degree) - a
82
+ eigs = np.linalg.eigvalsh(laplacian)
83
+ zero_multiplicity = np.sum(np.abs(eigs) < 1e-8)
84
+
85
+ # union-find to count connected components independently
86
+ parent = list(range(n))
87
+
88
+ def find(x):
89
+ while parent[x] != x:
90
+ parent[x] = parent[parent[x]]
91
+ x = parent[x]
92
+ return x
93
+
94
+ for i in range(n):
95
+ for j in range(i + 1, n):
96
+ if a[i, j] > 0:
97
+ ri, rj = find(i), find(j)
98
+ if ri != rj:
99
+ parent[ri] = rj
100
+ n_components = len({find(i) for i in range(n)})
101
+
102
+ assert zero_multiplicity == n_components
@@ -0,0 +1,118 @@
1
+ """Tests for the Haar-random matrix generators (physicskit.rmt.utils.haar) and
2
+ the HaarOrthogonalEnsemble.
3
+
4
+ ``haar_unitary`` is already indirectly validated via CUE's passing
5
+ spacing statistics (see tests/test_circular.py); ``haar_orthogonal`` and
6
+ ``haar_symplectic`` are checked directly here since they are new.
7
+ """
8
+
9
+ import numpy as np
10
+ import pytest
11
+ from cache_utils import cached_sample
12
+
13
+ import physicskit.rmt as rmt
14
+ from physicskit.rmt.utils.haar import haar_orthogonal, haar_symplectic, haar_unitary, symplectic_form
15
+
16
+
17
+ def test_haar_orthogonal_is_orthogonal():
18
+ rng = np.random.default_rng(0)
19
+ o = haar_orthogonal(20, rng)
20
+ assert np.abs(o.T @ o - np.eye(20)).max() < 1e-10
21
+
22
+
23
+ @pytest.mark.slow
24
+ def test_haar_orthogonal_first_column_is_uniform_on_sphere():
25
+ # Hallmark of Haar invariance: any fixed unit vector rotated by a
26
+ # Haar-random O(n) is uniform on the sphere -- mean 0, second
27
+ # moment 1/n per component.
28
+ rng = np.random.default_rng(1)
29
+ n = 5
30
+ first_columns = np.array([haar_orthogonal(n, rng)[:, 0] for _ in range(20_000)])
31
+ np.testing.assert_allclose(first_columns.mean(axis=0), 0.0, atol=0.03)
32
+ np.testing.assert_allclose((first_columns**2).mean(axis=0), 1.0 / n, atol=0.03)
33
+
34
+
35
+ def test_haar_orthogonal_determinant_is_balanced():
36
+ rng = np.random.default_rng(2)
37
+ dets = [np.linalg.det(haar_orthogonal(6, rng)) for _ in range(4000)]
38
+ dets = np.array(dets)
39
+ np.testing.assert_allclose(np.abs(dets), 1.0, atol=1e-8)
40
+ frac_positive = np.mean(dets > 0)
41
+ assert frac_positive == pytest.approx(0.5, abs=0.05)
42
+
43
+
44
+ def test_haar_symplectic_is_unitary_and_self_dual():
45
+ rng = np.random.default_rng(3)
46
+ n_quaternionic = 6
47
+ u = haar_symplectic(n_quaternionic, rng)
48
+ n = 2 * n_quaternionic
49
+ assert u.shape == (n, n)
50
+ assert np.abs(u.conj().T @ u - np.eye(n)).max() < 1e-10
51
+
52
+ z = symplectic_form(n)
53
+ u_dual = (-z) @ u.T @ z
54
+ assert np.abs(u_dual - u).max() < 1e-10
55
+
56
+
57
+ def test_haar_symplectic_eigenvalues_are_doubly_degenerate():
58
+ rng = np.random.default_rng(4)
59
+ u = haar_symplectic(5, rng)
60
+ eigs = np.linalg.eigvals(u)
61
+ order = np.lexsort((eigs.imag, eigs.real))
62
+ sorted_eigs = eigs[order]
63
+ pair_diffs = np.abs(sorted_eigs[0::2] - sorted_eigs[1::2])
64
+ assert pair_diffs.max() < 1e-8
65
+
66
+
67
+ def test_haar_unitary_still_exactly_unitary():
68
+ # Regression check: existing helper unaffected by the new additions.
69
+ rng = np.random.default_rng(5)
70
+ u = haar_unitary(10, rng)
71
+ assert np.abs(u.conj().T @ u - np.eye(10)).max() < 1e-10
72
+
73
+
74
+ # --- HaarOrthogonalEnsemble ---
75
+
76
+
77
+ def test_haar_orthogonal_ensemble_beta_is_none():
78
+ ens = rmt.ensembles.HaarOrthogonalEnsemble(n=20, seed=0)
79
+ assert ens.beta is None
80
+
81
+
82
+ def test_haar_orthogonal_ensemble_eigenvalues_on_unit_circle():
83
+ ens = rmt.ensembles.HaarOrthogonalEnsemble(n=30, seed=1)
84
+ spectrum = cached_sample(ens, n_samples=10)
85
+ radii = np.abs(spectrum.eigenvalues)
86
+ np.testing.assert_allclose(radii, 1.0, atol=1e-8)
87
+
88
+
89
+ def test_haar_orthogonal_ensemble_reproducibility():
90
+ ens_a = rmt.ensembles.HaarOrthogonalEnsemble(n=20, seed=42)
91
+ ens_b = rmt.ensembles.HaarOrthogonalEnsemble(n=20, seed=42)
92
+ spec_a = ens_a.sample(n_samples=3)
93
+ spec_b = ens_b.sample(n_samples=3)
94
+ np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
95
+
96
+
97
+ def test_haar_orthogonal_ensemble_has_real_eigenvalues_unlike_coe():
98
+ # The core structural distinction from COE (module docstring):
99
+ # a raw Haar-O(n) matrix genuinely has real (+-1) eigenvalues with
100
+ # non-negligible probability; COE/CUE generically do not.
101
+ ens = rmt.ensembles.HaarOrthogonalEnsemble(n=40, seed=2)
102
+ spectrum = cached_sample(ens, n_samples=200)
103
+ real_fraction_of_samples = np.mean(np.any(np.abs(spectrum.eigenvalues.imag) < 1e-8, axis=1))
104
+ assert real_fraction_of_samples > 0.5
105
+
106
+ # COE stores eigenvalue PHASES (theta in [0, 2*pi)), not e^{i*theta}
107
+ # directly -- convert back to compare on the same "is this real"
108
+ # footing as HaarOrthogonalEnsemble's complex output above.
109
+ coe = rmt.ensembles.COE(n=40, seed=3)
110
+ coe_spectrum = cached_sample(coe, n_samples=200)
111
+ coe_eigs = np.exp(1j * coe_spectrum.eigenvalues)
112
+ coe_real_fraction = np.mean(np.abs(coe_eigs.imag) < 1e-8)
113
+ assert coe_real_fraction < 0.01
114
+
115
+
116
+ def test_symplectic_form_rejects_odd_dimension():
117
+ with pytest.raises(ValueError):
118
+ symplectic_form(3)