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,508 @@
|
|
|
1
|
+
"""Matplotlib animation helpers.
|
|
2
|
+
|
|
3
|
+
:class:`SideBySideAnimator` is the production entry point: given a
|
|
4
|
+
:class:`physicskit.classical.core.base_system.SimulationResult`, it renders physical
|
|
5
|
+
space motion on the left panel and a live-updating phase-space
|
|
6
|
+
trajectory or energy-vs-time trace on the right, driven by a single
|
|
7
|
+
:class:`~matplotlib.animation.FuncAnimation`. Physical-space rendering
|
|
8
|
+
is necessarily system-specific (pendulum rods, lattice-chain beads, an
|
|
9
|
+
orbit, a 3D rigid body), so it is supplied via two small callbacks;
|
|
10
|
+
:func:`orbit_trace_animation` and :func:`pendulum_animation` are
|
|
11
|
+
ready-made convenience wrappers for the two most common cases.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
import matplotlib.pyplot as plt
|
|
20
|
+
import numpy as np
|
|
21
|
+
from matplotlib.animation import FuncAnimation
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"SideBySideAnimator",
|
|
25
|
+
"orbit_trace_animation",
|
|
26
|
+
"pendulum_animation",
|
|
27
|
+
"animate_elastic_pendulum",
|
|
28
|
+
"animate_rigid_body_tumble",
|
|
29
|
+
"animate_eulers_disk",
|
|
30
|
+
"animate_rattleback",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SideBySideAnimator:
|
|
35
|
+
"""Side-by-side animation: physical space (left) + phase-space or
|
|
36
|
+
energy diagnostics (right), built from a
|
|
37
|
+
:class:`~physicskit.classical.core.base_system.SimulationResult`.
|
|
38
|
+
|
|
39
|
+
Parameters
|
|
40
|
+
----------
|
|
41
|
+
result : SimulationResult
|
|
42
|
+
Output of ``system.integrate(...)``.
|
|
43
|
+
init_physical : Callable[[Axes], artists]
|
|
44
|
+
Called once with the left-panel axes; must create and return
|
|
45
|
+
whatever Matplotlib artists it will later update (a tuple/list).
|
|
46
|
+
update_physical : Callable[[artists, SimulationResult, int], artists]
|
|
47
|
+
Called every frame with the artists from ``init_physical``, the
|
|
48
|
+
full result, and the current sample index; must update the
|
|
49
|
+
artists in place and return them.
|
|
50
|
+
mode : {"phase", "energy"}
|
|
51
|
+
Right-panel content: a growing (q, p) trace for one degree of
|
|
52
|
+
freedom, or a growing energy(t) trace.
|
|
53
|
+
dof_index : int
|
|
54
|
+
Which column of ``result.q``/``result.p`` to plot when mode="phase"
|
|
55
|
+
and the system has more than one degree of freedom.
|
|
56
|
+
physical_3d : bool
|
|
57
|
+
If True, the left axes are created with a 3D projection.
|
|
58
|
+
stride : int
|
|
59
|
+
Render only every ``stride``-th recorded sample (for long runs).
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(
|
|
63
|
+
self,
|
|
64
|
+
result,
|
|
65
|
+
init_physical: Callable,
|
|
66
|
+
update_physical: Callable,
|
|
67
|
+
mode: str = "phase",
|
|
68
|
+
dof_index: int = 0,
|
|
69
|
+
figsize=(11, 5),
|
|
70
|
+
physical_3d: bool = False,
|
|
71
|
+
stride: int = 1,
|
|
72
|
+
left_title: str = "Physical space",
|
|
73
|
+
):
|
|
74
|
+
if mode not in ("phase", "energy"):
|
|
75
|
+
raise ValueError("mode must be 'phase' or 'energy'")
|
|
76
|
+
self.result = result
|
|
77
|
+
self.update_physical = update_physical
|
|
78
|
+
self.mode = mode
|
|
79
|
+
self.dof_index = dof_index
|
|
80
|
+
self.stride = max(1, stride)
|
|
81
|
+
self.frame_indices = np.arange(0, len(result.t), self.stride)
|
|
82
|
+
|
|
83
|
+
self.fig = plt.figure(figsize=figsize)
|
|
84
|
+
self.ax_left = self.fig.add_subplot(1, 2, 1, projection="3d" if physical_3d else None)
|
|
85
|
+
self.ax_left.set_title(left_title)
|
|
86
|
+
self.ax_right = self.fig.add_subplot(1, 2, 2)
|
|
87
|
+
self._physical_artists = init_physical(self.ax_left)
|
|
88
|
+
self._setup_right_panel()
|
|
89
|
+
self.anim: Optional[FuncAnimation] = None
|
|
90
|
+
|
|
91
|
+
def _dof(self, arr):
|
|
92
|
+
return arr[:, self.dof_index] if arr.ndim == 2 else arr
|
|
93
|
+
|
|
94
|
+
def _setup_right_panel(self):
|
|
95
|
+
if self.mode == "phase":
|
|
96
|
+
q, p = self._dof(self.result.q), self._dof(self.result.p)
|
|
97
|
+
self.ax_right.plot(q, p, color="lightgray", linewidth=0.7)
|
|
98
|
+
self.ax_right.set_xlabel("q")
|
|
99
|
+
self.ax_right.set_ylabel("p")
|
|
100
|
+
self.ax_right.set_title("Phase space")
|
|
101
|
+
(self._right_trace,) = self.ax_right.plot([], [], color="crimson", linewidth=1.3)
|
|
102
|
+
(self._right_point,) = self.ax_right.plot([], [], "o", color="crimson")
|
|
103
|
+
else:
|
|
104
|
+
e = self.result.energy
|
|
105
|
+
self.ax_right.set_xlabel("t")
|
|
106
|
+
self.ax_right.set_ylabel("Energy")
|
|
107
|
+
self.ax_right.set_title("Energy")
|
|
108
|
+
self.ax_right.set_xlim(self.result.t[0], self.result.t[-1])
|
|
109
|
+
pad = 0.05 * (np.max(e) - np.min(e) + 1e-12)
|
|
110
|
+
self.ax_right.set_ylim(np.min(e) - pad, np.max(e) + pad)
|
|
111
|
+
(self._right_trace,) = self.ax_right.plot([], [], color="crimson")
|
|
112
|
+
|
|
113
|
+
def _update_right(self, idx: int):
|
|
114
|
+
if self.mode == "phase":
|
|
115
|
+
q, p = self._dof(self.result.q)[: idx + 1], self._dof(self.result.p)[: idx + 1]
|
|
116
|
+
self._right_trace.set_data(q, p)
|
|
117
|
+
self._right_point.set_data(q[-1:], p[-1:])
|
|
118
|
+
return [self._right_trace, self._right_point]
|
|
119
|
+
self._right_trace.set_data(self.result.t[: idx + 1], self.result.energy[: idx + 1])
|
|
120
|
+
return [self._right_trace]
|
|
121
|
+
|
|
122
|
+
def _frame(self, frame_i: int):
|
|
123
|
+
idx = self.frame_indices[frame_i]
|
|
124
|
+
left_artists = self.update_physical(self._physical_artists, self.result, idx)
|
|
125
|
+
right_artists = self._update_right(idx)
|
|
126
|
+
return list(left_artists) + list(right_artists)
|
|
127
|
+
|
|
128
|
+
def build(self, interval: int = 30, blit: bool = False) -> FuncAnimation:
|
|
129
|
+
"""Construct the underlying ``FuncAnimation``.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
interval : int
|
|
134
|
+
Delay between frames, in milliseconds.
|
|
135
|
+
blit : bool
|
|
136
|
+
Forwarded to ``FuncAnimation``.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
matplotlib.animation.FuncAnimation
|
|
141
|
+
Note for docs/gallery use: Sphinx-Gallery's animation scraper
|
|
142
|
+
only detects bare ``matplotlib.animation.Animation`` objects
|
|
143
|
+
among a script's globals, not a ``SideBySideAnimator`` wrapping
|
|
144
|
+
one. Assign *this* return value (not the ``SideBySideAnimator``
|
|
145
|
+
instance) to the variable you want rendered as an animated GIF,
|
|
146
|
+
e.g. ``anim = SideBySideAnimator(...).build()``.
|
|
147
|
+
"""
|
|
148
|
+
self.anim = FuncAnimation(self.fig, self._frame, frames=len(self.frame_indices), interval=interval, blit=blit)
|
|
149
|
+
return self.anim
|
|
150
|
+
|
|
151
|
+
def save(self, filename: str, fps: int = None, **kwargs):
|
|
152
|
+
"""Save the animation, building it first if needed.
|
|
153
|
+
|
|
154
|
+
``fps`` defaults to 30 for the common case of a string/None
|
|
155
|
+
``writer`` (where Matplotlib constructs the writer for you);
|
|
156
|
+
pass ``fps=None`` explicitly (the default here) when supplying
|
|
157
|
+
an already-configured ``writer`` instance via ``kwargs`` --
|
|
158
|
+
Matplotlib rejects ``fps`` together with a writer instance,
|
|
159
|
+
since the instance already carries its own fps.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
filename : str
|
|
164
|
+
Output path.
|
|
165
|
+
fps : int, optional
|
|
166
|
+
Frame rate; see above for when to leave this ``None``.
|
|
167
|
+
**kwargs
|
|
168
|
+
Forwarded to ``FuncAnimation.save`` (e.g. ``writer=...``).
|
|
169
|
+
"""
|
|
170
|
+
if self.anim is None:
|
|
171
|
+
self.build()
|
|
172
|
+
if fps is None and "writer" not in kwargs:
|
|
173
|
+
fps = 30
|
|
174
|
+
if fps is not None:
|
|
175
|
+
kwargs["fps"] = fps
|
|
176
|
+
self.anim.save(filename, **kwargs)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def pendulum_animation(result, positions_func: Callable[[np.ndarray], np.ndarray], **kwargs) -> SideBySideAnimator:
|
|
180
|
+
"""Ready-made :class:`SideBySideAnimator` for rod-and-bob systems
|
|
181
|
+
(single or double pendulum, bead on a hoop).
|
|
182
|
+
|
|
183
|
+
``positions_func(q) -> flat array of (x, y) pairs`` maps a
|
|
184
|
+
configuration ``q`` to the pivot-to-bob chain of Cartesian points
|
|
185
|
+
to draw as connected rods (e.g. ``DoublePendulum.positions``).
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
190
|
+
Output of ``system.integrate(...)``.
|
|
191
|
+
positions_func : callable
|
|
192
|
+
``q -> flat array of (x, y) pairs``.
|
|
193
|
+
**kwargs
|
|
194
|
+
Forwarded to :class:`SideBySideAnimator`.
|
|
195
|
+
|
|
196
|
+
Returns
|
|
197
|
+
-------
|
|
198
|
+
SideBySideAnimator
|
|
199
|
+
"""
|
|
200
|
+
p0 = positions_func(result.q[0]).reshape(-1, 2)
|
|
201
|
+
reach = np.abs(p0).max() * 1.3 + 1e-6
|
|
202
|
+
|
|
203
|
+
def init_physical(ax):
|
|
204
|
+
ax.set_xlim(-reach, reach)
|
|
205
|
+
ax.set_ylim(-reach, reach)
|
|
206
|
+
ax.set_aspect("equal")
|
|
207
|
+
(rod,) = ax.plot([], [], "o-", lw=2, color="steelblue")
|
|
208
|
+
(trace,) = ax.plot([], [], lw=0.7, color="orange", alpha=0.6)
|
|
209
|
+
return rod, trace
|
|
210
|
+
|
|
211
|
+
def update_physical(artists, res, idx):
|
|
212
|
+
rod, trace = artists
|
|
213
|
+
pts = positions_func(res.q[idx]).reshape(-1, 2)
|
|
214
|
+
xs = np.concatenate([[0.0], pts[:, 0]])
|
|
215
|
+
ys = np.concatenate([[0.0], pts[:, 1]])
|
|
216
|
+
rod.set_data(xs, ys)
|
|
217
|
+
hist = np.array([positions_func(q).reshape(-1, 2)[-1] for q in res.q[: idx + 1 : max(1, idx // 200 or 1)]])
|
|
218
|
+
trace.set_data(hist[:, 0], hist[:, 1])
|
|
219
|
+
return rod, trace
|
|
220
|
+
|
|
221
|
+
return SideBySideAnimator(result, init_physical, update_physical, left_title="Pendulum", **kwargs)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def orbit_trace_animation(result, **kwargs) -> SideBySideAnimator:
|
|
225
|
+
"""Ready-made :class:`SideBySideAnimator` for a 2D central-force orbit.
|
|
226
|
+
|
|
227
|
+
E.g. :class:`physicskit.classical.systems.newtonian.KeplerSystem`: a growing
|
|
228
|
+
orbital path on the left, phase/energy diagnostics on the right.
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
233
|
+
Output of ``system.integrate(...)``.
|
|
234
|
+
**kwargs
|
|
235
|
+
Forwarded to :class:`SideBySideAnimator`.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
SideBySideAnimator
|
|
240
|
+
"""
|
|
241
|
+
q = result.q
|
|
242
|
+
reach = np.abs(q).max() * 1.2 + 1e-6
|
|
243
|
+
|
|
244
|
+
def init_physical(ax):
|
|
245
|
+
ax.set_xlim(-reach, reach)
|
|
246
|
+
ax.set_ylim(-reach, reach)
|
|
247
|
+
ax.set_aspect("equal")
|
|
248
|
+
ax.plot(0, 0, "o", color="gold", markersize=10)
|
|
249
|
+
(path,) = ax.plot([], [], color="steelblue", lw=1.0)
|
|
250
|
+
(body,) = ax.plot([], [], "o", color="steelblue", markersize=6)
|
|
251
|
+
return path, body
|
|
252
|
+
|
|
253
|
+
def update_physical(artists, res, idx):
|
|
254
|
+
path, body = artists
|
|
255
|
+
path.set_data(res.q[: idx + 1, 0], res.q[: idx + 1, 1])
|
|
256
|
+
body.set_data(res.q[idx : idx + 1, 0], res.q[idx : idx + 1, 1])
|
|
257
|
+
return path, body
|
|
258
|
+
|
|
259
|
+
return SideBySideAnimator(result, init_physical, update_physical, left_title="Orbit", **kwargs)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def animate_elastic_pendulum(system, result, interval: int = 30, trail: int = 200, n_coils: int = 12, coil_width: float = 0.06) -> FuncAnimation:
|
|
263
|
+
"""Animate a spring (elastic) pendulum: a zigzag coil connecting the
|
|
264
|
+
fixed pivot to the swinging, stretching bob.
|
|
265
|
+
|
|
266
|
+
Parameters
|
|
267
|
+
----------
|
|
268
|
+
system : physicskit.classical.systems.lagrangian.ElasticPendulum
|
|
269
|
+
Pendulum the `result` came from (used for its natural length `L0`).
|
|
270
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
271
|
+
Output of ``system.integrate(...)``; ``result.q`` columns are
|
|
272
|
+
``(s, theta)``.
|
|
273
|
+
interval : int, default 30
|
|
274
|
+
Delay between animation frames, in milliseconds.
|
|
275
|
+
trail : int, default 200
|
|
276
|
+
Number of most-recent samples kept visible as a trail behind the bob.
|
|
277
|
+
n_coils : int, default 12
|
|
278
|
+
Number of zigzag half-periods drawn along the spring.
|
|
279
|
+
coil_width : float, default 0.06
|
|
280
|
+
Zigzag amplitude, as a fraction of the natural length `L0`.
|
|
281
|
+
|
|
282
|
+
Returns
|
|
283
|
+
-------
|
|
284
|
+
matplotlib.animation.FuncAnimation
|
|
285
|
+
"""
|
|
286
|
+
s, theta = result.q[:, 0], result.q[:, 1]
|
|
287
|
+
r = system.L0 + s
|
|
288
|
+
x = r * np.sin(theta)
|
|
289
|
+
y = -r * np.cos(theta)
|
|
290
|
+
|
|
291
|
+
reach = float(np.max(r)) * 1.15 + 1e-6
|
|
292
|
+
fig, ax = plt.subplots(figsize=(6, 6))
|
|
293
|
+
ax.set_xlim(-reach, reach)
|
|
294
|
+
ax.set_ylim(-reach, reach)
|
|
295
|
+
ax.set_aspect("equal")
|
|
296
|
+
ax.set_title("Elastic pendulum")
|
|
297
|
+
|
|
298
|
+
(trail_line,) = ax.plot([], [], color="orange", lw=0.8, alpha=0.7)
|
|
299
|
+
(spring,) = ax.plot([], [], "-", color="steelblue", lw=1.3)
|
|
300
|
+
(bob,) = ax.plot([], [], "o", color="crimson", markersize=10)
|
|
301
|
+
|
|
302
|
+
n_pts = 2 * n_coils + 1
|
|
303
|
+
zigzag = coil_width * system.L0 * (-1.0) ** np.arange(n_pts)
|
|
304
|
+
zigzag[0] = zigzag[-1] = 0.0
|
|
305
|
+
frac = np.linspace(0.0, 1.0, n_pts)
|
|
306
|
+
|
|
307
|
+
n = result.t.shape[0]
|
|
308
|
+
|
|
309
|
+
def update(frame: int) -> tuple:
|
|
310
|
+
lo = max(0, frame - trail)
|
|
311
|
+
trail_line.set_data(x[lo : frame + 1], y[lo : frame + 1])
|
|
312
|
+
|
|
313
|
+
rx, ry = x[frame], y[frame]
|
|
314
|
+
perp = np.array([-ry, rx]) / (r[frame] + 1e-12)
|
|
315
|
+
spring.set_data(frac * rx + perp[0] * zigzag, frac * ry + perp[1] * zigzag)
|
|
316
|
+
bob.set_data([rx], [ry])
|
|
317
|
+
return trail_line, spring, bob
|
|
318
|
+
|
|
319
|
+
anim = FuncAnimation(fig, update, frames=n, interval=interval, blit=False)
|
|
320
|
+
return anim
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def animate_rigid_body_tumble(top, result, half_extents=(1.0, 0.6, 0.3), interval: int = 30, stride: int = 1) -> FuncAnimation:
|
|
324
|
+
"""Animate an :class:`~physicskit.classical.systems.rotations.EulerTop`'s
|
|
325
|
+
principal-axis box tumbling in the lab frame -- a literal picture of
|
|
326
|
+
the Dzhanibekov effect / tennis-racket theorem.
|
|
327
|
+
|
|
328
|
+
Renders the rigid body as a simple wireframe box whose three edge
|
|
329
|
+
half-lengths are chosen only to make the three principal axes visually
|
|
330
|
+
distinguishable (they are *not* derived from the actual moments of
|
|
331
|
+
inertia, which would require a full mass distribution, not just three
|
|
332
|
+
scalars). Each frame rotates the box's body-frame vertices into the lab
|
|
333
|
+
frame with ``top.rotation_matrix(state)``, built from the integrated
|
|
334
|
+
orientation quaternion -- so a spin started dominantly about the
|
|
335
|
+
intermediate-inertia axis is seen visibly flipping end-over-end.
|
|
336
|
+
|
|
337
|
+
Parameters
|
|
338
|
+
----------
|
|
339
|
+
top : physicskit.classical.systems.rotations.EulerTop
|
|
340
|
+
The system that produced `result` (used for
|
|
341
|
+
:meth:`~physicskit.classical.systems.rotations.EulerTop.rotation_matrix`).
|
|
342
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
343
|
+
Output of ``top.integrate(...)``; ``result.y[:, :7]`` must hold
|
|
344
|
+
``(w1, w2, w3, qw, qx, qy, qz)``.
|
|
345
|
+
half_extents : tuple of float, default (1.0, 0.6, 0.3)
|
|
346
|
+
Box half-widths along the body's (1, 2, 3) principal axes.
|
|
347
|
+
interval : int, default 30
|
|
348
|
+
Delay between animation frames, in milliseconds.
|
|
349
|
+
stride : int, default 1
|
|
350
|
+
Render only every ``stride``-th recorded sample.
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
matplotlib.animation.FuncAnimation
|
|
355
|
+
"""
|
|
356
|
+
a, b, c = half_extents
|
|
357
|
+
verts_body = np.array([[sx * a, sy * b, sz * c] for sx in (-1, 1) for sy in (-1, 1) for sz in (-1, 1)])
|
|
358
|
+
edges = [(i, j) for i in range(8) for j in range(i + 1, 8) if np.sum(verts_body[i] != verts_body[j]) == 1]
|
|
359
|
+
|
|
360
|
+
fig = plt.figure(figsize=(6, 6))
|
|
361
|
+
ax = fig.add_subplot(projection="3d")
|
|
362
|
+
reach = float(np.linalg.norm(half_extents)) * 1.2
|
|
363
|
+
ax.set_xlim(-reach, reach)
|
|
364
|
+
ax.set_ylim(-reach, reach)
|
|
365
|
+
ax.set_zlim(-reach, reach)
|
|
366
|
+
ax.set_title("Rigid-body tumble (Dzhanibekov effect)")
|
|
367
|
+
|
|
368
|
+
lines = [ax.plot([], [], [], color="steelblue", lw=1.5)[0] for _ in edges]
|
|
369
|
+
|
|
370
|
+
frame_indices = np.arange(0, result.y.shape[0], stride)
|
|
371
|
+
|
|
372
|
+
def update(frame_i: int) -> tuple:
|
|
373
|
+
idx = frame_indices[frame_i]
|
|
374
|
+
R = top.rotation_matrix(result.y[idx])
|
|
375
|
+
verts_world = verts_body @ R.T
|
|
376
|
+
for line, (i, j) in zip(lines, edges):
|
|
377
|
+
line.set_data_3d(
|
|
378
|
+
[verts_world[i, 0], verts_world[j, 0]],
|
|
379
|
+
[verts_world[i, 1], verts_world[j, 1]],
|
|
380
|
+
[verts_world[i, 2], verts_world[j, 2]],
|
|
381
|
+
)
|
|
382
|
+
return tuple(lines)
|
|
383
|
+
|
|
384
|
+
anim = FuncAnimation(fig, update, frames=len(frame_indices), interval=interval, blit=False)
|
|
385
|
+
return anim
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def animate_eulers_disk(system, result, radius: float = 1.0, interval: int = 30, trail: int = 400) -> FuncAnimation:
|
|
389
|
+
"""Animate an :class:`~physicskit.classical.systems.rotations.EulersDisk`'s
|
|
390
|
+
rolling contact point from above, spiraling inward and precessing
|
|
391
|
+
faster as the disk runs down.
|
|
392
|
+
|
|
393
|
+
The contact point's horizontal position is modeled as
|
|
394
|
+
``radius * sin(theta) * (cos(phi), sin(phi))`` -- as `theta` shrinks
|
|
395
|
+
toward the finite-time collapse (see
|
|
396
|
+
:class:`~physicskit.classical.systems.rotations.EulersDisk`), the spiral
|
|
397
|
+
visibly tightens toward the center while `phi` (and hence the visible
|
|
398
|
+
wobble/rattle rate) advances faster and faster.
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
system : physicskit.classical.systems.rotations.EulersDisk
|
|
403
|
+
System the `result` came from (used for `radius` only implicitly,
|
|
404
|
+
via the caller; kept for a consistent ``(system, result, ...)``
|
|
405
|
+
signature with the other animators here).
|
|
406
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
407
|
+
Output of ``system.integrate(...)``; ``result.y`` columns are
|
|
408
|
+
``(theta, phi)``.
|
|
409
|
+
radius : float, default 1.0
|
|
410
|
+
Visual disk radius (arbitrary; the model only fixes `theta`, `phi`).
|
|
411
|
+
interval : int, default 30
|
|
412
|
+
Delay between animation frames, in milliseconds.
|
|
413
|
+
trail : int, default 400
|
|
414
|
+
Number of most-recent samples kept visible as a trail.
|
|
415
|
+
|
|
416
|
+
Returns
|
|
417
|
+
-------
|
|
418
|
+
matplotlib.animation.FuncAnimation
|
|
419
|
+
"""
|
|
420
|
+
theta, phi = result.y[:, 0], result.y[:, 1]
|
|
421
|
+
contact_x = radius * np.sin(theta) * np.cos(phi)
|
|
422
|
+
contact_y = radius * np.sin(theta) * np.sin(phi)
|
|
423
|
+
|
|
424
|
+
fig, ax = plt.subplots(figsize=(6, 6))
|
|
425
|
+
reach = radius * 1.1
|
|
426
|
+
ax.set_xlim(-reach, reach)
|
|
427
|
+
ax.set_ylim(-reach, reach)
|
|
428
|
+
ax.set_aspect("equal")
|
|
429
|
+
ax.set_title("Euler's disk: contact point (top view)")
|
|
430
|
+
|
|
431
|
+
(trail_line,) = ax.plot([], [], color="steelblue", lw=0.8, alpha=0.7)
|
|
432
|
+
(point,) = ax.plot([], [], "o", color="crimson", markersize=8)
|
|
433
|
+
|
|
434
|
+
n = result.t.shape[0]
|
|
435
|
+
|
|
436
|
+
def update(frame: int) -> tuple:
|
|
437
|
+
lo = max(0, frame - trail)
|
|
438
|
+
trail_line.set_data(contact_x[lo : frame + 1], contact_y[lo : frame + 1])
|
|
439
|
+
point.set_data([contact_x[frame]], [contact_y[frame]])
|
|
440
|
+
return trail_line, point
|
|
441
|
+
|
|
442
|
+
anim = FuncAnimation(fig, update, frames=n, interval=interval, blit=False)
|
|
443
|
+
return anim
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def animate_rattleback(system, result, interval: int = 30, stride: int = 1) -> FuncAnimation:
|
|
447
|
+
"""Animate a :class:`~physicskit.classical.systems.rotations.Rattleback`'s
|
|
448
|
+
spin direction and spin-rate history side by side, so a reversal is
|
|
449
|
+
directly visible both ways: the indicator arrow visibly reversing its
|
|
450
|
+
sense of rotation, and the ``n3(t)`` trace crossing zero.
|
|
451
|
+
|
|
452
|
+
Parameters
|
|
453
|
+
----------
|
|
454
|
+
system : physicskit.classical.systems.rotations.Rattleback
|
|
455
|
+
System the `result` came from (unused beyond duck-typing
|
|
456
|
+
consistency with the other animators here).
|
|
457
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
458
|
+
Output of ``system.integrate(...)``; ``result.y[:, 2]`` is `n3`
|
|
459
|
+
(the spin-rate component).
|
|
460
|
+
interval : int, default 30
|
|
461
|
+
Delay between animation frames, in milliseconds.
|
|
462
|
+
stride : int, default 1
|
|
463
|
+
Render only every ``stride``-th recorded sample.
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
matplotlib.animation.FuncAnimation
|
|
468
|
+
"""
|
|
469
|
+
t = result.t
|
|
470
|
+
n3 = result.y[:, 2]
|
|
471
|
+
frame_indices = np.arange(0, len(t), stride)
|
|
472
|
+
|
|
473
|
+
# Cumulative rotation angle from integrating n3 dt: only its sign and
|
|
474
|
+
# rate of change carry meaning (this is a reduced toy model, not a true
|
|
475
|
+
# Euler angle), but it turns n3(t) into a visibly spinning indicator.
|
|
476
|
+
dt_arr = np.diff(t, prepend=t[0])
|
|
477
|
+
phase = np.cumsum(n3 * dt_arr)
|
|
478
|
+
|
|
479
|
+
fig, (ax_spin, ax_trace) = plt.subplots(1, 2, figsize=(11, 5))
|
|
480
|
+
|
|
481
|
+
ax_spin.set_xlim(-1.2, 1.2)
|
|
482
|
+
ax_spin.set_ylim(-1.2, 1.2)
|
|
483
|
+
ax_spin.set_aspect("equal")
|
|
484
|
+
ax_spin.set_title("Spin direction indicator")
|
|
485
|
+
ax_spin.plot([0], [0], "o", color="black", markersize=6)
|
|
486
|
+
(arrow,) = ax_spin.plot([], [], "-", color="steelblue", lw=3)
|
|
487
|
+
|
|
488
|
+
ax_trace.set_xlim(t[0], t[-1])
|
|
489
|
+
y_max = float(np.max(np.abs(n3))) + 1e-12
|
|
490
|
+
ax_trace.set_ylim(-1.05 * y_max, 1.05 * y_max)
|
|
491
|
+
ax_trace.axhline(0.0, color="gray", lw=1.0, ls="--")
|
|
492
|
+
ax_trace.set_xlabel("t")
|
|
493
|
+
ax_trace.set_ylabel("n3 (spin rate)")
|
|
494
|
+
ax_trace.set_title("Spin rate over time")
|
|
495
|
+
(trace,) = ax_trace.plot([], [], color="crimson", lw=1.2)
|
|
496
|
+
(point,) = ax_trace.plot([], [], "o", color="crimson")
|
|
497
|
+
|
|
498
|
+
def update(frame_i: int) -> tuple:
|
|
499
|
+
idx = frame_indices[frame_i]
|
|
500
|
+
ang = phase[idx]
|
|
501
|
+
arrow.set_data([0.0, np.cos(ang)], [0.0, np.sin(ang)])
|
|
502
|
+
arrow.set_color("steelblue" if n3[idx] >= 0 else "crimson")
|
|
503
|
+
trace.set_data(t[: idx + 1], n3[: idx + 1])
|
|
504
|
+
point.set_data([t[idx]], [n3[idx]])
|
|
505
|
+
return arrow, trace, point
|
|
506
|
+
|
|
507
|
+
anim = FuncAnimation(fig, update, frames=len(frame_indices), interval=interval, blit=False)
|
|
508
|
+
return anim
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""Interactive Plotly visualizers.
|
|
2
|
+
|
|
3
|
+
The Matplotlib-based plots in :mod:`physicskit.classical.visualizers.phase_space` are
|
|
4
|
+
static and best suited to documentation/reports. For exploratory work
|
|
5
|
+
-- rotating a rigid body's momentum trajectory around to see how it
|
|
6
|
+
sits on the Casimir sphere, or zooming into a precessing orbit's
|
|
7
|
+
perihelion -- an interactive, mouse-drag-and-zoom view is much more
|
|
8
|
+
useful, which is exactly what Plotly is for.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import plotly.graph_objects as go
|
|
15
|
+
|
|
16
|
+
__all__ = ["interactive_so3_momentum_sphere", "interactive_orbit"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def interactive_so3_momentum_sphere(omega_trajectory: np.ndarray, I1: float, I2: float, I3: float, n_grid: int = 40, title: str = None) -> go.Figure:
|
|
20
|
+
"""Interactive 3D Plotly view of a rigid body's body-frame angular
|
|
21
|
+
momentum L(t) = (I1*w1, I2*w2, I3*w3) on the Casimir sphere
|
|
22
|
+
``|L| = |L(0)|``, draggable/zoomable in a way the static Matplotlib
|
|
23
|
+
version (:func:`physicskit.classical.visualizers.phase_space.plot_so3_momentum_sphere`)
|
|
24
|
+
cannot be -- useful for directly inspecting how close a trajectory
|
|
25
|
+
passes to the unstable intermediate-axis pole.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
omega_trajectory : array, shape (n_steps, 3)
|
|
30
|
+
Body-frame angular velocity trajectory, e.g.
|
|
31
|
+
``result.y[:, :3]`` from integrating an
|
|
32
|
+
:class:`~physicskit.classical.systems.rotations.EulerTop`.
|
|
33
|
+
I1, I2, I3 : float
|
|
34
|
+
Principal moments of inertia.
|
|
35
|
+
n_grid : int
|
|
36
|
+
Resolution of the wireframe sphere.
|
|
37
|
+
title : str, optional
|
|
38
|
+
Plot title.
|
|
39
|
+
|
|
40
|
+
Returns
|
|
41
|
+
-------
|
|
42
|
+
plotly.graph_objects.Figure
|
|
43
|
+
"""
|
|
44
|
+
omega_trajectory = np.asarray(omega_trajectory)
|
|
45
|
+
L = np.column_stack([I1 * omega_trajectory[:, 0], I2 * omega_trajectory[:, 1], I3 * omega_trajectory[:, 2]])
|
|
46
|
+
radius = np.linalg.norm(L[0])
|
|
47
|
+
|
|
48
|
+
u = np.linspace(0, 2 * np.pi, n_grid)
|
|
49
|
+
v = np.linspace(0, np.pi, n_grid)
|
|
50
|
+
xs = radius * np.outer(np.cos(u), np.sin(v))
|
|
51
|
+
ys = radius * np.outer(np.sin(u), np.sin(v))
|
|
52
|
+
zs = radius * np.outer(np.ones_like(u), np.cos(v))
|
|
53
|
+
|
|
54
|
+
fig = go.Figure()
|
|
55
|
+
fig.add_surface(x=xs, y=ys, z=zs, opacity=0.25, showscale=False, colorscale=[[0, "lightgray"], [1, "lightgray"]], name="Casimir sphere")
|
|
56
|
+
fig.add_scatter3d(x=L[:, 0], y=L[:, 1], z=L[:, 2], mode="lines", line=dict(color="crimson", width=4), name="L(t)")
|
|
57
|
+
fig.add_scatter3d(x=[L[0, 0]], y=[L[0, 1]], z=[L[0, 2]], mode="markers", marker=dict(color="green", size=5), name="start")
|
|
58
|
+
fig.update_layout(
|
|
59
|
+
title=title or "Body-frame angular momentum on the Casimir sphere",
|
|
60
|
+
scene=dict(xaxis_title="L1", yaxis_title="L2", zaxis_title="L3", aspectmode="data"),
|
|
61
|
+
margin=dict(l=0, r=0, t=40, b=0),
|
|
62
|
+
)
|
|
63
|
+
return fig
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def interactive_orbit(result, system=None, title: str = None) -> go.Figure:
|
|
67
|
+
"""Interactive 2D Plotly view of a planar central-force orbit (e.g.
|
|
68
|
+
from :class:`~physicskit.classical.systems.newtonian.KeplerSystem`), with the
|
|
69
|
+
Laplace-Runge-Lenz vector overlaid at start and end if ``system``
|
|
70
|
+
exposes ``lrl_vector`` -- draggable/zoomable so a slow apsidal
|
|
71
|
+
precession is easy to inspect directly, unlike a static plot.
|
|
72
|
+
|
|
73
|
+
Parameters
|
|
74
|
+
----------
|
|
75
|
+
result : physicskit.classical.core.base_system.SimulationResult
|
|
76
|
+
Output of ``system.integrate(...)``.
|
|
77
|
+
system : object, optional
|
|
78
|
+
The system the result came from; if it defines
|
|
79
|
+
``lrl_vector(q, p) -> array``, the LRL direction at the start
|
|
80
|
+
and end of the trajectory is drawn as an arrow from the origin.
|
|
81
|
+
title : str, optional
|
|
82
|
+
Plot title.
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
plotly.graph_objects.Figure
|
|
87
|
+
"""
|
|
88
|
+
q = result.q
|
|
89
|
+
fig = go.Figure()
|
|
90
|
+
fig.add_scatter(x=q[:, 0], y=q[:, 1], mode="lines", line=dict(color="steelblue", width=1.5), name="orbit")
|
|
91
|
+
fig.add_scatter(x=[0], y=[0], mode="markers", marker=dict(color="gold", size=12), name="focus")
|
|
92
|
+
|
|
93
|
+
if system is not None and hasattr(system, "lrl_vector"):
|
|
94
|
+
for idx, (label, color) in ((0, ("LRL(0)", "green")), (-1, ("LRL(end)", "crimson"))):
|
|
95
|
+
vec = system.lrl_vector(q[idx], result.p[idx])
|
|
96
|
+
scale = np.max(np.abs(q)) / (np.linalg.norm(vec) + 1e-300) * 0.6
|
|
97
|
+
fig.add_scatter(
|
|
98
|
+
x=[0, vec[0] * scale],
|
|
99
|
+
y=[0, vec[1] * scale],
|
|
100
|
+
mode="lines+markers",
|
|
101
|
+
line=dict(color=color, width=2, dash="dash"),
|
|
102
|
+
marker=dict(size=[0, 6]),
|
|
103
|
+
name=label,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
fig.update_layout(
|
|
107
|
+
title=title or "Orbit",
|
|
108
|
+
xaxis_title="x",
|
|
109
|
+
yaxis_title="y",
|
|
110
|
+
yaxis=dict(scaleanchor="x", scaleratio=1),
|
|
111
|
+
margin=dict(l=0, r=0, t=40, b=0),
|
|
112
|
+
)
|
|
113
|
+
return fig
|