diff --git a/examples/GEAR/ZoomIn/zoom_in.yml b/examples/GEAR/ZoomIn/zoom_in.yml
index 1d01e6abd33454c4682bb2adfbfdb76a64e4948a..e0927d344f31b820743c83560893bce60982b1b9 100644
--- a/examples/GEAR/ZoomIn/zoom_in.yml
+++ b/examples/GEAR/ZoomIn/zoom_in.yml
@@ -55,7 +55,7 @@ SPH:
# Parameters related to the initial conditions
InitialConditions:
- file_name: ./zoom_in.hdf5 # The file to read
+ file_name: ./h177.hdf5 # The file to read
periodic: 1
cleanup_h_factors: 1 # Remove the h-factors inherited from Gadget
cleanup_velocity_factors: 1 # Remove the sqrt(a) factor in the velocities inherited from Gadget
diff --git a/examples/Logger/SimpleOrbits/makeIC.py b/examples/Logger/SimpleOrbits/makeIC.py
new file mode 100644
index 0000000000000000000000000000000000000000..a2418492584d0c79fa6eb900ca2b0f758c4c6084
--- /dev/null
+++ b/examples/Logger/SimpleOrbits/makeIC.py
@@ -0,0 +1,75 @@
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2020 Loic Hausammann (loic.hausammann@epfl.ch)
+#
+# 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 .
+#
+##############################################################################
+
+import numpy as np
+from astropy import units
+from swiftsimio import Writer
+import unyt
+
+np.random.seed(50)
+
+# parameters
+
+filename = "simple_orbits.hdf5"
+num_part = 1
+masses = 1.
+# If changed, need to update simple_orbits.yml
+M = units.solMass.to("earthMass")
+M = float(M)
+
+min_r = 0.2
+max_r = 5
+boxsize = 2 * max_r
+
+u_l = 1.49e13 # AU
+u_m = 5.97e27 # Earth mass
+u_v = 474047. # AU / yr
+u_t = u_l / u_v
+G = 1.189972e-04 # grav. const.
+
+# generate the coordinates
+dist = np.random.rand(num_part) * (max_r - min_r) + min_r
+angle = np.random.rand(num_part) * 2 * np.pi
+# more easy to do it in 2D, therefore coords[:, 2] == 0
+coords = np.zeros((num_part, 3))
+coords[:, 0] = dist * np.sin(angle)
+coords[:, 1] = dist * np.cos(angle)
+coords += boxsize * 0.5
+
+# generate the masses
+m = np.ones(num_part) * masses
+
+# generate the velocities
+sign = np.random.rand(num_part)
+sign[sign < 0.5] = -1
+sign[sign >= 0.5] = 1
+
+v = np.zeros((num_part, 3))
+v[:, 0] = sign * np.sqrt(G * M / (dist * (1 + np.tan(angle)**2)))
+v[:, 1] = - np.tan(angle) * v[:, 0]
+
+# Write the snapshot
+units = unyt.UnitSystem("Planets", unyt.AU, unyt.mearth, unyt.yr)
+
+snapshot = Writer(units, boxsize * unyt.AU)
+snapshot.dark_matter.coordinates = coords * unyt.AU
+snapshot.dark_matter.velocities = v * unyt.AU / unyt.yr
+snapshot.dark_matter.masses = m * unyt.mearth
+
+snapshot.write(filename)
diff --git a/examples/Logger/SimpleOrbits/mpl_style b/examples/Logger/SimpleOrbits/mpl_style
new file mode 100644
index 0000000000000000000000000000000000000000..9b85038b2490f0079ae09ecc45d24c0050863731
--- /dev/null
+++ b/examples/Logger/SimpleOrbits/mpl_style
@@ -0,0 +1,26 @@
+# Line Properties
+lines.linewidth: 2.
+
+# Font options
+font.size: 10
+font.family: STIXGeneral
+mathtext.fontset: stix
+
+# LaTeX options
+text.usetex: True
+
+# Legend options
+legend.frameon: True
+legend.fontsize: 10
+
+# Figure options for publications
+figure.dpi: 300
+
+# Histogram options
+hist.bins: auto
+
+# Ticks inside plots; more space devoted to plot.
+xtick.direction: in
+ytick.direction: in
+# Always plot ticks on top of data
+axes.axisbelow: False
diff --git a/examples/Logger/SimpleOrbits/plotSolution.py b/examples/Logger/SimpleOrbits/plotSolution.py
new file mode 100644
index 0000000000000000000000000000000000000000..b160e24964240b7a8a89fc03594aa4fb149b65bc
--- /dev/null
+++ b/examples/Logger/SimpleOrbits/plotSolution.py
@@ -0,0 +1,252 @@
+###############################################################################
+# This file is part of SWIFT.
+# Copyright (c) 2020 Loic Hausammann (loic.hausammann@epfl.ch)
+#
+# 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 .
+#
+##############################################################################
+
+from h5py import File as HDF5File
+import numpy as np
+from glob import glob
+import matplotlib.pyplot as plt
+import makeIC
+import sys
+sys.path.append("../../../logger/.libs/")
+import liblogger as logger
+
+# Plot parameters
+plt.style.use("mpl_style")
+
+center = np.array([0.5 * makeIC.boxsize]*3)
+id_focus = 0
+
+# Defines the constants
+G = 1.189972e-04 # gravitational constant
+M = 3.329460e+05 # mass of the sun
+
+# generate the figures
+fig_1 = plt.figure()
+fig_2 = plt.figure()
+fig_3 = plt.figure()
+
+
+def gravity(t, y):
+ """
+ Compute the equation of motion.
+
+ Parameters
+ ----------
+
+ t: float
+ Time of the step
+ y: np.array
+ Variable to evolve [x, y, vx, vy].
+ """
+ dy = np.zeros(4)
+ dy[:2] = y[2:]
+
+ r = np.sum((y[:2] - center[:2])**2)**0.5
+
+ a = - G * M / r**3
+ dy[2] = y[2] * a
+ dy[3] = y[3] * a
+ return dy
+
+
+def plotRelative(t, p, *args, **kwargs):
+ """
+ Wrapper around the function plot from matplotlib, but
+ plot the relative evolution of the variable.
+ """
+ p = (p - p[0]) / p[0]
+ plt.plot(t, p, *args, **kwargs)
+
+
+def doSnapshots():
+ """
+ Read the snapshots and plot the corresponding variables.
+ """
+
+ # get all the filenames
+ filenames = glob("simple_orbits_*.hdf5")
+ N = len(filenames)
+ filenames.sort()
+
+ # generate the output arrays
+ E = np.zeros((N, makeIC.num_part))
+ t = np.zeros(N)
+ p = np.zeros((N, 3))
+ v = np.zeros((N, 3))
+
+ for i, f in enumerate(filenames):
+ # get the data from the file
+ f = HDF5File(f, "r")
+ ids = f["PartType1/ParticleIDs"][:]
+ sort = np.argsort(ids)
+ ids = ids[sort]
+ pos = f["PartType1/Coordinates"][sort, :]
+ pos -= center
+ vel = f["PartType1/Velocities"][sort, :]
+
+ t[i] = f["Header"].attrs["Time"]
+
+ r = np.sum(pos**2, axis=1)**0.5
+ v2 = np.sum(vel**2, axis=1)
+ E[i, :] = 0.5 * v2 - G * M / r
+
+ # Get the pos / vel of the required particle
+ ind = ids == id_focus
+ p[i, :] = pos[ind, :]
+ v[i, :] = vel[ind, :]
+
+ # Compute the solution
+ y0 = np.zeros(4)
+ y0[:2] = p[0, :2]
+ y0[2:] = v[0, :2]
+
+ # compute the plotting variables
+ plt.figure(fig_1.number)
+ plotRelative(t, E, ".", label="Snapshot")
+
+ plt.figure(fig_2.number)
+ plt.plot(p[:, 0], p[:, 1], "-", label="Snapshot", lw=1.)
+
+ plt.figure(fig_3.number)
+ plt.plot(v[:, 0], v[:, 1], "-", label="Snapshot", lw=1.)
+
+
+def doStatistics():
+ """
+ Do the plots with the energy output file.
+ """
+ data = np.genfromtxt("energy.txt", names=True)
+
+ times = data["Time"]
+ E = data["E_tot"]
+ plt.figure(fig_1.number)
+ plotRelative(times, E, "-", label="Statistics")
+
+
+def doLogger():
+ """
+ Read the logfile and plot the corresponding variables.
+ """
+ basename = "index"
+ N = 1000
+ verbose = 0
+
+ # Get time limits
+ t_min, t_max = logger.getTimeLimits(basename, verbose)
+ times = np.linspace(t_min, t_max, N)
+
+ # Create output arrays
+ E = np.zeros((N, makeIC.num_part))
+ E_parts = np.zeros((N, makeIC.num_part))
+ p = np.zeros((N, 3))
+ v = np.zeros((N, 3))
+ t_parts = np.zeros((N, makeIC.num_part))
+ p_parts = np.zeros((N, 3))
+ v_parts = np.zeros((N, 3))
+
+ # Read the particles
+ parts = logger.loadSnapshotAtTime(
+ basename, times[0], verbose)
+
+ for i, t in enumerate(times):
+ # Get the next particles
+ interp = logger.moveForwardInTime(
+ basename, parts, t, verbose)
+ ids = interp["ids"]
+ sort = np.argsort(ids)
+ ids = ids[sort]
+ rel_pos = interp["positions"][sort, :] - center
+ vel = interp["velocities"][sort, :]
+
+ rel_pos_parts = parts["positions"][sort, :] - center
+ vel_parts = parts["velocities"][sort, :]
+
+ # Compute the interpolated variables
+ r = np.sum(rel_pos**2, axis=1)**0.5
+ v2 = np.sum(vel**2, axis=1)
+ E[i, :] = 0.5 * v2 - G * M / r
+ ind = ids == id_focus
+ p[i, :] = rel_pos[ind, :]
+ v[i, :] = vel[ind, :]
+
+ # Compute the variables of the last record
+ r = np.sum(rel_pos_parts**2, axis=1)**0.5
+ v2 = np.sum(vel_parts**2, axis=1)
+ E_parts[i, :] = 0.5 * v2 - G * M / r
+ t_parts[i, :] = parts["times"][sort]
+ ind = ids == id_focus
+ p_parts[i, :] = rel_pos_parts[ind, :]
+ v_parts[i, :] = vel_parts[ind, :]
+
+ # compute the plotting variables
+ plt.figure(fig_1.number)
+ plotRelative(t_parts, E_parts, "x", label="Logger")
+ plotRelative(times, E, "--", label="Logger (Interpolation)")
+
+ # Compute the solution
+ y0 = np.zeros(4)
+ y0[:2] = p[0, :2]
+ y0[2:] = v[0, :2]
+ t_parts, ind = np.unique(t_parts[:, 0], return_index=True)
+
+ # plot the solution
+ plt.figure(fig_2.number)
+ plt.plot(p_parts[:, 0], p_parts[:, 1], "x", label="Logger")
+
+ plt.figure(fig_3.number)
+ plt.plot(v_parts[:, 0], v_parts[:, 1], "x", label="Logger")
+
+ # Compute the solution
+ y0 = np.zeros(4)
+ y0[:2] = p[0, :2]
+ y0[2:] = v[0, :2]
+ plt.figure(fig_2.number)
+ plt.plot(p[:, 0], p[:, 1], ":r", label="Logger (Interpolation)")
+
+ plt.figure(fig_3.number)
+ plt.plot(v[:, 0], v[:, 1], ":r", label="Logger (Interpolation)")
+
+
+# do all the plots
+doStatistics()
+doSnapshots()
+doLogger()
+
+# add text
+plt.figure(fig_1.number)
+plt.xlabel("Time [yr]")
+plt.ylabel(r"$\frac{E - E(t=0)}{E(t=0)}$")
+plt.legend(ncol=2)
+plt.savefig("Energy.png")
+
+plt.figure(fig_2.number)
+plt.xlabel("Position [AU]")
+plt.ylabel("Position [AU]")
+plt.axis("equal")
+plt.legend()
+plt.savefig("Positions.pdf")
+
+plt.figure(fig_3.number)
+plt.xlabel("Velocity [AU / yr]")
+plt.ylabel("Velocity [AU / yr]")
+plt.axis("equal")
+plt.legend()
+plt.savefig("Velocities.png")
+
+plt.show()
diff --git a/examples/Logger/SimpleOrbits/run.sh b/examples/Logger/SimpleOrbits/run.sh
new file mode 100644
index 0000000000000000000000000000000000000000..e1742c357d445faf5fc92330c47300fe2dcff602
--- /dev/null
+++ b/examples/Logger/SimpleOrbits/run.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+ # Generate the initial conditions.
+echo "Generating initial conditions for the Simple Orbits example..."
+python makeIC.py
+
+# Run SWIFT
+../../swift --external-gravity --threads=1 simple_orbits.yml 2>&1 | tee output.log
+
+# Plot the solution
+python3 plotSolution.py
diff --git a/examples/Logger/SimpleOrbits/simple_orbits.yml b/examples/Logger/SimpleOrbits/simple_orbits.yml
new file mode 100644
index 0000000000000000000000000000000000000000..448f146f3cd38a97fbbf542e541a17438711e43b
--- /dev/null
+++ b/examples/Logger/SimpleOrbits/simple_orbits.yml
@@ -0,0 +1,47 @@
+# Define the system of units to use internally.
+InternalUnitSystem:
+ UnitMass_in_cgs: 5.97e27 # M_earth
+ UnitLength_in_cgs: 1.49e13 # AU
+ UnitVelocity_in_cgs: 474047. # AU / yr
+ 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: 4.2 # The end time of the simulation (in internal units).
+ dt_min: 1e-12 # The minimal time-step size of the simulation (in internal units).
+ dt_max: 1 # The maximal time-step size of the simulation (in internal units).
+
+# Parameters governing the snapshots
+Snapshots:
+ basename: simple_orbits # Common part of the name of output files
+ time_first: 0. # Time of the first output (in internal units)
+ delta_time: 5e-3 # Time difference between consecutive outputs (in internal units)
+
+# Parameters governing the conserved quantities statistics
+Statistics:
+ delta_time: 1 # Time between statistics output
+
+
+# Parameters related to the initial conditions
+InitialConditions:
+ file_name: simple_orbits.hdf5 # The file to read
+ periodic: 0
+
+
+# Point mass external potentials
+PointMassPotential:
+ useabspos: 0 # 0 -> positions based on centre, 1 -> absolute positions
+ position: [0.,0,0.] # location of external point mass (internal units)
+ mass: 332946 # mass of external point mass (solar mass in internal units)
+ timestep_mult: 1e-3 # Dimensionless pre-factor for the time-step condition
+
+
+# Parameters governing the logger snapshot system
+Logger:
+ delta_step: 4000 # Update the particle log every this many updates
+ basename: index # Common part of the filenames
+ initial_buffer_size: 0.01 # (Optional) Buffer size in GB
+ buffer_scale: 10 # (Optional) When buffer size is too small, update it with required memory times buffer_scale
+ index_mem_frac: 1e-6
diff --git a/examples/Planetary/EarthImpact/earth_impact.yml b/examples/Planetary/EarthImpact/earth_impact.yml
index b1df0a6974e060074b66b2ab9c3021aa02d65190..e98f7a072e913415feb5b0e5e27577ea693a01c6 100644
--- a/examples/Planetary/EarthImpact/earth_impact.yml
+++ b/examples/Planetary/EarthImpact/earth_impact.yml
@@ -55,4 +55,11 @@ Scheduler:
# Parameters related to the equation of state
EoS:
- planetary_use_Til: 1 # Whether to initialise the Tillotson EOS
\ No newline at end of file
+ planetary_use_Til: 1 # Whether to initialise the Tillotson EOS
+
+# Parameters governing the logger snapshot system
+Logger:
+ delta_step: 10 # Update the particle log every this many updates
+ basename: index # Common part of the filenames
+ initial_buffer_size: 0.01 # (Optional) Buffer size in GB
+ buffer_scale: 10 # (Optional) When buffer size is too small, update it with required memory times buffer_scale
diff --git a/examples/Planetary/EarthImpact/make_movie_logger.py b/examples/Planetary/EarthImpact/make_movie_logger.py
new file mode 100644
index 0000000000000000000000000000000000000000..5007543abe8e9984cf8cf86387c526e13b8c948a
--- /dev/null
+++ b/examples/Planetary/EarthImpact/make_movie_logger.py
@@ -0,0 +1,245 @@
+#!/usr/bin/env python3
+
+import numpy as np
+import matplotlib.pyplot as plt
+import sys
+import swiftsimio.visualisation.projection as vis
+from copy import deepcopy
+from shutil import copyfile
+import os
+from subprocess import call
+sys.path.append("../../../logger/.libs/")
+import liblogger as logger
+
+boxsize = 80.
+large_width = 15
+small_width = 0.4
+alpha_particle = 0
+
+width = 0
+basename = "index"
+resolution = 1080
+resolution_mini = resolution * 100 // 512
+id_foc = 99839
+v_max = 8.
+
+traj = None
+
+
+def selectParticles(pos, width):
+ ind1 = np.logical_and(pos[:, 0] > 0, pos[:, 0] < width)
+ ind2 = np.logical_and(pos[:, 1] > 0, pos[:, 1] < width)
+ ind3 = np.logical_and(ind1, ind2)
+
+ # avoid the zoom
+ size = resolution_mini * width / resolution
+ box = np.logical_and(pos[:, 0] > (width - size), pos[:, 1] < size)
+ ind3 = np.logical_and(~box, ind3)
+
+ return ind3
+
+
+def getImage(parts, width, center, res):
+ pos = parts["positions"]
+ pos = pos - center + 0.5 * width
+ pos /= width
+ h = parts["smoothing_lengths"] / width
+
+ m = np.ones(pos.shape[0])
+ # Do the projection
+ img = vis.scatter(pos[:, 0], pos[:, 1], m, h, res).T
+ img /= width**3
+ ind = img == 0
+ img[ind] = img[~ind].min()
+ img = np.log10(img)
+
+ return img
+
+
+def doProjection(parts, t, skip):
+ plt.figure(figsize=(8, 8))
+ global traj, id_foc
+
+ # evolve in time the particles
+ interp = logger.moveForwardInTime(basename, parts, t)
+
+ # Check if some particles where removed
+ ind = parts["smoothing_lengths"] == 0
+ ind = np.arange(parts.shape[0])[ind]
+ if len(ind) != 0:
+ parts = np.delete(parts, ind)
+ interp = np.delete(interp, ind)
+ id_foc -= np.sum(ind < id_foc)
+
+ # Get the position and the image center
+ pos = interp["positions"]
+ position = pos[id_foc, :]
+
+ # save the trajectory
+ if traj is None:
+ traj = position[np.newaxis, :]
+ else:
+ traj = np.append(traj, position[np.newaxis, :], axis=0)
+
+ # Do we want to generate the image?
+ if skip:
+ return parts
+
+ # Compute the images
+ img = getImage(interp, width, position, resolution)
+ img[:resolution_mini, -resolution_mini:] = getImage(
+ interp, 0.2 * boxsize, position, resolution_mini)
+ box = width * np.array([0, 1, 0, 1])
+ plt.imshow(img, origin="lower", extent=box, cmap="plasma",
+ vmin=0, vmax=v_max)
+
+ # plot the particles
+ pos = interp["positions"] - position + 0.5 * width
+ ind = selectParticles(pos, width)
+ ms = 2
+ plt.plot(pos[ind, 0], pos[ind, 1], "o", markersize=ms,
+ alpha=alpha_particle, color="silver")
+ plt.plot(pos[id_foc, 0], pos[id_foc, 1], "or", markersize=2*ms,
+ alpha=alpha_particle)
+
+ # plot time
+ plt.text(0.5 * width, 0.95 * width,
+ "Time = %0.2f Hours" % (t / (60 * 60)), color="w",
+ horizontalalignment="center")
+
+ # plot trajectory
+ tr = deepcopy(traj) - position + 0.5 * width
+ plt.plot(tr[:, 0], tr[:, 1], "-", color="w", alpha=alpha_particle)
+
+ # plot scale
+ plt.plot([0.05 * width, 0.15 * width], [0.05 * width, 0.05 * width],
+ "-w")
+ plt.text(0.1 * width, 0.06 * width, "%.2f R$_\oplus$" % (0.1 * width),
+ horizontalalignment='center', color="w")
+
+ # style
+ plt.axis("off")
+ plt.xlim(box[:2])
+ plt.ylim(box[2:])
+ plt.tight_layout()
+ plt.style.use('dark_background')
+ return parts
+
+
+def skipImage(i):
+ if os.path.isfile("output/image_%04i.png" % i):
+ print("Skipping image %i" % i)
+ return True
+ else:
+ return False
+
+
+def doMovie(parts, t0, t1, N, init):
+ times = np.linspace(t0, t1, N)
+ for i, t in enumerate(times):
+ print("Image %i / %i" % (i+1, N))
+ skip = skipImage(i + init)
+ parts = doProjection(parts, t, skip)
+ if not skip:
+ plt.savefig("output/image_%04i.png" % (i + init))
+ plt.close()
+ return init + N
+
+
+def doZoom(parts, w_init, w_end, N, init, t0, t1, increase_alpha):
+ global width, alpha_particle
+ widths = np.linspace(w_init, w_end, N)
+ alpha = np.linspace(-8, 0.2, N)
+ if not increase_alpha:
+ alpha = alpha[::-1]
+ k = 5 # parameter for the steepness
+ alpha = 1. / (1 + np.exp(- 2 * k * alpha))
+ times = np.linspace(t0, t1, N)
+ for i, w in enumerate(widths):
+ print("Image %i / %i" % (i+1, N))
+ skip = skipImage(i + init)
+ width = w
+ alpha_particle = alpha[i]
+ parts = doProjection(parts, times[i], skip)
+ if not skip:
+ plt.savefig("output/image_%04i.png" % (i + init))
+ plt.close()
+ return init + N
+
+
+def doStatic(init, number, ref):
+ # copy the first picture
+ for i in range(number):
+ copyfile("output/image_%04i.png" % ref,
+ "output/image_%04i.png" % (i + init))
+ return init + number
+
+
+def doTitle(frames):
+ plt.figure()
+ plt.axis("off")
+ box = np.array([0, 1, 0, 1])
+ plt.xlim(box[:2])
+ plt.ylim(box[2:])
+ plt.tight_layout()
+ plt.style.use('dark_background')
+
+ style = {
+ "verticalalignment": "center",
+ "horizontalalignment": "center",
+ "fontweight": "bold"
+ }
+ plt.text(0.5, 0.6, "Planetary Impact with the Particle Logger",
+ **style)
+ plt.text(0.5, 0.5, "L. Hausammann, J. Kegerreis and P. Gonnet 2020",
+ **style)
+
+ for i in range(frames):
+ plt.savefig("output/image_%04i.png" % i)
+
+ plt.close()
+ return frames
+
+
+def main():
+ t0, t1 = logger.getTimeLimits(basename)
+ parts = logger.loadSnapshotAtTime(basename, t0)
+
+ # Do a few title frames
+ init = doTitle(40)
+
+ after_title = init
+ frames_after_title = 10
+ init += frames_after_title
+
+ # do a zoom while moving forward in time
+ init = doZoom(parts, large_width, small_width, 50,
+ init=init, t0=t0, t1=0.07 * t1,
+ increase_alpha=True)
+
+ # Copy a few frames
+ doStatic(after_title, frames_after_title,
+ after_title + frames_after_title)
+ init = doStatic(init, 10, init-1)
+ # Do the main part of the movie
+ init = doMovie(parts, 0.07 * t1, 0.15 * t1, N=250,
+ init=init)
+ # copy a few frames
+ init = doStatic(init, 10, init-1)
+
+ # zoom out and finish the movie
+ init = doZoom(parts, small_width, large_width, 607,
+ init=init, t0=0.15 * t1, t1=t1,
+ increase_alpha=False)
+
+ # copy a few frames
+ init = doStatic(init, 10, init-1)
+
+
+if __name__ == "__main__":
+ main()
+
+ convert = "ffmpeg -i output/image_%04d.png -y -vcodec libx264 "
+ convert += "-profile:v high444 -refs 16 -crf 0 "
+ convert += "-preset ultrafast movie.mp4"
+ call(convert, shell=True)
diff --git a/logger/examples/reader_example.py b/logger/examples/reader_example.py
index b15446a6cd3d5b159038b56910eede5f571a6d07..62cca38a94014253e70c6e2f3562e4db99b8cdae 100644
--- a/logger/examples/reader_example.py
+++ b/logger/examples/reader_example.py
@@ -50,7 +50,7 @@ print("time: %g" % time)
# read dump
t = logger.getTimeLimits(basename)
-data = logger.loadSnapshotAtTime(basename, time)
+data = logger.loadSnapshotAtTime(basename, time, 2)
print("The data contains the following elements:")
print(data.dtype.names)
diff --git a/logger/logger_header.c b/logger/logger_header.c
index 2e2ffe07647dedfc204a0f2f31be1a08de1b4627..ea94f3907e94c9dade5449827daa4cead1ed8b0f 100644
--- a/logger/logger_header.c
+++ b/logger/logger_header.c
@@ -184,7 +184,6 @@ void header_read(struct header *h, struct logger_logfile *log) {
/* Check the logfile header's size. */
if (map != (char *)log->log.map + h->offset_first_record) {
- header_print(h);
size_t offset = (char *)map - (char *)log->log.map;
error("Wrong header size (in header %zi, current %zi).",
h->offset_first_record, offset);
diff --git a/logger/logger_index.c b/logger/logger_index.c
index 98368a89b93c707a9e1004bf400e0b898b53b261..80a0858d99dd394964180ad46abcf648076920bb 100644
--- a/logger/logger_index.c
+++ b/logger/logger_index.c
@@ -60,7 +60,9 @@ void logger_index_read_header(struct logger_index *index,
const char *filename) {
/* Open the file */
- message("Reading %s", filename);
+ if (index->reader->verbose > 1) {
+ message("Reading %s", filename);
+ }
logger_index_map_file(index, filename, 0);
/* Read times */
@@ -135,7 +137,7 @@ void logger_index_map_file(struct logger_index *index, const char *filename,
/* Check if need to sort the file */
if (sorted && !index->is_sorted) {
- if (index->reader->verbose > 0) {
+ if (index->reader->verbose > 1) {
message("Sorting the index file.");
}
/* Map the index file */
@@ -155,7 +157,7 @@ void logger_index_map_file(struct logger_index *index, const char *filename,
/* Free the index file before opening it again in read only */
logger_index_free(index);
- if (index->reader->verbose > 0) {
+ if (index->reader->verbose > 1) {
message("Sorting done.");
}
}
diff --git a/logger/logger_particle.c b/logger/logger_particle.c
index 480b0f7f50502a6c9e3f73dfd0ddfd70ee164b07..07cddd89b4e39fef863dcbfd387cfda440c1ae62 100644
--- a/logger/logger_particle.c
+++ b/logger/logger_particle.c
@@ -211,6 +211,104 @@ size_t logger_particle_read(struct logger_particle *part,
return offset;
}
+/**
+ * @brief Compute the quintic hermite spline interpolation.
+ *
+ * @param t0 The time at the left of the interval.
+ * @param x0 The function at the left of the interval.
+ * @param v0 The first derivative at the left of the interval.
+ * @param a0 The second derivative at the left of the interval.
+ * @param t1 The time at the right of the interval.
+ * @param x1 The function at the right of the interval.
+ * @param v1 The first derivative at the right of the interval.
+ * @param a1 The second derivative at the right of the interval.
+ * @param t The time of the interpolation.
+ *
+ * @return The function evaluated at t.
+ */
+double logger_particle_quintic_hermite_spline(double t0, double x0, float v0,
+ float a0, double t1, double x1,
+ float v1, float a1, double t) {
+
+ /* Generates recurring variables */
+ /* Time differences */
+ const double dt = t1 - t0;
+ const double dt2 = dt * dt;
+ const double dt3 = dt2 * dt;
+ const double dt4 = dt3 * dt;
+ const double dt5 = dt4 * dt;
+
+ const double t_t0 = t - t0;
+ const double t_t0_2 = t_t0 * t_t0;
+ const double t_t0_3 = t_t0_2 * t_t0;
+ const double t_t1 = t - t1;
+ const double t_t1_2 = t_t1 * t_t1;
+
+ /* Derivatives */
+ const double v0_dt = v0 * dt;
+ const double a0_dt2 = 0.5 * a0 * dt2;
+ const double v1_dt = v1 * dt;
+ const double a1_dt2 = 0.5 * a1 * dt2;
+
+ /* Do the first 3 terms of the hermite spline */
+ double x = x0 + v0 * t_t0 + 0.5 * a0 * t_t0_2;
+
+ /* Cubic term */
+ x += (x1 - x0 - v0_dt - a0_dt2) * t_t0_3 / dt3;
+
+ /* Quartic term */
+ x += (3. * x0 - 3. * x1 + v1_dt + 2. * v0_dt + a0_dt2) * t_t0_3 * t_t1 / dt4;
+
+ /* Quintic term */
+ x += (6. * x1 - 6. * x0 - 3. * v0_dt - 3. * v1_dt + a1_dt2 - a0_dt2) *
+ t_t0_3 * t_t1_2 / dt5;
+
+ return x;
+}
+
+/**
+ * @brief Compute the cubic hermite spline interpolation.
+ *
+ * @param t0 The time at the left of the interval.
+ * @param v0 The first derivative at the left of the interval.
+ * @param a0 The second derivative at the left of the interval.
+ * @param t1 The time at the right of the interval.
+ * @param v1 The first derivative at the right of the interval.
+ * @param a1 The second derivative at the right of the interval.
+ * @param t The time of the interpolation.
+ *
+ * @return The function evaluated at t.
+ */
+float logger_particle_cubic_hermite_spline(double t0, float v0, float a0,
+ double t1, float v1, float a1,
+ double t) {
+
+ /* Generates recurring variables */
+ /* Time differences */
+ const float dt = t1 - t0;
+ const float dt2 = dt * dt;
+ const float dt3 = dt2 * dt;
+
+ const float t_t0 = t - t0;
+ const float t_t0_2 = t_t0 * t_t0;
+ const float t_t1 = t - t1;
+
+ /* Derivatives */
+ const float a0_dt = a0 * dt;
+ const float a1_dt = a1 * dt;
+
+ /* Do the first 2 terms of the hermite spline */
+ float x = v0 + a0 * t_t0;
+
+ /* Square term */
+ x += (v1 - v0 - a0_dt) * t_t0_2 / dt2;
+
+ /* Cubic term */
+ x += (2. * v0 - 2. * v1 + a1_dt + a0_dt) * t_t0_2 * t_t1 / dt3;
+
+ return x;
+}
+
/**
* @brief interpolate two particles at a given time
*
@@ -245,17 +343,22 @@ void logger_particle_interpolate(struct logger_particle *part_curr,
scaling = (time - part_curr->time) / scaling;
- double tmp;
float ftmp;
/* interpolate vectors. */
- for (size_t i = 0; i < DIM; i++) {
- tmp = (part_next->pos[i] - part_curr->pos[i]);
- part_curr->pos[i] += tmp * scaling;
-
- ftmp = (part_next->vel[i] - part_curr->vel[i]);
- part_curr->vel[i] += ftmp * scaling;
-
+ for (int i = 0; i < 3; i++) {
+ /* Positions */
+ part_curr->pos[i] = logger_particle_quintic_hermite_spline(
+ part_curr->time, part_curr->pos[i], part_curr->vel[i],
+ part_curr->acc[i], part_next->time, part_next->pos[i],
+ part_next->vel[i], part_next->acc[i], time);
+
+ /* Velocities */
+ part_curr->vel[i] = logger_particle_cubic_hermite_spline(
+ part_curr->time, part_curr->vel[i], part_curr->acc[i], part_next->time,
+ part_next->vel[i], part_next->acc[i], time);
+
+ /* Accelerations */
ftmp = (part_next->acc[i] - part_curr->acc[i]);
part_curr->acc[i] += ftmp * scaling;
}
@@ -264,6 +367,12 @@ void logger_particle_interpolate(struct logger_particle *part_curr,
ftmp = (part_next->entropy - part_curr->entropy);
part_curr->entropy += ftmp * scaling;
+ ftmp = (part_next->h - part_curr->h);
+ part_curr->h += ftmp * scaling;
+
+ ftmp = (part_next->density - part_curr->density);
+ part_curr->density += ftmp * scaling;
+
/* set time. */
part_curr->time = time;
}
diff --git a/logger/logger_python_wrapper.c b/logger/logger_python_wrapper.c
index ee8042df8e6dc9fb4914c51fbf729149e4e3a6cf..48973daf969b605b1deed4c55739ad008dbe6df6 100644
--- a/logger/logger_python_wrapper.c
+++ b/logger/logger_python_wrapper.c
@@ -57,7 +57,7 @@ static PyObject *loadSnapshotAtTime(__attribute__((unused)) PyObject *self,
char *basename = NULL;
double time = 0;
- int verbose = 2;
+ int verbose = 1;
/* parse arguments. */
if (!PyArg_ParseTuple(args, "sd|i", &basename, &time, &verbose)) return NULL;
@@ -82,17 +82,14 @@ static PyObject *loadSnapshotAtTime(__attribute__((unused)) PyObject *self,
n_tot += n_parts[i];
}
-#ifdef SWIFT_DEBUG_CHECKS
- message("Found %lu particles", n_tot);
-#endif // SWIFT_DEBUG_CHECKS
+ if (verbose > 0) {
+ message("Found %lu particles", n_tot);
+ }
/* Allocate the output memory */
PyArrayObject *out = (PyArrayObject *)PyArray_SimpleNewFromDescr(
1, &n_tot, logger_particle_descr);
- /* Reference is stolen, therefore need to take it into account */
- Py_INCREF(logger_particle_descr);
-
void *data = PyArray_DATA(out);
/* Allows to use threads */
Py_BEGIN_ALLOW_THREADS;
@@ -125,7 +122,7 @@ static PyObject *getTimeLimits(__attribute__((unused)) PyObject *self,
/* declare variables. */
char *basename = NULL;
- int verbose = 2;
+ int verbose = 1;
/* parse arguments. */
if (!PyArg_ParseTuple(args, "s|i", &basename, &verbose)) return NULL;
@@ -177,6 +174,97 @@ static PyObject *pyReverseOffset(__attribute__((unused)) PyObject *self,
return Py_BuildValue("");
}
+/**
+ * @brief Move forward in time an array of particles.
+ *
+ * filename string filename of the log file.
+ * parts Numpy array containing the particles to evolve.
+ * time Time requested for the particles.
+ * verbose Verbose level
+ *
+ * returns The evolved array of particles.
+ */
+static PyObject *pyMoveForwardInTime(__attribute__((unused)) PyObject *self,
+ PyObject *args) {
+ /* input variables. */
+ char *filename = NULL;
+
+ int verbose = 0;
+ PyArrayObject *parts = NULL;
+ double time = 0;
+ int new_array = 1;
+
+ /* parse the arguments. */
+ if (!PyArg_ParseTuple(args, "sOd|ii", &filename, &parts, &time, &verbose,
+ &new_array))
+ return NULL;
+
+ /* Check parts */
+ if (!PyArray_Check(parts)) {
+ error("Expecting a numpy array of particles.");
+ }
+
+ if (PyArray_NDIM(parts) != 1) {
+ error("Expecting a 1D array of particles.");
+ }
+
+ if (PyArray_TYPE(parts) != logger_particle_descr->type_num) {
+ error("Expecting an array of particles.");
+ }
+
+ /* Create the interpolated array. */
+ PyArrayObject *interp = NULL;
+ if (new_array) {
+ interp =
+ (PyArrayObject *)PyArray_NewLikeArray(parts, NPY_ANYORDER, NULL, 0);
+
+ /* Check if the allocation was fine */
+ if (interp == NULL) {
+ return NULL;
+ }
+
+ /* Reference stolen in PyArray_NewLikeArray => incref */
+ Py_INCREF(PyArray_DESCR(parts));
+ } else {
+ interp = parts;
+ // We return it, therefore one more reference exists.
+ Py_INCREF(interp);
+ }
+
+ /* initialize the reader. */
+ struct logger_reader reader;
+ logger_reader_init(&reader, filename, verbose);
+
+ /* Get the offset of the requested time. */
+ size_t offset = logger_reader_get_next_offset_from_time(&reader, time);
+
+ /* Loop over all the particles */
+ size_t N = PyArray_DIM(parts, 0);
+ for (size_t i = 0; i < N; i++) {
+
+ /* Obtain the required particle records. */
+ struct logger_particle *p = PyArray_GETPTR1(parts, i);
+
+ /* Check that we are really going forward in time. */
+ if (time < p->time) {
+ error("Requesting to go backward in time (%g < %g)", time, p->time);
+ }
+ struct logger_particle new;
+ logger_reader_get_next_particle(&reader, p, &new, offset);
+
+ /* Interpolate the particle. */
+ struct logger_particle *p_ret = PyArray_GETPTR1(interp, i);
+ *p_ret = *p;
+
+ logger_particle_interpolate(p_ret, &new, time);
+ }
+
+ /* Free the reader. */
+ logger_reader_free(&reader);
+
+ return PyArray_Return(interp);
+}
+
/* definition of the method table. */
static PyMethodDef libloggerMethods[] = {
@@ -214,6 +302,24 @@ static PyMethodDef libloggerMethods[] = {
"-------\n\n"
"times: tuple\n"
" time min, time max\n"},
+ {"moveForwardInTime", pyMoveForwardInTime, METH_VARARGS,
+ "Move the particles forward in time.\n\n"
+ "Parameters\n"
+ "----------\n\n"
+ "basename: str\n"
+ " The basename of the index files.\n\n"
+ "parts: np.array\n"
+ " The array of particles.\n\n"
+ "time: double\n"
+ " The requested time for the particles.\n\n"
+ "verbose: int, optional\n"
+ " The verbose level of the loader.\n\n"
+ "new_array: bool, optional\n"
+ " Does the function return a new array (or use the provided one)?\n\n"
+ "Returns\n"
+ "-------\n\n"
+ "parts: np.array\n"
+ " The particles at the requested time.\n"},
{NULL, NULL, 0, NULL} /* Sentinel */
};
diff --git a/logger/logger_reader.c b/logger/logger_reader.c
index 854cf084e1d6c9c6fa5b066ce83b7cec5ad820ad..ce7799c656a9a3a3072b144b9b4ba242db6a4c87 100644
--- a/logger/logger_reader.c
+++ b/logger/logger_reader.c
@@ -378,6 +378,10 @@ double logger_reader_get_time_end(struct logger_reader *reader) {
size_t logger_reader_get_next_offset_from_time(struct logger_reader *reader,
double time) {
size_t ind = time_array_get_index_from_time(&reader->log.times, time);
+ /* We do not want to have the sentiel */
+ if (reader->log.times.size - 2 == ind) {
+ ind -= 1;
+ }
return reader->log.times.records[ind + 1].offset;
}
@@ -427,7 +431,11 @@ void logger_reader_get_next_particle(struct logger_reader *reader,
/* Are we at the end of the file? */
if (next_offset == 0) {
time_array_print(&reader->log.times);
- error("End of file for offset %zi", prev_offset);
+ error(
+ "End of file for particle %lli offset %zi when requesting time %g "
+ "with offset %zi",
+ prev->id, prev_offset,
+ time_array_get_time(&reader->log.times, time_offset), time_offset);
}
next_offset += prev_offset;
diff --git a/src/hydro/Planetary/hydro_part.h b/src/hydro/Planetary/hydro_part.h
index 4c0c4b786c7010a1d8080dbc934d661ca61c6c75..ca0a5b908b3ae140b154c41ce328c0f970a3a6a5 100644
--- a/src/hydro/Planetary/hydro_part.h
+++ b/src/hydro/Planetary/hydro_part.h
@@ -38,6 +38,7 @@
#include "cooling_struct.h"
#include "equation_of_state.h" // For enum material_id
#include "feedback_struct.h"
+#include "logger.h"
#include "star_formation_struct.h"
#include "timestep_limiter_struct.h"
#include "tracers_struct.h"
@@ -78,6 +79,10 @@ struct xpart {
/* Additional data used by the feedback */
struct feedback_part_data feedback_data;
+#ifdef WITH_LOGGER
+ /* Additional data for the particle logger */
+ struct logger_part_data logger_data;
+#endif
} SWIFT_STRUCT_ALIGN;
/**
diff --git a/src/logger.c b/src/logger.c
index 38056dd680a3b6feeb7291196897722aad760f34..c970385e27b561dc466c89bcf41a4845843f69bb 100644
--- a/src/logger.c
+++ b/src/logger.c
@@ -249,6 +249,7 @@ void logger_copy_part_fields(const struct part *p, unsigned int mask,
buff += logger_mask_data[logger_u].size;
mask &= ~logger_mask_data[logger_u].mask;
}
+#endif
/* Particle smoothing length as a single float. */
if (mask & logger_mask_data[logger_h].mask) {
@@ -275,8 +276,6 @@ void logger_copy_part_fields(const struct part *p, unsigned int mask,
mask &= ~logger_mask_data[logger_consts].mask;
}
-#endif
-
/* Special flags */
if (mask & logger_mask_data[logger_special_flags].mask) {
memcpy(buff, &special_flags, logger_mask_data[logger_special_flags].size);