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,269 @@
|
|
|
1
|
+
import matplotlib
|
|
2
|
+
|
|
3
|
+
matplotlib.use("Agg")
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pytest
|
|
7
|
+
from matplotlib.animation import PillowWriter
|
|
8
|
+
from matplotlib.collections import LineCollection
|
|
9
|
+
|
|
10
|
+
from physicskit.chaos.systems.continuous import DrivenPendulum, Lorenz, MagneticPendulum, RestrictedThreeBody
|
|
11
|
+
from physicskit.chaos.systems.maps import BakersMap
|
|
12
|
+
from physicskit.chaos.visualizers.dynamic_plots import (
|
|
13
|
+
animate_bakers_map,
|
|
14
|
+
animate_driven_pendulum,
|
|
15
|
+
animate_multi_orbit_map,
|
|
16
|
+
animate_phase_volume_contraction,
|
|
17
|
+
animate_restricted_three_body,
|
|
18
|
+
plot_colored_trajectory,
|
|
19
|
+
plotly_3d_trajectory,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_plot_colored_trajectory_returns_figure_and_axes_with_line_collection():
|
|
24
|
+
x = np.linspace(0.0, 1.0, 50)
|
|
25
|
+
y = np.sin(x * 10.0)
|
|
26
|
+
values = np.arange(50, dtype=np.float64)
|
|
27
|
+
|
|
28
|
+
_fig, ax = plot_colored_trajectory(x, y, values)
|
|
29
|
+
collections = [c for c in ax.collections if isinstance(c, LineCollection)]
|
|
30
|
+
assert len(collections) == 1
|
|
31
|
+
# One fewer segment than points (each segment joins consecutive points).
|
|
32
|
+
assert collections[0].get_segments().__len__() == 49
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_plot_colored_trajectory_sets_axis_limits_to_data_range():
|
|
36
|
+
x = np.array([0.0, 5.0, 2.0])
|
|
37
|
+
y = np.array([0.0, 1.0, -3.0])
|
|
38
|
+
values = np.array([0.0, 1.0, 2.0])
|
|
39
|
+
|
|
40
|
+
_, ax = plot_colored_trajectory(x, y, values)
|
|
41
|
+
assert ax.get_xlim() == (0.0, 5.0)
|
|
42
|
+
assert ax.get_ylim() == (-3.0, 1.0)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_plot_colored_trajectory_no_colorbar_when_disabled():
|
|
46
|
+
x = np.linspace(0.0, 1.0, 10)
|
|
47
|
+
y = np.linspace(0.0, 1.0, 10)
|
|
48
|
+
values = np.linspace(0.0, 1.0, 10)
|
|
49
|
+
|
|
50
|
+
fig, _ = plot_colored_trajectory(x, y, values, colorbar=False)
|
|
51
|
+
assert len(fig.axes) == 1 # just the main axes, no colorbar axes added
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_plotly_3d_trajectory_contains_all_points():
|
|
55
|
+
states = np.column_stack([np.linspace(0, 1, 20), np.linspace(0, 2, 20), np.linspace(0, 3, 20)])
|
|
56
|
+
fig = plotly_3d_trajectory(states)
|
|
57
|
+
trace = fig.data[0]
|
|
58
|
+
assert len(trace.x) == 20
|
|
59
|
+
np.testing.assert_allclose(trace.z, states[:, 2])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_plotly_3d_trajectory_color_by_sets_marker_color_array():
|
|
63
|
+
states = np.random.default_rng(0).uniform(size=(15, 3))
|
|
64
|
+
color_values = np.arange(15, dtype=np.float64)
|
|
65
|
+
fig = plotly_3d_trajectory(states, color_by=color_values)
|
|
66
|
+
trace = fig.data[0]
|
|
67
|
+
np.testing.assert_allclose(trace.line.color, color_values)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_animate_phase_volume_contraction_rejects_non_3d_system():
|
|
71
|
+
with pytest.raises(ValueError):
|
|
72
|
+
animate_phase_volume_contraction(MagneticPendulum())
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_animate_phase_volume_contraction_shrinks_the_cloud():
|
|
76
|
+
"""The whole point of the animation: the point cloud's volume must shrink
|
|
77
|
+
over time for a dissipative system, since that is what phase-space
|
|
78
|
+
volume contraction means geometrically. Uses a small ball and a short
|
|
79
|
+
horizon, where the linear (Jacobian-trace) contraction that
|
|
80
|
+
:func:`~physicskit.chaos.utils.metrics.phase_volume_expansion` predicts still
|
|
81
|
+
dominates -- over a long horizon the cloud instead stretches into a
|
|
82
|
+
thin sheet *along* the attractor, and simple size statistics (spread
|
|
83
|
+
along one axis, a covariance-based volume estimate) stop decreasing
|
|
84
|
+
monotonically even though it is still, in the measure-theoretic sense,
|
|
85
|
+
contracting."""
|
|
86
|
+
|
|
87
|
+
def _cloud_volume(anim, frame):
|
|
88
|
+
anim._draw_frame(frame)
|
|
89
|
+
x, y, z = anim._fig.axes[0].collections[0]._offsets3d
|
|
90
|
+
cov = np.cov(np.column_stack([x, y, z]).T)
|
|
91
|
+
return float(np.sqrt(np.linalg.det(cov)))
|
|
92
|
+
|
|
93
|
+
system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
|
|
94
|
+
anim = animate_phase_volume_contraction(
|
|
95
|
+
system,
|
|
96
|
+
n_points=200,
|
|
97
|
+
ball_radius=0.3,
|
|
98
|
+
t_max=0.5,
|
|
99
|
+
dt=0.005,
|
|
100
|
+
n_frames=10,
|
|
101
|
+
warmup_steps=200,
|
|
102
|
+
seed=0,
|
|
103
|
+
)
|
|
104
|
+
volumes = [_cloud_volume(anim, i) for i in range(10)]
|
|
105
|
+
assert all(v2 < v1 for v1, v2 in zip(volumes, volumes[1:]))
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_animate_phase_volume_contraction_respects_explicit_center_state():
|
|
109
|
+
system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
|
|
110
|
+
center = np.array([1.0, 1.0, 1.0])
|
|
111
|
+
anim = animate_phase_volume_contraction(system, center_state=center, ball_radius=0.5, n_points=50, t_max=1.0, n_frames=5)
|
|
112
|
+
anim._draw_frame(0)
|
|
113
|
+
x, y, z = anim._fig.axes[0].collections[0]._offsets3d
|
|
114
|
+
points0 = np.column_stack([x, y, z])
|
|
115
|
+
assert np.allclose(points0.mean(axis=0), center, atol=0.5)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _n_points_drawn(anim):
|
|
119
|
+
return sum(len(line.get_xdata()) for line in anim._fig.axes[0].lines)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_animate_multi_orbit_map_grows_every_orbit_together_not_one_at_a_time():
|
|
123
|
+
"""Every orbit must gain points on (almost) every frame -- not have whole
|
|
124
|
+
orbits complete one at a time -- so the picture keeps visibly changing
|
|
125
|
+
for the whole animation instead of mostly settling within a few frames."""
|
|
126
|
+
rng = np.random.default_rng(0)
|
|
127
|
+
orbits = [rng.uniform(size=(50, 2)) for _ in range(5)]
|
|
128
|
+
anim = animate_multi_orbit_map(orbits, n_frames=20)
|
|
129
|
+
|
|
130
|
+
anim._draw_frame(0)
|
|
131
|
+
counts = [_n_points_drawn(anim)]
|
|
132
|
+
for i in range(1, 20):
|
|
133
|
+
anim._draw_frame(i)
|
|
134
|
+
counts.append(_n_points_drawn(anim))
|
|
135
|
+
|
|
136
|
+
# Points accumulate monotonically and every orbit has contributed to the
|
|
137
|
+
# very first frame already (all 5 lines non-empty), not just one.
|
|
138
|
+
assert all(c2 >= c1 for c1, c2 in zip(counts, counts[1:]))
|
|
139
|
+
anim._draw_frame(0)
|
|
140
|
+
non_empty_lines_at_frame_0 = sum(1 for line in anim._fig.axes[0].lines if len(line.get_xdata()) > 0)
|
|
141
|
+
assert non_empty_lines_at_frame_0 == 5
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_animate_multi_orbit_map_reaches_full_length_on_last_frame():
|
|
145
|
+
orbits = [np.column_stack([np.arange(30), np.arange(30)]) for _ in range(3)]
|
|
146
|
+
anim = animate_multi_orbit_map(orbits, n_frames=10)
|
|
147
|
+
anim._draw_frame(9)
|
|
148
|
+
assert _n_points_drawn(anim) == 30 * 3
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def test_animate_multi_orbit_map_handles_orbits_of_different_lengths():
|
|
152
|
+
orbits = [np.zeros((10, 2)), np.zeros((30, 2))]
|
|
153
|
+
anim = animate_multi_orbit_map(orbits, n_frames=5)
|
|
154
|
+
anim._draw_frame(4)
|
|
155
|
+
lengths = [len(line.get_xdata()) for line in anim._fig.axes[0].lines]
|
|
156
|
+
assert lengths == [10, 30]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _bakers_scatter(anim):
|
|
160
|
+
return anim._fig.axes[0].collections[0]
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_animate_bakers_map_colors_left_and_right_half_differently():
|
|
164
|
+
n_points = 100
|
|
165
|
+
seed = 0
|
|
166
|
+
system = BakersMap(alpha=0.5)
|
|
167
|
+
anim = animate_bakers_map(
|
|
168
|
+
system,
|
|
169
|
+
n_points=n_points,
|
|
170
|
+
n_iterations=1,
|
|
171
|
+
frames_per_iteration=6,
|
|
172
|
+
stripe_colors=("tab:blue", "tab:red"),
|
|
173
|
+
seed=seed,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Reconstruct the same jittered starting grid `animate_bakers_map` builds
|
|
177
|
+
# internally, to know each marker's *original* x -- by frame 0 the
|
|
178
|
+
# stretch phase has already moved the markers, so their current offsets
|
|
179
|
+
# no longer reflect the left/right split the coloring is based on.
|
|
180
|
+
n_side = round(np.sqrt(n_points))
|
|
181
|
+
centers = (np.arange(n_side) + 0.5) / n_side
|
|
182
|
+
grid_x, _ = np.meshgrid(centers, centers)
|
|
183
|
+
rng = np.random.default_rng(seed)
|
|
184
|
+
jitter = rng.uniform(-0.15, 0.15, size=(2,) + grid_x.shape) / n_side
|
|
185
|
+
x0 = (grid_x + jitter[0]).ravel()
|
|
186
|
+
|
|
187
|
+
anim._draw_frame(0)
|
|
188
|
+
facecolors = _bakers_scatter(anim).get_facecolor()
|
|
189
|
+
unique_colors = {tuple(c) for c in facecolors}
|
|
190
|
+
assert len(unique_colors) == 2
|
|
191
|
+
# Color must actually track the left/right split, not just happen to
|
|
192
|
+
# take two values: every left-of-center point shares one color and every
|
|
193
|
+
# right-of-center point shares the other.
|
|
194
|
+
left_colors = {tuple(c) for c, x in zip(facecolors, x0) if x < 0.5}
|
|
195
|
+
right_colors = {tuple(c) for c, x in zip(facecolors, x0) if x >= 0.5}
|
|
196
|
+
assert len(left_colors) == 1
|
|
197
|
+
assert len(right_colors) == 1
|
|
198
|
+
assert left_colors != right_colors
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_animate_bakers_map_stretch_phase_extends_past_unit_square():
|
|
202
|
+
"""During the stretch phase the cloud should spill out past x = 1, since
|
|
203
|
+
the right-hand branch is drawn stretched into its own, offset slot
|
|
204
|
+
rather than snapping straight to its final (folded-back) position."""
|
|
205
|
+
system = BakersMap(alpha=0.5)
|
|
206
|
+
anim = animate_bakers_map(system, n_points=400, n_iterations=1, frames_per_iteration=9)
|
|
207
|
+
for i in range(4): # sequentially through frame 3 (mid-stretch, before the cut/pause frame)
|
|
208
|
+
anim._draw_frame(i)
|
|
209
|
+
x = _bakers_scatter(anim).get_offsets()[:, 0]
|
|
210
|
+
assert x.max() > 1.0
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def test_animate_bakers_map_returns_to_unit_square_after_full_iteration():
|
|
214
|
+
"""At the last frame of an iteration, every point must land exactly on
|
|
215
|
+
the analytic baker's map image of its starting point, back inside the
|
|
216
|
+
unit square (no residual stretch/offset left over)."""
|
|
217
|
+
alpha = 0.5
|
|
218
|
+
system = BakersMap(alpha=alpha)
|
|
219
|
+
n_points = 100
|
|
220
|
+
seed = 0
|
|
221
|
+
anim = animate_bakers_map(system, n_points=n_points, n_iterations=1, frames_per_iteration=6, seed=seed)
|
|
222
|
+
|
|
223
|
+
# Reconstruct the same jittered starting grid `animate_bakers_map` builds
|
|
224
|
+
# internally (a perfectly regular grid sits at exact dyadic rationals,
|
|
225
|
+
# which this map's doubling action on `x` would otherwise degenerate
|
|
226
|
+
# within a handful of iterations).
|
|
227
|
+
n_side = round(np.sqrt(n_points))
|
|
228
|
+
centers = (np.arange(n_side) + 0.5) / n_side
|
|
229
|
+
grid_x, grid_y = np.meshgrid(centers, centers)
|
|
230
|
+
rng = np.random.default_rng(seed)
|
|
231
|
+
jitter = rng.uniform(-0.15, 0.15, size=(2,) + grid_x.shape) / n_side
|
|
232
|
+
x0 = np.clip(grid_x + jitter[0], 0.0, 1.0 - 1e-12).ravel()
|
|
233
|
+
y0 = np.clip(grid_y + jitter[1], 0.0, 1.0 - 1e-12).ravel()
|
|
234
|
+
left = x0 < alpha
|
|
235
|
+
x_expected = np.where(left, x0 / alpha, (x0 - alpha) / (1.0 - alpha))
|
|
236
|
+
y_expected = np.where(left, alpha * y0, alpha + (1.0 - alpha) * y0)
|
|
237
|
+
|
|
238
|
+
for i in range(6): # sequentially through the last frame of the (only) iteration
|
|
239
|
+
anim._draw_frame(i)
|
|
240
|
+
offsets = _bakers_scatter(anim).get_offsets()
|
|
241
|
+
np.testing.assert_allclose(np.sort(offsets[:, 0]), np.sort(x_expected), atol=1e-10)
|
|
242
|
+
np.testing.assert_allclose(np.sort(offsets[:, 1]), np.sort(y_expected), atol=1e-10)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def test_animate_bakers_map_uses_square_markers():
|
|
246
|
+
system = BakersMap(alpha=0.5)
|
|
247
|
+
anim = animate_bakers_map(system, n_points=100, n_iterations=1, frames_per_iteration=6)
|
|
248
|
+
paths = _bakers_scatter(anim).get_paths()
|
|
249
|
+
assert len(paths) == 1 # scatter shares one path (the marker shape) across all points
|
|
250
|
+
# A square marker path has 4 (or 5, if closed) vertices.
|
|
251
|
+
assert len(paths[0].vertices) in (4, 5)
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
@pytest.mark.slow
|
|
255
|
+
def test_animate_restricted_three_body_saves_gif(tmp_path):
|
|
256
|
+
system = RestrictedThreeBody()
|
|
257
|
+
anim = animate_restricted_three_body(system, dt=0.001, n_steps=800, skip=20)
|
|
258
|
+
out = tmp_path / "cr3bp.gif"
|
|
259
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
260
|
+
assert out.exists() and out.stat().st_size > 0
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@pytest.mark.slow
|
|
264
|
+
def test_animate_driven_pendulum_saves_gif(tmp_path):
|
|
265
|
+
system = DrivenPendulum()
|
|
266
|
+
anim = animate_driven_pendulum(system, dt=0.02, n_steps=60)
|
|
267
|
+
out = tmp_path / "driven_pendulum.gif"
|
|
268
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
269
|
+
assert out.exists() and out.stat().st_size > 0
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numba import njit
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.core.integrators import leapfrog_integrate, rk4_integrate, yoshida4_integrate
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@njit
|
|
8
|
+
def _harmonic_force(pos, t, params):
|
|
9
|
+
"""Restoring force for a simple harmonic oscillator, for integrator tests."""
|
|
10
|
+
omega = params[0]
|
|
11
|
+
return -(omega**2) * pos
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@njit
|
|
15
|
+
def _harmonic_rhs(state, t, params):
|
|
16
|
+
"""RK4-form right-hand side for the same simple harmonic oscillator."""
|
|
17
|
+
omega = params[0]
|
|
18
|
+
out = np.empty(2)
|
|
19
|
+
out[0] = state[1]
|
|
20
|
+
out[1] = -(omega**2) * state[0]
|
|
21
|
+
return out
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _harmonic_energy(pos, vel, omega):
|
|
25
|
+
return 0.5 * vel**2 + 0.5 * omega**2 * pos**2
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_yoshida4_conserves_energy_better_than_leapfrog():
|
|
29
|
+
"""The 4th-order Yoshida integrator should have much smaller energy drift
|
|
30
|
+
than plain (2nd-order) leapfrog at the same step size, since it is built
|
|
31
|
+
from three leapfrog sub-steps composed to raise the order of accuracy."""
|
|
32
|
+
omega = 2.0
|
|
33
|
+
params = np.array([omega])
|
|
34
|
+
pos0, vel0 = np.array([1.0]), np.array([0.0])
|
|
35
|
+
dt, n_steps = 0.05, 5000
|
|
36
|
+
|
|
37
|
+
_, pos_lf, vel_lf = leapfrog_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
|
|
38
|
+
energy_lf = _harmonic_energy(pos_lf[:, 0], vel_lf[:, 0], omega)
|
|
39
|
+
drift_lf = np.max(np.abs(energy_lf - energy_lf[0]) / energy_lf[0])
|
|
40
|
+
|
|
41
|
+
_, pos_y4, vel_y4 = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
|
|
42
|
+
energy_y4 = _harmonic_energy(pos_y4[:, 0], vel_y4[:, 0], omega)
|
|
43
|
+
drift_y4 = np.max(np.abs(energy_y4 - energy_y4[0]) / energy_y4[0])
|
|
44
|
+
|
|
45
|
+
assert drift_y4 < drift_lf
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_yoshida4_energy_drift_does_not_grow_with_integration_time():
|
|
49
|
+
"""Being symplectic, Yoshida4's energy drift should stay bounded as the
|
|
50
|
+
integration horizon grows, unlike RK4's secularly-growing drift."""
|
|
51
|
+
omega = 2.0
|
|
52
|
+
params = np.array([omega])
|
|
53
|
+
pos0, vel0 = np.array([1.0]), np.array([0.0])
|
|
54
|
+
dt = 0.05
|
|
55
|
+
|
|
56
|
+
drifts = []
|
|
57
|
+
for n_steps in (2000, 20000, 200000):
|
|
58
|
+
_, pos, vel = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
|
|
59
|
+
energy = _harmonic_energy(pos[:, 0], vel[:, 0], omega)
|
|
60
|
+
drifts.append(np.max(np.abs(energy - energy[0]) / energy[0]))
|
|
61
|
+
|
|
62
|
+
# All three drifts should be the same order of magnitude (bounded), not
|
|
63
|
+
# growing proportionally with the 100x increase in integration horizon.
|
|
64
|
+
assert max(drifts) / min(drifts) < 10.0
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_rk4_energy_drift_grows_with_integration_time():
|
|
68
|
+
"""The non-symplectic RK4 integrator's energy drift grows with
|
|
69
|
+
integration horizon, in contrast to the symplectic integrators -- this is
|
|
70
|
+
exactly the failure mode the symplectic integrators exist to avoid."""
|
|
71
|
+
omega = 2.0
|
|
72
|
+
params = np.array([omega])
|
|
73
|
+
state0 = np.array([1.0, 0.0])
|
|
74
|
+
dt = 0.05
|
|
75
|
+
|
|
76
|
+
drifts = []
|
|
77
|
+
for n_steps in (2000, 200000):
|
|
78
|
+
_, states = rk4_integrate(_harmonic_rhs, state0, 0.0, dt, n_steps, params)
|
|
79
|
+
energy = _harmonic_energy(states[:, 0], states[:, 1], omega)
|
|
80
|
+
drifts.append(np.max(np.abs(energy - energy[0]) / energy[0]))
|
|
81
|
+
|
|
82
|
+
assert drifts[1] > 5.0 * drifts[0]
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_yoshida4_matches_leapfrog_for_a_single_leapfrog_substep_limit():
|
|
86
|
+
"""Sanity check: with a tiny step size, both integrators should agree
|
|
87
|
+
closely on the trajectory of a simple, smooth system."""
|
|
88
|
+
omega = 1.0
|
|
89
|
+
params = np.array([omega])
|
|
90
|
+
pos0, vel0 = np.array([1.0]), np.array([0.0])
|
|
91
|
+
dt, n_steps = 1e-4, 100
|
|
92
|
+
|
|
93
|
+
_, pos_lf, _ = leapfrog_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
|
|
94
|
+
_, pos_y4, _ = yoshida4_integrate(_harmonic_force, pos0, vel0, 0.0, dt, n_steps, params)
|
|
95
|
+
|
|
96
|
+
np.testing.assert_allclose(pos_lf, pos_y4, atol=1e-8)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from physicskit.chaos.systems.billiards import SinaiBilliard
|
|
4
|
+
from physicskit.chaos.systems.continuous import Lorenz
|
|
5
|
+
from physicskit.chaos.utils.io import load_arrays, load_system_config, save_arrays, save_system_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_save_and_load_system_config_roundtrips_scalar_params(tmp_path):
|
|
9
|
+
system = Lorenz(sigma=11.0, rho=27.5, beta=8.0 / 3.0)
|
|
10
|
+
path = tmp_path / "lorenz.json"
|
|
11
|
+
save_system_config(system, path)
|
|
12
|
+
restored = load_system_config(path)
|
|
13
|
+
|
|
14
|
+
assert type(restored) is Lorenz
|
|
15
|
+
assert restored.sigma == system.sigma
|
|
16
|
+
assert restored.rho == system.rho
|
|
17
|
+
assert restored.beta == system.beta
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_save_and_load_system_config_roundtrips_billiard_params(tmp_path):
|
|
21
|
+
billiard = SinaiBilliard(cell_size=2.5, scatterer_radius=0.7)
|
|
22
|
+
path = tmp_path / "sinai.json"
|
|
23
|
+
save_system_config(billiard, path)
|
|
24
|
+
restored = load_system_config(path)
|
|
25
|
+
|
|
26
|
+
assert type(restored) is SinaiBilliard
|
|
27
|
+
assert restored.cell_size == billiard.cell_size
|
|
28
|
+
assert restored.scatterer_radius == billiard.scatterer_radius
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_save_and_load_arrays_roundtrips_exactly(tmp_path):
|
|
32
|
+
t = np.linspace(0.0, 10.0, 50)
|
|
33
|
+
states = np.random.default_rng(0).normal(size=(50, 3))
|
|
34
|
+
path = tmp_path / "run.npz"
|
|
35
|
+
|
|
36
|
+
save_arrays(path, t=t, states=states)
|
|
37
|
+
loaded = load_arrays(path)
|
|
38
|
+
|
|
39
|
+
assert set(loaded.keys()) == {"t", "states"}
|
|
40
|
+
np.testing.assert_array_equal(loaded["t"], t)
|
|
41
|
+
np.testing.assert_array_equal(loaded["states"], states)
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.maps import BakersMap, LogisticMap
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_bakers_map_state_stays_in_unit_square():
|
|
8
|
+
"""Every iterate of the (area-preserving) baker's map must stay in [0, 1) x [0, 1)."""
|
|
9
|
+
system = BakersMap(alpha=0.5)
|
|
10
|
+
traj = system.trajectory(np.array([0.123, 0.456]), n_iter=1000)
|
|
11
|
+
assert np.all(traj >= 0.0)
|
|
12
|
+
assert np.all(traj < 1.0)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.mark.parametrize("alpha", [0.2, 0.5, 0.7])
|
|
16
|
+
def test_bakers_map_state_stays_in_unit_square_asymmetric(alpha):
|
|
17
|
+
system = BakersMap(alpha=alpha)
|
|
18
|
+
traj = system.trajectory(np.array([0.37, 0.81]), n_iter=1000)
|
|
19
|
+
assert np.all(traj >= 0.0)
|
|
20
|
+
assert np.all(traj < 1.0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_bakers_map_symmetric_lyapunov_exponents_are_log2():
|
|
24
|
+
"""For alpha=0.5, the analytic Lyapunov exponents are exactly +-ln(2)."""
|
|
25
|
+
system = BakersMap(alpha=0.5)
|
|
26
|
+
lam_plus, lam_minus = system.lyapunov_exponents()
|
|
27
|
+
assert lam_plus == pytest.approx(np.log(2.0))
|
|
28
|
+
assert lam_minus == pytest.approx(-np.log(2.0))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_bakers_map_lyapunov_exponents_are_symmetric_and_positive():
|
|
32
|
+
system = BakersMap(alpha=0.3)
|
|
33
|
+
lam_plus, lam_minus = system.lyapunov_exponents()
|
|
34
|
+
assert lam_plus > 0.0
|
|
35
|
+
assert lam_minus == pytest.approx(-lam_plus)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_bakers_map_symmetric_case_doubles_x_separation_each_step():
|
|
39
|
+
"""For alpha=0.5, two points starting in the same branch have their
|
|
40
|
+
x-separation exactly double at every step, until a branch-crossing wraps
|
|
41
|
+
one of them -- the literal "stretch" half of "stretch, cut, and stack"."""
|
|
42
|
+
system = BakersMap(alpha=0.5)
|
|
43
|
+
state_a = np.array([0.1, 0.2])
|
|
44
|
+
state_b = np.array([0.1 + 1e-6, 0.2])
|
|
45
|
+
for _ in range(10):
|
|
46
|
+
dx_before = abs(state_b[0] - state_a[0])
|
|
47
|
+
same_branch = (state_a[0] < 0.5) == (state_b[0] < 0.5)
|
|
48
|
+
state_a = system.step(state_a)
|
|
49
|
+
state_b = system.step(state_b)
|
|
50
|
+
if same_branch:
|
|
51
|
+
dx_after = abs(state_b[0] - state_a[0])
|
|
52
|
+
assert dx_after == pytest.approx(2.0 * dx_before, rel=1e-6)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_bakers_map_rejects_invalid_alpha():
|
|
56
|
+
with pytest.raises(ValueError):
|
|
57
|
+
BakersMap(alpha=0.0)
|
|
58
|
+
with pytest.raises(ValueError):
|
|
59
|
+
BakersMap(alpha=1.0)
|
|
60
|
+
with pytest.raises(ValueError):
|
|
61
|
+
BakersMap(alpha=1.5)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_logistic_map_step_matches_trajectory_first_iteration():
|
|
65
|
+
"""LogisticMap.trajectory() uses a fast, standalone numba loop rather
|
|
66
|
+
than calling .step() itself; check .step() directly gives the same
|
|
67
|
+
first iteration as trajectory() does, so the two paths can't diverge."""
|
|
68
|
+
system = LogisticMap(r=3.9)
|
|
69
|
+
state0 = np.array([0.37])
|
|
70
|
+
stepped = system.step(state0)
|
|
71
|
+
traj = system.trajectory(state0, n_iter=1)
|
|
72
|
+
assert stepped.shape == (1,)
|
|
73
|
+
np.testing.assert_allclose(stepped, traj[1])
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_logistic_map_state_stays_in_unit_interval():
|
|
77
|
+
system = LogisticMap(r=3.9)
|
|
78
|
+
traj = system.trajectory(np.array([0.37]), n_iter=2000)
|
|
79
|
+
assert traj.shape == (2001, 1)
|
|
80
|
+
assert np.all(traj >= 0.0)
|
|
81
|
+
assert np.all(traj <= 1.0)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_logistic_map_converges_to_known_fixed_point_below_r3():
|
|
85
|
+
"""For 1 < r < 3, every trajectory converges to the stable fixed point x* = 1 - 1/r."""
|
|
86
|
+
r = 2.5
|
|
87
|
+
system = LogisticMap(r=r)
|
|
88
|
+
traj = system.trajectory(np.array([0.1]), n_iter=500)
|
|
89
|
+
x_star = 1.0 - 1.0 / r
|
|
90
|
+
assert traj[-1, 0] == pytest.approx(x_star, abs=1e-6)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_logistic_map_period_two_cycle_just_above_r3():
|
|
94
|
+
"""Just above the first period-doubling bifurcation at r=3, orbits settle
|
|
95
|
+
into a period-2 cycle: x(n+2) == x(n) but x(n+1) != x(n)."""
|
|
96
|
+
system = LogisticMap(r=3.2)
|
|
97
|
+
traj = system.trajectory(np.array([0.1]), n_iter=2000)
|
|
98
|
+
tail = traj[-4:, 0]
|
|
99
|
+
assert tail[0] == pytest.approx(tail[2], abs=1e-6)
|
|
100
|
+
assert tail[1] == pytest.approx(tail[3], abs=1e-6)
|
|
101
|
+
assert abs(tail[0] - tail[1]) > 1e-3
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_logistic_map_sensitive_dependence_at_chaotic_r():
|
|
105
|
+
"""At r=4 (fully chaotic), two nearby trajectories must diverge noticeably
|
|
106
|
+
within a modest number of iterations."""
|
|
107
|
+
system = LogisticMap(r=4.0)
|
|
108
|
+
traj_a = system.trajectory(np.array([0.4]), n_iter=40)
|
|
109
|
+
traj_b = system.trajectory(np.array([0.4 + 1e-10]), n_iter=40)
|
|
110
|
+
assert abs(traj_a[-1, 0] - traj_b[-1, 0]) > 1e-3
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.continuous import DoublePendulum, Lorenz
|
|
5
|
+
from physicskit.chaos.systems.maps import HenonMap, StandardMap
|
|
6
|
+
from physicskit.chaos.utils.metrics import (
|
|
7
|
+
benettin_lyapunov_spectrum,
|
|
8
|
+
energy_drift,
|
|
9
|
+
lyapunov_exponent_from_divergence,
|
|
10
|
+
map_lyapunov_spectrum,
|
|
11
|
+
numerical_jacobian,
|
|
12
|
+
numerical_jacobian_map,
|
|
13
|
+
phase_volume_expansion,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_numerical_jacobian_matches_known_linear_system():
|
|
18
|
+
"""For dx/dt = A x, the Jacobian is exactly A everywhere."""
|
|
19
|
+
|
|
20
|
+
class Linear:
|
|
21
|
+
dim = 2
|
|
22
|
+
|
|
23
|
+
def rhs(self, state, t):
|
|
24
|
+
a = np.array([[0.0, 1.0], [-2.0, -3.0]])
|
|
25
|
+
return a @ state
|
|
26
|
+
|
|
27
|
+
jac = numerical_jacobian(Linear(), np.array([1.0, 2.0]), t=0.0)
|
|
28
|
+
np.testing.assert_allclose(jac, [[0.0, 1.0], [-2.0, -3.0]], atol=1e-6)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_numerical_jacobian_map_matches_known_henon_jacobian():
|
|
32
|
+
"""The Henon map's Jacobian at (x, y) is exactly [[-2*a*x, 1], [b, 0]]."""
|
|
33
|
+
henon = HenonMap(a=1.4, b=0.3)
|
|
34
|
+
x, y = 0.3, 0.1
|
|
35
|
+
jac = numerical_jacobian_map(henon, np.array([x, y]))
|
|
36
|
+
expected = np.array([[-2.0 * 1.4 * x, 1.0], [0.3, 0.0]])
|
|
37
|
+
np.testing.assert_allclose(jac, expected, atol=1e-5)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_lyapunov_exponent_from_divergence_recovers_known_exponential_rate():
|
|
41
|
+
t = np.linspace(0.0, 10.0, 200)
|
|
42
|
+
true_rate = 0.7
|
|
43
|
+
delta = 1e-6 * np.exp(true_rate * t)
|
|
44
|
+
estimate = lyapunov_exponent_from_divergence(t, delta)
|
|
45
|
+
assert estimate == pytest.approx(true_rate, rel=1e-3)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_benettin_lyapunov_spectrum_matches_known_lorenz_values():
|
|
49
|
+
"""The classic Lorenz parameters have well-documented Lyapunov exponents
|
|
50
|
+
of approximately (0.906, 0, -14.57)."""
|
|
51
|
+
system = Lorenz()
|
|
52
|
+
spectrum = benettin_lyapunov_spectrum(system, system.initial_state(), dt=0.005, n_steps=10000, n_transient=2000)
|
|
53
|
+
assert spectrum[0] == pytest.approx(0.906, abs=0.15)
|
|
54
|
+
assert abs(spectrum[1]) < 0.1
|
|
55
|
+
assert spectrum[2] == pytest.approx(-14.57, abs=1.5)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_benettin_lyapunov_spectrum_sum_matches_exact_lorenz_divergence():
|
|
59
|
+
"""The Lorenz system's phase-space divergence (trace of the Jacobian) is
|
|
60
|
+
exactly -(sigma + 1 + beta) everywhere, so the sum of the Lyapunov
|
|
61
|
+
spectrum -- estimated with no knowledge of that identity -- should match
|
|
62
|
+
it closely, regardless of how accurately the individual exponents (which
|
|
63
|
+
are harder to pin down individually) are estimated."""
|
|
64
|
+
system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
|
|
65
|
+
spectrum = benettin_lyapunov_spectrum(system, system.initial_state(), dt=0.005, n_steps=10000, n_transient=2000)
|
|
66
|
+
expected_sum = -(system.sigma + 1.0 + system.beta)
|
|
67
|
+
assert spectrum.sum() == pytest.approx(expected_sum, abs=0.05)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_map_lyapunov_spectrum_sum_matches_exact_henon_jacobian_determinant():
|
|
71
|
+
"""The Henon map's Jacobian determinant is exactly -b everywhere, so the
|
|
72
|
+
sum of its Lyapunov exponents must equal ln|b| to high precision --
|
|
73
|
+
regardless of how well the individual exponents are estimated."""
|
|
74
|
+
henon = HenonMap(a=1.4, b=0.3)
|
|
75
|
+
spectrum = map_lyapunov_spectrum(henon, henon.initial_state(), n_iter=10000, n_transient=1000)
|
|
76
|
+
assert spectrum.sum() == pytest.approx(np.log(0.3), abs=1e-4)
|
|
77
|
+
assert spectrum[0] > 0.0 > spectrum[1]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_map_lyapunov_spectrum_sums_to_zero_for_area_preserving_map():
|
|
81
|
+
"""The standard map is exactly area-preserving, so its Lyapunov spectrum
|
|
82
|
+
must sum to zero."""
|
|
83
|
+
system = StandardMap(k=2.0)
|
|
84
|
+
spectrum = map_lyapunov_spectrum(system, system.initial_state(), n_iter=10000, n_transient=1000)
|
|
85
|
+
assert spectrum.sum() == pytest.approx(0.0, abs=1e-4)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_energy_drift_is_zero_at_t0():
|
|
89
|
+
system = DoublePendulum()
|
|
90
|
+
t, states = system.trajectory(n_steps=500, dt=0.005)
|
|
91
|
+
drift = energy_drift(t, states, system.energy)
|
|
92
|
+
assert drift[0] == pytest.approx(0.0)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_energy_drift_stays_small_for_a_good_integration():
|
|
96
|
+
system = DoublePendulum()
|
|
97
|
+
t, states = system.trajectory(n_steps=2000, dt=0.005)
|
|
98
|
+
drift = energy_drift(t, states, system.energy)
|
|
99
|
+
assert np.max(np.abs(drift)) < 1e-3
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_phase_volume_expansion_starts_at_zero():
|
|
103
|
+
system = Lorenz()
|
|
104
|
+
t, states = system.trajectory(n_steps=500, dt=0.01)
|
|
105
|
+
log_volume = phase_volume_expansion(system, t, states)
|
|
106
|
+
assert log_volume[0] == pytest.approx(0.0)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_phase_volume_expansion_is_dissipative_for_lorenz():
|
|
110
|
+
"""Lorenz phase-space volume must contract (log volume strictly
|
|
111
|
+
decreasing on average) since the system is dissipative."""
|
|
112
|
+
system = Lorenz(sigma=10.0, rho=28.0, beta=8.0 / 3.0)
|
|
113
|
+
t, states = system.trajectory(n_steps=1000, dt=0.01)
|
|
114
|
+
log_volume = phase_volume_expansion(system, t, states)
|
|
115
|
+
assert log_volume[-1] < 0.0
|