"""
This file is part of SWIFT.
Copyright (c) 2019 Josh Borrow (joshua.borrow@durham.ac.uk)
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see .
Plots the solution for the ContactDiscontinuity_1D test.
"""
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
try:
from scipy.integrate import solve_ivp
solve_ode = True
except:
solve_ode = False
from swiftsimio import load
matplotlib.use("Agg")
def solve_analytic(u_0, u_1, t_0, t_1, alpha=0.1):
"""
Solves the analytic equation:
$$
\frac{d \Delta}{d t} = \kappa \alpha (
\sqrt{u_0 + \Delta} + \sqrt{u_1 + \Delta}
) (
u_1 - u_0 - 2 \Delta
)
$$
from time t0 to t1.
+ alpha is the gradient term
+ u_0 is the "low" state
+ u_1 is the "high" state.
"""
if not solve_ode:
return [0.0], [0.0]
def gradient(t, u):
"""
Returns du0/dt, du1/dt
"""
common = alpha * (np.sqrt(u[0]) + np.sqrt(u[1])) * (u[0] - u[1])
return np.array([-1.0 * common, 1.0 * common])
ret = solve_ivp(
gradient,
t_span=[t_0.value, t_1.value],
y0=[u_0.value, u_1.value],
t_eval=np.linspace(t_0.value, t_1.value, 100),
)
t = ret.t
high = ret.y[1]
low = ret.y[0]
return t, (high - low) * u_0.units
def get_data_dump(metadata):
"""
Gets a big data dump from the SWIFT metadata
"""
try:
viscosity = metadata.viscosity_info
except:
viscosity = "No info"
try:
diffusion = metadata.diffusion_info
except:
diffusion = "No info"
output = (
"SWIFT\n"
+ metadata.code_info
+ "\n\n"
+ "Compiler\n"
+ metadata.compiler_info
+ "\n\n"
+ "Hydrodynamics\n"
+ metadata.hydro_info
+ "\n\n"
+ "Viscosity\n"
+ viscosity
+ "\n\n"
+ "Diffusion\n"
+ diffusion
)
return output
def get_data_list(start: int, stop: int, handle: str):
"""
Gets a list of swiftsimio objects that contains all of the data.
"""
data = [load("{}_{:04d}.hdf5".format(handle, x)) for x in range(start, stop + 1)]
return data
def setup_axes(size=[8, 8], dpi=300):
"""
Sets up the axes with the correct labels, etc.
"""
fig, ax = plt.subplots(ncols=2, nrows=2, figsize=size, dpi=dpi)
ax = ax.flatten()
ax[0].axis("off")
ax[1].set_xlabel("Time [s]")
ax[1].set_ylabel("Relative energy difference $u/\\left$")
ax[2].set_xlabel("Time [s]")
ax[2].set_ylabel("Deviation in position relative to MIPS [$\\times 10^{6}$]")
ax[3].set_xlabel("Time [s]")
return fig, ax
def mean_std_max_min(data):
"""
Returns:
mean, stdev, max, min
for your data.
"""
means = np.array([np.mean(x) for x in data])
stdevs = np.array([np.std(x) for x in data])
maxs = np.array([np.max(x) for x in data])
mins = np.array([np.min(x) for x in data])
return means, stdevs, maxs, mins
def extract_plottables_u(data_list):
"""
Extracts the plottables for the internal energies. Returns:
mean, stdev, max, min differences between adjacent internal energies
"""
data = [
np.diff(x.gas.internal_energies.value) / np.mean(x.gas.internal_energies.value)
for x in data_list
]
return mean_std_max_min(data)
def extract_plottables_x(data_list):
"""
Extracts the plottables for positions. Returns:
mean, stdev, max, min * 1e6 deviations from original position
"""
n_part = data_list[0].metadata.n_gas
boxsize = data_list[0].metadata.boxsize[0].value
dx = boxsize / n_part
original_x = np.arange(n_part, dtype=float) * dx + (0.5 * dx)
deviations = [
1e6 * abs(original_x - x.gas.coordinates.value[:, 0]) / dx for x in data_list
]
return mean_std_max_min(deviations)
def extract_plottables_rho(data_list):
"""
Extracts the plottables for pressure. Returns:
mean, stdev, max, min * 1e6 deviations from mean density
"""
P = [x.gas.densities.value for x in data_list]
mean_P = [np.mean(x) for x in P]
deviations = [1e6 * (x - y) / x for x, y in zip(mean_P, P)]
return mean_std_max_min(deviations)
def extract_plottables_diff(data_list):
"""
Extracts the plottables for pressure. Returns:
mean, stdev, max, min * 1e6 deviations from mean density
"""
P = [x.gas.diffusion.value for x in data_list]
return mean_std_max_min(P)
def make_plot(start: int, stop: int, handle: str):
"""
Makes the plot and returns the figure and axes objects.
"""
fig, ax = setup_axes()
data_list = get_data_list(start, stop, handle)
data_dump = get_data_dump(data_list[0].metadata)
t = [x.metadata.t for x in data_list]
means, stdevs, maxs, mins = extract_plottables_u(data_list)
x_means, x_stdevs, x_maxs, x_mins = extract_plottables_x(data_list)
try:
alpha = np.mean([np.mean(x.gas.diffusion) for x in data_list])
except AttributeError:
# Must be using a non-diffusive scheme.
alpha = 0.0
ax[0].text(
0.5,
0.5,
data_dump,
ha="center",
va="center",
fontsize=8,
transform=ax[0].transAxes,
)
ax[1].fill_between(
t, means - stdevs, means + stdevs, color="C0", alpha=0.5, edgecolor="none"
)
ax[1].plot(t, means, label="Mean", c="C0")
ax[1].plot(t, maxs, label="Max", linestyle="dashed", c="C1")
ax[1].plot(t, mins, label="Min", linestyle="dashed", c="C2")
if solve_ode:
times_ode, diff = solve_analytic(
u_0=data_list[0].gas.internal_energies.min(),
u_1=data_list[0].gas.internal_energies.max(),
t_0=t[0],
t_1=t[-1],
alpha=(
np.sqrt(5.0 / 3.0 * (5.0 / 3.0 - 1.0))
* alpha
/ data_list[0].gas.smoothing_length[0].value
),
)
ax[1].plot(
times_ode,
(diff) / np.mean(data_list[0].gas.internal_energies),
label="Analytic",
linestyle="dotted",
c="C3",
)
# import pdb;pdb.set_trace()
ax[2].fill_between(
t,
x_means - x_stdevs,
x_means + x_stdevs,
color="C0",
alpha=0.5,
edgecolor="none",
)
ax[2].plot(t, x_means, label="Mean", c="C0")
ax[2].plot(t, x_maxs, label="Max", linestyle="dashed", c="C1")
ax[2].plot(t, x_mins, label="Min", linestyle="dashed", c="C2")
try:
# Give diffusion info a go; this may not be present
diff_means, diff_stdevs, diff_maxs, diff_mins = extract_plottables_diff(
data_list
)
ax[3].set_ylabel(r"Diffusion parameter $\alpha_{diff}$")
ax[3].fill_between(
t,
diff_means - diff_stdevs,
diff_means + diff_stdevs,
color="C0",
alpha=0.5,
edgecolor="none",
)
ax[3].plot(t, diff_means, label="Mean", c="C0")
ax[3].plot(t, diff_maxs, label="Max", linestyle="dashed", c="C1")
ax[3].plot(t, diff_mins, label="Min", linestyle="dashed", c="C2")
except:
# Diffusion info must not be present.
rho_means, rho_stdevs, rho_maxs, rho_mins = extract_plottables_rho(data_list)
ax[3].set_ylabel(
"Deviation from mean density $(\\rho_i - \\bar{\\rho}) / \\bar{\\rho}$ [$\\times 10^{6}$]"
)
ax[3].fill_between(
t,
rho_means - rho_stdevs,
rho_means + rho_stdevs,
color="C0",
alpha=0.5,
edgecolor="none",
)
ax[3].plot(t, rho_means, label="Mean", c="C0")
ax[3].plot(t, rho_maxs, label="Max", linestyle="dashed", c="C1")
ax[3].plot(t, rho_mins, label="Min", linestyle="dashed", c="C2")
ax[1].legend(loc=1, markerfirst=False)
ax[1].set_xlim(t[0], t[-1])
ax[2].set_xlim(t[0], t[-1])
ax[3].set_xlim(t[0], t[-1])
fig.tight_layout()
return fig, ax
if __name__ == "__main__":
import argparse as ap
parser = ap.ArgumentParser(
description="Makes a plot of the data from the ContactDiscontinuity_1D test."
)
parser.add_argument(
"-i", "--initial", help="Initial snapshot. Default: 0", type=int, default=0
)
parser.add_argument(
"-f", "--final", help="Final snapshot. Default: 50", type=int, default=50
)
parser.add_argument(
"-s",
"--snapshot",
help="First part of the snapshot filename. Default: diffusion",
type=str,
default="diffusion",
)
parser.add_argument(
"-o",
"--output",
help="Output filename. Default: diffusion.png",
type=str,
default="diffusion.png",
)
args = vars(parser.parse_args())
fig, ax = make_plot(args["initial"], args["final"], args["snapshot"])
fig.savefig(args["output"])