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,113 @@
|
|
|
1
|
+
"""Tests for the DynamicalSystem/ODESystem/HamiltonianSystem base-class
|
|
2
|
+
machinery in physicskit.classical.core.base_system that every concrete system
|
|
3
|
+
uses but that no single concrete system exercises end to end:
|
|
4
|
+
``reset()`` (never called by any real system or example), the
|
|
5
|
+
``force()``/``derivatives()`` defensive defaults for a non-separable
|
|
6
|
+
Hamiltonian system with no ``_deriv_njit`` of its own (every real
|
|
7
|
+
HamiltonianSystem subclass in this package is separable and never hits
|
|
8
|
+
them), and both base classes' "unknown integration method" guard.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
16
|
+
from physicskit.classical.core.base_system import HamiltonianSystem
|
|
17
|
+
from physicskit.classical.systems.newtonian import FoucaultPendulum, ProjectileMotion
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class _MinimalNonSeparableHamiltonian(HamiltonianSystem):
|
|
21
|
+
"""Smallest possible non-separable HamiltonianSystem subclass. Every
|
|
22
|
+
real system in this package is separable and provides its own
|
|
23
|
+
``_force_njit``/``_deriv_njit``, so the base class's defensive
|
|
24
|
+
defaults for the non-separable, nothing-provided case are otherwise
|
|
25
|
+
dead code."""
|
|
26
|
+
|
|
27
|
+
separable = False
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
super().__init__([0.0], [0.0])
|
|
31
|
+
|
|
32
|
+
def kinetic_energy(self, p):
|
|
33
|
+
return 0.5 * p[0] ** 2
|
|
34
|
+
|
|
35
|
+
def potential_energy(self, q):
|
|
36
|
+
return 0.0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_hamiltonian_system_reset_updates_state_and_time():
|
|
40
|
+
system = ProjectileMotion(q0=[0.0, 0.0], p0=[1.0, 1.0])
|
|
41
|
+
new_state = system.reset(state0=[5.0, 5.0, 2.0, 2.0], t0=3.0)
|
|
42
|
+
np.testing.assert_allclose(new_state, [5.0, 5.0, 2.0, 2.0])
|
|
43
|
+
assert system.t == 3.0
|
|
44
|
+
assert system.state is new_state
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_reset_with_no_state0_keeps_current_state_but_updates_time():
|
|
48
|
+
system = ProjectileMotion(q0=[1.0, 2.0], p0=[3.0, 4.0])
|
|
49
|
+
original_state = system.state
|
|
50
|
+
returned = system.reset(t0=7.0)
|
|
51
|
+
np.testing.assert_allclose(returned, original_state)
|
|
52
|
+
assert system.t == 7.0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_hamiltonian_system_force_raises_for_non_separable_system_without_force():
|
|
56
|
+
system = _MinimalNonSeparableHamiltonian()
|
|
57
|
+
with pytest.raises(NotImplementedError):
|
|
58
|
+
system.force(system.q, 0.0)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_hamiltonian_system_derivatives_raises_for_non_separable_system_without_deriv():
|
|
62
|
+
system = _MinimalNonSeparableHamiltonian()
|
|
63
|
+
with pytest.raises(NotImplementedError):
|
|
64
|
+
system.derivatives(0.0, system.state)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_hamiltonian_system_integrate_rejects_symplectic_method_for_non_separable_system():
|
|
68
|
+
system = _MinimalNonSeparableHamiltonian()
|
|
69
|
+
with pytest.raises(ValueError):
|
|
70
|
+
system.integrate((0.0, 1.0), dt=0.1, method="verlet")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_hamiltonian_system_integrate_rejects_unknown_method():
|
|
74
|
+
system = ProjectileMotion(q0=[0.0, 0.0], p0=[1.0, 1.0])
|
|
75
|
+
with pytest.raises(ValueError):
|
|
76
|
+
system.integrate((0.0, 1.0), dt=0.1, method="bogus")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_hamiltonian_system_force_matches_gravity_for_separable_system():
|
|
80
|
+
system = ProjectileMotion(q0=[0.0, 10.0], p0=[1.0, 0.0])
|
|
81
|
+
fx, fy = system.force(system.q, 0.0)
|
|
82
|
+
assert fx == pytest.approx(0.0)
|
|
83
|
+
assert fy == pytest.approx(-system.m * system.g)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_hamiltonian_system_derivatives_matches_auto_built_separable_deriv():
|
|
87
|
+
"""ProjectileMotion without drag never sets self._deriv_njit itself,
|
|
88
|
+
so .derivatives() must fall back to auto-building it from force()
|
|
89
|
+
(HamiltonianSystem._ensure_deriv_njit's separable branch)."""
|
|
90
|
+
system = ProjectileMotion(q0=[0.0, 10.0], p0=[1.0, 0.0])
|
|
91
|
+
d = system.derivatives(0.0, system.state)
|
|
92
|
+
assert d.shape == (4,)
|
|
93
|
+
vx, vy, fx, fy = d
|
|
94
|
+
assert vx == pytest.approx(system.p[0] * system.mass_inv)
|
|
95
|
+
assert vy == pytest.approx(system.p[1] * system.mass_inv)
|
|
96
|
+
assert fx == pytest.approx(0.0)
|
|
97
|
+
assert fy == pytest.approx(-system.m * system.g)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_ode_system_derivatives_matches_foucault_equations_by_hand():
|
|
101
|
+
system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.5, -0.2])
|
|
102
|
+
d = system.derivatives(0.0, system.state)
|
|
103
|
+
x, y, vx, vy = system.state
|
|
104
|
+
assert d[0] == pytest.approx(vx)
|
|
105
|
+
assert d[1] == pytest.approx(vy)
|
|
106
|
+
assert d[2] == pytest.approx(-(system.omega0**2) * x + 2 * system.omega_z * vy)
|
|
107
|
+
assert d[3] == pytest.approx(-(system.omega0**2) * y - 2 * system.omega_z * vx)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_ode_system_integrate_rejects_unknown_method():
|
|
111
|
+
system = FoucaultPendulum(q0=[1.0, 0.0], v0=[0.0, 0.0])
|
|
112
|
+
with pytest.raises(ValueError):
|
|
113
|
+
system.integrate((0.0, 1.0), dt=0.1, method="bogus")
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Targeted checks for physicskit.classical.systems.chains.SineGordonChain.kink beyond
|
|
2
|
+
energy conservation (covered in test_conservation.py): this exists
|
|
3
|
+
because of two real bugs found while building the examples gallery --
|
|
4
|
+
the original boost formula had a sign error, and using the default
|
|
5
|
+
width (2.0, arbitrary) rather than sqrt(k/m) (1.0, for the default
|
|
6
|
+
m=1, k=1) gave a profile that isn't actually a valid soliton and
|
|
7
|
+
visibly relaxes/radiates instead of propagating cleanly.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from physicskit.classical.systems.chains import SineGordonChain
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _front_position(q: np.ndarray) -> float:
|
|
18
|
+
"""Interpolated lattice site where q crosses pi (the kink's center)."""
|
|
19
|
+
return float(np.interp(np.pi, q, np.arange(len(q))))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_sine_gordon_chain_defaults_to_zero_state_when_q0_p0_omitted():
|
|
23
|
+
system = SineGordonChain(n=10)
|
|
24
|
+
np.testing.assert_allclose(system.q, np.zeros(10))
|
|
25
|
+
np.testing.assert_allclose(system.p, np.zeros(10))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_kink_moves_in_the_requested_direction():
|
|
29
|
+
n = 200
|
|
30
|
+
q_right, p_right = SineGordonChain.kink(n, center=80, width=1.0, velocity=0.4, polarity=1)
|
|
31
|
+
q_left, p_left = SineGordonChain.kink(n, center=80, width=1.0, velocity=-0.4, polarity=1)
|
|
32
|
+
|
|
33
|
+
right_mover = SineGordonChain(n=n, q0=q_right, p0=p_right)
|
|
34
|
+
left_mover = SineGordonChain(n=n, q0=q_left, p0=p_left)
|
|
35
|
+
|
|
36
|
+
res_right = right_mover.integrate((0, 50), dt=0.005, method="yoshida4")
|
|
37
|
+
res_left = left_mover.integrate((0, 50), dt=0.005, method="yoshida4")
|
|
38
|
+
|
|
39
|
+
assert _front_position(res_right.q[-1]) > _front_position(res_right.q[0])
|
|
40
|
+
assert _front_position(res_left.q[-1]) < _front_position(res_left.q[0])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_kink_with_matched_width_keeps_its_shape():
|
|
44
|
+
"""width = sqrt(k/m) = 1 (the default) is the only width for which
|
|
45
|
+
the profile is a genuine static/moving solution; its steepness
|
|
46
|
+
should stay essentially constant as it propagates."""
|
|
47
|
+
n = 200
|
|
48
|
+
q0, p0 = SineGordonChain.kink(n, center=60, width=1.0, velocity=0.3, polarity=1)
|
|
49
|
+
system = SineGordonChain(n=n, q0=q0, p0=p0)
|
|
50
|
+
result = system.integrate((0, 150), dt=0.005, method="yoshida4")
|
|
51
|
+
|
|
52
|
+
def measured_width(q):
|
|
53
|
+
return 2.0 / np.max(np.abs(np.diff(q)))
|
|
54
|
+
|
|
55
|
+
w0 = measured_width(result.q[0])
|
|
56
|
+
wf = measured_width(result.q[-1])
|
|
57
|
+
assert abs(wf - w0) / w0 < 0.15
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_kink_with_mismatched_width_visibly_relaxes():
|
|
61
|
+
"""A width that does NOT match sqrt(k/m) is not a genuine soliton
|
|
62
|
+
solution; its steepness should visibly change (relax toward the
|
|
63
|
+
natural width) as the system evolves -- the behavior the width=1.0
|
|
64
|
+
fix above avoids."""
|
|
65
|
+
n = 200
|
|
66
|
+
q0, p0 = SineGordonChain.kink(n, center=100, width=3.0, velocity=0.0, polarity=1)
|
|
67
|
+
system = SineGordonChain(n=n, q0=q0, p0=p0)
|
|
68
|
+
result = system.integrate((0, 100), dt=0.005, method="yoshida4")
|
|
69
|
+
|
|
70
|
+
def measured_width(q):
|
|
71
|
+
return 2.0 / np.max(np.abs(np.diff(q)))
|
|
72
|
+
|
|
73
|
+
w0 = measured_width(result.q[0])
|
|
74
|
+
wf = measured_width(result.q[-1])
|
|
75
|
+
assert abs(wf - w0) / w0 > 0.15
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_kink_rejects_superluminal_velocity():
|
|
79
|
+
import pytest
|
|
80
|
+
|
|
81
|
+
with pytest.raises(ValueError):
|
|
82
|
+
SineGordonChain.kink(100, center=50, width=1.0, velocity=1.0)
|
|
83
|
+
with pytest.raises(ValueError):
|
|
84
|
+
SineGordonChain.kink(100, center=50, width=1.0, velocity=-1.5)
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""Verify |H(t) - H(0)| / |H(0)| < 1e-6 over 10**5 integration steps for
|
|
2
|
+
every conservative system in physicskit.classical, using each system's default
|
|
3
|
+
symplectic integrator (Yoshida4 for separable Hamiltonians,
|
|
4
|
+
implicit-midpoint for non-separable Lagrangian/rigid-body systems).
|
|
5
|
+
|
|
6
|
+
Each system's timestep below was chosen (see the module-level
|
|
7
|
+
``N_STEPS``/tolerance) so that the discretization error -- which for a
|
|
8
|
+
2nd/4th-order symplectic method scales as a fixed power of dt -- comfortably
|
|
9
|
+
clears the 1e-6 relative-drift bar over exactly 10**5 steps; see
|
|
10
|
+
individual test docstrings for the physical timescale each dt resolves.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from physicskit.classical.systems.chains import FPUTChain, HarmonicChain, SineGordonChain
|
|
19
|
+
from physicskit.classical.systems.hamiltonian import HenonHeilesSystem, PendulumSwarm
|
|
20
|
+
from physicskit.classical.systems.lagrangian import BeadOnRotatingHoop, CoupledOscillators, DoublePendulum, ElasticPendulum
|
|
21
|
+
from physicskit.classical.systems.newtonian import FoucaultPendulum, KeplerSystem, ProjectileMotion
|
|
22
|
+
from physicskit.classical.systems.rotations import EulerTop, HeavySymmetricTop
|
|
23
|
+
from physicskit.classical.utils.conservation import lrl_drift, relative_energy_drift
|
|
24
|
+
|
|
25
|
+
N_STEPS = 100_000
|
|
26
|
+
TOL = 1e-6
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _assert_conserved(system, dt, method, n_steps=N_STEPS, tol=TOL):
|
|
30
|
+
result = system.integrate((0.0, n_steps * dt), dt=dt, method=method)
|
|
31
|
+
assert len(result.t) - 1 == n_steps
|
|
32
|
+
drift = relative_energy_drift(result.energy)
|
|
33
|
+
assert np.max(drift) < tol, f"energy drift {np.max(drift):.3e} exceeds tolerance {tol:.1e}"
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_projectile_motion_conserves_energy():
|
|
38
|
+
"""Free fall with an oblique launch velocity (the "cannonball"
|
|
39
|
+
problem), unconstrained over 1e5 steps (~100s of flight, well past
|
|
40
|
+
where a real cannonball would have hit the ground -- gravity alone
|
|
41
|
+
is still exactly conservative for arbitrarily long unconstrained
|
|
42
|
+
free fall)."""
|
|
43
|
+
system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81)
|
|
44
|
+
_assert_conserved(system, dt=1e-3, method="yoshida4")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_projectile_motion_matches_analytic_parabola():
|
|
48
|
+
"""Over the ~2s a real cannonball is actually airborne, a constant
|
|
49
|
+
force is exactly separable, so the symplectic integrator should
|
|
50
|
+
reproduce Galileo's closed-form parabola to floating-point
|
|
51
|
+
precision, not merely to some small tolerance."""
|
|
52
|
+
system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81)
|
|
53
|
+
result = system.integrate((0.0, 2.0), dt=1e-3, method="yoshida4")
|
|
54
|
+
x_analytic, y_analytic = ProjectileMotion.analytic_trajectory(20.0, 45.0, 9.81, result.t)
|
|
55
|
+
assert np.max(np.abs(result.q[:, 0] - x_analytic)) < 1e-9
|
|
56
|
+
assert np.max(np.abs(result.q[:, 1] - y_analytic)) < 1e-9
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_projectile_motion_with_drag_dissipates_energy_and_rejects_symplectic_methods():
|
|
60
|
+
"""Quadratic air drag makes the system genuinely non-conservative:
|
|
61
|
+
energy must decrease monotonically, and requesting a symplectic
|
|
62
|
+
method that can't see the (velocity-dependent) drag force must be
|
|
63
|
+
refused rather than silently ignoring it."""
|
|
64
|
+
system = ProjectileMotion.from_launch(speed=20.0, angle_deg=45.0, g=9.81, drag_coeff=0.05)
|
|
65
|
+
with pytest.raises(ValueError):
|
|
66
|
+
system.integrate((0.0, 1.0), dt=1e-3, method="yoshida4")
|
|
67
|
+
result = system.integrate((0.0, 2.0), dt=1e-3, method="rk4")
|
|
68
|
+
assert np.all(np.diff(result.energy) <= 1e-9)
|
|
69
|
+
assert result.energy[-1] < result.energy[0]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_kepler_pure_conserves_energy_and_lrl():
|
|
73
|
+
"""Pure 1/r Kepler orbit: energy AND the LRL vector should both be
|
|
74
|
+
conserved (no precession without a perturbation)."""
|
|
75
|
+
system = KeplerSystem.from_orbital_elements(a=1.0, e=0.5, k=1.0, mu=1.0)
|
|
76
|
+
lrl0 = system.lrl_vector()
|
|
77
|
+
result = _assert_conserved(system, dt=1e-3, method="yoshida4")
|
|
78
|
+
drift = lrl_drift(system, result.q, result.p)
|
|
79
|
+
assert np.max(drift) / np.linalg.norm(lrl0) < 1e-4
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_kepler_perturbed_conserves_energy():
|
|
83
|
+
"""With the post-Newtonian 1/r**3 correction switched on, the orbit
|
|
84
|
+
precesses (LRL direction rotates) but total energy remains conserved."""
|
|
85
|
+
system = KeplerSystem.from_orbital_elements(a=1.0, e=0.3, k=1.0, mu=1.0, c_pn=0.01)
|
|
86
|
+
_assert_conserved(system, dt=1e-3, method="yoshida4")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_foucault_pendulum_conserves_energy():
|
|
90
|
+
"""The Coriolis term is velocity-perpendicular and does no work, so
|
|
91
|
+
energy is exactly conserved even as the swing plane precesses;
|
|
92
|
+
implicit midpoint preserves this quadratic invariant to machine
|
|
93
|
+
precision for this linear system."""
|
|
94
|
+
system = FoucaultPendulum.from_deflection(amplitude=1.0, latitude_deg=48.85)
|
|
95
|
+
_assert_conserved(system, dt=1e-3, method="implicit_midpoint")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_foucault_pendulum_matches_analytic_precession():
|
|
99
|
+
"""Compare the numerical trajectory against the closed-form
|
|
100
|
+
small-oscillation solution, and check the swing plane is unwound
|
|
101
|
+
(stays near y=0) in the frame corotating at ``omega_z``."""
|
|
102
|
+
system = FoucaultPendulum.from_deflection(amplitude=1.0, latitude_deg=48.85)
|
|
103
|
+
result = system.integrate((0.0, 200.0), dt=1e-3, method="implicit_midpoint")
|
|
104
|
+
x_analytic, y_analytic = FoucaultPendulum.analytic_solution([1.0, 0.0], [0.0, 0.0], system.omega0, system.omega_z, result.t)
|
|
105
|
+
assert np.max(np.abs(result.y[:, 0] - x_analytic)) < 1e-5
|
|
106
|
+
assert np.max(np.abs(result.y[:, 1] - y_analytic)) < 1e-5
|
|
107
|
+
|
|
108
|
+
x_rot, y_rot = FoucaultPendulum.to_corotating_frame(result.y[:, 0], result.y[:, 1], result.t, system.omega_z)
|
|
109
|
+
assert np.max(np.abs(y_rot)) < 1e-3
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@pytest.mark.slow
|
|
113
|
+
def test_double_pendulum_conserves_energy():
|
|
114
|
+
"""Chaotic, non-separable system; validated via the implicit-midpoint
|
|
115
|
+
symplectic integrator on the Legendre-transformed Hamiltonian."""
|
|
116
|
+
system = DoublePendulum([2.0, 1.0], [0.5, -0.3])
|
|
117
|
+
_assert_conserved(system, dt=3e-5, method="implicit_midpoint", n_steps=10_000)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
@pytest.mark.slow
|
|
121
|
+
def test_bead_on_rotating_hoop_conserves_energy():
|
|
122
|
+
system = BeadOnRotatingHoop(0.3, 0.0, omega=3.0)
|
|
123
|
+
_assert_conserved(system, dt=2.2e-5, method="implicit_midpoint", n_steps=10_000)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@pytest.mark.slow
|
|
127
|
+
def test_coupled_oscillators_conserve_energy():
|
|
128
|
+
system = CoupledOscillators([0.1, -0.2, 0.15], [0.0, 0.0, 0.0], n=3)
|
|
129
|
+
_assert_conserved(system, dt=1e-4, method="implicit_midpoint", n_steps=10_000)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_henon_heiles_conserves_energy():
|
|
133
|
+
system = HenonHeilesSystem(np.array([0.0, 0.3]), np.array([0.3, 0.0]))
|
|
134
|
+
_assert_conserved(system, dt=5e-3, method="yoshida4")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@pytest.mark.slow
|
|
138
|
+
def test_elastic_pendulum_conserves_energy():
|
|
139
|
+
"""2-DOF nonlinear (stretch, swing) Lagrangian system; validated via
|
|
140
|
+
implicit midpoint on the Legendre-transformed Hamiltonian, same as
|
|
141
|
+
DoublePendulum."""
|
|
142
|
+
system = ElasticPendulum([0.1, 0.05], [0.0, 0.0])
|
|
143
|
+
_assert_conserved(system, dt=2e-5, method="implicit_midpoint", n_steps=10_000)
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@pytest.mark.slow
|
|
147
|
+
def test_pendulum_swarm_conserves_energy_and_liouville_area():
|
|
148
|
+
"""Every particle in the swarm independently conserves energy over
|
|
149
|
+
a 1e4-step run, and -- checked separately over a shorter
|
|
150
|
+
window, before the patch shears into a filament thin enough that a
|
|
151
|
+
convex-hull area estimate starts overstating it -- the occupied
|
|
152
|
+
phase-space area (a proxy for Liouville's theorem) is preserved."""
|
|
153
|
+
system = PendulumSwarm.from_box(1.0, 0.0, 0.05, 0.05, n=200, seed=1)
|
|
154
|
+
_assert_conserved(system, dt=1e-3, method="yoshida4", n_steps=10_000)
|
|
155
|
+
|
|
156
|
+
system2 = PendulumSwarm.from_box(1.0, 0.0, 0.05, 0.05, n=200, seed=1)
|
|
157
|
+
area0 = system2.phase_space_area()
|
|
158
|
+
system2.integrate((0.0, 5.0), dt=1e-3, method="yoshida4")
|
|
159
|
+
area1 = system2.phase_space_area()
|
|
160
|
+
assert abs(area1 - area0) / area0 < 0.05
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_harmonic_chain_conserves_energy():
|
|
164
|
+
rng = np.random.default_rng(0)
|
|
165
|
+
n = 16
|
|
166
|
+
system = HarmonicChain(rng.normal(0, 0.1, n), np.zeros(n), k=1.0)
|
|
167
|
+
_assert_conserved(system, dt=1e-2, method="yoshida4")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def test_fput_chain_conserves_energy():
|
|
171
|
+
"""N=32 Fermi-Pasta-Ulam-Tsingou beta-lattice."""
|
|
172
|
+
system = FPUTChain(n=32, beta=0.7, mode=1, amplitude=0.5)
|
|
173
|
+
_assert_conserved(system, dt=5e-2, method="yoshida4")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_fput_chain_shows_mode_recurrence():
|
|
177
|
+
"""The energy initially placed in the first normal mode should
|
|
178
|
+
remain the dominant mode throughout (the FPUT non-ergodicity
|
|
179
|
+
signature), not spread evenly across all 32 modes."""
|
|
180
|
+
system = FPUTChain(n=32, beta=0.7, mode=1, amplitude=0.5)
|
|
181
|
+
e0 = system.energy()
|
|
182
|
+
result = system.integrate((0.0, 5000.0), dt=5e-2, method="yoshida4")
|
|
183
|
+
energies = system.modal_energies(result.q[-1], result.p[-1])
|
|
184
|
+
assert energies[0] / e0 > 0.5
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_sine_gordon_chain_conserves_energy():
|
|
188
|
+
q0, p0 = SineGordonChain.kink(100, center=50, width=1.0, velocity=0.2)
|
|
189
|
+
system = SineGordonChain(n=100, q0=q0, p0=p0)
|
|
190
|
+
_assert_conserved(system, dt=1e-2, method="yoshida4")
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def test_euler_top_conserves_energy_and_angular_momentum():
|
|
194
|
+
"""Torque-free asymmetric top: both energy and |L|^2 are quadratic
|
|
195
|
+
invariants exactly preserved by implicit midpoint."""
|
|
196
|
+
system = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
197
|
+
L0 = system.angular_momentum_squared()
|
|
198
|
+
_assert_conserved(system, dt=1e-3, method="implicit_midpoint")
|
|
199
|
+
L1 = system.angular_momentum_squared()
|
|
200
|
+
assert abs(L1 - L0) / L0 < 1e-6
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def test_euler_top_intermediate_axis_instability():
|
|
204
|
+
"""Spinning (almost) exactly about the intermediate-inertia axis
|
|
205
|
+
(I1 < I2 < I3) must show the tumbling instability: the other two
|
|
206
|
+
body-frame components grow far beyond their tiny initial values."""
|
|
207
|
+
system = EulerTop([0.01, 1.0, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
208
|
+
result = system.integrate((0.0, 100.0), dt=1e-3, method="implicit_midpoint")
|
|
209
|
+
assert np.max(np.abs(result.y[:, 0])) > 0.5 # w1 tumbles far above its 0.01 start
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_euler_top_stable_axis_no_tumbling():
|
|
213
|
+
"""Spinning about the smallest-inertia axis is stable: the small
|
|
214
|
+
transverse perturbations stay small for the whole integration."""
|
|
215
|
+
system = EulerTop([1.0, 0.01, 0.01], I1=1.0, I2=2.0, I3=3.0)
|
|
216
|
+
result = system.integrate((0.0, 100.0), dt=1e-3, method="implicit_midpoint")
|
|
217
|
+
assert np.max(np.abs(result.y[:, 1])) < 0.1
|
|
218
|
+
assert np.max(np.abs(result.y[:, 2])) < 0.1
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
@pytest.mark.slow
|
|
222
|
+
def test_heavy_symmetric_top_conserves_energy():
|
|
223
|
+
system = HeavySymmetricTop([0.0, 0.5, 0.0], [0.0, 0.0, 20.0], I1=1.0, I3=0.5, M=1.0, l=1.0, g=9.81)
|
|
224
|
+
_assert_conserved(system, dt=5e-5, method="implicit_midpoint", n_steps=10_000)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
@pytest.mark.parametrize(
|
|
228
|
+
"system_factory,dt",
|
|
229
|
+
[
|
|
230
|
+
(lambda: KeplerSystem.from_orbital_elements(a=1.0, e=0.5), 1e-3),
|
|
231
|
+
(lambda: HenonHeilesSystem(np.array([0.0, 0.3]), np.array([0.3, 0.0])), 5e-3),
|
|
232
|
+
],
|
|
233
|
+
)
|
|
234
|
+
def test_verlet_and_yoshida4_agree_to_leading_order(system_factory, dt):
|
|
235
|
+
"""Sanity check that the 2nd-order Verlet and 4th-order Yoshida4
|
|
236
|
+
backends integrate the same separable system to consistent, small
|
|
237
|
+
energy drift (Yoshida4 should be at least as good as Verlet)."""
|
|
238
|
+
sys_v = system_factory()
|
|
239
|
+
sys_y = system_factory()
|
|
240
|
+
res_v = sys_v.integrate((0.0, 2000 * dt), dt=dt, method="verlet")
|
|
241
|
+
res_y = sys_y.integrate((0.0, 2000 * dt), dt=dt, method="yoshida4")
|
|
242
|
+
drift_v = np.max(relative_energy_drift(res_v.energy))
|
|
243
|
+
drift_y = np.max(relative_energy_drift(res_y.energy))
|
|
244
|
+
assert drift_y <= drift_v * 10 # Yoshida4 is not meaningfully worse than Verlet
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Direct tests for the standalone helpers in
|
|
2
|
+
physicskit.classical.utils.conservation that test_conservation.py's
|
|
3
|
+
end-to-end energy-drift checks never call: energy_drift() (only its
|
|
4
|
+
relative sibling is used there), angular_momentum_2d()/
|
|
5
|
+
angular_momentum_drift() (no test currently checks angular momentum at
|
|
6
|
+
all), and relative_energy_drift()'s zero-initial-energy edge case."""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from physicskit.classical.utils.conservation import (
|
|
13
|
+
angular_momentum_2d,
|
|
14
|
+
angular_momentum_drift,
|
|
15
|
+
energy_drift,
|
|
16
|
+
relative_energy_drift,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_energy_drift_is_absolute_deviation_from_initial_value():
|
|
21
|
+
energy = np.array([10.0, 10.5, 9.5, 10.0])
|
|
22
|
+
drift = energy_drift(energy)
|
|
23
|
+
np.testing.assert_allclose(drift, [0.0, 0.5, 0.5, 0.0])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_relative_energy_drift_is_infinite_when_initial_energy_is_zero():
|
|
27
|
+
energy = np.array([0.0, 1.0, -1.0])
|
|
28
|
+
drift = relative_energy_drift(energy)
|
|
29
|
+
assert np.all(np.isinf(drift))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_angular_momentum_2d_matches_cross_product_formula():
|
|
33
|
+
q = np.array([[1.0, 0.0], [0.0, 2.0]])
|
|
34
|
+
p = np.array([[0.0, 1.0], [3.0, 0.0]])
|
|
35
|
+
L = angular_momentum_2d(q, p)
|
|
36
|
+
np.testing.assert_allclose(L, [1.0 * 1.0 - 0.0 * 0.0, 0.0 * 0.0 - 2.0 * 3.0])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_angular_momentum_drift_is_zero_for_a_conserved_circular_orbit():
|
|
40
|
+
"""A particle moving on a circle at constant angular speed has
|
|
41
|
+
exactly conserved angular momentum, so drift should be exactly
|
|
42
|
+
zero at every sample."""
|
|
43
|
+
theta = np.linspace(0.0, 2.0 * np.pi, 50, endpoint=False)
|
|
44
|
+
q = np.column_stack([np.cos(theta), np.sin(theta)])
|
|
45
|
+
p = np.column_stack([-np.sin(theta), np.cos(theta)])
|
|
46
|
+
drift = angular_momentum_drift(q, p)
|
|
47
|
+
np.testing.assert_allclose(drift, 0.0, atol=1e-12)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Tests for physicskit.classical.systems.hamiltonian's action-angle helper and
|
|
2
|
+
PendulumSwarm.per_particle_energy(), neither of which any other test in
|
|
3
|
+
the suite calls (test_conservation.py only checks PendulumSwarm's total
|
|
4
|
+
energy/Liouville area, and pendulum_action_angle isn't used anywhere)."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from physicskit.classical.systems.hamiltonian import PendulumSwarm, pendulum_action_angle
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_pendulum_action_angle_small_oscillation_limit_matches_harmonic_period():
|
|
15
|
+
"""As E -> -g/l (the bottom of the well), a pendulum's period should
|
|
16
|
+
approach the small-oscillation harmonic result T = 2*pi/sqrt(g/l)."""
|
|
17
|
+
g_over_l = 2.0
|
|
18
|
+
_, period = pendulum_action_angle(-g_over_l + 1e-6, g_over_l)
|
|
19
|
+
assert period == pytest.approx(2.0 * np.pi / np.sqrt(g_over_l), rel=1e-3)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_pendulum_action_angle_rejects_energy_outside_librating_range():
|
|
23
|
+
g_over_l = 1.0
|
|
24
|
+
with pytest.raises(ValueError):
|
|
25
|
+
pendulum_action_angle(g_over_l, g_over_l)
|
|
26
|
+
with pytest.raises(ValueError):
|
|
27
|
+
pendulum_action_angle(-g_over_l, g_over_l)
|
|
28
|
+
with pytest.raises(ValueError):
|
|
29
|
+
pendulum_action_angle(2.0 * g_over_l, g_over_l)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_pendulum_swarm_per_particle_energy_sums_to_the_total_energy():
|
|
33
|
+
swarm = PendulumSwarm([0.1, 0.2, 0.3], [0.05, -0.05, 0.0], g_over_l=1.5)
|
|
34
|
+
per_particle = swarm.per_particle_energy()
|
|
35
|
+
assert per_particle.shape == (3,)
|
|
36
|
+
assert np.sum(per_particle) == pytest.approx(swarm.energy())
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_pendulum_swarm_per_particle_energy_accepts_explicit_state_override():
|
|
40
|
+
swarm = PendulumSwarm([0.1, 0.2, 0.3], [0.05, -0.05, 0.0], g_over_l=1.5)
|
|
41
|
+
default = swarm.per_particle_energy()
|
|
42
|
+
overridden = swarm.per_particle_energy(q=np.zeros(3), p=np.zeros(3))
|
|
43
|
+
np.testing.assert_allclose(overridden, -1.5)
|
|
44
|
+
assert not np.allclose(default, overridden)
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""Direct unit tests for physicskit.classical.core.integrators, independent of any
|
|
2
|
+
physicskit.classical.systems class. These exercise the four integrators (and the
|
|
3
|
+
make_separable_derivatives helper) against a plain harmonic oscillator
|
|
4
|
+
and a simple exponential-decay ODE with known analytic solutions, so a
|
|
5
|
+
regression here is caught at the numerical-core level rather than only
|
|
6
|
+
showing up as a mysterious failure in some system built on top of it.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
from numba import njit
|
|
13
|
+
|
|
14
|
+
from physicskit.classical.core.integrators import (
|
|
15
|
+
implicit_midpoint_integrate,
|
|
16
|
+
implicit_midpoint_step,
|
|
17
|
+
make_separable_derivatives,
|
|
18
|
+
rk4_integrate,
|
|
19
|
+
velocity_verlet_integrate,
|
|
20
|
+
yoshida4_integrate,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@njit(cache=False)
|
|
25
|
+
def _sho_force(q, t):
|
|
26
|
+
return -q # harmonic oscillator, k = m = 1
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@njit(cache=False)
|
|
30
|
+
def _exp_decay_deriv(t, y):
|
|
31
|
+
return -y
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _sho_energy(q, p):
|
|
35
|
+
return 0.5 * q**2 + 0.5 * p**2
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_rk4_matches_analytic_exponential_decay():
|
|
39
|
+
y0 = np.array([1.0])
|
|
40
|
+
dt = 1e-3
|
|
41
|
+
n_steps = 1000
|
|
42
|
+
ts, ys = rk4_integrate(_exp_decay_deriv, y0, 0.0, n_steps, dt)
|
|
43
|
+
analytic = np.exp(-ts)
|
|
44
|
+
assert np.max(np.abs(ys[:, 0] - analytic)) < 1e-9
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_verlet_and_yoshida4_conserve_harmonic_oscillator_energy():
|
|
48
|
+
q0, p0 = np.array([1.0]), np.array([0.0])
|
|
49
|
+
dt, n_steps = 0.01, 50_000
|
|
50
|
+
mass_inv = 1.0
|
|
51
|
+
|
|
52
|
+
ts, qs, ps = velocity_verlet_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
|
|
53
|
+
E = _sho_energy(qs[:, 0], ps[:, 0])
|
|
54
|
+
assert np.max(np.abs(E - E[0])) / E[0] < 1e-3 # 2nd order, bounded oscillation
|
|
55
|
+
|
|
56
|
+
ts, qs, ps = yoshida4_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
|
|
57
|
+
E4 = _sho_energy(qs[:, 0], ps[:, 0])
|
|
58
|
+
assert np.max(np.abs(E4 - E4[0])) / E4[0] < 1e-8 # 4th order: much tighter at the same dt
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_yoshida4_more_accurate_than_verlet_at_fixed_dt():
|
|
62
|
+
"""4th order should beat 2nd order by several orders of magnitude
|
|
63
|
+
at a dt small enough for both to be in their asymptotic regime."""
|
|
64
|
+
q0, p0 = np.array([1.0]), np.array([0.0])
|
|
65
|
+
dt, n_steps = 0.02, 5_000
|
|
66
|
+
mass_inv = 1.0
|
|
67
|
+
|
|
68
|
+
_, qs_v, ps_v = velocity_verlet_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
|
|
69
|
+
_, qs_y, ps_y = yoshida4_integrate(_sho_force, mass_inv, q0, p0, 0.0, n_steps, dt)
|
|
70
|
+
|
|
71
|
+
drift_v = np.max(np.abs(_sho_energy(qs_v[:, 0], ps_v[:, 0]) - 0.5))
|
|
72
|
+
drift_y = np.max(np.abs(_sho_energy(qs_y[:, 0], ps_y[:, 0]) - 0.5))
|
|
73
|
+
assert drift_y < drift_v * 1e-3
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def test_implicit_midpoint_conserves_harmonic_oscillator_energy():
|
|
77
|
+
y0 = np.array([1.0, 0.0])
|
|
78
|
+
|
|
79
|
+
@njit(cache=False)
|
|
80
|
+
def deriv(t, y):
|
|
81
|
+
out = np.empty(2)
|
|
82
|
+
out[0] = y[1]
|
|
83
|
+
out[1] = -y[0]
|
|
84
|
+
return out
|
|
85
|
+
|
|
86
|
+
dt, n_steps = 0.01, 50_000
|
|
87
|
+
ts, ys = implicit_midpoint_integrate(deriv, y0, 0.0, n_steps, dt)
|
|
88
|
+
E = _sho_energy(ys[:, 0], ys[:, 1])
|
|
89
|
+
assert np.max(np.abs(E - E[0])) / E[0] < 1e-10
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_implicit_midpoint_step_exactly_preserves_norm_for_a_rotation_generator():
|
|
93
|
+
"""For dy/dt = A y with A skew-symmetric (a pure rotation vector
|
|
94
|
+
field), |y|^2 is a quadratic invariant; a single implicit-midpoint
|
|
95
|
+
step -- solved via the Cayley transform of A, not the matrix
|
|
96
|
+
exponential -- should still preserve |y| exactly (to machine
|
|
97
|
+
precision) even though it does not reproduce the exact rotation
|
|
98
|
+
angle. This is a direct check of the step formula itself, distinct
|
|
99
|
+
from the aggregate energy-conservation checks elsewhere."""
|
|
100
|
+
|
|
101
|
+
@njit(cache=False)
|
|
102
|
+
def deriv(t, y):
|
|
103
|
+
out = np.empty(2)
|
|
104
|
+
out[0] = -y[1]
|
|
105
|
+
out[1] = y[0]
|
|
106
|
+
return out
|
|
107
|
+
|
|
108
|
+
y0 = np.array([1.0, 0.0])
|
|
109
|
+
dt = 0.1
|
|
110
|
+
y1 = implicit_midpoint_step(deriv, 0.0, y0, dt)
|
|
111
|
+
|
|
112
|
+
assert abs(np.linalg.norm(y1) - np.linalg.norm(y0)) < 1e-12
|
|
113
|
+
assert y1[1] > 0 # rotated counter-clockwise, matching the sign of the generator
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_make_separable_derivatives_matches_verlet_force():
|
|
117
|
+
mass_inv = 1.0
|
|
118
|
+
ndof = 1
|
|
119
|
+
deriv = make_separable_derivatives(_sho_force, mass_inv, ndof)
|
|
120
|
+
y = np.array([0.5, -0.3])
|
|
121
|
+
dy = deriv(0.0, y)
|
|
122
|
+
# dq/dt = p/m, dp/dt = force(q) = -q
|
|
123
|
+
assert np.allclose(dy, [-0.3, -0.5])
|