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.
- physicskit/__init__.py +51 -0
- physicskit/astro/__init__.py +66 -0
- physicskit/astro/cosmic_web.py +395 -0
- physicskit/astro/galactic_dynamics.py +137 -0
- physicskit/astro/nbody.py +208 -0
- physicskit/astro/orbital_mechanics.py +220 -0
- physicskit/astro/stellar_dynamo.py +628 -0
- physicskit/astro/stellar_structure.py +198 -0
- physicskit/astro/tests/__init__.py +0 -0
- physicskit/astro/tests/conftest.py +7 -0
- physicskit/astro/tests/test_cosmic_web.py +159 -0
- physicskit/astro/tests/test_galactic_dynamics.py +46 -0
- physicskit/astro/tests/test_nbody.py +81 -0
- physicskit/astro/tests/test_orbital_mechanics.py +73 -0
- physicskit/astro/tests/test_stellar_dynamo.py +198 -0
- physicskit/astro/tests/test_stellar_structure.py +52 -0
- physicskit/astro/tests/test_visualizers.py +159 -0
- physicskit/astro/visualizers.py +628 -0
- physicskit/chaos/__init__.py +55 -0
- physicskit/chaos/core/__init__.py +3 -0
- physicskit/chaos/core/base_system.py +342 -0
- physicskit/chaos/core/integrators.py +40 -0
- physicskit/chaos/exceptions.py +24 -0
- physicskit/chaos/quantum/__init__.py +38 -0
- physicskit/chaos/quantum/billiards.py +274 -0
- physicskit/chaos/quantum/husimi.py +85 -0
- physicskit/chaos/quantum/maps.py +366 -0
- physicskit/chaos/systems/__init__.py +38 -0
- physicskit/chaos/systems/billiards.py +1131 -0
- physicskit/chaos/systems/continuous.py +1276 -0
- physicskit/chaos/systems/maps.py +557 -0
- physicskit/chaos/tests/__init__.py +0 -0
- physicskit/chaos/tests/test_basins.py +76 -0
- physicskit/chaos/tests/test_bifurcation.py +132 -0
- physicskit/chaos/tests/test_billiards.py +115 -0
- physicskit/chaos/tests/test_continuous.py +224 -0
- physicskit/chaos/tests/test_dimension.py +69 -0
- physicskit/chaos/tests/test_divergence.py +90 -0
- physicskit/chaos/tests/test_dynamic_plots.py +269 -0
- physicskit/chaos/tests/test_integrators.py +96 -0
- physicskit/chaos/tests/test_io.py +41 -0
- physicskit/chaos/tests/test_maps.py +110 -0
- physicskit/chaos/tests/test_metrics.py +115 -0
- physicskit/chaos/tests/test_phase_space.py +73 -0
- physicskit/chaos/tests/test_properties.py +116 -0
- physicskit/chaos/tests/test_quantum_billiards.py +94 -0
- physicskit/chaos/tests/test_quantum_maps.py +144 -0
- physicskit/chaos/tests/test_recurrence.py +51 -0
- physicskit/chaos/tests/test_repr_and_api_consistency.py +134 -0
- physicskit/chaos/tests/test_section.py +49 -0
- physicskit/chaos/tests/test_spectral.py +48 -0
- physicskit/chaos/tests/test_streamlit_app.py +45 -0
- physicskit/chaos/tests/test_timeseries.py +131 -0
- physicskit/chaos/tests/test_viewer3d.py +41 -0
- physicskit/chaos/utils/__init__.py +46 -0
- physicskit/chaos/utils/dimension.py +139 -0
- physicskit/chaos/utils/io.py +95 -0
- physicskit/chaos/utils/metrics.py +304 -0
- physicskit/chaos/utils/recurrence.py +122 -0
- physicskit/chaos/utils/spectral.py +69 -0
- physicskit/chaos/utils/timeseries.py +303 -0
- physicskit/chaos/visualizers/__init__.py +85 -0
- physicskit/chaos/visualizers/basins.py +185 -0
- physicskit/chaos/visualizers/bifurcation.py +273 -0
- physicskit/chaos/visualizers/divergence.py +374 -0
- physicskit/chaos/visualizers/dynamic_plots.py +1372 -0
- physicskit/chaos/visualizers/phase_space.py +133 -0
- physicskit/chaos/visualizers/quantum.py +299 -0
- physicskit/chaos/visualizers/recurrence.py +91 -0
- physicskit/chaos/visualizers/section.py +161 -0
- physicskit/chaos/visualizers/theme.py +31 -0
- physicskit/chaos/visualizers/viewer3d.py +82 -0
- physicskit/classical/__init__.py +55 -0
- physicskit/classical/core/__init__.py +17 -0
- physicskit/classical/core/base_system.py +510 -0
- physicskit/classical/core/integrators.py +397 -0
- physicskit/classical/systems/__init__.py +32 -0
- physicskit/classical/systems/chains.py +334 -0
- physicskit/classical/systems/hamiltonian.py +215 -0
- physicskit/classical/systems/lagrangian.py +308 -0
- physicskit/classical/systems/newtonian.py +536 -0
- physicskit/classical/systems/rotations.py +614 -0
- physicskit/classical/tests/__init__.py +0 -0
- physicskit/classical/tests/conftest.py +7 -0
- physicskit/classical/tests/test_base_system.py +113 -0
- physicskit/classical/tests/test_chains.py +84 -0
- physicskit/classical/tests/test_conservation.py +244 -0
- physicskit/classical/tests/test_conservation_utils.py +47 -0
- physicskit/classical/tests/test_hamiltonian.py +44 -0
- physicskit/classical/tests/test_integrators.py +123 -0
- physicskit/classical/tests/test_lagrangian.py +69 -0
- physicskit/classical/tests/test_newtonian.py +45 -0
- physicskit/classical/tests/test_rotations.py +160 -0
- physicskit/classical/tests/test_stepsize.py +149 -0
- physicskit/classical/tests/test_symbolic.py +125 -0
- physicskit/classical/tests/test_visualizers.py +172 -0
- physicskit/classical/utils/__init__.py +20 -0
- physicskit/classical/utils/conservation.py +126 -0
- physicskit/classical/utils/stepsize.py +187 -0
- physicskit/classical/utils/symbolic.py +235 -0
- physicskit/classical/visualizers/__init__.py +25 -0
- physicskit/classical/visualizers/animations.py +508 -0
- physicskit/classical/visualizers/interactive.py +113 -0
- physicskit/classical/visualizers/modal_analysis.py +97 -0
- physicskit/classical/visualizers/phase_space.py +214 -0
- physicskit/condensed/__init__.py +122 -0
- physicskit/condensed/anderson_localization.py +155 -0
- physicskit/condensed/correlated.py +284 -0
- physicskit/condensed/ginzburg_landau.py +220 -0
- physicskit/condensed/landau_levels.py +246 -0
- physicskit/condensed/laughlin.py +229 -0
- physicskit/condensed/models.py +627 -0
- physicskit/condensed/tests/__init__.py +0 -0
- physicskit/condensed/tests/test_condensed.py +518 -0
- physicskit/condensed/tests/test_visualizers.py +87 -0
- physicskit/condensed/tight_binding.py +445 -0
- physicskit/condensed/topological_insulator_3d.py +178 -0
- physicskit/condensed/topology.py +217 -0
- physicskit/condensed/visualizers.py +296 -0
- physicskit/condensed/weyl.py +180 -0
- physicskit/constants.py +184 -0
- physicskit/fields/__init__.py +111 -0
- physicskit/fields/electrodynamics.py +671 -0
- physicskit/fields/quantum_fields.py +510 -0
- physicskit/fields/solitons.py +527 -0
- physicskit/fields/tests/__init__.py +0 -0
- physicskit/fields/tests/conftest.py +7 -0
- physicskit/fields/tests/test_field_animations.py +154 -0
- physicskit/fields/tests/test_fields.py +439 -0
- physicskit/fields/tests/test_visualizers.py +101 -0
- physicskit/fields/visualizers.py +503 -0
- physicskit/fluids/__init__.py +134 -0
- physicskit/fluids/core/__init__.py +23 -0
- physicskit/fluids/core/grid.py +165 -0
- physicskit/fluids/core/timestepping.py +231 -0
- physicskit/fluids/exceptions.py +26 -0
- physicskit/fluids/systems/__init__.py +70 -0
- physicskit/fluids/systems/compressible_flow.py +281 -0
- physicskit/fluids/systems/instabilities.py +343 -0
- physicskit/fluids/systems/navier_stokes.py +221 -0
- physicskit/fluids/systems/potential_flow.py +440 -0
- physicskit/fluids/systems/viscous_flow.py +425 -0
- physicskit/fluids/systems/vortex_dynamics.py +304 -0
- physicskit/fluids/tests/__init__.py +0 -0
- physicskit/fluids/tests/conftest.py +7 -0
- physicskit/fluids/tests/test_compressible_flow.py +66 -0
- physicskit/fluids/tests/test_instabilities.py +115 -0
- physicskit/fluids/tests/test_navier_stokes.py +67 -0
- physicskit/fluids/tests/test_potential_flow.py +92 -0
- physicskit/fluids/tests/test_static_visualizers.py +91 -0
- physicskit/fluids/tests/test_utils.py +100 -0
- physicskit/fluids/tests/test_viscous_flow.py +110 -0
- physicskit/fluids/tests/test_visualizers.py +34 -0
- physicskit/fluids/tests/test_vortex_dynamics.py +88 -0
- physicskit/fluids/utils/__init__.py +18 -0
- physicskit/fluids/utils/dimensionless.py +239 -0
- physicskit/fluids/utils/spectral_analysis.py +142 -0
- physicskit/fluids/visualizers/__init__.py +14 -0
- physicskit/fluids/visualizers/compressible.py +39 -0
- physicskit/fluids/visualizers/flow_fields.py +252 -0
- physicskit/fluids/visualizers/potential_flow.py +51 -0
- physicskit/fluids/visualizers/spectra.py +62 -0
- physicskit/fluids/visualizers/theme.py +38 -0
- physicskit/integrators/__init__.py +52 -0
- physicskit/integrators/adaptive.py +199 -0
- physicskit/integrators/fixed_step.py +360 -0
- physicskit/integrators/tests/__init__.py +0 -0
- physicskit/integrators/tests/test_integrators.py +101 -0
- physicskit/optics/__init__.py +124 -0
- physicskit/optics/_compat.py +5 -0
- physicskit/optics/gaussian.py +382 -0
- physicskit/optics/quantum_optics.py +428 -0
- physicskit/optics/ray.py +469 -0
- physicskit/optics/tests/__init__.py +0 -0
- physicskit/optics/tests/test_diffraction_animation.py +38 -0
- physicskit/optics/tests/test_gaussian.py +170 -0
- physicskit/optics/tests/test_quantum_optics.py +147 -0
- physicskit/optics/tests/test_ray.py +177 -0
- physicskit/optics/tests/test_visualizers.py +58 -0
- physicskit/optics/tests/test_wave.py +146 -0
- physicskit/optics/visualizers.py +264 -0
- physicskit/optics/wave.py +365 -0
- physicskit/particle/__init__.py +152 -0
- physicskit/particle/collider.py +382 -0
- physicskit/particle/confinement.py +125 -0
- physicskit/particle/decays.py +357 -0
- physicskit/particle/electroweak.py +300 -0
- physicskit/particle/kinematics.py +271 -0
- physicskit/particle/neutrinos.py +84 -0
- physicskit/particle/nuclear.py +100 -0
- physicskit/particle/scattering.py +159 -0
- physicskit/particle/tests/__init__.py +0 -0
- physicskit/particle/tests/conftest.py +7 -0
- physicskit/particle/tests/test_collider.py +119 -0
- physicskit/particle/tests/test_confinement.py +42 -0
- physicskit/particle/tests/test_decays.py +112 -0
- physicskit/particle/tests/test_electroweak.py +97 -0
- physicskit/particle/tests/test_kinematics.py +138 -0
- physicskit/particle/tests/test_neutrinos.py +36 -0
- physicskit/particle/tests/test_nuclear.py +45 -0
- physicskit/particle/tests/test_scattering.py +64 -0
- physicskit/particle/tests/test_visualizers.py +214 -0
- physicskit/particle/visualizers/__init__.py +38 -0
- physicskit/particle/visualizers/animations.py +705 -0
- physicskit/particle/visualizers/static.py +101 -0
- physicskit/plasma/__init__.py +176 -0
- physicskit/plasma/acceleration.py +152 -0
- physicskit/plasma/instabilities.py +417 -0
- physicskit/plasma/kinetic.py +588 -0
- physicskit/plasma/mhd.py +463 -0
- physicskit/plasma/single_particle.py +562 -0
- physicskit/plasma/tests/__init__.py +0 -0
- physicskit/plasma/tests/conftest.py +7 -0
- physicskit/plasma/tests/test_new_time_domain_features.py +222 -0
- physicskit/plasma/tests/test_plasma.py +232 -0
- physicskit/plasma/tests/test_visualizers.py +79 -0
- physicskit/plasma/turbulence.py +219 -0
- physicskit/plasma/visualizers.py +868 -0
- physicskit/plasma/waves.py +549 -0
- physicskit/quantum/__init__.py +180 -0
- physicskit/quantum/_compat.py +20 -0
- physicskit/quantum/chapters/__init__.py +0 -0
- physicskit/quantum/chapters/entanglement.py +489 -0
- physicskit/quantum/chapters/harmonic_spin.py +517 -0
- physicskit/quantum/chapters/hydrogen_am.py +292 -0
- physicskit/quantum/chapters/perturbation.py +350 -0
- physicskit/quantum/chapters/potentials.py +997 -0
- physicskit/quantum/chapters/spin.py +306 -0
- physicskit/quantum/chapters/wave_packets.py +541 -0
- physicskit/quantum/core/__init__.py +0 -0
- physicskit/quantum/core/eigensolvers.py +311 -0
- physicskit/quantum/core/operators.py +296 -0
- physicskit/quantum/core/solvers.py +271 -0
- physicskit/quantum/tests/__init__.py +0 -0
- physicskit/quantum/tests/conftest.py +7 -0
- physicskit/quantum/tests/test_animations.py +225 -0
- physicskit/quantum/tests/test_compat.py +43 -0
- physicskit/quantum/tests/test_eigensolvers.py +24 -0
- physicskit/quantum/tests/test_entanglement.py +127 -0
- physicskit/quantum/tests/test_harmonic_spin.py +140 -0
- physicskit/quantum/tests/test_hydrogen_am.py +48 -0
- physicskit/quantum/tests/test_measure.py +110 -0
- physicskit/quantum/tests/test_operators.py +105 -0
- physicskit/quantum/tests/test_perturbation.py +112 -0
- physicskit/quantum/tests/test_physics_checks.py +131 -0
- physicskit/quantum/tests/test_potentials.py +191 -0
- physicskit/quantum/tests/test_solvers.py +70 -0
- physicskit/quantum/tests/test_spin.py +22 -0
- physicskit/quantum/tests/test_unitarity.py +84 -0
- physicskit/quantum/tests/test_wave_packets.py +53 -0
- physicskit/quantum/utils/__init__.py +0 -0
- physicskit/quantum/utils/measure.py +253 -0
- physicskit/quantum/visualizers/__init__.py +0 -0
- physicskit/quantum/visualizers/bloch_sphere.py +205 -0
- physicskit/quantum/visualizers/entanglement.py +58 -0
- physicskit/quantum/visualizers/orbitals.py +187 -0
- physicskit/quantum/visualizers/phase_space.py +179 -0
- physicskit/quantum/visualizers/wavefunctions.py +230 -0
- physicskit/relativity/__init__.py +61 -0
- physicskit/relativity/chapters/__init__.py +33 -0
- physicskit/relativity/chapters/cosmology.py +190 -0
- physicskit/relativity/chapters/gw_merger.py +399 -0
- physicskit/relativity/chapters/kerr.py +371 -0
- physicskit/relativity/chapters/lensing.py +196 -0
- physicskit/relativity/chapters/neutron_star.py +149 -0
- physicskit/relativity/chapters/schwarzschild.py +412 -0
- physicskit/relativity/chapters/timekeeping.py +133 -0
- physicskit/relativity/core/__init__.py +12 -0
- physicskit/relativity/core/geodesics.py +216 -0
- physicskit/relativity/core/kerr_raytracer.py +259 -0
- physicskit/relativity/core/raytracer.py +241 -0
- physicskit/relativity/core/tensors.py +468 -0
- physicskit/relativity/tests/__init__.py +0 -0
- physicskit/relativity/tests/test_conservations.py +54 -0
- physicskit/relativity/tests/test_constants.py +34 -0
- physicskit/relativity/tests/test_cosmology.py +61 -0
- physicskit/relativity/tests/test_gw_merger.py +117 -0
- physicskit/relativity/tests/test_kerr.py +116 -0
- physicskit/relativity/tests/test_kerr_raytracer.py +103 -0
- physicskit/relativity/tests/test_lensing.py +75 -0
- physicskit/relativity/tests/test_neutron_star.py +48 -0
- physicskit/relativity/tests/test_raytracer.py +67 -0
- physicskit/relativity/tests/test_schwarzschild.py +126 -0
- physicskit/relativity/tests/test_shadow_render.py +81 -0
- physicskit/relativity/tests/test_tensors.py +86 -0
- physicskit/relativity/tests/test_timekeeping.py +51 -0
- physicskit/relativity/tests/test_visualizers_misc.py +115 -0
- physicskit/relativity/utils/__init__.py +1 -0
- physicskit/relativity/utils/constants.py +232 -0
- physicskit/relativity/visualizers/__init__.py +46 -0
- physicskit/relativity/visualizers/interactive.py +127 -0
- physicskit/relativity/visualizers/shadow_render.py +367 -0
- physicskit/relativity/visualizers/spacetime_3d.py +92 -0
- physicskit/relativity/visualizers/spacetime_diagrams.py +198 -0
- physicskit/relativity/visualizers/wave_plots.py +185 -0
- physicskit/rmt/__init__.py +8 -0
- physicskit/rmt/ensembles/__init__.py +108 -0
- physicskit/rmt/ensembles/banded.py +114 -0
- physicskit/rmt/ensembles/base.py +135 -0
- physicskit/rmt/ensembles/bdg.py +218 -0
- physicskit/rmt/ensembles/chiral.py +308 -0
- physicskit/rmt/ensembles/circular.py +104 -0
- physicskit/rmt/ensembles/crossover.py +82 -0
- physicskit/rmt/ensembles/density_matrix.py +148 -0
- physicskit/rmt/ensembles/effective_hamiltonian.py +230 -0
- physicskit/rmt/ensembles/embedded.py +207 -0
- physicskit/rmt/ensembles/gaussian.py +153 -0
- physicskit/rmt/ensembles/ginibre.py +110 -0
- physicskit/rmt/ensembles/girko.py +190 -0
- physicskit/rmt/ensembles/graph_laplacian.py +92 -0
- physicskit/rmt/ensembles/haar.py +57 -0
- physicskit/rmt/ensembles/jacobi.py +182 -0
- physicskit/rmt/ensembles/poisson.py +77 -0
- physicskit/rmt/ensembles/polynomial.py +139 -0
- physicskit/rmt/ensembles/pt_symmetric.py +120 -0
- physicskit/rmt/ensembles/single_ring.py +170 -0
- physicskit/rmt/ensembles/sparse.py +95 -0
- physicskit/rmt/ensembles/syk.py +170 -0
- physicskit/rmt/ensembles/truncated_unitary.py +80 -0
- physicskit/rmt/ensembles/universality.py +92 -0
- physicskit/rmt/ensembles/wishart.py +88 -0
- physicskit/rmt/py.typed +0 -0
- physicskit/rmt/spectrum.py +82 -0
- physicskit/rmt/stats/__init__.py +160 -0
- physicskit/rmt/stats/characteristic_polynomial.py +77 -0
- physicskit/rmt/stats/circular_law.py +54 -0
- physicskit/rmt/stats/correlations.py +162 -0
- physicskit/rmt/stats/density.py +70 -0
- physicskit/rmt/stats/entanglement.py +36 -0
- physicskit/rmt/stats/free_probability.py +202 -0
- physicskit/rmt/stats/localization.py +338 -0
- physicskit/rmt/stats/marchenko_pastur.py +55 -0
- physicskit/rmt/stats/pt_symmetric.py +265 -0
- physicskit/rmt/stats/ratios.py +110 -0
- physicskit/rmt/stats/real_ginibre.py +134 -0
- physicskit/rmt/stats/rigidity.py +272 -0
- physicskit/rmt/stats/single_ring.py +95 -0
- physicskit/rmt/stats/spacing.py +150 -0
- physicskit/rmt/stats/tracy_widom.py +175 -0
- physicskit/rmt/stats/truncated_unitary.py +40 -0
- physicskit/rmt/stats/unfolding.py +37 -0
- physicskit/rmt/stats/wachter.py +67 -0
- physicskit/rmt/tests/__init__.py +0 -0
- physicskit/rmt/tests/cache_utils.py +107 -0
- physicskit/rmt/tests/conftest.py +8 -0
- physicskit/rmt/tests/test_banded.py +86 -0
- physicskit/rmt/tests/test_base.py +29 -0
- physicskit/rmt/tests/test_bdg.py +133 -0
- physicskit/rmt/tests/test_characteristic_polynomial.py +50 -0
- physicskit/rmt/tests/test_chiral.py +273 -0
- physicskit/rmt/tests/test_circular.py +225 -0
- physicskit/rmt/tests/test_crossover.py +72 -0
- physicskit/rmt/tests/test_density.py +23 -0
- physicskit/rmt/tests/test_density_matrix.py +181 -0
- physicskit/rmt/tests/test_effective_hamiltonian.py +140 -0
- physicskit/rmt/tests/test_embedded.py +169 -0
- physicskit/rmt/tests/test_free_probability.py +81 -0
- physicskit/rmt/tests/test_gaussian_ensembles.py +22 -0
- physicskit/rmt/tests/test_gaussian_semicircle.py +127 -0
- physicskit/rmt/tests/test_ginibre_circular_law.py +131 -0
- physicskit/rmt/tests/test_girko.py +178 -0
- physicskit/rmt/tests/test_graph_laplacian.py +102 -0
- physicskit/rmt/tests/test_haar.py +118 -0
- physicskit/rmt/tests/test_jacobi_wachter.py +136 -0
- physicskit/rmt/tests/test_localization.py +240 -0
- physicskit/rmt/tests/test_poisson.py +149 -0
- physicskit/rmt/tests/test_polynomial.py +107 -0
- physicskit/rmt/tests/test_pt_symmetric.py +178 -0
- physicskit/rmt/tests/test_random_state.py +19 -0
- physicskit/rmt/tests/test_real_ginibre.py +112 -0
- physicskit/rmt/tests/test_rigidity_universality.py +167 -0
- physicskit/rmt/tests/test_single_ring.py +132 -0
- physicskit/rmt/tests/test_spacing_and_ratios.py +122 -0
- physicskit/rmt/tests/test_sparse.py +71 -0
- physicskit/rmt/tests/test_spectrum.py +27 -0
- physicskit/rmt/tests/test_syk.py +136 -0
- physicskit/rmt/tests/test_tracy_widom.py +113 -0
- physicskit/rmt/tests/test_tridiagonal.py +41 -0
- physicskit/rmt/tests/test_truncated_unitary.py +80 -0
- physicskit/rmt/tests/test_validation_base.py +25 -0
- physicskit/rmt/tests/test_validation_misc.py +67 -0
- physicskit/rmt/tests/test_wishart_marchenko_pastur.py +120 -0
- physicskit/rmt/utils/__init__.py +0 -0
- physicskit/rmt/utils/haar.py +128 -0
- physicskit/rmt/utils/random_state.py +24 -0
- physicskit/rmt/utils/tridiagonal.py +167 -0
- physicskit/rmt/validation/__init__.py +36 -0
- physicskit/rmt/validation/base.py +126 -0
- physicskit/rmt/validation/circular_law.py +80 -0
- physicskit/rmt/validation/marchenko_pastur.py +53 -0
- physicskit/rmt/validation/ratio_distribution.py +53 -0
- physicskit/rmt/validation/real_ginibre.py +60 -0
- physicskit/rmt/validation/sine_kernel.py +57 -0
- physicskit/rmt/validation/single_ring.py +68 -0
- physicskit/rmt/validation/tracy_widom.py +89 -0
- physicskit/rmt/validation/universality.py +100 -0
- physicskit/rmt/validation/wachter.py +52 -0
- physicskit/rmt/validation/wigner_semicircle.py +40 -0
- physicskit/rmt/validation/wigner_surmise.py +58 -0
- physicskit/semiclassical/__init__.py +94 -0
- physicskit/semiclassical/_compat.py +5 -0
- physicskit/semiclassical/core/__init__.py +41 -0
- physicskit/semiclassical/core/gutzwiller.py +215 -0
- physicskit/semiclassical/core/path_integral.py +594 -0
- physicskit/semiclassical/core/propagators.py +579 -0
- physicskit/semiclassical/core/wkb.py +308 -0
- physicskit/semiclassical/systems/__init__.py +13 -0
- physicskit/semiclassical/systems/scarring.py +278 -0
- physicskit/semiclassical/tests/__init__.py +0 -0
- physicskit/semiclassical/tests/test_gutzwiller.py +35 -0
- physicskit/semiclassical/tests/test_path_integral.py +194 -0
- physicskit/semiclassical/tests/test_propagators.py +82 -0
- physicskit/semiclassical/tests/test_scarring.py +43 -0
- physicskit/semiclassical/tests/test_visualizers.py +89 -0
- physicskit/semiclassical/tests/test_wkb.py +30 -0
- physicskit/semiclassical/visualizers/__init__.py +17 -0
- physicskit/semiclassical/visualizers/gutzwiller.py +55 -0
- physicskit/semiclassical/visualizers/path_integral.py +195 -0
- physicskit/semiclassical/visualizers/propagators.py +89 -0
- physicskit/semiclassical/visualizers/scarring.py +146 -0
- physicskit/semiclassical/visualizers/wkb.py +55 -0
- physicskit/statphys/__init__.py +80 -0
- physicskit/statphys/chapters/__init__.py +43 -0
- physicskit/statphys/chapters/ehrenfest_urn.py +112 -0
- physicskit/statphys/chapters/ising_lattice.py +431 -0
- physicskit/statphys/chapters/kpz_growth.py +129 -0
- physicskit/statphys/chapters/molecular_dynamics.py +258 -0
- physicskit/statphys/chapters/nonequilibrium_work.py +146 -0
- physicskit/statphys/chapters/percolation.py +305 -0
- physicskit/statphys/chapters/random_walk.py +142 -0
- physicskit/statphys/chapters/renormalization.py +148 -0
- physicskit/statphys/chapters/sandpile.py +109 -0
- physicskit/statphys/chapters/spin_glass.py +275 -0
- physicskit/statphys/core/__init__.py +10 -0
- physicskit/statphys/core/kpz_engine.py +53 -0
- physicskit/statphys/core/md_engine.py +157 -0
- physicskit/statphys/core/monte_carlo.py +462 -0
- physicskit/statphys/core/random_walk_engine.py +68 -0
- physicskit/statphys/core/sandpile_engine.py +52 -0
- physicskit/statphys/tests/__init__.py +0 -0
- physicskit/statphys/tests/test_dynamics.py +66 -0
- physicskit/statphys/tests/test_ehrenfest_urn.py +44 -0
- physicskit/statphys/tests/test_finite_size_scaling.py +62 -0
- physicskit/statphys/tests/test_interactive.py +33 -0
- physicskit/statphys/tests/test_ising.py +68 -0
- physicskit/statphys/tests/test_kpz.py +55 -0
- physicskit/statphys/tests/test_landau_theory.py +49 -0
- physicskit/statphys/tests/test_md_engine.py +31 -0
- physicskit/statphys/tests/test_molecular_dynamics.py +72 -0
- physicskit/statphys/tests/test_nonequilibrium_work.py +33 -0
- physicskit/statphys/tests/test_partition_function.py +26 -0
- physicskit/statphys/tests/test_percolation.py +116 -0
- physicskit/statphys/tests/test_potts_model.py +46 -0
- physicskit/statphys/tests/test_random_walk.py +50 -0
- physicskit/statphys/tests/test_renormalization.py +44 -0
- physicskit/statphys/tests/test_sandpile.py +44 -0
- physicskit/statphys/tests/test_spin_glass.py +73 -0
- physicskit/statphys/tests/test_thermodynamics.py +121 -0
- physicskit/statphys/tests/test_xy_model.py +45 -0
- physicskit/statphys/utils/__init__.py +1 -0
- physicskit/statphys/utils/dynamics.py +100 -0
- physicskit/statphys/utils/finite_size_scaling.py +171 -0
- physicskit/statphys/utils/landau_theory.py +151 -0
- physicskit/statphys/utils/partition_function.py +109 -0
- physicskit/statphys/utils/thermodynamics.py +356 -0
- physicskit/statphys/visualizers/__init__.py +87 -0
- physicskit/statphys/visualizers/interactive.py +161 -0
- physicskit/statphys/visualizers/jarzynski_render.py +44 -0
- physicskit/statphys/visualizers/kpz_render.py +65 -0
- physicskit/statphys/visualizers/lattice_render.py +212 -0
- physicskit/statphys/visualizers/particle_render.py +121 -0
- physicskit/statphys/visualizers/random_walk_render.py +106 -0
- physicskit/statphys/visualizers/rg_render.py +45 -0
- physicskit/statphys/visualizers/sandpile_render.py +65 -0
- physicskit/statphys/visualizers/spin_glass_render.py +40 -0
- physicskit/statphys/visualizers/urn_render.py +38 -0
- physicskit/statphys/visualizers/vortex_render.py +84 -0
- physicskit/tests/__init__.py +0 -0
- physicskit/tests/test_constants.py +38 -0
- physicskit-0.1.0.dist-info/METADATA +196 -0
- physicskit-0.1.0.dist-info/RECORD +484 -0
- physicskit-0.1.0.dist-info/WHEEL +5 -0
- physicskit-0.1.0.dist-info/licenses/LICENSE +21 -0
- 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
|