diff --git a/Makefile.am b/Makefile.am index 2c1af34d8df7b0bf83e6421c02db711c160cae3e..3ca9fd5e746dcbfa55d85e3632b3466d9ebc21ab 100644 --- a/Makefile.am +++ b/Makefile.am @@ -27,8 +27,10 @@ endif SUBDIRS += src argparse examples doc tests tools if HAVEEAGLECOOLING SUBDIRS += examples/Cooling/CoolingRates -endif +DIST_SUBDIRS = $(SUBDIRS) +else DIST_SUBDIRS = $(SUBDIRS) examples/Cooling/CoolingRates +endif # Common flags MYFLAGS = diff --git a/configure.ac b/configure.ac index 672dcd5680708de4abaf436be283e4e429e011a9..46271dc8699a41835cce4b04f10213a4e4056c48 100644 --- a/configure.ac +++ b/configure.ac @@ -2316,6 +2316,11 @@ case "$with_cooling" in with_cooling_name=$with_cooling ;; grackle_*) + + if test "$have_grackle" != "yes"; then + AC_MSG_ERROR([Grackle cooling: You need the grackle library for Grackle cooling. (--with-grackle=PATH)]) + fi + AC_DEFINE([COOLING_GRACKLE], [1], [Cooling via the grackle library]) primordial_chemistry=${with_cooling#*_} AC_DEFINE_UNQUOTED([COOLING_GRACKLE_MODE], [$primordial_chemistry], [Grackle chemistry network]) diff --git a/examples/Cooling/CoolingBox/coolingBox.yml b/examples/Cooling/CoolingBox/coolingBox.yml index 9087a6087be4e4e5ea905b3fcfb85627bc02c413..7f4393781496ecc71e81de2544638dcda4d45c58 100644 --- a/examples/Cooling/CoolingBox/coolingBox.yml +++ b/examples/Cooling/CoolingBox/coolingBox.yml @@ -81,3 +81,13 @@ EAGLEChemistry: # Solar abundances GEARPressureFloor: jeans_factor: 0. # Number of particles required to suppose a resolved clump and avoid the pressure floor. + +COLIBRECooling: + dir_name: ./UV_dust1_CR1_G1_shield1.hdf5 # Location of the cooling tables + H_reion_z: 7.5 # Redshift of Hydrogen re-ionization (Planck 2018) + H_reion_eV_p_H: 2.0 + He_reion_z_centre: 3.5 # Redshift of the centre of the Helium re-ionization Gaussian + He_reion_z_sigma: 0.5 # Spread in redshift of the Helium re-ionization Gaussian + He_reion_eV_p_H: 2.0 # Energy inject by Helium re-ionization in electron-volt per Hydrogen atom + delta_logTEOS_subgrid_properties: 0.3 # delta log T above the EOS below which the subgrid properties use Teq assumption + rapid_cooling_threshold: 0.333333 # Switch to rapid cooling regime for dt / t_cool above this threshold. diff --git a/examples/Cooling/CoolingRedshiftDependence/plotSolution.py b/examples/Cooling/CoolingRedshiftDependence/plotSolution.py index cb624be3cfb1f0f803cfce7a14fb1a772cccf515..2fe095ec948f78a8575561c62596bb078f3b67f0 100644 --- a/examples/Cooling/CoolingRedshiftDependence/plotSolution.py +++ b/examples/Cooling/CoolingRedshiftDependence/plotSolution.py @@ -36,6 +36,9 @@ def get_data_dump(metadata): + "$\\bf{Hydrodynamics}$\n" + metadata.hydro_info + "\n\n" + + "$\\bf{Cooling}$\n" + + metadata.subgrid_scheme["Cooling Model"].decode("utf-8") + + "\n\n" + "$\\bf{Viscosity}$\n" + viscosity + "\n\n" @@ -68,8 +71,6 @@ def setup_axes(): for a in ax[:-1]: a.set_xlim(0, 100) - fig.tight_layout(pad=0.5) - return fig, ax @@ -103,12 +104,15 @@ def get_data(handle: float, n_snaps: int): try: energies.append( np.mean( - (data.gas.internal_energies * data.gas.masses).to(erg).value + (data.gas.internal_energies * data.gas.masses) + .astype(np.float64) + .to(erg) + .value ) * data.metadata.scale_factor ** (2) ) radiated_energies.append( - np.mean(data.gas.radiated_energy.to(erg).value) + np.mean(data.gas.radiated_energy.astype(np.float64).to(erg).value) ) except AttributeError: continue @@ -152,7 +156,7 @@ def plot_single_data( label_radiated = "" label_sum = "" - ax[2].plot(data[0], data[3], label=label_energy, ls="dotted", C=f"C{run}") + ax[2].plot(data[0], data[3], label=label_energy, ls="dotted", color=f"C{run}") # ax[2].plot(data[0], data[4], label=label_radiated, ls="dashed", C=f"C{run}") @@ -222,4 +226,5 @@ if __name__ == "__main__": fig, ax = make_plot(handles, names, timestep_names) + fig.tight_layout(pad=0.5) fig.savefig("redshift_dependence.png", dpi=300) diff --git a/examples/Cooling/CoolingSedovBlast_3D/README b/examples/Cooling/CoolingSedovBlast_3D/README new file mode 100644 index 0000000000000000000000000000000000000000..361cdd69eac8108a5603cc3c2501ae1ae425d912 --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/README @@ -0,0 +1,29 @@ +Realistic SNe Sedov-Taylor explosion (realistic in the sense that it uses the +right order of magnitude for all variables). Meant to be run with const Lambda +cooling, but should also work with other cooling (the workflow also obtains +the EAGLE cooling tables). + +The included Makefile (run.make) contains the full workflow that runs the +simulation and creates a plot of the energy evolution, the evolution of the +shock radius and velocity, and a movie of the density, velocity and temperature +profile. + +To run the test, use + make -f run.make +this will run the simulation in a subdirectory called "run". + +To run in a different subdirectory (useful for comparisons), use + make -f run.make folder=my_subdirectory_name + +The energy and shock evolution scripts (plot_energy.py and plot_time_profile.py) +can also be run in comparison mode: + python3 plot_energy.py \ + subdirectory1/statistics.txt subdirectory1/statistics.txt \ + energy_1_vs_2.png \ + --names "Run 1" "Run 2" +and + python3 plot_time_evolution.py \ + subdirectory1/profile.txt subdirectory1/profile.txt \ + time_evolution_1_vs_2.png \ + --names "Run 1" "Run 2" +(there is not limit on the number of runs that can be included). diff --git a/examples/Cooling/CoolingSedovBlast_3D/getGlass.sh b/examples/Cooling/CoolingSedovBlast_3D/getGlass.sh new file mode 100755 index 0000000000000000000000000000000000000000..d5c5f590ac37c9c9431d626a2ea61b0c12c1513c --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/getGlass.sh @@ -0,0 +1,2 @@ +#!/bin/bash +wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassCube_64.hdf5 diff --git a/examples/Cooling/CoolingSedovBlast_3D/get_time_profile.py b/examples/Cooling/CoolingSedovBlast_3D/get_time_profile.py new file mode 100644 index 0000000000000000000000000000000000000000..00a5bb4817fc45088f4af60cdd95475d1d0e25cd --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/get_time_profile.py @@ -0,0 +1,69 @@ +import numpy as np +import h5py +import argparse +import scipy.stats as stats +import unyt + +argparser = argparse.ArgumentParser() +argparser.add_argument("input", nargs="+") +argparser.add_argument("output") +args = argparser.parse_args() + +nfile = len(args.input) + +time = np.zeros(nfile) +radius = np.zeros(nfile) +velocity = np.zeros(nfile) + +for ifile, file in enumerate(args.input): + with h5py.File(file, "r") as handle: + coords = handle["PartType0/Coordinates"][:] + rho = handle["PartType0/Densities"][:] + vs = handle["PartType0/Velocities"][:] + t = handle["Header"].attrs["Time"][0] + box = handle["Header"].attrs["BoxSize"][:] + + units = dict(handle["Units"].attrs) + uM = (units["Unit mass in cgs (U_M)"][0] * unyt.g).in_base("galactic") + uL = (units["Unit length in cgs (U_L)"][0] * unyt.cm).in_base("galactic") + ut = (units["Unit time in cgs (U_t)"][0] * unyt.s).in_base("galactic") + + coords = (coords * uL).in_base("galactic") + rho = (rho * uM / uL ** 3).in_base("galactic") + vs = (vs * uL / ut).in_base("galactic") + t = (t * ut).in_base("galactic") + box = (box * uL).in_base("galactic") + + coords -= 0.5 * box[None, :] + + x = np.sqrt((coords ** 2).sum(axis=1)) + v = (coords * vs).sum(axis=1) / x + + x.convert_to_units("kpc") + v.convert_to_units("km/s") + t.convert_to_units("Myr") + + rhohist, _, _ = stats.binned_statistic(x, rho, statistic="median", bins=100) + vhist, edges, _ = stats.binned_statistic(x, v, statistic="mean", bins=100) + mids = 0.5 * (edges[1:] + edges[:-1]) + + rhohist[np.isnan(rhohist)] = 0.0 + imax = np.argmax(rhohist) + + time[ifile] = t + radius[ifile] = mids[imax] + velocity[ifile] = vhist[imax] + +isort = np.argsort(time) + +time = time[isort] +radius = radius[isort] +velocity = velocity[isort] + +with open(args.output, "w") as handle: + handle.write("# Radial profile of shock wave\n") + handle.write("# Column 0: time (Myr)\n") + handle.write("# Column 1: radius (kpc)\n") + handle.write("# Column 2: velocity (km/s)\n") + for t, r, v in zip(time, radius, velocity): + handle.write(f"{t:.5e}\t{r:.5e}\t{v:.5e}\n") diff --git a/examples/Cooling/CoolingSedovBlast_3D/makeIC.py b/examples/Cooling/CoolingSedovBlast_3D/makeIC.py new file mode 100644 index 0000000000000000000000000000000000000000..3449d73253422428a6c4367cc33d08f429560da9 --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/makeIC.py @@ -0,0 +1,149 @@ +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2016 Matthieu Schaller (matthieu.schaller@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 <http://www.gnu.org/licenses/>. +# +############################################################################## + +import h5py +from numpy import * + +# Generates a swift IC file for the Sedov blast test in a periodic cubic box + +# Parameters +gamma = 5.0 / 3.0 # Gas adiabatic index +n0 = 0.1 # cm^-3 +mp = 1.67e-24 # g +kB = 1.381e-16 # erg K^-1 +pc = 3.086e18 # cm +rho0 = n0 * mp # Background density +T0 = 1.0e4 # Background pressure +E0 = 1.0e51 # Energy of the explosion +N_inject = 32 # Number of particles in which to inject energy +L = 256.0 * pc +fileName = "sedov.hdf5" + +print(rho0, "cm s^-3") + +uL = 1.0e3 * pc +uM = 1.99e30 +uv = 1.0e10 +ut = uL / uv +uU = uv ** 2 +print("ut:", ut / 3.154e7, "yr") + +# --------------------------------------------------- +glass = h5py.File("glassCube_64.hdf5", "r") + +# Read particle positions and h from the glass +pos = glass["/PartType0/Coordinates"][:, :] +h = glass["/PartType0/SmoothingLength"][:] + +pos *= L +h *= L + +for i in range(3): + pos[pos[:, i] < 0.0, i] += L + pos[pos[:, i] >= L, i] -= L + +numPart = size(h) + +# newpos = zeros((numPart*8,3)) +# newh = zeros(numPart*8) +# for ix in range(2): +# for iy in range(2): +# for iz in range(2): +# offset = ix*4+iy*2+iz +# newpos[offset*numPart:(offset+1)*numPart,:] = 0.5*pos[:,:] +# newpos[offset*numPart:(offset+1)*numPart,0] += 0.5*ix*L +# newpos[offset*numPart:(offset+1)*numPart,1] += 0.5*iy*L +# newpos[offset*numPart:(offset+1)*numPart,2] += 0.5*iz*L +# newh[offset*numPart:(offset+1)*numPart] = 0.5*h[:] + +# pos = newpos +# h = newh +print(h, h.min(), h.max()) + +numPart = size(h) +vol = L ** 3 + +# Generate extra arrays +v = zeros((numPart, 3)) +ids = linspace(1, numPart, numPart) +m = zeros(numPart) +u = zeros(numPart) +r = zeros(numPart) + +r = sqrt( + (pos[:, 0] - 0.5 * L) ** 2 + (pos[:, 1] - 0.5 * L) ** 2 + (pos[:, 2] - 0.5 * L) ** 2 +) +m[:] = rho0 * vol / numPart +# u[:] = P0 / (rho0 * (gamma - 1)) +u[:] = kB * T0 / ((gamma - 1.0) * mp) + +print(u.mean(), E0 / (N_inject * m[0])) +print(E0 / (N_inject * m[0]) / u.mean()) + +# Make the central particle detonate +index = argsort(r) +u[index[0:N_inject]] = E0 / (N_inject * m[0]) + +pos /= uL +h /= uL +L /= uL +m /= uM +u /= uU + +print("L:", L) +print("m:", m.mean()) +print("h:", h.mean()) +print("u:", u.mean(), u.min(), u.max()) +print("pos:", pos.min(), pos.max()) + +# -------------------------------------------------- + +# File +file = h5py.File(fileName, "w") + +# Header +grp = file.create_group("/Header") +grp.attrs["BoxSize"] = [L, L, L] +grp.attrs["NumPart_Total"] = [numPart, 0, 0, 0, 0, 0] +grp.attrs["NumPart_Total_HighWord"] = [0, 0, 0, 0, 0, 0] +grp.attrs["NumPart_ThisFile"] = [numPart, 0, 0, 0, 0, 0] +grp.attrs["Time"] = 0.0 +grp.attrs["NumFilesPerSnapshot"] = 1 +grp.attrs["MassTable"] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0] +grp.attrs["Flag_Entropy_ICs"] = 0 +grp.attrs["Dimension"] = 3 + +# Units +grp = file.create_group("/Units") +grp.attrs["Unit length in cgs (U_L)"] = uL +grp.attrs["Unit mass in cgs (U_M)"] = uM +grp.attrs["Unit time in cgs (U_t)"] = ut +grp.attrs["Unit current in cgs (U_I)"] = 1.0 +grp.attrs["Unit temperature in cgs (U_T)"] = 1.0 + +# Particle group +grp = file.create_group("/PartType0") +grp.create_dataset("Coordinates", data=pos, dtype="d") +grp.create_dataset("Velocities", data=v, dtype="f") +grp.create_dataset("Masses", data=m, dtype="f") +grp.create_dataset("SmoothingLength", data=h, dtype="f") +grp.create_dataset("InternalEnergy", data=u, dtype="f") +grp.create_dataset("ParticleIDs", data=ids, dtype="L") + +file.close() diff --git a/examples/Cooling/CoolingSedovBlast_3D/make_visualisations.make b/examples/Cooling/CoolingSedovBlast_3D/make_visualisations.make new file mode 100644 index 0000000000000000000000000000000000000000..8c31a20fc3a0fcfbff7ce282bccd3a0dd452d1a3 --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/make_visualisations.make @@ -0,0 +1,20 @@ +folder=. +snaps=$(shell ls ${folder}/sedov_*.hdf5) +imgs=$(patsubst ${folder}/sedov_%.hdf5,${folder}/SedovCooling_%.png,$(snaps)) + +all: ${folder}/profile.png ${folder}/SedovCooling.mp4 ${folder}/energy.png + +${folder}/energy.png: ${folder}/statistics.txt + python3 plot_energy.py ${folder}/statistics.txt ${folder}/energy.png + +${folder}/profile.png: ${folder}/profile.txt + python3 plot_time_profile.py ${folder}/profile.txt ${folder}/profile.png + +${folder}/profile.txt: $(snaps) + python3 get_time_profile.py $(snaps) ${folder}/profile.txt + +${folder}/SedovCooling.mp4: $(imgs) + ffmpeg -y -framerate 10 -pattern_type glob -i "${folder}/SedovCooling_*.png" -c:v libx264 ${folder}/SedovCooling.mp4 + +${folder}/SedovCooling_%.png: ${folder}/sedov_%.hdf5 + python3 plot_profile.py $< $@ diff --git a/examples/Cooling/CoolingSedovBlast_3D/plot_energy.py b/examples/Cooling/CoolingSedovBlast_3D/plot_energy.py new file mode 100644 index 0000000000000000000000000000000000000000..1c1e8a9d5394f3ae0270acbaa0210188f0100044 --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/plot_energy.py @@ -0,0 +1,49 @@ +import numpy as np +import argparse +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as pl + +argparser = argparse.ArgumentParser() +argparser.add_argument("input", nargs="+") +argparser.add_argument("output") +argparser.add_argument("--names", "-n", nargs="+") +args = argparser.parse_args() + +for ifile, file in enumerate(args.input): + + data = np.loadtxt( + file, + usecols=(1, 13, 14, 16), + dtype=[ + ("Time", np.float32), + ("Ekin", np.float32), + ("Etherm", np.float32), + ("Erad", np.float32), + ], + ) + + color = f"C{ifile}" + + if args.names: + pl.plot([], [], "-", color=color, label=args.names[ifile]) + + pl.plot(data["Time"], data["Ekin"], "-.", color=color) + pl.plot(data["Time"], data["Etherm"], "--", color=color) + pl.plot(data["Time"], data["Erad"], ":", color=color) + pl.plot( + data["Time"], data["Ekin"] + data["Etherm"] + data["Erad"], "-", color=color + ) + +pl.plot([], [], "k-", label="Total energy") +pl.plot([], [], "k-.", label="Kinetic energy") +pl.plot([], [], "k--", label="Thermal energy") +pl.plot([], [], "k:", label="Radiated energy") +pl.ylabel("Energy") +pl.xlabel("Time") + +pl.legend(loc="best", ncol=3) + +pl.tight_layout() +pl.savefig(args.output, dpi=300) diff --git a/examples/Cooling/CoolingSedovBlast_3D/plot_profile.py b/examples/Cooling/CoolingSedovBlast_3D/plot_profile.py new file mode 100644 index 0000000000000000000000000000000000000000..4c242da8bb3bc6cb2865cd792328c45686ad1db9 --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/plot_profile.py @@ -0,0 +1,93 @@ +import numpy as np +import h5py +import argparse +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as pl +import scipy.stats as stats +import unyt + + +def bin_vals(x, y, log=True): + stat = "median" if log else "mean" + hist, edges, _ = stats.binned_statistic(x, y, statistic=stat, bins=100) + mids = 0.5 * (edges[1:] + edges[:-1]) + return mids, hist + + +argparser = argparse.ArgumentParser() +argparser.add_argument("input") +argparser.add_argument("output") +args = argparser.parse_args() + +x = None +rho = None +T = None +v = None +time = None +with h5py.File(args.input, "r") as handle: + coords = handle["PartType0/Coordinates"][:] + rho = handle["PartType0/Densities"][:] + T = handle["PartType0/Temperatures"][:] + vs = handle["PartType0/Velocities"][:] + time = handle["Header"].attrs["Time"][0] + box = handle["Header"].attrs["BoxSize"][:] + + units = dict(handle["Units"].attrs) + uM = (units["Unit mass in cgs (U_M)"][0] * unyt.g).in_base("galactic") + uL = (units["Unit length in cgs (U_L)"][0] * unyt.cm).in_base("galactic") + ut = (units["Unit time in cgs (U_t)"][0] * unyt.s).in_base("galactic") + + coords = (coords * uL).in_base("galactic") + rho = (rho * uM / uL ** 3).in_base("galactic") + T = T * unyt.K + vs = (vs * uL / ut).in_base("galactic") + time = (time * ut).in_base("galactic") + box = (box * uL).in_base("galactic") + + coords -= 0.5 * box[None, :] + + x = np.sqrt((coords ** 2).sum(axis=1)) + v = (coords * vs).sum(axis=1) / x + +rhohist, edges, _ = stats.binned_statistic(x, rho, statistic="median", bins=100) +mids = 0.5 * (edges[1:] + edges[:-1]) +rhohist[np.isnan(rhohist)] = 0.0 +imax = np.argmax(rhohist) +xmax = mids[imax] + +x.name = "radius" +x.convert_to_units("kpc") +v.name = "radial velocity" +v.convert_to_units("km/s") +rho.name = "density" +rho.convert_to_units("g/cm**3") +T.name = "temperature" + +fig, ax = pl.subplots(1, 3, figsize=(8, 4), sharex=True) + +with unyt.matplotlib_support: + ax[0].semilogy(x, rho, ".") + b, h = bin_vals(x, rho) + ax[0].semilogy(b, h, "--") + + ax[1].plot(x, v, ".") + b, h = bin_vals(x, v, log=False) + ax[1].plot(b, h, "--") + + ax[2].semilogy(x, T, ".") + b, h = bin_vals(x, T) + ax[2].semilogy(b, h, "--") + +for a in ax: + a.axvline(x=xmax, color="k", linestyle="--") + +ax[0].set_ylim(1.0e-28, 1.0e-24) +ax[1].set_ylim(-10.0, 200.0) +ax[2].set_ylim(10.0, 1.0e8) + +ax[1].set_title(f"t = {time:.2e}") + +pl.tight_layout() +pl.savefig(args.output, dpi=300) diff --git a/examples/Cooling/CoolingSedovBlast_3D/plot_time_profile.py b/examples/Cooling/CoolingSedovBlast_3D/plot_time_profile.py new file mode 100644 index 0000000000000000000000000000000000000000..424850f3fef6544c9027733adb223d059cd3261e --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/plot_time_profile.py @@ -0,0 +1,41 @@ +import numpy as np +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as pl +import argparse +import unyt + +argparser = argparse.ArgumentParser() +argparser.add_argument("input", nargs="+") +argparser.add_argument("output") +argparser.add_argument("--names", "-n", nargs="+") +args = argparser.parse_args() + +fig, ax = pl.subplots(1, 2, sharex=True) + +for ifile, file in enumerate(args.input): + data = np.loadtxt( + file, + dtype=[("time", np.float32), ("radius", np.float32), ("velocity", np.float32)], + ) + + t = data["time"] * unyt.Myr + t.name = "time" + r = data["radius"] * unyt.kpc + r.name = "radius" + v = data["velocity"] * unyt.km / unyt.s + v.name = "velocity" + + label = None + if args.names: + label = args.names[ifile] + with unyt.matplotlib_support: + ax[0].plot(t, r, "-", label=label) + ax[1].plot(t, v, "-", label=label) + +if args.names: + ax[1].legend(loc="best") + +pl.tight_layout() +pl.savefig(args.output, dpi=300) diff --git a/examples/Cooling/CoolingSedovBlast_3D/run.make b/examples/Cooling/CoolingSedovBlast_3D/run.make new file mode 100644 index 0000000000000000000000000000000000000000..976962e80e071cdaea273d4ff86a2e986f3d101e --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/run.make @@ -0,0 +1,29 @@ +folder=run +pwd=$(shell pwd) + +${folder}/profile.png: ${folder}/sedov_0100.hdf5 + make -f make_visualisations.make -j 16 folder=${folder} + +${folder}/sedov_0100.hdf5: ${folder}/swift ${folder}/sedov.hdf5 ${folder}/coolingtables/redshifts.dat ${folder}/sedov.yml + cd ${folder}; ./swift --threads 8 --hydro --cooling --limiter sedov.yml + +${folder}/sedov.hdf5: sedov.hdf5 + ln -s ${pwd}/sedov.hdf5 ${folder}/sedov.hdf5 + +${folder}/coolingtables/redshifts.dat: coolingtables/redshifts.dat + ln -s ${pwd}/coolingtables ${folder}/coolingtables + +${folder}/swift: ../../../swift + mkdir -p ${folder}; ln -s ${pwd}/../../../swift ${folder}/swift + +${folder}/sedov.yml: sedov.yml + ln -s ${pwd}/sedov.yml ${folder}/sedov.yml + +sedov.hdf5: glassCube_64.hdf5 + python3 makeIC.py + +glassCube_64.hdf5: + bash getGlass.sh + +coolingtables/redshifts.dat: + bash ../getEagleCoolingTable.sh diff --git a/examples/Cooling/CoolingSedovBlast_3D/sedov.yml b/examples/Cooling/CoolingSedovBlast_3D/sedov.yml new file mode 100644 index 0000000000000000000000000000000000000000..352686068fb9d5a34d3617ab869b1a7ecd92eebf --- /dev/null +++ b/examples/Cooling/CoolingSedovBlast_3D/sedov.yml @@ -0,0 +1,99 @@ +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1.99e33 # g + UnitLength_in_cgs: 3.086e21 # cm + UnitVelocity_in_cgs: 1.e5 # Centimeters per second + UnitCurrent_in_cgs: 1 # Amperes + UnitTemp_in_cgs: 1 # Kelvin + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 1.e-3 # The end time of the simulation (in internal units). + dt_min: 1.e-13 # The minimal time-step size of the simulation (in internal units). + dt_max: 1.e-3 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: sedov # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 1.e-5 # Time difference between consecutive outputs (in internal units) + +# Parameters governing the conserved quantities statistics +Statistics: + delta_time: 1e-5 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration. + minimal_temperature: 1.e2 + max_ghost_iterations: 1000 + h_max: 0.04 # Maximum smoothing length. Since the density in the post-shock region becomes very low, we need to make sure that this does not exceed 1/3 of the periodic box (including kernel gamma ~ 2) + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./sedov.hdf5 # The file to read + periodic: 1 + +EAGLECooling: + H_reion_z: 11.5 + H_reion_eV_p_H: 2.0 + He_reion_z_centre: 3.5 + He_reion_z_sigma: 0.5 + He_reion_eV_p_H: 2.0 + dir_name: ./coolingtables/ + +EAGLEChemistry: + init_abundance_metal: 0.014 # Mass fraction in *all* metals + init_abundance_Hydrogen: 0.70649785 # Mass fraction in Hydrogen + init_abundance_Helium: 0.28055534 # Mass fraction in Helium + init_abundance_Carbon: 2.0665436e-3 # Mass fraction in Carbon + init_abundance_Nitrogen: 8.3562563e-4 # Mass fraction in Nitrogen + init_abundance_Oxygen: 5.4926244e-3 # Mass fraction in Oxygen + init_abundance_Neon: 1.4144605e-3 # Mass fraction in Neon + init_abundance_Magnesium: 5.907064e-4 # Mass fraction in Magnesium + init_abundance_Silicon: 6.825874e-4 # Mass fraction in Silicon + init_abundance_Iron: 1.1032152e-3 # Mass fraction in Iron + +EoS: + isothermal_internal_energy: 1240419161676.6465 + +LambdaTTableCooling: + file_name: cooling.dat + +ConstCooling: + cooling_rate: 1.e6 + min_energy: 0.01 + cooling_tstep_mult: 2 + +LambdaCooling: + lambda_nH2_cgs: 2.e-23 + cooling_tstep_mult: 0.1 + rapid_cooling: 1 + +Scheduler: + max_top_level_cells: 12 + cell_max_size: 8000000 + cell_sub_size_pair_hydro: 256000000 + cell_sub_size_self_hydro: 32000 + cell_sub_size_pair_stars: 256000000 + cell_sub_size_self_stars: 32000 + cell_sub_size_pair_grav: 256000000 + cell_sub_size_self_grav: 32000 + cell_split_size: 400 + cell_subdepth_diff_grav: 4 + cell_extra_parts: 0 + cell_extra_sparts: 100 + cell_extra_gparts: 0 + cell_extra_bparts: 0 + cell_extra_sinks: 0 + engine_max_parts_per_ghost: 1000 + engine_max_sparts_per_ghost: 1000 + engine_max_parts_per_cooling: 10000 + nr_queues: 4 + dependency_graph_frequency: 0 + task_level_output_frequency: 0 + tasks_per_cell: 100 + links_per_tasks: 25 + mpi_message_limit: 4 diff --git a/examples/EAGLE_DMO_low_z/EAGLE_DMO_100/eagle_100.yml b/examples/EAGLE_DMO_low_z/EAGLE_DMO_100/eagle_100.yml index 9042d2d1c07b35adaa5b747d314c225a8a540b9e..dcecb9edd2476a87741c95341000424b6745e083 100644 --- a/examples/EAGLE_DMO_low_z/EAGLE_DMO_100/eagle_100.yml +++ b/examples/EAGLE_DMO_low_z/EAGLE_DMO_100/eagle_100.yml @@ -42,10 +42,10 @@ Statistics: # Parameters for the self-gravity scheme Gravity: eta: 0.025 # Constant dimensionless multiplier for time integration. - MAC: geometric + MAC: adaptive + epsilon_fmm: 0.001 theta_cr: 0.7 # Opening angle (Multipole acceptance criterion) - use_tree_below_softening: 1 - mesh_side_length: 256 + mesh_side_length: 1024 comoving_DM_softening: 0.0026994 # Comoving DM softening length (in internal units). max_physical_DM_softening: 0.0007 # Max physical DM softening length (in internal units). diff --git a/examples/EAGLE_DMO_low_z/EAGLE_DMO_12/eagle_12.yml b/examples/EAGLE_DMO_low_z/EAGLE_DMO_12/eagle_12.yml index bddb5e35baf4e1fed97b79254ba00d5811fac126..cd1165f6f4216f749d0164eededd036325b01bfb 100644 --- a/examples/EAGLE_DMO_low_z/EAGLE_DMO_12/eagle_12.yml +++ b/examples/EAGLE_DMO_low_z/EAGLE_DMO_12/eagle_12.yml @@ -42,10 +42,10 @@ Statistics: # Parameters for the self-gravity scheme Gravity: eta: 0.025 # Constant dimensionless multiplier for time integration. - MAC: geometric + MAC: adaptive + epsilon_fmm: 0.001 theta_cr: 0.7 # Opening angle (Multipole acceptance criterion) - use_tree_below_softening: 1 - mesh_side_length: 32 + mesh_side_length: 128 comoving_DM_softening: 0.0026994 # Comoving DM softening length (in internal units). max_physical_DM_softening: 0.0007 # Max physical DM softening length (in internal units). diff --git a/examples/EAGLE_DMO_low_z/EAGLE_DMO_25/eagle_25.yml b/examples/EAGLE_DMO_low_z/EAGLE_DMO_25/eagle_25.yml index 4fd5d226735ba6c49055de9b381b908c0beb5c52..163aba63e7cd7c03a8cc19f55eda3015fdf0a322 100644 --- a/examples/EAGLE_DMO_low_z/EAGLE_DMO_25/eagle_25.yml +++ b/examples/EAGLE_DMO_low_z/EAGLE_DMO_25/eagle_25.yml @@ -42,10 +42,10 @@ Statistics: # Parameters for the self-gravity scheme Gravity: eta: 0.025 # Constant dimensionless multiplier for time integration. - MAC: geometric + MAC: adaptive + epsilon_fmm: 0.001 theta_cr: 0.7 # Opening angle (Multipole acceptance criterion) - use_tree_below_softening: 1 - mesh_side_length: 64 + mesh_side_length: 256 comoving_DM_softening: 0.0026994 # Comoving DM softening length (in internal units). max_physical_DM_softening: 0.0007 # Max physical DM softening length (in internal units). diff --git a/examples/EAGLE_DMO_low_z/EAGLE_DMO_50/eagle_50.yml b/examples/EAGLE_DMO_low_z/EAGLE_DMO_50/eagle_50.yml index 6f19042e6850b6960f03817116f9c2ea73c70bf5..b77a2bcabd43d94c21846fd40be7b1d499b292bb 100644 --- a/examples/EAGLE_DMO_low_z/EAGLE_DMO_50/eagle_50.yml +++ b/examples/EAGLE_DMO_low_z/EAGLE_DMO_50/eagle_50.yml @@ -41,10 +41,10 @@ Statistics: # Parameters for the self-gravity scheme Gravity: eta: 0.025 # Constant dimensionless multiplier for time integration. - MAC: geometric + MAC: adaptive + epsilon_fmm: 0.001 theta_cr: 0.7 # Opening angle (Multipole acceptance criterion) - use_tree_below_softening: 1 - mesh_side_length: 128 + mesh_side_length: 512 comoving_DM_softening: 0.0026994 # Comoving DM softening length (in internal units). max_physical_DM_softening: 0.0007 # Max physical DM softening length (in internal units). diff --git a/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/README b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/README new file mode 100644 index 0000000000000000000000000000000000000000..3cf3226b923b9b9206377770fac9fb1f6f68f06c --- /dev/null +++ b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/README @@ -0,0 +1,45 @@ +1D advection test for radiative transfer. +This test is identical to the examples/RadiativeTransferTests/Advection_1D +with the difference that particles aren't equally spaced on a line, but +separated into three regions with decreasing intervals between particles +in each of the region. + +More concretely, for a box of size 1, the separation is given as + +dx if x < 0.33 +dx/2 if 0.33 < x < 0.66 +dx/4 if x >= 0.66 + +This leads to the three regions having different time step sizes for RT. + + +Test that your method is TVD and the propagation speed of the photons is +correct. The ICs set up three photon groups: +- The first is a top hat function initial distribution where outside values + are zero. +- The second is a top hat function initial distribution where outside values + are nonzero. This distinction is important to test because photon energies + can't be negative, so these cases need to be tested individually. +- the third is a smooth Gaussian. + +This way, you can test multiple initial condition scenarios simultaneously. +There are no stars to act as sources. Also make sure that you choose your +photon frequencies in a way that doesn't interact with gas! + +The ICs are created to be compatible with GEAR_RT and SPHM1RT. Recommended configuration: +GEAR_RT: + --with-rt=GEAR_3 --with-rt-riemann-solver=GLF --with-hydro-dimension=1 --with-hydro=gizmo-mfv \ + --with-riemann-solver=hllc --with-stars=GEAR --with-feedback=none --with-grackle=$GRACKLE_ROOT + +SPHM1RT: + --with-rt=SPHM1RT_3 --with-hydro-dimension=1 --with-stars=basic --with-sundials=$SUNDIALS_ROOT + +IMPORTANT: Need SUNDIALS version = 5 . +SUNDIALS_ROOT is the root directory that contains the lib and include directories, e.g. on cosma: +SUNDIALS_ROOT=/cosma/local/sundials/5.1.0/ + +Note that in SPHM1RT any (SPH) hydro scheme is compatible. + +Note that if you want to use a reduced speed of light for this test, you also +need to adapt the fluxes in the initial conditions! They are generated assuming +that the speed of light is not reduced. diff --git a/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/makeIC.py b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/makeIC.py new file mode 100755 index 0000000000000000000000000000000000000000..8d61caffb0f9df90fa55c6addab1a4c078162b41 --- /dev/null +++ b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/makeIC.py @@ -0,0 +1,207 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# 2022 Tsang Keung Chan (chantsangkeung@gmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ----------------------------------------------------------- +# Add initial conditions for photon energies and fluxes +# for 1D advection of photons. +# First photon group: Top hat function with zero as the +# baseline. +# Second photon group: Top hat function with nonzero value +# as the baseline. +# Third photon group: Gaussian. +# ----------------------------------------------------------- + +import h5py +import numpy as np +import unyt +from swiftsimio import Writer + +# define unit system to use +unitsystem = unyt.unit_systems.cgs_unit_system + +# Box is 1 Mpc +boxsize = 1e10 * unitsystem["length"] + +# number of photon groups +nPhotonGroups = 3 + +# number of particles in each dimension +dx_init = boxsize / 1000 + +# filename of ICs to be generated +outputfilename = "advection_1D.hdf5" + + +def initial_condition(x): + """ + The initial conditions that will be advected + + x: particle position. 3D unyt array + + returns: + E: photon energy density for each photon group. List of scalars with size of nPhotonGroups + F: photon flux for each photon group. List with size of nPhotonGroups of numpy arrays of shape (3,) + """ + + # you can make the photon quantities unitless, the units will + # already have been written down in the writer. + + E_list = [] + F_list = [] + + # Group 1 Photons: + # ------------------- + + if x[0] < 0.33 * boxsize: + E = 0.0 + elif x[0] < 0.66 * boxsize: + E = 1.0 + else: + E = 0.0 + + # Assuming all photons flow in only one direction + # (optically thin regime, "free streaming limit"), + # we have that |F| = c * E + F = np.zeros(3, dtype=np.float64) + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + # Group 2 Photons: + # ------------------- + + if x[0] < 0.33 * boxsize: + E = 1.0 + elif x[0] < 0.66 * boxsize: + E = 3.0 + else: + E = 1.0 + + F = np.zeros(3, dtype=np.float64) + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + # Group 3 Photons: + # ------------------- + sigma = 0.1 * boxsize + mean = 0.5 * boxsize + amplitude = 2.0 + + E = amplitude * np.exp(-(x[0] - mean) ** 2 / (2 * sigma ** 2)) + F = np.zeros(3, dtype=np.float64) + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + return E_list, F_list + + +if __name__ == "__main__": + + xp_list = [] + volumes_list = [] + + x = 0.5 * dx_init + while x < boxsize: + if x < 0.33 * boxsize: + dx = dx_init + elif 0.33 * boxsize <= x <= 0.66 * boxsize: + dx = dx_init / 2 + else: + dx = dx_init / 4 + x = x + dx + xp_list.append(x) + volumes_list.append(dx) + + n_p = len(xp_list) + xp = unyt.unyt_array(np.zeros((n_p, 3), dtype=np.float64), boxsize.units) + volumes = unyt.unyt_array(np.zeros((n_p), dtype=np.float64), boxsize.units ** 3) + for i in range(n_p): + xp[i, 0] = xp_list[i] + volumes[i] = volumes_list[i] + + w = Writer(unyt.unit_systems.cgs_unit_system, boxsize, dimension=1) + + w.gas.coordinates = xp + w.gas.velocities = np.zeros(xp.shape) * (unyt.cm / unyt.s) + w.gas.masses = np.ones(xp.shape[0], dtype=np.float64) * 1000 * unyt.g + w.gas.internal_energy = ( + np.ones(xp.shape[0], dtype=np.float64) * (300.0 * unyt.kb * unyt.K) / unyt.g + ) + + # Generate initial guess for smoothing lengths based on MIPS + w.gas.generate_smoothing_lengths(boxsize=boxsize, dimension=1) + + # If IDs are not present, this automatically generates + w.write(outputfilename) + + # Now open file back up again and add photon groups + # you can make them unitless, the units have already been + # written down in the writer. In this case, it's in cgs. + + F = h5py.File(outputfilename, "r+") + header = F["Header"] + nparts = header.attrs["NumPart_ThisFile"][0] + parts = F["/PartType0"] + + for grp in range(nPhotonGroups): + dsetname = "PhotonEnergiesGroup{0:d}".format(grp + 1) + energydata = np.zeros((nparts), dtype=np.float32) + parts.create_dataset(dsetname, data=energydata) + + dsetname = "PhotonFluxesGroup{0:d}".format(grp + 1) + # if dsetname not in parts.keys(): + fluxdata = np.zeros((nparts, 3), dtype=np.float32) + parts.create_dataset(dsetname, data=fluxdata) + + for p in range(nparts): + E, Flux = initial_condition(xp[p]) + for g in range(nPhotonGroups): + # Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1) + # parts[Esetname][p] = E[g] / volumes[p] + # Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1) + # parts[Fsetname][p] = Flux[g] / volumes[p] + # Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1) + # parts[Esetname][p] = E[g] + # Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1) + # parts[Fsetname][p] = Flux[g] + Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1) + parts[Esetname][p] = E[g] * volumes[p] + Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1) + parts[Fsetname][p] = Flux[g] * volumes[p] + + # from matplotlib import pyplot as plt + # plt.figure() + # for g in range(nPhotonGroups): + # # Esetname = "PhotonEnergiesGroup{0:d}".format(g+1) + # # plt.plot(xp[:,0], parts[Esetname], label="E "+str(g+1)) + # Fsetname = "PhotonFluxesGroup{0:d}".format(g+1) + # plt.plot(xp[:,0], parts[Fsetname][:,0], label="F "+str(g+1)) + # plt.legend() + # plt.show() + + F.close() diff --git a/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/plotSolution.py b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/plotSolution.py new file mode 100755 index 0000000000000000000000000000000000000000..fb6a962976e3e94f1fa37fb9dbe88d889f6eabd8 --- /dev/null +++ b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/plotSolution.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ---------------------------------------------- +# plot photon data assuming a 1D problem +# give snapshot number as cmdline arg to plot +# single snapshot, otherwise this script plots +# all snapshots available in the workdir +# ---------------------------------------------- + +import os +import sys + +import matplotlib as mpl +import numpy as np +import swiftsimio +from matplotlib import pyplot as plt + +# Parameters users should/may tweak +snapshot_base = "output" # snapshot basename +fancy = True # fancy up the plots a bit +plot_analytical_solutions = True # overplot analytical solution + +# properties for all scatterplots +scatterplot_kwargs = { + "facecolor": "red", + "s": 4, + "alpha": 0.6, + "linewidth": 0.0, + "marker": ".", +} + +# properties for all analytical solution plots +analytical_solution_kwargs = {"linewidth": 1.0, "ls": "--", "c": "k", "alpha": 0.5} + +# ----------------------------------------------------------------------- + +if plot_analytical_solutions: + from makeIC import initial_condition + +mpl.rcParams["text.usetex"] = True + +# Read in cmdline arg: Are we plotting only one snapshot, or all? +plot_all = False # plot all snapshots +try: + snapnr = int(sys.argv[1]) +except IndexError: + plot_all = True + + +def get_snapshot_list(snapshot_basename="output"): + """ + Find the snapshot(s) that are to be plotted + and return their names as list + """ + + snaplist = [] + + if plot_all: + dirlist = os.listdir() + for f in dirlist: + if f.startswith(snapshot_basename) and f.endswith("hdf5"): + snaplist.append(f) + + snaplist = sorted(snaplist) + + else: + fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5" + if not os.path.exists(fname): + print("Didn't find file", fname) + quit(1) + snaplist.append(fname) + + return snaplist + + +def plot_photons(filename, energy_boundaries=None, flux_boundaries=None): + """ + Create the actual plot. + + filename: file to work with + energy_boundaries: list of [E_min, E_max] for each photon group. + If none, limits are set automatically. + flux_boundaries: list of [F_min, F_max] for each photon group. + If none, limits are set automatically. + """ + + print("working on", filename) + + # Read in data firt + data = swiftsimio.load(filename) + meta = data.metadata + scheme = str(meta.subgrid_scheme["RT Scheme"].decode("utf-8")) + + boxsize = meta.boxsize[0] + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + + for g in range(ngroups): + # workaround to access named columns data with swiftsimio visualisaiton + new_attribute_str = "radiation_energy" + str(g + 1) + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + setattr(data.gas, new_attribute_str, en) + + # prepare also the fluxes + for direction in ["X"]: + new_attribute_str = "radiation_flux" + str(g + 1) + direction + f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction) + setattr(data.gas, new_attribute_str, f) + + part_positions = data.gas.coordinates[:, 0].copy() + + # get analytical solutions + if plot_analytical_solutions: + + time = meta.time + speed = meta.reduced_lightspeed + + advected_positions = data.gas.coordinates[:].copy() + advected_positions[:, 0] -= speed * time + nparts = advected_positions.shape[0] + # add periodicity corrections + negatives = advected_positions < 0.0 + if negatives.any(): + while advected_positions.min() < 0.0: + advected_positions[negatives] += boxsize + overshooters = advected_positions > boxsize + if overshooters.any(): + while advected_positions.max() > boxsize: + advected_positions[overshooters] -= boxsize + + analytical_solutions = np.zeros((nparts, ngroups), dtype=np.float64) + for p in range(part_positions.shape[0]): + E, F = initial_condition(advected_positions[p]) + for g in range(ngroups): + analytical_solutions[p, g] = E[g] + + fig = plt.figure(figsize=(5.05 * ngroups, 5.4), dpi=200) + figname = filename[:-5] + ".png" + + for g in range(ngroups): + + # plot energy density + new_attribute_str = "radiation_energy" + str(g + 1) + photon_energy = getattr(data.gas, new_attribute_str) + + volumes = data.gas.masses / data.gas.densities + photon_energy_density = photon_energy / volumes + + ax = fig.add_subplot(2, ngroups, g + 1) + s = np.argsort(part_positions) + if plot_analytical_solutions: + ax.plot( + part_positions[s], + analytical_solutions[s, g], + **analytical_solution_kwargs, + label="analytical solution", + ) + ax.scatter( + part_positions, + photon_energy_density, + **scatterplot_kwargs, + label="simulation", + ) + ax.legend() + + ax.set_title("Group {0:2d}".format(g + 1)) + if g == 0: + ax.set_ylabel( + "Energy Density [$" + + photon_energy_density.units.latex_representation() + + "$]" + ) + ax.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]") + + # plot flux X + new_attribute_str = "radiation_flux" + str(g + 1) + "X" + photon_flux = getattr(data.gas, new_attribute_str) + + if scheme.startswith("SPH M1closure"): + photon_flux = photon_flux / volumes + + photon_flux = photon_flux.to("erg/cm**2/s") + + ax2 = fig.add_subplot(2, ngroups, g + 1 + ngroups) + ax2.scatter(part_positions, photon_flux, **scatterplot_kwargs) + + if g == 0: + ax2.set_ylabel( + "Flux X [$" + photon_flux.units.latex_representation() + "$]" + ) + ax2.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]") + + # add title + title = filename.replace("_", r"\_") # exception handle underscore for latex + if meta.cosmology is not None: + title += ", $z$ = {0:.2e}".format(meta.z) + title += ", $t$ = {0:.2e}".format(meta.time) + fig.suptitle(title) + + plt.tight_layout() + plt.savefig(figname) + plt.close() + + return + + +if __name__ == "__main__": + + snaplist = get_snapshot_list(snapshot_base) + + for f in snaplist: + plot_photons(f) diff --git a/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/rt_advection1D.yml b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/rt_advection1D.yml new file mode 100644 index 0000000000000000000000000000000000000000..d34aabc03d321e595a3e882c852ebaf7e4539deb --- /dev/null +++ b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/rt_advection1D.yml @@ -0,0 +1,81 @@ +MetaData: + run_name: RT_advection-1D + +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1. + UnitLength_in_cgs: 1. + UnitVelocity_in_cgs: 1. + UnitCurrent_in_cgs: 1. + UnitTemp_in_cgs: 1. + +# Parameters governing the time integration +TimeIntegration: + max_nr_rt_subcycles: 1 + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 4.e-1 # The end time of the simulation (in internal units). + dt_min: 1.e-8 # The minimal time-step size of the simulation (in internal units). + dt_max: 1.e-02 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: output # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 4.e-2 + +# Parameters governing the conserved quantities statistics +Statistics: + time_first: 0. + delta_time: 4.e-2 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.6 # Courant-Friedrich-Levy condition for time integration. + minimal_temperature: 10. # Kelvin + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./advection_1D.hdf5 # The file to read + periodic: 1 # periodic ICs + +Restarts: + delta_hours: 72 # (Optional) decimal hours between dumps of restart files. + +GEARRT: + f_reduce_c: 1. # reduce the speed of light for the RT solver by multiplying c with this factor + f_limit_cooling_time: 0.0 # (Optional) multiply the cooling time by this factor when estimating maximal next time step. Set to 0.0 to turn computation of cooling time off. + CFL_condition: 0.4 # CFL condition for RT, independent of hydro + photon_groups_Hz: [0., 1., 2.] # Lower photon frequency group bin edges in Hz. Needs to have exactly N elements, where N is the configured number of bins --with-RT=GEAR_N + stellar_luminosity_model: const # Which model to use to determine the stellar luminosities. + const_stellar_luminosities_LSol: [0., 0., 0.] # (Conditional) constant star luminosities for each photon frequency group to use if stellar_luminosity_model:const is set, in units of Solar Luminosity. + hydrogen_mass_fraction: 0.76 # total hydrogen (H + H+) mass fraction in the metal-free portion of the gas + stellar_spectrum_type: 0 # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum. + stellar_spectrum_const_max_frequency_Hz: 1.e17 # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. + skip_thermochemistry: 1 # ignore thermochemistry. + set_initial_ionization_mass_fractions: 1 # (Optional) manually overwrite initial mass fraction of each species (using the values you set below) + mass_fraction_HI: 0.76 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HI mass fractions to this value + mass_fraction_HII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HII mass fractions to this value + mass_fraction_HeI: 0.24 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeI mass fractions to this value + mass_fraction_HeII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeII mass fractions to this value + mass_fraction_HeIII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeIII mass fractions to this value + +SPHM1RT: + cred: 2.99792458e10 # reduce the speed of light in the code unit + CFL_condition: 0.1 # CFL condition for RT, independent of hydro + photon_groups_Hz: [1., 2.] # Photon frequency group bin edges in Hz. Needs to be 1 less than the number of groups (N) requested during the configuration (--with-RT=SPHM1RT_N). + use_const_emission_rates: 1 # (Optional) use constant emission rates for stars as defined with star_emission_rates parameter + star_emission_rates: [1e-32, 1e-32, 1e-32] # (Optional) constant star emission rates for each photon frequency group to use if use_constant_emission_rates is set. + stellar_spectrum_type: 0 # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum. + stellar_spectrum_const_max_frequency_Hz: 1.e17 # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. + skip_thermochemistry: 1 # (Optional) skip the thermochemistry. This is intended only for debugging and testing the radiation transport, as it breaks the purpose of RT. + init_mass_fraction_metal: 0. # (Optional) Inital mass fraction of particle mass in *all* metals (if it is set, the initial fraction will be over-written.) + init_mass_fraction_Hydrogen: 0.752 # (Conditional) (if init_mass_fraction_metal != -1.0f) Inital mass fraction of particle mass in Hydrogen + init_mass_fraction_Helium: 0.248 # (Conditional) (if init_mass_fraction_metal != -1.0f) Inital mass fraction of particle mass in Helium + useabundances: 0 # (Optional) use the species abundances below, instead of reading from initial condition + init_species_abundance_e: 0.0 # (Conditional) (if useabundances==1) free electron abundances (in unit hydrogen number density:nH) + init_species_abundance_HI: 1.0 # (Conditional) (if useabundances==1) HI abundances (in unit hydrogen number density:nH) + init_species_abundance_HII: 0.0 # (Conditional) (if useabundances==1) HII abundances (in unit hydrogen number density:nH) + init_species_abundance_HeI: 0.08244680851 # (Conditional) (if useabundances==1) HeI abundances (in unit hydrogen number density:nH) + init_species_abundance_HeII: 0.0 # (Conditional) (if useabundances==1) HeII abundances (in unit hydrogen number density:nH) + init_species_abundance_HeIII: 0.0 # (Conditional) (if useabundances==1) HeIII abundances (in unit hydrogen number density:nH) diff --git a/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/run.sh b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..13e8331da60a1f432ff5c966c4f2da997bfff19a --- /dev/null +++ b/examples/RadiativeTransferTests/AdvectionDifferentTimeStepSizes_1D/run.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# make run.sh fail if a subcommand fails +set -e +set -o pipefail + +if [ ! -f advection_1D.hdf5 ]; then + echo "Generating ICs" + python3 makeIC.py +fi + +# Run SWIFT with RT +../../../swift \ + --hydro \ + --threads=4 \ + --verbose=0 \ + --radiation \ + --stars \ + --feedback \ + --external-gravity \ + ./rt_advection1D.yml 2>&1 | tee output.log + +python3 ./plotSolution.py diff --git a/examples/RadiativeTransferTests/Advection_2D/makeIC.py b/examples/RadiativeTransferTests/Advection_2D/makeIC.py index 923f9a56bf5964d9fa4dcea4854ce62e64a9fc04..e9742cbd6279129c696a6e5eb9230c29868870f4 100755 --- a/examples/RadiativeTransferTests/Advection_2D/makeIC.py +++ b/examples/RadiativeTransferTests/Advection_2D/makeIC.py @@ -40,7 +40,7 @@ from swiftsimio import Writer # define unit system to use unitsystem = unyt.unit_systems.cgs_unit_system -# Box is 1 Mpc +# define box size boxsize = 1e10 * unitsystem["length"] # number of photon groups diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/README b/examples/RadiativeTransferTests/CollidingBeams_1D/README new file mode 100644 index 0000000000000000000000000000000000000000..0aaee5bb32546bc83f47a0bbb1c9c3d9178fabea --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/README @@ -0,0 +1,13 @@ +Collide two photon packets with each other, and see what happens. +Two photon groups contain packets using a top hat functions. One of +them has the lower value at zero, the other is at nonzero. The third +photon group packets are Gaussians. +Ideally, they should just pass through each other. But that is not +what moment based methods do with radiation. + + +To run with GEAR-RT, compile SWIFT with + + --with-rt=GEAR_2 --with-rt-riemann-solver=GLF --with-hydro-dimension=1 --with-hydro=gizmo-mfv \ + --with-riemann-solver=hllc --with-stars=GEAR --with-feedback=none --with-grackle=$GRACKLE_ROOT + diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py b/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py new file mode 100755 index 0000000000000000000000000000000000000000..ecfd7a24df2557f895a6115adffe9a6721f37c51 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/makeIC.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# 2022 Tsang Keung Chan (chantsangkeung@gmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ----------------------------------------------------------- +# Add initial conditions for photon energies and fluxes +# for 1D advection of photons. +# First photon group: Top hat function with zero as the +# baseline. +# Second photon group: Top hat function with nonzero value +# as the baseline. +# Third photon group: Gaussian. +# ----------------------------------------------------------- + +import h5py +import numpy as np +import unyt +from swiftsimio import Writer + +# define unit system to use +unitsystem = unyt.unit_systems.cgs_unit_system + +# Box is 1 Mpc +boxsize = 1e10 * unitsystem["length"] + +# number of photon groups +nPhotonGroups = 3 + +# number of particles in each dimension +n_p = 1000 + +# filename of ICs to be generated +outputfilename = "collision_1D.hdf5" + + +def initial_condition(x): + """ + The initial conditions that will be advected + + x: particle position. 3D unyt array + + returns: + E: photon energy density for each photon group. List of scalars with size of nPhotonGroups + F: photon flux for each photon group. List with size of nPhotonGroups of numpy arrays of shape (3,) + """ + + # you can make the photon quantities unitless, the units will + # already have been written down in the writer. + + E_list = [] + F_list = [] + + # Group 1 Photons: + # ------------------- + + F = np.zeros(3, dtype=np.float64) + + if x[0] < 0.33 * boxsize: + E = 1.0 + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + elif x[0] < 0.66 * boxsize: + E = 0.0 + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + else: + E = 1.0 + F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + # Group 2 Photons: + # ------------------- + + F = np.zeros(3, dtype=np.float64) + if x[0] < 0.33 * boxsize: + E = 3.0 + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + elif x[0] < 0.66 * boxsize: + E = 1.0 + if x[0] < 0.5 * boxsize: + F[0] = unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + else: + F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + else: + E = 3.0 + F[0] = -unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + # Group 3 Photons: + # ------------------- + sigma = 0.05 * boxsize + if x[0] < 0.5 * boxsize: + mean = 0.33 / 2 * boxsize + sign = 1 + else: + mean = (5.0 / 6.0) * boxsize + sign = -1 + amplitude = 2.0 + + E = amplitude * np.exp(-(x[0] - mean) ** 2 / (2 * sigma ** 2)) + F = np.zeros(3, dtype=np.float64) + F[0] = sign * unyt.c.to(unitsystem["length"] / unitsystem["time"]) * E + + E_list.append(E) + F_list.append(F) + + return E_list, F_list + + +if __name__ == "__main__": + + xp = unyt.unyt_array(np.zeros((n_p, 3), dtype=np.float64), boxsize.units) + + dx = boxsize / n_p + + for i in range(n_p): + xp[i, 0] = (i + 0.5) * dx + + w = Writer(unyt.unit_systems.cgs_unit_system, boxsize, dimension=1) + + w.gas.coordinates = xp + w.gas.velocities = np.zeros(xp.shape) * (unyt.cm / unyt.s) + w.gas.masses = np.ones(xp.shape[0], dtype=np.float64) * 1000 * unyt.g + w.gas.internal_energy = ( + np.ones(xp.shape[0], dtype=np.float64) * (300.0 * unyt.kb * unyt.K) / unyt.g + ) + + # Generate initial guess for smoothing lengths based on MIPS + w.gas.generate_smoothing_lengths(boxsize=boxsize, dimension=1) + + # If IDs are not present, this automatically generates + w.write(outputfilename) + + # Now open file back up again and add photon groups + # you can make them unitless, the units have already been + # written down in the writer. In this case, it's in cgs. + + F = h5py.File(outputfilename, "r+") + header = F["Header"] + nparts = header.attrs["NumPart_ThisFile"][0] + parts = F["/PartType0"] + + for grp in range(nPhotonGroups): + dsetname = "PhotonEnergiesGroup{0:d}".format(grp + 1) + energydata = np.zeros((nparts), dtype=np.float32) + parts.create_dataset(dsetname, data=energydata) + + dsetname = "PhotonFluxesGroup{0:d}".format(grp + 1) + # if dsetname not in parts.keys(): + fluxdata = np.zeros((nparts, 3), dtype=np.float32) + parts.create_dataset(dsetname, data=fluxdata) + + for p in range(nparts): + E, Flux = initial_condition(xp[p]) + for g in range(nPhotonGroups): + Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1) + parts[Esetname][p] = E[g] + Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1) + parts[Fsetname][p] = Flux[g] + + # from matplotlib import pyplot as plt + # plt.figure() + # for g in range(nPhotonGroups): + # Esetname = "PhotonEnergiesGroup{0:d}".format(g+1) + # plt.plot(xp[:,0], parts[Esetname], label="E "+str(g+1)) + # # Fsetname = "PhotonFluxesGroup{0:d}".format(g+1) + # # plt.plot(xp[:,0], parts[Fsetname][:,0], label="F "+str(g+1)) + # plt.legend() + # plt.show() + + F.close() diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py b/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py new file mode 100755 index 0000000000000000000000000000000000000000..4d5285467455e775e225a02a05378060070599c0 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/plotEnergies.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ---------------------------------------------- +# Plot the total photon energies in each photon +# group over the course of several snapshots +# ---------------------------------------------- + +import os +import sys + +import matplotlib as mpl +import numpy as np +import swiftsimio +from matplotlib import pyplot as plt + +# Parameters users should/may tweak +snapshot_base = "output" # snapshot basename + + +# ----------------------------------------------------------------------- + +mpl.rcParams["text.usetex"] = True + +# Read in cmdline arg: Are we plotting only one snapshot, or all? +plot_all = False # plot all snapshots +try: + snapnr = int(sys.argv[1]) +except IndexError: + plot_all = True + + +def get_snapshot_list(snapshot_basename="output"): + """ + Find the snapshot(s) that are to be plotted + and return their names as list """ + + snaplist = [] + + if plot_all: + dirlist = os.listdir() + for f in dirlist: + if f.startswith(snapshot_basename) and f.endswith("hdf5"): + snaplist.append(f) + + snaplist = sorted(snaplist) + + else: + fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5" + if not os.path.exists(fname): + print("Didn't find file", fname) + quit(1) + snaplist.append(fname) + + return snaplist + + +def plot_energies(snap_nrs, energy_sums): + """ + Create the actual plot. + """ + + # Plot plot plot! + fig = plt.figure(figsize=(5.0, 5.4), dpi=200) + figname = "energy_budget.png" + + ax1 = fig.add_subplot(1, 1, 1) + + ngroups = energy_sums.shape[1] + + for g in range(ngroups): + ax1.plot(snap_nrs, energy_sums[:, g], label=None) + ax1.scatter(snap_nrs, energy_sums[:, g], label="group {0:d}".format(g + 1)) + + ax1.set_xlabel("Snapshot") + ax1.set_ylabel( + r"Total energy [$" + energy_sums.units.latex_representation() + "$]", + usetex=True, + ) + + # add title + title = "Energy Budget" + ax1.set_title(title) + ax1.legend() + + plt.tight_layout() + plt.savefig(figname) + plt.close() + + return + + +def get_photon_energies(snaplist): + """ + Get total photon energy in each photon group for a list of + snapshots specified by `snaplist` + + snaplist: list of snapshot filenames + + returns: + + snap_nrs : list of integers of snapshot numbers + energy_sums: np.array(shape=(len snaplist, ngroups)) of + total photon energies per group per snapshot + """ + + data = swiftsimio.load(snaplist[0]) + meta = data.metadata + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + + energy_sums = np.zeros((len(snaplist), ngroups)) + snap_nrs = np.zeros(len(snaplist), dtype=int) + + for f, filename in enumerate(snaplist): + + data = swiftsimio.load(filename) + + for g in range(ngroups): + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + energy_sums[f, g] = en.sum() + + nrstring = filename[len(snapshot_base) + 1 : -len(".hdf5")] + nr = int(nrstring) + snap_nrs[f] = nr + + energy_sums = energy_sums * en.units + + sortind = np.argsort(snap_nrs) + snap_nrs = snap_nrs[sortind] + energy_sums = energy_sums[sortind] + + return snap_nrs, energy_sums + + +if __name__ == "__main__": + + snaplist = get_snapshot_list(snapshot_base) + snap_nrs, energy_sums = get_photon_energies(snaplist) + + plot_energies(snap_nrs, energy_sums) diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py b/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py new file mode 100755 index 0000000000000000000000000000000000000000..5e372d67976336086dac3579a524790242d238e8 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/plotSolution.py @@ -0,0 +1,264 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ---------------------------------------------- +# plot photon data assuming a 1D problem +# give snapshot number as cmdline arg to plot +# single snapshot, otherwise this script plots +# all snapshots available in the workdir +# ---------------------------------------------- + +import os +import sys + +import matplotlib as mpl +import numpy as np +import swiftsimio +from matplotlib import pyplot as plt + +# Parameters users should/may tweak +snapshot_base = "output" # snapshot basename + +# properties for all scatterplots +scatterplot_kwargs = { + "facecolor": "red", + "s": 4, + "alpha": 0.6, + "linewidth": 0.0, + "marker": ".", +} + +# ----------------------------------------------------------------------- + +mpl.rcParams["text.usetex"] = True + +# Read in cmdline arg: Are we plotting only one snapshot, or all? +plot_all = False # plot all snapshots +try: + snapnr = int(sys.argv[1]) +except IndexError: + plot_all = True + + +def get_snapshot_list(snapshot_basename="output"): + """ + Find the snapshot(s) that are to be plotted + and return their names as list + """ + + snaplist = [] + + if plot_all: + dirlist = os.listdir() + for f in dirlist: + if f.startswith(snapshot_basename) and f.endswith("hdf5"): + snaplist.append(f) + + snaplist = sorted(snaplist) + + else: + fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5" + if not os.path.exists(fname): + print("Didn't find file", fname) + quit(1) + snaplist.append(fname) + + return snaplist + + +def plot_photons(filename, energy_boundaries=None, flux_boundaries=None): + """ + Create the actual plot. + + filename: file to work with + energy_boundaries: list of [E_min, E_max] for each photon group. + If none, limits are set automatically. + flux_boundaries: list of [F_min, F_max] for each photon group. + If none, limits are set automatically. + """ + + print("working on", filename) + + # Read in data firt + data = swiftsimio.load(filename) + meta = data.metadata + scheme = str(meta.subgrid_scheme["RT Scheme"].decode("utf-8")) + + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + + for g in range(ngroups): + # workaround to access named columns data with swiftsimio visualisaiton + new_attribute_str = "radiation_energy" + str(g + 1) + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + setattr(data.gas, new_attribute_str, en) + + # prepare also the fluxes + for direction in ["X"]: + new_attribute_str = "radiation_flux" + str(g + 1) + direction + f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction) + setattr(data.gas, new_attribute_str, f) + + part_positions = data.gas.coordinates[:, 0].copy() + + # Plot plot plot! + fig = plt.figure(figsize=(5.0 * ngroups, 5.4), dpi=200) + figname = filename[:-5] + "-all-quantities.png" + + for g in range(ngroups): + + # plot energy + new_attribute_str = "radiation_energy" + str(g + 1) + photon_energy = getattr(data.gas, new_attribute_str) + + ax = fig.add_subplot(2, ngroups, g + 1) + + ax.scatter( + part_positions, photon_energy, **scatterplot_kwargs, label="simulation" + ) + ax.legend() + + ax.set_title("Group {0:2d}".format(g + 1)) + if g == 0: + ax.set_ylabel( + "Energies [$" + photon_energy.units.latex_representation() + "$]" + ) + ax.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]") + + if energy_boundaries is not None: + + if abs(energy_boundaries[g][1]) > abs(energy_boundaries[g][0]): + fixed_min = energy_boundaries[g][0] - 0.1 * abs(energy_boundaries[g][1]) + fixed_max = energy_boundaries[g][1] * 1.1 + else: + fixed_min = energy_boundaries[g][0] * 1.1 + fixed_max = energy_boundaries[g][1] + 0.1 * abs(energy_boundaries[g][0]) + ax.set_ylim(fixed_min, fixed_max) + + # plot flux X + new_attribute_str = "radiation_flux" + str(g + 1) + "X" + photon_flux = getattr(data.gas, new_attribute_str) + if scheme.startswith("GEAR M1closure"): + photon_flux = photon_flux.to("erg/cm**2/s") + elif scheme.startswith("SPH M1closure"): + photon_flux = photon_flux.to("erg*cm/s") + else: + print("Error: Unknown RT scheme " + scheme) + exit() + + ax = fig.add_subplot(2, ngroups, g + 1 + ngroups) + ax.scatter(part_positions, photon_flux, **scatterplot_kwargs) + + if g == 0: + ax.set_ylabel("Flux X [$" + photon_flux.units.latex_representation() + "$]") + ax.set_xlabel("x [$" + part_positions.units.latex_representation() + "$]") + + if flux_boundaries is not None: + + if abs(flux_boundaries[g][1]) > abs(flux_boundaries[g][0]): + fixed_min = flux_boundaries[g][0] - 0.1 * abs(flux_boundaries[g][1]) + fixed_max = flux_boundaries[g][1] * 1.1 + else: + fixed_min = flux_boundaries[g][0] * 1.1 + fixed_max = flux_boundaries[g][1] + 0.1 * abs(flux_boundaries[g][0]) + + ax.set_ylim(fixed_min, fixed_max) + + # add title + title = filename.replace("_", r"\_") # exception handle underscore for latex + if meta.cosmology is not None: + title += ", $z$ = {0:.2e}".format(meta.z) + title += ", $t$ = {0:.2e}".format(meta.time) + fig.suptitle(title) + + plt.tight_layout() + plt.savefig(figname) + plt.close() + + return + + +def get_minmax_vals(snaplist): + """ + Find minimal and maximal values for energy and flux, + so you can fix axes limits over all snapshots + + snaplist: list of snapshot filenames + + returns: + + energy_boundaries: list of [E_min, E_max] for each photon group + flux_boundaries: list of [Fx_min, Fy_max] for each photon group + """ + + emins = [] + emaxs = [] + fmins = [] + fmaxs = [] + + for filename in snaplist: + + data = swiftsimio.load(filename) + meta = data.metadata + + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + emin_group = [] + emax_group = [] + fluxmin_group = [] + fluxmax_group = [] + + for g in range(ngroups): + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + emin_group.append(en.min()) + emax_group.append(en.max()) + + for direction in ["X"]: + f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction) + fluxmin_group.append(f.min()) + fluxmax_group.append(f.max()) + + emins.append(emin_group) + emaxs.append(emax_group) + fmins.append(fluxmin_group) + fmaxs.append(fluxmax_group) + + energy_boundaries = [] + flux_boundaries = [] + for g in range(ngroups): + emin = min([emins[f][g] for f in range(len(snaplist))]) + emax = max([emaxs[f][g] for f in range(len(snaplist))]) + energy_boundaries.append([emin, emax]) + fmin = min([fmins[f][g] for f in range(len(snaplist))]) + fmax = max([fmaxs[f][g] for f in range(len(snaplist))]) + flux_boundaries.append([fmin, fmax]) + + return energy_boundaries, flux_boundaries + + +if __name__ == "__main__": + + snaplist = get_snapshot_list(snapshot_base) + energy_boundaries, flux_boundaries = get_minmax_vals(snaplist) + + for f in snaplist: + plot_photons( + f, energy_boundaries=energy_boundaries, flux_boundaries=flux_boundaries + ) diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml b/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml new file mode 100644 index 0000000000000000000000000000000000000000..25f24418dada5071893d5b8bc1679dfd77c4b614 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/rt_collision1D.yml @@ -0,0 +1,62 @@ +MetaData: + run_name: RT_collision-1D + +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1. + UnitLength_in_cgs: 1. + UnitVelocity_in_cgs: 1. + UnitCurrent_in_cgs: 1. + UnitTemp_in_cgs: 1. + +# Parameters governing the time integration +TimeIntegration: + max_nr_rt_subcycles: 1 + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 4.e-1 # The end time of the simulation (in internal units). + dt_min: 1.e-8 # The minimal time-step size of the simulation (in internal units). + dt_max: 1.e-02 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: output # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 2.e-2 + +# Parameters governing the conserved quantities statistics +Statistics: + time_first: 0. + delta_time: 4.e-2 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.6 # Courant-Friedrich-Levy condition for time integration. + minimal_temperature: 10. # Kelvin + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./collision_1D.hdf5 # The file to read + periodic: 1 # periodic ICs + +Restarts: + delta_hours: 72 # (Optional) decimal hours between dumps of restart files. + +GEARRT: + f_reduce_c: 1. # reduce the speed of light for the RT solver by multiplying c with this factor + f_limit_cooling_time: 0.0 # (Optional) multiply the cooling time by this factor when estimating maximal next time step. Set to 0.0 to turn computation of cooling time off. + CFL_condition: 0.8 # CFL condition for RT, independent of hydro + photon_groups_Hz: [0., 1., 2.] # Lower photon frequency group bin edges in Hz. Needs to have exactly N elements, where N is the configured number of bins --with-RT=GEAR_N + stellar_luminosity_model: const # Which model to use to determine the stellar luminosities. + const_stellar_luminosities_LSol: [0., 0., 0.] # (Conditional) constant star luminosities for each photon frequency group to use if stellar_luminosity_model:const is set, in units of Solar Luminosity. + hydrogen_mass_fraction: 0.76 # total hydrogen (H + H+) mass fraction in the metal-free portion of the gas + stellar_spectrum_type: 0 # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum. + stellar_spectrum_const_max_frequency_Hz: 1.e17 # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. + skip_thermochemistry: 1 # ignore thermochemistry. + set_initial_ionization_mass_fractions: 1 # (Optional) manually overwrite initial mass fraction of each species (using the values you set below) + mass_fraction_HI: 0.76 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HI mass fractions to this value + mass_fraction_HII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HII mass fractions to this value + mass_fraction_HeI: 0.24 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeI mass fractions to this value + mass_fraction_HeII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeII mass fractions to this value + mass_fraction_HeIII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeIII mass fractions to this value + diff --git a/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh b/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..1d121bcc7eeefbb6e257057f78cae61e1981cf58 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_1D/run.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# make run.sh fail if a subcommand fails +set -e +set -o pipefail + +if [ ! -f collision_1D.hdf5 ]; then + echo "Generating ICs" + python3 makeIC.py +fi + +# Run SWIFT with RT +../../../swift \ + --hydro \ + --threads=1 \ + --verbose=0 \ + --radiation \ + --stars \ + --feedback \ + --external-gravity \ + ./rt_collision1D.yml 2>&1 | tee output.log + +python3 ./plotEnergies.py +python3 ./plotSolution.py diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/README b/examples/RadiativeTransferTests/CollidingBeams_2D/README new file mode 100644 index 0000000000000000000000000000000000000000..7bc95ce3b54b0281536b7c1fea3bbbfce267e4e9 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/README @@ -0,0 +1,10 @@ +Collide packets of radiation against each other, and see how the M1 +closure treats them. + +Collide them horizontally, vertically, diagonally, and from the side. + +To run with GEAR-RT, configure SWIFT using + + --with-rt=GEAR_4 --with-rt-riemann-solver=GLF --with-hydro-dimension=2 --with-hydro=gizmo-mfv \ + --with-riemann-solver=hllc --with-stars=GEAR --with-feedback=none --with-grackle=$GRACKLE_ROOT + diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh b/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh new file mode 100755 index 0000000000000000000000000000000000000000..ae3c977064f5e7a408aa249c5fd9089b3c52ecb1 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/getGlass.sh @@ -0,0 +1,2 @@ +#!/bin/bash +wget http://virgodb.cosma.dur.ac.uk/swift-webstorage/ICs/glassPlane_128.hdf5 diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py b/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py new file mode 100755 index 0000000000000000000000000000000000000000..be2d50a7bb2a6503195a0d6c700f3ee6615091de --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/makeIC.py @@ -0,0 +1,302 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ------------------------------------------------------------- +# Add initial conditions for photon energies and fluxes +# for 2D advection of photons. +# First photon group: Top hat function with zero as the +# baseline, advects in x direction +# Second photon group: Top hat function with nonzero value +# as the baseline, advcts in y direction. +# Third photon group: Gaussian advecting diagonally +# Fourth photon group: Circle moving radially from the center +# ------------------------------------------------------------- + +import h5py +import numpy as np +import unyt +from swiftsimio import Writer + +# define unit system to use +unitsystem = unyt.unit_systems.cgs_unit_system + +# define box size +boxsize = 1e10 * unitsystem["length"] + +# number of photon groups +nPhotonGroups = 4 + +# filename of ICs to be generated +outputfilename = "collision_2D.hdf5" + + +def get_radiation_IC(pos, numPart, ngroups): + + Elist = [] + Flist = [] + + x = pos[:, 0] + y = pos[:, 1] + c = unyt.c.to(unitsystem["length"] / unitsystem["time"]) + + for g in range(ngroups): + + E = np.zeros(numPart) + F = np.zeros((numPart, 3)) + + # prepare particle array masks + flux_sign = np.ones(numPart, dtype=float) + + if g == 0: + # horizontal beams + # ----------------------- + + left = np.logical_and(x > 0.0, x < 1.0 / 3) + right = np.logical_and(x > 2.0 / 3, x < 1.0) + xcriterion = np.logical_or(left, right) + ycriterion = np.logical_and(y > 0.4, y < 0.6) + is_max = np.logical_and(xcriterion, ycriterion) + + flux_sign[right] = -1.0 + + Emax = 1.0 + E[is_max] = Emax + F[is_max, 0] = Emax * c * flux_sign[is_max] + + if g == 1: + # vertical beams, nonzero energy everywhere + # ------------------------------------------- + + top = np.logical_and(y > 2.0 / 3.0, y < 1.0) + bottom = np.logical_and(y > 0.0, y < 1.0 / 3.0) + + xcriterion = np.logical_and(x > 0.4, x < 0.6) + ycriterion = np.logical_or(top, bottom) + is_max = np.logical_and(xcriterion, ycriterion) + + flux_sign[y > 0.5] = -1.0 + + Emax = 2.0 + Emin = 1.0 + E[:] = Emin + E[is_max] = Emax + F[:, 1] = E * c * flux_sign + + if g == 2: + # diagonal beams + # ------------------------------------------- + + width = 0.1 + l = np.sqrt(2) / 3.0 + sin = np.sin(np.pi / 4) + xprime = l * sin + x0 = xprime - 0.5 * width * sin + x1 = xprime + 0.5 * width * sin + x2 = 1 - xprime - 0.5 * width * sin + x3 = 1 - xprime + 0.5 * width * sin + + def upper_line(x): + return x + 0.5 * width + + def lower_line(x): + return x - 0.5 * width + + def descending_left(x, xprime): + return -(x - xprime) + xprime + + def descending_right(x, xprime): + return -(x - 1.0 + xprime) + 1.0 - xprime + + first = x < x0 + lower_first = np.logical_and(y < upper_line(x), y > lower_line(x)) + firstcond = np.logical_and(first, lower_first) + + second = np.logical_and(x > x0, x < x1) + lower_second = np.logical_and( + y > lower_line(x), y < descending_left(x, xprime) + ) + secondcond = np.logical_and(second, lower_second) + + third = np.logical_and(x > x2, x < x3) + upper_third = np.logical_and( + y < upper_line(x), y > descending_right(x, xprime) + ) + thirdcond = np.logical_and(third, upper_third) + + fourth = np.logical_and(x > x3, x < 1.0) + upper_fourth = np.logical_and(y < upper_line(x), y > lower_line(x)) + fourthcond = np.logical_and(fourth, upper_fourth) + + Emax = 1.0 + E[firstcond] = Emax + E[secondcond] = Emax + E[thirdcond] = Emax + E[fourthcond] = Emax + + flux_sign[thirdcond] = -1.0 + flux_sign[fourthcond] = -1.0 + + F[:, 0] = E * c * flux_sign / np.sqrt(2) + F[:, 1] = E * c * flux_sign / np.sqrt(2) + + if g == 3: + # diagonal beams that meed in the middle + # ------------------------------------------- + + width = 0.1 + l = np.sqrt(2) / 3.0 + sin = np.sin(np.pi / 4) + xprime = l * sin + x0 = xprime - 0.5 * width * sin + x1 = xprime + 0.5 * width * sin + x2 = 1 - xprime - 0.5 * width * sin + x3 = 1 - xprime + 0.5 * width * sin + + def upper_line(x): + return x + 0.5 * width + + def lower_line(x): + return x - 0.5 * width + + def descending_left(x, xprime): + return -(x - xprime) + xprime + + def descending_lower(x, xprime): + return -(x - 1 + xprime) + xprime - 0.5 * width + + def descending_upper(x, xprime): + return -(x - 1.0 + xprime) + xprime + 0.5 * width + + def ascending_right(x, xprime): + return (x - 1 + xprime) + xprime + + first = x < x0 + lower_first = np.logical_and(y < upper_line(x), y > lower_line(x)) + firstcond = np.logical_and(first, lower_first) + + second = np.logical_and(x > x0, x < x1) + lower_second = np.logical_and( + y > lower_line(x), y < descending_left(x, xprime) + ) + secondcond = np.logical_and(second, lower_second) + + third = np.logical_and(x > x2, x < x3) + upper_third = np.logical_and( + y < ascending_right(x, xprime), y > descending_lower(x, xprime) + ) + thirdcond = np.logical_and(third, upper_third) + + fourth = np.logical_and(x > x3, x < 1.0) + upper_fourth = np.logical_and( + y < descending_upper(x, xprime), y > descending_lower(x, xprime) + ) + fourthcond = np.logical_and(fourth, upper_fourth) + + Emax = 1.0 + E[firstcond] = Emax + E[secondcond] = Emax + E[thirdcond] = Emax + E[fourthcond] = Emax + + flux_sign[thirdcond] = -1.0 + flux_sign[fourthcond] = -1.0 + + F[:, 0] = E * c * flux_sign / np.sqrt(2) + F[:, 1] = E * c / np.sqrt(2) + + # histE, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=E, bins=50) + # histFx, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=F[:,0], bins=50) + # histFy, _, _ = np.histogram2d(pos[:,0], pos[:,1], weights=F[:,1], bins=50) + # from matplotlib import pyplot as plt + # + # fig = plt.figure() + # ax1 = fig.add_subplot(1, 3, 1) + # ax1.imshow(histE.T, origin="lower") + # ax2 = fig.add_subplot(1, 3, 2) + # ax2.imshow(histFx.T, origin="lower") + # ax3 = fig.add_subplot(1, 3, 3) + # ax3.imshow(histFy.T, origin="lower") + # plt.show() + + Elist.append(E) + Flist.append(F) + + return Elist, Flist + + +if __name__ == "__main__": + glass = h5py.File("glassPlane_128.hdf5", "r") + + # Read particle positions and h from the glass + pos = glass["/PartType0/Coordinates"][:, :] + h = glass["/PartType0/SmoothingLength"][:] + glass.close() + + numPart = np.size(h) + + # get radiation IC while particle coordinates are still [0, 1) + Elist, Flist = get_radiation_IC(pos, numPart, nPhotonGroups) + + pos *= boxsize + h *= boxsize + + w = Writer(unyt.unit_systems.cgs_unit_system, boxsize, dimension=2) + + w.gas.coordinates = pos + w.gas.velocities = np.zeros((numPart, 3)) * (unyt.cm / unyt.s) + w.gas.masses = np.ones(numPart, dtype=np.float64) * 1000 * unyt.g + w.gas.internal_energy = ( + np.ones(numPart, dtype=np.float64) * (300.0 * unyt.kb * unyt.K) / unyt.g + ) + + # Generate initial guess for smoothing lengths based on MIPS + w.gas.smoothing_length = h + + # If IDs are not present, this automatically generates + w.write(outputfilename) + + # Now open file back up again and add photon groups + # you can make them unitless, the units have already been + # written down in the writer. In this case, it's in cgs. + + F = h5py.File(outputfilename, "r+") + header = F["Header"] + nparts = header.attrs["NumPart_ThisFile"][0] + parts = F["/PartType0"] + + for grp in range(nPhotonGroups): + dsetname = "PhotonEnergiesGroup{0:d}".format(grp + 1) + energydata = np.zeros(nparts, dtype=np.float32) + parts.create_dataset(dsetname, data=energydata) + + dsetname = "PhotonFluxesGroup{0:d}".format(grp + 1) + # if dsetname not in parts.keys(): + fluxdata = np.zeros((nparts, 3), dtype=np.float32) + parts.create_dataset(dsetname, data=fluxdata) + + for g in range(nPhotonGroups): + Esetname = "PhotonEnergiesGroup{0:d}".format(g + 1) + parts[Esetname][:] = Elist[g][:] + Fsetname = "PhotonFluxesGroup{0:d}".format(g + 1) + parts[Fsetname][:, :] = Flist[g][:, :] + + F.close() diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py b/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py new file mode 100755 index 0000000000000000000000000000000000000000..4d5285467455e775e225a02a05378060070599c0 --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/plotEnergies.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2022 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ---------------------------------------------- +# Plot the total photon energies in each photon +# group over the course of several snapshots +# ---------------------------------------------- + +import os +import sys + +import matplotlib as mpl +import numpy as np +import swiftsimio +from matplotlib import pyplot as plt + +# Parameters users should/may tweak +snapshot_base = "output" # snapshot basename + + +# ----------------------------------------------------------------------- + +mpl.rcParams["text.usetex"] = True + +# Read in cmdline arg: Are we plotting only one snapshot, or all? +plot_all = False # plot all snapshots +try: + snapnr = int(sys.argv[1]) +except IndexError: + plot_all = True + + +def get_snapshot_list(snapshot_basename="output"): + """ + Find the snapshot(s) that are to be plotted + and return their names as list """ + + snaplist = [] + + if plot_all: + dirlist = os.listdir() + for f in dirlist: + if f.startswith(snapshot_basename) and f.endswith("hdf5"): + snaplist.append(f) + + snaplist = sorted(snaplist) + + else: + fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5" + if not os.path.exists(fname): + print("Didn't find file", fname) + quit(1) + snaplist.append(fname) + + return snaplist + + +def plot_energies(snap_nrs, energy_sums): + """ + Create the actual plot. + """ + + # Plot plot plot! + fig = plt.figure(figsize=(5.0, 5.4), dpi=200) + figname = "energy_budget.png" + + ax1 = fig.add_subplot(1, 1, 1) + + ngroups = energy_sums.shape[1] + + for g in range(ngroups): + ax1.plot(snap_nrs, energy_sums[:, g], label=None) + ax1.scatter(snap_nrs, energy_sums[:, g], label="group {0:d}".format(g + 1)) + + ax1.set_xlabel("Snapshot") + ax1.set_ylabel( + r"Total energy [$" + energy_sums.units.latex_representation() + "$]", + usetex=True, + ) + + # add title + title = "Energy Budget" + ax1.set_title(title) + ax1.legend() + + plt.tight_layout() + plt.savefig(figname) + plt.close() + + return + + +def get_photon_energies(snaplist): + """ + Get total photon energy in each photon group for a list of + snapshots specified by `snaplist` + + snaplist: list of snapshot filenames + + returns: + + snap_nrs : list of integers of snapshot numbers + energy_sums: np.array(shape=(len snaplist, ngroups)) of + total photon energies per group per snapshot + """ + + data = swiftsimio.load(snaplist[0]) + meta = data.metadata + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + + energy_sums = np.zeros((len(snaplist), ngroups)) + snap_nrs = np.zeros(len(snaplist), dtype=int) + + for f, filename in enumerate(snaplist): + + data = swiftsimio.load(filename) + + for g in range(ngroups): + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + energy_sums[f, g] = en.sum() + + nrstring = filename[len(snapshot_base) + 1 : -len(".hdf5")] + nr = int(nrstring) + snap_nrs[f] = nr + + energy_sums = energy_sums * en.units + + sortind = np.argsort(snap_nrs) + snap_nrs = snap_nrs[sortind] + energy_sums = energy_sums[sortind] + + return snap_nrs, energy_sums + + +if __name__ == "__main__": + + snaplist = get_snapshot_list(snapshot_base) + snap_nrs, energy_sums = get_photon_energies(snaplist) + + plot_energies(snap_nrs, energy_sums) diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py b/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py new file mode 100755 index 0000000000000000000000000000000000000000..8f100e5fae99c15fb18d63b3f572db6f045d802f --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/plotSolution.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2021 Mladen Ivkovic (mladen.ivkovic@hotmail.com) +# +# 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 <http://www.gnu.org/licenses/>. +# +############################################################################## + + +# ---------------------------------------------------- +# plot photon data for 2D problems +# give snapshot number as cmdline arg to plot +# single snapshot, otherwise this script plots +# all snapshots available in the workdir +# ---------------------------------------------------- + +import gc +import os +import sys + +import matplotlib as mpl +import swiftsimio +from matplotlib import pyplot as plt +from mpl_toolkits.axes_grid1 import make_axes_locatable + +# Parameters users should/may tweak +plot_all_data = False # plot all groups and all photon quantities +snapshot_base = "output" # snapshot basename +fancy = True # fancy up the plots a bit? + +# parameters for imshow plots +imshow_kwargs = {"origin": "lower", "cmap": "viridis"} + + +projection_kwargs = {"resolution": 1024, "parallel": True} +# ----------------------------------------------------------------------- + + +# Read in cmdline arg: Are we plotting only one snapshot, or all? +plot_all = False +try: + snapnr = int(sys.argv[1]) +except IndexError: + plot_all = True + +mpl.rcParams["text.usetex"] = True + + +def get_snapshot_list(snapshot_basename="output"): + """ + Find the snapshot(s) that are to be plotted + and return their names as list + """ + + snaplist = [] + + if plot_all: + dirlist = os.listdir() + for f in dirlist: + if f.startswith(snapshot_basename) and f.endswith("hdf5"): + snaplist.append(f) + + snaplist = sorted(snaplist) + + else: + fname = snapshot_basename + "_" + str(snapnr).zfill(4) + ".hdf5" + if not os.path.exists(fname): + print("Didn't find file", fname) + quit(1) + snaplist.append(fname) + + return snaplist + + +def set_colorbar(ax, im): + divider = make_axes_locatable(ax) + cax = divider.append_axes("right", size="5%", pad=0.05) + plt.colorbar(im, cax=cax) + return + + +def plot_photons(filename, energy_boundaries=None, flux_boundaries=None): + """ + Create the actual plot. + + filename: file to work with + energy_boundaries: list of [E_min, E_max] for each photon group. + If none, limits are set automatically. + flux_boundaries: list of [F_min, F_max] for each photon group. + If none, limits are set automatically. + """ + + print("working on", filename) + + # Read in data first + data = swiftsimio.load(filename) + meta = data.metadata + + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + xlabel_units_str = meta.boxsize.units.latex_representation() + + global imshow_kwargs + imshow_kwargs["extent"] = [0, meta.boxsize[0].v, 0, meta.boxsize[1].v] + + for g in range(ngroups): + # workaround to access named columns data with swiftsimio visualisaiton + # add mass weights to remove surface density dependence in images + new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1) + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + en = en * data.gas.masses + setattr(data.gas, new_attribute_str, en) + + if plot_all_data: + # prepare also the fluxes + # for direction in ["X", "Y", "Z"]: + for direction in ["X", "Y"]: + new_attribute_str = ( + "mass_weighted_radiation_flux" + str(g + 1) + direction + ) + f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction) + f *= data.gas.masses + setattr(data.gas, new_attribute_str, f) + + # get mass surface density projection that we'll use to remove density dependence in image + mass_map = swiftsimio.visualisation.projection.project_gas( + data, project="masses", **projection_kwargs + ) + + if plot_all_data: + fig = plt.figure(figsize=(5 * 3, 5.05 * ngroups), dpi=200) + figname = filename[:-5] + "-all-quantities.png" + + for g in range(ngroups): + + # get energy projection + new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1) + photon_map = swiftsimio.visualisation.projection.project_gas( + data, project=new_attribute_str, **projection_kwargs + ) + photon_map = photon_map / mass_map + + ax = fig.add_subplot(ngroups, 3, g * 3 + 1) + if energy_boundaries is not None: + imshow_kwargs["vmin"] = energy_boundaries[g][0] + imshow_kwargs["vmax"] = energy_boundaries[g][1] + im = ax.imshow(photon_map.T, **imshow_kwargs) + set_colorbar(ax, im) + ax.set_ylabel("Group {0:2d}".format(g + 1)) + ax.set_xlabel("x [$" + xlabel_units_str + "$]") + if g == 0: + ax.set_title("Energies") + + # get flux X projection + new_attribute_str = "mass_weighted_radiation_flux" + str(g + 1) + "X" + photon_map = swiftsimio.visualisation.projection.project_gas( + data, project=new_attribute_str, **projection_kwargs + ) + photon_map = photon_map / mass_map + + ax = fig.add_subplot(ngroups, 3, g * 3 + 2) + if flux_boundaries is not None: + imshow_kwargs["vmin"] = flux_boundaries[g][0] + imshow_kwargs["vmax"] = flux_boundaries[g][1] + im = ax.imshow(photon_map.T, **imshow_kwargs) + set_colorbar(ax, im) + ax.set_xlabel("x [$" + xlabel_units_str + "$]") + ax.set_ylabel("y [$" + xlabel_units_str + "$]") + if g == 0: + ax.set_title("Flux X") + + # get flux Y projection + new_attribute_str = "mass_weighted_radiation_flux" + str(g + 1) + "Y" + photon_map = swiftsimio.visualisation.projection.project_gas( + data, project=new_attribute_str, **projection_kwargs + ) + photon_map = photon_map / mass_map + + ax = fig.add_subplot(ngroups, 3, g * 3 + 3) + im = ax.imshow(photon_map.T, **imshow_kwargs) + set_colorbar(ax, im) + ax.set_xlabel("x [$" + xlabel_units_str + "$]") + ax.set_ylabel("y [$" + xlabel_units_str + "$]") + if g == 0: + ax.set_title("Flux Y") + + else: # plot just energies + + fig = plt.figure(figsize=(5 * ngroups, 5), dpi=200) + figname = filename[:-5] + ".png" + + for g in range(ngroups): + + # get projection + new_attribute_str = "mass_weighted_radiation_energy" + str(g + 1) + photon_map = swiftsimio.visualisation.projection.project_gas( + data, project=new_attribute_str, **projection_kwargs + ) + photon_map = photon_map / mass_map + + ax = fig.add_subplot(1, ngroups, g + 1) + if energy_boundaries is not None: + imshow_kwargs["vmin"] = energy_boundaries[g][0] + imshow_kwargs["vmax"] = energy_boundaries[g][1] + im = ax.imshow(photon_map.T, **imshow_kwargs) + set_colorbar(ax, im) + ax.set_title("Group {0:2d}".format(g + 1)) + if g == 0: + ax.set_ylabel("Energies") + + # Add title + title = filename.replace("_", r"\_") # exception handle underscore for latex + if meta.cosmology is not None: + title += ", $z$ = {0:.2e}".format(meta.z) + title += ", $t$ = {0:.2e}".format(meta.time) + fig.suptitle(title) + + plt.tight_layout() + plt.savefig(figname) + plt.close() + gc.collect() + + return + + +def get_minmax_vals(snaplist): + """ + Find minimal and maximal values for energy and flux, + so you can fix axes limits over all snapshots + + snaplist: list of snapshot filenames + + returns: + + energy_boundaries: list of [E_min, E_max] for each photon group + flux_boundaries: list of [Fx_min, Fy_max] for each photon group + """ + + emins = [] + emaxs = [] + fmins = [] + fmaxs = [] + + for filename in snaplist: + + data = swiftsimio.load(filename) + meta = data.metadata + + ngroups = int(meta.subgrid_scheme["PhotonGroupNumber"]) + emin_group = [] + emax_group = [] + fluxmin_group = [] + fluxmax_group = [] + + for g in range(ngroups): + en = getattr(data.gas.photon_energies, "group" + str(g + 1)) + emin_group.append(en.min()) + emax_group.append(en.max()) + + dirmin = [] + dirmax = [] + for direction in ["X", "Y"]: + f = getattr(data.gas.photon_fluxes, "Group" + str(g + 1) + direction) + dirmin.append(f.min()) + dirmax.append(f.max()) + fluxmin_group.append(min(dirmin)) + fluxmax_group.append(max(dirmax)) + + emins.append(emin_group) + emaxs.append(emax_group) + fmins.append(fluxmin_group) + fmaxs.append(fluxmax_group) + + energy_boundaries = [] + flux_boundaries = [] + for g in range(ngroups): + emin = min([emins[f][g] for f in range(len(snaplist))]) + emax = max([emaxs[f][g] for f in range(len(snaplist))]) + energy_boundaries.append([emin, emax]) + fmin = min([fmins[f][g] for f in range(len(snaplist))]) + fmax = max([fmaxs[f][g] for f in range(len(snaplist))]) + flux_boundaries.append([fmin, fmax]) + + return energy_boundaries, flux_boundaries + + +if __name__ == "__main__": + + snaplist = get_snapshot_list(snapshot_base) + if fancy: + energy_boundaries, flux_boundaries = get_minmax_vals(snaplist) + else: + energy_boundaries = None + flux_boundaries = None + + for f in snaplist: + plot_photons( + f, energy_boundaries=energy_boundaries, flux_boundaries=flux_boundaries + ) diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml b/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml new file mode 100644 index 0000000000000000000000000000000000000000..44daff0259e0bd8165b8d18beb13afe459bb040c --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/rt_collision2D.yml @@ -0,0 +1,60 @@ +MetaData: + run_name: RT_collision-2D + +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1. + UnitLength_in_cgs: 1. + UnitVelocity_in_cgs: 1. + UnitCurrent_in_cgs: 1. + UnitTemp_in_cgs: 1. + +# Parameters governing the time integration +TimeIntegration: + max_nr_rt_subcycles: 1 + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 3.4e-1 # The end time of the simulation (in internal units). + dt_min: 1.e-08 # The minimal time-step size of the simulation (in internal units). + dt_max: 1.e-02 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: output # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 0.017 + +# Parameters governing the conserved quantities statistics +Statistics: + time_first: 0. + delta_time: 0.034 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + CFL_condition: 0.6 # Courant-Friedrich-Levy condition for time integration. + minimal_temperature: 10. # Kelvin + +# Parameters related to the initial conditions +InitialConditions: + file_name: ./collision_2D.hdf5 # The file to read + periodic: 1 # periodic ICs + +GEARRT: + f_reduce_c: 1. + # reduce the speed of light for the RT solver by multiplying c with this factor + f_limit_cooling_time: 0.0 # (Optional) multiply the cooling time by this factor when estimating maximal next time step. Set to 0.0 to turn computation of cooling time off. + CFL_condition: 0.8 # CFL condition for RT, independent of hydro + photon_groups_Hz: [1., 2., 3., 4.] # Lower photon frequency group bin edges in Hz. Needs to have exactly N elements, where N is the configured number of bins --with-RT=GEAR_N + stellar_luminosity_model: const # Which model to use to determine the stellar luminosities. + const_stellar_luminosities_LSol: [0., 0., 0., 0.] # (Conditional) constant star luminosities for each photon frequency group to use if stellar_luminosity_model:const is set, in units of Solar Luminosity. + hydrogen_mass_fraction: 0.76 # total hydrogen (H + H+) mass fraction in the metal-free portion of the gas + stellar_spectrum_type: 0 # Which radiation spectrum to use. 0: constant from 0 until some max frequency set by stellar_spectrum_const_max_frequency_Hz. 1: blackbody spectrum. + stellar_spectrum_const_max_frequency_Hz: 1.e17 # (Conditional) if stellar_spectrum_type=0, use this maximal frequency for the constant spectrum. + set_initial_ionization_mass_fractions: 1 # (Optional) manually overwrite initial mass fraction of each species (using the values you set below) + mass_fraction_HI: 0.76 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HI mass fractions to this value + mass_fraction_HII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HII mass fractions to this value + mass_fraction_HeI: 0.24 # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeI mass fractions to this value + mass_fraction_HeII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeII mass fractions to this value + mass_fraction_HeIII: 0. # (Conditional) If overwrite_initial_ionization_fractions=1, needed to set initial HeIII mass fractions to this value + skip_thermochemistry: 1 # Skip thermochemsitry. + diff --git a/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh b/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..e7af6a0ec80635dddb58397754590ca8854a732c --- /dev/null +++ b/examples/RadiativeTransferTests/CollidingBeams_2D/run.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# exit if anything fails +set -e +set -o pipefail + + # Generate the initial conditions if they are not present. +if [ ! -e glassPlane_128.hdf5 ] +then + echo "Fetching initial glass file for the 2D RT advection example..." + ./getGlass.sh +fi +if [ ! -e collision_2D.hdf5 ] +then + echo "Generating initial conditions for the 2D RT advection example..." + python3 makeIC.py +fi + +# Run SWIFT with RT +../../../swift \ + --hydro \ + --threads=4 \ + --verbose=0 \ + --radiation \ + --stars \ + --feedback \ + --external-gravity \ + ./rt_collision2D.yml 2>&1 | tee output.log + +python3 ./plotEnergies.py +python3 ./plotSolution.py diff --git a/src/cell.h b/src/cell.h index c992171d714deded60e97e7df6205973f25cb0c4..5c407990a4de577a27d14908c9500e85b7c2251a 100644 --- a/src/cell.h +++ b/src/cell.h @@ -480,7 +480,10 @@ struct cell { float dmin; /*! ID of the previous owner, e.g. runner. */ - int owner; + short int owner; + + /*! ID of a threadpool thread that maybe associated with this cell. */ + short int tpid; /*! ID of the node this cell lives on. */ int nodeID; diff --git a/src/cell_unskip.c b/src/cell_unskip.c index 538eeec0c5f01c9788447e713f709e4890bbf913..93d98b1b1910db988ed6372c66df711a32f9addd 100644 --- a/src/cell_unskip.c +++ b/src/cell_unskip.c @@ -3152,6 +3152,18 @@ int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s, if (ci_active) { scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_transport); } + } else if (ci_active) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the local cell is inactive and the remote cell is active, we + * still need to receive stuff to be able to do the force interaction + * on this node as well. + * The gradient recv is only necessary in normal steps in case we need + * to sort, not during sub-cycles. */ + if (!sub_cycle) + scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_gradient); + scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_transport); + if (sub_cycle) cell_set_skip_rt_sort_flag_up(ci); +#endif } /* Is the foreign cell active and will need stuff from us? */ @@ -3164,6 +3176,19 @@ int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s, scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_transport, ci_nodeID); } + } else if (cj_active) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the foreign cell is inactive, but the local cell is active, + * we still need to send stuff to be able to do the force interaction + * on both nodes. + * The gradient send is only necessary in normal steps in case we need + * to sort, not during sub-cycles. */ + if (!sub_cycle) + scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_gradient, + ci_nodeID); + scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_transport, + ci_nodeID); +#endif } } else if (cj_nodeID != nodeID) { @@ -3180,6 +3205,18 @@ int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s, if (cj_active) { scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_transport); } + } else if (cj_active) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the local cell is inactive and the remote cell is active, we + * still need to receive stuff to be able to do the force interaction + * on this node as well. + * The gradient recv is only necessary in normal steps in case we need + * to sort, not during sub-cycles. */ + if (!sub_cycle) + scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_gradient); + scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_transport); + if (sub_cycle) cell_set_skip_rt_sort_flag_up(cj); +#endif } /* Is the foreign cell active and will need stuff from us? */ @@ -3192,6 +3229,19 @@ int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s, scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_transport, cj_nodeID); } + } else if (ci_active) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the foreign cell is inactive, but the local cell is active, + * we still need to send stuff to be able to do the force interaction + * on both nodes + * The gradient send is only necessary in normal steps in case we need + * to sort, not during sub-cycles. */ + if (!sub_cycle) + scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_gradient, + cj_nodeID); + scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_transport, + cj_nodeID); +#endif } } #endif @@ -3240,7 +3290,52 @@ int cell_unskip_rt_tasks(struct cell *c, struct scheduler *s, scheduler_activate(s, c->rt.rt_transport_out); if (c->rt.rt_tchem != NULL) scheduler_activate(s, c->rt.rt_tchem); if (c->rt.rt_out != NULL) scheduler_activate(s, c->rt.rt_out); + } else { +#if defined(MPI_SYMMETRIC_FORCE_INTERACTION) && defined(WITH_MPI) + /* Additionally unskip force interactions between inactive local cell and + * active remote cell. (The cell unskip will only be called for active + * cells, so, we have to do this now, from the active remote cell). */ + for (struct link *l = c->rt.rt_transport; l != NULL; l = l->next) { + struct task *t = l->t; + if (t->type != task_type_pair && t->type != task_type_sub_pair) + continue; + + struct cell *ci = l->t->ci; + struct cell *cj = l->t->cj; + + const int ci_active = cell_is_rt_active(ci, e); + const int cj_active = cell_is_rt_active(cj, e); + const int ci_nodeID = ci->nodeID; + const int cj_nodeID = cj->nodeID; + if ((!ci_active && ci_nodeID == nodeID && cj_active && + cj_nodeID != nodeID) || + (!cj_active && cj_nodeID == nodeID && ci_active && + ci_nodeID != nodeID)) { + scheduler_activate(s, l->t); + + if (!sub_cycle) { + /* Activate sorts only during main/normal steps. */ + if (t->type == task_type_pair) { + atomic_or(&ci->hydro.requires_sorts, 1 << t->flags); + atomic_or(&cj->hydro.requires_sorts, 1 << t->flags); + ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort; + cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort; + + /* Check the sorts and activate them if needed. */ + cell_activate_rt_sorts(ci, t->flags, s); + cell_activate_rt_sorts(cj, t->flags, s); + } + + /* Store current values of dx_max and h_max. */ + else if (t->type == task_type_sub_pair) { + cell_activate_subcell_rt_tasks(ci, cj, s, sub_cycle); + } + } + } + } +#endif } + /* The rt_advance_cell_time tasks also run on foreign cells */ if (c->super != NULL && c->super->rt.rt_advance_cell_time != NULL) scheduler_activate(s, c->super->rt.rt_advance_cell_time); diff --git a/src/cooling/const_lambda/cooling.h b/src/cooling/const_lambda/cooling.h index b09e6610d58b5ae531b232b7539afb32082d658a..64a2d35040e25032961d906c38d02e0f9325ef2f 100644 --- a/src/cooling/const_lambda/cooling.h +++ b/src/cooling/const_lambda/cooling.h @@ -178,12 +178,24 @@ __attribute__((always_inline)) INLINE static void cooling_cool_part( total_du_dt = -u_old_com / ((2.5f + 0.0001f) * dt_therm); } - /* Update the internal energy time derivative */ - hydro_set_comoving_internal_energy_dt(p, total_du_dt); + if (cooling->rapid_cooling) { + const float u_new_com = u_old_com + total_du_dt * dt_therm; + const float u_new_phys = u_new_com * cosmo->a_factor_internal_energy; + hydro_set_physical_internal_energy(p, xp, cosmo, u_new_phys); + hydro_set_drifted_physical_internal_energy(p, cosmo, u_new_phys); + hydro_set_physical_internal_energy_dt(p, cosmo, 0.); + } else { + /* Update the internal energy time derivative */ + hydro_set_comoving_internal_energy_dt(p, total_du_dt); + } + const float actual_cooling_du_dt = total_du_dt - hydro_du_dt_com; + const float actual_cooling_du_dt_physical = actual_cooling_du_dt / cosmo->a / + cosmo->a * + cosmo->a_factor_internal_energy; /* Store the radiated energy (assuming dt will not change) */ xp->cooling_data.radiated_energy += - -hydro_get_mass(p) * cooling_du_dt_physical * dt; + -hydro_get_mass(p) * actual_cooling_du_dt_physical * dt; } /** @@ -415,6 +427,8 @@ static INLINE void cooling_init_backend(struct swift_params* parameter_file, parser_get_param_double(parameter_file, "LambdaCooling:lambda_nH2_cgs"); cooling->cooling_tstep_mult = parser_get_opt_param_float( parameter_file, "LambdaCooling:cooling_tstep_mult", FLT_MAX); + cooling->rapid_cooling = parser_get_opt_param_int( + parameter_file, "LambdaCooling:rapid_cooling", 0); /* Some useful conversion values */ cooling->conv_factor_density_to_cgs = @@ -463,6 +477,12 @@ static INLINE void cooling_print_backend( else message("Cooling function time-step size limited to %f of u/(du/dt)", cooling->cooling_tstep_mult); + + if (cooling->rapid_cooling) { + message("Using rapid cooling"); + } else { + message("Using normal cooling"); + } } /** diff --git a/src/cooling/const_lambda/cooling_io.h b/src/cooling/const_lambda/cooling_io.h index 0efba80c294b732e2e4e59d824143b879757e37d..9494731d047b4f1b7b03d03e85d128c286351ae9 100644 --- a/src/cooling/const_lambda/cooling_io.h +++ b/src/cooling/const_lambda/cooling_io.h @@ -51,6 +51,7 @@ __attribute__((always_inline)) INLINE static void cooling_write_flavour( io_write_attribute_s(h_grp, "Cooling Model", "Constant Lambda"); io_write_attribute_d(h_grp, "Lambda/n_H^2 [cgs]", cooling->lambda_nH2_cgs); + io_write_attribute_i(h_grp, "Rapid cooling", cooling->rapid_cooling); } #endif @@ -76,7 +77,7 @@ __attribute__((always_inline)) INLINE static int cooling_write_particles( struct io_props* list) { list[0] = io_make_output_field_convert_part( - "Temperature", FLOAT, 1, UNIT_CONV_TEMPERATURE, 0.f, parts, xparts, + "Temperatures", FLOAT, 1, UNIT_CONV_TEMPERATURE, 0.f, parts, xparts, convert_part_T, "Temperatures of the gas particles"); list[1] = io_make_output_field( diff --git a/src/cooling/const_lambda/cooling_properties.h b/src/cooling/const_lambda/cooling_properties.h index 8417c9112645713a43c1edf0600db98f604d030e..ecb5c9c8e25d41a1ada04727ad403d6db5bf3ce4 100644 --- a/src/cooling/const_lambda/cooling_properties.h +++ b/src/cooling/const_lambda/cooling_properties.h @@ -48,6 +48,9 @@ struct cooling_function_data { /*! Constant multiplication factor for time-step criterion */ float cooling_tstep_mult; + + /*! Use rapid cooling? */ + int rapid_cooling; }; #endif /* SWIFT_COOLING_PROPERTIES_CONST_LAMBDA_H */ diff --git a/src/cosmology.c b/src/cosmology.c index d7dc6a3f9fae48fb72d17d098e88b4b2057eac8d..6bb6d4c8f9feeb281104b6ff7f0dcab2dbfb0a53 100644 --- a/src/cosmology.c +++ b/src/cosmology.c @@ -1509,7 +1509,7 @@ void cosmology_write_model(hid_t h_grp, const struct cosmology *c) { io_write_attribute_d(h_grp, "T_nu_0 [eV]", c->T_nu_0_eV); io_write_attribute_d(h_grp, "N_eff", c->N_eff); io_write_attribute_d(h_grp, "N_ur", c->N_ur); - io_write_attribute_d(h_grp, "N_nu", c->N_nu); + io_write_attribute_i(h_grp, "N_nu", c->N_nu); if (c->N_nu > 0) { io_write_attribute(h_grp, "M_nu_eV", DOUBLE, c->M_nu_eV, c->N_nu); io_write_attribute(h_grp, "deg_nu", DOUBLE, c->deg_nu, c->N_nu); diff --git a/src/engine_config.c b/src/engine_config.c index b444c9a8ed357eb3e88728f6e2ff18e8bc049e20..3834398f812005087277640aabb8e493c47a5d4a 100644 --- a/src/engine_config.c +++ b/src/engine_config.c @@ -23,6 +23,7 @@ #include <config.h> /* System includes. */ +#include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> @@ -40,6 +41,7 @@ #include "part.h" #include "pressure_floor.h" #include "proxy.h" +#include "rt.h" #include "star_formation.h" #include "star_formation_logger.h" #include "stars_io.h" @@ -195,6 +197,9 @@ void engine_config(int restart, int fof, struct engine *e, e->restart_dt = 0; e->run_fof = 0; + /* Seed rand(). */ + srand(clocks_random_seed()); + /* Allow repartitioning to be changed between restarts. On restart this is * already allocated and freed on exit, so we need to copy over. */ #ifdef WITH_MPI @@ -807,6 +812,12 @@ void engine_config(int restart, int fof, struct engine *e, if (e->nodeID == 0) message("Using %d threads in the thread-pool", nr_pool_threads); + /* Cells per thread buffer. */ + e->s->cells_sub = + (struct cell **)calloc(nr_pool_threads + 1, sizeof(struct cell *)); + e->s->multipoles_sub = (struct gravity_tensors **)calloc( + nr_pool_threads + 1, sizeof(struct gravity_tensors *)); + /* First of all, init the barrier and lock it. */ if (swift_barrier_init(&e->wait_barrier, NULL, e->nr_threads + 1) != 0 || swift_barrier_init(&e->run_barrier, NULL, e->nr_threads + 1) != 0) diff --git a/src/engine_maketasks.c b/src/engine_maketasks.c index 7a2f38e4aa32abc062d39a906ed2d4bf4f69407c..edbcee18fd4015595de2cc8d017184d0f494b598 100644 --- a/src/engine_maketasks.c +++ b/src/engine_maketasks.c @@ -2813,6 +2813,32 @@ void engine_make_extra_hydroloop_tasks_mapper(void *map_data, int num_elements, sched, task_type_pair, task_subtype_rt_gradient, flags, 0, ci, cj); t_rt_transport = scheduler_addtask( sched, task_type_pair, task_subtype_rt_transport, flags, 0, ci, cj); +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* The order of operations for an inactive local cell interacting + * with an active foreign cell is not guaranteed because the gradient + * iact loops don't exist in that case. So we need an explicit + * dependency here to have sorted cells. */ + + /* Make all force tasks depend on the sorts */ + if (ci->hydro.super->rt.rt_sorts != NULL) + scheduler_addunlock(sched, ci->hydro.super->rt.rt_sorts, + t_rt_transport); + if (ci->hydro.super != cj->hydro.super) { + if (cj->hydro.super->rt.rt_sorts != NULL) + scheduler_addunlock(sched, cj->hydro.super->rt.rt_sorts, + t_rt_transport); + } + /* We need to ensure that a local inactive cell is sorted before + * the interaction in the transport loop. Local cells don't have an + * rt_sorts task. */ + if (ci->hydro.super->hydro.sorts != NULL) + scheduler_addunlock(sched, ci->hydro.super->hydro.sorts, + t_rt_transport); + if ((ci->hydro.super != cj->hydro.super) && + (cj->hydro.super->hydro.sorts != NULL)) + scheduler_addunlock(sched, cj->hydro.super->hydro.sorts, + t_rt_transport); +#endif } engine_addlink(e, &ci->hydro.force, t_force); @@ -3615,6 +3641,32 @@ void engine_make_extra_hydroloop_tasks_mapper(void *map_data, int num_elements, t_rt_transport = scheduler_addtask(sched, task_type_sub_pair, task_subtype_rt_transport, flags, 0, ci, cj); +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* The order of operations for an inactive local cell interacting + * with an active foreign cell is not guaranteed because the gradient + * iact loops don't exist in that case. So we need an explicit + * dependency here to have sorted cells. */ + + /* Make all force tasks depend on the sorts */ + if (ci->hydro.super->rt.rt_sorts != NULL) + scheduler_addunlock(sched, ci->hydro.super->rt.rt_sorts, + t_rt_transport); + if (ci->hydro.super != cj->hydro.super) { + if (cj->hydro.super->rt.rt_sorts != NULL) + scheduler_addunlock(sched, cj->hydro.super->rt.rt_sorts, + t_rt_transport); + } + /* We need to ensure that a local inactive cell is sorted before + * the interaction in the transport loop. Local cells don't have + * an rt_sort task. */ + if (ci->hydro.super->hydro.sorts != NULL) + scheduler_addunlock(sched, ci->hydro.super->hydro.sorts, + t_rt_transport); + if ((ci->hydro.super != cj->hydro.super) && + (cj->hydro.super->hydro.sorts != NULL)) + scheduler_addunlock(sched, cj->hydro.super->hydro.sorts, + t_rt_transport); +#endif } engine_addlink(e, &ci->hydro.force, t_force); diff --git a/src/engine_marktasks.c b/src/engine_marktasks.c index e7f1d0263d3b13200a56886583f60c8cf196910a..b027156ff0371abccdfdf9d19e0b4cf1942e3076 100644 --- a/src/engine_marktasks.c +++ b/src/engine_marktasks.c @@ -903,6 +903,37 @@ void engine_marktasks_mapper(void *map_data, int num_elements, with_timestep_limiter); } } + } + + /* Pair tasks between inactive local cells and active remote cells. */ + if ((ci_nodeID != nodeID && cj_nodeID == nodeID && ci_active_rt && + !cj_active_rt) || + (ci_nodeID == nodeID && cj_nodeID != nodeID && !ci_active_rt && + cj_active_rt)) { + + if (t_subtype == task_subtype_rt_transport) { + + scheduler_activate(s, t); + + /* Set the correct sorting flags */ + if (t_type == task_type_pair) { + + /* Store some values. */ + atomic_or(&ci->hydro.requires_sorts, 1 << t->flags); + atomic_or(&cj->hydro.requires_sorts, 1 << t->flags); + ci->hydro.dx_max_sort_old = ci->hydro.dx_max_sort; + cj->hydro.dx_max_sort_old = cj->hydro.dx_max_sort; + + /* Check the sorts and activate them if needed. */ + cell_activate_rt_sorts(ci, t->flags, s); + cell_activate_rt_sorts(cj, t->flags, s); + } + + /* Store current values of dx_max and h_max. */ + else if (t_type == task_type_sub_pair) { + cell_activate_subcell_rt_tasks(ci, cj, s, /*sub_cycle=*/0); + } + } #endif } @@ -1389,6 +1420,14 @@ void engine_marktasks_mapper(void *map_data, int num_elements, scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_transport); } + } else if (ci_active_rt) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the local cell is inactive and the remote cell is active, we + * still need to receive stuff to be able to do the force + * interaction on this node as well. */ + scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_gradient); + scheduler_activate_recv(s, ci->mpi.recv, task_subtype_rt_transport); +#endif } /* Is the foreign cell active and will need stuff from us? */ @@ -1401,6 +1440,16 @@ void engine_marktasks_mapper(void *map_data, int num_elements, scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_transport, ci_nodeID); } + } else if (cj_active_rt) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the foreign cell is inactive, but the local cell is active, + * we still need to send stuff to be able to do the force + * interaction on both nodes */ + scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_gradient, + ci_nodeID); + scheduler_activate_send(s, cj->mpi.send, task_subtype_rt_transport, + ci_nodeID); +#endif } } else if (cj_nodeID != nodeID) { @@ -1416,6 +1465,14 @@ void engine_marktasks_mapper(void *map_data, int num_elements, scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_transport); } + } else if (cj_active_rt) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the local cell is inactive and the remote cell is active, we + * still need to receive stuff to be able to do the force + * interaction on this node as well. */ + scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_gradient); + scheduler_activate_recv(s, cj->mpi.recv, task_subtype_rt_transport); +#endif } /* Is the foreign cell active and will need stuff from us? */ @@ -1430,6 +1487,16 @@ void engine_marktasks_mapper(void *map_data, int num_elements, scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_transport, cj_nodeID); } + } else if (ci_active_rt) { +#ifdef MPI_SYMMETRIC_FORCE_INTERACTION + /* If the foreign cell is inactive, but the local cell is active, + * we still need to send stuff to be able to do the force + * interaction on both nodes */ + scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_gradient, + cj_nodeID); + scheduler_activate_send(s, ci->mpi.send, task_subtype_rt_transport, + cj_nodeID); +#endif } } #endif diff --git a/src/hydro/Gizmo/MFM/hydro_part.h b/src/hydro/Gizmo/MFM/hydro_part.h index 509dd79ad661317714425bfc93939264f68c5b33..f476b860eaaca8d54386fe3f2502db3b58c67df5 100644 --- a/src/hydro/Gizmo/MFM/hydro_part.h +++ b/src/hydro/Gizmo/MFM/hydro_part.h @@ -84,19 +84,6 @@ struct part { } limiter; struct { - /* Variables used for timestep calculation. */ - struct { - - /* Maximum signal velocity among all the neighbours of the particle. The - * signal velocity encodes information about the relative fluid - * velocities - * AND particle velocities of the neighbour and this particle, as well - * as - * the sound speed of both particles. */ - float vmax; - - } timestepvars; - /* Quantities used during the force loop. */ struct { @@ -165,6 +152,19 @@ struct part { } geometry; + /* Variables used for timestep calculation. */ + struct { + + /* Maximum signal velocity among all the neighbours of the particle. The + * signal velocity encodes information about the relative fluid + * velocities + * AND particle velocities of the neighbour and this particle, as well + * as + * the sound speed of both particles. */ + float vmax; + + } timestepvars; + /*! Chemistry information */ struct chemistry_part_data chemistry_data; diff --git a/src/hydro/Gizmo/hydro_getters.h b/src/hydro/Gizmo/hydro_getters.h index c97d20d1b3855fe8f98565822df3fb945ae261b9..7a83e093680ed63466eedd8c3885cf19a9279b5c 100644 --- a/src/hydro/Gizmo/hydro_getters.h +++ b/src/hydro/Gizmo/hydro_getters.h @@ -113,7 +113,8 @@ __attribute__((always_inline)) INLINE static void hydro_part_get_slope_limiter( * @param p The particle of interest. */ __attribute__((always_inline)) INLINE static float -hydro_get_comoving_internal_energy(const struct part* restrict p) { +hydro_get_comoving_internal_energy(const struct part* restrict p, + const struct xpart* restrict xp) { if (p->rho > 0.0f) return gas_internal_energy_from_pressure(p->rho, p->P); @@ -134,7 +135,7 @@ hydro_get_physical_internal_energy(const struct part* restrict p, const struct cosmology* cosmo) { return cosmo->a_factor_internal_energy * - hydro_get_comoving_internal_energy(p); + hydro_get_comoving_internal_energy(p, xp); } /** @@ -158,7 +159,7 @@ hydro_get_drifted_physical_internal_energy(const struct part* restrict p, __attribute__((always_inline)) INLINE static float hydro_get_drifted_comoving_internal_energy(const struct part* restrict p) { - return hydro_get_comoving_internal_energy(p); + return hydro_get_comoving_internal_energy(p, NULL); } /** @@ -322,8 +323,30 @@ __attribute__((always_inline)) INLINE static void hydro_get_drifted_velocities( __attribute__((always_inline)) INLINE static float hydro_get_comoving_internal_energy_dt(const struct part* restrict p) { - error("Needs implementing"); - return 0.0f; + float W[5]; + hydro_part_get_primitive_variables(p, W); + + if (W[0] <= 0.0f) { + return 0.0f; + } + + const float rho_inv = 1.f / W[0]; + + float gradrho[3], gradvx[3], gradvy[3], gradvz[3], gradP[3]; + hydro_part_get_gradients(p, gradrho, gradvx, gradvy, gradvz, gradP); + + const float divv = gradvx[0] + gradvy[1] + gradvz[2]; + + float gradu[3] = {0.f, 0.f, 0.f}; + for (int i = 0; i < 3; i++) { + gradu[i] = hydro_one_over_gamma_minus_one * rho_inv * + (gradP[i] - rho_inv * W[4] * gradrho[i]); + } + + const float du_dt = -(W[1] * gradu[0] + W[2] * gradu[1] + W[3] * gradu[2]) - + rho_inv * W[4] * divv; + + return du_dt; } /** @@ -337,8 +360,9 @@ hydro_get_comoving_internal_energy_dt(const struct part* restrict p) { __attribute__((always_inline)) INLINE static float hydro_get_physical_internal_energy_dt(const struct part* restrict p, const struct cosmology* cosmo) { - error("Needs implementing"); - return 0.0f; + + return hydro_get_comoving_internal_energy_dt(p) * + cosmo->a_factor_internal_energy; } /** diff --git a/src/hydro/Gizmo/hydro_io.h b/src/hydro/Gizmo/hydro_io.h index c56db563e02d31cfdf8b99b99a9ade21d65c1796..171484205512e86718f4c5de60d826b0c77c7985 100644 --- a/src/hydro/Gizmo/hydro_io.h +++ b/src/hydro/Gizmo/hydro_io.h @@ -76,7 +76,7 @@ INLINE static void hydro_read_particles(struct part* parts, INLINE static void convert_u(const struct engine* e, const struct part* p, const struct xpart* xp, float* ret) { - ret[0] = hydro_get_comoving_internal_energy(p); + ret[0] = hydro_get_comoving_internal_energy(p, xp); } /** diff --git a/src/hydro/Gizmo/hydro_setters.h b/src/hydro/Gizmo/hydro_setters.h index fd7e6f8ca35cd0879bf2b0e2f40eba41fbb257d3..640912ccbb226a74f629afb0ac630079f6332854 100644 --- a/src/hydro/Gizmo/hydro_setters.h +++ b/src/hydro/Gizmo/hydro_setters.h @@ -195,7 +195,10 @@ __attribute__((always_inline)) INLINE static void hydro_set_mass( __attribute__((always_inline)) INLINE static void hydro_set_comoving_internal_energy_dt(struct part* restrict p, const float du_dt) { - error("Needs implementing"); + + const float old_du_dt = hydro_get_comoving_internal_energy_dt(p); + + p->flux.energy += p->conserved.mass * (du_dt - old_du_dt) * p->flux.dt; } /** @@ -211,8 +214,43 @@ __attribute__((always_inline)) INLINE static void hydro_set_physical_internal_energy_dt(struct part* restrict p, const struct cosmology* restrict cosmo, const float du_dt) { - error("Needs implementing"); + + hydro_set_comoving_internal_energy_dt( + p, du_dt / cosmo->a_factor_internal_energy); } + +/** + * @brief Sets the comoving internal energy of a particle + * + * @param p The particle of interest. + * @param u The comoving internal energy + */ +__attribute__((always_inline)) INLINE static void +hydro_set_comoving_internal_energy(struct part* p, const float u) { + + const float mass = p->conserved.mass; + if (mass <= 0.0f) { + return; + } + + const float Etherm = mass * u; + +#ifdef GIZMO_TOTAL_ENERGY + const float Ekin = 0.5f * + (p->conserved.momentum[0] * p->conserved.momentum[0] + + p->conserved.momentum[1] * p->conserved.momentum[1] + + p->conserved.momentum[2] * p->conserved.momentum[2]) / + mass; + + const float Etot = Ekin + Etherm; + p->conserved.energy = Etot; +#else + p->conserved.energy = Etherm; +#endif + + p->P = gas_pressure_from_internal_energy(p->rho, u); +} + /** * @brief Sets the physical entropy of a particle * @@ -225,7 +263,8 @@ __attribute__((always_inline)) INLINE static void hydro_set_physical_entropy( struct part* p, struct xpart* xp, const struct cosmology* cosmo, const float entropy) { - error("Needs implementing"); + const float u = gas_internal_energy_from_entropy(p->rho, entropy); + hydro_set_comoving_internal_energy(p, u); } /** @@ -240,7 +279,8 @@ __attribute__((always_inline)) INLINE static void hydro_set_physical_internal_energy(struct part* p, struct xpart* xp, const struct cosmology* cosmo, const float u) { - error("Need implementing"); + + hydro_set_comoving_internal_energy(p, u / cosmo->a_factor_internal_energy); } /** @@ -254,7 +294,8 @@ __attribute__((always_inline)) INLINE static void hydro_set_drifted_physical_internal_energy(struct part* p, const struct cosmology* cosmo, const float u) { - error("Need implementing"); + + hydro_set_comoving_internal_energy(p, u / cosmo->a_factor_internal_energy); } /** diff --git a/src/multipole.h b/src/multipole.h index 897623f9515add2e40f0cc544b1e9702ddddf96f..26c163a7c952361c63f923a60ca040647203a57e 100644 --- a/src/multipole.h +++ b/src/multipole.h @@ -1031,7 +1031,7 @@ __attribute__((nonnull)) INLINE static void gravity_P2M( float min_delta_vel[3] = {0., 0., 0.}; #if SELF_GRAVITY_MULTIPOLE_ORDER > 0 - double M_100 = 0., M_010 = 0., M_001 = 0.; + /* double M_100 = 0., M_010 = 0., M_001 = 0.; */ #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 1 double M_200 = 0., M_020 = 0., M_002 = 0.; @@ -1086,9 +1086,9 @@ __attribute__((nonnull)) INLINE static void gravity_P2M( const double m = gparts[k].mass; /* 1st order terms */ - M_100 += -m * X_100(dx); - M_010 += -m * X_010(dx); - M_001 += -m * X_001(dx); + /* M_100 += -m * X_100(dx); */ + /* M_010 += -m * X_010(dx); */ + /* M_001 += -m * X_001(dx); */ #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 1 diff --git a/src/power_spectrum.c b/src/power_spectrum.c index 0b947fee9e65ecb6f55ac044b526bd1bde80f2e7..324d48c231b6a6189973a40476640e4ec62461a2 100644 --- a/src/power_spectrum.c +++ b/src/power_spectrum.c @@ -51,6 +51,8 @@ #define power_data_default_fold_factor 4 #define power_data_default_window_order 3 +#ifdef HAVE_FFTW + /** * @brief Return the #power_type corresponding to a given string. */ @@ -104,137 +106,6 @@ INLINE static const char* get_powtype_filename(const enum power_type type) { return powtype_filenames[type]; } -/** - * @brief Initialize power spectra calculation. - * - * Reads in the power spectrum parameters, sets up FFTW - * (likely already done for PM), then sets up an FFT plan - * that can be reused every time. - * - * @param nr_threads The number of threads used. - */ -void power_init(struct power_spectrum_data* p, struct swift_params* params, - int nr_threads) { - -#ifdef HAVE_FFTW - - /* Get power spectrum parameters */ - p->Ngrid = parser_get_opt_param_int(params, "PowerSpectrum:grid_side_length", - power_data_default_grid_side_length); - p->Nfold = parser_get_param_int(params, "PowerSpectrum:num_folds"); - p->foldfac = parser_get_opt_param_int(params, "PowerSpectrum:fold_factor", - power_data_default_fold_factor); - p->windoworder = parser_get_opt_param_int( - params, "PowerSpectrum:window_order", power_data_default_window_order); - - if (p->windoworder > 3 || p->windoworder < 1) - error("Power spectrum calculation is not implemented for %dth order!", - p->windoworder); - if (p->windoworder == 1) - message("WARNING: window order is recommended to be at least 2 (CIC)."); - if (p->windoworder <= 2 && p->foldfac > 4) - message( - "WARNING: fold factor is recommended not to exceed 4 for a " - "mass assignment order of 2 (CIC) or below."); - if (p->windoworder == 3 && p->foldfac > 6) - message( - "WARNING: fold factor is recommended not to exceed 6 for a " - "mass assignment order of 3 (TSC) or below."); - - /* Make sensible choices for the k-cuts */ - const int kcutn = (p->windoworder >= 3) ? 90 : 70; - const int kcutleft = (int)(p->Ngrid / 256.0 * kcutn); - const int kcutright = (int)(p->Ngrid / 256.0 * (double)kcutn / p->foldfac); - if (kcutright < 10 || (kcutleft - kcutright) < 30) - error( - "Combination of power grid size and fold factor do not allow for " - "enough overlap between foldings!"); - - p->nr_threads = nr_threads; - -#ifdef HAVE_THREADED_FFTW - /* Initialise the thread-parallel FFTW version - (probably already done for the PM, but does not matter) */ - if (p->Ngrid >= 64) { - fftw_init_threads(); - fftw_plan_with_nthreads(nr_threads); - } -#else - message("Note that FFTW is not threaded!"); -#endif - - char** requested_spectra; - parser_get_param_string_array(params, "PowerSpectrum:requested_spectra", - &p->spectrumcount, &requested_spectra); - - p->types1 = - (enum power_type*)malloc(p->spectrumcount * sizeof(enum power_type)); - p->types2 = - (enum power_type*)malloc(p->spectrumcount * sizeof(enum power_type)); - - /* Parse which spectra are being requested */ - for (int i = 0; i < p->spectrumcount; ++i) { - - char* pstr = strtok(requested_spectra[i], "-"); - if (pstr == NULL) - error("Requested power spectra are not in the format type1-type2!"); - char type1[32]; - strcpy(type1, pstr); - - pstr = strtok(NULL, "-"); - if (pstr == NULL) - error("Requested power spectra are not in the format type1-type2!"); - char type2[32]; - strcpy(type2, pstr); - - p->types1[i] = power_spectrum_get_type(type1); - p->types2[i] = power_spectrum_get_type(type2); - } - - /* Initialize the plan only once -- much faster for FFTs run often! - * Does require us to allocate the grids, but we delete them right away. - * Plan can only be used for the same FFTW call */ - const int Ngrid = p->Ngrid; - - /* Grid is padded to allow for in-place FFT */ - p->powgrid = fftw_alloc_real(Ngrid * Ngrid * (Ngrid + 2)); - /* Pointer to grid to interpret it as complex data */ - p->powgridft = (fftw_complex*)p->powgrid; - - p->fftplanpow = fftw_plan_dft_r2c_3d(Ngrid, Ngrid, Ngrid, p->powgrid, - p->powgridft, FFTW_MEASURE); - - fftw_free(p->powgrid); - p->powgrid = NULL; - p->powgridft = NULL; - - /* Do the same for a second grid/plan to allow for cross power */ - - /* Grid is padded to allow for in-place FFT */ - p->powgrid2 = fftw_alloc_real(Ngrid * Ngrid * (Ngrid + 2)); - /* Pointer to grid to interpret it as complex data */ - p->powgridft2 = (fftw_complex*)p->powgrid2; - - p->fftplanpow2 = fftw_plan_dft_r2c_3d(Ngrid, Ngrid, Ngrid, p->powgrid2, - p->powgridft2, FFTW_MEASURE); - - fftw_free(p->powgrid2); - p->powgrid2 = NULL; - p->powgridft2 = NULL; - - /* Create directories for power spectra and foldings */ - if (engine_rank == 0) { - safe_checkdir("power_spectra", /*create=*/1); - safe_checkdir("power_spectra/foldings", /*create=*/1); - } - -#else - error("Trying to initialize the PS code without FFTW present!"); -#endif -} - -#ifdef HAVE_FFTW - /** * @brief Shared information for shot noise to be used by all the threads in the * pool. @@ -1365,6 +1236,135 @@ void power_spectrum(const enum power_type type1, const enum power_type type2, #endif /* HAVE_FFTW */ +/** + * @brief Initialize power spectra calculation. + * + * Reads in the power spectrum parameters, sets up FFTW + * (likely already done for PM), then sets up an FFT plan + * that can be reused every time. + * + * @param nr_threads The number of threads used. + */ +void power_init(struct power_spectrum_data* p, struct swift_params* params, + int nr_threads) { + +#ifdef HAVE_FFTW + + /* Get power spectrum parameters */ + p->Ngrid = parser_get_opt_param_int(params, "PowerSpectrum:grid_side_length", + power_data_default_grid_side_length); + p->Nfold = parser_get_param_int(params, "PowerSpectrum:num_folds"); + p->foldfac = parser_get_opt_param_int(params, "PowerSpectrum:fold_factor", + power_data_default_fold_factor); + p->windoworder = parser_get_opt_param_int( + params, "PowerSpectrum:window_order", power_data_default_window_order); + + if (p->windoworder > 3 || p->windoworder < 1) + error("Power spectrum calculation is not implemented for %dth order!", + p->windoworder); + if (p->windoworder == 1) + message("WARNING: window order is recommended to be at least 2 (CIC)."); + if (p->windoworder <= 2 && p->foldfac > 4) + message( + "WARNING: fold factor is recommended not to exceed 4 for a " + "mass assignment order of 2 (CIC) or below."); + if (p->windoworder == 3 && p->foldfac > 6) + message( + "WARNING: fold factor is recommended not to exceed 6 for a " + "mass assignment order of 3 (TSC) or below."); + + /* Make sensible choices for the k-cuts */ + const int kcutn = (p->windoworder >= 3) ? 90 : 70; + const int kcutleft = (int)(p->Ngrid / 256.0 * kcutn); + const int kcutright = (int)(p->Ngrid / 256.0 * (double)kcutn / p->foldfac); + if (kcutright < 10 || (kcutleft - kcutright) < 30) + error( + "Combination of power grid size and fold factor do not allow for " + "enough overlap between foldings!"); + + p->nr_threads = nr_threads; + +#ifdef HAVE_THREADED_FFTW + /* Initialise the thread-parallel FFTW version + (probably already done for the PM, but does not matter) */ + if (p->Ngrid >= 64) { + fftw_init_threads(); + fftw_plan_with_nthreads(nr_threads); + } +#else + message("Note that FFTW is not threaded!"); +#endif + + char** requested_spectra; + parser_get_param_string_array(params, "PowerSpectrum:requested_spectra", + &p->spectrumcount, &requested_spectra); + + p->types1 = + (enum power_type*)malloc(p->spectrumcount * sizeof(enum power_type)); + p->types2 = + (enum power_type*)malloc(p->spectrumcount * sizeof(enum power_type)); + + /* Parse which spectra are being requested */ + for (int i = 0; i < p->spectrumcount; ++i) { + + char* pstr = strtok(requested_spectra[i], "-"); + if (pstr == NULL) + error("Requested power spectra are not in the format type1-type2!"); + char type1[32]; + strcpy(type1, pstr); + + pstr = strtok(NULL, "-"); + if (pstr == NULL) + error("Requested power spectra are not in the format type1-type2!"); + char type2[32]; + strcpy(type2, pstr); + + p->types1[i] = power_spectrum_get_type(type1); + p->types2[i] = power_spectrum_get_type(type2); + } + + /* Initialize the plan only once -- much faster for FFTs run often! + * Does require us to allocate the grids, but we delete them right away. + * Plan can only be used for the same FFTW call */ + const int Ngrid = p->Ngrid; + + /* Grid is padded to allow for in-place FFT */ + p->powgrid = fftw_alloc_real(Ngrid * Ngrid * (Ngrid + 2)); + /* Pointer to grid to interpret it as complex data */ + p->powgridft = (fftw_complex*)p->powgrid; + + p->fftplanpow = fftw_plan_dft_r2c_3d(Ngrid, Ngrid, Ngrid, p->powgrid, + p->powgridft, FFTW_MEASURE); + + fftw_free(p->powgrid); + p->powgrid = NULL; + p->powgridft = NULL; + + /* Do the same for a second grid/plan to allow for cross power */ + + /* Grid is padded to allow for in-place FFT */ + p->powgrid2 = fftw_alloc_real(Ngrid * Ngrid * (Ngrid + 2)); + /* Pointer to grid to interpret it as complex data */ + p->powgridft2 = (fftw_complex*)p->powgrid2; + + p->fftplanpow2 = fftw_plan_dft_r2c_3d(Ngrid, Ngrid, Ngrid, p->powgrid2, + p->powgridft2, FFTW_MEASURE); + + fftw_free(p->powgrid2); + p->powgrid2 = NULL; + p->powgridft2 = NULL; + + /* Create directories for power spectra and foldings */ + if (engine_rank == 0) { + safe_checkdir("power_spectra", /*create=*/1); + safe_checkdir("power_spectra/foldings", /*create=*/1); + } + +#else + error("Trying to initialize the PS code without FFTW present!"); +#endif +} + void calc_all_power_spectra(struct power_spectrum_data* pow_data, const struct space* s, struct threadpool* tp, const int verbose) { diff --git a/src/rt/GEAR/rt.h b/src/rt/GEAR/rt.h index 92d8c3d11e954033963d805c573de9b64c939b20..58e2e8f0ec6800167b6a74413a5e60966cb73d77 100644 --- a/src/rt/GEAR/rt.h +++ b/src/rt/GEAR/rt.h @@ -132,9 +132,11 @@ __attribute__((always_inline)) INLINE static void rt_reset_part( * @brief Reset RT particle data which needs to be reset each sub-cycle. * * @param p the particle to work on + * @param cosmo Cosmology. + * @param dt the current particle RT time step */ __attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( - struct part* restrict p) { + struct part* restrict p, const struct cosmology* cosmo, double dt) { #ifdef SWIFT_RT_DEBUG_CHECKS rt_debugging_reset_each_subcycle(p); @@ -144,7 +146,8 @@ __attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( /* the Gizmo-style slope limiting doesn't help for RT as is, * so we're skipping it for now. */ /* rt_slope_limit_cell_init(p); */ - rt_part_reset_fluxes(p); + + p->rt_data.flux_dt = dt; } /** @@ -162,7 +165,8 @@ __attribute__((always_inline)) INLINE static void rt_first_init_part( rt_init_part(p); rt_reset_part(p, cosmo); rt_part_reset_mass_fluxes(p); - rt_reset_part_each_subcycle(p); + rt_reset_part_each_subcycle(p, cosmo, 0.); + rt_part_reset_fluxes(p); #ifdef SWIFT_RT_DEBUG_CHECKS p->rt_data.debug_radiation_absorbed_tot = 0ULL; @@ -477,15 +481,20 @@ __attribute__((always_inline)) INLINE static void rt_finalise_transport( for (int g = 0; g < RT_NGROUPS; g++) { const float e_old = rtd->radiation[g].energy_density; - /* Note: in this scheme, we're updating d/dt (U * V) + sum F * A = 0. + /* Note: in this scheme, we're updating d/dt (U * V) + sum F * A * dt = 0. * So we'll need the division by the volume here. */ - rtd->radiation[g].energy_density += rtd->flux[g].energy * dt * Vinv; - rtd->radiation[g].flux[0] += rtd->flux[g].flux[0] * dt * Vinv; - rtd->radiation[g].flux[1] += rtd->flux[g].flux[1] * dt * Vinv; - rtd->radiation[g].flux[2] += rtd->flux[g].flux[2] * dt * Vinv; + rtd->radiation[g].energy_density += rtd->flux[g].energy * Vinv; + rtd->radiation[g].flux[0] += rtd->flux[g].flux[0] * Vinv; + rtd->radiation[g].flux[1] += rtd->flux[g].flux[1] * Vinv; + rtd->radiation[g].flux[2] += rtd->flux[g].flux[2] * Vinv; rt_check_unphysical_state(&rtd->radiation[g].energy_density, rtd->radiation[g].flux, e_old, /*callloc=*/4); } + + /* Reset the fluxes now that they have been applied. */ + rt_part_reset_fluxes(p); + /* Mark the particle as inactive for now. */ + rtd->flux_dt = -1.f; } /** @@ -549,54 +558,60 @@ __attribute__((always_inline)) INLINE static void rt_kick_extra( } #endif - /* Update the mass fraction changes due to interparticle fluxes */ - const float current_mass = p->conserved.mass; - - const float current_mass_HI = - current_mass * p->rt_data.tchem.mass_fraction_HI; - const float current_mass_HII = - current_mass * p->rt_data.tchem.mass_fraction_HII; - const float current_mass_HeI = - current_mass * p->rt_data.tchem.mass_fraction_HeI; - const float current_mass_HeII = - current_mass * p->rt_data.tchem.mass_fraction_HeII; - const float current_mass_HeIII = - current_mass * p->rt_data.tchem.mass_fraction_HeIII; - - const float new_mass_HI = - current_mass_HI + p->rt_data.mass_flux.HI * dt_therm; - const float new_mass_HII = - current_mass_HII + p->rt_data.mass_flux.HII * dt_therm; - const float new_mass_HeI = - current_mass_HeI + p->rt_data.mass_flux.HeI * dt_therm; - const float new_mass_HeII = - current_mass_HeII + p->rt_data.mass_flux.HeII * dt_therm; - const float new_mass_HeIII = - current_mass_HeIII + p->rt_data.mass_flux.HeIII * dt_therm; - - const float new_mass_tot = new_mass_HI + new_mass_HII + new_mass_HeI + - new_mass_HeII + new_mass_HeIII; - - /* During the initial fake time step, if the mass fractions haven't been set - * up yet, we could encounter divisions by zero here, so skip that. If it - * isn't the initial time step, the error will be caught down the line by - * another call - * to rt_check_unphysical_mass_fractions() (not the one 10 lines below this) - */ - if (new_mass_tot == 0.f) return; - - const float new_mass_tot_inv = 1.f / new_mass_tot; - - p->rt_data.tchem.mass_fraction_HI = new_mass_HI * new_mass_tot_inv; - p->rt_data.tchem.mass_fraction_HII = new_mass_HII * new_mass_tot_inv; - p->rt_data.tchem.mass_fraction_HeI = new_mass_HeI * new_mass_tot_inv; - p->rt_data.tchem.mass_fraction_HeII = new_mass_HeII * new_mass_tot_inv; - p->rt_data.tchem.mass_fraction_HeIII = new_mass_HeIII * new_mass_tot_inv; - - rt_check_unphysical_mass_fractions(p); - - /* Don't update actual particle mass, that'll be done in the - * hydro_kick_extra calls */ + /* Note: We need to mimick here what Gizmo does for the mass fluxes. + * The relevant time scale is the hydro time step for the mass fluxes, + * not the RT times. We also need to prevent the kick to apply the mass + * fluxes twice, so exit if the particle time step < 0 */ + + if (p->flux.dt > 0.0f) { + /* Update the mass fraction changes due to interparticle fluxes */ + const float current_mass = p->conserved.mass; + + const float current_mass_HI = + current_mass * p->rt_data.tchem.mass_fraction_HI; + const float current_mass_HII = + current_mass * p->rt_data.tchem.mass_fraction_HII; + const float current_mass_HeI = + current_mass * p->rt_data.tchem.mass_fraction_HeI; + const float current_mass_HeII = + current_mass * p->rt_data.tchem.mass_fraction_HeII; + const float current_mass_HeIII = + current_mass * p->rt_data.tchem.mass_fraction_HeIII; + + const float new_mass_HI = current_mass_HI + p->rt_data.mass_flux.HI; + const float new_mass_HII = current_mass_HII + p->rt_data.mass_flux.HII; + const float new_mass_HeI = current_mass_HeI + p->rt_data.mass_flux.HeI; + const float new_mass_HeII = current_mass_HeII + p->rt_data.mass_flux.HeII; + const float new_mass_HeIII = + current_mass_HeIII + p->rt_data.mass_flux.HeIII; + + const float new_mass_tot = new_mass_HI + new_mass_HII + new_mass_HeI + + new_mass_HeII + new_mass_HeIII; + + /* During the initial fake time step, if the mass fractions haven't been set + * up yet, we could encounter divisions by zero here, so skip that. If it + * isn't the initial time step, the error will be caught down the line by + * another call to rt_check_unphysical_mass_fractions() (not the one 10 + * lines below this) */ + if (new_mass_tot == 0.f) return; + + const float new_mass_tot_inv = 1.f / new_mass_tot; + + p->rt_data.tchem.mass_fraction_HI = new_mass_HI * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HII = new_mass_HII * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeI = new_mass_HeI * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeII = new_mass_HeII * new_mass_tot_inv; + p->rt_data.tchem.mass_fraction_HeIII = new_mass_HeIII * new_mass_tot_inv; + + rt_check_unphysical_mass_fractions(p); + + /* Reset fluxes after they have been applied, so they can be collected + * again even when particle is inactive. */ + rt_part_reset_mass_fluxes(p); + + /* Don't update actual particle mass, that'll be done in the + * hydro_kick_extra calls */ + } } /** @@ -609,10 +624,7 @@ __attribute__((always_inline)) INLINE static void rt_kick_extra( * @param p particle to work on **/ __attribute__((always_inline)) INLINE static void rt_prepare_force( - struct part* p) { - - rt_part_reset_mass_fluxes(p); -} + struct part* p) {} /** * @brief Extra operations to be done during the drift diff --git a/src/rt/GEAR/rt_additions.h b/src/rt/GEAR/rt_additions.h index 34baa6ac94797afa1099e72d291af986aed85b1d..6e2e269c3cca08b23d547c992006d9b3407c8b51 100644 --- a/src/rt/GEAR/rt_additions.h +++ b/src/rt/GEAR/rt_additions.h @@ -43,58 +43,49 @@ __attribute__((always_inline)) INLINE static void rt_part_update_mass_fluxes( * its own mass fractions. If it's gaining mass, it's gaining mass * according to the interacting particle's mass fractions. */ + /* get the time step for the flux exchange. This is always the smallest time + step among the two particles */ + const float mindt = + (pj->flux.dt > 0.0f) ? fminf(pi->flux.dt, pj->flux.dt) : pi->flux.dt; + + const float mdt = mass_flux * mindt; + /* Convention: negative flux for "left" particle pi */ if (mass_flux < 0.f) { /* "left" particle is gaining mass */ - pi->rt_data.mass_flux.HI -= pj->rt_data.tchem.mass_fraction_HI * mass_flux; - pi->rt_data.mass_flux.HII -= - pj->rt_data.tchem.mass_fraction_HII * mass_flux; - pi->rt_data.mass_flux.HeI -= - pj->rt_data.tchem.mass_fraction_HeI * mass_flux; - pi->rt_data.mass_flux.HeII -= - pj->rt_data.tchem.mass_fraction_HeII * mass_flux; - pi->rt_data.mass_flux.HeIII -= - pj->rt_data.tchem.mass_fraction_HeIII * mass_flux; + pi->rt_data.mass_flux.HI -= pj->rt_data.tchem.mass_fraction_HI * mdt; + pi->rt_data.mass_flux.HII -= pj->rt_data.tchem.mass_fraction_HII * mdt; + pi->rt_data.mass_flux.HeI -= pj->rt_data.tchem.mass_fraction_HeI * mdt; + pi->rt_data.mass_flux.HeII -= pj->rt_data.tchem.mass_fraction_HeII * mdt; + pi->rt_data.mass_flux.HeIII -= pj->rt_data.tchem.mass_fraction_HeIII * mdt; } else { /* "left" particle is losing mass */ - pi->rt_data.mass_flux.HI -= pi->rt_data.tchem.mass_fraction_HI * mass_flux; - pi->rt_data.mass_flux.HII -= - pi->rt_data.tchem.mass_fraction_HII * mass_flux; - pi->rt_data.mass_flux.HeI -= - pi->rt_data.tchem.mass_fraction_HeI * mass_flux; - pi->rt_data.mass_flux.HeII -= - pi->rt_data.tchem.mass_fraction_HeII * mass_flux; - pi->rt_data.mass_flux.HeIII -= - pi->rt_data.tchem.mass_fraction_HeIII * mass_flux; + pi->rt_data.mass_flux.HI -= pi->rt_data.tchem.mass_fraction_HI * mdt; + pi->rt_data.mass_flux.HII -= pi->rt_data.tchem.mass_fraction_HII * mdt; + pi->rt_data.mass_flux.HeI -= pi->rt_data.tchem.mass_fraction_HeI * mdt; + pi->rt_data.mass_flux.HeII -= pi->rt_data.tchem.mass_fraction_HeII * mdt; + pi->rt_data.mass_flux.HeIII -= pi->rt_data.tchem.mass_fraction_HeIII * mdt; } - if (mode == 1) { - /* Update right particle as well */ + if (mode == 1 || (pj->flux.dt < 0.f)) { + /* Update right particle as well, even if it's inactive */ if (mass_flux > 0.f) { /* "right" particle is gaining mass */ - pj->rt_data.mass_flux.HI += - pi->rt_data.tchem.mass_fraction_HI * mass_flux; - pj->rt_data.mass_flux.HII += - pi->rt_data.tchem.mass_fraction_HII * mass_flux; - pj->rt_data.mass_flux.HeI += - pi->rt_data.tchem.mass_fraction_HeI * mass_flux; - pj->rt_data.mass_flux.HeII += - pi->rt_data.tchem.mass_fraction_HeII * mass_flux; + pj->rt_data.mass_flux.HI += pi->rt_data.tchem.mass_fraction_HI * mdt; + pj->rt_data.mass_flux.HII += pi->rt_data.tchem.mass_fraction_HII * mdt; + pj->rt_data.mass_flux.HeI += pi->rt_data.tchem.mass_fraction_HeI * mdt; + pj->rt_data.mass_flux.HeII += pi->rt_data.tchem.mass_fraction_HeII * mdt; pj->rt_data.mass_flux.HeIII += - pi->rt_data.tchem.mass_fraction_HeIII * mass_flux; + pi->rt_data.tchem.mass_fraction_HeIII * mdt; } else { /* "right" particle is losing mass */ - pj->rt_data.mass_flux.HI += - pj->rt_data.tchem.mass_fraction_HI * mass_flux; - pj->rt_data.mass_flux.HII += - pj->rt_data.tchem.mass_fraction_HII * mass_flux; - pj->rt_data.mass_flux.HeI += - pj->rt_data.tchem.mass_fraction_HeI * mass_flux; - pj->rt_data.mass_flux.HeII += - pj->rt_data.tchem.mass_fraction_HeII * mass_flux; + pj->rt_data.mass_flux.HI += pj->rt_data.tchem.mass_fraction_HI * mdt; + pj->rt_data.mass_flux.HII += pj->rt_data.tchem.mass_fraction_HII * mdt; + pj->rt_data.mass_flux.HeI += pj->rt_data.tchem.mass_fraction_HeI * mdt; + pj->rt_data.mass_flux.HeII += pj->rt_data.tchem.mass_fraction_HeII * mdt; pj->rt_data.mass_flux.HeIII += - pj->rt_data.tchem.mass_fraction_HeIII * mass_flux; + pj->rt_data.tchem.mass_fraction_HeIII * mdt; } } } diff --git a/src/rt/GEAR/rt_iact.h b/src/rt/GEAR/rt_iact.h index 927d5881f9e7fb0a2d23fbe16e4532afe9af3c67..3ad71d3b66616369ee45851cca73b4cbb9fae0ed 100644 --- a/src/rt/GEAR/rt_iact.h +++ b/src/rt/GEAR/rt_iact.h @@ -326,6 +326,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common( struct rt_part_data *restrict rti = &pi->rt_data; struct rt_part_data *restrict rtj = &pj->rt_data; + /* Get the time step for the flux exchange. This is always the smallest time + * step among the two particles. */ + const float mindt = + (rtj->flux_dt > 0.f) ? fminf(rti->flux_dt, rtj->flux_dt) : rti->flux_dt; for (int g = 0; g < RT_NGROUPS; g++) { @@ -350,15 +354,19 @@ __attribute__((always_inline)) INLINE static void runner_iact_rt_flux_common( * flux is subtracted from the left state, and added to the right * state, based on how we chose the unit vector. By this convention, * the time integration results in conserved quantity += flux * dt */ - rti->flux[g].energy -= totflux[0]; - rti->flux[g].flux[0] -= totflux[1]; - rti->flux[g].flux[1] -= totflux[2]; - rti->flux[g].flux[2] -= totflux[3]; - if (mode == 1) { - rtj->flux[g].energy += totflux[0]; - rtj->flux[g].flux[0] += totflux[1]; - rtj->flux[g].flux[1] += totflux[2]; - rtj->flux[g].flux[2] += totflux[3]; + /* Unlike in SPH schemes, we do need to update inactive neighbours, so that + * the fluxes are always exchanged symmetrically. Thanks to our sneaky use + * of flux_dt, we can detect inactive neighbours through their negative time + * step. */ + rti->flux[g].energy -= totflux[0] * mindt; + rti->flux[g].flux[0] -= totflux[1] * mindt; + rti->flux[g].flux[1] -= totflux[2] * mindt; + rti->flux[g].flux[2] -= totflux[3] * mindt; + if (mode == 1 || (rtj->flux_dt < 0.f)) { + rtj->flux[g].energy += totflux[0] * mindt; + rtj->flux[g].flux[0] += totflux[1] * mindt; + rtj->flux[g].flux[1] += totflux[2] * mindt; + rtj->flux[g].flux[2] += totflux[3] * mindt; } } } diff --git a/src/rt/GEAR/rt_struct.h b/src/rt/GEAR/rt_struct.h index 632bc15b759588007682c6c71b67bdbe18221f65..63cfebbb6a2e3337e317edea206d7dd183f7ac6e 100644 --- a/src/rt/GEAR/rt_struct.h +++ b/src/rt/GEAR/rt_struct.h @@ -39,6 +39,9 @@ struct rt_part_data { float flux[3]; } flux[RT_NGROUPS]; + /* Particle RT time step. */ + float flux_dt; + /* gradients of the radiation state. */ /* for the flux[3][3] quantity: * first index: x, y, z coordinate of the flux. diff --git a/src/rt/SPHM1RT/rt.h b/src/rt/SPHM1RT/rt.h index 0ae7c5805284fb83e684dbe75d5a3c373a7883f5..88437dfc1fe657f5e51037935fbf527484ffe596 100644 --- a/src/rt/SPHM1RT/rt.h +++ b/src/rt/SPHM1RT/rt.h @@ -55,7 +55,17 @@ __attribute__((always_inline)) INLINE static void rt_init_part( * @param cosmo Cosmology. */ __attribute__((always_inline)) INLINE static void rt_reset_part( - struct part* restrict p, const struct cosmology* cosmo) { + struct part* restrict p, const struct cosmology* cosmo) {} + +/** + * @brief Reset RT particle data which needs to be reset each sub-cycle. + * + * @param p the particle to work on + * @param cosmo Cosmology. + * @param dt the current particle RT time step + */ +__attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( + struct part* restrict p, const struct cosmology* cosmo, double dt) { struct rt_part_data* rpd = &p->rt_data; @@ -93,15 +103,7 @@ __attribute__((always_inline)) INLINE static void rt_reset_part( rt_check_unphysical_state(&rpd->conserved[g].urad, rpd->conserved[g].frad, urad_old, cred); } -} - -/** - * @brief Reset RT particle data which needs to be reset each sub-cycle. - * - * @param p the particle to work on - */ -__attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( - struct part* restrict p){}; +}; /** * @brief First initialisation of the RT hydro particle data. @@ -132,6 +134,7 @@ __attribute__((always_inline)) INLINE static void rt_first_init_part( rt_init_part(p); rt_reset_part(p, cosmo); + rt_reset_part_each_subcycle(p, cosmo, 0.); } /** diff --git a/src/rt/debug/rt.h b/src/rt/debug/rt.h index b36e0b2972d50608ec90a7db1e553417942de8a8..4b65951a634b830b9e3f23bfbf7a0dd4e7e52995 100644 --- a/src/rt/debug/rt.h +++ b/src/rt/debug/rt.h @@ -87,9 +87,11 @@ __attribute__((always_inline)) INLINE static void rt_reset_part( * @brief Reset RT particle data which needs to be reset each sub-cycle. * * @param p the particle to work on + * @param cosmo Cosmology. + * @param dt the current particle RT time step */ __attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( - struct part* restrict p) { + struct part* restrict p, const struct cosmology* cosmo, double dt) { rt_debugging_reset_each_subcycle(p); } @@ -107,7 +109,7 @@ __attribute__((always_inline)) INLINE static void rt_first_init_part( rt_init_part(p); rt_reset_part(p, cosmo); - rt_reset_part_each_subcycle(p); + rt_reset_part_each_subcycle(p, cosmo, 0.); p->rt_data.debug_radiation_absorbed_tot = 0ULL; } diff --git a/src/rt/none/rt.h b/src/rt/none/rt.h index 5f256cfc83deeaf7d2980de79f44316a763d42d2..3555a4953eb90c1d7aff4c5505058693b7179ab6 100644 --- a/src/rt/none/rt.h +++ b/src/rt/none/rt.h @@ -76,9 +76,11 @@ __attribute__((always_inline)) INLINE static void rt_reset_part( * @brief Reset RT particle data which needs to be reset each sub-cycle. * * @param p the particle to work on + * @param cosmo Cosmology. + * @param dt the current particle RT time step */ __attribute__((always_inline)) INLINE static void rt_reset_part_each_subcycle( - struct part* restrict p) {} + struct part* restrict p, const struct cosmology* cosmo, double dt) {} /** * @brief First initialisation of the RT hydro particle data. diff --git a/src/runner_ghost.c b/src/runner_ghost.c index e44ad9a05e7c3b461a4662d9d01fa4e8ddd89907..ce87f19dbcc856cc7f9b28eb9d8ed660b0f992d5 100644 --- a/src/runner_ghost.c +++ b/src/runner_ghost.c @@ -1603,6 +1603,8 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) { void runner_do_rt_ghost1(struct runner *r, struct cell *c, int timer) { const struct engine *e = r->e; + const int with_cosmology = (e->policy & engine_policy_cosmology); + const struct cosmology *cosmo = e->cosmology; int count = c->hydro.count; /* Anything to do here? */ @@ -1631,7 +1633,17 @@ void runner_do_rt_ghost1(struct runner *r, struct cell *c, int timer) { /* First reset everything that needs to be reset for the following * subcycle */ - rt_reset_part_each_subcycle(p); + const integertime_t ti_current_subcycle = e->ti_current_subcycle; + const integertime_t ti_step = + get_integer_timestep(p->rt_time_data.time_bin); + const integertime_t ti_begin = get_integer_time_begin( + ti_current_subcycle + 1, p->rt_time_data.time_bin); + const integertime_t ti_end = ti_begin + ti_step; + + const float dt = + rt_part_dt(ti_begin, ti_end, e->time_base, with_cosmology, cosmo); + + rt_reset_part_each_subcycle(p, cosmo, dt); /* Now finish up injection */ rt_finalise_injection(p, e->rt_props); diff --git a/src/runner_main.c b/src/runner_main.c index 4ef7ddbfdbaf664872dea94d7e1d63b3061cb0e9..17a4bf1246a2553ef95953b02ffa19f3bcdc6801 100644 --- a/src/runner_main.c +++ b/src/runner_main.c @@ -520,7 +520,7 @@ void *runner_main(void *data) { } else if (t->subtype == task_subtype_rt_gradient) { runner_do_recv_part(r, ci, 2, 1); } else if (t->subtype == task_subtype_rt_transport) { - runner_do_recv_part(r, ci, 0, 1); + runner_do_recv_part(r, ci, -1, 1); } else if (t->subtype == task_subtype_part_swallow) { cell_unpack_part_swallow(ci, (struct black_holes_part_data *)t->buff); diff --git a/src/runner_recv.c b/src/runner_recv.c index f225e1e6d6c7567b9cc06fbfccdae70e6b4e0017..c36f6d2349c29a6aa042f488ec9d49b524168153 100644 --- a/src/runner_recv.c +++ b/src/runner_recv.c @@ -70,6 +70,18 @@ void runner_do_recv_part(struct runner *r, struct cell *c, int clear_sorts, * need to force a sort after the gradient recv. */ clear_sorts = !cell_get_flag(c, cell_flag_skip_rt_sort); cell_clear_flag(c, cell_flag_skip_rt_sort); + } else if (clear_sorts == -1) { + /* When running with RT and symmetric MPI exchanges, the first RT + * recv tasks (gradients) may not necessarily run, i.e. in cases + * where a local cell is inactive and needs to be updated by an + * active foreign cell. Should that occur, we need to make sure + * that the skip_rt_sort cell flag has been properly cleaned up + * first. */ + cell_clear_flag(c, cell_flag_skip_rt_sort); + /* clear_sorts == -1 is abused to mark the case where this + * function is called for a recv_rt_transport task. We're not + * actually sorting now, so don't clear the sorts. */ + clear_sorts = 0; } /* Clear this cell's sorted mask. */ diff --git a/src/scheduler.c b/src/scheduler.c index 0e9c6f66132b7252134f0bb5b1b14593ca34574a..4a9a771c6c9466bc2c5ce02d90db1cfa713df250 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -1968,8 +1968,7 @@ void scheduler_reweight(struct scheduler *s, int verbose) { t->weight = 0.f; for (int j = 0; j < t->nr_unlock_tasks; j++) - if (t->unlock_tasks[j]->weight > t->weight) - t->weight = t->unlock_tasks[j]->weight; + t->weight += t->unlock_tasks[j]->weight; const float count_i = (t->ci != NULL) ? t->ci->hydro.count : 0.f; const float count_j = (t->cj != NULL) ? t->cj->hydro.count : 0.f; @@ -2447,8 +2446,6 @@ void scheduler_start(struct scheduler *s) { * @param t The #task. */ void scheduler_enqueue(struct scheduler *s, struct task *t) { - /* The target queue for this task. */ - int qid = -1; /* Ignore skipped tasks */ if (t->skip) return; @@ -2482,16 +2479,21 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { #endif /* Find the previous owner for each task type, and do - any pre-processing needed. */ + * any pre-processing needed. */ + short int qid = -1; + short int *owner = NULL; switch (t->type) { case task_type_self: case task_type_sub_self: if (t->subtype == task_subtype_grav || t->subtype == task_subtype_grav_bkg || - t->subtype == task_subtype_external_grav) + t->subtype == task_subtype_external_grav) { qid = t->ci->grav.super->owner; - else + owner = &t->ci->grav.super->owner; + } else { qid = t->ci->hydro.super->owner; + owner = &t->ci->hydro.super->owner; + } break; case task_type_sort: case task_type_stars_resort: @@ -2507,6 +2509,7 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { case task_type_drift_sink: case task_type_cooling: qid = t->ci->hydro.super->owner; + owner = &t->ci->hydro.super->owner; break; case task_type_grav_down: case task_type_grav_long_range: @@ -2519,6 +2522,7 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { case task_type_pack: case task_type_unpack: qid = t->ci->grav.super->owner; + owner = &t->ci->grav.super->owner; break; case task_type_kick1: case task_type_kick2: @@ -2527,25 +2531,16 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { case task_type_timestep_limiter: case task_type_timestep_sync: qid = t->ci->super->owner; + owner = &t->ci->super->owner; break; case task_type_pair: case task_type_sub_pair: - if (t->subtype == task_subtype_grav || - t->subtype == task_subtype_grav_zoombkg || - t->subtype == task_subtype_grav_bkg || - t->subtype == task_subtype_grav_bkgzoom || - t->subtype == task_subtype_external_grav) { - qid = t->ci->grav.super->owner; - if (qid < 0 || - s->queues[qid].count > s->queues[t->cj->grav.super->owner].count) - qid = t->cj->grav.super->owner; - } else if (t->subtype == task_subtype_grav_bkg_pool) { - qid = t->ci->grav.super->owner; - } else { - qid = t->ci->hydro.super->owner; - if (qid < 0 || - s->queues[qid].count > s->queues[t->cj->hydro.super->owner].count) - qid = t->cj->hydro.super->owner; + qid = t->ci->super->owner; + owner = &t->ci->super->owner; + if (qid < 0 || + s->queues[qid].count > s->queues[t->cj->super->owner].count) { + qid = t->cj->super->owner; + owner = &t->cj->super->owner; } break; case task_type_recv: @@ -2767,10 +2762,12 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { if (qid >= s->nr_queues) error("Bad computed qid."); - /* If no previous owner, pick a random queue. */ - /* Note that getticks() is random enough */ - if (qid < 0) qid = getticks() % s->nr_queues; - + /* If no qid, pick a random queue. */ + if (qid < 0) qid = rand() % s->nr_queues; + + /* Save qid as owner for next time a task accesses this cell. */ + if (owner != NULL) *owner = qid; + /* Increase the waiting counter. */ atomic_inc(&s->waiting); diff --git a/src/space.c b/src/space.c index 970fd3338425350e686839439644b2b055ea6abc..55a9d06dc898d49b44f3a8aaa8152f98a2d5e4ac 100644 --- a/src/space.c +++ b/src/space.c @@ -474,59 +474,55 @@ void space_map_cells_pre(struct space *s, int full, * @param nr_cells Number of #cell to pick up. * @param cells Array of @c nr_cells #cell pointers in which to store the * new cells. + * @param tpid ID of threadpool threadpool associated with cells_sub. */ void space_getcells(struct space *s, int nr_cells, struct cell **cells, - const int thread_id) { + const short int tpid) { /* For each requested cell... */ for (int j = 0; j < nr_cells; j++) { /* Is the cell buffer empty? */ - if (s->cells_sub[thread_id] == NULL) { - - if (swift_memalign("cells_sub", (void **)&s->cells_sub[thread_id], - cell_align, + if (s->cells_sub[tpid] == NULL) { + if (swift_memalign("cells_sub", (void **)&s->cells_sub[tpid], cell_align, space_cellallocchunk * sizeof(struct cell)) != 0) error("Failed to allocate more cells."); /* Clear the newly-allocated cells. */ - bzero(s->cells_sub[thread_id], - sizeof(struct cell) * space_cellallocchunk); + bzero(s->cells_sub[tpid], sizeof(struct cell) * space_cellallocchunk); /* Constructed a linked list */ for (int k = 0; k < space_cellallocchunk - 1; k++) - s->cells_sub[thread_id][k].next = &s->cells_sub[thread_id][k + 1]; - /* Signal the end of the list */ - s->cells_sub[thread_id][space_cellallocchunk - 1].next = NULL; + s->cells_sub[tpid][k].next = &s->cells_sub[tpid][k + 1]; + s->cells_sub[tpid][space_cellallocchunk - 1].next = NULL; } /* Is the multipole buffer empty? */ - if (s->with_self_gravity && s->multipoles_sub[thread_id] == NULL) { + if (s->with_self_gravity && s->multipoles_sub[tpid] == NULL) { if (swift_memalign( - "multipoles_sub", (void **)&s->multipoles_sub[thread_id], + "multipoles_sub", (void **)&s->multipoles_sub[tpid], multipole_align, space_cellallocchunk * sizeof(struct gravity_tensors)) != 0) error("Failed to allocate more multipoles."); /* Constructed a linked list */ for (int k = 0; k < space_cellallocchunk - 1; k++) - s->multipoles_sub[thread_id][k].next = - &s->multipoles_sub[thread_id][k + 1]; - /* Signal the end of the list */ - s->multipoles_sub[thread_id][space_cellallocchunk - 1].next = NULL; + s->multipoles_sub[tpid][k].next = &s->multipoles_sub[tpid][k + 1]; + s->multipoles_sub[tpid][space_cellallocchunk - 1].next = NULL; } /* Pick off the next cell. */ - cells[j] = s->cells_sub[thread_id]; - s->cells_sub[thread_id] = cells[j]->next; + cells[j] = s->cells_sub[tpid]; + s->cells_sub[tpid] = cells[j]->next; /* Hook the multipole */ if (s->with_self_gravity) { - cells[j]->grav.multipole = s->multipoles_sub[thread_id]; - s->multipoles_sub[thread_id] = cells[j]->grav.multipole->next; + cells[j]->grav.multipole = s->multipoles_sub[tpid]; + s->multipoles_sub[tpid] = cells[j]->grav.multipole->next; } } + /* Unlock the space. */ atomic_add(&s->tot_cells, nr_cells); /* Init some things in the cell we just got. */ @@ -538,7 +534,7 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells, bzero(cells[j], sizeof(struct cell)); cells[j]->grav.multipole = temp; cells[j]->nodeID = -1; - cells[j]->owner = thread_id; + cells[j]->tpid = tpid; if (lock_init(&cells[j]->hydro.lock) != 0 || lock_init(&cells[j]->grav.plock) != 0 || lock_init(&cells[j]->grav.mlock) != 0 || @@ -558,9 +554,8 @@ void space_getcells(struct space *s, int nr_cells, struct cell **cells, * @param s The #space. */ void space_free_buff_sort_indices(struct space *s) { - - for (int tid = 0; tid < s->e->nr_pool_threads; ++tid) { - for (struct cell *finger = s->cells_sub[tid]; finger != NULL; + for (short int tpid = 0; tpid < s->e->nr_pool_threads; ++tpid) { + for (struct cell *finger = s->cells_sub[tpid]; finger != NULL; finger = finger->next) { cell_free_hydro_sorts(finger); cell_free_stars_sorts(finger); diff --git a/src/space.h b/src/space.h index cc9253bb4b30bb74848cfa757656817cfcc516e2..f081521574ce6e7082168340391da44afd34c52a 100644 --- a/src/space.h +++ b/src/space.h @@ -159,13 +159,13 @@ struct space { /*! The (level 0) cells themselves. */ struct cell *cells_top; - /*! Buffer of unused cells for the sub-cells. */ + /*! Buffer of unused cells for the sub-cells. One chunk per thread. */ struct cell **cells_sub; /*! The multipoles associated with the top-level (level 0) cells */ struct gravity_tensors *multipoles_top; - /*! Buffer of unused multipoles for the sub-cells. */ + /*! Buffer of unused multipoles for the sub-cells. One chunk per thread. */ struct gravity_tensors **multipoles_sub; /*! The indices of the *local* top-level cells */ @@ -499,7 +499,7 @@ void space_bparts_sort(struct bpart *bparts, int *ind, int *counts, void space_sinks_sort(struct sink *sinks, int *ind, int *counts, int num_bins, ptrdiff_t sinks_offset); void space_getcells(struct space *s, int nr_cells, struct cell **cells, - const int thread_id); + const short int tid); void space_init(struct space *s, struct swift_params *params, const struct cosmology *cosmo, double dim[3], const struct hydro_props *hydro_properties, diff --git a/src/space_recycle.c b/src/space_recycle.c index a9f450bd7262d4853722b580e394dbf7bb5ae011..afa9cefbc3a4be93cbe30f6027a97eb2339710d8 100644 --- a/src/space_recycle.c +++ b/src/space_recycle.c @@ -255,28 +255,16 @@ void space_recycle(struct space *s, struct cell *c, const int lock) { lock_destroy(&c->stars.star_formation_lock) != 0) error("Failed to destroy spinlocks."); - /* Lock the space. */ - if (lock) lock_lock(&s->lock); - - /* Thread which allocated this cell */ - const int owner = c->owner; - /* Hook the multipole back in the buffer */ if (s->with_self_gravity) { - c->grav.multipole->next = s->multipoles_sub[owner]; - s->multipoles_sub[owner] = c->grav.multipole; + c->grav.multipole->next = s->multipoles_sub[c->tpid]; + s->multipoles_sub[c->tpid] = c->grav.multipole; } /* Hook this cell into the buffer. */ - c->next = s->cells_sub[owner]; - s->cells_sub[owner] = c; - if (lock) - s->tot_cells -= 1; - else - atomic_dec(&s->tot_cells); - - /* Unlock the space. */ - if (lock) lock_unlock_blind(&s->lock); + c->next = s->cells_sub[c->tpid]; + s->cells_sub[c->tpid] = c; + atomic_dec(&s->tot_cells); } /** @@ -324,18 +312,17 @@ void space_recycle_list(struct space *s, struct cell *cell_list_begin, /* Lock the space. */ lock_lock(&s->lock); - /* Thread which allocated this cell */ - const int owner = cell_list_begin->owner; - - /* Hook the cells into the buffer. */ - cell_list_end->next = s->cells_sub[owner]; - s->cells_sub[owner] = cell_list_begin; + /* Hook the cells into the buffer keeping tpid if we can. */ + short int tpid = cell_list_begin->tpid; + if (tpid < 0) tpid = 0; + cell_list_end->next = s->cells_sub[tpid]; + s->cells_sub[tpid] = cell_list_begin; atomic_sub(&s->tot_cells, count); /* Hook the multipoles into the buffer. */ if (s->with_self_gravity) { - multipole_list_end->next = s->multipoles_sub[owner]; - s->multipoles_sub[owner] = multipole_list_begin; + multipole_list_end->next = s->multipoles_sub[tpid]; + s->multipoles_sub[tpid] = multipole_list_begin; } /* Unlock the space. */ diff --git a/src/space_split.c b/src/space_split.c index e018cabbe29285ed2d654630f6b0b377c68677fd..811dff01dfc90a4cd50ce933db9396351c5944fe 100644 --- a/src/space_split.c +++ b/src/space_split.c @@ -57,7 +57,7 @@ void space_split_recursive(struct space *s, struct cell *c, struct cell_buff *restrict bbuff, struct cell_buff *restrict gbuff, struct cell_buff *restrict sink_buff, - const int thread_id) { + const short int tpid) { const int count = c->hydro.count; const int gcount = c->grav.count; @@ -96,11 +96,9 @@ void space_split_recursive(struct space *s, struct cell *c, struct engine *e = s->e; const integertime_t ti_current = e->ti_current; - /* Set the top level cell owner. Doing it here ensures top level cells - * have the same owner as their progeny. */ - if (depth == 0) { - c->owner = thread_id; - } + /* Set the top level cell tpid. Doing it here ensures top level cells + * have the same tpid as their progeny. */ + if (depth == 0) c->tpid = tpid; /* If the buff is NULL, allocate it, and remember to free it. */ const int allocate_buffer = (buff == NULL && gbuff == NULL && sbuff == NULL && @@ -221,7 +219,7 @@ void space_split_recursive(struct space *s, struct cell *c, c->split = 1; /* Create the cell's progeny. */ - space_getcells(s, 8, c->progeny, thread_id); + space_getcells(s, 8, c->progeny, tpid); for (int k = 0; k < 8; k++) { struct cell *cp = c->progeny[k]; cp->hydro.count = 0; @@ -719,6 +717,9 @@ void space_split_recursive(struct space *s, struct cell *c, c->black_holes.h_max_active = black_holes_h_max_active; c->maxdepth = maxdepth; + /* No runner owns this cell yet. We assign those during scheduling. */ + c->owner = -1; + /* Store the global max depth */ if (c->depth == 0) atomic_max(&s->maxdepth, maxdepth); @@ -773,10 +774,13 @@ void space_split_mapper(void *map_data, int num_cells, void *extra_data, float max_softening = 0.f; float max_mpole_power[SELF_GRAVITY_MULTIPOLE_ORDER + 1] = {0.f}; + /* Threadpool id of current thread. */ + short int tpid = threadpool_gettid(); + /* Loop over the non-empty cells */ for (int ind = 0; ind < num_cells; ind++) { struct cell *c = &cells_top[local_cells_with_particles[ind]]; - space_split_recursive(s, c, NULL, NULL, NULL, NULL, NULL, tid); + space_split_recursive(s, c, NULL, NULL, NULL, NULL, NULL, tpid); if (s->with_self_gravity) { min_a_grav = diff --git a/src/threadpool.c b/src/threadpool.c index c50b088e9c6077f579cea06307112616888c30c9..d1018fb89edc9d9c3d5f69d910d93e0e9ff3cc3b 100644 --- a/src/threadpool.c +++ b/src/threadpool.c @@ -39,6 +39,9 @@ #include "error.h" #include "minmax.h" +/* Keys for thread specific data. */ +static pthread_key_t threadpool_tid; + #ifdef SWIFT_DEBUG_THREADPOOL /** * @brief Store a log entry of the given chunk. @@ -137,6 +140,10 @@ void threadpool_dump_log(struct threadpool *tp, const char *filename, */ static void threadpool_chomp(struct threadpool *tp, const int tid) { + /* Store the thread ID as thread specific data. */ + int localtid = tid; + pthread_setspecific(threadpool_tid, &localtid); + /* Loop until we can't get a chunk. */ while (1) { /* Compute the desired chunk size. */ @@ -164,14 +171,10 @@ static void threadpool_chomp(struct threadpool *tp, const int tid) { #ifdef SWIFT_DEBUG_THREADPOOL const ticks tic = getticks(); #endif - if (tp->pass_tid) { - tp->map_function_tid( - (char *)tp->map_data + (tp->map_data_stride * task_ind), chunk_size, - tp->map_extra_data, tid); - } else { - tp->map_function((char *)tp->map_data + (tp->map_data_stride * task_ind), - chunk_size, tp->map_extra_data); - } + + tp->map_function((char *)tp->map_data + (tp->map_data_stride * task_ind), + chunk_size, tp->map_extra_data); + #ifdef SWIFT_DEBUG_THREADPOOL threadpool_log(tp, tid, chunk_size, tic, getticks()); #endif @@ -212,6 +215,13 @@ void threadpool_init(struct threadpool *tp, int num_threads) { /* Initialize the thread counters. */ tp->num_threads = num_threads; + /* Create thread local data areas. Only do this once for all threads. */ + pthread_key_create(&threadpool_tid, NULL); + + /* Store the main thread ID as thread specific data. */ + static int localtid = 0; + pthread_setspecific(threadpool_tid, &localtid); + #ifdef SWIFT_DEBUG_THREADPOOL if ((tp->logs = (struct mapper_log *)malloc(sizeof(struct mapper_log) * num_threads)) == NULL) @@ -455,3 +465,11 @@ void threadpool_clean(struct threadpool *tp) { free(tp->logs); #endif } + +/** + * @brief return the threadpool id of the current thread. + */ +int threadpool_gettid() { + int *tid = (int *)pthread_getspecific(threadpool_tid); + return *tid; +} diff --git a/src/threadpool.h b/src/threadpool.h index 84d24a02eeaaded95e2cca991e7970fc1c31a23d..73e263157e65d9dbf3410142e676207a7a826c62 100644 --- a/src/threadpool.h +++ b/src/threadpool.h @@ -108,10 +108,7 @@ void threadpool_init(struct threadpool *tp, int num_threads); void threadpool_map(struct threadpool *tp, threadpool_map_function map_function, void *map_data, size_t N, int stride, int chunk, void *extra_data); -void threadpool_map_with_tid(struct threadpool *tp, - threadpool_map_function_tid map_function, - void *map_data, size_t N, int stride, int chunk, - void *extra_data); +int threadpool_gettid(void); void threadpool_clean(struct threadpool *tp); #ifdef SWIFT_DEBUG_THREADPOOL void threadpool_reset_log(struct threadpool *tp); diff --git a/theory/Gizmo/gizmo-implementation-details/bert.tex b/theory/Gizmo/gizmo-implementation-details/bert.tex index 99975349d90ef99f736c37dd3a4ec8d2092189cb..455d7236f8ea2a18c3f8c22945240bec8a7f3256 100644 --- a/theory/Gizmo/gizmo-implementation-details/bert.tex +++ b/theory/Gizmo/gizmo-implementation-details/bert.tex @@ -78,7 +78,7 @@ The time derivatives are given by (eqn. (A4) in Hopkins) \vec{w'} & \rho' & 0\\ 0 & \vec{w'} & 1/\rho'\\ 0 & \gamma P' & \vec{w'} - \end{pmatrix} \vec{\nabla}X_k, + \end{pmatrix} \vec{\nabla}X_k, \label{eq:euler_primitive} \end{equation} or, for example~: \begin{equation} @@ -101,4 +101,47 @@ with $\vec{n}_k$ the unit vector along the coordinate axes ($k=x,y,z$) and $e = Finally, we use the fluxes to update the conserved variables (eqn. (23) in Hopkins)~: \begin{equation} \Delta C_k = -\Delta t \sum_j \vec{F_{ij}}.\vec{A_{ij}}. -\end{equation} \ No newline at end of file +\end{equation} + +\subsection{Other equations that are used} + +In order to couple the GIZMO schemes to subgrid physics, we need to provide a way to obtain the time +derivative of the internal energy, which is not a basic quantity in these schemes. This time derivative can be +obtained from the Euler equations, using the gradients. From \eqref{eq:euler_primitive}, we get + +\begin{equation} +\frac{\partial{}P}{\partial{}t} = -\gamma{}P \vec{\nabla{}}.\vec{w} - \vec{w}.\vec{\nabla{}}P, +\end{equation} +while we also have +\begin{equation} +\frac{\partial{}P}{\partial{}t} = \frac{\partial{}}{\partial{}t} \left( + (\gamma{}-1) \rho{} u +\right) = (\gamma{}-1)\rho{}\frac{\partial{}u}{\partial{}t} + (\gamma{}-1)u\frac{\partial{}\rho{}}{\partial{}t} +\end{equation} +or hence +\begin{equation} +\frac{\partial{}u}{\partial{}t} = \frac{1}{(\gamma{}-1)\rho{}} \frac{\partial{}P}{\partial{}t} - +\frac{u}{\rho{}} \left( -\vec{w}.\vec{\nabla{}}\rho{} - \rho{}\vec{\nabla{}}.\vec{w} \right), +\end{equation} +where we again used \eqref{eq:euler_primitive} in the last term. + +Combining these equations, we get +\begin{align} +\frac{\partial{}u}{\partial{}t} &= \frac{1}{(\gamma{}-1)\rho{}} \left( +-\gamma{}P \vec{\nabla{}}.\vec{w} - \vec{w}.\vec{\nabla{}}P +\right) - +\frac{u}{\rho{}} \left( -\vec{w}.\vec{\nabla{}}\rho{} - \rho{}\vec{\nabla{}}.\vec{w} \right) \\ +&= -\gamma{}u \vec{\nabla{}}.\vec{w} - \frac{1}{(\gamma{}-1)\rho{}} \vec{w}.\vec{\nabla{}}P ++ \frac{u}{\rho{}}\vec{w}.\vec{\nabla{}}\rho{} + u \vec{\nabla{}}.\vec{w} \\ +&= -(\gamma{}-1)u \vec{\nabla{}}.\vec{w} - \frac{1}{(\gamma{}-1)\rho{}} \vec{w}.\vec{\nabla{}}P ++ \frac{u}{\rho{}}\vec{w}.\vec{\nabla{}}\rho{}. +\end{align} + +For convenience, we can eliminate $u$ from this equation again: +\begin{equation} +\frac{\partial{}u}{\partial{}t} = -\frac{P}{\rho{}}\vec{\nabla{}}.\vec{w} +- \frac{1}{(\gamma{}-1)\rho{}} \vec{w} . \left( +\vec{\nabla{}}P - \frac{P}{\rho{}} \vec{\nabla{}}\rho{} +\right), +\end{equation} +where we recognise a factor $\vec{\nabla{}}u$ in the second term. diff --git a/tools/task_plots/analyse_threadpool_tasks.py b/tools/task_plots/analyse_threadpool_tasks.py index 5c25b7673bde02bb950f332d74382e2e8dc2e85b..34f75ea0732470732953fe40b0ce95ef7512c7ef 100755 --- a/tools/task_plots/analyse_threadpool_tasks.py +++ b/tools/task_plots/analyse_threadpool_tasks.py @@ -28,11 +28,6 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ -import matplotlib - -matplotlib.use("Agg") -import matplotlib.collections as collections -import matplotlib.ticker as plticker import pylab as pl import sys import argparse diff --git a/tools/task_plots/iplot_tasks.py b/tools/task_plots/iplot_tasks.py index a96ad977c2604581fa1c33a234dd1f19d4b0a62b..50e65ebf9b7b285ce6f8c55f20d1006db1c30e67 100755 --- a/tools/task_plots/iplot_tasks.py +++ b/tools/task_plots/iplot_tasks.py @@ -458,6 +458,35 @@ class Container: else: fig.canvas.mpl_connect("button_press_event", self.onclick) + # Space bar to dump all tasks. Use with caution... + fig.canvas.mpl_connect("key_press_event", self.dump) + + def dump(self, event): + # Dump all tasks to the console sorted by tic. + xlow = float(event.inaxes.viewLim.x0) + xhigh = float(event.inaxes.viewLim.x1) + + if event.key == " ": + dumps = {} + for thread in range(nthread): + tics = self.ltics[thread] + tocs = self.ltocs[thread] + labels = self.llabels[thread] + for i in range(len(tics)): + if (tics[i] > xlow and tics[i] < xhigh) or ( + tocs[i] > xlow and tocs[i] < xhigh + ): + tic = "{0:.3f}".format(tics[i]) + toc = "{0:.3f}".format(tocs[i]) + dumps[tics[i]] = ( + labels[i] + ", tic/toc = " + tic + " / " + toc + ) + print("") + print("Tasks in time range: " + str(xlow) + " -> " + str(xhigh)) + for key in sorted(dumps): + print(dumps[key]) + print("") + def onclick(self, event): # Find thread, then scan for bounded task. try: diff --git a/tools/task_plots/iplot_threadpool.py b/tools/task_plots/iplot_threadpool.py new file mode 100755 index 0000000000000000000000000000000000000000..70ca679e0a22a87c45f710d47b4f915a4f3a50ce --- /dev/null +++ b/tools/task_plots/iplot_threadpool.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python3 +""" +Interactive plot of a threadpool dump. + +Usage: + iplot_threadpool.py [options] input.dat + +where input.dat is a threadpool info file for a step. Use the '-Y interval' +flag of the swift or swift_mpi commands to create these (these will need to be +built with the --enable-threadpool-debugging configure option). + +The plot can be scrolled and zoomed using the standard matplotlib +controls, the type of task at a point can be queried by a mouse click +(unless the --motion option is in effect when a continuous readout is +shown) the task type and tic/toc range are reported in the terminal. + +Requires the tkinter module. + +This file is part of SWIFT. + +Copyright (C) 2022 Peter W. Draper (p.w.draper@durham.ac.uk) +All Rights Reserved. + +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 <http://www.gnu.org/licenses/>. +""" + +import matplotlib + +matplotlib.use("TkAgg") +import numpy as np +import matplotlib.backends.backend_tkagg as tkagg +from matplotlib.figure import Figure +import tkinter as tk +import math +import matplotlib.collections as collections +import matplotlib.ticker as plticker +import pylab as pl +import sys +import argparse + +# Handle the command line. +parser = argparse.ArgumentParser(description="Plot threadpool function graphs") + +parser.add_argument("input", help="Threadpool data file (-Y output)") +parser.add_argument( + "-m", + "--motion", + dest="motion", + help="Track mouse motion, otherwise clicks (def: clicks)", + default=False, + action="store_true", +) +parser.add_argument( + "-l", + "--limit", + dest="limit", + help="Upper time limit in millisecs (def: depends on data)", + default=0, + type=float, +) +parser.add_argument( + "--height", + dest="height", + help="Height of plot in inches (def: 4)", + default=4.0, + type=float, +) +parser.add_argument( + "--width", + dest="width", + help="Width of plot in inches (def: 16)", + default=16.0, + type=float, +) +parser.add_argument( + "-v", + "--verbose", + dest="verbose", + help="Show colour assignments and other details (def: False)", + default=False, + action="store_true", +) + +args = parser.parse_args() +infile = args.input +delta_t = args.limit + +# Basic plot configuration. +PLOT_PARAMS = { + "axes.labelsize": 10, + "axes.titlesize": 10, + "font.size": 12, + "xtick.labelsize": 10, + "ytick.labelsize": 10, + "figure.figsize": (args.width, args.height), + "figure.subplot.left": 0.03, + "figure.subplot.right": 0.995, + "figure.subplot.bottom": 0.09, + "figure.subplot.top": 0.99, + "figure.subplot.wspace": 0.0, + "figure.subplot.hspace": 0.0, + "lines.markersize": 6, + "lines.linewidth": 3.0, +} +pl.rcParams.update(PLOT_PARAMS) + +# A number of colours for the various types. Recycled when there are +# more task types than colours... +colours = [ + "cyan", + "lightgray", + "darkblue", + "yellow", + "tan", + "dodgerblue", + "sienna", + "aquamarine", + "bisque", + "blue", + "green", + "lightgreen", + "brown", + "purple", + "moccasin", + "olivedrab", + "chartreuse", + "olive", + "darkgreen", + "green", + "mediumseagreen", + "mediumaquamarine", + "darkslategrey", + "mediumturquoise", + "black", + "cadetblue", + "skyblue", + "red", + "slategray", + "gold", + "slateblue", + "blueviolet", + "mediumorchid", + "firebrick", + "magenta", + "hotpink", + "pink", + "orange", + "lightgreen", +] +maxcolours = len(colours) + +# Read header. First two lines. +with open(infile) as infid: + head = [next(infid) for x in range(2)] +header = head[1][2:].strip() +header = eval(header) +nthread = int(header["num_threads"]) + 1 +CPU_CLOCK = float(header["cpufreq"]) / 1000.0 +print("Number of threads: ", nthread) +if args.verbose: + print("CPU frequency:", CPU_CLOCK * 1000.0) + +# Read input. +data = pl.genfromtxt(infile, dtype=None, delimiter=" ", encoding=None) + +# Mixed types, so need to separate. +tics = [] +tocs = [] +funcs = [] +threads = [] +chunks = [] +for i in data: + if i[0] != "#": + funcs.append(i[0].replace("_mapper", "")) + if i[1] < 0: + threads.append(nthread - 1) + else: + threads.append(i[1]) + chunks.append(i[2]) + tics.append(i[3]) + tocs.append(i[4]) +tics = pl.array(tics) +tocs = pl.array(tocs) +funcs = pl.array(funcs) +threads = pl.array(threads) +chunks = pl.array(chunks) + +# Recover the start and end time +mintic_step = min(tics) +tic_step = mintic_step +toc_step = max(tocs) +print("# Min tic = ", mintic_step) + +# Calculate the time range, if not given. +delta_t = delta_t * CPU_CLOCK +if delta_t == 0: + dt = toc_step - tic_step + if dt > delta_t: + delta_t = dt + print("Data range: ", delta_t / CPU_CLOCK, "ms") + +# Once more doing the real gather and plots this time. +start_t = float(tic_step) +tics -= tic_step +tocs -= tic_step +end_t = (toc_step - start_t) / CPU_CLOCK + +# Get all "task" names and assign colours. +TASKTYPES = pl.unique(funcs) +print(TASKTYPES) + +# Set colours of tasks. +TASKCOLOURS = {} +ncolours = 0 +for task in TASKTYPES: + TASKCOLOURS[task] = colours[ncolours] + ncolours = (ncolours + 1) % maxcolours + +# For fiddling with colours... +if args.verbose: + print("#Selected colours:") + for task in sorted(TASKCOLOURS.keys()): + print("# " + task + ": " + TASKCOLOURS[task]) + +tasks = {} +tasks[-1] = [] +for i in range(nthread): + tasks[i] = [] + +for i in range(len(threads)): + thread = threads[i] + tasks[thread].append({}) + tasks[thread][-1]["type"] = funcs[i] + tic = tics[i] / CPU_CLOCK + toc = tocs[i] / CPU_CLOCK + tasks[thread][-1]["tic"] = tic + tasks[thread][-1]["toc"] = toc + tasks[thread][-1]["colour"] = TASKCOLOURS[funcs[i]] + +# Do the plotting. +fig = Figure() +ax = fig.add_subplot(1, 1, 1) +ax.set_xlim(-delta_t * 0.01 / CPU_CLOCK, delta_t * 1.01 / CPU_CLOCK) +ax.set_ylim(0.5, nthread + 1.0) + +ltics = [] +ltocs = [] +llabels = [] +for i in range(nthread): + + # Collect ranges and colours into arrays. Also indexed lists for lookup tables. + tictocs = [] + colours = [] + tics = [] + tocs = [] + labels = [] + for task in tasks[i]: + tictocs.append((task["tic"], task["toc"] - task["tic"])) + colours.append(task["colour"]) + + tics.append(task["tic"]) + tocs.append(task["toc"]) + labels.append(task["type"]) + + # Add to look up tables. + ltics.append(tics) + ltocs.append(tocs) + llabels.append(labels) + + # Now plot. + ax.broken_barh(tictocs, [i + 0.55, 0.9], facecolors=colours, linewidth=0) + +# Start and end of time-step +ax.plot([0, 0], [0, nthread + 1], "k--", linewidth=1) +ax.plot([end_t, end_t], [0, nthread + 1], "k--", linewidth=1) + +# Labels. +ax.set_xlabel("Wall clock time [ms]") +ax.set_ylabel("Thread ID") + +loc = plticker.MultipleLocator(base=1) +ax.yaxis.set_major_locator(loc) +ax.grid(True, which="major", axis="y", linestyle="-") + + +class Container: + def __init__(self, window, figure, motion, nthread, ltics, ltocs, llabels): + self.window = window + self.figure = figure + self.motion = motion + self.nthread = nthread + self.ltics = ltics + self.ltocs = ltocs + self.llabels = llabels + + def plot(self): + canvas = tkagg.FigureCanvasTkAgg(self.figure, master=self.window) + wcanvas = canvas.get_tk_widget() + wcanvas.config(width=1000, height=300) + wcanvas.pack(side=tk.TOP, expand=True, fill=tk.BOTH) + + toolbar = tkagg.NavigationToolbar2Tk(canvas, self.window) + toolbar.update() + self.output = tk.StringVar() + label = tk.Label( + self.window, textvariable=self.output, bg="white", fg="red", bd=2 + ) + label.pack(side=tk.RIGHT, expand=True, fill=tk.X) + wcanvas.pack(side=tk.TOP, expand=True, fill=tk.BOTH) + + canvas.draw() + + # Print task type using mouse clicks or motion. + if self.motion: + fig.canvas.mpl_connect("motion_notify_event", self.onclick) + else: + fig.canvas.mpl_connect("button_press_event", self.onclick) + + # Space bar to dump all tasks. Use with caution... + fig.canvas.mpl_connect("key_press_event", self.dump) + + def dump(self, event): + # Dump all tasks to the console sorted by tic. + xlow = float(event.inaxes.viewLim.x0) + xhigh = float(event.inaxes.viewLim.x1) + + if event.key == " ": + dumps = {} + for thread in range(nthread): + tics = self.ltics[thread] + tocs = self.ltocs[thread] + labels = self.llabels[thread] + for i in range(len(tics)): + if (tics[i] > xlow and tics[i] < xhigh) or ( + tocs[i] > xlow and tocs[i] < xhigh + ): + tic = "{0:.3f}".format(tics[i]) + toc = "{0:.3f}".format(tocs[i]) + dumps[tics[i]] = ( + labels[i] + ", tic/toc = " + tic + " / " + toc + ) + print("") + print("Tasks in time range: " + str(xlow) + " -> " + str(xhigh)) + for key in sorted(dumps): + print(dumps[key]) + print("") + + def onclick(self, event): + # Find thread, then scan for bounded task. + try: + thread = int(round(event.ydata)) - 1 + outstr = "none" + if thread >= 0 and thread < self.nthread: + tics = self.ltics[thread] + tocs = self.ltocs[thread] + labels = self.llabels[thread] + for i in range(len(tics)): + if event.xdata > tics[i] and event.xdata < tocs[i]: + tic = "{0:.3f}".format(tics[i]) + toc = "{0:.3f}".format(tocs[i]) + outstr = labels[i] + ", tic/toc = " + tic + " / " + toc + break + self.output.set(outstr) + except TypeError: + # Out of bounds clears field. + self.output.set("") + pass + + def quit(self): + self.window.destroy() + + +window = tk.Tk() +window.protocol("WM_DELETE_WINDOW", window.quit) +container = Container(window, fig, args.motion, nthread, ltics, ltocs, llabels) +container.plot() +window.mainloop() + +sys.exit(0) diff --git a/tools/task_plots/plot_threadpool.py b/tools/task_plots/plot_threadpool.py index 05f07a7939f1f28fb5b7d653e6e1b57db9776885..1da8fb2ac010c84041628c51fd7ff213aef06221 100755 --- a/tools/task_plots/plot_threadpool.py +++ b/tools/task_plots/plot_threadpool.py @@ -30,14 +30,15 @@ You should have received a copy of the GNU Lesser General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. """ +import argparse +import sys +import pylab as pl +import matplotlib.ticker as plticker +import matplotlib.collections as collections +import math import matplotlib matplotlib.use("Agg") -import matplotlib.collections as collections -import matplotlib.ticker as plticker -import pylab as pl -import sys -import argparse # Handle the command line. parser = argparse.ArgumentParser(description="Plot threadpool function graphs") @@ -289,7 +290,8 @@ j = 0 for task in tasks[nthread - expand]: tictocs.append((task["tic"], task["toc"] - task["tic"])) colours.append(task["colour"]) -ax.broken_barh(tictocs, [0, (nthread - 1)], facecolors=colours, linewidth=0, alpha=0.15) +ax.broken_barh(tictocs, [0, (nthread - 1)], + facecolors=colours, linewidth=0, alpha=0.15) # And we don't plot the fake thread. nthread = nthread - expand @@ -313,19 +315,24 @@ for i in range(nthread): ax.broken_barh(tictocs, [i + 0.05, 0.90], facecolors=colours, linewidth=0) # Legend and room for it. -nrow = len(typesseen) / 5 +nrow = math.ceil(len(typesseen) / 4) if not args.nolegend: - ax.fill_between([0, 0], nthread + 0.5, nthread + nrow + 0.5, facecolor="white") + ax.fill_between([0, 0], nthread, nthread + nrow, facecolor="white") ax.set_ylim(0, nthread + 0.5) ax.legend( - loc="upper center", shadow=True, bbox_to_anchor=(0.0, 1.3, 1.0, 0.2), ncol=6, fontsize=9 + loc="lower left", + shadow=True, + bbox_to_anchor=(0.0, 1.0, 1.0, 0.2), + mode="expand", + ncol=4, ) box = ax.get_position() ax.set_position([box.x0, box.y0, box.width, box.height * 0.65]) # Start and end of time-step real_start_t = (mintic_step - tic_step) / CPU_CLOCK -ax.plot([real_start_t, real_start_t], [0, nthread + nrow + 1], "k--", linewidth=1) +ax.plot([real_start_t, real_start_t], [ + 0, nthread + nrow + 1], "k--", linewidth=1) ax.plot([end_t, end_t], [0, nthread + nrow + 1], "k--", linewidth=1) @@ -340,8 +347,7 @@ loc = plticker.MultipleLocator(base=expand) ax.yaxis.set_major_locator(loc) ax.grid(True, which="major", axis="y", linestyle="-") -pl.show() -pl.savefig(outpng) +pl.savefig(outpng, bbox_inches="tight", dpi=100) print("Graphics done, output written to", outpng) sys.exit(0) diff --git a/tools/task_plots/process_plot_threadpool b/tools/task_plots/process_plot_threadpool index b1238b3668067f17b98099f6287fa63fec5d2395..87b8726924a19122144df4aad3a1b5ac5cf4842c 100755 --- a/tools/task_plots/process_plot_threadpool +++ b/tools/task_plots/process_plot_threadpool @@ -59,12 +59,12 @@ fi list="" for f in $files; do s=$(echo $f| sed 's,threadpool_info-step\(.*\).dat,\1,') - list="$list $f $s poolstep${s}r" + list="$list $f $s threadpool-step${s}" done # And process them, echo "Processing threadpool info files..." -echo $list | xargs -P $NPROCS -n 3 /bin/bash -c "${SCRIPTHOME}/plot_threadpool.py --expand 1 --limit $TIMERANGE --width 16 --height 4 \$0 \$2 " +echo $list | xargs -P $NPROCS -n 3 /bin/bash -c "${SCRIPTHOME}/plot_threadpool.py --expand 1 --limit $TIMERANGE --width 16 --height 8 \$0 \$2 " echo $list | xargs -P $NPROCS -n 3 /bin/bash -c "${SCRIPTHOME}/analyse_threadpool_tasks.py \$0 --html > \$2.stats" echo "Writing output threadpool-index.html file" @@ -84,17 +84,19 @@ echo $list | xargs -n 3 | while read f s g; do <h2>Step $s</h2> EOF cat <<EOF >> threadpool-index.html -<a href="poolstep${s}r${i}.html"><img src="poolstep${s}r${i}.png" width=400px/></a> +<a href="threadpool-step${s}.html"><img src="threadpool-step${s}.png" width=400px/></a> EOF - cat <<EOF > poolstep${s}r${i}.html + cat <<EOF > threadpool-step${s}.html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <body> -<img src="poolstep${s}r${i}.png"> +<img src="threadpool-step${s}.png"> <pre> +<nav>Jump to: <a href="#all">all threads</a> <a href="#dead">dead times</a></nav> EOF -cat poolstep${s}r${i}.stats >> poolstep${s}r${i}.html -cat <<EOF >> poolstep${s}r${i}.html +cat threadpool-step${s}.stats >> threadpool-step${s}.html +cat <<EOF >> threadpool-step${s}.html +</pre> </body> </html> EOF diff --git a/tools/task_plots/process_plot_threadpool_MPI b/tools/task_plots/process_plot_threadpool_MPI new file mode 100755 index 0000000000000000000000000000000000000000..826c60497ba17308aa7ff46cf82344475ba8894e --- /dev/null +++ b/tools/task_plots/process_plot_threadpool_MPI @@ -0,0 +1,116 @@ +#!/bin/bash +# +# Usage: +# process_plot_threadpool_MPI nprocess [time-range-ms] +# +# Description: +# Process all the threadpool info files in the current directory +# creating function graphs for steps and threads. MPI version +# +# The input files are created by a run using the "-Y interval" flag and +# should be named "threadpool_info-rank<m>-step<n>.dat" in the current +# directory. All located files will be processed using "nprocess" concurrent +# processes and all plots will have the same time range if one is given. +# An output HTML file "index.html" will be created to view all the plots. +# +# +# This file is part of SWIFT: +# +# Copyright (C) 2022 Peter W. Draper (p.w.draper@durham.ac.uk) +# All Rights Reserved. +# +# 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 <http://www.gnu.org/licenses/>. + +# Handle command-line +if test "$1" = ""; then + echo "Usage: $0 nprocess [time-range-ms]" + exit 1 +fi +NPROCS=$1 +TIMERANGE=0 +LIMIT="(autoranged)" +if test "$2" != ""; then + TIMERANGE=$2 + LIMIT="" +fi + +# Locate script. +SCRIPTHOME=$(dirname "$0") + +# Find all thread info files. Use sort to get into correct order, step then rank. +files=$(ls -1 threadpool_info-rank*-step*.dat| sort -t- -k3,3) +if test $? != 0; then + echo "Failed to find any threadpool info files" + exit 1 +fi + +# Construct list of names, the step no and names for the graphics. +list="" +for f in $files; do + r=$(echo $f| sed 's,threadpool_info-rank\(.*\)-step.*.dat,\1,') + s=$(echo $f| sed 's,threadpool_info-rank.*-step\(.*\).dat,\1,') + list="$list $f $s $r threadpool-step${s}-rank${r}" +done + +# And process them, +echo "Processing threadpool info files..." +echo $list | xargs -P $NPROCS -n 4 /bin/bash -c "${SCRIPTHOME}/plot_threadpool.py --expand 1 --limit $TIMERANGE --width 16 --height 4 \$0 \$3 " +echo $list | xargs -P $NPROCS -n 4 /bin/bash -c "${SCRIPTHOME}/analyse_threadpool_tasks.py \$0 --html > \$3.stats" + +echo "Writing output threadpool-index.html file" +# Construct document - serial. +cat <<EOF > threadpool-index.html + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> + <head> + <title>SWIFT threadpool tasks $LIMIT</title> + </head> + <body> + <h1>SWIFT threadpool tasks $LIMIT</h1> +EOF + +echo $list | xargs -n 4 | while read f s r g; do + cat <<EOF >> threadpool-index.html +<h2>Step $s Rank $r</h2> +EOF + cat <<EOF >> threadpool-index.html +<a href="threadpool-step${s}-rank${r}.html"><img src="threadpool-step${s}-rank${r}.png" width=400px/></a> +EOF + cat <<EOF > threadpool-step${s}-rank${r}.html + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<body> +<img src="threadpool-step${s}-rank${r}.png"> +<pre> +<nav>Jump to: <a href="#all">all threads</a> <a href="#dead">dead times</a></nav> + +<pre> +EOF +cat threadpool-step${s}-rank${r}.stats >> threadpool-step${s}-rank${r}.html +cat <<EOF >> threadpool-step${s}-rank${r}.html +</pre> +</body> +</html> +EOF + +done + +cat <<EOF >> threadpool-index.html + </body> +</html> +EOF + +echo "Finished" + +exit