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,334 @@
1
+ """Coupled-oscillator and soliton lattice chains.
2
+
3
+ All three chains are 1D lattices of N particles with fixed
4
+ (wall-anchored) boundary conditions, i.e. q_0 = q_{N+1} = 0. Each is a
5
+ separable Hamiltonian system H = sum_i p_i^2/(2m) + V(q), with the
6
+ force computed by a hand-written ``@njit`` loop over lattice sites (the
7
+ "lattice loops" the project brief calls out for mandatory Numba
8
+ acceleration) rather than through the symbolic engine, since the
9
+ physics is fixed and the loop structure is identical for every N.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from functools import lru_cache
15
+
16
+ import numpy as np
17
+ from numba import njit
18
+
19
+ from physicskit.classical.core.base_system import HamiltonianSystem
20
+
21
+ __all__ = ["HarmonicChain", "FPUTChain", "SineGordonChain"]
22
+
23
+
24
+ # ---------------------------------------------------------------------------
25
+ # Harmonic chain: V = sum_i k/2 (q_{i+1} - q_i)^2, fixed ends
26
+ # ---------------------------------------------------------------------------
27
+
28
+
29
+ @njit(cache=True)
30
+ def _harmonic_force(q, k):
31
+ n = q.shape[0]
32
+ out = np.empty(n)
33
+ for i in range(n):
34
+ left = q[i - 1] if i > 0 else 0.0
35
+ right = q[i + 1] if i < n - 1 else 0.0
36
+ out[i] = k * (left - 2.0 * q[i] + right)
37
+ return out
38
+
39
+
40
+ @lru_cache(maxsize=64)
41
+ def _make_harmonic_force(k: float):
42
+ @njit(cache=True)
43
+ def force(q, t):
44
+ return _harmonic_force(q, k)
45
+
46
+ return force
47
+
48
+
49
+ class _ChainBase(HamiltonianSystem):
50
+ separable = True
51
+
52
+ def __init__(self, q0, p0, m=1.0, k=1.0):
53
+ self.m, self.k = m, k
54
+ self.mass_inv = 1.0 / m
55
+ super().__init__(q0, p0)
56
+
57
+ def kinetic_energy(self, p: np.ndarray) -> float:
58
+ return 0.5 * float(np.sum(p * p)) / self.m
59
+
60
+ def normal_mode_frequencies(self) -> np.ndarray:
61
+ """Angular frequencies omega_k of the N linear normal modes of a fixed-fixed harmonic chain.
62
+
63
+ Returns
64
+ -------
65
+ ndarray, shape (n,)
66
+ omega_k for k = 1..N.
67
+ """
68
+ n = self.ndof
69
+ modes = np.arange(1, n + 1)
70
+ return 2.0 * np.sqrt(self.k / self.m) * np.sin(modes * np.pi / (2.0 * (n + 1)))
71
+
72
+ def normal_mode_coords(self, q: np.ndarray = None, p: np.ndarray = None):
73
+ """Transform (q, p) to normal-mode coordinates (Q_k, P_k).
74
+
75
+ Via the discrete sine transform appropriate for fixed-fixed
76
+ boundaries: ``Q_k = sqrt(2/(N+1)) * sum_i q_i sin(i*k*pi/(N+1))``,
77
+ ``i, k = 1..N``.
78
+
79
+ Parameters
80
+ ----------
81
+ q, p : ndarray, optional
82
+ State to transform; defaults to the current state.
83
+
84
+ Returns
85
+ -------
86
+ Q, P : ndarray, shape (n,)
87
+ """
88
+ q = self.q if q is None else q
89
+ p = self.p if p is None else p
90
+ n = self.ndof
91
+ i = np.arange(1, n + 1)
92
+ k = np.arange(1, n + 1)
93
+ S = np.sqrt(2.0 / (n + 1)) * np.sin(np.outer(i, k) * np.pi / (n + 1))
94
+ Q = S.T @ q
95
+ P = S.T @ p
96
+ return Q, P
97
+
98
+ def modal_energies(self, q: np.ndarray = None, p: np.ndarray = None) -> np.ndarray:
99
+ """Energy stored in each normal mode.
100
+
101
+ ``E_k = 0.5*(P_k^2/m + m*omega_k^2*Q_k^2)`` -- the diagnostic
102
+ used to show non-ergodic recurrence (the FPUT paradox) in
103
+ :class:`FPUTChain`.
104
+
105
+ Parameters
106
+ ----------
107
+ q, p : ndarray, optional
108
+ State to evaluate; defaults to the current state.
109
+
110
+ Returns
111
+ -------
112
+ ndarray, shape (n,)
113
+ E_k for k = 1..N.
114
+ """
115
+ Q, P = self.normal_mode_coords(q, p)
116
+ omega = self.normal_mode_frequencies()
117
+ return 0.5 * (P**2) / self.m + 0.5 * self.m * (omega**2) * (Q**2)
118
+
119
+
120
+ class HarmonicChain(_ChainBase):
121
+ """Linear chain of N masses coupled by identical springs k, both
122
+ ends fixed to walls -- the exactly-solvable reference lattice whose
123
+ normal modes are the sine waves used to analyze the non-linear
124
+ :class:`FPUTChain` below.
125
+
126
+ Parameters
127
+ ----------
128
+ q0, p0 : array-like, shape (n,)
129
+ Initial displacements and momenta.
130
+ m, k : float
131
+ Mass and spring constant (uniform across the chain).
132
+ """
133
+
134
+ def __init__(self, q0, p0, m=1.0, k=1.0):
135
+ super().__init__(q0, p0, m=m, k=k)
136
+ self._force_njit = _make_harmonic_force(k)
137
+
138
+ def potential_energy(self, q: np.ndarray) -> float:
139
+ n = q.shape[0]
140
+ ext = np.empty(n + 2)
141
+ ext[0] = 0.0
142
+ ext[-1] = 0.0
143
+ ext[1:-1] = q
144
+ return 0.5 * self.k * float(np.sum(np.diff(ext) ** 2))
145
+
146
+
147
+ # ---------------------------------------------------------------------------
148
+ # FPUT beta-lattice: V(r) = k/2 r^2 + beta/4 r^4
149
+ # ---------------------------------------------------------------------------
150
+
151
+
152
+ @njit(cache=True)
153
+ def _fput_force(q, k, beta):
154
+ n = q.shape[0]
155
+ out = np.empty(n)
156
+ for i in range(n):
157
+ left = q[i - 1] if i > 0 else 0.0
158
+ right = q[i + 1] if i < n - 1 else 0.0
159
+ r_right = right - q[i]
160
+ r_left = q[i] - left
161
+ f_right = k * r_right + beta * r_right**3
162
+ f_left = k * r_left + beta * r_left**3
163
+ out[i] = f_right - f_left
164
+ return out
165
+
166
+
167
+ @lru_cache(maxsize=64)
168
+ def _make_fput_force(k: float, beta: float):
169
+ @njit(cache=True)
170
+ def force(q, t):
171
+ return _fput_force(q, k, beta)
172
+
173
+ return force
174
+
175
+
176
+ class FPUTChain(_ChainBase):
177
+ """Fermi-Pasta-Ulam-Tsingou beta-lattice: N masses (default 32) with
178
+ the quartic non-linear coupling potential
179
+ V(r) = (k/2) r^2 + (beta/4) r^4, r = q_{i+1} - q_i.
180
+
181
+ Famous for its "paradox": exciting a single low-order normal mode
182
+ does *not* thermalize energy across all modes as naive ergodic
183
+ reasoning predicts; instead the energy in :meth:`modal_energies`
184
+ returns almost exactly to the initially excited mode after a
185
+ characteristic recurrence time (the FPUT recurrence).
186
+
187
+ Parameters
188
+ ----------
189
+ n : int
190
+ Number of masses.
191
+ m, k : float
192
+ Mass and (linear) spring constant.
193
+ beta : float
194
+ Quartic non-linearity coefficient.
195
+ mode : int
196
+ Which normal mode to excite initially.
197
+ amplitude : float
198
+ Initial amplitude of that mode.
199
+ """
200
+
201
+ def __init__(self, n=32, m=1.0, k=1.0, beta=0.0, mode=1, amplitude=1.0):
202
+ self.beta = beta
203
+ q0 = amplitude * np.sin(np.arange(1, n + 1) * mode * np.pi / (n + 1))
204
+ p0 = np.zeros(n)
205
+ super().__init__(q0, p0, m=m, k=k)
206
+ self._force_njit = _make_fput_force(k, beta)
207
+
208
+ def potential_energy(self, q: np.ndarray) -> float:
209
+ n = q.shape[0]
210
+ ext = np.empty(n + 2)
211
+ ext[0] = 0.0
212
+ ext[-1] = 0.0
213
+ ext[1:-1] = q
214
+ r = np.diff(ext)
215
+ return float(np.sum(0.5 * self.k * r**2 + 0.25 * self.beta * r**4))
216
+
217
+
218
+ # ---------------------------------------------------------------------------
219
+ # Discrete Sine-Gordon chain (Frenkel-Kontorova): kink/antikink solitons
220
+ # ---------------------------------------------------------------------------
221
+
222
+
223
+ @njit(cache=True)
224
+ def _sine_gordon_force(q, k):
225
+ n = q.shape[0]
226
+ out = np.empty(n)
227
+ for i in range(n):
228
+ left = q[i - 1] if i > 0 else q[i]
229
+ right = q[i + 1] if i < n - 1 else q[i]
230
+ out[i] = k * (left - 2.0 * q[i] + right) - np.sin(q[i])
231
+ return out
232
+
233
+
234
+ @lru_cache(maxsize=64)
235
+ def _make_sine_gordon_force(k: float):
236
+ @njit(cache=True)
237
+ def force(q, t):
238
+ return _sine_gordon_force(q, k)
239
+
240
+ return force
241
+
242
+
243
+ class SineGordonChain(_ChainBase):
244
+ """Discretized pendulum chain (Frenkel-Kontorova model): N coupled
245
+ pendulums, V = sum_i [k/2 (q_{i+1}-q_i)^2 + (1 - cos q_i)], with
246
+ free (Neumann-like) boundary conditions so a topological kink can
247
+ propagate off either end without an artificial restoring wall.
248
+
249
+ Continuum-limit kink initial data from :meth:`kink`
250
+ (q_i = 4*atan(exp((i - i0)/width)), width=1 for the default m=1,
251
+ k=1) launches a soliton that propagates and scatters off other
252
+ kinks/antikinks -- the discrete analogue of the exact sine-Gordon
253
+ soliton solutions.
254
+
255
+ Parameters
256
+ ----------
257
+ n : int
258
+ Number of pendulums.
259
+ m, k : float
260
+ Mass and coupling constant.
261
+ q0, p0 : array-like, shape (n,), optional
262
+ Initial displacements and momenta; defaults to all zeros. Use
263
+ :meth:`kink` to build soliton initial data instead.
264
+ """
265
+
266
+ def __init__(self, n=200, m=1.0, k=1.0, q0=None, p0=None):
267
+ if q0 is None:
268
+ q0 = np.zeros(n)
269
+ if p0 is None:
270
+ p0 = np.zeros(n)
271
+ super().__init__(q0, p0, m=m, k=k)
272
+ self._force_njit = _make_sine_gordon_force(k)
273
+
274
+ def potential_energy(self, q: np.ndarray) -> float:
275
+ r = np.diff(q) # free boundaries: no coupling to phantom exterior sites
276
+ return float(0.5 * self.k * np.sum(r**2) + np.sum(1.0 - np.cos(q)))
277
+
278
+ @staticmethod
279
+ def kink(n: int, center: float, width: float = 1.0, velocity: float = 0.0, polarity: int = 1) -> tuple:
280
+ """Continuum-limit single-kink (or antikink, polarity=-1) initial
281
+ condition, optionally boosted to move at ``velocity`` (sites per
282
+ unit time; to the right for positive ``velocity``, assuming the
283
+ default ``m=1, k=1`` -- i.e. a natural wave/"light" speed
284
+ ``c = sqrt(k/m) = 1``, the sine-Gordon equation's exact
285
+ Lorentz-invariance scale, so ``|velocity| < 1`` is required).
286
+
287
+ ``width`` is *not* a free shape knob: q_xx = sin(q) (the
288
+ continuum equation this chain approximates, with the default
289
+ m=1, k=1) is solved by q=4*atan(exp(x)) only for exactly unit
290
+ width. Passing a different ``width`` here without also scaling
291
+ ``k``/``m`` to match (``width = sqrt(k/m)``) gives a profile
292
+ that visibly relaxes/radiates under the true dynamics instead
293
+ of propagating as a clean, stable soliton.
294
+
295
+ The exact traveling-wave solution of the continuum sine-Gordon
296
+ equation is the *Lorentz-contracted* profile
297
+ ``q(x, t) = polarity * 4*atan(exp(gamma * (x - center - v*t) / width))``,
298
+ ``gamma = 1 / sqrt(1 - v**2)`` -- both the spatial profile at t=0
299
+ and its time derivative must be boosted together (not just the
300
+ momentum on top of the unboosted static profile, which is only
301
+ a small-v approximation and becomes badly wrong as ``|v|`` approaches 1).
302
+
303
+ Parameters
304
+ ----------
305
+ n : int
306
+ Chain length.
307
+ center : float
308
+ Lattice site the kink is centered on at t=0.
309
+ width : float
310
+ Soliton width; must equal ``sqrt(k/m)`` (1, for the default
311
+ m=1, k=1) for a genuine soliton solution.
312
+ velocity : float
313
+ Boost velocity, ``|velocity| < 1``.
314
+ polarity : {1, -1}
315
+ +1 for a kink, -1 for an antikink.
316
+
317
+ Returns
318
+ -------
319
+ q0, p0 : ndarray, shape (n,)
320
+ Initial displacements and momenta.
321
+
322
+ Raises
323
+ ------
324
+ ValueError
325
+ If ``abs(velocity) >= 1``.
326
+ """
327
+ if abs(velocity) >= 1.0:
328
+ raise ValueError("|velocity| must be < 1 (the natural wave speed sqrt(k/m), default 1) for a valid moving kink")
329
+ gamma = 1.0 / np.sqrt(1.0 - velocity**2)
330
+ i = np.arange(n)
331
+ u = gamma * (i - center) / width
332
+ q0 = polarity * 4.0 * np.arctan(np.exp(u))
333
+ p0 = -polarity * velocity * (2.0 * gamma / width) / np.cosh(u)
334
+ return q0, p0
@@ -0,0 +1,215 @@
1
+ """Hamiltonian phase-space systems illustrating Liouville mechanics.
2
+
3
+ :class:`HenonHeilesSystem` is the classic 2-DOF non-linear oscillator
4
+ whose Poincare sections show the breakdown of KAM tori as energy
5
+ increases. :class:`PendulumSwarm` evolves an ensemble of independent
6
+ phase points under identical 1-DOF pendulum dynamics to visually
7
+ demonstrate Liouville's theorem (phase-space volume/area is conserved
8
+ by Hamiltonian flow, even though the occupied region shears and
9
+ filaments). :func:`pendulum_action_angle` computes the action J(E) and
10
+ period of a simple pendulum via elliptic integrals, the archetypal
11
+ worked example of action-angle variables.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from functools import lru_cache
17
+
18
+ import numpy as np
19
+ from numba import njit
20
+ from scipy import special
21
+
22
+ from physicskit.classical.core.base_system import HamiltonianSystem
23
+
24
+ __all__ = ["HenonHeilesSystem", "PendulumSwarm", "pendulum_action_angle"]
25
+
26
+
27
+ @njit(cache=True)
28
+ def _henon_heiles_force(q, t):
29
+ x, y = q[0], q[1]
30
+ out = np.empty(2)
31
+ out[0] = -(x + 2.0 * x * y)
32
+ out[1] = -(y + x * x - y * y)
33
+ return out
34
+
35
+
36
+ class HenonHeilesSystem(HamiltonianSystem):
37
+ """H = (px^2 + py^2)/2 + (x^2 + y^2)/2 + x^2*y - y^3/3.
38
+
39
+ A non-integrable 2-DOF oscillator (originally a model of stellar
40
+ orbits in an axisymmetric galactic potential). Below E ~ 1/6 the
41
+ Poincare section (x, px) at y=0 is dominated by smooth KAM tori;
42
+ as E approaches and exceeds 1/6 the tori progressively break up
43
+ into chaotic seas.
44
+
45
+ Parameters
46
+ ----------
47
+ q0, p0 : array-like, shape (2,)
48
+ Initial position ``(x, y)`` and momentum ``(px, py)``.
49
+ """
50
+
51
+ separable = True
52
+ mass_inv = 1.0
53
+
54
+ def __init__(self, q0, p0):
55
+ self._force_njit = _henon_heiles_force
56
+ super().__init__(q0, p0)
57
+
58
+ def kinetic_energy(self, p: np.ndarray) -> float:
59
+ return 0.5 * float(np.dot(p, p))
60
+
61
+ def potential_energy(self, q: np.ndarray) -> float:
62
+ x, y = q[0], q[1]
63
+ return 0.5 * (x * x + y * y) + x * x * y - (y**3) / 3.0
64
+
65
+
66
+ @lru_cache(maxsize=64)
67
+ def _make_pendulum_force(g_over_l: float):
68
+ @njit(cache=True)
69
+ def force(q, t):
70
+ return -g_over_l * np.sin(q)
71
+
72
+ return force
73
+
74
+
75
+ class PendulumSwarm(HamiltonianSystem):
76
+ """An ensemble of N independent, identical simple pendulums,
77
+ H_i = p_i^2 / 2 - (g/l) cos(q_i) for each i = 1..N.
78
+
79
+ The pendulums are mutually decoupled (each is its own 1-DOF
80
+ Hamiltonian system) but are integrated together as one N-DOF
81
+ separable Hamiltonian so a whole ensemble can be evolved with a
82
+ single Yoshida4/Verlet call. Seeding N points inside a small
83
+ (dq, dp) box and watching the occupied phase-space patch shear
84
+ over time -- its area invariant even as its shape stretches into a
85
+ filament -- is the standard visual proof of Liouville's theorem.
86
+
87
+ Parameters
88
+ ----------
89
+ q0, p0 : array-like, shape (n,)
90
+ Initial angles and momenta, one pair per pendulum in the ensemble.
91
+ g_over_l : float
92
+ Ratio g/l shared by every pendulum.
93
+ """
94
+
95
+ separable = True
96
+
97
+ def __init__(self, q0, p0, g_over_l: float = 1.0):
98
+ self.g_over_l = g_over_l
99
+ self.mass_inv = 1.0
100
+ self._force_njit = _make_pendulum_force(g_over_l)
101
+ super().__init__(q0, p0)
102
+
103
+ @classmethod
104
+ def from_box(cls, q_center: float, p_center: float, dq: float, dp: float, n: int = 1000, g_over_l: float = 1.0, seed: int = 0):
105
+ """Seed N phase points uniformly inside a (dq x dp) box centered at (q_center, p_center).
106
+
107
+ Parameters
108
+ ----------
109
+ q_center, p_center : float
110
+ Box center.
111
+ dq, dp : float
112
+ Box width and height.
113
+ n : int
114
+ Number of phase points to seed.
115
+ g_over_l : float
116
+ Ratio g/l shared by every pendulum.
117
+ seed : int
118
+ Random seed for reproducibility.
119
+
120
+ Returns
121
+ -------
122
+ PendulumSwarm
123
+ """
124
+ rng = np.random.default_rng(seed)
125
+ q0 = q_center + (rng.random(n) - 0.5) * dq
126
+ p0 = p_center + (rng.random(n) - 0.5) * dp
127
+ return cls(q0, p0, g_over_l=g_over_l)
128
+
129
+ def kinetic_energy(self, p: np.ndarray) -> float:
130
+ return 0.5 * float(np.sum(p * p))
131
+
132
+ def potential_energy(self, q: np.ndarray) -> float:
133
+ return -self.g_over_l * float(np.sum(np.cos(q)))
134
+
135
+ def per_particle_energy(self, q: np.ndarray = None, p: np.ndarray = None) -> np.ndarray:
136
+ """Energy of each individual pendulum, used to color/track the swarm.
137
+
138
+ Conserved per-particle since the ensemble is decoupled.
139
+
140
+ Parameters
141
+ ----------
142
+ q, p : ndarray, optional
143
+ State to evaluate; defaults to the current state.
144
+
145
+ Returns
146
+ -------
147
+ ndarray, shape (n,)
148
+ """
149
+ q = self.q if q is None else q
150
+ p = self.p if p is None else p
151
+ return 0.5 * p * p - self.g_over_l * np.cos(q)
152
+
153
+ def phase_space_area(self, q: np.ndarray = None, p: np.ndarray = None) -> float:
154
+ """Convex-hull area of the current swarm's (q, p) point cloud.
155
+
156
+ A practical proxy for the occupied phase-space volume.
157
+
158
+ Parameters
159
+ ----------
160
+ q, p : ndarray, optional
161
+ State to evaluate; defaults to the current state.
162
+
163
+ Returns
164
+ -------
165
+ float
166
+ """
167
+ from scipy.spatial import ConvexHull
168
+
169
+ q = self.q if q is None else q
170
+ p = self.p if p is None else p
171
+ pts = np.column_stack([q, p])
172
+ return float(ConvexHull(pts).volume) # 'volume' is the 2D area for a 2D hull
173
+
174
+
175
+ def pendulum_action_angle(E: float, g_over_l: float = 1.0):
176
+ """Action J(E) and period T(E) for a simple pendulum H = p^2/2 - (g/l)cos(q).
177
+
178
+ For librating orbits (-g/l < E < g/l), uses the standard elliptic-integral
179
+ closed form: with k^2 = (E + g/l) / (2 g/l),
180
+
181
+ J(E) = (8/pi) * sqrt(g/l) * [E_ellip(k) - (1 - k^2) * K_ellip(k)]
182
+ T(E) = 4 * K_ellip(k) / sqrt(g/l)
183
+
184
+ where K, E are the complete elliptic integrals of the first/second
185
+ kind (SciPy's ``special.ellipk``/``ellipe`` take the parameter
186
+ m = k^2, not the modulus k).
187
+
188
+ Parameters
189
+ ----------
190
+ E : float
191
+ Energy, with ``-g_over_l < E < g_over_l`` (librating orbits only).
192
+ g_over_l : float
193
+ Ratio g/l.
194
+
195
+ Returns
196
+ -------
197
+ J : float
198
+ Action.
199
+ T : float
200
+ Orbital period.
201
+
202
+ Raises
203
+ ------
204
+ ValueError
205
+ If ``E`` is outside the librating range.
206
+ """
207
+ w0 = np.sqrt(g_over_l)
208
+ if -g_over_l >= E or g_over_l <= E:
209
+ raise ValueError("pendulum_action_angle only supports librating orbits (-g/l < E < g/l)")
210
+ m = (E + g_over_l) / (2.0 * g_over_l) # = k^2
211
+ K = special.ellipk(m)
212
+ Eei = special.ellipe(m)
213
+ J = (8.0 / np.pi) * w0 * (Eei - (1.0 - m) * K)
214
+ T = 4.0 * K / w0
215
+ return float(J), float(T)