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,69 @@
1
+ """Tests for physicskit.classical.systems.lagrangian's concrete systems and,
2
+ via them, the LagrangianSystem base class's own defaults that
3
+ test_conservation.py's energy-conservation checks never reach: direct
4
+ calls to ``acceleration()``/``derivatives()`` (every conservation test
5
+ goes through ``integrate()``, which calls the underlying njit
6
+ callbacks directly rather than through those convenience methods),
7
+ ``ElasticPendulum.positions()``, and the ``rk4``/unknown-method
8
+ branches of ``LagrangianSystem.integrate()`` (every existing test uses
9
+ the default ``implicit_midpoint``).
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import numpy as np
15
+ import pytest
16
+
17
+ from physicskit.classical.systems.lagrangian import BeadOnRotatingHoop, ElasticPendulum
18
+
19
+
20
+ def test_bead_on_rotating_hoop_acceleration_matches_euler_lagrange_by_hand():
21
+ """theta'' = Omega^2 sin(theta) cos(theta) - (g/R) sin(theta), derived
22
+ by hand from the Lagrangian in the class docstring."""
23
+ system = BeadOnRotatingHoop(0.3, 0.1, R=1.5, omega=2.0, g=9.81)
24
+ theta, thetadot = system.q, system.qdot
25
+ accel = system.acceleration(theta, thetadot, 0.0)
26
+ expected = system.omega**2 * np.sin(theta) * np.cos(theta) - (system.g / system.R) * np.sin(theta)
27
+ np.testing.assert_allclose(accel, expected, rtol=1e-6)
28
+
29
+
30
+ def test_bead_on_rotating_hoop_derivatives_matches_qdot_and_acceleration():
31
+ system = BeadOnRotatingHoop(0.3, 0.1, R=1.5, omega=2.0, g=9.81)
32
+ d = system.derivatives(0.0, system.state)
33
+ np.testing.assert_allclose(d[:1], system.qdot)
34
+ np.testing.assert_allclose(d[1:], system.acceleration(system.q, system.qdot, 0.0))
35
+
36
+
37
+ def test_lagrangian_system_integrate_rk4_matches_implicit_midpoint_over_short_time():
38
+ """rk4 is not symplectic, but over a short-enough window both
39
+ integrators should agree closely -- the standard cross-check that
40
+ the rk4 branch (which uses the direct (q, qdot) acceleration form,
41
+ not the canonical Hamiltonian form implicit_midpoint uses) is wired
42
+ correctly."""
43
+ dt = 1e-4
44
+ result_rk4 = BeadOnRotatingHoop(0.3, 0.0, omega=3.0).integrate((0.0, 0.05), dt=dt, method="rk4")
45
+ result_im = BeadOnRotatingHoop(0.3, 0.0, omega=3.0).integrate((0.0, 0.05), dt=dt, method="implicit_midpoint")
46
+ assert np.max(np.abs(result_rk4.q - result_im.q)) < 1e-5
47
+
48
+
49
+ def test_lagrangian_system_integrate_rejects_unknown_method():
50
+ system = BeadOnRotatingHoop(0.3, 0.0, omega=3.0)
51
+ with pytest.raises(ValueError):
52
+ system.integrate((0.0, 1.0), dt=0.1, method="bogus")
53
+
54
+
55
+ def test_elastic_pendulum_positions_matches_polar_to_cartesian_by_hand():
56
+ system = ElasticPendulum([0.2, 0.3], [0.0, 0.0], L0=1.0)
57
+ x, y = system.positions()
58
+ s, theta = system.q
59
+ r = system.L0 + s
60
+ assert x == pytest.approx(r * np.sin(theta))
61
+ assert y == pytest.approx(-r * np.cos(theta))
62
+
63
+
64
+ def test_elastic_pendulum_positions_defaults_to_current_state_but_accepts_override():
65
+ system = ElasticPendulum([0.2, 0.3], [0.0, 0.0], L0=1.0)
66
+ default = system.positions()
67
+ overridden = system.positions(np.array([0.0, 0.0]))
68
+ np.testing.assert_allclose(overridden, [0.0, -1.0])
69
+ assert not np.allclose(default, overridden)
@@ -0,0 +1,45 @@
1
+ """Tests for physicskit.classical.systems.newtonian helpers that
2
+ test_conservation.py's energy-conservation checks never call directly:
3
+ ProjectileMotion.range_and_max_height() (a standalone closed-form
4
+ helper, not used by any trajectory check), and FoucaultPendulum's
5
+ position/velocity convenience properties and precession_period()."""
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ from physicskit.classical.systems.newtonian import FoucaultPendulum, ProjectileMotion
13
+
14
+
15
+ def test_range_and_max_height_matches_analytic_trajectory_peak_and_landing():
16
+ """Cross-check the closed-form range/max-height formula against the
17
+ same speed/angle's full analytic_trajectory(), by finding where that
18
+ trajectory peaks and lands."""
19
+ speed, angle_deg, g = 20.0, 30.0, 9.81
20
+ rng, max_height = ProjectileMotion.range_and_max_height(speed, angle_deg, g)
21
+
22
+ t = np.linspace(0.0, 5.0, 200_000)
23
+ x, y = ProjectileMotion.analytic_trajectory(speed, angle_deg, g, t)
24
+ assert max_height == pytest.approx(y.max(), abs=1e-3)
25
+ landing_idx = np.argmax(y[1:] < 0.0) + 1
26
+ assert rng == pytest.approx(x[landing_idx], rel=1e-3)
27
+
28
+
29
+ def test_range_and_max_height_with_initial_height_raises_both():
30
+ rng_ground, height_ground = ProjectileMotion.range_and_max_height(15.0, 45.0, 9.81, height=0.0)
31
+ rng_elevated, height_elevated = ProjectileMotion.range_and_max_height(15.0, 45.0, 9.81, height=10.0)
32
+ assert rng_elevated > rng_ground
33
+ assert height_elevated == pytest.approx(height_ground + 10.0)
34
+
35
+
36
+ def test_foucault_pendulum_position_and_velocity_properties_match_state():
37
+ system = FoucaultPendulum(q0=[1.0, 2.0], v0=[0.3, -0.4])
38
+ np.testing.assert_allclose(system.position, [1.0, 2.0])
39
+ np.testing.assert_allclose(system.velocity, [0.3, -0.4])
40
+
41
+
42
+ def test_foucault_pendulum_precession_period_matches_sidereal_day_over_sin_latitude():
43
+ system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.0, 0.0], latitude_deg=48.85)
44
+ expected = (2.0 * np.pi / system.omega_earth) / np.sin(np.deg2rad(48.85))
45
+ assert system.precession_period() == pytest.approx(expected)
@@ -0,0 +1,160 @@
1
+ """Targeted checks for physicskit.classical.systems.rotations beyond energy conservation
2
+ (covered in test_conservation.py): the closed-form/harmonic-approximation
3
+ precession and nutation frequencies for HeavySymmetricTop, validated
4
+ against an FFT of an actual integrated trajectory.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import numpy as np
10
+ import pytest
11
+
12
+ from physicskit.classical.systems.rotations import (
13
+ EulersDisk,
14
+ EulerTop,
15
+ HeavySymmetricTop,
16
+ Rattleback,
17
+ eulers_disk_theta_analytic,
18
+ find_theta_equilibrium,
19
+ nutation_frequency,
20
+ precession_frequency,
21
+ )
22
+
23
+
24
+ def _measured_nutation_frequency(t: np.ndarray, theta_t: np.ndarray) -> float:
25
+ dt = t[1] - t[0]
26
+ spectrum = np.abs(np.fft.rfft(theta_t - theta_t.mean()))
27
+ freqs = np.fft.rfftfreq(len(theta_t), d=dt) * 2.0 * np.pi
28
+ peak_idx = np.argmax(spectrum[1:]) + 1
29
+ return float(freqs[peak_idx])
30
+
31
+
32
+ def test_theta_equilibrium_is_a_true_minimum_of_effective_potential():
33
+ I1, I3, Mgl = 1.0, 0.5, 9.81
34
+ p_phi, p_psi = 8.78, 10.0
35
+ theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
36
+ assert 0.0 < theta_eq < np.pi
37
+
38
+ from physicskit.classical.systems.rotations import effective_potential_symmetric_top
39
+
40
+ v_at_eq = effective_potential_symmetric_top(theta_eq, p_phi, p_psi, I1, I3, Mgl)
41
+ for delta in (-0.05, 0.05):
42
+ assert effective_potential_symmetric_top(theta_eq + delta, p_phi, p_psi, I1, I3, Mgl) > v_at_eq
43
+
44
+
45
+ def test_nutation_frequency_without_explicit_theta_eq_matches_passing_it_explicitly():
46
+ """theta_eq is optional and, if omitted, is located internally via
47
+ find_theta_equilibrium -- check that path gives the same answer as
48
+ passing the same equilibrium explicitly."""
49
+ I1, I3, Mgl = 1.0, 0.5, 9.81
50
+ p_phi, p_psi = 8.78, 10.0
51
+ theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
52
+ explicit = nutation_frequency(p_phi, p_psi, I1, I3, Mgl, theta_eq=theta_eq)
53
+ implicit = nutation_frequency(p_phi, p_psi, I1, I3, Mgl)
54
+ assert implicit == pytest.approx(explicit)
55
+
56
+
57
+ def test_heavy_symmetric_top_precession_nutation_rate_matches_qdot():
58
+ top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.2, -0.1, 0.3], I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81)
59
+ phidot, thetadot = top.precession_nutation_rate()
60
+ assert phidot == pytest.approx(top.qdot[0])
61
+ assert thetadot == pytest.approx(top.qdot[1])
62
+
63
+
64
+ def test_euler_top_omega_and_quaternion_properties_match_state():
65
+ omega0 = [0.01, 1.0, 0.01]
66
+ quat0 = [1.0, 0.0, 0.0, 0.0]
67
+ top = EulerTop(omega0, I1=1.0, I2=2.0, I3=3.0, quat0=quat0)
68
+ np.testing.assert_allclose(top.omega, omega0)
69
+ np.testing.assert_allclose(top.quaternion, quat0)
70
+
71
+
72
+ def test_eulers_disk_theta_and_phi_properties_match_state():
73
+ disk = EulersDisk(0.5, phi0=1.2, decay_rate=0.02, precession_const=1.0)
74
+ assert disk.theta == pytest.approx(0.5)
75
+ assert disk.phi == pytest.approx(1.2)
76
+
77
+
78
+ @pytest.mark.slow
79
+ def test_nutation_frequency_matches_fft_of_real_trajectory():
80
+ I1, I3, M, l, g = 1.0, 0.5, 1.0, 1.0, 9.81
81
+ top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=I1, I3=I3, M=M, l=l, g=g)
82
+ q0, qdot0 = top.q.copy(), top.qdot.copy()
83
+ p_phi, _, p_psi = top.momentum(q0, qdot0)
84
+
85
+ theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, M * g * l)
86
+ predicted = nutation_frequency(p_phi, p_psi, I1, I3, M * g * l, theta_eq=theta_eq)
87
+
88
+ result = top.integrate((0, 10.0), dt=2e-5, method="implicit_midpoint")
89
+ measured = _measured_nutation_frequency(result.t, result.q[:, 1])
90
+
91
+ assert abs(measured - predicted) / predicted < 0.02
92
+
93
+
94
+ @pytest.mark.slow
95
+ def test_precession_frequency_matches_mean_phi_rate():
96
+ I1, I3, M, l, g = 1.0, 0.5, 1.0, 1.0, 9.81
97
+ top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=I1, I3=I3, M=M, l=l, g=g)
98
+ q0, qdot0 = top.q.copy(), top.qdot.copy()
99
+ p_phi, _, p_psi = top.momentum(q0, qdot0)
100
+
101
+ theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, M * g * l)
102
+ predicted = precession_frequency(p_phi, p_psi, I1, theta_eq)
103
+
104
+ result = top.integrate((0, 10.0), dt=2e-5, method="implicit_midpoint")
105
+ measured = (result.q[-1, 0] - result.q[0, 0]) / (result.t[-1] - result.t[0])
106
+
107
+ assert abs(measured - predicted) / abs(predicted) < 0.02
108
+
109
+
110
+ def test_eulers_disk_theta_matches_closed_form_solution():
111
+ """Well below the collapse time (where the regularizing theta_floor
112
+ never engages), the numerically integrated theta(t) should match the
113
+ closed-form solution `eulers_disk_theta_analytic`."""
114
+ theta0, decay_rate = 0.5, 0.02
115
+ t_collapse = theta0**2 / (2.0 * decay_rate)
116
+
117
+ disk = EulersDisk(theta0, decay_rate=decay_rate, precession_const=1.0, theta_floor=1e-4)
118
+ t_end = 0.5 * t_collapse
119
+ dt = 1e-3
120
+ result = disk.integrate((0.0, t_end), dt=dt, method="rk4")
121
+
122
+ predicted = eulers_disk_theta_analytic(result.t, theta0, decay_rate)
123
+ np.testing.assert_allclose(result.y[:, 0], predicted, atol=1e-3)
124
+
125
+
126
+ def test_eulers_disk_precession_speeds_up_as_it_runs_down():
127
+ """The whole point of the model: phi advances faster and faster per
128
+ unit time as theta shrinks, since dphi/dt = precession_const/theta."""
129
+ disk = EulersDisk(0.5, decay_rate=0.02, precession_const=1.0, theta_floor=1e-4)
130
+ result = disk.integrate((0.0, 5.0), dt=1e-3, method="rk4")
131
+ phi_rate = np.diff(result.y[:, 1]) / np.diff(result.t)
132
+ # Later phi-rate (disk flatter, closer to collapse) must exceed the
133
+ # earlier phi-rate (disk still steep).
134
+ assert phi_rate[-1] > phi_rate[len(phi_rate) // 10]
135
+
136
+
137
+ def test_rattleback_reverses_spin_direction():
138
+ """Starting spun up hard about n3 with only a tiny rocking
139
+ perturbation, the toy model should show n3 collapse through zero and
140
+ become substantially negative (a genuine reversal), not merely decay
141
+ toward zero and stay positive."""
142
+ system = Rattleback([0.01, 0.01, 3.0])
143
+ result = system.integrate((0.0, 20.0), dt=5e-3, method="rk4")
144
+ n3 = result.y[:, 2]
145
+ assert n3[0] > 2.0
146
+ assert np.min(n3) < -0.5 # a substantial excursion to negative spin
147
+
148
+
149
+ def test_rattleback_energy_decays_overall():
150
+ """Despite the transient internal instability that drives the
151
+ reversal, the model is net dissipative (linear friction `mu` on all
152
+ three components): the quadratic 'energy' diagnostic averaged over the
153
+ first vs. last portion of a long run should show substantial decay."""
154
+ system = Rattleback([0.01, 0.01, 3.0])
155
+ result = system.integrate((0.0, 300.0), dt=5e-3, method="rk4")
156
+ energies = np.array([system.energy(y) for y in result.y])
157
+ n = len(energies)
158
+ early = np.mean(energies[: n // 20])
159
+ late = np.mean(energies[-n // 20 :])
160
+ assert late < 0.1 * early
@@ -0,0 +1,149 @@
1
+ """Tests for physicskit.classical.utils.stepsize.estimate_dt.
2
+
3
+ The critical property this checks is not just "it finds a dt," but
4
+ that the returned dt genuinely meets the tolerance at the *requested*
5
+ step count, including for a chaotic system where a naive short probe
6
+ can find a dt that looks fine briefly and then fails badly later.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+ import pytest
13
+
14
+ from physicskit.classical.systems.lagrangian import DoublePendulum
15
+ from physicskit.classical.systems.newtonian import KeplerSystem
16
+ from physicskit.classical.utils.conservation import relative_energy_drift
17
+ from physicskit.classical.utils.stepsize import estimate_dt
18
+
19
+
20
+ class _FakeSystemNeverConvergesAtProbe:
21
+ """Deterministically fails the fast probe stage at every dt, however
22
+ small -- used to exercise estimate_dt's shrink-loop-exhausted error
23
+ path without relying on a real system happening to never converge."""
24
+
25
+ def energy(self):
26
+ return 1.0
27
+
28
+ def integrate(self, t_span, dt, method):
29
+ del t_span, dt, method
30
+ result = type("Result", (), {})()
31
+ result.energy = np.array([1.0, 11.0])
32
+ return result
33
+
34
+
35
+ class _FakeSystemNeverConvergesAtTarget:
36
+ """Passes the fast probe at any dt, but fails verification at the
37
+ full target_steps at every dt -- used to exercise estimate_dt's
38
+ verification-stage error path (the case the module docstring
39
+ describes: a dt that looks fine briefly but fails over a longer
40
+ run) without depending on a real chaotic system's exact behavior."""
41
+
42
+ def __init__(self, probe_steps):
43
+ self.probe_steps = probe_steps
44
+
45
+ def energy(self):
46
+ return 1.0
47
+
48
+ def integrate(self, t_span, dt, method):
49
+ del method
50
+ n_steps = round((t_span[1] - t_span[0]) / dt)
51
+ result = type("Result", (), {})()
52
+ result.energy = np.array([1.0, 1.0]) if n_steps <= self.probe_steps else np.array([1.0, 11.0])
53
+ return result
54
+
55
+
56
+ def test_estimate_dt_quasi_periodic_kepler():
57
+ tol = 1e-6
58
+ est = estimate_dt(
59
+ lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
60
+ method="yoshida4",
61
+ tol=tol,
62
+ target_steps=20_000,
63
+ probe_steps=1000,
64
+ dt_initial=1e-2,
65
+ )
66
+ assert est.achieved_drift < tol
67
+
68
+ system = KeplerSystem.from_orbital_elements(a=1.0, e=0.5)
69
+ result = system.integrate((0.0, 20_000 * est.dt), dt=est.dt, method="yoshida4")
70
+ assert np.max(relative_energy_drift(result.energy)) < tol
71
+
72
+
73
+ def test_estimate_dt_bracket_search_grows_dt_across_several_doublings():
74
+ """A deliberately tiny dt_initial forces the fast-probe bracket
75
+ search to succeed across several successive doublings before it
76
+ finally fails, rather than failing on the very first growth step."""
77
+ est = estimate_dt(
78
+ lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
79
+ method="yoshida4",
80
+ tol=1e-6,
81
+ target_steps=2000,
82
+ probe_steps=1000,
83
+ dt_initial=1e-4,
84
+ )
85
+ assert est.achieved_drift < 1e-6
86
+ assert est.n_evaluations > 5 # several bracket/verification evaluations, not just one
87
+
88
+
89
+ def test_estimate_dt_bracket_search_shrinks_dt_across_several_halvings():
90
+ """A deliberately oversized dt_initial forces the fast-probe bracket
91
+ search to fail across several successive halvings before it
92
+ finally succeeds, rather than succeeding on the very first shrink."""
93
+ est = estimate_dt(
94
+ lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
95
+ method="yoshida4",
96
+ tol=1e-6,
97
+ target_steps=2000,
98
+ probe_steps=1000,
99
+ dt_initial=0.1024,
100
+ )
101
+ assert est.achieved_drift < 1e-6
102
+
103
+
104
+ def test_estimate_dt_raises_when_probe_never_converges_within_max_iter():
105
+ with pytest.raises(RuntimeError, match="shrink steps"):
106
+ estimate_dt(
107
+ lambda: _FakeSystemNeverConvergesAtProbe(),
108
+ method="unused",
109
+ tol=1e-6,
110
+ target_steps=100,
111
+ probe_steps=10,
112
+ dt_initial=1.0,
113
+ max_iter=3,
114
+ )
115
+
116
+
117
+ def test_estimate_dt_raises_when_target_verification_never_converges():
118
+ with pytest.raises(RuntimeError, match="verification shrink steps"):
119
+ estimate_dt(
120
+ lambda: _FakeSystemNeverConvergesAtTarget(probe_steps=10),
121
+ method="unused",
122
+ tol=1e-6,
123
+ target_steps=100,
124
+ probe_steps=10,
125
+ dt_initial=1.0,
126
+ max_iter=3,
127
+ )
128
+
129
+
130
+ def test_estimate_dt_verification_catches_chaotic_underestimate():
131
+ """A short probe alone can find a dt for the (chaotic) double
132
+ pendulum that passes over a few hundred steps but fails once run
133
+ for the full target_steps; estimate_dt must not return such a dt --
134
+ its own verification stage has to catch and shrink it."""
135
+ tol = 1e-6
136
+ target_steps = 20_000
137
+ est = estimate_dt(
138
+ lambda: DoublePendulum([2.0, 1.0], [0.5, -0.3]),
139
+ method="implicit_midpoint",
140
+ tol=tol,
141
+ target_steps=target_steps,
142
+ probe_steps=500,
143
+ dt_initial=1e-3,
144
+ )
145
+ assert est.achieved_drift < tol
146
+
147
+ system = DoublePendulum([2.0, 1.0], [0.5, -0.3])
148
+ result = system.integrate((0.0, target_steps * est.dt), dt=est.dt, method="implicit_midpoint")
149
+ assert np.max(relative_energy_drift(result.energy)) < tol
@@ -0,0 +1,125 @@
1
+ """Direct unit tests for physicskit.classical.utils.symbolic.LagrangianEngine, in
2
+ isolation from any physicskit.classical.systems class.
3
+
4
+ These exist specifically because of a real bug found during
5
+ development: a spurious sign flip in the Legendre-transform code
6
+ produced a self-consistent but unphysical (negative-definite kinetic
7
+ term) Hamiltonian for CoupledOscillators. It was NOT caught by that
8
+ system's own energy-conservation test, because implicit_midpoint
9
+ faithfully conserves whatever Hamiltonian it is handed, even a wrong
10
+ one -- energy conservation alone cannot distinguish a correctly-derived
11
+ Hamiltonian from a self-consistent but wrong one. The tests below check
12
+ LagrangianEngine's output against independently-known-correct physics
13
+ (closed-form accelerations, finite-difference Hamiltonian gradients),
14
+ which does catch that class of bug.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import numpy as np
20
+ import sympy as sp
21
+
22
+ from physicskit.classical.utils.symbolic import LagrangianEngine
23
+
24
+
25
+ def test_simple_harmonic_oscillator_acceleration_and_hamiltonian_signs():
26
+ """L = (m/2) qdot^2 - (k/2) q^2 must give qddot = -(k/m) q, p = m*qdot,
27
+ and H = p^2/(2m) + (k/2) q^2 with a POSITIVE kinetic term -- exactly
28
+ the sign the historical bug got backwards."""
29
+ m, k = 2.0, 3.0
30
+ q, qdot = sp.symbols("q qdot")
31
+ L = sp.Rational(1, 2) * m * qdot**2 - sp.Rational(1, 2) * k * q**2
32
+
33
+ engine = LagrangianEngine([q], [qdot], L)
34
+
35
+ q0, qdot0 = np.array([1.5]), np.array([0.7])
36
+ accel = engine.acceleration_njit(q0, qdot0, 0.0)
37
+ assert np.allclose(accel, [-(k / m) * q0[0]])
38
+
39
+ p0 = engine.momentum_njit(q0, qdot0)
40
+ assert np.allclose(p0, [m * qdot0[0]])
41
+
42
+ H = engine.hamiltonian_njit(q0, p0, 0.0)
43
+ expected_H = p0[0] ** 2 / (2 * m) + 0.5 * k * q0[0] ** 2
44
+ assert np.isclose(H, expected_H)
45
+
46
+ # The regression this guards against: a flipped Legendre transform
47
+ # sign gives H = -3*T + V instead of T + V, which for qdot != 0
48
+ # differs from the correct value by far more than float noise.
49
+ wrong_H = -3 * (0.5 * m * qdot0[0] ** 2) + 0.5 * k * q0[0] ** 2
50
+ assert not np.isclose(H, wrong_H)
51
+
52
+
53
+ def test_canonical_gradient_matches_finite_difference_of_hamiltonian():
54
+ """For a non-trivial single pendulum, dH/dq and dH/dp from
55
+ canonical_deriv_njit must match a central finite difference of
56
+ hamiltonian_njit itself -- a general, formula-agnostic consistency
57
+ check that would have caught the historical sign bug regardless of
58
+ which specific system triggered it."""
59
+ m, l, g = 1.3, 0.8, 9.81
60
+ q, qdot = sp.symbols("q qdot")
61
+ L = sp.Rational(1, 2) * m * l**2 * qdot**2 - m * g * l * (1 - sp.cos(q))
62
+
63
+ engine = LagrangianEngine([q], [qdot], L)
64
+
65
+ q0, p0 = np.array([0.6]), np.array([0.9])
66
+ y0 = np.concatenate([q0, p0])
67
+ dy = engine.canonical_deriv_njit(0.0, y0)
68
+ dq_analytic, dp_analytic = dy[0], dy[1]
69
+
70
+ eps = 1e-6
71
+
72
+ def H(qa, pa):
73
+ return float(engine.hamiltonian_njit(np.array([qa]), np.array([pa]), 0.0))
74
+
75
+ dHdq_fd = (H(q0[0] + eps, p0[0]) - H(q0[0] - eps, p0[0])) / (2 * eps)
76
+ dHdp_fd = (H(q0[0], p0[0] + eps) - H(q0[0], p0[0] - eps)) / (2 * eps)
77
+
78
+ assert np.isclose(dq_analytic, dHdp_fd, atol=1e-6)
79
+ assert np.isclose(dp_analytic, -dHdq_fd, atol=1e-6)
80
+
81
+
82
+ def test_two_dof_coupled_system_matches_finite_difference():
83
+ """Same finite-difference consistency check, but for a genuinely
84
+ coupled 2-DOF Lagrangian (unequal masses, linear coupling) -- the
85
+ structurally closest case to the CoupledOscillators bug."""
86
+ m1, m2, k = 1.7, 0.9, 2.5
87
+ q0s, q1s, w0s, w1s = sp.symbols("q0 q1 w0 w1")
88
+ L = sp.Rational(1, 2) * m1 * w0s**2 + sp.Rational(1, 2) * m2 * w1s**2 - sp.Rational(1, 2) * k * q0s**2 - sp.Rational(1, 2) * k * (q1s - q0s) ** 2
89
+ engine = LagrangianEngine([q0s, q1s], [w0s, w1s], L)
90
+
91
+ q0, p0 = np.array([0.3, -0.2]), np.array([0.5, 0.1])
92
+ y0 = np.concatenate([q0, p0])
93
+ dy = engine.canonical_deriv_njit(0.0, y0)
94
+ dq_analytic, dp_analytic = dy[:2], dy[2:]
95
+
96
+ eps = 1e-6
97
+
98
+ def H(qa, pa):
99
+ return float(engine.hamiltonian_njit(qa, pa, 0.0))
100
+
101
+ dHdq_fd = np.array([(H(q0 + eps * e, p0) - H(q0 - eps * e, p0)) / (2 * eps) for e in np.eye(2)])
102
+ dHdp_fd = np.array([(H(q0, p0 + eps * e) - H(q0, p0 - eps * e)) / (2 * eps) for e in np.eye(2)])
103
+
104
+ assert np.allclose(dq_analytic, dHdp_fd, atol=1e-6)
105
+ assert np.allclose(dp_analytic, -dHdq_fd, atol=1e-6)
106
+
107
+ # Momentum should be the classical p_i = m_i * qdot_i for this
108
+ # diagonal-mass-matrix system (a sign flip here would break it).
109
+ qdot0 = np.array([0.4, -0.1])
110
+ p_from_qdot = engine.momentum_njit(q0, qdot0)
111
+ assert np.allclose(p_from_qdot, [m1 * qdot0[0], m2 * qdot0[1]])
112
+
113
+
114
+ def test_explicit_time_dependence():
115
+ """A driven oscillator L = (1/2)qdot^2 - (1/2)q^2 + q*sin(t) must
116
+ give the textbook forced equation qddot = -q + sin(t)."""
117
+ t = sp.symbols("t")
118
+ q, qdot = sp.symbols("q qdot")
119
+ L = sp.Rational(1, 2) * qdot**2 - sp.Rational(1, 2) * q**2 + q * sp.sin(t)
120
+
121
+ engine = LagrangianEngine([q], [qdot], L, t=t)
122
+
123
+ q0, qdot0, t0 = np.array([0.4]), np.array([-0.2]), 1.1
124
+ accel = engine.acceleration_njit(q0, qdot0, t0)
125
+ assert np.allclose(accel, [-q0[0] + np.sin(t0)])