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,997 @@
|
|
|
1
|
+
r"""Quantum boxes and potential wells beyond the flat infinite box.
|
|
2
|
+
|
|
3
|
+
Covers asymmetric/step wells, the V-shaped 'quantum bouncer', the symmetric
|
|
4
|
+
double well (tunneling doublets), the finite square well (bound + continuum
|
|
5
|
+
states), and 2D quantum boxes (rectangle, circular dot, triangular/stadium
|
|
6
|
+
billiards -- the latter bridging to ``physicskit.chaos`` via quantum scars).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from scipy.special import airy, jn_zeros, jv
|
|
15
|
+
|
|
16
|
+
from .._compat import trapz
|
|
17
|
+
from ..core.eigensolvers import (
|
|
18
|
+
EigenResult,
|
|
19
|
+
NumerovSolver,
|
|
20
|
+
asymmetric_step_well,
|
|
21
|
+
double_well,
|
|
22
|
+
linear_gravitational_well,
|
|
23
|
+
)
|
|
24
|
+
from ..core.solvers import SplitOperatorSolver1D
|
|
25
|
+
from .wave_packets import free_gaussian_wavepacket
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"asymmetric_well_states",
|
|
29
|
+
"gravitational_bouncer_states",
|
|
30
|
+
"airy_bouncer_energies",
|
|
31
|
+
"airy_wavefunction",
|
|
32
|
+
"DoubleWellSimulator",
|
|
33
|
+
"DoubleWellResult",
|
|
34
|
+
"FiniteSquareWell",
|
|
35
|
+
"ScatteringResult",
|
|
36
|
+
"RectangularBox2D",
|
|
37
|
+
"Box2DEigenstate",
|
|
38
|
+
"CircularBox2D",
|
|
39
|
+
"CircularEigenstate",
|
|
40
|
+
"StadiumBilliard2D",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# --- Asymmetric / step wells ------------------------------------------------
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def asymmetric_well_states(
|
|
48
|
+
width: float = 2.0, V_left: float = 40.0, V_right: float = 15.0, x_extent: float = 6.0, n_points: int = 1200, n_states: int = 6
|
|
49
|
+
) -> EigenResult:
|
|
50
|
+
"""Bound states of a well with unequal wall heights.
|
|
51
|
+
|
|
52
|
+
The wavefunctions decay at different rates into the left vs. right
|
|
53
|
+
forbidden regions (asymmetric evanescent tails), unlike the symmetric
|
|
54
|
+
finite well.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
width : float, default=2.0
|
|
59
|
+
Full width of the zero-potential region.
|
|
60
|
+
V_left : float, default=40.0
|
|
61
|
+
Wall height on the left side.
|
|
62
|
+
V_right : float, default=15.0
|
|
63
|
+
Wall height on the right side.
|
|
64
|
+
x_extent : float, default=6.0
|
|
65
|
+
Half-width of the numerical domain, ``x in [-x_extent, x_extent]``.
|
|
66
|
+
n_points : int, default=1200
|
|
67
|
+
Number of grid points.
|
|
68
|
+
n_states : int, default=6
|
|
69
|
+
Number of bound states to compute.
|
|
70
|
+
|
|
71
|
+
Returns
|
|
72
|
+
-------
|
|
73
|
+
physicskit.quantum.core.eigensolvers.EigenResult
|
|
74
|
+
The energies and wavefunctions.
|
|
75
|
+
"""
|
|
76
|
+
x = np.linspace(-x_extent, x_extent, n_points)
|
|
77
|
+
V = asymmetric_step_well(width, V_left, V_right)
|
|
78
|
+
return NumerovSolver(x, V).solve(n_states)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# --- V-shaped (linear, gravitational) well ---------------------------------
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def gravitational_bouncer_states(alpha: float = 1.0, x_extent: float = 15.0, n_points: int = 2000, n_states: int = 8) -> EigenResult:
|
|
85
|
+
r"""The 'quantum bouncer': a particle falling under uniform gravity.
|
|
86
|
+
|
|
87
|
+
Solves for :math:`V(x) = \alpha x` (:math:`x>0`) with a hard floor at
|
|
88
|
+
:math:`x=0`. The grid's lower boundary supplies the floor (:math:`\psi`
|
|
89
|
+
is forced to vanish there), so the domain spans only
|
|
90
|
+
:math:`x \in [0, x_\text{extent}]`. Eigenstates are Airy functions
|
|
91
|
+
:math:`\mathrm{Ai}(z)`; the energies returned here should closely match
|
|
92
|
+
:func:`airy_bouncer_energies`.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
alpha : float, default=1.0
|
|
97
|
+
Slope of the linear potential (e.g. :math:`mg` for gravity).
|
|
98
|
+
x_extent : float, default=15.0
|
|
99
|
+
Extent of the numerical domain, ``x in [0, x_extent]``.
|
|
100
|
+
n_points : int, default=2000
|
|
101
|
+
Number of grid points.
|
|
102
|
+
n_states : int, default=8
|
|
103
|
+
Number of bound states to compute.
|
|
104
|
+
|
|
105
|
+
Returns
|
|
106
|
+
-------
|
|
107
|
+
physicskit.quantum.core.eigensolvers.EigenResult
|
|
108
|
+
The energies and wavefunctions.
|
|
109
|
+
"""
|
|
110
|
+
x = np.linspace(0.0, x_extent, n_points)
|
|
111
|
+
V = linear_gravitational_well(alpha)
|
|
112
|
+
return NumerovSolver(x, V).solve(n_states)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def airy_bouncer_energies(alpha: float = 1.0, n_states: int = 8, hbar: float = 1.0, m: float = 1.0) -> np.ndarray:
|
|
116
|
+
r"""Analytic bound-state energies for the half-line gravitational bouncer.
|
|
117
|
+
|
|
118
|
+
For :math:`V(x) = \alpha x` (:math:`x>0`, infinite floor at :math:`x=0`),
|
|
119
|
+
|
|
120
|
+
.. math::
|
|
121
|
+
|
|
122
|
+
E_n = -\alpha \left(\frac{\hbar^2}{2 m \alpha^2}\right)^{1/3} a_n,
|
|
123
|
+
|
|
124
|
+
where :math:`a_n < 0` are the zeros of the Airy function,
|
|
125
|
+
:math:`\mathrm{Ai}(a_n) = 0`.
|
|
126
|
+
|
|
127
|
+
Parameters
|
|
128
|
+
----------
|
|
129
|
+
alpha : float, default=1.0
|
|
130
|
+
Slope of the linear potential.
|
|
131
|
+
n_states : int, default=8
|
|
132
|
+
Number of energy levels to return.
|
|
133
|
+
hbar : float, default=1.0
|
|
134
|
+
Value of :math:`\hbar` to use.
|
|
135
|
+
m : float, default=1.0
|
|
136
|
+
Particle mass.
|
|
137
|
+
|
|
138
|
+
Returns
|
|
139
|
+
-------
|
|
140
|
+
numpy.ndarray
|
|
141
|
+
The lowest ``n_states`` analytic energies, ascending.
|
|
142
|
+
"""
|
|
143
|
+
from scipy.special import ai_zeros
|
|
144
|
+
|
|
145
|
+
a_zeros, _, _, _ = ai_zeros(n_states)
|
|
146
|
+
scale = (hbar**2 * alpha**2 / (2 * m)) ** (1 / 3)
|
|
147
|
+
return -a_zeros * scale
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def airy_wavefunction(x: np.ndarray, alpha: float = 1.0, n: int = 0, hbar: float = 1.0, m: float = 1.0) -> np.ndarray:
|
|
151
|
+
r"""Normalized Airy-function eigenstate for the half-line bouncer.
|
|
152
|
+
|
|
153
|
+
Parameters
|
|
154
|
+
----------
|
|
155
|
+
x : numpy.ndarray
|
|
156
|
+
Grid to evaluate the wavefunction on (values at :math:`x<0` are
|
|
157
|
+
forced to zero).
|
|
158
|
+
alpha : float, default=1.0
|
|
159
|
+
Slope of the linear potential :math:`V(x) = \alpha x`.
|
|
160
|
+
n : int, default=0
|
|
161
|
+
Quantum number (0 = ground state).
|
|
162
|
+
hbar : float, default=1.0
|
|
163
|
+
Value of :math:`\hbar` to use.
|
|
164
|
+
m : float, default=1.0
|
|
165
|
+
Particle mass.
|
|
166
|
+
|
|
167
|
+
Returns
|
|
168
|
+
-------
|
|
169
|
+
numpy.ndarray
|
|
170
|
+
The normalized wavefunction :math:`\psi_n(x)`.
|
|
171
|
+
"""
|
|
172
|
+
from scipy.special import ai_zeros
|
|
173
|
+
|
|
174
|
+
a_zeros, _, _, _ = ai_zeros(n + 1)
|
|
175
|
+
a_n = a_zeros[n]
|
|
176
|
+
length = (hbar**2 / (2 * m * alpha)) ** (1 / 3)
|
|
177
|
+
# z(x) = x/length + a_n, so that z(0) = a_n (a zero of Ai), satisfying
|
|
178
|
+
# the psi(0)=0 hard-floor boundary condition; x/length - a_n would put
|
|
179
|
+
# z(0) = -a_n > 0, the wrong (decaying) branch, and psi(0) != 0.
|
|
180
|
+
Ai, _, _, _ = airy(x / length + a_n)
|
|
181
|
+
psi = np.where(x >= 0, Ai, 0.0)
|
|
182
|
+
norm = np.sqrt(trapz(psi**2, x))
|
|
183
|
+
return psi / norm
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# --- Double well: tunneling doublets ---------------------------------------
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@dataclass
|
|
190
|
+
class DoubleWellResult:
|
|
191
|
+
"""Container for the output of :meth:`DoubleWellSimulator.solve`."""
|
|
192
|
+
|
|
193
|
+
x: np.ndarray
|
|
194
|
+
"""numpy.ndarray: The spatial grid."""
|
|
195
|
+
|
|
196
|
+
energies: np.ndarray
|
|
197
|
+
r"""numpy.ndarray: Eigenenergies :math:`E_n`, ascending."""
|
|
198
|
+
|
|
199
|
+
wavefunctions: np.ndarray
|
|
200
|
+
"""numpy.ndarray: Normalized eigenfunctions, shape ``(n_states, len(x))``."""
|
|
201
|
+
|
|
202
|
+
splitting: float
|
|
203
|
+
r"""float: The symmetric/antisymmetric doublet gap :math:`\Delta E = E_1 - E_0`."""
|
|
204
|
+
|
|
205
|
+
tunneling_period: float
|
|
206
|
+
r"""float: The tunneling oscillation period :math:`T = 2\pi\hbar/\Delta E`."""
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class DoubleWellSimulator:
|
|
210
|
+
r"""Symmetric double well :math:`V(x) = \lambda (x^2 - a^2)^2`.
|
|
211
|
+
|
|
212
|
+
The two lowest eigenstates form a nearly degenerate symmetric/
|
|
213
|
+
antisymmetric doublet whose splitting :math:`\Delta E = E_1 - E_0`
|
|
214
|
+
sets the period :math:`T=2\pi\hbar/\Delta E` of left-right tunneling
|
|
215
|
+
oscillations of a wavepacket initially localized in one well.
|
|
216
|
+
|
|
217
|
+
Parameters
|
|
218
|
+
----------
|
|
219
|
+
lam : float, default=0.5
|
|
220
|
+
Overall strength :math:`\lambda` of the quartic potential.
|
|
221
|
+
a : float, default=2.5
|
|
222
|
+
Half-separation of the two minima.
|
|
223
|
+
x_extent : float, default=6.0
|
|
224
|
+
Half-width of the numerical domain, ``x in [-x_extent, x_extent]``.
|
|
225
|
+
n_points : int, default=1600
|
|
226
|
+
Number of grid points.
|
|
227
|
+
hbar : float, default=1.0
|
|
228
|
+
Value of :math:`\hbar` to use.
|
|
229
|
+
m : float, default=1.0
|
|
230
|
+
Particle mass.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
def __init__(self, lam: float = 0.5, a: float = 2.5, x_extent: float = 6.0, n_points: int = 1600, hbar: float = 1.0, m: float = 1.0):
|
|
234
|
+
self.lam = lam
|
|
235
|
+
self.a = a
|
|
236
|
+
self.hbar = hbar
|
|
237
|
+
self.m = m
|
|
238
|
+
self.x = np.linspace(-x_extent, x_extent, n_points)
|
|
239
|
+
self.V = double_well(lam, a)
|
|
240
|
+
self._solver = NumerovSolver(self.x, self.V, hbar=hbar, m=m)
|
|
241
|
+
|
|
242
|
+
def solve(self, n_states: int = 6) -> DoubleWellResult:
|
|
243
|
+
"""Compute the eigenstates and the tunneling splitting/period.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
n_states : int, default=6
|
|
248
|
+
Number of bound states to compute (at least 2, for the doublet).
|
|
249
|
+
|
|
250
|
+
Returns
|
|
251
|
+
-------
|
|
252
|
+
DoubleWellResult
|
|
253
|
+
"""
|
|
254
|
+
res = self._solver.solve(n_states)
|
|
255
|
+
splitting = res.energies[1] - res.energies[0]
|
|
256
|
+
period = 2 * np.pi * self.hbar / splitting
|
|
257
|
+
return DoubleWellResult(res.x, res.energies, res.wavefunctions, splitting, period)
|
|
258
|
+
|
|
259
|
+
def localized_state(self, result: DoubleWellResult, side: str = "left") -> np.ndarray:
|
|
260
|
+
r"""Build a state localized in one well from the doublet.
|
|
261
|
+
|
|
262
|
+
.. math::
|
|
263
|
+
|
|
264
|
+
\psi(x) = \frac{\psi_0(x) \pm \psi_1(x)}{\sqrt 2},
|
|
265
|
+
|
|
266
|
+
with ``+`` for the left well and ``-`` for the right well.
|
|
267
|
+
|
|
268
|
+
Parameters
|
|
269
|
+
----------
|
|
270
|
+
result : DoubleWellResult
|
|
271
|
+
Output of :meth:`solve` (must include at least 2 states).
|
|
272
|
+
side : {'left', 'right'}, default='left'
|
|
273
|
+
Which well to localize the state in.
|
|
274
|
+
|
|
275
|
+
Returns
|
|
276
|
+
-------
|
|
277
|
+
numpy.ndarray
|
|
278
|
+
The localized wavefunction at :math:`t=0`.
|
|
279
|
+
"""
|
|
280
|
+
psi0, psi1 = result.wavefunctions[0], result.wavefunctions[1]
|
|
281
|
+
sign = 1.0 if side == "left" else -1.0
|
|
282
|
+
psi = (psi0 + sign * psi1) / np.sqrt(2)
|
|
283
|
+
return psi
|
|
284
|
+
|
|
285
|
+
def tunneling_wavefunction(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
|
|
286
|
+
r"""Time-dependent wavefunction of a state localized on ``side``.
|
|
287
|
+
|
|
288
|
+
Evolves :math:`\psi(x,0) = (\psi_0 \pm \psi_1)/\sqrt2` under the two
|
|
289
|
+
doublet energies; :math:`\lvert\psi(x,t)\rvert^2`
|
|
290
|
+
(:meth:`tunneling_oscillation`) periodically tunnels to the other
|
|
291
|
+
well with period :math:`T=2\pi\hbar/\Delta E`. Shaped for
|
|
292
|
+
:func:`~physicskit.quantum.visualizers.wavefunctions.animate_density`.
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
result : DoubleWellResult
|
|
297
|
+
Output of :meth:`solve`.
|
|
298
|
+
t : numpy.ndarray
|
|
299
|
+
Times to evaluate at.
|
|
300
|
+
side : {'left', 'right'}, default='left'
|
|
301
|
+
Which well the state starts localized in.
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
numpy.ndarray
|
|
306
|
+
Complex-valued :math:`\psi(x,t)`, shape ``(len(t), len(x))``.
|
|
307
|
+
"""
|
|
308
|
+
psi0, psi1 = result.wavefunctions[0], result.wavefunctions[1]
|
|
309
|
+
E0, E1 = result.energies[0], result.energies[1]
|
|
310
|
+
sign = 1.0 if side == "left" else -1.0
|
|
311
|
+
t = np.asarray(t)
|
|
312
|
+
phase0 = np.exp(-1j * E0 * t / self.hbar)[:, None]
|
|
313
|
+
phase1 = np.exp(-1j * E1 * t / self.hbar)[:, None]
|
|
314
|
+
return (psi0[None, :] * phase0 + sign * psi1[None, :] * phase1) / np.sqrt(2)
|
|
315
|
+
|
|
316
|
+
def tunneling_oscillation(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
|
|
317
|
+
r"""Time-dependent probability density of a state localized on ``side``.
|
|
318
|
+
|
|
319
|
+
See :meth:`tunneling_wavefunction`.
|
|
320
|
+
|
|
321
|
+
Parameters
|
|
322
|
+
----------
|
|
323
|
+
result : DoubleWellResult
|
|
324
|
+
Output of :meth:`solve`.
|
|
325
|
+
t : numpy.ndarray
|
|
326
|
+
Times to evaluate at.
|
|
327
|
+
side : {'left', 'right'}, default='left'
|
|
328
|
+
Which well the state starts localized in.
|
|
329
|
+
|
|
330
|
+
Returns
|
|
331
|
+
-------
|
|
332
|
+
numpy.ndarray
|
|
333
|
+
:math:`\lvert\psi(x,t)\rvert^2`, shape ``(len(t), len(x))``.
|
|
334
|
+
"""
|
|
335
|
+
return np.abs(self.tunneling_wavefunction(result, t, side=side)) ** 2
|
|
336
|
+
|
|
337
|
+
def left_well_probability(self, result: DoubleWellResult, t: np.ndarray, side: str = "left") -> np.ndarray:
|
|
338
|
+
r"""Probability of finding the particle in the left well (:math:`x<0`) vs. time.
|
|
339
|
+
|
|
340
|
+
.. math::
|
|
341
|
+
|
|
342
|
+
P(t) = \int_{-\infty}^0 \lvert\psi(x,t)\rvert^2\, dx,
|
|
343
|
+
|
|
344
|
+
oscillating with the tunneling period :math:`T=2\pi\hbar/\Delta E`.
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
result : DoubleWellResult
|
|
349
|
+
Output of :meth:`solve`.
|
|
350
|
+
t : numpy.ndarray
|
|
351
|
+
Times to evaluate at.
|
|
352
|
+
side : {'left', 'right'}, default='left'
|
|
353
|
+
Which well the state starts localized in.
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
numpy.ndarray
|
|
358
|
+
:math:`P(t)`, shape ``(len(t),)``.
|
|
359
|
+
"""
|
|
360
|
+
density_t = self.tunneling_oscillation(result, t, side=side)
|
|
361
|
+
mask = self.x < 0
|
|
362
|
+
return trapz(density_t[:, mask], self.x[mask], axis=1)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
# --- Finite square well: bound states + continuum scattering ---------------
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@dataclass
|
|
369
|
+
class ScatteringResult:
|
|
370
|
+
"""Container for the output of :meth:`FiniteSquareWell.scattering`."""
|
|
371
|
+
|
|
372
|
+
E: float
|
|
373
|
+
"""float: Incident energy."""
|
|
374
|
+
|
|
375
|
+
k1: float
|
|
376
|
+
"""float: Wavenumber outside the well."""
|
|
377
|
+
|
|
378
|
+
k2: complex
|
|
379
|
+
"""complex: Wavenumber inside the well/barrier. Purely imaginary in a
|
|
380
|
+
classically forbidden interior (sub-barrier tunneling), real otherwise."""
|
|
381
|
+
|
|
382
|
+
T: float
|
|
383
|
+
"""float: Transmission probability."""
|
|
384
|
+
|
|
385
|
+
R: float
|
|
386
|
+
r"""float: Reflection probability (:math:`R = 1 - T`)."""
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class FiniteSquareWell:
|
|
390
|
+
r"""A finite square well of depth :math:`V_0` and width :math:`a`,
|
|
391
|
+
|
|
392
|
+
.. math::
|
|
393
|
+
|
|
394
|
+
V(x) = \begin{cases} -V_0 & \lvert x\rvert < a/2 \\ 0 & \text{otherwise} \end{cases}.
|
|
395
|
+
|
|
396
|
+
Provides bound states (:math:`E<0`) via the Numerov solver and exact
|
|
397
|
+
analytic transmission/reflection coefficients for scattering states
|
|
398
|
+
(:math:`E>0`).
|
|
399
|
+
|
|
400
|
+
Parameters
|
|
401
|
+
----------
|
|
402
|
+
V0 : float, default=20.0
|
|
403
|
+
Well depth (:math:`V_0 > 0`; the floor sits at :math:`-V_0`).
|
|
404
|
+
width : float, default=2.0
|
|
405
|
+
Full width :math:`a` of the well.
|
|
406
|
+
hbar : float, default=1.0
|
|
407
|
+
Value of :math:`\hbar` to use.
|
|
408
|
+
m : float, default=1.0
|
|
409
|
+
Particle mass.
|
|
410
|
+
"""
|
|
411
|
+
|
|
412
|
+
def __init__(self, V0: float = 20.0, width: float = 2.0, hbar: float = 1.0, m: float = 1.0):
|
|
413
|
+
self.V0 = V0
|
|
414
|
+
self.width = width
|
|
415
|
+
self.hbar = hbar
|
|
416
|
+
self.m = m
|
|
417
|
+
|
|
418
|
+
def potential(self, x: np.ndarray) -> np.ndarray:
|
|
419
|
+
r"""Evaluate :math:`V(x)` (a barrier if ``V0 < 0``).
|
|
420
|
+
|
|
421
|
+
Parameters
|
|
422
|
+
----------
|
|
423
|
+
x : numpy.ndarray
|
|
424
|
+
|
|
425
|
+
Returns
|
|
426
|
+
-------
|
|
427
|
+
numpy.ndarray
|
|
428
|
+
"""
|
|
429
|
+
return np.where(np.abs(x) <= self.width / 2, -self.V0, 0.0)
|
|
430
|
+
|
|
431
|
+
def bound_states(self, x_extent: float = 6.0, n_points: int = 1600, n_states: int = 6) -> EigenResult:
|
|
432
|
+
"""Compute the bound states (:math:`E<0`).
|
|
433
|
+
|
|
434
|
+
Parameters
|
|
435
|
+
----------
|
|
436
|
+
x_extent : float, default=6.0
|
|
437
|
+
Half-width of the numerical domain.
|
|
438
|
+
n_points : int, default=1600
|
|
439
|
+
Number of grid points.
|
|
440
|
+
n_states : int, default=6
|
|
441
|
+
Number of Numerov eigenstates to request (some may be discarded
|
|
442
|
+
if they lie above the well rim; see Notes).
|
|
443
|
+
|
|
444
|
+
Returns
|
|
445
|
+
-------
|
|
446
|
+
physicskit.quantum.core.eigensolvers.EigenResult
|
|
447
|
+
Only the states with :math:`E<0` are kept -- the solver also
|
|
448
|
+
returns box-quantized "continuum" states above the well rim,
|
|
449
|
+
which are artifacts of the finite numerical domain and are
|
|
450
|
+
filtered out here.
|
|
451
|
+
"""
|
|
452
|
+
x = np.linspace(-x_extent, x_extent, n_points)
|
|
453
|
+
res = NumerovSolver(x, self.potential, hbar=self.hbar, m=self.m).solve(n_states)
|
|
454
|
+
bound_mask = res.energies < 0
|
|
455
|
+
return EigenResult(res.x, res.energies[bound_mask], res.wavefunctions[bound_mask])
|
|
456
|
+
|
|
457
|
+
def scattering(self, E: float) -> ScatteringResult:
|
|
458
|
+
r"""Exact 1D transmission/reflection coefficients for :math:`E>0`.
|
|
459
|
+
|
|
460
|
+
.. math::
|
|
461
|
+
|
|
462
|
+
T(E) = \left[1 + \frac{V_0^2 \sin^2(k_2 a)}{4 E (E+V_0)}\right]^{-1},
|
|
463
|
+
\qquad R = 1 - T,
|
|
464
|
+
|
|
465
|
+
with :math:`k_1=\sqrt{2mE}/\hbar` outside the well and
|
|
466
|
+
:math:`k_2=\sqrt{2m(E+V_0)}/\hbar` inside it. :math:`T(E)=1`
|
|
467
|
+
exactly whenever :math:`k_2 a` is a multiple of :math:`\pi`
|
|
468
|
+
(the Ramsauer-Townsend resonances).
|
|
469
|
+
|
|
470
|
+
The same formula analytically continues correctly into a
|
|
471
|
+
classically forbidden interior (:math:`E+V_0<0`, e.g. a *barrier*
|
|
472
|
+
built by passing a negative ``V0``): :math:`k_2` becomes purely
|
|
473
|
+
imaginary, :math:`\sin` becomes :math:`i\sinh` of a real argument,
|
|
474
|
+
and :math:`T` reduces to the standard sub-barrier tunneling
|
|
475
|
+
formula. This method evaluates :math:`k_2` with complex arithmetic
|
|
476
|
+
so that regime is handled automatically.
|
|
477
|
+
|
|
478
|
+
Parameters
|
|
479
|
+
----------
|
|
480
|
+
E : float
|
|
481
|
+
Incident kinetic energy (:math:`E>0`).
|
|
482
|
+
|
|
483
|
+
Returns
|
|
484
|
+
-------
|
|
485
|
+
ScatteringResult
|
|
486
|
+
"""
|
|
487
|
+
hbar, m, V0, a = self.hbar, self.m, self.V0, self.width
|
|
488
|
+
k1 = np.sqrt(2 * m * E) / hbar
|
|
489
|
+
k2 = np.sqrt(2 * m * (E + V0) + 0j) / hbar
|
|
490
|
+
T = 1.0 / (1.0 + (V0**2 * np.sin(k2 * a) ** 2) / (4 * E * (E + V0)))
|
|
491
|
+
T = float(np.real_if_close(T))
|
|
492
|
+
R = 1.0 - T
|
|
493
|
+
return ScatteringResult(E=E, k1=k1, k2=k2, T=T, R=R)
|
|
494
|
+
|
|
495
|
+
def transmission_spectrum(self, E_values: np.ndarray) -> np.ndarray:
|
|
496
|
+
"""Transmission probability :math:`T(E)` over an array of energies.
|
|
497
|
+
|
|
498
|
+
Parameters
|
|
499
|
+
----------
|
|
500
|
+
E_values : numpy.ndarray
|
|
501
|
+
Incident energies (:math:`E>0`).
|
|
502
|
+
|
|
503
|
+
Returns
|
|
504
|
+
-------
|
|
505
|
+
numpy.ndarray
|
|
506
|
+
:math:`T(E)` for each value in ``E_values``.
|
|
507
|
+
"""
|
|
508
|
+
return np.array([self.scattering(E).T for E in E_values])
|
|
509
|
+
|
|
510
|
+
def evanescent_wavefunction(self, result: EigenResult, n: int = 0) -> np.ndarray:
|
|
511
|
+
r"""The :math:`n`-th bound-state wavefunction.
|
|
512
|
+
|
|
513
|
+
Its exponential tails outside :math:`\lvert x\rvert < a/2` are
|
|
514
|
+
evanescent by construction of the Numerov solver.
|
|
515
|
+
|
|
516
|
+
Parameters
|
|
517
|
+
----------
|
|
518
|
+
result : physicskit.quantum.core.eigensolvers.EigenResult
|
|
519
|
+
Output of :meth:`bound_states`.
|
|
520
|
+
n : int, default=0
|
|
521
|
+
Which bound state to return (0 = ground state).
|
|
522
|
+
|
|
523
|
+
Returns
|
|
524
|
+
-------
|
|
525
|
+
numpy.ndarray
|
|
526
|
+
"""
|
|
527
|
+
return result.wavefunctions[n]
|
|
528
|
+
|
|
529
|
+
def wavepacket_scattering(
|
|
530
|
+
self,
|
|
531
|
+
x_extent: float = 25.0,
|
|
532
|
+
n_points: int = 2048,
|
|
533
|
+
x0: float = -10.0,
|
|
534
|
+
sigma0: float = 1.5,
|
|
535
|
+
k0: float | None = None,
|
|
536
|
+
dt: float = 2e-4,
|
|
537
|
+
n_steps: int = 6000,
|
|
538
|
+
save_every: int = 30,
|
|
539
|
+
):
|
|
540
|
+
r"""Propagate a Gaussian wavepacket incident on this well/barrier.
|
|
541
|
+
|
|
542
|
+
A single FFT split-operator run
|
|
543
|
+
(:class:`~physicskit.quantum.core.solvers.SplitOperatorSolver1D`) shared
|
|
544
|
+
by the tunnelling (``V0 < 0``, a barrier) and the resonant-scattering
|
|
545
|
+
(``V0 > 0``, a well) demos: the packet splits into a reflected and a
|
|
546
|
+
transmitted piece as it crosses :math:`\lvert x\rvert < \text{width}/2`.
|
|
547
|
+
|
|
548
|
+
Parameters
|
|
549
|
+
----------
|
|
550
|
+
x_extent : float, default=25.0
|
|
551
|
+
Half-width of the numerical domain.
|
|
552
|
+
n_points : int, default=2048
|
|
553
|
+
Number of grid points.
|
|
554
|
+
x0 : float, default=-10.0
|
|
555
|
+
Initial center of the packet (upstream of the well/barrier).
|
|
556
|
+
sigma0 : float, default=1.5
|
|
557
|
+
Initial packet width.
|
|
558
|
+
k0 : float or None, optional
|
|
559
|
+
Central wavenumber; defaults to a value giving mean kinetic
|
|
560
|
+
energy comparable to :math:`\lvert V_0\rvert`, so both partial
|
|
561
|
+
reflection and partial transmission occur.
|
|
562
|
+
dt : float, default=2e-4
|
|
563
|
+
Time step.
|
|
564
|
+
n_steps : int, default=6000
|
|
565
|
+
Total number of time steps.
|
|
566
|
+
save_every : int, default=30
|
|
567
|
+
Save a snapshot every this many steps.
|
|
568
|
+
|
|
569
|
+
Returns
|
|
570
|
+
-------
|
|
571
|
+
x : numpy.ndarray
|
|
572
|
+
The spatial grid.
|
|
573
|
+
frames : numpy.ndarray
|
|
574
|
+
Complex wavefunction snapshots, shape ``(n_saved, len(x))``.
|
|
575
|
+
times : numpy.ndarray
|
|
576
|
+
The time of each snapshot.
|
|
577
|
+
"""
|
|
578
|
+
if k0 is None:
|
|
579
|
+
k0 = np.sqrt(2 * self.m * max(abs(self.V0), 1.0)) / self.hbar
|
|
580
|
+
|
|
581
|
+
x = np.linspace(-x_extent, x_extent, n_points)
|
|
582
|
+
psi0 = free_gaussian_wavepacket(x, x0, sigma0, k0)
|
|
583
|
+
solver = SplitOperatorSolver1D(x, self.potential, hbar=self.hbar, m=self.m, dt=dt)
|
|
584
|
+
frames, times = solver.propagate(psi0, n_steps, save_every=save_every)
|
|
585
|
+
return x, frames, times
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
# --- 2D rectangular box ------------------------------------------------------
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
@dataclass
|
|
592
|
+
class Box2DEigenstate:
|
|
593
|
+
"""One eigenstate of :class:`RectangularBox2D`."""
|
|
594
|
+
|
|
595
|
+
nx: int
|
|
596
|
+
r"""int: Quantum number along :math:`x` (:math:`\ge 1`)."""
|
|
597
|
+
|
|
598
|
+
ny: int
|
|
599
|
+
r"""int: Quantum number along :math:`y` (:math:`\ge 1`)."""
|
|
600
|
+
|
|
601
|
+
energy: float
|
|
602
|
+
r"""float: The eigenenergy :math:`E_{n_x,n_y}`."""
|
|
603
|
+
|
|
604
|
+
def psi(self, X: np.ndarray, Y: np.ndarray, Lx: float, Ly: float) -> np.ndarray:
|
|
605
|
+
r"""Evaluate the eigenfunction on a grid.
|
|
606
|
+
|
|
607
|
+
.. math::
|
|
608
|
+
|
|
609
|
+
\psi_{n_x,n_y}(x,y) = \sqrt{\frac{4}{L_x L_y}}
|
|
610
|
+
\sin\!\left(\frac{n_x \pi x}{L_x}\right)
|
|
611
|
+
\sin\!\left(\frac{n_y \pi y}{L_y}\right).
|
|
612
|
+
|
|
613
|
+
Parameters
|
|
614
|
+
----------
|
|
615
|
+
X, Y : numpy.ndarray
|
|
616
|
+
Coordinate meshgrid (e.g. from :meth:`RectangularBox2D.grid`).
|
|
617
|
+
Lx, Ly : float
|
|
618
|
+
Box dimensions.
|
|
619
|
+
|
|
620
|
+
Returns
|
|
621
|
+
-------
|
|
622
|
+
numpy.ndarray
|
|
623
|
+
:math:`\psi_{n_x,n_y}(x,y)` on the grid.
|
|
624
|
+
"""
|
|
625
|
+
return np.sqrt(4 / (Lx * Ly)) * np.sin(self.nx * np.pi * X / Lx) * np.sin(self.ny * np.pi * Y / Ly)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class RectangularBox2D:
|
|
629
|
+
r"""Infinite 2D box of size :math:`L_x \times L_y`.
|
|
630
|
+
|
|
631
|
+
.. math::
|
|
632
|
+
|
|
633
|
+
E_{n_x,n_y} = \frac{\pi^2\hbar^2}{2m}
|
|
634
|
+
\left(\frac{n_x^2}{L_x^2} + \frac{n_y^2}{L_y^2}\right).
|
|
635
|
+
|
|
636
|
+
Rational ratios :math:`L_x/L_y` (e.g. the square box :math:`1{:}1`)
|
|
637
|
+
produce accidental degeneracies between distinct :math:`(n_x,n_y)` pairs.
|
|
638
|
+
|
|
639
|
+
Parameters
|
|
640
|
+
----------
|
|
641
|
+
Lx, Ly : float, default=1.0
|
|
642
|
+
Box dimensions.
|
|
643
|
+
hbar : float, default=1.0
|
|
644
|
+
Value of :math:`\hbar` to use.
|
|
645
|
+
m : float, default=1.0
|
|
646
|
+
Particle mass.
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
def __init__(self, Lx: float = 1.0, Ly: float = 1.0, hbar: float = 1.0, m: float = 1.0):
|
|
650
|
+
self.Lx, self.Ly, self.hbar, self.m = Lx, Ly, hbar, m
|
|
651
|
+
|
|
652
|
+
def energy(self, nx: int, ny: int) -> float:
|
|
653
|
+
r"""Eigenenergy :math:`E_{n_x,n_y}`.
|
|
654
|
+
|
|
655
|
+
Parameters
|
|
656
|
+
----------
|
|
657
|
+
nx, ny : int
|
|
658
|
+
Quantum numbers (each :math:`\ge 1`).
|
|
659
|
+
|
|
660
|
+
Returns
|
|
661
|
+
-------
|
|
662
|
+
float
|
|
663
|
+
"""
|
|
664
|
+
return (np.pi**2 * self.hbar**2 / (2 * self.m)) * ((nx / self.Lx) ** 2 + (ny / self.Ly) ** 2)
|
|
665
|
+
|
|
666
|
+
def spectrum(self, n_max: int = 6) -> list:
|
|
667
|
+
"""All eigenstates with :math:`1 \\le n_x, n_y \\le n_\\text{max}`, sorted by energy.
|
|
668
|
+
|
|
669
|
+
Parameters
|
|
670
|
+
----------
|
|
671
|
+
n_max : int, default=6
|
|
672
|
+
Maximum quantum number along each axis.
|
|
673
|
+
|
|
674
|
+
Returns
|
|
675
|
+
-------
|
|
676
|
+
list of Box2DEigenstate
|
|
677
|
+
"""
|
|
678
|
+
states = [Box2DEigenstate(nx, ny, self.energy(nx, ny)) for nx in range(1, n_max + 1) for ny in range(1, n_max + 1)]
|
|
679
|
+
return sorted(states, key=lambda s: s.energy)
|
|
680
|
+
|
|
681
|
+
def degeneracies(self, n_max: int = 6, tol: float = 1e-9) -> dict:
|
|
682
|
+
"""Group states by (rounded) energy to reveal degenerate levels.
|
|
683
|
+
|
|
684
|
+
Parameters
|
|
685
|
+
----------
|
|
686
|
+
n_max : int, default=6
|
|
687
|
+
Maximum quantum number along each axis.
|
|
688
|
+
tol : float, default=1e-9
|
|
689
|
+
Energy values within ``tol`` are treated as degenerate.
|
|
690
|
+
|
|
691
|
+
Returns
|
|
692
|
+
-------
|
|
693
|
+
dict
|
|
694
|
+
Maps each degenerate energy to its list of ``(nx, ny)`` pairs;
|
|
695
|
+
non-degenerate levels are omitted.
|
|
696
|
+
"""
|
|
697
|
+
groups: dict = {}
|
|
698
|
+
for s in self.spectrum(n_max):
|
|
699
|
+
key = round(s.energy / tol) * tol
|
|
700
|
+
groups.setdefault(key, []).append((s.nx, s.ny))
|
|
701
|
+
return {E: pairs for E, pairs in groups.items() if len(pairs) > 1}
|
|
702
|
+
|
|
703
|
+
def grid(self, n_points: int = 200):
|
|
704
|
+
"""A Cartesian meshgrid spanning the box.
|
|
705
|
+
|
|
706
|
+
Parameters
|
|
707
|
+
----------
|
|
708
|
+
n_points : int, default=200
|
|
709
|
+
Number of points along each axis.
|
|
710
|
+
|
|
711
|
+
Returns
|
|
712
|
+
-------
|
|
713
|
+
X, Y : numpy.ndarray
|
|
714
|
+
Meshgrid arrays (``indexing='ij'``).
|
|
715
|
+
"""
|
|
716
|
+
x = np.linspace(0, self.Lx, n_points)
|
|
717
|
+
y = np.linspace(0, self.Ly, n_points)
|
|
718
|
+
return np.meshgrid(x, y, indexing="ij")
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
# --- 2D circular quantum dot -------------------------------------------------
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@dataclass
|
|
725
|
+
class CircularEigenstate:
|
|
726
|
+
"""One eigenstate of :class:`CircularBox2D`."""
|
|
727
|
+
|
|
728
|
+
m: int
|
|
729
|
+
"""int: Angular momentum quantum number."""
|
|
730
|
+
|
|
731
|
+
n: int
|
|
732
|
+
r"""int: Radial quantum number (the state uses the :math:`n`-th positive
|
|
733
|
+
zero of :math:`J_{\lvert m\rvert}`)."""
|
|
734
|
+
|
|
735
|
+
k: float
|
|
736
|
+
r"""float: Wavenumber :math:`k_{mn} = z_{mn}/R`."""
|
|
737
|
+
|
|
738
|
+
energy: float
|
|
739
|
+
r"""float: The eigenenergy :math:`E_{mn} = \hbar^2 k_{mn}^2 / 2m`."""
|
|
740
|
+
|
|
741
|
+
def psi(self, r: np.ndarray, phi: np.ndarray, R: float) -> np.ndarray:
|
|
742
|
+
r"""Evaluate the eigenfunction on a polar grid.
|
|
743
|
+
|
|
744
|
+
.. math::
|
|
745
|
+
|
|
746
|
+
\psi_{mn}(r,\phi) = N_{mn}\, J_m(k_{mn} r)\, e^{i m \phi}.
|
|
747
|
+
|
|
748
|
+
Parameters
|
|
749
|
+
----------
|
|
750
|
+
r, phi : numpy.ndarray
|
|
751
|
+
Polar coordinate meshgrid (e.g. from :meth:`CircularBox2D.polar_grid`).
|
|
752
|
+
R : float
|
|
753
|
+
Dot radius (used to normalize :math:`N_{mn}`).
|
|
754
|
+
|
|
755
|
+
Returns
|
|
756
|
+
-------
|
|
757
|
+
numpy.ndarray
|
|
758
|
+
Complex-valued :math:`\psi_{mn}(r,\phi)`.
|
|
759
|
+
"""
|
|
760
|
+
radial = jv(self.m, self.k * r)
|
|
761
|
+
norm = 1.0 / np.sqrt(np.pi * R**2 * jv(self.m + 1, self.k * R) ** 2)
|
|
762
|
+
return norm * radial * np.exp(1j * self.m * phi)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
class CircularBox2D:
|
|
766
|
+
r"""Circular infinite well (quantum dot) of radius :math:`R`.
|
|
767
|
+
|
|
768
|
+
Eigenstates are Bessel functions
|
|
769
|
+
:math:`\psi_{mn}(r,\phi) \propto J_m(k_{mn} r)\, e^{im\phi}`, vanishing
|
|
770
|
+
at :math:`r=R`, showing nodal rings (radial quantum number :math:`n`)
|
|
771
|
+
and angular momentum quantization (:math:`m`).
|
|
772
|
+
|
|
773
|
+
Parameters
|
|
774
|
+
----------
|
|
775
|
+
R : float, default=1.0
|
|
776
|
+
Dot radius.
|
|
777
|
+
hbar : float, default=1.0
|
|
778
|
+
Value of :math:`\hbar` to use.
|
|
779
|
+
m_mass : float, default=1.0
|
|
780
|
+
Particle mass (named to avoid clashing with the angular momentum
|
|
781
|
+
quantum number ``m``).
|
|
782
|
+
"""
|
|
783
|
+
|
|
784
|
+
def __init__(self, R: float = 1.0, hbar: float = 1.0, m_mass: float = 1.0):
|
|
785
|
+
self.R = R
|
|
786
|
+
self.hbar = hbar
|
|
787
|
+
self.m_mass = m_mass
|
|
788
|
+
|
|
789
|
+
def eigenstate(self, m: int, n: int) -> CircularEigenstate:
|
|
790
|
+
r"""Build the :math:`(m,n)` eigenstate.
|
|
791
|
+
|
|
792
|
+
Parameters
|
|
793
|
+
----------
|
|
794
|
+
m : int
|
|
795
|
+
Angular momentum quantum number.
|
|
796
|
+
n : int
|
|
797
|
+
Radial index (:math:`n=1,2,3,\dots`); the state uses the
|
|
798
|
+
:math:`n`-th positive zero of the order-:math:`\lvert m\rvert`
|
|
799
|
+
Bessel function.
|
|
800
|
+
|
|
801
|
+
Returns
|
|
802
|
+
-------
|
|
803
|
+
CircularEigenstate
|
|
804
|
+
"""
|
|
805
|
+
zero = jn_zeros(abs(m), n)[-1]
|
|
806
|
+
k = zero / self.R
|
|
807
|
+
energy = self.hbar**2 * k**2 / (2 * self.m_mass)
|
|
808
|
+
return CircularEigenstate(m, n, k, energy)
|
|
809
|
+
|
|
810
|
+
def spectrum(self, m_max: int = 4, n_max: int = 4) -> list:
|
|
811
|
+
"""All eigenstates with :math:`\\lvert m\\rvert \\le m_\\text{max}`,
|
|
812
|
+
:math:`1 \\le n \\le n_\\text{max}`, sorted by energy.
|
|
813
|
+
|
|
814
|
+
Parameters
|
|
815
|
+
----------
|
|
816
|
+
m_max : int, default=4
|
|
817
|
+
Maximum :math:`\\lvert m \\rvert`.
|
|
818
|
+
n_max : int, default=4
|
|
819
|
+
Maximum radial index.
|
|
820
|
+
|
|
821
|
+
Returns
|
|
822
|
+
-------
|
|
823
|
+
list of CircularEigenstate
|
|
824
|
+
"""
|
|
825
|
+
states = [self.eigenstate(m, n) for m in range(-m_max, m_max + 1) for n in range(1, n_max + 1)]
|
|
826
|
+
return sorted(states, key=lambda s: s.energy)
|
|
827
|
+
|
|
828
|
+
def polar_grid(self, n_r: int = 150, n_phi: int = 150):
|
|
829
|
+
"""A polar meshgrid spanning the dot.
|
|
830
|
+
|
|
831
|
+
Parameters
|
|
832
|
+
----------
|
|
833
|
+
n_r : int, default=150
|
|
834
|
+
Number of radial points.
|
|
835
|
+
n_phi : int, default=150
|
|
836
|
+
Number of angular points.
|
|
837
|
+
|
|
838
|
+
Returns
|
|
839
|
+
-------
|
|
840
|
+
r, phi : numpy.ndarray
|
|
841
|
+
Meshgrid arrays (``indexing='ij'``).
|
|
842
|
+
"""
|
|
843
|
+
r = np.linspace(0, self.R, n_r)
|
|
844
|
+
phi = np.linspace(0, 2 * np.pi, n_phi)
|
|
845
|
+
return np.meshgrid(r, phi, indexing="ij")
|
|
846
|
+
|
|
847
|
+
|
|
848
|
+
# --- Triangular / stadium billiard (quantum scars, bridge to physicskit.chaos) -----
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
class StadiumBilliard2D:
|
|
852
|
+
"""Bunimovich stadium billiard: a rectangle of length ``L`` capped by two
|
|
853
|
+
semicircles of radius ``R``.
|
|
854
|
+
|
|
855
|
+
The classical dynamics is chaotic, and the quantum eigenstates of this
|
|
856
|
+
box can exhibit *scars* -- probability density concentrated along
|
|
857
|
+
unstable classical periodic orbits -- making this the natural bridge
|
|
858
|
+
between ``physicskit.quantum`` and ``physicskit.chaos``.
|
|
859
|
+
|
|
860
|
+
Eigenstates are obtained by direct diagonalization of the 2D
|
|
861
|
+
finite-difference Laplacian on a grid, with the wavefunction forced to
|
|
862
|
+
zero outside the stadium boundary (infinite-wall billiard).
|
|
863
|
+
|
|
864
|
+
Parameters
|
|
865
|
+
----------
|
|
866
|
+
L : float, default=1.0
|
|
867
|
+
Length of the central rectangular section.
|
|
868
|
+
R : float, default=0.5
|
|
869
|
+
Radius of the semicircular end-caps (also the half-height).
|
|
870
|
+
hbar : float, default=1.0
|
|
871
|
+
Value of :math:`\\hbar` to use.
|
|
872
|
+
m : float, default=1.0
|
|
873
|
+
Particle mass.
|
|
874
|
+
"""
|
|
875
|
+
|
|
876
|
+
def __init__(self, L: float = 1.0, R: float = 0.5, hbar: float = 1.0, m: float = 1.0):
|
|
877
|
+
self.L = L
|
|
878
|
+
self.R = R
|
|
879
|
+
self.hbar = hbar
|
|
880
|
+
self.m = m
|
|
881
|
+
|
|
882
|
+
def mask(self, X: np.ndarray, Y: np.ndarray) -> np.ndarray:
|
|
883
|
+
"""Boolean mask, ``True`` inside the stadium.
|
|
884
|
+
|
|
885
|
+
The stadium is a central :math:`L \\times 2R` rectangle with two
|
|
886
|
+
semicircular end-caps of radius :math:`R`.
|
|
887
|
+
|
|
888
|
+
Parameters
|
|
889
|
+
----------
|
|
890
|
+
X, Y : numpy.ndarray
|
|
891
|
+
Coordinate meshgrid (e.g. from :meth:`grid`).
|
|
892
|
+
|
|
893
|
+
Returns
|
|
894
|
+
-------
|
|
895
|
+
numpy.ndarray of bool
|
|
896
|
+
"""
|
|
897
|
+
inside_rect = (np.abs(X) <= self.L / 2) & (np.abs(Y) <= self.R)
|
|
898
|
+
left_cap = (X < -self.L / 2) & ((X + self.L / 2) ** 2 + Y**2 <= self.R**2)
|
|
899
|
+
right_cap = (X > self.L / 2) & ((X - self.L / 2) ** 2 + Y**2 <= self.R**2)
|
|
900
|
+
return inside_rect | left_cap | right_cap
|
|
901
|
+
|
|
902
|
+
def grid(self, n_points: int = 120):
|
|
903
|
+
"""A Cartesian meshgrid enclosing the stadium.
|
|
904
|
+
|
|
905
|
+
Parameters
|
|
906
|
+
----------
|
|
907
|
+
n_points : int, default=120
|
|
908
|
+
Number of points along the (longer) x-axis.
|
|
909
|
+
|
|
910
|
+
Returns
|
|
911
|
+
-------
|
|
912
|
+
(X, Y) : tuple of numpy.ndarray
|
|
913
|
+
Meshgrid arrays (``indexing='ij'``).
|
|
914
|
+
x, y : numpy.ndarray
|
|
915
|
+
The 1D coordinate arrays used to build the meshgrid.
|
|
916
|
+
"""
|
|
917
|
+
half_x = self.L / 2 + self.R
|
|
918
|
+
half_y = self.R * 1.05
|
|
919
|
+
x = np.linspace(-half_x, half_x, n_points)
|
|
920
|
+
dx = x[1] - x[0]
|
|
921
|
+
# Force dy == dx (an isotropic grid): with mismatched spacing the
|
|
922
|
+
# semicircular caps are staircase-approximated as ellipses rather
|
|
923
|
+
# than circles, which was causing non-monotonic, resolution-
|
|
924
|
+
# dependent errors in the eigenvalues.
|
|
925
|
+
n_y = max(int(round(2 * half_y / dx)) + 1, 20)
|
|
926
|
+
y = np.linspace(-half_y, half_y, n_y)
|
|
927
|
+
return np.meshgrid(x, y, indexing="ij"), x, y
|
|
928
|
+
|
|
929
|
+
def solve(self, n_points: int = 160, n_states: int = 6):
|
|
930
|
+
r"""Diagonalize :math:`-\hbar^2/2m\,\nabla^2` on the masked grid
|
|
931
|
+
(Dirichlet boundary).
|
|
932
|
+
|
|
933
|
+
The curved end-caps are approximated by a "staircased" boundary
|
|
934
|
+
(grid points are simply included or excluded by :meth:`mask`),
|
|
935
|
+
which converges only as :math:`O(h)`: low resolutions give
|
|
936
|
+
eigenvalues with a few percent of resolution-dependent error and
|
|
937
|
+
visibly blocky eigenstate edges. :meth:`grid` keeps the grid
|
|
938
|
+
isotropic (:math:`dx=dy`) so the caps are at least staircased as
|
|
939
|
+
circles rather than ellipses; beyond that, accuracy is improved
|
|
940
|
+
simply by raising ``n_points`` -- the sparse eigensolver used here
|
|
941
|
+
stays fast (well under a second) even at ``n_points=260``.
|
|
942
|
+
|
|
943
|
+
Parameters
|
|
944
|
+
----------
|
|
945
|
+
n_points : int, default=160
|
|
946
|
+
Number of grid points along the x-axis (see :meth:`grid`).
|
|
947
|
+
n_states : int, default=6
|
|
948
|
+
Number of lowest-energy eigenstates to compute.
|
|
949
|
+
|
|
950
|
+
Returns
|
|
951
|
+
-------
|
|
952
|
+
energies : numpy.ndarray
|
|
953
|
+
Eigenenergies, ascending, shape ``(n_states,)``.
|
|
954
|
+
wavefunctions : numpy.ndarray
|
|
955
|
+
Normalized eigenfunctions, shape ``(n_states, *X.shape)``, zero
|
|
956
|
+
outside the stadium.
|
|
957
|
+
X, Y : numpy.ndarray
|
|
958
|
+
The coordinate meshgrid.
|
|
959
|
+
mask : numpy.ndarray of bool
|
|
960
|
+
``True`` inside the stadium (see :meth:`mask`).
|
|
961
|
+
"""
|
|
962
|
+
(X, Y), x, y = self.grid(n_points)
|
|
963
|
+
inside = self.mask(X, Y)
|
|
964
|
+
idx = -np.ones(X.shape, dtype=int)
|
|
965
|
+
interior_points = np.argwhere(inside)
|
|
966
|
+
for order, (i, j) in enumerate(interior_points):
|
|
967
|
+
idx[i, j] = order
|
|
968
|
+
n_interior = interior_points.shape[0]
|
|
969
|
+
|
|
970
|
+
hx = x[1] - x[0]
|
|
971
|
+
hy = y[1] - y[0]
|
|
972
|
+
from scipy.sparse import lil_matrix
|
|
973
|
+
from scipy.sparse.linalg import eigsh
|
|
974
|
+
|
|
975
|
+
H = lil_matrix((n_interior, n_interior))
|
|
976
|
+
coeff = self.hbar**2 / (2 * self.m)
|
|
977
|
+
for order, (i, j) in enumerate(interior_points):
|
|
978
|
+
H[order, order] = coeff * (2 / hx**2 + 2 / hy**2)
|
|
979
|
+
for di, dj, h in ((1, 0, hx), (-1, 0, hx), (0, 1, hy), (0, -1, hy)):
|
|
980
|
+
ni, nj = i + di, j + dj
|
|
981
|
+
if 0 <= ni < X.shape[0] and 0 <= nj < X.shape[1] and idx[ni, nj] >= 0:
|
|
982
|
+
H[order, idx[ni, nj]] = -coeff / h**2
|
|
983
|
+
|
|
984
|
+
energies, vecs = eigsh(H.tocsr(), k=n_states, which="SM")
|
|
985
|
+
order = np.argsort(energies)
|
|
986
|
+
energies = energies[order]
|
|
987
|
+
vecs = vecs[:, order]
|
|
988
|
+
|
|
989
|
+
wavefunctions = np.zeros((n_states, *X.shape))
|
|
990
|
+
for k in range(n_states):
|
|
991
|
+
psi = np.zeros(X.shape)
|
|
992
|
+
for order_idx, (i, j) in enumerate(interior_points):
|
|
993
|
+
psi[i, j] = vecs[order_idx, k]
|
|
994
|
+
norm = np.sqrt(trapz(trapz(psi**2, y, axis=1), x))
|
|
995
|
+
wavefunctions[k] = psi / norm
|
|
996
|
+
|
|
997
|
+
return energies, wavefunctions, X, Y, inside
|