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,269 @@
1
+ import matplotlib
2
+
3
+ matplotlib.use("Agg")
4
+
5
+ import numpy as np
6
+ import pytest
7
+ from matplotlib.animation import PillowWriter
8
+ from matplotlib.collections import LineCollection
9
+
10
+ from physicskit.chaos.systems.continuous import DrivenPendulum, Lorenz, MagneticPendulum, RestrictedThreeBody
11
+ from physicskit.chaos.systems.maps import BakersMap
12
+ from physicskit.chaos.visualizers.dynamic_plots import (
13
+ animate_bakers_map,
14
+ animate_driven_pendulum,
15
+ animate_multi_orbit_map,
16
+ animate_phase_volume_contraction,
17
+ animate_restricted_three_body,
18
+ plot_colored_trajectory,
19
+ plotly_3d_trajectory,
20
+ )
21
+
22
+
23
+ def test_plot_colored_trajectory_returns_figure_and_axes_with_line_collection():
24
+ x = np.linspace(0.0, 1.0, 50)
25
+ y = np.sin(x * 10.0)
26
+ values = np.arange(50, dtype=np.float64)
27
+
28
+ _fig, ax = plot_colored_trajectory(x, y, values)
29
+ collections = [c for c in ax.collections if isinstance(c, LineCollection)]
30
+ assert len(collections) == 1
31
+ # One fewer segment than points (each segment joins consecutive points).
32
+ assert collections[0].get_segments().__len__() == 49
33
+
34
+
35
+ def test_plot_colored_trajectory_sets_axis_limits_to_data_range():
36
+ x = np.array([0.0, 5.0, 2.0])
37
+ y = np.array([0.0, 1.0, -3.0])
38
+ values = np.array([0.0, 1.0, 2.0])
39
+
40
+ _, ax = plot_colored_trajectory(x, y, values)
41
+ assert ax.get_xlim() == (0.0, 5.0)
42
+ assert ax.get_ylim() == (-3.0, 1.0)
43
+
44
+
45
+ def test_plot_colored_trajectory_no_colorbar_when_disabled():
46
+ x = np.linspace(0.0, 1.0, 10)
47
+ y = np.linspace(0.0, 1.0, 10)
48
+ values = np.linspace(0.0, 1.0, 10)
49
+
50
+ fig, _ = plot_colored_trajectory(x, y, values, colorbar=False)
51
+ assert len(fig.axes) == 1 # just the main axes, no colorbar axes added
52
+
53
+
54
+ def test_plotly_3d_trajectory_contains_all_points():
55
+ states = np.column_stack([np.linspace(0, 1, 20), np.linspace(0, 2, 20), np.linspace(0, 3, 20)])
56
+ fig = plotly_3d_trajectory(states)
57
+ trace = fig.data[0]
58
+ assert len(trace.x) == 20
59
+ np.testing.assert_allclose(trace.z, states[:, 2])
60
+
61
+
62
+ def test_plotly_3d_trajectory_color_by_sets_marker_color_array():
63
+ states = np.random.default_rng(0).uniform(size=(15, 3))
64
+ color_values = np.arange(15, dtype=np.float64)
65
+ fig = plotly_3d_trajectory(states, color_by=color_values)
66
+ trace = fig.data[0]
67
+ np.testing.assert_allclose(trace.line.color, color_values)
68
+
69
+
70
+ def test_animate_phase_volume_contraction_rejects_non_3d_system():
71
+ with pytest.raises(ValueError):
72
+ animate_phase_volume_contraction(MagneticPendulum())
73
+
74
+
75
+ def test_animate_phase_volume_contraction_shrinks_the_cloud():
76
+ """The whole point of the animation: the point cloud's volume must shrink
77
+ over time for a dissipative system, since that is what phase-space
78
+ volume contraction means geometrically. Uses a small ball and a short
79
+ horizon, where the linear (Jacobian-trace) contraction that
80
+ :func:`~physicskit.chaos.utils.metrics.phase_volume_expansion` predicts still
81
+ dominates -- over a long horizon the cloud instead stretches into a
82
+ thin sheet *along* the attractor, and simple size statistics (spread
83
+ along one axis, a covariance-based volume estimate) stop decreasing
84
+ monotonically even though it is still, in the measure-theoretic sense,
85
+ contracting."""
86
+
87
+ def _cloud_volume(anim, frame):
88
+ anim._draw_frame(frame)
89
+ x, y, z = anim._fig.axes[0].collections[0]._offsets3d
90
+ cov = np.cov(np.column_stack([x, y, z]).T)
91
+ return float(np.sqrt(np.linalg.det(cov)))
92
+
93
+ system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
94
+ anim = animate_phase_volume_contraction(
95
+ system,
96
+ n_points=200,
97
+ ball_radius=0.3,
98
+ t_max=0.5,
99
+ dt=0.005,
100
+ n_frames=10,
101
+ warmup_steps=200,
102
+ seed=0,
103
+ )
104
+ volumes = [_cloud_volume(anim, i) for i in range(10)]
105
+ assert all(v2 < v1 for v1, v2 in zip(volumes, volumes[1:]))
106
+
107
+
108
+ def test_animate_phase_volume_contraction_respects_explicit_center_state():
109
+ system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
110
+ center = np.array([1.0, 1.0, 1.0])
111
+ anim = animate_phase_volume_contraction(system, center_state=center, ball_radius=0.5, n_points=50, t_max=1.0, n_frames=5)
112
+ anim._draw_frame(0)
113
+ x, y, z = anim._fig.axes[0].collections[0]._offsets3d
114
+ points0 = np.column_stack([x, y, z])
115
+ assert np.allclose(points0.mean(axis=0), center, atol=0.5)
116
+
117
+
118
+ def _n_points_drawn(anim):
119
+ return sum(len(line.get_xdata()) for line in anim._fig.axes[0].lines)
120
+
121
+
122
+ def test_animate_multi_orbit_map_grows_every_orbit_together_not_one_at_a_time():
123
+ """Every orbit must gain points on (almost) every frame -- not have whole
124
+ orbits complete one at a time -- so the picture keeps visibly changing
125
+ for the whole animation instead of mostly settling within a few frames."""
126
+ rng = np.random.default_rng(0)
127
+ orbits = [rng.uniform(size=(50, 2)) for _ in range(5)]
128
+ anim = animate_multi_orbit_map(orbits, n_frames=20)
129
+
130
+ anim._draw_frame(0)
131
+ counts = [_n_points_drawn(anim)]
132
+ for i in range(1, 20):
133
+ anim._draw_frame(i)
134
+ counts.append(_n_points_drawn(anim))
135
+
136
+ # Points accumulate monotonically and every orbit has contributed to the
137
+ # very first frame already (all 5 lines non-empty), not just one.
138
+ assert all(c2 >= c1 for c1, c2 in zip(counts, counts[1:]))
139
+ anim._draw_frame(0)
140
+ non_empty_lines_at_frame_0 = sum(1 for line in anim._fig.axes[0].lines if len(line.get_xdata()) > 0)
141
+ assert non_empty_lines_at_frame_0 == 5
142
+
143
+
144
+ def test_animate_multi_orbit_map_reaches_full_length_on_last_frame():
145
+ orbits = [np.column_stack([np.arange(30), np.arange(30)]) for _ in range(3)]
146
+ anim = animate_multi_orbit_map(orbits, n_frames=10)
147
+ anim._draw_frame(9)
148
+ assert _n_points_drawn(anim) == 30 * 3
149
+
150
+
151
+ def test_animate_multi_orbit_map_handles_orbits_of_different_lengths():
152
+ orbits = [np.zeros((10, 2)), np.zeros((30, 2))]
153
+ anim = animate_multi_orbit_map(orbits, n_frames=5)
154
+ anim._draw_frame(4)
155
+ lengths = [len(line.get_xdata()) for line in anim._fig.axes[0].lines]
156
+ assert lengths == [10, 30]
157
+
158
+
159
+ def _bakers_scatter(anim):
160
+ return anim._fig.axes[0].collections[0]
161
+
162
+
163
+ def test_animate_bakers_map_colors_left_and_right_half_differently():
164
+ n_points = 100
165
+ seed = 0
166
+ system = BakersMap(alpha=0.5)
167
+ anim = animate_bakers_map(
168
+ system,
169
+ n_points=n_points,
170
+ n_iterations=1,
171
+ frames_per_iteration=6,
172
+ stripe_colors=("tab:blue", "tab:red"),
173
+ seed=seed,
174
+ )
175
+
176
+ # Reconstruct the same jittered starting grid `animate_bakers_map` builds
177
+ # internally, to know each marker's *original* x -- by frame 0 the
178
+ # stretch phase has already moved the markers, so their current offsets
179
+ # no longer reflect the left/right split the coloring is based on.
180
+ n_side = round(np.sqrt(n_points))
181
+ centers = (np.arange(n_side) + 0.5) / n_side
182
+ grid_x, _ = np.meshgrid(centers, centers)
183
+ rng = np.random.default_rng(seed)
184
+ jitter = rng.uniform(-0.15, 0.15, size=(2,) + grid_x.shape) / n_side
185
+ x0 = (grid_x + jitter[0]).ravel()
186
+
187
+ anim._draw_frame(0)
188
+ facecolors = _bakers_scatter(anim).get_facecolor()
189
+ unique_colors = {tuple(c) for c in facecolors}
190
+ assert len(unique_colors) == 2
191
+ # Color must actually track the left/right split, not just happen to
192
+ # take two values: every left-of-center point shares one color and every
193
+ # right-of-center point shares the other.
194
+ left_colors = {tuple(c) for c, x in zip(facecolors, x0) if x < 0.5}
195
+ right_colors = {tuple(c) for c, x in zip(facecolors, x0) if x >= 0.5}
196
+ assert len(left_colors) == 1
197
+ assert len(right_colors) == 1
198
+ assert left_colors != right_colors
199
+
200
+
201
+ def test_animate_bakers_map_stretch_phase_extends_past_unit_square():
202
+ """During the stretch phase the cloud should spill out past x = 1, since
203
+ the right-hand branch is drawn stretched into its own, offset slot
204
+ rather than snapping straight to its final (folded-back) position."""
205
+ system = BakersMap(alpha=0.5)
206
+ anim = animate_bakers_map(system, n_points=400, n_iterations=1, frames_per_iteration=9)
207
+ for i in range(4): # sequentially through frame 3 (mid-stretch, before the cut/pause frame)
208
+ anim._draw_frame(i)
209
+ x = _bakers_scatter(anim).get_offsets()[:, 0]
210
+ assert x.max() > 1.0
211
+
212
+
213
+ def test_animate_bakers_map_returns_to_unit_square_after_full_iteration():
214
+ """At the last frame of an iteration, every point must land exactly on
215
+ the analytic baker's map image of its starting point, back inside the
216
+ unit square (no residual stretch/offset left over)."""
217
+ alpha = 0.5
218
+ system = BakersMap(alpha=alpha)
219
+ n_points = 100
220
+ seed = 0
221
+ anim = animate_bakers_map(system, n_points=n_points, n_iterations=1, frames_per_iteration=6, seed=seed)
222
+
223
+ # Reconstruct the same jittered starting grid `animate_bakers_map` builds
224
+ # internally (a perfectly regular grid sits at exact dyadic rationals,
225
+ # which this map's doubling action on `x` would otherwise degenerate
226
+ # within a handful of iterations).
227
+ n_side = round(np.sqrt(n_points))
228
+ centers = (np.arange(n_side) + 0.5) / n_side
229
+ grid_x, grid_y = np.meshgrid(centers, centers)
230
+ rng = np.random.default_rng(seed)
231
+ jitter = rng.uniform(-0.15, 0.15, size=(2,) + grid_x.shape) / n_side
232
+ x0 = np.clip(grid_x + jitter[0], 0.0, 1.0 - 1e-12).ravel()
233
+ y0 = np.clip(grid_y + jitter[1], 0.0, 1.0 - 1e-12).ravel()
234
+ left = x0 < alpha
235
+ x_expected = np.where(left, x0 / alpha, (x0 - alpha) / (1.0 - alpha))
236
+ y_expected = np.where(left, alpha * y0, alpha + (1.0 - alpha) * y0)
237
+
238
+ for i in range(6): # sequentially through the last frame of the (only) iteration
239
+ anim._draw_frame(i)
240
+ offsets = _bakers_scatter(anim).get_offsets()
241
+ np.testing.assert_allclose(np.sort(offsets[:, 0]), np.sort(x_expected), atol=1e-10)
242
+ np.testing.assert_allclose(np.sort(offsets[:, 1]), np.sort(y_expected), atol=1e-10)
243
+
244
+
245
+ def test_animate_bakers_map_uses_square_markers():
246
+ system = BakersMap(alpha=0.5)
247
+ anim = animate_bakers_map(system, n_points=100, n_iterations=1, frames_per_iteration=6)
248
+ paths = _bakers_scatter(anim).get_paths()
249
+ assert len(paths) == 1 # scatter shares one path (the marker shape) across all points
250
+ # A square marker path has 4 (or 5, if closed) vertices.
251
+ assert len(paths[0].vertices) in (4, 5)
252
+
253
+
254
+ @pytest.mark.slow
255
+ def test_animate_restricted_three_body_saves_gif(tmp_path):
256
+ system = RestrictedThreeBody()
257
+ anim = animate_restricted_three_body(system, dt=0.001, n_steps=800, skip=20)
258
+ out = tmp_path / "cr3bp.gif"
259
+ anim.save(out, writer=PillowWriter(fps=10))
260
+ assert out.exists() and out.stat().st_size > 0
261
+
262
+
263
+ @pytest.mark.slow
264
+ def test_animate_driven_pendulum_saves_gif(tmp_path):
265
+ system = DrivenPendulum()
266
+ anim = animate_driven_pendulum(system, dt=0.02, n_steps=60)
267
+ out = tmp_path / "driven_pendulum.gif"
268
+ anim.save(out, writer=PillowWriter(fps=10))
269
+ assert out.exists() and out.stat().st_size > 0
@@ -0,0 +1,96 @@
1
+ import numpy as np
2
+ from numba import njit
3
+
4
+ from physicskit.chaos.core.integrators import leapfrog_integrate, rk4_integrate, yoshida4_integrate
5
+
6
+
7
+ @njit
8
+ def _harmonic_force(pos, t, params):
9
+ """Restoring force for a simple harmonic oscillator, for integrator tests."""
10
+ omega = params[0]
11
+ return -(omega**2) * pos
12
+
13
+
14
+ @njit
15
+ def _harmonic_rhs(state, t, params):
16
+ """RK4-form right-hand side for the same simple harmonic oscillator."""
17
+ omega = params[0]
18
+ out = np.empty(2)
19
+ out[0] = state[1]
20
+ out[1] = -(omega**2) * state[0]
21
+ return out
22
+
23
+
24
+ def _harmonic_energy(pos, vel, omega):
25
+ return 0.5 * vel**2 + 0.5 * omega**2 * pos**2
26
+
27
+
28
+ def test_yoshida4_conserves_energy_better_than_leapfrog():
29
+ """The 4th-order Yoshida integrator should have much smaller energy drift
30
+ than plain (2nd-order) leapfrog at the same step size, since it is built
31
+ from three leapfrog sub-steps composed to raise the order of accuracy."""
32
+ omega = 2.0
33
+ params = np.array([omega])
34
+ pos0, vel0 = np.array([1.0]), np.array([0.0])
35
+ dt, n_steps = 0.05, 5000
36
+
37
+ _, pos_lf, vel_lf = leapfrog_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
38
+ energy_lf = _harmonic_energy(pos_lf[:, 0], vel_lf[:, 0], omega)
39
+ drift_lf = np.max(np.abs(energy_lf - energy_lf[0]) / energy_lf[0])
40
+
41
+ _, pos_y4, vel_y4 = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
42
+ energy_y4 = _harmonic_energy(pos_y4[:, 0], vel_y4[:, 0], omega)
43
+ drift_y4 = np.max(np.abs(energy_y4 - energy_y4[0]) / energy_y4[0])
44
+
45
+ assert drift_y4 < drift_lf
46
+
47
+
48
+ def test_yoshida4_energy_drift_does_not_grow_with_integration_time():
49
+ """Being symplectic, Yoshida4's energy drift should stay bounded as the
50
+ integration horizon grows, unlike RK4's secularly-growing drift."""
51
+ omega = 2.0
52
+ params = np.array([omega])
53
+ pos0, vel0 = np.array([1.0]), np.array([0.0])
54
+ dt = 0.05
55
+
56
+ drifts = []
57
+ for n_steps in (2000, 20000, 200000):
58
+ _, pos, vel = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
59
+ energy = _harmonic_energy(pos[:, 0], vel[:, 0], omega)
60
+ drifts.append(np.max(np.abs(energy - energy[0]) / energy[0]))
61
+
62
+ # All three drifts should be the same order of magnitude (bounded), not
63
+ # growing proportionally with the 100x increase in integration horizon.
64
+ assert max(drifts) / min(drifts) < 10.0
65
+
66
+
67
+ def test_rk4_energy_drift_grows_with_integration_time():
68
+ """The non-symplectic RK4 integrator's energy drift grows with
69
+ integration horizon, in contrast to the symplectic integrators -- this is
70
+ exactly the failure mode the symplectic integrators exist to avoid."""
71
+ omega = 2.0
72
+ params = np.array([omega])
73
+ state0 = np.array([1.0, 0.0])
74
+ dt = 0.05
75
+
76
+ drifts = []
77
+ for n_steps in (2000, 200000):
78
+ _, states = rk4_integrate(_harmonic_rhs, state0, 0.0, dt, n_steps, params)
79
+ energy = _harmonic_energy(states[:, 0], states[:, 1], omega)
80
+ drifts.append(np.max(np.abs(energy - energy[0]) / energy[0]))
81
+
82
+ assert drifts[1] > 5.0 * drifts[0]
83
+
84
+
85
+ def test_yoshida4_matches_leapfrog_for_a_single_leapfrog_substep_limit():
86
+ """Sanity check: with a tiny step size, both integrators should agree
87
+ closely on the trajectory of a simple, smooth system."""
88
+ omega = 1.0
89
+ params = np.array([omega])
90
+ pos0, vel0 = np.array([1.0]), np.array([0.0])
91
+ dt, n_steps = 1e-4, 100
92
+
93
+ _, pos_lf, _ = leapfrog_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
94
+ _, pos_y4, _ = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
95
+
96
+ np.testing.assert_allclose(pos_lf, pos_y4, atol=1e-8)
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+
3
+ from physicskit.chaos.systems.billiards import SinaiBilliard
4
+ from physicskit.chaos.systems.continuous import Lorenz
5
+ from physicskit.chaos.utils.io import load_arrays, load_system_config, save_arrays, save_system_config
6
+
7
+
8
+ def test_save_and_load_system_config_roundtrips_scalar_params(tmp_path):
9
+ system = Lorenz(sigma=11.0, rho=27.5, beta=8.0 / 3.0)
10
+ path = tmp_path / "lorenz.json"
11
+ save_system_config(system, path)
12
+ restored = load_system_config(path)
13
+
14
+ assert type(restored) is Lorenz
15
+ assert restored.sigma == system.sigma
16
+ assert restored.rho == system.rho
17
+ assert restored.beta == system.beta
18
+
19
+
20
+ def test_save_and_load_system_config_roundtrips_billiard_params(tmp_path):
21
+ billiard = SinaiBilliard(cell_size=2.5, scatterer_radius=0.7)
22
+ path = tmp_path / "sinai.json"
23
+ save_system_config(billiard, path)
24
+ restored = load_system_config(path)
25
+
26
+ assert type(restored) is SinaiBilliard
27
+ assert restored.cell_size == billiard.cell_size
28
+ assert restored.scatterer_radius == billiard.scatterer_radius
29
+
30
+
31
+ def test_save_and_load_arrays_roundtrips_exactly(tmp_path):
32
+ t = np.linspace(0.0, 10.0, 50)
33
+ states = np.random.default_rng(0).normal(size=(50, 3))
34
+ path = tmp_path / "run.npz"
35
+
36
+ save_arrays(path, t=t, states=states)
37
+ loaded = load_arrays(path)
38
+
39
+ assert set(loaded.keys()) == {"t", "states"}
40
+ np.testing.assert_array_equal(loaded["t"], t)
41
+ np.testing.assert_array_equal(loaded["states"], states)
@@ -0,0 +1,110 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.maps import BakersMap, LogisticMap
5
+
6
+
7
+ def test_bakers_map_state_stays_in_unit_square():
8
+ """Every iterate of the (area-preserving) baker's map must stay in [0, 1) x [0, 1)."""
9
+ system = BakersMap(alpha=0.5)
10
+ traj = system.trajectory(np.array([0.123, 0.456]), n_iter=1000)
11
+ assert np.all(traj >= 0.0)
12
+ assert np.all(traj < 1.0)
13
+
14
+
15
+ @pytest.mark.parametrize("alpha", [0.2, 0.5, 0.7])
16
+ def test_bakers_map_state_stays_in_unit_square_asymmetric(alpha):
17
+ system = BakersMap(alpha=alpha)
18
+ traj = system.trajectory(np.array([0.37, 0.81]), n_iter=1000)
19
+ assert np.all(traj >= 0.0)
20
+ assert np.all(traj < 1.0)
21
+
22
+
23
+ def test_bakers_map_symmetric_lyapunov_exponents_are_log2():
24
+ """For alpha=0.5, the analytic Lyapunov exponents are exactly +-ln(2)."""
25
+ system = BakersMap(alpha=0.5)
26
+ lam_plus, lam_minus = system.lyapunov_exponents()
27
+ assert lam_plus == pytest.approx(np.log(2.0))
28
+ assert lam_minus == pytest.approx(-np.log(2.0))
29
+
30
+
31
+ def test_bakers_map_lyapunov_exponents_are_symmetric_and_positive():
32
+ system = BakersMap(alpha=0.3)
33
+ lam_plus, lam_minus = system.lyapunov_exponents()
34
+ assert lam_plus > 0.0
35
+ assert lam_minus == pytest.approx(-lam_plus)
36
+
37
+
38
+ def test_bakers_map_symmetric_case_doubles_x_separation_each_step():
39
+ """For alpha=0.5, two points starting in the same branch have their
40
+ x-separation exactly double at every step, until a branch-crossing wraps
41
+ one of them -- the literal "stretch" half of "stretch, cut, and stack"."""
42
+ system = BakersMap(alpha=0.5)
43
+ state_a = np.array([0.1, 0.2])
44
+ state_b = np.array([0.1 + 1e-6, 0.2])
45
+ for _ in range(10):
46
+ dx_before = abs(state_b[0] - state_a[0])
47
+ same_branch = (state_a[0] < 0.5) == (state_b[0] < 0.5)
48
+ state_a = system.step(state_a)
49
+ state_b = system.step(state_b)
50
+ if same_branch:
51
+ dx_after = abs(state_b[0] - state_a[0])
52
+ assert dx_after == pytest.approx(2.0 * dx_before, rel=1e-6)
53
+
54
+
55
+ def test_bakers_map_rejects_invalid_alpha():
56
+ with pytest.raises(ValueError):
57
+ BakersMap(alpha=0.0)
58
+ with pytest.raises(ValueError):
59
+ BakersMap(alpha=1.0)
60
+ with pytest.raises(ValueError):
61
+ BakersMap(alpha=1.5)
62
+
63
+
64
+ def test_logistic_map_step_matches_trajectory_first_iteration():
65
+ """LogisticMap.trajectory() uses a fast, standalone numba loop rather
66
+ than calling .step() itself; check .step() directly gives the same
67
+ first iteration as trajectory() does, so the two paths can't diverge."""
68
+ system = LogisticMap(r=3.9)
69
+ state0 = np.array([0.37])
70
+ stepped = system.step(state0)
71
+ traj = system.trajectory(state0, n_iter=1)
72
+ assert stepped.shape == (1,)
73
+ np.testing.assert_allclose(stepped, traj[1])
74
+
75
+
76
+ def test_logistic_map_state_stays_in_unit_interval():
77
+ system = LogisticMap(r=3.9)
78
+ traj = system.trajectory(np.array([0.37]), n_iter=2000)
79
+ assert traj.shape == (2001, 1)
80
+ assert np.all(traj >= 0.0)
81
+ assert np.all(traj <= 1.0)
82
+
83
+
84
+ def test_logistic_map_converges_to_known_fixed_point_below_r3():
85
+ """For 1 < r < 3, every trajectory converges to the stable fixed point x* = 1 - 1/r."""
86
+ r = 2.5
87
+ system = LogisticMap(r=r)
88
+ traj = system.trajectory(np.array([0.1]), n_iter=500)
89
+ x_star = 1.0 - 1.0 / r
90
+ assert traj[-1, 0] == pytest.approx(x_star, abs=1e-6)
91
+
92
+
93
+ def test_logistic_map_period_two_cycle_just_above_r3():
94
+ """Just above the first period-doubling bifurcation at r=3, orbits settle
95
+ into a period-2 cycle: x(n+2) == x(n) but x(n+1) != x(n)."""
96
+ system = LogisticMap(r=3.2)
97
+ traj = system.trajectory(np.array([0.1]), n_iter=2000)
98
+ tail = traj[-4:, 0]
99
+ assert tail[0] == pytest.approx(tail[2], abs=1e-6)
100
+ assert tail[1] == pytest.approx(tail[3], abs=1e-6)
101
+ assert abs(tail[0] - tail[1]) > 1e-3
102
+
103
+
104
+ def test_logistic_map_sensitive_dependence_at_chaotic_r():
105
+ """At r=4 (fully chaotic), two nearby trajectories must diverge noticeably
106
+ within a modest number of iterations."""
107
+ system = LogisticMap(r=4.0)
108
+ traj_a = system.trajectory(np.array([0.4]), n_iter=40)
109
+ traj_b = system.trajectory(np.array([0.4 + 1e-10]), n_iter=40)
110
+ assert abs(traj_a[-1, 0] - traj_b[-1, 0]) > 1e-3
@@ -0,0 +1,115 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.continuous import DoublePendulum, Lorenz
5
+ from physicskit.chaos.systems.maps import HenonMap, StandardMap
6
+ from physicskit.chaos.utils.metrics import (
7
+ benettin_lyapunov_spectrum,
8
+ energy_drift,
9
+ lyapunov_exponent_from_divergence,
10
+ map_lyapunov_spectrum,
11
+ numerical_jacobian,
12
+ numerical_jacobian_map,
13
+ phase_volume_expansion,
14
+ )
15
+
16
+
17
+ def test_numerical_jacobian_matches_known_linear_system():
18
+ """For dx/dt = A x, the Jacobian is exactly A everywhere."""
19
+
20
+ class Linear:
21
+ dim = 2
22
+
23
+ def rhs(self, state, t):
24
+ a = np.array([[0.0, 1.0], [-2.0, -3.0]])
25
+ return a @ state
26
+
27
+ jac = numerical_jacobian(Linear(), np.array([1.0, 2.0]), t=0.0)
28
+ np.testing.assert_allclose(jac, [[0.0, 1.0], [-2.0, -3.0]], atol=1e-6)
29
+
30
+
31
+ def test_numerical_jacobian_map_matches_known_henon_jacobian():
32
+ """The Henon map's Jacobian at (x, y) is exactly [[-2*a*x, 1], [b, 0]]."""
33
+ henon = HenonMap(a=1.4, b=0.3)
34
+ x, y = 0.3, 0.1
35
+ jac = numerical_jacobian_map(henon, np.array([x, y]))
36
+ expected = np.array([[-2.0 * 1.4 * x, 1.0], [0.3, 0.0]])
37
+ np.testing.assert_allclose(jac, expected, atol=1e-5)
38
+
39
+
40
+ def test_lyapunov_exponent_from_divergence_recovers_known_exponential_rate():
41
+ t = np.linspace(0.0, 10.0, 200)
42
+ true_rate = 0.7
43
+ delta = 1e-6 * np.exp(true_rate * t)
44
+ estimate = lyapunov_exponent_from_divergence(t, delta)
45
+ assert estimate == pytest.approx(true_rate, rel=1e-3)
46
+
47
+
48
+ def test_benettin_lyapunov_spectrum_matches_known_lorenz_values():
49
+ """The classic Lorenz parameters have well-documented Lyapunov exponents
50
+ of approximately (0.906, 0, -14.57)."""
51
+ system = Lorenz()
52
+ spectrum = benettin_lyapunov_spectrum(system, system.initial_state(), dt=0.005, n_steps=10000, n_transient=2000)
53
+ assert spectrum[0] == pytest.approx(0.906, abs=0.15)
54
+ assert abs(spectrum[1]) < 0.1
55
+ assert spectrum[2] == pytest.approx(-14.57, abs=1.5)
56
+
57
+
58
+ def test_benettin_lyapunov_spectrum_sum_matches_exact_lorenz_divergence():
59
+ """The Lorenz system's phase-space divergence (trace of the Jacobian) is
60
+ exactly -(sigma + 1 + beta) everywhere, so the sum of the Lyapunov
61
+ spectrum -- estimated with no knowledge of that identity -- should match
62
+ it closely, regardless of how accurately the individual exponents (which
63
+ are harder to pin down individually) are estimated."""
64
+ system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
65
+ spectrum = benettin_lyapunov_spectrum(system, system.initial_state(), dt=0.005, n_steps=10000, n_transient=2000)
66
+ expected_sum = -(system.sigma + 1.0 + system.beta)
67
+ assert spectrum.sum() == pytest.approx(expected_sum, abs=0.05)
68
+
69
+
70
+ def test_map_lyapunov_spectrum_sum_matches_exact_henon_jacobian_determinant():
71
+ """The Henon map's Jacobian determinant is exactly -b everywhere, so the
72
+ sum of its Lyapunov exponents must equal ln|b| to high precision --
73
+ regardless of how well the individual exponents are estimated."""
74
+ henon = HenonMap(a=1.4, b=0.3)
75
+ spectrum = map_lyapunov_spectrum(henon, henon.initial_state(), n_iter=10000, n_transient=1000)
76
+ assert spectrum.sum() == pytest.approx(np.log(0.3), abs=1e-4)
77
+ assert spectrum[0] > 0.0 > spectrum[1]
78
+
79
+
80
+ def test_map_lyapunov_spectrum_sums_to_zero_for_area_preserving_map():
81
+ """The standard map is exactly area-preserving, so its Lyapunov spectrum
82
+ must sum to zero."""
83
+ system = StandardMap(k=2.0)
84
+ spectrum = map_lyapunov_spectrum(system, system.initial_state(), n_iter=10000, n_transient=1000)
85
+ assert spectrum.sum() == pytest.approx(0.0, abs=1e-4)
86
+
87
+
88
+ def test_energy_drift_is_zero_at_t0():
89
+ system = DoublePendulum()
90
+ t, states = system.trajectory(n_steps=500, dt=0.005)
91
+ drift = energy_drift(t, states, system.energy)
92
+ assert drift[0] == pytest.approx(0.0)
93
+
94
+
95
+ def test_energy_drift_stays_small_for_a_good_integration():
96
+ system = DoublePendulum()
97
+ t, states = system.trajectory(n_steps=2000, dt=0.005)
98
+ drift = energy_drift(t, states, system.energy)
99
+ assert np.max(np.abs(drift)) < 1e-3
100
+
101
+
102
+ def test_phase_volume_expansion_starts_at_zero():
103
+ system = Lorenz()
104
+ t, states = system.trajectory(n_steps=500, dt=0.01)
105
+ log_volume = phase_volume_expansion(system, t, states)
106
+ assert log_volume[0] == pytest.approx(0.0)
107
+
108
+
109
+ def test_phase_volume_expansion_is_dissipative_for_lorenz():
110
+ """Lorenz phase-space volume must contract (log volume strictly
111
+ decreasing on average) since the system is dissipative."""
112
+ system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
113
+ t, states = system.trajectory(n_steps=1000, dt=0.01)
114
+ log_volume = phase_volume_expansion(system, t, states)
115
+ assert log_volume[-1] < 0.0