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,172 @@
|
|
|
1
|
+
"""Tests for physicskit.classical.visualizers, previously untested (the whole package
|
|
2
|
+
had only manual, un-persisted smoke checks during development). These
|
|
3
|
+
run headlessly (see conftest.py's Agg backend) and check both "does it
|
|
4
|
+
run without raising" and, where practical, that the output is actually
|
|
5
|
+
correct (e.g. Poincare-section crossings at known locations), not just
|
|
6
|
+
that a figure object came back.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
from matplotlib.animation import PillowWriter
|
|
14
|
+
|
|
15
|
+
from physicskit.classical.systems.chains import FPUTChain
|
|
16
|
+
from physicskit.classical.systems.hamiltonian import HenonHeilesSystem
|
|
17
|
+
from physicskit.classical.systems.lagrangian import DoublePendulum, ElasticPendulum
|
|
18
|
+
from physicskit.classical.systems.newtonian import KeplerSystem
|
|
19
|
+
from physicskit.classical.systems.rotations import EulersDisk, EulerTop, Rattleback
|
|
20
|
+
from physicskit.classical.visualizers.animations import (
|
|
21
|
+
SideBySideAnimator,
|
|
22
|
+
animate_elastic_pendulum,
|
|
23
|
+
animate_eulers_disk,
|
|
24
|
+
animate_rattleback,
|
|
25
|
+
animate_rigid_body_tumble,
|
|
26
|
+
orbit_trace_animation,
|
|
27
|
+
pendulum_animation,
|
|
28
|
+
)
|
|
29
|
+
from physicskit.classical.visualizers.interactive import interactive_orbit, interactive_so3_momentum_sphere
|
|
30
|
+
from physicskit.classical.visualizers.modal_analysis import animate_modal_energies, plot_modal_energy_bars
|
|
31
|
+
from physicskit.classical.visualizers.phase_space import (
|
|
32
|
+
plot_phase_portrait,
|
|
33
|
+
plot_phase_swarm,
|
|
34
|
+
plot_poincare_section,
|
|
35
|
+
plot_so3_momentum_sphere,
|
|
36
|
+
poincare_section,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_poincare_section_finds_known_crossings():
|
|
41
|
+
"""Synthetic q(t) = sin(t), p(t) = cos(t): q crosses zero going
|
|
42
|
+
upward (direction=+1, p>0) exactly at t = 0, 2*pi, 4*pi, ..."""
|
|
43
|
+
t = np.linspace(0, 4 * np.pi, 20_000)
|
|
44
|
+
q = np.sin(t)
|
|
45
|
+
p = np.cos(t)
|
|
46
|
+
|
|
47
|
+
cq, cp = poincare_section(q, p, section_q=q, section_p=p, value=0.0, direction=1)
|
|
48
|
+
|
|
49
|
+
assert len(cq) == 2 # crossings at t=0 (endpoint, not detected by the interior scan) is excluded; t=2pi, 4pi found
|
|
50
|
+
assert np.allclose(cq, 0.0, atol=1e-3)
|
|
51
|
+
assert np.allclose(cp, 1.0, atol=1e-3)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_plot_phase_portrait_1d_and_2d():
|
|
55
|
+
q1 = np.linspace(0, 1, 50)
|
|
56
|
+
p1 = np.cos(q1)
|
|
57
|
+
ax = plot_phase_portrait(q1, p1)
|
|
58
|
+
assert ax is not None
|
|
59
|
+
|
|
60
|
+
q2 = np.column_stack([q1, q1 * 2])
|
|
61
|
+
p2 = np.column_stack([p1, -p1])
|
|
62
|
+
ax2 = plot_phase_portrait(q2, p2, label="test")
|
|
63
|
+
assert len(ax2.lines) >= 2
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def test_plot_phase_swarm_and_poincare_wrapper():
|
|
67
|
+
q = np.random.default_rng(0).normal(size=200)
|
|
68
|
+
p = np.random.default_rng(1).normal(size=200)
|
|
69
|
+
ax = plot_phase_swarm(q, p)
|
|
70
|
+
assert ax is not None
|
|
71
|
+
|
|
72
|
+
hh = HenonHeilesSystem(np.array([0.0, 0.3]), np.array([0.3, 0.0]))
|
|
73
|
+
result = hh.integrate((0, 100), dt=0.01, method="yoshida4")
|
|
74
|
+
ax2 = plot_poincare_section(result.q[:, 0], result.p[:, 0], result.q[:, 1], result.p[:, 1], value=0.0, direction=1)
|
|
75
|
+
assert ax2 is not None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_plot_so3_momentum_sphere():
|
|
79
|
+
top = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
80
|
+
result = top.integrate((0, 10), dt=1e-3, method="implicit_midpoint")
|
|
81
|
+
ax = plot_so3_momentum_sphere(result.y[:, :3], I1=1.0, I2=2.0, I3=3.0)
|
|
82
|
+
assert ax is not None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.slow
|
|
86
|
+
def test_modal_energy_bars_and_animation(tmp_path):
|
|
87
|
+
chain = FPUTChain(n=16, beta=0.5, mode=1, amplitude=0.5)
|
|
88
|
+
result = chain.integrate((0, 5), dt=0.05, method="yoshida4") # short: this only needs a few frames
|
|
89
|
+
|
|
90
|
+
ax = plot_modal_energy_bars(chain, result.q[0], result.p[0])
|
|
91
|
+
assert len(ax.patches) == 16
|
|
92
|
+
|
|
93
|
+
anim = animate_modal_energies(chain, result, stride=10)
|
|
94
|
+
out = tmp_path / "modal_energy.gif"
|
|
95
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
96
|
+
assert out.exists() and out.stat().st_size > 0
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@pytest.mark.slow
|
|
100
|
+
def test_side_by_side_animator_pendulum_and_orbit(tmp_path):
|
|
101
|
+
dp = DoublePendulum([2.0, 1.0], [0.5, -0.3])
|
|
102
|
+
result = dp.integrate((0, 0.02), dt=1e-4, method="implicit_midpoint") # short: just needs a few frames
|
|
103
|
+
anim = pendulum_animation(result, dp.positions, mode="phase", stride=10)
|
|
104
|
+
assert isinstance(anim, SideBySideAnimator)
|
|
105
|
+
fig = anim.build()
|
|
106
|
+
assert fig is not None
|
|
107
|
+
out1 = tmp_path / "pendulum.gif"
|
|
108
|
+
anim.save(out1, writer=PillowWriter(fps=10))
|
|
109
|
+
assert out1.exists() and out1.stat().st_size > 0
|
|
110
|
+
|
|
111
|
+
kep = KeplerSystem.from_orbital_elements(a=1.0, e=0.4)
|
|
112
|
+
result2 = kep.integrate((0, 0.5), dt=1e-3, method="yoshida4")
|
|
113
|
+
anim2 = orbit_trace_animation(result2, mode="energy", stride=50)
|
|
114
|
+
out2 = tmp_path / "orbit.gif"
|
|
115
|
+
anim2.save(out2, writer=PillowWriter(fps=10))
|
|
116
|
+
assert out2.exists() and out2.stat().st_size > 0
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@pytest.mark.slow
|
|
120
|
+
def test_animate_elastic_pendulum_saves_gif(tmp_path):
|
|
121
|
+
system = ElasticPendulum([0.1, 0.05], [0.0, 0.0])
|
|
122
|
+
# short: animate_elastic_pendulum has no stride and renders one frame
|
|
123
|
+
# per sample, so this only needs enough samples for a valid animation
|
|
124
|
+
result = system.integrate((0, 0.1), dt=1e-3, method="implicit_midpoint")
|
|
125
|
+
anim = animate_elastic_pendulum(system, result)
|
|
126
|
+
out = tmp_path / "elastic_pendulum.gif"
|
|
127
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
128
|
+
assert out.exists() and out.stat().st_size > 0
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@pytest.mark.slow
|
|
132
|
+
def test_animate_rigid_body_tumble_saves_gif(tmp_path):
|
|
133
|
+
top = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
134
|
+
result = top.integrate((0, 2.0), dt=1e-3, method="implicit_midpoint")
|
|
135
|
+
anim = animate_rigid_body_tumble(top, result, stride=20)
|
|
136
|
+
out = tmp_path / "rigid_body_tumble.gif"
|
|
137
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
138
|
+
assert out.exists() and out.stat().st_size > 0
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@pytest.mark.slow
|
|
142
|
+
def test_animate_eulers_disk_saves_gif(tmp_path):
|
|
143
|
+
disk = EulersDisk(0.5, decay_rate=0.02, precession_const=1.0)
|
|
144
|
+
# short: animate_eulers_disk has no stride and renders one frame per
|
|
145
|
+
# sample (collapse time here is t_f=6.25, so 0.3 stays far from it)
|
|
146
|
+
result = disk.integrate((0.0, 0.15), dt=1e-3, method="rk4")
|
|
147
|
+
anim = animate_eulers_disk(disk, result)
|
|
148
|
+
out = tmp_path / "eulers_disk.gif"
|
|
149
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
150
|
+
assert out.exists() and out.stat().st_size > 0
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@pytest.mark.slow
|
|
154
|
+
def test_animate_rattleback_saves_gif(tmp_path):
|
|
155
|
+
system = Rattleback([0.01, 0.01, 3.0])
|
|
156
|
+
result = system.integrate((0.0, 8.0), dt=5e-3, method="rk4")
|
|
157
|
+
anim = animate_rattleback(system, result, stride=20)
|
|
158
|
+
out = tmp_path / "rattleback.gif"
|
|
159
|
+
anim.save(out, writer=PillowWriter(fps=10))
|
|
160
|
+
assert out.exists() and out.stat().st_size > 0
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_interactive_visualizers_return_populated_figures():
|
|
164
|
+
top = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
165
|
+
result = top.integrate((0, 10), dt=1e-3, method="implicit_midpoint")
|
|
166
|
+
fig = interactive_so3_momentum_sphere(result.y[:, :3], I1=1.0, I2=2.0, I3=3.0)
|
|
167
|
+
assert len(fig.data) == 3 # sphere surface + trajectory + start marker
|
|
168
|
+
|
|
169
|
+
kep = KeplerSystem.from_orbital_elements(a=1.0, e=0.3, c_pn=0.01)
|
|
170
|
+
result2 = kep.integrate((0, 50), dt=1e-3, method="yoshida4")
|
|
171
|
+
fig2 = interactive_orbit(result2, system=kep)
|
|
172
|
+
assert len(fig2.data) == 4 # orbit + focus + 2 LRL arrows
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from physicskit.classical.utils.conservation import (
|
|
2
|
+
angular_momentum_2d,
|
|
3
|
+
angular_momentum_drift,
|
|
4
|
+
energy_drift,
|
|
5
|
+
lrl_drift,
|
|
6
|
+
relative_energy_drift,
|
|
7
|
+
)
|
|
8
|
+
from physicskit.classical.utils.stepsize import StepSizeEstimate, estimate_dt
|
|
9
|
+
from physicskit.classical.utils.symbolic import LagrangianEngine
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"LagrangianEngine",
|
|
13
|
+
"energy_drift",
|
|
14
|
+
"relative_energy_drift",
|
|
15
|
+
"angular_momentum_2d",
|
|
16
|
+
"angular_momentum_drift",
|
|
17
|
+
"lrl_drift",
|
|
18
|
+
"StepSizeEstimate",
|
|
19
|
+
"estimate_dt",
|
|
20
|
+
]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Conservation-law diagnostics: energy drift, angular momentum, and the
|
|
2
|
+
Laplace-Runge-Lenz (LRL) vector.
|
|
3
|
+
|
|
4
|
+
These are thin, system-agnostic helpers that operate on the arrays
|
|
5
|
+
already produced by ``system.integrate(...)`` (a
|
|
6
|
+
:class:`physicskit.classical.core.base_system.SimulationResult`), used both by the
|
|
7
|
+
test suite (``tests/test_conservation.py``) and by the visualizers to
|
|
8
|
+
annotate plots with drift metrics.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"energy_drift",
|
|
17
|
+
"relative_energy_drift",
|
|
18
|
+
"angular_momentum_2d",
|
|
19
|
+
"angular_momentum_drift",
|
|
20
|
+
"lrl_drift",
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def energy_drift(energy: np.ndarray) -> np.ndarray:
|
|
25
|
+
"""Absolute energy deviation from the initial value.
|
|
26
|
+
|
|
27
|
+
Parameters
|
|
28
|
+
----------
|
|
29
|
+
energy : ndarray, shape (n_steps + 1,)
|
|
30
|
+
Energy trajectory, e.g. ``result.energy``.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
ndarray
|
|
35
|
+
``|H(t) - H(0)|`` at each sample.
|
|
36
|
+
"""
|
|
37
|
+
energy = np.asarray(energy, dtype=np.float64)
|
|
38
|
+
return np.abs(energy - energy[0])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def relative_energy_drift(energy: np.ndarray) -> np.ndarray:
|
|
42
|
+
"""Relative energy deviation from the initial value.
|
|
43
|
+
|
|
44
|
+
This is the quantity conventionally checked against a tolerance
|
|
45
|
+
(e.g. < 1e-6) to certify a symplectic integrator is behaving
|
|
46
|
+
correctly over long integrations.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
energy : ndarray, shape (n_steps + 1,)
|
|
51
|
+
Energy trajectory, e.g. ``result.energy``.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
ndarray
|
|
56
|
+
``|H(t) - H(0)| / |H(0)|`` at each sample; ``inf`` everywhere
|
|
57
|
+
if ``H(0)`` is exactly zero.
|
|
58
|
+
"""
|
|
59
|
+
energy = np.asarray(energy, dtype=np.float64)
|
|
60
|
+
e0 = energy[0]
|
|
61
|
+
if e0 == 0.0:
|
|
62
|
+
return np.full_like(energy, np.inf)
|
|
63
|
+
return np.abs(energy - e0) / np.abs(e0)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def angular_momentum_2d(q: np.ndarray, p: np.ndarray) -> np.ndarray:
|
|
67
|
+
"""Out-of-plane angular momentum for a planar trajectory.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
q, p : ndarray, shape (n_steps, 2)
|
|
72
|
+
Position and momentum trajectories.
|
|
73
|
+
|
|
74
|
+
Returns
|
|
75
|
+
-------
|
|
76
|
+
ndarray, shape (n_steps,)
|
|
77
|
+
``L = x*py - y*px`` at each sample.
|
|
78
|
+
"""
|
|
79
|
+
q = np.asarray(q, dtype=np.float64)
|
|
80
|
+
p = np.asarray(p, dtype=np.float64)
|
|
81
|
+
return q[:, 0] * p[:, 1] - q[:, 1] * p[:, 0]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def angular_momentum_drift(q: np.ndarray, p: np.ndarray) -> np.ndarray:
|
|
85
|
+
"""Absolute drift of the planar angular momentum from its initial value.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
q, p : ndarray, shape (n_steps, 2)
|
|
90
|
+
Position and momentum trajectories.
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
ndarray, shape (n_steps,)
|
|
95
|
+
``|L(t) - L(0)|`` at each sample.
|
|
96
|
+
"""
|
|
97
|
+
L = angular_momentum_2d(q, p)
|
|
98
|
+
return np.abs(L - L[0])
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def lrl_drift(system, q: np.ndarray, p: np.ndarray) -> np.ndarray:
|
|
102
|
+
"""Drift of the Laplace-Runge-Lenz vector's magnitude across a trajectory.
|
|
103
|
+
|
|
104
|
+
For the unperturbed 1/r potential the LRL vector is exactly
|
|
105
|
+
conserved (both magnitude and direction); its direction sweeping
|
|
106
|
+
out an angle over time is the visual signature of apsidal
|
|
107
|
+
precession once a perturbation is present, so magnitude drift
|
|
108
|
+
remaining small while direction changes is the expected, physically
|
|
109
|
+
correct behavior for a perturbed orbit.
|
|
110
|
+
|
|
111
|
+
Parameters
|
|
112
|
+
----------
|
|
113
|
+
system : object
|
|
114
|
+
A :class:`physicskit.classical.systems.newtonian.KeplerSystem`-like object
|
|
115
|
+
exposing ``lrl_vector(q, p) -> array``.
|
|
116
|
+
q, p : ndarray, shape (n_steps, 2)
|
|
117
|
+
Position and momentum trajectories.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
ndarray, shape (n_steps,)
|
|
122
|
+
``||A(t)| - |A(0)||`` at each sample.
|
|
123
|
+
"""
|
|
124
|
+
vecs = np.array([system.lrl_vector(qi, pi) for qi, pi in zip(q, p)])
|
|
125
|
+
mags = np.linalg.norm(vecs, axis=1)
|
|
126
|
+
return np.abs(mags - mags[0])
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Automated timestep selection.
|
|
2
|
+
|
|
3
|
+
None of physicskit.classical's integrators are adaptive -- Verlet, Yoshida4, and
|
|
4
|
+
implicit midpoint all take a single fixed ``dt`` for the whole run.
|
|
5
|
+
Choosing that ``dt`` by hand (integrate a trial run, check the energy
|
|
6
|
+
drift, halve or double, repeat) is exactly the kind of "black art" a
|
|
7
|
+
numerical library should not leave to its users. :func:`estimate_dt`
|
|
8
|
+
automates that search.
|
|
9
|
+
|
|
10
|
+
For a quasi-periodic system (e.g. an unperturbed Kepler orbit), the
|
|
11
|
+
characteristic energy-drift amplitude at a given ``dt`` is essentially
|
|
12
|
+
independent of how many periods you run for, so a short probe
|
|
13
|
+
integration is enough. **That is not true for chaotic systems**: a
|
|
14
|
+
short probe can find a ``dt`` that looks fine over a few hundred steps
|
|
15
|
+
but fails badly once the trajectory (e.g. a double pendulum) later
|
|
16
|
+
swings through a more demanding configuration than the probe window
|
|
17
|
+
happened to sample. To guard against exactly that,
|
|
18
|
+
:func:`estimate_dt` always re-verifies its candidate at the full
|
|
19
|
+
intended step count (``target_steps``) and shrinks ``dt`` further if
|
|
20
|
+
that longer run doesn't also meet the tolerance -- it does not just
|
|
21
|
+
trust the fast probe.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
__all__ = ["StepSizeEstimate", "estimate_dt"]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class StepSizeEstimate:
|
|
36
|
+
"""Result of :func:`estimate_dt`.
|
|
37
|
+
|
|
38
|
+
Attributes
|
|
39
|
+
----------
|
|
40
|
+
dt : float
|
|
41
|
+
The recommended, verified step size.
|
|
42
|
+
achieved_drift : float
|
|
43
|
+
Relative energy drift measured at ``dt`` over ``verified_at_steps``.
|
|
44
|
+
verified_at_steps : int
|
|
45
|
+
Step count the verification stage actually confirmed.
|
|
46
|
+
n_evaluations : int
|
|
47
|
+
Total number of trial integrations run during the search.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
dt: float
|
|
51
|
+
achieved_drift: float
|
|
52
|
+
verified_at_steps: int
|
|
53
|
+
n_evaluations: int
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _drift_for(system_factory, method, dt, n_steps) -> float:
|
|
57
|
+
system = system_factory()
|
|
58
|
+
e0 = system.energy()
|
|
59
|
+
result = system.integrate((0.0, n_steps * dt), dt=dt, method=method)
|
|
60
|
+
denom = abs(e0) if e0 != 0.0 else 1.0
|
|
61
|
+
return float(np.max(np.abs(result.energy - e0)) / denom)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def estimate_dt(
|
|
65
|
+
system_factory: Callable[[], object],
|
|
66
|
+
method: str,
|
|
67
|
+
tol: float = 1e-6,
|
|
68
|
+
target_steps: int = 100_000,
|
|
69
|
+
probe_steps: int = 2000,
|
|
70
|
+
dt_initial: float = 1e-2,
|
|
71
|
+
growth: float = 2.0,
|
|
72
|
+
rel_precision: float = 0.05,
|
|
73
|
+
max_iter: int = 40,
|
|
74
|
+
) -> StepSizeEstimate:
|
|
75
|
+
"""Find the largest ``dt`` for which ``target_steps`` of ``method``
|
|
76
|
+
keeps the relative energy drift below ``tol``.
|
|
77
|
+
|
|
78
|
+
A fast bracket-and-bisect search over a short ``probe_steps``
|
|
79
|
+
window finds an initial candidate; that candidate is then always
|
|
80
|
+
re-checked at the full ``target_steps`` and shrunk further (by
|
|
81
|
+
``growth``) if the longer run doesn't also clear ``tol`` -- see the
|
|
82
|
+
module docstring for why this verification stage matters for
|
|
83
|
+
chaotic systems.
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
system_factory : callable
|
|
88
|
+
Zero-argument callable returning a *fresh* system instance in
|
|
89
|
+
its initial state (a new instance each call, since
|
|
90
|
+
``integrate`` mutates state) -- e.g.
|
|
91
|
+
``lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5)``.
|
|
92
|
+
method : str
|
|
93
|
+
Integration method to pass to ``system.integrate`` (e.g.
|
|
94
|
+
``"yoshida4"`` or ``"implicit_midpoint"``).
|
|
95
|
+
tol : float
|
|
96
|
+
Target relative energy-drift tolerance,
|
|
97
|
+
``max|H(t) - H(0)| / |H(0)|``.
|
|
98
|
+
target_steps : int
|
|
99
|
+
The step count you actually intend to integrate for; the
|
|
100
|
+
returned ``dt`` is verified to meet ``tol`` over this many
|
|
101
|
+
steps, not just over the fast probe.
|
|
102
|
+
probe_steps : int
|
|
103
|
+
Length of each *fast* bracketing/bisection trial, in steps.
|
|
104
|
+
Should cover at least a few characteristic oscillation periods.
|
|
105
|
+
dt_initial : float
|
|
106
|
+
Starting guess for the fast probe stage.
|
|
107
|
+
growth : float
|
|
108
|
+
Factor used both to bracket a pass/fail boundary during the
|
|
109
|
+
fast probe stage, and to shrink ``dt`` during verification.
|
|
110
|
+
rel_precision : float
|
|
111
|
+
Stop bisecting once the probe-stage bracket
|
|
112
|
+
``[dt_good, dt_bad]`` has ``(dt_bad - dt_good) / dt_good``
|
|
113
|
+
below this.
|
|
114
|
+
max_iter : int
|
|
115
|
+
Safety cap on iterations, applied separately to the
|
|
116
|
+
bracketing, bisection, and verification stages.
|
|
117
|
+
|
|
118
|
+
Returns
|
|
119
|
+
-------
|
|
120
|
+
StepSizeEstimate
|
|
121
|
+
``.dt`` is the recommended, *verified* step size;
|
|
122
|
+
``.achieved_drift`` is the relative drift measured at that
|
|
123
|
+
``dt`` over ``target_steps`` (i.e. what verification actually
|
|
124
|
+
confirmed, not the fast-probe estimate).
|
|
125
|
+
"""
|
|
126
|
+
n_evals = 0
|
|
127
|
+
|
|
128
|
+
def probe_drift(dt: float) -> float:
|
|
129
|
+
nonlocal n_evals
|
|
130
|
+
n_evals += 1
|
|
131
|
+
return _drift_for(system_factory, method, dt, probe_steps)
|
|
132
|
+
|
|
133
|
+
def target_drift(dt: float) -> float:
|
|
134
|
+
nonlocal n_evals
|
|
135
|
+
n_evals += 1
|
|
136
|
+
return _drift_for(system_factory, method, dt, target_steps)
|
|
137
|
+
|
|
138
|
+
# Stage 1: fast bracket + bisect over the short probe window.
|
|
139
|
+
dt = dt_initial
|
|
140
|
+
drift = probe_drift(dt)
|
|
141
|
+
|
|
142
|
+
if drift < tol:
|
|
143
|
+
dt_good, dt_bad = dt, None
|
|
144
|
+
for _ in range(max_iter):
|
|
145
|
+
dt *= growth
|
|
146
|
+
drift = probe_drift(dt)
|
|
147
|
+
if drift >= tol:
|
|
148
|
+
dt_bad = dt
|
|
149
|
+
break
|
|
150
|
+
dt_good = dt
|
|
151
|
+
else:
|
|
152
|
+
dt_bad = dt # never failed within max_iter growth steps; use the last value as the bracket edge
|
|
153
|
+
else:
|
|
154
|
+
dt_bad = dt
|
|
155
|
+
dt_good = None
|
|
156
|
+
for _ in range(max_iter):
|
|
157
|
+
dt /= growth
|
|
158
|
+
drift = probe_drift(dt)
|
|
159
|
+
if drift < tol:
|
|
160
|
+
dt_good = dt
|
|
161
|
+
break
|
|
162
|
+
dt_bad = dt
|
|
163
|
+
else:
|
|
164
|
+
raise RuntimeError(f"Could not find a dt meeting tol={tol:.1e} within {max_iter} shrink steps")
|
|
165
|
+
|
|
166
|
+
lo, hi = dt_good, dt_bad
|
|
167
|
+
for _ in range(max_iter):
|
|
168
|
+
if (hi - lo) / lo < rel_precision:
|
|
169
|
+
break
|
|
170
|
+
mid = np.sqrt(lo * hi)
|
|
171
|
+
if probe_drift(mid) < tol:
|
|
172
|
+
lo = mid
|
|
173
|
+
else:
|
|
174
|
+
hi = mid
|
|
175
|
+
|
|
176
|
+
# Stage 2: verify (and if necessary shrink further) at the full target_steps.
|
|
177
|
+
candidate = lo
|
|
178
|
+
for _ in range(max_iter):
|
|
179
|
+
drift = target_drift(candidate)
|
|
180
|
+
if drift < tol:
|
|
181
|
+
return StepSizeEstimate(dt=candidate, achieved_drift=drift, verified_at_steps=target_steps, n_evaluations=n_evals)
|
|
182
|
+
candidate /= growth
|
|
183
|
+
raise RuntimeError(
|
|
184
|
+
f"Could not find a dt meeting tol={tol:.1e} over target_steps={target_steps} within {max_iter} "
|
|
185
|
+
"verification shrink steps -- this system's worst-case error may need a much smaller dt than "
|
|
186
|
+
"usual (common for chaotic systems); try a smaller dt_initial."
|
|
187
|
+
)
|