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,536 @@
|
|
|
1
|
+
"""Newtonian vector-dynamics systems.
|
|
2
|
+
|
|
3
|
+
:class:`ProjectileMotion` is the classic "cannonball" problem -- free
|
|
4
|
+
fall under constant gravity with an oblique (angled) initial velocity,
|
|
5
|
+
the trajectory-is-a-parabola result at the historical root of
|
|
6
|
+
Newtonian dynamics -- with an optional quadratic air-drag term.
|
|
7
|
+
|
|
8
|
+
:class:`KeplerSystem` integrates a reduced 2-body problem in the
|
|
9
|
+
center-of-mass frame as a planar, separable Hamiltonian system in
|
|
10
|
+
Cartesian coordinates q = (x, y), p = (px, py), with::
|
|
11
|
+
|
|
12
|
+
H(q, p) = |p|^2 / (2 mu) + V(r), r = |q|
|
|
13
|
+
V(r) = -k / r + c_eps / r**(1 + eps) + c_pn / r**3
|
|
14
|
+
|
|
15
|
+
The ``c_eps`` term is an adjustable power-law perturbation to the pure
|
|
16
|
+
1/r potential; ``c_pn`` is an effective post-Newtonian correction that
|
|
17
|
+
reproduces the qualitative apsidal (perihelion) precession General
|
|
18
|
+
Relativity predicts for Mercury's orbit. Both terms are still purely
|
|
19
|
+
radial (velocity-independent), so the system remains conservative and
|
|
20
|
+
separable -- exactly integrable with the symplectic Verlet/Yoshida4
|
|
21
|
+
backends -- while the Laplace-Runge-Lenz vector, exactly conserved for
|
|
22
|
+
the pure Kepler problem, slowly precesses once either perturbation is
|
|
23
|
+
turned on.
|
|
24
|
+
|
|
25
|
+
:class:`FoucaultPendulum` is a small-oscillation pendulum viewed from
|
|
26
|
+
the rotating Earth: Newton's second law in a non-inertial frame picks
|
|
27
|
+
up a velocity-dependent Coriolis term that precesses the swing plane
|
|
28
|
+
without doing any work, so energy stays exactly conserved even as the
|
|
29
|
+
plane of swing rotates -- the effect Leon Foucault used in 1851 to
|
|
30
|
+
demonstrate Earth's rotation mechanically.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from __future__ import annotations
|
|
34
|
+
|
|
35
|
+
from functools import lru_cache
|
|
36
|
+
|
|
37
|
+
import numpy as np
|
|
38
|
+
from numba import njit
|
|
39
|
+
|
|
40
|
+
from physicskit.classical.core.base_system import HamiltonianSystem, ODESystem
|
|
41
|
+
|
|
42
|
+
__all__ = ["ProjectileMotion", "KeplerSystem", "FoucaultPendulum"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@lru_cache(maxsize=64)
|
|
46
|
+
def _make_projectile_force(m: float, g: float):
|
|
47
|
+
@njit(cache=True)
|
|
48
|
+
def force(q, t):
|
|
49
|
+
out = np.empty(2)
|
|
50
|
+
out[0] = 0.0
|
|
51
|
+
out[1] = -m * g
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
return force
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@lru_cache(maxsize=64)
|
|
58
|
+
def _make_projectile_deriv_with_drag(m: float, g: float, drag_coeff: float):
|
|
59
|
+
"""Full (non-conservative) [dq, dp] derivative including quadratic
|
|
60
|
+
air drag, ``F_drag = -drag_coeff * |v| * v``, used by the 'rk4' and
|
|
61
|
+
'implicit_midpoint' fallback methods when drag_coeff != 0 (drag
|
|
62
|
+
depends on velocity/momentum, so it cannot enter a separable
|
|
63
|
+
force(q, t) and is invisible to the Verlet/Yoshida4 backends)."""
|
|
64
|
+
|
|
65
|
+
@njit(cache=False)
|
|
66
|
+
def deriv(t, y):
|
|
67
|
+
px, py = y[2], y[3]
|
|
68
|
+
vx, vy = px / m, py / m
|
|
69
|
+
speed = np.sqrt(vx * vx + vy * vy)
|
|
70
|
+
out = np.empty(4)
|
|
71
|
+
out[0] = vx
|
|
72
|
+
out[1] = vy
|
|
73
|
+
out[2] = -drag_coeff * speed * vx
|
|
74
|
+
out[3] = -m * g - drag_coeff * speed * vy
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
return deriv
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ProjectileMotion(HamiltonianSystem):
|
|
81
|
+
"""Free fall under constant gravity with an oblique initial velocity
|
|
82
|
+
-- the "cannonball" problem -- with optional quadratic air drag.
|
|
83
|
+
|
|
84
|
+
Without drag this is the separable Hamiltonian
|
|
85
|
+
``H(q, p) = |p|^2/(2m) + m*g*y``, q = (x, y), whose exact solution is
|
|
86
|
+
Galileo's parabolic trajectory (see :meth:`analytic_trajectory`);
|
|
87
|
+
because the force is constant, the symplectic Verlet/Yoshida4
|
|
88
|
+
backends reproduce it to machine precision at any step size.
|
|
89
|
+
|
|
90
|
+
With ``drag_coeff > 0`` a quadratic air-resistance force
|
|
91
|
+
``F = -drag_coeff * |v| * v`` is added. This makes the system
|
|
92
|
+
genuinely dissipative (energy decreases monotonically, there is no
|
|
93
|
+
closed-form trajectory), so it is only available through the
|
|
94
|
+
``rk4``/``implicit_midpoint`` methods -- ``verlet``/``yoshida4``
|
|
95
|
+
would silently ignore it (their force callback only sees the
|
|
96
|
+
conservative gravity term) and are therefore refused outright.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
q0, p0 : array-like, shape (2,)
|
|
101
|
+
Initial position and momentum, q = (x, y), p = (px, py).
|
|
102
|
+
m, g : float
|
|
103
|
+
Projectile mass and gravitational acceleration.
|
|
104
|
+
drag_coeff : float
|
|
105
|
+
Quadratic air-drag coefficient (0 for the vacuum/exact case).
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
separable = True
|
|
109
|
+
|
|
110
|
+
def __init__(self, q0, p0, m: float = 1.0, g: float = 9.81, drag_coeff: float = 0.0):
|
|
111
|
+
self.m, self.g, self.drag_coeff = m, g, drag_coeff
|
|
112
|
+
self.mass_inv = 1.0 / m
|
|
113
|
+
self._force_njit = _make_projectile_force(m, g)
|
|
114
|
+
if drag_coeff:
|
|
115
|
+
self._deriv_njit = _make_projectile_deriv_with_drag(m, g, drag_coeff)
|
|
116
|
+
super().__init__(q0, p0)
|
|
117
|
+
|
|
118
|
+
def integrate(self, t_span, dt: float, method: str = "yoshida4"):
|
|
119
|
+
if self.drag_coeff and method in ("verlet", "yoshida4"):
|
|
120
|
+
raise ValueError(
|
|
121
|
+
f"method='{method}' only sees the conservative force and would silently ignore drag_coeff != 0; use method='rk4' or 'implicit_midpoint' instead"
|
|
122
|
+
)
|
|
123
|
+
return super().integrate(t_span, dt, method=method)
|
|
124
|
+
|
|
125
|
+
def kinetic_energy(self, p: np.ndarray) -> float:
|
|
126
|
+
return float(np.dot(p, p)) / (2.0 * self.m)
|
|
127
|
+
|
|
128
|
+
def potential_energy(self, q: np.ndarray) -> float:
|
|
129
|
+
return self.m * self.g * q[1]
|
|
130
|
+
|
|
131
|
+
@classmethod
|
|
132
|
+
def from_launch(cls, speed: float, angle_deg: float, m: float = 1.0, g: float = 9.81, drag_coeff: float = 0.0, height: float = 0.0):
|
|
133
|
+
"""Construct a system launched from ``(0, height)`` -- the natural "aim the cannon" entry point.
|
|
134
|
+
|
|
135
|
+
Parameters
|
|
136
|
+
----------
|
|
137
|
+
speed : float
|
|
138
|
+
Launch speed.
|
|
139
|
+
angle_deg : float
|
|
140
|
+
Launch angle above the horizontal, in degrees.
|
|
141
|
+
m, g : float
|
|
142
|
+
Projectile mass and gravitational acceleration.
|
|
143
|
+
drag_coeff : float
|
|
144
|
+
Quadratic air-drag coefficient (0 for the vacuum/exact case).
|
|
145
|
+
height : float
|
|
146
|
+
Initial height.
|
|
147
|
+
|
|
148
|
+
Returns
|
|
149
|
+
-------
|
|
150
|
+
ProjectileMotion
|
|
151
|
+
"""
|
|
152
|
+
theta = np.deg2rad(angle_deg)
|
|
153
|
+
q0 = np.array([0.0, height])
|
|
154
|
+
p0 = m * speed * np.array([np.cos(theta), np.sin(theta)])
|
|
155
|
+
return cls(q0, p0, m=m, g=g, drag_coeff=drag_coeff)
|
|
156
|
+
|
|
157
|
+
@staticmethod
|
|
158
|
+
def analytic_trajectory(speed: float, angle_deg: float, g: float, t, height: float = 0.0):
|
|
159
|
+
"""Exact vacuum trajectory x(t), y(t) -- Galileo's parabola.
|
|
160
|
+
|
|
161
|
+
Used to validate the numerical integration (``drag_coeff=0`` only).
|
|
162
|
+
|
|
163
|
+
Parameters
|
|
164
|
+
----------
|
|
165
|
+
speed, angle_deg : float
|
|
166
|
+
Launch speed and angle above the horizontal, in degrees.
|
|
167
|
+
g : float
|
|
168
|
+
Gravitational acceleration.
|
|
169
|
+
t : array-like
|
|
170
|
+
Times at which to evaluate the trajectory.
|
|
171
|
+
height : float
|
|
172
|
+
Initial height.
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
x, y : ndarray
|
|
177
|
+
"""
|
|
178
|
+
theta = np.deg2rad(angle_deg)
|
|
179
|
+
t = np.asarray(t, dtype=np.float64)
|
|
180
|
+
vx0, vy0 = speed * np.cos(theta), speed * np.sin(theta)
|
|
181
|
+
x = vx0 * t
|
|
182
|
+
y = height + vy0 * t - 0.5 * g * t**2
|
|
183
|
+
return x, y
|
|
184
|
+
|
|
185
|
+
@staticmethod
|
|
186
|
+
def range_and_max_height(speed: float, angle_deg: float, g: float, height: float = 0.0):
|
|
187
|
+
"""Closed-form (vacuum) horizontal range and maximum height.
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
speed, angle_deg : float
|
|
192
|
+
Launch speed and angle above the horizontal, in degrees.
|
|
193
|
+
g : float
|
|
194
|
+
Gravitational acceleration.
|
|
195
|
+
height : float
|
|
196
|
+
Initial height.
|
|
197
|
+
|
|
198
|
+
Returns
|
|
199
|
+
-------
|
|
200
|
+
range, max_height : float
|
|
201
|
+
"""
|
|
202
|
+
theta = np.deg2rad(angle_deg)
|
|
203
|
+
vx0, vy0 = speed * np.cos(theta), speed * np.sin(theta)
|
|
204
|
+
t_apex = vy0 / g
|
|
205
|
+
max_height = height + vy0 * t_apex - 0.5 * g * t_apex**2
|
|
206
|
+
t_flight = (vy0 + np.sqrt(vy0**2 + 2.0 * g * height)) / g
|
|
207
|
+
rng = vx0 * t_flight
|
|
208
|
+
return float(rng), float(max_height)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@lru_cache(maxsize=64)
|
|
212
|
+
def _make_force_njit(k: float, eps: float, c_eps: float, c_pn: float, softening: float):
|
|
213
|
+
@njit(cache=False)
|
|
214
|
+
def force(q, t):
|
|
215
|
+
x, y = q[0], q[1]
|
|
216
|
+
r2 = x * x + y * y + softening * softening
|
|
217
|
+
r = np.sqrt(r2)
|
|
218
|
+
# dV/dr for V(r) = -k/r + c_eps * r**(-(1+eps)) + c_pn * r**(-3)
|
|
219
|
+
dVdr = k / r2 - c_eps * (1.0 + eps) * r ** (-(2.0 + eps)) - 3.0 * c_pn * r ** (-4.0)
|
|
220
|
+
# force = -dV/dq = -dVdr * (q / r)
|
|
221
|
+
fmag = -dVdr / r
|
|
222
|
+
out = np.empty(2)
|
|
223
|
+
out[0] = fmag * x
|
|
224
|
+
out[1] = fmag * y
|
|
225
|
+
return out
|
|
226
|
+
|
|
227
|
+
return force
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class KeplerSystem(HamiltonianSystem):
|
|
231
|
+
"""Planar 2-body Kepler problem with optional non-Newtonian perturbations.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
q0, p0 : array-like, shape (2,)
|
|
236
|
+
Initial position and (reduced-mass) momentum.
|
|
237
|
+
k : float
|
|
238
|
+
Gravitational coupling ``G * M * mu`` (standard Kepler strength).
|
|
239
|
+
mu : float
|
|
240
|
+
Reduced mass (sets kinetic energy ``T = |p|^2 / (2 mu)``).
|
|
241
|
+
eps, c_eps : float
|
|
242
|
+
Exponent and coefficient of an additional ``c_eps / r**(1+eps)``
|
|
243
|
+
perturbing potential (eps=0 reduces to an extra inverse-square term).
|
|
244
|
+
c_pn : float
|
|
245
|
+
Coefficient of the effective post-Newtonian ``c_pn / r**3``
|
|
246
|
+
correction driving perihelion precession.
|
|
247
|
+
softening : float
|
|
248
|
+
Small Plummer-style softening length to avoid a singular force
|
|
249
|
+
at r=0 (0 for the exact point-mass limit).
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
separable = True
|
|
253
|
+
|
|
254
|
+
def __init__(self, q0, p0, k=1.0, mu=1.0, eps=0.0, c_eps=0.0, c_pn=0.0, softening=0.0):
|
|
255
|
+
self.k = k
|
|
256
|
+
self.mu = mu
|
|
257
|
+
self.eps = eps
|
|
258
|
+
self.c_eps = c_eps
|
|
259
|
+
self.c_pn = c_pn
|
|
260
|
+
self.softening = softening
|
|
261
|
+
self.mass_inv = 1.0 / mu
|
|
262
|
+
self._force_njit = _make_force_njit(k, eps, c_eps, c_pn, softening)
|
|
263
|
+
super().__init__(q0, p0)
|
|
264
|
+
|
|
265
|
+
def kinetic_energy(self, p: np.ndarray) -> float:
|
|
266
|
+
return float(np.dot(p, p)) / (2.0 * self.mu)
|
|
267
|
+
|
|
268
|
+
def potential_energy(self, q: np.ndarray) -> float:
|
|
269
|
+
r = np.sqrt(q[0] ** 2 + q[1] ** 2 + self.softening**2)
|
|
270
|
+
return -self.k / r + self.c_eps * r ** (-(1.0 + self.eps)) + self.c_pn * r ** (-3.0)
|
|
271
|
+
|
|
272
|
+
def angular_momentum(self, q: np.ndarray = None, p: np.ndarray = None) -> float:
|
|
273
|
+
"""Out-of-plane angular momentum ``L = x*py - y*px``.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
q, p : ndarray, optional
|
|
278
|
+
State to evaluate; defaults to the current state.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
float
|
|
283
|
+
"""
|
|
284
|
+
q = self.q if q is None else q
|
|
285
|
+
p = self.p if p is None else p
|
|
286
|
+
return float(q[0] * p[1] - q[1] * p[0])
|
|
287
|
+
|
|
288
|
+
def lrl_vector(self, q: np.ndarray = None, p: np.ndarray = None) -> np.ndarray:
|
|
289
|
+
"""Laplace-Runge-Lenz vector ``A = p x L - mu*k*r_hat`` (planar form).
|
|
290
|
+
|
|
291
|
+
Exactly conserved (a fixed vector pointing at perihelion) for the
|
|
292
|
+
unperturbed 1/r potential; its slow rotation once ``c_eps`` or
|
|
293
|
+
``c_pn`` is nonzero is the precession signature (e.g. Mercury's
|
|
294
|
+
perihelion advance).
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
q, p : ndarray, optional
|
|
299
|
+
State to evaluate; defaults to the current state.
|
|
300
|
+
|
|
301
|
+
Returns
|
|
302
|
+
-------
|
|
303
|
+
ndarray, shape (2,)
|
|
304
|
+
"""
|
|
305
|
+
q = self.q if q is None else q
|
|
306
|
+
p = self.p if p is None else p
|
|
307
|
+
L = self.angular_momentum(q, p)
|
|
308
|
+
r = np.linalg.norm(q)
|
|
309
|
+
# p x L in 2D (L is the out-of-plane scalar): (py*L, -px*L)
|
|
310
|
+
px_L = np.array([p[1] * L, -p[0] * L])
|
|
311
|
+
return px_L - self.mu * self.k * q / r
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def from_orbital_elements(cls, a: float, e: float, k: float = 1.0, mu: float = 1.0, **kwargs):
|
|
315
|
+
"""Construct a system starting at perihelion (pure two-body reference orbit).
|
|
316
|
+
|
|
317
|
+
Parameters
|
|
318
|
+
----------
|
|
319
|
+
a : float
|
|
320
|
+
Semi-major axis.
|
|
321
|
+
e : float
|
|
322
|
+
Eccentricity.
|
|
323
|
+
k, mu : float
|
|
324
|
+
Gravitational coupling and reduced mass, as in ``__init__``.
|
|
325
|
+
**kwargs
|
|
326
|
+
Forwarded to ``__init__`` (e.g. ``eps``, ``c_eps``, ``c_pn``, ``softening``).
|
|
327
|
+
|
|
328
|
+
Returns
|
|
329
|
+
-------
|
|
330
|
+
KeplerSystem
|
|
331
|
+
"""
|
|
332
|
+
r_peri = a * (1.0 - e)
|
|
333
|
+
v_peri = np.sqrt(k / mu * (2.0 / r_peri - 1.0 / a))
|
|
334
|
+
q0 = np.array([r_peri, 0.0])
|
|
335
|
+
p0 = np.array([0.0, mu * v_peri])
|
|
336
|
+
return cls(q0, p0, k=k, mu=mu, **kwargs)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
_SIDEREAL_DAY = 86164.0905
|
|
340
|
+
"""float: Earth's rotation period relative to the stars, in seconds."""
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
@lru_cache(maxsize=64)
|
|
344
|
+
def _make_foucault_deriv(omega0: float, omega_z: float):
|
|
345
|
+
@njit(cache=True)
|
|
346
|
+
def deriv(t, state):
|
|
347
|
+
x, y, vx, vy = state[0], state[1], state[2], state[3]
|
|
348
|
+
out = np.empty(4)
|
|
349
|
+
out[0] = vx
|
|
350
|
+
out[1] = vy
|
|
351
|
+
out[2] = -omega0 * omega0 * x + 2.0 * omega_z * vy
|
|
352
|
+
out[3] = -omega0 * omega0 * y - 2.0 * omega_z * vx
|
|
353
|
+
return out
|
|
354
|
+
|
|
355
|
+
return deriv
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class FoucaultPendulum(ODESystem):
|
|
359
|
+
"""Small-oscillation pendulum viewed from Earth's rotating frame.
|
|
360
|
+
|
|
361
|
+
Swinging near a fixed vertical, a pendulum's horizontal displacement
|
|
362
|
+
q = (x, y) (x = East, y = North) obeys the isotropic harmonic
|
|
363
|
+
equation ``qddot = -omega0**2 * q`` in a non-rotating (inertial)
|
|
364
|
+
frame. Viewed instead from the surface of the rotating Earth, that
|
|
365
|
+
frame itself turns about the local vertical at
|
|
366
|
+
``omega_z = omega_earth * sin(latitude)`` -- the projection of
|
|
367
|
+
Earth's angular velocity onto the local vertical -- which adds a
|
|
368
|
+
velocity-dependent Coriolis term::
|
|
369
|
+
|
|
370
|
+
xddot = -omega0**2 * x + 2 * omega_z * ydot
|
|
371
|
+
yddot = -omega0**2 * y - 2 * omega_z * xdot
|
|
372
|
+
|
|
373
|
+
Because the Coriolis force is always perpendicular to the velocity
|
|
374
|
+
it does no work, so the (non-rotating-frame) mechanical energy
|
|
375
|
+
``T + V = |v|**2 / 2 + omega0**2 * |q|**2 / 2`` stays exactly
|
|
376
|
+
conserved even as the plane of swing itself steadily precesses --
|
|
377
|
+
exactly the effect Leon Foucault used in 1851 to demonstrate
|
|
378
|
+
Earth's rotation mechanically, without reference to the sky.
|
|
379
|
+
|
|
380
|
+
Parameters
|
|
381
|
+
----------
|
|
382
|
+
q0, v0 : array-like, shape (2,)
|
|
383
|
+
Initial horizontal displacement (x, y) and velocity (vx, vy) of
|
|
384
|
+
the bob relative to the vertical through the pivot.
|
|
385
|
+
length, g : float
|
|
386
|
+
Pendulum length and gravitational acceleration, setting the
|
|
387
|
+
non-rotating swing frequency ``omega0 = sqrt(g / length)``.
|
|
388
|
+
latitude_deg : float
|
|
389
|
+
Geographic latitude in degrees (default 48.85, the Paris
|
|
390
|
+
Pantheon's latitude for Foucault's original 1851 demonstration).
|
|
391
|
+
omega_earth : float
|
|
392
|
+
Earth's sidereal rotation rate, ``2*pi / 86164.0905`` rad/s by default.
|
|
393
|
+
"""
|
|
394
|
+
|
|
395
|
+
def __init__(
|
|
396
|
+
self,
|
|
397
|
+
q0,
|
|
398
|
+
v0,
|
|
399
|
+
length: float = 67.0,
|
|
400
|
+
g: float = 9.81,
|
|
401
|
+
latitude_deg: float = 48.85,
|
|
402
|
+
omega_earth: float = 2.0 * np.pi / _SIDEREAL_DAY,
|
|
403
|
+
):
|
|
404
|
+
self.length, self.g = length, g
|
|
405
|
+
self.omega0 = float(np.sqrt(g / length))
|
|
406
|
+
self.latitude_deg = latitude_deg
|
|
407
|
+
self.omega_earth = omega_earth
|
|
408
|
+
self.omega_z = float(omega_earth * np.sin(np.deg2rad(latitude_deg)))
|
|
409
|
+
self._deriv_njit = _make_foucault_deriv(self.omega0, self.omega_z)
|
|
410
|
+
state0 = np.concatenate([np.asarray(q0, dtype=np.float64), np.asarray(v0, dtype=np.float64)])
|
|
411
|
+
super().__init__(state0)
|
|
412
|
+
|
|
413
|
+
@property
|
|
414
|
+
def position(self) -> np.ndarray:
|
|
415
|
+
"""ndarray, shape (2,): Current horizontal displacement (x, y)."""
|
|
416
|
+
return self.state[:2]
|
|
417
|
+
|
|
418
|
+
@property
|
|
419
|
+
def velocity(self) -> np.ndarray:
|
|
420
|
+
"""ndarray, shape (2,): Current horizontal velocity (vx, vy)."""
|
|
421
|
+
return self.state[2:]
|
|
422
|
+
|
|
423
|
+
def energy(self, state: np.ndarray = None) -> float:
|
|
424
|
+
state = self.state if state is None else state
|
|
425
|
+
x, y, vx, vy = state[0], state[1], state[2], state[3]
|
|
426
|
+
return 0.5 * (vx * vx + vy * vy) + 0.5 * self.omega0**2 * (x * x + y * y)
|
|
427
|
+
|
|
428
|
+
@classmethod
|
|
429
|
+
def from_deflection(
|
|
430
|
+
cls,
|
|
431
|
+
amplitude: float,
|
|
432
|
+
angle_deg: float = 0.0,
|
|
433
|
+
length: float = 67.0,
|
|
434
|
+
g: float = 9.81,
|
|
435
|
+
latitude_deg: float = 48.85,
|
|
436
|
+
omega_earth: float = 2.0 * np.pi / _SIDEREAL_DAY,
|
|
437
|
+
):
|
|
438
|
+
"""Construct a system released from rest at a fixed deflection.
|
|
439
|
+
|
|
440
|
+
This is how a real Foucault pendulum is started: pulled aside
|
|
441
|
+
and tied off, then released from rest.
|
|
442
|
+
|
|
443
|
+
Parameters
|
|
444
|
+
----------
|
|
445
|
+
amplitude : float
|
|
446
|
+
Initial horizontal displacement magnitude.
|
|
447
|
+
angle_deg : float
|
|
448
|
+
Direction of the initial displacement, in degrees
|
|
449
|
+
counterclockwise from East.
|
|
450
|
+
length, g, latitude_deg, omega_earth : float
|
|
451
|
+
As in ``__init__``.
|
|
452
|
+
|
|
453
|
+
Returns
|
|
454
|
+
-------
|
|
455
|
+
FoucaultPendulum
|
|
456
|
+
"""
|
|
457
|
+
theta = np.deg2rad(angle_deg)
|
|
458
|
+
q0 = amplitude * np.array([np.cos(theta), np.sin(theta)])
|
|
459
|
+
v0 = np.zeros(2)
|
|
460
|
+
return cls(q0, v0, length=length, g=g, latitude_deg=latitude_deg, omega_earth=omega_earth)
|
|
461
|
+
|
|
462
|
+
def precession_period(self) -> float:
|
|
463
|
+
"""Time for the swing plane to complete one full precession.
|
|
464
|
+
|
|
465
|
+
Equal to the sidereal day divided by ``sin(latitude)`` -- a
|
|
466
|
+
"Foucault day" -- e.g. about 32 hours at Paris's latitude.
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
float
|
|
471
|
+
"""
|
|
472
|
+
return 2.0 * np.pi / abs(self.omega_z)
|
|
473
|
+
|
|
474
|
+
@staticmethod
|
|
475
|
+
def analytic_solution(q0, v0, omega0: float, omega_z: float, t):
|
|
476
|
+
"""Exact horizontal trajectory x(t), y(t) in the small-oscillation approximation.
|
|
477
|
+
|
|
478
|
+
The complex displacement ``w = x + i*y`` satisfies the linear
|
|
479
|
+
equation ``wddot + 2i*omega_z*wdot + omega0**2*w = 0``.
|
|
480
|
+
Substituting ``w = exp(-i*omega_z*t) * u(t)`` reduces this to
|
|
481
|
+
plain simple harmonic motion for ``u`` at frequency
|
|
482
|
+
``Omega = sqrt(omega0**2 + omega_z**2)``: the solution is an
|
|
483
|
+
ordinary (non-rotating) harmonic swing pattern ``u(t)``, rotated
|
|
484
|
+
rigidly as a whole at the constant rate ``omega_z`` -- exactly
|
|
485
|
+
the precession Foucault observed.
|
|
486
|
+
|
|
487
|
+
Parameters
|
|
488
|
+
----------
|
|
489
|
+
q0, v0 : array-like, shape (2,)
|
|
490
|
+
Initial displacement and velocity.
|
|
491
|
+
omega0, omega_z : float
|
|
492
|
+
Non-rotating swing frequency and the local-vertical
|
|
493
|
+
component of Earth's angular velocity.
|
|
494
|
+
t : array-like
|
|
495
|
+
Times at which to evaluate the trajectory.
|
|
496
|
+
|
|
497
|
+
Returns
|
|
498
|
+
-------
|
|
499
|
+
x, y : ndarray
|
|
500
|
+
"""
|
|
501
|
+
t = np.asarray(t, dtype=np.float64)
|
|
502
|
+
Omega = np.sqrt(omega0**2 + omega_z**2)
|
|
503
|
+
w0 = complex(q0[0], q0[1])
|
|
504
|
+
wdot0 = complex(v0[0], v0[1])
|
|
505
|
+
u0 = w0
|
|
506
|
+
udot0 = wdot0 + 1j * omega_z * w0
|
|
507
|
+
u = u0 * np.cos(Omega * t) + (udot0 / Omega) * np.sin(Omega * t)
|
|
508
|
+
w = np.exp(-1j * omega_z * t) * u
|
|
509
|
+
return w.real, w.imag
|
|
510
|
+
|
|
511
|
+
@staticmethod
|
|
512
|
+
def to_corotating_frame(x, y, t, omega_z: float):
|
|
513
|
+
"""Rotate (x, y) by ``+omega_z * t``, undoing the precession.
|
|
514
|
+
|
|
515
|
+
In this frame the pendulum swings in a single fixed plane, just
|
|
516
|
+
like an ordinary non-rotating pendulum -- the frame in which
|
|
517
|
+
Foucault's precession is "unwound".
|
|
518
|
+
|
|
519
|
+
Parameters
|
|
520
|
+
----------
|
|
521
|
+
x, y : array-like
|
|
522
|
+
Trajectory in the lab (ground) frame.
|
|
523
|
+
t : array-like
|
|
524
|
+
Corresponding times.
|
|
525
|
+
omega_z : float
|
|
526
|
+
Local-vertical component of Earth's angular velocity.
|
|
527
|
+
|
|
528
|
+
Returns
|
|
529
|
+
-------
|
|
530
|
+
x_rot, y_rot : ndarray
|
|
531
|
+
"""
|
|
532
|
+
x, y, t = np.asarray(x), np.asarray(y), np.asarray(t)
|
|
533
|
+
c, s = np.cos(omega_z * t), np.sin(omega_z * t)
|
|
534
|
+
x_rot = c * x - s * y
|
|
535
|
+
y_rot = s * x + c * y
|
|
536
|
+
return x_rot, y_rot
|