diff --git a/examples/SantaBarbara/rhoTPlot.py b/examples/SantaBarbara/rhoTPlot.py new file mode 100644 index 0000000000000000000000000000000000000000..c290268eaa548e188bb652104ea9e726ea88a267 --- /dev/null +++ b/examples/SantaBarbara/rhoTPlot.py @@ -0,0 +1,258 @@ +""" +Makes a rho-T plot. Uses the swiftsimio library. +""" + +import matplotlib.pyplot as plt +import numpy as np + +from swiftsimio import SWIFTDataset, SWIFTMetadata, SWIFTUnits + +from unyt import mh, cm, Gyr +from tqdm import tqdm +from matplotlib.colors import LogNorm +from matplotlib.animation import FuncAnimation + +# Constants; these could be put in the parameter file but are rarely changed. +density_bounds = [1e-8, 1e4] # in nh/cm^3 +temperature_bounds = [1e2, 1e8] # in K +bins = 128 + +# Plotting controls +cmap = "viridis" + + +def get_data(filename): + """ + Grabs the data (T in Kelvin and density in mh / cm^3). + """ + + data = SWIFTDataset(filename) + + data.gas.density.convert_to_units(mh / (cm ** 3)) + data.gas.temperature.convert_to_cgs() + + return data.gas.density, data.gas.temperature + + +def make_hist(filename, density_bounds, temperature_bounds, bins): + """ + Makes the histogram for filename with bounds as lower, higher + for the bins and "bins" the number of bins along each dimension. + + Also returns the edges for pcolormesh to use. + """ + + density_bins = np.logspace( + np.log10(density_bounds[0]), np.log10(density_bounds[1]), bins + ) + temperature_bins = np.logspace( + np.log10(temperature_bounds[0]), np.log10(temperature_bounds[1]), bins + ) + + H, density_edges, temperature_edges = np.histogram2d( + *get_data(filename), bins=[density_bins, temperature_bins] + ) + + return H.T, density_edges, temperature_edges + + +def setup_axes(): + """ + Creates the figure and axis object. + """ + fig, ax = plt.subplots(1, figsize=(6, 5), dpi=300) + + ax.set_xlabel("Density [$n_H$ cm$^{-3}$]") + ax.set_ylabel("Temperature [K]") + + ax.loglog() + + return fig, ax + + +def make_single_image(filename, density_bounds, temperature_bounds, bins): + """ + Makes a single image and saves it to rhoTPlot_{filename}.png. + + Filename should be given _without_ hdf5 extension. + """ + + fig, ax = setup_axes() + hist, d, T = make_hist( + "{:s}.hdf5".format(filename), density_bounds, temperature_bounds, bins + ) + + mappable = ax.pcolormesh(d, T, hist, cmap=cmap, norm=LogNorm()) + fig.colorbar(mappable, label="Number of particles", pad=0) + + fig.tight_layout() + + fig.savefig("rhoTPlot_{:s}.png".format(filename)) + + return + + +def make_movie(args, density_bounds, temperature_bounds, bins): + """ + Makes a movie and saves it to rhoTPlot_{stub}.mp4. + """ + + fig, ax = setup_axes() + + def grab_metadata(n): + filename = "{:s}_{:04d}.hdf5".format(args["stub"], n) + data = SWIFTMetadata(filename) + + return data + + def grab_data(n): + filename = "{:s}_{:04d}.hdf5".format(args["stub"], n) + + H, _, _ = make_hist(filename, density_bounds, temperature_bounds, bins) + + # Need to ravel because pcolormesh's set_array takes a 1D array. Might + # as well do it here, beacuse 1d arrays are easier to max() than 2d. + return H.ravel() + + histograms = [ + grab_data(n) + for n in tqdm( + range(args["initial"], args["final"] + 1), desc="Histogramming data" + ) + ] + + metadata = [ + grab_metadata(n) + for n in tqdm( + range(args["initial"], args["final"] + 1), desc="Grabbing metadata" + ) + ] + + units = SWIFTUnits("{:s}_{:04d}.hdf5".format(args["stub"], args["initial"])) + + # Need to get a reasonable norm so that we don't overshoot. + max_particles = max([x.max() for x in histograms]) + + norm = LogNorm(vmin=1, vmax=max_particles) + + # First, let's make the initial frame (we need this for our d, T values that we + # got rid of in grab_data. + hist, d, T = make_hist( + "{:s}_{:04d}.hdf5".format(args["stub"], args["initial"]), + density_bounds, + temperature_bounds, + bins, + ) + + mappable = ax.pcolormesh(d, T, hist, cmap=cmap, norm=norm) + fig.colorbar(mappable, label="Number of particles", pad=0) + + fig.tight_layout() + + # Once we've rearranged the figure with tight_layout(), we can start laing + # Down the metadata text. + + def format_metadata(metadata: SWIFTMetadata): + t = metadata.t * units.units["Unit time in cgs (U_t)"] + t.convert_to_units(Gyr) + + x = "$a$: {:2.2f}\n$z$: {:2.2f}\n$t$: {:2.2f}".format( + metadata.a, metadata.z, t + ) + + return x + + text = ax.text( + 0.025, + 0.975, + format_metadata(metadata[0]), + ha="left", + va="top", + transform=ax.transAxes, + ) + + ax.text( + 0.975, + 0.975, + metadata[0].code["Git Revision"].decode("utf-8"), + ha="right", + va="top", + transform=ax.transAxes, + ) + + def animate(data): + mappable.set_array(histograms[data]) + text.set_text(format_metadata(metadata[data])) + + return mappable + + animation = FuncAnimation( + fig, animate, range(len(histograms)), fargs=[], interval=1000 / 25 + ) + + animation.save("rhoTPlot_{:s}.mp4".format(args["stub"])) + + return + + +if __name__ == "__main__": + import argparse as ap + + parser = ap.ArgumentParser( + description=""" + Plotting script for making a rho-T plot. + Takes the filename handle, start, and (optionally) stop + snapshots. If stop is not given, png plot is produced for + that snapshot. If given, a movie is made. + """ + ) + + parser.add_argument( + "-i", + "--initial", + help="""Initial snapshot number. Default: 0.""", + default=0, + required=False, + type=int, + ) + + parser.add_argument( + "-f", + "--final", + help="""Final snapshot number. Default: 0.""", + default=0, + required=False, + type=int, + ) + + parser.add_argument( + "-s", + "--stub", + help="""Stub for the filename (e.g. santabarbara). This is + the first part of the filename for the snapshots, + not including the final underscore. Required.""", + type=str, + required=True, + ) + + args = vars(parser.parse_args()) + + if args["final"] <= args["initial"]: + # Run in single image mode. + filename = "{:s}_{:04d}".format(args["stub"], args["initial"]) + + make_single_image( + filename, + density_bounds=density_bounds, + temperature_bounds=temperature_bounds, + bins=bins, + ) + + else: + # Movie mode! + make_movie( + args, + density_bounds=density_bounds, + temperature_bounds=temperature_bounds, + bins=bins, + )