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,671 @@
|
|
|
1
|
+
"""Finite-difference time-domain (FDTD) solutions of Maxwell's equations on a Yee grid.
|
|
2
|
+
|
|
3
|
+
Implements the staggered-grid (Yee 1966) leapfrog update for 1D plane waves
|
|
4
|
+
and 2D TMz-mode propagation, plus a graded-conductivity absorbing boundary
|
|
5
|
+
(a simplified, non-split-field approximation to Berenger's Perfectly
|
|
6
|
+
Matched Layer) that damps outgoing waves instead of reflecting them off a
|
|
7
|
+
hard electric wall.
|
|
8
|
+
|
|
9
|
+
Units are SI throughout; :data:`EPS0`, :data:`MU0`, :data:`C0` are the
|
|
10
|
+
vacuum permittivity, permeability, and speed of light.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
|
|
17
|
+
from physicskit import constants as _const
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"EPS0",
|
|
21
|
+
"MU0",
|
|
22
|
+
"C0",
|
|
23
|
+
"courant_limit_1d",
|
|
24
|
+
"courant_limit_2d",
|
|
25
|
+
"fdtd_1d",
|
|
26
|
+
"fdtd_2d_tmz",
|
|
27
|
+
"fdtd_2d_tmz_evolve",
|
|
28
|
+
"pml_conductivity_profile",
|
|
29
|
+
"pml_conductivity_profile_2d",
|
|
30
|
+
"oscillating_dipole_source",
|
|
31
|
+
"dielectric_slab",
|
|
32
|
+
"tmz_cavity_mode",
|
|
33
|
+
"poynting_vector_tmz",
|
|
34
|
+
"flux_tube_field_1d",
|
|
35
|
+
"flux_tube_energy_density_2d",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
EPS0 = _const.VACUUM_PERMITTIVITY
|
|
39
|
+
MU0 = _const.VACUUM_PERMEABILITY
|
|
40
|
+
C0 = _const.C
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def courant_limit_1d(dx: float) -> float:
|
|
44
|
+
"""Maximum stable time step for the 1D FDTD update.
|
|
45
|
+
|
|
46
|
+
Parameters
|
|
47
|
+
----------
|
|
48
|
+
dx : float
|
|
49
|
+
Spatial grid spacing in meters.
|
|
50
|
+
|
|
51
|
+
Returns
|
|
52
|
+
-------
|
|
53
|
+
float
|
|
54
|
+
The Courant stability limit :math:`\\Delta t = \\Delta x / c_0`.
|
|
55
|
+
|
|
56
|
+
Examples
|
|
57
|
+
--------
|
|
58
|
+
>>> round(float(courant_limit_1d(1e-3) * C0 / 1e-3), 6)
|
|
59
|
+
1.0
|
|
60
|
+
"""
|
|
61
|
+
return dx / C0
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def courant_limit_2d(dx: float, dy: float) -> float:
|
|
65
|
+
"""Maximum stable time step for the 2D FDTD update.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
dx, dy : float
|
|
70
|
+
Spatial grid spacing in meters along each axis.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
float
|
|
75
|
+
The Courant stability limit
|
|
76
|
+
:math:`\\Delta t = 1 / (c_0 \\sqrt{1/\\Delta x^2 + 1/\\Delta y^2})`.
|
|
77
|
+
|
|
78
|
+
Examples
|
|
79
|
+
--------
|
|
80
|
+
>>> dt = courant_limit_2d(1e-3, 1e-3)
|
|
81
|
+
>>> round(float(dt * C0 / 1e-3), 6)
|
|
82
|
+
0.707107
|
|
83
|
+
"""
|
|
84
|
+
return 1.0 / (C0 * np.sqrt(1.0 / dx**2 + 1.0 / dy**2))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def pml_conductivity_profile(n_cells: int, pml_width: int, dx: float, order: int = 3, sigma_max: float | None = None) -> np.ndarray:
|
|
88
|
+
"""Build a polynomial-graded electric conductivity profile absorbing at both ends of a 1D grid.
|
|
89
|
+
|
|
90
|
+
A simplified (non-split-field) approximation to Berenger's Perfectly
|
|
91
|
+
Matched Layer: rather than solving auxiliary split-field equations,
|
|
92
|
+
this grades the ordinary medium's electric conductivity smoothly up
|
|
93
|
+
from zero over ``pml_width`` cells at each boundary, damping outgoing
|
|
94
|
+
waves via the standard lossy-dielectric FDTD update
|
|
95
|
+
(see ``sigma`` in :func:`fdtd_1d`) instead of reflecting them off a
|
|
96
|
+
hard wall.
|
|
97
|
+
|
|
98
|
+
Parameters
|
|
99
|
+
----------
|
|
100
|
+
n_cells : int
|
|
101
|
+
Total number of grid points.
|
|
102
|
+
pml_width : int
|
|
103
|
+
Number of cells over which the conductivity ramps from 0 to ``sigma_max``.
|
|
104
|
+
dx : float
|
|
105
|
+
Spatial grid spacing in meters, used to scale the default ``sigma_max``.
|
|
106
|
+
order : int, default=3
|
|
107
|
+
Polynomial grading order (Sadiku's rule of thumb uses a cubic ramp).
|
|
108
|
+
sigma_max : float, optional
|
|
109
|
+
Peak conductivity at the outermost cell. Defaults to the standard
|
|
110
|
+
empirical optimum ``(order + 1) / (150 * pi * dx)``.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
ndarray, shape (n_cells,)
|
|
115
|
+
Conductivity profile, zero in the interior and ramping up at both edges.
|
|
116
|
+
|
|
117
|
+
Examples
|
|
118
|
+
--------
|
|
119
|
+
>>> sigma = pml_conductivity_profile(200, pml_width=20, dx=1e-3)
|
|
120
|
+
>>> bool(np.all(sigma[20:-20] == 0))
|
|
121
|
+
True
|
|
122
|
+
>>> bool(sigma[0] > sigma[10] > 0)
|
|
123
|
+
True
|
|
124
|
+
"""
|
|
125
|
+
if sigma_max is None:
|
|
126
|
+
sigma_max = (order + 1) / (150 * np.pi * dx)
|
|
127
|
+
sigma = np.zeros(n_cells)
|
|
128
|
+
ramp = (np.arange(pml_width) / pml_width) ** order * sigma_max
|
|
129
|
+
sigma[:pml_width] = ramp[::-1]
|
|
130
|
+
sigma[-pml_width:] = ramp
|
|
131
|
+
return sigma
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def pml_conductivity_profile_2d(shape: tuple, pml_width: int, dx: float, dy: float, order: int = 3, sigma_max: float | None = None) -> np.ndarray:
|
|
135
|
+
"""Build a 2D graded-conductivity absorbing boundary by combining two 1D PML profiles.
|
|
136
|
+
|
|
137
|
+
Reuses :func:`pml_conductivity_profile` along each axis and takes the
|
|
138
|
+
pointwise maximum, so a cell absorbs at whichever rate is larger --
|
|
139
|
+
correct in the corners, where both the ``x`` and ``y`` ramps are active.
|
|
140
|
+
|
|
141
|
+
Parameters
|
|
142
|
+
----------
|
|
143
|
+
shape : tuple(int, int)
|
|
144
|
+
``(Nx, Ny)`` grid shape.
|
|
145
|
+
pml_width : int
|
|
146
|
+
Number of cells over which each axis's conductivity ramps up.
|
|
147
|
+
dx, dy : float
|
|
148
|
+
Spatial grid spacing along each axis.
|
|
149
|
+
order : int, default=3
|
|
150
|
+
Polynomial grading order, passed to :func:`pml_conductivity_profile`.
|
|
151
|
+
sigma_max : float, optional
|
|
152
|
+
Peak conductivity; defaults (per axis) to the standard empirical optimum.
|
|
153
|
+
|
|
154
|
+
Returns
|
|
155
|
+
-------
|
|
156
|
+
ndarray, shape (Nx, Ny)
|
|
157
|
+
Conductivity map, zero in the interior and ramping up at all four edges.
|
|
158
|
+
|
|
159
|
+
Examples
|
|
160
|
+
--------
|
|
161
|
+
>>> sigma = pml_conductivity_profile_2d((100, 80), pml_width=10, dx=1e-3, dy=1e-3)
|
|
162
|
+
>>> bool(np.all(sigma[10:-10, 10:-10] == 0))
|
|
163
|
+
True
|
|
164
|
+
>>> bool(sigma[0, 0] > 0)
|
|
165
|
+
True
|
|
166
|
+
"""
|
|
167
|
+
Nx, Ny = shape
|
|
168
|
+
sx = pml_conductivity_profile(Nx, pml_width, dx, order, sigma_max)
|
|
169
|
+
sy = pml_conductivity_profile(Ny, pml_width, dy, order, sigma_max)
|
|
170
|
+
return np.maximum(sx[:, None], sy[None, :])
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def fdtd_1d(Ez0: np.ndarray, Hy0: np.ndarray, eps_r: np.ndarray, mu_r: np.ndarray, steps: int, dt: float, dx: float, sigma: np.ndarray | None = None) -> tuple:
|
|
174
|
+
"""Evolve a 1D plane wave (Ez, Hy) on a Yee grid using leapfrog FDTD.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
Ez0 : ndarray, shape (N,)
|
|
179
|
+
Initial electric field.
|
|
180
|
+
Hy0 : ndarray, shape (N-1,)
|
|
181
|
+
Initial magnetic field, staggered half a cell ahead of ``Ez0``.
|
|
182
|
+
eps_r, mu_r : ndarray, shape (N,)
|
|
183
|
+
Relative permittivity and permeability at each ``Ez`` grid point.
|
|
184
|
+
steps : int
|
|
185
|
+
Number of time steps to advance.
|
|
186
|
+
dt : float
|
|
187
|
+
Time step in seconds; must satisfy :func:`courant_limit_1d`.
|
|
188
|
+
dx : float
|
|
189
|
+
Spatial grid spacing in meters.
|
|
190
|
+
sigma : ndarray, shape (N,), optional
|
|
191
|
+
Electric conductivity (S/m) at each grid point, e.g. from
|
|
192
|
+
:func:`pml_conductivity_profile`, for a lossy/absorbing boundary.
|
|
193
|
+
Defaults to a lossless grid with hard (PEC) walls at both ends.
|
|
194
|
+
|
|
195
|
+
Returns
|
|
196
|
+
-------
|
|
197
|
+
Ez, Hy : ndarray
|
|
198
|
+
Fields at the final time step, same shapes as ``Ez0``, ``Hy0``.
|
|
199
|
+
|
|
200
|
+
See Also
|
|
201
|
+
--------
|
|
202
|
+
fdtd_2d_tmz : The 2D (TMz-mode) analog of this solver.
|
|
203
|
+
|
|
204
|
+
Examples
|
|
205
|
+
--------
|
|
206
|
+
A smooth Gaussian pulse, launched with the impedance-matched initial
|
|
207
|
+
condition of a purely right-moving wave, propagates at exactly the
|
|
208
|
+
vacuum speed of light:
|
|
209
|
+
|
|
210
|
+
>>> import numpy as np
|
|
211
|
+
>>> N = 800
|
|
212
|
+
>>> dx = 1e-3
|
|
213
|
+
>>> dt = 0.99 * courant_limit_1d(dx)
|
|
214
|
+
>>> eta0 = np.sqrt(MU0 / EPS0)
|
|
215
|
+
>>> x0, sigma_pulse = 100, 25
|
|
216
|
+
>>> Ez0 = np.exp(-((np.arange(N) - x0) ** 2) / (2 * sigma_pulse ** 2))
|
|
217
|
+
>>> xh = np.arange(N - 1) + 0.5
|
|
218
|
+
>>> Hy0 = np.exp(-((xh - x0) ** 2) / (2 * sigma_pulse ** 2)) / eta0
|
|
219
|
+
>>> eps_r, mu_r = np.ones(N), np.ones(N)
|
|
220
|
+
>>> Ez, Hy = fdtd_1d(Ez0, Hy0, eps_r, mu_r, steps=360, dt=dt, dx=dx)
|
|
221
|
+
>>> peak = np.argmax(Ez)
|
|
222
|
+
>>> measured_speed = (peak - x0) * dx / (360 * dt)
|
|
223
|
+
>>> round(float(measured_speed / C0), 3)
|
|
224
|
+
1.002
|
|
225
|
+
"""
|
|
226
|
+
Ez = np.array(Ez0, dtype=float, copy=True)
|
|
227
|
+
Hy = np.array(Hy0, dtype=float, copy=True)
|
|
228
|
+
if sigma is None:
|
|
229
|
+
Ca = np.ones_like(Ez)
|
|
230
|
+
Cb = dt / (EPS0 * eps_r * dx)
|
|
231
|
+
else:
|
|
232
|
+
loss = sigma * dt / (2 * EPS0 * eps_r)
|
|
233
|
+
Ca = (1 - loss) / (1 + loss)
|
|
234
|
+
Cb = (dt / (EPS0 * eps_r * dx)) / (1 + loss)
|
|
235
|
+
for _ in range(steps):
|
|
236
|
+
Hy += -dt / (MU0 * mu_r[:-1] * dx) * (Ez[1:] - Ez[:-1])
|
|
237
|
+
Ez[1:-1] = Ca[1:-1] * Ez[1:-1] - Cb[1:-1] * (Hy[1:] - Hy[:-1])
|
|
238
|
+
Ez[0] = 0.0
|
|
239
|
+
Ez[-1] = 0.0
|
|
240
|
+
return Ez, Hy
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def fdtd_2d_tmz(Ez0: np.ndarray, Hx0: np.ndarray, Hy0: np.ndarray, eps_r: np.ndarray, mu_r: np.ndarray, steps: int, dt: float, dx: float, dy: float) -> tuple:
|
|
244
|
+
"""Evolve a 2D TMz-mode field (Ez, Hx, Hy) on a Yee grid using leapfrog FDTD.
|
|
245
|
+
|
|
246
|
+
Parameters
|
|
247
|
+
----------
|
|
248
|
+
Ez0 : ndarray, shape (Nx, Ny)
|
|
249
|
+
Initial (out-of-plane) electric field.
|
|
250
|
+
Hx0, Hy0 : ndarray, shape (Nx, Ny)
|
|
251
|
+
Initial in-plane magnetic field components.
|
|
252
|
+
eps_r, mu_r : ndarray, shape (Nx, Ny)
|
|
253
|
+
Relative permittivity and permeability maps.
|
|
254
|
+
steps : int
|
|
255
|
+
Number of time steps to advance.
|
|
256
|
+
dt : float
|
|
257
|
+
Time step in seconds; must satisfy :func:`courant_limit_2d`.
|
|
258
|
+
dx, dy : float
|
|
259
|
+
Spatial grid spacing in meters along each axis.
|
|
260
|
+
|
|
261
|
+
Returns
|
|
262
|
+
-------
|
|
263
|
+
Ez, Hx, Hy : ndarray, shape (Nx, Ny)
|
|
264
|
+
Fields at the final time step.
|
|
265
|
+
|
|
266
|
+
See Also
|
|
267
|
+
--------
|
|
268
|
+
fdtd_1d : The 1D analog of this solver.
|
|
269
|
+
poynting_vector_tmz : Energy flux from the evolved fields.
|
|
270
|
+
|
|
271
|
+
Examples
|
|
272
|
+
--------
|
|
273
|
+
>>> import numpy as np
|
|
274
|
+
>>> Nx, Ny = 50, 50
|
|
275
|
+
>>> Ez0 = np.zeros((Nx, Ny))
|
|
276
|
+
>>> Hx0 = np.zeros((Nx, Ny))
|
|
277
|
+
>>> Hy0 = np.zeros((Nx, Ny))
|
|
278
|
+
>>> eps_r, mu_r = np.ones((Nx, Ny)), np.ones((Nx, Ny))
|
|
279
|
+
>>> dx = dy = 1e-3
|
|
280
|
+
>>> dt = 0.5 * courant_limit_2d(dx, dy)
|
|
281
|
+
>>> Ez0[Nx // 2, Ny // 2] = 1.0
|
|
282
|
+
>>> Ez, Hx, Hy = fdtd_2d_tmz(Ez0, Hx0, Hy0, eps_r, mu_r, steps=10, dt=dt, dx=dx, dy=dy)
|
|
283
|
+
>>> Ez.shape
|
|
284
|
+
(50, 50)
|
|
285
|
+
"""
|
|
286
|
+
Ez = np.array(Ez0, dtype=float, copy=True)
|
|
287
|
+
Hx = np.array(Hx0, dtype=float, copy=True)
|
|
288
|
+
Hy = np.array(Hy0, dtype=float, copy=True)
|
|
289
|
+
for _ in range(steps):
|
|
290
|
+
_fdtd_2d_tmz_step(Ez, Hx, Hy, eps_r, mu_r, dt, dx, dy)
|
|
291
|
+
return Ez, Hx, Hy
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
def _fdtd_2d_tmz_step(Ez, Hx, Hy, eps_r, mu_r, dt, dx, dy, Ca=None, Cb=None):
|
|
295
|
+
"""One leapfrog TMz update, in place; the shared inner loop of :func:`fdtd_2d_tmz` and :func:`fdtd_2d_tmz_evolve`.
|
|
296
|
+
|
|
297
|
+
With ``Ca``/``Cb`` left as ``None`` this is exactly the lossless,
|
|
298
|
+
hard-wall (PEC) update. Passing loss coefficients (as built from a
|
|
299
|
+
conductivity map by :func:`fdtd_2d_tmz_evolve`) instead applies the
|
|
300
|
+
same lossy-dielectric update used by :func:`fdtd_1d`, generalized to 2D.
|
|
301
|
+
"""
|
|
302
|
+
Hx[:, :-1] += -dt / (MU0 * mu_r[:, :-1] * dy) * (Ez[:, 1:] - Ez[:, :-1])
|
|
303
|
+
Hy[:-1, :] += dt / (MU0 * mu_r[:-1, :] * dx) * (Ez[1:, :] - Ez[:-1, :])
|
|
304
|
+
curl_h = (Hy[1:-1, 1:-1] - Hy[:-2, 1:-1]) / dx - (Hx[1:-1, 1:-1] - Hx[1:-1, :-2]) / dy
|
|
305
|
+
if Ca is None:
|
|
306
|
+
Ez[1:-1, 1:-1] += dt / (EPS0 * eps_r[1:-1, 1:-1]) * curl_h
|
|
307
|
+
else:
|
|
308
|
+
Ez[1:-1, 1:-1] = Ca[1:-1, 1:-1] * Ez[1:-1, 1:-1] + Cb[1:-1, 1:-1] * curl_h
|
|
309
|
+
Ez[0, :] = 0.0
|
|
310
|
+
Ez[-1, :] = 0.0
|
|
311
|
+
Ez[:, 0] = 0.0
|
|
312
|
+
Ez[:, -1] = 0.0
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def fdtd_2d_tmz_evolve(
|
|
316
|
+
Ez0: np.ndarray,
|
|
317
|
+
Hx0: np.ndarray,
|
|
318
|
+
Hy0: np.ndarray,
|
|
319
|
+
eps_r: np.ndarray,
|
|
320
|
+
mu_r: np.ndarray,
|
|
321
|
+
steps: int,
|
|
322
|
+
dt: float,
|
|
323
|
+
dx: float,
|
|
324
|
+
dy: float,
|
|
325
|
+
sigma: np.ndarray | None = None,
|
|
326
|
+
source=None,
|
|
327
|
+
snapshot_stride: int = 1,
|
|
328
|
+
) -> tuple:
|
|
329
|
+
"""Time-step the 2D TMz FDTD update while recording ``Ez`` snapshots, for animation.
|
|
330
|
+
|
|
331
|
+
Unlike :func:`fdtd_2d_tmz`, which returns only the field at the final
|
|
332
|
+
step, this driver records ``Ez`` every ``snapshot_stride`` steps and
|
|
333
|
+
supports two features layered on top of the same base update: a lossy
|
|
334
|
+
absorbing boundary (``sigma``, e.g. from :func:`pml_conductivity_profile_2d`,
|
|
335
|
+
so an outgoing wave is damped instead of reflecting off the hard PEC
|
|
336
|
+
walls) and a soft source callback injected into ``Ez`` after every
|
|
337
|
+
field update (e.g. :func:`oscillating_dipole_source`).
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
Ez0, Hx0, Hy0, eps_r, mu_r, steps, dt, dx, dy
|
|
342
|
+
As in :func:`fdtd_2d_tmz`.
|
|
343
|
+
sigma : ndarray, shape (Nx, Ny), optional
|
|
344
|
+
Electric conductivity map. ``None`` (default) reproduces
|
|
345
|
+
:func:`fdtd_2d_tmz`'s lossless, hard-wall behavior exactly.
|
|
346
|
+
source : callable, optional
|
|
347
|
+
``source(Ez, t)``, called after each field update and expected to
|
|
348
|
+
mutate ``Ez`` in place (a soft source: added to, not overwriting,
|
|
349
|
+
the field already there).
|
|
350
|
+
snapshot_stride : int, default=1
|
|
351
|
+
Record ``Ez`` every this many steps (plus the initial condition).
|
|
352
|
+
|
|
353
|
+
Returns
|
|
354
|
+
-------
|
|
355
|
+
snapshots : ndarray, shape (n_recorded, Nx, Ny)
|
|
356
|
+
``Ez`` at ``t=0`` and after every recorded step.
|
|
357
|
+
times : ndarray, shape (n_recorded,)
|
|
358
|
+
Time of each recorded snapshot.
|
|
359
|
+
|
|
360
|
+
See Also
|
|
361
|
+
--------
|
|
362
|
+
fdtd_2d_tmz : The single-shot (final-state-only) solver this wraps.
|
|
363
|
+
|
|
364
|
+
Examples
|
|
365
|
+
--------
|
|
366
|
+
>>> import numpy as np
|
|
367
|
+
>>> Nx, Ny = 40, 40
|
|
368
|
+
>>> Ez0 = np.zeros((Nx, Ny))
|
|
369
|
+
>>> Hx0 = Hy0 = np.zeros((Nx, Ny))
|
|
370
|
+
>>> eps_r = mu_r = np.ones((Nx, Ny))
|
|
371
|
+
>>> dx = dy = 1e-3
|
|
372
|
+
>>> dt = 0.5 * courant_limit_2d(dx, dy)
|
|
373
|
+
>>> source = oscillating_dipole_source(Nx // 2, Ny // 2, amplitude=1.0, freq=5e10)
|
|
374
|
+
>>> snaps, times = fdtd_2d_tmz_evolve(Ez0, Hx0, Hy0, eps_r, mu_r, steps=20, dt=dt, dx=dx, dy=dy, source=source, snapshot_stride=5)
|
|
375
|
+
>>> snaps.shape
|
|
376
|
+
(5, 40, 40)
|
|
377
|
+
"""
|
|
378
|
+
Ez = np.array(Ez0, dtype=float, copy=True)
|
|
379
|
+
Hx = np.array(Hx0, dtype=float, copy=True)
|
|
380
|
+
Hy = np.array(Hy0, dtype=float, copy=True)
|
|
381
|
+
if sigma is None:
|
|
382
|
+
Ca = Cb = None
|
|
383
|
+
else:
|
|
384
|
+
loss = sigma * dt / (2 * EPS0 * eps_r)
|
|
385
|
+
Ca = (1 - loss) / (1 + loss)
|
|
386
|
+
Cb = (dt / (EPS0 * eps_r)) / (1 + loss)
|
|
387
|
+
n_snap = steps // snapshot_stride + 1
|
|
388
|
+
snapshots = np.empty((n_snap, *Ez.shape))
|
|
389
|
+
times = np.empty(n_snap)
|
|
390
|
+
snapshots[0] = Ez
|
|
391
|
+
times[0] = 0.0
|
|
392
|
+
idx = 1
|
|
393
|
+
for step in range(1, steps + 1):
|
|
394
|
+
_fdtd_2d_tmz_step(Ez, Hx, Hy, eps_r, mu_r, dt, dx, dy, Ca, Cb)
|
|
395
|
+
t = step * dt
|
|
396
|
+
if source is not None:
|
|
397
|
+
source(Ez, t)
|
|
398
|
+
if step % snapshot_stride == 0:
|
|
399
|
+
snapshots[idx] = Ez
|
|
400
|
+
times[idx] = t
|
|
401
|
+
idx += 1
|
|
402
|
+
return snapshots[:idx], times[:idx]
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def oscillating_dipole_source(i0: int, j0: int, amplitude: float, freq: float):
|
|
406
|
+
"""Build a soft, sinusoidally-oscillating point source for :func:`fdtd_2d_tmz_evolve`.
|
|
407
|
+
|
|
408
|
+
Models a simple oscillating dipole antenna: a source current
|
|
409
|
+
concentrated at a single Yee cell, injected as a *soft* source (added
|
|
410
|
+
to the existing field rather than overwriting it, so outgoing waves
|
|
411
|
+
already at that cell are not clobbered).
|
|
412
|
+
|
|
413
|
+
Parameters
|
|
414
|
+
----------
|
|
415
|
+
i0, j0 : int
|
|
416
|
+
Grid indices of the source cell.
|
|
417
|
+
amplitude : float
|
|
418
|
+
Peak field amplitude injected each step.
|
|
419
|
+
freq : float
|
|
420
|
+
Oscillation frequency in Hz.
|
|
421
|
+
|
|
422
|
+
Returns
|
|
423
|
+
-------
|
|
424
|
+
callable
|
|
425
|
+
``source(Ez, t)``, suitable for :func:`fdtd_2d_tmz_evolve`'s
|
|
426
|
+
``source`` argument: adds ``amplitude * sin(2*pi*freq*t)`` to
|
|
427
|
+
``Ez[i0, j0]`` in place.
|
|
428
|
+
|
|
429
|
+
Examples
|
|
430
|
+
--------
|
|
431
|
+
>>> import numpy as np
|
|
432
|
+
>>> Ez = np.zeros((10, 10))
|
|
433
|
+
>>> source = oscillating_dipole_source(5, 5, amplitude=2.0, freq=1.0)
|
|
434
|
+
>>> source(Ez, t=0.25)
|
|
435
|
+
>>> round(float(Ez[5, 5]), 6)
|
|
436
|
+
2.0
|
|
437
|
+
"""
|
|
438
|
+
|
|
439
|
+
def _source(Ez, t):
|
|
440
|
+
Ez[i0, j0] += amplitude * np.sin(2.0 * np.pi * freq * t)
|
|
441
|
+
|
|
442
|
+
return _source
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def dielectric_slab(shape: tuple, i_start: int, i_end: int, eps_r_slab: float = 4.0) -> np.ndarray:
|
|
446
|
+
"""A planar dielectric slab: ``eps_r_slab`` between grid columns ``i_start`` and ``i_end``, vacuum elsewhere.
|
|
447
|
+
|
|
448
|
+
A minimal demo permittivity map for :func:`fdtd_2d_tmz` / :func:`fdtd_2d_tmz_evolve`:
|
|
449
|
+
a wave launched from one side crossing ``i_start`` partially reflects
|
|
450
|
+
and partially refracts/transmits into the slab, and again at ``i_end``
|
|
451
|
+
on the way out, illustrating propagation through a medium interface.
|
|
452
|
+
|
|
453
|
+
Parameters
|
|
454
|
+
----------
|
|
455
|
+
shape : tuple(int, int)
|
|
456
|
+
``(Nx, Ny)`` grid shape.
|
|
457
|
+
i_start, i_end : int
|
|
458
|
+
Grid-index bounds of the slab along the ``x`` axis (rows ``i_start:i_end``).
|
|
459
|
+
eps_r_slab : float, default=4.0
|
|
460
|
+
Relative permittivity inside the slab.
|
|
461
|
+
|
|
462
|
+
Returns
|
|
463
|
+
-------
|
|
464
|
+
ndarray, shape (Nx, Ny)
|
|
465
|
+
``eps_r`` map: ``eps_r_slab`` inside the slab, ``1.0`` outside.
|
|
466
|
+
|
|
467
|
+
Examples
|
|
468
|
+
--------
|
|
469
|
+
>>> eps_r = dielectric_slab((40, 20), i_start=15, i_end=25, eps_r_slab=4.0)
|
|
470
|
+
>>> float(eps_r[10, 5]), float(eps_r[20, 5])
|
|
471
|
+
(1.0, 4.0)
|
|
472
|
+
"""
|
|
473
|
+
Nx, Ny = shape
|
|
474
|
+
eps_r = np.ones((Nx, Ny))
|
|
475
|
+
eps_r[i_start:i_end, :] = eps_r_slab
|
|
476
|
+
return eps_r
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def tmz_cavity_mode(shape: tuple, dx: float, dy: float, m: int, n: int) -> tuple:
|
|
480
|
+
"""The analytic :math:`TM_{mn}` standing-wave mode of an ideal rectangular PEC cavity.
|
|
481
|
+
|
|
482
|
+
:func:`fdtd_2d_tmz` (and :func:`fdtd_2d_tmz_evolve` without ``sigma``)
|
|
483
|
+
already enforce ``Ez=0`` on all four edges every step -- exactly a
|
|
484
|
+
perfectly-conducting (PEC) cavity wall, no absorbing boundary needed.
|
|
485
|
+
Seeding the grid with this analytic mode shape (which already vanishes
|
|
486
|
+
on the boundary) launches a standing wave that should oscillate in
|
|
487
|
+
place at ``omega_mn`` rather than propagate.
|
|
488
|
+
|
|
489
|
+
Parameters
|
|
490
|
+
----------
|
|
491
|
+
shape : tuple(int, int)
|
|
492
|
+
``(Nx, Ny)`` grid shape.
|
|
493
|
+
dx, dy : float
|
|
494
|
+
Grid spacing; the cavity spans ``Lx=(Nx-1)*dx`` by ``Ly=(Ny-1)*dy``.
|
|
495
|
+
m, n : int
|
|
496
|
+
Mode indices (number of half-wavelengths along each axis).
|
|
497
|
+
|
|
498
|
+
Returns
|
|
499
|
+
-------
|
|
500
|
+
Ez0 : ndarray, shape (Nx, Ny)
|
|
501
|
+
:math:`\\sin(m\\pi x/L_x)\\sin(n\\pi y/L_y)`.
|
|
502
|
+
omega_mn : float
|
|
503
|
+
The mode's angular frequency, :math:`c_0\\pi\\sqrt{(m/L_x)^2+(n/L_y)^2}`.
|
|
504
|
+
|
|
505
|
+
Examples
|
|
506
|
+
--------
|
|
507
|
+
>>> Ez0, omega = tmz_cavity_mode((41, 41), dx=1e-3, dy=1e-3, m=1, n=1)
|
|
508
|
+
>>> bool(np.all(Ez0[0, :] == 0) and np.all(Ez0[:, 0] == 0))
|
|
509
|
+
True
|
|
510
|
+
>>> bool(omega > 0)
|
|
511
|
+
True
|
|
512
|
+
"""
|
|
513
|
+
Nx, Ny = shape
|
|
514
|
+
Lx, Ly = (Nx - 1) * dx, (Ny - 1) * dy
|
|
515
|
+
x = np.arange(Nx) * dx
|
|
516
|
+
y = np.arange(Ny) * dy
|
|
517
|
+
X, Y = np.meshgrid(x, y, indexing="ij")
|
|
518
|
+
Ez0 = np.sin(m * np.pi * X / Lx) * np.sin(n * np.pi * Y / Ly)
|
|
519
|
+
# sin(m*pi) / sin(n*pi) at the boundary is 0 only up to floating-point roundoff
|
|
520
|
+
# (e.g. ~1e-16, not exactly 0) -- clamp it exactly, since it must vanish there by construction.
|
|
521
|
+
Ez0[0, :] = 0.0
|
|
522
|
+
Ez0[-1, :] = 0.0
|
|
523
|
+
Ez0[:, 0] = 0.0
|
|
524
|
+
Ez0[:, -1] = 0.0
|
|
525
|
+
omega_mn = C0 * np.pi * np.sqrt((m / Lx) ** 2 + (n / Ly) ** 2)
|
|
526
|
+
return Ez0, omega_mn
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def poynting_vector_tmz(Ez: np.ndarray, Hx: np.ndarray, Hy: np.ndarray) -> tuple:
|
|
530
|
+
"""Poynting vector :math:`\\mathbf{S} = \\mathbf{E} \\times \\mathbf{H}` for a TMz-mode field.
|
|
531
|
+
|
|
532
|
+
Parameters
|
|
533
|
+
----------
|
|
534
|
+
Ez : ndarray
|
|
535
|
+
Out-of-plane electric field.
|
|
536
|
+
Hx, Hy : ndarray
|
|
537
|
+
In-plane magnetic field components, same shape as ``Ez``.
|
|
538
|
+
|
|
539
|
+
Returns
|
|
540
|
+
-------
|
|
541
|
+
Sx, Sy : ndarray
|
|
542
|
+
In-plane energy flux density components, :math:`S_x = -E_z H_y`,
|
|
543
|
+
:math:`S_y = E_z H_x`.
|
|
544
|
+
|
|
545
|
+
Examples
|
|
546
|
+
--------
|
|
547
|
+
>>> import numpy as np
|
|
548
|
+
>>> Ez = np.array([[2.0]])
|
|
549
|
+
>>> Hx = np.array([[3.0]])
|
|
550
|
+
>>> Hy = np.array([[5.0]])
|
|
551
|
+
>>> poynting_vector_tmz(Ez, Hx, Hy)
|
|
552
|
+
(array([[-10.]]), array([[6.]]))
|
|
553
|
+
"""
|
|
554
|
+
return -Ez * Hy, Ez * Hx
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
# -- Toy confinement ("flux tube") model -------------------------------------
|
|
558
|
+
#
|
|
559
|
+
# NOT a lattice-QCD or first-principles gauge-theory calculation. This is the
|
|
560
|
+
# textbook illustrative picture of confinement (the "dual superconductor" /
|
|
561
|
+
# bag-model flux tube): unlike an ordinary Coulomb field, which spreads out
|
|
562
|
+
# in all directions and whose energy is independent of source separation,
|
|
563
|
+
# confinement is modeled here simply by *forbidding* the field from
|
|
564
|
+
# spreading transversely -- it is squeezed into a tube of fixed
|
|
565
|
+
# cross-section connecting the two charges. Gauss's law then forces the
|
|
566
|
+
# field strength inside the tube to stay at the fixed value set by the
|
|
567
|
+
# enclosed charge, independent of how far apart the charges are, so the
|
|
568
|
+
# field energy density is uniform along the whole tube and the total field
|
|
569
|
+
# energy grows *linearly* with separation -- the defining signature of
|
|
570
|
+
# confinement, reproduced here by direct construction rather than derived
|
|
571
|
+
# from an underlying gauge-invariant Lagrangian.
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
def flux_tube_field_1d(x: np.ndarray, separation: float, flux_quantum: float = 1.0, wall_width: float | None = None) -> np.ndarray:
|
|
575
|
+
"""Static 1D confined flux profile between two opposite point charges (toy confinement model).
|
|
576
|
+
|
|
577
|
+
Solves the 1D Gauss law :math:`d(\\text{flux})/dx = \\rho(x)` for a field
|
|
578
|
+
confined to a fixed-cross-section tube connecting two opposite charges
|
|
579
|
+
at :math:`x=\\pm\\text{separation}/2`, each smoothed into a Gaussian of
|
|
580
|
+
width ``wall_width`` (a numerical regularization of the point charge,
|
|
581
|
+
not a physical effect). The field is computed by direct numerical
|
|
582
|
+
integration (cumulative trapezoidal sum) of this charge density, so it
|
|
583
|
+
is genuinely "solved for" at each separation rather than assumed.
|
|
584
|
+
|
|
585
|
+
Parameters
|
|
586
|
+
----------
|
|
587
|
+
x : ndarray
|
|
588
|
+
1D grid the charges live on.
|
|
589
|
+
separation : float
|
|
590
|
+
Distance between the two charges.
|
|
591
|
+
flux_quantum : float, default=1.0
|
|
592
|
+
Charge magnitude (equivalently, the plateau field strength inside the tube).
|
|
593
|
+
wall_width : float, optional
|
|
594
|
+
Smoothing width of each point charge. Defaults to ``3 * (x[1] - x[0])``,
|
|
595
|
+
i.e. a few grid cells -- small enough to look like a point charge
|
|
596
|
+
but resolved on the grid.
|
|
597
|
+
|
|
598
|
+
Returns
|
|
599
|
+
-------
|
|
600
|
+
ndarray, same shape as ``x``
|
|
601
|
+
The confined field, approximately ``+flux_quantum`` between the
|
|
602
|
+
charges and ``0`` outside, with smooth transitions of width ``wall_width``.
|
|
603
|
+
|
|
604
|
+
See Also
|
|
605
|
+
--------
|
|
606
|
+
flux_tube_energy_density_2d : The corresponding 2D energy-density map, for animation.
|
|
607
|
+
|
|
608
|
+
Examples
|
|
609
|
+
--------
|
|
610
|
+
>>> import numpy as np
|
|
611
|
+
>>> x = np.linspace(-20, 20, 2000)
|
|
612
|
+
>>> field = flux_tube_field_1d(x, separation=10.0, flux_quantum=1.0)
|
|
613
|
+
>>> bool(abs(field[np.argmin(np.abs(x))] - 1.0) < 0.05)
|
|
614
|
+
True
|
|
615
|
+
>>> bool(abs(field[0]) < 0.05)
|
|
616
|
+
True
|
|
617
|
+
"""
|
|
618
|
+
dx = x[1] - x[0]
|
|
619
|
+
if wall_width is None:
|
|
620
|
+
wall_width = 3.0 * dx
|
|
621
|
+
x_left, x_right = -separation / 2.0, separation / 2.0
|
|
622
|
+
norm = 1.0 / (wall_width * np.sqrt(2.0 * np.pi))
|
|
623
|
+
rho = flux_quantum * norm * (np.exp(-0.5 * ((x - x_left) / wall_width) ** 2) - np.exp(-0.5 * ((x - x_right) / wall_width) ** 2))
|
|
624
|
+
field = np.concatenate(([0.0], np.cumsum((rho[1:] + rho[:-1]) * 0.5 * dx)))
|
|
625
|
+
return field
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def flux_tube_energy_density_2d(
|
|
629
|
+
shape: tuple, dx: float, dy: float, separation: float, flux_quantum: float = 1.0, tube_width: float | None = None
|
|
630
|
+
) -> np.ndarray:
|
|
631
|
+
"""2D energy-density map of the confined flux tube, for visualizing it as a stretching heatmap.
|
|
632
|
+
|
|
633
|
+
Extrudes :func:`flux_tube_field_1d` (evaluated along the axis joining
|
|
634
|
+
the two charges) into 2D by giving it a fixed-width Gaussian transverse
|
|
635
|
+
profile -- the "fixed cross-section" that produces confinement -- and
|
|
636
|
+
forms the field energy density :math:`\\tfrac{1}{2}\\,\\text{field}(x)^2\\times\\text{transverse}(y)^2`.
|
|
637
|
+
|
|
638
|
+
Parameters
|
|
639
|
+
----------
|
|
640
|
+
shape : tuple(int, int)
|
|
641
|
+
``(Nx, Ny)`` grid shape.
|
|
642
|
+
dx, dy : float
|
|
643
|
+
Grid spacing; the charges are placed on the centered grid at
|
|
644
|
+
:math:`x=\\pm\\text{separation}/2`, :math:`y=0`.
|
|
645
|
+
separation : float
|
|
646
|
+
Distance between the two charges.
|
|
647
|
+
flux_quantum : float, default=1.0
|
|
648
|
+
Charge magnitude, as in :func:`flux_tube_field_1d`.
|
|
649
|
+
tube_width : float, optional
|
|
650
|
+
Transverse (``y``) width of the flux tube. Defaults to ``5 * dy``.
|
|
651
|
+
|
|
652
|
+
Returns
|
|
653
|
+
-------
|
|
654
|
+
ndarray, shape (Nx, Ny)
|
|
655
|
+
Energy density, largest along the tube connecting the charges and
|
|
656
|
+
decaying away from its axis.
|
|
657
|
+
|
|
658
|
+
Examples
|
|
659
|
+
--------
|
|
660
|
+
>>> e = flux_tube_energy_density_2d((200, 40), dx=0.2, dy=0.2, separation=10.0)
|
|
661
|
+
>>> bool(e.sum() > 0)
|
|
662
|
+
True
|
|
663
|
+
"""
|
|
664
|
+
Nx, Ny = shape
|
|
665
|
+
if tube_width is None:
|
|
666
|
+
tube_width = 5.0 * dy
|
|
667
|
+
x = (np.arange(Nx) - Nx // 2) * dx
|
|
668
|
+
y = (np.arange(Ny) - Ny // 2) * dy
|
|
669
|
+
field_x = flux_tube_field_1d(x, separation, flux_quantum)
|
|
670
|
+
transverse = np.exp(-0.5 * (y / tube_width) ** 2)
|
|
671
|
+
return 0.5 * (field_x[:, None] ** 2) * (transverse[None, :] ** 2)
|