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,628 @@
|
|
|
1
|
+
r"""Stellar convection and the alpha-omega mean-field magnetic dynamo.
|
|
2
|
+
|
|
3
|
+
The Sun's large-scale magnetic field -- the one that reverses polarity
|
|
4
|
+
every ~11 years and paints the "butterfly diagram" of sunspots migrating
|
|
5
|
+
from mid-latitudes toward the equator -- is not a fossil field left over
|
|
6
|
+
from formation. It is continuously regenerated by the interplay of two
|
|
7
|
+
processes going on simultaneously in the convection zone: small-scale,
|
|
8
|
+
helical turbulent convection (the "alpha effect", parametrizing the net
|
|
9
|
+
effect of many individually chaotic convective eddies twisted by the
|
|
10
|
+
Sun's rotation and stratification) and the large-scale rotational shear
|
|
11
|
+
between the Sun's differentially rotating layers (the "Omega effect",
|
|
12
|
+
which stretches a poloidal field into a much stronger toroidal one).
|
|
13
|
+
Together these sustain the field against Ohmic decay -- this is the
|
|
14
|
+
alpha-omega dynamo mechanism first proposed by Parker (1955), and it is
|
|
15
|
+
the standard textbook explanation for the solar cycle.
|
|
16
|
+
|
|
17
|
+
A crucial and easy-to-miss subtlety (Zeldovich's antidynamo theorem) is
|
|
18
|
+
that a strictly two-dimensional, fully resolved magnetic field confined
|
|
19
|
+
to a plane *cannot* be sustained by 2D fluid motion alone -- genuine
|
|
20
|
+
dynamo action is an intrinsically three-dimensional effect, arising from
|
|
21
|
+
the helical (non-mirror-symmetric) twisting that only a 3D, rotating,
|
|
22
|
+
stratified flow can produce. This module therefore does not attempt to
|
|
23
|
+
build one dynamically self-consistent 2D "dynamo simulation" (which
|
|
24
|
+
would either not be a real dynamo, or would silently smuggle in 3D
|
|
25
|
+
physics through the back door). Instead, exactly as the textbook
|
|
26
|
+
treatment splits the problem, it implements two separate,
|
|
27
|
+
self-contained pieces:
|
|
28
|
+
|
|
29
|
+
- A resolved 2D Boussinesq convection simulation
|
|
30
|
+
(:func:`simulate_stellar_convection`) showing the turbulent
|
|
31
|
+
convective rolls and plumes whose unresolved, helical, three-dimensional
|
|
32
|
+
fine structure is -- in the real Sun -- the physical origin of the
|
|
33
|
+
alpha effect. This piece is a qualitative picture of the turbulence,
|
|
34
|
+
not a dynamo; by Zeldovich's theorem it could never sustain a resolved
|
|
35
|
+
2D field, and it is not asked to.
|
|
36
|
+
- The linearized 1D alpha-omega **mean-field** dynamo equations
|
|
37
|
+
(:func:`simulate_alpha_omega_dynamo`), in which the alpha effect and
|
|
38
|
+
the rotational shear enter as prescribed coefficients rather than
|
|
39
|
+
being derived from the convection simulation above. This piece *does*
|
|
40
|
+
robustly sustain and grow a large-scale field, and reproduces the
|
|
41
|
+
real solar dynamo's most famous piece of phenomenology: a migrating
|
|
42
|
+
dynamo wave, whose space-time diagram is the butterfly diagram.
|
|
43
|
+
|
|
44
|
+
The two pieces are deliberately not coupled to each other; each is
|
|
45
|
+
correct and complete on its own terms.
|
|
46
|
+
|
|
47
|
+
Part 1: 2D convective rolls
|
|
48
|
+
----------------------------
|
|
49
|
+
|
|
50
|
+
A standard doubly periodic, pseudo-spectral Boussinesq system for
|
|
51
|
+
vorticity :math:`\omega`, streamfunction :math:`\psi`, and temperature
|
|
52
|
+
perturbation :math:`T'` about an unstably stratified periodic background
|
|
53
|
+
:math:`T_{eq}(y) = -\beta\cos(2\pi y/L_y)`:
|
|
54
|
+
|
|
55
|
+
.. math::
|
|
56
|
+
|
|
57
|
+
\partial_t \omega + \mathbf{u}\cdot\nabla\omega
|
|
58
|
+
= \nu\nabla^2\omega + g\alpha_{th}\,\partial_x T' \\
|
|
59
|
+
\partial_t T' + \mathbf{u}\cdot\nabla T'
|
|
60
|
+
= \kappa\nabla^2 T' - u_y\,\frac{dT_{eq}}{dy}
|
|
61
|
+
+ \frac{T_{eq}(y) - T'}{\tau_{relax}} \\
|
|
62
|
+
\nabla^2\psi = -\omega, \qquad \mathbf{u} = (\partial_y\psi, -\partial_x\psi)
|
|
63
|
+
|
|
64
|
+
The Newtonian-cooling term :math:`(T_{eq}-T')/\tau_{relax}` (together
|
|
65
|
+
with the advection of the background gradient, :math:`-u_y\,dT_{eq}/dy`)
|
|
66
|
+
is the standard trick for sustaining an unstable stratification in a
|
|
67
|
+
periodic box with no physical walls -- without it, convection would mix
|
|
68
|
+
the background profile flat and decay back to a quiescent state.
|
|
69
|
+
|
|
70
|
+
Part 2: The alpha-omega mean-field dynamo
|
|
71
|
+
-------------------------------------------
|
|
72
|
+
|
|
73
|
+
A linearized 1D reduction (Parker 1955; standard in mean-field dynamo
|
|
74
|
+
theory) for the poloidal-flux proxy :math:`A(x,t)` and toroidal field
|
|
75
|
+
:math:`B(x,t)`, periodic in :math:`x` (a local-Cartesian stand-in for
|
|
76
|
+
stellar latitude or radius):
|
|
77
|
+
|
|
78
|
+
.. math::
|
|
79
|
+
|
|
80
|
+
\partial_t A = \alpha B + \eta\,\partial_x^2 A \\
|
|
81
|
+
\partial_t B = G\,\partial_x A + \eta\,\partial_x^2 B
|
|
82
|
+
|
|
83
|
+
with constant alpha-effect coefficient :math:`\alpha`, shear rate
|
|
84
|
+
(Omega effect) :math:`G`, and magnetic diffusivity :math:`\eta`.
|
|
85
|
+
Substituting a plane-wave mode :math:`(A,B)\propto e^{ikx-i\omega_f t+\sigma t}`
|
|
86
|
+
turns this linear system into the 2x2 eigenvalue problem
|
|
87
|
+
|
|
88
|
+
.. math::
|
|
89
|
+
|
|
90
|
+
\begin{pmatrix}\sigma-i\omega_f+\eta k^2 & -\alpha \\ -iGk & \sigma-i\omega_f+\eta k^2\end{pmatrix}
|
|
91
|
+
\begin{pmatrix}A\\B\end{pmatrix} = 0,
|
|
92
|
+
|
|
93
|
+
whose characteristic equation, with :math:`s=\sigma-i\omega_f+\eta k^2`,
|
|
94
|
+
is :math:`s^2 = i\alpha G k`. Since :math:`i\alpha Gk` is purely
|
|
95
|
+
imaginary, its principal square root always has real and imaginary parts
|
|
96
|
+
of equal magnitude, :math:`\sqrt{|\alpha Gk|/2}` -- giving the closed-form
|
|
97
|
+
growth rate and wave frequency implemented in
|
|
98
|
+
:func:`alpha_omega_growth_rate` and :func:`alpha_omega_wave_frequency`,
|
|
99
|
+
and checked against the full simulation in this module's test suite.
|
|
100
|
+
Because the system is linear, each Fourier mode's amplitude can be
|
|
101
|
+
advanced *exactly* (no time-discretization error at all) via the 2x2
|
|
102
|
+
matrix exponential of its own coefficient matrix -- see
|
|
103
|
+
:func:`simulate_alpha_omega_dynamo`.
|
|
104
|
+
"""
|
|
105
|
+
|
|
106
|
+
from __future__ import annotations
|
|
107
|
+
|
|
108
|
+
import numpy as np
|
|
109
|
+
from numpy.typing import NDArray
|
|
110
|
+
|
|
111
|
+
__all__ = [
|
|
112
|
+
"convective_roll_rhs",
|
|
113
|
+
"simulate_stellar_convection",
|
|
114
|
+
"kinetic_energy",
|
|
115
|
+
"alpha_omega_growth_rate",
|
|
116
|
+
"alpha_omega_wave_frequency",
|
|
117
|
+
"simulate_alpha_omega_dynamo",
|
|
118
|
+
"dominant_mode_growth_rate",
|
|
119
|
+
]
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
# ---------------------------------------------------------------------------
|
|
123
|
+
# Part 1: 2D convective rolls (Boussinesq vorticity-streamfunction-temperature)
|
|
124
|
+
# ---------------------------------------------------------------------------
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _spectral_grid_2d(nx: int, ny: int, Lx: float, Ly: float):
|
|
128
|
+
"""Build the doubly periodic real-space and wavenumber grids for Part 1.
|
|
129
|
+
|
|
130
|
+
Fields are laid out with shape ``(ny, nx)`` -- rows indexed by ``y``,
|
|
131
|
+
columns by ``x`` -- the usual image-like convention (matches
|
|
132
|
+
``imshow``'s default axes and this module's documented snapshot
|
|
133
|
+
shape). Self-contained (not imported from :mod:`physicskit.fluids`),
|
|
134
|
+
per this package's no-cross-subpackage-imports convention, but built
|
|
135
|
+
the same way as :func:`physicskit.fluids.core.grid.spectral_grid`:
|
|
136
|
+
``np.fft.fftfreq``-based wavenumbers, and the ``k=0`` mode of ``K2``
|
|
137
|
+
set to 1 so the Poisson solve is safe to divide by.
|
|
138
|
+
"""
|
|
139
|
+
x = np.linspace(0.0, Lx, nx, endpoint=False)
|
|
140
|
+
y = np.linspace(0.0, Ly, ny, endpoint=False)
|
|
141
|
+
X, Y = np.meshgrid(x, y, indexing="xy")
|
|
142
|
+
kx = 2.0 * np.pi * np.fft.fftfreq(nx, d=Lx / nx)
|
|
143
|
+
ky = 2.0 * np.pi * np.fft.fftfreq(ny, d=Ly / ny)
|
|
144
|
+
KX, KY = np.meshgrid(kx, ky, indexing="xy")
|
|
145
|
+
K2 = KX**2 + KY**2
|
|
146
|
+
K2 = K2.copy()
|
|
147
|
+
K2[0, 0] = 1.0
|
|
148
|
+
return X, Y, KX, KY, K2
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def _poisson_solve_streamfunction(omega: NDArray[np.float64], K2: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
152
|
+
"""Solve :math:`\\nabla^2\\psi=-\\omega` for the streamfunction via FFT (see :func:`_spectral_grid_2d`)."""
|
|
153
|
+
omega_hat = np.fft.fft2(omega)
|
|
154
|
+
psi_hat = omega_hat / K2
|
|
155
|
+
psi_hat[0, 0] = 0.0
|
|
156
|
+
return np.real(np.fft.ifft2(psi_hat))
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _velocity_from_streamfunction(psi: NDArray[np.float64], KX: NDArray[np.float64], KY: NDArray[np.float64]):
|
|
160
|
+
"""Recover :math:`\\mathbf{u}=(\\partial_y\\psi,-\\partial_x\\psi)` from the streamfunction spectrally."""
|
|
161
|
+
psi_hat = np.fft.fft2(psi)
|
|
162
|
+
u = np.real(np.fft.ifft2(1j * KY * psi_hat))
|
|
163
|
+
v = np.real(np.fft.ifft2(-1j * KX * psi_hat))
|
|
164
|
+
return u, v
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def convective_roll_rhs(
|
|
168
|
+
omega: NDArray[np.float64],
|
|
169
|
+
T: NDArray[np.float64],
|
|
170
|
+
Y: NDArray[np.float64],
|
|
171
|
+
KX: NDArray[np.float64],
|
|
172
|
+
KY: NDArray[np.float64],
|
|
173
|
+
K2: NDArray[np.float64],
|
|
174
|
+
nu: float,
|
|
175
|
+
kappa: float,
|
|
176
|
+
g_alpha: float,
|
|
177
|
+
beta: float,
|
|
178
|
+
Ly: float,
|
|
179
|
+
tau_relax: float,
|
|
180
|
+
):
|
|
181
|
+
"""Right-hand side of the 2D Boussinesq convective-roll system, evaluated pseudo-spectrally.
|
|
182
|
+
|
|
183
|
+
Mirrors the structure of the Rayleigh-Taylor Boussinesq right-hand
|
|
184
|
+
side used elsewhere in this codebase for a buoyancy-vorticity system
|
|
185
|
+
(vorticity forced by the buoyancy field's :math:`x`-derivative,
|
|
186
|
+
:math:`g\\,\\partial_x b`, since gravity and the background
|
|
187
|
+
stratification both point along :math:`y`), plus the Newtonian-cooling
|
|
188
|
+
forcing that sustains the unstable background profile
|
|
189
|
+
:math:`T_{eq}(y)=-\\beta\\cos(2\\pi y/L_y)` against being mixed away by
|
|
190
|
+
the convection it drives (see the module docstring).
|
|
191
|
+
|
|
192
|
+
Parameters
|
|
193
|
+
----------
|
|
194
|
+
omega, T : ndarray of float, shape (ny, nx)
|
|
195
|
+
Vorticity and temperature-perturbation fields.
|
|
196
|
+
Y : ndarray of float, shape (ny, nx)
|
|
197
|
+
The ``y`` coordinate grid, from :func:`_spectral_grid_2d`, used to
|
|
198
|
+
evaluate the background profile :math:`T_{eq}(y)` and its
|
|
199
|
+
gradient pointwise.
|
|
200
|
+
KX, KY, K2 : ndarray of float, shape (ny, nx)
|
|
201
|
+
Wavenumber grids from :func:`_spectral_grid_2d`.
|
|
202
|
+
nu : float
|
|
203
|
+
Kinematic viscosity (vorticity diffusivity).
|
|
204
|
+
kappa : float
|
|
205
|
+
Thermal diffusivity.
|
|
206
|
+
g_alpha : float
|
|
207
|
+
Combined buoyancy coefficient :math:`g\\,\\alpha_{th}`.
|
|
208
|
+
beta : float
|
|
209
|
+
Amplitude of the background stratification :math:`T_{eq}(y)`.
|
|
210
|
+
Ly : float
|
|
211
|
+
Domain size along :math:`y` (the period of :math:`T_{eq}`).
|
|
212
|
+
tau_relax : float
|
|
213
|
+
Newtonian-cooling relaxation time back to :math:`T_{eq}(y)`.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
domega_dt, dT_dt : ndarray of float, shape (ny, nx)
|
|
218
|
+
Time derivatives of vorticity and temperature perturbation.
|
|
219
|
+
"""
|
|
220
|
+
psi = _poisson_solve_streamfunction(omega, K2)
|
|
221
|
+
u, v = _velocity_from_streamfunction(psi, KX, KY)
|
|
222
|
+
|
|
223
|
+
omega_hat = np.fft.fft2(omega)
|
|
224
|
+
domega_dx = np.real(np.fft.ifft2(1j * KX * omega_hat))
|
|
225
|
+
domega_dy = np.real(np.fft.ifft2(1j * KY * omega_hat))
|
|
226
|
+
diffusion_omega = np.real(np.fft.ifft2(-nu * K2 * omega_hat))
|
|
227
|
+
|
|
228
|
+
T_hat = np.fft.fft2(T)
|
|
229
|
+
dT_dx = np.real(np.fft.ifft2(1j * KX * T_hat))
|
|
230
|
+
dT_dy = np.real(np.fft.ifft2(1j * KY * T_hat))
|
|
231
|
+
diffusion_T = np.real(np.fft.ifft2(-kappa * K2 * T_hat))
|
|
232
|
+
|
|
233
|
+
Teq = -beta * np.cos(2.0 * np.pi * Y / Ly)
|
|
234
|
+
dTeq_dy = beta * (2.0 * np.pi / Ly) * np.sin(2.0 * np.pi * Y / Ly)
|
|
235
|
+
|
|
236
|
+
domega_dt = -(u * domega_dx + v * domega_dy) + diffusion_omega + g_alpha * dT_dx
|
|
237
|
+
dT_dt = -(u * dT_dx + v * dT_dy) + diffusion_T - v * dTeq_dy + (Teq - T) / tau_relax
|
|
238
|
+
return domega_dt, dT_dt
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def simulate_stellar_convection(
|
|
242
|
+
nx: int,
|
|
243
|
+
ny: int,
|
|
244
|
+
Lx: float,
|
|
245
|
+
Ly: float,
|
|
246
|
+
nu: float = 0.08,
|
|
247
|
+
kappa: float = 0.08,
|
|
248
|
+
g_alpha: float = 2.0,
|
|
249
|
+
beta: float = 1.0,
|
|
250
|
+
tau_relax: float = 0.5,
|
|
251
|
+
dt: float = 0.005,
|
|
252
|
+
n_steps: int = 5000,
|
|
253
|
+
save_every: int = 50,
|
|
254
|
+
seed: int | None = None,
|
|
255
|
+
):
|
|
256
|
+
"""Time-step 2D Boussinesq convective rolls with pseudo-spectral RK4.
|
|
257
|
+
|
|
258
|
+
Starts from a small random vorticity and temperature perturbation
|
|
259
|
+
(root-mean-square amplitude ``1e-3``) and integrates
|
|
260
|
+
:func:`convective_roll_rhs` with classical 4th-order Runge-Kutta,
|
|
261
|
+
exactly the scheme used for the Boussinesq Rayleigh-Taylor system
|
|
262
|
+
elsewhere in this codebase. The default parameters were tuned (see
|
|
263
|
+
this module's test suite) so that the periodic background
|
|
264
|
+
stratification :math:`T_{eq}(y)=-\\beta\\cos(2\\pi y/L_y)`, sustained
|
|
265
|
+
against mixing by the ``tau_relax`` Newtonian-cooling forcing, robustly
|
|
266
|
+
grows the seed noise into sustained convective rolls -- kinetic energy
|
|
267
|
+
measurably larger at the end of the run than at the start -- without
|
|
268
|
+
blowing up.
|
|
269
|
+
|
|
270
|
+
Parameters
|
|
271
|
+
----------
|
|
272
|
+
nx, ny : int
|
|
273
|
+
Number of grid points along ``x`` and ``y``.
|
|
274
|
+
Lx, Ly : float
|
|
275
|
+
Physical domain size along ``x`` and ``y``.
|
|
276
|
+
nu : float, default 0.08
|
|
277
|
+
Kinematic viscosity.
|
|
278
|
+
kappa : float, default 0.08
|
|
279
|
+
Thermal diffusivity.
|
|
280
|
+
g_alpha : float, default 2.0
|
|
281
|
+
Combined buoyancy coefficient :math:`g\\,\\alpha_{th}`.
|
|
282
|
+
beta : float, default 1.0
|
|
283
|
+
Amplitude of the background stratification.
|
|
284
|
+
tau_relax : float, default 0.5
|
|
285
|
+
Newtonian-cooling relaxation time.
|
|
286
|
+
dt : float, default 0.005
|
|
287
|
+
Time step.
|
|
288
|
+
n_steps : int, default 5000
|
|
289
|
+
Total number of RK4 steps.
|
|
290
|
+
save_every : int, default 50
|
|
291
|
+
Save a snapshot every this many steps.
|
|
292
|
+
seed : int, optional
|
|
293
|
+
Random seed for the initial perturbation.
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
times : ndarray of float, shape (n_saved,)
|
|
298
|
+
omega_snapshots : ndarray of float, shape (n_saved, ny, nx)
|
|
299
|
+
T_snapshots : ndarray of float, shape (n_saved, ny, nx)
|
|
300
|
+
|
|
301
|
+
See Also
|
|
302
|
+
--------
|
|
303
|
+
convective_roll_rhs : The right-hand side stepped here.
|
|
304
|
+
kinetic_energy : Diagnostic used to confirm convection is sustained.
|
|
305
|
+
|
|
306
|
+
Examples
|
|
307
|
+
--------
|
|
308
|
+
>>> times, omega_snaps, T_snaps = simulate_stellar_convection(
|
|
309
|
+
... 32, 32, 2 * np.pi, 2 * np.pi, n_steps=20, save_every=10, seed=0)
|
|
310
|
+
>>> omega_snaps.shape[1:]
|
|
311
|
+
(32, 32)
|
|
312
|
+
>>> bool(np.all(np.isfinite(omega_snaps)))
|
|
313
|
+
True
|
|
314
|
+
"""
|
|
315
|
+
X, Y, KX, KY, K2 = _spectral_grid_2d(nx, ny, Lx, Ly)
|
|
316
|
+
|
|
317
|
+
rng = np.random.default_rng(seed)
|
|
318
|
+
omega = 1.0e-3 * rng.standard_normal((ny, nx))
|
|
319
|
+
T = 1.0e-3 * rng.standard_normal((ny, nx))
|
|
320
|
+
|
|
321
|
+
times = [0.0]
|
|
322
|
+
omega_snapshots = [omega.copy()]
|
|
323
|
+
T_snapshots = [T.copy()]
|
|
324
|
+
|
|
325
|
+
t = 0.0
|
|
326
|
+
for step in range(1, n_steps + 1):
|
|
327
|
+
k1o, k1T = convective_roll_rhs(omega, T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
|
|
328
|
+
k2o, k2T = convective_roll_rhs(omega + dt / 2 * k1o, T + dt / 2 * k1T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
|
|
329
|
+
k3o, k3T = convective_roll_rhs(omega + dt / 2 * k2o, T + dt / 2 * k2T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
|
|
330
|
+
k4o, k4T = convective_roll_rhs(omega + dt * k3o, T + dt * k3T, Y, KX, KY, K2, nu, kappa, g_alpha, beta, Ly, tau_relax)
|
|
331
|
+
omega = omega + dt / 6 * (k1o + 2 * k2o + 2 * k3o + k4o)
|
|
332
|
+
T = T + dt / 6 * (k1T + 2 * k2T + 2 * k3T + k4T)
|
|
333
|
+
t += dt
|
|
334
|
+
if step % save_every == 0:
|
|
335
|
+
times.append(t)
|
|
336
|
+
omega_snapshots.append(omega.copy())
|
|
337
|
+
T_snapshots.append(T.copy())
|
|
338
|
+
|
|
339
|
+
return np.array(times), np.array(omega_snapshots), np.array(T_snapshots)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def kinetic_energy(omega_snapshot: NDArray[np.float64], KX: NDArray[np.float64], KY: NDArray[np.float64], K2: NDArray[np.float64]) -> float:
|
|
343
|
+
"""Total kinetic energy of the flow implied by a vorticity snapshot.
|
|
344
|
+
|
|
345
|
+
Recovers the streamfunction and velocity field from `omega_snapshot`
|
|
346
|
+
via the same spectral Poisson solve used during time-stepping, then
|
|
347
|
+
sums :math:`\\frac{1}{2}(u^2+v^2)` over the grid. Used as the
|
|
348
|
+
diagnostic that convection in :func:`simulate_stellar_convection`
|
|
349
|
+
actually spins up (grows) rather than decaying back to quiescence.
|
|
350
|
+
|
|
351
|
+
Parameters
|
|
352
|
+
----------
|
|
353
|
+
omega_snapshot : ndarray of float, shape (ny, nx)
|
|
354
|
+
A single vorticity snapshot, e.g. one entry of the
|
|
355
|
+
`omega_snapshots` array returned by :func:`simulate_stellar_convection`.
|
|
356
|
+
KX, KY, K2 : ndarray of float, shape (ny, nx)
|
|
357
|
+
Wavenumber grids from :func:`_spectral_grid_2d`.
|
|
358
|
+
|
|
359
|
+
Returns
|
|
360
|
+
-------
|
|
361
|
+
float
|
|
362
|
+
Total kinetic energy :math:`\\sum \\frac{1}{2}(u^2+v^2)`.
|
|
363
|
+
|
|
364
|
+
Examples
|
|
365
|
+
--------
|
|
366
|
+
>>> import numpy as np
|
|
367
|
+
>>> _, _, KX, KY, K2 = _spectral_grid_2d(32, 32, 2 * np.pi, 2 * np.pi)
|
|
368
|
+
>>> KE = kinetic_energy(np.zeros((32, 32)), KX, KY, K2)
|
|
369
|
+
>>> KE == 0.0
|
|
370
|
+
True
|
|
371
|
+
"""
|
|
372
|
+
psi = _poisson_solve_streamfunction(omega_snapshot, K2)
|
|
373
|
+
u, v = _velocity_from_streamfunction(psi, KX, KY)
|
|
374
|
+
return float(0.5 * np.sum(u**2 + v**2))
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
# ---------------------------------------------------------------------------
|
|
378
|
+
# Part 2: alpha-omega mean-field dynamo
|
|
379
|
+
# ---------------------------------------------------------------------------
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def alpha_omega_growth_rate(alpha: float, shear: float, k, eta: float):
|
|
383
|
+
"""Closed-form linear growth rate of the alpha-omega dynamo wave.
|
|
384
|
+
|
|
385
|
+
From the dispersion relation :math:`s^2=i\\alpha G k` (see the module
|
|
386
|
+
docstring), the growing branch's real part is
|
|
387
|
+
:math:`\\sigma=\\sqrt{|\\alpha G k|/2}-\\eta k^2` -- positive
|
|
388
|
+
(dynamo action beats Ohmic decay) once :math:`|\\alpha Gk|` is large
|
|
389
|
+
enough relative to :math:`\\eta^2k^4`, i.e. at long enough wavelength
|
|
390
|
+
for fixed diffusivity.
|
|
391
|
+
|
|
392
|
+
Parameters
|
|
393
|
+
----------
|
|
394
|
+
alpha : float
|
|
395
|
+
Alpha-effect coefficient.
|
|
396
|
+
shear : float
|
|
397
|
+
Rotational shear rate :math:`G` (the Omega effect).
|
|
398
|
+
k : float or ndarray
|
|
399
|
+
Wavenumber(s).
|
|
400
|
+
eta : float
|
|
401
|
+
Magnetic diffusivity.
|
|
402
|
+
|
|
403
|
+
Returns
|
|
404
|
+
-------
|
|
405
|
+
float or ndarray
|
|
406
|
+
Growth rate :math:`\\sigma`, same shape as `k`.
|
|
407
|
+
|
|
408
|
+
See Also
|
|
409
|
+
--------
|
|
410
|
+
alpha_omega_wave_frequency : The companion imaginary part (wave frequency magnitude).
|
|
411
|
+
simulate_alpha_omega_dynamo : Full simulation this closed form is checked against.
|
|
412
|
+
|
|
413
|
+
Examples
|
|
414
|
+
--------
|
|
415
|
+
>>> round(alpha_omega_growth_rate(alpha=1.0, shear=5.0, k=1.0, eta=0.05), 4)
|
|
416
|
+
1.5311
|
|
417
|
+
"""
|
|
418
|
+
k = np.asarray(k, dtype=float)
|
|
419
|
+
sigma = np.sqrt(np.abs(alpha * shear * k) / 2.0) - eta * k**2
|
|
420
|
+
return sigma if sigma.ndim else float(sigma)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def alpha_omega_wave_frequency(alpha: float, shear: float, k):
|
|
424
|
+
"""Closed-form magnitude of the alpha-omega dynamo wave's angular frequency.
|
|
425
|
+
|
|
426
|
+
:math:`|\\omega_f| = \\sqrt{|\\alpha Gk|/2}` (see the module docstring
|
|
427
|
+
for the derivation). Only the magnitude is returned; the propagation
|
|
428
|
+
*direction* depends on the sign of :math:`\\alpha Gk` and this
|
|
429
|
+
module's specific stepping convention -- empirically, with
|
|
430
|
+
:func:`simulate_alpha_omega_dynamo`'s time-stepping and the
|
|
431
|
+
:math:`e^{ikx}` Fourier convention used throughout, the wave
|
|
432
|
+
(for ``alpha * shear > 0``, i.e. a positive-``k`` mode with
|
|
433
|
+
:math:`\\alpha Gk>0``) migrates toward **decreasing** :math:`x`, i.e.
|
|
434
|
+
it travels in the ``-x`` direction (verified against the simulation
|
|
435
|
+
in this module's test suite and visible in
|
|
436
|
+
:func:`physicskit.astro.visualizers.plot_dynamo_butterfly_diagram`'s
|
|
437
|
+
diagonal stripes).
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
alpha : float
|
|
442
|
+
Alpha-effect coefficient.
|
|
443
|
+
shear : float
|
|
444
|
+
Rotational shear rate :math:`G`.
|
|
445
|
+
k : float or ndarray
|
|
446
|
+
Wavenumber(s).
|
|
447
|
+
|
|
448
|
+
Returns
|
|
449
|
+
-------
|
|
450
|
+
float or ndarray
|
|
451
|
+
:math:`|\\omega_f|`, same shape as `k`.
|
|
452
|
+
|
|
453
|
+
Examples
|
|
454
|
+
--------
|
|
455
|
+
>>> round(alpha_omega_wave_frequency(alpha=1.0, shear=5.0, k=1.0), 4)
|
|
456
|
+
1.5811
|
|
457
|
+
"""
|
|
458
|
+
k = np.asarray(k, dtype=float)
|
|
459
|
+
freq = np.sqrt(np.abs(alpha * shear * k) / 2.0)
|
|
460
|
+
return freq if freq.ndim else float(freq)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def simulate_alpha_omega_dynamo(
|
|
464
|
+
A0: NDArray[np.float64],
|
|
465
|
+
B0: NDArray[np.float64],
|
|
466
|
+
alpha: float,
|
|
467
|
+
shear: float,
|
|
468
|
+
eta: float,
|
|
469
|
+
Lx: float,
|
|
470
|
+
dt: float,
|
|
471
|
+
n_steps: int,
|
|
472
|
+
save_every: int = 1,
|
|
473
|
+
):
|
|
474
|
+
"""Time-step the linear 1D alpha-omega mean-field dynamo equations exactly, per Fourier mode.
|
|
475
|
+
|
|
476
|
+
Because :math:`\\partial_t A=\\alpha B+\\eta\\partial_x^2 A`,
|
|
477
|
+
:math:`\\partial_t B=G\\partial_x A+\\eta\\partial_x^2 B` is linear,
|
|
478
|
+
each Fourier mode's amplitude pair :math:`(\\hat A(k,t),\\hat B(k,t))`
|
|
479
|
+
evolves independently under a 2x2 constant-coefficient linear ODE,
|
|
480
|
+
:math:`d/dt\\,(\\hat A,\\hat B)^T = M(k)\\,(\\hat A,\\hat B)^T` with
|
|
481
|
+
|
|
482
|
+
.. math::
|
|
483
|
+
|
|
484
|
+
M(k) = \\begin{pmatrix}-\\eta k^2 & \\alpha \\\\ iGk & -\\eta k^2\\end{pmatrix},
|
|
485
|
+
|
|
486
|
+
so it can be advanced *exactly* over a step `dt` via the matrix
|
|
487
|
+
exponential :math:`\\exp(M(k)\\,dt)`, with no time-discretization error
|
|
488
|
+
whatsoever. Writing :math:`M(k)=-\\eta k^2 I + N(k)` with
|
|
489
|
+
:math:`N(k)=\\begin{pmatrix}0&\\alpha\\\\iGk&0\\end{pmatrix}`, one has
|
|
490
|
+
:math:`N(k)^2 = i\\alpha Gk\\,I`, so with :math:`s_0=\\sqrt{i\\alpha Gk}`
|
|
491
|
+
(principal branch; :math:`s_0=0` at :math:`k=0`),
|
|
492
|
+
|
|
493
|
+
.. math::
|
|
494
|
+
|
|
495
|
+
\\exp(N(k)\\,dt) = \\cosh(s_0 dt)\\,I + \\frac{\\sinh(s_0 dt)}{s_0}\\,N(k)
|
|
496
|
+
|
|
497
|
+
(the usual generalized Euler formula for a matrix whose square is a
|
|
498
|
+
scalar multiple of the identity), and
|
|
499
|
+
:math:`\\exp(M(k)\\,dt) = e^{-\\eta k^2 dt}\\exp(N(k)\\,dt)`. This is
|
|
500
|
+
applied to every Fourier mode at once (vectorized over `k`, not
|
|
501
|
+
looped), then inverse-transformed back to real space.
|
|
502
|
+
|
|
503
|
+
Parameters
|
|
504
|
+
----------
|
|
505
|
+
A0, B0 : ndarray of float, shape (nx,)
|
|
506
|
+
Initial poloidal-flux proxy and toroidal field on a periodic grid
|
|
507
|
+
of ``nx = len(A0)`` points over ``[0, Lx)``.
|
|
508
|
+
alpha : float
|
|
509
|
+
Alpha-effect coefficient.
|
|
510
|
+
shear : float
|
|
511
|
+
Rotational shear rate :math:`G`.
|
|
512
|
+
eta : float
|
|
513
|
+
Magnetic diffusivity.
|
|
514
|
+
Lx : float
|
|
515
|
+
Physical domain size.
|
|
516
|
+
dt : float
|
|
517
|
+
Time step.
|
|
518
|
+
n_steps : int
|
|
519
|
+
Total number of steps.
|
|
520
|
+
save_every : int, default 1
|
|
521
|
+
Save a snapshot every this many steps.
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
times : ndarray of float, shape (n_saved,)
|
|
526
|
+
A_snapshots : ndarray of float, shape (n_saved, nx)
|
|
527
|
+
B_snapshots : ndarray of float, shape (n_saved, nx)
|
|
528
|
+
|
|
529
|
+
See Also
|
|
530
|
+
--------
|
|
531
|
+
alpha_omega_growth_rate : Closed-form growth rate this stepping is checked against.
|
|
532
|
+
dominant_mode_growth_rate : Extracts the numerically observed growth rate from the output here.
|
|
533
|
+
|
|
534
|
+
Examples
|
|
535
|
+
--------
|
|
536
|
+
>>> import numpy as np
|
|
537
|
+
>>> x = np.linspace(0, 2 * np.pi, 32, endpoint=False)
|
|
538
|
+
>>> A0 = 1e-3 * np.cos(x)
|
|
539
|
+
>>> B0 = np.zeros_like(A0)
|
|
540
|
+
>>> times, A_snaps, B_snaps = simulate_alpha_omega_dynamo(A0, B0, alpha=1.0, shear=5.0, eta=0.05, Lx=2 * np.pi, dt=0.01, n_steps=20, save_every=5)
|
|
541
|
+
>>> A_snaps.shape
|
|
542
|
+
(5, 32)
|
|
543
|
+
>>> bool(np.all(np.isfinite(A_snaps)))
|
|
544
|
+
True
|
|
545
|
+
"""
|
|
546
|
+
A0 = np.asarray(A0, dtype=float)
|
|
547
|
+
B0 = np.asarray(B0, dtype=float)
|
|
548
|
+
nx = A0.shape[0]
|
|
549
|
+
k = 2.0 * np.pi * np.fft.fftfreq(nx, d=Lx / nx)
|
|
550
|
+
|
|
551
|
+
c = 1j * alpha * shear * k
|
|
552
|
+
s0 = np.sqrt(c)
|
|
553
|
+
dt_s0 = s0 * dt
|
|
554
|
+
cosh_term = np.cosh(dt_s0)
|
|
555
|
+
sinh_over_s0 = np.where(s0 == 0, dt, np.divide(np.sinh(dt_s0), s0, out=np.zeros_like(dt_s0), where=(s0 != 0)))
|
|
556
|
+
decay = np.exp(-eta * k**2 * dt)
|
|
557
|
+
|
|
558
|
+
A_hat = np.fft.fft(A0)
|
|
559
|
+
B_hat = np.fft.fft(B0)
|
|
560
|
+
|
|
561
|
+
times = [0.0]
|
|
562
|
+
A_snapshots = [A0.copy()]
|
|
563
|
+
B_snapshots = [B0.copy()]
|
|
564
|
+
|
|
565
|
+
t = 0.0
|
|
566
|
+
for step in range(1, n_steps + 1):
|
|
567
|
+
new_A_hat = decay * (cosh_term * A_hat + sinh_over_s0 * alpha * B_hat)
|
|
568
|
+
new_B_hat = decay * (cosh_term * B_hat + sinh_over_s0 * (1j * k * shear) * A_hat)
|
|
569
|
+
A_hat, B_hat = new_A_hat, new_B_hat
|
|
570
|
+
t += dt
|
|
571
|
+
if step % save_every == 0:
|
|
572
|
+
times.append(t)
|
|
573
|
+
A_snapshots.append(np.real(np.fft.ifft(A_hat)))
|
|
574
|
+
B_snapshots.append(np.real(np.fft.ifft(B_hat)))
|
|
575
|
+
|
|
576
|
+
return np.array(times), np.array(A_snapshots), np.array(B_snapshots)
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
def dominant_mode_growth_rate(A_snapshots: NDArray[np.float64], dt_save: float, window_frac: float = 0.5) -> float:
|
|
580
|
+
"""Numerically observed exponential growth rate of the total field energy.
|
|
581
|
+
|
|
582
|
+
Fits a line to :math:`\\log\\left(\\sum_x A(x,t)^2\\right)` versus time
|
|
583
|
+
(least squares) and returns half the slope -- since energy
|
|
584
|
+
:math:`\\propto A^2` grows as :math:`e^{2\\sigma t}` for a field
|
|
585
|
+
growing as :math:`e^{\\sigma t}`. Only the *last* `window_frac` of the
|
|
586
|
+
snapshots are used for the fit: an alpha-omega dynamo mode has two
|
|
587
|
+
eigenbranches (:math:`\\sigma\\pm` in the module docstring's
|
|
588
|
+
derivation), and unless the initial condition happens to be a pure
|
|
589
|
+
eigenmode of the faster-growing branch (e.g. `B0` all zero, as in
|
|
590
|
+
this module's typical test setup), the early evolution is a
|
|
591
|
+
transient mix of both branches that will bias a whole-history fit;
|
|
592
|
+
restricting to the late-time window isolates the asymptotically
|
|
593
|
+
dominant branch, which is what :func:`alpha_omega_growth_rate`
|
|
594
|
+
predicts. Used to validate :func:`simulate_alpha_omega_dynamo`
|
|
595
|
+
against that closed form.
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
A_snapshots : ndarray of float, shape (n_saved, nx)
|
|
600
|
+
Field snapshots, e.g. `A_snapshots` or `B_snapshots` from
|
|
601
|
+
:func:`simulate_alpha_omega_dynamo`.
|
|
602
|
+
dt_save : float
|
|
603
|
+
Time interval between consecutive snapshots.
|
|
604
|
+
window_frac : float, default 0.5
|
|
605
|
+
Fraction of the snapshot history (counted from the end) used for
|
|
606
|
+
the fit.
|
|
607
|
+
|
|
608
|
+
Returns
|
|
609
|
+
-------
|
|
610
|
+
float
|
|
611
|
+
The fitted growth rate :math:`\\sigma`.
|
|
612
|
+
|
|
613
|
+
Examples
|
|
614
|
+
--------
|
|
615
|
+
>>> import numpy as np
|
|
616
|
+
>>> t = np.arange(10) * 0.1
|
|
617
|
+
>>> sigma_true = 2.0
|
|
618
|
+
>>> A_snaps = np.exp(sigma_true * t)[:, None] * np.ones((10, 4))
|
|
619
|
+
>>> round(dominant_mode_growth_rate(A_snaps, dt_save=0.1), 6)
|
|
620
|
+
2.0
|
|
621
|
+
"""
|
|
622
|
+
energy = np.sum(A_snapshots**2, axis=1)
|
|
623
|
+
n = energy.shape[0]
|
|
624
|
+
times = np.arange(n) * dt_save
|
|
625
|
+
start = max(0, n - max(2, int(np.ceil(n * window_frac))))
|
|
626
|
+
log_energy = np.log(energy[start:] + 1e-300)
|
|
627
|
+
slope, _ = np.polyfit(times[start:], log_energy, 1)
|
|
628
|
+
return float(slope / 2.0)
|