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,132 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.continuous import DrivenPendulum, Duffing
5
+ from physicskit.chaos.systems.maps import LogisticMap
6
+ from physicskit.chaos.visualizers.bifurcation import (
7
+ bifurcation_diagram,
8
+ map_bifurcation_sampler,
9
+ stroboscopic_bifurcation_sampler,
10
+ )
11
+
12
+
13
+ def test_bifurcation_diagram_flattens_and_repeats_params_correctly():
14
+ def sample_fn(param):
15
+ return np.array([param, param + 1.0])
16
+
17
+ params, samples = bifurcation_diagram([0.0, 10.0], sample_fn)
18
+ np.testing.assert_allclose(params, [0.0, 0.0, 10.0, 10.0])
19
+ np.testing.assert_allclose(samples, [0.0, 1.0, 10.0, 11.0])
20
+
21
+
22
+ def test_bifurcation_diagram_with_multiple_threads_matches_sequential():
23
+ def sample_fn(param):
24
+ return np.array([param, param + 1.0])
25
+
26
+ param_values = np.linspace(0.0, 10.0, 15)
27
+ params_seq, samples_seq = bifurcation_diagram(param_values, sample_fn, n_jobs=1)
28
+ params_par, samples_par = bifurcation_diagram(param_values, sample_fn, n_jobs=4, show_progress=True)
29
+ np.testing.assert_allclose(params_par, params_seq)
30
+ np.testing.assert_allclose(samples_par, samples_seq)
31
+
32
+
33
+ def test_map_bifurcation_sampler_shows_fixed_point_below_r3():
34
+ """Below r=3, the logistic map converges to a stable fixed point: every
35
+ surviving sample should be (nearly) identical."""
36
+ sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=200, n_keep=20)
37
+ samples = sampler(2.5)
38
+ assert np.ptp(samples) < 1e-6
39
+
40
+
41
+ def test_map_bifurcation_sampler_shows_period_two_cycle_above_r3():
42
+ """Just above r=3, the logistic map settles into a period-2 cycle: the
43
+ surviving samples should cluster into exactly two distinct values."""
44
+ sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=500, n_keep=20)
45
+ samples = sampler(3.2)
46
+ unique_values = np.unique(np.round(samples, decimals=4))
47
+ assert unique_values.size == 2
48
+
49
+
50
+ def test_map_bifurcation_sampler_shows_many_values_when_chaotic():
51
+ """At r=3.9 (chaotic), the surviving samples should spread over many
52
+ distinct values rather than clustering into a small cycle."""
53
+ sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=500, n_keep=50)
54
+ samples = sampler(3.9)
55
+ unique_values = np.unique(np.round(samples, decimals=3))
56
+ assert unique_values.size > 20
57
+
58
+
59
+ def test_bifurcation_diagram_with_map_sampler_over_a_parameter_sweep():
60
+ sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=200, n_keep=10)
61
+ r_values = np.linspace(2.4, 4.0, 20)
62
+ params, samples = bifurcation_diagram(r_values, sampler)
63
+ # n_keep=10 plus the trajectory's initial state (row 0) surviving the
64
+ # transient slice, times 20 parameter values.
65
+ assert params.shape == samples.shape == (220,)
66
+ assert np.all(np.isfinite(samples))
67
+ assert np.all((samples >= 0.0) & (samples <= 1.0))
68
+
69
+
70
+ def test_stroboscopic_bifurcation_sampler_on_duffing_is_finite_and_correct_shape():
71
+ omega = 1.2
72
+ period = 2.0 * np.pi / omega
73
+ sampler = stroboscopic_bifurcation_sampler(
74
+ lambda gamma: Duffing(delta=0.3, alpha=-1.0, beta=1.0, gamma=gamma, omega=omega),
75
+ state0=np.array([0.1, 0.0]),
76
+ sample_period=period,
77
+ n_transient_periods=20,
78
+ n_keep_periods=5,
79
+ component=0,
80
+ dt=0.05,
81
+ )
82
+ samples = sampler(0.3)
83
+ assert samples.shape == (5,)
84
+ assert np.all(np.isfinite(samples))
85
+
86
+
87
+ def test_stroboscopic_bifurcation_sampler_shows_fixed_point_for_weak_forcing():
88
+ """With no forcing at all, the (linear, damped) oscillator settles to a
89
+ fixed point: every stroboscopic sample should be (nearly) identical."""
90
+ omega = 1.0
91
+ period = 2.0 * np.pi / omega
92
+ sampler = stroboscopic_bifurcation_sampler(
93
+ lambda gamma: Duffing(delta=0.3, alpha=1.0, beta=0.0, gamma=gamma, omega=omega),
94
+ state0=np.array([1.0, 0.0]),
95
+ sample_period=period,
96
+ n_transient_periods=30,
97
+ n_keep_periods=5,
98
+ component=0,
99
+ dt=0.05,
100
+ )
101
+ samples = sampler(0.0)
102
+ assert np.ptp(samples) < 1e-3
103
+
104
+
105
+ @pytest.mark.slow
106
+ def test_driven_pendulum_stroboscopic_sampler_shows_period_doubling_amplitude_dependence():
107
+ """The driven pendulum's forcing-amplitude sweep is the classic
108
+ period-doubling demo: a weak forcing amplitude should settle to a
109
+ single stroboscopic value (period-1 locked to the drive), while a
110
+ strong one (the module's default, deep in the chaotic regime) should
111
+ show many distinct surviving values."""
112
+ omega_d = 2.0 / 3.0
113
+ period = 2.0 * np.pi / omega_d
114
+
115
+ def make_system(A):
116
+ return DrivenPendulum(damping=0.5, g_over_l=1.0, A=A, omega_d=omega_d)
117
+
118
+ sampler = stroboscopic_bifurcation_sampler(
119
+ make_system,
120
+ state0=np.array([0.2, 0.0]),
121
+ sample_period=period,
122
+ n_transient_periods=50,
123
+ n_keep_periods=15,
124
+ component=0,
125
+ dt=0.02,
126
+ )
127
+ weak_samples = sampler(0.5)
128
+ assert np.ptp(weak_samples) < 1e-2
129
+
130
+ strong_samples = sampler(1.5)
131
+ unique_values = np.unique(np.round(strong_samples, decimals=2))
132
+ assert unique_values.size > 5
@@ -0,0 +1,115 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.billiards import (
5
+ BunimovichStadium,
6
+ CircleBilliard,
7
+ EllipseBilliard,
8
+ RectangleBilliard,
9
+ SinaiBilliard,
10
+ TruncatedCircleBilliard,
11
+ )
12
+
13
+ ALL_BILLIARDS = [
14
+ lambda: CircleBilliard(radius=1.0),
15
+ lambda: RectangleBilliard(width=2.0, height=1.0),
16
+ lambda: SinaiBilliard(cell_size=2.0, scatterer_radius=0.5),
17
+ lambda: BunimovichStadium(radius=1.0, straight_length=2.0),
18
+ lambda: TruncatedCircleBilliard(radius=1.0, cut=0.3),
19
+ # Fewer segments than the default (2000) to keep the test suite fast; the
20
+ # polygon-approximation physics is exercised the same way either way.
21
+ lambda: EllipseBilliard(semi_major=1.5, semi_minor=1.0, n_segments=300),
22
+ ]
23
+
24
+
25
+ @pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
26
+ def test_speed_conserved_across_bounces(make_billiard):
27
+ """Specular reflection must preserve |v| = 1 at every bounce."""
28
+ billiard = make_billiard()
29
+ pos = billiard.sample_interior_point()
30
+ rng = np.random.default_rng(0)
31
+ for angle in rng.uniform(0, 2 * np.pi, size=10):
32
+ vel = np.array([np.cos(angle), np.sin(angle)])
33
+ result = billiard.simulate(pos, vel, n_bounces=200)
34
+ speeds = np.hypot(result["vx"], result["vy"])
35
+ np.testing.assert_allclose(speeds, 1.0, atol=1e-9)
36
+
37
+
38
+ @pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
39
+ def test_bounce_points_lie_within_perimeter_range(make_billiard):
40
+ """Boundary arclength coordinate s must stay within [0, perimeter)."""
41
+ billiard = make_billiard()
42
+ pos = billiard.sample_interior_point()
43
+ result = billiard.simulate(pos, np.array([1.0, 0.3]), n_bounces=200)
44
+ assert np.all(result["s"] >= -1e-6)
45
+ assert np.all(result["s"] <= billiard.perimeter() + 1e-6)
46
+
47
+
48
+ @pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
49
+ def test_sin_phi_within_unit_range(make_billiard):
50
+ billiard = make_billiard()
51
+ pos = billiard.sample_interior_point()
52
+ result = billiard.simulate(pos, np.array([0.7, 0.5]), n_bounces=200)
53
+ assert np.all(result["sin_phi"] >= -1.0 - 1e-9)
54
+ assert np.all(result["sin_phi"] <= 1.0 + 1e-9)
55
+
56
+
57
+ @pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
58
+ def test_bounce_points_on_or_inside_boundary(make_billiard):
59
+ """No bounce point should end up strictly outside the billiard's convex-ish
60
+ bounding region (a coarse sanity check against ray-tracing bugs)."""
61
+ billiard = make_billiard()
62
+ pos = billiard.sample_interior_point()
63
+ result = billiard.simulate(pos, np.array([0.4, 0.9]), n_bounces=100)
64
+ boundary = billiard.boundary_polyline()
65
+ margin = 1e-6
66
+ # boundary_polyline() separates disjoint closed components (e.g. Sinai's
67
+ # outer wall and inner scatterer) with a row of NaN; use nanmax/nanmin so
68
+ # that doesn't turn the bounding box into NaN.
69
+ assert result["x"].max() <= np.nanmax(boundary[:, 0]) + margin
70
+ assert result["x"].min() >= np.nanmin(boundary[:, 0]) - margin
71
+ assert result["y"].max() <= np.nanmax(boundary[:, 1]) + margin
72
+ assert result["y"].min() >= np.nanmin(boundary[:, 1]) - margin
73
+
74
+
75
+ def test_circle_billiard_is_integrable_constant_sin_phi():
76
+ """In a circular billiard, sin(phi) is an exact invariant of the motion."""
77
+ billiard = CircleBilliard(radius=1.0)
78
+ result = billiard.simulate(np.array([0.3, 0.0]), np.array([0.2, 1.0]), n_bounces=100)
79
+ np.testing.assert_allclose(result["sin_phi"], result["sin_phi"][0], atol=1e-8)
80
+
81
+
82
+ def test_sinai_billiard_rejects_scatterer_larger_than_cell():
83
+ with pytest.raises(ValueError):
84
+ SinaiBilliard(cell_size=2.0, scatterer_radius=1.5)
85
+
86
+
87
+ def test_truncated_circle_rejects_invalid_cut():
88
+ with pytest.raises(ValueError):
89
+ TruncatedCircleBilliard(radius=1.0, cut=1.5)
90
+
91
+
92
+ def test_ellipse_billiard_rejects_invalid_axes():
93
+ with pytest.raises(ValueError):
94
+ EllipseBilliard(semi_major=1.0, semi_minor=1.5) # major must exceed minor
95
+ with pytest.raises(ValueError):
96
+ EllipseBilliard(semi_major=-1.0, semi_minor=0.5)
97
+ with pytest.raises(ValueError):
98
+ EllipseBilliard(semi_major=1.0, semi_minor=0.0)
99
+
100
+
101
+ def test_ellipse_billiard_foci_at_known_positions():
102
+ billiard = EllipseBilliard(semi_major=1.5, semi_minor=1.0)
103
+ f1, f2 = billiard.foci()
104
+ c = np.sqrt(1.5**2 - 1.0**2)
105
+ np.testing.assert_allclose(f1, [c, 0.0])
106
+ np.testing.assert_allclose(f2, [-c, 0.0])
107
+
108
+
109
+ def test_ellipse_billiard_sin_phi_stays_bounded_by_caustic():
110
+ """A single ellipse-billiard trajectory stays tangent to one confocal
111
+ caustic, so its sin(phi) values are confined to a bounded sub-range of
112
+ [-1, 1] rather than filling it (unlike a chaotic billiard)."""
113
+ billiard = EllipseBilliard(semi_major=1.5, semi_minor=1.0, n_segments=1000)
114
+ result = billiard.simulate(billiard.sample_interior_point(), np.array([0.3, 1.0]), n_bounces=300)
115
+ assert result["sin_phi"].max() - result["sin_phi"].min() < 1.0
@@ -0,0 +1,224 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.continuous import (
5
+ Chua,
6
+ DoublePendulum,
7
+ DrivenPendulum,
8
+ Duffing,
9
+ MagneticPendulum,
10
+ RestrictedThreeBody,
11
+ Rossler,
12
+ effective_potential,
13
+ lagrange_points,
14
+ )
15
+ from physicskit.chaos.utils.metrics import energy_drift
16
+
17
+
18
+ def test_rossler_attractor_trajectory_is_finite_and_matches_rhs():
19
+ system = Rossler(a=0.2, b=0.2, c=5.7)
20
+ assert system.initial_state().tolist() == [1.0, 1.0, 1.0]
21
+ t, states = system.trajectory(n_steps=2000, dt=0.01)
22
+ assert np.all(np.isfinite(states))
23
+ assert t.shape == (2001,)
24
+
25
+ deriv = system.rhs(states[0], t[0])
26
+ assert deriv.shape == (3,)
27
+ assert np.allclose(deriv, [-states[0, 1] - states[0, 2], states[0, 0] + 0.2 * states[0, 1], 0.2 + states[0, 2] * (states[0, 0] - 5.7)])
28
+
29
+
30
+ def test_double_pendulum_and_magnetic_pendulum_rhs_match_trajectory():
31
+ dp = DoublePendulum()
32
+ state0 = dp.initial_state()
33
+ d = dp.rhs(state0, 0.0)
34
+ assert d.shape == state0.shape
35
+ assert np.all(np.isfinite(d))
36
+
37
+ mp = MagneticPendulum()
38
+ state0_mp = mp.initial_state()
39
+ d_mp = mp.rhs(state0_mp, 0.0)
40
+ assert d_mp.shape == state0_mp.shape
41
+ assert np.all(np.isfinite(d_mp))
42
+
43
+
44
+ def test_duffing_initial_state_and_rhs():
45
+ system = Duffing(delta=0.3, alpha=-1.0, beta=1.0, gamma=0.3, omega=1.2)
46
+ state0 = system.initial_state()
47
+ assert state0.shape == (2,)
48
+ d = system.rhs(state0, 0.0)
49
+ assert d.shape == (2,)
50
+ assert np.all(np.isfinite(d))
51
+ _, states = system.trajectory(state0=state0, n_steps=100, dt=0.01)
52
+ assert np.all(np.isfinite(states))
53
+
54
+
55
+ def test_chua_trajectory_is_finite_and_bounded():
56
+ """Chua's circuit is dissipative and settles onto a bounded attractor;
57
+ integrating it must never blow up to infinity/NaN."""
58
+ system = Chua()
59
+ _, states = system.trajectory(n_steps=5000, dt=0.01)
60
+ assert np.all(np.isfinite(states))
61
+ # The classic double-scroll attractor stays within a modest, known range
62
+ # of the parameters used here; a much looser bound just guards against
63
+ # numerical blow-up regressions.
64
+ assert np.all(np.abs(states) < 20.0)
65
+
66
+
67
+ def test_chua_rhs_at_origin_is_zero():
68
+ """The origin is an equilibrium of Chua's circuit: h(0) = 0, so
69
+ rhs((0, 0, 0)) == (0, 0, 0)."""
70
+ system = Chua()
71
+ derivative = system.rhs(np.array([0.0, 0.0, 0.0]), 0.0)
72
+ np.testing.assert_allclose(derivative, [0.0, 0.0, 0.0], atol=1e-12)
73
+
74
+
75
+ def test_chua_trajectory_is_dissipative():
76
+ """Chua's circuit is dissipative (its equilibria are unstable but phase
77
+ volume contracts on average): starting near, but not at, the origin, the
78
+ trajectory should not simply run away to the bounding box unboundedly."""
79
+ system = Chua()
80
+ _, states = system.trajectory(state0=np.array([0.1, 0.0, 0.0]), n_steps=5000, dt=0.01)
81
+ assert np.all(np.isfinite(states))
82
+ assert np.all(np.abs(states) < 20.0)
83
+
84
+
85
+ def test_restricted_three_body_jacobi_constant_is_conserved():
86
+ """The Jacobi constant is an exact invariant of CR3BP motion; a good RK4
87
+ integration should conserve it to a tight tolerance."""
88
+ system = RestrictedThreeBody()
89
+ t, states = system.trajectory(dt=0.001, n_steps=5000)
90
+ drift = energy_drift(t, states, system.jacobi_constant)
91
+ assert np.max(np.abs(drift)) < 1e-4
92
+
93
+
94
+ def test_restricted_three_body_arenstorf_orbit_is_approximately_periodic():
95
+ """The default initial condition is the classic Arenstorf periodic orbit:
96
+ integrating for exactly one period should return close to the start."""
97
+ system = RestrictedThreeBody()
98
+ state0 = system.initial_state()
99
+ period = 17.0652165601579625588917206249
100
+ dt = 0.0001
101
+ _, states = system.trajectory(dt=dt, n_steps=round(period / dt))
102
+ assert np.max(np.abs(states[-1] - state0)) < 0.01
103
+
104
+
105
+ def test_restricted_three_body_rhs_matches_known_acceleration_at_a_point():
106
+ """Spot-check the CR3BP right-hand side at a simple point against a
107
+ hand-computed value, to catch sign/algebra errors in the Coriolis terms."""
108
+ system = RestrictedThreeBody(mu=0.1)
109
+ # On the x-axis with zero velocity, the Coriolis terms vanish and the
110
+ # y-acceleration is exactly zero by symmetry.
111
+ derivative = system.rhs(np.array([0.5, 0.0, 0.0, 0.0]), 0.0)
112
+ assert derivative[1] == 0.0 # dy/dt = vy = 0
113
+ assert derivative[3] == 0.0 # dvy/dt = 0 by symmetry on the x-axis
114
+
115
+
116
+ def test_lagrange_points_collinear_points_are_ordered_and_at_equilibrium():
117
+ """L3 < -mu (primary) < L1 < 1-mu (secondary) < L2 on the x-axis, and
118
+ each collinear point must be a genuine root of the rotating-frame
119
+ x-acceleration (zero net force at rest)."""
120
+ mu = 0.012277471
121
+ points = lagrange_points(mu)
122
+ x_l1, x_l2, x_l3 = points[0, 0], points[1, 0], points[2, 0]
123
+ assert x_l3 < -mu < x_l1 < (1.0 - mu) < x_l2
124
+
125
+ system = RestrictedThreeBody(mu=mu)
126
+ for x in (x_l1, x_l2, x_l3):
127
+ derivative = system.rhs(np.array([x, 0.0, 0.0, 0.0]), 0.0)
128
+ assert abs(derivative[2]) < 1e-8 # zero net x-acceleration at rest
129
+
130
+
131
+ def test_lagrange_points_l4_l5_form_equilateral_triangles_with_primaries():
132
+ """L4 and L5 are, by construction, equidistant from both primaries at
133
+ exactly the primary-primary separation (unit distance in these units)."""
134
+ mu = 0.012277471
135
+ points = lagrange_points(mu)
136
+ primary = np.array([-mu, 0.0])
137
+ secondary = np.array([1.0 - mu, 0.0])
138
+ for lx, ly in points[3:]:
139
+ p = np.array([lx, ly])
140
+ assert np.hypot(*(p - primary)) == pytest.approx(1.0, abs=1e-10)
141
+ assert np.hypot(*(p - secondary)) == pytest.approx(1.0, abs=1e-10)
142
+
143
+
144
+ def test_effective_potential_matches_jacobi_constant_formula():
145
+ """RestrictedThreeBody.jacobi_constant is defined as
146
+ ``2*effective_potential(x, y, mu) - speed**2``; check the two stay
147
+ consistent (a regression guard against the two formulas drifting apart
148
+ if either is edited independently)."""
149
+ system = RestrictedThreeBody(mu=0.1)
150
+ state = np.array([0.3, 0.4, 0.1, -0.2])
151
+ x, y, vx, vy = state
152
+ expected = 2.0 * effective_potential(x, y, system.mu) - (vx * vx + vy * vy)
153
+ assert system.jacobi_constant(state) == pytest.approx(expected)
154
+
155
+
156
+ def test_effective_potential_is_vectorized_over_a_grid():
157
+ """effective_potential must work directly on np.meshgrid output, since
158
+ that's how zero-velocity-curve plots use it."""
159
+ x = np.linspace(-1.5, 1.5, 5)
160
+ y = np.linspace(-1.5, 1.5, 5)
161
+ xx, yy = np.meshgrid(x, y)
162
+ omega = effective_potential(xx, yy, 0.012277471)
163
+ assert omega.shape == xx.shape
164
+ assert np.all(np.isfinite(omega))
165
+
166
+
167
+ def test_effective_potential_is_minimized_at_lagrange_points_4_and_5():
168
+ """L4/L5 are the global minima of the effective potential; nearby points
169
+ (that aren't also Lagrange points) must have a strictly larger value."""
170
+ mu = 0.012277471
171
+ lx, ly = lagrange_points(mu)[3]
172
+ omega_l4 = effective_potential(lx, ly, mu)
173
+ for dx, dy in [(0.05, 0.0), (-0.05, 0.0), (0.0, 0.05), (0.0, -0.05)]:
174
+ assert effective_potential(lx + dx, ly + dy, mu) > omega_l4
175
+
176
+
177
+ def test_driven_pendulum_rhs_matches_hand_derivation():
178
+ """Spot-check the driven-pendulum right-hand side at a simple point."""
179
+ system = DrivenPendulum(damping=0.5, g_over_l=1.0, A=1.5, omega_d=2.0 / 3.0)
180
+ derivative = system.rhs(np.array([0.0, 0.0]), 0.0)
181
+ # theta=0, omega=0, t=0: dtheta/dt=0, domega/dt = A*cos(0) = A
182
+ assert derivative[0] == 0.0
183
+ assert derivative[1] == pytest.approx(1.5)
184
+
185
+
186
+ def test_driven_pendulum_trajectory_is_finite():
187
+ """The classic chaotic parameter set should integrate stably (bounded
188
+ omega; theta itself is allowed to wind arbitrarily as the pendulum
189
+ goes over the top, which happens for these parameters)."""
190
+ system = DrivenPendulum()
191
+ _, states = system.trajectory(n_steps=5000, dt=0.02)
192
+ assert np.all(np.isfinite(states))
193
+ assert np.all(np.abs(states[:, 1]) < 20.0)
194
+
195
+
196
+ def test_magnetic_pendulum_settles_near_a_magnet():
197
+ """Starting the (damped) bob very close to one magnet should leave it
198
+ settled even closer to that same magnet."""
199
+ system = MagneticPendulum()
200
+ magnet = system.magnet_positions[0]
201
+ state0 = np.array([magnet[0] + 0.05, magnet[1], 0.0, 0.0])
202
+ _, states = system.trajectory(state0=state0, n_steps=5000, dt=0.02)
203
+ final_dist = np.hypot(*(states[-1, :2] - magnet))
204
+ initial_dist = np.hypot(*(state0[:2] - magnet))
205
+ assert final_dist < initial_dist
206
+
207
+
208
+ def test_magnetic_pendulum_shows_sensitive_dependence_near_a_basin_boundary():
209
+ """Two very close starting points can settle near different magnets --
210
+ the hallmark of a fractal basin boundary."""
211
+ system = MagneticPendulum()
212
+ outcomes = set()
213
+ for x0 in np.linspace(-0.05, 0.05, 11):
214
+ _, states = system.trajectory(state0=np.array([x0, 0.0, 0.0, 0.0]), n_steps=5000, dt=0.02)
215
+ final = states[-1, :2]
216
+ dists = np.hypot(*(system.magnet_positions - final).T)
217
+ outcomes.add(int(np.argmin(dists)))
218
+ assert len(outcomes) > 1
219
+
220
+
221
+ def test_magnetic_pendulum_trajectory_is_finite():
222
+ system = MagneticPendulum()
223
+ _, states = system.trajectory(n_steps=5000, dt=0.02)
224
+ assert np.all(np.isfinite(states))
@@ -0,0 +1,69 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.maps import HenonMap
5
+ from physicskit.chaos.utils.dimension import box_counting_dimension, correlation_dimension
6
+
7
+
8
+ def _cantor_set_points(n_iter=10):
9
+ """Midpoints of the intervals surviving `n_iter` steps of the middle-thirds
10
+ Cantor set construction; its exact box-counting/correlation dimension is
11
+ log(2)/log(3), giving a ground-truth target for both estimators."""
12
+ intervals = [(0.0, 1.0)]
13
+ for _ in range(n_iter):
14
+ new_intervals = []
15
+ for a, b in intervals:
16
+ third = (b - a) / 3.0
17
+ new_intervals.append((a, a + third))
18
+ new_intervals.append((b - third, b))
19
+ intervals = new_intervals
20
+ return np.array([[(a + b) / 2.0] for a, b in intervals])
21
+
22
+
23
+ CANTOR_DIMENSION = np.log(2) / np.log(3)
24
+
25
+
26
+ def test_box_counting_dimension_matches_cantor_set_exact_value():
27
+ points = _cantor_set_points(n_iter=10)
28
+ d0, epsilons, counts = box_counting_dimension(points, n_scales=15, eps_min=3.0**-8, eps_max=3.0**-2)
29
+ assert d0 == pytest.approx(CANTOR_DIMENSION, abs=0.05)
30
+ assert epsilons.shape == (15,)
31
+ assert counts.shape == (15,)
32
+ assert np.all(counts >= 1.0)
33
+
34
+
35
+ def test_correlation_dimension_matches_cantor_set_exact_value():
36
+ points = _cantor_set_points(n_iter=10)
37
+ d2, _, correlation_sums = correlation_dimension(points, n_scales=15, eps_min=3.0**-8, eps_max=3.0**-2)
38
+ assert d2 == pytest.approx(CANTOR_DIMENSION, abs=0.05)
39
+ assert np.all((correlation_sums >= 0.0) & (correlation_sums <= 1.0))
40
+
41
+
42
+ def test_box_counting_dimension_of_a_line_segment_is_about_one():
43
+ rng = np.random.default_rng(0)
44
+ points = np.column_stack([rng.uniform(size=5000), np.zeros(5000)])
45
+ d0, _, _ = box_counting_dimension(points)
46
+ assert d0 == pytest.approx(1.0, abs=0.15)
47
+
48
+
49
+ def test_box_counting_dimension_of_a_filled_square_approaches_two():
50
+ """With enough points to avoid box-counting's finite-sample saturation,
51
+ a uniformly-filled 2D region should have dimension close to 2."""
52
+ rng = np.random.default_rng(0)
53
+ points = rng.uniform(size=(100000, 2))
54
+ d0, _, _ = box_counting_dimension(points)
55
+ assert d0 == pytest.approx(2.0, abs=0.15)
56
+
57
+
58
+ def test_henon_attractor_dimension_matches_known_literature_value():
59
+ """The classic Henon attractor (a=1.4, b=0.3) has a well-documented
60
+ fractal dimension of approximately 1.2-1.3."""
61
+ system = HenonMap(a=1.4, b=0.3)
62
+ traj = system.trajectory(np.array([0.0, 0.0]), n_iter=8000)
63
+ traj = traj[500:] # discard transient
64
+
65
+ d0, _, _ = box_counting_dimension(traj)
66
+ assert 1.1 < d0 < 1.4
67
+
68
+ d2, _, _ = correlation_dimension(traj)
69
+ assert 1.0 < d2 < 1.4
@@ -0,0 +1,90 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.billiards import CircleBilliard, TruncatedCircleBilliard
5
+ from physicskit.chaos.systems.continuous import Lorenz
6
+ from physicskit.chaos.visualizers.divergence import (
7
+ billiard_trajectory_divergence,
8
+ plot_billiard_divergence,
9
+ plot_trajectory_ensemble,
10
+ trajectory_ensemble,
11
+ )
12
+ from physicskit.chaos.visualizers.dynamic_plots import animate_billiard_divergence
13
+
14
+
15
+ def test_circle_billiard_separation_is_exactly_conserved():
16
+ """The circle is integrable (sin(phi) is an exact invariant), so two
17
+ nearby rays' separation should stay essentially unchanged forever --
18
+ no exponential growth, because there is no chaos to be sensitive to."""
19
+ billiard = CircleBilliard(radius=1.0)
20
+ delta_0 = 1e-8
21
+ _, separation = billiard_trajectory_divergence(
22
+ billiard,
23
+ pos=billiard.sample_interior_point(),
24
+ vel=(0.2, 1.0),
25
+ delta_0=delta_0,
26
+ n_bounces=100,
27
+ )
28
+ assert np.allclose(separation, delta_0, rtol=1e-6)
29
+
30
+
31
+ @pytest.mark.parametrize("cut", [0.3, 0.05, 0.02])
32
+ def test_truncated_circle_billiard_diverges_for_any_cut(cut):
33
+ """Even a tiny truncation destroys integrability: separation must grow
34
+ by many orders of magnitude over enough bounces, for any cut depth."""
35
+ billiard = TruncatedCircleBilliard(radius=1.0, cut=cut)
36
+ delta_0 = 1e-8
37
+ _, separation = billiard_trajectory_divergence(
38
+ billiard,
39
+ pos=billiard.sample_interior_point(),
40
+ vel=(0.2, 1.0),
41
+ delta_0=delta_0,
42
+ n_bounces=300,
43
+ )
44
+ assert separation.max() > 1e-3
45
+
46
+
47
+ def test_billiard_trajectory_divergence_shapes():
48
+ billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
49
+ bounce, separation = billiard_trajectory_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=50)
50
+ assert bounce.shape == (50,)
51
+ assert separation.shape == (50,)
52
+ assert np.array_equal(bounce, np.arange(1, 51))
53
+
54
+
55
+ def test_plot_billiard_divergence_estimates_positive_exponent_for_chaotic_billiard():
56
+ billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
57
+ _, ax, lam = plot_billiard_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=100)
58
+ assert lam > 0.0
59
+ assert ax.get_xlabel() == "bounce number k"
60
+
61
+
62
+ def test_animate_billiard_divergence_runs():
63
+ billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
64
+ anim = animate_billiard_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=30, trail=10)
65
+ anim._draw_frame(20)
66
+
67
+
68
+ def test_trajectory_ensemble_shape_and_zero_spread_members_share_the_reference():
69
+ """With spread=0, every ensemble member is launched from exactly the
70
+ same state as the reference, so all members' trajectories must be
71
+ identical to each other."""
72
+ system = Lorenz()
73
+ all_states = trajectory_ensemble(system, n_members=5, spread=0.0, dt=0.01, n_steps=50, seed=0)
74
+ assert all_states.shape == (5, 51, system.dim)
75
+ for member in all_states[1:]:
76
+ np.testing.assert_allclose(member, all_states[0])
77
+
78
+
79
+ def test_trajectory_ensemble_members_start_within_the_requested_spread():
80
+ system = Lorenz()
81
+ state0 = system.initial_state()
82
+ all_states = trajectory_ensemble(system, state0=state0, n_members=10, spread=1e-3, dt=0.01, n_steps=10, seed=0)
83
+ offsets = np.linalg.norm(all_states[:, 0, :] - state0, axis=1)
84
+ assert np.all(offsets < 1e-3 + 1e-12)
85
+
86
+
87
+ def test_plot_trajectory_ensemble_draws_one_line_per_member():
88
+ system = Lorenz()
89
+ _, ax = plot_trajectory_ensemble(system, n_members=6, spread=1e-4, dt=0.01, n_steps=20, seed=0)
90
+ assert len(ax.get_lines()) == 6