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,1276 @@
|
|
|
1
|
+
"""Continuous-time chaotic dynamical systems: Lorenz, Rossler, Double Pendulum, Duffing, Chua.
|
|
2
|
+
|
|
3
|
+
Each system exposes a plain-Python :meth:`rhs` (for interactive use / plotting)
|
|
4
|
+
plus a module-level Numba ``@njit`` right-hand-side function with the signature
|
|
5
|
+
``rhs(state, t, params) -> ndarray`` compatible with
|
|
6
|
+
:func:`physicskit.chaos.core.integrators.rk4_integrate`, and a convenience
|
|
7
|
+
:meth:`trajectory` method that drives that integrator.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from numba import njit
|
|
14
|
+
from numpy.typing import NDArray
|
|
15
|
+
from scipy.optimize import brentq
|
|
16
|
+
|
|
17
|
+
from physicskit.chaos.core.base_system import DynamicalSystem
|
|
18
|
+
from physicskit.chaos.core.integrators import rk4_integrate
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Lorenz system
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@njit(cache=True)
|
|
26
|
+
def _lorenz_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
27
|
+
"""Lorenz vector field ``dx/dt = f(x, t; sigma, rho, beta)``.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
state : ndarray of float, shape (3,)
|
|
32
|
+
State vector ``(x, y, z)``.
|
|
33
|
+
t : float
|
|
34
|
+
Current time (unused; the system is autonomous).
|
|
35
|
+
params : ndarray of float, shape (3,)
|
|
36
|
+
Parameters ``(sigma, rho, beta)``.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
ndarray of float, shape (3,)
|
|
41
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
42
|
+
"""
|
|
43
|
+
sigma, rho, beta = params[0], params[1], params[2]
|
|
44
|
+
x, y, z = state[0], state[1], state[2]
|
|
45
|
+
out = np.empty(3)
|
|
46
|
+
out[0] = sigma * (y - x)
|
|
47
|
+
out[1] = x * (rho - z) - y
|
|
48
|
+
out[2] = x * y - beta * z
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Lorenz(DynamicalSystem):
|
|
53
|
+
"""The Lorenz attractor.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
sigma : float, default 10.0
|
|
58
|
+
Prandtl-number-like parameter.
|
|
59
|
+
rho : float, default 28.0
|
|
60
|
+
Rayleigh-number-like parameter.
|
|
61
|
+
beta : float, default 8.0 / 3.0
|
|
62
|
+
Geometric parameter.
|
|
63
|
+
|
|
64
|
+
Attributes
|
|
65
|
+
----------
|
|
66
|
+
sigma, rho, beta : float
|
|
67
|
+
System parameters.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
#: State dimension, always 3.
|
|
71
|
+
dim = 3
|
|
72
|
+
|
|
73
|
+
def __init__(self, sigma: float = 10.0, rho: float = 28.0, beta: float = 8.0 / 3.0):
|
|
74
|
+
self.sigma = float(sigma)
|
|
75
|
+
self.rho = float(rho)
|
|
76
|
+
self.beta = float(beta)
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def params(self) -> NDArray[np.float64]:
|
|
80
|
+
"""Parameter vector ``(sigma, rho, beta)``.
|
|
81
|
+
|
|
82
|
+
Returns
|
|
83
|
+
-------
|
|
84
|
+
ndarray of float, shape (3,)
|
|
85
|
+
"""
|
|
86
|
+
return np.array([self.sigma, self.rho, self.beta])
|
|
87
|
+
|
|
88
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
89
|
+
"""Evaluate the Lorenz vector field.
|
|
90
|
+
|
|
91
|
+
Parameters
|
|
92
|
+
----------
|
|
93
|
+
state : ndarray of float, shape (3,)
|
|
94
|
+
State vector ``(x, y, z)``.
|
|
95
|
+
t : float
|
|
96
|
+
Current time (unused; the system is autonomous).
|
|
97
|
+
|
|
98
|
+
Returns
|
|
99
|
+
-------
|
|
100
|
+
ndarray of float, shape (3,)
|
|
101
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
102
|
+
"""
|
|
103
|
+
return np.asarray(_lorenz_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
104
|
+
|
|
105
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
106
|
+
"""Default initial condition ``(1, 1, 1)``.
|
|
107
|
+
|
|
108
|
+
Returns
|
|
109
|
+
-------
|
|
110
|
+
ndarray of float, shape (3,)
|
|
111
|
+
"""
|
|
112
|
+
return np.array([1.0, 1.0, 1.0])
|
|
113
|
+
|
|
114
|
+
def trajectory(
|
|
115
|
+
self,
|
|
116
|
+
state0: NDArray[np.float64] | None = None,
|
|
117
|
+
t0: float = 0.0,
|
|
118
|
+
dt: float = 0.01,
|
|
119
|
+
n_steps: int = 10000,
|
|
120
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
121
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
122
|
+
|
|
123
|
+
Parameters
|
|
124
|
+
----------
|
|
125
|
+
state0 : array_like of float, shape (3,), optional
|
|
126
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
127
|
+
t0 : float, default 0.0
|
|
128
|
+
Initial time.
|
|
129
|
+
dt : float, default 0.01
|
|
130
|
+
Integration step size.
|
|
131
|
+
n_steps : int, default 10000
|
|
132
|
+
Number of integration steps.
|
|
133
|
+
|
|
134
|
+
Returns
|
|
135
|
+
-------
|
|
136
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
137
|
+
states : ndarray of float, shape (n_steps + 1, 3)
|
|
138
|
+
"""
|
|
139
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
140
|
+
return rk4_integrate(_lorenz_rhs, state0, t0, dt, n_steps, self.params)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------
|
|
144
|
+
# Rossler system
|
|
145
|
+
# ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@njit(cache=True)
|
|
149
|
+
def _rossler_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
150
|
+
"""Rossler vector field ``dx/dt = f(x, t; a, b, c)``.
|
|
151
|
+
|
|
152
|
+
Parameters
|
|
153
|
+
----------
|
|
154
|
+
state : ndarray of float, shape (3,)
|
|
155
|
+
State vector ``(x, y, z)``.
|
|
156
|
+
t : float
|
|
157
|
+
Current time (unused; the system is autonomous).
|
|
158
|
+
params : ndarray of float, shape (3,)
|
|
159
|
+
Parameters ``(a, b, c)``.
|
|
160
|
+
|
|
161
|
+
Returns
|
|
162
|
+
-------
|
|
163
|
+
ndarray of float, shape (3,)
|
|
164
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
165
|
+
"""
|
|
166
|
+
a, b, c = params[0], params[1], params[2]
|
|
167
|
+
x, y, z = state[0], state[1], state[2]
|
|
168
|
+
out = np.empty(3)
|
|
169
|
+
out[0] = -y - z
|
|
170
|
+
out[1] = x + a * y
|
|
171
|
+
out[2] = b + z * (x - c)
|
|
172
|
+
return out
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class Rossler(DynamicalSystem):
|
|
176
|
+
"""The Rossler attractor.
|
|
177
|
+
|
|
178
|
+
Parameters
|
|
179
|
+
----------
|
|
180
|
+
a : float, default 0.2
|
|
181
|
+
System parameter.
|
|
182
|
+
b : float, default 0.2
|
|
183
|
+
System parameter.
|
|
184
|
+
c : float, default 5.7
|
|
185
|
+
System parameter.
|
|
186
|
+
|
|
187
|
+
Attributes
|
|
188
|
+
----------
|
|
189
|
+
a, b, c : float
|
|
190
|
+
System parameters.
|
|
191
|
+
"""
|
|
192
|
+
|
|
193
|
+
#: State dimension, always 3.
|
|
194
|
+
dim = 3
|
|
195
|
+
|
|
196
|
+
def __init__(self, a: float = 0.2, b: float = 0.2, c: float = 5.7):
|
|
197
|
+
self.a = float(a)
|
|
198
|
+
self.b = float(b)
|
|
199
|
+
self.c = float(c)
|
|
200
|
+
|
|
201
|
+
@property
|
|
202
|
+
def params(self) -> NDArray[np.float64]:
|
|
203
|
+
"""Parameter vector ``(a, b, c)``.
|
|
204
|
+
|
|
205
|
+
Returns
|
|
206
|
+
-------
|
|
207
|
+
ndarray of float, shape (3,)
|
|
208
|
+
"""
|
|
209
|
+
return np.array([self.a, self.b, self.c])
|
|
210
|
+
|
|
211
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
212
|
+
"""Evaluate the Rossler vector field.
|
|
213
|
+
|
|
214
|
+
Parameters
|
|
215
|
+
----------
|
|
216
|
+
state : ndarray of float, shape (3,)
|
|
217
|
+
State vector ``(x, y, z)``.
|
|
218
|
+
t : float
|
|
219
|
+
Current time (unused; the system is autonomous).
|
|
220
|
+
|
|
221
|
+
Returns
|
|
222
|
+
-------
|
|
223
|
+
ndarray of float, shape (3,)
|
|
224
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
225
|
+
"""
|
|
226
|
+
return np.asarray(_rossler_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
227
|
+
|
|
228
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
229
|
+
"""Default initial condition ``(1, 1, 1)``.
|
|
230
|
+
|
|
231
|
+
Returns
|
|
232
|
+
-------
|
|
233
|
+
ndarray of float, shape (3,)
|
|
234
|
+
"""
|
|
235
|
+
return np.array([1.0, 1.0, 1.0])
|
|
236
|
+
|
|
237
|
+
def trajectory(
|
|
238
|
+
self,
|
|
239
|
+
state0: NDArray[np.float64] | None = None,
|
|
240
|
+
t0: float = 0.0,
|
|
241
|
+
dt: float = 0.01,
|
|
242
|
+
n_steps: int = 10000,
|
|
243
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
244
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
state0 : array_like of float, shape (3,), optional
|
|
249
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
250
|
+
t0 : float, default 0.0
|
|
251
|
+
Initial time.
|
|
252
|
+
dt : float, default 0.01
|
|
253
|
+
Integration step size.
|
|
254
|
+
n_steps : int, default 10000
|
|
255
|
+
Number of integration steps.
|
|
256
|
+
|
|
257
|
+
Returns
|
|
258
|
+
-------
|
|
259
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
260
|
+
states : ndarray of float, shape (n_steps + 1, 3)
|
|
261
|
+
"""
|
|
262
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
263
|
+
return rk4_integrate(_rossler_rhs, state0, t0, dt, n_steps, self.params)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# ---------------------------------------------------------------------------
|
|
267
|
+
# Double pendulum
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@njit(cache=True)
|
|
272
|
+
def _double_pendulum_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
273
|
+
"""Double-pendulum vector field ``dx/dt = f(x, t; m1, m2, l1, l2, g)``.
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
state : ndarray of float, shape (4,)
|
|
278
|
+
State vector ``(theta1, theta2, omega1, omega2)``.
|
|
279
|
+
t : float
|
|
280
|
+
Current time (unused; the system is autonomous).
|
|
281
|
+
params : ndarray of float, shape (5,)
|
|
282
|
+
Parameters ``(m1, m2, l1, l2, g)``.
|
|
283
|
+
|
|
284
|
+
Returns
|
|
285
|
+
-------
|
|
286
|
+
ndarray of float, shape (4,)
|
|
287
|
+
Time derivative ``(dtheta1/dt, dtheta2/dt, domega1/dt, domega2/dt)``.
|
|
288
|
+
"""
|
|
289
|
+
m1, m2, l1, l2, g = params[0], params[1], params[2], params[3], params[4]
|
|
290
|
+
th1, th2, w1, w2 = state[0], state[1], state[2], state[3]
|
|
291
|
+
delta = th1 - th2
|
|
292
|
+
den1 = l1 * (2.0 * m1 + m2 - m2 * np.cos(2.0 * delta))
|
|
293
|
+
den2 = l2 * (2.0 * m1 + m2 - m2 * np.cos(2.0 * delta))
|
|
294
|
+
|
|
295
|
+
dw1 = (
|
|
296
|
+
-g * (2.0 * m1 + m2) * np.sin(th1) - m2 * g * np.sin(th1 - 2.0 * th2) - 2.0 * np.sin(delta) * m2 * (w2 * w2 * l2 + w1 * w1 * l1 * np.cos(delta))
|
|
297
|
+
) / den1
|
|
298
|
+
dw2 = (2.0 * np.sin(delta) * (w1 * w1 * l1 * (m1 + m2) + g * (m1 + m2) * np.cos(th1) + w2 * w2 * l2 * m2 * np.cos(delta))) / den2
|
|
299
|
+
|
|
300
|
+
out = np.empty(4)
|
|
301
|
+
out[0] = w1
|
|
302
|
+
out[1] = w2
|
|
303
|
+
out[2] = dw1
|
|
304
|
+
out[3] = dw2
|
|
305
|
+
return out
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class DoublePendulum(DynamicalSystem):
|
|
309
|
+
"""Planar double pendulum: point masses on massless rods.
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
m1 : float, default 1.0
|
|
314
|
+
Mass of the first (inner) bob.
|
|
315
|
+
m2 : float, default 1.0
|
|
316
|
+
Mass of the second (outer) bob.
|
|
317
|
+
l1 : float, default 1.0
|
|
318
|
+
Length of the first (inner) rod.
|
|
319
|
+
l2 : float, default 1.0
|
|
320
|
+
Length of the second (outer) rod.
|
|
321
|
+
g : float, default 9.81
|
|
322
|
+
Gravitational acceleration.
|
|
323
|
+
|
|
324
|
+
Attributes
|
|
325
|
+
----------
|
|
326
|
+
m1, m2, l1, l2, g : float
|
|
327
|
+
System parameters.
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
#: State dimension, always 4. State is ``(theta1, theta2, omega1, omega2)``.
|
|
331
|
+
dim = 4
|
|
332
|
+
|
|
333
|
+
def __init__(self, m1: float = 1.0, m2: float = 1.0, l1: float = 1.0, l2: float = 1.0, g: float = 9.81):
|
|
334
|
+
self.m1, self.m2, self.l1, self.l2, self.g = (
|
|
335
|
+
float(m1),
|
|
336
|
+
float(m2),
|
|
337
|
+
float(l1),
|
|
338
|
+
float(l2),
|
|
339
|
+
float(g),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
@property
|
|
343
|
+
def params(self) -> NDArray[np.float64]:
|
|
344
|
+
"""Parameter vector ``(m1, m2, l1, l2, g)``.
|
|
345
|
+
|
|
346
|
+
Returns
|
|
347
|
+
-------
|
|
348
|
+
ndarray of float, shape (5,)
|
|
349
|
+
"""
|
|
350
|
+
return np.array([self.m1, self.m2, self.l1, self.l2, self.g])
|
|
351
|
+
|
|
352
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
353
|
+
"""Evaluate the double-pendulum vector field.
|
|
354
|
+
|
|
355
|
+
Parameters
|
|
356
|
+
----------
|
|
357
|
+
state : ndarray of float, shape (4,)
|
|
358
|
+
State vector ``(theta1, theta2, omega1, omega2)``.
|
|
359
|
+
t : float
|
|
360
|
+
Current time (unused; the system is autonomous).
|
|
361
|
+
|
|
362
|
+
Returns
|
|
363
|
+
-------
|
|
364
|
+
ndarray of float, shape (4,)
|
|
365
|
+
Time derivative ``(dtheta1/dt, dtheta2/dt, domega1/dt, domega2/dt)``.
|
|
366
|
+
"""
|
|
367
|
+
return np.asarray(_double_pendulum_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
368
|
+
|
|
369
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
370
|
+
"""Default initial condition: both rods horizontal, at rest.
|
|
371
|
+
|
|
372
|
+
Returns
|
|
373
|
+
-------
|
|
374
|
+
ndarray of float, shape (4,)
|
|
375
|
+
"""
|
|
376
|
+
return np.array([np.pi / 2.0, np.pi / 2.0, 0.0, 0.0])
|
|
377
|
+
|
|
378
|
+
def trajectory(
|
|
379
|
+
self,
|
|
380
|
+
state0: NDArray[np.float64] | None = None,
|
|
381
|
+
t0: float = 0.0,
|
|
382
|
+
dt: float = 0.005,
|
|
383
|
+
n_steps: int = 10000,
|
|
384
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
385
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
386
|
+
|
|
387
|
+
Parameters
|
|
388
|
+
----------
|
|
389
|
+
state0 : array_like of float, shape (4,), optional
|
|
390
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
391
|
+
t0 : float, default 0.0
|
|
392
|
+
Initial time.
|
|
393
|
+
dt : float, default 0.005
|
|
394
|
+
Integration step size.
|
|
395
|
+
n_steps : int, default 10000
|
|
396
|
+
Number of integration steps.
|
|
397
|
+
|
|
398
|
+
Returns
|
|
399
|
+
-------
|
|
400
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
401
|
+
states : ndarray of float, shape (n_steps + 1, 4)
|
|
402
|
+
"""
|
|
403
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
404
|
+
return rk4_integrate(_double_pendulum_rhs, state0, t0, dt, n_steps, self.params)
|
|
405
|
+
|
|
406
|
+
def energy(self, state: NDArray[np.float64]) -> float:
|
|
407
|
+
"""Total mechanical energy, useful for checking integrator drift.
|
|
408
|
+
|
|
409
|
+
Parameters
|
|
410
|
+
----------
|
|
411
|
+
state : array_like of float, shape (4,)
|
|
412
|
+
State vector ``(theta1, theta2, omega1, omega2)``.
|
|
413
|
+
|
|
414
|
+
Returns
|
|
415
|
+
-------
|
|
416
|
+
float
|
|
417
|
+
Total (kinetic + potential) mechanical energy.
|
|
418
|
+
"""
|
|
419
|
+
th1, th2, w1, w2 = state
|
|
420
|
+
m1, m2, l1, l2, g = self.m1, self.m2, self.l1, self.l2, self.g
|
|
421
|
+
kinetic = 0.5 * m1 * (l1 * w1) ** 2 + 0.5 * m2 * ((l1 * w1) ** 2 + (l2 * w2) ** 2 + 2 * l1 * l2 * w1 * w2 * np.cos(th1 - th2))
|
|
422
|
+
potential = -(m1 + m2) * g * l1 * np.cos(th1) - m2 * g * l2 * np.cos(th2)
|
|
423
|
+
return float(kinetic + potential)
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ---------------------------------------------------------------------------
|
|
427
|
+
# Duffing oscillator
|
|
428
|
+
# ---------------------------------------------------------------------------
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@njit(cache=True)
|
|
432
|
+
def _duffing_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
433
|
+
"""Duffing vector field ``dx/dt = f(x, t; delta, alpha, beta, gamma, omega)``.
|
|
434
|
+
|
|
435
|
+
Parameters
|
|
436
|
+
----------
|
|
437
|
+
state : ndarray of float, shape (2,)
|
|
438
|
+
State vector ``(x, v)``.
|
|
439
|
+
t : float
|
|
440
|
+
Current time (the system is non-autonomous: forcing depends on `t`).
|
|
441
|
+
params : ndarray of float, shape (5,)
|
|
442
|
+
Parameters ``(delta, alpha, beta, gamma, omega)``.
|
|
443
|
+
|
|
444
|
+
Returns
|
|
445
|
+
-------
|
|
446
|
+
ndarray of float, shape (2,)
|
|
447
|
+
Time derivative ``(dx/dt, dv/dt)``.
|
|
448
|
+
"""
|
|
449
|
+
delta, alpha, beta, gamma, omega = params[0], params[1], params[2], params[3], params[4]
|
|
450
|
+
x, v = state[0], state[1]
|
|
451
|
+
out = np.empty(2)
|
|
452
|
+
out[0] = v
|
|
453
|
+
out[1] = -delta * v - alpha * x - beta * x**3 + gamma * np.cos(omega * t)
|
|
454
|
+
return out
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class Duffing(DynamicalSystem):
|
|
458
|
+
"""The forced, damped Duffing oscillator.
|
|
459
|
+
|
|
460
|
+
Governed by ``x'' + delta*x' + alpha*x + beta*x^3 = gamma*cos(omega*t)``.
|
|
461
|
+
|
|
462
|
+
Parameters
|
|
463
|
+
----------
|
|
464
|
+
delta : float, default 0.3
|
|
465
|
+
Damping coefficient.
|
|
466
|
+
alpha : float, default -1.0
|
|
467
|
+
Linear stiffness.
|
|
468
|
+
beta : float, default 1.0
|
|
469
|
+
Cubic (nonlinear) stiffness.
|
|
470
|
+
gamma : float, default 0.37
|
|
471
|
+
Forcing amplitude.
|
|
472
|
+
omega : float, default 1.2
|
|
473
|
+
Forcing angular frequency.
|
|
474
|
+
|
|
475
|
+
Attributes
|
|
476
|
+
----------
|
|
477
|
+
delta, alpha, beta, gamma, omega : float
|
|
478
|
+
System parameters.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
#: State dimension, always 2. State is ``(x, v)``.
|
|
482
|
+
dim = 2
|
|
483
|
+
|
|
484
|
+
def __init__(
|
|
485
|
+
self,
|
|
486
|
+
delta: float = 0.3,
|
|
487
|
+
alpha: float = -1.0,
|
|
488
|
+
beta: float = 1.0,
|
|
489
|
+
gamma: float = 0.37,
|
|
490
|
+
omega: float = 1.2,
|
|
491
|
+
):
|
|
492
|
+
self.delta, self.alpha, self.beta, self.gamma, self.omega = (
|
|
493
|
+
float(delta),
|
|
494
|
+
float(alpha),
|
|
495
|
+
float(beta),
|
|
496
|
+
float(gamma),
|
|
497
|
+
float(omega),
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
@property
|
|
501
|
+
def params(self) -> NDArray[np.float64]:
|
|
502
|
+
"""Parameter vector ``(delta, alpha, beta, gamma, omega)``.
|
|
503
|
+
|
|
504
|
+
Returns
|
|
505
|
+
-------
|
|
506
|
+
ndarray of float, shape (5,)
|
|
507
|
+
"""
|
|
508
|
+
return np.array([self.delta, self.alpha, self.beta, self.gamma, self.omega])
|
|
509
|
+
|
|
510
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
511
|
+
"""Evaluate the Duffing vector field.
|
|
512
|
+
|
|
513
|
+
Parameters
|
|
514
|
+
----------
|
|
515
|
+
state : ndarray of float, shape (2,)
|
|
516
|
+
State vector ``(x, v)``.
|
|
517
|
+
t : float
|
|
518
|
+
Current time (the forcing term depends on `t`).
|
|
519
|
+
|
|
520
|
+
Returns
|
|
521
|
+
-------
|
|
522
|
+
ndarray of float, shape (2,)
|
|
523
|
+
Time derivative ``(dx/dt, dv/dt)``.
|
|
524
|
+
"""
|
|
525
|
+
return np.asarray(_duffing_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
526
|
+
|
|
527
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
528
|
+
"""Default initial condition ``(1, 0)``.
|
|
529
|
+
|
|
530
|
+
Returns
|
|
531
|
+
-------
|
|
532
|
+
ndarray of float, shape (2,)
|
|
533
|
+
"""
|
|
534
|
+
return np.array([1.0, 0.0])
|
|
535
|
+
|
|
536
|
+
def trajectory(
|
|
537
|
+
self,
|
|
538
|
+
state0: NDArray[np.float64] | None = None,
|
|
539
|
+
t0: float = 0.0,
|
|
540
|
+
dt: float = 0.01,
|
|
541
|
+
n_steps: int = 10000,
|
|
542
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
543
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
544
|
+
|
|
545
|
+
Parameters
|
|
546
|
+
----------
|
|
547
|
+
state0 : array_like of float, shape (2,), optional
|
|
548
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
549
|
+
t0 : float, default 0.0
|
|
550
|
+
Initial time.
|
|
551
|
+
dt : float, default 0.01
|
|
552
|
+
Integration step size.
|
|
553
|
+
n_steps : int, default 10000
|
|
554
|
+
Number of integration steps.
|
|
555
|
+
|
|
556
|
+
Returns
|
|
557
|
+
-------
|
|
558
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
559
|
+
states : ndarray of float, shape (n_steps + 1, 2)
|
|
560
|
+
"""
|
|
561
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
562
|
+
return rk4_integrate(_duffing_rhs, state0, t0, dt, n_steps, self.params)
|
|
563
|
+
|
|
564
|
+
|
|
565
|
+
# ---------------------------------------------------------------------------
|
|
566
|
+
# Chua's circuit
|
|
567
|
+
# ---------------------------------------------------------------------------
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
@njit(cache=True)
|
|
571
|
+
def _chua_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
572
|
+
"""Chua's circuit vector field ``dx/dt = f(x, t; alpha, beta, m0, m1)``.
|
|
573
|
+
|
|
574
|
+
Parameters
|
|
575
|
+
----------
|
|
576
|
+
state : ndarray of float, shape (3,)
|
|
577
|
+
State vector ``(x, y, z)``.
|
|
578
|
+
t : float
|
|
579
|
+
Current time (unused; the system is autonomous).
|
|
580
|
+
params : ndarray of float, shape (4,)
|
|
581
|
+
Parameters ``(alpha, beta, m0, m1)``.
|
|
582
|
+
|
|
583
|
+
Returns
|
|
584
|
+
-------
|
|
585
|
+
ndarray of float, shape (3,)
|
|
586
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
587
|
+
"""
|
|
588
|
+
alpha, beta, m0, m1 = params[0], params[1], params[2], params[3]
|
|
589
|
+
x, y, z = state[0], state[1], state[2]
|
|
590
|
+
h = m1 * x + 0.5 * (m0 - m1) * (abs(x + 1.0) - abs(x - 1.0))
|
|
591
|
+
out = np.empty(3)
|
|
592
|
+
out[0] = alpha * (y - x - h)
|
|
593
|
+
out[1] = x - y + z
|
|
594
|
+
out[2] = -beta * y
|
|
595
|
+
return out
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
class Chua(DynamicalSystem):
|
|
599
|
+
"""Chua's circuit: a simple chaotic electronic oscillator.
|
|
600
|
+
|
|
601
|
+
Built from just a resistor, two capacitors, an inductor, and one
|
|
602
|
+
piecewise-linear nonlinear resistor (the "Chua diode", ``h`` below), this
|
|
603
|
+
is one of the simplest physical systems known to be chaotic, and the
|
|
604
|
+
first to have its chaos confirmed experimentally in real hardware. For
|
|
605
|
+
the classic parameters below it produces the famous *double-scroll*
|
|
606
|
+
attractor: two spiral lobes, with the trajectory unpredictably switching
|
|
607
|
+
between them.
|
|
608
|
+
|
|
609
|
+
.. math::
|
|
610
|
+
|
|
611
|
+
\\dot{x} &= \\alpha (y - x - h(x)) \\\\
|
|
612
|
+
\\dot{y} &= x - y + z \\\\
|
|
613
|
+
\\dot{z} &= -\\beta y \\\\
|
|
614
|
+
h(x) &= m_1 x + \\tfrac{1}{2}(m_0 - m_1)(|x + 1| - |x - 1|)
|
|
615
|
+
|
|
616
|
+
Parameters
|
|
617
|
+
----------
|
|
618
|
+
alpha : float, default 15.6
|
|
619
|
+
Ratio of the two capacitances.
|
|
620
|
+
beta : float, default 28.0
|
|
621
|
+
Ratio involving the inductance and second capacitance.
|
|
622
|
+
m0 : float, default -8/7
|
|
623
|
+
Inner (small-``|x|``) slope of the Chua diode's piecewise-linear
|
|
624
|
+
characteristic.
|
|
625
|
+
m1 : float, default -5/7
|
|
626
|
+
Outer (large-``|x|``) slope of the Chua diode's piecewise-linear
|
|
627
|
+
characteristic.
|
|
628
|
+
|
|
629
|
+
Attributes
|
|
630
|
+
----------
|
|
631
|
+
alpha, beta, m0, m1 : float
|
|
632
|
+
System parameters.
|
|
633
|
+
"""
|
|
634
|
+
|
|
635
|
+
#: State dimension, always 3.
|
|
636
|
+
dim = 3
|
|
637
|
+
|
|
638
|
+
def __init__(
|
|
639
|
+
self,
|
|
640
|
+
alpha: float = 15.6,
|
|
641
|
+
beta: float = 28.0,
|
|
642
|
+
m0: float = -8.0 / 7.0,
|
|
643
|
+
m1: float = -5.0 / 7.0,
|
|
644
|
+
):
|
|
645
|
+
self.alpha = float(alpha)
|
|
646
|
+
self.beta = float(beta)
|
|
647
|
+
self.m0 = float(m0)
|
|
648
|
+
self.m1 = float(m1)
|
|
649
|
+
|
|
650
|
+
@property
|
|
651
|
+
def params(self) -> NDArray[np.float64]:
|
|
652
|
+
"""Parameter vector ``(alpha, beta, m0, m1)``.
|
|
653
|
+
|
|
654
|
+
Returns
|
|
655
|
+
-------
|
|
656
|
+
ndarray of float, shape (4,)
|
|
657
|
+
"""
|
|
658
|
+
return np.array([self.alpha, self.beta, self.m0, self.m1])
|
|
659
|
+
|
|
660
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
661
|
+
"""Evaluate Chua's circuit vector field.
|
|
662
|
+
|
|
663
|
+
Parameters
|
|
664
|
+
----------
|
|
665
|
+
state : ndarray of float, shape (3,)
|
|
666
|
+
State vector ``(x, y, z)``.
|
|
667
|
+
t : float
|
|
668
|
+
Current time (unused; the system is autonomous).
|
|
669
|
+
|
|
670
|
+
Returns
|
|
671
|
+
-------
|
|
672
|
+
ndarray of float, shape (3,)
|
|
673
|
+
Time derivative ``(dx/dt, dy/dt, dz/dt)``.
|
|
674
|
+
"""
|
|
675
|
+
return np.asarray(_chua_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
676
|
+
|
|
677
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
678
|
+
"""Default initial condition, slightly off the unstable origin.
|
|
679
|
+
|
|
680
|
+
Returns
|
|
681
|
+
-------
|
|
682
|
+
ndarray of float, shape (3,)
|
|
683
|
+
"""
|
|
684
|
+
return np.array([0.7, 0.0, 0.0])
|
|
685
|
+
|
|
686
|
+
def trajectory(
|
|
687
|
+
self,
|
|
688
|
+
state0: NDArray[np.float64] | None = None,
|
|
689
|
+
t0: float = 0.0,
|
|
690
|
+
dt: float = 0.01,
|
|
691
|
+
n_steps: int = 20000,
|
|
692
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
693
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
694
|
+
|
|
695
|
+
Parameters
|
|
696
|
+
----------
|
|
697
|
+
state0 : array_like of float, shape (3,), optional
|
|
698
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
699
|
+
t0 : float, default 0.0
|
|
700
|
+
Initial time.
|
|
701
|
+
dt : float, default 0.01
|
|
702
|
+
Integration step size.
|
|
703
|
+
n_steps : int, default 20000
|
|
704
|
+
Number of integration steps.
|
|
705
|
+
|
|
706
|
+
Returns
|
|
707
|
+
-------
|
|
708
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
709
|
+
states : ndarray of float, shape (n_steps + 1, 3)
|
|
710
|
+
"""
|
|
711
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
712
|
+
return rk4_integrate(_chua_rhs, state0, t0, dt, n_steps, self.params)
|
|
713
|
+
|
|
714
|
+
|
|
715
|
+
# ---------------------------------------------------------------------------
|
|
716
|
+
# Restricted three-body problem
|
|
717
|
+
# ---------------------------------------------------------------------------
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
@njit(cache=True)
|
|
721
|
+
def _restricted_three_body_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
722
|
+
"""CR3BP vector field (planar, rotating frame) ``dx/dt = f(x, t; mu)``.
|
|
723
|
+
|
|
724
|
+
Parameters
|
|
725
|
+
----------
|
|
726
|
+
state : ndarray of float, shape (4,)
|
|
727
|
+
State vector ``(x, y, vx, vy)`` in the rotating frame.
|
|
728
|
+
t : float
|
|
729
|
+
Current time (unused; the system is autonomous in the rotating frame).
|
|
730
|
+
params : ndarray of float, shape (1,)
|
|
731
|
+
Parameters ``(mu,)``.
|
|
732
|
+
|
|
733
|
+
Returns
|
|
734
|
+
-------
|
|
735
|
+
ndarray of float, shape (4,)
|
|
736
|
+
Time derivative ``(dx/dt, dy/dt, dvx/dt, dvy/dt)``.
|
|
737
|
+
"""
|
|
738
|
+
mu = params[0]
|
|
739
|
+
x, y, vx, vy = state[0], state[1], state[2], state[3]
|
|
740
|
+
r1 = np.sqrt((x + mu) ** 2 + y * y)
|
|
741
|
+
r2 = np.sqrt((x - 1.0 + mu) ** 2 + y * y)
|
|
742
|
+
out = np.empty(4)
|
|
743
|
+
out[0] = vx
|
|
744
|
+
out[1] = vy
|
|
745
|
+
out[2] = 2.0 * vy + x - (1.0 - mu) * (x + mu) / r1**3 - mu * (x - 1.0 + mu) / r2**3
|
|
746
|
+
out[3] = -2.0 * vx + y - (1.0 - mu) * y / r1**3 - mu * y / r2**3
|
|
747
|
+
return out
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
class RestrictedThreeBody(DynamicalSystem):
|
|
751
|
+
"""The planar circular restricted three-body problem (CR3BP).
|
|
752
|
+
|
|
753
|
+
A massless third body moves under the gravity of two massive primaries
|
|
754
|
+
(masses ``1 - mu`` and ``mu``, in normalized units) that are themselves in
|
|
755
|
+
a fixed circular orbit about their common center of mass. In the
|
|
756
|
+
rotating (co-precessing) reference frame, the primaries sit fixed at
|
|
757
|
+
``(-mu, 0)`` and ``(1 - mu, 0)``, and the third body's motion picks up
|
|
758
|
+
centrifugal and Coriolis terms alongside the two gravitational pulls.
|
|
759
|
+
|
|
760
|
+
This system is what led Poincare to the first discovery of deterministic
|
|
761
|
+
chaos: he found that, unlike the exactly solvable two-body problem, CR3BP
|
|
762
|
+
trajectories can depend on initial conditions in an essentially
|
|
763
|
+
unpredictable way. It also genuinely coexists with regular
|
|
764
|
+
(quasi-periodic, KAM-stable) motion -- the default initial condition
|
|
765
|
+
below is the classic *Arenstorf orbit*, a stable *periodic* orbit famous
|
|
766
|
+
in the numerical-methods literature as an ODE-solver stress test (it
|
|
767
|
+
passes very close to the smaller primary); see the example gallery for a
|
|
768
|
+
nearby, only slightly perturbed initial condition that is chaotic instead.
|
|
769
|
+
|
|
770
|
+
Because of the velocity-dependent Coriolis terms, this system is *not* a
|
|
771
|
+
separable Hamiltonian of the form ``pos'' = force(pos, t)``, so it must
|
|
772
|
+
be integrated with :func:`physicskit.chaos.core.integrators.rk4_integrate`
|
|
773
|
+
(the symplectic :func:`~physicskit.chaos.core.integrators.leapfrog_integrate` /
|
|
774
|
+
:func:`~physicskit.chaos.core.integrators.yoshida4_integrate` do not apply).
|
|
775
|
+
|
|
776
|
+
Parameters
|
|
777
|
+
----------
|
|
778
|
+
mu : float, default 0.012277471
|
|
779
|
+
Mass parameter (mass of the smaller primary, in units where the
|
|
780
|
+
total mass is 1); the default is the Earth-Moon-like value used in
|
|
781
|
+
the classic Arenstorf orbit.
|
|
782
|
+
|
|
783
|
+
Attributes
|
|
784
|
+
----------
|
|
785
|
+
mu : float
|
|
786
|
+
Mass parameter.
|
|
787
|
+
"""
|
|
788
|
+
|
|
789
|
+
#: State dimension, always 4. State is ``(x, y, vx, vy)``.
|
|
790
|
+
dim = 4
|
|
791
|
+
|
|
792
|
+
def __init__(self, mu: float = 0.012277471):
|
|
793
|
+
self.mu = float(mu)
|
|
794
|
+
|
|
795
|
+
@property
|
|
796
|
+
def params(self) -> NDArray[np.float64]:
|
|
797
|
+
"""Parameter vector ``(mu,)``.
|
|
798
|
+
|
|
799
|
+
Returns
|
|
800
|
+
-------
|
|
801
|
+
ndarray of float, shape (1,)
|
|
802
|
+
"""
|
|
803
|
+
return np.array([self.mu])
|
|
804
|
+
|
|
805
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
806
|
+
"""Evaluate the CR3BP vector field.
|
|
807
|
+
|
|
808
|
+
Parameters
|
|
809
|
+
----------
|
|
810
|
+
state : ndarray of float, shape (4,)
|
|
811
|
+
State vector ``(x, y, vx, vy)``.
|
|
812
|
+
t : float
|
|
813
|
+
Current time (unused; the system is autonomous in the rotating
|
|
814
|
+
frame).
|
|
815
|
+
|
|
816
|
+
Returns
|
|
817
|
+
-------
|
|
818
|
+
ndarray of float, shape (4,)
|
|
819
|
+
Time derivative ``(dx/dt, dy/dt, dvx/dt, dvy/dt)``.
|
|
820
|
+
"""
|
|
821
|
+
return np.asarray(_restricted_three_body_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
822
|
+
|
|
823
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
824
|
+
"""The classic Arenstorf periodic-orbit initial condition.
|
|
825
|
+
|
|
826
|
+
Returns
|
|
827
|
+
-------
|
|
828
|
+
ndarray of float, shape (4,)
|
|
829
|
+
"""
|
|
830
|
+
return np.array([0.994, 0.0, 0.0, -2.00158510637908252240537862224])
|
|
831
|
+
|
|
832
|
+
def trajectory(
|
|
833
|
+
self,
|
|
834
|
+
state0: NDArray[np.float64] | None = None,
|
|
835
|
+
t0: float = 0.0,
|
|
836
|
+
dt: float = 0.0005,
|
|
837
|
+
n_steps: int = 40000,
|
|
838
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
839
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
840
|
+
|
|
841
|
+
Parameters
|
|
842
|
+
----------
|
|
843
|
+
state0 : array_like of float, shape (4,), optional
|
|
844
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
845
|
+
t0 : float, default 0.0
|
|
846
|
+
Initial time.
|
|
847
|
+
dt : float, default 0.0005
|
|
848
|
+
Integration step size (small, since the Arenstorf orbit's default
|
|
849
|
+
initial condition passes very close to the smaller primary).
|
|
850
|
+
n_steps : int, default 40000
|
|
851
|
+
Number of integration steps.
|
|
852
|
+
|
|
853
|
+
Returns
|
|
854
|
+
-------
|
|
855
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
856
|
+
states : ndarray of float, shape (n_steps + 1, 4)
|
|
857
|
+
"""
|
|
858
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
859
|
+
return rk4_integrate(_restricted_three_body_rhs, state0, t0, dt, n_steps, self.params)
|
|
860
|
+
|
|
861
|
+
def jacobi_constant(self, state: NDArray[np.float64]) -> float:
|
|
862
|
+
"""The Jacobi constant: the CR3BP's conserved rotating-frame energy analog.
|
|
863
|
+
|
|
864
|
+
Parameters
|
|
865
|
+
----------
|
|
866
|
+
state : array_like of float, shape (4,)
|
|
867
|
+
State vector ``(x, y, vx, vy)``.
|
|
868
|
+
|
|
869
|
+
Returns
|
|
870
|
+
-------
|
|
871
|
+
float
|
|
872
|
+
The Jacobi constant ``C = 2*Omega(x, y) - (vx^2 + vy^2)``, where
|
|
873
|
+
``Omega`` is the effective (gravitational + centrifugal)
|
|
874
|
+
potential. Conserved along any trajectory; useful for checking
|
|
875
|
+
integrator fidelity (e.g. with
|
|
876
|
+
:func:`physicskit.chaos.utils.metrics.energy_drift`).
|
|
877
|
+
"""
|
|
878
|
+
mu = self.mu
|
|
879
|
+
x, y, vx, vy = state
|
|
880
|
+
omega = effective_potential(x, y, mu)
|
|
881
|
+
return float(2.0 * omega - (vx * vx + vy * vy))
|
|
882
|
+
|
|
883
|
+
|
|
884
|
+
def effective_potential(x: NDArray[np.float64] | float, y: NDArray[np.float64] | float, mu: float) -> NDArray[np.float64]:
|
|
885
|
+
"""The CR3BP's effective (gravitational + centrifugal) potential ``Omega(x, y)``.
|
|
886
|
+
|
|
887
|
+
Vectorized over `x`/`y` (works directly on ``np.meshgrid`` output), so it
|
|
888
|
+
is the building block for both
|
|
889
|
+
:meth:`RestrictedThreeBody.jacobi_constant` and that orbit's *zero-velocity
|
|
890
|
+
curve*: since ``C = 2*Omega(x, y) - speed**2``, a trajectory with Jacobi
|
|
891
|
+
constant `C` can only reach points where ``Omega(x, y) >= C / 2`` (speed
|
|
892
|
+
would otherwise be imaginary) -- the boundary ``Omega(x, y) == C / 2`` is
|
|
893
|
+
a curve the body can never cross, carving the plane into allowed and
|
|
894
|
+
forbidden ("Hill") regions.
|
|
895
|
+
|
|
896
|
+
Parameters
|
|
897
|
+
----------
|
|
898
|
+
x, y : float or ndarray of float
|
|
899
|
+
Rotating-frame position(s).
|
|
900
|
+
mu : float
|
|
901
|
+
CR3BP mass parameter.
|
|
902
|
+
|
|
903
|
+
Returns
|
|
904
|
+
-------
|
|
905
|
+
ndarray of float
|
|
906
|
+
``Omega(x, y) = 0.5*(x^2 + y^2) + (1 - mu)/r1 + mu/r2``.
|
|
907
|
+
"""
|
|
908
|
+
x = np.asarray(x, dtype=np.float64)
|
|
909
|
+
y = np.asarray(y, dtype=np.float64)
|
|
910
|
+
r1 = np.sqrt((x + mu) ** 2 + y * y)
|
|
911
|
+
r2 = np.sqrt((x - 1.0 + mu) ** 2 + y * y)
|
|
912
|
+
return 0.5 * (x * x + y * y) + (1.0 - mu) / r1 + mu / r2
|
|
913
|
+
|
|
914
|
+
|
|
915
|
+
def lagrange_points(mu: float) -> NDArray[np.float64]:
|
|
916
|
+
"""The five Lagrange (libration) equilibrium points of the CR3BP, in
|
|
917
|
+
the rotating frame, for mass parameter `mu`.
|
|
918
|
+
|
|
919
|
+
L1, L2, L3 are the collinear equilibria on the ``y = 0`` axis: points
|
|
920
|
+
where a body held at rest (``vx = vy = 0``) in the rotating frame feels
|
|
921
|
+
zero net acceleration, found here by root-finding
|
|
922
|
+
:func:`_restricted_three_body_rhs`'s ``ax`` output directly (rather
|
|
923
|
+
than re-deriving the quintic equations by hand) in the three intervals
|
|
924
|
+
it is known to have exactly one root each: between the primaries
|
|
925
|
+
(L1), beyond the smaller primary (L2), and beyond the larger primary,
|
|
926
|
+
on the opposite side (L3). L4 and L5 form equilateral triangles with
|
|
927
|
+
the two primaries and have the closed-form rotating-frame position
|
|
928
|
+
``(0.5 - mu, +-sqrt(3)/2)``.
|
|
929
|
+
|
|
930
|
+
Parameters
|
|
931
|
+
----------
|
|
932
|
+
mu : float
|
|
933
|
+
CR3BP mass parameter.
|
|
934
|
+
|
|
935
|
+
Returns
|
|
936
|
+
-------
|
|
937
|
+
ndarray of float, shape (5, 2)
|
|
938
|
+
``(x, y)`` of L1 through L5, in that order.
|
|
939
|
+
"""
|
|
940
|
+
|
|
941
|
+
def ax_residual(x: float) -> float:
|
|
942
|
+
state = np.array([x, 0.0, 0.0, 0.0])
|
|
943
|
+
return float(_restricted_three_body_rhs(state, 0.0, np.array([mu]))[2])
|
|
944
|
+
|
|
945
|
+
eps = 1e-8
|
|
946
|
+
x_l1 = brentq(ax_residual, -mu + eps, 1.0 - mu - eps)
|
|
947
|
+
x_l2 = brentq(ax_residual, 1.0 - mu + eps, 1.0 - mu + 2.0)
|
|
948
|
+
x_l3 = brentq(ax_residual, -mu - 2.0, -mu - eps)
|
|
949
|
+
return np.array(
|
|
950
|
+
[
|
|
951
|
+
[x_l1, 0.0],
|
|
952
|
+
[x_l2, 0.0],
|
|
953
|
+
[x_l3, 0.0],
|
|
954
|
+
[0.5 - mu, np.sqrt(3.0) / 2.0],
|
|
955
|
+
[0.5 - mu, -np.sqrt(3.0) / 2.0],
|
|
956
|
+
]
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
|
|
960
|
+
# ---------------------------------------------------------------------------
|
|
961
|
+
# Magnetic pendulum
|
|
962
|
+
# ---------------------------------------------------------------------------
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
@njit(cache=True)
|
|
966
|
+
def magnetic_pendulum_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
967
|
+
"""Magnetic pendulum vector field ``dx/dt = f(x, t; params)``.
|
|
968
|
+
|
|
969
|
+
Public (not underscore-prefixed) so it can be passed directly to the
|
|
970
|
+
low-level, Numba-accelerated integrators in
|
|
971
|
+
:mod:`physicskit.chaos.core.integrators` for high-throughput grid computations
|
|
972
|
+
such as :func:`physicskit.chaos.visualizers.basins.basin_of_attraction`, which
|
|
973
|
+
needs to integrate one trajectory per pixel of a basin map.
|
|
974
|
+
|
|
975
|
+
Parameters
|
|
976
|
+
----------
|
|
977
|
+
state : ndarray of float, shape (4,)
|
|
978
|
+
State vector ``(x, y, vx, vy)``.
|
|
979
|
+
t : float
|
|
980
|
+
Current time (unused; the system is autonomous).
|
|
981
|
+
params : ndarray of float, shape (4 + 2*n_magnets,)
|
|
982
|
+
``(friction, spring, height, strength, mx_0, my_0, mx_1, my_1, ...)``:
|
|
983
|
+
see :class:`MagneticPendulum` for the meaning of each.
|
|
984
|
+
|
|
985
|
+
Returns
|
|
986
|
+
-------
|
|
987
|
+
ndarray of float, shape (4,)
|
|
988
|
+
Time derivative ``(dx/dt, dy/dt, dvx/dt, dvy/dt)``.
|
|
989
|
+
"""
|
|
990
|
+
friction, spring, height, strength = params[0], params[1], params[2], params[3]
|
|
991
|
+
x, y, vx, vy = state[0], state[1], state[2], state[3]
|
|
992
|
+
n_magnets = (params.shape[0] - 4) // 2
|
|
993
|
+
|
|
994
|
+
ax = -friction * vx - spring * x
|
|
995
|
+
ay = -friction * vy - spring * y
|
|
996
|
+
for k in range(n_magnets):
|
|
997
|
+
mx = params[4 + 2 * k]
|
|
998
|
+
my = params[4 + 2 * k + 1]
|
|
999
|
+
dx = x - mx
|
|
1000
|
+
dy = y - my
|
|
1001
|
+
r3 = (dx * dx + dy * dy + height * height) ** 1.5
|
|
1002
|
+
ax -= strength * dx / r3
|
|
1003
|
+
ay -= strength * dy / r3
|
|
1004
|
+
|
|
1005
|
+
out = np.empty(4)
|
|
1006
|
+
out[0] = vx
|
|
1007
|
+
out[1] = vy
|
|
1008
|
+
out[2] = ax
|
|
1009
|
+
out[3] = ay
|
|
1010
|
+
return out
|
|
1011
|
+
|
|
1012
|
+
|
|
1013
|
+
class MagneticPendulum(DynamicalSystem):
|
|
1014
|
+
"""A pendulum bob swinging over several fixed magnets: a multistable, chaotic system.
|
|
1015
|
+
|
|
1016
|
+
A damped pendulum bob, modeled in the small-swing (flat, 2D) limit, is
|
|
1017
|
+
pulled down toward the origin by a linear restoring force and attracted
|
|
1018
|
+
toward each of several fixed magnets by an inverse-square-like force
|
|
1019
|
+
(softened by a "height" offset `height`, the bob's height above the
|
|
1020
|
+
magnet plane, which avoids a force singularity directly above a magnet).
|
|
1021
|
+
Friction eventually settles the bob at rest near whichever magnet "won" --
|
|
1022
|
+
but *which* magnet wins depends on the starting position with famously
|
|
1023
|
+
fractal sensitivity, making this the classic system for visualizing
|
|
1024
|
+
fractal basin boundaries; see
|
|
1025
|
+
:func:`physicskit.chaos.visualizers.basins.plot_basin_of_attraction`.
|
|
1026
|
+
|
|
1027
|
+
Parameters
|
|
1028
|
+
----------
|
|
1029
|
+
magnet_positions : array_like of float, shape (n_magnets, 2), optional
|
|
1030
|
+
Magnet ``(x, y)`` positions; defaults to 3 magnets at the vertices of
|
|
1031
|
+
an equilateral triangle inscribed in the unit circle.
|
|
1032
|
+
friction : float, default 0.2
|
|
1033
|
+
Damping coefficient.
|
|
1034
|
+
spring : float, default 0.2
|
|
1035
|
+
Linear restoring-force coefficient (pulling the bob back toward the
|
|
1036
|
+
origin, as in the small-swing limit of gravity).
|
|
1037
|
+
height : float, default 0.2
|
|
1038
|
+
The bob's height above the magnet plane; softens the force near a
|
|
1039
|
+
magnet (larger values give a gentler, less singular pull).
|
|
1040
|
+
strength : float, default 1.0
|
|
1041
|
+
Magnet attraction strength.
|
|
1042
|
+
|
|
1043
|
+
Attributes
|
|
1044
|
+
----------
|
|
1045
|
+
magnet_positions : ndarray of float, shape (n_magnets, 2)
|
|
1046
|
+
Magnet positions.
|
|
1047
|
+
friction, spring, height, strength : float
|
|
1048
|
+
System parameters.
|
|
1049
|
+
"""
|
|
1050
|
+
|
|
1051
|
+
#: State dimension, always 4. State is ``(x, y, vx, vy)``.
|
|
1052
|
+
dim = 4
|
|
1053
|
+
|
|
1054
|
+
def __init__(
|
|
1055
|
+
self,
|
|
1056
|
+
magnet_positions: NDArray[np.float64] | None = None,
|
|
1057
|
+
friction: float = 0.2,
|
|
1058
|
+
spring: float = 0.2,
|
|
1059
|
+
height: float = 0.2,
|
|
1060
|
+
strength: float = 1.0,
|
|
1061
|
+
):
|
|
1062
|
+
if magnet_positions is None:
|
|
1063
|
+
angles = np.array([90.0, 210.0, 330.0]) * np.pi / 180.0
|
|
1064
|
+
magnet_positions = np.column_stack([np.cos(angles), np.sin(angles)])
|
|
1065
|
+
self.magnet_positions = np.asarray(magnet_positions, dtype=np.float64)
|
|
1066
|
+
self.friction = float(friction)
|
|
1067
|
+
self.spring = float(spring)
|
|
1068
|
+
self.height = float(height)
|
|
1069
|
+
self.strength = float(strength)
|
|
1070
|
+
|
|
1071
|
+
@property
|
|
1072
|
+
def params(self) -> NDArray[np.float64]:
|
|
1073
|
+
"""Parameter vector ``(friction, spring, height, strength, mx_0, my_0, ...)``.
|
|
1074
|
+
|
|
1075
|
+
Returns
|
|
1076
|
+
-------
|
|
1077
|
+
ndarray of float, shape (4 + 2*n_magnets,)
|
|
1078
|
+
"""
|
|
1079
|
+
return np.concatenate(
|
|
1080
|
+
[
|
|
1081
|
+
[self.friction, self.spring, self.height, self.strength],
|
|
1082
|
+
self.magnet_positions.ravel(),
|
|
1083
|
+
]
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
1087
|
+
"""Evaluate the magnetic pendulum vector field.
|
|
1088
|
+
|
|
1089
|
+
Parameters
|
|
1090
|
+
----------
|
|
1091
|
+
state : ndarray of float, shape (4,)
|
|
1092
|
+
State vector ``(x, y, vx, vy)``.
|
|
1093
|
+
t : float
|
|
1094
|
+
Current time (unused; the system is autonomous).
|
|
1095
|
+
|
|
1096
|
+
Returns
|
|
1097
|
+
-------
|
|
1098
|
+
ndarray of float, shape (4,)
|
|
1099
|
+
Time derivative ``(dx/dt, dy/dt, dvx/dt, dvy/dt)``.
|
|
1100
|
+
"""
|
|
1101
|
+
return np.asarray(magnetic_pendulum_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
1102
|
+
|
|
1103
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
1104
|
+
"""Default initial condition: released from rest, off-center.
|
|
1105
|
+
|
|
1106
|
+
Returns
|
|
1107
|
+
-------
|
|
1108
|
+
ndarray of float, shape (4,)
|
|
1109
|
+
"""
|
|
1110
|
+
return np.array([0.5, 0.5, 0.0, 0.0])
|
|
1111
|
+
|
|
1112
|
+
def trajectory(
|
|
1113
|
+
self,
|
|
1114
|
+
state0: NDArray[np.float64] | None = None,
|
|
1115
|
+
t0: float = 0.0,
|
|
1116
|
+
dt: float = 0.02,
|
|
1117
|
+
n_steps: int = 5000,
|
|
1118
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
1119
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
1120
|
+
|
|
1121
|
+
Parameters
|
|
1122
|
+
----------
|
|
1123
|
+
state0 : array_like of float, shape (4,), optional
|
|
1124
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
1125
|
+
t0 : float, default 0.0
|
|
1126
|
+
Initial time.
|
|
1127
|
+
dt : float, default 0.02
|
|
1128
|
+
Integration step size.
|
|
1129
|
+
n_steps : int, default 5000
|
|
1130
|
+
Number of integration steps.
|
|
1131
|
+
|
|
1132
|
+
Returns
|
|
1133
|
+
-------
|
|
1134
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
1135
|
+
states : ndarray of float, shape (n_steps + 1, 4)
|
|
1136
|
+
"""
|
|
1137
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
1138
|
+
return rk4_integrate(magnetic_pendulum_rhs, state0, t0, dt, n_steps, self.params)
|
|
1139
|
+
|
|
1140
|
+
|
|
1141
|
+
# ---------------------------------------------------------------------------
|
|
1142
|
+
# Driven, damped pendulum
|
|
1143
|
+
# ---------------------------------------------------------------------------
|
|
1144
|
+
|
|
1145
|
+
|
|
1146
|
+
@njit(cache=True)
|
|
1147
|
+
def _driven_pendulum_rhs(state: NDArray[np.float64], t: float, params: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
1148
|
+
"""Driven-pendulum vector field ``dx/dt = f(x, t; damping, g_over_l, A, omega_d)``.
|
|
1149
|
+
|
|
1150
|
+
Parameters
|
|
1151
|
+
----------
|
|
1152
|
+
state : ndarray of float, shape (2,)
|
|
1153
|
+
State vector ``(theta, omega)``.
|
|
1154
|
+
t : float
|
|
1155
|
+
Current time (the forcing term depends on `t`).
|
|
1156
|
+
params : ndarray of float, shape (4,)
|
|
1157
|
+
Parameters ``(damping, g_over_l, A, omega_d)``.
|
|
1158
|
+
|
|
1159
|
+
Returns
|
|
1160
|
+
-------
|
|
1161
|
+
ndarray of float, shape (2,)
|
|
1162
|
+
Time derivative ``(dtheta/dt, domega/dt)``.
|
|
1163
|
+
"""
|
|
1164
|
+
damping, g_over_l, A, omega_d = params[0], params[1], params[2], params[3]
|
|
1165
|
+
theta, omega = state[0], state[1]
|
|
1166
|
+
out = np.empty(2)
|
|
1167
|
+
out[0] = omega
|
|
1168
|
+
out[1] = -damping * omega - g_over_l * np.sin(theta) + A * np.cos(omega_d * t)
|
|
1169
|
+
return out
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
class DrivenPendulum(DynamicalSystem):
|
|
1173
|
+
"""The damped, sinusoidally forced pendulum.
|
|
1174
|
+
|
|
1175
|
+
Governed by ``theta'' + damping*theta' + (g/l)*sin(theta) = A*cos(omega_d*t)``
|
|
1176
|
+
-- the classic mechanical system for the period-doubling route to chaos
|
|
1177
|
+
(Baker & Gollub, *Chaotic Dynamics: An Introduction*): for fixed
|
|
1178
|
+
damping and forcing frequency, sweeping the forcing amplitude `A`
|
|
1179
|
+
produces the same period-doubling cascade as the logistic map. Feed
|
|
1180
|
+
:meth:`rhs` to
|
|
1181
|
+
:func:`physicskit.chaos.visualizers.bifurcation.stroboscopic_bifurcation_sampler`
|
|
1182
|
+
(sampling `theta` once per forcing period ``2*pi/omega_d``) to build
|
|
1183
|
+
that bifurcation diagram directly with
|
|
1184
|
+
:func:`physicskit.chaos.visualizers.bifurcation.bifurcation_diagram`.
|
|
1185
|
+
|
|
1186
|
+
Parameters
|
|
1187
|
+
----------
|
|
1188
|
+
damping : float, default 0.5
|
|
1189
|
+
Viscous damping coefficient.
|
|
1190
|
+
g_over_l : float, default 1.0
|
|
1191
|
+
``g/l``, the undamped/undriven pendulum's small-angle frequency
|
|
1192
|
+
squared.
|
|
1193
|
+
A : float, default 1.5
|
|
1194
|
+
Forcing torque amplitude; the default, with the other defaults
|
|
1195
|
+
below, is deep in the chaotic regime.
|
|
1196
|
+
omega_d : float, default 2.0/3.0
|
|
1197
|
+
Forcing angular frequency.
|
|
1198
|
+
|
|
1199
|
+
Attributes
|
|
1200
|
+
----------
|
|
1201
|
+
damping, g_over_l, A, omega_d : float
|
|
1202
|
+
System parameters.
|
|
1203
|
+
"""
|
|
1204
|
+
|
|
1205
|
+
#: State dimension, always 2. State is ``(theta, omega)``.
|
|
1206
|
+
dim = 2
|
|
1207
|
+
|
|
1208
|
+
def __init__(self, damping: float = 0.5, g_over_l: float = 1.0, A: float = 1.5, omega_d: float = 2.0 / 3.0):
|
|
1209
|
+
self.damping = float(damping)
|
|
1210
|
+
self.g_over_l = float(g_over_l)
|
|
1211
|
+
self.A = float(A)
|
|
1212
|
+
self.omega_d = float(omega_d)
|
|
1213
|
+
|
|
1214
|
+
@property
|
|
1215
|
+
def params(self) -> NDArray[np.float64]:
|
|
1216
|
+
"""Parameter vector ``(damping, g_over_l, A, omega_d)``.
|
|
1217
|
+
|
|
1218
|
+
Returns
|
|
1219
|
+
-------
|
|
1220
|
+
ndarray of float, shape (4,)
|
|
1221
|
+
"""
|
|
1222
|
+
return np.array([self.damping, self.g_over_l, self.A, self.omega_d])
|
|
1223
|
+
|
|
1224
|
+
def rhs(self, state: NDArray[np.float64], t: float) -> NDArray[np.float64]:
|
|
1225
|
+
"""Evaluate the driven-pendulum vector field.
|
|
1226
|
+
|
|
1227
|
+
Parameters
|
|
1228
|
+
----------
|
|
1229
|
+
state : ndarray of float, shape (2,)
|
|
1230
|
+
State vector ``(theta, omega)``.
|
|
1231
|
+
t : float
|
|
1232
|
+
Current time (the forcing term depends on `t`).
|
|
1233
|
+
|
|
1234
|
+
Returns
|
|
1235
|
+
-------
|
|
1236
|
+
ndarray of float, shape (2,)
|
|
1237
|
+
Time derivative ``(dtheta/dt, domega/dt)``.
|
|
1238
|
+
"""
|
|
1239
|
+
return np.asarray(_driven_pendulum_rhs(np.asarray(state, dtype=np.float64), t, self.params))
|
|
1240
|
+
|
|
1241
|
+
def initial_state(self) -> NDArray[np.float64]:
|
|
1242
|
+
"""Default initial condition: small angle, at rest.
|
|
1243
|
+
|
|
1244
|
+
Returns
|
|
1245
|
+
-------
|
|
1246
|
+
ndarray of float, shape (2,)
|
|
1247
|
+
"""
|
|
1248
|
+
return np.array([0.2, 0.0])
|
|
1249
|
+
|
|
1250
|
+
def trajectory(
|
|
1251
|
+
self,
|
|
1252
|
+
state0: NDArray[np.float64] | None = None,
|
|
1253
|
+
t0: float = 0.0,
|
|
1254
|
+
dt: float = 0.02,
|
|
1255
|
+
n_steps: int = 5000,
|
|
1256
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
1257
|
+
"""Integrate a trajectory with the Numba-accelerated RK4 integrator.
|
|
1258
|
+
|
|
1259
|
+
Parameters
|
|
1260
|
+
----------
|
|
1261
|
+
state0 : array_like of float, shape (2,), optional
|
|
1262
|
+
Initial state; defaults to :meth:`initial_state`.
|
|
1263
|
+
t0 : float, default 0.0
|
|
1264
|
+
Initial time.
|
|
1265
|
+
dt : float, default 0.02
|
|
1266
|
+
Integration step size.
|
|
1267
|
+
n_steps : int, default 5000
|
|
1268
|
+
Number of integration steps.
|
|
1269
|
+
|
|
1270
|
+
Returns
|
|
1271
|
+
-------
|
|
1272
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
1273
|
+
states : ndarray of float, shape (n_steps + 1, 2)
|
|
1274
|
+
"""
|
|
1275
|
+
state0 = self.initial_state() if state0 is None else np.asarray(state0, dtype=np.float64)
|
|
1276
|
+
return rk4_integrate(_driven_pendulum_rhs, state0, t0, dt, n_steps, self.params)
|