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,120 @@
|
|
|
1
|
+
"""Tests for LOE/LUE/LSE and the Marchenko-Pastur law benchmark.
|
|
2
|
+
|
|
3
|
+
The bidiagonal beta-Laguerre construction (see
|
|
4
|
+
``physicskit.rmt.utils.tridiagonal.sample_laguerre_beta_eigenvalues``) was
|
|
5
|
+
verified during development against dense X^T X / m constructions at
|
|
6
|
+
beta=1, 2 (two-sample KS test between pooled eigenvalues, matching to
|
|
7
|
+
within Monte Carlo noise) and against the exact Marchenko-Pastur law
|
|
8
|
+
directly (including support edges) at beta=1, 2, 4. Like the semicircle
|
|
9
|
+
law, Marchenko-Pastur is an *exact* N -> infinity limit (unlike the
|
|
10
|
+
Wigner/Atas surmises), so convergence-rate assertions here follow the
|
|
11
|
+
same "shrinks as N grows" philosophy as the semicircle tests.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import numpy as np
|
|
15
|
+
import pytest
|
|
16
|
+
from cache_utils import cached_sample
|
|
17
|
+
|
|
18
|
+
import physicskit.rmt as rmt
|
|
19
|
+
|
|
20
|
+
CLASSICAL_WISHART = [
|
|
21
|
+
("LOE", rmt.ensembles.LOE, 1.0),
|
|
22
|
+
("LUE", rmt.ensembles.LUE, 2.0),
|
|
23
|
+
("LSE", rmt.ensembles.LSE, 4.0),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
GAMMA = 0.3 # aspect ratio n/m used across most tests
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_WISHART)
|
|
30
|
+
def test_beta_is_correct(name, cls, expected_beta):
|
|
31
|
+
ens = cls(n=50, m=200, seed=0)
|
|
32
|
+
assert ens.beta == expected_beta
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_WISHART)
|
|
36
|
+
def test_gamma_property(name, cls, expected_beta):
|
|
37
|
+
ens = cls(n=100, m=400, seed=0)
|
|
38
|
+
assert ens.gamma == pytest.approx(0.25)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_WISHART)
|
|
42
|
+
def test_sample_shape_reproducibility_and_nonnegativity(name, cls, expected_beta):
|
|
43
|
+
ens_a = cls(n=200, m=int(200 / GAMMA), seed=123)
|
|
44
|
+
ens_b = cls(n=200, m=int(200 / GAMMA), seed=123)
|
|
45
|
+
spec_a = ens_a.sample(n_samples=5)
|
|
46
|
+
spec_b = ens_b.sample(n_samples=5)
|
|
47
|
+
|
|
48
|
+
assert spec_a.eigenvalues.shape == (5, 200)
|
|
49
|
+
np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
|
|
50
|
+
# Wishart matrices are positive semi-definite -- eigenvalues >= 0.
|
|
51
|
+
assert np.all(spec_a.eigenvalues >= -1e-10)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_WISHART)
|
|
55
|
+
def test_support_matches_marchenko_pastur_edges(name, cls, expected_beta):
|
|
56
|
+
n = 3000
|
|
57
|
+
m = int(n / GAMMA)
|
|
58
|
+
ens = cls(n=n, m=m, seed=2)
|
|
59
|
+
spectrum = cached_sample(ens, n_samples=1)
|
|
60
|
+
lo, hi = rmt.stats.mp_support(ens.gamma)
|
|
61
|
+
eigs = spectrum.rescaled.ravel()
|
|
62
|
+
# small finite-size tolerance around the exact edges
|
|
63
|
+
assert eigs.min() > lo - 0.15
|
|
64
|
+
assert eigs.max() < hi + 0.15
|
|
65
|
+
assert eigs.min() < lo + 0.2
|
|
66
|
+
assert eigs.max() > hi - 0.2
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@pytest.mark.parametrize("name,cls,expected_beta", CLASSICAL_WISHART)
|
|
70
|
+
def test_marchenko_pastur_convergence_shrinks_with_n(name, cls, expected_beta):
|
|
71
|
+
# MP is an exact limit (like the semicircle law), so -- unlike the
|
|
72
|
+
# Wigner/Atas surmises -- convergence-to-zero across N is the right
|
|
73
|
+
# thing to check, via the same robust log-log trend as
|
|
74
|
+
# test_gaussian_semicircle.test_semicircle_convergence_rate_decreases_with_n.
|
|
75
|
+
# Looped manually (rather than via benchmark.convergence_curve) so
|
|
76
|
+
# each N's sample can be disk-cached individually.
|
|
77
|
+
from scipy.stats import linregress
|
|
78
|
+
|
|
79
|
+
benchmark = rmt.validation.MarchenkoPastur(gamma=GAMMA)
|
|
80
|
+
n_values = [50, 150, 450, 1350]
|
|
81
|
+
|
|
82
|
+
results = []
|
|
83
|
+
for n in n_values:
|
|
84
|
+
ens = cls(n=n, m=int(n / GAMMA), seed=17)
|
|
85
|
+
spectrum = cached_sample(ens, n_samples=25)
|
|
86
|
+
results.append(benchmark.validate(spectrum, seed=17))
|
|
87
|
+
|
|
88
|
+
ks_stats = np.array([r.ks_statistic for r in results])
|
|
89
|
+
slope = linregress(np.log(n_values), np.log(ks_stats)).slope
|
|
90
|
+
assert slope < -0.3, f"{name}: expected shrinking KS distance, slope={slope}"
|
|
91
|
+
assert ks_stats[-1] < 0.005
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def test_continuum_beta_also_matches_marchenko_pastur():
|
|
95
|
+
ens = rmt.ensembles.LaguerreBetaEnsemble(n=800, m=int(800 / GAMMA), beta=1.7, seed=3)
|
|
96
|
+
spectrum = cached_sample(ens, n_samples=25)
|
|
97
|
+
benchmark = rmt.validation.MarchenkoPastur(gamma=GAMMA)
|
|
98
|
+
result = benchmark.validate(spectrum, seed=3)
|
|
99
|
+
assert result.ks_statistic < 0.01
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def test_invalid_parameters_rejected():
|
|
103
|
+
with pytest.raises(ValueError):
|
|
104
|
+
rmt.ensembles.LOE(n=100, m=50, seed=0) # m < n
|
|
105
|
+
with pytest.raises(ValueError):
|
|
106
|
+
rmt.ensembles.LaguerreBetaEnsemble(n=100, m=200, beta=0, seed=0)
|
|
107
|
+
with pytest.raises(ValueError):
|
|
108
|
+
rmt.validation.MarchenkoPastur(gamma=1.5) # only (0, 1] supported
|
|
109
|
+
with pytest.raises(ValueError):
|
|
110
|
+
rmt.validation.MarchenkoPastur(gamma=0.0)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_mp_cdf_is_zero_below_and_one_above_support_and_monotonic():
|
|
114
|
+
gamma = 0.5
|
|
115
|
+
lo, hi = rmt.stats.mp_support(gamma)
|
|
116
|
+
x = np.linspace(lo - 0.5, hi + 0.5, 20)
|
|
117
|
+
cdf = rmt.stats.mp_cdf(x, gamma)
|
|
118
|
+
assert cdf[0] == 0.0
|
|
119
|
+
assert cdf[-1] == 1.0
|
|
120
|
+
assert np.all(np.diff(cdf) >= -1e-12)
|
|
File without changes
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Generate Haar-distributed random unitary matrices via the corrected
|
|
2
|
+
QR decomposition trick.
|
|
3
|
+
|
|
4
|
+
Reference: F. Mezzadri, "How to generate random matrices from the
|
|
5
|
+
classical compact groups", Notices of the AMS 54 (2007) 592
|
|
6
|
+
(arXiv:math-ph/0609050).
|
|
7
|
+
|
|
8
|
+
A naive QR decomposition of a complex Ginibre matrix does NOT produce a
|
|
9
|
+
Haar-distributed unitary matrix: the QR decomposition is not unique (Q can
|
|
10
|
+
be multiplied by any unitary diagonal matrix while R is adjusted to
|
|
11
|
+
compensate), and standard LAPACK routines resolve that ambiguity in a way
|
|
12
|
+
that is not rotation-invariant. Fixing the phases of R's diagonal so they
|
|
13
|
+
are all real and positive removes the ambiguity and makes Q exactly
|
|
14
|
+
Haar-distributed (Mezzadri's Theorem 1). Verified empirically: the
|
|
15
|
+
resulting matrix is unitary to machine precision, and eigenvalue phases
|
|
16
|
+
are uniform on the circle (see physicskit.rmt.ensembles.circular.CUE and
|
|
17
|
+
tests/test_circular.py).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import numpy as np
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def haar_unitary(n: int, rng: np.random.Generator) -> np.ndarray:
|
|
24
|
+
"""A single n x n Haar-distributed random unitary matrix.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
n : int
|
|
29
|
+
rng : numpy.random.Generator
|
|
30
|
+
|
|
31
|
+
Returns
|
|
32
|
+
-------
|
|
33
|
+
numpy.ndarray, shape (n, n), complex
|
|
34
|
+
"""
|
|
35
|
+
z = (rng.standard_normal((n, n)) + 1j * rng.standard_normal((n, n))) / np.sqrt(2.0)
|
|
36
|
+
q, r = np.linalg.qr(z)
|
|
37
|
+
d = np.diagonal(r)
|
|
38
|
+
ph = d / np.abs(d)
|
|
39
|
+
return q * ph # broadcasts the phase correction over columns of q
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def symplectic_form(n: int) -> np.ndarray:
|
|
43
|
+
"""The n x n (n even) symplectic form Z = I_(n/2) kron [[0, 1], [-1, 0]],
|
|
44
|
+
used to build the "dual" of a unitary matrix for the Circular
|
|
45
|
+
Symplectic Ensemble (see physicskit.rmt.ensembles.circular.CSE). Satisfies
|
|
46
|
+
Z^2 = -I, so Z^{-1} = -Z.
|
|
47
|
+
|
|
48
|
+
Parameters
|
|
49
|
+
----------
|
|
50
|
+
n : int
|
|
51
|
+
Dimension; must be even.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
numpy.ndarray, shape (n, n), complex
|
|
56
|
+
"""
|
|
57
|
+
if n % 2 != 0:
|
|
58
|
+
raise ValueError("symplectic_form requires an even dimension")
|
|
59
|
+
block = np.array([[0.0, 1.0], [-1.0, 0.0]], dtype=complex)
|
|
60
|
+
return np.kron(np.eye(n // 2, dtype=complex), block)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def haar_orthogonal(n: int, rng: np.random.Generator) -> np.ndarray:
|
|
64
|
+
"""A single n x n Haar-distributed random orthogonal matrix (O(n),
|
|
65
|
+
both determinant signs -- not restricted to SO(n)).
|
|
66
|
+
|
|
67
|
+
Same corrected-QR-decomposition trick as ``haar_unitary`` (Mezzadri
|
|
68
|
+
2007 covers O(n) via the identical construction), with the
|
|
69
|
+
diagonal-sign correction (real case) in place of the phase
|
|
70
|
+
correction (complex case): naive QR is non-unique up to
|
|
71
|
+
multiplying Q's columns by +-1 while adjusting R to compensate, and
|
|
72
|
+
LAPACK's default resolution of that ambiguity is not rotation-
|
|
73
|
+
invariant. Verified empirically during development: the resulting
|
|
74
|
+
matrix is orthogonal to machine precision, its first column is
|
|
75
|
+
uniformly distributed on the unit sphere (mean 0, second moment
|
|
76
|
+
1/n per component), and its determinant is +1 or -1 with equal
|
|
77
|
+
probability -- see ``tests/test_haar.py``.
|
|
78
|
+
|
|
79
|
+
Parameters
|
|
80
|
+
----------
|
|
81
|
+
n : int
|
|
82
|
+
rng : numpy.random.Generator
|
|
83
|
+
|
|
84
|
+
Returns
|
|
85
|
+
-------
|
|
86
|
+
numpy.ndarray, shape (n, n), real
|
|
87
|
+
"""
|
|
88
|
+
z = rng.standard_normal((n, n))
|
|
89
|
+
q, r = np.linalg.qr(z)
|
|
90
|
+
d = np.diagonal(r)
|
|
91
|
+
sign = np.sign(d)
|
|
92
|
+
sign[sign == 0.0] = 1.0
|
|
93
|
+
return q * sign
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def haar_symplectic(n_quaternionic: int, rng: np.random.Generator) -> np.ndarray:
|
|
97
|
+
"""A single Haar-distributed random element of the compact
|
|
98
|
+
symplectic group Sp(n_quaternionic), embedded as a
|
|
99
|
+
2*n_quaternionic x 2*n_quaternionic unitary matrix.
|
|
100
|
+
|
|
101
|
+
Construction: U = V^R @ V, where V is Haar-unitary on
|
|
102
|
+
U(2*n_quaternionic) and V^R = Z @ V^T @ Z^{-1} is its symplectic
|
|
103
|
+
dual (Z the symplectic form) -- the same construction already used
|
|
104
|
+
to sample the Circular Symplectic Ensemble
|
|
105
|
+
(``physicskit.rmt.ensembles.circular.CSE``), exposed here directly as a
|
|
106
|
+
reusable raw-matrix generator rather than only as an eigenvalue
|
|
107
|
+
intermediate. Verified directly (not merely inherited from CSE's
|
|
108
|
+
validated spacing statistics): U is unitary to machine precision,
|
|
109
|
+
satisfies the defining self-duality condition U^R == U exactly, and
|
|
110
|
+
its eigenvalues come in exact double-degenerate pairs -- see
|
|
111
|
+
``tests/test_haar.py``.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
n_quaternionic : int
|
|
116
|
+
The quaternionic dimension; the returned matrix has shape
|
|
117
|
+
(2*n_quaternionic, 2*n_quaternionic).
|
|
118
|
+
rng : numpy.random.Generator
|
|
119
|
+
|
|
120
|
+
Returns
|
|
121
|
+
-------
|
|
122
|
+
numpy.ndarray, shape (2*n_quaternionic, 2*n_quaternionic), complex
|
|
123
|
+
"""
|
|
124
|
+
n = 2 * n_quaternionic
|
|
125
|
+
v = haar_unitary(n, rng)
|
|
126
|
+
z = symplectic_form(n)
|
|
127
|
+
v_dual = (-z) @ v.T @ z # Z^{-1} = -Z since Z^2 = -I
|
|
128
|
+
return v_dual @ v
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Centralized RNG handling so every ensemble and benchmark in physicskit.rmt
|
|
2
|
+
is exactly reproducible from a seed."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def as_generator(seed: int | np.random.Generator | None) -> np.random.Generator:
|
|
10
|
+
"""Coerce ``seed`` into a numpy Generator.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
seed : int, numpy.random.Generator, or None
|
|
15
|
+
An existing Generator (returned as-is), an integer seed, or
|
|
16
|
+
None (fresh, non-reproducible entropy).
|
|
17
|
+
|
|
18
|
+
Returns
|
|
19
|
+
-------
|
|
20
|
+
numpy.random.Generator
|
|
21
|
+
"""
|
|
22
|
+
if isinstance(seed, np.random.Generator):
|
|
23
|
+
return seed
|
|
24
|
+
return np.random.default_rng(seed)
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Dumitriu & Edelman (2002) tridiagonal model for the Gaussian beta-ensemble.
|
|
2
|
+
|
|
3
|
+
Reference: I. Dumitriu and A. Edelman, "Matrix Models for Beta Ensembles",
|
|
4
|
+
J. Math. Phys. 43, 5830 (2002).
|
|
5
|
+
|
|
6
|
+
The eigenvalues of the tridiagonal matrix
|
|
7
|
+
|
|
8
|
+
H_beta = tridiag( off_diag, diag, off_diag )
|
|
9
|
+
|
|
10
|
+
with
|
|
11
|
+
|
|
12
|
+
diag[i] ~ N(0, 2) i.i.d., i = 1..n
|
|
13
|
+
off_diag[i] ~ chi(df = beta * (n - i)) independent, i = 1..n-1
|
|
14
|
+
|
|
15
|
+
have exactly the joint eigenvalue density of the Gaussian beta-ensemble
|
|
16
|
+
|
|
17
|
+
f(x_1, ..., x_n) ~ exp(-beta/4 * sum_i x_i^2) * prod_{i<j} ``|x_i - x_j|^beta``
|
|
18
|
+
|
|
19
|
+
for beta = 1, 2, 4 this recovers GOE, GUE, GSE eigenvalue statistics exactly
|
|
20
|
+
(not just asymptotically), and for any beta > 0 it gives the continuum
|
|
21
|
+
Dyson-index generalization -- without ever forming or diagonalizing a dense
|
|
22
|
+
N x N matrix. Diagonalizing a tridiagonal matrix is O(n^2) rather than the
|
|
23
|
+
O(n^3) required for a general dense Hermitian eigensolve, which is what
|
|
24
|
+
makes it practical to push n high enough for the asymptotic theorems
|
|
25
|
+
(semicircle law, Tracy-Widom edge statistics, etc.) to actually apply.
|
|
26
|
+
|
|
27
|
+
Raw eigenvalues from this construction have empirical spread that grows
|
|
28
|
+
like sqrt(n * beta); divide by that factor to reach the standard semicircle
|
|
29
|
+
normalization with support [-2, 2]. See ``natural_scale`` on
|
|
30
|
+
``HermiteBetaEnsemble`` in ``physicskit.rmt.ensembles.gaussian``.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
import numpy as np
|
|
34
|
+
from scipy.linalg import eigh_tridiagonal
|
|
35
|
+
from scipy.stats import chi
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def sample_hermite_beta_eigenvalues(n: int, beta: float, rng: np.random.Generator) -> np.ndarray:
|
|
39
|
+
"""Draw one realization of eigenvalues from the beta-Hermite ensemble.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
n : int
|
|
44
|
+
Matrix dimension.
|
|
45
|
+
beta : float
|
|
46
|
+
Dyson index. beta=1 -> GOE, beta=2 -> GUE, beta=4 -> GSE; any
|
|
47
|
+
beta > 0 is a valid continuum generalization.
|
|
48
|
+
rng : numpy.random.Generator
|
|
49
|
+
|
|
50
|
+
Returns
|
|
51
|
+
-------
|
|
52
|
+
numpy.ndarray, shape (n,)
|
|
53
|
+
Raw (unnormalized) eigenvalues, ascending order.
|
|
54
|
+
"""
|
|
55
|
+
if n < 2:
|
|
56
|
+
raise ValueError("n must be at least 2 for the tridiagonal model")
|
|
57
|
+
diag = rng.normal(loc=0.0, scale=np.sqrt(2.0), size=n)
|
|
58
|
+
dof = beta * np.arange(n - 1, 0, -1) # (n-1)*beta, (n-2)*beta, ..., beta
|
|
59
|
+
off_diag = chi.rvs(dof, random_state=rng)
|
|
60
|
+
return eigh_tridiagonal(diag, off_diag, eigvals_only=True)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def sample_hermite_beta_eigenvalues_and_vectors(n: int, beta: float, rng: np.random.Generator) -> tuple[np.ndarray, np.ndarray]:
|
|
64
|
+
"""Draw one realization of eigenvalues AND eigenvectors from the
|
|
65
|
+
beta-Hermite ensemble.
|
|
66
|
+
|
|
67
|
+
The tridiagonal model preserves not only the exact beta-Hermite
|
|
68
|
+
eigenVALUE density but also the ensemble's rotationally/unitarily-
|
|
69
|
+
invariant eigenVECTOR distribution (Dumitriu-Edelman 2002; also
|
|
70
|
+
Edelman-Sutton): since GOE/GUE/GSE (and their continuum-beta
|
|
71
|
+
generalization) are invariant under conjugation by an orthogonal/
|
|
72
|
+
unitary/symplectic change of basis, the tridiagonal model's
|
|
73
|
+
eigenvectors are a faithful stand-in for a dense-matrix
|
|
74
|
+
diagonalization's eigenvectors, not merely a computational
|
|
75
|
+
convenience for the eigenvalues alone. This makes them usable, e.g.,
|
|
76
|
+
as the delocalized reference case for inverse-participation-ratio
|
|
77
|
+
statistics (see ``physicskit.rmt.stats.localization``) -- verified
|
|
78
|
+
numerically here: the mean IPR of the columns returned matches the
|
|
79
|
+
exact Haar/Gaussian-vector result ``(beta/2+1)/(n*beta/2+1)`` (see
|
|
80
|
+
``physicskit.rmt.stats.localization.ipr_theory`` and
|
|
81
|
+
``tests/test_localization.py``).
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
n : int
|
|
86
|
+
Matrix dimension.
|
|
87
|
+
beta : float
|
|
88
|
+
Dyson index. beta=1 -> GOE, beta=2 -> GUE, beta=4 -> GSE; any
|
|
89
|
+
beta > 0 is a valid continuum generalization.
|
|
90
|
+
rng : numpy.random.Generator
|
|
91
|
+
|
|
92
|
+
Returns
|
|
93
|
+
-------
|
|
94
|
+
eigenvalues : numpy.ndarray, shape (n,)
|
|
95
|
+
Raw (unnormalized) eigenvalues, ascending order.
|
|
96
|
+
eigenvectors : numpy.ndarray, shape (n, n)
|
|
97
|
+
Column ``i`` is the eigenvector for ``eigenvalues[i]``.
|
|
98
|
+
"""
|
|
99
|
+
if n < 2:
|
|
100
|
+
raise ValueError("n must be at least 2 for the tridiagonal model")
|
|
101
|
+
diag = rng.normal(loc=0.0, scale=np.sqrt(2.0), size=n)
|
|
102
|
+
dof = beta * np.arange(n - 1, 0, -1)
|
|
103
|
+
off_diag = chi.rvs(dof, random_state=rng)
|
|
104
|
+
return eigh_tridiagonal(diag, off_diag, eigvals_only=False)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def sample_laguerre_beta_eigenvalues(m: int, n: int, beta: float, rng: np.random.Generator) -> np.ndarray:
|
|
108
|
+
"""Draw one realization of eigenvalues from the beta-Laguerre
|
|
109
|
+
(Wishart) ensemble, corresponding to an m x n rectangular Gaussian
|
|
110
|
+
data matrix (m "samples", n "variables", m >= n) with i.i.d.
|
|
111
|
+
unit-variance entries -- real for beta=1, complex for beta=2,
|
|
112
|
+
quaternionic for beta=4; any beta > 0 is a valid continuum
|
|
113
|
+
generalization.
|
|
114
|
+
|
|
115
|
+
Construction: a classical fact (Householder-bidiagonalization of a
|
|
116
|
+
Gaussian matrix yields independent chi-distributed bidiagonal
|
|
117
|
+
entries) generalized to arbitrary beta following the same pattern as
|
|
118
|
+
the Dumitriu-Edelman tridiagonal Hermite model. The n x n bidiagonal
|
|
119
|
+
matrix
|
|
120
|
+
|
|
121
|
+
B = bidiag( diagonal: chi(beta*m), chi(beta*(m-1)), ..., chi(beta*(m-n+1));
|
|
122
|
+
subdiagonal: chi(beta*(n-1)), chi(beta*(n-2)), ..., chi(beta) )
|
|
123
|
+
|
|
124
|
+
gives W = B B^T / (beta * m) eigenvalues matching the standard
|
|
125
|
+
Marchenko-Pastur normalization (support [(1-sqrt(gamma))^2,
|
|
126
|
+
(1+sqrt(gamma))^2] with gamma = n/m as m, n -> infinity).
|
|
127
|
+
|
|
128
|
+
Verified numerically at beta=1, 2 against dense X^T X / m
|
|
129
|
+
constructions (real and complex Ginibre-type X respectively): pooled
|
|
130
|
+
eigenvalue samples agree via two-sample KS test to within Monte Carlo
|
|
131
|
+
noise. All three classical beta (1, 2, 4) converge to the exact
|
|
132
|
+
Marchenko-Pastur law with correctly located support edges.
|
|
133
|
+
|
|
134
|
+
Parameters
|
|
135
|
+
----------
|
|
136
|
+
m : int
|
|
137
|
+
Number of samples (rows) of the underlying data matrix, m >= n.
|
|
138
|
+
n : int
|
|
139
|
+
Number of variables (columns) of the underlying data matrix.
|
|
140
|
+
beta : float
|
|
141
|
+
Dyson index. beta=1 -> real, beta=2 -> complex, beta=4 ->
|
|
142
|
+
quaternionic; any beta > 0 is a valid continuum generalization.
|
|
143
|
+
rng : numpy.random.Generator
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
numpy.ndarray, shape (n,)
|
|
148
|
+
Raw eigenvalues (already in Marchenko-Pastur normalization,
|
|
149
|
+
i.e. natural_scale = 1 for this ensemble), ascending order.
|
|
150
|
+
"""
|
|
151
|
+
if n < 2:
|
|
152
|
+
raise ValueError("n must be at least 2 for the bidiagonal model")
|
|
153
|
+
if m < n:
|
|
154
|
+
raise ValueError("m (samples) must be >= n (variables)")
|
|
155
|
+
diag_dof = beta * (m - np.arange(n))
|
|
156
|
+
sub_dof = beta * (n - 1 - np.arange(n - 1))
|
|
157
|
+
diag = chi.rvs(diag_dof, random_state=rng)
|
|
158
|
+
sub = chi.rvs(sub_dof, random_state=rng) if n > 1 else np.array([])
|
|
159
|
+
B = np.diag(diag)
|
|
160
|
+
if n > 1:
|
|
161
|
+
B += np.diag(sub, -1)
|
|
162
|
+
W = (B @ B.T) / (beta * m)
|
|
163
|
+
# Real advantage over dense X^T X: no m x n matrix (and no O(m*n^2)
|
|
164
|
+
# matrix product) is ever formed regardless of how large m is --
|
|
165
|
+
# generating B costs O(n) chi draws, independent of m. The final
|
|
166
|
+
# eigenvalue solve on the n x n matrix W is O(n^3) either way.
|
|
167
|
+
return np.linalg.eigvalsh(W)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from .base import Benchmark, ValidationResult
|
|
2
|
+
from .circular_law import CircularLaw
|
|
3
|
+
from .marchenko_pastur import MarchenkoPastur
|
|
4
|
+
from .ratio_distribution import RatioDistribution
|
|
5
|
+
from .real_ginibre import RealEigenvalueCountResult, RealGinibreEigenvalueCount
|
|
6
|
+
from .sine_kernel import CorrelationValidationResult, SineKernel
|
|
7
|
+
from .single_ring import SingleRingTheorem
|
|
8
|
+
from .tracy_widom import TracyWidom
|
|
9
|
+
from .universality import (
|
|
10
|
+
DEFAULT_ENTRY_DISTRIBUTIONS,
|
|
11
|
+
UniversalityResult,
|
|
12
|
+
check_universality,
|
|
13
|
+
)
|
|
14
|
+
from .wachter import Wachter
|
|
15
|
+
from .wigner_semicircle import WignerSemicircle
|
|
16
|
+
from .wigner_surmise import WignerSurmise
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"Benchmark",
|
|
20
|
+
"ValidationResult",
|
|
21
|
+
"WignerSemicircle",
|
|
22
|
+
"WignerSurmise",
|
|
23
|
+
"RatioDistribution",
|
|
24
|
+
"MarchenkoPastur",
|
|
25
|
+
"SineKernel",
|
|
26
|
+
"CorrelationValidationResult",
|
|
27
|
+
"CircularLaw",
|
|
28
|
+
"TracyWidom",
|
|
29
|
+
"Wachter",
|
|
30
|
+
"check_universality",
|
|
31
|
+
"UniversalityResult",
|
|
32
|
+
"DEFAULT_ENTRY_DISTRIBUTIONS",
|
|
33
|
+
"RealGinibreEigenvalueCount",
|
|
34
|
+
"RealEigenvalueCountResult",
|
|
35
|
+
"SingleRingTheorem",
|
|
36
|
+
]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Base class for paper-replication benchmarks.
|
|
2
|
+
|
|
3
|
+
A single fixed-N Kolmogorov-Smirnov test with a p > 0.05 pass criterion is
|
|
4
|
+
*not* a reliable way to validate convergence to an asymptotic law: KS test
|
|
5
|
+
power grows with N, so real, expected finite-size deviations become more
|
|
6
|
+
likely to trigger rejection as N grows, not less. Instead, every Benchmark
|
|
7
|
+
here supports ``convergence_curve``, which tracks a distance-to-theory
|
|
8
|
+
metric across a sequence of N values and expects it to *shrink* as N grows
|
|
9
|
+
-- that is the statistically meaningful way to demonstrate asymptotic
|
|
10
|
+
convergence.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from dataclasses import dataclass
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
from scipy.stats import kstest, wasserstein_distance
|
|
21
|
+
|
|
22
|
+
from ..ensembles.base import MatrixEnsemble
|
|
23
|
+
from ..spectrum import Spectrum
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class ValidationResult:
|
|
28
|
+
"""Result of comparing one Spectrum against a benchmark's theoretical
|
|
29
|
+
distribution."""
|
|
30
|
+
|
|
31
|
+
ks_statistic: float
|
|
32
|
+
ks_pvalue: float
|
|
33
|
+
wasserstein_distance: float
|
|
34
|
+
n_eigenvalues: int
|
|
35
|
+
|
|
36
|
+
def __repr__(self) -> str:
|
|
37
|
+
return (
|
|
38
|
+
f"ValidationResult(ks_statistic={self.ks_statistic:.5f}, "
|
|
39
|
+
f"ks_pvalue={self.ks_pvalue:.3g}, "
|
|
40
|
+
f"wasserstein_distance={self.wasserstein_distance:.5f}, "
|
|
41
|
+
f"n_eigenvalues={self.n_eigenvalues})"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Benchmark(ABC):
|
|
46
|
+
"""Ties one theoretical distribution to a validation procedure.
|
|
47
|
+
|
|
48
|
+
Subclasses implement ``theoretical_cdf`` (for the one-sample KS test,
|
|
49
|
+
which needs no simulated reference sample) and ``reference_samples``
|
|
50
|
+
(for the Wasserstein-distance comparison, which does).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@abstractmethod
|
|
54
|
+
def theoretical_cdf(self, x: np.ndarray) -> np.ndarray:
|
|
55
|
+
"""Exact theoretical CDF, vectorized over x."""
|
|
56
|
+
raise NotImplementedError # pragma: no cover -- unreachable, see MatrixEnsemble._sample_eigenvalues
|
|
57
|
+
|
|
58
|
+
@abstractmethod
|
|
59
|
+
def reference_samples(self, size: int, rng: np.random.Generator) -> np.ndarray:
|
|
60
|
+
"""Draw ``size`` i.i.d. samples from the theoretical distribution."""
|
|
61
|
+
raise NotImplementedError # pragma: no cover -- unreachable, see MatrixEnsemble._sample_eigenvalues
|
|
62
|
+
|
|
63
|
+
#: Reference-sample size used for the Wasserstein comparison. Kept
|
|
64
|
+
#: large and fixed (rather than tied to len(data)) so the reference
|
|
65
|
+
#: sample's own Monte Carlo noise stays well below the eigenvalue-side
|
|
66
|
+
#: signal even once the ensemble has converged tightly to theory --
|
|
67
|
+
#: otherwise the noise floor from a same-size reference sample can
|
|
68
|
+
#: swamp the (small, real) improvement between two already-close N
|
|
69
|
+
#: values and make the distance non-monotonic by chance.
|
|
70
|
+
reference_size: int = 200_000
|
|
71
|
+
|
|
72
|
+
def validate(self, spectrum: Spectrum, seed: int | np.random.Generator | None = None) -> ValidationResult:
|
|
73
|
+
"""Compare a Spectrum's rescaled eigenvalues against theory.
|
|
74
|
+
|
|
75
|
+
Returns a ValidationResult with both a one-sample KS statistic
|
|
76
|
+
(against the exact theoretical CDF) and a Wasserstein distance
|
|
77
|
+
(against simulated reference samples) -- report both rather than
|
|
78
|
+
gating on a single p-value threshold.
|
|
79
|
+
"""
|
|
80
|
+
data = spectrum.rescaled.ravel()
|
|
81
|
+
ks = kstest(data, self.theoretical_cdf)
|
|
82
|
+
rng = np.random.default_rng(seed)
|
|
83
|
+
reference = self.reference_samples(self.reference_size, rng)
|
|
84
|
+
wd = wasserstein_distance(data, reference)
|
|
85
|
+
return ValidationResult(
|
|
86
|
+
ks_statistic=float(ks.statistic),
|
|
87
|
+
ks_pvalue=float(ks.pvalue),
|
|
88
|
+
wasserstein_distance=float(wd),
|
|
89
|
+
n_eigenvalues=len(data),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
def convergence_curve(
|
|
93
|
+
self,
|
|
94
|
+
ensemble_factory: Callable[..., MatrixEnsemble],
|
|
95
|
+
n_values: list[int],
|
|
96
|
+
n_samples: int = 20,
|
|
97
|
+
seed: int | np.random.Generator | None = None,
|
|
98
|
+
) -> list[tuple[int, ValidationResult]]:
|
|
99
|
+
"""Track distance-to-theory across increasing matrix size N.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
ensemble_factory : callable
|
|
104
|
+
``ensemble_factory(n, seed=...)`` returning a fresh
|
|
105
|
+
``MatrixEnsemble`` instance of size n.
|
|
106
|
+
n_values : sequence of int
|
|
107
|
+
Matrix sizes to test, in increasing order.
|
|
108
|
+
n_samples : int
|
|
109
|
+
Independent matrix draws to pool at each N.
|
|
110
|
+
seed : int or None
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
list of (n, ValidationResult)
|
|
115
|
+
The KS statistic (and Wasserstein distance) in each result is
|
|
116
|
+
expected to shrink as n increases; this is the check to make,
|
|
117
|
+
not a single p > 0.05 threshold at one N.
|
|
118
|
+
"""
|
|
119
|
+
rng = np.random.default_rng(seed)
|
|
120
|
+
results = []
|
|
121
|
+
for n in n_values:
|
|
122
|
+
ensemble = ensemble_factory(n, seed=int(rng.integers(1 << 31)))
|
|
123
|
+
spectrum = ensemble.sample(n_samples=n_samples)
|
|
124
|
+
result = self.validate(spectrum, seed=int(rng.integers(1 << 31)))
|
|
125
|
+
results.append((n, result))
|
|
126
|
+
return results
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Circular law benchmark for the Ginibre ensembles.
|
|
2
|
+
|
|
3
|
+
Reference: J. Ginibre, J. Math. Phys. 6 (1965) 440.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from collections.abc import Callable
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from ..ensembles.base import MatrixEnsemble
|
|
13
|
+
from ..spectrum import Spectrum
|
|
14
|
+
from ..stats.circular_law import circular_law_radial_cdf, circular_law_radial_pdf
|
|
15
|
+
from .base import Benchmark, ValidationResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CircularLaw(Benchmark):
|
|
19
|
+
"""Validates a Ginibre-type Spectrum's eigenvalue *radii* against the
|
|
20
|
+
circular law's radial marginal (F(r) = r^2 on [0, 1]).
|
|
21
|
+
|
|
22
|
+
Overrides ``validate`` (rather than just ``theoretical_cdf`` /
|
|
23
|
+
``reference_samples``) because the base class compares
|
|
24
|
+
``spectrum.rescaled`` directly, but here it's the eigenvalue
|
|
25
|
+
*magnitudes* -- not the (complex) rescaled eigenvalues themselves --
|
|
26
|
+
that are compared to a 1-D theoretical distribution.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def theoretical_pdf(self, r: np.ndarray) -> np.ndarray:
|
|
30
|
+
return circular_law_radial_pdf(r)
|
|
31
|
+
|
|
32
|
+
def theoretical_cdf(self, r: np.ndarray) -> np.ndarray:
|
|
33
|
+
return circular_law_radial_cdf(r)
|
|
34
|
+
|
|
35
|
+
def reference_samples(self, size: int, rng: np.random.Generator) -> np.ndarray:
|
|
36
|
+
# Inverse-CDF sampling: F(r) = r^2 => r = sqrt(U), U ~ Uniform(0,1)
|
|
37
|
+
u = rng.uniform(0.0, 1.0, size=size)
|
|
38
|
+
return np.sqrt(u)
|
|
39
|
+
|
|
40
|
+
def validate(self, spectrum: Spectrum, seed: int | np.random.Generator | None = None) -> ValidationResult:
|
|
41
|
+
radii = np.abs(spectrum.rescaled.ravel())
|
|
42
|
+
ks_result = self._ks_and_wasserstein(radii, seed)
|
|
43
|
+
return ks_result
|
|
44
|
+
|
|
45
|
+
def _ks_and_wasserstein(self, radii: np.ndarray, seed: int | np.random.Generator | None) -> ValidationResult:
|
|
46
|
+
from scipy.stats import kstest, wasserstein_distance
|
|
47
|
+
|
|
48
|
+
ks = kstest(radii, self.theoretical_cdf)
|
|
49
|
+
rng = np.random.default_rng(seed)
|
|
50
|
+
reference = self.reference_samples(self.reference_size, rng)
|
|
51
|
+
wd = wasserstein_distance(radii, reference)
|
|
52
|
+
return ValidationResult(
|
|
53
|
+
ks_statistic=float(ks.statistic),
|
|
54
|
+
ks_pvalue=float(ks.pvalue),
|
|
55
|
+
wasserstein_distance=float(wd),
|
|
56
|
+
n_eigenvalues=len(radii),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def convergence_curve(
|
|
60
|
+
self,
|
|
61
|
+
ensemble_factory: Callable[..., MatrixEnsemble],
|
|
62
|
+
n_values: list[int],
|
|
63
|
+
n_samples: int = 20,
|
|
64
|
+
seed: int | np.random.Generator | None = None,
|
|
65
|
+
) -> list[tuple[int, ValidationResult]]:
|
|
66
|
+
"""Same convergence-rate philosophy as the other exact-limit
|
|
67
|
+
benchmarks (semicircle, Marchenko-Pastur): track KS distance to
|
|
68
|
+
the circular law as N grows. Convergence here is known to be
|
|
69
|
+
slower than the Hermitian ensembles' (weaker eigenvalue rigidity
|
|
70
|
+
for non-Hermitian matrices), so expect a shallower log-log slope
|
|
71
|
+
-- verified during development to be reliably negative but
|
|
72
|
+
smaller in magnitude than the semicircle/MP cases."""
|
|
73
|
+
rng = np.random.default_rng(seed)
|
|
74
|
+
results = []
|
|
75
|
+
for n in n_values:
|
|
76
|
+
ensemble = ensemble_factory(n, seed=int(rng.integers(1 << 31)))
|
|
77
|
+
spectrum = ensemble.sample(n_samples=n_samples)
|
|
78
|
+
result = self.validate(spectrum, seed=int(rng.integers(1 << 31)))
|
|
79
|
+
results.append((n, result))
|
|
80
|
+
return results
|