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,48 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.utils.spectral import autocorrelation, power_spectrum
5
+
6
+
7
+ def test_power_spectrum_of_pure_sine_peaks_at_its_frequency():
8
+ dt = 0.01
9
+ t = np.arange(0, 10, dt)
10
+ freq = 3.0
11
+ series = np.sin(2.0 * np.pi * freq * t)
12
+
13
+ frequencies, power = power_spectrum(series, dt=dt)
14
+ peak_freq = frequencies[np.argmax(power)]
15
+ assert peak_freq == pytest.approx(freq, abs=0.05)
16
+
17
+
18
+ def test_power_spectrum_shapes_and_nonnegative():
19
+ series = np.random.default_rng(0).normal(size=200)
20
+ frequencies, power = power_spectrum(series, dt=1.0)
21
+ assert frequencies.shape == power.shape == (101,)
22
+ assert np.all(power >= 0.0)
23
+
24
+
25
+ def test_autocorrelation_of_pure_sine_is_periodic():
26
+ dt = 0.01
27
+ t = np.arange(0, 20, dt)
28
+ freq = 2.0
29
+ series = np.sin(2.0 * np.pi * freq * t)
30
+
31
+ acf = autocorrelation(series, max_lag=len(series) - 1)
32
+ assert acf[0] == pytest.approx(1.0)
33
+ period_samples = round(1.0 / freq / dt)
34
+ assert acf[period_samples] == pytest.approx(1.0, abs=0.05)
35
+
36
+
37
+ def test_autocorrelation_of_white_noise_decays_immediately():
38
+ rng = np.random.default_rng(0)
39
+ series = rng.normal(size=5000)
40
+ acf = autocorrelation(series, max_lag=50)
41
+ assert acf[0] == pytest.approx(1.0)
42
+ assert abs(acf[1]) < 0.1
43
+
44
+
45
+ def test_autocorrelation_default_max_lag():
46
+ series = np.random.default_rng(0).normal(size=50)
47
+ acf = autocorrelation(series)
48
+ assert acf.shape == (50,)
@@ -0,0 +1,45 @@
1
+ """Smoke tests for the Streamlit explorer app, run headlessly via
2
+ ``streamlit.testing.v1.AppTest`` (no browser or server needed)."""
3
+
4
+ from pathlib import Path
5
+
6
+ import pytest
7
+
8
+ st_testing = pytest.importorskip("streamlit.testing.v1")
9
+
10
+ APP_PATH = str(Path(__file__).resolve().parent.parent / "app" / "streamlit_app.py")
11
+
12
+
13
+ def _run() -> "st_testing.AppTest":
14
+ at = st_testing.AppTest.from_file(APP_PATH)
15
+ at.run(timeout=120)
16
+ return at
17
+
18
+
19
+ def test_app_loads_with_no_exceptions_in_default_state():
20
+ at = _run()
21
+ assert not at.exception
22
+
23
+
24
+ @pytest.mark.parametrize("demo", ["Billiards", "Continuous systems", "Bifurcation diagrams", "Basins of attraction"])
25
+ def test_each_demo_runs_with_no_exceptions(demo):
26
+ at = _run()
27
+ at.selectbox[0].set_value(demo).run(timeout=120)
28
+ assert not at.exception
29
+
30
+
31
+ def test_every_billiard_shape_runs_with_no_exceptions():
32
+ at = _run()
33
+ for option in list(at.selectbox[1].options):
34
+ at.selectbox[1].set_value(option).run(timeout=120)
35
+ assert not at.exception, f"{option} raised: {list(at.exception)}"
36
+
37
+
38
+ def test_every_map_and_sweep_parameter_runs_with_no_exceptions():
39
+ at = _run()
40
+ at.selectbox[0].set_value("Bifurcation diagrams").run(timeout=120)
41
+ for map_name in list(at.selectbox[1].options):
42
+ at.selectbox[1].set_value(map_name).run(timeout=120)
43
+ for param_name in list(at.selectbox[2].options):
44
+ at.selectbox[2].set_value(param_name).run(timeout=120)
45
+ assert not at.exception, f"{map_name}/{param_name} raised: {list(at.exception)}"
@@ -0,0 +1,131 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.chaos.systems.continuous import Lorenz
5
+ from physicskit.chaos.utils.timeseries import (
6
+ average_log_divergence,
7
+ delay_embed,
8
+ iaaft_surrogate,
9
+ rosenstein_lyapunov,
10
+ surrogate_test,
11
+ )
12
+
13
+
14
+ def test_delay_embed_shape_and_values():
15
+ series = np.arange(10.0)
16
+ embedded = delay_embed(series, dim=3, tau=2)
17
+ assert embedded.shape == (6, 3)
18
+ np.testing.assert_allclose(embedded[0], [0.0, 2.0, 4.0])
19
+ np.testing.assert_allclose(embedded[-1], [5.0, 7.0, 9.0])
20
+
21
+
22
+ def test_delay_embed_rejects_series_too_short():
23
+ with pytest.raises(ValueError):
24
+ delay_embed(np.arange(5.0), dim=10, tau=1)
25
+
26
+
27
+ def test_average_log_divergence_returns_matching_shapes():
28
+ rng = np.random.default_rng(0)
29
+ series = rng.normal(size=500)
30
+ steps, log_div = average_log_divergence(series, dim=3, tau=1, max_iter=20)
31
+ assert steps.shape == log_div.shape
32
+ assert steps.size > 0
33
+
34
+
35
+ def test_average_log_divergence_stops_early_once_no_pairs_remain_in_range():
36
+ """With max_iter set far beyond what the series length can support, the
37
+ forward-evolution loop must break out early (rather than index out of
38
+ bounds) once every candidate pair's forward offset runs past the end of
39
+ the series."""
40
+ series = np.sin(np.linspace(0, 20, 60))
41
+ steps, log_div = average_log_divergence(series, dim=2, tau=1, max_iter=1000)
42
+ assert steps.shape == log_div.shape
43
+ assert steps.size > 0
44
+ assert steps[-1] < 1000 - 1
45
+
46
+
47
+ def test_average_log_divergence_skips_steps_with_only_exact_zero_distances():
48
+ """A constant series makes every embedded point identical, so its
49
+ nearest-neighbor distance is exactly zero at every forward step; those
50
+ steps must be skipped (not averaged as log(0) = -inf), leaving no valid
51
+ steps at all here."""
52
+ series = np.full(50, 3.0)
53
+ steps, log_div = average_log_divergence(series, dim=2, tau=1, max_iter=5)
54
+ assert steps.shape == (0,)
55
+ assert log_div.shape == (0,)
56
+
57
+
58
+ @pytest.mark.slow
59
+ def test_rosenstein_lyapunov_matches_known_lorenz_exponent():
60
+ """The Lorenz system's largest Lyapunov exponent is well known to be
61
+ approximately 0.905; estimating it from a raw x(t) time series (no
62
+ equations used) with Rosenstein's algorithm should get reasonably close."""
63
+ system = Lorenz()
64
+ dt = 0.01
65
+ _, states = system.trajectory(n_steps=12000, dt=dt)
66
+ x = states[1000:, 0]
67
+
68
+ lam = rosenstein_lyapunov(x, dim=5, tau=10, dt=dt, fit_fraction=0.3)
69
+ assert 0.5 < lam < 1.3
70
+
71
+
72
+ def test_rosenstein_lyapunov_rejects_too_short_series():
73
+ with pytest.raises(ValueError):
74
+ rosenstein_lyapunov(np.arange(5.0), dim=3, tau=1)
75
+
76
+
77
+ def test_iaaft_surrogate_preserves_mean_and_std():
78
+ rng = np.random.default_rng(0)
79
+ series = rng.normal(loc=3.0, scale=2.0, size=1000)
80
+ surrogate = iaaft_surrogate(series, n_iter=50, seed=1)
81
+ assert surrogate.mean() == pytest.approx(series.mean(), abs=1e-9)
82
+ assert surrogate.std() == pytest.approx(series.std(), abs=1e-9)
83
+
84
+
85
+ def test_iaaft_surrogate_preserves_power_spectrum_closely():
86
+ system = Lorenz()
87
+ _, states = system.trajectory(n_steps=4000, dt=0.01)
88
+ x = states[500:, 0]
89
+
90
+ surrogate = iaaft_surrogate(x, n_iter=100, seed=0)
91
+ original_spectrum = np.abs(np.fft.rfft(x))
92
+ surrogate_spectrum = np.abs(np.fft.rfft(surrogate))
93
+ relative_error = np.max(np.abs(original_spectrum - surrogate_spectrum)) / np.max(original_spectrum)
94
+ assert relative_error < 0.05
95
+
96
+
97
+ @pytest.mark.slow
98
+ def test_surrogate_test_detects_nonlinear_structure_in_lorenz():
99
+ """Lorenz x(t) has genuine deterministic/nonlinear structure that IAAFT
100
+ surrogates (linear stochastic realizations with the same spectrum) lack,
101
+ so the observed statistic should be many surrogate-std-devs away from the
102
+ surrogate distribution."""
103
+ system = Lorenz()
104
+ dt = 0.01
105
+ _, states = system.trajectory(n_steps=6000, dt=dt)
106
+ x = states[1000:, 0]
107
+
108
+ def statistic(series):
109
+ return rosenstein_lyapunov(series, dim=5, tau=10, dt=dt, fit_fraction=0.3)
110
+
111
+ _observed, surrogate_values, significance = surrogate_test(x, statistic, n_surrogates=6, n_iter=15, seed=0)
112
+ assert surrogate_values.shape == (6,)
113
+ assert significance > 2.0
114
+
115
+
116
+ def test_surrogate_test_finds_no_structure_in_pure_noise():
117
+ """White noise has no structure beyond its (flat) spectrum and
118
+ distribution, so a nonlinear statistic (here, the same Rosenstein
119
+ exponent used to detect Lorenz's structure) should come out statistically
120
+ indistinguishable between the noise and its own surrogates (low
121
+ significance) -- unlike a trivially IAAFT-invariant statistic such as the
122
+ standard deviation, which every surrogate reproduces exactly by
123
+ construction and would give a meaningless zero-variance comparison."""
124
+ rng = np.random.default_rng(0)
125
+ noise = rng.normal(size=2000)
126
+
127
+ def statistic(series):
128
+ return rosenstein_lyapunov(series, dim=5, tau=1, fit_fraction=0.3)
129
+
130
+ _observed, _surrogate_values, significance = surrogate_test(noise, statistic, n_surrogates=9, n_iter=20, seed=1)
131
+ assert significance < 2.0
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ pv_module = pytest.importorskip("pyvista")
5
+
6
+ from physicskit.chaos.systems.continuous import Lorenz
7
+ from physicskit.chaos.visualizers.viewer3d import pyvista_trajectory
8
+
9
+
10
+ def test_pyvista_trajectory_returns_plotter_with_expected_point_count():
11
+ system = Lorenz()
12
+ _, states = system.trajectory(n_steps=200, dt=0.01)
13
+ plotter = pyvista_trajectory(states, off_screen=True)
14
+ try:
15
+ assert isinstance(plotter, pv_module.Plotter)
16
+ image = plotter.screenshot()
17
+ assert image.ndim == 3 and image.shape[2] == 3
18
+ finally:
19
+ plotter.close()
20
+
21
+
22
+ def test_pyvista_trajectory_color_by_speed_runs_without_error():
23
+ system = Lorenz()
24
+ _, states = system.trajectory(n_steps=200, dt=0.01)
25
+ plotter = pyvista_trajectory(states, color_by="speed", off_screen=True)
26
+ try:
27
+ plotter.screenshot()
28
+ finally:
29
+ plotter.close()
30
+
31
+
32
+ def test_pyvista_trajectory_uses_selected_components():
33
+ states = np.column_stack([np.linspace(0, 1, 20), np.linspace(0, 2, 20), np.linspace(0, 3, 20), np.linspace(0, 4, 20)])
34
+ plotter = pyvista_trajectory(states, components=(1, 2, 3), off_screen=True)
35
+ try:
36
+ mesh = plotter.meshes[0]
37
+ np.testing.assert_allclose(mesh.points[:, 0], states[:, 1])
38
+ np.testing.assert_allclose(mesh.points[:, 1], states[:, 2])
39
+ np.testing.assert_allclose(mesh.points[:, 2], states[:, 3])
40
+ finally:
41
+ plotter.close()
@@ -0,0 +1,46 @@
1
+ from physicskit.chaos.utils.dimension import box_counting_dimension, correlation_dimension
2
+ from physicskit.chaos.utils.io import load_arrays, load_system_config, save_arrays, save_system_config
3
+ from physicskit.chaos.utils.metrics import (
4
+ benettin_lyapunov_spectrum,
5
+ energy_drift,
6
+ lyapunov_exponent_from_divergence,
7
+ map_lyapunov_spectrum,
8
+ numerical_jacobian,
9
+ numerical_jacobian_map,
10
+ phase_volume_expansion,
11
+ )
12
+ from physicskit.chaos.utils.recurrence import kac_lemma_estimate, recurrence_matrix, recurrence_times
13
+ from physicskit.chaos.utils.spectral import autocorrelation, power_spectrum
14
+ from physicskit.chaos.utils.timeseries import (
15
+ average_log_divergence,
16
+ delay_embed,
17
+ iaaft_surrogate,
18
+ rosenstein_lyapunov,
19
+ surrogate_test,
20
+ )
21
+
22
+ __all__ = [
23
+ "autocorrelation",
24
+ "average_log_divergence",
25
+ "benettin_lyapunov_spectrum",
26
+ "box_counting_dimension",
27
+ "correlation_dimension",
28
+ "delay_embed",
29
+ "energy_drift",
30
+ "iaaft_surrogate",
31
+ "kac_lemma_estimate",
32
+ "load_arrays",
33
+ "load_system_config",
34
+ "lyapunov_exponent_from_divergence",
35
+ "map_lyapunov_spectrum",
36
+ "numerical_jacobian",
37
+ "numerical_jacobian_map",
38
+ "phase_volume_expansion",
39
+ "power_spectrum",
40
+ "recurrence_matrix",
41
+ "recurrence_times",
42
+ "rosenstein_lyapunov",
43
+ "save_arrays",
44
+ "save_system_config",
45
+ "surrogate_test",
46
+ ]
@@ -0,0 +1,139 @@
1
+ """Fractal dimension estimators: box-counting (capacity) and correlation dimension.
2
+
3
+ Both estimators quantify how the "size" of a point cloud (typically a sampled
4
+ strange attractor) scales as the measuring resolution ``epsilon`` shrinks,
5
+ and both reduce to the ordinary embedding dimension for a non-fractal set
6
+ (e.g. a filled disk in 2D gives dimension ~2, a line segment gives dimension
7
+ ~1) but return a *non-integer* value for a genuine fractal attractor.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import numpy as np
13
+ from numpy.typing import ArrayLike, NDArray
14
+ from scipy.spatial.distance import pdist
15
+
16
+
17
+ def box_counting_dimension(
18
+ points: ArrayLike,
19
+ n_scales: int = 20,
20
+ eps_min: float | None = None,
21
+ eps_max: float | None = None,
22
+ ) -> tuple[float, NDArray[np.float64], NDArray[np.float64]]:
23
+ """Estimate the box-counting (capacity) dimension ``D0`` of a point cloud.
24
+
25
+ Covers the bounding box of `points` with a grid of boxes of side
26
+ `epsilon`, counts the number of occupied boxes ``N(epsilon)`` over a
27
+ range of `epsilon`, and estimates ``D0`` as the slope of
28
+ ``log(N(epsilon))`` vs. ``log(1/epsilon)`` (via ordinary least squares).
29
+
30
+ Parameters
31
+ ----------
32
+ points : array_like of float, shape (n, dim)
33
+ Point cloud to measure (e.g. a sampled attractor trajectory).
34
+ n_scales : int, default 20
35
+ Number of `epsilon` values to evaluate, log-spaced between `eps_min`
36
+ and `eps_max`.
37
+ eps_min : float, optional
38
+ Smallest box size; defaults to ``1/200`` of the point cloud's extent.
39
+ eps_max : float, optional
40
+ Largest box size; defaults to ``1/2`` of the point cloud's extent.
41
+
42
+ Returns
43
+ -------
44
+ D0 : float
45
+ Estimated box-counting dimension.
46
+ epsilons : ndarray of float, shape (n_scales,)
47
+ The box sizes evaluated.
48
+ counts : ndarray of float, shape (n_scales,)
49
+ The number of occupied boxes ``N(epsilon)`` at each box size.
50
+
51
+ Notes
52
+ -----
53
+ Once `epsilon` is small enough that most occupied boxes contain only a
54
+ single point, ``N(epsilon)`` stops scaling like the true fractal measure
55
+ and instead approaches the point count itself, biasing ``D0`` low. If the
56
+ estimate looks suspiciously low (e.g. well under 2 for a point cloud that
57
+ should densely fill a 2D region), either supply a larger `eps_min` or use
58
+ more sample points so the default (finer) `eps_min` stays in the regime
59
+ where boxes still contain multiple points on average.
60
+ """
61
+ points = np.atleast_2d(np.asarray(points, dtype=np.float64))
62
+ mins = points.min(axis=0)
63
+ extent = float(np.max(points.max(axis=0) - mins))
64
+ if eps_max is None:
65
+ eps_max = extent / 2.0
66
+ if eps_min is None:
67
+ eps_min = extent / 200.0
68
+
69
+ epsilons = np.geomspace(eps_max, eps_min, n_scales)
70
+ counts = np.empty(n_scales)
71
+ for i, eps in enumerate(epsilons):
72
+ box_idx = np.floor((points - mins) / eps).astype(np.int64)
73
+ counts[i] = np.unique(box_idx, axis=0).shape[0]
74
+
75
+ slope, _ = np.polyfit(np.log(1.0 / epsilons), np.log(counts), 1)
76
+ return float(slope), epsilons, counts
77
+
78
+
79
+ def correlation_dimension(
80
+ points: ArrayLike,
81
+ n_scales: int = 20,
82
+ eps_min: float | None = None,
83
+ eps_max: float | None = None,
84
+ ) -> tuple[float, NDArray[np.float64], NDArray[np.float64]]:
85
+ """Estimate the correlation dimension ``D2`` via the Grassberger-Procaccia algorithm.
86
+
87
+ Computes the correlation sum ``C(epsilon)`` -- the fraction of all point
88
+ pairs closer together than `epsilon` -- over a range of `epsilon`, and
89
+ estimates ``D2`` as the slope of ``log(C(epsilon))`` vs. ``log(epsilon)``
90
+ (via ordinary least squares). ``D2`` is generally a slightly tighter
91
+ (never larger) estimate of an attractor's fractal dimension than the
92
+ box-counting ``D0``, and converges faster with the number of sample
93
+ points since it uses pairwise distances directly rather than a fixed grid.
94
+
95
+ Parameters
96
+ ----------
97
+ points : array_like of float, shape (n, dim)
98
+ Point cloud to measure (e.g. a sampled attractor trajectory). Keep
99
+ `points` to at most a few thousand rows: this computes all
100
+ :math:`O(n^2)` pairwise distances.
101
+ n_scales : int, default 20
102
+ Number of `epsilon` values to evaluate, log-spaced between `eps_min`
103
+ and `eps_max`.
104
+ eps_min : float, optional
105
+ Smallest neighborhood radius; defaults to the 5th percentile of the
106
+ pairwise distances. The very smallest pairwise distances are
107
+ dominated by finite-sample discreteness rather than the attractor's
108
+ true fractal scaling, so a percentile well above the minimum gives a
109
+ more reliable default than the minimum itself.
110
+ eps_max : float, optional
111
+ Largest neighborhood radius; defaults to the 50th percentile (median)
112
+ of the pairwise distances. The largest distances are dominated by
113
+ ``C(epsilon)`` saturating toward 1 (once `epsilon` exceeds the
114
+ attractor's overall diameter), which likewise biases a naive default.
115
+
116
+ Returns
117
+ -------
118
+ D2 : float
119
+ Estimated correlation dimension.
120
+ epsilons : ndarray of float, shape (n_scales,)
121
+ The neighborhood radii evaluated.
122
+ correlation_sums : ndarray of float, shape (n_scales,)
123
+ The correlation sum ``C(epsilon)`` at each radius.
124
+ """
125
+ points = np.atleast_2d(np.asarray(points, dtype=np.float64))
126
+ pair_dists = pdist(points)
127
+
128
+ if eps_max is None:
129
+ eps_max = float(np.percentile(pair_dists, 50))
130
+ if eps_min is None:
131
+ eps_min = float(np.percentile(pair_dists[pair_dists > 0], 5))
132
+
133
+ epsilons = np.geomspace(eps_max, eps_min, n_scales)
134
+ n_pairs = pair_dists.shape[0]
135
+ correlation_sums = np.array([np.count_nonzero(pair_dists < eps) / n_pairs for eps in epsilons])
136
+
137
+ mask = correlation_sums > 0
138
+ slope, _ = np.polyfit(np.log(epsilons[mask]), np.log(correlation_sums[mask]), 1)
139
+ return float(slope), epsilons, correlation_sums
@@ -0,0 +1,95 @@
1
+ """Save and load system configurations and array results (trajectories,
2
+ basin grids, Poincare sections, ...) to disk.
3
+
4
+ A system's configuration is recovered generically from its ``__init__``
5
+ signature (see :func:`physicskit.chaos.core.base_system.get_init_params`), the same
6
+ introspection that already powers every system's ``__repr__`` -- so
7
+ :func:`save_system_config` / :func:`load_system_config` work for any
8
+ concrete system in physicskit.chaos without a per-class serializer. Array results
9
+ are saved as plain ``.npz`` archives via :func:`save_arrays` /
10
+ :func:`load_arrays`.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import importlib
16
+ import json
17
+ from pathlib import Path
18
+ from typing import Any
19
+
20
+ import numpy as np
21
+ from numpy.typing import NDArray
22
+
23
+ from physicskit.chaos.core.base_system import get_init_params
24
+
25
+
26
+ def save_system_config(obj: object, path: str | Path) -> None:
27
+ """Save a system's class and constructor parameters as JSON.
28
+
29
+ Parameters
30
+ ----------
31
+ obj : object
32
+ A physicskit.chaos system instance (or any object whose ``__init__``
33
+ parameters are stored under same-named attributes).
34
+ path : str or pathlib.Path
35
+ Destination ``.json`` file.
36
+ """
37
+ params = {name: value.tolist() if isinstance(value, np.ndarray) else value for name, value in get_init_params(obj).items()}
38
+ config = {
39
+ "module": type(obj).__module__,
40
+ "class": type(obj).__qualname__,
41
+ "params": params,
42
+ }
43
+ Path(path).write_text(json.dumps(config, indent=2))
44
+
45
+
46
+ def load_system_config(path: str | Path) -> Any:
47
+ """Reconstruct a system instance saved by :func:`save_system_config`.
48
+
49
+ Parameters
50
+ ----------
51
+ path : str or pathlib.Path
52
+ Source ``.json`` file.
53
+
54
+ Returns
55
+ -------
56
+ object
57
+ A new instance of the saved class, constructed from its saved
58
+ parameters.
59
+ """
60
+ config = json.loads(Path(path).read_text())
61
+ module = importlib.import_module(config["module"])
62
+ cls = getattr(module, config["class"])
63
+ return cls(**config["params"])
64
+
65
+
66
+ def save_arrays(path: str | Path, **arrays: NDArray[Any]) -> None:
67
+ """Save named arrays (trajectories, grids, Poincare sections, ...) to a ``.npz`` archive.
68
+
69
+ Parameters
70
+ ----------
71
+ path : str or pathlib.Path
72
+ Destination ``.npz`` file.
73
+ **arrays
74
+ Arrays to save, keyed by name (e.g. ``save_arrays("run.npz", t=t,
75
+ states=states)``).
76
+ """
77
+ np.savez(path, **arrays) # type: ignore[arg-type] # numpy stubs mistype **kwds here
78
+
79
+
80
+ def load_arrays(path: str | Path) -> dict[str, NDArray[Any]]:
81
+ """Load a ``.npz`` archive saved by :func:`save_arrays`.
82
+
83
+ Parameters
84
+ ----------
85
+ path : str or pathlib.Path
86
+ Source ``.npz`` file.
87
+
88
+ Returns
89
+ -------
90
+ dict
91
+ Mapping of array name to array, matching the keyword arguments
92
+ `save_arrays` was called with.
93
+ """
94
+ with np.load(path) as data:
95
+ return {key: data[key] for key in data.files}