PyMHD 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.
- pymhd/__init__.py +31 -0
- pymhd/derivatives/TENO.py +278 -0
- pymhd/derivatives/WENO.py +323 -0
- pymhd/derivatives/__init__.py +24 -0
- pymhd/derivatives/compact.py +365 -0
- pymhd/derivatives/derivative.py +926 -0
- pymhd/numdiss.py +598 -0
- pymhd/plot/__init__.py +48 -0
- pymhd/plot/nd.py +1519 -0
- pymhd/plot/slc.py +648 -0
- pymhd/plot/spc.py +249 -0
- pymhd/preprocess/Athena.py +847 -0
- pymhd/preprocess/__init__.py +69 -0
- pymhd/preprocess/helper/NOTICE +42 -0
- pymhd/preprocess/helper/bin_convert.py +2000 -0
- pymhd/preprocess/helper/make_athdf.py +45 -0
- pymhd/spectra.py +376 -0
- pymhd/turbulence.py +917 -0
- pymhd-0.1.0.dist-info/METADATA +100 -0
- pymhd-0.1.0.dist-info/RECORD +22 -0
- pymhd-0.1.0.dist-info/WHEEL +4 -0
- pymhd-0.1.0.dist-info/licenses/LICENSE +21 -0
pymhd/plot/spc.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# PyMHD: Python for Magnetohydrodynamic Turbulence.
|
|
2
|
+
# Copyright (c) 2026 Yuyang Hua (华宇阳)
|
|
3
|
+
# License: MIT
|
|
4
|
+
|
|
5
|
+
"""
|
|
6
|
+
pymhd/plot/spc.py
|
|
7
|
+
-----------------
|
|
8
|
+
|
|
9
|
+
Plotting tools for turbulent energy spectra:
|
|
10
|
+
- plotShell(): plot shell-integrated spectra.
|
|
11
|
+
- plotAnisotropic(): plot anisotropic spectra along x, y, z.
|
|
12
|
+
- plotAxisymmetric(): plot axisymmetric spectra along Bx/Bz.
|
|
13
|
+
- plot(): plot spectra based on the type of the EnergySpectra object.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
from typing import Literal
|
|
20
|
+
|
|
21
|
+
import matplotlib.pyplot as plt
|
|
22
|
+
|
|
23
|
+
from matplotlib.axes import Axes
|
|
24
|
+
|
|
25
|
+
from ..spectra import EnergySpectra, Spectrum1D, get1D
|
|
26
|
+
|
|
27
|
+
# Font: Computer Modern
|
|
28
|
+
plt.rcParams['font.family'] = 'serif'
|
|
29
|
+
plt.rcParams['font.serif'] = ['cmr10']
|
|
30
|
+
plt.rcParams['mathtext.fontset'] = 'cm' # Computer Modern
|
|
31
|
+
plt.rcParams['axes.unicode_minus'] = False
|
|
32
|
+
plt.rcParams['axes.formatter.use_mathtext'] = True
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def plotShell(spc: EnergySpectra, outdir: Path) -> None:
|
|
36
|
+
"""Plot a single shell-integrated spectra.
|
|
37
|
+
|
|
38
|
+
Currently hard-coded to plot a k^{5/3}-compensated spectrum.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
spc : EnergySpectra, the energy spectra object.
|
|
43
|
+
outdir: Path, the output directory.
|
|
44
|
+
"""
|
|
45
|
+
fig, ax = plt.subplots(figsize=(8.0, 6.0))
|
|
46
|
+
slope = 5.0 / 3.0
|
|
47
|
+
|
|
48
|
+
kin1D = get1D(spc.totEkin, mode="shell")
|
|
49
|
+
k = kin1D.k
|
|
50
|
+
ax.loglog(k, kin1D.Ek * k**slope, label="Kinetic", lw=2.0, color="k")
|
|
51
|
+
|
|
52
|
+
if spc.totEmag is not None:
|
|
53
|
+
mag1D = get1D(spc.totEmag, mode="shell")
|
|
54
|
+
ax.loglog(k, mag1D.Ek * k**slope, label="Magnetic", lw=2.0, color="r")
|
|
55
|
+
|
|
56
|
+
ax.set_xlabel(r"$k$", fontsize=14)
|
|
57
|
+
ax.set_ylabel(r"$k^{5/3}E(k)$", fontsize=14)
|
|
58
|
+
ax.tick_params(axis="both", which="major", labelsize=12)
|
|
59
|
+
ax.tick_params(axis="both", which="minor", labelsize=10)
|
|
60
|
+
ax.grid(True, which="both", ls="--", alpha=0.4)
|
|
61
|
+
ax.legend(loc="lower left", fontsize=12)
|
|
62
|
+
fig.tight_layout()
|
|
63
|
+
fig.savefig(outdir / "shell.pdf", bbox_inches="tight")
|
|
64
|
+
plt.close(fig)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def plotAnisotropic(spc: EnergySpectra, outdir: Path) -> None:
|
|
68
|
+
"""Plot anisotropic spectra along x, y, z.
|
|
69
|
+
|
|
70
|
+
Currently hard-coded to plot a k^{3/2}-compensated spectrum for MRI-driven turbulence.
|
|
71
|
+
|
|
72
|
+
Two rows:
|
|
73
|
+
- first row : averaged spectra E(k_i) for i = x, y, z;
|
|
74
|
+
- second row: shell-integrated spectra E_i(|k|) for i = x, y, z.
|
|
75
|
+
"""
|
|
76
|
+
assert spc.totEmag is not None
|
|
77
|
+
assert spc.xEmag is not None
|
|
78
|
+
assert spc.yEmag is not None
|
|
79
|
+
assert spc.zEmag is not None
|
|
80
|
+
|
|
81
|
+
axes: tuple[Literal["x"], Literal["y"], Literal["z"]] = ("x", "y", "z")
|
|
82
|
+
Ekins = {"x": spc.xEkin, "y": spc.yEkin, "z": spc.zEkin}
|
|
83
|
+
Emags = {"x": spc.xEmag, "y": spc.yEmag, "z": spc.zEmag}
|
|
84
|
+
|
|
85
|
+
fig, axs = plt.subplots(2, 3, figsize=(18, 12), sharey="row")
|
|
86
|
+
slope = 3 / 2
|
|
87
|
+
|
|
88
|
+
# First row: E(k_i) for i = x, y, z.
|
|
89
|
+
for i, axis in enumerate(axes):
|
|
90
|
+
kin1D = get1D(spc.totEkin, mode="avg", axis=axis)
|
|
91
|
+
k = kin1D.k
|
|
92
|
+
axs[0, i].loglog(k, kin1D.Ek * k**slope, label=r"$E_{\mathrm{kin}}$", lw=2.5, color="k")
|
|
93
|
+
|
|
94
|
+
mag1D = get1D(spc.totEmag, mode="avg", axis=axis)
|
|
95
|
+
axs[0, i].loglog(k, mag1D.Ek * k**slope, label=r"$E_{\mathrm{mag}}$", lw=2.5, color="r")
|
|
96
|
+
|
|
97
|
+
axs[0, i].set_xlabel(r"$k_{%s}$" % axis)
|
|
98
|
+
axs[0, i].grid(True, which="both", ls="--", alpha=0.4)
|
|
99
|
+
axs[0, i].tick_params(axis="both", direction="in", which="both", pad=6.5)
|
|
100
|
+
|
|
101
|
+
axs[0, 0].set_ylabel(r"$k_i^{3/2} E(k_i)$")
|
|
102
|
+
for i in range(3):
|
|
103
|
+
axs[0, i].legend(loc="lower left", fontsize=14)
|
|
104
|
+
|
|
105
|
+
# Second row: shell-integrated spectra E_i(|k|) for i = x, y, z.
|
|
106
|
+
for i, axis in enumerate(axes):
|
|
107
|
+
|
|
108
|
+
shellEkin = get1D(Ekins[axis], mode="shell")
|
|
109
|
+
k, Ek = shellEkin.k, shellEkin.Ek
|
|
110
|
+
axs[1, i].loglog(k, Ek * k**slope, label=r"$E_{\mathrm{kin}}$", lw=2.5, color="k")
|
|
111
|
+
|
|
112
|
+
shellEmag = get1D(Emags[axis], mode="shell")
|
|
113
|
+
k, Ek = shellEmag.k, shellEmag.Ek
|
|
114
|
+
axs[1, i].loglog(k, Ek * k**slope, label=r"$E_{\mathrm{mag}}$", lw=2.5, color="r")
|
|
115
|
+
|
|
116
|
+
axs[1, i].set_xlabel(r"$k$")
|
|
117
|
+
axs[1, i].grid(True, which="both", ls="--", alpha=0.4)
|
|
118
|
+
axs[1, i].tick_params(axis="both", direction="in", which="both", pad=6.5)
|
|
119
|
+
axs[1, i].legend(loc="lower left", fontsize=14)
|
|
120
|
+
|
|
121
|
+
axs[1, 0].set_ylabel(r"$k^{3/2}E_i(k)$")
|
|
122
|
+
fig.tight_layout()
|
|
123
|
+
fig.savefig(outdir / "anisotropic.pdf", bbox_inches="tight")
|
|
124
|
+
plt.close(fig)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def plotAxisymmetric(spc: EnergySpectra, outdir: Path) -> None:
|
|
128
|
+
r"""Plot axisymmetric spectra for Afvénic turbulence.
|
|
129
|
+
|
|
130
|
+
The background field direction is derived from spc.type:
|
|
131
|
+
- spc.type == 'Bx' -> axis='x';
|
|
132
|
+
- spc.type == 'Bz' -> axis='z'.
|
|
133
|
+
|
|
134
|
+
Two rows:
|
|
135
|
+
- first row : k_\perp (left) and k_\parallel (right) spectra E(k_\perp)/E(k_\parallel);
|
|
136
|
+
- second row: shell-integrated spectra E_perp(|k|) and E_para(|k|).
|
|
137
|
+
"""
|
|
138
|
+
assert spc.totEmag is not None
|
|
139
|
+
assert spc.xEmag is not None
|
|
140
|
+
assert spc.yEmag is not None
|
|
141
|
+
assert spc.zEmag is not None
|
|
142
|
+
|
|
143
|
+
axismap: dict[Literal["Bx", "Bz"], Literal["x", "z"]] = {
|
|
144
|
+
"Bx": "x",
|
|
145
|
+
"Bz": "z",
|
|
146
|
+
}
|
|
147
|
+
if spc.type not in axismap:
|
|
148
|
+
raise ValueError(f"plotAxisymmetric requires Bx or Bz, got {spc.type!r}")
|
|
149
|
+
axis = axismap[spc.type]
|
|
150
|
+
|
|
151
|
+
# Component energy sums for the second row.
|
|
152
|
+
# perp: sum of the two directions perpendicular to the background field.
|
|
153
|
+
# para: the background-field direction.
|
|
154
|
+
if axis == "z":
|
|
155
|
+
perpEkin = spc.xEkin + spc.yEkin
|
|
156
|
+
paraEkin = spc.zEkin
|
|
157
|
+
perpEmag = spc.xEmag + spc.yEmag
|
|
158
|
+
paraEmag = spc.zEmag
|
|
159
|
+
else: # axis == "x"
|
|
160
|
+
perpEkin = spc.yEkin + spc.zEkin
|
|
161
|
+
paraEkin = spc.xEkin
|
|
162
|
+
perpEmag = spc.yEmag + spc.zEmag
|
|
163
|
+
paraEmag = spc.xEmag
|
|
164
|
+
|
|
165
|
+
shellPerpEkin = get1D(perpEkin, mode="shell")
|
|
166
|
+
shellParaEkin = get1D(paraEkin, mode="shell")
|
|
167
|
+
shellPerpEmag = get1D(perpEmag, mode="shell")
|
|
168
|
+
shellParaEmag = get1D(paraEmag, mode="shell")
|
|
169
|
+
|
|
170
|
+
fig, axs = plt.subplots(2, 2, figsize=(12.0, 10.0))
|
|
171
|
+
|
|
172
|
+
# ===== First row =====
|
|
173
|
+
# anisotropic spectra of total kinetic and magnetic energy
|
|
174
|
+
|
|
175
|
+
first: list[tuple[Literal["perp", "para"], float]] = [
|
|
176
|
+
("perp", 5.0 / 3.0),
|
|
177
|
+
("para", 2.0 )
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
for ax, (mode, slope) in zip(axs[0], first):
|
|
181
|
+
|
|
182
|
+
kin1D = get1D(spc.totEkin, mode=mode, axis=axis)
|
|
183
|
+
mag1D = get1D(spc.totEmag, mode=mode, axis=axis)
|
|
184
|
+
|
|
185
|
+
ax.loglog(kin1D.k, kin1D.Ek * kin1D.k**slope, label=r"$E_{\mathrm{kin}}$", lw=2.0, color="k")
|
|
186
|
+
ax.loglog(mag1D.k, mag1D.Ek * mag1D.k**slope, label=r"$E_{\mathrm{mag}}$", lw=2.0, color="r")
|
|
187
|
+
|
|
188
|
+
ax.grid(True, which="both", ls="--", alpha=0.4)
|
|
189
|
+
|
|
190
|
+
ticksize = 12
|
|
191
|
+
ax.tick_params(axis="both", which="major", direction="in", labelsize=ticksize)
|
|
192
|
+
ax.tick_params(axis="both", which="minor", direction="in", labelsize=ticksize - 1)
|
|
193
|
+
|
|
194
|
+
fontsize = 14
|
|
195
|
+
axs[0, 0].set_xlabel(r"$k_{\perp}$" , fontsize=fontsize)
|
|
196
|
+
axs[0, 0].set_ylabel(r"$k_{\perp}^{5/3} E(k_{\perp})$" , fontsize=fontsize)
|
|
197
|
+
axs[0, 1].set_xlabel(r"$k_{\parallel}$" , fontsize=fontsize)
|
|
198
|
+
axs[0, 1].set_ylabel(r"$k_{\parallel}^{2} E(k_{\parallel})$", fontsize=fontsize)
|
|
199
|
+
|
|
200
|
+
axs[0, 0].legend(loc="lower left", fontsize=12)
|
|
201
|
+
axs[0, 1].legend(loc="lower left", fontsize=12)
|
|
202
|
+
|
|
203
|
+
# ===== Second row =====
|
|
204
|
+
# shell-integrated spectra of perp/para components
|
|
205
|
+
|
|
206
|
+
ticksize = 12
|
|
207
|
+
slope = 5.0 / 3.0
|
|
208
|
+
second: list[tuple[Axes, Spectrum1D, Spectrum1D, str, str]] = [
|
|
209
|
+
(axs[1, 0], shellPerpEkin, shellPerpEmag, r"$k$", r"$k^{5/3} E_{\perp}(k)$"),
|
|
210
|
+
(axs[1, 1], shellParaEkin, shellParaEmag, r"$k$", r"$k^{5/3} E_{\parallel}(k)$"),
|
|
211
|
+
]
|
|
212
|
+
for ax, kin, mag, xlabel, ylabel in second:
|
|
213
|
+
|
|
214
|
+
ax.loglog(kin.k, kin.Ek * kin.k**slope, label=r"$E_{\mathrm{kin}}$", lw=2.0, color="k")
|
|
215
|
+
ax.loglog(mag.k, mag.Ek * mag.k**slope, label=r"$E_{\mathrm{mag}}$", lw=2.0, color="r")
|
|
216
|
+
|
|
217
|
+
ax.set_xlabel(xlabel, fontsize=fontsize)
|
|
218
|
+
ax.set_ylabel(ylabel, fontsize=fontsize)
|
|
219
|
+
ax.grid(True, which="both", ls="--", alpha=0.4)
|
|
220
|
+
ax.tick_params(axis="both", which="major", direction="in", labelsize=ticksize)
|
|
221
|
+
ax.tick_params(axis="both", which="minor", direction="in", labelsize=ticksize - 1)
|
|
222
|
+
ax.legend(loc="lower left", fontsize=12)
|
|
223
|
+
|
|
224
|
+
fig.tight_layout()
|
|
225
|
+
fig.subplots_adjust(hspace=0.15)
|
|
226
|
+
fig.savefig(outdir / "axisymmetric.pdf", bbox_inches="tight")
|
|
227
|
+
plt.close(fig)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def plot(spc: EnergySpectra) -> None:
|
|
231
|
+
"""Plot 1D spectra based on the type of the EnergySpectra object.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
spc : EnergySpectra, the energy spectra object.
|
|
236
|
+
"""
|
|
237
|
+
outdir = Path("spectra")
|
|
238
|
+
outdir.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
|
|
240
|
+
# plot shell-integrated spectra for all types
|
|
241
|
+
plotShell(spc, outdir=outdir)
|
|
242
|
+
|
|
243
|
+
# plot axisymmetric spectra for Afvénic turbulence
|
|
244
|
+
if spc.type in ("Bx", "Bz"):
|
|
245
|
+
plotAxisymmetric(spc, outdir=outdir)
|
|
246
|
+
|
|
247
|
+
# plot anisotropic spectra for MRI-driven turbulence
|
|
248
|
+
if spc.type == "MRI":
|
|
249
|
+
plotAnisotropic(spc, outdir=outdir)
|