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,136 @@
|
|
|
1
|
+
"""Tests for JOE, JUE and the Wachter distribution benchmark.
|
|
2
|
+
|
|
3
|
+
The Wachter formula and its parametrization (a=m1/n, b=m2/n, inverse
|
|
4
|
+
aspect ratios) were NOT taken from memory -- an initial guess at the
|
|
5
|
+
formula (with a,b as the more familiar n/m aspect ratios) gave support
|
|
6
|
+
edges outside [0, 1], which is impossible for this ensemble, immediately
|
|
7
|
+
flagging the error. The correct formula and parametrization were found
|
|
8
|
+
via a targeted search and then verified numerically against both real
|
|
9
|
+
(beta=1) and complex (beta=2) simulations before being used here -- see
|
|
10
|
+
``physicskit.rmt/stats/wachter.py`` and the design notes.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pytest
|
|
15
|
+
from cache_utils import cached_sample
|
|
16
|
+
from scipy.stats import linregress
|
|
17
|
+
|
|
18
|
+
import physicskit.rmt as rmt
|
|
19
|
+
|
|
20
|
+
JACOBI = [
|
|
21
|
+
("JOE", rmt.ensembles.JOE, 1),
|
|
22
|
+
("JUE", rmt.ensembles.JUE, 2),
|
|
23
|
+
("JSE", rmt.ensembles.JSE, 4),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
28
|
+
def test_beta_is_correct(name, cls, beta):
|
|
29
|
+
ens = cls(n=50, m1=150, m2=200, seed=0)
|
|
30
|
+
assert ens.beta == beta
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
34
|
+
def test_a_b_properties(name, cls, beta):
|
|
35
|
+
ens = cls(n=100, m1=200, m2=300, seed=0)
|
|
36
|
+
assert ens.a == pytest.approx(2.0)
|
|
37
|
+
assert ens.b == pytest.approx(3.0)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
41
|
+
def test_eigenvalues_in_unit_interval(name, cls, beta):
|
|
42
|
+
ens = cls(n=200, m1=400, m2=600, seed=1)
|
|
43
|
+
spectrum = cached_sample(ens, n_samples=10)
|
|
44
|
+
assert np.all(spectrum.eigenvalues >= -1e-9)
|
|
45
|
+
assert np.all(spectrum.eigenvalues <= 1.0 + 1e-9)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
49
|
+
def test_reproducibility(name, cls, beta):
|
|
50
|
+
ens_a = cls(n=80, m1=160, m2=240, seed=42)
|
|
51
|
+
ens_b = cls(n=80, m1=160, m2=240, seed=42)
|
|
52
|
+
spec_a = ens_a.sample(n_samples=3)
|
|
53
|
+
spec_b = ens_b.sample(n_samples=3)
|
|
54
|
+
np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
58
|
+
def test_support_matches_wachter_edges(name, cls, beta):
|
|
59
|
+
n, m1, m2 = 2000, 4000, 6000
|
|
60
|
+
ens = cls(n=n, m1=m1, m2=m2, seed=2)
|
|
61
|
+
spectrum = cached_sample(ens, n_samples=1)
|
|
62
|
+
lo, hi = rmt.stats.wachter_support(m1 / n, m2 / n)
|
|
63
|
+
eigs = spectrum.eigenvalues.ravel()
|
|
64
|
+
assert eigs.min() > lo - 0.05
|
|
65
|
+
assert eigs.max() < hi + 0.05
|
|
66
|
+
assert eigs.min() < lo + 0.08
|
|
67
|
+
assert eigs.max() > hi - 0.08
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.parametrize("name,cls,beta", JACOBI)
|
|
71
|
+
def test_wachter_convergence_shrinks_with_n(name, cls, beta):
|
|
72
|
+
# Looped manually (rather than via benchmark.convergence_curve) so
|
|
73
|
+
# each N's sample can be disk-cached individually.
|
|
74
|
+
m1_ratio, m2_ratio = 2.0, 3.0 # fixed inverse aspect ratios as n scales
|
|
75
|
+
n_values = [50, 150, 450]
|
|
76
|
+
|
|
77
|
+
benchmark = rmt.validation.Wachter(a=m1_ratio, b=m2_ratio)
|
|
78
|
+
results = []
|
|
79
|
+
for n in n_values:
|
|
80
|
+
ens = cls(n=n, m1=int(n * m1_ratio), m2=int(n * m2_ratio), seed=9)
|
|
81
|
+
spectrum = cached_sample(ens, n_samples=20)
|
|
82
|
+
results.append(benchmark.validate(spectrum, seed=9))
|
|
83
|
+
|
|
84
|
+
ks_stats = np.array([r.ks_statistic for r in results])
|
|
85
|
+
slope = linregress(np.log(n_values), np.log(ks_stats)).slope
|
|
86
|
+
assert slope < -0.3, f"{name}: expected shrinking KS distance, slope={slope}"
|
|
87
|
+
assert ks_stats[-1] < 0.01
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_invalid_parameters_rejected():
|
|
91
|
+
with pytest.raises(ValueError):
|
|
92
|
+
rmt.ensembles.JOE(n=100, m1=50, m2=200, seed=0) # m1 < n
|
|
93
|
+
with pytest.raises(ValueError):
|
|
94
|
+
rmt.ensembles.JacobiBetaEnsemble(n=100, m1=200, m2=200, beta=3, seed=0)
|
|
95
|
+
with pytest.raises(ValueError):
|
|
96
|
+
rmt.validation.Wachter(a=0.5, b=2.0) # a < 1 invalid
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_jse_eigenvalue_count_is_n_not_2n():
|
|
100
|
+
# JSE's quaternion embedding internally builds 2n x 2n matrices; only
|
|
101
|
+
# the n distinct (post-Kramers-deduplication) eigenvalues should be
|
|
102
|
+
# returned, matching GSE/CSE/EffGSE's convention -- see module
|
|
103
|
+
# docstring in physicskit.rmt/ensembles/jacobi.py.
|
|
104
|
+
n = 40
|
|
105
|
+
ens = rmt.ensembles.JSE(n=n, m1=80, m2=120, seed=0)
|
|
106
|
+
spectrum = ens.sample(n_samples=2)
|
|
107
|
+
assert spectrum.eigenvalues.shape[1] == n
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_wachter_pdf_zero_outside_support():
|
|
111
|
+
a, b = 2.0, 3.0
|
|
112
|
+
lo, hi = rmt.stats.wachter_support(a, b)
|
|
113
|
+
assert rmt.stats.wachter_pdf(np.array([lo - 0.1, hi + 0.1]), a, b)[0] == 0.0
|
|
114
|
+
assert rmt.stats.wachter_pdf(np.array([lo - 0.1, hi + 0.1]), a, b)[1] == 0.0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def test_wachter_pdf_scalar_integrand_is_zero_outside_support():
|
|
118
|
+
"""quad() integrates _wachter_pdf_scalar directly (not the vectorized
|
|
119
|
+
wachter_pdf above) to build wachter_cdf; it needs the same
|
|
120
|
+
zero-outside-support guard."""
|
|
121
|
+
from physicskit.rmt.stats.wachter import _wachter_pdf_scalar
|
|
122
|
+
|
|
123
|
+
a, b = 2.0, 3.0
|
|
124
|
+
lo, hi = rmt.stats.wachter_support(a, b)
|
|
125
|
+
assert _wachter_pdf_scalar(lo - 0.1, a, b, lo, hi) == 0.0
|
|
126
|
+
assert _wachter_pdf_scalar(hi + 0.1, a, b, lo, hi) == 0.0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_wachter_cdf_is_zero_below_and_one_above_support_and_monotonic():
|
|
130
|
+
a, b = 2.0, 3.0
|
|
131
|
+
lo, hi = rmt.stats.wachter_support(a, b)
|
|
132
|
+
x = np.linspace(lo - 0.1, hi + 0.1, 20)
|
|
133
|
+
cdf = rmt.stats.wachter_cdf(x, a, b)
|
|
134
|
+
assert cdf[0] == 0.0
|
|
135
|
+
assert cdf[-1] == 1.0
|
|
136
|
+
assert np.all(np.diff(cdf) >= -1e-12)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Tests for eigenvector-based localization statistics (inverse
|
|
2
|
+
participation ratio) and the ``sample(return_eigenvectors=True)`` API.
|
|
3
|
+
|
|
4
|
+
``ipr_theory``'s Dirichlet-moment formula was verified during
|
|
5
|
+
development against direct Haar-random-vector Monte Carlo at beta=1, 2,
|
|
6
|
+
4 before being trusted (see ``physicskit.rmt.stats.localization``). The
|
|
7
|
+
tridiagonal-model ensembles (GOE/GUE/GSE via the fast
|
|
8
|
+
Dumitriu-Edelman sampler) do NOT get eigenvector support from that
|
|
9
|
+
formula for free: their raw eigenvectors were checked during development
|
|
10
|
+
and found to be more localized than a true Haar vector (IPR ~2-3x too
|
|
11
|
+
large, growing with n) -- only a genuinely dense diagonalization gives
|
|
12
|
+
Haar-distributed eigenvectors, which is why
|
|
13
|
+
``HermiteBetaEnsemble._sample_eigenvalues_and_vectors`` uses a separate
|
|
14
|
+
dense construction for beta in (1, 2, 4). GSE (beta=4) eigenvectors are
|
|
15
|
+
naturally 2n-dimensional complex vectors (n quaternionic "sites"), so
|
|
16
|
+
they need the dedicated ``inverse_participation_ratio_quaternionic``
|
|
17
|
+
(folding pairs of components first) rather than the plain
|
|
18
|
+
``inverse_participation_ratio`` -- verified during development that the
|
|
19
|
+
folded IPR matches ``ipr_theory(n, beta=4)`` to four significant figures.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
import pytest
|
|
24
|
+
|
|
25
|
+
import physicskit.rmt as rmt
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_ensemble_without_eigenvector_support_raises():
|
|
29
|
+
ens = rmt.ensembles.LOE(n=20, m=25, seed=0)
|
|
30
|
+
with pytest.raises(NotImplementedError):
|
|
31
|
+
ens.sample(return_eigenvectors=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_gse_eigenvectors_are_normalized_and_shaped_2n_by_n():
|
|
35
|
+
n = 20
|
|
36
|
+
ens = rmt.ensembles.GSE(n=n, seed=0)
|
|
37
|
+
spectrum = ens.sample(n_samples=2, return_eigenvectors=True)
|
|
38
|
+
assert spectrum.eigenvectors is not None
|
|
39
|
+
assert spectrum.eigenvalues.shape == (2, n)
|
|
40
|
+
assert spectrum.eigenvectors.shape == (2, 2 * n, n)
|
|
41
|
+
norms = np.sum(np.abs(spectrum.eigenvectors) ** 2, axis=1)
|
|
42
|
+
np.testing.assert_allclose(norms, 1.0, atol=1e-8)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def test_gse_quaternionic_ipr_matches_haar_theory():
|
|
46
|
+
n = 200
|
|
47
|
+
ens = rmt.ensembles.GSE(n=n, seed=1)
|
|
48
|
+
spectrum = ens.sample(n_samples=20, return_eigenvectors=True)
|
|
49
|
+
ipr = rmt.stats.inverse_participation_ratio_quaternionic(spectrum.eigenvectors)
|
|
50
|
+
theory = rmt.stats.ipr_theory(n, beta=4)
|
|
51
|
+
assert ipr.mean() == pytest.approx(theory, rel=0.15)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_naive_ipr_on_gse_eigenvectors_does_not_match_theory():
|
|
55
|
+
# Documents the pitfall: calling the plain (non-quaternionic) IPR
|
|
56
|
+
# function directly on GSE eigenvectors treats all 2n complex
|
|
57
|
+
# components as independent sites, which does NOT match
|
|
58
|
+
# ipr_theory(n, beta=4) -- inverse_participation_ratio_quaternionic
|
|
59
|
+
# must be used instead (see module docstring).
|
|
60
|
+
n = 200
|
|
61
|
+
ens = rmt.ensembles.GSE(n=n, seed=1)
|
|
62
|
+
spectrum = ens.sample(n_samples=20, return_eigenvectors=True)
|
|
63
|
+
naive_ipr = rmt.stats.inverse_participation_ratio(spectrum.eigenvectors)
|
|
64
|
+
theory = rmt.stats.ipr_theory(n, beta=4)
|
|
65
|
+
assert naive_ipr.mean() != pytest.approx(theory, rel=0.15)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@pytest.mark.parametrize("cls", [rmt.ensembles.GOE, rmt.ensembles.GUE])
|
|
69
|
+
def test_dense_gaussian_ensemble_eigenvectors_are_normalized(cls):
|
|
70
|
+
ens = cls(n=30, seed=0)
|
|
71
|
+
spectrum = ens.sample(n_samples=2, return_eigenvectors=True)
|
|
72
|
+
assert spectrum.eigenvectors is not None
|
|
73
|
+
assert spectrum.eigenvectors.shape == (2, 30, 30)
|
|
74
|
+
norms = np.sum(np.abs(spectrum.eigenvectors) ** 2, axis=1)
|
|
75
|
+
np.testing.assert_allclose(norms, 1.0, atol=1e-8)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.parametrize("cls,beta", [(rmt.ensembles.GOE, 1), (rmt.ensembles.GUE, 2)])
|
|
79
|
+
def test_dense_gaussian_ensemble_ipr_matches_haar_theory(cls, beta):
|
|
80
|
+
n = 250
|
|
81
|
+
ens = cls(n=n, seed=1)
|
|
82
|
+
spectrum = ens.sample(n_samples=30, return_eigenvectors=True)
|
|
83
|
+
ipr = rmt.stats.inverse_participation_ratio(spectrum.eigenvectors)
|
|
84
|
+
theory = rmt.stats.ipr_theory(n, beta)
|
|
85
|
+
assert ipr.mean() == pytest.approx(theory, rel=0.15)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_ipr_theory_matches_direct_haar_vector_simulation():
|
|
89
|
+
# Independent check of the closed-form formula itself, bypassing any
|
|
90
|
+
# ensemble machinery: a Haar-random (Gaussian, then normalized) real
|
|
91
|
+
# vector's mean IPR should match ipr_theory(n, beta=1) directly.
|
|
92
|
+
rng = np.random.default_rng(7)
|
|
93
|
+
n = 100
|
|
94
|
+
x = rng.standard_normal((20_000, n))
|
|
95
|
+
x /= np.linalg.norm(x, axis=1, keepdims=True)
|
|
96
|
+
empirical = np.mean(np.sum(x**4, axis=1))
|
|
97
|
+
assert empirical == pytest.approx(rmt.stats.ipr_theory(n, beta=1), rel=0.05)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def test_participation_ratio_is_reciprocal_of_ipr():
|
|
101
|
+
rng = np.random.default_rng(3)
|
|
102
|
+
vecs = rng.standard_normal((5, 10, 4))
|
|
103
|
+
ipr = rmt.stats.inverse_participation_ratio(vecs)
|
|
104
|
+
pr = rmt.stats.participation_ratio(vecs)
|
|
105
|
+
np.testing.assert_allclose(pr, 1.0 / ipr)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_pbrm_localization_transition_via_ipr():
|
|
109
|
+
# The defining physics of PowerLawBandedEnsemble (see its module
|
|
110
|
+
# docstring): small b (narrow band) -> localized eigenvectors (IPR
|
|
111
|
+
# much larger than the delocalized/GOE-like baseline); large b
|
|
112
|
+
# (effectively unbanded) -> delocalized eigenvectors, IPR approaching
|
|
113
|
+
# the Haar-vector theory value.
|
|
114
|
+
n = 300
|
|
115
|
+
localized = rmt.ensembles.PowerLawBandedEnsemble(n=n, b=0.5, alpha=2.0, seed=10)
|
|
116
|
+
delocalized = rmt.ensembles.PowerLawBandedEnsemble(n=n, b=50.0, alpha=2.0, seed=11)
|
|
117
|
+
|
|
118
|
+
# cache_utils.cached_sample never requests eigenvectors, so sample directly.
|
|
119
|
+
spec_localized = localized.sample(n_samples=10, return_eigenvectors=True)
|
|
120
|
+
spec_delocalized = delocalized.sample(n_samples=10, return_eigenvectors=True)
|
|
121
|
+
|
|
122
|
+
ipr_localized = rmt.stats.inverse_participation_ratio(spec_localized.eigenvectors).mean()
|
|
123
|
+
ipr_delocalized = rmt.stats.inverse_participation_ratio(spec_delocalized.eigenvectors).mean()
|
|
124
|
+
theory_delocalized = rmt.stats.ipr_theory(n, beta=1)
|
|
125
|
+
|
|
126
|
+
assert ipr_localized > 10 * ipr_delocalized
|
|
127
|
+
assert ipr_delocalized == pytest.approx(theory_delocalized, rel=0.3)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_eigenvectors_absent_by_default():
|
|
131
|
+
ens = rmt.ensembles.GOE(n=20, seed=0)
|
|
132
|
+
spectrum = ens.sample(n_samples=2)
|
|
133
|
+
assert spectrum.eigenvectors is None
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
# --- multifractal dimension spectrum ---
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_generalized_ipr_matches_plain_ipr_at_q_2():
|
|
140
|
+
rng = np.random.default_rng(4)
|
|
141
|
+
vecs = rng.standard_normal((5, 10, 4))
|
|
142
|
+
np.testing.assert_allclose(rmt.stats.generalized_ipr(vecs, q=2.0), rmt.stats.inverse_participation_ratio(vecs))
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def test_generalized_ipr_rejects_q_equals_1():
|
|
146
|
+
rng = np.random.default_rng(4)
|
|
147
|
+
vecs = rng.standard_normal((5, 4))
|
|
148
|
+
with pytest.raises(ValueError):
|
|
149
|
+
rmt.stats.generalized_ipr(vecs, q=1.0)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_multifractal_dimension_is_one_for_delocalized_pbrm():
|
|
153
|
+
# Large, fixed band-width b -> effectively GOE-like/delocalized, so
|
|
154
|
+
# D_2 should be close to 1 (E[I_2] ~ 1/n exactly).
|
|
155
|
+
def factory(n, seed):
|
|
156
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
|
|
157
|
+
|
|
158
|
+
d_2, r_squared = rmt.stats.multifractal_dimension(factory, n_values=[100, 200, 400, 800], q=2.0, n_samples=10, seed=0)
|
|
159
|
+
assert d_2 == pytest.approx(1.0, abs=0.1)
|
|
160
|
+
assert r_squared > 0.99
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def test_multifractal_dimension_is_zero_for_localized_pbrm():
|
|
164
|
+
# Small, fixed band-width b -> strongly localized, so E[I_2] stays
|
|
165
|
+
# roughly n-independent, giving D_2 close to 0.
|
|
166
|
+
def factory(n, seed):
|
|
167
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=0.3, alpha=2.0, seed=seed)
|
|
168
|
+
|
|
169
|
+
d_2, _ = rmt.stats.multifractal_dimension(factory, n_values=[100, 200, 400, 800], q=2.0, n_samples=10, seed=1)
|
|
170
|
+
assert d_2 == pytest.approx(0.0, abs=0.2)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
# --- f(alpha) singularity spectrum ---
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_mass_exponent_is_exactly_zero_at_q_1():
|
|
177
|
+
def factory(n, seed):
|
|
178
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
|
|
179
|
+
|
|
180
|
+
tau_1, r_squared = rmt.stats.mass_exponent(factory, n_values=[100, 200, 400], q=1.0, n_samples=5, seed=0)
|
|
181
|
+
assert tau_1 == 0.0
|
|
182
|
+
assert r_squared == 1.0
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_multifractal_dimension_rejects_q_1():
|
|
186
|
+
def factory(n, seed):
|
|
187
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
|
|
188
|
+
|
|
189
|
+
with pytest.raises(ValueError):
|
|
190
|
+
rmt.stats.multifractal_dimension(factory, n_values=[100, 200], q=1.0, n_samples=5, seed=0)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
@pytest.mark.slow
|
|
194
|
+
def test_mass_exponent_matches_multifractal_dimension_relation():
|
|
195
|
+
def factory(n, seed):
|
|
196
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
|
|
197
|
+
|
|
198
|
+
n_values = [50, 100, 200, 400]
|
|
199
|
+
tau_2, _ = rmt.stats.mass_exponent(factory, n_values, q=2.0, n_samples=10, seed=0)
|
|
200
|
+
d_2, _ = rmt.stats.multifractal_dimension(factory, n_values, q=2.0, n_samples=10, seed=0)
|
|
201
|
+
assert tau_2 / (2.0 - 1.0) == pytest.approx(d_2)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.mark.slow
|
|
205
|
+
def test_singularity_spectrum_is_a_single_point_for_delocalized_system():
|
|
206
|
+
# Fully delocalized (D_q=1 for all q) means tau(q)=q-1 exactly, so
|
|
207
|
+
# alpha(q)=1 and f(alpha)=1 for every q -- the degenerate
|
|
208
|
+
# "monofractal" limit of the singularity spectrum.
|
|
209
|
+
def factory(n, seed):
|
|
210
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1000.0, alpha=2.0, seed=seed)
|
|
211
|
+
|
|
212
|
+
alpha, f_alpha = rmt.stats.singularity_spectrum(
|
|
213
|
+
factory,
|
|
214
|
+
n_values=[100, 200, 400, 800],
|
|
215
|
+
q_values=np.array([0.5, 1.0, 1.5, 2.0]),
|
|
216
|
+
n_samples=10,
|
|
217
|
+
seed=0,
|
|
218
|
+
)
|
|
219
|
+
np.testing.assert_allclose(alpha, 1.0, atol=0.15)
|
|
220
|
+
np.testing.assert_allclose(f_alpha, 1.0, atol=0.15)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@pytest.mark.slow
|
|
224
|
+
def test_singularity_spectrum_is_positive_and_concave_at_small_q_for_critical_pbrm():
|
|
225
|
+
# At PBRM's multifractal critical point, the moderate-|q| portion of
|
|
226
|
+
# f(alpha) should be positive (a genuine fractal dimension) and
|
|
227
|
+
# concave (f(alpha) decreasing in |q-1|) -- see module/function
|
|
228
|
+
# docstring for why larger |q| is not asserted here (harder
|
|
229
|
+
# convergence, possible legitimate negative-dimension branch).
|
|
230
|
+
def factory(n, seed):
|
|
231
|
+
return rmt.ensembles.PowerLawBandedEnsemble(n=n, b=1.0, alpha=1.0, seed=seed)
|
|
232
|
+
|
|
233
|
+
q_values = np.array([0.5, 1.0, 1.5])
|
|
234
|
+
_, f_alpha = rmt.stats.singularity_spectrum(factory, n_values=[75, 150, 300, 600], q_values=q_values, n_samples=12, seed=2)
|
|
235
|
+
assert np.all(f_alpha > 0.0)
|
|
236
|
+
# Standard multifractal shape: f(alpha(q)) decreases monotonically as
|
|
237
|
+
# q increases (alpha(q) itself decreases with q, with the global max
|
|
238
|
+
# of f at q=0 -- not asserted here since q=0 needs even larger n for
|
|
239
|
+
# reliable convergence); over this q range it should be decreasing.
|
|
240
|
+
assert f_alpha[0] > f_alpha[1] > f_alpha[2]
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"""Tests for the Poisson ensemble -- the integrable-system (Berry-Tabor)
|
|
2
|
+
null model that RMT level-repulsion statistics are always contrasted
|
|
3
|
+
against.
|
|
4
|
+
|
|
5
|
+
Unlike GOE/GUE/GSE, whose spacing and ratio distributions are only
|
|
6
|
+
approximated by a surmise (exact for a reduced few-level model), the
|
|
7
|
+
Poisson process's spacing and ratio laws are exactly known in closed
|
|
8
|
+
form -- so tests here check against exact theory throughout, not a
|
|
9
|
+
surmise. The ratio-statistic law (2/(1+r)**2 for the min/max-normalized
|
|
10
|
+
r used by ``physicskit.rmt.stats.ratios.ratio_statistics``) is derived directly
|
|
11
|
+
in the module docstring from the ratio of two i.i.d. Exponential(1)
|
|
12
|
+
variables; ``test_ratio_statistic_matches_derived_exact_formula`` checks
|
|
13
|
+
that derivation numerically before it is used anywhere else.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
import pytest
|
|
18
|
+
from cache_utils import cached_sample
|
|
19
|
+
from scipy.stats import expon, kstest
|
|
20
|
+
|
|
21
|
+
import physicskit.rmt as rmt
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_beta_is_none():
|
|
25
|
+
ens = rmt.ensembles.PoissonEnsemble(n=30, seed=0)
|
|
26
|
+
assert ens.beta is None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_shape_and_reproducibility():
|
|
30
|
+
ens_a = rmt.ensembles.PoissonEnsemble(n=50, seed=42)
|
|
31
|
+
ens_b = rmt.ensembles.PoissonEnsemble(n=50, seed=42)
|
|
32
|
+
spec_a = ens_a.sample(n_samples=5)
|
|
33
|
+
spec_b = ens_b.sample(n_samples=5)
|
|
34
|
+
assert spec_a.eigenvalues.shape == (5, 50)
|
|
35
|
+
np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_levels_are_strictly_increasing():
|
|
39
|
+
# Partial sums of strictly positive Exponential draws.
|
|
40
|
+
ens = rmt.ensembles.PoissonEnsemble(n=100, seed=1)
|
|
41
|
+
spectrum = cached_sample(ens, n_samples=5)
|
|
42
|
+
for row in spectrum.eigenvalues:
|
|
43
|
+
assert np.all(np.diff(row) > 0)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_mean_spacing_is_one():
|
|
47
|
+
ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=2)
|
|
48
|
+
spectrum = cached_sample(ens, n_samples=10)
|
|
49
|
+
spacings = np.concatenate([np.diff(row) for row in spectrum.eigenvalues])
|
|
50
|
+
assert spacings.mean() == pytest.approx(1.0, abs=0.03)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_spacing_distribution_matches_exact_exponential_law():
|
|
54
|
+
# No unfolding needed (see module docstring) -- raw spacings are
|
|
55
|
+
# already Exponential(1) by construction, checked directly against
|
|
56
|
+
# theory rather than a surmise.
|
|
57
|
+
ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=3)
|
|
58
|
+
spectrum = cached_sample(ens, n_samples=20)
|
|
59
|
+
spacings = np.concatenate([np.diff(row) for row in spectrum.eigenvalues])
|
|
60
|
+
ks = kstest(spacings, expon(scale=1.0).cdf)
|
|
61
|
+
assert ks.statistic < 0.01
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_ratio_statistic_matches_derived_exact_formula():
|
|
65
|
+
# Verify 2/(1+r)**2 (module docstring derivation) directly against
|
|
66
|
+
# Monte Carlo ratios of independent Exponential(1) pairs, before
|
|
67
|
+
# trusting it against the ensemble itself.
|
|
68
|
+
rng = np.random.default_rng(4)
|
|
69
|
+
x = rng.exponential(1.0, size=2_000_000)
|
|
70
|
+
y = rng.exponential(1.0, size=2_000_000)
|
|
71
|
+
r = np.minimum(x, y) / np.maximum(x, y)
|
|
72
|
+
|
|
73
|
+
def poisson_ratio_cdf(r):
|
|
74
|
+
return 2.0 * r / (1.0 + r)
|
|
75
|
+
|
|
76
|
+
ks = kstest(r, poisson_ratio_cdf)
|
|
77
|
+
assert ks.statistic < 0.002
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_ensemble_ratio_statistic_matches_exact_poisson_law():
|
|
81
|
+
ens = rmt.ensembles.PoissonEnsemble(n=2000, seed=5)
|
|
82
|
+
spectrum = cached_sample(ens, n_samples=20)
|
|
83
|
+
ratios = rmt.stats.ratio_statistics(spectrum)
|
|
84
|
+
|
|
85
|
+
def poisson_ratio_cdf(r):
|
|
86
|
+
return 2.0 * r / (1.0 + r)
|
|
87
|
+
|
|
88
|
+
ks = kstest(ratios, poisson_ratio_cdf)
|
|
89
|
+
assert ks.statistic < 0.01
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_number_variance_grows_linearly_like_poisson_theory():
|
|
93
|
+
# No unfolding needed -- raw levels already have unit mean density,
|
|
94
|
+
# so window-counting is done directly rather than via
|
|
95
|
+
# ``number_variance_empirical`` (which expects a fixed-shape
|
|
96
|
+
# theoretical CDF to unfold against, the wrong abstraction for an
|
|
97
|
+
# unbounded, already-uniform-density process -- see module
|
|
98
|
+
# docstring).
|
|
99
|
+
ens = rmt.ensembles.PoissonEnsemble(n=4000, seed=6)
|
|
100
|
+
spectrum = cached_sample(ens, n_samples=15)
|
|
101
|
+
l_values = [2, 5, 10, 20]
|
|
102
|
+
rng = np.random.default_rng(7)
|
|
103
|
+
|
|
104
|
+
empirical = []
|
|
105
|
+
for length in l_values:
|
|
106
|
+
counts = []
|
|
107
|
+
for row in spectrum.eigenvalues:
|
|
108
|
+
lo, hi = row[0], row[-1]
|
|
109
|
+
starts = rng.uniform(lo, hi - length, size=200)
|
|
110
|
+
counts.extend(np.sum((row >= s) & (row < s + length)) for s in starts)
|
|
111
|
+
empirical.append(np.var(counts))
|
|
112
|
+
empirical = np.array(empirical)
|
|
113
|
+
|
|
114
|
+
theory = rmt.stats.number_variance_poisson(l_values)
|
|
115
|
+
np.testing.assert_allclose(empirical, theory, atol=0.6)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_poisson_number_variance_is_much_larger_than_sampled_gue():
|
|
119
|
+
# The defining qualitative contrast this whole ensemble exists to
|
|
120
|
+
# provide: GUE's number variance grows logarithmically (level
|
|
121
|
+
# repulsion/rigidity), Poisson's grows linearly -- checked here
|
|
122
|
+
# against an actually-sampled GUE spectrum, not just GUE's theory
|
|
123
|
+
# curve (already covered by test_rigidity_universality.py).
|
|
124
|
+
l_values = [10, 20]
|
|
125
|
+
poisson_theory = rmt.stats.number_variance_poisson(l_values)
|
|
126
|
+
|
|
127
|
+
gue_ens = rmt.ensembles.GUE(n=4000, seed=8)
|
|
128
|
+
gue_spectrum = cached_sample(gue_ens, n_samples=15)
|
|
129
|
+
gue_empirical = rmt.stats.number_variance_empirical(
|
|
130
|
+
gue_spectrum,
|
|
131
|
+
rmt.stats.semicircle_cdf,
|
|
132
|
+
l_values,
|
|
133
|
+
n_windows=200,
|
|
134
|
+
seed=9,
|
|
135
|
+
)
|
|
136
|
+
assert np.all(gue_empirical < poisson_theory / 3)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_generalized_wigner_surmise_at_beta_zero_is_not_poisson():
|
|
140
|
+
# Documents the important distinction from the module docstring: the
|
|
141
|
+
# Gaussian-tailed generalized Wigner surmise does NOT reduce to the
|
|
142
|
+
# exponential Poisson spacing law at beta=0 -- it gives a
|
|
143
|
+
# half-Gaussian instead. Concretely, both densities are normalized
|
|
144
|
+
# (integrate to 1) but disagree sharply at s=0: Poisson's exact
|
|
145
|
+
# density there is exp(0)=1, the beta=0 surmise's is 2/pi.
|
|
146
|
+
poisson_density_at_zero = 1.0
|
|
147
|
+
surmise_density_at_zero = rmt.stats.wigner_surmise_pdf(0.0, beta=0)
|
|
148
|
+
assert surmise_density_at_zero == pytest.approx(2.0 / np.pi)
|
|
149
|
+
assert abs(surmise_density_at_zero - poisson_density_at_zero) > 0.3
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""Tests for the polynomial/biorthogonal ensemble (products of Ginibre
|
|
2
|
+
matrices).
|
|
3
|
+
|
|
4
|
+
L=1 is checked directly against the already-validated
|
|
5
|
+
``MarchenkoPastur`` benchmark (reused, not reimplemented -- it's the
|
|
6
|
+
exact same theoretical object). L >= 2 has no simple closed-form
|
|
7
|
+
density implemented here, so it is validated against the one thing that
|
|
8
|
+
*is* known in exact closed form for general L: the Fuss-Catalan moments
|
|
9
|
+
(Penson-Zyczkowski 2011), themselves checked against Monte Carlo before
|
|
10
|
+
being trusted (see ``test_fuss_catalan_moment_matches_monte_carlo``).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import pytest
|
|
15
|
+
from cache_utils import cached_sample
|
|
16
|
+
|
|
17
|
+
import physicskit.rmt as rmt
|
|
18
|
+
from physicskit.rmt.ensembles.polynomial import fuss_catalan_moment
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_default_num_factors_is_two():
|
|
22
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=30, seed=0)
|
|
23
|
+
assert ens.num_factors == 2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_beta_1_real_factors_produce_finite_eigenvalues():
|
|
27
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=8, num_factors=2, beta=1, seed=7)
|
|
28
|
+
spectrum = ens.sample(n_samples=2)
|
|
29
|
+
assert np.all(np.isfinite(spectrum.eigenvalues))
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_eigenvalues_are_nonnegative():
|
|
33
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=50, num_factors=3, seed=1)
|
|
34
|
+
spectrum = cached_sample(ens, n_samples=5)
|
|
35
|
+
assert np.all(spectrum.eigenvalues >= -1e-8)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_reproducibility():
|
|
39
|
+
ens_a = rmt.ensembles.PolynomialEnsemble(n=40, num_factors=2, seed=42)
|
|
40
|
+
ens_b = rmt.ensembles.PolynomialEnsemble(n=40, num_factors=2, seed=42)
|
|
41
|
+
spec_a = ens_a.sample(n_samples=3)
|
|
42
|
+
spec_b = ens_b.sample(n_samples=3)
|
|
43
|
+
np.testing.assert_allclose(spec_a.eigenvalues, spec_b.eigenvalues)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_invalid_parameters_rejected():
|
|
47
|
+
with pytest.raises(ValueError):
|
|
48
|
+
rmt.ensembles.PolynomialEnsemble(n=10, num_factors=0, seed=0)
|
|
49
|
+
with pytest.raises(ValueError):
|
|
50
|
+
rmt.ensembles.PolynomialEnsemble(n=10, beta=4, seed=0)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("beta", [1, 2])
|
|
54
|
+
def test_num_factors_one_matches_marchenko_pastur_exactly(beta):
|
|
55
|
+
# L=1 is, EXACTLY (not approximately), the classical Wishart/LUE-LOE
|
|
56
|
+
# ensemble -- reusing the already-validated MarchenkoPastur
|
|
57
|
+
# benchmark directly, rather than a separate implementation, is
|
|
58
|
+
# itself part of the point (see module docstring).
|
|
59
|
+
n = 2000
|
|
60
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=1, beta=beta, seed=2)
|
|
61
|
+
spectrum = cached_sample(ens, n_samples=15)
|
|
62
|
+
benchmark = rmt.validation.MarchenkoPastur(gamma=1.0)
|
|
63
|
+
result = benchmark.validate(spectrum, seed=2)
|
|
64
|
+
assert result.ks_statistic < 0.02
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_fuss_catalan_moment_matches_monte_carlo():
|
|
68
|
+
# Verify the exact moment formula itself (not assumed from the
|
|
69
|
+
# literature citation) via a large, independent Monte Carlo
|
|
70
|
+
# generalized-Ginibre-product simulation, for L=1..4.
|
|
71
|
+
rng = np.random.default_rng(3)
|
|
72
|
+
for num_factors in [1, 2, 3, 4]:
|
|
73
|
+
n = 400
|
|
74
|
+
x = np.eye(n, dtype=complex)
|
|
75
|
+
for _ in range(num_factors):
|
|
76
|
+
factor = (rng.standard_normal((n, n)) + 1j * rng.standard_normal((n, n))) / np.sqrt(2.0)
|
|
77
|
+
x = x @ factor
|
|
78
|
+
eigs = np.linalg.eigvalsh(x.conj().T @ x) / n**num_factors
|
|
79
|
+
for k in [1, 2]:
|
|
80
|
+
empirical = np.mean(eigs**k)
|
|
81
|
+
theory = fuss_catalan_moment(k, num_factors)
|
|
82
|
+
assert empirical == pytest.approx(theory, rel=0.1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.parametrize("num_factors", [1, 2, 3, 4])
|
|
86
|
+
def test_ensemble_moments_match_fuss_catalan(num_factors):
|
|
87
|
+
n = 300
|
|
88
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=num_factors, beta=2, seed=4)
|
|
89
|
+
spectrum = cached_sample(ens, n_samples=10)
|
|
90
|
+
eigs = spectrum.rescaled.ravel()
|
|
91
|
+
for k in [1, 2]:
|
|
92
|
+
empirical = np.mean(eigs**k)
|
|
93
|
+
theory = fuss_catalan_moment(k, num_factors)
|
|
94
|
+
assert empirical == pytest.approx(theory, rel=0.1)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@pytest.mark.parametrize("num_factors", [1, 2, 3])
|
|
98
|
+
def test_spectrum_stays_near_fuss_catalan_support_edge(num_factors):
|
|
99
|
+
# Largest rescaled eigenvalue should approach, but not meaningfully
|
|
100
|
+
# exceed, the exact Fuss-Catalan support edge (L+1)**(L+1) / L**L.
|
|
101
|
+
n = 400
|
|
102
|
+
ens = rmt.ensembles.PolynomialEnsemble(n=n, num_factors=num_factors, beta=2, seed=5)
|
|
103
|
+
spectrum = cached_sample(ens, n_samples=5)
|
|
104
|
+
edge = (num_factors + 1) ** (num_factors + 1) / num_factors**num_factors
|
|
105
|
+
max_eig = spectrum.rescaled.max()
|
|
106
|
+
assert max_eig < edge * 1.1
|
|
107
|
+
assert max_eig > edge * 0.7
|