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,304 @@
|
|
|
1
|
+
"""Point-vortex N-body dynamics: the Biot-Savart law and the von Karman vortex street.
|
|
2
|
+
|
|
3
|
+
Concentrating all of a 2D flow's vorticity into a finite set of
|
|
4
|
+
infinitesimal points turns the vorticity-transport PDE into a system of
|
|
5
|
+
ordinary differential equations: each point vortex simply advects with the
|
|
6
|
+
velocity field induced by every *other* point vortex (a vortex induces no
|
|
7
|
+
velocity on itself), via the 2D Biot-Savart law. This is an exact solution
|
|
8
|
+
of the incompressible Euler equations for a singular vorticity distribution,
|
|
9
|
+
not merely a discretization of the continuous vorticity-transport equation
|
|
10
|
+
solved by :mod:`physicskit.fluids.systems.navier_stokes` -- and it is the
|
|
11
|
+
setting in which Theodore von Karman analyzed the staggered, alternating
|
|
12
|
+
vortex street shed behind a bluff body.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
from numba import njit
|
|
19
|
+
from numpy.typing import ArrayLike, NDArray
|
|
20
|
+
|
|
21
|
+
from physicskit.fluids.exceptions import InvalidParameterError
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"point_vortex_velocities",
|
|
25
|
+
"PointVortexSystem",
|
|
26
|
+
"von_karman_vortex_street",
|
|
27
|
+
"VON_KARMAN_SPACING_RATIO",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
#: Theodore von Karman's spacing ratio ``h/l`` (row separation over
|
|
31
|
+
#: along-row spacing) at which a doubly-infinite staggered vortex row is
|
|
32
|
+
#: linearly stable to vortex-row perturbations (von Karman & Rubach, 1912).
|
|
33
|
+
VON_KARMAN_SPACING_RATIO: float = 0.2805
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@njit(cache=True)
|
|
37
|
+
def _point_vortex_velocities(positions: NDArray[np.float64], circulations: NDArray[np.float64], core: float) -> NDArray[np.float64]:
|
|
38
|
+
"""Biot-Savart velocity induced on each point vortex by every other one.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
positions : ndarray of float, shape (n, 2)
|
|
43
|
+
Vortex positions ``(x, y)``.
|
|
44
|
+
circulations : ndarray of float, shape (n,)
|
|
45
|
+
Vortex circulations :math:`\\Gamma_i`.
|
|
46
|
+
core : float
|
|
47
|
+
Regularization (Rankine core) radius; keeps the induced velocity
|
|
48
|
+
finite if two vortices coincide.
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
ndarray of float, shape (n, 2)
|
|
53
|
+
Velocity at each vortex's own position.
|
|
54
|
+
"""
|
|
55
|
+
n = positions.shape[0]
|
|
56
|
+
velocities = np.zeros((n, 2))
|
|
57
|
+
core2 = core * core
|
|
58
|
+
for i in range(n):
|
|
59
|
+
vx, vy = 0.0, 0.0
|
|
60
|
+
xi, yi = positions[i, 0], positions[i, 1]
|
|
61
|
+
for j in range(n):
|
|
62
|
+
if i == j:
|
|
63
|
+
continue
|
|
64
|
+
dx = xi - positions[j, 0]
|
|
65
|
+
dy = yi - positions[j, 1]
|
|
66
|
+
r2 = dx * dx + dy * dy + core2
|
|
67
|
+
factor = circulations[j] / (2.0 * np.pi * r2)
|
|
68
|
+
vx -= factor * dy
|
|
69
|
+
vy += factor * dx
|
|
70
|
+
velocities[i, 0] = vx
|
|
71
|
+
velocities[i, 1] = vy
|
|
72
|
+
return velocities
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@njit(cache=True)
|
|
76
|
+
def _point_vortex_rk4_integrate(
|
|
77
|
+
positions0: NDArray[np.float64], circulations: NDArray[np.float64], core: float, dt: float, n_steps: int
|
|
78
|
+
) -> NDArray[np.float64]:
|
|
79
|
+
"""Integrate the point-vortex system with RK4.
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
positions0 : ndarray of float, shape (n, 2)
|
|
84
|
+
Initial vortex positions.
|
|
85
|
+
circulations : ndarray of float, shape (n,)
|
|
86
|
+
Vortex circulations.
|
|
87
|
+
core : float
|
|
88
|
+
Regularization core radius.
|
|
89
|
+
dt : float
|
|
90
|
+
Time step.
|
|
91
|
+
n_steps : int
|
|
92
|
+
Number of steps to advance.
|
|
93
|
+
|
|
94
|
+
Returns
|
|
95
|
+
-------
|
|
96
|
+
ndarray of float, shape (n_steps + 1, n, 2)
|
|
97
|
+
Vortex positions at each step, including the initial positions.
|
|
98
|
+
"""
|
|
99
|
+
n = positions0.shape[0]
|
|
100
|
+
trajectory = np.empty((n_steps + 1, n, 2))
|
|
101
|
+
trajectory[0] = positions0
|
|
102
|
+
positions = positions0.copy()
|
|
103
|
+
for step in range(n_steps):
|
|
104
|
+
k1 = _point_vortex_velocities(positions, circulations, core)
|
|
105
|
+
k2 = _point_vortex_velocities(positions + 0.5 * dt * k1, circulations, core)
|
|
106
|
+
k3 = _point_vortex_velocities(positions + 0.5 * dt * k2, circulations, core)
|
|
107
|
+
k4 = _point_vortex_velocities(positions + dt * k3, circulations, core)
|
|
108
|
+
positions = positions + (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
|
|
109
|
+
trajectory[step + 1] = positions
|
|
110
|
+
return trajectory
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def point_vortex_velocities(positions: ArrayLike, circulations: ArrayLike, core: float = 1e-6) -> NDArray[np.float64]:
|
|
114
|
+
"""Velocity induced on each point vortex by every other vortex (Biot-Savart law).
|
|
115
|
+
|
|
116
|
+
The 2D Biot-Savart law: an isolated point vortex of circulation
|
|
117
|
+
:math:`\\Gamma_j` at :math:`\\mathbf{r}_j` induces a purely azimuthal
|
|
118
|
+
velocity :math:`\\Gamma_j/(2\\pi r)` at distance `r`, and a system of `n`
|
|
119
|
+
vortices simply superposes these fields (Laplace's equation is linear,
|
|
120
|
+
just as in :mod:`physicskit.fluids.systems.potential_flow` -- indeed a
|
|
121
|
+
point vortex here *is* :func:`~physicskit.fluids.systems.potential_flow.point_vortex_potential`,
|
|
122
|
+
evaluated at every other vortex's location rather than on a field grid).
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
positions : array_like of float, shape (n, 2)
|
|
127
|
+
Vortex positions ``(x, y)``.
|
|
128
|
+
circulations : array_like of float, shape (n,)
|
|
129
|
+
Vortex circulations :math:`\\Gamma_i` (positive counterclockwise).
|
|
130
|
+
core : float, default 1e-6
|
|
131
|
+
Regularization (Rankine core) radius, keeping the induced velocity
|
|
132
|
+
finite if two vortex cores coincide; negligible for any pair
|
|
133
|
+
separated by much more than `core`.
|
|
134
|
+
|
|
135
|
+
Returns
|
|
136
|
+
-------
|
|
137
|
+
ndarray of float, shape (n, 2)
|
|
138
|
+
Velocity at each vortex's own position, induced by all the others.
|
|
139
|
+
|
|
140
|
+
Examples
|
|
141
|
+
--------
|
|
142
|
+
A pair of counter-rotating vortices separated by distance `d` induces
|
|
143
|
+
equal and opposite velocities of magnitude :math:`\\Gamma/(2\\pi d)` on
|
|
144
|
+
each other, translating the pair sideways rather than rotating it:
|
|
145
|
+
|
|
146
|
+
>>> import numpy as np
|
|
147
|
+
>>> Gamma, d = 1.0, 2.0
|
|
148
|
+
>>> positions = np.array([[0.0, d / 2], [0.0, -d / 2]])
|
|
149
|
+
>>> circulations = np.array([Gamma, -Gamma])
|
|
150
|
+
>>> vel = point_vortex_velocities(positions, circulations)
|
|
151
|
+
>>> bool(np.allclose(vel[0], vel[1])) # both translate together
|
|
152
|
+
True
|
|
153
|
+
>>> round(float(vel[0, 0]), 6) == round(Gamma / (2 * np.pi * d), 6)
|
|
154
|
+
True
|
|
155
|
+
"""
|
|
156
|
+
positions = np.asarray(positions, dtype=np.float64)
|
|
157
|
+
circulations = np.asarray(circulations, dtype=np.float64)
|
|
158
|
+
if positions.shape[0] != circulations.shape[0]:
|
|
159
|
+
raise InvalidParameterError("positions and circulations must have the same number of vortices")
|
|
160
|
+
return _point_vortex_velocities(positions, circulations, core)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class PointVortexSystem:
|
|
164
|
+
"""A system of interacting 2D point vortices, advected by their mutual Biot-Savart field.
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
positions : array_like of float, shape (n, 2)
|
|
169
|
+
Initial vortex positions ``(x, y)``.
|
|
170
|
+
circulations : array_like of float, shape (n,)
|
|
171
|
+
Vortex circulations :math:`\\Gamma_i`; fixed for the system's lifetime
|
|
172
|
+
(point vortices carry their circulation with them unchanged).
|
|
173
|
+
core : float, default 1e-6
|
|
174
|
+
Regularization core radius (see :func:`point_vortex_velocities`).
|
|
175
|
+
|
|
176
|
+
Attributes
|
|
177
|
+
----------
|
|
178
|
+
positions : ndarray of float, shape (n, 2)
|
|
179
|
+
Current vortex positions.
|
|
180
|
+
circulations : ndarray of float, shape (n,)
|
|
181
|
+
Vortex circulations.
|
|
182
|
+
core : float
|
|
183
|
+
Regularization core radius.
|
|
184
|
+
n_vortices : int
|
|
185
|
+
Number of vortices, `n`.
|
|
186
|
+
|
|
187
|
+
Raises
|
|
188
|
+
------
|
|
189
|
+
InvalidParameterError
|
|
190
|
+
If `positions` and `circulations` have inconsistent lengths.
|
|
191
|
+
|
|
192
|
+
Examples
|
|
193
|
+
--------
|
|
194
|
+
>>> import numpy as np
|
|
195
|
+
>>> system = PointVortexSystem(positions=[[1.0, 0.0], [-1.0, 0.0]], circulations=[1.0, 1.0])
|
|
196
|
+
>>> t, trajectory = system.trajectory(dt=0.01, n_steps=10)
|
|
197
|
+
>>> trajectory.shape
|
|
198
|
+
(11, 2, 2)
|
|
199
|
+
"""
|
|
200
|
+
|
|
201
|
+
def __init__(self, positions: ArrayLike, circulations: ArrayLike, core: float = 1e-6):
|
|
202
|
+
positions = np.asarray(positions, dtype=np.float64)
|
|
203
|
+
circulations = np.asarray(circulations, dtype=np.float64)
|
|
204
|
+
if positions.shape[0] != circulations.shape[0]:
|
|
205
|
+
raise InvalidParameterError("positions and circulations must have the same number of vortices")
|
|
206
|
+
self.positions = positions
|
|
207
|
+
self.circulations = circulations
|
|
208
|
+
self.core = float(core)
|
|
209
|
+
self.n_vortices = positions.shape[0]
|
|
210
|
+
|
|
211
|
+
def velocities(self) -> NDArray[np.float64]:
|
|
212
|
+
"""Velocity currently induced on each vortex.
|
|
213
|
+
|
|
214
|
+
Returns
|
|
215
|
+
-------
|
|
216
|
+
ndarray of float, shape (n, 2)
|
|
217
|
+
"""
|
|
218
|
+
return _point_vortex_velocities(self.positions, self.circulations, self.core)
|
|
219
|
+
|
|
220
|
+
def trajectory(self, dt: float = 0.01, n_steps: int = 1000) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
221
|
+
"""Integrate the vortex trajectories forward with RK4.
|
|
222
|
+
|
|
223
|
+
Parameters
|
|
224
|
+
----------
|
|
225
|
+
dt : float, default 0.01
|
|
226
|
+
Time step.
|
|
227
|
+
n_steps : int, default 1000
|
|
228
|
+
Number of steps to advance.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
times : ndarray of float, shape (n_steps + 1,)
|
|
233
|
+
positions : ndarray of float, shape (n_steps + 1, n, 2)
|
|
234
|
+
Vortex positions at each step, including the current positions.
|
|
235
|
+
"""
|
|
236
|
+
times = np.arange(n_steps + 1) * dt
|
|
237
|
+
trajectory = _point_vortex_rk4_integrate(self.positions, self.circulations, self.core, dt, n_steps)
|
|
238
|
+
return times, trajectory
|
|
239
|
+
|
|
240
|
+
def __repr__(self) -> str:
|
|
241
|
+
return f"PointVortexSystem(n_vortices={self.n_vortices}, circulations={self.circulations!r})"
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def von_karman_vortex_street(
|
|
245
|
+
n_pairs: int, spacing_l: float, spacing_ratio: float = VON_KARMAN_SPACING_RATIO
|
|
246
|
+
) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
|
|
247
|
+
"""Build a finite, doubly staggered von Karman vortex street.
|
|
248
|
+
|
|
249
|
+
Two parallel rows of point vortices, offset from each other by half the
|
|
250
|
+
along-row spacing `spacing_l` and by a row separation
|
|
251
|
+
``h = spacing_ratio * spacing_l``, with alternating-sign circulation
|
|
252
|
+
within each row so that adjacent vortices across the two rows always
|
|
253
|
+
have *opposite* sign -- the staggered, alternating pattern observed in
|
|
254
|
+
the wake behind a bluff body. Von Karman showed that a doubly *infinite*
|
|
255
|
+
row of this form is linearly stable to vortex-row perturbations at
|
|
256
|
+
exactly one spacing ratio, :data:`VON_KARMAN_SPACING_RATIO`; any other
|
|
257
|
+
ratio (or any symmetric, unstaggered arrangement) grows unstable, which
|
|
258
|
+
is why the observed spacing behind real bluff bodies clusters so
|
|
259
|
+
consistently near this value.
|
|
260
|
+
|
|
261
|
+
Parameters
|
|
262
|
+
----------
|
|
263
|
+
n_pairs : int
|
|
264
|
+
Number of vortex pairs (one from each row) to construct; must be at
|
|
265
|
+
least 2 to form a discernible street.
|
|
266
|
+
spacing_l : float
|
|
267
|
+
Spacing between same-row, same-sign vortices along the street.
|
|
268
|
+
spacing_ratio : float, default :data:`VON_KARMAN_SPACING_RATIO`
|
|
269
|
+
Row separation over along-row spacing, ``h / spacing_l``.
|
|
270
|
+
|
|
271
|
+
Returns
|
|
272
|
+
-------
|
|
273
|
+
positions : ndarray of float, shape (2*n_pairs, 2)
|
|
274
|
+
Vortex positions.
|
|
275
|
+
circulations : ndarray of float, shape (2*n_pairs,)
|
|
276
|
+
Vortex circulations, alternating in sign.
|
|
277
|
+
|
|
278
|
+
Raises
|
|
279
|
+
------
|
|
280
|
+
InvalidParameterError
|
|
281
|
+
If `n_pairs` is smaller than 2 or `spacing_l` is not positive.
|
|
282
|
+
|
|
283
|
+
Examples
|
|
284
|
+
--------
|
|
285
|
+
>>> positions, circulations = von_karman_vortex_street(n_pairs=6, spacing_l=1.0)
|
|
286
|
+
>>> positions.shape
|
|
287
|
+
(12, 2)
|
|
288
|
+
>>> bool(np.isclose(circulations.sum(), 0.0)) # equal and opposite in each row
|
|
289
|
+
True
|
|
290
|
+
"""
|
|
291
|
+
if n_pairs < 2:
|
|
292
|
+
raise InvalidParameterError(f"n_pairs must be at least 2, got {n_pairs}")
|
|
293
|
+
if spacing_l <= 0:
|
|
294
|
+
raise InvalidParameterError(f"spacing_l must be positive, got {spacing_l}")
|
|
295
|
+
h = spacing_ratio * spacing_l
|
|
296
|
+
positions = np.empty((2 * n_pairs, 2))
|
|
297
|
+
circulations = np.empty(2 * n_pairs)
|
|
298
|
+
for i in range(n_pairs):
|
|
299
|
+
sign = 1.0 if i % 2 == 0 else -1.0
|
|
300
|
+
positions[2 * i] = [i * spacing_l, h / 2.0]
|
|
301
|
+
circulations[2 * i] = sign
|
|
302
|
+
positions[2 * i + 1] = [(i + 0.5) * spacing_l, -h / 2.0]
|
|
303
|
+
circulations[2 * i + 1] = -sign
|
|
304
|
+
return positions, circulations
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from physicskit.fluids.exceptions import InvalidParameterError
|
|
4
|
+
from physicskit.fluids.systems.compressible_flow import (
|
|
5
|
+
normal_shock_relations,
|
|
6
|
+
rankine_hugoniot_jump_conditions,
|
|
7
|
+
sod_shock_tube,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_normal_shock_is_identity_at_mach_one():
|
|
12
|
+
jump = normal_shock_relations(M1=1.0)
|
|
13
|
+
assert jump["p2_p1"] == pytest.approx(1.0)
|
|
14
|
+
assert jump["rho2_rho1"] == pytest.approx(1.0)
|
|
15
|
+
assert jump["M2"] == pytest.approx(1.0)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_normal_shock_downstream_mach_is_always_subsonic():
|
|
19
|
+
for M1 in (1.5, 2.0, 5.0, 10.0):
|
|
20
|
+
jump = normal_shock_relations(M1)
|
|
21
|
+
assert jump["M2"] < 1.0
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_normal_shock_satisfies_rankine_hugoniot_conditions():
|
|
25
|
+
gamma = 1.4
|
|
26
|
+
rho1, u1, p1 = 1.0, 3.0, 1.0 / gamma # c1 = 1, so M1 = u1
|
|
27
|
+
jump = normal_shock_relations(M1=u1, gamma=gamma)
|
|
28
|
+
rho2 = rho1 * jump["rho2_rho1"]
|
|
29
|
+
u2 = rho1 * u1 / rho2
|
|
30
|
+
p2 = p1 * jump["p2_p1"]
|
|
31
|
+
residuals = rankine_hugoniot_jump_conditions(rho1, u1, p1, rho2, u2, p2)
|
|
32
|
+
assert max(abs(v) for v in residuals.values()) < 1e-10
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_normal_shock_rejects_subsonic_upstream():
|
|
36
|
+
with pytest.raises(InvalidParameterError):
|
|
37
|
+
normal_shock_relations(M1=0.5)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_sod_shock_tube_conserves_left_and_right_far_field_states():
|
|
41
|
+
result = sod_shock_tube(nx=200, t_final=0.15)
|
|
42
|
+
assert result["rho"][0] == pytest.approx(1.0, abs=1e-2)
|
|
43
|
+
assert result["p"][0] == pytest.approx(1.0, abs=1e-2)
|
|
44
|
+
assert result["rho"][-1] == pytest.approx(0.125, abs=1e-2)
|
|
45
|
+
assert result["p"][-1] == pytest.approx(0.1, abs=1e-2)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_sod_shock_tube_density_is_monotonically_decreasing_left_to_right():
|
|
49
|
+
"""The Sod problem's three waves (rarefaction, contact, shock) all
|
|
50
|
+
connect the high state to the low state without an intermediate reversal."""
|
|
51
|
+
result = sod_shock_tube(nx=200, t_final=0.15)
|
|
52
|
+
# allow small non-monotonicity from numerical diffusion by smoothing lightly
|
|
53
|
+
rho = result["rho"]
|
|
54
|
+
assert rho[0] > rho[len(rho) // 2] > rho[-1]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_sod_shock_tube_rejects_invalid_cfl():
|
|
58
|
+
with pytest.raises(InvalidParameterError):
|
|
59
|
+
sod_shock_tube(nx=100, cfl=1.5)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_sod_shock_tube_rejects_too_few_cells_and_nonpositive_t_final():
|
|
63
|
+
with pytest.raises(InvalidParameterError):
|
|
64
|
+
sod_shock_tube(nx=3)
|
|
65
|
+
with pytest.raises(InvalidParameterError):
|
|
66
|
+
sod_shock_tube(nx=100, t_final=0.0)
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.fluids.exceptions import InvalidParameterError
|
|
5
|
+
from physicskit.fluids.systems.instabilities import (
|
|
6
|
+
kelvin_helmholtz_growth_rate,
|
|
7
|
+
kelvin_helmholtz_ic,
|
|
8
|
+
rayleigh_taylor_growth_rate,
|
|
9
|
+
rayleigh_taylor_ic,
|
|
10
|
+
simulate_kelvin_helmholtz,
|
|
11
|
+
simulate_rayleigh_taylor,
|
|
12
|
+
)
|
|
13
|
+
from physicskit.fluids.systems.navier_stokes import simulate_vorticity_streamfunction
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_kelvin_helmholtz_growth_rate_is_linear_in_k_and_delta_u():
|
|
17
|
+
assert kelvin_helmholtz_growth_rate(k=2.0, delta_u=3.0) == pytest.approx(3.0)
|
|
18
|
+
assert kelvin_helmholtz_growth_rate(k=4.0, delta_u=3.0) == pytest.approx(6.0)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.slow
|
|
22
|
+
def test_kelvin_helmholtz_shear_layer_perturbation_grows():
|
|
23
|
+
n = 96
|
|
24
|
+
omega0 = kelvin_helmholtz_ic(n, 2 * np.pi, shear_width=0.1, perturbation_amplitude=0.05)
|
|
25
|
+
amps = []
|
|
26
|
+
omega = omega0.copy()
|
|
27
|
+
for _ in range(3):
|
|
28
|
+
result = simulate_vorticity_streamfunction(omega, nu=0.001, dt=0.0025, steps=100, length=2 * np.pi)
|
|
29
|
+
omega = result["omega"]
|
|
30
|
+
dev = omega - omega.mean(axis=0, keepdims=True)
|
|
31
|
+
amps.append(np.sqrt(np.mean(dev**2)))
|
|
32
|
+
assert amps[-1] > 2 * amps[0]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_simulate_kelvin_helmholtz_matches_direct_vorticity_streamfunction_call():
|
|
36
|
+
n, length = 64, 2 * np.pi
|
|
37
|
+
omega0 = kelvin_helmholtz_ic(n, length, shear_width=0.1, perturbation_amplitude=0.05)
|
|
38
|
+
result_wrapper = simulate_kelvin_helmholtz(omega0, nu=0.001, dt=0.0025, steps=50, length=length)
|
|
39
|
+
result_direct = simulate_vorticity_streamfunction(omega0, nu=0.001, dt=0.0025, steps=50, length=length)
|
|
40
|
+
assert result_wrapper["omega"] == pytest.approx(result_direct["omega"])
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@pytest.mark.slow
|
|
44
|
+
def test_simulate_kelvin_helmholtz_shear_layer_rolls_up():
|
|
45
|
+
n, length = 96, 2 * np.pi
|
|
46
|
+
omega0 = kelvin_helmholtz_ic(n, length, shear_width=0.1, perturbation_amplitude=0.05)
|
|
47
|
+
omega = omega0.copy()
|
|
48
|
+
amps = []
|
|
49
|
+
for _ in range(3):
|
|
50
|
+
result = simulate_kelvin_helmholtz(omega, nu=0.001, dt=0.0025, steps=100, length=length)
|
|
51
|
+
omega = result["omega"]
|
|
52
|
+
dev = omega - omega.mean(axis=0, keepdims=True)
|
|
53
|
+
amps.append(np.sqrt(np.mean(dev**2)))
|
|
54
|
+
assert amps[-1] > 2 * amps[0]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_simulate_kelvin_helmholtz_rejects_nonpositive_viscosity():
|
|
58
|
+
omega0 = kelvin_helmholtz_ic(32, 2 * np.pi, shear_width=0.1, perturbation_amplitude=0.05)
|
|
59
|
+
with pytest.raises(InvalidParameterError):
|
|
60
|
+
simulate_kelvin_helmholtz(omega0, nu=-0.001, dt=0.0025, steps=1, length=2 * np.pi)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_rayleigh_taylor_growth_rate_matches_sqrt_agk():
|
|
64
|
+
A, g, k = 0.3, 9.8, 2.0
|
|
65
|
+
assert rayleigh_taylor_growth_rate(k, A, g) == pytest.approx(np.sqrt(A * g * k))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_rayleigh_taylor_growth_rate_rejects_invalid_atwood_number():
|
|
69
|
+
with pytest.raises(InvalidParameterError):
|
|
70
|
+
rayleigh_taylor_growth_rate(k=1.0, atwood_number=1.5, g=1.0)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_rayleigh_taylor_growth_rate_rejects_negative_k():
|
|
74
|
+
with pytest.raises(InvalidParameterError):
|
|
75
|
+
rayleigh_taylor_growth_rate(k=-1.0, atwood_number=0.3, g=1.0)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_rayleigh_taylor_ic_rejects_invalid_atwood_number():
|
|
79
|
+
with pytest.raises(InvalidParameterError):
|
|
80
|
+
rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=1.5)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_simulate_rayleigh_taylor_rejects_nonpositive_kappa_and_g():
|
|
84
|
+
omega0, buoyancy0 = rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=0.2)
|
|
85
|
+
with pytest.raises(InvalidParameterError):
|
|
86
|
+
simulate_rayleigh_taylor(omega0, buoyancy0, nu=0.001, kappa=0.0, g=1.0, dt=0.01, steps=1, length=2 * np.pi)
|
|
87
|
+
with pytest.raises(InvalidParameterError):
|
|
88
|
+
simulate_rayleigh_taylor(omega0, buoyancy0, nu=0.001, kappa=0.001, g=0.0, dt=0.01, steps=1, length=2 * np.pi)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def test_rayleigh_taylor_ic_starts_at_rest_with_heavy_fluid_on_top():
|
|
92
|
+
omega0, buoyancy0 = rayleigh_taylor_ic(64, 2 * np.pi, atwood_number=0.3)
|
|
93
|
+
assert np.all(omega0 == 0.0)
|
|
94
|
+
top_mean = buoyancy0[:, -1].mean()
|
|
95
|
+
bottom_mean = buoyancy0[:, 0].mean()
|
|
96
|
+
assert bottom_mean > 0 > top_mean # bottom is light (positive b), top is heavy
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_rayleigh_taylor_interface_perturbation_grows_into_the_nonlinear_regime():
|
|
100
|
+
n, length = 64, 2 * np.pi
|
|
101
|
+
omega0, buoyancy0 = rayleigh_taylor_ic(n, length, atwood_number=0.3, perturbation_amplitude=0.01)
|
|
102
|
+
omega, buoyancy = omega0.copy(), buoyancy0.copy()
|
|
103
|
+
amps = []
|
|
104
|
+
for _ in range(10):
|
|
105
|
+
result = simulate_rayleigh_taylor(omega, buoyancy, nu=0.002, kappa=0.002, g=1.0, dt=0.01, steps=50, length=length)
|
|
106
|
+
omega, buoyancy = result["omega"], result["buoyancy"]
|
|
107
|
+
dev = buoyancy - buoyancy.mean(axis=0, keepdims=True)
|
|
108
|
+
amps.append(np.sqrt(np.mean(dev**2)))
|
|
109
|
+
assert amps[-1] > 3 * amps[0]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_simulate_rayleigh_taylor_rejects_nonpositive_parameters():
|
|
113
|
+
omega0, buoyancy0 = rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=0.2)
|
|
114
|
+
with pytest.raises(InvalidParameterError):
|
|
115
|
+
simulate_rayleigh_taylor(omega0, buoyancy0, nu=-1.0, kappa=0.001, g=1.0, dt=0.01, steps=1, length=2 * np.pi)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.fluids.core.grid import spectral_grid, vorticity_from_velocity
|
|
5
|
+
from physicskit.fluids.exceptions import InvalidParameterError
|
|
6
|
+
from physicskit.fluids.systems.navier_stokes import NavierStokes2D, simulate_vorticity_streamfunction
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_vorticity_from_velocity_matches_analytic_curl():
|
|
10
|
+
n, length = 48, 2 * np.pi
|
|
11
|
+
X, Y, KX, KY, K2 = spectral_grid(n, length)
|
|
12
|
+
u, v = np.sin(Y), np.zeros_like(X)
|
|
13
|
+
# omega = dv/dx - du/dy = 0 - cos(Y) = -cos(Y)
|
|
14
|
+
omega = vorticity_from_velocity(u, v, KX, KY)
|
|
15
|
+
np.testing.assert_allclose(omega, -np.cos(Y), atol=1e-10)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_viscous_decay_reduces_peak_vorticity():
|
|
19
|
+
n, length = 48, 2 * np.pi
|
|
20
|
+
X, Y, KX, KY, K2 = spectral_grid(n, length)
|
|
21
|
+
omega0 = np.sin(X) * np.sin(Y)
|
|
22
|
+
result = simulate_vorticity_streamfunction(omega0, nu=0.05, dt=0.01, steps=50, length=length)
|
|
23
|
+
assert np.max(np.abs(result["omega"])) < np.max(np.abs(omega0))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_navier_stokes_2d_class_matches_functional_api():
|
|
27
|
+
n, length, nu, dt, steps = 32, 2 * np.pi, 0.05, 0.01, 10
|
|
28
|
+
X, Y, KX, KY, K2 = spectral_grid(n, length)
|
|
29
|
+
omega0 = np.sin(X) * np.sin(Y)
|
|
30
|
+
solver = NavierStokes2D(n=n, length=length, nu=nu)
|
|
31
|
+
result_class = solver.simulate(omega0, dt=dt, steps=steps)
|
|
32
|
+
result_func = simulate_vorticity_streamfunction(omega0, nu=nu, dt=dt, steps=steps, length=length)
|
|
33
|
+
np.testing.assert_allclose(result_class["omega"], result_func["omega"], atol=1e-10)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_velocity_field_is_divergence_free():
|
|
37
|
+
"""The streamfunction formulation guarantees div(u) = 0 identically;
|
|
38
|
+
check it numerically to catch any sign/index error in the spectral derivatives."""
|
|
39
|
+
n, length = 48, 2 * np.pi
|
|
40
|
+
solver = NavierStokes2D(n=n, length=length, nu=0.01)
|
|
41
|
+
omega0 = np.sin(2 * solver.X) * np.cos(solver.Y) + np.cos(solver.X)
|
|
42
|
+
u, v = solver.velocity(omega0)
|
|
43
|
+
u_hat, v_hat = np.fft.fft2(u), np.fft.fft2(v)
|
|
44
|
+
divergence = np.real(np.fft.ifft2(1j * solver.KX * u_hat + 1j * solver.KY * v_hat))
|
|
45
|
+
assert np.max(np.abs(divergence)) < 1e-8
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_navier_stokes_rejects_nonpositive_viscosity():
|
|
49
|
+
with pytest.raises(InvalidParameterError):
|
|
50
|
+
NavierStokes2D(n=32, length=2 * np.pi, nu=0.0)
|
|
51
|
+
with pytest.raises(InvalidParameterError):
|
|
52
|
+
simulate_vorticity_streamfunction(np.zeros((16, 16)), nu=-0.1, dt=0.01, steps=1, length=2 * np.pi)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_spectral_grid_rejects_too_coarse_resolution():
|
|
56
|
+
with pytest.raises(InvalidParameterError):
|
|
57
|
+
spectral_grid(n=4, length=1.0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_spectral_grid_rejects_nonpositive_length():
|
|
61
|
+
with pytest.raises(InvalidParameterError):
|
|
62
|
+
spectral_grid(n=32, length=0.0)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_navier_stokes_2d_repr():
|
|
66
|
+
solver = NavierStokes2D(n=32, length=2 * np.pi, nu=0.05)
|
|
67
|
+
assert repr(solver) == "NavierStokes2D(n=32, length=6.283185307179586, nu=0.05)"
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import pytest
|
|
3
|
+
|
|
4
|
+
from physicskit.fluids.exceptions import InvalidParameterError
|
|
5
|
+
from physicskit.fluids.systems.potential_flow import (
|
|
6
|
+
PotentialFlow,
|
|
7
|
+
flow_past_cylinder,
|
|
8
|
+
kutta_joukowski_lift,
|
|
9
|
+
pressure_coefficient,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_cylinder_surface_is_a_single_streamline():
|
|
14
|
+
"""Uniform flow + doublet places a circular streamline exactly at the
|
|
15
|
+
doublet's radius; every point on that circle must share one streamfunction value."""
|
|
16
|
+
flow = flow_past_cylinder(U_inf=2.0, radius=1.5, circulation=3.0)
|
|
17
|
+
theta = np.linspace(0, 2 * np.pi, 37)
|
|
18
|
+
x, y = 1.5 * np.cos(theta), 1.5 * np.sin(theta)
|
|
19
|
+
psi = flow.streamfunction(x, y)
|
|
20
|
+
assert np.max(np.abs(psi - psi[0])) < 1e-6
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_cylinder_surface_speed_matches_classic_formula():
|
|
24
|
+
"""On a lift-free cylinder, the tangential surface speed is exactly
|
|
25
|
+
2*U_inf*sin(theta) -- the textbook result, checked independently of the
|
|
26
|
+
velocity() finite-difference implementation via the surface Cp."""
|
|
27
|
+
U_inf, R = 1.0, 1.0
|
|
28
|
+
flow = flow_past_cylinder(U_inf=U_inf, radius=R, circulation=0.0)
|
|
29
|
+
theta = np.linspace(0.01, np.pi - 0.01, 25) # avoid stagnation points
|
|
30
|
+
x, y = R * np.cos(theta), R * np.sin(theta)
|
|
31
|
+
Cp = flow.pressure_coefficient(x, y)
|
|
32
|
+
expected_speed = 2.0 * U_inf * np.sin(theta)
|
|
33
|
+
expected_Cp = 1.0 - (expected_speed / U_inf) ** 2
|
|
34
|
+
np.testing.assert_allclose(Cp, expected_Cp, atol=1e-4)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_kutta_joukowski_lift_matches_cylinder_pressure_integral():
|
|
38
|
+
"""Integrating Cp around the lifting cylinder's surface should reproduce
|
|
39
|
+
the Kutta-Joukowski lift, an independent cross-check of two different
|
|
40
|
+
ways to compute the same force."""
|
|
41
|
+
U_inf, R, rho, Gamma = 1.0, 1.0, 1.0, 1.0
|
|
42
|
+
flow = flow_past_cylinder(U_inf=U_inf, radius=R, circulation=Gamma)
|
|
43
|
+
theta = np.linspace(0, 2 * np.pi, 2000, endpoint=False)
|
|
44
|
+
x, y = R * np.cos(theta), R * np.sin(theta)
|
|
45
|
+
Cp = flow.pressure_coefficient(x, y)
|
|
46
|
+
p = Cp * (0.5 * rho * U_inf**2)
|
|
47
|
+
# Lift is -oint p * sin(theta) * R dtheta (normal force projected onto y);
|
|
48
|
+
# a plain Riemann sum over a uniform grid is exact for a periodic integrand.
|
|
49
|
+
dtheta = theta[1] - theta[0]
|
|
50
|
+
lift_numeric = -np.sum(p * np.sin(theta) * R) * dtheta
|
|
51
|
+
lift_theory = kutta_joukowski_lift(rho, U_inf, Gamma)
|
|
52
|
+
# the sign of the pressure-integral convention above is independent of
|
|
53
|
+
# the theorem itself; only the magnitude is being cross-checked here.
|
|
54
|
+
assert abs(lift_numeric) == pytest.approx(lift_theory, rel=0.02)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def test_flow_past_cylinder_rejects_nonpositive_radius():
|
|
58
|
+
with pytest.raises(InvalidParameterError):
|
|
59
|
+
flow_past_cylinder(U_inf=1.0, radius=0.0)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_flow_past_cylinder_rejects_nonpositive_u_inf():
|
|
63
|
+
with pytest.raises(InvalidParameterError):
|
|
64
|
+
flow_past_cylinder(U_inf=0.0, radius=1.0)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_pressure_coefficient_is_one_at_stagnation():
|
|
68
|
+
"""Cp = 1 exactly where the local speed vanishes (a stagnation point)."""
|
|
69
|
+
assert pressure_coefficient(u=0.0, v=0.0, U_inf=1.0) == 1.0
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_pressure_coefficient_rejects_nonpositive_u_inf():
|
|
73
|
+
with pytest.raises(InvalidParameterError):
|
|
74
|
+
pressure_coefficient(u=0.0, v=0.0, U_inf=0.0)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_kutta_joukowski_lift_rejects_nonpositive_rho_and_u_inf():
|
|
78
|
+
with pytest.raises(InvalidParameterError):
|
|
79
|
+
kutta_joukowski_lift(rho=0.0, U_inf=1.0, circulation=1.0)
|
|
80
|
+
with pytest.raises(InvalidParameterError):
|
|
81
|
+
kutta_joukowski_lift(rho=1.2, U_inf=0.0, circulation=1.0)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_source_and_sink_pair_has_zero_net_streamfunction_far_away():
|
|
85
|
+
"""A source and an equal-strength sink placed symmetrically must produce
|
|
86
|
+
a flow that looks, from far away, like nothing at all (net source strength zero)."""
|
|
87
|
+
flow = PotentialFlow(U_inf=0.0)
|
|
88
|
+
flow.add_source(strength=5.0, x0=-0.1, y0=0.0)
|
|
89
|
+
flow.add_source(strength=-5.0, x0=0.1, y0=0.0)
|
|
90
|
+
far_x, far_y = 1000.0, 1000.0
|
|
91
|
+
u, v = flow.velocity(far_x, far_y)
|
|
92
|
+
assert np.hypot(u, v) < 1e-6
|