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.
Files changed (484) hide show
  1. physicskit/__init__.py +51 -0
  2. physicskit/astro/__init__.py +66 -0
  3. physicskit/astro/cosmic_web.py +395 -0
  4. physicskit/astro/galactic_dynamics.py +137 -0
  5. physicskit/astro/nbody.py +208 -0
  6. physicskit/astro/orbital_mechanics.py +220 -0
  7. physicskit/astro/stellar_dynamo.py +628 -0
  8. physicskit/astro/stellar_structure.py +198 -0
  9. physicskit/astro/tests/__init__.py +0 -0
  10. physicskit/astro/tests/conftest.py +7 -0
  11. physicskit/astro/tests/test_cosmic_web.py +159 -0
  12. physicskit/astro/tests/test_galactic_dynamics.py +46 -0
  13. physicskit/astro/tests/test_nbody.py +81 -0
  14. physicskit/astro/tests/test_orbital_mechanics.py +73 -0
  15. physicskit/astro/tests/test_stellar_dynamo.py +198 -0
  16. physicskit/astro/tests/test_stellar_structure.py +52 -0
  17. physicskit/astro/tests/test_visualizers.py +159 -0
  18. physicskit/astro/visualizers.py +628 -0
  19. physicskit/chaos/__init__.py +55 -0
  20. physicskit/chaos/core/__init__.py +3 -0
  21. physicskit/chaos/core/base_system.py +342 -0
  22. physicskit/chaos/core/integrators.py +40 -0
  23. physicskit/chaos/exceptions.py +24 -0
  24. physicskit/chaos/quantum/__init__.py +38 -0
  25. physicskit/chaos/quantum/billiards.py +274 -0
  26. physicskit/chaos/quantum/husimi.py +85 -0
  27. physicskit/chaos/quantum/maps.py +366 -0
  28. physicskit/chaos/systems/__init__.py +38 -0
  29. physicskit/chaos/systems/billiards.py +1131 -0
  30. physicskit/chaos/systems/continuous.py +1276 -0
  31. physicskit/chaos/systems/maps.py +557 -0
  32. physicskit/chaos/tests/__init__.py +0 -0
  33. physicskit/chaos/tests/test_basins.py +76 -0
  34. physicskit/chaos/tests/test_bifurcation.py +132 -0
  35. physicskit/chaos/tests/test_billiards.py +115 -0
  36. physicskit/chaos/tests/test_continuous.py +224 -0
  37. physicskit/chaos/tests/test_dimension.py +69 -0
  38. physicskit/chaos/tests/test_divergence.py +90 -0
  39. physicskit/chaos/tests/test_dynamic_plots.py +269 -0
  40. physicskit/chaos/tests/test_integrators.py +96 -0
  41. physicskit/chaos/tests/test_io.py +41 -0
  42. physicskit/chaos/tests/test_maps.py +110 -0
  43. physicskit/chaos/tests/test_metrics.py +115 -0
  44. physicskit/chaos/tests/test_phase_space.py +73 -0
  45. physicskit/chaos/tests/test_properties.py +116 -0
  46. physicskit/chaos/tests/test_quantum_billiards.py +94 -0
  47. physicskit/chaos/tests/test_quantum_maps.py +144 -0
  48. physicskit/chaos/tests/test_recurrence.py +51 -0
  49. physicskit/chaos/tests/test_repr_and_api_consistency.py +134 -0
  50. physicskit/chaos/tests/test_section.py +49 -0
  51. physicskit/chaos/tests/test_spectral.py +48 -0
  52. physicskit/chaos/tests/test_streamlit_app.py +45 -0
  53. physicskit/chaos/tests/test_timeseries.py +131 -0
  54. physicskit/chaos/tests/test_viewer3d.py +41 -0
  55. physicskit/chaos/utils/__init__.py +46 -0
  56. physicskit/chaos/utils/dimension.py +139 -0
  57. physicskit/chaos/utils/io.py +95 -0
  58. physicskit/chaos/utils/metrics.py +304 -0
  59. physicskit/chaos/utils/recurrence.py +122 -0
  60. physicskit/chaos/utils/spectral.py +69 -0
  61. physicskit/chaos/utils/timeseries.py +303 -0
  62. physicskit/chaos/visualizers/__init__.py +85 -0
  63. physicskit/chaos/visualizers/basins.py +185 -0
  64. physicskit/chaos/visualizers/bifurcation.py +273 -0
  65. physicskit/chaos/visualizers/divergence.py +374 -0
  66. physicskit/chaos/visualizers/dynamic_plots.py +1372 -0
  67. physicskit/chaos/visualizers/phase_space.py +133 -0
  68. physicskit/chaos/visualizers/quantum.py +299 -0
  69. physicskit/chaos/visualizers/recurrence.py +91 -0
  70. physicskit/chaos/visualizers/section.py +161 -0
  71. physicskit/chaos/visualizers/theme.py +31 -0
  72. physicskit/chaos/visualizers/viewer3d.py +82 -0
  73. physicskit/classical/__init__.py +55 -0
  74. physicskit/classical/core/__init__.py +17 -0
  75. physicskit/classical/core/base_system.py +510 -0
  76. physicskit/classical/core/integrators.py +397 -0
  77. physicskit/classical/systems/__init__.py +32 -0
  78. physicskit/classical/systems/chains.py +334 -0
  79. physicskit/classical/systems/hamiltonian.py +215 -0
  80. physicskit/classical/systems/lagrangian.py +308 -0
  81. physicskit/classical/systems/newtonian.py +536 -0
  82. physicskit/classical/systems/rotations.py +614 -0
  83. physicskit/classical/tests/__init__.py +0 -0
  84. physicskit/classical/tests/conftest.py +7 -0
  85. physicskit/classical/tests/test_base_system.py +113 -0
  86. physicskit/classical/tests/test_chains.py +84 -0
  87. physicskit/classical/tests/test_conservation.py +244 -0
  88. physicskit/classical/tests/test_conservation_utils.py +47 -0
  89. physicskit/classical/tests/test_hamiltonian.py +44 -0
  90. physicskit/classical/tests/test_integrators.py +123 -0
  91. physicskit/classical/tests/test_lagrangian.py +69 -0
  92. physicskit/classical/tests/test_newtonian.py +45 -0
  93. physicskit/classical/tests/test_rotations.py +160 -0
  94. physicskit/classical/tests/test_stepsize.py +149 -0
  95. physicskit/classical/tests/test_symbolic.py +125 -0
  96. physicskit/classical/tests/test_visualizers.py +172 -0
  97. physicskit/classical/utils/__init__.py +20 -0
  98. physicskit/classical/utils/conservation.py +126 -0
  99. physicskit/classical/utils/stepsize.py +187 -0
  100. physicskit/classical/utils/symbolic.py +235 -0
  101. physicskit/classical/visualizers/__init__.py +25 -0
  102. physicskit/classical/visualizers/animations.py +508 -0
  103. physicskit/classical/visualizers/interactive.py +113 -0
  104. physicskit/classical/visualizers/modal_analysis.py +97 -0
  105. physicskit/classical/visualizers/phase_space.py +214 -0
  106. physicskit/condensed/__init__.py +122 -0
  107. physicskit/condensed/anderson_localization.py +155 -0
  108. physicskit/condensed/correlated.py +284 -0
  109. physicskit/condensed/ginzburg_landau.py +220 -0
  110. physicskit/condensed/landau_levels.py +246 -0
  111. physicskit/condensed/laughlin.py +229 -0
  112. physicskit/condensed/models.py +627 -0
  113. physicskit/condensed/tests/__init__.py +0 -0
  114. physicskit/condensed/tests/test_condensed.py +518 -0
  115. physicskit/condensed/tests/test_visualizers.py +87 -0
  116. physicskit/condensed/tight_binding.py +445 -0
  117. physicskit/condensed/topological_insulator_3d.py +178 -0
  118. physicskit/condensed/topology.py +217 -0
  119. physicskit/condensed/visualizers.py +296 -0
  120. physicskit/condensed/weyl.py +180 -0
  121. physicskit/constants.py +184 -0
  122. physicskit/fields/__init__.py +111 -0
  123. physicskit/fields/electrodynamics.py +671 -0
  124. physicskit/fields/quantum_fields.py +510 -0
  125. physicskit/fields/solitons.py +527 -0
  126. physicskit/fields/tests/__init__.py +0 -0
  127. physicskit/fields/tests/conftest.py +7 -0
  128. physicskit/fields/tests/test_field_animations.py +154 -0
  129. physicskit/fields/tests/test_fields.py +439 -0
  130. physicskit/fields/tests/test_visualizers.py +101 -0
  131. physicskit/fields/visualizers.py +503 -0
  132. physicskit/fluids/__init__.py +134 -0
  133. physicskit/fluids/core/__init__.py +23 -0
  134. physicskit/fluids/core/grid.py +165 -0
  135. physicskit/fluids/core/timestepping.py +231 -0
  136. physicskit/fluids/exceptions.py +26 -0
  137. physicskit/fluids/systems/__init__.py +70 -0
  138. physicskit/fluids/systems/compressible_flow.py +281 -0
  139. physicskit/fluids/systems/instabilities.py +343 -0
  140. physicskit/fluids/systems/navier_stokes.py +221 -0
  141. physicskit/fluids/systems/potential_flow.py +440 -0
  142. physicskit/fluids/systems/viscous_flow.py +425 -0
  143. physicskit/fluids/systems/vortex_dynamics.py +304 -0
  144. physicskit/fluids/tests/__init__.py +0 -0
  145. physicskit/fluids/tests/conftest.py +7 -0
  146. physicskit/fluids/tests/test_compressible_flow.py +66 -0
  147. physicskit/fluids/tests/test_instabilities.py +115 -0
  148. physicskit/fluids/tests/test_navier_stokes.py +67 -0
  149. physicskit/fluids/tests/test_potential_flow.py +92 -0
  150. physicskit/fluids/tests/test_static_visualizers.py +91 -0
  151. physicskit/fluids/tests/test_utils.py +100 -0
  152. physicskit/fluids/tests/test_viscous_flow.py +110 -0
  153. physicskit/fluids/tests/test_visualizers.py +34 -0
  154. physicskit/fluids/tests/test_vortex_dynamics.py +88 -0
  155. physicskit/fluids/utils/__init__.py +18 -0
  156. physicskit/fluids/utils/dimensionless.py +239 -0
  157. physicskit/fluids/utils/spectral_analysis.py +142 -0
  158. physicskit/fluids/visualizers/__init__.py +14 -0
  159. physicskit/fluids/visualizers/compressible.py +39 -0
  160. physicskit/fluids/visualizers/flow_fields.py +252 -0
  161. physicskit/fluids/visualizers/potential_flow.py +51 -0
  162. physicskit/fluids/visualizers/spectra.py +62 -0
  163. physicskit/fluids/visualizers/theme.py +38 -0
  164. physicskit/integrators/__init__.py +52 -0
  165. physicskit/integrators/adaptive.py +199 -0
  166. physicskit/integrators/fixed_step.py +360 -0
  167. physicskit/integrators/tests/__init__.py +0 -0
  168. physicskit/integrators/tests/test_integrators.py +101 -0
  169. physicskit/optics/__init__.py +124 -0
  170. physicskit/optics/_compat.py +5 -0
  171. physicskit/optics/gaussian.py +382 -0
  172. physicskit/optics/quantum_optics.py +428 -0
  173. physicskit/optics/ray.py +469 -0
  174. physicskit/optics/tests/__init__.py +0 -0
  175. physicskit/optics/tests/test_diffraction_animation.py +38 -0
  176. physicskit/optics/tests/test_gaussian.py +170 -0
  177. physicskit/optics/tests/test_quantum_optics.py +147 -0
  178. physicskit/optics/tests/test_ray.py +177 -0
  179. physicskit/optics/tests/test_visualizers.py +58 -0
  180. physicskit/optics/tests/test_wave.py +146 -0
  181. physicskit/optics/visualizers.py +264 -0
  182. physicskit/optics/wave.py +365 -0
  183. physicskit/particle/__init__.py +152 -0
  184. physicskit/particle/collider.py +382 -0
  185. physicskit/particle/confinement.py +125 -0
  186. physicskit/particle/decays.py +357 -0
  187. physicskit/particle/electroweak.py +300 -0
  188. physicskit/particle/kinematics.py +271 -0
  189. physicskit/particle/neutrinos.py +84 -0
  190. physicskit/particle/nuclear.py +100 -0
  191. physicskit/particle/scattering.py +159 -0
  192. physicskit/particle/tests/__init__.py +0 -0
  193. physicskit/particle/tests/conftest.py +7 -0
  194. physicskit/particle/tests/test_collider.py +119 -0
  195. physicskit/particle/tests/test_confinement.py +42 -0
  196. physicskit/particle/tests/test_decays.py +112 -0
  197. physicskit/particle/tests/test_electroweak.py +97 -0
  198. physicskit/particle/tests/test_kinematics.py +138 -0
  199. physicskit/particle/tests/test_neutrinos.py +36 -0
  200. physicskit/particle/tests/test_nuclear.py +45 -0
  201. physicskit/particle/tests/test_scattering.py +64 -0
  202. physicskit/particle/tests/test_visualizers.py +214 -0
  203. physicskit/particle/visualizers/__init__.py +38 -0
  204. physicskit/particle/visualizers/animations.py +705 -0
  205. physicskit/particle/visualizers/static.py +101 -0
  206. physicskit/plasma/__init__.py +176 -0
  207. physicskit/plasma/acceleration.py +152 -0
  208. physicskit/plasma/instabilities.py +417 -0
  209. physicskit/plasma/kinetic.py +588 -0
  210. physicskit/plasma/mhd.py +463 -0
  211. physicskit/plasma/single_particle.py +562 -0
  212. physicskit/plasma/tests/__init__.py +0 -0
  213. physicskit/plasma/tests/conftest.py +7 -0
  214. physicskit/plasma/tests/test_new_time_domain_features.py +222 -0
  215. physicskit/plasma/tests/test_plasma.py +232 -0
  216. physicskit/plasma/tests/test_visualizers.py +79 -0
  217. physicskit/plasma/turbulence.py +219 -0
  218. physicskit/plasma/visualizers.py +868 -0
  219. physicskit/plasma/waves.py +549 -0
  220. physicskit/quantum/__init__.py +180 -0
  221. physicskit/quantum/_compat.py +20 -0
  222. physicskit/quantum/chapters/__init__.py +0 -0
  223. physicskit/quantum/chapters/entanglement.py +489 -0
  224. physicskit/quantum/chapters/harmonic_spin.py +517 -0
  225. physicskit/quantum/chapters/hydrogen_am.py +292 -0
  226. physicskit/quantum/chapters/perturbation.py +350 -0
  227. physicskit/quantum/chapters/potentials.py +997 -0
  228. physicskit/quantum/chapters/spin.py +306 -0
  229. physicskit/quantum/chapters/wave_packets.py +541 -0
  230. physicskit/quantum/core/__init__.py +0 -0
  231. physicskit/quantum/core/eigensolvers.py +311 -0
  232. physicskit/quantum/core/operators.py +296 -0
  233. physicskit/quantum/core/solvers.py +271 -0
  234. physicskit/quantum/tests/__init__.py +0 -0
  235. physicskit/quantum/tests/conftest.py +7 -0
  236. physicskit/quantum/tests/test_animations.py +225 -0
  237. physicskit/quantum/tests/test_compat.py +43 -0
  238. physicskit/quantum/tests/test_eigensolvers.py +24 -0
  239. physicskit/quantum/tests/test_entanglement.py +127 -0
  240. physicskit/quantum/tests/test_harmonic_spin.py +140 -0
  241. physicskit/quantum/tests/test_hydrogen_am.py +48 -0
  242. physicskit/quantum/tests/test_measure.py +110 -0
  243. physicskit/quantum/tests/test_operators.py +105 -0
  244. physicskit/quantum/tests/test_perturbation.py +112 -0
  245. physicskit/quantum/tests/test_physics_checks.py +131 -0
  246. physicskit/quantum/tests/test_potentials.py +191 -0
  247. physicskit/quantum/tests/test_solvers.py +70 -0
  248. physicskit/quantum/tests/test_spin.py +22 -0
  249. physicskit/quantum/tests/test_unitarity.py +84 -0
  250. physicskit/quantum/tests/test_wave_packets.py +53 -0
  251. physicskit/quantum/utils/__init__.py +0 -0
  252. physicskit/quantum/utils/measure.py +253 -0
  253. physicskit/quantum/visualizers/__init__.py +0 -0
  254. physicskit/quantum/visualizers/bloch_sphere.py +205 -0
  255. physicskit/quantum/visualizers/entanglement.py +58 -0
  256. physicskit/quantum/visualizers/orbitals.py +187 -0
  257. physicskit/quantum/visualizers/phase_space.py +179 -0
  258. physicskit/quantum/visualizers/wavefunctions.py +230 -0
  259. physicskit/relativity/__init__.py +61 -0
  260. physicskit/relativity/chapters/__init__.py +33 -0
  261. physicskit/relativity/chapters/cosmology.py +190 -0
  262. physicskit/relativity/chapters/gw_merger.py +399 -0
  263. physicskit/relativity/chapters/kerr.py +371 -0
  264. physicskit/relativity/chapters/lensing.py +196 -0
  265. physicskit/relativity/chapters/neutron_star.py +149 -0
  266. physicskit/relativity/chapters/schwarzschild.py +412 -0
  267. physicskit/relativity/chapters/timekeeping.py +133 -0
  268. physicskit/relativity/core/__init__.py +12 -0
  269. physicskit/relativity/core/geodesics.py +216 -0
  270. physicskit/relativity/core/kerr_raytracer.py +259 -0
  271. physicskit/relativity/core/raytracer.py +241 -0
  272. physicskit/relativity/core/tensors.py +468 -0
  273. physicskit/relativity/tests/__init__.py +0 -0
  274. physicskit/relativity/tests/test_conservations.py +54 -0
  275. physicskit/relativity/tests/test_constants.py +34 -0
  276. physicskit/relativity/tests/test_cosmology.py +61 -0
  277. physicskit/relativity/tests/test_gw_merger.py +117 -0
  278. physicskit/relativity/tests/test_kerr.py +116 -0
  279. physicskit/relativity/tests/test_kerr_raytracer.py +103 -0
  280. physicskit/relativity/tests/test_lensing.py +75 -0
  281. physicskit/relativity/tests/test_neutron_star.py +48 -0
  282. physicskit/relativity/tests/test_raytracer.py +67 -0
  283. physicskit/relativity/tests/test_schwarzschild.py +126 -0
  284. physicskit/relativity/tests/test_shadow_render.py +81 -0
  285. physicskit/relativity/tests/test_tensors.py +86 -0
  286. physicskit/relativity/tests/test_timekeeping.py +51 -0
  287. physicskit/relativity/tests/test_visualizers_misc.py +115 -0
  288. physicskit/relativity/utils/__init__.py +1 -0
  289. physicskit/relativity/utils/constants.py +232 -0
  290. physicskit/relativity/visualizers/__init__.py +46 -0
  291. physicskit/relativity/visualizers/interactive.py +127 -0
  292. physicskit/relativity/visualizers/shadow_render.py +367 -0
  293. physicskit/relativity/visualizers/spacetime_3d.py +92 -0
  294. physicskit/relativity/visualizers/spacetime_diagrams.py +198 -0
  295. physicskit/relativity/visualizers/wave_plots.py +185 -0
  296. physicskit/rmt/__init__.py +8 -0
  297. physicskit/rmt/ensembles/__init__.py +108 -0
  298. physicskit/rmt/ensembles/banded.py +114 -0
  299. physicskit/rmt/ensembles/base.py +135 -0
  300. physicskit/rmt/ensembles/bdg.py +218 -0
  301. physicskit/rmt/ensembles/chiral.py +308 -0
  302. physicskit/rmt/ensembles/circular.py +104 -0
  303. physicskit/rmt/ensembles/crossover.py +82 -0
  304. physicskit/rmt/ensembles/density_matrix.py +148 -0
  305. physicskit/rmt/ensembles/effective_hamiltonian.py +230 -0
  306. physicskit/rmt/ensembles/embedded.py +207 -0
  307. physicskit/rmt/ensembles/gaussian.py +153 -0
  308. physicskit/rmt/ensembles/ginibre.py +110 -0
  309. physicskit/rmt/ensembles/girko.py +190 -0
  310. physicskit/rmt/ensembles/graph_laplacian.py +92 -0
  311. physicskit/rmt/ensembles/haar.py +57 -0
  312. physicskit/rmt/ensembles/jacobi.py +182 -0
  313. physicskit/rmt/ensembles/poisson.py +77 -0
  314. physicskit/rmt/ensembles/polynomial.py +139 -0
  315. physicskit/rmt/ensembles/pt_symmetric.py +120 -0
  316. physicskit/rmt/ensembles/single_ring.py +170 -0
  317. physicskit/rmt/ensembles/sparse.py +95 -0
  318. physicskit/rmt/ensembles/syk.py +170 -0
  319. physicskit/rmt/ensembles/truncated_unitary.py +80 -0
  320. physicskit/rmt/ensembles/universality.py +92 -0
  321. physicskit/rmt/ensembles/wishart.py +88 -0
  322. physicskit/rmt/py.typed +0 -0
  323. physicskit/rmt/spectrum.py +82 -0
  324. physicskit/rmt/stats/__init__.py +160 -0
  325. physicskit/rmt/stats/characteristic_polynomial.py +77 -0
  326. physicskit/rmt/stats/circular_law.py +54 -0
  327. physicskit/rmt/stats/correlations.py +162 -0
  328. physicskit/rmt/stats/density.py +70 -0
  329. physicskit/rmt/stats/entanglement.py +36 -0
  330. physicskit/rmt/stats/free_probability.py +202 -0
  331. physicskit/rmt/stats/localization.py +338 -0
  332. physicskit/rmt/stats/marchenko_pastur.py +55 -0
  333. physicskit/rmt/stats/pt_symmetric.py +265 -0
  334. physicskit/rmt/stats/ratios.py +110 -0
  335. physicskit/rmt/stats/real_ginibre.py +134 -0
  336. physicskit/rmt/stats/rigidity.py +272 -0
  337. physicskit/rmt/stats/single_ring.py +95 -0
  338. physicskit/rmt/stats/spacing.py +150 -0
  339. physicskit/rmt/stats/tracy_widom.py +175 -0
  340. physicskit/rmt/stats/truncated_unitary.py +40 -0
  341. physicskit/rmt/stats/unfolding.py +37 -0
  342. physicskit/rmt/stats/wachter.py +67 -0
  343. physicskit/rmt/tests/__init__.py +0 -0
  344. physicskit/rmt/tests/cache_utils.py +107 -0
  345. physicskit/rmt/tests/conftest.py +8 -0
  346. physicskit/rmt/tests/test_banded.py +86 -0
  347. physicskit/rmt/tests/test_base.py +29 -0
  348. physicskit/rmt/tests/test_bdg.py +133 -0
  349. physicskit/rmt/tests/test_characteristic_polynomial.py +50 -0
  350. physicskit/rmt/tests/test_chiral.py +273 -0
  351. physicskit/rmt/tests/test_circular.py +225 -0
  352. physicskit/rmt/tests/test_crossover.py +72 -0
  353. physicskit/rmt/tests/test_density.py +23 -0
  354. physicskit/rmt/tests/test_density_matrix.py +181 -0
  355. physicskit/rmt/tests/test_effective_hamiltonian.py +140 -0
  356. physicskit/rmt/tests/test_embedded.py +169 -0
  357. physicskit/rmt/tests/test_free_probability.py +81 -0
  358. physicskit/rmt/tests/test_gaussian_ensembles.py +22 -0
  359. physicskit/rmt/tests/test_gaussian_semicircle.py +127 -0
  360. physicskit/rmt/tests/test_ginibre_circular_law.py +131 -0
  361. physicskit/rmt/tests/test_girko.py +178 -0
  362. physicskit/rmt/tests/test_graph_laplacian.py +102 -0
  363. physicskit/rmt/tests/test_haar.py +118 -0
  364. physicskit/rmt/tests/test_jacobi_wachter.py +136 -0
  365. physicskit/rmt/tests/test_localization.py +240 -0
  366. physicskit/rmt/tests/test_poisson.py +149 -0
  367. physicskit/rmt/tests/test_polynomial.py +107 -0
  368. physicskit/rmt/tests/test_pt_symmetric.py +178 -0
  369. physicskit/rmt/tests/test_random_state.py +19 -0
  370. physicskit/rmt/tests/test_real_ginibre.py +112 -0
  371. physicskit/rmt/tests/test_rigidity_universality.py +167 -0
  372. physicskit/rmt/tests/test_single_ring.py +132 -0
  373. physicskit/rmt/tests/test_spacing_and_ratios.py +122 -0
  374. physicskit/rmt/tests/test_sparse.py +71 -0
  375. physicskit/rmt/tests/test_spectrum.py +27 -0
  376. physicskit/rmt/tests/test_syk.py +136 -0
  377. physicskit/rmt/tests/test_tracy_widom.py +113 -0
  378. physicskit/rmt/tests/test_tridiagonal.py +41 -0
  379. physicskit/rmt/tests/test_truncated_unitary.py +80 -0
  380. physicskit/rmt/tests/test_validation_base.py +25 -0
  381. physicskit/rmt/tests/test_validation_misc.py +67 -0
  382. physicskit/rmt/tests/test_wishart_marchenko_pastur.py +120 -0
  383. physicskit/rmt/utils/__init__.py +0 -0
  384. physicskit/rmt/utils/haar.py +128 -0
  385. physicskit/rmt/utils/random_state.py +24 -0
  386. physicskit/rmt/utils/tridiagonal.py +167 -0
  387. physicskit/rmt/validation/__init__.py +36 -0
  388. physicskit/rmt/validation/base.py +126 -0
  389. physicskit/rmt/validation/circular_law.py +80 -0
  390. physicskit/rmt/validation/marchenko_pastur.py +53 -0
  391. physicskit/rmt/validation/ratio_distribution.py +53 -0
  392. physicskit/rmt/validation/real_ginibre.py +60 -0
  393. physicskit/rmt/validation/sine_kernel.py +57 -0
  394. physicskit/rmt/validation/single_ring.py +68 -0
  395. physicskit/rmt/validation/tracy_widom.py +89 -0
  396. physicskit/rmt/validation/universality.py +100 -0
  397. physicskit/rmt/validation/wachter.py +52 -0
  398. physicskit/rmt/validation/wigner_semicircle.py +40 -0
  399. physicskit/rmt/validation/wigner_surmise.py +58 -0
  400. physicskit/semiclassical/__init__.py +94 -0
  401. physicskit/semiclassical/_compat.py +5 -0
  402. physicskit/semiclassical/core/__init__.py +41 -0
  403. physicskit/semiclassical/core/gutzwiller.py +215 -0
  404. physicskit/semiclassical/core/path_integral.py +594 -0
  405. physicskit/semiclassical/core/propagators.py +579 -0
  406. physicskit/semiclassical/core/wkb.py +308 -0
  407. physicskit/semiclassical/systems/__init__.py +13 -0
  408. physicskit/semiclassical/systems/scarring.py +278 -0
  409. physicskit/semiclassical/tests/__init__.py +0 -0
  410. physicskit/semiclassical/tests/test_gutzwiller.py +35 -0
  411. physicskit/semiclassical/tests/test_path_integral.py +194 -0
  412. physicskit/semiclassical/tests/test_propagators.py +82 -0
  413. physicskit/semiclassical/tests/test_scarring.py +43 -0
  414. physicskit/semiclassical/tests/test_visualizers.py +89 -0
  415. physicskit/semiclassical/tests/test_wkb.py +30 -0
  416. physicskit/semiclassical/visualizers/__init__.py +17 -0
  417. physicskit/semiclassical/visualizers/gutzwiller.py +55 -0
  418. physicskit/semiclassical/visualizers/path_integral.py +195 -0
  419. physicskit/semiclassical/visualizers/propagators.py +89 -0
  420. physicskit/semiclassical/visualizers/scarring.py +146 -0
  421. physicskit/semiclassical/visualizers/wkb.py +55 -0
  422. physicskit/statphys/__init__.py +80 -0
  423. physicskit/statphys/chapters/__init__.py +43 -0
  424. physicskit/statphys/chapters/ehrenfest_urn.py +112 -0
  425. physicskit/statphys/chapters/ising_lattice.py +431 -0
  426. physicskit/statphys/chapters/kpz_growth.py +129 -0
  427. physicskit/statphys/chapters/molecular_dynamics.py +258 -0
  428. physicskit/statphys/chapters/nonequilibrium_work.py +146 -0
  429. physicskit/statphys/chapters/percolation.py +305 -0
  430. physicskit/statphys/chapters/random_walk.py +142 -0
  431. physicskit/statphys/chapters/renormalization.py +148 -0
  432. physicskit/statphys/chapters/sandpile.py +109 -0
  433. physicskit/statphys/chapters/spin_glass.py +275 -0
  434. physicskit/statphys/core/__init__.py +10 -0
  435. physicskit/statphys/core/kpz_engine.py +53 -0
  436. physicskit/statphys/core/md_engine.py +157 -0
  437. physicskit/statphys/core/monte_carlo.py +462 -0
  438. physicskit/statphys/core/random_walk_engine.py +68 -0
  439. physicskit/statphys/core/sandpile_engine.py +52 -0
  440. physicskit/statphys/tests/__init__.py +0 -0
  441. physicskit/statphys/tests/test_dynamics.py +66 -0
  442. physicskit/statphys/tests/test_ehrenfest_urn.py +44 -0
  443. physicskit/statphys/tests/test_finite_size_scaling.py +62 -0
  444. physicskit/statphys/tests/test_interactive.py +33 -0
  445. physicskit/statphys/tests/test_ising.py +68 -0
  446. physicskit/statphys/tests/test_kpz.py +55 -0
  447. physicskit/statphys/tests/test_landau_theory.py +49 -0
  448. physicskit/statphys/tests/test_md_engine.py +31 -0
  449. physicskit/statphys/tests/test_molecular_dynamics.py +72 -0
  450. physicskit/statphys/tests/test_nonequilibrium_work.py +33 -0
  451. physicskit/statphys/tests/test_partition_function.py +26 -0
  452. physicskit/statphys/tests/test_percolation.py +116 -0
  453. physicskit/statphys/tests/test_potts_model.py +46 -0
  454. physicskit/statphys/tests/test_random_walk.py +50 -0
  455. physicskit/statphys/tests/test_renormalization.py +44 -0
  456. physicskit/statphys/tests/test_sandpile.py +44 -0
  457. physicskit/statphys/tests/test_spin_glass.py +73 -0
  458. physicskit/statphys/tests/test_thermodynamics.py +121 -0
  459. physicskit/statphys/tests/test_xy_model.py +45 -0
  460. physicskit/statphys/utils/__init__.py +1 -0
  461. physicskit/statphys/utils/dynamics.py +100 -0
  462. physicskit/statphys/utils/finite_size_scaling.py +171 -0
  463. physicskit/statphys/utils/landau_theory.py +151 -0
  464. physicskit/statphys/utils/partition_function.py +109 -0
  465. physicskit/statphys/utils/thermodynamics.py +356 -0
  466. physicskit/statphys/visualizers/__init__.py +87 -0
  467. physicskit/statphys/visualizers/interactive.py +161 -0
  468. physicskit/statphys/visualizers/jarzynski_render.py +44 -0
  469. physicskit/statphys/visualizers/kpz_render.py +65 -0
  470. physicskit/statphys/visualizers/lattice_render.py +212 -0
  471. physicskit/statphys/visualizers/particle_render.py +121 -0
  472. physicskit/statphys/visualizers/random_walk_render.py +106 -0
  473. physicskit/statphys/visualizers/rg_render.py +45 -0
  474. physicskit/statphys/visualizers/sandpile_render.py +65 -0
  475. physicskit/statphys/visualizers/spin_glass_render.py +40 -0
  476. physicskit/statphys/visualizers/urn_render.py +38 -0
  477. physicskit/statphys/visualizers/vortex_render.py +84 -0
  478. physicskit/tests/__init__.py +0 -0
  479. physicskit/tests/test_constants.py +38 -0
  480. physicskit-0.1.0.dist-info/METADATA +196 -0
  481. physicskit-0.1.0.dist-info/RECORD +484 -0
  482. physicskit-0.1.0.dist-info/WHEEL +5 -0
  483. physicskit-0.1.0.dist-info/licenses/LICENSE +21 -0
  484. 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)