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,469 @@
1
+ """Geometric ray optics: paraxial ray transfer (ABCD) matrices and optical systems.
2
+
3
+ In the paraxial approximation, a light ray at a given plane along the
4
+ optical axis is fully described by two numbers -- its height above the
5
+ axis, :math:`y`, and the angle it makes with the axis, :math:`\\theta`
6
+ (in radians, small-angle/paraxial regime) -- collected into a state vector
7
+ :math:`(y, \\theta)^T`. Every simple optical element (propagation through
8
+ free space, refraction at an interface, a thin or thick lens, a curved
9
+ mirror, a graded-index medium, ...) acts on this vector as a linear map, a
10
+ :math:`2\\times 2` "ABCD" matrix:
11
+
12
+ .. math::
13
+
14
+ \\begin{pmatrix} y_{\\text{out}} \\\\ \\theta_{\\text{out}} \\end{pmatrix}
15
+ = \\begin{pmatrix} A & B \\\\ C & D \\end{pmatrix}
16
+ \\begin{pmatrix} y_{\\text{in}} \\\\ \\theta_{\\text{in}} \\end{pmatrix}.
17
+
18
+ This formalism -- developed piecemeal through the 19th century (Gauss's
19
+ theory of optical systems) and formalized for laser resonator design by
20
+ Kogelnik and Li in the 1960s -- reduces the analysis of an arbitrarily long
21
+ chain of lenses, mirrors, and gaps to ordinary matrix multiplication: the
22
+ matrix of a compound system is just the product of the matrices of its
23
+ elements, applied in the order light encounters them (rightmost first).
24
+ The same matrices reappear in :mod:`physicskit.optics.gaussian` to
25
+ propagate the complex beam parameter of a Gaussian laser beam, and their
26
+ trace controls whether a laser cavity is a stable resonator.
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import numpy as np
32
+
33
+ __all__ = [
34
+ "free_space",
35
+ "thin_lens",
36
+ "flat_interface",
37
+ "curved_interface",
38
+ "thick_lens",
39
+ "spherical_mirror",
40
+ "grin_medium",
41
+ "OpticalElement",
42
+ "OpticalSystem",
43
+ "cavity_round_trip_matrix",
44
+ "cavity_stability",
45
+ ]
46
+
47
+
48
+ def free_space(d):
49
+ """ABCD matrix for propagation through a distance ``d`` of free space (or any homogeneous medium).
50
+
51
+ .. math::
52
+
53
+ M = \\begin{pmatrix} 1 & d \\\\ 0 & 1 \\end{pmatrix}
54
+
55
+ A ray's height changes in proportion to its angle and the distance
56
+ traveled; its angle is unchanged.
57
+
58
+ Parameters
59
+ ----------
60
+ d : float
61
+ Propagation distance.
62
+
63
+ Returns
64
+ -------
65
+ ndarray of shape (2, 2)
66
+
67
+ Examples
68
+ --------
69
+ >>> free_space(2.0)
70
+ array([[1., 2.],
71
+ [0., 1.]])
72
+ """
73
+ return np.array([[1.0, float(d)], [0.0, 1.0]])
74
+
75
+
76
+ def thin_lens(f):
77
+ """ABCD matrix for a thin lens of focal length ``f``.
78
+
79
+ .. math::
80
+
81
+ M = \\begin{pmatrix} 1 & 0 \\\\ -1/f & 1 \\end{pmatrix}
82
+
83
+ Parameters
84
+ ----------
85
+ f : float
86
+ Focal length (positive for a converging lens, negative for
87
+ diverging).
88
+
89
+ Returns
90
+ -------
91
+ ndarray of shape (2, 2)
92
+
93
+ Examples
94
+ --------
95
+ >>> thin_lens(0.5)
96
+ array([[ 1., 0.],
97
+ [-2., 1.]])
98
+ """
99
+ return np.array([[1.0, 0.0], [-1.0 / f, 1.0]])
100
+
101
+
102
+ def flat_interface(n1, n2):
103
+ """ABCD matrix for refraction at a flat interface, index ``n1`` to ``n2``.
104
+
105
+ .. math::
106
+
107
+ M = \\begin{pmatrix} 1 & 0 \\\\ 0 & n_1/n_2 \\end{pmatrix}
108
+
109
+ Parameters
110
+ ----------
111
+ n1 : float
112
+ Refractive index of the incident medium.
113
+ n2 : float
114
+ Refractive index of the transmitted medium.
115
+
116
+ Returns
117
+ -------
118
+ ndarray of shape (2, 2)
119
+
120
+ Examples
121
+ --------
122
+ >>> flat_interface(1.0, 1.5)
123
+ array([[1. , 0. ],
124
+ [0. , 0.66666667]])
125
+ """
126
+ return np.array([[1.0, 0.0], [0.0, n1 / n2]])
127
+
128
+
129
+ def curved_interface(R, n1, n2):
130
+ """ABCD matrix for refraction at a spherical interface of radius ``R``, index ``n1`` to ``n2``.
131
+
132
+ .. math::
133
+
134
+ M = \\begin{pmatrix} 1 & 0 \\\\ (n_1-n_2)/(R n_2) & n_1/n_2 \\end{pmatrix}
135
+
136
+ Parameters
137
+ ----------
138
+ R : float
139
+ Radius of curvature of the interface, positive if the center of
140
+ curvature lies on the outgoing (transmitted) side of the surface.
141
+ n1 : float
142
+ Refractive index of the incident medium.
143
+ n2 : float
144
+ Refractive index of the transmitted medium.
145
+
146
+ Returns
147
+ -------
148
+ ndarray of shape (2, 2)
149
+
150
+ See Also
151
+ --------
152
+ flat_interface : The :math:`R \\to \\infty` limit.
153
+
154
+ Examples
155
+ --------
156
+ >>> np.allclose(curved_interface(1.0e12, 1.0, 1.5), flat_interface(1.0, 1.5), atol=1e-6)
157
+ True
158
+ """
159
+ return np.array([[1.0, 0.0], [(n1 - n2) / (R * n2), n1 / n2]])
160
+
161
+
162
+ def thick_lens(R1, R2, t, n, n_ext=1.0):
163
+ """ABCD matrix for a thick lens: two curved interfaces separated by thickness ``t``.
164
+
165
+ Composes, in the order light passes through them, refraction into the
166
+ lens at the first surface, propagation across the lens body, and
167
+ refraction back out at the second surface:
168
+
169
+ .. math::
170
+
171
+ M = M_{R_2}\\,M_t\\,M_{R_1}, \\qquad
172
+ M_{R_1} = \\text{curved\\_interface}(R_1, n_{\\text{ext}}, n), \\\\
173
+ M_t = \\text{free\\_space}(t) \\ \\text{(in medium } n\\text{)}, \\qquad
174
+ M_{R_2} = \\text{curved\\_interface}(R_2, n, n_{\\text{ext}}).
175
+
176
+ Parameters
177
+ ----------
178
+ R1 : float
179
+ Radius of curvature of the first (entrance) surface.
180
+ R2 : float
181
+ Radius of curvature of the second (exit) surface.
182
+ t : float
183
+ Center thickness of the lens.
184
+ n : float
185
+ Refractive index of the lens material.
186
+ n_ext : float, default=1.0
187
+ Refractive index of the surrounding medium (air, by default).
188
+
189
+ Returns
190
+ -------
191
+ ndarray of shape (2, 2)
192
+
193
+ Examples
194
+ --------
195
+ A symmetric biconvex lens in air; the resulting matrix has unit
196
+ determinant, as any lossless ABCD system must:
197
+
198
+ >>> M = thick_lens(R1=0.1, R2=-0.1, t=0.01, n=1.5)
199
+ >>> round(float(np.linalg.det(M)), 8)
200
+ 1.0
201
+ """
202
+ m1 = curved_interface(R1, n_ext, n)
203
+ m2 = free_space(t)
204
+ m3 = curved_interface(R2, n, n_ext)
205
+ return m3 @ m2 @ m1
206
+
207
+
208
+ def spherical_mirror(R):
209
+ """ABCD matrix for reflection from a spherical mirror of radius of curvature ``R``.
210
+
211
+ .. math::
212
+
213
+ M = \\begin{pmatrix} 1 & 0 \\\\ -2/R & 1 \\end{pmatrix}
214
+
215
+ Parameters
216
+ ----------
217
+ R : float
218
+ Radius of curvature, positive for a concave mirror as seen by the
219
+ incoming ray (i.e. a focusing mirror).
220
+
221
+ Returns
222
+ -------
223
+ ndarray of shape (2, 2)
224
+
225
+ Examples
226
+ --------
227
+ >>> spherical_mirror(2.0)
228
+ array([[ 1., 0.],
229
+ [-1., 1.]])
230
+ """
231
+ return np.array([[1.0, 0.0], [-2.0 / R, 1.0]])
232
+
233
+
234
+ def grin_medium(n0, n2_coeff, d):
235
+ """ABCD matrix for a graded-index (GRIN) medium of length ``d``.
236
+
237
+ For the standard quadratic radial index profile
238
+
239
+ .. math::
240
+
241
+ n(r) = n_0\\left(1 - \\frac{n_2 r^2}{2}\\right),
242
+
243
+ paraxial rays oscillate sinusoidally about the axis, giving
244
+
245
+ .. math::
246
+
247
+ A = D = \\cos(\\sqrt{n_2}\\,d), \\qquad
248
+ B = \\frac{\\sin(\\sqrt{n_2}\\,d)}{n_0\\sqrt{n_2}}, \\qquad
249
+ C = -n_0\\sqrt{n_2}\\,\\sin(\\sqrt{n_2}\\,d).
250
+
251
+ As ``n2_coeff`` :math:`\\to 0` (a homogeneous medium of index
252
+ :math:`n_0`), :math:`A=D\\to 1`, :math:`C\\to 0`, and
253
+ :math:`B \\to d/n_0` -- matching :func:`free_space` exactly only for
254
+ :math:`n_0 = 1`. This ``d/n_0`` scaling (rather than plain ``d``) for
255
+ :math:`n_0 \\neq 1` is inherent to the standard GRIN formula quoted
256
+ above; callers comparing against :func:`free_space` in this limit
257
+ should do so at :math:`n_0 = 1`.
258
+
259
+ Parameters
260
+ ----------
261
+ n0 : float
262
+ On-axis refractive index.
263
+ n2_coeff : float
264
+ Quadratic index-gradient coefficient :math:`n_2` (units of
265
+ 1/length^2). Must be non-negative; ``0`` gives a homogeneous
266
+ medium.
267
+ d : float
268
+ Length of the GRIN medium.
269
+
270
+ Returns
271
+ -------
272
+ ndarray of shape (2, 2)
273
+
274
+ Examples
275
+ --------
276
+ A tiny gradient barely distinguishable from a homogeneous medium of
277
+ index 1 reduces to plain free-space propagation:
278
+
279
+ >>> np.allclose(grin_medium(n0=1.0, n2_coeff=1e-8, d=2.0), free_space(2.0), atol=1e-4)
280
+ True
281
+ """
282
+ if n2_coeff <= 0.0:
283
+ return np.array([[1.0, float(d) / n0], [0.0, 1.0]])
284
+ sqrt_n2 = np.sqrt(n2_coeff)
285
+ arg = sqrt_n2 * d
286
+ A = np.cos(arg)
287
+ B = np.sin(arg) / (n0 * sqrt_n2)
288
+ C = -n0 * sqrt_n2 * np.sin(arg)
289
+ return np.array([[A, B], [C, A]])
290
+
291
+
292
+ class OpticalElement:
293
+ """A single named optical element wrapping one ABCD matrix.
294
+
295
+ Parameters
296
+ ----------
297
+ matrix : array_like, shape (2, 2)
298
+ The element's ray transfer matrix, e.g. from :func:`thin_lens` or
299
+ :func:`free_space`.
300
+ name : str, default=""
301
+ Human-readable label (e.g. ``"f=50mm lens"``).
302
+ length : float, default=0.0
303
+ Physical length occupied by this element along the optical axis
304
+ (zero for a "thin" element such as a lens or mirror).
305
+
306
+ Examples
307
+ --------
308
+ >>> elem = OpticalElement(thin_lens(0.05), name="focusing lens", length=0.0)
309
+ >>> elem.name
310
+ 'focusing lens'
311
+ """
312
+
313
+ def __init__(self, matrix, name="", length=0.0):
314
+ self.matrix = np.asarray(matrix, dtype=float)
315
+ self.name = name
316
+ self.length = length
317
+
318
+
319
+ class OpticalSystem:
320
+ """An ordered sequence of :class:`OpticalElement` forming a compound optical system.
321
+
322
+ Parameters
323
+ ----------
324
+ elements : list of OpticalElement
325
+ The elements in the order light passes through them: ``elements[0]``
326
+ is hit first.
327
+
328
+ Examples
329
+ --------
330
+ A single thin lens followed by propagation over its focal length
331
+ focuses any parallel ray bundle back to the axis:
332
+
333
+ >>> f = 0.1
334
+ >>> sys = OpticalSystem([
335
+ ... OpticalElement(thin_lens(f), name="lens"),
336
+ ... OpticalElement(free_space(f), name="propagate to focus"),
337
+ ... ])
338
+ >>> trajectory = sys.trace_ray(y0=0.01, theta0=0.0)
339
+ >>> abs(float(trajectory[-1, 0])) < 1e-12
340
+ True
341
+ """
342
+
343
+ def __init__(self, elements):
344
+ self.elements = list(elements)
345
+
346
+ def system_matrix(self):
347
+ """Total ABCD matrix of the system, :math:`M = M_n \\cdots M_2 M_1`.
348
+
349
+ ``elements[0]`` is applied first (it is the rightmost factor), so
350
+ it acts on the incoming ray state before any later element.
351
+
352
+ Returns
353
+ -------
354
+ ndarray of shape (2, 2)
355
+ """
356
+ M = np.eye(2)
357
+ for element in self.elements:
358
+ M = element.matrix @ M
359
+ return M
360
+
361
+ def trace_ray(self, y0, theta0):
362
+ """Trace a single ray through every element, recording its state at each step.
363
+
364
+ Parameters
365
+ ----------
366
+ y0 : float
367
+ Initial height.
368
+ theta0 : float
369
+ Initial angle, in radians.
370
+
371
+ Returns
372
+ -------
373
+ ndarray of shape (n_elements + 1, 2)
374
+ Row 0 is the input state ``[y0, theta0]``; row ``i`` (for
375
+ ``i >= 1``) is the state after passing through
376
+ ``elements[0], ..., elements[i-1]``.
377
+ """
378
+ n = len(self.elements)
379
+ states = np.empty((n + 1, 2))
380
+ state = np.array([float(y0), float(theta0)])
381
+ states[0] = state
382
+ for i, element in enumerate(self.elements):
383
+ state = element.matrix @ state
384
+ states[i + 1] = state
385
+ return states
386
+
387
+ @property
388
+ def stability_parameter(self):
389
+ """Resonator stability parameter :math:`(A+D)/2` of the system matrix.
390
+
391
+ Returns
392
+ -------
393
+ float
394
+ """
395
+ M = self.system_matrix()
396
+ return (M[0, 0] + M[1, 1]) / 2.0
397
+
398
+ def is_stable(self):
399
+ """Whether the system satisfies the resonator stability condition :math:`|A+D| \\le 2`.
400
+
401
+ Returns
402
+ -------
403
+ bool
404
+ """
405
+ return cavity_stability(self.system_matrix())
406
+
407
+
408
+ def cavity_round_trip_matrix(elements):
409
+ """Round-trip ABCD matrix of a laser cavity, given its elements in traversal order.
410
+
411
+ Equivalent to ``OpticalSystem(elements).system_matrix()``; provided as
412
+ a standalone function for cavities analyzed without constructing a full
413
+ :class:`OpticalSystem`.
414
+
415
+ Parameters
416
+ ----------
417
+ elements : list of OpticalElement
418
+ The elements encountered over one full round trip, in order.
419
+
420
+ Returns
421
+ -------
422
+ ndarray of shape (2, 2)
423
+
424
+ See Also
425
+ --------
426
+ cavity_stability : Test the resulting matrix for resonator stability.
427
+
428
+ Examples
429
+ --------
430
+ >>> M = cavity_round_trip_matrix([
431
+ ... OpticalElement(spherical_mirror(2.0), name="M1"),
432
+ ... OpticalElement(free_space(1.0), name="gap"),
433
+ ... OpticalElement(spherical_mirror(2.0), name="M2"),
434
+ ... OpticalElement(free_space(1.0), name="gap"),
435
+ ... ])
436
+ >>> cavity_stability(M)
437
+ True
438
+ """
439
+ M = np.eye(2)
440
+ for element in elements:
441
+ M = element.matrix @ M
442
+ return M
443
+
444
+
445
+ def cavity_stability(M):
446
+ """Resonator stability test :math:`|A+D| \\le 2` (equivalently :math:`|\\operatorname{tr} M| \\le 2`).
447
+
448
+ A laser cavity with round-trip matrix ``M`` supports stable,
449
+ non-diverging paraxial ray bundles if and only if this holds.
450
+
451
+ Parameters
452
+ ----------
453
+ M : array_like, shape (2, 2)
454
+ A round-trip ABCD matrix, e.g. from :func:`cavity_round_trip_matrix`
455
+ or :meth:`OpticalSystem.system_matrix`.
456
+
457
+ Returns
458
+ -------
459
+ bool
460
+
461
+ Examples
462
+ --------
463
+ >>> cavity_stability(np.array([[1.0, 0.0], [0.0, 1.0]]))
464
+ True
465
+ >>> cavity_stability(np.array([[3.0, 0.0], [0.0, 3.0]]))
466
+ False
467
+ """
468
+ M = np.asarray(M, dtype=float)
469
+ return bool(abs(np.trace(M)) <= 2.0)
File without changes
@@ -0,0 +1,38 @@
1
+ """Animation smoke + sanity tests for physicskit.optics.visualizers.animate_diffraction_propagation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import numpy as np
6
+ import pytest
7
+ from matplotlib.animation import PillowWriter
8
+
9
+ from physicskit.optics.visualizers import animate_diffraction_propagation
10
+ from physicskit.optics.wave import angular_spectrum_propagate, double_slit_aperture, intensity
11
+
12
+
13
+ @pytest.mark.slow
14
+ def test_diffraction_propagation_animation(tmp_path):
15
+ dx = 1e-3
16
+ ap = double_slit_aperture((64, 64), dx=dx, width=2e-3, separation=1e-2).astype(complex)
17
+ z_values = np.linspace(0.01, 2.0, 6)
18
+ anim = animate_diffraction_propagation(ap, wavelength=0.5e-3, z_values=z_values, dx=dx)
19
+ out = tmp_path / "diffraction.gif"
20
+ anim.save(out, writer=PillowWriter(fps=10))
21
+ assert out.exists() and out.stat().st_size > 0
22
+
23
+
24
+ def test_pattern_widens_as_z_increases_matching_angular_spectrum_directly():
25
+ """The animation just steps angular_spectrum_propagate over z -- sanity-check that the
26
+ interference pattern's spatial extent (its second moment) actually grows with z, as
27
+ expected of a diverging diffraction pattern developing from the near to the far field."""
28
+ dx = 1e-3
29
+ ap = double_slit_aperture((64, 64), dx=dx, width=2e-3, separation=1e-2).astype(complex)
30
+ z_small, z_large = 0.05, 3.0
31
+ x = (np.arange(64) - 32) * dx
32
+ X, _ = np.meshgrid(x, x)
33
+
34
+ def spread(z):
35
+ I = intensity(angular_spectrum_propagate(ap, wavelength=0.5e-3, z=z, dx=dx))
36
+ return np.sqrt(np.sum(I * X**2) / np.sum(I))
37
+
38
+ assert spread(z_large) > spread(z_small)
@@ -0,0 +1,170 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.optics.gaussian import (
5
+ GaussianBeam,
6
+ hermite_gaussian_mode,
7
+ laguerre_gaussian_mode,
8
+ m2_beam_waist,
9
+ propagate_q,
10
+ q_to_beam_params,
11
+ )
12
+
13
+
14
+ def _free_space(d):
15
+ return np.array([[1.0, d], [0.0, 1.0]])
16
+
17
+
18
+ def _thin_lens(f):
19
+ return np.array([[1.0, 0.0], [-1.0 / f, 1.0]])
20
+
21
+
22
+ def test_propagate_q_free_space_shifts_real_part():
23
+ q0 = 1j * 2.0
24
+ M = _free_space(3.0)
25
+ q1 = propagate_q(q0, M)
26
+ assert q1 == pytest.approx(3.0 + 2.0j)
27
+
28
+
29
+ def test_propagate_q_thin_lens_leaves_flat_wavefront_flat():
30
+ # A collimated (plane-wave, R=inf) beam is represented by q real & huge;
31
+ # instead check a lens leaves an on-axis waist's q consistent with 1/q - 1/f rule.
32
+ q0 = 1j * 5.0
33
+ f = 10.0
34
+ q1 = propagate_q(q0, _thin_lens(f))
35
+ assert 1.0 / q1 == pytest.approx(1.0 / q0 - 1.0 / f)
36
+
37
+
38
+ def test_q_to_beam_params_at_waist_gives_infinite_radius_of_curvature():
39
+ zR = 4.0
40
+ wavelength = 1.0e-3
41
+ w0 = np.sqrt(wavelength * zR / np.pi)
42
+ w, R = q_to_beam_params(1j * zR, wavelength)
43
+ assert R == np.inf
44
+ assert w == pytest.approx(w0)
45
+
46
+
47
+ def test_symmetric_two_lens_imaging_system_reproduces_object_waist():
48
+ # Symmetric imaging system built directly from the ABCD matrices given
49
+ # in the house convention: free_space(f) -> lens(f) -> free_space(f) ->
50
+ # lens(f) -> free_space(f), starting from a beam waist at the object
51
+ # plane. This should reproduce the object-plane waist size at the image
52
+ # plane (unit magnification, by symmetry).
53
+ wavelength = 0.5e-3
54
+ w0 = 0.1
55
+ f = 50.0
56
+
57
+ beam = GaussianBeam(wavelength=wavelength, w0=w0, z0=0.0)
58
+ q0 = beam.q_parameter(0.0)
59
+
60
+ M = _free_space(f) @ _thin_lens(f) @ _free_space(f) @ _thin_lens(f) @ _free_space(f)
61
+ q_image = propagate_q(q0, M)
62
+ w_image, _ = q_to_beam_params(q_image, wavelength)
63
+
64
+ assert w_image == pytest.approx(w0, rel=1e-6)
65
+
66
+
67
+ def test_waist_at_z0_equals_w0_exactly():
68
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.2, z0=1.5)
69
+ assert beam.waist(beam.z0) == pytest.approx(0.2)
70
+ assert beam.waist(beam.z0) == 0.2
71
+
72
+
73
+ def test_radius_of_curvature_is_infinite_at_the_waist():
74
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.2, z0=1.5)
75
+ assert beam.radius_of_curvature(beam.z0) == np.inf
76
+
77
+
78
+ def test_radius_of_curvature_far_from_waist_approaches_z_minus_z0():
79
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.05, z0=0.0)
80
+ z = 1.0e6 # z >> zR, so R(z) -> z - z0
81
+ assert beam.radius_of_curvature(z) == pytest.approx(z, rel=1e-3)
82
+
83
+
84
+ def test_radius_of_curvature_accepts_an_array_of_positions():
85
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.2, z0=1.5)
86
+ z = np.array([1.5, 1.0e6])
87
+ R = beam.radius_of_curvature(z)
88
+ assert isinstance(R, np.ndarray)
89
+ assert R[0] == np.inf
90
+ assert R[1] == pytest.approx(z[1], rel=1e-3)
91
+
92
+
93
+ def test_divergence_angle_matches_far_field_waist_slope():
94
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.02, z0=0.0)
95
+ z_far = 1000.0 * beam.rayleigh_range
96
+ slope = beam.waist(z_far) / (z_far - beam.z0)
97
+ assert slope == pytest.approx(beam.divergence_angle, rel=1e-3)
98
+
99
+
100
+ def test_gouy_phase_is_zero_at_waist_and_approaches_pi_over_2_far_away():
101
+ beam = GaussianBeam(wavelength=1.0e-3, w0=0.05, z0=0.0)
102
+ assert beam.gouy_phase(0.0) == pytest.approx(0.0)
103
+ assert beam.gouy_phase(1.0e6) == pytest.approx(np.pi / 2.0, abs=1e-3)
104
+
105
+
106
+ def test_hermite_gaussian_mode_shape_and_fundamental_matches_gaussian_beam():
107
+ beam = GaussianBeam(wavelength=1.0, w0=1.0, z0=0.0)
108
+ x = np.linspace(-2, 2, 9)
109
+ y = np.linspace(-2, 2, 9)
110
+ X, Y = np.meshgrid(x, y)
111
+ field = hermite_gaussian_mode(X, Y, 0.0, beam, 0, 0)
112
+ assert field.shape == X.shape
113
+ assert np.iscomplexobj(field)
114
+ expected_intensity = np.exp(-2.0 * (X**2 + Y**2) / beam.waist(0.0) ** 2)
115
+ assert np.allclose(np.abs(field) ** 2, expected_intensity)
116
+
117
+
118
+ def test_hermite_gaussian_mode_gouy_phase_scales_with_mode_order():
119
+ # Use x=1 (rather than the axis) so H_0(sqrt2 x/w) and H_1(sqrt2 x/w)
120
+ # are both strictly positive real numbers, isolating the Gouy phase
121
+ # contribution (m+n+1)*zeta(z) from any sign flip of the real
122
+ # Hermite-polynomial amplitude factor.
123
+ beam = GaussianBeam(wavelength=1.0, w0=1.0, z0=0.0)
124
+ z = 3.0 * beam.rayleigh_range
125
+ u00 = hermite_gaussian_mode(1.0, 0.0, z, beam, 0, 0)
126
+ u10 = hermite_gaussian_mode(1.0, 0.0, z, beam, 1, 0)
127
+ phase_diff = np.angle(u10) - np.angle(u00)
128
+ expected = beam.gouy_phase(z) # (1+0+1)*zeta - (0+0+1)*zeta = zeta
129
+ assert phase_diff == pytest.approx(expected, abs=1e-8)
130
+
131
+
132
+ def test_laguerre_gaussian_mode_shape_and_ring_structure():
133
+ beam = GaussianBeam(wavelength=1.0, w0=1.0, z0=0.0)
134
+ r = np.linspace(0, 3, 50)
135
+ phi = np.zeros_like(r)
136
+ field = laguerre_gaussian_mode(r, phi, 0.0, beam, l=1, p=0)
137
+ assert field.shape == r.shape
138
+ # LG_0^1 has a phase singularity (zero amplitude) on-axis
139
+ assert abs(field[0]) == pytest.approx(0.0, abs=1e-12)
140
+ assert np.max(np.abs(field)) > 0.0
141
+
142
+
143
+ def test_laguerre_gaussian_mode_orbital_phase_winds_by_2pi_l():
144
+ beam = GaussianBeam(wavelength=1.0, w0=1.0, z0=0.0)
145
+ r = np.full(9, 0.5)
146
+ phi = np.linspace(0.0, 2 * np.pi, 9, endpoint=False)
147
+ field = laguerre_gaussian_mode(r, phi, 0.0, beam, l=2, p=0)
148
+ phase = np.unwrap(np.angle(field))
149
+ total_winding = phase[-1] + (phase[-1] - phase[-2]) - phase[0]
150
+ assert np.round(total_winding / (2 * np.pi)) == pytest.approx(2.0, abs=0.2)
151
+
152
+
153
+ def test_m2_beam_waist_reduces_to_ideal_at_M2_equals_1():
154
+ wavelength = 1.0e-3
155
+ w0 = 0.1
156
+ z = np.linspace(-5, 5, 11)
157
+ beam = GaussianBeam(wavelength=wavelength, w0=w0, z0=0.0)
158
+ ideal = beam.waist(z)
159
+ m2 = m2_beam_waist(z, wavelength, w0, M2=1.0)
160
+ assert np.allclose(ideal, m2)
161
+
162
+
163
+ def test_m2_beam_waist_diverges_faster_for_larger_M2():
164
+ wavelength = 1.0e-3
165
+ w0 = 0.1
166
+ z = 10.0
167
+ w_ideal = m2_beam_waist(z, wavelength, w0, M2=1.0)
168
+ w_poor = m2_beam_waist(z, wavelength, w0, M2=3.0)
169
+ assert w_poor > w_ideal
170
+ assert m2_beam_waist(0.0, wavelength, w0, M2=3.0) == pytest.approx(w0)