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,113 @@
1
+ """Tests for the DynamicalSystem/ODESystem/HamiltonianSystem base-class
2
+ machinery in physicskit.classical.core.base_system that every concrete system
3
+ uses but that no single concrete system exercises end to end:
4
+ ``reset()`` (never called by any real system or example), the
5
+ ``force()``/``derivatives()`` defensive defaults for a non-separable
6
+ Hamiltonian system with no ``_deriv_njit`` of its own (every real
7
+ HamiltonianSystem subclass in this package is separable and never hits
8
+ them), and both base classes' "unknown integration method" guard.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import numpy as np
14
+ import pytest
15
+
16
+ from physicskit.classical.core.base_system import HamiltonianSystem
17
+ from physicskit.classical.systems.newtonian import FoucaultPendulum, ProjectileMotion
18
+
19
+
20
+ class _MinimalNonSeparableHamiltonian(HamiltonianSystem):
21
+ """Smallest possible non-separable HamiltonianSystem subclass. Every
22
+ real system in this package is separable and provides its own
23
+ ``_force_njit``/``_deriv_njit``, so the base class's defensive
24
+ defaults for the non-separable, nothing-provided case are otherwise
25
+ dead code."""
26
+
27
+ separable = False
28
+
29
+ def __init__(self):
30
+ super().__init__([0.0], [0.0])
31
+
32
+ def kinetic_energy(self, p):
33
+ return 0.5 * p[0] ** 2
34
+
35
+ def potential_energy(self, q):
36
+ return 0.0
37
+
38
+
39
+ def test_hamiltonian_system_reset_updates_state_and_time():
40
+ system = ProjectileMotion(q0=[0.0, 0.0], p0=[1.0, 1.0])
41
+ new_state = system.reset(state0=[5.0, 5.0, 2.0, 2.0], t0=3.0)
42
+ np.testing.assert_allclose(new_state, [5.0, 5.0, 2.0, 2.0])
43
+ assert system.t == 3.0
44
+ assert system.state is new_state
45
+
46
+
47
+ def test_reset_with_no_state0_keeps_current_state_but_updates_time():
48
+ system = ProjectileMotion(q0=[1.0, 2.0], p0=[3.0, 4.0])
49
+ original_state = system.state
50
+ returned = system.reset(t0=7.0)
51
+ np.testing.assert_allclose(returned, original_state)
52
+ assert system.t == 7.0
53
+
54
+
55
+ def test_hamiltonian_system_force_raises_for_non_separable_system_without_force():
56
+ system = _MinimalNonSeparableHamiltonian()
57
+ with pytest.raises(NotImplementedError):
58
+ system.force(system.q, 0.0)
59
+
60
+
61
+ def test_hamiltonian_system_derivatives_raises_for_non_separable_system_without_deriv():
62
+ system = _MinimalNonSeparableHamiltonian()
63
+ with pytest.raises(NotImplementedError):
64
+ system.derivatives(0.0, system.state)
65
+
66
+
67
+ def test_hamiltonian_system_integrate_rejects_symplectic_method_for_non_separable_system():
68
+ system = _MinimalNonSeparableHamiltonian()
69
+ with pytest.raises(ValueError):
70
+ system.integrate((0.0, 1.0), dt=0.1, method="verlet")
71
+
72
+
73
+ def test_hamiltonian_system_integrate_rejects_unknown_method():
74
+ system = ProjectileMotion(q0=[0.0, 0.0], p0=[1.0, 1.0])
75
+ with pytest.raises(ValueError):
76
+ system.integrate((0.0, 1.0), dt=0.1, method="bogus")
77
+
78
+
79
+ def test_hamiltonian_system_force_matches_gravity_for_separable_system():
80
+ system = ProjectileMotion(q0=[0.0, 10.0], p0=[1.0, 0.0])
81
+ fx, fy = system.force(system.q, 0.0)
82
+ assert fx == pytest.approx(0.0)
83
+ assert fy == pytest.approx(-system.m * system.g)
84
+
85
+
86
+ def test_hamiltonian_system_derivatives_matches_auto_built_separable_deriv():
87
+ """ProjectileMotion without drag never sets self._deriv_njit itself,
88
+ so .derivatives() must fall back to auto-building it from force()
89
+ (HamiltonianSystem._ensure_deriv_njit's separable branch)."""
90
+ system = ProjectileMotion(q0=[0.0, 10.0], p0=[1.0, 0.0])
91
+ d = system.derivatives(0.0, system.state)
92
+ assert d.shape == (4,)
93
+ vx, vy, fx, fy = d
94
+ assert vx == pytest.approx(system.p[0] * system.mass_inv)
95
+ assert vy == pytest.approx(system.p[1] * system.mass_inv)
96
+ assert fx == pytest.approx(0.0)
97
+ assert fy == pytest.approx(-system.m * system.g)
98
+
99
+
100
+ def test_ode_system_derivatives_matches_foucault_equations_by_hand():
101
+ system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.5, -0.2])
102
+ d = system.derivatives(0.0, system.state)
103
+ x, y, vx, vy = system.state
104
+ assert d[0] == pytest.approx(vx)
105
+ assert d[1] == pytest.approx(vy)
106
+ assert d[2] == pytest.approx(-(system.omega0**2) * x + 2 * system.omega_z * vy)
107
+ assert d[3] == pytest.approx(-(system.omega0**2) * y - 2 * system.omega_z * vx)
108
+
109
+
110
+ def test_ode_system_integrate_rejects_unknown_method():
111
+ system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.0, 0.0])
112
+ with pytest.raises(ValueError):
113
+ system.integrate((0.0, 1.0), dt=0.1, method="bogus")
@@ -0,0 +1,84 @@
1
+ """Targeted checks for physicskit.classical.systems.chains.SineGordonChain.kink beyond
2
+ energy conservation (covered in test_conservation.py): this exists
3
+ because of two real bugs found while building the examples gallery --
4
+ the original boost formula had a sign error, and using the default
5
+ width (2.0, arbitrary) rather than sqrt(k/m) (1.0, for the default
6
+ m=1, k=1) gave a profile that isn't actually a valid soliton and
7
+ visibly relaxes/radiates instead of propagating cleanly.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import numpy as np
13
+
14
+ from physicskit.classical.systems.chains import SineGordonChain
15
+
16
+
17
+ def _front_position(q: np.ndarray) -> float:
18
+ """Interpolated lattice site where q crosses pi (the kink's center)."""
19
+ return float(np.interp(np.pi, q, np.arange(len(q))))
20
+
21
+
22
+ def test_sine_gordon_chain_defaults_to_zero_state_when_q0_p0_omitted():
23
+ system = SineGordonChain(n=10)
24
+ np.testing.assert_allclose(system.q, np.zeros(10))
25
+ np.testing.assert_allclose(system.p, np.zeros(10))
26
+
27
+
28
+ def test_kink_moves_in_the_requested_direction():
29
+ n = 200
30
+ q_right, p_right = SineGordonChain.kink(n, center=80, width=1.0, velocity=0.4, polarity=1)
31
+ q_left, p_left = SineGordonChain.kink(n, center=80, width=1.0, velocity=-0.4, polarity=1)
32
+
33
+ right_mover = SineGordonChain(n=n, q0=q_right, p0=p_right)
34
+ left_mover = SineGordonChain(n=n, q0=q_left, p0=p_left)
35
+
36
+ res_right = right_mover.integrate((0, 50), dt=0.005, method="yoshida4")
37
+ res_left = left_mover.integrate((0, 50), dt=0.005, method="yoshida4")
38
+
39
+ assert _front_position(res_right.q[-1]) > _front_position(res_right.q[0])
40
+ assert _front_position(res_left.q[-1]) < _front_position(res_left.q[0])
41
+
42
+
43
+ def test_kink_with_matched_width_keeps_its_shape():
44
+ """width = sqrt(k/m) = 1 (the default) is the only width for which
45
+ the profile is a genuine static/moving solution; its steepness
46
+ should stay essentially constant as it propagates."""
47
+ n = 200
48
+ q0, p0 = SineGordonChain.kink(n, center=60, width=1.0, velocity=0.3, polarity=1)
49
+ system = SineGordonChain(n=n, q0=q0, p0=p0)
50
+ result = system.integrate((0, 150), dt=0.005, method="yoshida4")
51
+
52
+ def measured_width(q):
53
+ return 2.0 / np.max(np.abs(np.diff(q)))
54
+
55
+ w0 = measured_width(result.q[0])
56
+ wf = measured_width(result.q[-1])
57
+ assert abs(wf - w0) / w0 < 0.15
58
+
59
+
60
+ def test_kink_with_mismatched_width_visibly_relaxes():
61
+ """A width that does NOT match sqrt(k/m) is not a genuine soliton
62
+ solution; its steepness should visibly change (relax toward the
63
+ natural width) as the system evolves -- the behavior the width=1.0
64
+ fix above avoids."""
65
+ n = 200
66
+ q0, p0 = SineGordonChain.kink(n, center=100, width=3.0, velocity=0.0, polarity=1)
67
+ system = SineGordonChain(n=n, q0=q0, p0=p0)
68
+ result = system.integrate((0, 100), dt=0.005, method="yoshida4")
69
+
70
+ def measured_width(q):
71
+ return 2.0 / np.max(np.abs(np.diff(q)))
72
+
73
+ w0 = measured_width(result.q[0])
74
+ wf = measured_width(result.q[-1])
75
+ assert abs(wf - w0) / w0 > 0.15
76
+
77
+
78
+ def test_kink_rejects_superluminal_velocity():
79
+ import pytest
80
+
81
+ with pytest.raises(ValueError):
82
+ SineGordonChain.kink(100, center=50, width=1.0, velocity=1.0)
83
+ with pytest.raises(ValueError):
84
+ SineGordonChain.kink(100, center=50, width=1.0, velocity=-1.5)
@@ -0,0 +1,244 @@
1
+ """Verify |H(t) - H(0)| / |H(0)| < 1e-6 over 10**5 integration steps for
2
+ every conservative system in physicskit.classical, using each system's default
3
+ symplectic integrator (Yoshida4 for separable Hamiltonians,
4
+ implicit-midpoint for non-separable Lagrangian/rigid-body systems).
5
+
6
+ Each system's timestep below was chosen (see the module-level
7
+ ``N_STEPS``/tolerance) so that the discretization error -- which for a
8
+ 2nd/4th-order symplectic method scales as a fixed power of dt -- comfortably
9
+ clears the 1e-6 relative-drift bar over exactly 10**5 steps; see
10
+ individual test docstrings for the physical timescale each dt resolves.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import numpy as np
16
+ import pytest
17
+
18
+ from physicskit.classical.systems.chains import FPUTChain, HarmonicChain, SineGordonChain
19
+ from physicskit.classical.systems.hamiltonian import HenonHeilesSystem, PendulumSwarm
20
+ from physicskit.classical.systems.lagrangian import BeadOnRotatingHoop, CoupledOscillators, DoublePendulum, ElasticPendulum
21
+ from physicskit.classical.systems.newtonian import FoucaultPendulum, KeplerSystem, ProjectileMotion
22
+ from physicskit.classical.systems.rotations import EulerTop, HeavySymmetricTop
23
+ from physicskit.classical.utils.conservation import lrl_drift, relative_energy_drift
24
+
25
+ N_STEPS = 100_000
26
+ TOL = 1e-6
27
+
28
+
29
+ def _assert_conserved(system, dt, method, n_steps=N_STEPS, tol=TOL):
30
+ result = system.integrate((0.0, n_steps * dt), dt=dt, method=method)
31
+ assert len(result.t) - 1 == n_steps
32
+ drift = relative_energy_drift(result.energy)
33
+ assert np.max(drift) < tol, f"energy drift {np.max(drift):.3e} exceeds tolerance {tol:.1e}"
34
+ return result
35
+
36
+
37
+ def test_projectile_motion_conserves_energy():
38
+ """Free fall with an oblique launch velocity (the "cannonball"
39
+ problem), unconstrained over 1e5 steps (~100s of flight, well past
40
+ where a real cannonball would have hit the ground -- gravity alone
41
+ is still exactly conservative for arbitrarily long unconstrained
42
+ free fall)."""
43
+ system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81)
44
+ _assert_conserved(system, dt=1e-3, method="yoshida4")
45
+
46
+
47
+ def test_projectile_motion_matches_analytic_parabola():
48
+ """Over the ~2s a real cannonball is actually airborne, a constant
49
+ force is exactly separable, so the symplectic integrator should
50
+ reproduce Galileo's closed-form parabola to floating-point
51
+ precision, not merely to some small tolerance."""
52
+ system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81)
53
+ result = system.integrate((0.0, 2.0), dt=1e-3, method="yoshida4")
54
+ x_analytic, y_analytic = ProjectileMotion.analytic_trajectory(20.0, 45.0, 9.81, result.t)
55
+ assert np.max(np.abs(result.q[:, 0] - x_analytic)) < 1e-9
56
+ assert np.max(np.abs(result.q[:, 1] - y_analytic)) < 1e-9
57
+
58
+
59
+ def test_projectile_motion_with_drag_dissipates_energy_and_rejects_symplectic_methods():
60
+ """Quadratic air drag makes the system genuinely non-conservative:
61
+ energy must decrease monotonically, and requesting a symplectic
62
+ method that can't see the (velocity-dependent) drag force must be
63
+ refused rather than silently ignoring it."""
64
+ system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81, drag_coeff=0.05)
65
+ with pytest.raises(ValueError):
66
+ system.integrate((0.0, 1.0), dt=1e-3, method="yoshida4")
67
+ result = system.integrate((0.0, 2.0), dt=1e-3, method="rk4")
68
+ assert np.all(np.diff(result.energy) <= 1e-9)
69
+ assert result.energy[-1] < result.energy[0]
70
+
71
+
72
+ def test_kepler_pure_conserves_energy_and_lrl():
73
+ """Pure 1/r Kepler orbit: energy AND the LRL vector should both be
74
+ conserved (no precession without a perturbation)."""
75
+ system = KeplerSystem.from_orbital_elements(a=1.0, e=0.5, k=1.0, mu=1.0)
76
+ lrl0 = system.lrl_vector()
77
+ result = _assert_conserved(system, dt=1e-3, method="yoshida4")
78
+ drift = lrl_drift(system, result.q, result.p)
79
+ assert np.max(drift) / np.linalg.norm(lrl0) < 1e-4
80
+
81
+
82
+ def test_kepler_perturbed_conserves_energy():
83
+ """With the post-Newtonian 1/r**3 correction switched on, the orbit
84
+ precesses (LRL direction rotates) but total energy remains conserved."""
85
+ system = KeplerSystem.from_orbital_elements(a=1.0, e=0.3, k=1.0, mu=1.0, c_pn=0.01)
86
+ _assert_conserved(system, dt=1e-3, method="yoshida4")
87
+
88
+
89
+ def test_foucault_pendulum_conserves_energy():
90
+ """The Coriolis term is velocity-perpendicular and does no work, so
91
+ energy is exactly conserved even as the swing plane precesses;
92
+ implicit midpoint preserves this quadratic invariant to machine
93
+ precision for this linear system."""
94
+ system = FoucaultPendulum.from_deflection(amplitude=1.0, latitude_deg=48.85)
95
+ _assert_conserved(system, dt=1e-3, method="implicit_midpoint")
96
+
97
+
98
+ def test_foucault_pendulum_matches_analytic_precession():
99
+ """Compare the numerical trajectory against the closed-form
100
+ small-oscillation solution, and check the swing plane is unwound
101
+ (stays near y=0) in the frame corotating at ``omega_z``."""
102
+ system = FoucaultPendulum.from_deflection(amplitude=1.0, latitude_deg=48.85)
103
+ result = system.integrate((0.0, 200.0), dt=1e-3, method="implicit_midpoint")
104
+ x_analytic, y_analytic = FoucaultPendulum.analytic_solution([1.0, 0.0], [0.0, 0.0], system.omega0, system.omega_z, result.t)
105
+ assert np.max(np.abs(result.y[:, 0] - x_analytic)) < 1e-5
106
+ assert np.max(np.abs(result.y[:, 1] - y_analytic)) < 1e-5
107
+
108
+ x_rot, y_rot = FoucaultPendulum.to_corotating_frame(result.y[:, 0], result.y[:, 1], result.t, system.omega_z)
109
+ assert np.max(np.abs(y_rot)) < 1e-3
110
+
111
+
112
+ @pytest.mark.slow
113
+ def test_double_pendulum_conserves_energy():
114
+ """Chaotic, non-separable system; validated via the implicit-midpoint
115
+ symplectic integrator on the Legendre-transformed Hamiltonian."""
116
+ system = DoublePendulum([2.0, 1.0], [0.5, -0.3])
117
+ _assert_conserved(system, dt=3e-5, method="implicit_midpoint", n_steps=10_000)
118
+
119
+
120
+ @pytest.mark.slow
121
+ def test_bead_on_rotating_hoop_conserves_energy():
122
+ system = BeadOnRotatingHoop(0.3, 0.0, omega=3.0)
123
+ _assert_conserved(system, dt=2.2e-5, method="implicit_midpoint", n_steps=10_000)
124
+
125
+
126
+ @pytest.mark.slow
127
+ def test_coupled_oscillators_conserve_energy():
128
+ system = CoupledOscillators([0.1, -0.2, 0.15], [0.0, 0.0, 0.0], n=3)
129
+ _assert_conserved(system, dt=1e-4, method="implicit_midpoint", n_steps=10_000)
130
+
131
+
132
+ def test_henon_heiles_conserves_energy():
133
+ system = HenonHeilesSystem(np.array([0.0, 0.3]), np.array([0.3, 0.0]))
134
+ _assert_conserved(system, dt=5e-3, method="yoshida4")
135
+
136
+
137
+ @pytest.mark.slow
138
+ def test_elastic_pendulum_conserves_energy():
139
+ """2-DOF nonlinear (stretch, swing) Lagrangian system; validated via
140
+ implicit midpoint on the Legendre-transformed Hamiltonian, same as
141
+ DoublePendulum."""
142
+ system = ElasticPendulum([0.1, 0.05], [0.0, 0.0])
143
+ _assert_conserved(system, dt=2e-5, method="implicit_midpoint", n_steps=10_000)
144
+
145
+
146
+ @pytest.mark.slow
147
+ def test_pendulum_swarm_conserves_energy_and_liouville_area():
148
+ """Every particle in the swarm independently conserves energy over
149
+ a 1e4-step run, and -- checked separately over a shorter
150
+ window, before the patch shears into a filament thin enough that a
151
+ convex-hull area estimate starts overstating it -- the occupied
152
+ phase-space area (a proxy for Liouville's theorem) is preserved."""
153
+ system = PendulumSwarm.from_box(1.0, 0.0, 0.05, 0.05, n=200, seed=1)
154
+ _assert_conserved(system, dt=1e-3, method="yoshida4", n_steps=10_000)
155
+
156
+ system2 = PendulumSwarm.from_box(1.0, 0.0, 0.05, 0.05, n=200, seed=1)
157
+ area0 = system2.phase_space_area()
158
+ system2.integrate((0.0, 5.0), dt=1e-3, method="yoshida4")
159
+ area1 = system2.phase_space_area()
160
+ assert abs(area1 - area0) / area0 < 0.05
161
+
162
+
163
+ def test_harmonic_chain_conserves_energy():
164
+ rng = np.random.default_rng(0)
165
+ n = 16
166
+ system = HarmonicChain(rng.normal(0, 0.1, n), np.zeros(n), k=1.0)
167
+ _assert_conserved(system, dt=1e-2, method="yoshida4")
168
+
169
+
170
+ def test_fput_chain_conserves_energy():
171
+ """N=32 Fermi-Pasta-Ulam-Tsingou beta-lattice."""
172
+ system = FPUTChain(n=32, beta=0.7, mode=1, amplitude=0.5)
173
+ _assert_conserved(system, dt=5e-2, method="yoshida4")
174
+
175
+
176
+ def test_fput_chain_shows_mode_recurrence():
177
+ """The energy initially placed in the first normal mode should
178
+ remain the dominant mode throughout (the FPUT non-ergodicity
179
+ signature), not spread evenly across all 32 modes."""
180
+ system = FPUTChain(n=32, beta=0.7, mode=1, amplitude=0.5)
181
+ e0 = system.energy()
182
+ result = system.integrate((0.0, 5000.0), dt=5e-2, method="yoshida4")
183
+ energies = system.modal_energies(result.q[-1], result.p[-1])
184
+ assert energies[0] / e0 > 0.5
185
+
186
+
187
+ def test_sine_gordon_chain_conserves_energy():
188
+ q0, p0 = SineGordonChain.kink(100, center=50, width=1.0, velocity=0.2)
189
+ system = SineGordonChain(n=100, q0=q0, p0=p0)
190
+ _assert_conserved(system, dt=1e-2, method="yoshida4")
191
+
192
+
193
+ def test_euler_top_conserves_energy_and_angular_momentum():
194
+ """Torque-free asymmetric top: both energy and |L|^2 are quadratic
195
+ invariants exactly preserved by implicit midpoint."""
196
+ system = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
197
+ L0 = system.angular_momentum_squared()
198
+ _assert_conserved(system, dt=1e-3, method="implicit_midpoint")
199
+ L1 = system.angular_momentum_squared()
200
+ assert abs(L1 - L0) / L0 < 1e-6
201
+
202
+
203
+ def test_euler_top_intermediate_axis_instability():
204
+ """Spinning (almost) exactly about the intermediate-inertia axis
205
+ (I1 < I2 < I3) must show the tumbling instability: the other two
206
+ body-frame components grow far beyond their tiny initial values."""
207
+ system = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
208
+ result = system.integrate((0.0, 100.0), dt=1e-3, method="implicit_midpoint")
209
+ assert np.max(np.abs(result.y[:, 0])) > 0.5 # w1 tumbles far above its 0.01 start
210
+
211
+
212
+ def test_euler_top_stable_axis_no_tumbling():
213
+ """Spinning about the smallest-inertia axis is stable: the small
214
+ transverse perturbations stay small for the whole integration."""
215
+ system = EulerTop([1.0, 0.01, 0.01], I1=1.0, I2=2.0, I3=3.0)
216
+ result = system.integrate((0.0, 100.0), dt=1e-3, method="implicit_midpoint")
217
+ assert np.max(np.abs(result.y[:, 1])) < 0.1
218
+ assert np.max(np.abs(result.y[:, 2])) < 0.1
219
+
220
+
221
+ @pytest.mark.slow
222
+ def test_heavy_symmetric_top_conserves_energy():
223
+ system = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81)
224
+ _assert_conserved(system, dt=5e-5, method="implicit_midpoint", n_steps=10_000)
225
+
226
+
227
+ @pytest.mark.parametrize(
228
+ "system_factory,dt",
229
+ [
230
+ (lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5), 1e-3),
231
+ (lambda: HenonHeilesSystem(np.array([0.0, 0.3]), np.array([0.3, 0.0])), 5e-3),
232
+ ],
233
+ )
234
+ def test_verlet_and_yoshida4_agree_to_leading_order(system_factory, dt):
235
+ """Sanity check that the 2nd-order Verlet and 4th-order Yoshida4
236
+ backends integrate the same separable system to consistent, small
237
+ energy drift (Yoshida4 should be at least as good as Verlet)."""
238
+ sys_v = system_factory()
239
+ sys_y = system_factory()
240
+ res_v = sys_v.integrate((0.0, 2000 * dt), dt=dt, method="verlet")
241
+ res_y = sys_y.integrate((0.0, 2000 * dt), dt=dt, method="yoshida4")
242
+ drift_v = np.max(relative_energy_drift(res_v.energy))
243
+ drift_y = np.max(relative_energy_drift(res_y.energy))
244
+ assert drift_y <= drift_v * 10 # Yoshida4 is not meaningfully worse than Verlet
@@ -0,0 +1,47 @@
1
+ """Direct tests for the standalone helpers in
2
+ physicskit.classical.utils.conservation that test_conservation.py's
3
+ end-to-end energy-drift checks never call: energy_drift() (only its
4
+ relative sibling is used there), angular_momentum_2d()/
5
+ angular_momentum_drift() (no test currently checks angular momentum at
6
+ all), and relative_energy_drift()'s zero-initial-energy edge case."""
7
+
8
+ from __future__ import annotations
9
+
10
+ import numpy as np
11
+
12
+ from physicskit.classical.utils.conservation import (
13
+ angular_momentum_2d,
14
+ angular_momentum_drift,
15
+ energy_drift,
16
+ relative_energy_drift,
17
+ )
18
+
19
+
20
+ def test_energy_drift_is_absolute_deviation_from_initial_value():
21
+ energy = np.array([10.0, 10.5, 9.5, 10.0])
22
+ drift = energy_drift(energy)
23
+ np.testing.assert_allclose(drift, [0.0, 0.5, 0.5, 0.0])
24
+
25
+
26
+ def test_relative_energy_drift_is_infinite_when_initial_energy_is_zero():
27
+ energy = np.array([0.0, 1.0, -1.0])
28
+ drift = relative_energy_drift(energy)
29
+ assert np.all(np.isinf(drift))
30
+
31
+
32
+ def test_angular_momentum_2d_matches_cross_product_formula():
33
+ q = np.array([[1.0, 0.0], [0.0, 2.0]])
34
+ p = np.array([[0.0, 1.0], [3.0, 0.0]])
35
+ L = angular_momentum_2d(q, p)
36
+ np.testing.assert_allclose(L, [1.0 * 1.0 - 0.0 * 0.0, 0.0 * 0.0 - 2.0 * 3.0])
37
+
38
+
39
+ def test_angular_momentum_drift_is_zero_for_a_conserved_circular_orbit():
40
+ """A particle moving on a circle at constant angular speed has
41
+ exactly conserved angular momentum, so drift should be exactly
42
+ zero at every sample."""
43
+ theta = np.linspace(0.0, 2.0 * np.pi, 50, endpoint=False)
44
+ q = np.column_stack([np.cos(theta), np.sin(theta)])
45
+ p = np.column_stack([-np.sin(theta), np.cos(theta)])
46
+ drift = angular_momentum_drift(q, p)
47
+ np.testing.assert_allclose(drift, 0.0, atol=1e-12)
@@ -0,0 +1,44 @@
1
+ """Tests for physicskit.classical.systems.hamiltonian's action-angle helper and
2
+ PendulumSwarm.per_particle_energy(), neither of which any other test in
3
+ the suite calls (test_conservation.py only checks PendulumSwarm's total
4
+ energy/Liouville area, and pendulum_action_angle isn't used anywhere)."""
5
+
6
+ from __future__ import annotations
7
+
8
+ import numpy as np
9
+ import pytest
10
+
11
+ from physicskit.classical.systems.hamiltonian import PendulumSwarm, pendulum_action_angle
12
+
13
+
14
+ def test_pendulum_action_angle_small_oscillation_limit_matches_harmonic_period():
15
+ """As E -> -g/l (the bottom of the well), a pendulum's period should
16
+ approach the small-oscillation harmonic result T = 2*pi/sqrt(g/l)."""
17
+ g_over_l = 2.0
18
+ _, period = pendulum_action_angle(-g_over_l + 1e-6, g_over_l)
19
+ assert period == pytest.approx(2.0 * np.pi / np.sqrt(g_over_l), rel=1e-3)
20
+
21
+
22
+ def test_pendulum_action_angle_rejects_energy_outside_librating_range():
23
+ g_over_l = 1.0
24
+ with pytest.raises(ValueError):
25
+ pendulum_action_angle(g_over_l, g_over_l)
26
+ with pytest.raises(ValueError):
27
+ pendulum_action_angle(-g_over_l, g_over_l)
28
+ with pytest.raises(ValueError):
29
+ pendulum_action_angle(2.0 * g_over_l, g_over_l)
30
+
31
+
32
+ def test_pendulum_swarm_per_particle_energy_sums_to_the_total_energy():
33
+ swarm = PendulumSwarm([0.1, 0.2, 0.3], [0.05, -0.05, 0.0], g_over_l=1.5)
34
+ per_particle = swarm.per_particle_energy()
35
+ assert per_particle.shape == (3,)
36
+ assert np.sum(per_particle) == pytest.approx(swarm.energy())
37
+
38
+
39
+ def test_pendulum_swarm_per_particle_energy_accepts_explicit_state_override():
40
+ swarm = PendulumSwarm([0.1, 0.2, 0.3], [0.05, -0.05, 0.0], g_over_l=1.5)
41
+ default = swarm.per_particle_energy()
42
+ overridden = swarm.per_particle_energy(q=np.zeros(3), p=np.zeros(3))
43
+ np.testing.assert_allclose(overridden, -1.5)
44
+ assert not np.allclose(default, overridden)
@@ -0,0 +1,123 @@
1
+ """Direct unit tests for physicskit.classical.core.integrators, independent of any
2
+ physicskit.classical.systems class. These exercise the four integrators (and the
3
+ make_separable_derivatives helper) against a plain harmonic oscillator
4
+ and a simple exponential-decay ODE with known analytic solutions, so a
5
+ regression here is caught at the numerical-core level rather than only
6
+ showing up as a mysterious failure in some system built on top of it.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+ from numba import njit
13
+
14
+ from physicskit.classical.core.integrators import (
15
+ implicit_midpoint_integrate,
16
+ implicit_midpoint_step,
17
+ make_separable_derivatives,
18
+ rk4_integrate,
19
+ velocity_verlet_integrate,
20
+ yoshida4_integrate,
21
+ )
22
+
23
+
24
+ @njit(cache=False)
25
+ def _sho_force(q, t):
26
+ return -q # harmonic oscillator, k = m = 1
27
+
28
+
29
+ @njit(cache=False)
30
+ def _exp_decay_deriv(t, y):
31
+ return -y
32
+
33
+
34
+ def _sho_energy(q, p):
35
+ return 0.5 * q**2 + 0.5 * p**2
36
+
37
+
38
+ def test_rk4_matches_analytic_exponential_decay():
39
+ y0 = np.array([1.0])
40
+ dt = 1e-3
41
+ n_steps = 1000
42
+ ts, ys = rk4_integrate(_exp_decay_deriv, y0, 0.0, n_steps, dt)
43
+ analytic = np.exp(-ts)
44
+ assert np.max(np.abs(ys[:, 0] - analytic)) < 1e-9
45
+
46
+
47
+ def test_verlet_and_yoshida4_conserve_harmonic_oscillator_energy():
48
+ q0, p0 = np.array([1.0]), np.array([0.0])
49
+ dt, n_steps = 0.01, 50_000
50
+ mass_inv = 1.0
51
+
52
+ ts, qs, ps = velocity_verlet_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
53
+ E = _sho_energy(qs[:, 0], ps[:, 0])
54
+ assert np.max(np.abs(E - E[0])) / E[0] < 1e-3 # 2nd order, bounded oscillation
55
+
56
+ ts, qs, ps = yoshida4_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
57
+ E4 = _sho_energy(qs[:, 0], ps[:, 0])
58
+ assert np.max(np.abs(E4 - E4[0])) / E4[0] < 1e-8 # 4th order: much tighter at the same dt
59
+
60
+
61
+ def test_yoshida4_more_accurate_than_verlet_at_fixed_dt():
62
+ """4th order should beat 2nd order by several orders of magnitude
63
+ at a dt small enough for both to be in their asymptotic regime."""
64
+ q0, p0 = np.array([1.0]), np.array([0.0])
65
+ dt, n_steps = 0.02, 5_000
66
+ mass_inv = 1.0
67
+
68
+ _, qs_v, ps_v = velocity_verlet_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
69
+ _, qs_y, ps_y = yoshida4_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
70
+
71
+ drift_v = np.max(np.abs(_sho_energy(qs_v[:, 0], ps_v[:, 0]) - 0.5))
72
+ drift_y = np.max(np.abs(_sho_energy(qs_y[:, 0], ps_y[:, 0]) - 0.5))
73
+ assert drift_y < drift_v * 1e-3
74
+
75
+
76
+ def test_implicit_midpoint_conserves_harmonic_oscillator_energy():
77
+ y0 = np.array([1.0, 0.0])
78
+
79
+ @njit(cache=False)
80
+ def deriv(t, y):
81
+ out = np.empty(2)
82
+ out[0] = y[1]
83
+ out[1] = -y[0]
84
+ return out
85
+
86
+ dt, n_steps = 0.01, 50_000
87
+ ts, ys = implicit_midpoint_integrate(deriv, y0, 0.0, n_steps, dt)
88
+ E = _sho_energy(ys[:, 0], ys[:, 1])
89
+ assert np.max(np.abs(E - E[0])) / E[0] < 1e-10
90
+
91
+
92
+ def test_implicit_midpoint_step_exactly_preserves_norm_for_a_rotation_generator():
93
+ """For dy/dt = A y with A skew-symmetric (a pure rotation vector
94
+ field), |y|^2 is a quadratic invariant; a single implicit-midpoint
95
+ step -- solved via the Cayley transform of A, not the matrix
96
+ exponential -- should still preserve |y| exactly (to machine
97
+ precision) even though it does not reproduce the exact rotation
98
+ angle. This is a direct check of the step formula itself, distinct
99
+ from the aggregate energy-conservation checks elsewhere."""
100
+
101
+ @njit(cache=False)
102
+ def deriv(t, y):
103
+ out = np.empty(2)
104
+ out[0] = -y[1]
105
+ out[1] = y[0]
106
+ return out
107
+
108
+ y0 = np.array([1.0, 0.0])
109
+ dt = 0.1
110
+ y1 = implicit_midpoint_step(deriv, 0.0, y0, dt)
111
+
112
+ assert abs(np.linalg.norm(y1) - np.linalg.norm(y0)) < 1e-12
113
+ assert y1[1] > 0 # rotated counter-clockwise, matching the sign of the generator
114
+
115
+
116
+ def test_make_separable_derivatives_matches_verlet_force():
117
+ mass_inv = 1.0
118
+ ndof = 1
119
+ deriv = make_separable_derivatives(_sho_force, mass_inv, ndof)
120
+ y = np.array([0.5, -0.3])
121
+ dy = deriv(0.0, y)
122
+ # dq/dt = p/m, dp/dt = force(q) = -q
123
+ assert np.allclose(dy, [-0.3, -0.5])