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,508 @@
1
+ """Matplotlib animation helpers.
2
+
3
+ :class:`SideBySideAnimator` is the production entry point: given a
4
+ :class:`physicskit.classical.core.base_system.SimulationResult`, it renders physical
5
+ space motion on the left panel and a live-updating phase-space
6
+ trajectory or energy-vs-time trace on the right, driven by a single
7
+ :class:`~matplotlib.animation.FuncAnimation`. Physical-space rendering
8
+ is necessarily system-specific (pendulum rods, lattice-chain beads, an
9
+ orbit, a 3D rigid body), so it is supplied via two small callbacks;
10
+ :func:`orbit_trace_animation` and :func:`pendulum_animation` are
11
+ ready-made convenience wrappers for the two most common cases.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from collections.abc import Callable
17
+ from typing import Optional
18
+
19
+ import matplotlib.pyplot as plt
20
+ import numpy as np
21
+ from matplotlib.animation import FuncAnimation
22
+
23
+ __all__ = [
24
+ "SideBySideAnimator",
25
+ "orbit_trace_animation",
26
+ "pendulum_animation",
27
+ "animate_elastic_pendulum",
28
+ "animate_rigid_body_tumble",
29
+ "animate_eulers_disk",
30
+ "animate_rattleback",
31
+ ]
32
+
33
+
34
+ class SideBySideAnimator:
35
+ """Side-by-side animation: physical space (left) + phase-space or
36
+ energy diagnostics (right), built from a
37
+ :class:`~physicskit.classical.core.base_system.SimulationResult`.
38
+
39
+ Parameters
40
+ ----------
41
+ result : SimulationResult
42
+ Output of ``system.integrate(...)``.
43
+ init_physical : Callable[[Axes], artists]
44
+ Called once with the left-panel axes; must create and return
45
+ whatever Matplotlib artists it will later update (a tuple/list).
46
+ update_physical : Callable[[artists, SimulationResult, int], artists]
47
+ Called every frame with the artists from ``init_physical``, the
48
+ full result, and the current sample index; must update the
49
+ artists in place and return them.
50
+ mode : {"phase", "energy"}
51
+ Right-panel content: a growing (q, p) trace for one degree of
52
+ freedom, or a growing energy(t) trace.
53
+ dof_index : int
54
+ Which column of ``result.q``/``result.p`` to plot when mode="phase"
55
+ and the system has more than one degree of freedom.
56
+ physical_3d : bool
57
+ If True, the left axes are created with a 3D projection.
58
+ stride : int
59
+ Render only every ``stride``-th recorded sample (for long runs).
60
+ """
61
+
62
+ def __init__(
63
+ self,
64
+ result,
65
+ init_physical: Callable,
66
+ update_physical: Callable,
67
+ mode: str = "phase",
68
+ dof_index: int = 0,
69
+ figsize=(11, 5),
70
+ physical_3d: bool = False,
71
+ stride: int = 1,
72
+ left_title: str = "Physical space",
73
+ ):
74
+ if mode not in ("phase", "energy"):
75
+ raise ValueError("mode must be 'phase' or 'energy'")
76
+ self.result = result
77
+ self.update_physical = update_physical
78
+ self.mode = mode
79
+ self.dof_index = dof_index
80
+ self.stride = max(1, stride)
81
+ self.frame_indices = np.arange(0, len(result.t), self.stride)
82
+
83
+ self.fig = plt.figure(figsize=figsize)
84
+ self.ax_left = self.fig.add_subplot(1, 2, 1, projection="3d" if physical_3d else None)
85
+ self.ax_left.set_title(left_title)
86
+ self.ax_right = self.fig.add_subplot(1, 2, 2)
87
+ self._physical_artists = init_physical(self.ax_left)
88
+ self._setup_right_panel()
89
+ self.anim: Optional[FuncAnimation] = None
90
+
91
+ def _dof(self, arr):
92
+ return arr[:, self.dof_index] if arr.ndim == 2 else arr
93
+
94
+ def _setup_right_panel(self):
95
+ if self.mode == "phase":
96
+ q, p = self._dof(self.result.q), self._dof(self.result.p)
97
+ self.ax_right.plot(q, p, color="lightgray", linewidth=0.7)
98
+ self.ax_right.set_xlabel("q")
99
+ self.ax_right.set_ylabel("p")
100
+ self.ax_right.set_title("Phase space")
101
+ (self._right_trace,) = self.ax_right.plot([], [], color="crimson", linewidth=1.3)
102
+ (self._right_point,) = self.ax_right.plot([], [], "o", color="crimson")
103
+ else:
104
+ e = self.result.energy
105
+ self.ax_right.set_xlabel("t")
106
+ self.ax_right.set_ylabel("Energy")
107
+ self.ax_right.set_title("Energy")
108
+ self.ax_right.set_xlim(self.result.t[0], self.result.t[-1])
109
+ pad = 0.05 * (np.max(e) - np.min(e) + 1e-12)
110
+ self.ax_right.set_ylim(np.min(e) - pad, np.max(e) + pad)
111
+ (self._right_trace,) = self.ax_right.plot([], [], color="crimson")
112
+
113
+ def _update_right(self, idx: int):
114
+ if self.mode == "phase":
115
+ q, p = self._dof(self.result.q)[: idx + 1], self._dof(self.result.p)[: idx + 1]
116
+ self._right_trace.set_data(q, p)
117
+ self._right_point.set_data(q[-1:], p[-1:])
118
+ return [self._right_trace, self._right_point]
119
+ self._right_trace.set_data(self.result.t[: idx + 1], self.result.energy[: idx + 1])
120
+ return [self._right_trace]
121
+
122
+ def _frame(self, frame_i: int):
123
+ idx = self.frame_indices[frame_i]
124
+ left_artists = self.update_physical(self._physical_artists, self.result, idx)
125
+ right_artists = self._update_right(idx)
126
+ return list(left_artists) + list(right_artists)
127
+
128
+ def build(self, interval: int = 30, blit: bool = False) -> FuncAnimation:
129
+ """Construct the underlying ``FuncAnimation``.
130
+
131
+ Parameters
132
+ ----------
133
+ interval : int
134
+ Delay between frames, in milliseconds.
135
+ blit : bool
136
+ Forwarded to ``FuncAnimation``.
137
+
138
+ Returns
139
+ -------
140
+ matplotlib.animation.FuncAnimation
141
+ Note for docs/gallery use: Sphinx-Gallery's animation scraper
142
+ only detects bare ``matplotlib.animation.Animation`` objects
143
+ among a script's globals, not a ``SideBySideAnimator`` wrapping
144
+ one. Assign *this* return value (not the ``SideBySideAnimator``
145
+ instance) to the variable you want rendered as an animated GIF,
146
+ e.g. ``anim = SideBySideAnimator(...).build()``.
147
+ """
148
+ self.anim = FuncAnimation(self.fig, self._frame, frames=len(self.frame_indices), interval=interval, blit=blit)
149
+ return self.anim
150
+
151
+ def save(self, filename: str, fps: int = None, **kwargs):
152
+ """Save the animation, building it first if needed.
153
+
154
+ ``fps`` defaults to 30 for the common case of a string/None
155
+ ``writer`` (where Matplotlib constructs the writer for you);
156
+ pass ``fps=None`` explicitly (the default here) when supplying
157
+ an already-configured ``writer`` instance via ``kwargs`` --
158
+ Matplotlib rejects ``fps`` together with a writer instance,
159
+ since the instance already carries its own fps.
160
+
161
+ Parameters
162
+ ----------
163
+ filename : str
164
+ Output path.
165
+ fps : int, optional
166
+ Frame rate; see above for when to leave this ``None``.
167
+ **kwargs
168
+ Forwarded to ``FuncAnimation.save`` (e.g. ``writer=...``).
169
+ """
170
+ if self.anim is None:
171
+ self.build()
172
+ if fps is None and "writer" not in kwargs:
173
+ fps = 30
174
+ if fps is not None:
175
+ kwargs["fps"] = fps
176
+ self.anim.save(filename, **kwargs)
177
+
178
+
179
+ def pendulum_animation(result, positions_func: Callable[[np.ndarray], np.ndarray], **kwargs) -> SideBySideAnimator:
180
+ """Ready-made :class:`SideBySideAnimator` for rod-and-bob systems
181
+ (single or double pendulum, bead on a hoop).
182
+
183
+ ``positions_func(q) -> flat array of (x, y) pairs`` maps a
184
+ configuration ``q`` to the pivot-to-bob chain of Cartesian points
185
+ to draw as connected rods (e.g. ``DoublePendulum.positions``).
186
+
187
+ Parameters
188
+ ----------
189
+ result : physicskit.classical.core.base_system.SimulationResult
190
+ Output of ``system.integrate(...)``.
191
+ positions_func : callable
192
+ ``q -> flat array of (x, y) pairs``.
193
+ **kwargs
194
+ Forwarded to :class:`SideBySideAnimator`.
195
+
196
+ Returns
197
+ -------
198
+ SideBySideAnimator
199
+ """
200
+ p0 = positions_func(result.q[0]).reshape(-1, 2)
201
+ reach = np.abs(p0).max() * 1.3 + 1e-6
202
+
203
+ def init_physical(ax):
204
+ ax.set_xlim(-reach, reach)
205
+ ax.set_ylim(-reach, reach)
206
+ ax.set_aspect("equal")
207
+ (rod,) = ax.plot([], [], "o-", lw=2, color="steelblue")
208
+ (trace,) = ax.plot([], [], lw=0.7, color="orange", alpha=0.6)
209
+ return rod, trace
210
+
211
+ def update_physical(artists, res, idx):
212
+ rod, trace = artists
213
+ pts = positions_func(res.q[idx]).reshape(-1, 2)
214
+ xs = np.concatenate([[0.0], pts[:, 0]])
215
+ ys = np.concatenate([[0.0], pts[:, 1]])
216
+ rod.set_data(xs, ys)
217
+ hist = np.array([positions_func(q).reshape(-1, 2)[-1] for q in res.q[: idx + 1 : max(1, idx // 200 or 1)]])
218
+ trace.set_data(hist[:, 0], hist[:, 1])
219
+ return rod, trace
220
+
221
+ return SideBySideAnimator(result, init_physical, update_physical, left_title="Pendulum", **kwargs)
222
+
223
+
224
+ def orbit_trace_animation(result, **kwargs) -> SideBySideAnimator:
225
+ """Ready-made :class:`SideBySideAnimator` for a 2D central-force orbit.
226
+
227
+ E.g. :class:`physicskit.classical.systems.newtonian.KeplerSystem`: a growing
228
+ orbital path on the left, phase/energy diagnostics on the right.
229
+
230
+ Parameters
231
+ ----------
232
+ result : physicskit.classical.core.base_system.SimulationResult
233
+ Output of ``system.integrate(...)``.
234
+ **kwargs
235
+ Forwarded to :class:`SideBySideAnimator`.
236
+
237
+ Returns
238
+ -------
239
+ SideBySideAnimator
240
+ """
241
+ q = result.q
242
+ reach = np.abs(q).max() * 1.2 + 1e-6
243
+
244
+ def init_physical(ax):
245
+ ax.set_xlim(-reach, reach)
246
+ ax.set_ylim(-reach, reach)
247
+ ax.set_aspect("equal")
248
+ ax.plot(0, 0, "o", color="gold", markersize=10)
249
+ (path,) = ax.plot([], [], color="steelblue", lw=1.0)
250
+ (body,) = ax.plot([], [], "o", color="steelblue", markersize=6)
251
+ return path, body
252
+
253
+ def update_physical(artists, res, idx):
254
+ path, body = artists
255
+ path.set_data(res.q[: idx + 1, 0], res.q[: idx + 1, 1])
256
+ body.set_data(res.q[idx : idx + 1, 0], res.q[idx : idx + 1, 1])
257
+ return path, body
258
+
259
+ return SideBySideAnimator(result, init_physical, update_physical, left_title="Orbit", **kwargs)
260
+
261
+
262
+ def animate_elastic_pendulum(system, result, interval: int = 30, trail: int = 200, n_coils: int = 12, coil_width: float = 0.06) -> FuncAnimation:
263
+ """Animate a spring (elastic) pendulum: a zigzag coil connecting the
264
+ fixed pivot to the swinging, stretching bob.
265
+
266
+ Parameters
267
+ ----------
268
+ system : physicskit.classical.systems.lagrangian.ElasticPendulum
269
+ Pendulum the `result` came from (used for its natural length `L0`).
270
+ result : physicskit.classical.core.base_system.SimulationResult
271
+ Output of ``system.integrate(...)``; ``result.q`` columns are
272
+ ``(s, theta)``.
273
+ interval : int, default 30
274
+ Delay between animation frames, in milliseconds.
275
+ trail : int, default 200
276
+ Number of most-recent samples kept visible as a trail behind the bob.
277
+ n_coils : int, default 12
278
+ Number of zigzag half-periods drawn along the spring.
279
+ coil_width : float, default 0.06
280
+ Zigzag amplitude, as a fraction of the natural length `L0`.
281
+
282
+ Returns
283
+ -------
284
+ matplotlib.animation.FuncAnimation
285
+ """
286
+ s, theta = result.q[:, 0], result.q[:, 1]
287
+ r = system.L0 + s
288
+ x = r * np.sin(theta)
289
+ y = -r * np.cos(theta)
290
+
291
+ reach = float(np.max(r)) * 1.15 + 1e-6
292
+ fig, ax = plt.subplots(figsize=(6, 6))
293
+ ax.set_xlim(-reach, reach)
294
+ ax.set_ylim(-reach, reach)
295
+ ax.set_aspect("equal")
296
+ ax.set_title("Elastic pendulum")
297
+
298
+ (trail_line,) = ax.plot([], [], color="orange", lw=0.8, alpha=0.7)
299
+ (spring,) = ax.plot([], [], "-", color="steelblue", lw=1.3)
300
+ (bob,) = ax.plot([], [], "o", color="crimson", markersize=10)
301
+
302
+ n_pts = 2 * n_coils + 1
303
+ zigzag = coil_width * system.L0 * (-1.0) ** np.arange(n_pts)
304
+ zigzag[0] = zigzag[-1] = 0.0
305
+ frac = np.linspace(0.0, 1.0, n_pts)
306
+
307
+ n = result.t.shape[0]
308
+
309
+ def update(frame: int) -> tuple:
310
+ lo = max(0, frame - trail)
311
+ trail_line.set_data(x[lo : frame + 1], y[lo : frame + 1])
312
+
313
+ rx, ry = x[frame], y[frame]
314
+ perp = np.array([-ry, rx]) / (r[frame] + 1e-12)
315
+ spring.set_data(frac * rx + perp[0] * zigzag, frac * ry + perp[1] * zigzag)
316
+ bob.set_data([rx], [ry])
317
+ return trail_line, spring, bob
318
+
319
+ anim = FuncAnimation(fig, update, frames=n, interval=interval, blit=False)
320
+ return anim
321
+
322
+
323
+ def animate_rigid_body_tumble(top, result, half_extents=(1.0, 0.6, 0.3), interval: int = 30, stride: int = 1) -> FuncAnimation:
324
+ """Animate an :class:`~physicskit.classical.systems.rotations.EulerTop`'s
325
+ principal-axis box tumbling in the lab frame -- a literal picture of
326
+ the Dzhanibekov effect / tennis-racket theorem.
327
+
328
+ Renders the rigid body as a simple wireframe box whose three edge
329
+ half-lengths are chosen only to make the three principal axes visually
330
+ distinguishable (they are *not* derived from the actual moments of
331
+ inertia, which would require a full mass distribution, not just three
332
+ scalars). Each frame rotates the box's body-frame vertices into the lab
333
+ frame with ``top.rotation_matrix(state)``, built from the integrated
334
+ orientation quaternion -- so a spin started dominantly about the
335
+ intermediate-inertia axis is seen visibly flipping end-over-end.
336
+
337
+ Parameters
338
+ ----------
339
+ top : physicskit.classical.systems.rotations.EulerTop
340
+ The system that produced `result` (used for
341
+ :meth:`~physicskit.classical.systems.rotations.EulerTop.rotation_matrix`).
342
+ result : physicskit.classical.core.base_system.SimulationResult
343
+ Output of ``top.integrate(...)``; ``result.y[:, :7]`` must hold
344
+ ``(w1, w2, w3, qw, qx, qy, qz)``.
345
+ half_extents : tuple of float, default (1.0, 0.6, 0.3)
346
+ Box half-widths along the body's (1, 2, 3) principal axes.
347
+ interval : int, default 30
348
+ Delay between animation frames, in milliseconds.
349
+ stride : int, default 1
350
+ Render only every ``stride``-th recorded sample.
351
+
352
+ Returns
353
+ -------
354
+ matplotlib.animation.FuncAnimation
355
+ """
356
+ a, b, c = half_extents
357
+ verts_body = np.array([[sx * a, sy * b, sz * c] for sx in (-1, 1) for sy in (-1, 1) for sz in (-1, 1)])
358
+ edges = [(i, j) for i in range(8) for j in range(i + 1, 8) if np.sum(verts_body[i] != verts_body[j]) == 1]
359
+
360
+ fig = plt.figure(figsize=(6, 6))
361
+ ax = fig.add_subplot(projection="3d")
362
+ reach = float(np.linalg.norm(half_extents)) * 1.2
363
+ ax.set_xlim(-reach, reach)
364
+ ax.set_ylim(-reach, reach)
365
+ ax.set_zlim(-reach, reach)
366
+ ax.set_title("Rigid-body tumble (Dzhanibekov effect)")
367
+
368
+ lines = [ax.plot([], [], [], color="steelblue", lw=1.5)[0] for _ in edges]
369
+
370
+ frame_indices = np.arange(0, result.y.shape[0], stride)
371
+
372
+ def update(frame_i: int) -> tuple:
373
+ idx = frame_indices[frame_i]
374
+ R = top.rotation_matrix(result.y[idx])
375
+ verts_world = verts_body @ R.T
376
+ for line, (i, j) in zip(lines, edges):
377
+ line.set_data_3d(
378
+ [verts_world[i, 0], verts_world[j, 0]],
379
+ [verts_world[i, 1], verts_world[j, 1]],
380
+ [verts_world[i, 2], verts_world[j, 2]],
381
+ )
382
+ return tuple(lines)
383
+
384
+ anim = FuncAnimation(fig, update, frames=len(frame_indices), interval=interval, blit=False)
385
+ return anim
386
+
387
+
388
+ def animate_eulers_disk(system, result, radius: float = 1.0, interval: int = 30, trail: int = 400) -> FuncAnimation:
389
+ """Animate an :class:`~physicskit.classical.systems.rotations.EulersDisk`'s
390
+ rolling contact point from above, spiraling inward and precessing
391
+ faster as the disk runs down.
392
+
393
+ The contact point's horizontal position is modeled as
394
+ ``radius * sin(theta) * (cos(phi), sin(phi))`` -- as `theta` shrinks
395
+ toward the finite-time collapse (see
396
+ :class:`~physicskit.classical.systems.rotations.EulersDisk`), the spiral
397
+ visibly tightens toward the center while `phi` (and hence the visible
398
+ wobble/rattle rate) advances faster and faster.
399
+
400
+ Parameters
401
+ ----------
402
+ system : physicskit.classical.systems.rotations.EulersDisk
403
+ System the `result` came from (used for `radius` only implicitly,
404
+ via the caller; kept for a consistent ``(system, result, ...)``
405
+ signature with the other animators here).
406
+ result : physicskit.classical.core.base_system.SimulationResult
407
+ Output of ``system.integrate(...)``; ``result.y`` columns are
408
+ ``(theta, phi)``.
409
+ radius : float, default 1.0
410
+ Visual disk radius (arbitrary; the model only fixes `theta`, `phi`).
411
+ interval : int, default 30
412
+ Delay between animation frames, in milliseconds.
413
+ trail : int, default 400
414
+ Number of most-recent samples kept visible as a trail.
415
+
416
+ Returns
417
+ -------
418
+ matplotlib.animation.FuncAnimation
419
+ """
420
+ theta, phi = result.y[:, 0], result.y[:, 1]
421
+ contact_x = radius * np.sin(theta) * np.cos(phi)
422
+ contact_y = radius * np.sin(theta) * np.sin(phi)
423
+
424
+ fig, ax = plt.subplots(figsize=(6, 6))
425
+ reach = radius * 1.1
426
+ ax.set_xlim(-reach, reach)
427
+ ax.set_ylim(-reach, reach)
428
+ ax.set_aspect("equal")
429
+ ax.set_title("Euler's disk: contact point (top view)")
430
+
431
+ (trail_line,) = ax.plot([], [], color="steelblue", lw=0.8, alpha=0.7)
432
+ (point,) = ax.plot([], [], "o", color="crimson", markersize=8)
433
+
434
+ n = result.t.shape[0]
435
+
436
+ def update(frame: int) -> tuple:
437
+ lo = max(0, frame - trail)
438
+ trail_line.set_data(contact_x[lo : frame + 1], contact_y[lo : frame + 1])
439
+ point.set_data([contact_x[frame]], [contact_y[frame]])
440
+ return trail_line, point
441
+
442
+ anim = FuncAnimation(fig, update, frames=n, interval=interval, blit=False)
443
+ return anim
444
+
445
+
446
+ def animate_rattleback(system, result, interval: int = 30, stride: int = 1) -> FuncAnimation:
447
+ """Animate a :class:`~physicskit.classical.systems.rotations.Rattleback`'s
448
+ spin direction and spin-rate history side by side, so a reversal is
449
+ directly visible both ways: the indicator arrow visibly reversing its
450
+ sense of rotation, and the ``n3(t)`` trace crossing zero.
451
+
452
+ Parameters
453
+ ----------
454
+ system : physicskit.classical.systems.rotations.Rattleback
455
+ System the `result` came from (unused beyond duck-typing
456
+ consistency with the other animators here).
457
+ result : physicskit.classical.core.base_system.SimulationResult
458
+ Output of ``system.integrate(...)``; ``result.y[:, 2]`` is `n3`
459
+ (the spin-rate component).
460
+ interval : int, default 30
461
+ Delay between animation frames, in milliseconds.
462
+ stride : int, default 1
463
+ Render only every ``stride``-th recorded sample.
464
+
465
+ Returns
466
+ -------
467
+ matplotlib.animation.FuncAnimation
468
+ """
469
+ t = result.t
470
+ n3 = result.y[:, 2]
471
+ frame_indices = np.arange(0, len(t), stride)
472
+
473
+ # Cumulative rotation angle from integrating n3 dt: only its sign and
474
+ # rate of change carry meaning (this is a reduced toy model, not a true
475
+ # Euler angle), but it turns n3(t) into a visibly spinning indicator.
476
+ dt_arr = np.diff(t, prepend=t[0])
477
+ phase = np.cumsum(n3 * dt_arr)
478
+
479
+ fig, (ax_spin, ax_trace) = plt.subplots(1, 2, figsize=(11, 5))
480
+
481
+ ax_spin.set_xlim(-1.2, 1.2)
482
+ ax_spin.set_ylim(-1.2, 1.2)
483
+ ax_spin.set_aspect("equal")
484
+ ax_spin.set_title("Spin direction indicator")
485
+ ax_spin.plot([0], [0], "o", color="black", markersize=6)
486
+ (arrow,) = ax_spin.plot([], [], "-", color="steelblue", lw=3)
487
+
488
+ ax_trace.set_xlim(t[0], t[-1])
489
+ y_max = float(np.max(np.abs(n3))) + 1e-12
490
+ ax_trace.set_ylim(-1.05 * y_max, 1.05 * y_max)
491
+ ax_trace.axhline(0.0, color="gray", lw=1.0, ls="--")
492
+ ax_trace.set_xlabel("t")
493
+ ax_trace.set_ylabel("n3 (spin rate)")
494
+ ax_trace.set_title("Spin rate over time")
495
+ (trace,) = ax_trace.plot([], [], color="crimson", lw=1.2)
496
+ (point,) = ax_trace.plot([], [], "o", color="crimson")
497
+
498
+ def update(frame_i: int) -> tuple:
499
+ idx = frame_indices[frame_i]
500
+ ang = phase[idx]
501
+ arrow.set_data([0.0, np.cos(ang)], [0.0, np.sin(ang)])
502
+ arrow.set_color("steelblue" if n3[idx] >= 0 else "crimson")
503
+ trace.set_data(t[: idx + 1], n3[: idx + 1])
504
+ point.set_data([t[idx]], [n3[idx]])
505
+ return arrow, trace, point
506
+
507
+ anim = FuncAnimation(fig, update, frames=len(frame_indices), interval=interval, blit=False)
508
+ return anim
@@ -0,0 +1,113 @@
1
+ """Interactive Plotly visualizers.
2
+
3
+ The Matplotlib-based plots in :mod:`physicskit.classical.visualizers.phase_space` are
4
+ static and best suited to documentation/reports. For exploratory work
5
+ -- rotating a rigid body's momentum trajectory around to see how it
6
+ sits on the Casimir sphere, or zooming into a precessing orbit's
7
+ perihelion -- an interactive, mouse-drag-and-zoom view is much more
8
+ useful, which is exactly what Plotly is for.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import numpy as np
14
+ import plotly.graph_objects as go
15
+
16
+ __all__ = ["interactive_so3_momentum_sphere", "interactive_orbit"]
17
+
18
+
19
+ def interactive_so3_momentum_sphere(omega_trajectory: np.ndarray, I1: float, I2: float, I3: float, n_grid: int = 40, title: str = None) -> go.Figure:
20
+ """Interactive 3D Plotly view of a rigid body's body-frame angular
21
+ momentum L(t) = (I1*w1, I2*w2, I3*w3) on the Casimir sphere
22
+ ``|L| = |L(0)|``, draggable/zoomable in a way the static Matplotlib
23
+ version (:func:`physicskit.classical.visualizers.phase_space.plot_so3_momentum_sphere`)
24
+ cannot be -- useful for directly inspecting how close a trajectory
25
+ passes to the unstable intermediate-axis pole.
26
+
27
+ Parameters
28
+ ----------
29
+ omega_trajectory : array, shape (n_steps, 3)
30
+ Body-frame angular velocity trajectory, e.g.
31
+ ``result.y[:, :3]`` from integrating an
32
+ :class:`~physicskit.classical.systems.rotations.EulerTop`.
33
+ I1, I2, I3 : float
34
+ Principal moments of inertia.
35
+ n_grid : int
36
+ Resolution of the wireframe sphere.
37
+ title : str, optional
38
+ Plot title.
39
+
40
+ Returns
41
+ -------
42
+ plotly.graph_objects.Figure
43
+ """
44
+ omega_trajectory = np.asarray(omega_trajectory)
45
+ L = np.column_stack([I1 * omega_trajectory[:, 0], I2 * omega_trajectory[:, 1], I3 * omega_trajectory[:, 2]])
46
+ radius = np.linalg.norm(L[0])
47
+
48
+ u = np.linspace(0, 2 * np.pi, n_grid)
49
+ v = np.linspace(0, np.pi, n_grid)
50
+ xs = radius * np.outer(np.cos(u), np.sin(v))
51
+ ys = radius * np.outer(np.sin(u), np.sin(v))
52
+ zs = radius * np.outer(np.ones_like(u), np.cos(v))
53
+
54
+ fig = go.Figure()
55
+ fig.add_surface(x=xs, y=ys, z=zs, opacity=0.25, showscale=False, colorscale=[[0, "lightgray"], [1, "lightgray"]], name="Casimir sphere")
56
+ fig.add_scatter3d(x=L[:, 0], y=L[:, 1], z=L[:, 2], mode="lines", line=dict(color="crimson", width=4), name="L(t)")
57
+ fig.add_scatter3d(x=[L[0, 0]], y=[L[0, 1]], z=[L[0, 2]], mode="markers", marker=dict(color="green", size=5), name="start")
58
+ fig.update_layout(
59
+ title=title or "Body-frame angular momentum on the Casimir sphere",
60
+ scene=dict(xaxis_title="L1", yaxis_title="L2", zaxis_title="L3", aspectmode="data"),
61
+ margin=dict(l=0, r=0, t=40, b=0),
62
+ )
63
+ return fig
64
+
65
+
66
+ def interactive_orbit(result, system=None, title: str = None) -> go.Figure:
67
+ """Interactive 2D Plotly view of a planar central-force orbit (e.g.
68
+ from :class:`~physicskit.classical.systems.newtonian.KeplerSystem`), with the
69
+ Laplace-Runge-Lenz vector overlaid at start and end if ``system``
70
+ exposes ``lrl_vector`` -- draggable/zoomable so a slow apsidal
71
+ precession is easy to inspect directly, unlike a static plot.
72
+
73
+ Parameters
74
+ ----------
75
+ result : physicskit.classical.core.base_system.SimulationResult
76
+ Output of ``system.integrate(...)``.
77
+ system : object, optional
78
+ The system the result came from; if it defines
79
+ ``lrl_vector(q, p) -> array``, the LRL direction at the start
80
+ and end of the trajectory is drawn as an arrow from the origin.
81
+ title : str, optional
82
+ Plot title.
83
+
84
+ Returns
85
+ -------
86
+ plotly.graph_objects.Figure
87
+ """
88
+ q = result.q
89
+ fig = go.Figure()
90
+ fig.add_scatter(x=q[:, 0], y=q[:, 1], mode="lines", line=dict(color="steelblue", width=1.5), name="orbit")
91
+ fig.add_scatter(x=[0], y=[0], mode="markers", marker=dict(color="gold", size=12), name="focus")
92
+
93
+ if system is not None and hasattr(system, "lrl_vector"):
94
+ for idx, (label, color) in ((0, ("LRL(0)", "green")), (-1, ("LRL(end)", "crimson"))):
95
+ vec = system.lrl_vector(q[idx], result.p[idx])
96
+ scale = np.max(np.abs(q)) / (np.linalg.norm(vec) + 1e-300) * 0.6
97
+ fig.add_scatter(
98
+ x=[0, vec[0] * scale],
99
+ y=[0, vec[1] * scale],
100
+ mode="lines+markers",
101
+ line=dict(color=color, width=2, dash="dash"),
102
+ marker=dict(size=[0, 6]),
103
+ name=label,
104
+ )
105
+
106
+ fig.update_layout(
107
+ title=title or "Orbit",
108
+ xaxis_title="x",
109
+ yaxis_title="y",
110
+ yaxis=dict(scaleanchor="x", scaleratio=1),
111
+ margin=dict(l=0, r=0, t=40, b=0),
112
+ )
113
+ return fig