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,132 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.continuous import DrivenPendulum, Duffing
|
|
5
|
+
from physicskit.chaos.systems.maps import LogisticMap
|
|
6
|
+
from physicskit.chaos.visualizers.bifurcation import (
|
|
7
|
+
bifurcation_diagram,
|
|
8
|
+
map_bifurcation_sampler,
|
|
9
|
+
stroboscopic_bifurcation_sampler,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_bifurcation_diagram_flattens_and_repeats_params_correctly():
|
|
14
|
+
def sample_fn(param):
|
|
15
|
+
return np.array([param, param + 1.0])
|
|
16
|
+
|
|
17
|
+
params, samples = bifurcation_diagram([0.0, 10.0], sample_fn)
|
|
18
|
+
np.testing.assert_allclose(params, [0.0, 0.0, 10.0, 10.0])
|
|
19
|
+
np.testing.assert_allclose(samples, [0.0, 1.0, 10.0, 11.0])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_bifurcation_diagram_with_multiple_threads_matches_sequential():
|
|
23
|
+
def sample_fn(param):
|
|
24
|
+
return np.array([param, param + 1.0])
|
|
25
|
+
|
|
26
|
+
param_values = np.linspace(0.0, 10.0, 15)
|
|
27
|
+
params_seq, samples_seq = bifurcation_diagram(param_values, sample_fn, n_jobs=1)
|
|
28
|
+
params_par, samples_par = bifurcation_diagram(param_values, sample_fn, n_jobs=4, show_progress=True)
|
|
29
|
+
np.testing.assert_allclose(params_par, params_seq)
|
|
30
|
+
np.testing.assert_allclose(samples_par, samples_seq)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_map_bifurcation_sampler_shows_fixed_point_below_r3():
|
|
34
|
+
"""Below r=3, the logistic map converges to a stable fixed point: every
|
|
35
|
+
surviving sample should be (nearly) identical."""
|
|
36
|
+
sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=200, n_keep=20)
|
|
37
|
+
samples = sampler(2.5)
|
|
38
|
+
assert np.ptp(samples) < 1e-6
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_map_bifurcation_sampler_shows_period_two_cycle_above_r3():
|
|
42
|
+
"""Just above r=3, the logistic map settles into a period-2 cycle: the
|
|
43
|
+
surviving samples should cluster into exactly two distinct values."""
|
|
44
|
+
sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=500, n_keep=20)
|
|
45
|
+
samples = sampler(3.2)
|
|
46
|
+
unique_values = np.unique(np.round(samples, decimals=4))
|
|
47
|
+
assert unique_values.size == 2
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def test_map_bifurcation_sampler_shows_many_values_when_chaotic():
|
|
51
|
+
"""At r=3.9 (chaotic), the surviving samples should spread over many
|
|
52
|
+
distinct values rather than clustering into a small cycle."""
|
|
53
|
+
sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=500, n_keep=50)
|
|
54
|
+
samples = sampler(3.9)
|
|
55
|
+
unique_values = np.unique(np.round(samples, decimals=3))
|
|
56
|
+
assert unique_values.size > 20
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_bifurcation_diagram_with_map_sampler_over_a_parameter_sweep():
|
|
60
|
+
sampler = map_bifurcation_sampler(lambda r: LogisticMap(r=r), state0=np.array([0.5]), n_transient=200, n_keep=10)
|
|
61
|
+
r_values = np.linspace(2.4, 4.0, 20)
|
|
62
|
+
params, samples = bifurcation_diagram(r_values, sampler)
|
|
63
|
+
# n_keep=10 plus the trajectory's initial state (row 0) surviving the
|
|
64
|
+
# transient slice, times 20 parameter values.
|
|
65
|
+
assert params.shape == samples.shape == (220,)
|
|
66
|
+
assert np.all(np.isfinite(samples))
|
|
67
|
+
assert np.all((samples >= 0.0) & (samples <= 1.0))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def test_stroboscopic_bifurcation_sampler_on_duffing_is_finite_and_correct_shape():
|
|
71
|
+
omega = 1.2
|
|
72
|
+
period = 2.0 * np.pi / omega
|
|
73
|
+
sampler = stroboscopic_bifurcation_sampler(
|
|
74
|
+
lambda gamma: Duffing(delta=0.3, alpha=-1.0, beta=1.0, gamma=gamma, omega=omega),
|
|
75
|
+
state0=np.array([0.1, 0.0]),
|
|
76
|
+
sample_period=period,
|
|
77
|
+
n_transient_periods=20,
|
|
78
|
+
n_keep_periods=5,
|
|
79
|
+
component=0,
|
|
80
|
+
dt=0.05,
|
|
81
|
+
)
|
|
82
|
+
samples = sampler(0.3)
|
|
83
|
+
assert samples.shape == (5,)
|
|
84
|
+
assert np.all(np.isfinite(samples))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_stroboscopic_bifurcation_sampler_shows_fixed_point_for_weak_forcing():
|
|
88
|
+
"""With no forcing at all, the (linear, damped) oscillator settles to a
|
|
89
|
+
fixed point: every stroboscopic sample should be (nearly) identical."""
|
|
90
|
+
omega = 1.0
|
|
91
|
+
period = 2.0 * np.pi / omega
|
|
92
|
+
sampler = stroboscopic_bifurcation_sampler(
|
|
93
|
+
lambda gamma: Duffing(delta=0.3, alpha=1.0, beta=0.0, gamma=gamma, omega=omega),
|
|
94
|
+
state0=np.array([1.0, 0.0]),
|
|
95
|
+
sample_period=period,
|
|
96
|
+
n_transient_periods=30,
|
|
97
|
+
n_keep_periods=5,
|
|
98
|
+
component=0,
|
|
99
|
+
dt=0.05,
|
|
100
|
+
)
|
|
101
|
+
samples = sampler(0.0)
|
|
102
|
+
assert np.ptp(samples) < 1e-3
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@pytest.mark.slow
|
|
106
|
+
def test_driven_pendulum_stroboscopic_sampler_shows_period_doubling_amplitude_dependence():
|
|
107
|
+
"""The driven pendulum's forcing-amplitude sweep is the classic
|
|
108
|
+
period-doubling demo: a weak forcing amplitude should settle to a
|
|
109
|
+
single stroboscopic value (period-1 locked to the drive), while a
|
|
110
|
+
strong one (the module's default, deep in the chaotic regime) should
|
|
111
|
+
show many distinct surviving values."""
|
|
112
|
+
omega_d = 2.0 / 3.0
|
|
113
|
+
period = 2.0 * np.pi / omega_d
|
|
114
|
+
|
|
115
|
+
def make_system(A):
|
|
116
|
+
return DrivenPendulum(damping=0.5, g_over_l=1.0, A=A, omega_d=omega_d)
|
|
117
|
+
|
|
118
|
+
sampler = stroboscopic_bifurcation_sampler(
|
|
119
|
+
make_system,
|
|
120
|
+
state0=np.array([0.2, 0.0]),
|
|
121
|
+
sample_period=period,
|
|
122
|
+
n_transient_periods=50,
|
|
123
|
+
n_keep_periods=15,
|
|
124
|
+
component=0,
|
|
125
|
+
dt=0.02,
|
|
126
|
+
)
|
|
127
|
+
weak_samples = sampler(0.5)
|
|
128
|
+
assert np.ptp(weak_samples) < 1e-2
|
|
129
|
+
|
|
130
|
+
strong_samples = sampler(1.5)
|
|
131
|
+
unique_values = np.unique(np.round(strong_samples, decimals=2))
|
|
132
|
+
assert unique_values.size > 5
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.billiards import (
|
|
5
|
+
BunimovichStadium,
|
|
6
|
+
CircleBilliard,
|
|
7
|
+
EllipseBilliard,
|
|
8
|
+
RectangleBilliard,
|
|
9
|
+
SinaiBilliard,
|
|
10
|
+
TruncatedCircleBilliard,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
ALL_BILLIARDS = [
|
|
14
|
+
lambda: CircleBilliard(radius=1.0),
|
|
15
|
+
lambda: RectangleBilliard(width=2.0, height=1.0),
|
|
16
|
+
lambda: SinaiBilliard(cell_size=2.0, scatterer_radius=0.5),
|
|
17
|
+
lambda: BunimovichStadium(radius=1.0, straight_length=2.0),
|
|
18
|
+
lambda: TruncatedCircleBilliard(radius=1.0, cut=0.3),
|
|
19
|
+
# Fewer segments than the default (2000) to keep the test suite fast; the
|
|
20
|
+
# polygon-approximation physics is exercised the same way either way.
|
|
21
|
+
lambda: EllipseBilliard(semi_major=1.5, semi_minor=1.0, n_segments=300),
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
|
|
26
|
+
def test_speed_conserved_across_bounces(make_billiard):
|
|
27
|
+
"""Specular reflection must preserve |v| = 1 at every bounce."""
|
|
28
|
+
billiard = make_billiard()
|
|
29
|
+
pos = billiard.sample_interior_point()
|
|
30
|
+
rng = np.random.default_rng(0)
|
|
31
|
+
for angle in rng.uniform(0, 2 * np.pi, size=10):
|
|
32
|
+
vel = np.array([np.cos(angle), np.sin(angle)])
|
|
33
|
+
result = billiard.simulate(pos, vel, n_bounces=200)
|
|
34
|
+
speeds = np.hypot(result["vx"], result["vy"])
|
|
35
|
+
np.testing.assert_allclose(speeds, 1.0, atol=1e-9)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
|
|
39
|
+
def test_bounce_points_lie_within_perimeter_range(make_billiard):
|
|
40
|
+
"""Boundary arclength coordinate s must stay within [0, perimeter)."""
|
|
41
|
+
billiard = make_billiard()
|
|
42
|
+
pos = billiard.sample_interior_point()
|
|
43
|
+
result = billiard.simulate(pos, np.array([1.0, 0.3]), n_bounces=200)
|
|
44
|
+
assert np.all(result["s"] >= -1e-6)
|
|
45
|
+
assert np.all(result["s"] <= billiard.perimeter() + 1e-6)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
|
|
49
|
+
def test_sin_phi_within_unit_range(make_billiard):
|
|
50
|
+
billiard = make_billiard()
|
|
51
|
+
pos = billiard.sample_interior_point()
|
|
52
|
+
result = billiard.simulate(pos, np.array([0.7, 0.5]), n_bounces=200)
|
|
53
|
+
assert np.all(result["sin_phi"] >= -1.0 - 1e-9)
|
|
54
|
+
assert np.all(result["sin_phi"] <= 1.0 + 1e-9)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("make_billiard", ALL_BILLIARDS)
|
|
58
|
+
def test_bounce_points_on_or_inside_boundary(make_billiard):
|
|
59
|
+
"""No bounce point should end up strictly outside the billiard's convex-ish
|
|
60
|
+
bounding region (a coarse sanity check against ray-tracing bugs)."""
|
|
61
|
+
billiard = make_billiard()
|
|
62
|
+
pos = billiard.sample_interior_point()
|
|
63
|
+
result = billiard.simulate(pos, np.array([0.4, 0.9]), n_bounces=100)
|
|
64
|
+
boundary = billiard.boundary_polyline()
|
|
65
|
+
margin = 1e-6
|
|
66
|
+
# boundary_polyline() separates disjoint closed components (e.g. Sinai's
|
|
67
|
+
# outer wall and inner scatterer) with a row of NaN; use nanmax/nanmin so
|
|
68
|
+
# that doesn't turn the bounding box into NaN.
|
|
69
|
+
assert result["x"].max() <= np.nanmax(boundary[:, 0]) + margin
|
|
70
|
+
assert result["x"].min() >= np.nanmin(boundary[:, 0]) - margin
|
|
71
|
+
assert result["y"].max() <= np.nanmax(boundary[:, 1]) + margin
|
|
72
|
+
assert result["y"].min() >= np.nanmin(boundary[:, 1]) - margin
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_circle_billiard_is_integrable_constant_sin_phi():
|
|
76
|
+
"""In a circular billiard, sin(phi) is an exact invariant of the motion."""
|
|
77
|
+
billiard = CircleBilliard(radius=1.0)
|
|
78
|
+
result = billiard.simulate(np.array([0.3, 0.0]), np.array([0.2, 1.0]), n_bounces=100)
|
|
79
|
+
np.testing.assert_allclose(result["sin_phi"], result["sin_phi"][0], atol=1e-8)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_sinai_billiard_rejects_scatterer_larger_than_cell():
|
|
83
|
+
with pytest.raises(ValueError):
|
|
84
|
+
SinaiBilliard(cell_size=2.0, scatterer_radius=1.5)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_truncated_circle_rejects_invalid_cut():
|
|
88
|
+
with pytest.raises(ValueError):
|
|
89
|
+
TruncatedCircleBilliard(radius=1.0, cut=1.5)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_ellipse_billiard_rejects_invalid_axes():
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
EllipseBilliard(semi_major=1.0, semi_minor=1.5) # major must exceed minor
|
|
95
|
+
with pytest.raises(ValueError):
|
|
96
|
+
EllipseBilliard(semi_major=-1.0, semi_minor=0.5)
|
|
97
|
+
with pytest.raises(ValueError):
|
|
98
|
+
EllipseBilliard(semi_major=1.0, semi_minor=0.0)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_ellipse_billiard_foci_at_known_positions():
|
|
102
|
+
billiard = EllipseBilliard(semi_major=1.5, semi_minor=1.0)
|
|
103
|
+
f1, f2 = billiard.foci()
|
|
104
|
+
c = np.sqrt(1.5**2 - 1.0**2)
|
|
105
|
+
np.testing.assert_allclose(f1, [c, 0.0])
|
|
106
|
+
np.testing.assert_allclose(f2, [-c, 0.0])
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_ellipse_billiard_sin_phi_stays_bounded_by_caustic():
|
|
110
|
+
"""A single ellipse-billiard trajectory stays tangent to one confocal
|
|
111
|
+
caustic, so its sin(phi) values are confined to a bounded sub-range of
|
|
112
|
+
[-1, 1] rather than filling it (unlike a chaotic billiard)."""
|
|
113
|
+
billiard = EllipseBilliard(semi_major=1.5, semi_minor=1.0, n_segments=1000)
|
|
114
|
+
result = billiard.simulate(billiard.sample_interior_point(), np.array([0.3, 1.0]), n_bounces=300)
|
|
115
|
+
assert result["sin_phi"].max() - result["sin_phi"].min() < 1.0
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.continuous import (
|
|
5
|
+
Chua,
|
|
6
|
+
DoublePendulum,
|
|
7
|
+
DrivenPendulum,
|
|
8
|
+
Duffing,
|
|
9
|
+
MagneticPendulum,
|
|
10
|
+
RestrictedThreeBody,
|
|
11
|
+
Rossler,
|
|
12
|
+
effective_potential,
|
|
13
|
+
lagrange_points,
|
|
14
|
+
)
|
|
15
|
+
from physicskit.chaos.utils.metrics import energy_drift
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_rossler_attractor_trajectory_is_finite_and_matches_rhs():
|
|
19
|
+
system = Rossler(a=0.2, b=0.2, c=5.7)
|
|
20
|
+
assert system.initial_state().tolist() == [1.0, 1.0, 1.0]
|
|
21
|
+
t, states = system.trajectory(n_steps=2000, dt=0.01)
|
|
22
|
+
assert np.all(np.isfinite(states))
|
|
23
|
+
assert t.shape == (2001,)
|
|
24
|
+
|
|
25
|
+
deriv = system.rhs(states[0], t[0])
|
|
26
|
+
assert deriv.shape == (3,)
|
|
27
|
+
assert np.allclose(deriv, [-states[0, 1] - states[0, 2], states[0, 0] + 0.2 * states[0, 1], 0.2 + states[0, 2] * (states[0, 0] - 5.7)])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_double_pendulum_and_magnetic_pendulum_rhs_match_trajectory():
|
|
31
|
+
dp = DoublePendulum()
|
|
32
|
+
state0 = dp.initial_state()
|
|
33
|
+
d = dp.rhs(state0, 0.0)
|
|
34
|
+
assert d.shape == state0.shape
|
|
35
|
+
assert np.all(np.isfinite(d))
|
|
36
|
+
|
|
37
|
+
mp = MagneticPendulum()
|
|
38
|
+
state0_mp = mp.initial_state()
|
|
39
|
+
d_mp = mp.rhs(state0_mp, 0.0)
|
|
40
|
+
assert d_mp.shape == state0_mp.shape
|
|
41
|
+
assert np.all(np.isfinite(d_mp))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_duffing_initial_state_and_rhs():
|
|
45
|
+
system = Duffing(delta=0.3, alpha=-1.0, beta=1.0, gamma=0.3, omega=1.2)
|
|
46
|
+
state0 = system.initial_state()
|
|
47
|
+
assert state0.shape == (2,)
|
|
48
|
+
d = system.rhs(state0, 0.0)
|
|
49
|
+
assert d.shape == (2,)
|
|
50
|
+
assert np.all(np.isfinite(d))
|
|
51
|
+
_, states = system.trajectory(state0=state0, n_steps=100, dt=0.01)
|
|
52
|
+
assert np.all(np.isfinite(states))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_chua_trajectory_is_finite_and_bounded():
|
|
56
|
+
"""Chua's circuit is dissipative and settles onto a bounded attractor;
|
|
57
|
+
integrating it must never blow up to infinity/NaN."""
|
|
58
|
+
system = Chua()
|
|
59
|
+
_, states = system.trajectory(n_steps=5000, dt=0.01)
|
|
60
|
+
assert np.all(np.isfinite(states))
|
|
61
|
+
# The classic double-scroll attractor stays within a modest, known range
|
|
62
|
+
# of the parameters used here; a much looser bound just guards against
|
|
63
|
+
# numerical blow-up regressions.
|
|
64
|
+
assert np.all(np.abs(states) < 20.0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_chua_rhs_at_origin_is_zero():
|
|
68
|
+
"""The origin is an equilibrium of Chua's circuit: h(0) = 0, so
|
|
69
|
+
rhs((0, 0, 0)) == (0, 0, 0)."""
|
|
70
|
+
system = Chua()
|
|
71
|
+
derivative = system.rhs(np.array([0.0, 0.0, 0.0]), 0.0)
|
|
72
|
+
np.testing.assert_allclose(derivative, [0.0, 0.0, 0.0], atol=1e-12)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_chua_trajectory_is_dissipative():
|
|
76
|
+
"""Chua's circuit is dissipative (its equilibria are unstable but phase
|
|
77
|
+
volume contracts on average): starting near, but not at, the origin, the
|
|
78
|
+
trajectory should not simply run away to the bounding box unboundedly."""
|
|
79
|
+
system = Chua()
|
|
80
|
+
_, states = system.trajectory(state0=np.array([0.1, 0.0, 0.0]), n_steps=5000, dt=0.01)
|
|
81
|
+
assert np.all(np.isfinite(states))
|
|
82
|
+
assert np.all(np.abs(states) < 20.0)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_restricted_three_body_jacobi_constant_is_conserved():
|
|
86
|
+
"""The Jacobi constant is an exact invariant of CR3BP motion; a good RK4
|
|
87
|
+
integration should conserve it to a tight tolerance."""
|
|
88
|
+
system = RestrictedThreeBody()
|
|
89
|
+
t, states = system.trajectory(dt=0.001, n_steps=5000)
|
|
90
|
+
drift = energy_drift(t, states, system.jacobi_constant)
|
|
91
|
+
assert np.max(np.abs(drift)) < 1e-4
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_restricted_three_body_arenstorf_orbit_is_approximately_periodic():
|
|
95
|
+
"""The default initial condition is the classic Arenstorf periodic orbit:
|
|
96
|
+
integrating for exactly one period should return close to the start."""
|
|
97
|
+
system = RestrictedThreeBody()
|
|
98
|
+
state0 = system.initial_state()
|
|
99
|
+
period = 17.0652165601579625588917206249
|
|
100
|
+
dt = 0.0001
|
|
101
|
+
_, states = system.trajectory(dt=dt, n_steps=round(period / dt))
|
|
102
|
+
assert np.max(np.abs(states[-1] - state0)) < 0.01
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def test_restricted_three_body_rhs_matches_known_acceleration_at_a_point():
|
|
106
|
+
"""Spot-check the CR3BP right-hand side at a simple point against a
|
|
107
|
+
hand-computed value, to catch sign/algebra errors in the Coriolis terms."""
|
|
108
|
+
system = RestrictedThreeBody(mu=0.1)
|
|
109
|
+
# On the x-axis with zero velocity, the Coriolis terms vanish and the
|
|
110
|
+
# y-acceleration is exactly zero by symmetry.
|
|
111
|
+
derivative = system.rhs(np.array([0.5, 0.0, 0.0, 0.0]), 0.0)
|
|
112
|
+
assert derivative[1] == 0.0 # dy/dt = vy = 0
|
|
113
|
+
assert derivative[3] == 0.0 # dvy/dt = 0 by symmetry on the x-axis
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_lagrange_points_collinear_points_are_ordered_and_at_equilibrium():
|
|
117
|
+
"""L3 < -mu (primary) < L1 < 1-mu (secondary) < L2 on the x-axis, and
|
|
118
|
+
each collinear point must be a genuine root of the rotating-frame
|
|
119
|
+
x-acceleration (zero net force at rest)."""
|
|
120
|
+
mu = 0.012277471
|
|
121
|
+
points = lagrange_points(mu)
|
|
122
|
+
x_l1, x_l2, x_l3 = points[0, 0], points[1, 0], points[2, 0]
|
|
123
|
+
assert x_l3 < -mu < x_l1 < (1.0 - mu) < x_l2
|
|
124
|
+
|
|
125
|
+
system = RestrictedThreeBody(mu=mu)
|
|
126
|
+
for x in (x_l1, x_l2, x_l3):
|
|
127
|
+
derivative = system.rhs(np.array([x, 0.0, 0.0, 0.0]), 0.0)
|
|
128
|
+
assert abs(derivative[2]) < 1e-8 # zero net x-acceleration at rest
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_lagrange_points_l4_l5_form_equilateral_triangles_with_primaries():
|
|
132
|
+
"""L4 and L5 are, by construction, equidistant from both primaries at
|
|
133
|
+
exactly the primary-primary separation (unit distance in these units)."""
|
|
134
|
+
mu = 0.012277471
|
|
135
|
+
points = lagrange_points(mu)
|
|
136
|
+
primary = np.array([-mu, 0.0])
|
|
137
|
+
secondary = np.array([1.0 - mu, 0.0])
|
|
138
|
+
for lx, ly in points[3:]:
|
|
139
|
+
p = np.array([lx, ly])
|
|
140
|
+
assert np.hypot(*(p - primary)) == pytest.approx(1.0, abs=1e-10)
|
|
141
|
+
assert np.hypot(*(p - secondary)) == pytest.approx(1.0, abs=1e-10)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_effective_potential_matches_jacobi_constant_formula():
|
|
145
|
+
"""RestrictedThreeBody.jacobi_constant is defined as
|
|
146
|
+
``2*effective_potential(x, y, mu) - speed**2``; check the two stay
|
|
147
|
+
consistent (a regression guard against the two formulas drifting apart
|
|
148
|
+
if either is edited independently)."""
|
|
149
|
+
system = RestrictedThreeBody(mu=0.1)
|
|
150
|
+
state = np.array([0.3, 0.4, 0.1, -0.2])
|
|
151
|
+
x, y, vx, vy = state
|
|
152
|
+
expected = 2.0 * effective_potential(x, y, system.mu) - (vx * vx + vy * vy)
|
|
153
|
+
assert system.jacobi_constant(state) == pytest.approx(expected)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_effective_potential_is_vectorized_over_a_grid():
|
|
157
|
+
"""effective_potential must work directly on np.meshgrid output, since
|
|
158
|
+
that's how zero-velocity-curve plots use it."""
|
|
159
|
+
x = np.linspace(-1.5, 1.5, 5)
|
|
160
|
+
y = np.linspace(-1.5, 1.5, 5)
|
|
161
|
+
xx, yy = np.meshgrid(x, y)
|
|
162
|
+
omega = effective_potential(xx, yy, 0.012277471)
|
|
163
|
+
assert omega.shape == xx.shape
|
|
164
|
+
assert np.all(np.isfinite(omega))
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_effective_potential_is_minimized_at_lagrange_points_4_and_5():
|
|
168
|
+
"""L4/L5 are the global minima of the effective potential; nearby points
|
|
169
|
+
(that aren't also Lagrange points) must have a strictly larger value."""
|
|
170
|
+
mu = 0.012277471
|
|
171
|
+
lx, ly = lagrange_points(mu)[3]
|
|
172
|
+
omega_l4 = effective_potential(lx, ly, mu)
|
|
173
|
+
for dx, dy in [(0.05, 0.0), (-0.05, 0.0), (0.0, 0.05), (0.0, -0.05)]:
|
|
174
|
+
assert effective_potential(lx + dx, ly + dy, mu) > omega_l4
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def test_driven_pendulum_rhs_matches_hand_derivation():
|
|
178
|
+
"""Spot-check the driven-pendulum right-hand side at a simple point."""
|
|
179
|
+
system = DrivenPendulum(damping=0.5, g_over_l=1.0, A=1.5, omega_d=2.0 / 3.0)
|
|
180
|
+
derivative = system.rhs(np.array([0.0, 0.0]), 0.0)
|
|
181
|
+
# theta=0, omega=0, t=0: dtheta/dt=0, domega/dt = A*cos(0) = A
|
|
182
|
+
assert derivative[0] == 0.0
|
|
183
|
+
assert derivative[1] == pytest.approx(1.5)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_driven_pendulum_trajectory_is_finite():
|
|
187
|
+
"""The classic chaotic parameter set should integrate stably (bounded
|
|
188
|
+
omega; theta itself is allowed to wind arbitrarily as the pendulum
|
|
189
|
+
goes over the top, which happens for these parameters)."""
|
|
190
|
+
system = DrivenPendulum()
|
|
191
|
+
_, states = system.trajectory(n_steps=5000, dt=0.02)
|
|
192
|
+
assert np.all(np.isfinite(states))
|
|
193
|
+
assert np.all(np.abs(states[:, 1]) < 20.0)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_magnetic_pendulum_settles_near_a_magnet():
|
|
197
|
+
"""Starting the (damped) bob very close to one magnet should leave it
|
|
198
|
+
settled even closer to that same magnet."""
|
|
199
|
+
system = MagneticPendulum()
|
|
200
|
+
magnet = system.magnet_positions[0]
|
|
201
|
+
state0 = np.array([magnet[0] + 0.05, magnet[1], 0.0, 0.0])
|
|
202
|
+
_, states = system.trajectory(state0=state0, n_steps=5000, dt=0.02)
|
|
203
|
+
final_dist = np.hypot(*(states[-1, :2] - magnet))
|
|
204
|
+
initial_dist = np.hypot(*(state0[:2] - magnet))
|
|
205
|
+
assert final_dist < initial_dist
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_magnetic_pendulum_shows_sensitive_dependence_near_a_basin_boundary():
|
|
209
|
+
"""Two very close starting points can settle near different magnets --
|
|
210
|
+
the hallmark of a fractal basin boundary."""
|
|
211
|
+
system = MagneticPendulum()
|
|
212
|
+
outcomes = set()
|
|
213
|
+
for x0 in np.linspace(-0.05, 0.05, 11):
|
|
214
|
+
_, states = system.trajectory(state0=np.array([x0, 0.0, 0.0, 0.0]), n_steps=5000, dt=0.02)
|
|
215
|
+
final = states[-1, :2]
|
|
216
|
+
dists = np.hypot(*(system.magnet_positions - final).T)
|
|
217
|
+
outcomes.add(int(np.argmin(dists)))
|
|
218
|
+
assert len(outcomes) > 1
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def test_magnetic_pendulum_trajectory_is_finite():
|
|
222
|
+
system = MagneticPendulum()
|
|
223
|
+
_, states = system.trajectory(n_steps=5000, dt=0.02)
|
|
224
|
+
assert np.all(np.isfinite(states))
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.maps import HenonMap
|
|
5
|
+
from physicskit.chaos.utils.dimension import box_counting_dimension, correlation_dimension
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _cantor_set_points(n_iter=10):
|
|
9
|
+
"""Midpoints of the intervals surviving `n_iter` steps of the middle-thirds
|
|
10
|
+
Cantor set construction; its exact box-counting/correlation dimension is
|
|
11
|
+
log(2)/log(3), giving a ground-truth target for both estimators."""
|
|
12
|
+
intervals = [(0.0, 1.0)]
|
|
13
|
+
for _ in range(n_iter):
|
|
14
|
+
new_intervals = []
|
|
15
|
+
for a, b in intervals:
|
|
16
|
+
third = (b - a) / 3.0
|
|
17
|
+
new_intervals.append((a, a + third))
|
|
18
|
+
new_intervals.append((b - third, b))
|
|
19
|
+
intervals = new_intervals
|
|
20
|
+
return np.array([[(a + b) / 2.0] for a, b in intervals])
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
CANTOR_DIMENSION = np.log(2) / np.log(3)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_box_counting_dimension_matches_cantor_set_exact_value():
|
|
27
|
+
points = _cantor_set_points(n_iter=10)
|
|
28
|
+
d0, epsilons, counts = box_counting_dimension(points, n_scales=15, eps_min=3.0**-8, eps_max=3.0**-2)
|
|
29
|
+
assert d0 == pytest.approx(CANTOR_DIMENSION, abs=0.05)
|
|
30
|
+
assert epsilons.shape == (15,)
|
|
31
|
+
assert counts.shape == (15,)
|
|
32
|
+
assert np.all(counts >= 1.0)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_correlation_dimension_matches_cantor_set_exact_value():
|
|
36
|
+
points = _cantor_set_points(n_iter=10)
|
|
37
|
+
d2, _, correlation_sums = correlation_dimension(points, n_scales=15, eps_min=3.0**-8, eps_max=3.0**-2)
|
|
38
|
+
assert d2 == pytest.approx(CANTOR_DIMENSION, abs=0.05)
|
|
39
|
+
assert np.all((correlation_sums >= 0.0) & (correlation_sums <= 1.0))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_box_counting_dimension_of_a_line_segment_is_about_one():
|
|
43
|
+
rng = np.random.default_rng(0)
|
|
44
|
+
points = np.column_stack([rng.uniform(size=5000), np.zeros(5000)])
|
|
45
|
+
d0, _, _ = box_counting_dimension(points)
|
|
46
|
+
assert d0 == pytest.approx(1.0, abs=0.15)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_box_counting_dimension_of_a_filled_square_approaches_two():
|
|
50
|
+
"""With enough points to avoid box-counting's finite-sample saturation,
|
|
51
|
+
a uniformly-filled 2D region should have dimension close to 2."""
|
|
52
|
+
rng = np.random.default_rng(0)
|
|
53
|
+
points = rng.uniform(size=(100000, 2))
|
|
54
|
+
d0, _, _ = box_counting_dimension(points)
|
|
55
|
+
assert d0 == pytest.approx(2.0, abs=0.15)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_henon_attractor_dimension_matches_known_literature_value():
|
|
59
|
+
"""The classic Henon attractor (a=1.4, b=0.3) has a well-documented
|
|
60
|
+
fractal dimension of approximately 1.2-1.3."""
|
|
61
|
+
system = HenonMap(a=1.4, b=0.3)
|
|
62
|
+
traj = system.trajectory(np.array([0.0, 0.0]), n_iter=8000)
|
|
63
|
+
traj = traj[500:] # discard transient
|
|
64
|
+
|
|
65
|
+
d0, _, _ = box_counting_dimension(traj)
|
|
66
|
+
assert 1.1 < d0 < 1.4
|
|
67
|
+
|
|
68
|
+
d2, _, _ = correlation_dimension(traj)
|
|
69
|
+
assert 1.0 < d2 < 1.4
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.billiards import CircleBilliard, TruncatedCircleBilliard
|
|
5
|
+
from physicskit.chaos.systems.continuous import Lorenz
|
|
6
|
+
from physicskit.chaos.visualizers.divergence import (
|
|
7
|
+
billiard_trajectory_divergence,
|
|
8
|
+
plot_billiard_divergence,
|
|
9
|
+
plot_trajectory_ensemble,
|
|
10
|
+
trajectory_ensemble,
|
|
11
|
+
)
|
|
12
|
+
from physicskit.chaos.visualizers.dynamic_plots import animate_billiard_divergence
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_circle_billiard_separation_is_exactly_conserved():
|
|
16
|
+
"""The circle is integrable (sin(phi) is an exact invariant), so two
|
|
17
|
+
nearby rays' separation should stay essentially unchanged forever --
|
|
18
|
+
no exponential growth, because there is no chaos to be sensitive to."""
|
|
19
|
+
billiard = CircleBilliard(radius=1.0)
|
|
20
|
+
delta_0 = 1e-8
|
|
21
|
+
_, separation = billiard_trajectory_divergence(
|
|
22
|
+
billiard,
|
|
23
|
+
pos=billiard.sample_interior_point(),
|
|
24
|
+
vel=(0.2, 1.0),
|
|
25
|
+
delta_0=delta_0,
|
|
26
|
+
n_bounces=100,
|
|
27
|
+
)
|
|
28
|
+
assert np.allclose(separation, delta_0, rtol=1e-6)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.mark.parametrize("cut", [0.3, 0.05, 0.02])
|
|
32
|
+
def test_truncated_circle_billiard_diverges_for_any_cut(cut):
|
|
33
|
+
"""Even a tiny truncation destroys integrability: separation must grow
|
|
34
|
+
by many orders of magnitude over enough bounces, for any cut depth."""
|
|
35
|
+
billiard = TruncatedCircleBilliard(radius=1.0, cut=cut)
|
|
36
|
+
delta_0 = 1e-8
|
|
37
|
+
_, separation = billiard_trajectory_divergence(
|
|
38
|
+
billiard,
|
|
39
|
+
pos=billiard.sample_interior_point(),
|
|
40
|
+
vel=(0.2, 1.0),
|
|
41
|
+
delta_0=delta_0,
|
|
42
|
+
n_bounces=300,
|
|
43
|
+
)
|
|
44
|
+
assert separation.max() > 1e-3
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_billiard_trajectory_divergence_shapes():
|
|
48
|
+
billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
|
|
49
|
+
bounce, separation = billiard_trajectory_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=50)
|
|
50
|
+
assert bounce.shape == (50,)
|
|
51
|
+
assert separation.shape == (50,)
|
|
52
|
+
assert np.array_equal(bounce, np.arange(1, 51))
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_plot_billiard_divergence_estimates_positive_exponent_for_chaotic_billiard():
|
|
56
|
+
billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
|
|
57
|
+
_, ax, lam = plot_billiard_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=100)
|
|
58
|
+
assert lam > 0.0
|
|
59
|
+
assert ax.get_xlabel() == "bounce number k"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_animate_billiard_divergence_runs():
|
|
63
|
+
billiard = TruncatedCircleBilliard(radius=1.0, cut=0.05)
|
|
64
|
+
anim = animate_billiard_divergence(billiard, pos=billiard.sample_interior_point(), vel=(0.2, 1.0), n_bounces=30, trail=10)
|
|
65
|
+
anim._draw_frame(20)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_trajectory_ensemble_shape_and_zero_spread_members_share_the_reference():
|
|
69
|
+
"""With spread=0, every ensemble member is launched from exactly the
|
|
70
|
+
same state as the reference, so all members' trajectories must be
|
|
71
|
+
identical to each other."""
|
|
72
|
+
system = Lorenz()
|
|
73
|
+
all_states = trajectory_ensemble(system, n_members=5, spread=0.0, dt=0.01, n_steps=50, seed=0)
|
|
74
|
+
assert all_states.shape == (5, 51, system.dim)
|
|
75
|
+
for member in all_states[1:]:
|
|
76
|
+
np.testing.assert_allclose(member, all_states[0])
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_trajectory_ensemble_members_start_within_the_requested_spread():
|
|
80
|
+
system = Lorenz()
|
|
81
|
+
state0 = system.initial_state()
|
|
82
|
+
all_states = trajectory_ensemble(system, state0=state0, n_members=10, spread=1e-3, dt=0.01, n_steps=10, seed=0)
|
|
83
|
+
offsets = np.linalg.norm(all_states[:, 0, :] - state0, axis=1)
|
|
84
|
+
assert np.all(offsets < 1e-3 + 1e-12)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_plot_trajectory_ensemble_draws_one_line_per_member():
|
|
88
|
+
system = Lorenz()
|
|
89
|
+
_, ax = plot_trajectory_ensemble(system, n_members=6, spread=1e-4, dt=0.01, n_steps=20, seed=0)
|
|
90
|
+
assert len(ax.get_lines()) == 6
|