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,273 @@
|
|
|
1
|
+
"""Generic bifurcation diagrams: sweep a parameter, plot the attractor cross-section.
|
|
2
|
+
|
|
3
|
+
A bifurcation diagram sweeps one parameter of a system and, for each value,
|
|
4
|
+
plots the "surviving" long-term values of some observable after discarding
|
|
5
|
+
an initial transient: a single point for a stable fixed point, a handful of
|
|
6
|
+
points for a period-``k`` cycle, and a dense band for chaos. The core
|
|
7
|
+
:func:`bifurcation_diagram` function is deliberately sampling-agnostic (it
|
|
8
|
+
just calls a user-supplied ``sample_fn(param) -> ndarray`` for each parameter
|
|
9
|
+
value), so it works equally well for:
|
|
10
|
+
|
|
11
|
+
- **Discrete maps** (:class:`physicskit.chaos.core.base_system.DiscreteMap`), where
|
|
12
|
+
the natural "sample" is the tail of the map's own iterated trajectory --
|
|
13
|
+
see :func:`map_bifurcation_sampler`.
|
|
14
|
+
- **Continuous, periodically-forced flows**
|
|
15
|
+
(:class:`physicskit.chaos.core.base_system.DynamicalSystem`), where the natural
|
|
16
|
+
"sample" is a *stroboscopic* Poincare section: the state sampled once per
|
|
17
|
+
forcing period, which turns the continuous flow into an effective discrete
|
|
18
|
+
map -- see :func:`stroboscopic_bifurcation_sampler`.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
25
|
+
from typing import Any, cast
|
|
26
|
+
|
|
27
|
+
import matplotlib.pyplot as plt
|
|
28
|
+
import numpy as np
|
|
29
|
+
from matplotlib.axes import Axes
|
|
30
|
+
from matplotlib.figure import Figure
|
|
31
|
+
from numpy.typing import ArrayLike, NDArray
|
|
32
|
+
from scipy.integrate import solve_ivp
|
|
33
|
+
from tqdm.auto import tqdm
|
|
34
|
+
|
|
35
|
+
from physicskit.chaos.core.base_system import DiscreteMap, DynamicalSystem
|
|
36
|
+
from physicskit.chaos.visualizers import theme
|
|
37
|
+
|
|
38
|
+
#: A function mapping one parameter value to the array of long-term
|
|
39
|
+
#: "surviving" observable values sampled for that parameter.
|
|
40
|
+
SampleFunc = Callable[[float], NDArray[np.float64]]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def bifurcation_diagram(
|
|
44
|
+
param_values: ArrayLike,
|
|
45
|
+
sample_fn: SampleFunc,
|
|
46
|
+
n_jobs: int = 1,
|
|
47
|
+
show_progress: bool = False,
|
|
48
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
49
|
+
"""Generate raw bifurcation-diagram data by sweeping a parameter.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
param_values : array_like of float, shape (n_params,)
|
|
54
|
+
Parameter values to sweep.
|
|
55
|
+
sample_fn : callable
|
|
56
|
+
Function ``sample_fn(param) -> ndarray`` returning the array of
|
|
57
|
+
long-term ("surviving", post-transient) observable values for one
|
|
58
|
+
parameter value. See :func:`map_bifurcation_sampler` and
|
|
59
|
+
:func:`stroboscopic_bifurcation_sampler` for ready-made samplers.
|
|
60
|
+
n_jobs : int, default 1
|
|
61
|
+
Number of worker threads to evaluate `sample_fn` with, via
|
|
62
|
+
:class:`concurrent.futures.ThreadPoolExecutor`. Each parameter value
|
|
63
|
+
is fully independent, so this is embarrassingly parallel -- but the
|
|
64
|
+
speedup depends on `sample_fn` releasing the GIL while it runs:
|
|
65
|
+
:func:`stroboscopic_bifurcation_sampler` does (via
|
|
66
|
+
:func:`scipy.integrate.solve_ivp`), and so does
|
|
67
|
+
:func:`map_bifurcation_sampler` (the underlying Numba kernels are
|
|
68
|
+
compiled with ``nogil=True``); an arbitrary pure-Python `sample_fn`
|
|
69
|
+
will not see a speedup. ``1`` (the default) runs sequentially with no
|
|
70
|
+
thread-pool overhead.
|
|
71
|
+
show_progress : bool, default False
|
|
72
|
+
Display a `tqdm` progress bar over parameter values.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
params : ndarray of float, shape (N,)
|
|
77
|
+
Parameter value repeated once per sample.
|
|
78
|
+
samples : ndarray of float, shape (N,)
|
|
79
|
+
The concatenated samples from every parameter value, aligned with
|
|
80
|
+
`params` (``N`` is the total sample count summed across all
|
|
81
|
+
parameter values, not necessarily ``n_params`` times a fixed count).
|
|
82
|
+
"""
|
|
83
|
+
params = np.atleast_1d(np.asarray(param_values, dtype=np.float64))
|
|
84
|
+
|
|
85
|
+
if n_jobs == 1:
|
|
86
|
+
results = [sample_fn(float(p)) for p in tqdm(params, disable=not show_progress, desc="sweeping")]
|
|
87
|
+
else:
|
|
88
|
+
with ThreadPoolExecutor(max_workers=n_jobs) as pool:
|
|
89
|
+
results = list(
|
|
90
|
+
tqdm(
|
|
91
|
+
pool.map(lambda p: sample_fn(float(p)), params),
|
|
92
|
+
total=params.shape[0],
|
|
93
|
+
disable=not show_progress,
|
|
94
|
+
desc="sweeping",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
all_params = []
|
|
99
|
+
all_samples = []
|
|
100
|
+
for p, raw_samples in zip(params, results):
|
|
101
|
+
samples = np.atleast_1d(np.asarray(raw_samples, dtype=np.float64))
|
|
102
|
+
all_params.append(np.full(samples.shape[0], p, dtype=np.float64))
|
|
103
|
+
all_samples.append(samples)
|
|
104
|
+
return np.concatenate(all_params), np.concatenate(all_samples)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def map_bifurcation_sampler(
|
|
108
|
+
make_map: Callable[[float], DiscreteMap],
|
|
109
|
+
state0: ArrayLike,
|
|
110
|
+
n_transient: int,
|
|
111
|
+
n_keep: int,
|
|
112
|
+
component: int = 0,
|
|
113
|
+
) -> SampleFunc:
|
|
114
|
+
"""Build a :func:`bifurcation_diagram` sampler for a :class:`~physicskit.chaos.core.base_system.DiscreteMap`.
|
|
115
|
+
|
|
116
|
+
Parameters
|
|
117
|
+
----------
|
|
118
|
+
make_map : callable
|
|
119
|
+
Function ``make_map(param) -> DiscreteMap`` constructing a fresh map
|
|
120
|
+
instance for one parameter value.
|
|
121
|
+
state0 : array_like of float, shape (dim,)
|
|
122
|
+
Initial state to iterate from, for every parameter value.
|
|
123
|
+
n_transient : int
|
|
124
|
+
Number of initial iterations to discard.
|
|
125
|
+
n_keep : int
|
|
126
|
+
Number of iterations to keep (and return) after the transient.
|
|
127
|
+
component : int, default 0
|
|
128
|
+
Index of the state component to sample.
|
|
129
|
+
|
|
130
|
+
Returns
|
|
131
|
+
-------
|
|
132
|
+
callable
|
|
133
|
+
A function ``sample_fn(param) -> ndarray`` of shape ``(n_keep,)``,
|
|
134
|
+
suitable as the `sample_fn` argument to :func:`bifurcation_diagram`.
|
|
135
|
+
"""
|
|
136
|
+
|
|
137
|
+
def sample_fn(param: float) -> NDArray[np.float64]:
|
|
138
|
+
system = make_map(param)
|
|
139
|
+
trajectory = system.trajectory(np.asarray(state0, dtype=np.float64), n_transient + n_keep)
|
|
140
|
+
return trajectory[n_transient:, component]
|
|
141
|
+
|
|
142
|
+
return sample_fn
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def stroboscopic_bifurcation_sampler(
|
|
146
|
+
make_system: Callable[[float], DynamicalSystem],
|
|
147
|
+
state0: ArrayLike,
|
|
148
|
+
sample_period: float,
|
|
149
|
+
n_transient_periods: int,
|
|
150
|
+
n_keep_periods: int,
|
|
151
|
+
component: int = 0,
|
|
152
|
+
dt: float = 0.01,
|
|
153
|
+
rtol: float = 1e-9,
|
|
154
|
+
atol: float = 1e-11,
|
|
155
|
+
) -> SampleFunc:
|
|
156
|
+
"""Build a :func:`bifurcation_diagram` sampler for a periodically-forced continuous flow.
|
|
157
|
+
|
|
158
|
+
Integrates the system and records its state stroboscopically -- once
|
|
159
|
+
every `sample_period` (typically the forcing period of a driven
|
|
160
|
+
oscillator such as :class:`physicskit.chaos.systems.continuous.Duffing`) -- which
|
|
161
|
+
turns the continuous flow into an effective discrete map for bifurcation
|
|
162
|
+
purposes. Uses :func:`scipy.integrate.solve_ivp` against the system's
|
|
163
|
+
plain-Python :meth:`~physicskit.chaos.core.base_system.DynamicalSystem.rhs`
|
|
164
|
+
(rather than the Numba-jitted low-level integrators, which require a
|
|
165
|
+
jitted right-hand side that arbitrary systems may not expose), evaluated
|
|
166
|
+
exactly at the stroboscopic sample times.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
make_system : callable
|
|
171
|
+
Function ``make_system(param) -> DynamicalSystem`` constructing a
|
|
172
|
+
fresh system instance for one parameter value.
|
|
173
|
+
state0 : array_like of float, shape (dim,)
|
|
174
|
+
Initial state to integrate from, for every parameter value.
|
|
175
|
+
sample_period : float
|
|
176
|
+
Time between stroboscopic samples (typically the forcing period).
|
|
177
|
+
n_transient_periods : int
|
|
178
|
+
Number of initial stroboscopic samples to discard.
|
|
179
|
+
n_keep_periods : int
|
|
180
|
+
Number of stroboscopic samples to keep (and return) after the
|
|
181
|
+
transient.
|
|
182
|
+
component : int, default 0
|
|
183
|
+
Index of the state component to sample.
|
|
184
|
+
dt : float, default 0.01
|
|
185
|
+
Approximate internal integration step size passed to `solve_ivp` as
|
|
186
|
+
`max_step` (the adaptive solver still lands exactly on each
|
|
187
|
+
stroboscopic sample time via `t_eval`). A parameter sweep calls this
|
|
188
|
+
once per parameter value, so loosening `dt`, `rtol`, and `atol` can
|
|
189
|
+
matter a great deal for the total sweep's running time.
|
|
190
|
+
rtol, atol : float, default 1e-9, 1e-11
|
|
191
|
+
Relative and absolute error tolerances passed to `solve_ivp`.
|
|
192
|
+
|
|
193
|
+
Returns
|
|
194
|
+
-------
|
|
195
|
+
callable
|
|
196
|
+
A function ``sample_fn(param) -> ndarray`` of shape
|
|
197
|
+
``(n_keep_periods,)``, suitable as the `sample_fn` argument to
|
|
198
|
+
:func:`bifurcation_diagram`.
|
|
199
|
+
"""
|
|
200
|
+
n_periods = n_transient_periods + n_keep_periods
|
|
201
|
+
sample_times = sample_period * np.arange(1, n_periods + 1)
|
|
202
|
+
|
|
203
|
+
def sample_fn(param: float) -> NDArray[np.float64]:
|
|
204
|
+
system = make_system(param)
|
|
205
|
+
|
|
206
|
+
def _rhs(t: float, state: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
207
|
+
return system.rhs(state, t)
|
|
208
|
+
|
|
209
|
+
solution = solve_ivp(
|
|
210
|
+
_rhs,
|
|
211
|
+
(0.0, sample_times[-1]),
|
|
212
|
+
np.asarray(state0, dtype=np.float64),
|
|
213
|
+
t_eval=sample_times,
|
|
214
|
+
max_step=dt,
|
|
215
|
+
rtol=rtol,
|
|
216
|
+
atol=atol,
|
|
217
|
+
)
|
|
218
|
+
stroboscopic = solution.y[component]
|
|
219
|
+
return np.asarray(stroboscopic[n_transient_periods:], dtype=np.float64)
|
|
220
|
+
|
|
221
|
+
return sample_fn
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def plot_bifurcation_diagram(
|
|
225
|
+
param_values: ArrayLike,
|
|
226
|
+
sample_fn: SampleFunc,
|
|
227
|
+
ax: Axes | None = None,
|
|
228
|
+
n_jobs: int = 1,
|
|
229
|
+
show_progress: bool = False,
|
|
230
|
+
**scatter_kwargs: Any,
|
|
231
|
+
) -> tuple[Figure, Axes]:
|
|
232
|
+
"""Plot a bifurcation diagram.
|
|
233
|
+
|
|
234
|
+
Parameters
|
|
235
|
+
----------
|
|
236
|
+
param_values : array_like of float, shape (n_params,)
|
|
237
|
+
Parameter values to sweep.
|
|
238
|
+
sample_fn : callable
|
|
239
|
+
Function ``sample_fn(param) -> ndarray`` returning the long-term
|
|
240
|
+
surviving observable values for one parameter value; see
|
|
241
|
+
:func:`map_bifurcation_sampler` / :func:`stroboscopic_bifurcation_sampler`.
|
|
242
|
+
ax : matplotlib.axes.Axes, optional
|
|
243
|
+
Axes to draw into; a new figure and axes are created if omitted.
|
|
244
|
+
n_jobs : int, default 1
|
|
245
|
+
See :func:`bifurcation_diagram`.
|
|
246
|
+
show_progress : bool, default False
|
|
247
|
+
Display a `tqdm` progress bar over parameter values.
|
|
248
|
+
**scatter_kwargs
|
|
249
|
+
Additional keyword arguments forwarded to ``ax.plot``.
|
|
250
|
+
|
|
251
|
+
Returns
|
|
252
|
+
-------
|
|
253
|
+
fig : matplotlib.figure.Figure
|
|
254
|
+
ax : matplotlib.axes.Axes
|
|
255
|
+
"""
|
|
256
|
+
params, samples = bifurcation_diagram(param_values, sample_fn, n_jobs=n_jobs, show_progress=show_progress)
|
|
257
|
+
param_array = np.asarray(param_values, dtype=np.float64)
|
|
258
|
+
|
|
259
|
+
if ax is None:
|
|
260
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
261
|
+
else:
|
|
262
|
+
fig = cast(Figure, ax.figure)
|
|
263
|
+
|
|
264
|
+
kwargs: dict[str, Any] = {
|
|
265
|
+
"marker": ",",
|
|
266
|
+
"linestyle": "none",
|
|
267
|
+
"color": theme.STRUCTURE,
|
|
268
|
+
"alpha": 0.6,
|
|
269
|
+
}
|
|
270
|
+
kwargs.update(scatter_kwargs)
|
|
271
|
+
ax.plot(params, samples, **kwargs)
|
|
272
|
+
ax.set_xlim(float(np.min(param_array)), float(np.max(param_array)))
|
|
273
|
+
return fig, ax
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
"""Live trajectory-separation tracking to visually demonstrate sensitivity to
|
|
2
|
+
initial conditions (a positive largest Lyapunov exponent)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, cast
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
from matplotlib.axes import Axes
|
|
11
|
+
from matplotlib.figure import Figure
|
|
12
|
+
from numpy.typing import ArrayLike, NDArray
|
|
13
|
+
from scipy.integrate import solve_ivp
|
|
14
|
+
|
|
15
|
+
from physicskit.chaos.core.base_system import BilliardSystem, DynamicalSystem
|
|
16
|
+
from physicskit.chaos.utils.metrics import lyapunov_exponent_from_divergence
|
|
17
|
+
from physicskit.chaos.visualizers import theme
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def trajectory_divergence(
|
|
21
|
+
system: DynamicalSystem,
|
|
22
|
+
state0: ArrayLike | None = None,
|
|
23
|
+
delta_0: float = 1e-8,
|
|
24
|
+
t_max: float = 100.0,
|
|
25
|
+
n_points: int = 2000,
|
|
26
|
+
direction: ArrayLike | None = None,
|
|
27
|
+
seed: int | None = None,
|
|
28
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
29
|
+
"""Integrate two nearby trajectories and track their separation over time.
|
|
30
|
+
|
|
31
|
+
Parameters
|
|
32
|
+
----------
|
|
33
|
+
system : DynamicalSystem
|
|
34
|
+
System to integrate.
|
|
35
|
+
state0 : array_like of float, shape (dim,), optional
|
|
36
|
+
Initial state of the reference trajectory; defaults to
|
|
37
|
+
``system.initial_state()``.
|
|
38
|
+
delta_0 : float, default 1e-8
|
|
39
|
+
Initial separation between the reference and perturbed trajectories.
|
|
40
|
+
t_max : float, default 100.0
|
|
41
|
+
Total integration time.
|
|
42
|
+
n_points : int, default 2000
|
|
43
|
+
Number of evenly-spaced time points to evaluate.
|
|
44
|
+
direction : array_like of float, shape (dim,), optional
|
|
45
|
+
Direction of the initial perturbation; a random unit vector is used
|
|
46
|
+
if omitted (normalized internally regardless).
|
|
47
|
+
seed : int, optional
|
|
48
|
+
Seed for the random direction generator, for reproducibility.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
t : ndarray of float, shape (n_points,)
|
|
53
|
+
Time points.
|
|
54
|
+
delta_t : ndarray of float, shape (n_points,)
|
|
55
|
+
Euclidean separation between the two trajectories at each time in `t`.
|
|
56
|
+
"""
|
|
57
|
+
state0 = system.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
58
|
+
rng = np.random.default_rng(seed)
|
|
59
|
+
direction = rng.normal(size=system.dim) if direction is None else np.asarray(direction, dtype=np.float64)
|
|
60
|
+
direction = direction / float(np.linalg.norm(direction))
|
|
61
|
+
state0_perturbed = state0 + delta_0 * direction
|
|
62
|
+
|
|
63
|
+
t_eval = np.linspace(0.0, t_max, n_points)
|
|
64
|
+
|
|
65
|
+
def _rhs(t: float, state: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
66
|
+
return system.rhs(state, t)
|
|
67
|
+
|
|
68
|
+
sol1 = solve_ivp(_rhs, (0.0, t_max), state0, t_eval=t_eval, rtol=1e-9, atol=1e-11)
|
|
69
|
+
sol2 = solve_ivp(_rhs, (0.0, t_max), state0_perturbed, t_eval=t_eval, rtol=1e-9, atol=1e-11)
|
|
70
|
+
|
|
71
|
+
delta_t = np.asarray(np.linalg.norm(sol1.y - sol2.y, axis=0), dtype=np.float64)
|
|
72
|
+
return sol1.t, delta_t
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def plot_lyapunov_divergence(
|
|
76
|
+
system: DynamicalSystem,
|
|
77
|
+
delta_0: float = 1e-8,
|
|
78
|
+
t_max: float = 100.0,
|
|
79
|
+
n_points: int = 2000,
|
|
80
|
+
state0: ArrayLike | None = None,
|
|
81
|
+
fit_fraction: float = 0.5,
|
|
82
|
+
ax: Axes | None = None,
|
|
83
|
+
seed: int | None = None,
|
|
84
|
+
) -> tuple[Figure, Axes, float]:
|
|
85
|
+
"""Plot ``ln(delta_t / delta_0)`` vs. `t` for two nearby trajectories.
|
|
86
|
+
|
|
87
|
+
A straight, positively-sloped line demonstrates exponential divergence
|
|
88
|
+
(``lambda_max > 0``); the slope is estimated by a linear fit over the
|
|
89
|
+
first `fit_fraction` of the time range, before the separation saturates
|
|
90
|
+
at the attractor's scale.
|
|
91
|
+
|
|
92
|
+
Parameters
|
|
93
|
+
----------
|
|
94
|
+
system : DynamicalSystem
|
|
95
|
+
System to integrate.
|
|
96
|
+
delta_0 : float, default 1e-8
|
|
97
|
+
Initial separation between the reference and perturbed trajectories.
|
|
98
|
+
t_max : float, default 100.0
|
|
99
|
+
Total integration time.
|
|
100
|
+
n_points : int, default 2000
|
|
101
|
+
Number of evenly-spaced time points to evaluate.
|
|
102
|
+
state0 : array_like of float, shape (dim,), optional
|
|
103
|
+
Initial state of the reference trajectory; defaults to
|
|
104
|
+
``system.initial_state()``.
|
|
105
|
+
fit_fraction : float, default 0.5
|
|
106
|
+
Fraction of the time range (from ``t=0``) used to fit the
|
|
107
|
+
exponential-growth slope.
|
|
108
|
+
ax : matplotlib.axes.Axes, optional
|
|
109
|
+
Axes to draw into; a new figure and axes are created if omitted.
|
|
110
|
+
seed : int, optional
|
|
111
|
+
Seed for the random perturbation direction, for reproducibility.
|
|
112
|
+
|
|
113
|
+
Returns
|
|
114
|
+
-------
|
|
115
|
+
fig : matplotlib.figure.Figure
|
|
116
|
+
ax : matplotlib.axes.Axes
|
|
117
|
+
lambda_max : float
|
|
118
|
+
Estimated largest Lyapunov exponent from the fitted slope.
|
|
119
|
+
"""
|
|
120
|
+
t, delta_t = trajectory_divergence(system, state0=state0, delta_0=delta_0, t_max=t_max, n_points=n_points, seed=seed)
|
|
121
|
+
log_ratio = np.log(np.clip(delta_t, 1e-300, None) / delta_0)
|
|
122
|
+
|
|
123
|
+
n_fit = max(2, int(fit_fraction * len(t)))
|
|
124
|
+
lam = lyapunov_exponent_from_divergence(t[:n_fit], delta_t[:n_fit] / delta_0)
|
|
125
|
+
|
|
126
|
+
if ax is None:
|
|
127
|
+
fig, ax = plt.subplots(figsize=(7, 5))
|
|
128
|
+
else:
|
|
129
|
+
fig = cast(Figure, ax.figure)
|
|
130
|
+
|
|
131
|
+
ax.plot(t, log_ratio, lw=1.2, color=theme.PRIMARY, label=r"$\ln(\delta_t/\delta_0)$")
|
|
132
|
+
ax.plot(
|
|
133
|
+
t[:n_fit],
|
|
134
|
+
lam * t[:n_fit],
|
|
135
|
+
"--",
|
|
136
|
+
color=theme.MUTED,
|
|
137
|
+
label=rf"fit: $\lambda_{{max}} \approx {lam:.3f}$",
|
|
138
|
+
)
|
|
139
|
+
ax.set_xlabel("t")
|
|
140
|
+
ax.set_ylabel(r"$\ln(\delta_t / \delta_0)$")
|
|
141
|
+
ax.set_title(f"{system.__class__.__name__} trajectory divergence")
|
|
142
|
+
ax.legend()
|
|
143
|
+
return fig, ax, lam
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def trajectory_ensemble(
|
|
147
|
+
system: DynamicalSystem,
|
|
148
|
+
state0: ArrayLike | None = None,
|
|
149
|
+
n_members: int = 40,
|
|
150
|
+
spread: float = 1e-4,
|
|
151
|
+
dt: float = 0.01,
|
|
152
|
+
n_steps: int = 10000,
|
|
153
|
+
seed: int | None = None,
|
|
154
|
+
) -> NDArray[np.float64]:
|
|
155
|
+
"""Integrate an ensemble of trajectories launched from a small ball
|
|
156
|
+
around a shared initial state.
|
|
157
|
+
|
|
158
|
+
Visualizing the result (e.g. with :func:`plot_trajectory_ensemble`)
|
|
159
|
+
makes sensitivity to initial conditions directly visible: a regular
|
|
160
|
+
orbit's members stay bundled together for the whole run, while a
|
|
161
|
+
chaotic orbit's members visibly fan out, in contrast to
|
|
162
|
+
:func:`trajectory_divergence`, which only tracks a *single* pair's
|
|
163
|
+
separation as a number.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
system : DynamicalSystem
|
|
168
|
+
System to integrate.
|
|
169
|
+
state0 : array_like of float, shape (dim,), optional
|
|
170
|
+
Center of the initial ball; defaults to ``system.initial_state()``.
|
|
171
|
+
n_members : int, default 40
|
|
172
|
+
Number of trajectories in the ensemble.
|
|
173
|
+
spread : float, default 1e-4
|
|
174
|
+
Radius of the ball each member's initial state is drawn from.
|
|
175
|
+
dt : float, default 0.01
|
|
176
|
+
Integration step size.
|
|
177
|
+
n_steps : int, default 10000
|
|
178
|
+
Number of integration steps per trajectory.
|
|
179
|
+
seed : int, optional
|
|
180
|
+
Seed for the random initial-state offsets, for reproducibility.
|
|
181
|
+
|
|
182
|
+
Returns
|
|
183
|
+
-------
|
|
184
|
+
ndarray of float, shape (n_members, n_steps + 1, dim)
|
|
185
|
+
Every member's full trajectory.
|
|
186
|
+
"""
|
|
187
|
+
state0 = system.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
188
|
+
rng = np.random.default_rng(seed)
|
|
189
|
+
|
|
190
|
+
all_states = np.empty((n_members, n_steps + 1, system.dim))
|
|
191
|
+
for i in range(n_members):
|
|
192
|
+
offset = rng.normal(size=system.dim)
|
|
193
|
+
offset *= spread / float(np.linalg.norm(offset))
|
|
194
|
+
_, states = system.trajectory(state0=state0 + offset, dt=dt, n_steps=n_steps)
|
|
195
|
+
all_states[i] = states
|
|
196
|
+
return all_states
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def plot_trajectory_ensemble(
|
|
200
|
+
system: DynamicalSystem,
|
|
201
|
+
state0: ArrayLike | None = None,
|
|
202
|
+
n_members: int = 40,
|
|
203
|
+
spread: float = 1e-4,
|
|
204
|
+
dt: float = 0.01,
|
|
205
|
+
n_steps: int = 10000,
|
|
206
|
+
x_index: int = 0,
|
|
207
|
+
y_index: int = 1,
|
|
208
|
+
seed: int | None = None,
|
|
209
|
+
ax: Axes | None = None,
|
|
210
|
+
**line_kwargs: Any,
|
|
211
|
+
) -> tuple[Figure, Axes]:
|
|
212
|
+
"""Plot an ensemble of nearby trajectories overlaid on the same axes.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
system : DynamicalSystem
|
|
217
|
+
System to integrate.
|
|
218
|
+
state0 : array_like of float, shape (dim,), optional
|
|
219
|
+
Center of the initial ball; defaults to ``system.initial_state()``.
|
|
220
|
+
n_members : int, default 40
|
|
221
|
+
Number of trajectories in the ensemble.
|
|
222
|
+
spread : float, default 1e-4
|
|
223
|
+
Radius of the ball each member's initial state is drawn from.
|
|
224
|
+
dt : float, default 0.01
|
|
225
|
+
Integration step size.
|
|
226
|
+
n_steps : int, default 10000
|
|
227
|
+
Number of integration steps per trajectory.
|
|
228
|
+
x_index, y_index : int, default 0, 1
|
|
229
|
+
State indices to use as the plotted x/y coordinates.
|
|
230
|
+
seed : int, optional
|
|
231
|
+
Seed for the random initial-state offsets, for reproducibility.
|
|
232
|
+
ax : matplotlib.axes.Axes, optional
|
|
233
|
+
Axes to draw into; a new figure and axes are created if omitted.
|
|
234
|
+
**line_kwargs
|
|
235
|
+
Additional keyword arguments forwarded to every ``ax.plot`` call.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
fig : matplotlib.figure.Figure
|
|
240
|
+
ax : matplotlib.axes.Axes
|
|
241
|
+
"""
|
|
242
|
+
all_states = trajectory_ensemble(system, state0=state0, n_members=n_members, spread=spread, dt=dt, n_steps=n_steps, seed=seed)
|
|
243
|
+
|
|
244
|
+
if ax is None:
|
|
245
|
+
fig, ax = plt.subplots(figsize=(7, 6))
|
|
246
|
+
else:
|
|
247
|
+
fig = cast(Figure, ax.figure)
|
|
248
|
+
|
|
249
|
+
kwargs: dict[str, Any] = {"color": theme.PRIMARY, "lw": 0.5, "alpha": 0.5}
|
|
250
|
+
kwargs.update(line_kwargs)
|
|
251
|
+
for states in all_states:
|
|
252
|
+
ax.plot(states[:, x_index], states[:, y_index], **kwargs)
|
|
253
|
+
ax.set_title(f"{system.__class__.__name__} trajectory ensemble ({n_members} members, spread={spread:g})")
|
|
254
|
+
return fig, ax
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def billiard_trajectory_divergence(
|
|
258
|
+
billiard: BilliardSystem,
|
|
259
|
+
pos: ArrayLike,
|
|
260
|
+
vel: ArrayLike,
|
|
261
|
+
delta_0: float = 1e-8,
|
|
262
|
+
n_bounces: int = 200,
|
|
263
|
+
) -> tuple[NDArray[np.int64], NDArray[np.float64]]:
|
|
264
|
+
"""Trace two nearby billiard rays and track their bounce-by-bounce separation.
|
|
265
|
+
|
|
266
|
+
Unlike :func:`trajectory_divergence` (continuous flows, compared at
|
|
267
|
+
matched *times*), two billiard trajectories are naturally compared at
|
|
268
|
+
matched *bounce number*: the reference ray and a ray launched at a
|
|
269
|
+
slightly perturbed angle both hit the boundary `n_bounces` times, and
|
|
270
|
+
the Euclidean distance between their `k`-th hit points is tracked as `k`
|
|
271
|
+
increases.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
billiard : BilliardSystem
|
|
276
|
+
Billiard to trace both rays within.
|
|
277
|
+
pos : array_like of float, shape (2,)
|
|
278
|
+
Shared initial position ``(x, y)``; must lie in the billiard's
|
|
279
|
+
interior.
|
|
280
|
+
vel : array_like of float, shape (2,)
|
|
281
|
+
Initial velocity direction ``(vx, vy)`` of the reference ray;
|
|
282
|
+
normalized internally.
|
|
283
|
+
delta_0 : float, default 1e-8
|
|
284
|
+
Initial angular perturbation (radians) applied to `vel` to launch
|
|
285
|
+
the second ray.
|
|
286
|
+
n_bounces : int, default 200
|
|
287
|
+
Number of reflections to trace for each ray.
|
|
288
|
+
|
|
289
|
+
Returns
|
|
290
|
+
-------
|
|
291
|
+
bounce : ndarray of int, shape (n_bounces,)
|
|
292
|
+
Bounce index, ``1, 2, ..., n_bounces``.
|
|
293
|
+
separation : ndarray of float, shape (n_bounces,)
|
|
294
|
+
Euclidean distance between the two rays' hit points at each bounce.
|
|
295
|
+
"""
|
|
296
|
+
vel_ref = np.asarray(vel, dtype=np.float64)
|
|
297
|
+
vel_ref = vel_ref / np.linalg.norm(vel_ref)
|
|
298
|
+
angle = float(np.arctan2(vel_ref[1], vel_ref[0])) + delta_0
|
|
299
|
+
vel_perturbed = np.array([np.cos(angle), np.sin(angle)])
|
|
300
|
+
|
|
301
|
+
result = billiard.simulate(pos, vel_ref, n_bounces)
|
|
302
|
+
result_perturbed = billiard.simulate(pos, vel_perturbed, n_bounces)
|
|
303
|
+
|
|
304
|
+
separation = np.hypot(result["x"] - result_perturbed["x"], result["y"] - result_perturbed["y"])
|
|
305
|
+
bounce = np.arange(1, n_bounces + 1)
|
|
306
|
+
return bounce, separation
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def plot_billiard_divergence(
|
|
310
|
+
billiard: BilliardSystem,
|
|
311
|
+
pos: ArrayLike,
|
|
312
|
+
vel: ArrayLike,
|
|
313
|
+
delta_0: float = 1e-8,
|
|
314
|
+
n_bounces: int = 200,
|
|
315
|
+
fit_fraction: float = 0.5,
|
|
316
|
+
ax: Axes | None = None,
|
|
317
|
+
) -> tuple[Figure, Axes, float]:
|
|
318
|
+
"""Plot ``ln(separation / delta_0)`` vs. bounce number for two nearby billiard rays.
|
|
319
|
+
|
|
320
|
+
A straight, positively-sloped line demonstrates exponential divergence
|
|
321
|
+
per bounce; the slope is estimated by a linear fit over the first
|
|
322
|
+
`fit_fraction` of the bounces, before the separation saturates at the
|
|
323
|
+
table's scale.
|
|
324
|
+
|
|
325
|
+
Parameters
|
|
326
|
+
----------
|
|
327
|
+
billiard : BilliardSystem
|
|
328
|
+
Billiard to trace both rays within.
|
|
329
|
+
pos : array_like of float, shape (2,)
|
|
330
|
+
Shared initial position ``(x, y)``; must lie in the billiard's
|
|
331
|
+
interior.
|
|
332
|
+
vel : array_like of float, shape (2,)
|
|
333
|
+
Initial velocity direction ``(vx, vy)`` of the reference ray.
|
|
334
|
+
delta_0 : float, default 1e-8
|
|
335
|
+
Initial angular perturbation (radians) applied to `vel`.
|
|
336
|
+
n_bounces : int, default 200
|
|
337
|
+
Number of reflections to trace for each ray.
|
|
338
|
+
fit_fraction : float, default 0.5
|
|
339
|
+
Fraction of the bounces (from the first) used to fit the
|
|
340
|
+
exponential-growth slope.
|
|
341
|
+
ax : matplotlib.axes.Axes, optional
|
|
342
|
+
Axes to draw into; a new figure and axes are created if omitted.
|
|
343
|
+
|
|
344
|
+
Returns
|
|
345
|
+
-------
|
|
346
|
+
fig : matplotlib.figure.Figure
|
|
347
|
+
ax : matplotlib.axes.Axes
|
|
348
|
+
lambda_per_bounce : float
|
|
349
|
+
Estimated divergence rate, in nats per bounce.
|
|
350
|
+
"""
|
|
351
|
+
bounce, separation = billiard_trajectory_divergence(billiard, pos, vel, delta_0=delta_0, n_bounces=n_bounces)
|
|
352
|
+
log_ratio = np.log(np.clip(separation, 1e-300, None) / delta_0)
|
|
353
|
+
|
|
354
|
+
n_fit = max(2, int(fit_fraction * len(bounce)))
|
|
355
|
+
lam = lyapunov_exponent_from_divergence(bounce[:n_fit].astype(np.float64), separation[:n_fit] / delta_0)
|
|
356
|
+
|
|
357
|
+
if ax is None:
|
|
358
|
+
fig, ax = plt.subplots(figsize=(7, 5))
|
|
359
|
+
else:
|
|
360
|
+
fig = cast(Figure, ax.figure)
|
|
361
|
+
|
|
362
|
+
ax.plot(bounce, log_ratio, lw=1.2, color=theme.PRIMARY, label=r"$\ln(\delta_k/\delta_0)$")
|
|
363
|
+
ax.plot(
|
|
364
|
+
bounce[:n_fit],
|
|
365
|
+
lam * bounce[:n_fit],
|
|
366
|
+
"--",
|
|
367
|
+
color=theme.MUTED,
|
|
368
|
+
label=rf"fit: $\lambda \approx {lam:.3f}$ / bounce",
|
|
369
|
+
)
|
|
370
|
+
ax.set_xlabel("bounce number k")
|
|
371
|
+
ax.set_ylabel(r"$\ln(\delta_k / \delta_0)$")
|
|
372
|
+
ax.set_title(f"{billiard.__class__.__name__} trajectory divergence")
|
|
373
|
+
ax.legend()
|
|
374
|
+
return fig, ax, lam
|