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,304 @@
1
+ """Point-vortex N-body dynamics: the Biot-Savart law and the von Karman vortex street.
2
+
3
+ Concentrating all of a 2D flow's vorticity into a finite set of
4
+ infinitesimal points turns the vorticity-transport PDE into a system of
5
+ ordinary differential equations: each point vortex simply advects with the
6
+ velocity field induced by every *other* point vortex (a vortex induces no
7
+ velocity on itself), via the 2D Biot-Savart law. This is an exact solution
8
+ of the incompressible Euler equations for a singular vorticity distribution,
9
+ not merely a discretization of the continuous vorticity-transport equation
10
+ solved by :mod:`physicskit.fluids.systems.navier_stokes` -- and it is the
11
+ setting in which Theodore von Karman analyzed the staggered, alternating
12
+ vortex street shed behind a bluff body.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import numpy as np
18
+ from numba import njit
19
+ from numpy.typing import ArrayLike, NDArray
20
+
21
+ from physicskit.fluids.exceptions import InvalidParameterError
22
+
23
+ __all__ = [
24
+ "point_vortex_velocities",
25
+ "PointVortexSystem",
26
+ "von_karman_vortex_street",
27
+ "VON_KARMAN_SPACING_RATIO",
28
+ ]
29
+
30
+ #: Theodore von Karman's spacing ratio ``h/l`` (row separation over
31
+ #: along-row spacing) at which a doubly-infinite staggered vortex row is
32
+ #: linearly stable to vortex-row perturbations (von Karman & Rubach, 1912).
33
+ VON_KARMAN_SPACING_RATIO: float = 0.2805
34
+
35
+
36
+ @njit(cache=True)
37
+ def _point_vortex_velocities(positions: NDArray[np.float64], circulations: NDArray[np.float64], core: float) -> NDArray[np.float64]:
38
+ """Biot-Savart velocity induced on each point vortex by every other one.
39
+
40
+ Parameters
41
+ ----------
42
+ positions : ndarray of float, shape (n, 2)
43
+ Vortex positions ``(x, y)``.
44
+ circulations : ndarray of float, shape (n,)
45
+ Vortex circulations :math:`\\Gamma_i`.
46
+ core : float
47
+ Regularization (Rankine core) radius; keeps the induced velocity
48
+ finite if two vortices coincide.
49
+
50
+ Returns
51
+ -------
52
+ ndarray of float, shape (n, 2)
53
+ Velocity at each vortex's own position.
54
+ """
55
+ n = positions.shape[0]
56
+ velocities = np.zeros((n, 2))
57
+ core2 = core * core
58
+ for i in range(n):
59
+ vx, vy = 0.0, 0.0
60
+ xi, yi = positions[i, 0], positions[i, 1]
61
+ for j in range(n):
62
+ if i == j:
63
+ continue
64
+ dx = xi - positions[j, 0]
65
+ dy = yi - positions[j, 1]
66
+ r2 = dx * dx + dy * dy + core2
67
+ factor = circulations[j] / (2.0 * np.pi * r2)
68
+ vx -= factor * dy
69
+ vy += factor * dx
70
+ velocities[i, 0] = vx
71
+ velocities[i, 1] = vy
72
+ return velocities
73
+
74
+
75
+ @njit(cache=True)
76
+ def _point_vortex_rk4_integrate(
77
+ positions0: NDArray[np.float64], circulations: NDArray[np.float64], core: float, dt: float, n_steps: int
78
+ ) -> NDArray[np.float64]:
79
+ """Integrate the point-vortex system with RK4.
80
+
81
+ Parameters
82
+ ----------
83
+ positions0 : ndarray of float, shape (n, 2)
84
+ Initial vortex positions.
85
+ circulations : ndarray of float, shape (n,)
86
+ Vortex circulations.
87
+ core : float
88
+ Regularization core radius.
89
+ dt : float
90
+ Time step.
91
+ n_steps : int
92
+ Number of steps to advance.
93
+
94
+ Returns
95
+ -------
96
+ ndarray of float, shape (n_steps + 1, n, 2)
97
+ Vortex positions at each step, including the initial positions.
98
+ """
99
+ n = positions0.shape[0]
100
+ trajectory = np.empty((n_steps + 1, n, 2))
101
+ trajectory[0] = positions0
102
+ positions = positions0.copy()
103
+ for step in range(n_steps):
104
+ k1 = _point_vortex_velocities(positions, circulations, core)
105
+ k2 = _point_vortex_velocities(positions + 0.5 * dt * k1, circulations, core)
106
+ k3 = _point_vortex_velocities(positions + 0.5 * dt * k2, circulations, core)
107
+ k4 = _point_vortex_velocities(positions + dt * k3, circulations, core)
108
+ positions = positions + (dt / 6.0) * (k1 + 2.0 * k2 + 2.0 * k3 + k4)
109
+ trajectory[step + 1] = positions
110
+ return trajectory
111
+
112
+
113
+ def point_vortex_velocities(positions: ArrayLike, circulations: ArrayLike, core: float = 1e-6) -> NDArray[np.float64]:
114
+ """Velocity induced on each point vortex by every other vortex (Biot-Savart law).
115
+
116
+ The 2D Biot-Savart law: an isolated point vortex of circulation
117
+ :math:`\\Gamma_j` at :math:`\\mathbf{r}_j` induces a purely azimuthal
118
+ velocity :math:`\\Gamma_j/(2\\pi r)` at distance `r`, and a system of `n`
119
+ vortices simply superposes these fields (Laplace's equation is linear,
120
+ just as in :mod:`physicskit.fluids.systems.potential_flow` -- indeed a
121
+ point vortex here *is* :func:`~physicskit.fluids.systems.potential_flow.point_vortex_potential`,
122
+ evaluated at every other vortex's location rather than on a field grid).
123
+
124
+ Parameters
125
+ ----------
126
+ positions : array_like of float, shape (n, 2)
127
+ Vortex positions ``(x, y)``.
128
+ circulations : array_like of float, shape (n,)
129
+ Vortex circulations :math:`\\Gamma_i` (positive counterclockwise).
130
+ core : float, default 1e-6
131
+ Regularization (Rankine core) radius, keeping the induced velocity
132
+ finite if two vortex cores coincide; negligible for any pair
133
+ separated by much more than `core`.
134
+
135
+ Returns
136
+ -------
137
+ ndarray of float, shape (n, 2)
138
+ Velocity at each vortex's own position, induced by all the others.
139
+
140
+ Examples
141
+ --------
142
+ A pair of counter-rotating vortices separated by distance `d` induces
143
+ equal and opposite velocities of magnitude :math:`\\Gamma/(2\\pi d)` on
144
+ each other, translating the pair sideways rather than rotating it:
145
+
146
+ >>> import numpy as np
147
+ >>> Gamma, d = 1.0, 2.0
148
+ >>> positions = np.array([[0.0, d / 2], [0.0, -d / 2]])
149
+ >>> circulations = np.array([Gamma, -Gamma])
150
+ >>> vel = point_vortex_velocities(positions, circulations)
151
+ >>> bool(np.allclose(vel[0], vel[1])) # both translate together
152
+ True
153
+ >>> round(float(vel[0, 0]), 6) == round(Gamma / (2 * np.pi * d), 6)
154
+ True
155
+ """
156
+ positions = np.asarray(positions, dtype=np.float64)
157
+ circulations = np.asarray(circulations, dtype=np.float64)
158
+ if positions.shape[0] != circulations.shape[0]:
159
+ raise InvalidParameterError("positions and circulations must have the same number of vortices")
160
+ return _point_vortex_velocities(positions, circulations, core)
161
+
162
+
163
+ class PointVortexSystem:
164
+ """A system of interacting 2D point vortices, advected by their mutual Biot-Savart field.
165
+
166
+ Parameters
167
+ ----------
168
+ positions : array_like of float, shape (n, 2)
169
+ Initial vortex positions ``(x, y)``.
170
+ circulations : array_like of float, shape (n,)
171
+ Vortex circulations :math:`\\Gamma_i`; fixed for the system's lifetime
172
+ (point vortices carry their circulation with them unchanged).
173
+ core : float, default 1e-6
174
+ Regularization core radius (see :func:`point_vortex_velocities`).
175
+
176
+ Attributes
177
+ ----------
178
+ positions : ndarray of float, shape (n, 2)
179
+ Current vortex positions.
180
+ circulations : ndarray of float, shape (n,)
181
+ Vortex circulations.
182
+ core : float
183
+ Regularization core radius.
184
+ n_vortices : int
185
+ Number of vortices, `n`.
186
+
187
+ Raises
188
+ ------
189
+ InvalidParameterError
190
+ If `positions` and `circulations` have inconsistent lengths.
191
+
192
+ Examples
193
+ --------
194
+ >>> import numpy as np
195
+ >>> system = PointVortexSystem(positions=[[1.0, 0.0], [-1.0, 0.0]], circulations=[1.0, 1.0])
196
+ >>> t, trajectory = system.trajectory(dt=0.01, n_steps=10)
197
+ >>> trajectory.shape
198
+ (11, 2, 2)
199
+ """
200
+
201
+ def __init__(self, positions: ArrayLike, circulations: ArrayLike, core: float = 1e-6):
202
+ positions = np.asarray(positions, dtype=np.float64)
203
+ circulations = np.asarray(circulations, dtype=np.float64)
204
+ if positions.shape[0] != circulations.shape[0]:
205
+ raise InvalidParameterError("positions and circulations must have the same number of vortices")
206
+ self.positions = positions
207
+ self.circulations = circulations
208
+ self.core = float(core)
209
+ self.n_vortices = positions.shape[0]
210
+
211
+ def velocities(self) -> NDArray[np.float64]:
212
+ """Velocity currently induced on each vortex.
213
+
214
+ Returns
215
+ -------
216
+ ndarray of float, shape (n, 2)
217
+ """
218
+ return _point_vortex_velocities(self.positions, self.circulations, self.core)
219
+
220
+ def trajectory(self, dt: float = 0.01, n_steps: int = 1000) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
221
+ """Integrate the vortex trajectories forward with RK4.
222
+
223
+ Parameters
224
+ ----------
225
+ dt : float, default 0.01
226
+ Time step.
227
+ n_steps : int, default 1000
228
+ Number of steps to advance.
229
+
230
+ Returns
231
+ -------
232
+ times : ndarray of float, shape (n_steps + 1,)
233
+ positions : ndarray of float, shape (n_steps + 1, n, 2)
234
+ Vortex positions at each step, including the current positions.
235
+ """
236
+ times = np.arange(n_steps + 1) * dt
237
+ trajectory = _point_vortex_rk4_integrate(self.positions, self.circulations, self.core, dt, n_steps)
238
+ return times, trajectory
239
+
240
+ def __repr__(self) -> str:
241
+ return f"PointVortexSystem(n_vortices={self.n_vortices}, circulations={self.circulations!r})"
242
+
243
+
244
+ def von_karman_vortex_street(
245
+ n_pairs: int, spacing_l: float, spacing_ratio: float = VON_KARMAN_SPACING_RATIO
246
+ ) -> tuple[NDArray[np.float64], NDArray[np.float64]]:
247
+ """Build a finite, doubly staggered von Karman vortex street.
248
+
249
+ Two parallel rows of point vortices, offset from each other by half the
250
+ along-row spacing `spacing_l` and by a row separation
251
+ ``h = spacing_ratio * spacing_l``, with alternating-sign circulation
252
+ within each row so that adjacent vortices across the two rows always
253
+ have *opposite* sign -- the staggered, alternating pattern observed in
254
+ the wake behind a bluff body. Von Karman showed that a doubly *infinite*
255
+ row of this form is linearly stable to vortex-row perturbations at
256
+ exactly one spacing ratio, :data:`VON_KARMAN_SPACING_RATIO`; any other
257
+ ratio (or any symmetric, unstaggered arrangement) grows unstable, which
258
+ is why the observed spacing behind real bluff bodies clusters so
259
+ consistently near this value.
260
+
261
+ Parameters
262
+ ----------
263
+ n_pairs : int
264
+ Number of vortex pairs (one from each row) to construct; must be at
265
+ least 2 to form a discernible street.
266
+ spacing_l : float
267
+ Spacing between same-row, same-sign vortices along the street.
268
+ spacing_ratio : float, default :data:`VON_KARMAN_SPACING_RATIO`
269
+ Row separation over along-row spacing, ``h / spacing_l``.
270
+
271
+ Returns
272
+ -------
273
+ positions : ndarray of float, shape (2*n_pairs, 2)
274
+ Vortex positions.
275
+ circulations : ndarray of float, shape (2*n_pairs,)
276
+ Vortex circulations, alternating in sign.
277
+
278
+ Raises
279
+ ------
280
+ InvalidParameterError
281
+ If `n_pairs` is smaller than 2 or `spacing_l` is not positive.
282
+
283
+ Examples
284
+ --------
285
+ >>> positions, circulations = von_karman_vortex_street(n_pairs=6, spacing_l=1.0)
286
+ >>> positions.shape
287
+ (12, 2)
288
+ >>> bool(np.isclose(circulations.sum(), 0.0)) # equal and opposite in each row
289
+ True
290
+ """
291
+ if n_pairs < 2:
292
+ raise InvalidParameterError(f"n_pairs must be at least 2, got {n_pairs}")
293
+ if spacing_l <= 0:
294
+ raise InvalidParameterError(f"spacing_l must be positive, got {spacing_l}")
295
+ h = spacing_ratio * spacing_l
296
+ positions = np.empty((2 * n_pairs, 2))
297
+ circulations = np.empty(2 * n_pairs)
298
+ for i in range(n_pairs):
299
+ sign = 1.0 if i % 2 == 0 else -1.0
300
+ positions[2 * i] = [i * spacing_l, h / 2.0]
301
+ circulations[2 * i] = sign
302
+ positions[2 * i + 1] = [(i + 0.5) * spacing_l, -h / 2.0]
303
+ circulations[2 * i + 1] = -sign
304
+ return positions, circulations
File without changes
@@ -0,0 +1,7 @@
1
+ """Test-session setup: force Matplotlib's non-interactive Agg backend
2
+ before anything imports pyplot, so the suite (and CI, which has no
3
+ display) can exercise the plotting/animation code headlessly."""
4
+
5
+ import matplotlib
6
+
7
+ matplotlib.use("Agg")
@@ -0,0 +1,66 @@
1
+ import pytest
2
+
3
+ from physicskit.fluids.exceptions import InvalidParameterError
4
+ from physicskit.fluids.systems.compressible_flow import (
5
+ normal_shock_relations,
6
+ rankine_hugoniot_jump_conditions,
7
+ sod_shock_tube,
8
+ )
9
+
10
+
11
+ def test_normal_shock_is_identity_at_mach_one():
12
+ jump = normal_shock_relations(M1=1.0)
13
+ assert jump["p2_p1"] == pytest.approx(1.0)
14
+ assert jump["rho2_rho1"] == pytest.approx(1.0)
15
+ assert jump["M2"] == pytest.approx(1.0)
16
+
17
+
18
+ def test_normal_shock_downstream_mach_is_always_subsonic():
19
+ for M1 in (1.5, 2.0, 5.0, 10.0):
20
+ jump = normal_shock_relations(M1)
21
+ assert jump["M2"] < 1.0
22
+
23
+
24
+ def test_normal_shock_satisfies_rankine_hugoniot_conditions():
25
+ gamma = 1.4
26
+ rho1, u1, p1 = 1.0, 3.0, 1.0 / gamma # c1 = 1, so M1 = u1
27
+ jump = normal_shock_relations(M1=u1, gamma=gamma)
28
+ rho2 = rho1 * jump["rho2_rho1"]
29
+ u2 = rho1 * u1 / rho2
30
+ p2 = p1 * jump["p2_p1"]
31
+ residuals = rankine_hugoniot_jump_conditions(rho1, u1, p1, rho2, u2, p2)
32
+ assert max(abs(v) for v in residuals.values()) < 1e-10
33
+
34
+
35
+ def test_normal_shock_rejects_subsonic_upstream():
36
+ with pytest.raises(InvalidParameterError):
37
+ normal_shock_relations(M1=0.5)
38
+
39
+
40
+ def test_sod_shock_tube_conserves_left_and_right_far_field_states():
41
+ result = sod_shock_tube(nx=200, t_final=0.15)
42
+ assert result["rho"][0] == pytest.approx(1.0, abs=1e-2)
43
+ assert result["p"][0] == pytest.approx(1.0, abs=1e-2)
44
+ assert result["rho"][-1] == pytest.approx(0.125, abs=1e-2)
45
+ assert result["p"][-1] == pytest.approx(0.1, abs=1e-2)
46
+
47
+
48
+ def test_sod_shock_tube_density_is_monotonically_decreasing_left_to_right():
49
+ """The Sod problem's three waves (rarefaction, contact, shock) all
50
+ connect the high state to the low state without an intermediate reversal."""
51
+ result = sod_shock_tube(nx=200, t_final=0.15)
52
+ # allow small non-monotonicity from numerical diffusion by smoothing lightly
53
+ rho = result["rho"]
54
+ assert rho[0] > rho[len(rho) // 2] > rho[-1]
55
+
56
+
57
+ def test_sod_shock_tube_rejects_invalid_cfl():
58
+ with pytest.raises(InvalidParameterError):
59
+ sod_shock_tube(nx=100, cfl=1.5)
60
+
61
+
62
+ def test_sod_shock_tube_rejects_too_few_cells_and_nonpositive_t_final():
63
+ with pytest.raises(InvalidParameterError):
64
+ sod_shock_tube(nx=3)
65
+ with pytest.raises(InvalidParameterError):
66
+ sod_shock_tube(nx=100, t_final=0.0)
@@ -0,0 +1,115 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.fluids.exceptions import InvalidParameterError
5
+ from physicskit.fluids.systems.instabilities import (
6
+ kelvin_helmholtz_growth_rate,
7
+ kelvin_helmholtz_ic,
8
+ rayleigh_taylor_growth_rate,
9
+ rayleigh_taylor_ic,
10
+ simulate_kelvin_helmholtz,
11
+ simulate_rayleigh_taylor,
12
+ )
13
+ from physicskit.fluids.systems.navier_stokes import simulate_vorticity_streamfunction
14
+
15
+
16
+ def test_kelvin_helmholtz_growth_rate_is_linear_in_k_and_delta_u():
17
+ assert kelvin_helmholtz_growth_rate(k=2.0, delta_u=3.0) == pytest.approx(3.0)
18
+ assert kelvin_helmholtz_growth_rate(k=4.0, delta_u=3.0) == pytest.approx(6.0)
19
+
20
+
21
+ @pytest.mark.slow
22
+ def test_kelvin_helmholtz_shear_layer_perturbation_grows():
23
+ n = 96
24
+ omega0 = kelvin_helmholtz_ic(n, 2 * np.pi, shear_width=0.1, perturbation_amplitude=0.05)
25
+ amps = []
26
+ omega = omega0.copy()
27
+ for _ in range(3):
28
+ result = simulate_vorticity_streamfunction(omega, nu=0.001, dt=0.0025, steps=100, length=2 * np.pi)
29
+ omega = result["omega"]
30
+ dev = omega - omega.mean(axis=0, keepdims=True)
31
+ amps.append(np.sqrt(np.mean(dev**2)))
32
+ assert amps[-1] > 2 * amps[0]
33
+
34
+
35
+ def test_simulate_kelvin_helmholtz_matches_direct_vorticity_streamfunction_call():
36
+ n, length = 64, 2 * np.pi
37
+ omega0 = kelvin_helmholtz_ic(n, length, shear_width=0.1, perturbation_amplitude=0.05)
38
+ result_wrapper = simulate_kelvin_helmholtz(omega0, nu=0.001, dt=0.0025, steps=50, length=length)
39
+ result_direct = simulate_vorticity_streamfunction(omega0, nu=0.001, dt=0.0025, steps=50, length=length)
40
+ assert result_wrapper["omega"] == pytest.approx(result_direct["omega"])
41
+
42
+
43
+ @pytest.mark.slow
44
+ def test_simulate_kelvin_helmholtz_shear_layer_rolls_up():
45
+ n, length = 96, 2 * np.pi
46
+ omega0 = kelvin_helmholtz_ic(n, length, shear_width=0.1, perturbation_amplitude=0.05)
47
+ omega = omega0.copy()
48
+ amps = []
49
+ for _ in range(3):
50
+ result = simulate_kelvin_helmholtz(omega, nu=0.001, dt=0.0025, steps=100, length=length)
51
+ omega = result["omega"]
52
+ dev = omega - omega.mean(axis=0, keepdims=True)
53
+ amps.append(np.sqrt(np.mean(dev**2)))
54
+ assert amps[-1] > 2 * amps[0]
55
+
56
+
57
+ def test_simulate_kelvin_helmholtz_rejects_nonpositive_viscosity():
58
+ omega0 = kelvin_helmholtz_ic(32, 2 * np.pi, shear_width=0.1, perturbation_amplitude=0.05)
59
+ with pytest.raises(InvalidParameterError):
60
+ simulate_kelvin_helmholtz(omega0, nu=-0.001, dt=0.0025, steps=1, length=2 * np.pi)
61
+
62
+
63
+ def test_rayleigh_taylor_growth_rate_matches_sqrt_agk():
64
+ A, g, k = 0.3, 9.8, 2.0
65
+ assert rayleigh_taylor_growth_rate(k, A, g) == pytest.approx(np.sqrt(A * g * k))
66
+
67
+
68
+ def test_rayleigh_taylor_growth_rate_rejects_invalid_atwood_number():
69
+ with pytest.raises(InvalidParameterError):
70
+ rayleigh_taylor_growth_rate(k=1.0, atwood_number=1.5, g=1.0)
71
+
72
+
73
+ def test_rayleigh_taylor_growth_rate_rejects_negative_k():
74
+ with pytest.raises(InvalidParameterError):
75
+ rayleigh_taylor_growth_rate(k=-1.0, atwood_number=0.3, g=1.0)
76
+
77
+
78
+ def test_rayleigh_taylor_ic_rejects_invalid_atwood_number():
79
+ with pytest.raises(InvalidParameterError):
80
+ rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=1.5)
81
+
82
+
83
+ def test_simulate_rayleigh_taylor_rejects_nonpositive_kappa_and_g():
84
+ omega0, buoyancy0 = rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=0.2)
85
+ with pytest.raises(InvalidParameterError):
86
+ simulate_rayleigh_taylor(omega0, buoyancy0, nu=0.001, kappa=0.0, g=1.0, dt=0.01, steps=1, length=2 * np.pi)
87
+ with pytest.raises(InvalidParameterError):
88
+ simulate_rayleigh_taylor(omega0, buoyancy0, nu=0.001, kappa=0.001, g=0.0, dt=0.01, steps=1, length=2 * np.pi)
89
+
90
+
91
+ def test_rayleigh_taylor_ic_starts_at_rest_with_heavy_fluid_on_top():
92
+ omega0, buoyancy0 = rayleigh_taylor_ic(64, 2 * np.pi, atwood_number=0.3)
93
+ assert np.all(omega0 == 0.0)
94
+ top_mean = buoyancy0[:, -1].mean()
95
+ bottom_mean = buoyancy0[:, 0].mean()
96
+ assert bottom_mean > 0 > top_mean # bottom is light (positive b), top is heavy
97
+
98
+
99
+ def test_rayleigh_taylor_interface_perturbation_grows_into_the_nonlinear_regime():
100
+ n, length = 64, 2 * np.pi
101
+ omega0, buoyancy0 = rayleigh_taylor_ic(n, length, atwood_number=0.3, perturbation_amplitude=0.01)
102
+ omega, buoyancy = omega0.copy(), buoyancy0.copy()
103
+ amps = []
104
+ for _ in range(10):
105
+ result = simulate_rayleigh_taylor(omega, buoyancy, nu=0.002, kappa=0.002, g=1.0, dt=0.01, steps=50, length=length)
106
+ omega, buoyancy = result["omega"], result["buoyancy"]
107
+ dev = buoyancy - buoyancy.mean(axis=0, keepdims=True)
108
+ amps.append(np.sqrt(np.mean(dev**2)))
109
+ assert amps[-1] > 3 * amps[0]
110
+
111
+
112
+ def test_simulate_rayleigh_taylor_rejects_nonpositive_parameters():
113
+ omega0, buoyancy0 = rayleigh_taylor_ic(32, 2 * np.pi, atwood_number=0.2)
114
+ with pytest.raises(InvalidParameterError):
115
+ simulate_rayleigh_taylor(omega0, buoyancy0, nu=-1.0, kappa=0.001, g=1.0, dt=0.01, steps=1, length=2 * np.pi)
@@ -0,0 +1,67 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.fluids.core.grid import spectral_grid, vorticity_from_velocity
5
+ from physicskit.fluids.exceptions import InvalidParameterError
6
+ from physicskit.fluids.systems.navier_stokes import NavierStokes2D, simulate_vorticity_streamfunction
7
+
8
+
9
+ def test_vorticity_from_velocity_matches_analytic_curl():
10
+ n, length = 48, 2 * np.pi
11
+ X, Y, KX, KY, K2 = spectral_grid(n, length)
12
+ u, v = np.sin(Y), np.zeros_like(X)
13
+ # omega = dv/dx - du/dy = 0 - cos(Y) = -cos(Y)
14
+ omega = vorticity_from_velocity(u, v, KX, KY)
15
+ np.testing.assert_allclose(omega, -np.cos(Y), atol=1e-10)
16
+
17
+
18
+ def test_viscous_decay_reduces_peak_vorticity():
19
+ n, length = 48, 2 * np.pi
20
+ X, Y, KX, KY, K2 = spectral_grid(n, length)
21
+ omega0 = np.sin(X) * np.sin(Y)
22
+ result = simulate_vorticity_streamfunction(omega0, nu=0.05, dt=0.01, steps=50, length=length)
23
+ assert np.max(np.abs(result["omega"])) < np.max(np.abs(omega0))
24
+
25
+
26
+ def test_navier_stokes_2d_class_matches_functional_api():
27
+ n, length, nu, dt, steps = 32, 2 * np.pi, 0.05, 0.01, 10
28
+ X, Y, KX, KY, K2 = spectral_grid(n, length)
29
+ omega0 = np.sin(X) * np.sin(Y)
30
+ solver = NavierStokes2D(n=n, length=length, nu=nu)
31
+ result_class = solver.simulate(omega0, dt=dt, steps=steps)
32
+ result_func = simulate_vorticity_streamfunction(omega0, nu=nu, dt=dt, steps=steps, length=length)
33
+ np.testing.assert_allclose(result_class["omega"], result_func["omega"], atol=1e-10)
34
+
35
+
36
+ def test_velocity_field_is_divergence_free():
37
+ """The streamfunction formulation guarantees div(u) = 0 identically;
38
+ check it numerically to catch any sign/index error in the spectral derivatives."""
39
+ n, length = 48, 2 * np.pi
40
+ solver = NavierStokes2D(n=n, length=length, nu=0.01)
41
+ omega0 = np.sin(2 * solver.X) * np.cos(solver.Y) + np.cos(solver.X)
42
+ u, v = solver.velocity(omega0)
43
+ u_hat, v_hat = np.fft.fft2(u), np.fft.fft2(v)
44
+ divergence = np.real(np.fft.ifft2(1j * solver.KX * u_hat + 1j * solver.KY * v_hat))
45
+ assert np.max(np.abs(divergence)) < 1e-8
46
+
47
+
48
+ def test_navier_stokes_rejects_nonpositive_viscosity():
49
+ with pytest.raises(InvalidParameterError):
50
+ NavierStokes2D(n=32, length=2 * np.pi, nu=0.0)
51
+ with pytest.raises(InvalidParameterError):
52
+ simulate_vorticity_streamfunction(np.zeros((16, 16)), nu=-0.1, dt=0.01, steps=1, length=2 * np.pi)
53
+
54
+
55
+ def test_spectral_grid_rejects_too_coarse_resolution():
56
+ with pytest.raises(InvalidParameterError):
57
+ spectral_grid(n=4, length=1.0)
58
+
59
+
60
+ def test_spectral_grid_rejects_nonpositive_length():
61
+ with pytest.raises(InvalidParameterError):
62
+ spectral_grid(n=32, length=0.0)
63
+
64
+
65
+ def test_navier_stokes_2d_repr():
66
+ solver = NavierStokes2D(n=32, length=2 * np.pi, nu=0.05)
67
+ assert repr(solver) == "NavierStokes2D(n=32, length=6.283185307179586, nu=0.05)"
@@ -0,0 +1,92 @@
1
+ import numpy as np
2
+ import pytest
3
+
4
+ from physicskit.fluids.exceptions import InvalidParameterError
5
+ from physicskit.fluids.systems.potential_flow import (
6
+ PotentialFlow,
7
+ flow_past_cylinder,
8
+ kutta_joukowski_lift,
9
+ pressure_coefficient,
10
+ )
11
+
12
+
13
+ def test_cylinder_surface_is_a_single_streamline():
14
+ """Uniform flow + doublet places a circular streamline exactly at the
15
+ doublet's radius; every point on that circle must share one streamfunction value."""
16
+ flow = flow_past_cylinder(U_inf=2.0, radius=1.5, circulation=3.0)
17
+ theta = np.linspace(0, 2 * np.pi, 37)
18
+ x, y = 1.5 * np.cos(theta), 1.5 * np.sin(theta)
19
+ psi = flow.streamfunction(x, y)
20
+ assert np.max(np.abs(psi - psi[0])) < 1e-6
21
+
22
+
23
+ def test_cylinder_surface_speed_matches_classic_formula():
24
+ """On a lift-free cylinder, the tangential surface speed is exactly
25
+ 2*U_inf*sin(theta) -- the textbook result, checked independently of the
26
+ velocity() finite-difference implementation via the surface Cp."""
27
+ U_inf, R = 1.0, 1.0
28
+ flow = flow_past_cylinder(U_inf=U_inf, radius=R, circulation=0.0)
29
+ theta = np.linspace(0.01, np.pi - 0.01, 25) # avoid stagnation points
30
+ x, y = R * np.cos(theta), R * np.sin(theta)
31
+ Cp = flow.pressure_coefficient(x, y)
32
+ expected_speed = 2.0 * U_inf * np.sin(theta)
33
+ expected_Cp = 1.0 - (expected_speed / U_inf) ** 2
34
+ np.testing.assert_allclose(Cp, expected_Cp, atol=1e-4)
35
+
36
+
37
+ def test_kutta_joukowski_lift_matches_cylinder_pressure_integral():
38
+ """Integrating Cp around the lifting cylinder's surface should reproduce
39
+ the Kutta-Joukowski lift, an independent cross-check of two different
40
+ ways to compute the same force."""
41
+ U_inf, R, rho, Gamma = 1.0, 1.0, 1.0, 1.0
42
+ flow = flow_past_cylinder(U_inf=U_inf, radius=R, circulation=Gamma)
43
+ theta = np.linspace(0, 2 * np.pi, 2000, endpoint=False)
44
+ x, y = R * np.cos(theta), R * np.sin(theta)
45
+ Cp = flow.pressure_coefficient(x, y)
46
+ p = Cp * (0.5 * rho * U_inf**2)
47
+ # Lift is -oint p * sin(theta) * R dtheta (normal force projected onto y);
48
+ # a plain Riemann sum over a uniform grid is exact for a periodic integrand.
49
+ dtheta = theta[1] - theta[0]
50
+ lift_numeric = -np.sum(p * np.sin(theta) * R) * dtheta
51
+ lift_theory = kutta_joukowski_lift(rho, U_inf, Gamma)
52
+ # the sign of the pressure-integral convention above is independent of
53
+ # the theorem itself; only the magnitude is being cross-checked here.
54
+ assert abs(lift_numeric) == pytest.approx(lift_theory, rel=0.02)
55
+
56
+
57
+ def test_flow_past_cylinder_rejects_nonpositive_radius():
58
+ with pytest.raises(InvalidParameterError):
59
+ flow_past_cylinder(U_inf=1.0, radius=0.0)
60
+
61
+
62
+ def test_flow_past_cylinder_rejects_nonpositive_u_inf():
63
+ with pytest.raises(InvalidParameterError):
64
+ flow_past_cylinder(U_inf=0.0, radius=1.0)
65
+
66
+
67
+ def test_pressure_coefficient_is_one_at_stagnation():
68
+ """Cp = 1 exactly where the local speed vanishes (a stagnation point)."""
69
+ assert pressure_coefficient(u=0.0, v=0.0, U_inf=1.0) == 1.0
70
+
71
+
72
+ def test_pressure_coefficient_rejects_nonpositive_u_inf():
73
+ with pytest.raises(InvalidParameterError):
74
+ pressure_coefficient(u=0.0, v=0.0, U_inf=0.0)
75
+
76
+
77
+ def test_kutta_joukowski_lift_rejects_nonpositive_rho_and_u_inf():
78
+ with pytest.raises(InvalidParameterError):
79
+ kutta_joukowski_lift(rho=0.0, U_inf=1.0, circulation=1.0)
80
+ with pytest.raises(InvalidParameterError):
81
+ kutta_joukowski_lift(rho=1.2, U_inf=0.0, circulation=1.0)
82
+
83
+
84
+ def test_source_and_sink_pair_has_zero_net_streamfunction_far_away():
85
+ """A source and an equal-strength sink placed symmetrically must produce
86
+ a flow that looks, from far away, like nothing at all (net source strength zero)."""
87
+ flow = PotentialFlow(U_inf=0.0)
88
+ flow.add_source(strength=5.0, x0=-0.1, y0=0.0)
89
+ flow.add_source(strength=-5.0, x0=0.1, y0=0.0)
90
+ far_x, far_y = 1000.0, 1000.0
91
+ u, v = flow.velocity(far_x, far_y)
92
+ assert np.hypot(u, v) < 1e-6