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,46 @@
1
+ """Matplotlib- and Plotly-based visualizers for every physicskit.relativity chapter.
2
+
3
+ - :mod:`physicskit.relativity.visualizers.shadow_render` -- ray-traced black hole shadows
4
+ and lensed, redshift-shaded accretion disks (Schwarzschild and Kerr).
5
+ - :mod:`physicskit.relativity.visualizers.spacetime_3d` -- Flamm paraboloid embedding
6
+ diagrams.
7
+ - :mod:`physicskit.relativity.visualizers.spacetime_diagrams` -- Kruskal-Szekeres and
8
+ Penrose-Carter conformal diagrams.
9
+ - :mod:`physicskit.relativity.visualizers.wave_plots` -- gravitational wave strain and
10
+ spatial ripple-pattern plots.
11
+ - :mod:`physicskit.relativity.visualizers.interactive` -- Plotly-based interactive 3D
12
+ orbits and shadow images.
13
+ """
14
+
15
+ from physicskit.relativity.visualizers.interactive import interactive_orbit_3d, interactive_shadow_image
16
+ from physicskit.relativity.visualizers.shadow_render import plot_black_hole_shadow, render_black_hole_image
17
+ from physicskit.relativity.visualizers.spacetime_3d import flamm_paraboloid, plot_flamm_paraboloid
18
+ from physicskit.relativity.visualizers.spacetime_diagrams import (
19
+ kruskal_coordinates,
20
+ penrose_carter_coordinates,
21
+ plot_kruskal_diagram,
22
+ plot_penrose_diagram,
23
+ )
24
+ from physicskit.relativity.visualizers.wave_plots import (
25
+ animate_wave_ripple,
26
+ plot_strain_waveform,
27
+ plot_wave_ripple,
28
+ wave_ripple_snapshot,
29
+ )
30
+
31
+ __all__ = [
32
+ "animate_wave_ripple",
33
+ "flamm_paraboloid",
34
+ "interactive_orbit_3d",
35
+ "interactive_shadow_image",
36
+ "kruskal_coordinates",
37
+ "penrose_carter_coordinates",
38
+ "plot_black_hole_shadow",
39
+ "plot_flamm_paraboloid",
40
+ "plot_kruskal_diagram",
41
+ "plot_penrose_diagram",
42
+ "plot_strain_waveform",
43
+ "plot_wave_ripple",
44
+ "render_black_hole_image",
45
+ "wave_ripple_snapshot",
46
+ ]
@@ -0,0 +1,127 @@
1
+ """Interactive Plotly visualizations.
2
+
3
+ Every other module in :mod:`physicskit.relativity.visualizers` produces static Matplotlib
4
+ figures. This module offers pan/zoom/rotate-enabled Plotly figures instead
5
+ -- convenient for interactively exploring a 3D orbit or a ray-traced image
6
+ in a Jupyter notebook, or for embedding in a standalone HTML page.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+ import plotly.graph_objects as go
13
+
14
+ __all__ = ["interactive_orbit_3d", "interactive_shadow_image"]
15
+
16
+
17
+ def interactive_orbit_3d(trajectory, M=1.0, horizon_radius=None):
18
+ """Build an interactive 3D plot of an integrated geodesic trajectory.
19
+
20
+ Parameters
21
+ ----------
22
+ trajectory : dict of str -> ndarray
23
+ Output of ``SchwarzschildBlackHole.integrate_geodesic`` (keys
24
+ ``"r"``, ``"theta"``, ``"phi"``) or a dict with equivalent
25
+ equatorial ``"r"``, ``"phi"`` arrays (``"theta"`` then defaults to
26
+ :math:`\\pi/2` throughout).
27
+ M : float, default=1.0
28
+ Black hole mass, used to draw the event horizon sphere.
29
+ horizon_radius : float, optional
30
+ Overrides the horizon radius (defaults to ``2*M``).
31
+
32
+ Returns
33
+ -------
34
+ plotly.graph_objects.Figure
35
+
36
+ Examples
37
+ --------
38
+ >>> from physicskit.relativity.chapters.schwarzschild import SchwarzschildBlackHole
39
+ >>> bh = SchwarzschildBlackHole(M=1.0)
40
+ >>> y0 = bh.circular_orbit_initial_state(r0=10.0)
41
+ >>> traj = bh.integrate_geodesic(y0, dtau=0.05, n_steps=200)
42
+ >>> fig = interactive_orbit_3d(traj, M=1.0)
43
+ >>> len(fig.data) > 0
44
+ True
45
+ """
46
+ r = np.asarray(trajectory["r"], dtype=np.float64)
47
+ theta = np.asarray(trajectory.get("theta", np.full_like(r, np.pi / 2.0)), dtype=np.float64)
48
+ phi = np.asarray(trajectory["phi"], dtype=np.float64)
49
+
50
+ x = r * np.sin(theta) * np.cos(phi)
51
+ y = r * np.sin(theta) * np.sin(phi)
52
+ z = r * np.cos(theta)
53
+
54
+ if horizon_radius is None:
55
+ horizon_radius = 2.0 * M
56
+
57
+ fig = go.Figure()
58
+ fig.add_trace(go.Scatter3d(x=x, y=y, z=z, mode="lines", line={"color": "steelblue", "width": 4}, name="geodesic"))
59
+
60
+ u = np.linspace(0.0, 2.0 * np.pi, 40)
61
+ v = np.linspace(0.0, np.pi, 20)
62
+ hx = horizon_radius * np.outer(np.cos(u), np.sin(v))
63
+ hy = horizon_radius * np.outer(np.sin(u), np.sin(v))
64
+ hz = horizon_radius * np.outer(np.ones_like(u), np.cos(v))
65
+ fig.add_trace(
66
+ go.Surface(
67
+ x=hx,
68
+ y=hy,
69
+ z=hz,
70
+ showscale=False,
71
+ colorscale=[[0, "black"], [1, "black"]],
72
+ name="horizon",
73
+ )
74
+ )
75
+
76
+ fig.update_layout(
77
+ title="Geodesic trajectory",
78
+ scene={
79
+ "xaxis_title": "x [M]",
80
+ "yaxis_title": "y [M]",
81
+ "zaxis_title": "z [M]",
82
+ "aspectmode": "data",
83
+ },
84
+ height=650,
85
+ )
86
+ return fig
87
+
88
+
89
+ def interactive_shadow_image(result):
90
+ """Build an interactive heatmap of a ray-traced black hole image.
91
+
92
+ Parameters
93
+ ----------
94
+ result : dict
95
+ Output of ``physicskit.relativity.visualizers.shadow_render.render_black_hole_image``.
96
+
97
+ Returns
98
+ -------
99
+ plotly.graph_objects.Figure
100
+ """
101
+ outcomes = result["outcomes"]
102
+ hit_radii = result["hit_radii"]
103
+ extent = result["extent"]
104
+ ny, nx = outcomes.shape
105
+
106
+ display = np.where(outcomes == 3, hit_radii, np.where(outcomes == 1, -1.0, np.nan))
107
+ x = np.linspace(extent[0], extent[1], nx)
108
+ y = np.linspace(extent[2], extent[3], ny)
109
+
110
+ fig = go.Figure(
111
+ data=go.Heatmap(
112
+ z=display,
113
+ x=x,
114
+ y=y,
115
+ colorscale="inferno",
116
+ colorbar={"title": "disk radius [M]<br>(-1 = shadow)"},
117
+ hoverongaps=False,
118
+ )
119
+ )
120
+ fig.update_yaxes(autorange="reversed", scaleanchor="x", scaleratio=1)
121
+ fig.update_layout(
122
+ title="Black hole shadow and accretion disk (interactive)",
123
+ xaxis_title="impact parameter x [M]",
124
+ yaxis_title="impact parameter y [M]",
125
+ height=650,
126
+ )
127
+ return fig
@@ -0,0 +1,367 @@
1
+ """Visualizations of black hole shadows and gravitationally lensed accretion disks.
2
+
3
+ :func:`render_black_hole_image` dispatches on the spin parameter ``a``: at
4
+ ``a=0`` it uses the fast, exact 2D Schwarzschild orbit-equation raytracer in
5
+ :mod:`physicskit.relativity.core.raytracer`; at ``a>0`` it uses the more general Carter-
6
+ formalism Kerr raytracer in :mod:`physicskit.relativity.core.kerr_raytracer`, which
7
+ reproduces the same physics at ``a=0`` (and is slower, hence the dedicated
8
+ fast path). Both report each pixel's disk-frame azimuthal angular momentum
9
+ :math:`L_\\phi` alongside the usual outcome/hit-radius arrays, letting
10
+ :func:`plot_black_hole_shadow` optionally color the accretion disk by its
11
+ combined gravitational and Doppler redshift rather than radius alone.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import matplotlib.pyplot as plt
17
+ import numpy as np
18
+ from matplotlib.animation import FuncAnimation
19
+
20
+ from physicskit.relativity.core.kerr_raytracer import render_kerr_shadow_image
21
+ from physicskit.relativity.core.raytracer import (
22
+ OUTCOME_CAPTURED,
23
+ OUTCOME_DISK,
24
+ OUTCOME_ESCAPED,
25
+ OUTCOME_UNRESOLVED,
26
+ camera_basis,
27
+ render_shadow_image,
28
+ )
29
+
30
+ __all__ = ["animate_shadow_spin_sweep", "plot_black_hole_shadow", "render_black_hole_image"]
31
+
32
+
33
+ def _pixel_grids(ny, nx, screen_half_width, screen_half_height):
34
+ """Per-pixel (alpha, beta) screen coordinates, each of shape (ny, nx)."""
35
+ alpha = screen_half_width * (2.0 * np.arange(nx) / (nx - 1) - 1.0)
36
+ beta = screen_half_height * (1.0 - 2.0 * np.arange(ny) / (ny - 1))
37
+ return np.meshgrid(alpha, beta)
38
+
39
+
40
+ def render_black_hole_image(
41
+ M,
42
+ a=0.0,
43
+ observer_distance=500.0,
44
+ inclination=1.3,
45
+ screen_half_width=15.0,
46
+ ny=200,
47
+ nx=200,
48
+ r_disk_inner=None,
49
+ r_disk_outer=None,
50
+ n_steps=None,
51
+ **kwargs,
52
+ ):
53
+ """Backward ray-trace a black hole shadow and thin equatorial accretion disk.
54
+
55
+ Parameters
56
+ ----------
57
+ M : float
58
+ Black hole mass, in geometrized units.
59
+ a : float, default=0.0
60
+ Kerr spin parameter, :math:`0 \\le a < M`. ``a=0`` uses the fast,
61
+ exact Schwarzschild orbit-equation raytracer; ``a>0`` uses the more
62
+ general (and slower) Kerr Carter-formalism raytracer.
63
+ observer_distance : float, default=500.0
64
+ Observer distance from the black hole, in units of ``M`` (should be
65
+ :math:`\\gg M` for the flat, far-away camera approximation to hold).
66
+ inclination : float, default=1.3
67
+ Observer inclination from the disk's normal axis, in radians
68
+ (``pi/2`` is edge-on; ``1.3`` rad :math:`\\approx 74^\\circ` gives a
69
+ classic, strongly lensed oblique view).
70
+ screen_half_width : float, default=15.0
71
+ Half-width of the camera screen, in units of ``M``.
72
+ ny, nx : int, default=200
73
+ Image resolution, in pixels.
74
+ r_disk_inner, r_disk_outer : float, optional
75
+ Accretion disk radii. Default to the ISCO (prograde, for Kerr) and
76
+ ``20M``.
77
+ n_steps : int, optional
78
+ Maximum steps per ray; forwarded to the underlying raytracer
79
+ (defaults differ between the Schwarzschild and Kerr backends).
80
+ **kwargs
81
+ Extra keyword arguments forwarded to the underlying raytracer
82
+ (e.g. ``dphi`` for Schwarzschild, ``dlambda_max`` for Kerr).
83
+
84
+ Returns
85
+ -------
86
+ dict
87
+ Keys ``"outcomes"``, ``"hit_radii"``, ``"L_phi"`` (disk-frame
88
+ azimuthal angular momentum of each pixel's photon, used for
89
+ redshift coloring), ``"extent"``, ``"r_disk_inner"``,
90
+ ``"r_disk_outer"``, ``"M"``, ``"a"``, ready for
91
+ :func:`plot_black_hole_shadow`.
92
+
93
+ Examples
94
+ --------
95
+ >>> result = render_black_hole_image(M=1.0, ny=20, nx=20)
96
+ >>> result["outcomes"].shape
97
+ (20, 20)
98
+ """
99
+ if r_disk_inner is None:
100
+ if a == 0.0:
101
+ r_disk_inner = 6.0 * M
102
+ else:
103
+ from physicskit.relativity.chapters.kerr import KerrBlackHole
104
+
105
+ r_disk_inner = KerrBlackHole(M=M, a=a).isco_radius(prograde=True)
106
+ if r_disk_outer is None:
107
+ r_disk_outer = 20.0 * M
108
+
109
+ alpha_grid, beta_grid = _pixel_grids(ny, nx, screen_half_width, screen_half_width)
110
+
111
+ if a == 0.0:
112
+ view_dir, right, up = camera_basis(inclination)
113
+ obs_pos = -view_dir * observer_distance
114
+ outcomes, hit_radii = render_shadow_image(
115
+ ny,
116
+ nx,
117
+ screen_half_width,
118
+ screen_half_width,
119
+ obs_pos,
120
+ right,
121
+ up,
122
+ view_dir,
123
+ M,
124
+ r_disk_inner,
125
+ r_disk_outer,
126
+ n_steps=n_steps or 3000,
127
+ **kwargs,
128
+ )
129
+ # L_phi = z-component of (ray_origin x view_dir); disk axis is +Z.
130
+ # Only the x,y components of each vector enter a cross product's z-component.
131
+ ray_origin_x = obs_pos[0] + alpha_grid * right[0] + beta_grid * up[0]
132
+ ray_origin_y = obs_pos[1] + alpha_grid * right[1] + beta_grid * up[1]
133
+ L_phi = ray_origin_x * view_dir[1] - ray_origin_y * view_dir[0]
134
+ else:
135
+ outcomes, hit_radii = render_kerr_shadow_image(
136
+ ny,
137
+ nx,
138
+ screen_half_width,
139
+ screen_half_width,
140
+ observer_distance,
141
+ inclination,
142
+ a,
143
+ M,
144
+ r_disk_inner,
145
+ r_disk_outer,
146
+ n_steps=n_steps or 4000,
147
+ **kwargs,
148
+ )
149
+ L_phi = -alpha_grid * np.sin(inclination)
150
+
151
+ return {
152
+ "outcomes": outcomes,
153
+ "hit_radii": hit_radii,
154
+ "L_phi": L_phi,
155
+ "extent": (-screen_half_width, screen_half_width, -screen_half_width, screen_half_width),
156
+ "r_disk_inner": r_disk_inner,
157
+ "r_disk_outer": r_disk_outer,
158
+ "M": M,
159
+ "a": a,
160
+ "inclination": inclination,
161
+ }
162
+
163
+
164
+ def plot_black_hole_shadow(result, ax=None, cmap_disk="inferno", redshift=False):
165
+ """Render a :func:`render_black_hole_image` result as a matplotlib image.
166
+
167
+ Captured and unresolved (deep near-critical) pixels are drawn black
168
+ (the shadow); escaped pixels are drawn as a dark background; disk-hit
169
+ pixels are colored by a temperature-like radius gradient, optionally
170
+ modulated by their combined gravitational and Doppler redshift.
171
+
172
+ Parameters
173
+ ----------
174
+ result : dict
175
+ Output of :func:`render_black_hole_image`.
176
+ ax : matplotlib.axes.Axes, optional
177
+ Axes to draw into. A new figure and axes are created if not given.
178
+ cmap_disk : str, default="inferno"
179
+ Colormap used for the accretion disk's radius gradient.
180
+ redshift : bool, default=False
181
+ If True, scale each disk pixel's brightness by :math:`g^3` (the
182
+ approximate relativistic Doppler-beaming boost of bolometric
183
+ intensity for a locally isotropic emitter), so the side of the
184
+ disk rotating toward the observer appears brighter and the
185
+ receding side dimmer -- the signature asymmetric brightness seen
186
+ in real black hole images.
187
+
188
+ Returns
189
+ -------
190
+ matplotlib.axes.Axes
191
+ """
192
+ if ax is None:
193
+ _, ax = plt.subplots(figsize=(6, 6))
194
+
195
+ image = shadow_image_array(result, cmap_disk=cmap_disk, redshift=redshift)
196
+ ax.imshow(image, extent=result["extent"], origin="upper")
197
+ ax.set_xlabel("impact parameter x [M]")
198
+ ax.set_ylabel("impact parameter y [M]")
199
+ title = "Black hole shadow and gravitationally lensed accretion disk"
200
+ if redshift:
201
+ title += " (Doppler-shaded)"
202
+ ax.set_title(title)
203
+ return ax
204
+
205
+
206
+ def shadow_image_array(result, cmap_disk="inferno", redshift=False):
207
+ """Build the RGB image array a :func:`render_black_hole_image` result maps to.
208
+
209
+ Factored out of :func:`plot_black_hole_shadow` so
210
+ :func:`animate_shadow_spin_sweep` can reuse the identical shadow/disk
211
+ coloring logic when assembling animation frames.
212
+
213
+ Parameters
214
+ ----------
215
+ result : dict
216
+ Output of :func:`render_black_hole_image`.
217
+ cmap_disk : str, default="inferno"
218
+ Colormap used for the accretion disk's radius gradient.
219
+ redshift : bool, default=False
220
+ If True, scale each disk pixel's brightness by the combined
221
+ gravitational and Doppler redshift, exactly as in
222
+ :func:`plot_black_hole_shadow`.
223
+
224
+ Returns
225
+ -------
226
+ ndarray of shape (ny, nx, 3)
227
+ RGB image, clipped to ``[0, 1]``.
228
+ """
229
+ outcomes = result["outcomes"]
230
+ hit_radii = result["hit_radii"]
231
+ image = np.zeros((*outcomes.shape, 3))
232
+
233
+ escaped_mask = outcomes == OUTCOME_ESCAPED
234
+ image[escaped_mask] = [0.02, 0.02, 0.06]
235
+
236
+ disk_mask = outcomes == OUTCOME_DISK
237
+ if np.any(disk_mask):
238
+ r_in, r_out = result["r_disk_inner"], result["r_disk_outer"]
239
+ norm_r = np.clip((hit_radii - r_in) / max(r_out - r_in, 1e-12), 0.0, 1.0)
240
+ cmap = plt.get_cmap(cmap_disk)
241
+ colors = cmap(1.0 - norm_r)[..., :3]
242
+
243
+ if redshift:
244
+ g = _redshift_factor(result, disk_mask)
245
+ brightness = np.clip(g**3, 0.15, 4.0)
246
+ brightness_norm = brightness / np.max(brightness[disk_mask])
247
+ colors = colors * brightness_norm[..., None]
248
+
249
+ image[disk_mask] = colors[disk_mask]
250
+
251
+ captured_mask = (outcomes == OUTCOME_CAPTURED) | (outcomes == OUTCOME_UNRESOLVED)
252
+ image[captured_mask] = [0.0, 0.0, 0.0]
253
+
254
+ return np.clip(image, 0.0, 1.0)
255
+
256
+
257
+ def animate_shadow_spin_sweep(
258
+ M=1.0,
259
+ a_values=None,
260
+ observer_distance=500.0,
261
+ inclination=1.3,
262
+ screen_half_width=15.0,
263
+ ny=100,
264
+ nx=100,
265
+ r_disk_inner=None,
266
+ r_disk_outer=20.0,
267
+ redshift=True,
268
+ cmap_disk="inferno",
269
+ interval=200,
270
+ ):
271
+ """Animate a black hole's shadow and lensed accretion disk as the spin :math:`a` is swept.
272
+
273
+ Backward ray-traces a full image at each spin value with
274
+ :func:`render_black_hole_image`, then plays the resulting frames back
275
+ with :class:`~matplotlib.animation.FuncAnimation`: the shadow visibly
276
+ shrinks and flattens toward the equatorial photon orbit, the inner
277
+ edge of the disk creeps inward toward the shrinking ISCO, and (with
278
+ ``redshift=True``) the Doppler-brightened approaching side of the disk
279
+ grows more pronounced, as :math:`a \\to M`.
280
+
281
+ Parameters
282
+ ----------
283
+ M : float, default=1.0
284
+ Black hole mass, in geometrized units.
285
+ a_values : array_like of float, optional
286
+ Spin values to sweep through, each satisfying ``0 <= a < M``.
287
+ Defaults to 50 values from 0 to ``0.98 * M``.
288
+ observer_distance, inclination, screen_half_width, ny, nx : see :func:`render_black_hole_image`.
289
+ r_disk_inner : float, optional
290
+ Inner disk radius; defaults to the ISCO at each frame's spin
291
+ (:meth:`~physicskit.relativity.chapters.kerr.KerrBlackHole.isco_radius`
292
+ at ``a=0`` reduces to the Schwarzschild ISCO, :math:`6M`), so the
293
+ disk visibly creeps inward as the spin increases.
294
+ r_disk_outer : float, default=20.0
295
+ Outer disk radius, held fixed across frames.
296
+ redshift : bool, default=True
297
+ Doppler/gravitational-redshift-shade the disk, as in
298
+ :func:`plot_black_hole_shadow`.
299
+ cmap_disk : str, default="inferno"
300
+ Colormap for the disk.
301
+ interval : int, default=200
302
+ Delay between frames, in milliseconds.
303
+
304
+ Returns
305
+ -------
306
+ matplotlib.animation.FuncAnimation
307
+ Assign it to a variable to keep it alive, and display it with
308
+ ``plt.show()`` or save it with ``anim.save(...)``.
309
+ """
310
+ from physicskit.relativity.chapters.kerr import KerrBlackHole
311
+ from physicskit.relativity.chapters.schwarzschild import SchwarzschildBlackHole
312
+
313
+ if a_values is None:
314
+ a_values = np.linspace(0.0, 0.98 * M, 50)
315
+
316
+ frames = []
317
+ for a in a_values:
318
+ r_in = r_disk_inner
319
+ if r_in is None:
320
+ r_in = SchwarzschildBlackHole(M=M).isco_radius if a == 0.0 else KerrBlackHole(M=M, a=a).isco_radius(prograde=True)
321
+ result = render_black_hole_image(
322
+ M=M,
323
+ a=a,
324
+ observer_distance=observer_distance,
325
+ inclination=inclination,
326
+ screen_half_width=screen_half_width,
327
+ ny=ny,
328
+ nx=nx,
329
+ r_disk_inner=r_in,
330
+ r_disk_outer=r_disk_outer,
331
+ )
332
+ image = shadow_image_array(result, cmap_disk=cmap_disk, redshift=redshift)
333
+ frames.append((image, result["extent"], r_in))
334
+
335
+ fig, ax = plt.subplots(figsize=(6, 6))
336
+ im = ax.imshow(frames[0][0], extent=frames[0][1], origin="upper")
337
+ ax.set_xlabel("impact parameter x [M]")
338
+ ax.set_ylabel("impact parameter y [M]")
339
+ title = ax.set_title(f"a = {a_values[0]:.2f} M, ISCO = {frames[0][2]:.2f} M")
340
+
341
+ def update(frame_idx):
342
+ image, extent, r_in = frames[frame_idx]
343
+ im.set_data(image)
344
+ im.set_extent(extent)
345
+ title.set_text(f"a = {a_values[frame_idx]:.2f} M, ISCO = {r_in:.2f} M")
346
+ return im, title
347
+
348
+ return FuncAnimation(fig, update, frames=len(frames), interval=interval, blit=False)
349
+
350
+
351
+ def _redshift_factor(result, disk_mask):
352
+ """Compute the disk-frame redshift factor g at every disk-hit pixel."""
353
+ M, a = result["M"], result["a"]
354
+ hit_radii = result["hit_radii"]
355
+ L_phi = result["L_phi"]
356
+ g = np.ones_like(hit_radii)
357
+ if a == 0.0:
358
+ from physicskit.relativity.chapters.schwarzschild import SchwarzschildBlackHole
359
+
360
+ bh = SchwarzschildBlackHole(M=M)
361
+ g[disk_mask] = bh.disk_redshift_factor(hit_radii[disk_mask], L_phi[disk_mask])
362
+ else:
363
+ from physicskit.relativity.chapters.kerr import KerrBlackHole
364
+
365
+ bh = KerrBlackHole(M=M, a=a)
366
+ g[disk_mask] = bh.disk_redshift_factor(hit_radii[disk_mask], L_phi[disk_mask], prograde=True)
367
+ return g
@@ -0,0 +1,92 @@
1
+ """3D embedding diagrams: visualizing spacetime curvature as physical geometry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import matplotlib.pyplot as plt
6
+ import numpy as np
7
+
8
+ __all__ = ["flamm_paraboloid", "plot_flamm_paraboloid"]
9
+
10
+
11
+ def flamm_paraboloid(M, r_min=None, r_max=None, n_r=100, n_phi=100):
12
+ """Compute Flamm's paraboloid: an embedding of the Schwarzschild equatorial slice in flat 3D space.
13
+
14
+ The spatial part of the Schwarzschild metric restricted to
15
+ :math:`t = \\text{const}`, :math:`\\theta = \\pi/2` is
16
+
17
+ .. math::
18
+
19
+ d\\ell^2 = \\frac{dr^2}{1 - 2M/r} + r^2 d\\phi^2
20
+
21
+ Embedding this curved 2-surface as a surface of revolution
22
+ :math:`z(r)` in flat 3D Euclidean space (matching the proper radial
23
+ distance along the surface to that of the curved metric) gives Flamm's
24
+ paraboloid,
25
+
26
+ .. math::
27
+
28
+ z(r) = 2\\sqrt{2M(r - 2M)}, \\qquad r \\ge 2M
29
+
30
+ the classic "rubber sheet" funnel shape used to visualize gravity as
31
+ curved geometry -- though unlike the popular rubber-sheet cartoon, this
32
+ is an exact embedding of the *actual* spatial curvature, not gravity
33
+ acting on a sheet.
34
+
35
+ Parameters
36
+ ----------
37
+ M : float
38
+ Mass, in geometrized units.
39
+ r_min : float, optional
40
+ Inner radius. Defaults to the horizon, ``2M``.
41
+ r_max : float, optional
42
+ Outer radius. Defaults to ``20M``.
43
+ n_r, n_phi : int, default=100
44
+ Grid resolution in the radial and azimuthal directions.
45
+
46
+ Returns
47
+ -------
48
+ X, Y, Z : ndarray of shape (n_phi, n_r)
49
+ Cartesian embedding coordinates, suitable for
50
+ ``Axes3D.plot_surface``.
51
+ """
52
+ if r_min is None:
53
+ r_min = 2.0 * M
54
+ if r_max is None:
55
+ r_max = 20.0 * M
56
+ r = np.linspace(r_min, r_max, n_r)
57
+ phi = np.linspace(0.0, 2.0 * np.pi, n_phi)
58
+ R, Phi = np.meshgrid(r, phi)
59
+ Z = 2.0 * np.sqrt(2.0 * M * np.clip(R - 2.0 * M, 0.0, None))
60
+ X = R * np.cos(Phi)
61
+ Y = R * np.sin(Phi)
62
+ return X, Y, Z
63
+
64
+
65
+ def plot_flamm_paraboloid(M, ax=None, r_max=None, cmap="viridis"):
66
+ """Plot Flamm's paraboloid as a 3D surface.
67
+
68
+ Parameters
69
+ ----------
70
+ M : float
71
+ Mass, in geometrized units.
72
+ ax : mpl_toolkits.mplot3d.Axes3D, optional
73
+ 3D axes to draw into. A new figure and axes are created if not given.
74
+ r_max : float, optional
75
+ Outer radius, forwarded to :func:`flamm_paraboloid`.
76
+ cmap : str, default="viridis"
77
+ Matplotlib colormap name.
78
+
79
+ Returns
80
+ -------
81
+ mpl_toolkits.mplot3d.Axes3D
82
+ """
83
+ X, Y, Z = flamm_paraboloid(M, r_max=r_max)
84
+ if ax is None:
85
+ fig = plt.figure(figsize=(6, 6))
86
+ ax = fig.add_subplot(111, projection="3d")
87
+ ax.plot_surface(X, Y, -Z, cmap=cmap, linewidth=0, antialiased=True, alpha=0.95)
88
+ ax.set_xlabel("x [M]")
89
+ ax.set_ylabel("y [M]")
90
+ ax.set_zlabel("z (embedding depth) [M]")
91
+ ax.set_title("Flamm's paraboloid: Schwarzschild spatial curvature")
92
+ return ax