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,48 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.utils.spectral import autocorrelation, power_spectrum
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def test_power_spectrum_of_pure_sine_peaks_at_its_frequency():
|
|
8
|
+
dt = 0.01
|
|
9
|
+
t = np.arange(0, 10, dt)
|
|
10
|
+
freq = 3.0
|
|
11
|
+
series = np.sin(2.0 * np.pi * freq * t)
|
|
12
|
+
|
|
13
|
+
frequencies, power = power_spectrum(series, dt=dt)
|
|
14
|
+
peak_freq = frequencies[np.argmax(power)]
|
|
15
|
+
assert peak_freq == pytest.approx(freq, abs=0.05)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_power_spectrum_shapes_and_nonnegative():
|
|
19
|
+
series = np.random.default_rng(0).normal(size=200)
|
|
20
|
+
frequencies, power = power_spectrum(series, dt=1.0)
|
|
21
|
+
assert frequencies.shape == power.shape == (101,)
|
|
22
|
+
assert np.all(power >= 0.0)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_autocorrelation_of_pure_sine_is_periodic():
|
|
26
|
+
dt = 0.01
|
|
27
|
+
t = np.arange(0, 20, dt)
|
|
28
|
+
freq = 2.0
|
|
29
|
+
series = np.sin(2.0 * np.pi * freq * t)
|
|
30
|
+
|
|
31
|
+
acf = autocorrelation(series, max_lag=len(series) - 1)
|
|
32
|
+
assert acf[0] == pytest.approx(1.0)
|
|
33
|
+
period_samples = round(1.0 / freq / dt)
|
|
34
|
+
assert acf[period_samples] == pytest.approx(1.0, abs=0.05)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_autocorrelation_of_white_noise_decays_immediately():
|
|
38
|
+
rng = np.random.default_rng(0)
|
|
39
|
+
series = rng.normal(size=5000)
|
|
40
|
+
acf = autocorrelation(series, max_lag=50)
|
|
41
|
+
assert acf[0] == pytest.approx(1.0)
|
|
42
|
+
assert abs(acf[1]) < 0.1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_autocorrelation_default_max_lag():
|
|
46
|
+
series = np.random.default_rng(0).normal(size=50)
|
|
47
|
+
acf = autocorrelation(series)
|
|
48
|
+
assert acf.shape == (50,)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Smoke tests for the Streamlit explorer app, run headlessly via
|
|
2
|
+
``streamlit.testing.v1.AppTest`` (no browser or server needed)."""
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
st_testing = pytest.importorskip("streamlit.testing.v1")
|
|
9
|
+
|
|
10
|
+
APP_PATH = str(Path(__file__).resolve().parent.parent / "app" / "streamlit_app.py")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _run() -> "st_testing.AppTest":
|
|
14
|
+
at = st_testing.AppTest.from_file(APP_PATH)
|
|
15
|
+
at.run(timeout=120)
|
|
16
|
+
return at
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_app_loads_with_no_exceptions_in_default_state():
|
|
20
|
+
at = _run()
|
|
21
|
+
assert not at.exception
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@pytest.mark.parametrize("demo", ["Billiards", "Continuous systems", "Bifurcation diagrams", "Basins of attraction"])
|
|
25
|
+
def test_each_demo_runs_with_no_exceptions(demo):
|
|
26
|
+
at = _run()
|
|
27
|
+
at.selectbox[0].set_value(demo).run(timeout=120)
|
|
28
|
+
assert not at.exception
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_every_billiard_shape_runs_with_no_exceptions():
|
|
32
|
+
at = _run()
|
|
33
|
+
for option in list(at.selectbox[1].options):
|
|
34
|
+
at.selectbox[1].set_value(option).run(timeout=120)
|
|
35
|
+
assert not at.exception, f"{option} raised: {list(at.exception)}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_every_map_and_sweep_parameter_runs_with_no_exceptions():
|
|
39
|
+
at = _run()
|
|
40
|
+
at.selectbox[0].set_value("Bifurcation diagrams").run(timeout=120)
|
|
41
|
+
for map_name in list(at.selectbox[1].options):
|
|
42
|
+
at.selectbox[1].set_value(map_name).run(timeout=120)
|
|
43
|
+
for param_name in list(at.selectbox[2].options):
|
|
44
|
+
at.selectbox[2].set_value(param_name).run(timeout=120)
|
|
45
|
+
assert not at.exception, f"{map_name}/{param_name} raised: {list(at.exception)}"
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.chaos.systems.continuous import Lorenz
|
|
5
|
+
from physicskit.chaos.utils.timeseries import (
|
|
6
|
+
average_log_divergence,
|
|
7
|
+
delay_embed,
|
|
8
|
+
iaaft_surrogate,
|
|
9
|
+
rosenstein_lyapunov,
|
|
10
|
+
surrogate_test,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_delay_embed_shape_and_values():
|
|
15
|
+
series = np.arange(10.0)
|
|
16
|
+
embedded = delay_embed(series, dim=3, tau=2)
|
|
17
|
+
assert embedded.shape == (6, 3)
|
|
18
|
+
np.testing.assert_allclose(embedded[0], [0.0, 2.0, 4.0])
|
|
19
|
+
np.testing.assert_allclose(embedded[-1], [5.0, 7.0, 9.0])
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_delay_embed_rejects_series_too_short():
|
|
23
|
+
with pytest.raises(ValueError):
|
|
24
|
+
delay_embed(np.arange(5.0), dim=10, tau=1)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_average_log_divergence_returns_matching_shapes():
|
|
28
|
+
rng = np.random.default_rng(0)
|
|
29
|
+
series = rng.normal(size=500)
|
|
30
|
+
steps, log_div = average_log_divergence(series, dim=3, tau=1, max_iter=20)
|
|
31
|
+
assert steps.shape == log_div.shape
|
|
32
|
+
assert steps.size > 0
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_average_log_divergence_stops_early_once_no_pairs_remain_in_range():
|
|
36
|
+
"""With max_iter set far beyond what the series length can support, the
|
|
37
|
+
forward-evolution loop must break out early (rather than index out of
|
|
38
|
+
bounds) once every candidate pair's forward offset runs past the end of
|
|
39
|
+
the series."""
|
|
40
|
+
series = np.sin(np.linspace(0, 20, 60))
|
|
41
|
+
steps, log_div = average_log_divergence(series, dim=2, tau=1, max_iter=1000)
|
|
42
|
+
assert steps.shape == log_div.shape
|
|
43
|
+
assert steps.size > 0
|
|
44
|
+
assert steps[-1] < 1000 - 1
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_average_log_divergence_skips_steps_with_only_exact_zero_distances():
|
|
48
|
+
"""A constant series makes every embedded point identical, so its
|
|
49
|
+
nearest-neighbor distance is exactly zero at every forward step; those
|
|
50
|
+
steps must be skipped (not averaged as log(0) = -inf), leaving no valid
|
|
51
|
+
steps at all here."""
|
|
52
|
+
series = np.full(50, 3.0)
|
|
53
|
+
steps, log_div = average_log_divergence(series, dim=2, tau=1, max_iter=5)
|
|
54
|
+
assert steps.shape == (0,)
|
|
55
|
+
assert log_div.shape == (0,)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@pytest.mark.slow
|
|
59
|
+
def test_rosenstein_lyapunov_matches_known_lorenz_exponent():
|
|
60
|
+
"""The Lorenz system's largest Lyapunov exponent is well known to be
|
|
61
|
+
approximately 0.905; estimating it from a raw x(t) time series (no
|
|
62
|
+
equations used) with Rosenstein's algorithm should get reasonably close."""
|
|
63
|
+
system = Lorenz()
|
|
64
|
+
dt = 0.01
|
|
65
|
+
_, states = system.trajectory(n_steps=12000, dt=dt)
|
|
66
|
+
x = states[1000:, 0]
|
|
67
|
+
|
|
68
|
+
lam = rosenstein_lyapunov(x, dim=5, tau=10, dt=dt, fit_fraction=0.3)
|
|
69
|
+
assert 0.5 < lam < 1.3
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_rosenstein_lyapunov_rejects_too_short_series():
|
|
73
|
+
with pytest.raises(ValueError):
|
|
74
|
+
rosenstein_lyapunov(np.arange(5.0), dim=3, tau=1)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_iaaft_surrogate_preserves_mean_and_std():
|
|
78
|
+
rng = np.random.default_rng(0)
|
|
79
|
+
series = rng.normal(loc=3.0, scale=2.0, size=1000)
|
|
80
|
+
surrogate = iaaft_surrogate(series, n_iter=50, seed=1)
|
|
81
|
+
assert surrogate.mean() == pytest.approx(series.mean(), abs=1e-9)
|
|
82
|
+
assert surrogate.std() == pytest.approx(series.std(), abs=1e-9)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_iaaft_surrogate_preserves_power_spectrum_closely():
|
|
86
|
+
system = Lorenz()
|
|
87
|
+
_, states = system.trajectory(n_steps=4000, dt=0.01)
|
|
88
|
+
x = states[500:, 0]
|
|
89
|
+
|
|
90
|
+
surrogate = iaaft_surrogate(x, n_iter=100, seed=0)
|
|
91
|
+
original_spectrum = np.abs(np.fft.rfft(x))
|
|
92
|
+
surrogate_spectrum = np.abs(np.fft.rfft(surrogate))
|
|
93
|
+
relative_error = np.max(np.abs(original_spectrum - surrogate_spectrum)) / np.max(original_spectrum)
|
|
94
|
+
assert relative_error < 0.05
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.slow
|
|
98
|
+
def test_surrogate_test_detects_nonlinear_structure_in_lorenz():
|
|
99
|
+
"""Lorenz x(t) has genuine deterministic/nonlinear structure that IAAFT
|
|
100
|
+
surrogates (linear stochastic realizations with the same spectrum) lack,
|
|
101
|
+
so the observed statistic should be many surrogate-std-devs away from the
|
|
102
|
+
surrogate distribution."""
|
|
103
|
+
system = Lorenz()
|
|
104
|
+
dt = 0.01
|
|
105
|
+
_, states = system.trajectory(n_steps=6000, dt=dt)
|
|
106
|
+
x = states[1000:, 0]
|
|
107
|
+
|
|
108
|
+
def statistic(series):
|
|
109
|
+
return rosenstein_lyapunov(series, dim=5, tau=10, dt=dt, fit_fraction=0.3)
|
|
110
|
+
|
|
111
|
+
_observed, surrogate_values, significance = surrogate_test(x, statistic, n_surrogates=6, n_iter=15, seed=0)
|
|
112
|
+
assert surrogate_values.shape == (6,)
|
|
113
|
+
assert significance > 2.0
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_surrogate_test_finds_no_structure_in_pure_noise():
|
|
117
|
+
"""White noise has no structure beyond its (flat) spectrum and
|
|
118
|
+
distribution, so a nonlinear statistic (here, the same Rosenstein
|
|
119
|
+
exponent used to detect Lorenz's structure) should come out statistically
|
|
120
|
+
indistinguishable between the noise and its own surrogates (low
|
|
121
|
+
significance) -- unlike a trivially IAAFT-invariant statistic such as the
|
|
122
|
+
standard deviation, which every surrogate reproduces exactly by
|
|
123
|
+
construction and would give a meaningless zero-variance comparison."""
|
|
124
|
+
rng = np.random.default_rng(0)
|
|
125
|
+
noise = rng.normal(size=2000)
|
|
126
|
+
|
|
127
|
+
def statistic(series):
|
|
128
|
+
return rosenstein_lyapunov(series, dim=5, tau=1, fit_fraction=0.3)
|
|
129
|
+
|
|
130
|
+
_observed, _surrogate_values, significance = surrogate_test(noise, statistic, n_surrogates=9, n_iter=20, seed=1)
|
|
131
|
+
assert significance < 2.0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
pv_module = pytest.importorskip("pyvista")
|
|
5
|
+
|
|
6
|
+
from physicskit.chaos.systems.continuous import Lorenz
|
|
7
|
+
from physicskit.chaos.visualizers.viewer3d import pyvista_trajectory
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_pyvista_trajectory_returns_plotter_with_expected_point_count():
|
|
11
|
+
system = Lorenz()
|
|
12
|
+
_, states = system.trajectory(n_steps=200, dt=0.01)
|
|
13
|
+
plotter = pyvista_trajectory(states, off_screen=True)
|
|
14
|
+
try:
|
|
15
|
+
assert isinstance(plotter, pv_module.Plotter)
|
|
16
|
+
image = plotter.screenshot()
|
|
17
|
+
assert image.ndim == 3 and image.shape[2] == 3
|
|
18
|
+
finally:
|
|
19
|
+
plotter.close()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_pyvista_trajectory_color_by_speed_runs_without_error():
|
|
23
|
+
system = Lorenz()
|
|
24
|
+
_, states = system.trajectory(n_steps=200, dt=0.01)
|
|
25
|
+
plotter = pyvista_trajectory(states, color_by="speed", off_screen=True)
|
|
26
|
+
try:
|
|
27
|
+
plotter.screenshot()
|
|
28
|
+
finally:
|
|
29
|
+
plotter.close()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_pyvista_trajectory_uses_selected_components():
|
|
33
|
+
states = np.column_stack([np.linspace(0, 1, 20), np.linspace(0, 2, 20), np.linspace(0, 3, 20), np.linspace(0, 4, 20)])
|
|
34
|
+
plotter = pyvista_trajectory(states, components=(1, 2, 3), off_screen=True)
|
|
35
|
+
try:
|
|
36
|
+
mesh = plotter.meshes[0]
|
|
37
|
+
np.testing.assert_allclose(mesh.points[:, 0], states[:, 1])
|
|
38
|
+
np.testing.assert_allclose(mesh.points[:, 1], states[:, 2])
|
|
39
|
+
np.testing.assert_allclose(mesh.points[:, 2], states[:, 3])
|
|
40
|
+
finally:
|
|
41
|
+
plotter.close()
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
from physicskit.chaos.utils.dimension import box_counting_dimension, correlation_dimension
|
|
2
|
+
from physicskit.chaos.utils.io import load_arrays, load_system_config, save_arrays, save_system_config
|
|
3
|
+
from physicskit.chaos.utils.metrics import (
|
|
4
|
+
benettin_lyapunov_spectrum,
|
|
5
|
+
energy_drift,
|
|
6
|
+
lyapunov_exponent_from_divergence,
|
|
7
|
+
map_lyapunov_spectrum,
|
|
8
|
+
numerical_jacobian,
|
|
9
|
+
numerical_jacobian_map,
|
|
10
|
+
phase_volume_expansion,
|
|
11
|
+
)
|
|
12
|
+
from physicskit.chaos.utils.recurrence import kac_lemma_estimate, recurrence_matrix, recurrence_times
|
|
13
|
+
from physicskit.chaos.utils.spectral import autocorrelation, power_spectrum
|
|
14
|
+
from physicskit.chaos.utils.timeseries import (
|
|
15
|
+
average_log_divergence,
|
|
16
|
+
delay_embed,
|
|
17
|
+
iaaft_surrogate,
|
|
18
|
+
rosenstein_lyapunov,
|
|
19
|
+
surrogate_test,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
__all__ = [
|
|
23
|
+
"autocorrelation",
|
|
24
|
+
"average_log_divergence",
|
|
25
|
+
"benettin_lyapunov_spectrum",
|
|
26
|
+
"box_counting_dimension",
|
|
27
|
+
"correlation_dimension",
|
|
28
|
+
"delay_embed",
|
|
29
|
+
"energy_drift",
|
|
30
|
+
"iaaft_surrogate",
|
|
31
|
+
"kac_lemma_estimate",
|
|
32
|
+
"load_arrays",
|
|
33
|
+
"load_system_config",
|
|
34
|
+
"lyapunov_exponent_from_divergence",
|
|
35
|
+
"map_lyapunov_spectrum",
|
|
36
|
+
"numerical_jacobian",
|
|
37
|
+
"numerical_jacobian_map",
|
|
38
|
+
"phase_volume_expansion",
|
|
39
|
+
"power_spectrum",
|
|
40
|
+
"recurrence_matrix",
|
|
41
|
+
"recurrence_times",
|
|
42
|
+
"rosenstein_lyapunov",
|
|
43
|
+
"save_arrays",
|
|
44
|
+
"save_system_config",
|
|
45
|
+
"surrogate_test",
|
|
46
|
+
]
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""Fractal dimension estimators: box-counting (capacity) and correlation dimension.
|
|
2
|
+
|
|
3
|
+
Both estimators quantify how the "size" of a point cloud (typically a sampled
|
|
4
|
+
strange attractor) scales as the measuring resolution ``epsilon`` shrinks,
|
|
5
|
+
and both reduce to the ordinary embedding dimension for a non-fractal set
|
|
6
|
+
(e.g. a filled disk in 2D gives dimension ~2, a line segment gives dimension
|
|
7
|
+
~1) but return a *non-integer* value for a genuine fractal attractor.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numpy.typing import ArrayLike, NDArray
|
|
14
|
+
from scipy.spatial.distance import pdist
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def box_counting_dimension(
|
|
18
|
+
points: ArrayLike,
|
|
19
|
+
n_scales: int = 20,
|
|
20
|
+
eps_min: float | None = None,
|
|
21
|
+
eps_max: float | None = None,
|
|
22
|
+
) -> tuple[float, NDArray[np.float64], NDArray[np.float64]]:
|
|
23
|
+
"""Estimate the box-counting (capacity) dimension ``D0`` of a point cloud.
|
|
24
|
+
|
|
25
|
+
Covers the bounding box of `points` with a grid of boxes of side
|
|
26
|
+
`epsilon`, counts the number of occupied boxes ``N(epsilon)`` over a
|
|
27
|
+
range of `epsilon`, and estimates ``D0`` as the slope of
|
|
28
|
+
``log(N(epsilon))`` vs. ``log(1/epsilon)`` (via ordinary least squares).
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
points : array_like of float, shape (n, dim)
|
|
33
|
+
Point cloud to measure (e.g. a sampled attractor trajectory).
|
|
34
|
+
n_scales : int, default 20
|
|
35
|
+
Number of `epsilon` values to evaluate, log-spaced between `eps_min`
|
|
36
|
+
and `eps_max`.
|
|
37
|
+
eps_min : float, optional
|
|
38
|
+
Smallest box size; defaults to ``1/200`` of the point cloud's extent.
|
|
39
|
+
eps_max : float, optional
|
|
40
|
+
Largest box size; defaults to ``1/2`` of the point cloud's extent.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
D0 : float
|
|
45
|
+
Estimated box-counting dimension.
|
|
46
|
+
epsilons : ndarray of float, shape (n_scales,)
|
|
47
|
+
The box sizes evaluated.
|
|
48
|
+
counts : ndarray of float, shape (n_scales,)
|
|
49
|
+
The number of occupied boxes ``N(epsilon)`` at each box size.
|
|
50
|
+
|
|
51
|
+
Notes
|
|
52
|
+
-----
|
|
53
|
+
Once `epsilon` is small enough that most occupied boxes contain only a
|
|
54
|
+
single point, ``N(epsilon)`` stops scaling like the true fractal measure
|
|
55
|
+
and instead approaches the point count itself, biasing ``D0`` low. If the
|
|
56
|
+
estimate looks suspiciously low (e.g. well under 2 for a point cloud that
|
|
57
|
+
should densely fill a 2D region), either supply a larger `eps_min` or use
|
|
58
|
+
more sample points so the default (finer) `eps_min` stays in the regime
|
|
59
|
+
where boxes still contain multiple points on average.
|
|
60
|
+
"""
|
|
61
|
+
points = np.atleast_2d(np.asarray(points, dtype=np.float64))
|
|
62
|
+
mins = points.min(axis=0)
|
|
63
|
+
extent = float(np.max(points.max(axis=0) - mins))
|
|
64
|
+
if eps_max is None:
|
|
65
|
+
eps_max = extent / 2.0
|
|
66
|
+
if eps_min is None:
|
|
67
|
+
eps_min = extent / 200.0
|
|
68
|
+
|
|
69
|
+
epsilons = np.geomspace(eps_max, eps_min, n_scales)
|
|
70
|
+
counts = np.empty(n_scales)
|
|
71
|
+
for i, eps in enumerate(epsilons):
|
|
72
|
+
box_idx = np.floor((points - mins) / eps).astype(np.int64)
|
|
73
|
+
counts[i] = np.unique(box_idx, axis=0).shape[0]
|
|
74
|
+
|
|
75
|
+
slope, _ = np.polyfit(np.log(1.0 / epsilons), np.log(counts), 1)
|
|
76
|
+
return float(slope), epsilons, counts
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def correlation_dimension(
|
|
80
|
+
points: ArrayLike,
|
|
81
|
+
n_scales: int = 20,
|
|
82
|
+
eps_min: float | None = None,
|
|
83
|
+
eps_max: float | None = None,
|
|
84
|
+
) -> tuple[float, NDArray[np.float64], NDArray[np.float64]]:
|
|
85
|
+
"""Estimate the correlation dimension ``D2`` via the Grassberger-Procaccia algorithm.
|
|
86
|
+
|
|
87
|
+
Computes the correlation sum ``C(epsilon)`` -- the fraction of all point
|
|
88
|
+
pairs closer together than `epsilon` -- over a range of `epsilon`, and
|
|
89
|
+
estimates ``D2`` as the slope of ``log(C(epsilon))`` vs. ``log(epsilon)``
|
|
90
|
+
(via ordinary least squares). ``D2`` is generally a slightly tighter
|
|
91
|
+
(never larger) estimate of an attractor's fractal dimension than the
|
|
92
|
+
box-counting ``D0``, and converges faster with the number of sample
|
|
93
|
+
points since it uses pairwise distances directly rather than a fixed grid.
|
|
94
|
+
|
|
95
|
+
Parameters
|
|
96
|
+
----------
|
|
97
|
+
points : array_like of float, shape (n, dim)
|
|
98
|
+
Point cloud to measure (e.g. a sampled attractor trajectory). Keep
|
|
99
|
+
`points` to at most a few thousand rows: this computes all
|
|
100
|
+
:math:`O(n^2)` pairwise distances.
|
|
101
|
+
n_scales : int, default 20
|
|
102
|
+
Number of `epsilon` values to evaluate, log-spaced between `eps_min`
|
|
103
|
+
and `eps_max`.
|
|
104
|
+
eps_min : float, optional
|
|
105
|
+
Smallest neighborhood radius; defaults to the 5th percentile of the
|
|
106
|
+
pairwise distances. The very smallest pairwise distances are
|
|
107
|
+
dominated by finite-sample discreteness rather than the attractor's
|
|
108
|
+
true fractal scaling, so a percentile well above the minimum gives a
|
|
109
|
+
more reliable default than the minimum itself.
|
|
110
|
+
eps_max : float, optional
|
|
111
|
+
Largest neighborhood radius; defaults to the 50th percentile (median)
|
|
112
|
+
of the pairwise distances. The largest distances are dominated by
|
|
113
|
+
``C(epsilon)`` saturating toward 1 (once `epsilon` exceeds the
|
|
114
|
+
attractor's overall diameter), which likewise biases a naive default.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
D2 : float
|
|
119
|
+
Estimated correlation dimension.
|
|
120
|
+
epsilons : ndarray of float, shape (n_scales,)
|
|
121
|
+
The neighborhood radii evaluated.
|
|
122
|
+
correlation_sums : ndarray of float, shape (n_scales,)
|
|
123
|
+
The correlation sum ``C(epsilon)`` at each radius.
|
|
124
|
+
"""
|
|
125
|
+
points = np.atleast_2d(np.asarray(points, dtype=np.float64))
|
|
126
|
+
pair_dists = pdist(points)
|
|
127
|
+
|
|
128
|
+
if eps_max is None:
|
|
129
|
+
eps_max = float(np.percentile(pair_dists, 50))
|
|
130
|
+
if eps_min is None:
|
|
131
|
+
eps_min = float(np.percentile(pair_dists[pair_dists > 0], 5))
|
|
132
|
+
|
|
133
|
+
epsilons = np.geomspace(eps_max, eps_min, n_scales)
|
|
134
|
+
n_pairs = pair_dists.shape[0]
|
|
135
|
+
correlation_sums = np.array([np.count_nonzero(pair_dists < eps) / n_pairs for eps in epsilons])
|
|
136
|
+
|
|
137
|
+
mask = correlation_sums > 0
|
|
138
|
+
slope, _ = np.polyfit(np.log(epsilons[mask]), np.log(correlation_sums[mask]), 1)
|
|
139
|
+
return float(slope), epsilons, correlation_sums
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""Save and load system configurations and array results (trajectories,
|
|
2
|
+
basin grids, Poincare sections, ...) to disk.
|
|
3
|
+
|
|
4
|
+
A system's configuration is recovered generically from its ``__init__``
|
|
5
|
+
signature (see :func:`physicskit.chaos.core.base_system.get_init_params`), the same
|
|
6
|
+
introspection that already powers every system's ``__repr__`` -- so
|
|
7
|
+
:func:`save_system_config` / :func:`load_system_config` work for any
|
|
8
|
+
concrete system in physicskit.chaos without a per-class serializer. Array results
|
|
9
|
+
are saved as plain ``.npz`` archives via :func:`save_arrays` /
|
|
10
|
+
:func:`load_arrays`.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import importlib
|
|
16
|
+
import json
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
from numpy.typing import NDArray
|
|
22
|
+
|
|
23
|
+
from physicskit.chaos.core.base_system import get_init_params
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def save_system_config(obj: object, path: str | Path) -> None:
|
|
27
|
+
"""Save a system's class and constructor parameters as JSON.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
obj : object
|
|
32
|
+
A physicskit.chaos system instance (or any object whose ``__init__``
|
|
33
|
+
parameters are stored under same-named attributes).
|
|
34
|
+
path : str or pathlib.Path
|
|
35
|
+
Destination ``.json`` file.
|
|
36
|
+
"""
|
|
37
|
+
params = {name: value.tolist() if isinstance(value, np.ndarray) else value for name, value in get_init_params(obj).items()}
|
|
38
|
+
config = {
|
|
39
|
+
"module": type(obj).__module__,
|
|
40
|
+
"class": type(obj).__qualname__,
|
|
41
|
+
"params": params,
|
|
42
|
+
}
|
|
43
|
+
Path(path).write_text(json.dumps(config, indent=2))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def load_system_config(path: str | Path) -> Any:
|
|
47
|
+
"""Reconstruct a system instance saved by :func:`save_system_config`.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
path : str or pathlib.Path
|
|
52
|
+
Source ``.json`` file.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
object
|
|
57
|
+
A new instance of the saved class, constructed from its saved
|
|
58
|
+
parameters.
|
|
59
|
+
"""
|
|
60
|
+
config = json.loads(Path(path).read_text())
|
|
61
|
+
module = importlib.import_module(config["module"])
|
|
62
|
+
cls = getattr(module, config["class"])
|
|
63
|
+
return cls(**config["params"])
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def save_arrays(path: str | Path, **arrays: NDArray[Any]) -> None:
|
|
67
|
+
"""Save named arrays (trajectories, grids, Poincare sections, ...) to a ``.npz`` archive.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
path : str or pathlib.Path
|
|
72
|
+
Destination ``.npz`` file.
|
|
73
|
+
**arrays
|
|
74
|
+
Arrays to save, keyed by name (e.g. ``save_arrays("run.npz", t=t,
|
|
75
|
+
states=states)``).
|
|
76
|
+
"""
|
|
77
|
+
np.savez(path, **arrays) # type: ignore[arg-type] # numpy stubs mistype **kwds here
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_arrays(path: str | Path) -> dict[str, NDArray[Any]]:
|
|
81
|
+
"""Load a ``.npz`` archive saved by :func:`save_arrays`.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
path : str or pathlib.Path
|
|
86
|
+
Source ``.npz`` file.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
dict
|
|
91
|
+
Mapping of array name to array, matching the keyword arguments
|
|
92
|
+
`save_arrays` was called with.
|
|
93
|
+
"""
|
|
94
|
+
with np.load(path) as data:
|
|
95
|
+
return {key: data[key] for key in data.files}
|