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,628 @@
1
+ r"""Stellar convection and the alpha-omega mean-field magnetic dynamo.
2
+
3
+ The Sun's large-scale magnetic field -- the one that reverses polarity
4
+ every ~11 years and paints the "butterfly diagram" of sunspots migrating
5
+ from mid-latitudes toward the equator -- is not a fossil field left over
6
+ from formation. It is continuously regenerated by the interplay of two
7
+ processes going on simultaneously in the convection zone: small-scale,
8
+ helical turbulent convection (the "alpha effect", parametrizing the net
9
+ effect of many individually chaotic convective eddies twisted by the
10
+ Sun's rotation and stratification) and the large-scale rotational shear
11
+ between the Sun's differentially rotating layers (the "Omega effect",
12
+ which stretches a poloidal field into a much stronger toroidal one).
13
+ Together these sustain the field against Ohmic decay -- this is the
14
+ alpha-omega dynamo mechanism first proposed by Parker (1955), and it is
15
+ the standard textbook explanation for the solar cycle.
16
+
17
+ A crucial and easy-to-miss subtlety (Zeldovich's antidynamo theorem) is
18
+ that a strictly two-dimensional, fully resolved magnetic field confined
19
+ to a plane *cannot* be sustained by 2D fluid motion alone -- genuine
20
+ dynamo action is an intrinsically three-dimensional effect, arising from
21
+ the helical (non-mirror-symmetric) twisting that only a 3D, rotating,
22
+ stratified flow can produce. This module therefore does not attempt to
23
+ build one dynamically self-consistent 2D "dynamo simulation" (which
24
+ would either not be a real dynamo, or would silently smuggle in 3D
25
+ physics through the back door). Instead, exactly as the textbook
26
+ treatment splits the problem, it implements two separate,
27
+ self-contained pieces:
28
+
29
+ - A resolved 2D Boussinesq convection simulation
30
+ (:func:`simulate_stellar_convection`) showing the turbulent
31
+ convective rolls and plumes whose unresolved, helical, three-dimensional
32
+ fine structure is -- in the real Sun -- the physical origin of the
33
+ alpha effect. This piece is a qualitative picture of the turbulence,
34
+ not a dynamo; by Zeldovich's theorem it could never sustain a resolved
35
+ 2D field, and it is not asked to.
36
+ - The linearized 1D alpha-omega **mean-field** dynamo equations
37
+ (:func:`simulate_alpha_omega_dynamo`), in which the alpha effect and
38
+ the rotational shear enter as prescribed coefficients rather than
39
+ being derived from the convection simulation above. This piece *does*
40
+ robustly sustain and grow a large-scale field, and reproduces the
41
+ real solar dynamo's most famous piece of phenomenology: a migrating
42
+ dynamo wave, whose space-time diagram is the butterfly diagram.
43
+
44
+ The two pieces are deliberately not coupled to each other; each is
45
+ correct and complete on its own terms.
46
+
47
+ Part 1: 2D convective rolls
48
+ ----------------------------
49
+
50
+ A standard doubly periodic, pseudo-spectral Boussinesq system for
51
+ vorticity :math:`\omega`, streamfunction :math:`\psi`, and temperature
52
+ perturbation :math:`T'` about an unstably stratified periodic background
53
+ :math:`T_{eq}(y) = -\beta\cos(2\pi y/L_y)`:
54
+
55
+ .. math::
56
+
57
+ \partial_t \omega + \mathbf{u}\cdot\nabla\omega
58
+ = \nu\nabla^2\omega + g\alpha_{th}\,\partial_x T' \\
59
+ \partial_t T' + \mathbf{u}\cdot\nabla T'
60
+ = \kappa\nabla^2 T' - u_y\,\frac{dT_{eq}}{dy}
61
+ + \frac{T_{eq}(y) - T'}{\tau_{relax}} \\
62
+ \nabla^2\psi = -\omega, \qquad \mathbf{u} = (\partial_y\psi, -\partial_x\psi)
63
+
64
+ The Newtonian-cooling term :math:`(T_{eq}-T')/\tau_{relax}` (together
65
+ with the advection of the background gradient, :math:`-u_y\,dT_{eq}/dy`)
66
+ is the standard trick for sustaining an unstable stratification in a
67
+ periodic box with no physical walls -- without it, convection would mix
68
+ the background profile flat and decay back to a quiescent state.
69
+
70
+ Part 2: The alpha-omega mean-field dynamo
71
+ -------------------------------------------
72
+
73
+ A linearized 1D reduction (Parker 1955; standard in mean-field dynamo
74
+ theory) for the poloidal-flux proxy :math:`A(x,t)` and toroidal field
75
+ :math:`B(x,t)`, periodic in :math:`x` (a local-Cartesian stand-in for
76
+ stellar latitude or radius):
77
+
78
+ .. math::
79
+
80
+ \partial_t A = \alpha B + \eta\,\partial_x^2 A \\
81
+ \partial_t B = G\,\partial_x A + \eta\,\partial_x^2 B
82
+
83
+ with constant alpha-effect coefficient :math:`\alpha`, shear rate
84
+ (Omega effect) :math:`G`, and magnetic diffusivity :math:`\eta`.
85
+ Substituting a plane-wave mode :math:`(A,B)\propto e^{ikx-i\omega_f t+\sigma t}`
86
+ turns this linear system into the 2x2 eigenvalue problem
87
+
88
+ .. math::
89
+
90
+ \begin{pmatrix}\sigma-i\omega_f+\eta k^2 & -\alpha \\ -iGk & \sigma-i\omega_f+\eta k^2\end{pmatrix}
91
+ \begin{pmatrix}A\\B\end{pmatrix} = 0,
92
+
93
+ whose characteristic equation, with :math:`s=\sigma-i\omega_f+\eta k^2`,
94
+ is :math:`s^2 = i\alpha G k`. Since :math:`i\alpha Gk` is purely
95
+ imaginary, its principal square root always has real and imaginary parts
96
+ of equal magnitude, :math:`\sqrt{|\alpha Gk|/2}` -- giving the closed-form
97
+ growth rate and wave frequency implemented in
98
+ :func:`alpha_omega_growth_rate` and :func:`alpha_omega_wave_frequency`,
99
+ and checked against the full simulation in this module's test suite.
100
+ Because the system is linear, each Fourier mode's amplitude can be
101
+ advanced *exactly* (no time-discretization error at all) via the 2x2
102
+ matrix exponential of its own coefficient matrix -- see
103
+ :func:`simulate_alpha_omega_dynamo`.
104
+ """
105
+
106
+ from __future__ import annotations
107
+
108
+ import numpy as np
109
+ from numpy.typing import NDArray
110
+
111
+ __all__ = [
112
+ "convective_roll_rhs",
113
+ "simulate_stellar_convection",
114
+ "kinetic_energy",
115
+ "alpha_omega_growth_rate",
116
+ "alpha_omega_wave_frequency",
117
+ "simulate_alpha_omega_dynamo",
118
+ "dominant_mode_growth_rate",
119
+ ]
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Part 1: 2D convective rolls (Boussinesq vorticity-streamfunction-temperature)
124
+ # ---------------------------------------------------------------------------
125
+
126
+
127
+ def _spectral_grid_2d(nx: int, ny: int, Lx: float, Ly: float):
128
+ """Build the doubly periodic real-space and wavenumber grids for Part 1.
129
+
130
+ Fields are laid out with shape ``(ny, nx)`` -- rows indexed by ``y``,
131
+ columns by ``x`` -- the usual image-like convention (matches
132
+ ``imshow``'s default axes and this module's documented snapshot
133
+ shape). Self-contained (not imported from :mod:`physicskit.fluids`),
134
+ per this package's no-cross-subpackage-imports convention, but built
135
+ the same way as :func:`physicskit.fluids.core.grid.spectral_grid`:
136
+ ``np.fft.fftfreq``-based wavenumbers, and the ``k=0`` mode of ``K2``
137
+ set to 1 so the Poisson solve is safe to divide by.
138
+ """
139
+ x = np.linspace(0.0, Lx, nx, endpoint=False)
140
+ y = np.linspace(0.0, Ly, ny, endpoint=False)
141
+ X, Y = np.meshgrid(x, y, indexing="xy")
142
+ kx = 2.0 * np.pi * np.fft.fftfreq(nx, d=Lx / nx)
143
+ ky = 2.0 * np.pi * np.fft.fftfreq(ny, d=Ly / ny)
144
+ KX, KY = np.meshgrid(kx, ky, indexing="xy")
145
+ K2 = KX**2 + KY**2
146
+ K2 = K2.copy()
147
+ K2[0, 0] = 1.0
148
+ return X, Y, KX, KY, K2
149
+
150
+
151
+ def _poisson_solve_streamfunction(omega: NDArray[np.float64], K2: NDArray[np.float64]) -> NDArray[np.float64]:
152
+ """Solve :math:`\\nabla^2\\psi=-\\omega` for the streamfunction via FFT (see :func:`_spectral_grid_2d`)."""
153
+ omega_hat = np.fft.fft2(omega)
154
+ psi_hat = omega_hat / K2
155
+ psi_hat[0, 0] = 0.0
156
+ return np.real(np.fft.ifft2(psi_hat))
157
+
158
+
159
+ def _velocity_from_streamfunction(psi: NDArray[np.float64], KX: NDArray[np.float64], KY: NDArray[np.float64]):
160
+ """Recover :math:`\\mathbf{u}=(\\partial_y\\psi,-\\partial_x\\psi)` from the streamfunction spectrally."""
161
+ psi_hat = np.fft.fft2(psi)
162
+ u = np.real(np.fft.ifft2(1j * KY * psi_hat))
163
+ v = np.real(np.fft.ifft2(-1j * KX * psi_hat))
164
+ return u, v
165
+
166
+
167
+ def convective_roll_rhs(
168
+ omega: NDArray[np.float64],
169
+ T: NDArray[np.float64],
170
+ Y: NDArray[np.float64],
171
+ KX: NDArray[np.float64],
172
+ KY: NDArray[np.float64],
173
+ K2: NDArray[np.float64],
174
+ nu: float,
175
+ kappa: float,
176
+ g_alpha: float,
177
+ beta: float,
178
+ Ly: float,
179
+ tau_relax: float,
180
+ ):
181
+ """Right-hand side of the 2D Boussinesq convective-roll system, evaluated pseudo-spectrally.
182
+
183
+ Mirrors the structure of the Rayleigh-Taylor Boussinesq right-hand
184
+ side used elsewhere in this codebase for a buoyancy-vorticity system
185
+ (vorticity forced by the buoyancy field's :math:`x`-derivative,
186
+ :math:`g\\,\\partial_x b`, since gravity and the background
187
+ stratification both point along :math:`y`), plus the Newtonian-cooling
188
+ forcing that sustains the unstable background profile
189
+ :math:`T_{eq}(y)=-\\beta\\cos(2\\pi y/L_y)` against being mixed away by
190
+ the convection it drives (see the module docstring).
191
+
192
+ Parameters
193
+ ----------
194
+ omega, T : ndarray of float, shape (ny, nx)
195
+ Vorticity and temperature-perturbation fields.
196
+ Y : ndarray of float, shape (ny, nx)
197
+ The ``y`` coordinate grid, from :func:`_spectral_grid_2d`, used to
198
+ evaluate the background profile :math:`T_{eq}(y)` and its
199
+ gradient pointwise.
200
+ KX, KY, K2 : ndarray of float, shape (ny, nx)
201
+ Wavenumber grids from :func:`_spectral_grid_2d`.
202
+ nu : float
203
+ Kinematic viscosity (vorticity diffusivity).
204
+ kappa : float
205
+ Thermal diffusivity.
206
+ g_alpha : float
207
+ Combined buoyancy coefficient :math:`g\\,\\alpha_{th}`.
208
+ beta : float
209
+ Amplitude of the background stratification :math:`T_{eq}(y)`.
210
+ Ly : float
211
+ Domain size along :math:`y` (the period of :math:`T_{eq}`).
212
+ tau_relax : float
213
+ Newtonian-cooling relaxation time back to :math:`T_{eq}(y)`.
214
+
215
+ Returns
216
+ -------
217
+ domega_dt, dT_dt : ndarray of float, shape (ny, nx)
218
+ Time derivatives of vorticity and temperature perturbation.
219
+ """
220
+ psi = _poisson_solve_streamfunction(omega, K2)
221
+ u, v = _velocity_from_streamfunction(psi, KX, KY)
222
+
223
+ omega_hat = np.fft.fft2(omega)
224
+ domega_dx = np.real(np.fft.ifft2(1j * KX * omega_hat))
225
+ domega_dy = np.real(np.fft.ifft2(1j * KY * omega_hat))
226
+ diffusion_omega = np.real(np.fft.ifft2(-nu * K2 * omega_hat))
227
+
228
+ T_hat = np.fft.fft2(T)
229
+ dT_dx = np.real(np.fft.ifft2(1j * KX * T_hat))
230
+ dT_dy = np.real(np.fft.ifft2(1j * KY * T_hat))
231
+ diffusion_T = np.real(np.fft.ifft2(-kappa * K2 * T_hat))
232
+
233
+ Teq = -beta * np.cos(2.0 * np.pi * Y / Ly)
234
+ dTeq_dy = beta * (2.0 * np.pi / Ly) * np.sin(2.0 * np.pi * Y / Ly)
235
+
236
+ domega_dt = -(u * domega_dx + v * domega_dy) + diffusion_omega + g_alpha * dT_dx
237
+ dT_dt = -(u * dT_dx + v * dT_dy) + diffusion_T - v * dTeq_dy + (Teq - T) / tau_relax
238
+ return domega_dt, dT_dt
239
+
240
+
241
+ def simulate_stellar_convection(
242
+ nx: int,
243
+ ny: int,
244
+ Lx: float,
245
+ Ly: float,
246
+ nu: float = 0.08,
247
+ kappa: float = 0.08,
248
+ g_alpha: float = 2.0,
249
+ beta: float = 1.0,
250
+ tau_relax: float = 0.5,
251
+ dt: float = 0.005,
252
+ n_steps: int = 5000,
253
+ save_every: int = 50,
254
+ seed: int | None = None,
255
+ ):
256
+ """Time-step 2D Boussinesq convective rolls with pseudo-spectral RK4.
257
+
258
+ Starts from a small random vorticity and temperature perturbation
259
+ (root-mean-square amplitude ``1e-3``) and integrates
260
+ :func:`convective_roll_rhs` with classical 4th-order Runge-Kutta,
261
+ exactly the scheme used for the Boussinesq Rayleigh-Taylor system
262
+ elsewhere in this codebase. The default parameters were tuned (see
263
+ this module's test suite) so that the periodic background
264
+ stratification :math:`T_{eq}(y)=-\\beta\\cos(2\\pi y/L_y)`, sustained
265
+ against mixing by the ``tau_relax`` Newtonian-cooling forcing, robustly
266
+ grows the seed noise into sustained convective rolls -- kinetic energy
267
+ measurably larger at the end of the run than at the start -- without
268
+ blowing up.
269
+
270
+ Parameters
271
+ ----------
272
+ nx, ny : int
273
+ Number of grid points along ``x`` and ``y``.
274
+ Lx, Ly : float
275
+ Physical domain size along ``x`` and ``y``.
276
+ nu : float, default 0.08
277
+ Kinematic viscosity.
278
+ kappa : float, default 0.08
279
+ Thermal diffusivity.
280
+ g_alpha : float, default 2.0
281
+ Combined buoyancy coefficient :math:`g\\,\\alpha_{th}`.
282
+ beta : float, default 1.0
283
+ Amplitude of the background stratification.
284
+ tau_relax : float, default 0.5
285
+ Newtonian-cooling relaxation time.
286
+ dt : float, default 0.005
287
+ Time step.
288
+ n_steps : int, default 5000
289
+ Total number of RK4 steps.
290
+ save_every : int, default 50
291
+ Save a snapshot every this many steps.
292
+ seed : int, optional
293
+ Random seed for the initial perturbation.
294
+
295
+ Returns
296
+ -------
297
+ times : ndarray of float, shape (n_saved,)
298
+ omega_snapshots : ndarray of float, shape (n_saved, ny, nx)
299
+ T_snapshots : ndarray of float, shape (n_saved, ny, nx)
300
+
301
+ See Also
302
+ --------
303
+ convective_roll_rhs : The right-hand side stepped here.
304
+ kinetic_energy : Diagnostic used to confirm convection is sustained.
305
+
306
+ Examples
307
+ --------
308
+ >>> times, omega_snaps, T_snaps = simulate_stellar_convection(
309
+ ... 32, 32, 2 * np.pi, 2 * np.pi, n_steps=20, save_every=10, seed=0)
310
+ >>> omega_snaps.shape[1:]
311
+ (32, 32)
312
+ >>> bool(np.all(np.isfinite(omega_snaps)))
313
+ True
314
+ """
315
+ X, Y, KX, KY, K2 = _spectral_grid_2d(nx, ny, Lx, Ly)
316
+
317
+ rng = np.random.default_rng(seed)
318
+ omega = 1.0e-3 * rng.standard_normal((ny, nx))
319
+ T = 1.0e-3 * rng.standard_normal((ny, nx))
320
+
321
+ times = [0.0]
322
+ omega_snapshots = [omega.copy()]
323
+ T_snapshots = [T.copy()]
324
+
325
+ t = 0.0
326
+ for step in range(1, n_steps + 1):
327
+ k1o, k1T = convective_roll_rhs(omega, T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
328
+ k2o, k2T = convective_roll_rhs(omega + dt / 2 * k1o, T + dt / 2 * k1T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
329
+ k3o, k3T = convective_roll_rhs(omega + dt / 2 * k2o, T + dt / 2 * k2T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
330
+ k4o, k4T = convective_roll_rhs(omega + dt * k3o, T + dt * k3T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
331
+ omega = omega + dt / 6 * (k1o + 2 * k2o + 2 * k3o + k4o)
332
+ T = T + dt / 6 * (k1T + 2 * k2T + 2 * k3T + k4T)
333
+ t += dt
334
+ if step % save_every == 0:
335
+ times.append(t)
336
+ omega_snapshots.append(omega.copy())
337
+ T_snapshots.append(T.copy())
338
+
339
+ return np.array(times), np.array(omega_snapshots), np.array(T_snapshots)
340
+
341
+
342
+ def kinetic_energy(omega_snapshot: NDArray[np.float64], KX: NDArray[np.float64], KY: NDArray[np.float64], K2: NDArray[np.float64]) -> float:
343
+ """Total kinetic energy of the flow implied by a vorticity snapshot.
344
+
345
+ Recovers the streamfunction and velocity field from `omega_snapshot`
346
+ via the same spectral Poisson solve used during time-stepping, then
347
+ sums :math:`\\frac{1}{2}(u^2+v^2)` over the grid. Used as the
348
+ diagnostic that convection in :func:`simulate_stellar_convection`
349
+ actually spins up (grows) rather than decaying back to quiescence.
350
+
351
+ Parameters
352
+ ----------
353
+ omega_snapshot : ndarray of float, shape (ny, nx)
354
+ A single vorticity snapshot, e.g. one entry of the
355
+ `omega_snapshots` array returned by :func:`simulate_stellar_convection`.
356
+ KX, KY, K2 : ndarray of float, shape (ny, nx)
357
+ Wavenumber grids from :func:`_spectral_grid_2d`.
358
+
359
+ Returns
360
+ -------
361
+ float
362
+ Total kinetic energy :math:`\\sum \\frac{1}{2}(u^2+v^2)`.
363
+
364
+ Examples
365
+ --------
366
+ >>> import numpy as np
367
+ >>> _, _, KX, KY, K2 = _spectral_grid_2d(32, 32, 2 * np.pi, 2 * np.pi)
368
+ >>> KE = kinetic_energy(np.zeros((32, 32)), KX, KY, K2)
369
+ >>> KE == 0.0
370
+ True
371
+ """
372
+ psi = _poisson_solve_streamfunction(omega_snapshot, K2)
373
+ u, v = _velocity_from_streamfunction(psi, KX, KY)
374
+ return float(0.5 * np.sum(u**2 + v**2))
375
+
376
+
377
+ # ---------------------------------------------------------------------------
378
+ # Part 2: alpha-omega mean-field dynamo
379
+ # ---------------------------------------------------------------------------
380
+
381
+
382
+ def alpha_omega_growth_rate(alpha: float, shear: float, k, eta: float):
383
+ """Closed-form linear growth rate of the alpha-omega dynamo wave.
384
+
385
+ From the dispersion relation :math:`s^2=i\\alpha G k` (see the module
386
+ docstring), the growing branch's real part is
387
+ :math:`\\sigma=\\sqrt{|\\alpha G k|/2}-\\eta k^2` -- positive
388
+ (dynamo action beats Ohmic decay) once :math:`|\\alpha Gk|` is large
389
+ enough relative to :math:`\\eta^2k^4`, i.e. at long enough wavelength
390
+ for fixed diffusivity.
391
+
392
+ Parameters
393
+ ----------
394
+ alpha : float
395
+ Alpha-effect coefficient.
396
+ shear : float
397
+ Rotational shear rate :math:`G` (the Omega effect).
398
+ k : float or ndarray
399
+ Wavenumber(s).
400
+ eta : float
401
+ Magnetic diffusivity.
402
+
403
+ Returns
404
+ -------
405
+ float or ndarray
406
+ Growth rate :math:`\\sigma`, same shape as `k`.
407
+
408
+ See Also
409
+ --------
410
+ alpha_omega_wave_frequency : The companion imaginary part (wave frequency magnitude).
411
+ simulate_alpha_omega_dynamo : Full simulation this closed form is checked against.
412
+
413
+ Examples
414
+ --------
415
+ >>> round(alpha_omega_growth_rate(alpha=1.0, shear=5.0, k=1.0, eta=0.05), 4)
416
+ 1.5311
417
+ """
418
+ k = np.asarray(k, dtype=float)
419
+ sigma = np.sqrt(np.abs(alpha * shear * k) / 2.0) - eta * k**2
420
+ return sigma if sigma.ndim else float(sigma)
421
+
422
+
423
+ def alpha_omega_wave_frequency(alpha: float, shear: float, k):
424
+ """Closed-form magnitude of the alpha-omega dynamo wave's angular frequency.
425
+
426
+ :math:`|\\omega_f| = \\sqrt{|\\alpha Gk|/2}` (see the module docstring
427
+ for the derivation). Only the magnitude is returned; the propagation
428
+ *direction* depends on the sign of :math:`\\alpha Gk` and this
429
+ module's specific stepping convention -- empirically, with
430
+ :func:`simulate_alpha_omega_dynamo`'s time-stepping and the
431
+ :math:`e^{ikx}` Fourier convention used throughout, the wave
432
+ (for ``alpha * shear > 0``, i.e. a positive-``k`` mode with
433
+ :math:`\\alpha Gk>0``) migrates toward **decreasing** :math:`x`, i.e.
434
+ it travels in the ``-x`` direction (verified against the simulation
435
+ in this module's test suite and visible in
436
+ :func:`physicskit.astro.visualizers.plot_dynamo_butterfly_diagram`'s
437
+ diagonal stripes).
438
+
439
+ Parameters
440
+ ----------
441
+ alpha : float
442
+ Alpha-effect coefficient.
443
+ shear : float
444
+ Rotational shear rate :math:`G`.
445
+ k : float or ndarray
446
+ Wavenumber(s).
447
+
448
+ Returns
449
+ -------
450
+ float or ndarray
451
+ :math:`|\\omega_f|`, same shape as `k`.
452
+
453
+ Examples
454
+ --------
455
+ >>> round(alpha_omega_wave_frequency(alpha=1.0, shear=5.0, k=1.0), 4)
456
+ 1.5811
457
+ """
458
+ k = np.asarray(k, dtype=float)
459
+ freq = np.sqrt(np.abs(alpha * shear * k) / 2.0)
460
+ return freq if freq.ndim else float(freq)
461
+
462
+
463
+ def simulate_alpha_omega_dynamo(
464
+ A0: NDArray[np.float64],
465
+ B0: NDArray[np.float64],
466
+ alpha: float,
467
+ shear: float,
468
+ eta: float,
469
+ Lx: float,
470
+ dt: float,
471
+ n_steps: int,
472
+ save_every: int = 1,
473
+ ):
474
+ """Time-step the linear 1D alpha-omega mean-field dynamo equations exactly, per Fourier mode.
475
+
476
+ Because :math:`\\partial_t A=\\alpha B+\\eta\\partial_x^2 A`,
477
+ :math:`\\partial_t B=G\\partial_x A+\\eta\\partial_x^2 B` is linear,
478
+ each Fourier mode's amplitude pair :math:`(\\hat A(k,t),\\hat B(k,t))`
479
+ evolves independently under a 2x2 constant-coefficient linear ODE,
480
+ :math:`d/dt\\,(\\hat A,\\hat B)^T = M(k)\\,(\\hat A,\\hat B)^T` with
481
+
482
+ .. math::
483
+
484
+ M(k) = \\begin{pmatrix}-\\eta k^2 & \\alpha \\\\ iGk & -\\eta k^2\\end{pmatrix},
485
+
486
+ so it can be advanced *exactly* over a step `dt` via the matrix
487
+ exponential :math:`\\exp(M(k)\\,dt)`, with no time-discretization error
488
+ whatsoever. Writing :math:`M(k)=-\\eta k^2 I + N(k)` with
489
+ :math:`N(k)=\\begin{pmatrix}0&\\alpha\\\\iGk&0\\end{pmatrix}`, one has
490
+ :math:`N(k)^2 = i\\alpha Gk\\,I`, so with :math:`s_0=\\sqrt{i\\alpha Gk}`
491
+ (principal branch; :math:`s_0=0` at :math:`k=0`),
492
+
493
+ .. math::
494
+
495
+ \\exp(N(k)\\,dt) = \\cosh(s_0 dt)\\,I + \\frac{\\sinh(s_0 dt)}{s_0}\\,N(k)
496
+
497
+ (the usual generalized Euler formula for a matrix whose square is a
498
+ scalar multiple of the identity), and
499
+ :math:`\\exp(M(k)\\,dt) = e^{-\\eta k^2 dt}\\exp(N(k)\\,dt)`. This is
500
+ applied to every Fourier mode at once (vectorized over `k`, not
501
+ looped), then inverse-transformed back to real space.
502
+
503
+ Parameters
504
+ ----------
505
+ A0, B0 : ndarray of float, shape (nx,)
506
+ Initial poloidal-flux proxy and toroidal field on a periodic grid
507
+ of ``nx = len(A0)`` points over ``[0, Lx)``.
508
+ alpha : float
509
+ Alpha-effect coefficient.
510
+ shear : float
511
+ Rotational shear rate :math:`G`.
512
+ eta : float
513
+ Magnetic diffusivity.
514
+ Lx : float
515
+ Physical domain size.
516
+ dt : float
517
+ Time step.
518
+ n_steps : int
519
+ Total number of steps.
520
+ save_every : int, default 1
521
+ Save a snapshot every this many steps.
522
+
523
+ Returns
524
+ -------
525
+ times : ndarray of float, shape (n_saved,)
526
+ A_snapshots : ndarray of float, shape (n_saved, nx)
527
+ B_snapshots : ndarray of float, shape (n_saved, nx)
528
+
529
+ See Also
530
+ --------
531
+ alpha_omega_growth_rate : Closed-form growth rate this stepping is checked against.
532
+ dominant_mode_growth_rate : Extracts the numerically observed growth rate from the output here.
533
+
534
+ Examples
535
+ --------
536
+ >>> import numpy as np
537
+ >>> x = np.linspace(0, 2 * np.pi, 32, endpoint=False)
538
+ >>> A0 = 1e-3 * np.cos(x)
539
+ >>> B0 = np.zeros_like(A0)
540
+ >>> times, A_snaps, B_snaps = simulate_alpha_omega_dynamo(A0, B0, alpha=1.0, shear=5.0, eta=0.05, Lx=2 * np.pi, dt=0.01, n_steps=20, save_every=5)
541
+ >>> A_snaps.shape
542
+ (5, 32)
543
+ >>> bool(np.all(np.isfinite(A_snaps)))
544
+ True
545
+ """
546
+ A0 = np.asarray(A0, dtype=float)
547
+ B0 = np.asarray(B0, dtype=float)
548
+ nx = A0.shape[0]
549
+ k = 2.0 * np.pi * np.fft.fftfreq(nx, d=Lx / nx)
550
+
551
+ c = 1j * alpha * shear * k
552
+ s0 = np.sqrt(c)
553
+ dt_s0 = s0 * dt
554
+ cosh_term = np.cosh(dt_s0)
555
+ sinh_over_s0 = np.where(s0 == 0, dt, np.divide(np.sinh(dt_s0), s0, out=np.zeros_like(dt_s0), where=(s0 != 0)))
556
+ decay = np.exp(-eta * k**2 * dt)
557
+
558
+ A_hat = np.fft.fft(A0)
559
+ B_hat = np.fft.fft(B0)
560
+
561
+ times = [0.0]
562
+ A_snapshots = [A0.copy()]
563
+ B_snapshots = [B0.copy()]
564
+
565
+ t = 0.0
566
+ for step in range(1, n_steps + 1):
567
+ new_A_hat = decay * (cosh_term * A_hat + sinh_over_s0 * alpha * B_hat)
568
+ new_B_hat = decay * (cosh_term * B_hat + sinh_over_s0 * (1j * k * shear) * A_hat)
569
+ A_hat, B_hat = new_A_hat, new_B_hat
570
+ t += dt
571
+ if step % save_every == 0:
572
+ times.append(t)
573
+ A_snapshots.append(np.real(np.fft.ifft(A_hat)))
574
+ B_snapshots.append(np.real(np.fft.ifft(B_hat)))
575
+
576
+ return np.array(times), np.array(A_snapshots), np.array(B_snapshots)
577
+
578
+
579
+ def dominant_mode_growth_rate(A_snapshots: NDArray[np.float64], dt_save: float, window_frac: float = 0.5) -> float:
580
+ """Numerically observed exponential growth rate of the total field energy.
581
+
582
+ Fits a line to :math:`\\log\\left(\\sum_x A(x,t)^2\\right)` versus time
583
+ (least squares) and returns half the slope -- since energy
584
+ :math:`\\propto A^2` grows as :math:`e^{2\\sigma t}` for a field
585
+ growing as :math:`e^{\\sigma t}`. Only the *last* `window_frac` of the
586
+ snapshots are used for the fit: an alpha-omega dynamo mode has two
587
+ eigenbranches (:math:`\\sigma\\pm` in the module docstring's
588
+ derivation), and unless the initial condition happens to be a pure
589
+ eigenmode of the faster-growing branch (e.g. `B0` all zero, as in
590
+ this module's typical test setup), the early evolution is a
591
+ transient mix of both branches that will bias a whole-history fit;
592
+ restricting to the late-time window isolates the asymptotically
593
+ dominant branch, which is what :func:`alpha_omega_growth_rate`
594
+ predicts. Used to validate :func:`simulate_alpha_omega_dynamo`
595
+ against that closed form.
596
+
597
+ Parameters
598
+ ----------
599
+ A_snapshots : ndarray of float, shape (n_saved, nx)
600
+ Field snapshots, e.g. `A_snapshots` or `B_snapshots` from
601
+ :func:`simulate_alpha_omega_dynamo`.
602
+ dt_save : float
603
+ Time interval between consecutive snapshots.
604
+ window_frac : float, default 0.5
605
+ Fraction of the snapshot history (counted from the end) used for
606
+ the fit.
607
+
608
+ Returns
609
+ -------
610
+ float
611
+ The fitted growth rate :math:`\\sigma`.
612
+
613
+ Examples
614
+ --------
615
+ >>> import numpy as np
616
+ >>> t = np.arange(10) * 0.1
617
+ >>> sigma_true = 2.0
618
+ >>> A_snaps = np.exp(sigma_true * t)[:, None] * np.ones((10, 4))
619
+ >>> round(dominant_mode_growth_rate(A_snaps, dt_save=0.1), 6)
620
+ 2.0
621
+ """
622
+ energy = np.sum(A_snapshots**2, axis=1)
623
+ n = energy.shape[0]
624
+ times = np.arange(n) * dt_save
625
+ start = max(0, n - max(2, int(np.ceil(n * window_frac))))
626
+ log_energy = np.log(energy[start:] + 1e-300)
627
+ slope, _ = np.polyfit(times[start:], log_energy, 1)
628
+ return float(slope / 2.0)