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,371 @@
1
+ """The Kerr black hole: frame dragging, the ergosphere, and the Penrose process.
2
+
3
+ Roy Kerr's 1963 exact solution describes a rotating black hole, characterized
4
+ by mass :math:`M` and spin parameter :math:`a = J/M \\in [0, M)`. Rotation
5
+ drags spacetime itself around with it (frame dragging / the Lense-Thirring
6
+ effect), creates a region outside the horizon -- the ergosphere -- where no
7
+ observer can remain stationary, and permits Penrose's remarkable 1969
8
+ proposal: a particle that splits inside the ergosphere can send one fragment
9
+ into the black hole with *negative* energy (as measured at infinity), so the
10
+ other fragment escapes carrying away more energy than the original particle
11
+ had -- extracting rotational energy from the black hole itself.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import numpy as np
17
+
18
+ from physicskit.relativity.core.geodesics import integrate_kerr_equatorial_geodesic
19
+
20
+ __all__ = ["KerrBlackHole"]
21
+
22
+
23
+ class KerrBlackHole:
24
+ """A rotating black hole of mass ``M`` and spin parameter ``a``, in geometrized units.
25
+
26
+ Parameters
27
+ ----------
28
+ M : float, default=1.0
29
+ Mass, in geometrized length units.
30
+ a : float, default=0.0
31
+ Spin parameter :math:`a = J/M`, with :math:`0 \\le a < M`.
32
+ ``a=0`` recovers Schwarzschild.
33
+ """
34
+
35
+ def __init__(self, M=1.0, a=0.0):
36
+ if not (0.0 <= a < M):
37
+ raise ValueError("spin parameter a must satisfy 0 <= a < M")
38
+ self.M = M
39
+ self.a = a
40
+
41
+ @property
42
+ def outer_horizon_radius(self):
43
+ """Event horizon radius, :math:`r_+ = M + \\sqrt{M^2 - a^2}`."""
44
+ return self.M + np.sqrt(self.M**2 - self.a**2)
45
+
46
+ @property
47
+ def inner_horizon_radius(self):
48
+ """Inner (Cauchy) horizon radius, :math:`r_- = M - \\sqrt{M^2 - a^2}`."""
49
+ return self.M - np.sqrt(self.M**2 - self.a**2)
50
+
51
+ @property
52
+ def dimensionless_spin(self):
53
+ """Dimensionless spin :math:`a_* = a/M \\in [0, 1)`."""
54
+ return self.a / self.M
55
+
56
+ def ergosphere_radius(self, theta):
57
+ """Static-limit (ergosphere outer boundary) radius at polar angle ``theta``.
58
+
59
+ .. math::
60
+
61
+ r_E(\\theta) = M + \\sqrt{M^2 - a^2\\cos^2\\theta}
62
+
63
+ The ergosphere is the region :math:`r_+ < r < r_E(\\theta)`, where
64
+ the ``t``-Killing vector becomes spacelike: no observer can remain
65
+ at fixed :math:`(r, \\theta, \\phi)` there, no matter how powerful
66
+ their rocket -- they are inevitably dragged around in :math:`\\phi`
67
+ by the rotating spacetime itself.
68
+
69
+ Parameters
70
+ ----------
71
+ theta : float or array_like
72
+ Polar angle (``pi/2`` is the equator, where the ergosphere is
73
+ widest, :math:`r_E = 2M`).
74
+
75
+ Returns
76
+ -------
77
+ float or ndarray
78
+ """
79
+ theta = np.asarray(theta, dtype=np.float64)
80
+ return self.M + np.sqrt(self.M**2 - self.a**2 * np.cos(theta) ** 2)
81
+
82
+ def is_inside_ergosphere(self, r, theta):
83
+ """Whether a point :math:`(r, \\theta)` lies inside the ergosphere.
84
+
85
+ Parameters
86
+ ----------
87
+ r : float
88
+ Radial coordinate.
89
+ theta : float
90
+ Polar angle.
91
+
92
+ Returns
93
+ -------
94
+ bool
95
+ """
96
+ return self.outer_horizon_radius < r < self.ergosphere_radius(theta)
97
+
98
+ def isco_radius(self, prograde=True):
99
+ """Innermost stable circular equatorial orbit radius (Bardeen-Press-Teukolsky 1972).
100
+
101
+ .. math::
102
+
103
+ Z_1 = 1 + (1-a_*^2)^{1/3}\\left[(1+a_*)^{1/3} + (1-a_*)^{1/3}\\right], \\quad
104
+ Z_2 = \\sqrt{3a_*^2 + Z_1^2}
105
+
106
+ .. math::
107
+
108
+ \\frac{r_{\\text{ISCO}}}{M} = 3 + Z_2 \\mp
109
+ \\sqrt{(3-Z_1)(3+Z_1+2Z_2)}
110
+
111
+ with the :math:`-` sign for a prograde (co-rotating) orbit and
112
+ :math:`+` for retrograde. Prograde ISCOs shrink toward the horizon
113
+ as spin increases (:math:`r_{\\text{ISCO}} \\to M` as
114
+ :math:`a \\to M`); retrograde ISCOs grow toward :math:`9M`.
115
+
116
+ Parameters
117
+ ----------
118
+ prograde : bool, default=True
119
+ Whether the orbit co-rotates with the black hole.
120
+
121
+ Returns
122
+ -------
123
+ float
124
+ """
125
+ a_star = self.dimensionless_spin
126
+ Z1 = 1.0 + (1.0 - a_star**2) ** (1.0 / 3.0) * ((1.0 + a_star) ** (1.0 / 3.0) + (1.0 - a_star) ** (1.0 / 3.0))
127
+ Z2 = np.sqrt(3.0 * a_star**2 + Z1**2)
128
+ sign = -1.0 if prograde else 1.0
129
+ return self.M * (3.0 + Z2 + sign * np.sqrt((3.0 - Z1) * (3.0 + Z1 + 2.0 * Z2)))
130
+
131
+ def frame_dragging_angular_velocity(self, r):
132
+ """Angular velocity of a zero-angular-momentum observer (ZAMO) in the equatorial plane.
133
+
134
+ .. math::
135
+
136
+ \\omega(r) = -\\frac{g_{t\\phi}}{g_{\\phi\\phi}}
137
+ = \\frac{2Ma}{r^3 + a^2 r + 2Ma^2}
138
+
139
+ The rate at which locally non-rotating observers are swept around
140
+ in :math:`\\phi` purely by the geometry -- frame dragging. At large
141
+ :math:`r` this falls off as :math:`2Ma/r^3`, the classic
142
+ Lense-Thirring precession rate; it rises sharply approaching the
143
+ ergosphere, where even a photon fired against the rotation is
144
+ dragged forward.
145
+
146
+ Parameters
147
+ ----------
148
+ r : float or array_like
149
+ Equatorial radial coordinate, :math:`r > r_+`.
150
+
151
+ Returns
152
+ -------
153
+ float or ndarray
154
+ """
155
+ r = np.asarray(r, dtype=np.float64)
156
+ M, a = self.M, self.a
157
+ return 2.0 * M * a / (r**3 + a**2 * r + 2.0 * M * a**2)
158
+
159
+ def irreducible_mass(self):
160
+ """Irreducible mass, :math:`M_{\\text{irr}} = \\sqrt{M r_+ / 2}`.
161
+
162
+ The irreducible mass can never decrease in any classical process
163
+ (Hawking's area theorem: it is proportional to
164
+ :math:`\\sqrt{\\text{horizon area}}`), which is what caps how much
165
+ rotational energy the Penrose process can extract.
166
+
167
+ Returns
168
+ -------
169
+ float
170
+ """
171
+ return np.sqrt(self.M * self.outer_horizon_radius / 2.0)
172
+
173
+ def max_penrose_efficiency(self):
174
+ """Maximum fraction of the black hole's mass extractable via the Penrose process.
175
+
176
+ .. math::
177
+
178
+ \\eta_{\\max} = 1 - \\frac{M_{\\text{irr}}}{M}
179
+ = 1 - \\sqrt{\\frac{r_+}{2M}}
180
+
181
+ Rises from 0 at :math:`a=0` to :math:`1 - 1/\\sqrt{2} \\approx
182
+ 29.3\\%` for an extremal (:math:`a \\to M`) black hole.
183
+
184
+ Returns
185
+ -------
186
+ float
187
+ """
188
+ return 1.0 - self.irreducible_mass() / self.M
189
+
190
+ def penrose_energy_gain(self, initial_energy, fragment_energy_infalling):
191
+ """Energy gained by the escaping fragment in a Penrose-process split.
192
+
193
+ A particle of specific energy ``initial_energy`` (measured at
194
+ infinity) splits, inside the ergosphere, into two fragments. If one
195
+ fragment falls into the horizon with negative specific energy
196
+ ``fragment_energy_infalling`` (possible only inside the ergosphere,
197
+ where the ``t``-Killing vector is spacelike), energy conservation at
198
+ the split point requires the escaping fragment to carry away
199
+
200
+ .. math::
201
+
202
+ E_{\\text{out}} = E_{\\text{in}} - E_{\\text{infalling}} > E_{\\text{in}}
203
+
204
+ Parameters
205
+ ----------
206
+ initial_energy : float
207
+ Specific energy of the infalling particle before it splits.
208
+ fragment_energy_infalling : float
209
+ Specific energy of the fragment that falls into the horizon;
210
+ must be negative for energy to be extracted.
211
+
212
+ Returns
213
+ -------
214
+ float
215
+ Specific energy of the escaping fragment.
216
+
217
+ Examples
218
+ --------
219
+ >>> bh = KerrBlackHole(M=1.0, a=0.9)
220
+ >>> e_out = bh.penrose_energy_gain(initial_energy=1.0, fragment_energy_infalling=-0.05)
221
+ >>> e_out > 1.0
222
+ True
223
+ """
224
+ if fragment_energy_infalling >= 0.0:
225
+ raise ValueError("a Penrose process requires a negative-energy infalling fragment")
226
+ return initial_energy - fragment_energy_infalling
227
+
228
+ def circular_orbit_conserved_quantities(self, r0, prograde=True):
229
+ """Conserved specific energy and angular momentum of a circular equatorial orbit.
230
+
231
+ Standard Bardeen-Press-Teukolsky (1972) result:
232
+
233
+ .. math::
234
+
235
+ E = \\frac{r^{3/2} - 2Mr^{1/2} + a M^{1/2}}
236
+ {r^{3/4}\\sqrt{r^{3/2} - 3Mr^{1/2} + 2aM^{1/2}}}, \\qquad
237
+ L = \\frac{M^{1/2}\\left(r^2 - 2aM^{1/2}r^{1/2} + a^2\\right)}
238
+ {r^{3/4}\\sqrt{r^{3/2} - 3Mr^{1/2} + 2aM^{1/2}}}
239
+
240
+ with :math:`a \\to -a` for a retrograde orbit.
241
+
242
+ Parameters
243
+ ----------
244
+ r0 : float
245
+ Orbital radius; should exceed the appropriate
246
+ :meth:`isco_radius` for the orbit to be stable.
247
+ prograde : bool, default=True
248
+ Whether the orbit co-rotates with the black hole.
249
+
250
+ Returns
251
+ -------
252
+ E : float
253
+ Specific energy.
254
+ L : float
255
+ Specific angular momentum.
256
+ """
257
+ M = self.M
258
+ a_eff = self.a if prograde else -self.a
259
+ r = r0
260
+ denom = r**0.75 * np.sqrt(r**1.5 - 3.0 * M * r**0.5 + 2.0 * a_eff * M**0.5)
261
+ E = (r**1.5 - 2.0 * M * r**0.5 + a_eff * M**0.5) / denom
262
+ L = np.sqrt(M) * (r**2 - 2.0 * a_eff * np.sqrt(M) * r**0.5 + a_eff**2) / denom
263
+ return E, L
264
+
265
+ def integrate_equatorial_geodesic(self, r0, E, L, mu2, dtau, n_steps):
266
+ """Integrate an equatorial Kerr geodesic of conserved energy ``E`` and momentum ``L``.
267
+
268
+ Parameters
269
+ ----------
270
+ r0 : float
271
+ Initial radius.
272
+ E : float
273
+ Conserved specific energy.
274
+ L : float
275
+ Conserved specific angular momentum.
276
+ mu2 : float
277
+ ``1.0`` for a timelike geodesic, ``0.0`` for a null (photon) geodesic.
278
+ dtau : float
279
+ Affine-parameter (or proper-time) step size.
280
+ n_steps : int
281
+ Maximum number of steps.
282
+
283
+ Returns
284
+ -------
285
+ dict of str -> ndarray
286
+ Keys ``"t"``, ``"r"``, ``"phi"``, ``"ur"``, plus ``"norm"``: the
287
+ four-velocity normalization :math:`g_{\\mu\\nu}u^\\mu u^\\nu`
288
+ at every step (should stay close to :math:`-\\mu^2`; its spread
289
+ measures integration error).
290
+ """
291
+ M, a = self.M, self.a
292
+ Delta0 = r0**2 - 2.0 * M * r0 + a**2
293
+ A0 = E * (r0**2 + a**2) - L * a
294
+ ur0 = np.sqrt(max((A0**2 - Delta0 * (mu2 * r0**2 + (L - a * E) ** 2)) / r0**4, 0.0))
295
+ y0 = np.array([0.0, r0, 0.0, -ur0])
296
+
297
+ trajectory, n_valid = integrate_kerr_equatorial_geodesic(y0, E, L, a, M, mu2, dtau, n_steps)
298
+ trajectory = trajectory[: n_valid + 1]
299
+ t, r, phi, ur = trajectory.T
300
+
301
+ Delta = r**2 - 2.0 * M * r + a**2
302
+ A = E * (r**2 + a**2) - L * a
303
+ ut = (-a * (a * E - L) + (r**2 + a**2) * A / Delta) / r**2
304
+ uphi = (-(a * E - L) + a * A / Delta) / r**2
305
+
306
+ g_tt = -(1.0 - 2.0 * M / r)
307
+ g_tphi = -2.0 * M * a / r
308
+ g_rr = r**2 / Delta
309
+ g_phiphi = r**2 + a**2 + 2.0 * M * a**2 / r
310
+ norm = g_tt * ut**2 + 2.0 * g_tphi * ut * uphi + g_rr * ur**2 + g_phiphi * uphi**2
311
+
312
+ return {"t": t, "r": r, "phi": phi, "ur": ur, "ut": ut, "uphi": uphi, "norm": norm}
313
+
314
+ def keplerian_angular_velocity(self, r, prograde=True):
315
+ """Coordinate angular velocity of a circular equatorial geodesic orbit.
316
+
317
+ .. math::
318
+
319
+ \\Omega(r) = \\pm\\frac{\\sqrt{M}}{r^{3/2} \\pm a\\sqrt{M}}
320
+
321
+ with the upper sign for a prograde (co-rotating) orbit. Reduces to
322
+ the Keplerian :math:`\\sqrt{M/r^3}` at :math:`a=0`.
323
+
324
+ Parameters
325
+ ----------
326
+ r : float or array_like
327
+ Orbital radius.
328
+ prograde : bool, default=True
329
+ Whether the orbit co-rotates with the black hole.
330
+
331
+ Returns
332
+ -------
333
+ float or ndarray
334
+ """
335
+ r = np.asarray(r, dtype=np.float64)
336
+ sign = 1.0 if prograde else -1.0
337
+ return sign * np.sqrt(self.M) / (r**1.5 + sign * self.a * np.sqrt(self.M))
338
+
339
+ def disk_redshift_factor(self, r, impact_parameter, prograde=True):
340
+ """Combined gravitational + Doppler redshift factor for a circularly orbiting disk emitter.
341
+
342
+ Same construction as
343
+ :meth:`physicskit.relativity.chapters.schwarzschild.SchwarzschildBlackHole.disk_redshift_factor`,
344
+ :math:`g = 1/[u^t(1 - b\\Omega)]`, using the Kerr equatorial metric
345
+ and :meth:`keplerian_angular_velocity` in place of their
346
+ Schwarzschild counterparts.
347
+
348
+ Parameters
349
+ ----------
350
+ r : float or array_like
351
+ Disk radius at emission.
352
+ impact_parameter : float or array_like
353
+ The received photon's impact parameter :math:`b = L_{\\text{ph}}/E_{\\text{ph}}`.
354
+ prograde : bool, default=True
355
+ Whether the disk co-rotates with the black hole.
356
+
357
+ Returns
358
+ -------
359
+ float or ndarray
360
+ The redshift factor :math:`g`.
361
+ """
362
+ r = np.asarray(r, dtype=np.float64)
363
+ b = np.asarray(impact_parameter, dtype=np.float64)
364
+ M, a = self.M, self.a
365
+ Omega = self.keplerian_angular_velocity(r, prograde=prograde)
366
+
367
+ g_tt = -(1.0 - 2.0 * M / r)
368
+ g_tphi = -2.0 * M * a / r
369
+ g_phiphi = r**2 + a**2 + 2.0 * M * a**2 / r
370
+ u_t = 1.0 / np.sqrt(-(g_tt + 2.0 * g_tphi * Omega + g_phiphi * Omega**2))
371
+ return 1.0 / (u_t * (1.0 - b * Omega))
@@ -0,0 +1,196 @@
1
+ """Point-source gravitational lensing: Einstein rings and multiple images.
2
+
3
+ When a point source, a point (or compact) lens, and an observer are exactly
4
+ aligned, the source's light is bent symmetrically around the lens into a
5
+ complete ring -- an Einstein ring, predicted by Einstein in 1936 (though he
6
+ believed it would never be observable) and first imaged for a galaxy-scale
7
+ lens in 1988. For any other alignment, a point lens splits a single source
8
+ into exactly two images, one on each side of the lens, with a combined
9
+ brightness that can be dramatically magnified -- the basis of gravitational
10
+ microlensing surveys used to detect exoplanets and dark, compact objects.
11
+ This module solves the thin-lens equation analytically in the weak-field
12
+ (point-mass) limit, and provides the exact (numerically integrated)
13
+ deflection angle for exploring strong-field corrections near the photon
14
+ sphere.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import numpy as np
20
+
21
+ __all__ = ["PointMassLens", "exact_deflection_angle"]
22
+
23
+
24
+ class PointMassLens:
25
+ """A point-mass (weak-field) gravitational lens: Einstein ring, images, and magnification.
26
+
27
+ Uses the thin-lens approximation with the leading-order deflection
28
+ angle :math:`\\hat\\alpha(b) = 4M/b`, valid whenever the source is not
29
+ aligned so closely that its image angle approaches the photon sphere
30
+ (see :func:`exact_deflection_angle` for the strong-field case).
31
+
32
+ Parameters
33
+ ----------
34
+ M : float
35
+ Lens mass, in geometrized units.
36
+ D_L : float
37
+ Observer-lens distance, in the same geometrized length units as ``M``.
38
+ D_S : float
39
+ Observer-source distance (:math:`D_S > D_L`), in the same units as ``M``.
40
+
41
+ Notes
42
+ -----
43
+ Every formula here is a ratio of ``M``, ``D_L``, ``D_S`` (angles and
44
+ magnifications, not an absolute strain), so it is scale-invariant: a
45
+ normalized convention (``M=1``, distances "in units of M") gives
46
+ exactly the same angles as real SI-consistent geometrized values would.
47
+ Unlike :class:`~physicskit.relativity.chapters.gw_merger.BinaryMerger`,
48
+ there is no absolute-scale trap here -- any self-consistent unit choice
49
+ works.
50
+
51
+ Attributes
52
+ ----------
53
+ D_LS : float
54
+ Lens-source distance, :math:`D_S - D_L` (flat-space subtraction;
55
+ this is a local, non-cosmological thin-lens treatment).
56
+ """
57
+
58
+ def __init__(self, M, D_L, D_S):
59
+ if D_S <= D_L:
60
+ raise ValueError("D_S must exceed D_L (the source must be behind the lens)")
61
+ self.M = M
62
+ self.D_L = D_L
63
+ self.D_S = D_S
64
+ self.D_LS = D_S - D_L
65
+
66
+ def einstein_angle(self):
67
+ """Einstein ring angular radius, :math:`\\theta_E = \\sqrt{4M D_{LS}/(D_L D_S)}`.
68
+
69
+ The angular radius at which a perfectly aligned (:math:`\\beta=0`)
70
+ point source appears as a complete ring.
71
+
72
+ Returns
73
+ -------
74
+ float
75
+ """
76
+ return np.sqrt(4.0 * self.M * self.D_LS / (self.D_L * self.D_S))
77
+
78
+ def image_angles(self, beta):
79
+ """Angular positions of the two images of a source at angular offset ``beta``.
80
+
81
+ Solves the thin-lens equation
82
+ :math:`\\beta = \\theta - \\theta_E^2/\\theta` (equivalent to a
83
+ quadratic in :math:`\\theta`) exactly:
84
+
85
+ .. math::
86
+
87
+ \\theta_\\pm = \\frac{\\beta \\pm \\sqrt{\\beta^2 + 4\\theta_E^2}}{2}
88
+
89
+ Parameters
90
+ ----------
91
+ beta : float
92
+ True (unlensed) angular offset of the source from the lens.
93
+
94
+ Returns
95
+ -------
96
+ theta_plus : float
97
+ Position of the primary image, on the same side as the source
98
+ (:math:`\\theta_+ > 0`).
99
+ theta_minus : float
100
+ Position of the secondary image, on the opposite side
101
+ (:math:`\\theta_- < 0`), closer to the lens and fainter.
102
+
103
+ Examples
104
+ --------
105
+ >>> lens = PointMassLens(M=1.0, D_L=1000.0, D_S=2000.0)
106
+ >>> theta_plus, theta_minus = lens.image_angles(beta=0.0)
107
+ >>> bool(np.isclose(theta_plus, lens.einstein_angle()))
108
+ True
109
+ >>> bool(np.isclose(theta_minus, -lens.einstein_angle()))
110
+ True
111
+ """
112
+ theta_E = self.einstein_angle()
113
+ discriminant = np.sqrt(beta**2 + 4.0 * theta_E**2)
114
+ theta_plus = (beta + discriminant) / 2.0
115
+ theta_minus = (beta - discriminant) / 2.0
116
+ return theta_plus, theta_minus
117
+
118
+ def magnification(self, beta):
119
+ """Magnification of each image, and their sum (total observed brightening).
120
+
121
+ Standard point-lens (Paczynski 1986) result, with
122
+ :math:`u = \\beta/\\theta_E`:
123
+
124
+ .. math::
125
+
126
+ \\mu_\\pm = \\frac{1}{2}\\left[\\frac{u^2+2}{u\\sqrt{u^2+4}} \\pm 1\\right]
127
+
128
+ Parameters
129
+ ----------
130
+ beta : float
131
+ True angular offset of the source from the lens (``beta != 0``).
132
+
133
+ Returns
134
+ -------
135
+ mu_plus : float
136
+ Magnification of the primary image (always :math:`> 1`).
137
+ mu_minus : float
138
+ Magnification of the secondary image (its absolute value is
139
+ :math:`< 1`; it has opposite parity, conventionally signed
140
+ negative).
141
+ total : float
142
+ Total observed magnification, :math:`|\\mu_+| + |\\mu_-|`.
143
+ """
144
+ theta_E = self.einstein_angle()
145
+ u = beta / theta_E
146
+ base = (u**2 + 2.0) / (u * np.sqrt(u**2 + 4.0))
147
+ mu_plus = 0.5 * (base + 1.0)
148
+ mu_minus = 0.5 * (base - 1.0)
149
+ return mu_plus, mu_minus, abs(mu_plus) + abs(mu_minus)
150
+
151
+
152
+ def exact_deflection_angle(bh, impact_parameter, r_far=2.0e5, dtau=None, n_steps=300000):
153
+ """Exact (numerically integrated) Schwarzschild light deflection angle.
154
+
155
+ Unlike the weak-field :math:`4M/b` formula used by
156
+ :class:`PointMassLens`, this integrates the actual null geodesic from
157
+ far away, through closest approach, and back out to far away, and
158
+ measures the total bending directly -- valid arbitrarily close to the
159
+ photon sphere (where the deflection formally diverges as
160
+ :math:`b \\to b_c = 3\\sqrt{3}M`).
161
+
162
+ Parameters
163
+ ----------
164
+ bh : SchwarzschildBlackHole
165
+ The lensing black hole.
166
+ impact_parameter : float
167
+ Impact parameter :math:`b`; must exceed
168
+ ``bh.critical_impact_parameter`` for the photon to escape.
169
+ r_far : float, default=2e5
170
+ Starting (and target ending) radius, used to approximate "infinity."
171
+ dtau : float, optional
172
+ Affine-parameter step size. Defaults to ``3 * r_far / n_steps``, which
173
+ comfortably covers the round trip to and from ``r_far`` while still
174
+ resolving the near-photon-sphere region for impact parameters not
175
+ too close to critical.
176
+ n_steps : int, default=300000
177
+ Maximum integration steps.
178
+
179
+ Returns
180
+ -------
181
+ float
182
+ The deflection angle, in radians.
183
+
184
+ Examples
185
+ --------
186
+ >>> from physicskit.relativity.chapters.schwarzschild import SchwarzschildBlackHole
187
+ >>> bh = SchwarzschildBlackHole(M=1.0)
188
+ >>> delta = exact_deflection_angle(bh, impact_parameter=50.0)
189
+ >>> bool(abs(delta - bh.light_deflection_angle(50.0)) < 0.01)
190
+ True
191
+ """
192
+ if dtau is None:
193
+ dtau = 3.0 * r_far / n_steps
194
+ y0 = bh.null_geodesic_initial_state(r0=r_far, impact_parameter=impact_parameter, ingoing=True)
195
+ traj = bh.integrate_geodesic(y0, dtau=dtau, n_steps=n_steps)
196
+ return (traj["phi"][-1] - traj["phi"][0]) - np.pi