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