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,303 @@
1
+ """Analysis of raw time series with unknown governing equations.
2
+
3
+ Every other Lyapunov/dimension tool in physicskit.chaos works on a
4
+ :class:`~physicskit.chaos.core.base_system.DynamicalSystem` (or a point cloud
5
+ generated from one). This module instead works on a bare, scalar (or
6
+ already-embedded) time series with no known equations of motion -- the
7
+ situation for real experimental or observational data -- via delay-coordinate
8
+ (Takens) embedding, the Rosenstein algorithm for the largest Lyapunov
9
+ exponent, and IAAFT surrogate-data significance testing.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ from collections.abc import Callable
15
+
16
+ import numpy as np
17
+ from numpy.typing import ArrayLike, NDArray
18
+ from scipy.spatial import cKDTree
19
+ from tqdm.auto import tqdm
20
+
21
+
22
+ def delay_embed(series: ArrayLike, dim: int, tau: int = 1) -> NDArray[np.float64]:
23
+ """Reconstruct a delay-coordinate (Takens) embedding of a scalar time series.
24
+
25
+ Parameters
26
+ ----------
27
+ series : array_like of float, shape (n,)
28
+ Scalar time series.
29
+ dim : int
30
+ Embedding dimension.
31
+ tau : int, default 1
32
+ Delay, in samples, between successive coordinates.
33
+
34
+ Returns
35
+ -------
36
+ ndarray of float, shape (n - (dim - 1) * tau, dim)
37
+ Row ``i`` is ``(series[i], series[i + tau], ..., series[i + (dim-1)*tau])``.
38
+
39
+ Notes
40
+ -----
41
+ Choosing `dim` and `tau` well matters a great deal and is its own
42
+ sub-field; this function does not attempt to choose them for you. Common
43
+ heuristics are the first minimum of the time-delayed mutual information
44
+ for `tau`, and the false-nearest-neighbors method for `dim`.
45
+ """
46
+ series = np.asarray(series, dtype=np.float64)
47
+ n = series.shape[0] - (dim - 1) * tau
48
+ if n <= 0:
49
+ raise ValueError("series is too short for the requested dim and tau")
50
+ return np.column_stack([series[i * tau : i * tau + n] for i in range(dim)])
51
+
52
+
53
+ def _nearest_neighbors_excluding_theiler_window(embedded: NDArray[np.float64], min_tsep: int) -> NDArray[np.int64]:
54
+ """For each embedded point, find its nearest neighbor at least `min_tsep`
55
+ samples away in time, via a KD-tree (``O(n log n)``) rather than a full
56
+ ``O(n^2)`` pairwise distance matrix -- the latter is both slow and, for
57
+ anything beyond a few thousand points, memory-prohibitive (e.g. ~2.9 GB
58
+ of ``float64`` distances for a 19,000-point series).
59
+ """
60
+ n = embedded.shape[0]
61
+ tree = cKDTree(embedded)
62
+ k = min(n, max(4, 2 * min_tsep + 4))
63
+ _, candidate_idx = tree.query(embedded, k=k)
64
+ candidate_idx = np.atleast_2d(candidate_idx)
65
+
66
+ row_idx = np.arange(n)[:, None]
67
+ valid = np.abs(candidate_idx - row_idx) > min_tsep
68
+ has_valid = valid.any(axis=1)
69
+ first_valid_col = np.argmax(valid, axis=1)
70
+ nearest = candidate_idx[np.arange(n), first_valid_col]
71
+
72
+ # Rare fallback for points whose k nearest neighbors are all within the
73
+ # Theiler window: search directly, excluding just that window.
74
+ for i in np.flatnonzero(~has_valid):
75
+ dists_i = np.linalg.norm(embedded - embedded[i], axis=1)
76
+ dists_i[max(0, i - min_tsep) : i + min_tsep + 1] = np.inf
77
+ nearest[i] = np.argmin(dists_i)
78
+
79
+ return np.asarray(nearest, dtype=np.int64)
80
+
81
+
82
+ def average_log_divergence(
83
+ series: ArrayLike,
84
+ dim: int,
85
+ tau: int = 1,
86
+ min_tsep: int | None = None,
87
+ max_iter: int | None = None,
88
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
89
+ """Rosenstein's average log nearest-neighbor divergence curve.
90
+
91
+ Embeds `series` (via :func:`delay_embed`), finds each embedded point's
92
+ nearest neighbor (excluding temporally close points, within `min_tsep`,
93
+ to avoid spuriously "finding" a neighbor that is really just the same
94
+ part of the trajectory shifted slightly in time), and tracks how the
95
+ distance to that neighbor grows as both points evolve forward -- the
96
+ time-series analog of :func:`physicskit.chaos.visualizers.divergence.trajectory_divergence`,
97
+ reconstructed from a single observed series with unknown dynamics.
98
+
99
+ Parameters
100
+ ----------
101
+ series : array_like of float, shape (n,)
102
+ Scalar time series.
103
+ dim : int
104
+ Embedding dimension.
105
+ tau : int, default 1
106
+ Embedding delay, in samples.
107
+ min_tsep : int, optional
108
+ Minimum temporal separation (in samples) for a candidate nearest
109
+ neighbor; defaults to `tau`.
110
+ max_iter : int, optional
111
+ Number of forward-evolution steps to track; defaults to
112
+ ``n_embedded // 10``.
113
+
114
+ Returns
115
+ -------
116
+ steps : ndarray of int, shape (m,)
117
+ Forward-evolution step counts (in samples) with at least one valid
118
+ pair to average over.
119
+ log_divergence : ndarray of float, shape (m,)
120
+ The mean, over all valid pairs, of ``log(||embedded[i+k] -
121
+ embedded[nearest[i]+k]||)`` at each step `k`. Its slope (in the
122
+ linear-looking early region) estimates the largest Lyapunov
123
+ exponent, in units of "per sample"; see :func:`rosenstein_lyapunov`.
124
+ """
125
+ embedded = delay_embed(series, dim, tau)
126
+ n = embedded.shape[0]
127
+ if min_tsep is None:
128
+ min_tsep = tau
129
+ if max_iter is None:
130
+ max_iter = max(1, n // 10)
131
+
132
+ nearest = _nearest_neighbors_excluding_theiler_window(embedded, min_tsep)
133
+ idx = np.arange(n)
134
+
135
+ steps = []
136
+ log_divergence = []
137
+ for k in range(max_iter):
138
+ valid = (idx + k < n) & (nearest + k < n)
139
+ if not np.any(valid):
140
+ break
141
+ d = np.linalg.norm(embedded[idx[valid] + k] - embedded[nearest[valid] + k], axis=1)
142
+ d = d[d > 0]
143
+ if d.size == 0:
144
+ continue
145
+ steps.append(k)
146
+ log_divergence.append(np.mean(np.log(d)))
147
+
148
+ return np.array(steps), np.array(log_divergence)
149
+
150
+
151
+ def rosenstein_lyapunov(
152
+ series: ArrayLike,
153
+ dim: int,
154
+ tau: int = 1,
155
+ dt: float = 1.0,
156
+ min_tsep: int | None = None,
157
+ max_iter: int | None = None,
158
+ fit_fraction: float = 0.5,
159
+ ) -> float:
160
+ """Estimate the largest Lyapunov exponent of a scalar time series (Rosenstein's algorithm).
161
+
162
+ Unlike every other Lyapunov estimator in physicskit.chaos, this one requires no
163
+ known equations of motion -- only a sampled time series -- making it the
164
+ tool to reach for on real experimental or observational data. Internally
165
+ calls :func:`average_log_divergence` and fits its slope over the first
166
+ `fit_fraction` of the curve, mirroring
167
+ :func:`physicskit.chaos.utils.metrics.lyapunov_exponent_from_divergence`.
168
+
169
+ Parameters
170
+ ----------
171
+ series : array_like of float, shape (n,)
172
+ Scalar time series.
173
+ dim : int
174
+ Embedding dimension.
175
+ tau : int, default 1
176
+ Embedding delay, in samples.
177
+ dt : float, default 1.0
178
+ Time between consecutive samples of `series`, used to convert the
179
+ per-sample slope into a per-time-unit exponent.
180
+ min_tsep : int, optional
181
+ Minimum temporal separation for a candidate nearest neighbor;
182
+ defaults to `tau`.
183
+ max_iter : int, optional
184
+ Number of forward-evolution steps to track; defaults to
185
+ ``n_embedded // 10``.
186
+ fit_fraction : float, default 0.5
187
+ Fraction of the (start of the) divergence curve used for the linear
188
+ fit, before it saturates at the attractor's scale.
189
+
190
+ Returns
191
+ -------
192
+ float
193
+ Estimated largest Lyapunov exponent, in units of 1/`dt`.
194
+ """
195
+ steps, log_divergence = average_log_divergence(series, dim, tau=tau, min_tsep=min_tsep, max_iter=max_iter)
196
+ if steps.size < 2:
197
+ raise ValueError("not enough divergence-curve points to fit a slope")
198
+ n_fit = max(2, int(fit_fraction * steps.size))
199
+ slope, _ = np.polyfit(steps[:n_fit] * dt, log_divergence[:n_fit], 1)
200
+ return float(slope)
201
+
202
+
203
+ def iaaft_surrogate(series: ArrayLike, n_iter: int = 100, seed: int | None = None) -> NDArray[np.float64]:
204
+ """Generate one IAAFT surrogate of a time series.
205
+
206
+ The Iterative Amplitude-Adjusted Fourier Transform (Schreiber & Schmitz
207
+ 1996) produces a surrogate series with (very close to) the same value
208
+ distribution and the same power spectrum as `series`, but with its phase
209
+ relationships randomized -- i.e. a realization of a linear, Gaussian
210
+ stochastic process with the same second-order statistics. Any nonlinear
211
+ structure (including determinism/chaos) present in `series` but absent
212
+ from its surrogates is evidence the series is not just linearly
213
+ correlated noise; see :func:`surrogate_test`.
214
+
215
+ Parameters
216
+ ----------
217
+ series : array_like of float, shape (n,)
218
+ Time series to generate a surrogate of.
219
+ n_iter : int, default 100
220
+ Number of alternating spectrum/distribution adjustment iterations.
221
+ seed : int, optional
222
+ Seed for the initial random shuffle, for reproducibility.
223
+
224
+ Returns
225
+ -------
226
+ ndarray of float, shape (n,)
227
+ One IAAFT surrogate realization.
228
+ """
229
+ series = np.asarray(series, dtype=np.float64)
230
+ n = series.shape[0]
231
+ rng = np.random.default_rng(seed)
232
+ sorted_original = np.sort(series)
233
+ target_amplitude = np.abs(np.fft.rfft(series))
234
+
235
+ surrogate = rng.permutation(series)
236
+ for _ in range(n_iter):
237
+ phases = np.angle(np.fft.rfft(surrogate))
238
+ surrogate = np.fft.irfft(target_amplitude * np.exp(1j * phases), n=n)
239
+ ranks = np.argsort(np.argsort(surrogate))
240
+ surrogate = sorted_original[ranks]
241
+ return surrogate
242
+
243
+
244
+ def surrogate_test(
245
+ series: ArrayLike,
246
+ statistic_fn: Callable[[NDArray[np.float64]], float],
247
+ n_surrogates: int = 39,
248
+ n_iter: int = 100,
249
+ seed: int | None = None,
250
+ show_progress: bool = False,
251
+ ) -> tuple[float, NDArray[np.float64], float]:
252
+ """Test whether a time series shows structure beyond a linear stochastic process.
253
+
254
+ Computes `statistic_fn` (e.g. :func:`rosenstein_lyapunov`, or
255
+ :func:`physicskit.chaos.utils.dimension.correlation_dimension` wrapped to return
256
+ just its dimension estimate) on `series` and on `n_surrogates` IAAFT
257
+ surrogates (:func:`iaaft_surrogate`), then reports how many standard
258
+ deviations the observed value is from the surrogate distribution. The
259
+ default ``n_surrogates=39`` follows the standard convention (Theiler et
260
+ al. 1992) of giving a one-sided 2.5% significance threshold when
261
+ `statistic_fn` is significantly larger (or smaller) than every surrogate.
262
+
263
+ Parameters
264
+ ----------
265
+ series : array_like of float, shape (n,)
266
+ Time series to test.
267
+ statistic_fn : callable
268
+ Function ``statistic_fn(series) -> float`` computing the statistic of
269
+ interest (e.g. a Lyapunov exponent or fractal dimension estimate).
270
+ n_surrogates : int, default 39
271
+ Number of IAAFT surrogates to generate.
272
+ n_iter : int, default 100
273
+ Number of IAAFT iterations per surrogate.
274
+ seed : int, optional
275
+ Seed for surrogate generation, for reproducibility.
276
+ show_progress : bool, default False
277
+ Display a `tqdm` progress bar over surrogates.
278
+
279
+ Returns
280
+ -------
281
+ observed : float
282
+ ``statistic_fn(series)``.
283
+ surrogate_values : ndarray of float, shape (n_surrogates,)
284
+ ``statistic_fn`` evaluated on each surrogate.
285
+ significance : float
286
+ ``|observed - mean(surrogate_values)| / std(surrogate_values)``: the
287
+ number of surrogate standard deviations the observed value is from
288
+ the surrogate mean. Values well above ~2-3 are typically taken as
289
+ evidence of nonlinear structure not explained by a linear process
290
+ with the same spectrum and distribution.
291
+ """
292
+ series = np.asarray(series, dtype=np.float64)
293
+ rng = np.random.default_rng(seed)
294
+ observed = statistic_fn(series)
295
+ surrogate_values = np.array(
296
+ [
297
+ statistic_fn(iaaft_surrogate(series, n_iter=n_iter, seed=int(rng.integers(0, 2**32))))
298
+ for _ in tqdm(range(n_surrogates), disable=not show_progress, desc="surrogates")
299
+ ]
300
+ )
301
+ sigma = float(np.std(surrogate_values))
302
+ significance = abs(observed - float(np.mean(surrogate_values))) / sigma if sigma > 0 else np.inf
303
+ return observed, surrogate_values, significance
@@ -0,0 +1,85 @@
1
+ from physicskit.chaos.visualizers import theme
2
+ from physicskit.chaos.visualizers.basins import basin_of_attraction, plot_basin_of_attraction
3
+ from physicskit.chaos.visualizers.bifurcation import (
4
+ bifurcation_diagram,
5
+ map_bifurcation_sampler,
6
+ plot_bifurcation_diagram,
7
+ stroboscopic_bifurcation_sampler,
8
+ )
9
+ from physicskit.chaos.visualizers.divergence import (
10
+ billiard_trajectory_divergence,
11
+ plot_billiard_divergence,
12
+ plot_lyapunov_divergence,
13
+ plot_trajectory_ensemble,
14
+ trajectory_divergence,
15
+ trajectory_ensemble,
16
+ )
17
+ from physicskit.chaos.visualizers.dynamic_plots import (
18
+ animate_bakers_map,
19
+ animate_billiard_divergence,
20
+ animate_billiard_trajectory,
21
+ animate_double_pendulum,
22
+ animate_magnetic_pendulum,
23
+ animate_map_cobweb,
24
+ animate_map_orbit,
25
+ animate_multi_orbit_map,
26
+ animate_phase_volume_contraction,
27
+ animate_trajectory_2d,
28
+ animate_trajectory_3d,
29
+ plot_colored_trajectory,
30
+ plotly_3d_trajectory,
31
+ plotly_billiard_trajectory,
32
+ plotly_poincare_section,
33
+ )
34
+ from physicskit.chaos.visualizers.phase_space import plot_billiard_trajectory, plot_poincare_section
35
+ from physicskit.chaos.visualizers.quantum import (
36
+ animate_husimi_evolution,
37
+ plot_billiard_eigenstate,
38
+ plot_husimi,
39
+ plot_quantum_spectrum,
40
+ plot_weyl_law,
41
+ )
42
+ from physicskit.chaos.visualizers.recurrence import plot_recurrence_matrix, plot_recurrence_times
43
+ from physicskit.chaos.visualizers.section import plot_poincare_map, poincare_crossings
44
+
45
+ __all__ = [
46
+ "animate_bakers_map",
47
+ "animate_billiard_divergence",
48
+ "animate_billiard_trajectory",
49
+ "animate_double_pendulum",
50
+ "animate_husimi_evolution",
51
+ "animate_magnetic_pendulum",
52
+ "animate_map_cobweb",
53
+ "animate_map_orbit",
54
+ "animate_multi_orbit_map",
55
+ "animate_phase_volume_contraction",
56
+ "animate_trajectory_2d",
57
+ "animate_trajectory_3d",
58
+ "basin_of_attraction",
59
+ "bifurcation_diagram",
60
+ "billiard_trajectory_divergence",
61
+ "map_bifurcation_sampler",
62
+ "plot_basin_of_attraction",
63
+ "plot_bifurcation_diagram",
64
+ "plot_billiard_divergence",
65
+ "plot_billiard_eigenstate",
66
+ "plot_billiard_trajectory",
67
+ "plot_colored_trajectory",
68
+ "plot_husimi",
69
+ "plot_lyapunov_divergence",
70
+ "plot_poincare_map",
71
+ "plot_poincare_section",
72
+ "plot_quantum_spectrum",
73
+ "plot_recurrence_matrix",
74
+ "plot_recurrence_times",
75
+ "plot_trajectory_ensemble",
76
+ "plot_weyl_law",
77
+ "plotly_3d_trajectory",
78
+ "plotly_billiard_trajectory",
79
+ "plotly_poincare_section",
80
+ "poincare_crossings",
81
+ "stroboscopic_bifurcation_sampler",
82
+ "theme",
83
+ "trajectory_divergence",
84
+ "trajectory_ensemble",
85
+ ]
@@ -0,0 +1,185 @@
1
+ """Basin-of-attraction mapping: which stable state does each initial condition end up in?
2
+
3
+ For a multistable, dissipative system with several stable attractors (e.g.
4
+ :class:`physicskit.chaos.systems.continuous.MagneticPendulum`, with one attractor per
5
+ magnet), the basin of attraction of an attractor is the set of initial
6
+ conditions that end up there. Basin boundaries between attractors are often
7
+ *fractal* -- an arbitrarily small change in initial position can flip which
8
+ attractor "wins" -- and this module maps that structure over a 2D grid.
9
+
10
+ Because this means integrating one trajectory per pixel of the map, the
11
+ integration itself is a Numba-jitted, parallelized loop rather than the
12
+ :func:`scipy.integrate.solve_ivp`-based approach used by
13
+ :mod:`physicskit.chaos.visualizers.bifurcation`: `rhs` here must be a module-level
14
+ ``@njit`` function (state, t, params) -> ndarray``, exactly like the ones
15
+ :mod:`physicskit.chaos.systems.continuous` exposes publicly (e.g.
16
+ :func:`physicskit.chaos.systems.continuous.magnetic_pendulum_rhs`).
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ from collections.abc import Callable
22
+ from typing import cast
23
+
24
+ import matplotlib.pyplot as plt
25
+ import numpy as np
26
+ from matplotlib.axes import Axes
27
+ from matplotlib.figure import Figure
28
+ from numba import njit, prange
29
+ from numpy.typing import ArrayLike, NDArray
30
+
31
+ from physicskit.chaos.core.integrators import rk4_step
32
+ from physicskit.chaos.visualizers import theme
33
+
34
+
35
+ @njit(cache=True, parallel=True)
36
+ def _compute_basin_grid(rhs, xs, ys, vx0, vy0, dt, n_steps, attractors, params) -> NDArray[np.int64]:
37
+ n_y = ys.shape[0]
38
+ n_x = xs.shape[0]
39
+ n_attractors = attractors.shape[0]
40
+ labels = np.empty((n_y, n_x), dtype=np.int64)
41
+ for i in prange(n_y): # type: ignore[attr-defined] # numba lacks type stubs for prange
42
+ for j in range(n_x):
43
+ state = np.array([xs[j], ys[i], vx0, vy0])
44
+ for _ in range(n_steps):
45
+ state = rk4_step(rhs, state, 0.0, dt, params)
46
+ best_label = 0
47
+ best_dist = 1e18
48
+ for k in range(n_attractors):
49
+ dx = state[0] - attractors[k, 0]
50
+ dy = state[1] - attractors[k, 1]
51
+ dist = dx * dx + dy * dy
52
+ if dist < best_dist:
53
+ best_dist = dist
54
+ best_label = k
55
+ labels[i, j] = best_label
56
+ return labels
57
+
58
+
59
+ def basin_of_attraction(
60
+ rhs: Callable,
61
+ params: ArrayLike,
62
+ x_range: tuple[float, float],
63
+ y_range: tuple[float, float],
64
+ attractors: ArrayLike,
65
+ resolution: int = 200,
66
+ velocity0: tuple[float, float] = (0.0, 0.0),
67
+ dt: float = 0.02,
68
+ n_steps: int = 2000,
69
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64], NDArray[np.int64]]:
70
+ """Compute a basin-of-attraction map over a 2D grid of initial positions.
71
+
72
+ For every point on an ``resolution x resolution`` grid spanning
73
+ `x_range` x `y_range`, integrates a trajectory of the system defined by
74
+ `rhs` starting at that position (with initial velocity `velocity0`) for
75
+ `n_steps` of size `dt`, then labels the grid point by whichever entry of
76
+ `attractors` its final position ends up closest to.
77
+
78
+ Parameters
79
+ ----------
80
+ rhs : callable
81
+ A module-level Numba ``@njit`` function ``rhs(state, t, params) ->
82
+ ndarray``, with a 4-dimensional state ``(x, y, vx, vy)`` (e.g.
83
+ :func:`physicskit.chaos.systems.continuous.magnetic_pendulum_rhs`).
84
+ params : array_like of float
85
+ Parameter vector passed through to `rhs`.
86
+ x_range, y_range : tuple of float
87
+ ``(min, max)`` extent of the grid along each axis.
88
+ attractors : array_like of float, shape (n_attractors, 2)
89
+ Known ``(x, y)`` positions of the system's stable attractors.
90
+ resolution : int, default 200
91
+ Number of grid points along each axis (the grid has
92
+ ``resolution**2`` points total, each requiring one full
93
+ trajectory integration).
94
+ velocity0 : tuple of float, default (0.0, 0.0)
95
+ Initial velocity ``(vx, vy)``, the same for every grid point.
96
+ dt : float, default 0.02
97
+ Integration step size.
98
+ n_steps : int, default 2000
99
+ Number of integration steps per grid point; should be long enough
100
+ for the trajectory to have settled near an attractor.
101
+
102
+ Returns
103
+ -------
104
+ xs, ys : ndarray of float, shape (resolution,)
105
+ Grid coordinates along each axis.
106
+ labels : ndarray of int64, shape (resolution, resolution)
107
+ ``labels[i, j]`` is the index into `attractors` of the attractor
108
+ closest to the trajectory launched from ``(xs[j], ys[i])``.
109
+ """
110
+ xs = np.linspace(x_range[0], x_range[1], resolution)
111
+ ys = np.linspace(y_range[0], y_range[1], resolution)
112
+ labels = _compute_basin_grid(
113
+ rhs,
114
+ xs,
115
+ ys,
116
+ float(velocity0[0]),
117
+ float(velocity0[1]),
118
+ dt,
119
+ n_steps,
120
+ np.asarray(attractors, dtype=np.float64),
121
+ np.asarray(params, dtype=np.float64),
122
+ )
123
+ return xs, ys, labels
124
+
125
+
126
+ def plot_basin_of_attraction(
127
+ rhs: Callable,
128
+ params: ArrayLike,
129
+ x_range: tuple[float, float],
130
+ y_range: tuple[float, float],
131
+ attractors: ArrayLike,
132
+ resolution: int = 200,
133
+ velocity0: tuple[float, float] = (0.0, 0.0),
134
+ dt: float = 0.02,
135
+ n_steps: int = 2000,
136
+ ax: Axes | None = None,
137
+ cmap: str = theme.QUALITATIVE_CMAP,
138
+ ) -> tuple[Figure, Axes]:
139
+ """Plot a basin-of-attraction map.
140
+
141
+ Parameters
142
+ ----------
143
+ rhs, params, x_range, y_range, attractors, resolution, velocity0, dt, n_steps
144
+ See :func:`basin_of_attraction`.
145
+ ax : matplotlib.axes.Axes, optional
146
+ Axes to draw into; a new figure and axes are created if omitted.
147
+ cmap : str, default "tab10" (:data:`physicskit.chaos.visualizers.theme.QUALITATIVE_CMAP`)
148
+ Colormap used for the (discrete) attractor labels.
149
+
150
+ Returns
151
+ -------
152
+ fig : matplotlib.figure.Figure
153
+ ax : matplotlib.axes.Axes
154
+ """
155
+ xs, ys, labels = basin_of_attraction(
156
+ rhs,
157
+ params,
158
+ x_range,
159
+ y_range,
160
+ attractors,
161
+ resolution=resolution,
162
+ velocity0=velocity0,
163
+ dt=dt,
164
+ n_steps=n_steps,
165
+ )
166
+ attractors = np.asarray(attractors, dtype=np.float64)
167
+
168
+ if ax is None:
169
+ fig, ax = plt.subplots(figsize=(7, 7))
170
+ else:
171
+ fig = cast(Figure, ax.figure)
172
+
173
+ ax.imshow(
174
+ labels,
175
+ extent=(xs.min(), xs.max(), ys.min(), ys.max()),
176
+ origin="lower",
177
+ cmap=cmap,
178
+ interpolation="nearest",
179
+ aspect="equal",
180
+ )
181
+ ax.scatter(attractors[:, 0], attractors[:, 1], c=theme.STRUCTURE, marker="x", s=60, zorder=3)
182
+ ax.set_xlabel("x")
183
+ ax.set_ylabel("y")
184
+ ax.set_title("Basin of attraction")
185
+ return fig, ax