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,69 @@
|
|
|
1
|
+
"""Tests for physicskit.classical.systems.lagrangian's concrete systems and,
|
|
2
|
+
via them, the LagrangianSystem base class's own defaults that
|
|
3
|
+
test_conservation.py's energy-conservation checks never reach: direct
|
|
4
|
+
calls to ``acceleration()``/``derivatives()`` (every conservation test
|
|
5
|
+
goes through ``integrate()``, which calls the underlying njit
|
|
6
|
+
callbacks directly rather than through those convenience methods),
|
|
7
|
+
``ElasticPendulum.positions()``, and the ``rk4``/unknown-method
|
|
8
|
+
branches of ``LagrangianSystem.integrate()`` (every existing test uses
|
|
9
|
+
the default ``implicit_midpoint``).
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from physicskit.classical.systems.lagrangian import BeadOnRotatingHoop, ElasticPendulum
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_bead_on_rotating_hoop_acceleration_matches_euler_lagrange_by_hand():
|
|
21
|
+
"""theta'' = Omega^2 sin(theta) cos(theta) - (g/R) sin(theta), derived
|
|
22
|
+
by hand from the Lagrangian in the class docstring."""
|
|
23
|
+
system = BeadOnRotatingHoop(0.3, 0.1, R=1.5, omega=2.0, g=9.81)
|
|
24
|
+
theta, thetadot = system.q, system.qdot
|
|
25
|
+
accel = system.acceleration(theta, thetadot, 0.0)
|
|
26
|
+
expected = system.omega**2 * np.sin(theta) * np.cos(theta) - (system.g / system.R) * np.sin(theta)
|
|
27
|
+
np.testing.assert_allclose(accel, expected, rtol=1e-6)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_bead_on_rotating_hoop_derivatives_matches_qdot_and_acceleration():
|
|
31
|
+
system = BeadOnRotatingHoop(0.3, 0.1, R=1.5, omega=2.0, g=9.81)
|
|
32
|
+
d = system.derivatives(0.0, system.state)
|
|
33
|
+
np.testing.assert_allclose(d[:1], system.qdot)
|
|
34
|
+
np.testing.assert_allclose(d[1:], system.acceleration(system.q, system.qdot, 0.0))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_lagrangian_system_integrate_rk4_matches_implicit_midpoint_over_short_time():
|
|
38
|
+
"""rk4 is not symplectic, but over a short-enough window both
|
|
39
|
+
integrators should agree closely -- the standard cross-check that
|
|
40
|
+
the rk4 branch (which uses the direct (q, qdot) acceleration form,
|
|
41
|
+
not the canonical Hamiltonian form implicit_midpoint uses) is wired
|
|
42
|
+
correctly."""
|
|
43
|
+
dt = 1e-4
|
|
44
|
+
result_rk4 = BeadOnRotatingHoop(0.3, 0.0, omega=3.0).integrate((0.0, 0.05), dt=dt, method="rk4")
|
|
45
|
+
result_im = BeadOnRotatingHoop(0.3, 0.0, omega=3.0).integrate((0.0, 0.05), dt=dt, method="implicit_midpoint")
|
|
46
|
+
assert np.max(np.abs(result_rk4.q - result_im.q)) < 1e-5
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_lagrangian_system_integrate_rejects_unknown_method():
|
|
50
|
+
system = BeadOnRotatingHoop(0.3, 0.0, omega=3.0)
|
|
51
|
+
with pytest.raises(ValueError):
|
|
52
|
+
system.integrate((0.0, 1.0), dt=0.1, method="bogus")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_elastic_pendulum_positions_matches_polar_to_cartesian_by_hand():
|
|
56
|
+
system = ElasticPendulum([0.2, 0.3], [0.0, 0.0], L0=1.0)
|
|
57
|
+
x, y = system.positions()
|
|
58
|
+
s, theta = system.q
|
|
59
|
+
r = system.L0 + s
|
|
60
|
+
assert x == pytest.approx(r * np.sin(theta))
|
|
61
|
+
assert y == pytest.approx(-r * np.cos(theta))
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_elastic_pendulum_positions_defaults_to_current_state_but_accepts_override():
|
|
65
|
+
system = ElasticPendulum([0.2, 0.3], [0.0, 0.0], L0=1.0)
|
|
66
|
+
default = system.positions()
|
|
67
|
+
overridden = system.positions(np.array([0.0, 0.0]))
|
|
68
|
+
np.testing.assert_allclose(overridden, [0.0, -1.0])
|
|
69
|
+
assert not np.allclose(default, overridden)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""Tests for physicskit.classical.systems.newtonian helpers that
|
|
2
|
+
test_conservation.py's energy-conservation checks never call directly:
|
|
3
|
+
ProjectileMotion.range_and_max_height() (a standalone closed-form
|
|
4
|
+
helper, not used by any trajectory check), and FoucaultPendulum's
|
|
5
|
+
position/velocity convenience properties and precession_period()."""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from physicskit.classical.systems.newtonian import FoucaultPendulum, ProjectileMotion
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_range_and_max_height_matches_analytic_trajectory_peak_and_landing():
|
|
16
|
+
"""Cross-check the closed-form range/max-height formula against the
|
|
17
|
+
same speed/angle's full analytic_trajectory(), by finding where that
|
|
18
|
+
trajectory peaks and lands."""
|
|
19
|
+
speed, angle_deg, g = 20.0, 30.0, 9.81
|
|
20
|
+
rng, max_height = ProjectileMotion.range_and_max_height(speed, angle_deg, g)
|
|
21
|
+
|
|
22
|
+
t = np.linspace(0.0, 5.0, 200_000)
|
|
23
|
+
x, y = ProjectileMotion.analytic_trajectory(speed, angle_deg, g, t)
|
|
24
|
+
assert max_height == pytest.approx(y.max(), abs=1e-3)
|
|
25
|
+
landing_idx = np.argmax(y[1:] < 0.0) + 1
|
|
26
|
+
assert rng == pytest.approx(x[landing_idx], rel=1e-3)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_range_and_max_height_with_initial_height_raises_both():
|
|
30
|
+
rng_ground, height_ground = ProjectileMotion.range_and_max_height(15.0, 45.0, 9.81, height=0.0)
|
|
31
|
+
rng_elevated, height_elevated = ProjectileMotion.range_and_max_height(15.0, 45.0, 9.81, height=10.0)
|
|
32
|
+
assert rng_elevated > rng_ground
|
|
33
|
+
assert height_elevated == pytest.approx(height_ground + 10.0)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_foucault_pendulum_position_and_velocity_properties_match_state():
|
|
37
|
+
system = FoucaultPendulum(q0=[1.0, 2.0], v0=[0.3, -0.4])
|
|
38
|
+
np.testing.assert_allclose(system.position, [1.0, 2.0])
|
|
39
|
+
np.testing.assert_allclose(system.velocity, [0.3, -0.4])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_foucault_pendulum_precession_period_matches_sidereal_day_over_sin_latitude():
|
|
43
|
+
system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.0, 0.0], latitude_deg=48.85)
|
|
44
|
+
expected = (2.0 * np.pi / system.omega_earth) / np.sin(np.deg2rad(48.85))
|
|
45
|
+
assert system.precession_period() == pytest.approx(expected)
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Targeted checks for physicskit.classical.systems.rotations beyond energy conservation
|
|
2
|
+
(covered in test_conservation.py): the closed-form/harmonic-approximation
|
|
3
|
+
precession and nutation frequencies for HeavySymmetricTop, validated
|
|
4
|
+
against an FFT of an actual integrated trajectory.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from physicskit.classical.systems.rotations import (
|
|
13
|
+
EulersDisk,
|
|
14
|
+
EulerTop,
|
|
15
|
+
HeavySymmetricTop,
|
|
16
|
+
Rattleback,
|
|
17
|
+
eulers_disk_theta_analytic,
|
|
18
|
+
find_theta_equilibrium,
|
|
19
|
+
nutation_frequency,
|
|
20
|
+
precession_frequency,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _measured_nutation_frequency(t: np.ndarray, theta_t: np.ndarray) -> float:
|
|
25
|
+
dt = t[1] - t[0]
|
|
26
|
+
spectrum = np.abs(np.fft.rfft(theta_t - theta_t.mean()))
|
|
27
|
+
freqs = np.fft.rfftfreq(len(theta_t), d=dt) * 2.0 * np.pi
|
|
28
|
+
peak_idx = np.argmax(spectrum[1:]) + 1
|
|
29
|
+
return float(freqs[peak_idx])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_theta_equilibrium_is_a_true_minimum_of_effective_potential():
|
|
33
|
+
I1, I3, Mgl = 1.0, 0.5, 9.81
|
|
34
|
+
p_phi, p_psi = 8.78, 10.0
|
|
35
|
+
theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
|
|
36
|
+
assert 0.0 < theta_eq < np.pi
|
|
37
|
+
|
|
38
|
+
from physicskit.classical.systems.rotations import effective_potential_symmetric_top
|
|
39
|
+
|
|
40
|
+
v_at_eq = effective_potential_symmetric_top(theta_eq, p_phi, p_psi, I1, I3, Mgl)
|
|
41
|
+
for delta in (-0.05, 0.05):
|
|
42
|
+
assert effective_potential_symmetric_top(theta_eq + delta, p_phi, p_psi, I1, I3, Mgl) > v_at_eq
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_nutation_frequency_without_explicit_theta_eq_matches_passing_it_explicitly():
|
|
46
|
+
"""theta_eq is optional and, if omitted, is located internally via
|
|
47
|
+
find_theta_equilibrium -- check that path gives the same answer as
|
|
48
|
+
passing the same equilibrium explicitly."""
|
|
49
|
+
I1, I3, Mgl = 1.0, 0.5, 9.81
|
|
50
|
+
p_phi, p_psi = 8.78, 10.0
|
|
51
|
+
theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
|
|
52
|
+
explicit = nutation_frequency(p_phi, p_psi, I1, I3, Mgl, theta_eq=theta_eq)
|
|
53
|
+
implicit = nutation_frequency(p_phi, p_psi, I1, I3, Mgl)
|
|
54
|
+
assert implicit == pytest.approx(explicit)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_heavy_symmetric_top_precession_nutation_rate_matches_qdot():
|
|
58
|
+
top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.2, -0.1, 0.3], I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81)
|
|
59
|
+
phidot, thetadot = top.precession_nutation_rate()
|
|
60
|
+
assert phidot == pytest.approx(top.qdot[0])
|
|
61
|
+
assert thetadot == pytest.approx(top.qdot[1])
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_euler_top_omega_and_quaternion_properties_match_state():
|
|
65
|
+
omega0 = [0.01, 1.0, 0.01]
|
|
66
|
+
quat0 = [1.0, 0.0, 0.0, 0.0]
|
|
67
|
+
top = EulerTop(omega0, I1=1.0, I2=2.0, I3=3.0, quat0=quat0)
|
|
68
|
+
np.testing.assert_allclose(top.omega, omega0)
|
|
69
|
+
np.testing.assert_allclose(top.quaternion, quat0)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_eulers_disk_theta_and_phi_properties_match_state():
|
|
73
|
+
disk = EulersDisk(0.5, phi0=1.2, decay_rate=0.02, precession_const=1.0)
|
|
74
|
+
assert disk.theta == pytest.approx(0.5)
|
|
75
|
+
assert disk.phi == pytest.approx(1.2)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.slow
|
|
79
|
+
def test_nutation_frequency_matches_fft_of_real_trajectory():
|
|
80
|
+
I1, I3, M, l, g = 1.0, 0.5, 1.0, 1.0, 9.81
|
|
81
|
+
top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=I1, I3=I3, M=M, l=l, g=g)
|
|
82
|
+
q0, qdot0 = top.q.copy(), top.qdot.copy()
|
|
83
|
+
p_phi, _, p_psi = top.momentum(q0, qdot0)
|
|
84
|
+
|
|
85
|
+
theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, M * g * l)
|
|
86
|
+
predicted = nutation_frequency(p_phi, p_psi, I1, I3, M * g * l, theta_eq=theta_eq)
|
|
87
|
+
|
|
88
|
+
result = top.integrate((0, 10.0), dt=2e-5, method="implicit_midpoint")
|
|
89
|
+
measured = _measured_nutation_frequency(result.t, result.q[:, 1])
|
|
90
|
+
|
|
91
|
+
assert abs(measured - predicted) / predicted < 0.02
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@pytest.mark.slow
|
|
95
|
+
def test_precession_frequency_matches_mean_phi_rate():
|
|
96
|
+
I1, I3, M, l, g = 1.0, 0.5, 1.0, 1.0, 9.81
|
|
97
|
+
top = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=I1, I3=I3, M=M, l=l, g=g)
|
|
98
|
+
q0, qdot0 = top.q.copy(), top.qdot.copy()
|
|
99
|
+
p_phi, _, p_psi = top.momentum(q0, qdot0)
|
|
100
|
+
|
|
101
|
+
theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, M * g * l)
|
|
102
|
+
predicted = precession_frequency(p_phi, p_psi, I1, theta_eq)
|
|
103
|
+
|
|
104
|
+
result = top.integrate((0, 10.0), dt=2e-5, method="implicit_midpoint")
|
|
105
|
+
measured = (result.q[-1, 0] - result.q[0, 0]) / (result.t[-1] - result.t[0])
|
|
106
|
+
|
|
107
|
+
assert abs(measured - predicted) / abs(predicted) < 0.02
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_eulers_disk_theta_matches_closed_form_solution():
|
|
111
|
+
"""Well below the collapse time (where the regularizing theta_floor
|
|
112
|
+
never engages), the numerically integrated theta(t) should match the
|
|
113
|
+
closed-form solution `eulers_disk_theta_analytic`."""
|
|
114
|
+
theta0, decay_rate = 0.5, 0.02
|
|
115
|
+
t_collapse = theta0**2 / (2.0 * decay_rate)
|
|
116
|
+
|
|
117
|
+
disk = EulersDisk(theta0, decay_rate=decay_rate, precession_const=1.0, theta_floor=1e-4)
|
|
118
|
+
t_end = 0.5 * t_collapse
|
|
119
|
+
dt = 1e-3
|
|
120
|
+
result = disk.integrate((0.0, t_end), dt=dt, method="rk4")
|
|
121
|
+
|
|
122
|
+
predicted = eulers_disk_theta_analytic(result.t, theta0, decay_rate)
|
|
123
|
+
np.testing.assert_allclose(result.y[:, 0], predicted, atol=1e-3)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_eulers_disk_precession_speeds_up_as_it_runs_down():
|
|
127
|
+
"""The whole point of the model: phi advances faster and faster per
|
|
128
|
+
unit time as theta shrinks, since dphi/dt = precession_const/theta."""
|
|
129
|
+
disk = EulersDisk(0.5, decay_rate=0.02, precession_const=1.0, theta_floor=1e-4)
|
|
130
|
+
result = disk.integrate((0.0, 5.0), dt=1e-3, method="rk4")
|
|
131
|
+
phi_rate = np.diff(result.y[:, 1]) / np.diff(result.t)
|
|
132
|
+
# Later phi-rate (disk flatter, closer to collapse) must exceed the
|
|
133
|
+
# earlier phi-rate (disk still steep).
|
|
134
|
+
assert phi_rate[-1] > phi_rate[len(phi_rate) // 10]
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_rattleback_reverses_spin_direction():
|
|
138
|
+
"""Starting spun up hard about n3 with only a tiny rocking
|
|
139
|
+
perturbation, the toy model should show n3 collapse through zero and
|
|
140
|
+
become substantially negative (a genuine reversal), not merely decay
|
|
141
|
+
toward zero and stay positive."""
|
|
142
|
+
system = Rattleback([0.01, 0.01, 3.0])
|
|
143
|
+
result = system.integrate((0.0, 20.0), dt=5e-3, method="rk4")
|
|
144
|
+
n3 = result.y[:, 2]
|
|
145
|
+
assert n3[0] > 2.0
|
|
146
|
+
assert np.min(n3) < -0.5 # a substantial excursion to negative spin
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_rattleback_energy_decays_overall():
|
|
150
|
+
"""Despite the transient internal instability that drives the
|
|
151
|
+
reversal, the model is net dissipative (linear friction `mu` on all
|
|
152
|
+
three components): the quadratic 'energy' diagnostic averaged over the
|
|
153
|
+
first vs. last portion of a long run should show substantial decay."""
|
|
154
|
+
system = Rattleback([0.01, 0.01, 3.0])
|
|
155
|
+
result = system.integrate((0.0, 300.0), dt=5e-3, method="rk4")
|
|
156
|
+
energies = np.array([system.energy(y) for y in result.y])
|
|
157
|
+
n = len(energies)
|
|
158
|
+
early = np.mean(energies[: n // 20])
|
|
159
|
+
late = np.mean(energies[-n // 20 :])
|
|
160
|
+
assert late < 0.1 * early
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Tests for physicskit.classical.utils.stepsize.estimate_dt.
|
|
2
|
+
|
|
3
|
+
The critical property this checks is not just "it finds a dt," but
|
|
4
|
+
that the returned dt genuinely meets the tolerance at the *requested*
|
|
5
|
+
step count, including for a chaotic system where a naive short probe
|
|
6
|
+
can find a dt that looks fine briefly and then fails badly later.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from physicskit.classical.systems.lagrangian import DoublePendulum
|
|
15
|
+
from physicskit.classical.systems.newtonian import KeplerSystem
|
|
16
|
+
from physicskit.classical.utils.conservation import relative_energy_drift
|
|
17
|
+
from physicskit.classical.utils.stepsize import estimate_dt
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _FakeSystemNeverConvergesAtProbe:
|
|
21
|
+
"""Deterministically fails the fast probe stage at every dt, however
|
|
22
|
+
small -- used to exercise estimate_dt's shrink-loop-exhausted error
|
|
23
|
+
path without relying on a real system happening to never converge."""
|
|
24
|
+
|
|
25
|
+
def energy(self):
|
|
26
|
+
return 1.0
|
|
27
|
+
|
|
28
|
+
def integrate(self, t_span, dt, method):
|
|
29
|
+
del t_span, dt, method
|
|
30
|
+
result = type("Result", (), {})()
|
|
31
|
+
result.energy = np.array([1.0, 11.0])
|
|
32
|
+
return result
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class _FakeSystemNeverConvergesAtTarget:
|
|
36
|
+
"""Passes the fast probe at any dt, but fails verification at the
|
|
37
|
+
full target_steps at every dt -- used to exercise estimate_dt's
|
|
38
|
+
verification-stage error path (the case the module docstring
|
|
39
|
+
describes: a dt that looks fine briefly but fails over a longer
|
|
40
|
+
run) without depending on a real chaotic system's exact behavior."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, probe_steps):
|
|
43
|
+
self.probe_steps = probe_steps
|
|
44
|
+
|
|
45
|
+
def energy(self):
|
|
46
|
+
return 1.0
|
|
47
|
+
|
|
48
|
+
def integrate(self, t_span, dt, method):
|
|
49
|
+
del method
|
|
50
|
+
n_steps = round((t_span[1] - t_span[0]) / dt)
|
|
51
|
+
result = type("Result", (), {})()
|
|
52
|
+
result.energy = np.array([1.0, 1.0]) if n_steps <= self.probe_steps else np.array([1.0, 11.0])
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_estimate_dt_quasi_periodic_kepler():
|
|
57
|
+
tol = 1e-6
|
|
58
|
+
est = estimate_dt(
|
|
59
|
+
lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
|
|
60
|
+
method="yoshida4",
|
|
61
|
+
tol=tol,
|
|
62
|
+
target_steps=20_000,
|
|
63
|
+
probe_steps=1000,
|
|
64
|
+
dt_initial=1e-2,
|
|
65
|
+
)
|
|
66
|
+
assert est.achieved_drift < tol
|
|
67
|
+
|
|
68
|
+
system = KeplerSystem.from_orbital_elements(a=1.0, e=0.5)
|
|
69
|
+
result = system.integrate((0.0, 20_000 * est.dt), dt=est.dt, method="yoshida4")
|
|
70
|
+
assert np.max(relative_energy_drift(result.energy)) < tol
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_estimate_dt_bracket_search_grows_dt_across_several_doublings():
|
|
74
|
+
"""A deliberately tiny dt_initial forces the fast-probe bracket
|
|
75
|
+
search to succeed across several successive doublings before it
|
|
76
|
+
finally fails, rather than failing on the very first growth step."""
|
|
77
|
+
est = estimate_dt(
|
|
78
|
+
lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
|
|
79
|
+
method="yoshida4",
|
|
80
|
+
tol=1e-6,
|
|
81
|
+
target_steps=2000,
|
|
82
|
+
probe_steps=1000,
|
|
83
|
+
dt_initial=1e-4,
|
|
84
|
+
)
|
|
85
|
+
assert est.achieved_drift < 1e-6
|
|
86
|
+
assert est.n_evaluations > 5 # several bracket/verification evaluations, not just one
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_estimate_dt_bracket_search_shrinks_dt_across_several_halvings():
|
|
90
|
+
"""A deliberately oversized dt_initial forces the fast-probe bracket
|
|
91
|
+
search to fail across several successive halvings before it
|
|
92
|
+
finally succeeds, rather than succeeding on the very first shrink."""
|
|
93
|
+
est = estimate_dt(
|
|
94
|
+
lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5),
|
|
95
|
+
method="yoshida4",
|
|
96
|
+
tol=1e-6,
|
|
97
|
+
target_steps=2000,
|
|
98
|
+
probe_steps=1000,
|
|
99
|
+
dt_initial=0.1024,
|
|
100
|
+
)
|
|
101
|
+
assert est.achieved_drift < 1e-6
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_estimate_dt_raises_when_probe_never_converges_within_max_iter():
|
|
105
|
+
with pytest.raises(RuntimeError, match="shrink steps"):
|
|
106
|
+
estimate_dt(
|
|
107
|
+
lambda: _FakeSystemNeverConvergesAtProbe(),
|
|
108
|
+
method="unused",
|
|
109
|
+
tol=1e-6,
|
|
110
|
+
target_steps=100,
|
|
111
|
+
probe_steps=10,
|
|
112
|
+
dt_initial=1.0,
|
|
113
|
+
max_iter=3,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_estimate_dt_raises_when_target_verification_never_converges():
|
|
118
|
+
with pytest.raises(RuntimeError, match="verification shrink steps"):
|
|
119
|
+
estimate_dt(
|
|
120
|
+
lambda: _FakeSystemNeverConvergesAtTarget(probe_steps=10),
|
|
121
|
+
method="unused",
|
|
122
|
+
tol=1e-6,
|
|
123
|
+
target_steps=100,
|
|
124
|
+
probe_steps=10,
|
|
125
|
+
dt_initial=1.0,
|
|
126
|
+
max_iter=3,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_estimate_dt_verification_catches_chaotic_underestimate():
|
|
131
|
+
"""A short probe alone can find a dt for the (chaotic) double
|
|
132
|
+
pendulum that passes over a few hundred steps but fails once run
|
|
133
|
+
for the full target_steps; estimate_dt must not return such a dt --
|
|
134
|
+
its own verification stage has to catch and shrink it."""
|
|
135
|
+
tol = 1e-6
|
|
136
|
+
target_steps = 20_000
|
|
137
|
+
est = estimate_dt(
|
|
138
|
+
lambda: DoublePendulum([2.0, 1.0], [0.5, -0.3]),
|
|
139
|
+
method="implicit_midpoint",
|
|
140
|
+
tol=tol,
|
|
141
|
+
target_steps=target_steps,
|
|
142
|
+
probe_steps=500,
|
|
143
|
+
dt_initial=1e-3,
|
|
144
|
+
)
|
|
145
|
+
assert est.achieved_drift < tol
|
|
146
|
+
|
|
147
|
+
system = DoublePendulum([2.0, 1.0], [0.5, -0.3])
|
|
148
|
+
result = system.integrate((0.0, target_steps * est.dt), dt=est.dt, method="implicit_midpoint")
|
|
149
|
+
assert np.max(relative_energy_drift(result.energy)) < tol
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Direct unit tests for physicskit.classical.utils.symbolic.LagrangianEngine, in
|
|
2
|
+
isolation from any physicskit.classical.systems class.
|
|
3
|
+
|
|
4
|
+
These exist specifically because of a real bug found during
|
|
5
|
+
development: a spurious sign flip in the Legendre-transform code
|
|
6
|
+
produced a self-consistent but unphysical (negative-definite kinetic
|
|
7
|
+
term) Hamiltonian for CoupledOscillators. It was NOT caught by that
|
|
8
|
+
system's own energy-conservation test, because implicit_midpoint
|
|
9
|
+
faithfully conserves whatever Hamiltonian it is handed, even a wrong
|
|
10
|
+
one -- energy conservation alone cannot distinguish a correctly-derived
|
|
11
|
+
Hamiltonian from a self-consistent but wrong one. The tests below check
|
|
12
|
+
LagrangianEngine's output against independently-known-correct physics
|
|
13
|
+
(closed-form accelerations, finite-difference Hamiltonian gradients),
|
|
14
|
+
which does catch that class of bug.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
import sympy as sp
|
|
21
|
+
|
|
22
|
+
from physicskit.classical.utils.symbolic import LagrangianEngine
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_simple_harmonic_oscillator_acceleration_and_hamiltonian_signs():
|
|
26
|
+
"""L = (m/2) qdot^2 - (k/2) q^2 must give qddot = -(k/m) q, p = m*qdot,
|
|
27
|
+
and H = p^2/(2m) + (k/2) q^2 with a POSITIVE kinetic term -- exactly
|
|
28
|
+
the sign the historical bug got backwards."""
|
|
29
|
+
m, k = 2.0, 3.0
|
|
30
|
+
q, qdot = sp.symbols("q qdot")
|
|
31
|
+
L = sp.Rational(1, 2) * m * qdot**2 - sp.Rational(1, 2) * k * q**2
|
|
32
|
+
|
|
33
|
+
engine = LagrangianEngine([q], [qdot], L)
|
|
34
|
+
|
|
35
|
+
q0, qdot0 = np.array([1.5]), np.array([0.7])
|
|
36
|
+
accel = engine.acceleration_njit(q0, qdot0, 0.0)
|
|
37
|
+
assert np.allclose(accel, [-(k / m) * q0[0]])
|
|
38
|
+
|
|
39
|
+
p0 = engine.momentum_njit(q0, qdot0)
|
|
40
|
+
assert np.allclose(p0, [m * qdot0[0]])
|
|
41
|
+
|
|
42
|
+
H = engine.hamiltonian_njit(q0, p0, 0.0)
|
|
43
|
+
expected_H = p0[0] ** 2 / (2 * m) + 0.5 * k * q0[0] ** 2
|
|
44
|
+
assert np.isclose(H, expected_H)
|
|
45
|
+
|
|
46
|
+
# The regression this guards against: a flipped Legendre transform
|
|
47
|
+
# sign gives H = -3*T + V instead of T + V, which for qdot != 0
|
|
48
|
+
# differs from the correct value by far more than float noise.
|
|
49
|
+
wrong_H = -3 * (0.5 * m * qdot0[0] ** 2) + 0.5 * k * q0[0] ** 2
|
|
50
|
+
assert not np.isclose(H, wrong_H)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_canonical_gradient_matches_finite_difference_of_hamiltonian():
|
|
54
|
+
"""For a non-trivial single pendulum, dH/dq and dH/dp from
|
|
55
|
+
canonical_deriv_njit must match a central finite difference of
|
|
56
|
+
hamiltonian_njit itself -- a general, formula-agnostic consistency
|
|
57
|
+
check that would have caught the historical sign bug regardless of
|
|
58
|
+
which specific system triggered it."""
|
|
59
|
+
m, l, g = 1.3, 0.8, 9.81
|
|
60
|
+
q, qdot = sp.symbols("q qdot")
|
|
61
|
+
L = sp.Rational(1, 2) * m * l**2 * qdot**2 - m * g * l * (1 - sp.cos(q))
|
|
62
|
+
|
|
63
|
+
engine = LagrangianEngine([q], [qdot], L)
|
|
64
|
+
|
|
65
|
+
q0, p0 = np.array([0.6]), np.array([0.9])
|
|
66
|
+
y0 = np.concatenate([q0, p0])
|
|
67
|
+
dy = engine.canonical_deriv_njit(0.0, y0)
|
|
68
|
+
dq_analytic, dp_analytic = dy[0], dy[1]
|
|
69
|
+
|
|
70
|
+
eps = 1e-6
|
|
71
|
+
|
|
72
|
+
def H(qa, pa):
|
|
73
|
+
return float(engine.hamiltonian_njit(np.array([qa]), np.array([pa]), 0.0))
|
|
74
|
+
|
|
75
|
+
dHdq_fd = (H(q0[0] + eps, p0[0]) - H(q0[0] - eps, p0[0])) / (2 * eps)
|
|
76
|
+
dHdp_fd = (H(q0[0], p0[0] + eps) - H(q0[0], p0[0] - eps)) / (2 * eps)
|
|
77
|
+
|
|
78
|
+
assert np.isclose(dq_analytic, dHdp_fd, atol=1e-6)
|
|
79
|
+
assert np.isclose(dp_analytic, -dHdq_fd, atol=1e-6)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_two_dof_coupled_system_matches_finite_difference():
|
|
83
|
+
"""Same finite-difference consistency check, but for a genuinely
|
|
84
|
+
coupled 2-DOF Lagrangian (unequal masses, linear coupling) -- the
|
|
85
|
+
structurally closest case to the CoupledOscillators bug."""
|
|
86
|
+
m1, m2, k = 1.7, 0.9, 2.5
|
|
87
|
+
q0s, q1s, w0s, w1s = sp.symbols("q0 q1 w0 w1")
|
|
88
|
+
L = sp.Rational(1, 2) * m1 * w0s**2 + sp.Rational(1, 2) * m2 * w1s**2 - sp.Rational(1, 2) * k * q0s**2 - sp.Rational(1, 2) * k * (q1s - q0s) ** 2
|
|
89
|
+
engine = LagrangianEngine([q0s, q1s], [w0s, w1s], L)
|
|
90
|
+
|
|
91
|
+
q0, p0 = np.array([0.3, -0.2]), np.array([0.5, 0.1])
|
|
92
|
+
y0 = np.concatenate([q0, p0])
|
|
93
|
+
dy = engine.canonical_deriv_njit(0.0, y0)
|
|
94
|
+
dq_analytic, dp_analytic = dy[:2], dy[2:]
|
|
95
|
+
|
|
96
|
+
eps = 1e-6
|
|
97
|
+
|
|
98
|
+
def H(qa, pa):
|
|
99
|
+
return float(engine.hamiltonian_njit(qa, pa, 0.0))
|
|
100
|
+
|
|
101
|
+
dHdq_fd = np.array([(H(q0 + eps * e, p0) - H(q0 - eps * e, p0)) / (2 * eps) for e in np.eye(2)])
|
|
102
|
+
dHdp_fd = np.array([(H(q0, p0 + eps * e) - H(q0, p0 - eps * e)) / (2 * eps) for e in np.eye(2)])
|
|
103
|
+
|
|
104
|
+
assert np.allclose(dq_analytic, dHdp_fd, atol=1e-6)
|
|
105
|
+
assert np.allclose(dp_analytic, -dHdq_fd, atol=1e-6)
|
|
106
|
+
|
|
107
|
+
# Momentum should be the classical p_i = m_i * qdot_i for this
|
|
108
|
+
# diagonal-mass-matrix system (a sign flip here would break it).
|
|
109
|
+
qdot0 = np.array([0.4, -0.1])
|
|
110
|
+
p_from_qdot = engine.momentum_njit(q0, qdot0)
|
|
111
|
+
assert np.allclose(p_from_qdot, [m1 * qdot0[0], m2 * qdot0[1]])
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def test_explicit_time_dependence():
|
|
115
|
+
"""A driven oscillator L = (1/2)qdot^2 - (1/2)q^2 + q*sin(t) must
|
|
116
|
+
give the textbook forced equation qddot = -q + sin(t)."""
|
|
117
|
+
t = sp.symbols("t")
|
|
118
|
+
q, qdot = sp.symbols("q qdot")
|
|
119
|
+
L = sp.Rational(1, 2) * qdot**2 - sp.Rational(1, 2) * q**2 + q * sp.sin(t)
|
|
120
|
+
|
|
121
|
+
engine = LagrangianEngine([q], [qdot], L, t=t)
|
|
122
|
+
|
|
123
|
+
q0, qdot0, t0 = np.array([0.4]), np.array([-0.2]), 1.1
|
|
124
|
+
accel = engine.acceleration_njit(q0, qdot0, t0)
|
|
125
|
+
assert np.allclose(accel, [-q0[0] + np.sin(t0)])
|