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,614 @@
|
|
|
1
|
+
"""Rigid-body rotation systems: free tops and heavy symmetric tops.
|
|
2
|
+
|
|
3
|
+
:class:`EulerTop` integrates the torque-free rigid body (Euler's
|
|
4
|
+
equations for the body-frame angular velocity) together with the
|
|
5
|
+
orientation quaternion, and demonstrates the Intermediate Axis Theorem
|
|
6
|
+
(the "Dzhanibekov effect" / tennis-racket effect): rotation about the
|
|
7
|
+
axis of intermediate moment of inertia is unstable, while rotation
|
|
8
|
+
about the axes of largest and smallest moment is stable.
|
|
9
|
+
|
|
10
|
+
:class:`HeavySymmetricTop` is a symmetric top with one point fixed,
|
|
11
|
+
spinning under gravity -- derived symbolically (like the Lagrangian
|
|
12
|
+
systems) in the three Euler angles (phi, theta, psi), since its
|
|
13
|
+
Hamiltonian is intrinsically non-separable (the kinetic term's
|
|
14
|
+
coefficients depend on theta).
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from functools import lru_cache
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
import sympy as sp
|
|
23
|
+
from numba import njit
|
|
24
|
+
from scipy.optimize import minimize_scalar
|
|
25
|
+
|
|
26
|
+
from physicskit.classical.core.base_system import LagrangianSystem, ODESystem
|
|
27
|
+
from physicskit.classical.utils.symbolic import LagrangianEngine
|
|
28
|
+
|
|
29
|
+
__all__ = [
|
|
30
|
+
"EulerTop",
|
|
31
|
+
"HeavySymmetricTop",
|
|
32
|
+
"EulersDisk",
|
|
33
|
+
"Rattleback",
|
|
34
|
+
"effective_potential_symmetric_top",
|
|
35
|
+
"find_theta_equilibrium",
|
|
36
|
+
"nutation_frequency",
|
|
37
|
+
"precession_frequency",
|
|
38
|
+
"eulers_disk_theta_analytic",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ---------------------------------------------------------------------------
|
|
43
|
+
# Free asymmetric rigid body (Euler top)
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@lru_cache(maxsize=64)
|
|
48
|
+
def _make_euler_top_deriv(I1: float, I2: float, I3: float):
|
|
49
|
+
@njit(cache=False)
|
|
50
|
+
def deriv(t, y):
|
|
51
|
+
w1, w2, w3 = y[0], y[1], y[2]
|
|
52
|
+
qw, qx, qy, qz = y[3], y[4], y[5], y[6]
|
|
53
|
+
|
|
54
|
+
dw1 = (I2 - I3) / I1 * w2 * w3
|
|
55
|
+
dw2 = (I3 - I1) / I2 * w3 * w1
|
|
56
|
+
dw3 = (I1 - I2) / I3 * w1 * w2
|
|
57
|
+
|
|
58
|
+
# dq/dt = 0.5 * q (x) (0, w1, w2, w3) (Hamilton product, body-frame omega)
|
|
59
|
+
dqw = -0.5 * (qx * w1 + qy * w2 + qz * w3)
|
|
60
|
+
dqx = 0.5 * (qw * w1 + qy * w3 - qz * w2)
|
|
61
|
+
dqy = 0.5 * (qw * w2 + qz * w1 - qx * w3)
|
|
62
|
+
dqz = 0.5 * (qw * w3 + qx * w2 - qy * w1)
|
|
63
|
+
|
|
64
|
+
out = np.empty(7)
|
|
65
|
+
out[0], out[1], out[2] = dw1, dw2, dw3
|
|
66
|
+
out[3], out[4], out[5], out[6] = dqw, dqx, dqy, dqz
|
|
67
|
+
return out
|
|
68
|
+
|
|
69
|
+
return deriv
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class EulerTop(ODESystem):
|
|
73
|
+
"""Torque-free rigid body: Euler's equations for omega = (w1, w2, w3)
|
|
74
|
+
in the body frame, coupled to the orientation quaternion (qw, qx, qy, qz).
|
|
75
|
+
|
|
76
|
+
State: ``[w1, w2, w3, qw, qx, qy, qz]``. The default integrator,
|
|
77
|
+
``implicit_midpoint``, exactly conserves both the rotational kinetic
|
|
78
|
+
energy and the (squared) angular momentum magnitude and quaternion
|
|
79
|
+
norm -- all quadratic invariants of this quadratic ODE (a classical
|
|
80
|
+
result: the implicit midpoint / average-vector-field map preserves
|
|
81
|
+
every quadratic invariant of any ODE, not only linear ones).
|
|
82
|
+
|
|
83
|
+
Excite a spin dominantly about the intermediate-inertia axis (e.g.
|
|
84
|
+
``omega0 = [0.01, 1.0, 0.01]`` with ``I1 < I2 < I3``) to see the
|
|
85
|
+
periodic tumbling of the Intermediate Axis Theorem.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
omega0 : array-like, shape (3,)
|
|
90
|
+
Initial body-frame angular velocity.
|
|
91
|
+
I1, I2, I3 : float
|
|
92
|
+
Principal moments of inertia.
|
|
93
|
+
quat0 : array-like, shape (4,), optional
|
|
94
|
+
Initial orientation quaternion ``(w, x, y, z)``; defaults to
|
|
95
|
+
the identity orientation. Normalized automatically.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
def __init__(self, omega0, I1=1.0, I2=2.0, I3=3.0, quat0=None):
|
|
99
|
+
self.I1, self.I2, self.I3 = I1, I2, I3
|
|
100
|
+
omega0 = np.asarray(omega0, dtype=np.float64)
|
|
101
|
+
quat0 = np.array([1.0, 0.0, 0.0, 0.0]) if quat0 is None else np.asarray(quat0, dtype=np.float64)
|
|
102
|
+
quat0 = quat0 / np.linalg.norm(quat0)
|
|
103
|
+
self._deriv_njit = _make_euler_top_deriv(I1, I2, I3)
|
|
104
|
+
super().__init__(np.concatenate([omega0, quat0]))
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def omega(self) -> np.ndarray:
|
|
108
|
+
return self.state[:3]
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def quaternion(self) -> np.ndarray:
|
|
112
|
+
return self.state[3:]
|
|
113
|
+
|
|
114
|
+
def energy(self, state: np.ndarray = None) -> float:
|
|
115
|
+
"""Rotational kinetic energy.
|
|
116
|
+
|
|
117
|
+
Parameters
|
|
118
|
+
----------
|
|
119
|
+
state : ndarray, optional
|
|
120
|
+
State to evaluate; defaults to the current state.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
float
|
|
125
|
+
"""
|
|
126
|
+
state = self.state if state is None else state
|
|
127
|
+
w1, w2, w3 = state[0], state[1], state[2]
|
|
128
|
+
return 0.5 * (self.I1 * w1**2 + self.I2 * w2**2 + self.I3 * w3**2)
|
|
129
|
+
|
|
130
|
+
def angular_momentum_squared(self, state: np.ndarray = None) -> float:
|
|
131
|
+
"""``|L|^2`` in the body frame.
|
|
132
|
+
|
|
133
|
+
Parameters
|
|
134
|
+
----------
|
|
135
|
+
state : ndarray, optional
|
|
136
|
+
State to evaluate; defaults to the current state.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
float
|
|
141
|
+
"""
|
|
142
|
+
state = self.state if state is None else state
|
|
143
|
+
w1, w2, w3 = state[0], state[1], state[2]
|
|
144
|
+
return (self.I1 * w1) ** 2 + (self.I2 * w2) ** 2 + (self.I3 * w3) ** 2
|
|
145
|
+
|
|
146
|
+
def rotation_matrix(self, state: np.ndarray = None) -> np.ndarray:
|
|
147
|
+
"""3x3 body-to-world rotation matrix from the current quaternion.
|
|
148
|
+
|
|
149
|
+
Parameters
|
|
150
|
+
----------
|
|
151
|
+
state : ndarray, optional
|
|
152
|
+
State to evaluate; defaults to the current state.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
ndarray, shape (3, 3)
|
|
157
|
+
"""
|
|
158
|
+
state = self.state if state is None else state
|
|
159
|
+
qw, qx, qy, qz = state[3], state[4], state[5], state[6]
|
|
160
|
+
return np.array(
|
|
161
|
+
[
|
|
162
|
+
[1 - 2 * (qy**2 + qz**2), 2 * (qx * qy - qz * qw), 2 * (qx * qz + qy * qw)],
|
|
163
|
+
[2 * (qx * qy + qz * qw), 1 - 2 * (qx**2 + qz**2), 2 * (qy * qz - qx * qw)],
|
|
164
|
+
[2 * (qx * qz - qy * qw), 2 * (qy * qz + qx * qw), 1 - 2 * (qx**2 + qy**2)],
|
|
165
|
+
]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
# ---------------------------------------------------------------------------
|
|
170
|
+
# Heavy symmetric top (Lagrange top): fixed point under gravity
|
|
171
|
+
# ---------------------------------------------------------------------------
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@lru_cache(maxsize=32)
|
|
175
|
+
def _heavy_symmetric_top_engine(I1: float, I3: float, M: float, l: float, g: float) -> LagrangianEngine:
|
|
176
|
+
phi, theta, psi, phid, thd, psid = sp.symbols("phi theta psi phid thd psid")
|
|
177
|
+
I1s, I3s, Ms, ls, gs = sp.symbols("I1 I3 M l g")
|
|
178
|
+
|
|
179
|
+
L = (
|
|
180
|
+
sp.Rational(1, 2) * I1s * (thd**2 + phid**2 * sp.sin(theta) ** 2)
|
|
181
|
+
+ sp.Rational(1, 2) * I3s * (psid + phid * sp.cos(theta)) ** 2
|
|
182
|
+
- Ms * gs * ls * sp.cos(theta)
|
|
183
|
+
)
|
|
184
|
+
params = {I1s: I1, I3s: I3, Ms: M, ls: l, gs: g}
|
|
185
|
+
return LagrangianEngine([phi, theta, psi], [phid, thd, psid], L, params=params)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class HeavySymmetricTop(LagrangianSystem):
|
|
189
|
+
"""Symmetric top with one point fixed, spinning under gravity.
|
|
190
|
+
|
|
191
|
+
Generalized coordinates q = (phi, theta, psi) -- the standard
|
|
192
|
+
z-x-z Euler angles (precession, nutation, spin). Lagrangian:
|
|
193
|
+
|
|
194
|
+
L = (I1/2)(thetadot^2 + phidot^2 sin^2(theta))
|
|
195
|
+
+ (I3/2)(psidot + phidot cos(theta))^2 - M*g*l*cos(theta)
|
|
196
|
+
|
|
197
|
+
where I1 is the transverse moment of inertia about the fixed point,
|
|
198
|
+
I3 the axial moment, M the mass, and l the distance from the pivot
|
|
199
|
+
to the center of mass. Because M(q) depends on theta, this is a
|
|
200
|
+
genuinely non-separable Hamiltonian system -- exactly the case
|
|
201
|
+
:class:`physicskit.classical.utils.symbolic.LagrangianEngine` and
|
|
202
|
+
``implicit_midpoint`` were built to handle.
|
|
203
|
+
|
|
204
|
+
Parameters
|
|
205
|
+
----------
|
|
206
|
+
angles0, angledots0 : array-like, shape (3,)
|
|
207
|
+
Initial ``(phi, theta, psi)`` and their time derivatives.
|
|
208
|
+
I1 : float
|
|
209
|
+
Transverse moment of inertia about the fixed point.
|
|
210
|
+
I3 : float
|
|
211
|
+
Axial moment of inertia.
|
|
212
|
+
M : float
|
|
213
|
+
Mass.
|
|
214
|
+
l : float
|
|
215
|
+
Distance from the pivot to the center of mass.
|
|
216
|
+
g : float
|
|
217
|
+
Gravitational acceleration.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def __init__(self, angles0, angledots0, I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81):
|
|
221
|
+
self.I1, self.I3, self.M, self.l, self.g = I1, I3, M, l, g
|
|
222
|
+
self.engine = _heavy_symmetric_top_engine(I1, I3, M, l, g)
|
|
223
|
+
self._accel_njit = self.engine.acceleration_njit
|
|
224
|
+
self._momentum_njit = self.engine.momentum_njit
|
|
225
|
+
self._canonical_deriv_njit = self.engine.canonical_deriv_njit
|
|
226
|
+
self._velocity_njit = self.engine.velocity_njit
|
|
227
|
+
self._deriv_njit = self.engine.full_deriv_njit
|
|
228
|
+
self._hamiltonian_njit = self.engine.hamiltonian_njit
|
|
229
|
+
|
|
230
|
+
super().__init__(angles0, angledots0)
|
|
231
|
+
|
|
232
|
+
def energy(self, state: np.ndarray = None) -> float:
|
|
233
|
+
state = self.state if state is None else state
|
|
234
|
+
q, qdot = self.split(state, self.ndof)
|
|
235
|
+
p = self._momentum_njit(q, qdot)
|
|
236
|
+
return float(self._hamiltonian_njit(q, p, 0.0))
|
|
237
|
+
|
|
238
|
+
def precession_nutation_rate(self, state: np.ndarray = None):
|
|
239
|
+
"""Instantaneous (phidot, thetadot) -- precession and nutation rates.
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
state : ndarray, optional
|
|
244
|
+
State to evaluate; defaults to the current state.
|
|
245
|
+
|
|
246
|
+
Returns
|
|
247
|
+
-------
|
|
248
|
+
phidot, thetadot : float
|
|
249
|
+
"""
|
|
250
|
+
state = self.state if state is None else state
|
|
251
|
+
_, qdot = self.split(state, self.ndof)
|
|
252
|
+
return float(qdot[0]), float(qdot[1])
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def effective_potential_symmetric_top(theta, p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float):
|
|
256
|
+
"""V_eff(theta) for the heavy symmetric top, given the two conserved
|
|
257
|
+
(cyclic-coordinate) momenta p_phi and p_psi:
|
|
258
|
+
|
|
259
|
+
V_eff(theta) = (p_phi - p_psi cos(theta))^2 / (2 I1 sin^2(theta))
|
|
260
|
+
+ p_psi^2 / (2 I3) + Mgl * cos(theta)
|
|
261
|
+
|
|
262
|
+
Motion in theta is confined to where the total energy E >= V_eff(theta);
|
|
263
|
+
the turning points bound the nutation range.
|
|
264
|
+
|
|
265
|
+
Parameters
|
|
266
|
+
----------
|
|
267
|
+
theta : array-like
|
|
268
|
+
Angle(s) to evaluate at.
|
|
269
|
+
p_phi, p_psi : float
|
|
270
|
+
Conserved (cyclic-coordinate) momenta.
|
|
271
|
+
I1, I3 : float
|
|
272
|
+
Transverse and axial moments of inertia.
|
|
273
|
+
Mgl : float
|
|
274
|
+
``M * g * l`` (mass times gravity times pivot-to-CM distance).
|
|
275
|
+
|
|
276
|
+
Returns
|
|
277
|
+
-------
|
|
278
|
+
ndarray
|
|
279
|
+
"""
|
|
280
|
+
theta = np.asarray(theta, dtype=np.float64)
|
|
281
|
+
return (p_phi - p_psi * np.cos(theta)) ** 2 / (2.0 * I1 * np.sin(theta) ** 2) + p_psi**2 / (2.0 * I3) + Mgl * np.cos(theta)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def find_theta_equilibrium(p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float, bracket=(1e-3, np.pi - 1e-3)) -> float:
|
|
285
|
+
"""The nutation angle theta_eq at which V_eff(theta) is minimized.
|
|
286
|
+
|
|
287
|
+
The angle steady (non-nutating) precession would hold at, and the
|
|
288
|
+
center about which a real top's nutation oscillates.
|
|
289
|
+
|
|
290
|
+
Parameters
|
|
291
|
+
----------
|
|
292
|
+
p_phi, p_psi : float
|
|
293
|
+
Conserved (cyclic-coordinate) momenta.
|
|
294
|
+
I1, I3 : float
|
|
295
|
+
Transverse and axial moments of inertia.
|
|
296
|
+
Mgl : float
|
|
297
|
+
``M * g * l``.
|
|
298
|
+
bracket : tuple of float
|
|
299
|
+
Search interval for the minimization.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
float
|
|
304
|
+
"""
|
|
305
|
+
result = minimize_scalar(
|
|
306
|
+
lambda th: effective_potential_symmetric_top(th, p_phi, p_psi, I1, I3, Mgl),
|
|
307
|
+
bounds=bracket,
|
|
308
|
+
method="bounded",
|
|
309
|
+
)
|
|
310
|
+
return float(result.x)
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def nutation_frequency(p_phi: float, p_psi: float, I1: float, I3: float, Mgl: float, theta_eq: float = None, dtheta: float = 1e-5) -> float:
|
|
314
|
+
"""Small-oscillation nutation frequency about ``theta_eq``.
|
|
315
|
+
|
|
316
|
+
Treating theta as a 1-DOF particle of "mass" I1 moving in the
|
|
317
|
+
effective potential V_eff (see :func:`effective_potential_symmetric_top`),
|
|
318
|
+
the harmonic-approximation frequency about its minimum is
|
|
319
|
+
``sqrt(V_eff''(theta_eq) / I1)``, found here by a central finite
|
|
320
|
+
difference. If ``theta_eq`` is not supplied it is located with
|
|
321
|
+
:func:`find_theta_equilibrium`.
|
|
322
|
+
|
|
323
|
+
Parameters
|
|
324
|
+
----------
|
|
325
|
+
p_phi, p_psi : float
|
|
326
|
+
Conserved (cyclic-coordinate) momenta.
|
|
327
|
+
I1, I3 : float
|
|
328
|
+
Transverse and axial moments of inertia.
|
|
329
|
+
Mgl : float
|
|
330
|
+
``M * g * l``.
|
|
331
|
+
theta_eq : float, optional
|
|
332
|
+
Equilibrium angle; computed via :func:`find_theta_equilibrium`
|
|
333
|
+
if omitted.
|
|
334
|
+
dtheta : float
|
|
335
|
+
Finite-difference step.
|
|
336
|
+
|
|
337
|
+
Returns
|
|
338
|
+
-------
|
|
339
|
+
float
|
|
340
|
+
"""
|
|
341
|
+
if theta_eq is None:
|
|
342
|
+
theta_eq = find_theta_equilibrium(p_phi, p_psi, I1, I3, Mgl)
|
|
343
|
+
V = lambda th: effective_potential_symmetric_top(th, p_phi, p_psi, I1, I3, Mgl) # noqa: E731
|
|
344
|
+
d2V = (V(theta_eq + dtheta) - 2.0 * V(theta_eq) + V(theta_eq - dtheta)) / dtheta**2
|
|
345
|
+
return float(np.sqrt(d2V / I1))
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
# ---------------------------------------------------------------------------
|
|
349
|
+
# Euler's disk: a rolling, spinning disk running down to a finite-time stop
|
|
350
|
+
# ---------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
@lru_cache(maxsize=32)
|
|
354
|
+
def _make_eulers_disk_deriv(decay_rate: float, precession_const: float, theta_floor: float):
|
|
355
|
+
@njit(cache=False)
|
|
356
|
+
def deriv(t, y):
|
|
357
|
+
theta = y[0]
|
|
358
|
+
theta_c = theta if theta > theta_floor else theta_floor
|
|
359
|
+
out = np.empty(2)
|
|
360
|
+
out[0] = -decay_rate / theta_c
|
|
361
|
+
out[1] = precession_const / theta_c
|
|
362
|
+
return out
|
|
363
|
+
|
|
364
|
+
return deriv
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class EulersDisk(ODESystem):
|
|
368
|
+
"""Simplified rolling-disk-with-dissipation model of "Euler's disk".
|
|
369
|
+
|
|
370
|
+
A coin or disk spun and set rolling on a table settles into a
|
|
371
|
+
near-steady state described, at each instant, by just its inclination
|
|
372
|
+
angle ``theta`` (between the disk plane and the table) and a precession
|
|
373
|
+
angle ``phi`` (the azimuthal angle of the rolling contact point around
|
|
374
|
+
the disk's resting position). As real Euler's-disk demonstrations
|
|
375
|
+
strikingly show, the contact point's precession visibly speeds up
|
|
376
|
+
(and the characteristic rattling sound rises in pitch) right up until
|
|
377
|
+
the disk suddenly stops -- a *finite-time singularity*, famously
|
|
378
|
+
analyzed by Moffatt (2000, Nature 404, 833) for an idealized
|
|
379
|
+
viscous-dissipation mechanism. The exact dissipation mechanism (viscous
|
|
380
|
+
air drag vs. rolling friction vs. contact-point slipping) and even the
|
|
381
|
+
precise power-law exponent are still debated in the literature (e.g.
|
|
382
|
+
the response by Van den Engh et al., 2004); rather than committing to
|
|
383
|
+
one first-principles derivation, this class uses a standard *reduced*
|
|
384
|
+
phenomenological pair of ODEs that reproduces the same qualitative
|
|
385
|
+
finite-time-collapse behavior:
|
|
386
|
+
|
|
387
|
+
.. math::
|
|
388
|
+
|
|
389
|
+
\\dot\\theta = -\\text{decay\\_rate} / \\theta, \\qquad
|
|
390
|
+
\\dot\\phi = \\text{precession\\_const} / \\theta
|
|
391
|
+
|
|
392
|
+
i.e. both the precession rate and (by assumption) the dissipation rate
|
|
393
|
+
diverge as ``theta -> 0``, which is exactly what drives theta to zero
|
|
394
|
+
in finite time. Ignoring the regularizing `theta_floor`, the first
|
|
395
|
+
equation integrates in closed form to
|
|
396
|
+
``theta(t) = sqrt(theta0**2 - 2*decay_rate*t)`` (see
|
|
397
|
+
:func:`eulers_disk_theta_analytic`), vanishing at the finite collapse
|
|
398
|
+
time ``t_f = theta0**2 / (2*decay_rate)``. `theta_floor` clips both
|
|
399
|
+
right-hand sides near ``theta = 0`` so the (unphysical) literal
|
|
400
|
+
singularity never actually has to be integrated through.
|
|
401
|
+
|
|
402
|
+
State: ``[theta, phi]``.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
theta0 : float
|
|
407
|
+
Initial inclination angle (radians), measured from horizontal
|
|
408
|
+
(``theta = pi/2`` would be an upright, unspun disk; realistic
|
|
409
|
+
"rattling" motion is at small `theta0`, e.g. 0.2-0.6 rad).
|
|
410
|
+
phi0 : float, default 0.0
|
|
411
|
+
Initial precession angle.
|
|
412
|
+
decay_rate : float, default 0.05
|
|
413
|
+
Rate constant governing how fast theta collapses; sets the
|
|
414
|
+
collapse time ``t_f = theta0**2 / (2*decay_rate)``.
|
|
415
|
+
precession_const : float, default 1.0
|
|
416
|
+
Rate constant governing the precession rate at a given theta.
|
|
417
|
+
theta_floor : float, default 1e-3
|
|
418
|
+
Regularizing floor for `theta` in both right-hand sides.
|
|
419
|
+
|
|
420
|
+
Attributes
|
|
421
|
+
----------
|
|
422
|
+
decay_rate, precession_const, theta_floor : float
|
|
423
|
+
System parameters.
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
def __init__(self, theta0: float, phi0: float = 0.0, decay_rate: float = 0.05, precession_const: float = 1.0, theta_floor: float = 1e-3):
|
|
427
|
+
self.decay_rate, self.precession_const, self.theta_floor = decay_rate, precession_const, theta_floor
|
|
428
|
+
self._deriv_njit = _make_eulers_disk_deriv(decay_rate, precession_const, theta_floor)
|
|
429
|
+
super().__init__(np.array([theta0, phi0], dtype=np.float64))
|
|
430
|
+
|
|
431
|
+
@property
|
|
432
|
+
def theta(self) -> float:
|
|
433
|
+
return self.state[0]
|
|
434
|
+
|
|
435
|
+
@property
|
|
436
|
+
def phi(self) -> float:
|
|
437
|
+
return self.state[1]
|
|
438
|
+
|
|
439
|
+
def energy(self, state: np.ndarray = None) -> float:
|
|
440
|
+
"""Not a true conserved energy -- the whole point of this model is
|
|
441
|
+
dissipation. Returns `theta` itself, a monotonically decreasing
|
|
442
|
+
proxy that (like the disk's actual energy) vanishes at the
|
|
443
|
+
finite-time collapse, just so this fits the common ``energy()``
|
|
444
|
+
diagnostic slot shared by every :class:`~physicskit.classical.core.base_system.ODESystem`.
|
|
445
|
+
|
|
446
|
+
Parameters
|
|
447
|
+
----------
|
|
448
|
+
state : ndarray, optional
|
|
449
|
+
State to evaluate; defaults to the current state.
|
|
450
|
+
|
|
451
|
+
Returns
|
|
452
|
+
-------
|
|
453
|
+
float
|
|
454
|
+
"""
|
|
455
|
+
state = self.state if state is None else state
|
|
456
|
+
return float(state[0])
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def eulers_disk_theta_analytic(t, theta0: float, decay_rate: float) -> np.ndarray:
|
|
460
|
+
"""Closed-form ``theta(t)`` for :class:`EulersDisk`, ignoring the
|
|
461
|
+
regularizing floor: solves ``dtheta/dt = -decay_rate/theta`` exactly as
|
|
462
|
+
``theta(t) = sqrt(theta0**2 - 2*decay_rate*t)``, valid up to the finite
|
|
463
|
+
collapse time ``t_f = theta0**2 / (2*decay_rate)`` (clipped to 0
|
|
464
|
+
thereafter).
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
t : array-like
|
|
469
|
+
Time(s) to evaluate at.
|
|
470
|
+
theta0 : float
|
|
471
|
+
Initial inclination angle.
|
|
472
|
+
decay_rate : float
|
|
473
|
+
Decay-rate constant; see :class:`EulersDisk`.
|
|
474
|
+
|
|
475
|
+
Returns
|
|
476
|
+
-------
|
|
477
|
+
ndarray
|
|
478
|
+
"""
|
|
479
|
+
t = np.asarray(t, dtype=np.float64)
|
|
480
|
+
return np.sqrt(np.clip(theta0**2 - 2.0 * decay_rate * t, 0.0, None))
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
# ---------------------------------------------------------------------------
|
|
484
|
+
# Rattleback (celt stone): a toy model of one-way spin instability/reversal
|
|
485
|
+
# ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
@lru_cache(maxsize=32)
|
|
489
|
+
def _make_rattleback_deriv(I1: float, I2: float, I3: float, gamma: float, mu: float, eta: float):
|
|
490
|
+
@njit(cache=False)
|
|
491
|
+
def deriv(t, y):
|
|
492
|
+
n1, n2, n3 = y[0], y[1], y[2]
|
|
493
|
+
dn1 = (I2 - I3) / I1 * n2 * n3 + gamma * n3 * n3 - mu * n1 - eta * n1**3
|
|
494
|
+
dn2 = (I3 - I1) / I2 * n3 * n1 - mu * n2 - eta * n2**3
|
|
495
|
+
dn3 = (I1 - I2) / I3 * n1 * n2 - mu * n3
|
|
496
|
+
out = np.empty(3)
|
|
497
|
+
out[0], out[1], out[2] = dn1, dn2, dn3
|
|
498
|
+
return out
|
|
499
|
+
|
|
500
|
+
return deriv
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
class Rattleback(ODESystem):
|
|
504
|
+
"""Toy reduced model of rattleback (Celt stone) spin reversal.
|
|
505
|
+
|
|
506
|
+
A rattleback -- a boat-shaped top whose principal axes of inertia are
|
|
507
|
+
slightly misaligned from its geometric (contact-ellipsoid) axes --
|
|
508
|
+
famously spins stably in one sense but, when spun the other way,
|
|
509
|
+
wobbles with growing amplitude until it stops and reverses into the
|
|
510
|
+
stable sense. The true mechanism is the nonholonomic rolling-without-
|
|
511
|
+
slipping contact-point dynamics of a rigid ellipsoidal body on a plane
|
|
512
|
+
(see Garcia & Hubbard, "Spin reversal of the rattleback: theory and
|
|
513
|
+
experiment," Proc. R. Soc. Lond. A 418, 1988), which requires tracking
|
|
514
|
+
the body's full orientation, the moving contact point, and the no-slip
|
|
515
|
+
constraint.
|
|
516
|
+
|
|
517
|
+
**This class does not do that.** It is a deliberately simplified toy
|
|
518
|
+
model built directly on :class:`EulerTop`'s free-rigid-body equations
|
|
519
|
+
for reduced state variables ``(n1, n2, n3)`` -- ``n3`` playing the role
|
|
520
|
+
of spin about the (roughly vertical) axis, ``n1, n2`` the two
|
|
521
|
+
"rocking"/tipping-mode rates -- with two modifications standing in for
|
|
522
|
+
the real contact-point physics:
|
|
523
|
+
|
|
524
|
+
1. A term ``gamma * n3**2``, added to the ``n1`` equation only. The
|
|
525
|
+
real rolling constraint on a rattleback whose principal-inertia axes
|
|
526
|
+
are misaligned (by some small angle) from its geometric axes
|
|
527
|
+
couples the vertical spin asymmetrically into the two rocking
|
|
528
|
+
modes; adding a term that is even in ``n3`` to only *one* of the two
|
|
529
|
+
symmetric rocking equations is the minimal way to break the
|
|
530
|
+
``n3 -> -n3`` symmetry an ordinary (non-reversing) Euler top has --
|
|
531
|
+
exactly the qualitative signature of a rattleback: spin one way
|
|
532
|
+
pumps energy into rocking, spin the other way does not.
|
|
533
|
+
2. Linear damping (`mu`) on all three components plus cubic,
|
|
534
|
+
Landau-type self-saturation (`eta`) on ``n1, n2``, so the rocking
|
|
535
|
+
amplitude the instability pumps up saturates rather than diverging,
|
|
536
|
+
and the whole system eventually settles to rest -- as a real
|
|
537
|
+
rattleback, slowed by genuine friction, does.
|
|
538
|
+
|
|
539
|
+
With `gamma` = 0 this reduces to a damped :class:`EulerTop` and never
|
|
540
|
+
reverses; for `gamma` large enough relative to the damping, a spin
|
|
541
|
+
started predominantly in ``n3`` visibly collapses, overshoots into
|
|
542
|
+
negative ``n3`` (the reversal), and then decays -- reproducing the
|
|
543
|
+
qualitative phenomenology (decelerate, wobble, reverse, decay to rest)
|
|
544
|
+
without claiming quantitative accuracy for any specific physical body.
|
|
545
|
+
|
|
546
|
+
State: ``[n1, n2, n3]``.
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
n0 : array-like, shape (3,)
|
|
551
|
+
Initial ``(n1, n2, n3)``; e.g. ``[0.01, 0.01, 3.0]`` models a
|
|
552
|
+
rattleback spun up hard about its ``n3`` axis with only a tiny
|
|
553
|
+
rocking perturbation.
|
|
554
|
+
I1, I2, I3 : float
|
|
555
|
+
Principal moments of inertia (same role as in :class:`EulerTop`).
|
|
556
|
+
gamma : float, default 1.0
|
|
557
|
+
Strength of the symmetry-breaking rolling-constraint correction.
|
|
558
|
+
mu : float, default 0.01
|
|
559
|
+
Linear (frictional) damping rate on all three components.
|
|
560
|
+
eta : float, default 0.01
|
|
561
|
+
Cubic self-saturation coefficient on ``n1, n2``.
|
|
562
|
+
|
|
563
|
+
Attributes
|
|
564
|
+
----------
|
|
565
|
+
I1, I2, I3, gamma, mu, eta : float
|
|
566
|
+
System parameters.
|
|
567
|
+
"""
|
|
568
|
+
|
|
569
|
+
def __init__(self, n0, I1: float = 1.0, I2: float = 1.5, I3: float = 2.0, gamma: float = 1.0, mu: float = 0.01, eta: float = 0.01):
|
|
570
|
+
self.I1, self.I2, self.I3 = I1, I2, I3
|
|
571
|
+
self.gamma, self.mu, self.eta = gamma, mu, eta
|
|
572
|
+
self._deriv_njit = _make_rattleback_deriv(I1, I2, I3, gamma, mu, eta)
|
|
573
|
+
super().__init__(np.asarray(n0, dtype=np.float64))
|
|
574
|
+
|
|
575
|
+
def energy(self, state: np.ndarray = None) -> float:
|
|
576
|
+
"""``0.5*(I1*n1^2 + I2*n2^2 + I3*n3^2)``: not conserved (this
|
|
577
|
+
system is dissipative and, transiently, unstable by construction),
|
|
578
|
+
but the natural quadratic diagnostic for watching the overall
|
|
579
|
+
motion decay toward rest.
|
|
580
|
+
|
|
581
|
+
Parameters
|
|
582
|
+
----------
|
|
583
|
+
state : ndarray, optional
|
|
584
|
+
State to evaluate; defaults to the current state.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
float
|
|
589
|
+
"""
|
|
590
|
+
state = self.state if state is None else state
|
|
591
|
+
n1, n2, n3 = state[0], state[1], state[2]
|
|
592
|
+
return 0.5 * (self.I1 * n1**2 + self.I2 * n2**2 + self.I3 * n3**2)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def precession_frequency(p_phi: float, p_psi: float, I1: float, theta_eq: float) -> float:
|
|
596
|
+
"""Mean precession rate phidot at nutation angle ``theta_eq``:
|
|
597
|
+
``(p_phi - p_psi*cos(theta_eq)) / (I1*sin(theta_eq)**2)`` -- the
|
|
598
|
+
standard steady-precession-rate approximation, exact when the top
|
|
599
|
+
is not nutating at all (theta held fixed at theta_eq).
|
|
600
|
+
|
|
601
|
+
Parameters
|
|
602
|
+
----------
|
|
603
|
+
p_phi, p_psi : float
|
|
604
|
+
Conserved (cyclic-coordinate) momenta.
|
|
605
|
+
I1 : float
|
|
606
|
+
Transverse moment of inertia.
|
|
607
|
+
theta_eq : float
|
|
608
|
+
Nutation angle to evaluate at.
|
|
609
|
+
|
|
610
|
+
Returns
|
|
611
|
+
-------
|
|
612
|
+
float
|
|
613
|
+
"""
|
|
614
|
+
return float((p_phi - p_psi * np.cos(theta_eq)) / (I1 * np.sin(theta_eq) ** 2))
|
|
File without changes
|