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,614 @@
1
+ """Rigid-body rotation systems: free tops and heavy symmetric tops.
2
+
3
+ :class:`EulerTop` integrates the torque-free rigid body (Euler's
4
+ equations for the body-frame angular velocity) together with the
5
+ orientation quaternion, and demonstrates the Intermediate Axis Theorem
6
+ (the "Dzhanibekov effect" / tennis-racket effect): rotation about the
7
+ axis of intermediate moment of inertia is unstable, while rotation
8
+ about the axes of largest and smallest moment is stable.
9
+
10
+ :class:`HeavySymmetricTop` is a symmetric top with one point fixed,
11
+ spinning under gravity -- derived symbolically (like the Lagrangian
12
+ systems) in the three Euler angles (phi, theta, psi), since its
13
+ Hamiltonian is intrinsically non-separable (the kinetic term's
14
+ coefficients depend on theta).
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from functools import lru_cache
20
+
21
+ import numpy as np
22
+ import sympy as sp
23
+ from numba import njit
24
+ from scipy.optimize import minimize_scalar
25
+
26
+ from physicskit.classical.core.base_system import LagrangianSystem, ODESystem
27
+ from physicskit.classical.utils.symbolic import LagrangianEngine
28
+
29
+ __all__ = [
30
+ "EulerTop",
31
+ "HeavySymmetricTop",
32
+ "EulersDisk",
33
+ "Rattleback",
34
+ "effective_potential_symmetric_top",
35
+ "find_theta_equilibrium",
36
+ "nutation_frequency",
37
+ "precession_frequency",
38
+ "eulers_disk_theta_analytic",
39
+ ]
40
+
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # Free asymmetric rigid body (Euler top)
44
+ # ---------------------------------------------------------------------------
45
+
46
+
47
+ @lru_cache(maxsize=64)
48
+ def _make_euler_top_deriv(I1: float, I2: float, I3: float):
49
+ @njit(cache=False)
50
+ def deriv(t, y):
51
+ w1, w2, w3 = y[0], y[1], y[2]
52
+ qw, qx, qy, qz = y[3], y[4], y[5], y[6]
53
+
54
+ dw1 = (I2 - I3) / I1 * w2 * w3
55
+ dw2 = (I3 - I1) / I2 * w3 * w1
56
+ dw3 = (I1 - I2) / I3 * w1 * w2
57
+
58
+ # dq/dt = 0.5 * q (x) (0, w1, w2, w3) (Hamilton product, body-frame omega)
59
+ dqw = -0.5 * (qx * w1 + qy * w2 + qz * w3)
60
+ dqx = 0.5 * (qw * w1 + qy * w3 - qz * w2)
61
+ dqy = 0.5 * (qw * w2 + qz * w1 - qx * w3)
62
+ dqz = 0.5 * (qw * w3 + qx * w2 - qy * w1)
63
+
64
+ out = np.empty(7)
65
+ out[0], out[1], out[2] = dw1, dw2, dw3
66
+ out[3], out[4], out[5], out[6] = dqw, dqx, dqy, dqz
67
+ return out
68
+
69
+ return deriv
70
+
71
+
72
+ class EulerTop(ODESystem):
73
+ """Torque-free rigid body: Euler's equations for omega = (w1, w2, w3)
74
+ in the body frame, coupled to the orientation quaternion (qw, qx, qy, qz).
75
+
76
+ State: ``[w1, w2, w3, qw, qx, qy, qz]``. The default integrator,
77
+ ``implicit_midpoint``, exactly conserves both the rotational kinetic
78
+ energy and the (squared) angular momentum magnitude and quaternion
79
+ norm -- all quadratic invariants of this quadratic ODE (a classical
80
+ result: the implicit midpoint / average-vector-field map preserves
81
+ every quadratic invariant of any ODE, not only linear ones).
82
+
83
+ Excite a spin dominantly about the intermediate-inertia axis (e.g.
84
+ ``omega0 = [0.01, 1.0, 0.01]`` with ``I1 < I2 < I3``) to see the
85
+ periodic tumbling of the Intermediate Axis Theorem.
86
+
87
+ Parameters
88
+ ----------
89
+ omega0 : array-like, shape (3,)
90
+ Initial body-frame angular velocity.
91
+ I1, I2, I3 : float
92
+ Principal moments of inertia.
93
+ quat0 : array-like, shape (4,), optional
94
+ Initial orientation quaternion ``(w, x, y, z)``; defaults to
95
+ the identity orientation. Normalized automatically.
96
+ """
97
+
98
+ def __init__(self, omega0, I1=1.0, I2=2.0, I3=3.0, quat0=None):
99
+ self.I1, self.I2, self.I3 = I1, I2, I3
100
+ omega0 = np.asarray(omega0, dtype=np.float64)
101
+ quat0 = np.array([1.0, 0.0, 0.0, 0.0]) if quat0 is None else np.asarray(quat0, dtype=np.float64)
102
+ quat0 = quat0 / np.linalg.norm(quat0)
103
+ self._deriv_njit = _make_euler_top_deriv(I1, I2, I3)
104
+ super().__init__(np.concatenate([omega0, quat0]))
105
+
106
+ @property
107
+ def omega(self) -> np.ndarray:
108
+ return self.state[:3]
109
+
110
+ @property
111
+ def quaternion(self) -> np.ndarray:
112
+ return self.state[3:]
113
+
114
+ def energy(self, state: np.ndarray = None) -> float:
115
+ """Rotational kinetic energy.
116
+
117
+ Parameters
118
+ ----------
119
+ state : ndarray, optional
120
+ State to evaluate; defaults to the current state.
121
+
122
+ Returns
123
+ -------
124
+ float
125
+ """
126
+ state = self.state if state is None else state
127
+ w1, w2, w3 = state[0], state[1], state[2]
128
+ return 0.5 * (self.I1 * w1**2 + self.I2 * w2**2 + self.I3 * w3**2)
129
+
130
+ def angular_momentum_squared(self, state: np.ndarray = None) -> float:
131
+ """``|L|^2`` in the body frame.
132
+
133
+ Parameters
134
+ ----------
135
+ state : ndarray, optional
136
+ State to evaluate; defaults to the current state.
137
+
138
+ Returns
139
+ -------
140
+ float
141
+ """
142
+ state = self.state if state is None else state
143
+ w1, w2, w3 = state[0], state[1], state[2]
144
+ return (self.I1 * w1) ** 2 + (self.I2 * w2) ** 2 + (self.I3 * w3) ** 2
145
+
146
+ def rotation_matrix(self, state: np.ndarray = None) -> np.ndarray:
147
+ """3x3 body-to-world rotation matrix from the current quaternion.
148
+
149
+ Parameters
150
+ ----------
151
+ state : ndarray, optional
152
+ State to evaluate; defaults to the current state.
153
+
154
+ Returns
155
+ -------
156
+ ndarray, shape (3, 3)
157
+ """
158
+ state = self.state if state is None else state
159
+ qw, qx, qy, qz = state[3], state[4], state[5], state[6]
160
+ return np.array(
161
+ [
162
+ [1 - 2 * (qy**2 + qz**2), 2 * (qx * qy - qz * qw), 2 * (qx * qz + qy * qw)],
163
+ [2 * (qx * qy + qz * qw), 1 - 2 * (qx**2 + qz**2), 2 * (qy * qz - qx * qw)],
164
+ [2 * (qx * qz - qy * qw), 2 * (qy * qz + qx * qw), 1 - 2 * (qx**2 + qy**2)],
165
+ ]
166
+ )
167
+
168
+
169
+ # ---------------------------------------------------------------------------
170
+ # Heavy symmetric top (Lagrange top): fixed point under gravity
171
+ # ---------------------------------------------------------------------------
172
+
173
+
174
+ @lru_cache(maxsize=32)
175
+ def _heavy_symmetric_top_engine(I1: float, I3: float, M: float, l: float, g: float) -> LagrangianEngine:
176
+ phi, theta, psi, phid, thd, psid = sp.symbols("phi theta psi phid thd psid")
177
+ I1s, I3s, Ms, ls, gs = sp.symbols("I1 I3 M l g")
178
+
179
+ L = (
180
+ sp.Rational(1, 2) * I1s * (thd**2 + phid**2 * sp.sin(theta) ** 2)
181
+ + sp.Rational(1, 2) * I3s * (psid + phid * sp.cos(theta)) ** 2
182
+ - Ms * gs * ls * sp.cos(theta)
183
+ )
184
+ params = {I1s: I1, I3s: I3, Ms: M, ls: l, gs: g}
185
+ return LagrangianEngine([phi, theta, psi], [phid, thd, psid], L, params=params)
186
+
187
+
188
+ class HeavySymmetricTop(LagrangianSystem):
189
+ """Symmetric top with one point fixed, spinning under gravity.
190
+
191
+ Generalized coordinates q = (phi, theta, psi) -- the standard
192
+ z-x-z Euler angles (precession, nutation, spin). Lagrangian:
193
+
194
+ L = (I1/2)(thetadot^2 + phidot^2 sin^2(theta))
195
+ + (I3/2)(psidot + phidot cos(theta))^2 - M*g*l*cos(theta)
196
+
197
+ where I1 is the transverse moment of inertia about the fixed point,
198
+ I3 the axial moment, M the mass, and l the distance from the pivot
199
+ to the center of mass. Because M(q) depends on theta, this is a
200
+ genuinely non-separable Hamiltonian system -- exactly the case
201
+ :class:`physicskit.classical.utils.symbolic.LagrangianEngine` and
202
+ ``implicit_midpoint`` were built to handle.
203
+
204
+ Parameters
205
+ ----------
206
+ angles0, angledots0 : array-like, shape (3,)
207
+ Initial ``(phi, theta, psi)`` and their time derivatives.
208
+ I1 : float
209
+ Transverse moment of inertia about the fixed point.
210
+ I3 : float
211
+ Axial moment of inertia.
212
+ M : float
213
+ Mass.
214
+ l : float
215
+ Distance from the pivot to the center of mass.
216
+ g : float
217
+ Gravitational acceleration.
218
+ """
219
+
220
+ def __init__(self, angles0, angledots0, I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81):
221
+ self.I1, self.I3, self.M, self.l, self.g = I1, I3, M, l, g
222
+ self.engine = _heavy_symmetric_top_engine(I1, I3, M, l, g)
223
+ self._accel_njit = self.engine.acceleration_njit
224
+ self._momentum_njit = self.engine.momentum_njit
225
+ self._canonical_deriv_njit = self.engine.canonical_deriv_njit
226
+ self._velocity_njit = self.engine.velocity_njit
227
+ self._deriv_njit = self.engine.full_deriv_njit
228
+ self._hamiltonian_njit = self.engine.hamiltonian_njit
229
+
230
+ super().__init__(angles0, angledots0)
231
+
232
+ def energy(self, state: np.ndarray = None) -> float:
233
+ state = self.state if state is None else state
234
+ q, qdot = self.split(state, self.ndof)
235
+ p = self._momentum_njit(q, qdot)
236
+ return float(self._hamiltonian_njit(q, p, 0.0))
237
+
238
+ def precession_nutation_rate(self, state: np.ndarray = None):
239
+ """Instantaneous (phidot, thetadot) -- precession and nutation rates.
240
+
241
+ Parameters
242
+ ----------
243
+ state : ndarray, optional
244
+ State to evaluate; defaults to the current state.
245
+
246
+ Returns
247
+ -------
248
+ phidot, thetadot : float
249
+ """
250
+ state = self.state if state is None else state
251
+ _, qdot = self.split(state, self.ndof)
252
+ return float(qdot[0]), float(qdot[1])
253
+
254
+
255
+ def effective_potential_symmetric_top(theta, p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float):
256
+ """V_eff(theta) for the heavy symmetric top, given the two conserved
257
+ (cyclic-coordinate) momenta p_phi and p_psi:
258
+
259
+ V_eff(theta) = (p_phi - p_psi cos(theta))^2 / (2 I1 sin^2(theta))
260
+ + p_psi^2 / (2 I3) + Mgl * cos(theta)
261
+
262
+ Motion in theta is confined to where the total energy E >= V_eff(theta);
263
+ the turning points bound the nutation range.
264
+
265
+ Parameters
266
+ ----------
267
+ theta : array-like
268
+ Angle(s) to evaluate at.
269
+ p_phi, p_psi : float
270
+ Conserved (cyclic-coordinate) momenta.
271
+ I1, I3 : float
272
+ Transverse and axial moments of inertia.
273
+ Mgl : float
274
+ ``M * g * l`` (mass times gravity times pivot-to-CM distance).
275
+
276
+ Returns
277
+ -------
278
+ ndarray
279
+ """
280
+ theta = np.asarray(theta, dtype=np.float64)
281
+ return (p_phi - p_psi * np.cos(theta)) ** 2 / (2.0 * I1 * np.sin(theta) ** 2) + p_psi**2 / (2.0 * I3) + Mgl * np.cos(theta)
282
+
283
+
284
+ def find_theta_equilibrium(p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float, bracket=(1e-3, np.pi - 1e-3)) -> float:
285
+ """The nutation angle theta_eq at which V_eff(theta) is minimized.
286
+
287
+ The angle steady (non-nutating) precession would hold at, and the
288
+ center about which a real top's nutation oscillates.
289
+
290
+ Parameters
291
+ ----------
292
+ p_phi, p_psi : float
293
+ Conserved (cyclic-coordinate) momenta.
294
+ I1, I3 : float
295
+ Transverse and axial moments of inertia.
296
+ Mgl : float
297
+ ``M * g * l``.
298
+ bracket : tuple of float
299
+ Search interval for the minimization.
300
+
301
+ Returns
302
+ -------
303
+ float
304
+ """
305
+ result = minimize_scalar(
306
+ lambda th: effective_potential_symmetric_top(th, p_phi, p_psi, I1, I3, Mgl),
307
+ bounds=bracket,
308
+ method="bounded",
309
+ )
310
+ return float(result.x)
311
+
312
+
313
+ def nutation_frequency(p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float, theta_eq: float = None, dtheta: float = 1e-5) -> float:
314
+ """Small-oscillation nutation frequency about ``theta_eq``.
315
+
316
+ Treating theta as a 1-DOF particle of "mass" I1 moving in the
317
+ effective potential V_eff (see :func:`effective_potential_symmetric_top`),
318
+ the harmonic-approximation frequency about its minimum is
319
+ ``sqrt(V_eff''(theta_eq) / I1)``, found here by a central finite
320
+ difference. If ``theta_eq`` is not supplied it is located with
321
+ :func:`find_theta_equilibrium`.
322
+
323
+ Parameters
324
+ ----------
325
+ p_phi, p_psi : float
326
+ Conserved (cyclic-coordinate) momenta.
327
+ I1, I3 : float
328
+ Transverse and axial moments of inertia.
329
+ Mgl : float
330
+ ``M * g * l``.
331
+ theta_eq : float, optional
332
+ Equilibrium angle; computed via :func:`find_theta_equilibrium`
333
+ if omitted.
334
+ dtheta : float
335
+ Finite-difference step.
336
+
337
+ Returns
338
+ -------
339
+ float
340
+ """
341
+ if theta_eq is None:
342
+ theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
343
+ V = lambda th: effective_potential_symmetric_top(th, p_phi, p_psi, I1, I3, Mgl) # noqa: E731
344
+ d2V = (V(theta_eq + dtheta) - 2.0 * V(theta_eq) + V(theta_eq - dtheta)) / dtheta**2
345
+ return float(np.sqrt(d2V / I1))
346
+
347
+
348
+ # ---------------------------------------------------------------------------
349
+ # Euler's disk: a rolling, spinning disk running down to a finite-time stop
350
+ # ---------------------------------------------------------------------------
351
+
352
+
353
+ @lru_cache(maxsize=32)
354
+ def _make_eulers_disk_deriv(decay_rate: float, precession_const: float, theta_floor: float):
355
+ @njit(cache=False)
356
+ def deriv(t, y):
357
+ theta = y[0]
358
+ theta_c = theta if theta > theta_floor else theta_floor
359
+ out = np.empty(2)
360
+ out[0] = -decay_rate / theta_c
361
+ out[1] = precession_const / theta_c
362
+ return out
363
+
364
+ return deriv
365
+
366
+
367
+ class EulersDisk(ODESystem):
368
+ """Simplified rolling-disk-with-dissipation model of "Euler's disk".
369
+
370
+ A coin or disk spun and set rolling on a table settles into a
371
+ near-steady state described, at each instant, by just its inclination
372
+ angle ``theta`` (between the disk plane and the table) and a precession
373
+ angle ``phi`` (the azimuthal angle of the rolling contact point around
374
+ the disk's resting position). As real Euler's-disk demonstrations
375
+ strikingly show, the contact point's precession visibly speeds up
376
+ (and the characteristic rattling sound rises in pitch) right up until
377
+ the disk suddenly stops -- a *finite-time singularity*, famously
378
+ analyzed by Moffatt (2000, Nature 404, 833) for an idealized
379
+ viscous-dissipation mechanism. The exact dissipation mechanism (viscous
380
+ air drag vs. rolling friction vs. contact-point slipping) and even the
381
+ precise power-law exponent are still debated in the literature (e.g.
382
+ the response by Van den Engh et al., 2004); rather than committing to
383
+ one first-principles derivation, this class uses a standard *reduced*
384
+ phenomenological pair of ODEs that reproduces the same qualitative
385
+ finite-time-collapse behavior:
386
+
387
+ .. math::
388
+
389
+ \\dot\\theta = -\\text{decay\\_rate} / \\theta, \\qquad
390
+ \\dot\\phi = \\text{precession\\_const} / \\theta
391
+
392
+ i.e. both the precession rate and (by assumption) the dissipation rate
393
+ diverge as ``theta -> 0``, which is exactly what drives theta to zero
394
+ in finite time. Ignoring the regularizing `theta_floor`, the first
395
+ equation integrates in closed form to
396
+ ``theta(t) = sqrt(theta0**2 - 2*decay_rate*t)`` (see
397
+ :func:`eulers_disk_theta_analytic`), vanishing at the finite collapse
398
+ time ``t_f = theta0**2 / (2*decay_rate)``. `theta_floor` clips both
399
+ right-hand sides near ``theta = 0`` so the (unphysical) literal
400
+ singularity never actually has to be integrated through.
401
+
402
+ State: ``[theta, phi]``.
403
+
404
+ Parameters
405
+ ----------
406
+ theta0 : float
407
+ Initial inclination angle (radians), measured from horizontal
408
+ (``theta = pi/2`` would be an upright, unspun disk; realistic
409
+ "rattling" motion is at small `theta0`, e.g. 0.2-0.6 rad).
410
+ phi0 : float, default 0.0
411
+ Initial precession angle.
412
+ decay_rate : float, default 0.05
413
+ Rate constant governing how fast theta collapses; sets the
414
+ collapse time ``t_f = theta0**2 / (2*decay_rate)``.
415
+ precession_const : float, default 1.0
416
+ Rate constant governing the precession rate at a given theta.
417
+ theta_floor : float, default 1e-3
418
+ Regularizing floor for `theta` in both right-hand sides.
419
+
420
+ Attributes
421
+ ----------
422
+ decay_rate, precession_const, theta_floor : float
423
+ System parameters.
424
+ """
425
+
426
+ def __init__(self, theta0: float, phi0: float = 0.0, decay_rate: float = 0.05, precession_const: float = 1.0, theta_floor: float = 1e-3):
427
+ self.decay_rate, self.precession_const, self.theta_floor = decay_rate, precession_const, theta_floor
428
+ self._deriv_njit = _make_eulers_disk_deriv(decay_rate, precession_const, theta_floor)
429
+ super().__init__(np.array([theta0, phi0], dtype=np.float64))
430
+
431
+ @property
432
+ def theta(self) -> float:
433
+ return self.state[0]
434
+
435
+ @property
436
+ def phi(self) -> float:
437
+ return self.state[1]
438
+
439
+ def energy(self, state: np.ndarray = None) -> float:
440
+ """Not a true conserved energy -- the whole point of this model is
441
+ dissipation. Returns `theta` itself, a monotonically decreasing
442
+ proxy that (like the disk's actual energy) vanishes at the
443
+ finite-time collapse, just so this fits the common ``energy()``
444
+ diagnostic slot shared by every :class:`~physicskit.classical.core.base_system.ODESystem`.
445
+
446
+ Parameters
447
+ ----------
448
+ state : ndarray, optional
449
+ State to evaluate; defaults to the current state.
450
+
451
+ Returns
452
+ -------
453
+ float
454
+ """
455
+ state = self.state if state is None else state
456
+ return float(state[0])
457
+
458
+
459
+ def eulers_disk_theta_analytic(t, theta0: float, decay_rate: float) -> np.ndarray:
460
+ """Closed-form ``theta(t)`` for :class:`EulersDisk`, ignoring the
461
+ regularizing floor: solves ``dtheta/dt = -decay_rate/theta`` exactly as
462
+ ``theta(t) = sqrt(theta0**2 - 2*decay_rate*t)``, valid up to the finite
463
+ collapse time ``t_f = theta0**2 / (2*decay_rate)`` (clipped to 0
464
+ thereafter).
465
+
466
+ Parameters
467
+ ----------
468
+ t : array-like
469
+ Time(s) to evaluate at.
470
+ theta0 : float
471
+ Initial inclination angle.
472
+ decay_rate : float
473
+ Decay-rate constant; see :class:`EulersDisk`.
474
+
475
+ Returns
476
+ -------
477
+ ndarray
478
+ """
479
+ t = np.asarray(t, dtype=np.float64)
480
+ return np.sqrt(np.clip(theta0**2 - 2.0 * decay_rate * t, 0.0, None))
481
+
482
+
483
+ # ---------------------------------------------------------------------------
484
+ # Rattleback (celt stone): a toy model of one-way spin instability/reversal
485
+ # ---------------------------------------------------------------------------
486
+
487
+
488
+ @lru_cache(maxsize=32)
489
+ def _make_rattleback_deriv(I1: float, I2: float, I3: float, gamma: float, mu: float, eta: float):
490
+ @njit(cache=False)
491
+ def deriv(t, y):
492
+ n1, n2, n3 = y[0], y[1], y[2]
493
+ dn1 = (I2 - I3) / I1 * n2 * n3 + gamma * n3 * n3 - mu * n1 - eta * n1**3
494
+ dn2 = (I3 - I1) / I2 * n3 * n1 - mu * n2 - eta * n2**3
495
+ dn3 = (I1 - I2) / I3 * n1 * n2 - mu * n3
496
+ out = np.empty(3)
497
+ out[0], out[1], out[2] = dn1, dn2, dn3
498
+ return out
499
+
500
+ return deriv
501
+
502
+
503
+ class Rattleback(ODESystem):
504
+ """Toy reduced model of rattleback (Celt stone) spin reversal.
505
+
506
+ A rattleback -- a boat-shaped top whose principal axes of inertia are
507
+ slightly misaligned from its geometric (contact-ellipsoid) axes --
508
+ famously spins stably in one sense but, when spun the other way,
509
+ wobbles with growing amplitude until it stops and reverses into the
510
+ stable sense. The true mechanism is the nonholonomic rolling-without-
511
+ slipping contact-point dynamics of a rigid ellipsoidal body on a plane
512
+ (see Garcia & Hubbard, "Spin reversal of the rattleback: theory and
513
+ experiment," Proc. R. Soc. Lond. A 418, 1988), which requires tracking
514
+ the body's full orientation, the moving contact point, and the no-slip
515
+ constraint.
516
+
517
+ **This class does not do that.** It is a deliberately simplified toy
518
+ model built directly on :class:`EulerTop`'s free-rigid-body equations
519
+ for reduced state variables ``(n1, n2, n3)`` -- ``n3`` playing the role
520
+ of spin about the (roughly vertical) axis, ``n1, n2`` the two
521
+ "rocking"/tipping-mode rates -- with two modifications standing in for
522
+ the real contact-point physics:
523
+
524
+ 1. A term ``gamma * n3**2``, added to the ``n1`` equation only. The
525
+ real rolling constraint on a rattleback whose principal-inertia axes
526
+ are misaligned (by some small angle) from its geometric axes
527
+ couples the vertical spin asymmetrically into the two rocking
528
+ modes; adding a term that is even in ``n3`` to only *one* of the two
529
+ symmetric rocking equations is the minimal way to break the
530
+ ``n3 -> -n3`` symmetry an ordinary (non-reversing) Euler top has --
531
+ exactly the qualitative signature of a rattleback: spin one way
532
+ pumps energy into rocking, spin the other way does not.
533
+ 2. Linear damping (`mu`) on all three components plus cubic,
534
+ Landau-type self-saturation (`eta`) on ``n1, n2``, so the rocking
535
+ amplitude the instability pumps up saturates rather than diverging,
536
+ and the whole system eventually settles to rest -- as a real
537
+ rattleback, slowed by genuine friction, does.
538
+
539
+ With `gamma` = 0 this reduces to a damped :class:`EulerTop` and never
540
+ reverses; for `gamma` large enough relative to the damping, a spin
541
+ started predominantly in ``n3`` visibly collapses, overshoots into
542
+ negative ``n3`` (the reversal), and then decays -- reproducing the
543
+ qualitative phenomenology (decelerate, wobble, reverse, decay to rest)
544
+ without claiming quantitative accuracy for any specific physical body.
545
+
546
+ State: ``[n1, n2, n3]``.
547
+
548
+ Parameters
549
+ ----------
550
+ n0 : array-like, shape (3,)
551
+ Initial ``(n1, n2, n3)``; e.g. ``[0.01, 0.01, 3.0]`` models a
552
+ rattleback spun up hard about its ``n3`` axis with only a tiny
553
+ rocking perturbation.
554
+ I1, I2, I3 : float
555
+ Principal moments of inertia (same role as in :class:`EulerTop`).
556
+ gamma : float, default 1.0
557
+ Strength of the symmetry-breaking rolling-constraint correction.
558
+ mu : float, default 0.01
559
+ Linear (frictional) damping rate on all three components.
560
+ eta : float, default 0.01
561
+ Cubic self-saturation coefficient on ``n1, n2``.
562
+
563
+ Attributes
564
+ ----------
565
+ I1, I2, I3, gamma, mu, eta : float
566
+ System parameters.
567
+ """
568
+
569
+ def __init__(self, n0, I1: float = 1.0, I2: float = 1.5, I3: float = 2.0, gamma: float = 1.0, mu: float = 0.01, eta: float = 0.01):
570
+ self.I1, self.I2, self.I3 = I1, I2, I3
571
+ self.gamma, self.mu, self.eta = gamma, mu, eta
572
+ self._deriv_njit = _make_rattleback_deriv(I1, I2, I3, gamma, mu, eta)
573
+ super().__init__(np.asarray(n0, dtype=np.float64))
574
+
575
+ def energy(self, state: np.ndarray = None) -> float:
576
+ """``0.5*(I1*n1^2 + I2*n2^2 + I3*n3^2)``: not conserved (this
577
+ system is dissipative and, transiently, unstable by construction),
578
+ but the natural quadratic diagnostic for watching the overall
579
+ motion decay toward rest.
580
+
581
+ Parameters
582
+ ----------
583
+ state : ndarray, optional
584
+ State to evaluate; defaults to the current state.
585
+
586
+ Returns
587
+ -------
588
+ float
589
+ """
590
+ state = self.state if state is None else state
591
+ n1, n2, n3 = state[0], state[1], state[2]
592
+ return 0.5 * (self.I1 * n1**2 + self.I2 * n2**2 + self.I3 * n3**2)
593
+
594
+
595
+ def precession_frequency(p_phi: float, p_psi: float, I1: float, theta_eq: float) -> float:
596
+ """Mean precession rate phidot at nutation angle ``theta_eq``:
597
+ ``(p_phi - p_psi*cos(theta_eq)) / (I1*sin(theta_eq)**2)`` -- the
598
+ standard steady-precession-rate approximation, exact when the top
599
+ is not nutating at all (theta held fixed at theta_eq).
600
+
601
+ Parameters
602
+ ----------
603
+ p_phi, p_psi : float
604
+ Conserved (cyclic-coordinate) momenta.
605
+ I1 : float
606
+ Transverse moment of inertia.
607
+ theta_eq : float
608
+ Nutation angle to evaluate at.
609
+
610
+ Returns
611
+ -------
612
+ float
613
+ """
614
+ return float((p_phi - p_psi * np.cos(theta_eq)) / (I1 * np.sin(theta_eq) ** 2))
File without changes
@@ -0,0 +1,7 @@
1
+ """Test-session setup: force Matplotlib's non-interactive Agg backend
2
+ before anything imports pyplot, so the suite (and CI, which has no
3
+ display) can exercise the plotting/animation code headlessly."""
4
+
5
+ import matplotlib
6
+
7
+ matplotlib.use("Agg")