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,272 @@
|
|
|
1
|
+
"""Number variance Sigma^2(L): a long-range spectral rigidity statistic,
|
|
2
|
+
more discriminating than nearest-neighbor spacing alone at distinguishing
|
|
3
|
+
correlated (GOE/GUE/GSE) from uncorrelated (Poisson) level sequences.
|
|
4
|
+
|
|
5
|
+
References
|
|
6
|
+
----------
|
|
7
|
+
F. J. Dyson, M. L. Mehta, "Statistical Theory of the Energy Levels of
|
|
8
|
+
Complex Systems. IV", J. Math. Phys. 4 (1963) 701 -- the original
|
|
9
|
+
number variance formulas.
|
|
10
|
+
M. L. Mehta, *Random Matrices* (3rd ed.), Academic Press, 2004.
|
|
11
|
+
|
|
12
|
+
Sigma^2(L) is the variance of the number of (unfolded) eigenvalues found
|
|
13
|
+
in a randomly placed interval of length L. For an uncorrelated (Poisson)
|
|
14
|
+
sequence, Sigma^2(L) = L exactly. Level repulsion in GOE/GUE/GSE makes
|
|
15
|
+
the spectrum far more "rigid": Sigma^2(L) grows only logarithmically,
|
|
16
|
+
|
|
17
|
+
Sigma^2_beta(L) = (2 / (beta * pi^2)) * ln(L) + K_beta + O(1/L)
|
|
18
|
+
|
|
19
|
+
(Dyson-Mehta 1963) -- the leading log-coefficient's 1/beta dependence is
|
|
20
|
+
generic across the Gaussian ensembles; the additive constant K_beta
|
|
21
|
+
differs by symmetry class.
|
|
22
|
+
|
|
23
|
+
Two levels of precision are implemented:
|
|
24
|
+
|
|
25
|
+
- **beta=2 (GUE): exact**, via direct numerical integration of the
|
|
26
|
+
bulk two-point correlation function already validated for CUE in
|
|
27
|
+
``physicskit.rmt.stats.correlations`` (spacing/rigidity statistics are
|
|
28
|
+
universal in beta across the Gaussian and circular families, so the
|
|
29
|
+
same sine-kernel Y_2(r) = (sin(pi*r)/(pi*r))^2 applies here)::
|
|
30
|
+
|
|
31
|
+
Sigma^2(L) = L - 2 * integral_0^L (L - r) * Y_2(r) dr
|
|
32
|
+
|
|
33
|
+
This is not merely asymptotic -- it holds at any L within the bulk
|
|
34
|
+
sine-kernel limit, and was verified numerically during development
|
|
35
|
+
against a Monte Carlo GUE window-counting estimate.
|
|
36
|
+
|
|
37
|
+
- **beta=1 (GOE) and beta=4 (GSE): large-L asymptotic formulas**,
|
|
38
|
+
since deriving beta=1,4 analogues of the sine-kernel integral would
|
|
39
|
+
require the fuller Pfaffian point-process machinery deferred
|
|
40
|
+
elsewhere in this package (see ``physicskit.rmt.stats.correlations``). The
|
|
41
|
+
GOE formula is confirmed by two independent secondary sources; the
|
|
42
|
+
GSE formula follows the same commonly-cited structural pattern and
|
|
43
|
+
was verified numerically against Monte Carlo GSE window-counting
|
|
44
|
+
before being included here (both confirmed to agree with simulation
|
|
45
|
+
at L = 5, 10, 20 to within a few percent -- see
|
|
46
|
+
``tests/test_rigidity.py``).
|
|
47
|
+
|
|
48
|
+
Also implemented here: the companion Dyson-Mehta Delta_3(L) statistic
|
|
49
|
+
(least-squares deviation of the staircase counting function from a
|
|
50
|
+
locally fit straight line), via ``spectral_rigidity_empirical`` (a
|
|
51
|
+
direct, formula-free estimator valid for any point process) and
|
|
52
|
+
``spectral_rigidity_theory`` (the exact integral relation to Sigma^2(L),
|
|
53
|
+
inheriting the same beta=2-exact / beta=1,4-asymptotic precision).
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
from __future__ import annotations
|
|
57
|
+
|
|
58
|
+
from collections.abc import Callable
|
|
59
|
+
|
|
60
|
+
import numpy as np
|
|
61
|
+
from scipy.integrate import quad
|
|
62
|
+
|
|
63
|
+
from ..spectrum import Spectrum
|
|
64
|
+
|
|
65
|
+
_GAMMA_E = 0.5772156649015329 # Euler-Mascheroni constant
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def number_variance_empirical(
|
|
69
|
+
spectrum: Spectrum,
|
|
70
|
+
cdf_func: Callable[[np.ndarray], np.ndarray],
|
|
71
|
+
l_values: np.ndarray,
|
|
72
|
+
n_windows: int = 200,
|
|
73
|
+
seed: int | np.random.Generator | None = None,
|
|
74
|
+
) -> np.ndarray:
|
|
75
|
+
"""Estimate Sigma^2(L) for each L in ``l_values`` by counting
|
|
76
|
+
unfolded eigenvalues in many randomly placed windows.
|
|
77
|
+
|
|
78
|
+
Parameters
|
|
79
|
+
----------
|
|
80
|
+
spectrum : physicskit.rmt.spectrum.Spectrum
|
|
81
|
+
cdf_func : callable
|
|
82
|
+
Theoretical CDF used for unfolding (e.g.
|
|
83
|
+
``physicskit.rmt.stats.density.semicircle_cdf``).
|
|
84
|
+
l_values : sequence of float
|
|
85
|
+
n_windows : int
|
|
86
|
+
Random window placements per sample.
|
|
87
|
+
seed : int or None
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
numpy.ndarray
|
|
92
|
+
Sigma^2(L) estimates, one per entry in ``l_values``.
|
|
93
|
+
"""
|
|
94
|
+
rng = np.random.default_rng(seed)
|
|
95
|
+
results = []
|
|
96
|
+
for length in l_values:
|
|
97
|
+
all_counts = []
|
|
98
|
+
for row in spectrum.rescaled:
|
|
99
|
+
n = len(row)
|
|
100
|
+
unfolded = n * cdf_func(row)
|
|
101
|
+
lo, hi = unfolded[0], unfolded[-1]
|
|
102
|
+
if hi - lo < 3 * length:
|
|
103
|
+
continue # window plus margin doesn't fit; skip this sample at this L
|
|
104
|
+
starts = rng.uniform(lo, hi - length, size=n_windows)
|
|
105
|
+
counts = np.array([np.sum((unfolded >= s) & (unfolded < s + length)) for s in starts])
|
|
106
|
+
all_counts.append(counts)
|
|
107
|
+
results.append(np.var(np.concatenate(all_counts)))
|
|
108
|
+
return np.array(results)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def number_variance_poisson(l_values: np.ndarray) -> np.ndarray:
|
|
112
|
+
"""Exact number variance for an uncorrelated (Poisson) sequence:
|
|
113
|
+
Sigma^2(L) = L. The "soft spectrum" baseline that GOE/GUE/GSE's
|
|
114
|
+
logarithmic rigidity is contrasted against."""
|
|
115
|
+
return np.asarray(l_values, dtype=float)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _sine_kernel_y2(r: np.ndarray) -> np.ndarray:
|
|
119
|
+
r = np.asarray(r, dtype=float)
|
|
120
|
+
out = np.ones_like(r)
|
|
121
|
+
mask = np.abs(r) > 1e-10
|
|
122
|
+
out[mask] = (np.sin(np.pi * r[mask]) / (np.pi * r[mask])) ** 2
|
|
123
|
+
return out
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def number_variance_gue_exact(l_values: np.ndarray) -> np.ndarray:
|
|
127
|
+
"""Exact bulk-sine-kernel number variance (beta=2 / GUE / CUE):
|
|
128
|
+
Sigma^2(L) = L - 2 * integral_0^L (L-r) * Y_2(r) dr.
|
|
129
|
+
"""
|
|
130
|
+
l_values = np.atleast_1d(np.asarray(l_values, dtype=float))
|
|
131
|
+
out = np.empty_like(l_values)
|
|
132
|
+
for i, length in enumerate(l_values):
|
|
133
|
+
# limit=200 (vs scipy's default 50): the oscillatory sine-kernel
|
|
134
|
+
# integrand at large L otherwise triggers a subdivision-limit
|
|
135
|
+
# warning even though the result is already converged (verified
|
|
136
|
+
# numerically -- the value is identical to 1e-7 either way, this
|
|
137
|
+
# just silences a spurious warning rather than fixing an
|
|
138
|
+
# accuracy problem).
|
|
139
|
+
val, _ = quad(lambda r, length=length: (length - r) * _sine_kernel_y2(r), 0, length, limit=200)
|
|
140
|
+
out[i] = length - 2.0 * val
|
|
141
|
+
return out
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def number_variance_goe_asymptotic(l_values: np.ndarray) -> np.ndarray:
|
|
145
|
+
"""Large-L asymptotic number variance for beta=1 (GOE):
|
|
146
|
+
Sigma^2(L) = (2/pi^2) * [ln(2*pi*L) + gamma_E + 1 - pi^2/8].
|
|
147
|
+
"""
|
|
148
|
+
l_values = np.asarray(l_values, dtype=float)
|
|
149
|
+
return (2.0 / np.pi**2) * (np.log(2 * np.pi * l_values) + _GAMMA_E + 1.0 - np.pi**2 / 8.0)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def number_variance_gse_asymptotic(l_values: np.ndarray) -> np.ndarray:
|
|
153
|
+
"""Large-L asymptotic number variance for beta=4 (GSE):
|
|
154
|
+
Sigma^2(L) = (1/(2*pi^2)) * [ln(4*pi*L) + gamma_E + 1 + pi^2/8].
|
|
155
|
+
"""
|
|
156
|
+
l_values = np.asarray(l_values, dtype=float)
|
|
157
|
+
return (1.0 / (2 * np.pi**2)) * (np.log(4 * np.pi * l_values) + _GAMMA_E + 1.0 + np.pi**2 / 8.0)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def number_variance_theory(l_values: np.ndarray, beta: float) -> np.ndarray:
|
|
161
|
+
"""Dispatch to the appropriate theoretical Sigma^2(L) by beta:
|
|
162
|
+
exact for beta=2, large-L asymptotic for beta=1, 4."""
|
|
163
|
+
if beta == 1:
|
|
164
|
+
return number_variance_goe_asymptotic(l_values)
|
|
165
|
+
if beta == 2:
|
|
166
|
+
return number_variance_gue_exact(l_values)
|
|
167
|
+
if beta == 4:
|
|
168
|
+
return number_variance_gse_asymptotic(l_values)
|
|
169
|
+
raise ValueError(f"Only beta in (1, 2, 4) have implemented formulas here, got {beta}")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def spectral_rigidity_empirical(
|
|
173
|
+
spectrum: Spectrum,
|
|
174
|
+
cdf_func: Callable[[np.ndarray], np.ndarray],
|
|
175
|
+
l_values: np.ndarray,
|
|
176
|
+
n_windows: int = 200,
|
|
177
|
+
n_grid_per_window: int = 1500,
|
|
178
|
+
seed: int | np.random.Generator | None = None,
|
|
179
|
+
) -> np.ndarray:
|
|
180
|
+
"""Estimate the Dyson-Mehta Delta_3(L) statistic for each L in
|
|
181
|
+
``l_values``, directly from its definition: the mean-square deviation
|
|
182
|
+
of the (unfolded) staircase counting function N(xi) from its best-fit
|
|
183
|
+
line, over many randomly placed windows of length L.
|
|
184
|
+
|
|
185
|
+
Delta_3(L) = (1/L) * min_{A,B} <integral_x^{x+L} (N(xi) - A*xi - B)^2 dxi>
|
|
186
|
+
|
|
187
|
+
Unlike ``number_variance_theory``, no closed-form kernel is assumed
|
|
188
|
+
here -- each window's line fit is solved numerically (least squares
|
|
189
|
+
against N(xi) sampled on a fine grid), so this estimator is valid for
|
|
190
|
+
ANY point process, not just the classical beta-ensembles. Verified
|
|
191
|
+
against the exact Poisson result (``spectral_rigidity_poisson``,
|
|
192
|
+
Delta_3(L) = L/15) and against ``spectral_rigidity_theory`` at
|
|
193
|
+
beta=2 (GUE) during development -- both matched to within Monte Carlo
|
|
194
|
+
noise (a few percent) -- see ``tests/test_rigidity.py``.
|
|
195
|
+
|
|
196
|
+
Parameters
|
|
197
|
+
----------
|
|
198
|
+
spectrum : physicskit.rmt.spectrum.Spectrum
|
|
199
|
+
cdf_func : callable
|
|
200
|
+
Theoretical CDF used for unfolding (e.g.
|
|
201
|
+
``physicskit.rmt.stats.density.semicircle_cdf``).
|
|
202
|
+
l_values : numpy.ndarray
|
|
203
|
+
n_windows : int
|
|
204
|
+
Random window placements per sample.
|
|
205
|
+
n_grid_per_window : int
|
|
206
|
+
Grid resolution used to approximate the line-fit integral within
|
|
207
|
+
each window.
|
|
208
|
+
seed : int, numpy.random.Generator, or None, optional
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
numpy.ndarray
|
|
213
|
+
Delta_3(L) estimates, one per entry in ``l_values``.
|
|
214
|
+
"""
|
|
215
|
+
rng = np.random.default_rng(seed)
|
|
216
|
+
results = []
|
|
217
|
+
for length in l_values:
|
|
218
|
+
vals = []
|
|
219
|
+
for row in spectrum.rescaled:
|
|
220
|
+
n = len(row)
|
|
221
|
+
unfolded = n * cdf_func(row)
|
|
222
|
+
lo, hi = unfolded[0], unfolded[-1]
|
|
223
|
+
if hi - lo < 3 * length:
|
|
224
|
+
continue
|
|
225
|
+
starts = rng.uniform(lo, hi - length, size=n_windows)
|
|
226
|
+
for start in starts:
|
|
227
|
+
grid = np.linspace(start, start + length, n_grid_per_window)
|
|
228
|
+
counts = np.searchsorted(unfolded, grid, side="right").astype(float)
|
|
229
|
+
design = np.vstack([grid, np.ones_like(grid)]).T
|
|
230
|
+
coeffs, *_ = np.linalg.lstsq(design, counts, rcond=None)
|
|
231
|
+
residual = counts - design @ coeffs
|
|
232
|
+
vals.append(np.mean(residual**2))
|
|
233
|
+
results.append(np.mean(vals))
|
|
234
|
+
return np.array(results)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def spectral_rigidity_poisson(l_values: np.ndarray) -> np.ndarray:
|
|
238
|
+
"""Exact Dyson-Mehta Delta_3(L) for an uncorrelated (Poisson)
|
|
239
|
+
sequence: Delta_3(L) = L/15 -- verified numerically here against
|
|
240
|
+
``spectral_rigidity_empirical`` on ``PoissonEnsemble`` samples (see
|
|
241
|
+
``tests/test_rigidity.py``), the same "soft spectrum" baseline
|
|
242
|
+
``number_variance_poisson`` provides for Sigma^2(L)."""
|
|
243
|
+
return np.asarray(l_values, dtype=float) / 15.0
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def spectral_rigidity_theory(l_values: np.ndarray, beta: float) -> np.ndarray:
|
|
247
|
+
"""Theoretical Delta_3(L) at Dyson index beta (1, 2, or 4), via its
|
|
248
|
+
exact integral relation to the number variance:
|
|
249
|
+
|
|
250
|
+
Delta_3(L) = (2/L^4) * integral_0^L (L^3 - 2*L^2*r + r^3) * Sigma^2(r) dr
|
|
251
|
+
|
|
252
|
+
(Dyson-Mehta 1963; also e.g. Guhr, Mueller-Groeling, Weidenmueller,
|
|
253
|
+
"Random-matrix theories in quantum physics", Phys. Rep. 299 (1998)
|
|
254
|
+
189, Eq. (2.14).) Sigma^2 is dispatched via
|
|
255
|
+
``number_variance_theory``, so this inherits the same precision:
|
|
256
|
+
exact at beta=2, large-L asymptotic at beta=1, 4. Verified numerically
|
|
257
|
+
against ``spectral_rigidity_empirical`` during development: beta=2
|
|
258
|
+
matched Monte Carlo GUE to within ~1%, beta=1 matched Monte Carlo GOE
|
|
259
|
+
to within the same few-percent tolerance already documented for
|
|
260
|
+
``number_variance_goe_asymptotic`` -- see ``tests/test_rigidity.py``.
|
|
261
|
+
"""
|
|
262
|
+
l_values = np.atleast_1d(np.asarray(l_values, dtype=float))
|
|
263
|
+
out = np.empty_like(l_values)
|
|
264
|
+
for i, length in enumerate(l_values):
|
|
265
|
+
|
|
266
|
+
def integrand(r: float, length: float = length) -> float:
|
|
267
|
+
sigma2 = number_variance_theory(np.array([r]), beta)[0]
|
|
268
|
+
return (length**3 - 2.0 * length**2 * r + r**3) * sigma2
|
|
269
|
+
|
|
270
|
+
val, _ = quad(integrand, 0, length, limit=200)
|
|
271
|
+
out[i] = 2.0 / length**4 * val
|
|
272
|
+
return out
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"""The single ring theorem's radii formula (see
|
|
2
|
+
``physicskit.rmt.ensembles.single_ring`` for the full construction and
|
|
3
|
+
references): a bi-unitarily-invariant non-Hermitian ensemble's
|
|
4
|
+
eigenvalues fill the annulus ``r_in <= |z| <= r_out``, with
|
|
5
|
+
|
|
6
|
+
r_out = sqrt(<s^2>) r_in = 1 / sqrt(<1/s^2>)
|
|
7
|
+
|
|
8
|
+
for singular values s.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def single_ring_radii(singular_values: np.ndarray) -> tuple[float, float]:
|
|
15
|
+
"""Theoretical single-ring radii (r_in, r_out) from a set of
|
|
16
|
+
singular values (or samples from the limiting singular-value
|
|
17
|
+
distribution).
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
singular_values : numpy.ndarray
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
r_in : float
|
|
26
|
+
r_out : float
|
|
27
|
+
"""
|
|
28
|
+
singular_values = np.asarray(singular_values, dtype=float)
|
|
29
|
+
r_out = np.sqrt(np.mean(singular_values**2))
|
|
30
|
+
r_in = 1.0 / np.sqrt(np.mean(1.0 / singular_values**2))
|
|
31
|
+
return float(r_in), float(r_out)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def single_ring_radii_wishart_theory(gamma: float) -> tuple[float, float]:
|
|
35
|
+
"""Exact single-ring radii for ``NonHermitianWishartEnsemble`` at
|
|
36
|
+
aspect ratio gamma = n/m < 1: r_out = 1, r_in = sqrt(1 - gamma), from
|
|
37
|
+
the Marchenko-Pastur distribution's exact moments E[X] = 1 and
|
|
38
|
+
E[1/X] = 1/(1-gamma) -- verified numerically during development (see
|
|
39
|
+
``tests/test_single_ring.py``) before being used here.
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
gamma : float
|
|
44
|
+
Aspect ratio n/m, must be in (0, 1).
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
r_in : float
|
|
49
|
+
r_out : float
|
|
50
|
+
"""
|
|
51
|
+
if not 0.0 < gamma < 1.0:
|
|
52
|
+
raise ValueError(f"gamma must be in (0, 1), got {gamma}")
|
|
53
|
+
return float(np.sqrt(1.0 - gamma)), 1.0
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def annulus_radial_cdf(r: np.ndarray, r_in: float, r_out: float) -> np.ndarray:
|
|
57
|
+
"""Radial marginal CDF of the uniform distribution on the annulus
|
|
58
|
+
``r_in <= |z| <= r_out``: F(r) = (r^2 - r_in^2) / (r_out^2 - r_in^2),
|
|
59
|
+
the direct generalization of
|
|
60
|
+
``physicskit.rmt.stats.circular_law.circular_law_radial_cdf`` (its r_in=0,
|
|
61
|
+
r_out=1 special case).
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
r : numpy.ndarray
|
|
66
|
+
r_in : float
|
|
67
|
+
r_out : float
|
|
68
|
+
|
|
69
|
+
Returns
|
|
70
|
+
-------
|
|
71
|
+
numpy.ndarray
|
|
72
|
+
"""
|
|
73
|
+
r = np.clip(np.asarray(r, dtype=float), r_in, r_out)
|
|
74
|
+
return (r**2 - r_in**2) / (r_out**2 - r_in**2)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def annulus_radial_pdf(r: np.ndarray, r_in: float, r_out: float) -> np.ndarray:
|
|
78
|
+
"""Radial marginal density of the uniform distribution on the
|
|
79
|
+
annulus ``r_in <= |z| <= r_out``: f(r) = 2r / (r_out^2 - r_in^2).
|
|
80
|
+
|
|
81
|
+
Parameters
|
|
82
|
+
----------
|
|
83
|
+
r : numpy.ndarray
|
|
84
|
+
r_in : float
|
|
85
|
+
r_out : float
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
numpy.ndarray
|
|
90
|
+
"""
|
|
91
|
+
r = np.asarray(r, dtype=float)
|
|
92
|
+
out = np.zeros_like(r)
|
|
93
|
+
mask = (r >= r_in) & (r <= r_out)
|
|
94
|
+
out[mask] = 2.0 * r[mask] / (r_out**2 - r_in**2)
|
|
95
|
+
return out
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Nearest-neighbor spacing distribution P(s), and the (generalized)
|
|
2
|
+
Wigner surmise it is validated against.
|
|
3
|
+
|
|
4
|
+
References
|
|
5
|
+
----------
|
|
6
|
+
E. P. Wigner -- the original surmise, for GOE (beta=1).
|
|
7
|
+
M. L. Mehta, *Random Matrices* (3rd ed.), Academic Press, 2004 -- the
|
|
8
|
+
GUE/GSE surmises and their derivation from the 2x2 ensemble.
|
|
9
|
+
|
|
10
|
+
The beta-dependent closed form used here::
|
|
11
|
+
|
|
12
|
+
P_beta(s) = a(beta) * s**beta * exp(-b(beta) * s**2)
|
|
13
|
+
|
|
14
|
+
with a, b fixed by normalization (integral P ds = 1) and unit mean
|
|
15
|
+
spacing (integral s*P ds = 1), reduces exactly to the classical
|
|
16
|
+
GOE/GUE/GSE surmises at beta = 1, 2, 4 and is the standard two-parameter
|
|
17
|
+
generalization to continuum beta (see e.g. G. Livan, M. Novaes, P. Vivo,
|
|
18
|
+
"Introduction to Random Matrices: Theory and Practice", Springer, 2018,
|
|
19
|
+
Sec. 2.4). It is a surmise -- exact for the 2x2 ensemble, an excellent
|
|
20
|
+
but not exact approximation to the true N-level spacing distribution --
|
|
21
|
+
which is why it is validated here as a benchmark rather than assumed.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
|
|
28
|
+
import numpy as np
|
|
29
|
+
from scipy.special import gamma, gammainc, gammaincinv
|
|
30
|
+
|
|
31
|
+
from ..spectrum import Spectrum
|
|
32
|
+
from .unfolding import unfold_by_cdf
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def nearest_neighbor_spacings(
|
|
36
|
+
spectrum: Spectrum,
|
|
37
|
+
cdf_func: Callable[[np.ndarray], np.ndarray],
|
|
38
|
+
edge_trim: float = 0.1,
|
|
39
|
+
) -> np.ndarray:
|
|
40
|
+
"""Unfolded nearest-neighbor spacings, pooled across every sample in
|
|
41
|
+
a Spectrum.
|
|
42
|
+
|
|
43
|
+
Parameters
|
|
44
|
+
----------
|
|
45
|
+
spectrum : physicskit.rmt.spectrum.Spectrum
|
|
46
|
+
cdf_func : callable
|
|
47
|
+
Theoretical CDF used for unfolding (e.g.
|
|
48
|
+
``physicskit.rmt.stats.density.semicircle_cdf`` for the Gaussian
|
|
49
|
+
ensembles).
|
|
50
|
+
edge_trim : float
|
|
51
|
+
Fraction of eigenvalues discarded from each edge *after*
|
|
52
|
+
unfolding, before taking spacings. The mean spectral density --
|
|
53
|
+
and therefore unfolding accuracy -- is least reliable near the
|
|
54
|
+
spectrum edges (soft-edge Tracy-Widom effects live exactly
|
|
55
|
+
there), so edge eigenvalues bias the bulk spacing statistic if
|
|
56
|
+
included. Trimming must happen after unfolding, not before:
|
|
57
|
+
unfolding uses the total eigenvalue count N as the scale factor,
|
|
58
|
+
so trimming first would unfold against the wrong N and bias the
|
|
59
|
+
mean spacing away from 1.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
numpy.ndarray
|
|
64
|
+
Pooled unfolded spacings from all samples.
|
|
65
|
+
"""
|
|
66
|
+
all_spacings = []
|
|
67
|
+
for row in spectrum.rescaled:
|
|
68
|
+
n = len(row)
|
|
69
|
+
unfolded = unfold_by_cdf(row, cdf_func)
|
|
70
|
+
lo = int(edge_trim * n)
|
|
71
|
+
hi = n - lo
|
|
72
|
+
if hi - lo < 2:
|
|
73
|
+
continue
|
|
74
|
+
all_spacings.append(np.diff(unfolded[lo:hi]))
|
|
75
|
+
return np.concatenate(all_spacings)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def circular_spacings(spectrum: Spectrum) -> np.ndarray:
|
|
79
|
+
"""Unfolded nearest-neighbor spacings for a circular ensemble
|
|
80
|
+
(COE/CUE/CSE), including the wraparound spacing between the largest
|
|
81
|
+
and smallest phase.
|
|
82
|
+
|
|
83
|
+
Unlike the Gaussian/Wishart ensembles, a circular ensemble has no
|
|
84
|
+
spectrum edge (it lives on a circle) and no unfolding-accuracy
|
|
85
|
+
concerns (``spectrum.rescaled`` is already exactly unfolded, see
|
|
86
|
+
``physicskit.rmt.ensembles.circular``), so every spacing is used -- no edge
|
|
87
|
+
trimming, and one extra "wraparound" spacing per sample compared to
|
|
88
|
+
the linear-spectrum case.
|
|
89
|
+
"""
|
|
90
|
+
all_spacings = []
|
|
91
|
+
for row in spectrum.rescaled:
|
|
92
|
+
n = len(row)
|
|
93
|
+
row_sorted = np.sort(row)
|
|
94
|
+
spacings = np.diff(row_sorted)
|
|
95
|
+
wraparound = (row_sorted[0] + n) - row_sorted[-1]
|
|
96
|
+
all_spacings.append(np.concatenate([spacings, [wraparound]]))
|
|
97
|
+
return np.concatenate(all_spacings)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def wigner_surmise_params(beta: float) -> tuple[float, float]:
|
|
101
|
+
"""(a, b) for P_beta(s) = a * s**beta * exp(-b * s**2), normalized to
|
|
102
|
+
integral P(s) ds = 1 and integral s * P(s) ds = 1 (unit mean spacing).
|
|
103
|
+
|
|
104
|
+
Derivation: writing the two normalization integrals in terms of
|
|
105
|
+
Gamma functions and eliminating a gives
|
|
106
|
+
b = [Gamma((beta+2)/2) / Gamma((beta+1)/2)]**2
|
|
107
|
+
a = 2 * b**((beta+1)/2) / Gamma((beta+1)/2)
|
|
108
|
+
Verified numerically against the classical closed forms at
|
|
109
|
+
beta=1 (a=pi/2, b=pi/4), beta=2 (a=32/pi^2, b=4/pi), and
|
|
110
|
+
beta=4 (a=2**18/(3**6 pi**3), b=64/(9 pi)).
|
|
111
|
+
"""
|
|
112
|
+
b = (gamma((beta + 2) / 2) / gamma((beta + 1) / 2)) ** 2
|
|
113
|
+
a = 2 * b ** ((beta + 1) / 2) / gamma((beta + 1) / 2)
|
|
114
|
+
return a, b
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def wigner_surmise_pdf(s: np.ndarray, beta: float) -> np.ndarray:
|
|
118
|
+
"""Generalized Wigner surmise density at spacing(s) s."""
|
|
119
|
+
a, b = wigner_surmise_params(beta)
|
|
120
|
+
s = np.asarray(s, dtype=float)
|
|
121
|
+
out = np.zeros_like(s)
|
|
122
|
+
mask = s >= 0
|
|
123
|
+
out[mask] = a * s[mask] ** beta * np.exp(-b * s[mask] ** 2)
|
|
124
|
+
return out
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def wigner_surmise_cdf(s: np.ndarray, beta: float) -> np.ndarray:
|
|
128
|
+
"""Generalized Wigner surmise CDF.
|
|
129
|
+
|
|
130
|
+
Closed form: substituting u = t^2 in the defining integral reduces it
|
|
131
|
+
to the regularized lower incomplete gamma function,
|
|
132
|
+
F(s) = P((beta+1)/2, b*s^2)
|
|
133
|
+
(verified numerically against direct quadrature of the PDF).
|
|
134
|
+
"""
|
|
135
|
+
_, b = wigner_surmise_params(beta)
|
|
136
|
+
s = np.asarray(s, dtype=float)
|
|
137
|
+
out = np.zeros_like(s)
|
|
138
|
+
mask = s >= 0
|
|
139
|
+
out[mask] = gammainc((beta + 1) / 2, b * s[mask] ** 2)
|
|
140
|
+
return out
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def wigner_surmise_samples(size: int | tuple[int, ...], beta: float, rng: np.random.Generator) -> np.ndarray:
|
|
144
|
+
"""Exact samples from the generalized Wigner surmise via inverse-CDF
|
|
145
|
+
sampling (the CDF's closed form as a regularized incomplete gamma
|
|
146
|
+
function has a matching closed-form inverse, ``gammaincinv``, so no
|
|
147
|
+
rejection sampling is needed)."""
|
|
148
|
+
_, b = wigner_surmise_params(beta)
|
|
149
|
+
u = rng.uniform(0.0, 1.0, size=size)
|
|
150
|
+
return np.sqrt(gammaincinv((beta + 1) / 2, u) / b)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""The Tracy-Widom distributions F_1, F_2, F_4: limiting law of the
|
|
2
|
+
largest eigenvalue at the soft edge of the Gaussian ensembles.
|
|
3
|
+
|
|
4
|
+
References
|
|
5
|
+
----------
|
|
6
|
+
C. A. Tracy, H. Widom, "Level-spacing distributions and the Airy
|
|
7
|
+
kernel", Commun. Math. Phys. 159 (1994) 151 -- F_2 (GUE, beta=2).
|
|
8
|
+
C. A. Tracy, H. Widom, "On orthogonal and symplectic matrix ensembles",
|
|
9
|
+
Commun. Math. Phys. 177 (1996) 727 -- F_1 (GOE, beta=1) and F_4
|
|
10
|
+
(GSE, beta=4).
|
|
11
|
+
|
|
12
|
+
Construction
|
|
13
|
+
------------
|
|
14
|
+
All three distributions are built from q(x), the Hastings-McLeod
|
|
15
|
+
solution of the Painleve II equation
|
|
16
|
+
|
|
17
|
+
q''(x) = x*q(x) + 2*q(x)^3, q(x) ~ Ai(x) as x -> +infinity
|
|
18
|
+
|
|
19
|
+
(the unique solution with this decaying tail; other solutions of the
|
|
20
|
+
same ODE blow up). q is obtained numerically by integrating *backward*
|
|
21
|
+
from a large positive x0 (where q(x0) = Ai(x0), q'(x0) = Ai'(x0) to
|
|
22
|
+
machine precision) down to a negative x_min -- forward integration from
|
|
23
|
+
-infinity is unstable, since nearby non-Hastings-McLeod solutions
|
|
24
|
+
diverge exponentially in that direction.
|
|
25
|
+
|
|
26
|
+
Numerical domain note: backward integration itself becomes unstable
|
|
27
|
+
(round-off eventually kicks the trajectory onto a diverging branch) for
|
|
28
|
+
x below about -9 to -10. This implementation restricts to [-6, 6] by
|
|
29
|
+
default, verified to match the known asymptotic q(x) ~ sqrt(-x/2) to
|
|
30
|
+
better than 0.2% at x=-6, and F_2/F_1/F_4 are already extremely close to
|
|
31
|
+
0 there (the left tail decays like ``exp(-|s|^3/12)``), so truncating the
|
|
32
|
+
domain has negligible effect on the distributions actually used.
|
|
33
|
+
|
|
34
|
+
Given q, the three distributions are:
|
|
35
|
+
|
|
36
|
+
F_2(s) = exp(-integral_s^inf (x-s) q(x)^2 dx)
|
|
37
|
+
F_1(s) = sqrt(F_2(s)) * exp(-(1/2) * integral_s^inf q(x) dx)
|
|
38
|
+
F_4(s) = sqrt(F_2(s)) * cosh((1/2) * integral_s^inf q(x) dx)
|
|
39
|
+
|
|
40
|
+
Edge-scaling note (important, and the one detail here that is NOT
|
|
41
|
+
standard textbook boilerplate): for a Gaussian beta-ensemble normalized
|
|
42
|
+
to semicircle support [-2, 2] (as ``physicskit.rmt.ensembles.gaussian`` uses),
|
|
43
|
+
the largest eigenvalue converges as
|
|
44
|
+
|
|
45
|
+
n^(2/3) * (lambda_max - 2) -> F_1 (GOE, beta=1)
|
|
46
|
+
n^(2/3) * (lambda_max - 2) -> F_2 (GUE, beta=2)
|
|
47
|
+
(2n)^(2/3) * (lambda_max - 2) -> F_4 (GSE, beta=4)
|
|
48
|
+
|
|
49
|
+
The GSE case's extra factor of 2 in the effective scale is easy to get
|
|
50
|
+
wrong from memory (and was NOT assumed here) -- it was determined by
|
|
51
|
+
directly comparing candidate scalings against Monte Carlo largest-
|
|
52
|
+
eigenvalue samples from the already-validated ``GSE`` ensemble, via a
|
|
53
|
+
KS test, rather than taken from a half-remembered formula. See the
|
|
54
|
+
design/development notes and ``tests/test_tracy_widom.py``.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
from __future__ import annotations
|
|
58
|
+
|
|
59
|
+
from dataclasses import dataclass
|
|
60
|
+
|
|
61
|
+
import numpy as np
|
|
62
|
+
from scipy.integrate import cumulative_trapezoid, solve_ivp
|
|
63
|
+
from scipy.special import airy
|
|
64
|
+
|
|
65
|
+
from ..spectrum import Spectrum
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class _PainleveIISolution:
|
|
70
|
+
grid: np.ndarray
|
|
71
|
+
F1: np.ndarray
|
|
72
|
+
F2: np.ndarray
|
|
73
|
+
F4: np.ndarray
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
_CACHE: dict[tuple[float, float, int], _PainleveIISolution] = {}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _solve(x_max: float = 6.0, x_min: float = -6.0, n_grid: int = 20000) -> _PainleveIISolution:
|
|
80
|
+
"""Solve Painleve II once and build F1/F2/F4 on a grid. Cached by
|
|
81
|
+
(x_max, x_min, n_grid) since this is a one-time cost reused by every
|
|
82
|
+
subsequent CDF/PDF/sampling call."""
|
|
83
|
+
key = (x_max, x_min, n_grid)
|
|
84
|
+
if key in _CACHE:
|
|
85
|
+
return _CACHE[key]
|
|
86
|
+
|
|
87
|
+
def rhs(x: float, y: tuple[float, float]) -> list[float]:
|
|
88
|
+
q, qp = y
|
|
89
|
+
return [qp, x * q + 2.0 * q**3]
|
|
90
|
+
|
|
91
|
+
ai, aip, _, _ = airy(x_max)
|
|
92
|
+
sol = solve_ivp(
|
|
93
|
+
rhs,
|
|
94
|
+
[x_max, x_min],
|
|
95
|
+
[ai, aip],
|
|
96
|
+
dense_output=True,
|
|
97
|
+
rtol=1e-12,
|
|
98
|
+
atol=1e-14,
|
|
99
|
+
max_step=0.005,
|
|
100
|
+
)
|
|
101
|
+
if not sol.success:
|
|
102
|
+
raise RuntimeError(f"Painleve II integration failed: {sol.message}")
|
|
103
|
+
|
|
104
|
+
grid = np.linspace(x_min, x_max, n_grid)
|
|
105
|
+
q_vals = sol.sol(grid)[0]
|
|
106
|
+
|
|
107
|
+
# Reverse cumulative integrals (needed since every quantity here is
|
|
108
|
+
# defined as an integral from s to +infinity, i.e. against the grid's
|
|
109
|
+
# natural ascending order).
|
|
110
|
+
xs_rev = grid[::-1]
|
|
111
|
+
q2_rev = (q_vals**2)[::-1]
|
|
112
|
+
q_rev = q_vals[::-1]
|
|
113
|
+
|
|
114
|
+
a_s = -cumulative_trapezoid(q2_rev, xs_rev, initial=0.0)[::-1] # int_s^inf q^2 dx
|
|
115
|
+
b_s = -cumulative_trapezoid(xs_rev * q2_rev, xs_rev, initial=0.0)[::-1] # int_s^inf x q^2 dx
|
|
116
|
+
i_s = -cumulative_trapezoid(q_rev, xs_rev, initial=0.0)[::-1] # int_s^inf q dx
|
|
117
|
+
|
|
118
|
+
r_s = b_s - grid * a_s
|
|
119
|
+
f2 = np.exp(-r_s)
|
|
120
|
+
f2_clipped = np.clip(f2, 0.0, 1.0)
|
|
121
|
+
f1 = np.sqrt(f2_clipped) * np.exp(-0.5 * i_s)
|
|
122
|
+
f4 = np.sqrt(f2_clipped) * np.cosh(0.5 * i_s)
|
|
123
|
+
|
|
124
|
+
result = _PainleveIISolution(grid=grid, F1=f1, F2=np.clip(f2, 0.0, 1.0), F4=np.clip(f4, 0.0, 1.0))
|
|
125
|
+
_CACHE[key] = result
|
|
126
|
+
return result
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# beta -> which precomputed curve, and the edge-scaling exponent base
|
|
130
|
+
# (n for beta=1,2; 2*n for beta=4 -- see module docstring).
|
|
131
|
+
_BETA_CURVE = {1: "F1", 2: "F2", 4: "F4"}
|
|
132
|
+
_BETA_SCALE_MULTIPLIER = {1: 1.0, 2: 1.0, 4: 2.0}
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def largest_eigenvalues(spectrum: Spectrum) -> np.ndarray:
|
|
136
|
+
"""The largest (rescaled) eigenvalue from each sample in a Spectrum
|
|
137
|
+
-- the raw statistic that Tracy-Widom-type soft-edge laws govern."""
|
|
138
|
+
return spectrum.rescaled.max(axis=1)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def tracy_widom_cdf(s: np.ndarray, beta: int) -> np.ndarray:
|
|
142
|
+
"""CDF of the Tracy-Widom distribution at Dyson index beta (1, 2, or 4)."""
|
|
143
|
+
if beta not in _BETA_CURVE:
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Only beta in {{1, 2, 4}} have closed-form Tracy-Widom laws "
|
|
146
|
+
f"(F_1, F_2, F_4); beta={beta} is not supported. The general-beta "
|
|
147
|
+
f"soft-edge law exists (Ramirez-Rider-Virag stochastic Airy "
|
|
148
|
+
f"operator) but has no elementary closed form."
|
|
149
|
+
)
|
|
150
|
+
sol = _solve()
|
|
151
|
+
curve = getattr(sol, _BETA_CURVE[beta])
|
|
152
|
+
s = np.asarray(s, dtype=float)
|
|
153
|
+
return np.interp(s, sol.grid, curve, left=0.0, right=1.0)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def tracy_widom_edge_scale(n: int, beta: int) -> float:
|
|
157
|
+
"""Factor by which (lambda_max - 2) should be multiplied to compare
|
|
158
|
+
against the beta-indexed Tracy-Widom CDF: n^(2/3) for beta=1, 2;
|
|
159
|
+
(2n)^(2/3) for beta=4 (see module docstring for how the beta=4 factor
|
|
160
|
+
was determined)."""
|
|
161
|
+
if beta not in _BETA_SCALE_MULTIPLIER:
|
|
162
|
+
raise ValueError(f"Only beta in {{1, 2, 4}} are supported, got {beta}")
|
|
163
|
+
return (_BETA_SCALE_MULTIPLIER[beta] * n) ** (2.0 / 3.0)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def tracy_widom_rvs(size: int | tuple[int, ...], beta: int, rng: np.random.Generator) -> np.ndarray:
|
|
167
|
+
"""Sample from the Tracy-Widom distribution via inverse-CDF
|
|
168
|
+
interpolation on the same grid used for the CDF."""
|
|
169
|
+
if beta not in _BETA_CURVE:
|
|
170
|
+
raise ValueError(f"Only beta in {{1, 2, 4}} are supported, got {beta}")
|
|
171
|
+
sol = _solve()
|
|
172
|
+
curve = getattr(sol, _BETA_CURVE[beta])
|
|
173
|
+
# curve is monotonically increasing in practice; interpolate its inverse
|
|
174
|
+
u = rng.uniform(curve[0], curve[-1], size=size)
|
|
175
|
+
return np.interp(u, curve, sol.grid)
|