diff --git a/.gitignore b/.gitignore index 3c6ecf6ece8e10520e37bd5f305a533e28976e33..2a16a4a681b86d30940001fb2ce86dfb9d27a558 100644 --- a/.gitignore +++ b/.gitignore @@ -73,6 +73,9 @@ tests/testRiemannExact tests/testRiemannTRRS tests/testRiemannHLLC tests/testMatrixInversion +tests/testVoronoi1D +tests/testVoronoi2D +tests/testVoronoi3D tests/testDump tests/testLogger tests/benchmarkInteractions diff --git a/configure.ac b/configure.ac index 8409327116f6f1af519f3b065cd0e0291f4bdfb0..6c0a06005778c499c53ae8e596a0685a78886542 100644 --- a/configure.ac +++ b/configure.ac @@ -587,7 +587,7 @@ fi # Hydro scheme. AC_ARG_WITH([hydro], [AS_HELP_STRING([--with-hydro=<scheme>], - [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo default: gadget2@:>@] + [Hydro dynamics to use @<:@gadget2, minimal, hopkins, default, gizmo, shadowfax default: gadget2@:>@] )], [with_hydro="$withval"], [with_hydro="gadget2"] @@ -608,6 +608,9 @@ case "$with_hydro" in gizmo) AC_DEFINE([GIZMO_SPH], [1], [GIZMO SPH]) ;; + shadowfax) + AC_DEFINE([SHADOWFAX_SPH], [1], [Shadowfax SPH]) + ;; *) AC_MSG_ERROR([Unknown hydrodynamics scheme: $with_hydro]) @@ -796,6 +799,15 @@ case "$with_potential" in ;; esac +# Gravity multipole order +AC_ARG_WITH([multipole-order], + [AS_HELP_STRING([--with-multipole-order=<order>], + [order of the multipole and gravitational field expansion @<:@ default: 3@:>@] + )], + [with_multipole_order="$withval"], + [with_multipole_order="3"] +) +AC_DEFINE_UNQUOTED([SELF_GRAVITY_MULTIPOLE_ORDER], [$with_multipole_order], [Multipole order]) # Check for git, needed for revision stamps. @@ -843,6 +855,7 @@ AC_MSG_RESULT([ Riemann solver : $with_riemann Cooling function : $with_cooling External potential : $with_potential + Multipole order : $with_multipole_order Task debugging : $enable_task_debugging Debugging checks : $enable_debugging_checks diff --git a/examples/EAGLE_12/eagle_12.yml b/examples/EAGLE_12/eagle_12.yml index bb5f97f029e1d50d81bbdccae9ac620e9e0e6f08..67ce7c530263f7905a0d5147832dfc39148d753d 100644 --- a/examples/EAGLE_12/eagle_12.yml +++ b/examples/EAGLE_12/eagle_12.yml @@ -13,6 +13,9 @@ TimeIntegration: dt_min: 1e-10 # The minimal time-step size of the simulation (in internal units). dt_max: 1e-4 # The maximal time-step size of the simulation (in internal units). +Scheduler: + cell_split_size: 50 + # Parameters governing the snapshots Snapshots: basename: eagle # Common part of the name of output files @@ -23,6 +26,13 @@ Snapshots: Statistics: delta_time: 1e-2 # Time between statistics output +# Parameters for the self-gravity scheme +Gravity: + eta: 0.025 # Constant dimensionless multiplier for time integration. + epsilon: 0.0001 # Softening length (in internal units). + a_smooth: 1000. + r_cut: 4. + # 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). diff --git a/examples/EAGLE_25/eagle_25.yml b/examples/EAGLE_25/eagle_25.yml index 12a413b7e2c45443601c0b9753383b90942298b0..c755768bcfafebf3efe6307080e9e85d3a0a4bf5 100644 --- a/examples/EAGLE_25/eagle_25.yml +++ b/examples/EAGLE_25/eagle_25.yml @@ -23,6 +23,13 @@ Snapshots: Statistics: delta_time: 1e-2 # Time between statistics output +# Parameters for the self-gravity scheme +Gravity: + eta: 0.025 # Constant dimensionless multiplier for time integration. + epsilon: 0.0001 # Softening length (in internal units). + a_smooth: 1000. + r_cut: 4. + # 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). diff --git a/examples/UniformDMBox/plot_gravity_checks.py b/examples/UniformDMBox/plot_gravity_checks.py new file mode 100644 index 0000000000000000000000000000000000000000..5efd5847ca9749fffaee48e586c0a1976fbac9d5 --- /dev/null +++ b/examples/UniformDMBox/plot_gravity_checks.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python + +import sys +import glob +import re +import numpy as np +import matplotlib.pyplot as plt + +params = {'axes.labelsize': 14, +'axes.titlesize': 18, +'font.size': 12, +'legend.fontsize': 12, +'xtick.labelsize': 14, +'ytick.labelsize': 14, +'text.usetex': True, +'figure.figsize': (10, 10), +'figure.subplot.left' : 0.06, +'figure.subplot.right' : 0.99 , +'figure.subplot.bottom' : 0.06 , +'figure.subplot.top' : 0.985 , +'figure.subplot.wspace' : 0.14 , +'figure.subplot.hspace' : 0.14 , +'lines.markersize' : 6, +'lines.linewidth' : 3., +'text.latex.unicode': True +} +plt.rcParams.update(params) +plt.rc('font',**{'family':'sans-serif','sans-serif':['Times']}) + +min_error = 1e-6 +max_error = 1e-1 +num_bins = 51 + +# Construct the bins +bin_edges = np.linspace(np.log10(min_error), np.log10(max_error), num_bins + 1) +bin_size = (np.log10(max_error) - np.log10(min_error)) / num_bins +bins = 0.5*(bin_edges[1:] + bin_edges[:-1]) +bin_edges = 10**bin_edges +bins = 10**bins + +# Colours +cols = ['b', 'g', 'r', 'm'] + +# Time-step to plot +step = int(sys.argv[1]) + +# Find the files for the different expansion orders +order_list = glob.glob("gravity_checks_step%d_order*.dat"%step) +num_order = len(order_list) + +# Get the multipole orders +order = np.zeros(num_order) +for i in range(num_order): + order[i] = int(order_list[i][26]) + +# Start the plot +plt.figure() + +# Get the Gadget-2 data if existing +gadget2_file_list = glob.glob("forcetest_gadget2.txt") +if len(gadget2_file_list) != 0: + + gadget2_data = np.loadtxt(gadget2_file_list[0]) + gadget2_ids = gadget2_data[:,0] + gadget2_pos = gadget2_data[:,1:4] + gadget2_a_exact = gadget2_data[:,4:7] + gadget2_a_grav = gadget2_data[:, 7:10] + + # Sort stuff + sort_index = np.argsort(gadget2_ids) + gadget2_ids = gadget2_ids[sort_index] + gadget2_pos = gadget2_pos[sort_index, :] + gadget2_a_exact = gadget2_a_exact[sort_index, :] + gadget2_a_grav = gadget2_a_grav[sort_index, :] + + # Compute the error norm + diff = gadget2_a_exact - gadget2_a_grav + + norm_diff = np.sqrt(diff[:,0]**2 + diff[:,1]**2 + diff[:,2]**2) + norm_a = np.sqrt(gadget2_a_exact[:,0]**2 + gadget2_a_exact[:,1]**2 + gadget2_a_exact[:,2]**2) + + norm_error = norm_diff / norm_a + error_x = abs(diff[:,0]) / norm_a + error_y = abs(diff[:,1]) / norm_a + error_z = abs(diff[:,2]) / norm_a + + # Bin the error + norm_error_hist,_ = np.histogram(norm_error, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_x_hist,_ = np.histogram(error_x, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_y_hist,_ = np.histogram(error_y, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_z_hist,_ = np.histogram(error_z, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + + norm_median = np.median(norm_error) + median_x = np.median(error_x) + median_y = np.median(error_y) + median_z = np.median(error_z) + + norm_per95 = np.percentile(norm_error,95) + per95_x = np.percentile(error_x,95) + per95_y = np.percentile(error_y,95) + per95_z = np.percentile(error_z,95) + + plt.subplot(221) + plt.semilogx(bins, norm_error_hist, 'k--', label="Gadget-2") + plt.plot([norm_median, norm_median], [2.7, 3], 'k-', lw=1) + plt.plot([norm_per95, norm_per95], [2.7, 3], 'k:', lw=1) + plt.subplot(222) + plt.semilogx(bins, error_x_hist, 'k--', label="Gadget-2") + plt.plot([median_x, median_x], [1.8, 2], 'k-', lw=1) + plt.plot([per95_x, per95_x], [1.8, 2], 'k:', lw=1) + plt.subplot(223) + plt.semilogx(bins, error_y_hist, 'k--', label="Gadget-2") + plt.plot([median_y, median_y], [1.8, 2], 'k-', lw=1) + plt.plot([per95_y, per95_y], [1.8, 2], 'k:', lw=1) + plt.subplot(224) + plt.semilogx(bins, error_z_hist, 'k--', label="Gadget-2") + plt.plot([median_z, median_z], [1.8, 2], 'k-', lw=1) + plt.plot([per95_z, per95_z], [1.8, 2], 'k:', lw=1) + + +# Plot the different histograms +for i in range(num_order-1, -1, -1): + data = np.loadtxt(order_list[i]) + ids = data[:,0] + pos = data[:,1:4] + a_exact = data[:,4:7] + a_grav = data[:, 7:10] + + # Sort stuff + sort_index = np.argsort(ids) + ids = ids[sort_index] + pos = pos[sort_index, :] + a_exact = a_exact[sort_index, :] + a_grav = a_grav[sort_index, :] + + # Cross-checks + if not np.array_equal(ids, gadget2_ids): + print "Comparing different IDs !" + + if not np.array_equal(pos, gadget2_pos): + print "Comparing different positions ! max difference:", np.max(pos - gadget2_pos) + + if not np.array_equal(a_exact, gadget2_a_exact): + print "Comparing different exact accelerations ! max difference:", np.max(a_exact - gadget2_a_exact) + + + # Compute the error norm + diff = a_exact - a_grav + + norm_diff = np.sqrt(diff[:,0]**2 + diff[:,1]**2 + diff[:,2]**2) + norm_a = np.sqrt(a_exact[:,0]**2 + a_exact[:,1]**2 + a_exact[:,2]**2) + + norm_error = norm_diff / norm_a + error_x = abs(diff[:,0]) / norm_a + error_y = abs(diff[:,1]) / norm_a + error_z = abs(diff[:,2]) / norm_a + + # Bin the error + norm_error_hist,_ = np.histogram(norm_error, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_x_hist,_ = np.histogram(error_x, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_y_hist,_ = np.histogram(error_y, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + error_z_hist,_ = np.histogram(error_z, bins=bin_edges, density=False) / (np.size(norm_error) * bin_size) + + norm_median = np.median(norm_error) + median_x = np.median(error_x) + median_y = np.median(error_y) + median_z = np.median(error_z) + + norm_per95 = np.percentile(norm_error,95) + per95_x = np.percentile(error_x,95) + per95_y = np.percentile(error_y,95) + per95_z = np.percentile(error_z,95) + + plt.subplot(221) + plt.semilogx(bins, norm_error_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.plot([norm_median, norm_median], [2.7, 3],'-', color=cols[i], lw=1) + plt.plot([norm_per95, norm_per95], [2.7, 3],':', color=cols[i], lw=1) + plt.subplot(222) + plt.semilogx(bins, error_x_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.plot([median_x, median_x], [1.8, 2],'-', color=cols[i], lw=1) + plt.plot([per95_x, per95_x], [1.8, 2],':', color=cols[i], lw=1) + plt.subplot(223) + plt.semilogx(bins, error_y_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.plot([median_y, median_y], [1.8, 2],'-', color=cols[i], lw=1) + plt.plot([per95_y, per95_y], [1.8, 2],':', color=cols[i], lw=1) + plt.subplot(224) + plt.semilogx(bins, error_z_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.plot([median_z, median_z], [1.8, 2],'-', color=cols[i], lw=1) + plt.plot([per95_z, per95_z], [1.8, 2],':', color=cols[i], lw=1) + + +plt.subplot(221) +plt.xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$") +plt.ylabel("Density") +plt.xlim(min_error, 2*max_error) +plt.ylim(0,3) +plt.legend(loc="upper left") +plt.subplot(222) +plt.xlabel("$\delta a_x/|\overrightarrow{a}_{exact}|$") +plt.ylabel("Density") +plt.xlim(min_error, 2*max_error) +plt.ylim(0,2) +#plt.legend(loc="center left") +plt.subplot(223) +plt.xlabel("$\delta a_y/|\overrightarrow{a}_{exact}|$") +plt.ylabel("Density") +plt.xlim(min_error, 2*max_error) +plt.ylim(0,2) +#plt.legend(loc="center left") +plt.subplot(224) +plt.xlabel("$\delta a_z/|\overrightarrow{a}_{exact}|$") +plt.ylabel("Density") +plt.xlim(min_error, 2*max_error) +plt.ylim(0,2) +#plt.legend(loc="center left") + + + +plt.savefig("gravity_checks_step%d.png"%step) diff --git a/examples/main.c b/examples/main.c index 034b800887928c049a610c27ef7c916573c71be6..2b741f75b236de9a3d64d382c442d4f53713e1b2 100644 --- a/examples/main.c +++ b/examples/main.c @@ -323,14 +323,14 @@ int main(int argc, char *argv[]) { /* How large are the parts? */ if (myrank == 0) { - message("sizeof(part) is %4zi bytes.", sizeof(struct part)); - message("sizeof(xpart) is %4zi bytes.", sizeof(struct xpart)); - message("sizeof(spart) is %4zi bytes.", sizeof(struct spart)); - message("sizeof(gpart) is %4zi bytes.", sizeof(struct gpart)); - message("sizeof(multipole) is %4zi bytes.", sizeof(struct multipole)); - message("sizeof(acc_tensor) is %4zi bytes.", sizeof(struct acc_tensor)); - message("sizeof(task) is %4zi bytes.", sizeof(struct task)); - message("sizeof(cell) is %4zi bytes.", sizeof(struct cell)); + message("sizeof(part) is %4zi bytes.", sizeof(struct part)); + message("sizeof(xpart) is %4zi bytes.", sizeof(struct xpart)); + message("sizeof(spart) is %4zi bytes.", sizeof(struct spart)); + message("sizeof(gpart) is %4zi bytes.", sizeof(struct gpart)); + message("sizeof(multipole) is %4zi bytes.", sizeof(struct multipole)); + message("sizeof(grav_tensor) is %4zi bytes.", sizeof(struct grav_tensor)); + message("sizeof(task) is %4zi bytes.", sizeof(struct task)); + message("sizeof(cell) is %4zi bytes.", sizeof(struct cell)); } /* Read the parameter file */ diff --git a/examples/plot_tasks.py b/examples/plot_tasks.py index 978448b3cd049c6ff31a92c7255851390ccc700c..1be59d1c8449970321b8ef9053ddf24b4559dabd 100755 --- a/examples/plot_tasks.py +++ b/examples/plot_tasks.py @@ -57,14 +57,15 @@ pl.rcParams.update(PLOT_PARAMS) # Tasks and subtypes. Indexed as in tasks.h. TASKTYPES = ["none", "sort", "self", "pair", "sub_self", "sub_pair", "init", "ghost", "extra_ghost", "drift", "kick1", "kick2", - "timestep", "send", "recv", "grav_gather_m", "grav_fft", - "grav_mm", "grav_up", "cooling", "sourceterms", "count"] + "timestep", "send", "recv", "grav_top_level", "grav_long_range", + "grav_mm", "grav_down", "cooling", "sourceterms", "count"] SUBTYPES = ["none", "density", "gradient", "force", "grav", "external_grav", - "tend", "xv", "rho", "gpart", "count"] + "tend", "xv", "rho", "gpart", "multipole", "spart", "count"] # Task/subtypes of interest. -FULLTYPES = ["self/force", "self/density", "sub_self/force", - "sub_self/density", "pair/force", "pair/density", "sub_pair/force", +FULLTYPES = ["self/force", "self/density", "self/grav", "sub_self/force", + "sub_self/density", "pair/force", "pair/density", "pair/grav", + "sub_pair/force", "sub_pair/density", "recv/xv", "send/xv", "recv/rho", "send/rho", "recv/tend", "send/tend"] diff --git a/src/Makefile.am b/src/Makefile.am index 2c049dc3a2215a0ebd5a96f2a293972ae3e49970..14e435f663f01d8faa5f12720398b58633300093 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -45,7 +45,8 @@ include_HEADERS = space.h runner.h queue.h task.h lock.h cell.h part.h const.h \ physical_constants.h physical_constants_cgs.h potential.h version.h \ hydro_properties.h riemann.h threadpool.h cooling.h cooling_struct.h sourceterms.h \ sourceterms_struct.h statistics.h memswap.h cache.h runner_doiact_vec.h profiler.h \ - dump.h logger.h active.h timeline.h sort.h xmf.h gravity_properties.h gravity_derivatives.h + dump.h logger.h active.h timeline.h xmf.h gravity_properties.h gravity_derivatives.h \ + vector_power.h hydro_space.h sort_part.h # Common source files AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \ @@ -55,7 +56,8 @@ AM_SOURCES = space.c runner.c queue.c task.c cell.c engine.c \ physical_constants.c potential.c hydro_properties.c \ runner_doiact_fft.c threadpool.c cooling.c sourceterms.c \ statistics.c runner_doiact_vec.c profiler.c dump.c logger.c \ - part_type.c xmf.c gravity_properties.c gravity.c + part_type.c xmf.c gravity_properties.c gravity.c \ + hydro_space.c # Include files for distribution, not installation. nobase_noinst_HEADERS = align.h approx_math.h atomic.h cycle.h error.h inline.h kernel_hydro.h kernel_gravity.h \ @@ -75,8 +77,31 @@ nobase_noinst_HEADERS = align.h approx_math.h atomic.h cycle.h error.h inline.h hydro/Gadget2/hydro_debug.h hydro/Gadget2/hydro_part.h \ hydro/PressureEntropy/hydro.h hydro/PressureEntropy/hydro_iact.h hydro/PressureEntropy/hydro_io.h \ hydro/PressureEntropy/hydro_debug.h hydro/PressureEntropy/hydro_part.h \ - hydro/Gizmo/hydro.h hydro/Gizmo/hydro_iact.h hydro/Gizmo/hydro_io.h \ - hydro/Gizmo/hydro_debug.h hydro/Gizmo/hydro_part.h \ + hydro/Gizmo/hydro.h hydro/Gizmo/hydro_iact.h \ + hydro/Gizmo/hydro_io.h hydro/Gizmo/hydro_debug.h \ + hydro/Gizmo/hydro_part.h \ + hydro/Gizmo/hydro_gradients_gizmo.h \ + hydro/Gizmo/hydro_gradients.h \ + hydro/Gizmo/hydro_gradients_sph.h \ + hydro/Gizmo/hydro_slope_limiters_cell.h \ + hydro/Gizmo/hydro_slope_limiters_face.h \ + hydro/Gizmo/hydro_slope_limiters.h \ + hydro/Shadowswift/hydro_debug.h \ + hydro/Shadowswift/hydro_gradients.h hydro/Shadowswift/hydro.h \ + hydro/Shadowswift/hydro_iact.h \ + hydro/Shadowswift/hydro_io.h \ + hydro/Shadowswift/hydro_part.h \ + hydro/Shadowswift/hydro_slope_limiters_cell.h \ + hydro/Shadowswift/hydro_slope_limiters_face.h \ + hydro/Shadowswift/hydro_slope_limiters.h \ + hydro/Shadowswift/voronoi1d_algorithm.h \ + hydro/Shadowswift/voronoi1d_cell.h \ + hydro/Shadowswift/voronoi2d_algorithm.h \ + hydro/Shadowswift/voronoi2d_cell.h \ + hydro/Shadowswift/voronoi3d_algorithm.h \ + hydro/Shadowswift/voronoi3d_cell.h \ + hydro/Shadowswift/voronoi_algorithm.h \ + hydro/Shadowswift/voronoi_cell.h \ riemann/riemann_hllc.h riemann/riemann_trrs.h \ riemann/riemann_exact.h riemann/riemann_vacuum.h \ stars.h stars_io.h \ diff --git a/src/cache.h b/src/cache.h index 6af5ce2871d5118b8a11f0c8d62fd88e0417e5e3..19bbeca0473c8a9e66306095d12b0dc6379c60a9 100644 --- a/src/cache.h +++ b/src/cache.h @@ -26,7 +26,7 @@ #include "cell.h" #include "error.h" #include "part.h" -#include "sort.h" +#include "sort_part.h" #include "vector.h" #define NUM_VEC_PROC 2 diff --git a/src/cell.c b/src/cell.c index 753bdd55061ebd2b2cdd2067691bd8319ca5a623..874f03f0cad3257d866b70ec63fd8a6bbcd4b6a7 100644 --- a/src/cell.c +++ b/src/cell.c @@ -1114,7 +1114,7 @@ void cell_check_multipole(struct cell *c, void *data) { #ifdef SWIFT_DEBUG_CHECKS struct gravity_tensors ma; - const double tolerance = 1e-5; /* Relative */ + const double tolerance = 1e-3; /* Relative */ /* First recurse */ if (c->split) @@ -1127,8 +1127,7 @@ void cell_check_multipole(struct cell *c, void *data) { gravity_P2M(&ma, c->gparts, c->gcount); /* Now compare the multipole expansion */ - if (!gravity_multipole_equal(&ma.m_pole, &c->multipole->m_pole, - tolerance)) { + if (!gravity_multipole_equal(&ma, c->multipole, tolerance)) { message("Multipoles are not equal at depth=%d!", c->depth); message("Correct answer:"); gravity_multipole_print(&ma.m_pole); @@ -1487,7 +1486,7 @@ void cell_drift_all_multipoles(struct cell *c, const struct engine *e) { } else if (ti_current > ti_old_multipole) { /* Drift the multipole */ - gravity_multipole_drift(c->multipole, dt); + gravity_drift(c->multipole, dt); } /* Update the time of the last drift */ @@ -1504,7 +1503,21 @@ void cell_drift_all_multipoles(struct cell *c, const struct engine *e) { * @param e The #engine (to get ti_current). */ void cell_drift_multipole(struct cell *c, const struct engine *e) { - error("To be implemented"); + + const double timeBase = e->timeBase; + const integertime_t ti_old_multipole = c->ti_old_multipole; + const integertime_t ti_current = e->ti_current; + + /* Drift from the last time the cell was drifted to the current time */ + const double dt = (ti_current - ti_old_multipole) * timeBase; + + /* Check that we are actually going to move forward. */ + if (ti_current < ti_old_multipole) error("Attempt to drift to the past"); + + if (ti_current > ti_old_multipole) gravity_drift(c->multipole, dt); + + /* Update the time of the last drift */ + c->ti_old_multipole = ti_current; } /** diff --git a/src/const.h b/src/const.h index 88c1a1af1cfc36cc401fdfea0b077f79fcd13bc0..6962ee8bca32e92664e3f20cdb23e7cb6fbc4abd 100644 --- a/src/const.h +++ b/src/const.h @@ -39,9 +39,6 @@ /* Thermal energy per unit mass used as a constant for the isothermal EoS */ #define const_isothermal_internal_energy 20.2615290634f -/* Self gravity stuff. */ -#define const_gravity_multipole_order 1 - /* Type of gradients to use (GIZMO_SPH only) */ /* If no option is chosen, no gradients are used (first order scheme) */ //#define GRADIENTS_SPH @@ -57,6 +54,23 @@ //#define GIZMO_FIX_PARTICLES //#define GIZMO_TOTAL_ENERGY +/* Types of gradients to use for SHADOWFAX_SPH */ +/* If no option is chosen, no gradients are used (first order scheme) */ +#define SHADOWFAX_GRADIENTS + +/* SHADOWFAX_SPH slope limiters */ +#define SHADOWFAX_SLOPE_LIMITER_PER_FACE +#define SHADOWFAX_SLOPE_LIMITER_CELL_WIDE + +/* Options to control SHADOWFAX_SPH */ +/* This option disables cell movement */ +//#define SHADOWFAX_FIX_CELLS +/* This option enables cell steering, i.e. trying to keep the cells regular by + adding a correction to the cell velocities.*/ +#define SHADOWFAX_STEER_CELL_MOTION +/* This option evolves the total energy instead of the thermal energy */ +//#define SHADOWFAX_TOTAL_ENERGY + /* Source terms */ #define SOURCETERMS_NONE //#define SOURCETERMS_SN_FEEDBACK diff --git a/src/debug.c b/src/debug.c index f5f2f4974a6f2d0e8da8fce71e98233a2ed3deeb..3732ee5e769277deb393926ea2dc6f04fba93782 100644 --- a/src/debug.c +++ b/src/debug.c @@ -49,6 +49,8 @@ #include "./hydro/Default/hydro_debug.h" #elif defined(GIZMO_SPH) #include "./hydro/Gizmo/hydro_debug.h" +#elif defined(SHADOWFAX_SPH) +#include "./hydro/Shadowswift/hydro_debug.h" #else #error "Invalid choice of SPH variant" #endif diff --git a/src/engine.c b/src/engine.c index fd6cb92bb09604dafac751160413932c19788469..c0b84fc902e576697546870f7bb334fb995c3ac2 100644 --- a/src/engine.c +++ b/src/engine.c @@ -66,6 +66,7 @@ #include "runner.h" #include "serial_io.h" #include "single_io.h" +#include "sort_part.h" #include "statistics.h" #include "timers.h" #include "tools.h" @@ -3029,6 +3030,13 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) { if (e->nodeID == 0) message("Computing initial gas densities."); + /* Initialise the softening lengths */ + if (e->policy & engine_policy_self_gravity) { + + for (size_t i = 0; i < s->nr_gparts; ++i) + gravity_init_softening(&s->gparts[i], e->gravity_properties); + } + /* Construct all cells and tasks to start everything */ engine_rebuild(e); @@ -3061,16 +3069,17 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) { } #ifdef SWIFT_DEBUG_CHECKS - /* Let's store the total mass in the system for future checks */ - e->s->total_mass = 0.; - for (size_t i = 0; i < s->nr_gparts; ++i) - e->s->total_mass += s->gparts[i].mass; -#ifdef WITH_MPI - if (MPI_Allreduce(MPI_IN_PLACE, &e->s->total_mass, 1, MPI_DOUBLE, MPI_SUM, - MPI_COMM_WORLD) != MPI_SUCCESS) - error("Failed to all-reduce total mass in the system."); -#endif - message("Total mass in the system: %e", e->s->total_mass); + /* Check that we have the correct total mass in the top-level multipoles */ + size_t num_gpart_mpole = 0; + if (e->policy & engine_policy_self_gravity) { + for (int i = 0; i < e->s->nr_cells; ++i) + num_gpart_mpole += e->s->cells_top[i].multipole->m_pole.num_gpart; + if (num_gpart_mpole != e->s->nr_gparts) + error( + "Multipoles don't contain the total number of gpart s->nr_gpart=%zd, " + "m_poles=%zd", + e->s->nr_gparts, num_gpart_mpole); + } #endif /* Now time to get ready for the first time-step */ @@ -3085,9 +3094,19 @@ void engine_init_particles(struct engine *e, int flag_entropy_ICs) { /* Print the number of active tasks ? */ if (e->verbose) engine_print_task_counts(e); +#ifdef SWIFT_GRAVITY_FORCE_CHECKS + /* Run the brute-force gravity calculation for some gparts */ + gravity_exact_force_compute(e->s, e); +#endif + /* Run the 0th time-step */ engine_launch(e, e->nr_threads); +#ifdef SWIFT_GRAVITY_FORCE_CHECKS + /* Check the accuracy of the gravity calculation */ + gravity_exact_force_check(e->s, e, 1e-1); +#endif + /* Recover the (integer) end of the next time-step */ engine_collect_timestep(e); @@ -3160,6 +3179,17 @@ void engine_step(struct engine *e) { /* Print the number of active tasks ? */ if (e->verbose) engine_print_task_counts(e); +#ifdef SWIFT_DEBUG_CHECKS + /* Check that we have the correct total mass in the top-level multipoles */ + size_t num_gpart_mpole = 0; + if (e->policy & engine_policy_self_gravity) { + for (int i = 0; i < e->s->nr_cells; ++i) + num_gpart_mpole += e->s->cells_top[i].multipole->m_pole.num_gpart; + if (num_gpart_mpole != e->s->nr_gparts) + error("Multipoles don't contain the total number of gpart"); + } +#endif + #ifdef SWIFT_GRAVITY_FORCE_CHECKS /* Run the brute-force gravity calculation for some gparts */ gravity_exact_force_compute(e->s, e); diff --git a/src/gravity.c b/src/gravity.c index 86f9fa82e3eb693cc3c051420fb9c7bff277eb9f..369c6b1b0ab0458f7c6ab5057261a7cade97a64c 100644 --- a/src/gravity.c +++ b/src/gravity.c @@ -20,6 +20,9 @@ /* Config parameters. */ #include "../config.h" +/* Some standard headers. */ +#include <stdio.h> + /* This object's header. */ #include "gravity.h" @@ -53,9 +56,7 @@ void gravity_exact_force_compute(struct space *s, const struct engine *e) { gpart_is_active(gpi, e)) { /* Be ready for the calculation */ - gpi->a_grav[0] = 0.f; - gpi->a_grav[1] = 0.f; - gpi->a_grav[2] = 0.f; + double a_grav[3] = {0., 0., 0.}; /* Interact it with all other particles in the space.*/ for (size_t j = 0; j < s->nr_gparts; ++j) { @@ -66,36 +67,55 @@ void gravity_exact_force_compute(struct space *s, const struct engine *e) { struct gpart *gpj = &s->gparts[j]; /* Compute the pairwise distance. */ - float dx[3] = {gpi->x[0] - gpj->x[0], // x - gpi->x[1] - gpj->x[1], // y - gpi->x[2] - gpj->x[2]}; // z - const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; + const double dx[3] = {gpi->x[0] - gpj->x[0], // x + gpi->x[1] - gpj->x[1], // y + gpi->x[2] - gpj->x[2]}; // z + const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; - runner_iact_grav_pp_nonsym(0.f, r2, dx, gpi, gpj); - } + const double r = sqrtf(r2); + const double ir = 1.f / r; + const double mj = gpj->mass; + const double hi = gpi->epsilon; + double f; + const double f_lr = 1.; - /* Finish the calculation */ - gravity_end_force(gpi, const_G); + if (r >= hi) { - /* Store the exact answer */ - gpi->a_grav_exact[0] = gpi->a_grav[0]; - gpi->a_grav_exact[1] = gpi->a_grav[1]; - gpi->a_grav_exact[2] = gpi->a_grav[2]; + /* Get Newtonian gravity */ + f = mj * ir * ir * ir * f_lr; + + } else { + + const double hi_inv = 1.f / hi; + const double hi_inv3 = hi_inv * hi_inv * hi_inv; + const double ui = r * hi_inv; + float W; + + kernel_grav_eval(ui, &W); - /* Restore everything */ - gpi->a_grav[0] = 0.f; - gpi->a_grav[1] = 0.f; - gpi->a_grav[2] = 0.f; + /* Get softened gravity */ + f = mj * hi_inv3 * W * f_lr; + } + + const double fdx[3] = {f * dx[0], f * dx[1], f * dx[2]}; + + a_grav[0] -= fdx[0]; + a_grav[1] -= fdx[1]; + a_grav[2] -= fdx[2]; + } + + /* Store the exact answer */ + gpi->a_grav_exact[0] = a_grav[0] * const_G; + gpi->a_grav_exact[1] = a_grav[1] * const_G; + gpi->a_grav_exact[2] = a_grav[2] * const_G; counter++; } } - message("Computed exact gravity for %d gparts.", counter); + message("Computed exact gravity for %d gparts (took %.3f %s). ", counter, + clocks_from_ticks(getticks() - tic), clocks_getunit()); - if (e->verbose) - message("took %.3f %s.", clocks_from_ticks(getticks() - tic), - clocks_getunit()); #else error("Gravity checking function called without the corresponding flag."); #endif @@ -118,17 +138,24 @@ void gravity_exact_force_check(struct space *s, const struct engine *e, #ifdef SWIFT_GRAVITY_FORCE_CHECKS - const double const_G = e->physical_constants->const_newton_G; - int counter = 0; /* Some accumulators */ - float err_rel[3]; - float err_rel_max[3] = {0.f, 0.f, 0.f}; - float err_rel_min[3] = {FLT_MAX, FLT_MAX, FLT_MAX}; - float err_rel_mean[3] = {0.f, 0.f, 0.f}; - float err_rel_mean2[3] = {0.f, 0.f, 0.f}; - float err_rel_std[3] = {0.f, 0.f, 0.f}; + float err_rel_max = 0.f; + float err_rel_min = FLT_MAX; + float err_rel_mean = 0.f; + float err_rel_mean2 = 0.f; + float err_rel_std = 0.f; + + char file_name[100]; + sprintf(file_name, "gravity_checks_step%d_order%d.dat", e->step, + SELF_GRAVITY_MULTIPOLE_ORDER); + FILE *file = fopen(file_name, "w"); + fprintf(file, "# Gravity accuracy test G = %16.8e\n", + e->physical_constants->const_newton_G); + fprintf(file, "# %16s %16s %16s %16s %16s %16s %16s %16s %16s %16s\n", "id", + "pos[0]", "pos[1]", "pos[2]", "a_exact[0]", "a_exact[1]", + "a_exact[2]", "a_grav[0]", "a_grav[1]", "a_grav[2]"); for (size_t i = 0; i < s->nr_gparts; ++i) { @@ -138,48 +165,62 @@ void gravity_exact_force_check(struct space *s, const struct engine *e, if (gpi->id_or_neg_offset % SWIFT_GRAVITY_FORCE_CHECKS == 0 && gpart_is_starting(gpi, e)) { + fprintf(file, + "%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e " + "%16.8e \n", + gpi->id_or_neg_offset, gpi->x[0], gpi->x[1], gpi->x[2], + gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2], + gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2]); + + const float diff[3] = {gpi->a_grav[0] - gpi->a_grav_exact[0], + gpi->a_grav[1] - gpi->a_grav_exact[1], + gpi->a_grav[2] - gpi->a_grav_exact[2]}; + + const float diff_norm = + sqrtf(diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]); + const float a_norm = sqrtf(gpi->a_grav_exact[0] * gpi->a_grav_exact[0] + + gpi->a_grav_exact[1] * gpi->a_grav_exact[1] + + gpi->a_grav_exact[2] * gpi->a_grav_exact[2]); + /* Compute relative error */ - for (int k = 0; k < 3; ++k) - if (fabsf(gpi->a_grav_exact[k]) > FLT_EPSILON * const_G) - err_rel[k] = (gpi->a_grav[k] - gpi->a_grav_exact[k]) / - fabsf(gpi->a_grav_exact[k]); - else - err_rel[k] = 0.f; + const float err_rel = diff_norm / a_norm; /* Check that we are not above tolerance */ - if (fabsf(err_rel[0]) > rel_tol || fabsf(err_rel[1]) > rel_tol || - fabsf(err_rel[2]) > rel_tol) - error("Error too large ! gp->a_grav=[%e %e %e] gp->a_exact=[%e %e %e]", - gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2], - gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2]); - - /* Construct some statistics */ - for (int k = 0; k < 3; ++k) { - err_rel_max[k] = max(err_rel_max[k], fabsf(err_rel[k])); - err_rel_min[k] = min(err_rel_min[k], fabsf(err_rel[k])); - err_rel_mean[k] += err_rel[k]; - err_rel_mean2[k] += err_rel[k] * err_rel[k]; + if (err_rel > rel_tol) { + message( + "Error too large ! gp->a_grav=[%3.6e %3.6e %3.6e] " + "gp->a_exact=[%3.6e %3.6e %3.6e], " + "gp->num_interacted=%lld, err=%f", + gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2], + gpi->a_grav_exact[0], gpi->a_grav_exact[1], gpi->a_grav_exact[2], + gpi->num_interacted, err_rel); + + continue; } + /* Construct some statistics */ + err_rel_max = max(err_rel_max, fabsf(err_rel)); + err_rel_min = min(err_rel_min, fabsf(err_rel)); + err_rel_mean += err_rel; + err_rel_mean2 += err_rel * err_rel; counter++; } } + /* Be nice */ + fclose(file); + /* Final operation on the stats */ if (counter > 0) { - for (int k = 0; k < 3; ++k) { - err_rel_mean[k] /= counter; - err_rel_mean2[k] /= counter; - err_rel_std[k] = - sqrtf(err_rel_mean2[k] - err_rel_mean[k] * err_rel_mean[k]); - } + err_rel_mean /= counter; + err_rel_mean2 /= counter; + err_rel_std = sqrtf(err_rel_mean2 - err_rel_mean * err_rel_mean); } /* Report on the findings */ message("Checked gravity for %d gparts.", counter); - for (int k = 0; k < 3; ++k) - message("Error on a_grav[%d]: min=%e max=%e mean=%e std=%e", k, - err_rel_min[k], err_rel_max[k], err_rel_mean[k], err_rel_std[k]); + message("Error on |a_grav|: min=%e max=%e mean=%e std=%e", err_rel_min, + err_rel_max, err_rel_mean, err_rel_std); #else error("Gravity checking function called without the corresponding flag."); diff --git a/src/gravity/Default/gravity.h b/src/gravity/Default/gravity.h index 93a65e2f5a70e09ad14280cf9c334753359fb8b5..e3487ecd23e01b83e711dc07a6c97fd5ecf6a50d 100644 --- a/src/gravity/Default/gravity.h +++ b/src/gravity/Default/gravity.h @@ -27,6 +27,8 @@ /** * @brief Computes the gravity time-step of a given particle due to self-gravity * + * We use Gadget-2's type 0 time-step criterion. + * * @param gp Pointer to the g-particle data. * @param grav_props Constants used in the gravity scheme. */ @@ -38,9 +40,10 @@ gravity_compute_timestep_self(const struct gpart* const gp, gp->a_grav[1] * gp->a_grav[1] + gp->a_grav[2] * gp->a_grav[2]; - const float ac = (ac2 > 0.f) ? sqrtf(ac2) : FLT_MIN; + const float ac_inv = (ac2 > 0.f) ? 1.f / sqrtf(ac2) : FLT_MAX; - const float dt = sqrtf(2.f * grav_props->eta * gp->epsilon / ac); + /* Note that 0.714285714 = 2. (from Gadget) / 2.8 (Plummer softening) */ + const float dt = sqrtf(0.714285714f * grav_props->eta * gp->epsilon * ac_inv); return dt; } @@ -62,7 +65,7 @@ __attribute__((always_inline)) INLINE static void gravity_init_gpart( gp->a_grav[2] = 0.f; #ifdef SWIFT_DEBUG_CHECKS - gp->mass_interacted = 0.; + gp->num_interacted = 0; #endif } @@ -113,9 +116,22 @@ __attribute__((always_inline)) INLINE static void gravity_first_init_gpart( struct gpart* gp) { gp->time_bin = 0; - gp->epsilon = 0.; // MATTHIEU + gp->epsilon = 0.f; gravity_init_gpart(gp); } +/** + * @brief Initialises the softening of the g-particles + * + * @param gp The particle to act upon. + * @param grav_props The properties of the gravity scheme. + */ +__attribute__((always_inline)) INLINE static void gravity_init_softening( + struct gpart* gp, const struct gravity_props* grav_props) { + + /* Note 2.8 is the Plummer-equivalent correction */ + gp->epsilon = 2.8f * grav_props->epsilon; +} + #endif /* SWIFT_DEFAULT_GRAVITY_H */ diff --git a/src/gravity/Default/gravity_iact.h b/src/gravity/Default/gravity_iact.h index 7c7c5b9d244534ef3f6b5f509062019bfcd5f9fb..eca5c2491cbdcf5f0eca01417c8e6b29efc53459 100644 --- a/src/gravity/Default/gravity_iact.h +++ b/src/gravity/Default/gravity_iact.h @@ -44,6 +44,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp( const float u = r * rlr_inv; float f_lr, fi, fj, W; +#ifdef SWIFT_DEBUG_CHECKS + if (r == 0.f) error("Interacting particles with 0 distance"); +#endif + /* Get long-range correction */ kernel_long_grav_eval(u, &f_lr); @@ -107,6 +111,10 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_nonsym( const float u = r * rlr_inv; float f_lr, f, W; +#ifdef SWIFT_DEBUG_CHECKS + if (r == 0.f) error("Interacting particles with 0 distance"); +#endif + /* Get long-range correction */ kernel_long_grav_eval(u, &f_lr); @@ -141,51 +149,7 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm( float rlr_inv, float r2, const float *dx, struct gpart *gp, const struct multipole *multi) { - /* Apply the gravitational acceleration. */ - const float r = sqrtf(r2); - const float ir = 1.f / r; - const float u = r * rlr_inv; - const float mrinv3 = multi->mass * ir * ir * ir; - - /* Get long-range correction */ - float f_lr; - kernel_long_grav_eval(u, &f_lr); - -#if const_gravity_multipole_order < 2 - - /* 0th and 1st order terms */ - gp->a_grav[0] += mrinv3 * f_lr * dx[0]; - gp->a_grav[1] += mrinv3 * f_lr * dx[1]; - gp->a_grav[2] += mrinv3 * f_lr * dx[2]; - -#elif const_gravity_multipole_order == 2 - /* Terms up to 2nd order (quadrupole) */ - - /* Follows the notation in Bonsai */ - const float mrinv5 = mrinv3 * ir * ir; - const float mrinv7 = mrinv5 * ir * ir; - - const float D1 = -mrinv3; - const float D2 = 3.f * mrinv5; - const float D3 = -15.f * mrinv7; - - const float q = multi->I_xx + multi->I_yy + multi->I_zz; - const float qRx = - multi->I_xx * dx[0] + multi->I_xy * dx[1] + multi->I_xz * dx[2]; - const float qRy = - multi->I_xy * dx[0] + multi->I_yy * dx[1] + multi->I_yz * dx[2]; - const float qRz = - multi->I_xz * dx[0] + multi->I_yz * dx[1] + multi->I_zz * dx[2]; - const float qRR = qRx * dx[0] + qRy * dx[1] + qRz * dx[2]; - const float C = D1 + 0.5f * D2 * q + 0.5f * D3 * qRR; - - gp->a_grav[0] -= f_lr * (C * dx[0] + D2 * qRx); - gp->a_grav[1] -= f_lr * (C * dx[1] + D2 * qRy); - gp->a_grav[2] -= f_lr * (C * dx[2] + D2 * qRz); - -#else -#error "Multipoles of order >2 not yet implemented." -#endif + error("Dead function"); } #endif /* SWIFT_DEFAULT_GRAVITY_IACT_H */ diff --git a/src/gravity/Default/gravity_part.h b/src/gravity/Default/gravity_part.h index 00ae0f5b05cd95750c34b60e2353a9fc1d0a5c32..bb1307a1a2fc1dcd71202e3426c99f3e30c0de9a 100644 --- a/src/gravity/Default/gravity_part.h +++ b/src/gravity/Default/gravity_part.h @@ -52,8 +52,8 @@ struct gpart { #ifdef SWIFT_DEBUG_CHECKS - /* Total mass this gpart interacted with */ - double mass_interacted; + /* Numer of gparts this gpart interacted with */ + long long num_interacted; /* Time of the last drift */ integertime_t ti_drift; @@ -66,7 +66,7 @@ struct gpart { #ifdef SWIFT_GRAVITY_FORCE_CHECKS /* Brute-force particle acceleration. */ - float a_grav_exact[3]; + double a_grav_exact[3]; #endif diff --git a/src/gravity_derivatives.h b/src/gravity_derivatives.h index 4730d9df5dc573de74fde422e1b7dafc0ee0994a..e5c7722bc3e5ebed50b1062c74e3dad830c9b145 100644 --- a/src/gravity_derivatives.h +++ b/src/gravity_derivatives.h @@ -19,14 +19,31 @@ #ifndef SWIFT_GRAVITY_DERIVATIVE_H #define SWIFT_GRAVITY_DERIVATIVE_H +/** + * @file gravity_derivatives.h + * @brief Derivatives (up to 3rd order) of the gravitational potential. + * + * We use the notation of Dehnen, Computational Astrophysics and Cosmology, + * 1, 1, pp. 24 (2014), arXiv:1405.2255 + */ + /* Some standard headers. */ #include <math.h> /* Local headers. */ #include "inline.h" +/*************************/ +/* 0th order derivatives */ +/*************************/ + /** * @brief \f$ \phi(r_x, r_y, r_z) \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) */ __attribute__((always_inline)) INLINE static double D_000(double r_x, double r_y, @@ -36,8 +53,17 @@ __attribute__((always_inline)) INLINE static double D_000(double r_x, return r_inv; } +/*************************/ +/* 1st order derivatives */ +/*************************/ + /** * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) */ __attribute__((always_inline)) INLINE static double D_100(double r_x, double r_y, @@ -49,6 +75,11 @@ __attribute__((always_inline)) INLINE static double D_100(double r_x, /** * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) */ __attribute__((always_inline)) INLINE static double D_010(double r_x, double r_y, @@ -60,6 +91,11 @@ __attribute__((always_inline)) INLINE static double D_010(double r_x, /** * @brief \f$ \frac{\partial\phi(r_x, r_y, r_z)}{\partial r_x} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) */ __attribute__((always_inline)) INLINE static double D_001(double r_x, double r_y, @@ -69,4 +105,306 @@ __attribute__((always_inline)) INLINE static double D_001(double r_x, return -r_z * r_inv * r_inv * r_inv; } +/*************************/ +/* 2nd order derivatives */ +/*************************/ + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x^2} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_200(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv3 = r_inv * r_inv2; + const double r_inv5 = r_inv3 * r_inv2; + return 3. * r_x * r_x * r_inv5 - r_inv3; +} + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_y^2} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_020(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv3 = r_inv * r_inv2; + const double r_inv5 = r_inv3 * r_inv2; + return 3. * r_y * r_y * r_inv5 - r_inv3; +} + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_z^2} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_002(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv3 = r_inv * r_inv2; + const double r_inv5 = r_inv3 * r_inv2; + return 3. * r_z * r_z * r_inv5 - r_inv3; +} + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x\partial r_y} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_110(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + return 3. * r_x * r_y * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_x\partial r_z} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_101(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + return 3. * r_x * r_z * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^2\phi(r_x, r_y, r_z)}{\partial r_y\partial r_z} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_011(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + return 3. * r_y * r_z * r_inv5; +} + +/*************************/ +/* 3rd order derivatives */ +/*************************/ + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^3} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_300(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_x * r_x * r_x * r_inv7 + 9. * r_x * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y^3} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_030(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_y * r_y * r_y * r_inv7 + 9. * r_y * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_z^3} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_003(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_z * r_z * r_z * r_inv7 + 9. * r_z * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^2\partial r_y} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_210(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_x * r_x * r_y * r_inv7 + 3. * r_y * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x^2\partial r_z} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_201(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_x * r_x * r_z * r_inv7 + 3. * r_z * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x\partial r_y^2} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_120(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_x * r_y * r_y * r_inv7 + 3. * r_x * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y^2\partial r_z} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_021(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_z * r_y * r_y * r_inv7 + 3. * r_z * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_x\partial r_z^2} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_102(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_x * r_z * r_z * r_inv7 + 3. * r_x * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_y\partial r_z^2} + * \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_012(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv2 = r_inv * r_inv; + const double r_inv5 = r_inv2 * r_inv2 * r_inv; + const double r_inv7 = r_inv5 * r_inv2; + return -15. * r_y * r_z * r_z * r_inv7 + 3. * r_y * r_inv5; +} + +/** + * @brief \f$ \frac{\partial^3\phi(r_x, r_y, r_z)}{\partial r_z\partial + * r_y\partial r_z} \f$. + * + * @param r_x x-coordinate of the distance vector (\f$ r_x \f$). + * @param r_y y-coordinate of the distance vector (\f$ r_y \f$). + * @param r_z z-coordinate of the distance vector (\f$ r_z \f$). + * @param r_inv Inverse of the norm of the distance vector (\f$ |r|^{-1} \f$) + */ +__attribute__((always_inline)) INLINE static double D_111(double r_x, + double r_y, + double r_z, + double r_inv) { + const double r_inv3 = r_inv * r_inv * r_inv; + const double r_inv7 = r_inv3 * r_inv3 * r_inv; + return -15. * r_x * r_y * r_z * r_inv7; +} + #endif /* SWIFT_GRAVITY_DERIVATIVE_H */ diff --git a/src/hydro.h b/src/hydro.h index 3dce6df074767c15828c3a0c9eec738b32b5d7a3..abb49d35b204bbaf986f502d796883e7eb778e7f 100644 --- a/src/hydro.h +++ b/src/hydro.h @@ -47,6 +47,11 @@ #include "./hydro/Gizmo/hydro.h" #include "./hydro/Gizmo/hydro_iact.h" #define SPH_IMPLEMENTATION "GIZMO (Hopkins 2015)" +#elif defined(SHADOWFAX_SPH) +#include "./hydro/Shadowswift/hydro.h" +#include "./hydro/Shadowswift/hydro_iact.h" +#define SPH_IMPLEMENTATION \ + "Shadowfax moving mesh (Vandenbroucke and De Rijcke 2016)" #else #error "Invalid choice of SPH variant" #endif diff --git a/src/hydro/Default/hydro.h b/src/hydro/Default/hydro.h index a614d08c30b21f9e7d422bf6b6a09d10d2e89799..051c22f46b3ecdff5d3de910e0f75409b0e78f02 100644 --- a/src/hydro/Default/hydro.h +++ b/src/hydro/Default/hydro.h @@ -22,6 +22,7 @@ #include "adiabatic_index.h" #include "approx_math.h" #include "equation_of_state.h" +#include "hydro_space.h" #include "minmax.h" #include <float.h> @@ -165,9 +166,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra( * the variaous density tasks * * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. */ __attribute__((always_inline)) INLINE static void hydro_init_part( - struct part *restrict p) { + struct part *restrict p, const struct hydro_space *hs) { p->density.wcount = 0.f; p->density.wcount_dh = 0.f; p->rho = 0.f; @@ -400,7 +402,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part( xp->u_full = p->u; hydro_reset_acceleration(p); - hydro_init_part(p); + hydro_init_part(p, NULL); } #endif /* SWIFT_DEFAULT_HYDRO_H */ diff --git a/src/hydro/Gadget2/hydro.h b/src/hydro/Gadget2/hydro.h index cc7b422ccbe7c678969df5779a4d4a054c65528e..747c81a8e64c18a06b04160cfab326a3521c5901 100644 --- a/src/hydro/Gadget2/hydro.h +++ b/src/hydro/Gadget2/hydro.h @@ -36,6 +36,7 @@ #include "dimension.h" #include "equation_of_state.h" #include "hydro_properties.h" +#include "hydro_space.h" #include "kernel_hydro.h" #include "minmax.h" @@ -169,9 +170,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra( * the variaous density tasks * * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. */ __attribute__((always_inline)) INLINE static void hydro_init_part( - struct part *restrict p) { + struct part *restrict p, const struct hydro_space *hs) { p->rho = 0.f; p->density.wcount = 0.f; @@ -456,7 +458,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part( xp->entropy_full = p->entropy; hydro_reset_acceleration(p); - hydro_init_part(p); + hydro_init_part(p, NULL); } #endif /* SWIFT_GADGET2_HYDRO_H */ diff --git a/src/hydro/Gizmo/hydro.h b/src/hydro/Gizmo/hydro.h index 643489912a6c6b1db921e73b508910cc670d49ae..60ff8ccee0e1a1e9c3477a10293f8981ff9b837e 100644 --- a/src/hydro/Gizmo/hydro.h +++ b/src/hydro/Gizmo/hydro.h @@ -23,6 +23,7 @@ #include "approx_math.h" #include "equation_of_state.h" #include "hydro_gradients.h" +#include "hydro_space.h" #include "minmax.h" #include "riemann.h" @@ -145,9 +146,10 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part( * Simply makes sure all necessary variables are initialized to zero. * * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. */ __attribute__((always_inline)) INLINE static void hydro_init_part( - struct part* p) { + struct part* p, const struct hydro_space* hs) { p->density.wcount = 0.0f; p->density.wcount_dh = 0.0f; diff --git a/src/hydro/Minimal/hydro.h b/src/hydro/Minimal/hydro.h index 56078a82569fb0bc30347d5c01831e9eecfd48f4..8f216a550ae061d01a594ff23d57575e754f85dc 100644 --- a/src/hydro/Minimal/hydro.h +++ b/src/hydro/Minimal/hydro.h @@ -38,6 +38,7 @@ #include "dimension.h" #include "equation_of_state.h" #include "hydro_properties.h" +#include "hydro_space.h" #include "kernel_hydro.h" #include "minmax.h" @@ -183,9 +184,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra( * density sub-structure of a particle get zeroed in here. * * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. */ __attribute__((always_inline)) INLINE static void hydro_init_part( - struct part *restrict p) { + struct part *restrict p, const struct hydro_space *hs) { p->density.wcount = 0.f; p->density.wcount_dh = 0.f; @@ -429,7 +431,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part( xp->u_full = p->u; hydro_reset_acceleration(p); - hydro_init_part(p); + hydro_init_part(p, NULL); } #endif /* SWIFT_MINIMAL_HYDRO_H */ diff --git a/src/hydro/PressureEntropy/hydro.h b/src/hydro/PressureEntropy/hydro.h index 20238896f1458d0abebacca4865968a3a671c886..4c4868cd3703e5ec5466d4878749a61284b19344 100644 --- a/src/hydro/PressureEntropy/hydro.h +++ b/src/hydro/PressureEntropy/hydro.h @@ -36,6 +36,7 @@ #include "dimension.h" #include "equation_of_state.h" #include "hydro_properties.h" +#include "hydro_space.h" #include "kernel_hydro.h" #include "minmax.h" @@ -169,9 +170,10 @@ __attribute__((always_inline)) INLINE static void hydro_timestep_extra( * the variaous density tasks * * @param p The particle to act upon + * @param hs #hydro_space containing hydro specific space information. */ __attribute__((always_inline)) INLINE static void hydro_init_part( - struct part *restrict p) { + struct part *restrict p, const struct hydro_space *hs) { p->rho = 0.f; p->rho_bar = 0.f; @@ -474,7 +476,7 @@ __attribute__((always_inline)) INLINE static void hydro_first_init_part( xp->v_full[2] = p->v[2]; hydro_reset_acceleration(p); - hydro_init_part(p); + hydro_init_part(p, NULL); } #endif /* SWIFT_PRESSURE_ENTROPY_HYDRO_H */ diff --git a/src/hydro/Shadowswift/hydro.h b/src/hydro/Shadowswift/hydro.h new file mode 100644 index 0000000000000000000000000000000000000000..0568d47ee7ed33c59790cbca943cccbf1ceda58f --- /dev/null +++ b/src/hydro/Shadowswift/hydro.h @@ -0,0 +1,591 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include <float.h> +#include "adiabatic_index.h" +#include "approx_math.h" +#include "equation_of_state.h" +#include "hydro_gradients.h" +#include "hydro_space.h" +#include "voronoi_algorithm.h" + +/** + * @brief Computes the hydro time-step of a given particle + * + * @param p Pointer to the particle data. + * @param xp Pointer to the extended particle data. + * @param hydro_properties Pointer to the hydro parameters. + */ +__attribute__((always_inline)) INLINE static float hydro_compute_timestep( + const struct part* restrict p, const struct xpart* restrict xp, + const struct hydro_props* restrict hydro_properties) { + + const float CFL_condition = hydro_properties->CFL_condition; + + float R = get_radius_dimension_sphere(p->cell.volume); + + if (p->timestepvars.vmax == 0.) { + /* vmax can be zero in vacuum. We force the time step to become the maximal + time step in this case */ + return FLT_MAX; + } else { + return CFL_condition * R / fabsf(p->timestepvars.vmax); + } +} + +/** + * @brief Does some extra hydro operations once the actual physical time step + * for the particle is known. + * + * We use this to store the physical time step, since it is used for the flux + * exchange during the force loop. + * + * We also set the active flag of the particle to inactive. It will be set to + * active in hydro_init_part, which is called the next time the particle becomes + * active. + * + * @param p The particle to act upon. + * @param dt Physical time step of the particle during the next step. + */ +__attribute__((always_inline)) INLINE static void hydro_timestep_extra( + struct part* p, float dt) { + + p->force.dt = dt; + p->force.active = 0; +} + +/** + * @brief Initialises the particles for the first time + * + * This function is called only once just after the ICs have been + * read in to do some conversions. + * + * In this case, we copy the particle velocities into the corresponding + * primitive variable field. We do this because the particle velocities in GIZMO + * can be independent of the actual fluid velocity. The latter is stored as a + * primitive variable and integrated using the linear momentum, a conserved + * variable. + * + * @param p The particle to act upon + * @param xp The extended particle data to act upon + */ +__attribute__((always_inline)) INLINE static void hydro_first_init_part( + struct part* p, struct xpart* xp) { + + const float mass = p->conserved.mass; + + p->primitives.v[0] = p->v[0]; + p->primitives.v[1] = p->v[1]; + p->primitives.v[2] = p->v[2]; + + p->conserved.momentum[0] = mass * p->primitives.v[0]; + p->conserved.momentum[1] = mass * p->primitives.v[1]; + p->conserved.momentum[2] = mass * p->primitives.v[2]; + +#ifdef EOS_ISOTHERMAL_GAS + p->conserved.energy = mass * const_isothermal_internal_energy; +#else + p->conserved.energy *= mass; +#endif + +#ifdef SHADOWFAX_TOTAL_ENERGY + p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + + p->conserved.momentum[1] * p->primitives.v[1] + + p->conserved.momentum[2] * p->primitives.v[2]); +#endif + +#if defined(SHADOWFAX_FIX_CELLS) + p->v[0] = 0.; + p->v[1] = 0.; + p->v[2] = 0.; +#endif + + /* set the initial velocity of the cells */ + xp->v_full[0] = p->v[0]; + xp->v_full[1] = p->v[1]; + xp->v_full[2] = p->v[2]; +} + +/** + * @brief Prepares a particle for the volume calculation. + * + * Simply makes sure all necessary variables are initialized to zero. + * Initializes the Voronoi cell. + * + * @param p The particle to act upon + * @param hs #hydro_space containing extra information about the space. + */ +__attribute__((always_inline)) INLINE static void hydro_init_part( + struct part* p, const struct hydro_space* hs) { + + p->density.wcount = 0.0f; + p->density.wcount_dh = 0.0f; + + voronoi_cell_init(&p->cell, p->x, hs->anchor, hs->side); + + /* Set the active flag to active. */ + p->force.active = 1; +} + +/** + * @brief Finishes the volume calculation. + * + * Calls the finalize method on the Voronoi cell, which calculates the volume + * and centroid of the cell. We use the return value of this function to set + * a new value for the smoothing length and possibly force another iteration + * of the volume calculation for this particle. We then use the volume to + * convert conserved variables into primitive variables. + * + * This method also initializes the gradient variables (if gradients are used). + * + * @param p The particle to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_end_density( + struct part* restrict p) { + + float volume; + float m, momentum[3], energy; + + hydro_gradients_init(p); + + float hnew = voronoi_cell_finalize(&p->cell); + /* Enforce hnew as new smoothing length in the iteration + This is annoyingly difficult, as we do not have access to the variables + that govern the loop... + So here's an idea: let's force in some method somewhere that makes sure + r->e->hydro_properties->target_neighbours is 1, and + r->e->hydro_properties->delta_neighbours is 0. + This way, we can accept an iteration by setting p->density.wcount to 1. + To get the right correction for h, we set wcount to something else + (say 0), and then set p->density.wcount_dh to 1/(hnew-h). */ + if (hnew < p->h) { + /* Iteration succesful: we accept, but manually set h to a smaller value + for the next time step */ + p->density.wcount = 1.0f; + p->h = 1.1f * hnew; + } else { + /* Iteration not succesful: we force h to become 1.1*hnew */ + p->density.wcount = 0.0f; + p->density.wcount_dh = 1.0f / (1.1f * hnew - p->h); + return; + } + volume = p->cell.volume; + +#ifdef SWIFT_DEBUG_CHECKS + /* the last condition checks for NaN: a NaN value always evaluates to false, + even when checked against itself */ + if (volume == 0. || volume == INFINITY || volume != volume) { + error("Invalid value for cell volume (%g)!", volume); + } +#endif + + /* compute primitive variables */ + /* eqns (3)-(5) */ + m = p->conserved.mass; + if (m > 0.) { + momentum[0] = p->conserved.momentum[0]; + momentum[1] = p->conserved.momentum[1]; + momentum[2] = p->conserved.momentum[2]; + p->primitives.rho = m / volume; + p->primitives.v[0] = momentum[0] / m; + p->primitives.v[1] = momentum[1] / m; + p->primitives.v[2] = momentum[2] / m; + + energy = p->conserved.energy; + +#ifdef SHADOWFAX_TOTAL_ENERGY + energy -= 0.5f * (momentum[0] * p->primitives.v[0] + + momentum[1] * p->primitives.v[1] + + momentum[2] * p->primitives.v[2]); +#endif + + energy /= m; + + p->primitives.P = + gas_pressure_from_internal_energy(p->primitives.rho, energy); + } else { + p->primitives.rho = 0.; + p->primitives.v[0] = 0.; + p->primitives.v[1] = 0.; + p->primitives.v[2] = 0.; + p->primitives.P = 0.; + } + +#ifdef SWIFT_DEBUG_CHECKS + if (p->primitives.rho < 0.) { + error("Negative density!"); + } + + if (p->primitives.P < 0.) { + error("Negative pressure!"); + } +#endif +} + +/** + * @brief Prepare a particle for the gradient calculation. + * + * The name of this method is confusing, as this method is really called after + * the density loop and before the gradient loop. + * + * We use it to set the physical timestep for the particle and to copy the + * actual velocities, which we need to boost our interfaces during the flux + * calculation. We also initialize the variables used for the time step + * calculation. + * + * @param p The particle to act upon. + * @param xp The extended particle data to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_prepare_force( + struct part* restrict p, struct xpart* restrict xp) { + + /* Initialize time step criterion variables */ + p->timestepvars.vmax = 0.0f; + + /* Set the actual velocity of the particle */ + p->force.v_full[0] = xp->v_full[0]; + p->force.v_full[1] = xp->v_full[1]; + p->force.v_full[2] = xp->v_full[2]; +} + +/** + * @brief Finishes the gradient calculation. + * + * Just a wrapper around hydro_gradients_finalize, which can be an empty method, + * in which case no gradients are used. + * + * @param p The particle to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_end_gradient( + struct part* p) { + + hydro_gradients_finalize(p); +} + +/** + * @brief Reset acceleration fields of a particle + * + * This is actually not necessary for Shadowswift, since we just set the + * accelerations after the flux calculation. + * + * @param p The particle to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_reset_acceleration( + struct part* p) { + + /* Reset the acceleration. */ + p->a_hydro[0] = 0.0f; + p->a_hydro[1] = 0.0f; + p->a_hydro[2] = 0.0f; + + /* Reset the time derivatives. */ + p->force.h_dt = 0.0f; +} + +/** + * @brief Sets the values to be predicted in the drifts to their values at a + * kick time + * + * @param p The particle. + * @param xp The extended data of this particle. + */ +__attribute__((always_inline)) INLINE static void hydro_reset_predicted_values( + struct part* restrict p, const struct xpart* restrict xp) {} + +/** + * @brief Converts the hydrodynamic variables from the initial condition file to + * conserved variables that can be used during the integration + * + * Requires the volume to be known. + * + * The initial condition file contains a mixture of primitive and conserved + * variables. Mass is a conserved variable, and we just copy the particle + * mass into the corresponding conserved quantity. We need the volume to + * also derive a density, which is then used to convert the internal energy + * to a pressure. However, we do not actually use these variables anymore. + * We do need to initialize the linear momentum, based on the mass and the + * velocity of the particle. + * + * @param p The particle to act upon. + * @param xp The extended particle data to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_convert_quantities( + struct part* p, struct xpart* xp) {} + +/** + * @brief Extra operations to be done during the drift + * + * Not used for Shadowswift. + * + * @param p Particle to act upon. + * @param xp The extended particle data to act upon. + * @param dt The drift time-step. + */ +__attribute__((always_inline)) INLINE static void hydro_predict_extra( + struct part* p, struct xpart* xp, float dt) {} + +/** + * @brief Set the particle acceleration after the flux loop. + * + * @param p Particle to act upon. + */ +__attribute__((always_inline)) INLINE static void hydro_end_force( + struct part* p) {} + +/** + * @brief Extra operations done during the kick + * + * Not used for Shadowswift. + * + * @param p Particle to act upon. + * @param xp Extended particle data to act upon. + * @param dt Physical time step. + */ +__attribute__((always_inline)) INLINE static void hydro_kick_extra( + struct part* p, struct xpart* xp, float dt) { + + float vcell[3]; + + /* Update the conserved variables. We do this here and not in the kick, + since we need the updated variables below. */ + p->conserved.mass += p->conserved.flux.mass; + p->conserved.momentum[0] += p->conserved.flux.momentum[0]; + p->conserved.momentum[1] += p->conserved.flux.momentum[1]; + p->conserved.momentum[2] += p->conserved.flux.momentum[2]; + p->conserved.energy += p->conserved.flux.energy; + +#ifdef EOS_ISOTHERMAL_GAS + /* reset the thermal energy */ + p->conserved.energy = p->conserved.mass * const_isothermal_internal_energy; + +#ifdef SHADOWFAX_TOTAL_ENERGY + p->conserved.energy += 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + + p->conserved.momentum[1] * p->primitives.v[1] + + p->conserved.momentum[2] * p->primitives.v[2]); +#endif + +#endif + + /* reset fluxes */ + /* we can only do this here, since we need to keep the fluxes for inactive + particles */ + p->conserved.flux.mass = 0.0f; + p->conserved.flux.momentum[0] = 0.0f; + p->conserved.flux.momentum[1] = 0.0f; + p->conserved.flux.momentum[2] = 0.0f; + p->conserved.flux.energy = 0.0f; + + if (p->conserved.mass > 0.) { + /* We want the cell velocity to be as close as possible to the fluid + velocity */ + vcell[0] = p->conserved.momentum[0] / p->conserved.mass; + vcell[1] = p->conserved.momentum[1] / p->conserved.mass; + vcell[2] = p->conserved.momentum[2] / p->conserved.mass; + } else { + vcell[0] = 0.; + vcell[1] = 0.; + vcell[2] = 0.; + } + +#ifdef SHADOWFAX_STEER_CELL_MOTION + /* To prevent stupid things like cell crossovers or generators that move + outside their cell, we steer the motion of the cell somewhat */ + if (p->primitives.rho) { + float centroid[3], d[3]; + float volume, csnd, R, vfac, fac, dnrm; + voronoi_get_centroid(&p->cell, centroid); + d[0] = centroid[0] - p->x[0]; + d[1] = centroid[1] - p->x[1]; + d[2] = centroid[2] - p->x[2]; + dnrm = sqrtf(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]); + csnd = sqrtf(hydro_gamma * p->primitives.P / p->primitives.rho); + volume = p->cell.volume; + R = get_radius_dimension_sphere(volume); + fac = 4.0f * dnrm / R; + if (fac > 0.9f) { + if (fac < 1.1f) { + vfac = csnd * (dnrm - 0.225f * R) / dnrm / (0.05f * R); + } else { + vfac = csnd / dnrm; + } + } else { + vfac = 0.0f; + } + vcell[0] += vfac * d[0]; + vcell[1] += vfac * d[1]; + vcell[2] += vfac * d[2]; + } +#endif + +#if defined(SHADOWFAX_FIX_CELLS) + xp->v_full[0] = 0.; + xp->v_full[1] = 0.; + xp->v_full[2] = 0.; + + p->v[0] = 0.; + p->v[1] = 0.; + p->v[2] = 0.; +#else + xp->v_full[0] = vcell[0]; + xp->v_full[1] = vcell[1]; + xp->v_full[2] = vcell[2]; + + p->v[0] = xp->v_full[0]; + p->v[1] = xp->v_full[1]; + p->v[2] = xp->v_full[2]; +#endif +} + +/** + * @brief Returns the internal energy of a particle + * + * @param p The particle of interest. + * @return Internal energy of the particle. + */ +__attribute__((always_inline)) INLINE static float hydro_get_internal_energy( + const struct part* restrict p) { + + if (p->primitives.rho > 0.) { + return gas_internal_energy_from_pressure(p->primitives.rho, + p->primitives.P); + } else { + return 0.; + } +} + +/** + * @brief Returns the entropy of a particle + * + * @param p The particle of interest. + * @return Entropy of the particle. + */ +__attribute__((always_inline)) INLINE static float hydro_get_entropy( + const struct part* restrict p) { + + if (p->primitives.rho > 0.) { + return gas_entropy_from_pressure(p->primitives.rho, p->primitives.P); + } else { + return 0.; + } +} + +/** + * @brief Returns the sound speed of a particle + * + * @param p The particle of interest. + * @param Sound speed of the particle. + */ +__attribute__((always_inline)) INLINE static float hydro_get_soundspeed( + const struct part* restrict p) { + + if (p->primitives.rho > 0.) { + return gas_soundspeed_from_pressure(p->primitives.rho, p->primitives.P); + } else { + return 0.; + } +} + +/** + * @brief Returns the pressure of a particle + * + * @param p The particle of interest + * @param Pressure of the particle. + */ +__attribute__((always_inline)) INLINE static float hydro_get_pressure( + const struct part* restrict p) { + + return p->primitives.P; +} + +/** + * @brief Returns the mass of a particle + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float hydro_get_mass( + const struct part* restrict p) { + + return p->conserved.mass; +} + +/** + * @brief Returns the density of a particle + * + * @param p The particle of interest + */ +__attribute__((always_inline)) INLINE static float hydro_get_density( + const struct part* restrict p) { + + return p->primitives.rho; +} + +/** + * @brief Modifies the thermal state of a particle to the imposed internal + * energy + * + * This overrides the current state of the particle but does *not* change its + * time-derivatives + * + * @param p The particle + * @param u The new internal energy + */ +__attribute__((always_inline)) INLINE static void hydro_set_internal_energy( + struct part* restrict p, float u) { + + if (p->primitives.rho > 0.) { + p->conserved.energy = u * p->conserved.mass; + +#ifdef SHADOWFAX_TOTAL_ENERGY + p->conserved.energy += + 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + + p->conserved.momentum[1] * p->primitives.v[1] + + p->conserved.momentum[2] * p->primitives.v[2]); +#endif + + p->primitives.P = gas_pressure_from_internal_energy(p->primitives.rho, u); + } +} + +/** + * @brief Modifies the thermal state of a particle to the imposed entropy + * + * This overrides the current state of the particle but does *not* change its + * time-derivatives + * + * @param p The particle + * @param S The new entropy + */ +__attribute__((always_inline)) INLINE static void hydro_set_entropy( + struct part* restrict p, float S) { + + if (p->primitives.rho > 0.) { + p->conserved.energy = + gas_internal_energy_from_entropy(p->primitives.rho, S) * + p->conserved.mass; + +#ifdef SHADOWFAX_TOTAL_ENERGY + p->conserved.energy += + 0.5f * (p->conserved.momentum[0] * p->primitives.v[0] + + p->conserved.momentum[1] * p->primitives.v[1] + + p->conserved.momentum[2] * p->primitives.v[2]); +#endif + + p->primitives.P = gas_pressure_from_entropy(p->primitives.rho, S); + } +} diff --git a/src/hydro/Shadowswift/hydro_debug.h b/src/hydro/Shadowswift/hydro_debug.h new file mode 100644 index 0000000000000000000000000000000000000000..7cd7f89c8112ebcf1930c5ca52cb389139191975 --- /dev/null +++ b/src/hydro/Shadowswift/hydro_debug.h @@ -0,0 +1,69 @@ +/******************************************************************************* + * 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/>. + * + ******************************************************************************/ + +__attribute__((always_inline)) INLINE static void hydro_debug_particle( + const struct part* p, const struct xpart* xp) { + printf( + "x=[%.16e,%.16e,%.16e], " + "v=[%.3e,%.3e,%.3e], " + "a=[%.3e,%.3e,%.3e], " + "h=%.3e, " + "primitives={" + "v=[%.3e,%.3e,%.3e], " + "rho=%.3e, " + "P=%.3e, " + "gradients={" + "rho=[%.3e,%.3e,%.3e], " + "v=[[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e],[%.3e,%.3e,%.3e]], " + "P=[%.3e,%.3e,%.3e]}, " + "limiter={" + "rho=[%.3e,%.3e], " + "v=[[%.3e,%.3e],[%.3e,%.3e],[%.3e,%.3e]], " + "P=[%.3e,%.3e], " + "maxr=%.3e}}, " + "conserved={" + "momentum=[%.3e,%.3e,%.3e], " + "mass=%.3e, " + "energy=%.3e}, " + "timestepvars={" + "vmax=%.3e}, " + "density={" + "wcount_dh=%.3e, " + "wcount=%.3e}", + p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], p->a_hydro[0], + p->a_hydro[1], p->a_hydro[2], p->h, p->primitives.v[0], + p->primitives.v[1], p->primitives.v[2], p->primitives.rho, + p->primitives.P, p->primitives.gradients.rho[0], + p->primitives.gradients.rho[1], p->primitives.gradients.rho[2], + p->primitives.gradients.v[0][0], p->primitives.gradients.v[0][1], + p->primitives.gradients.v[0][2], p->primitives.gradients.v[1][0], + p->primitives.gradients.v[1][1], p->primitives.gradients.v[1][2], + p->primitives.gradients.v[2][0], p->primitives.gradients.v[2][1], + p->primitives.gradients.v[2][2], p->primitives.gradients.P[0], + p->primitives.gradients.P[1], p->primitives.gradients.P[2], + p->primitives.limiter.rho[0], p->primitives.limiter.rho[1], + p->primitives.limiter.v[0][0], p->primitives.limiter.v[0][1], + p->primitives.limiter.v[1][0], p->primitives.limiter.v[1][1], + p->primitives.limiter.v[2][0], p->primitives.limiter.v[2][1], + p->primitives.limiter.P[0], p->primitives.limiter.P[1], + p->primitives.limiter.maxr, p->conserved.momentum[0], + p->conserved.momentum[1], p->conserved.momentum[2], p->conserved.mass, + p->conserved.energy, p->timestepvars.vmax, p->density.wcount_dh, + p->density.wcount); +} diff --git a/src/hydro/Shadowswift/hydro_gradients.h b/src/hydro/Shadowswift/hydro_gradients.h new file mode 100644 index 0000000000000000000000000000000000000000..1aea49790d998d3912a80fa1376cbd1e183f26f7 --- /dev/null +++ b/src/hydro/Shadowswift/hydro_gradients.h @@ -0,0 +1,215 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_HYDRO_GRADIENTS_H +#define SWIFT_HYDRO_GRADIENTS_H + +#include "hydro_slope_limiters.h" + +#if defined(SHADOWFAX_GRADIENTS) + +#define HYDRO_GRADIENT_IMPLEMENTATION "Shadowfax gradients (Springel 2010)" +#include "hydro_gradients_shadowfax.h" + +#else + +/* No gradients. Perfectly acceptable, but we have to provide empty functions */ +#define HYDRO_GRADIENT_IMPLEMENTATION "No gradients (first order scheme)" + +/** + * @brief Initialize gradient variables + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_init( + struct part* p) {} + +/** + * @brief Gradient calculations done during the neighbour loop + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_collect( + float r2, float* dx, float hi, float hj, struct part* pi, struct part* pj) { +} + +/** + * @brief Gradient calculations done during the neighbour loop: non-symmetric + * version + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void +hydro_gradients_nonsym_collect(float r2, float* dx, float hi, float hj, + struct part* pi, struct part* pj) {} + +/** + * @brief Finalize the gradient variables after all data have been collected + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_finalize( + struct part* p) {} + +#endif + +/** + * @brief Gradients reconstruction. Is the same for all gradient types (although + * gradients_none does nothing, since all gradients are zero -- are they?). + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_predict( + struct part* pi, struct part* pj, float hi, float hj, float* dx, float r, + float* xij_i, float* Wi, float* Wj, float mindt) { + + float dWi[5], dWj[5]; + float xij_j[3]; + + /* xij_j = real_midpoint - pj->x + = xij_i + pi->x - pj->x + = xij_i + dx */ + xij_j[0] = xij_i[0] + dx[0]; + xij_j[1] = xij_i[1] + dx[1]; + xij_j[2] = xij_i[2] + dx[2]; + + dWi[0] = pi->primitives.gradients.rho[0] * xij_i[0] + + pi->primitives.gradients.rho[1] * xij_i[1] + + pi->primitives.gradients.rho[2] * xij_i[2]; + dWi[1] = pi->primitives.gradients.v[0][0] * xij_i[0] + + pi->primitives.gradients.v[0][1] * xij_i[1] + + pi->primitives.gradients.v[0][2] * xij_i[2]; + dWi[2] = pi->primitives.gradients.v[1][0] * xij_i[0] + + pi->primitives.gradients.v[1][1] * xij_i[1] + + pi->primitives.gradients.v[1][2] * xij_i[2]; + dWi[3] = pi->primitives.gradients.v[2][0] * xij_i[0] + + pi->primitives.gradients.v[2][1] * xij_i[1] + + pi->primitives.gradients.v[2][2] * xij_i[2]; + dWi[4] = pi->primitives.gradients.P[0] * xij_i[0] + + pi->primitives.gradients.P[1] * xij_i[1] + + pi->primitives.gradients.P[2] * xij_i[2]; + + dWj[0] = pj->primitives.gradients.rho[0] * xij_j[0] + + pj->primitives.gradients.rho[1] * xij_j[1] + + pj->primitives.gradients.rho[2] * xij_j[2]; + dWj[1] = pj->primitives.gradients.v[0][0] * xij_j[0] + + pj->primitives.gradients.v[0][1] * xij_j[1] + + pj->primitives.gradients.v[0][2] * xij_j[2]; + dWj[2] = pj->primitives.gradients.v[1][0] * xij_j[0] + + pj->primitives.gradients.v[1][1] * xij_j[1] + + pj->primitives.gradients.v[1][2] * xij_j[2]; + dWj[3] = pj->primitives.gradients.v[2][0] * xij_j[0] + + pj->primitives.gradients.v[2][1] * xij_j[1] + + pj->primitives.gradients.v[2][2] * xij_j[2]; + dWj[4] = pj->primitives.gradients.P[0] * xij_j[0] + + pj->primitives.gradients.P[1] * xij_j[1] + + pj->primitives.gradients.P[2] * xij_j[2]; + + hydro_slope_limit_face(Wi, Wj, dWi, dWj, xij_i, xij_j, r); + + /* time */ + dWi[0] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.rho[0] + + Wi[2] * pi->primitives.gradients.rho[1] + + Wi[3] * pi->primitives.gradients.rho[2] + + Wi[0] * (pi->primitives.gradients.v[0][0] + + pi->primitives.gradients.v[1][1] + + pi->primitives.gradients.v[2][2])); + dWi[1] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[0][0] + + Wi[2] * pi->primitives.gradients.v[0][1] + + Wi[3] * pi->primitives.gradients.v[0][2] + + pi->primitives.gradients.P[0] / Wi[0]); + dWi[2] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[1][0] + + Wi[2] * pi->primitives.gradients.v[1][1] + + Wi[3] * pi->primitives.gradients.v[1][2] + + pi->primitives.gradients.P[1] / Wi[0]); + dWi[3] -= 0.5 * mindt * (Wi[1] * pi->primitives.gradients.v[2][0] + + Wi[2] * pi->primitives.gradients.v[2][1] + + Wi[3] * pi->primitives.gradients.v[2][2] + + pi->primitives.gradients.P[2] / Wi[0]); + dWi[4] -= + 0.5 * mindt * (Wi[1] * pi->primitives.gradients.P[0] + + Wi[2] * pi->primitives.gradients.P[1] + + Wi[3] * pi->primitives.gradients.P[2] + + hydro_gamma * Wi[4] * (pi->primitives.gradients.v[0][0] + + pi->primitives.gradients.v[1][1] + + pi->primitives.gradients.v[2][2])); + + dWj[0] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.rho[0] + + Wj[2] * pj->primitives.gradients.rho[1] + + Wj[3] * pj->primitives.gradients.rho[2] + + Wj[0] * (pj->primitives.gradients.v[0][0] + + pj->primitives.gradients.v[1][1] + + pj->primitives.gradients.v[2][2])); + dWj[1] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[0][0] + + Wj[2] * pj->primitives.gradients.v[0][1] + + Wj[3] * pj->primitives.gradients.v[0][2] + + pj->primitives.gradients.P[0] / Wj[0]); + dWj[2] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[1][0] + + Wj[2] * pj->primitives.gradients.v[1][1] + + Wj[3] * pj->primitives.gradients.v[1][2] + + pj->primitives.gradients.P[1] / Wj[0]); + dWj[3] -= 0.5 * mindt * (Wj[1] * pj->primitives.gradients.v[2][0] + + Wj[2] * pj->primitives.gradients.v[2][1] + + Wj[3] * pj->primitives.gradients.v[2][2] + + pj->primitives.gradients.P[2] / Wj[0]); + dWj[4] -= + 0.5 * mindt * (Wj[1] * pj->primitives.gradients.P[0] + + Wj[2] * pj->primitives.gradients.P[1] + + Wj[3] * pj->primitives.gradients.P[2] + + hydro_gamma * Wj[4] * (pj->primitives.gradients.v[0][0] + + pj->primitives.gradients.v[1][1] + + pj->primitives.gradients.v[2][2])); + + Wi[0] += dWi[0]; + Wi[1] += dWi[1]; + Wi[2] += dWi[2]; + Wi[3] += dWi[3]; + Wi[4] += dWi[4]; + + Wj[0] += dWj[0]; + Wj[1] += dWj[1]; + Wj[2] += dWj[2]; + Wj[3] += dWj[3]; + Wj[4] += dWj[4]; + + /* Sanity check: if density or pressure becomes negative after the + interpolation, just reset them */ + if (Wi[0] < 0.0f) { + Wi[0] -= dWi[0]; + } + if (Wi[4] < 0.0f) { + Wi[4] -= dWi[4]; + } + if (Wj[0] < 0.0f) { + Wj[0] -= dWj[0]; + } + if (Wj[4] < 0.0f) { + Wj[4] -= dWj[4]; + } +} + +#endif // SWIFT_HYDRO_GRADIENTS_H diff --git a/src/hydro/Shadowswift/hydro_gradients_shadowfax.h b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h new file mode 100644 index 0000000000000000000000000000000000000000..9ca40a604da3dc12bbb48ac033cd078f0561d8ab --- /dev/null +++ b/src/hydro/Shadowswift/hydro_gradients_shadowfax.h @@ -0,0 +1,217 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "voronoi_algorithm.h" + +/** + * @brief Initialize gradient variables + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_init( + struct part *p) { + + p->primitives.gradients.rho[0] = 0.0f; + p->primitives.gradients.rho[1] = 0.0f; + p->primitives.gradients.rho[2] = 0.0f; + + p->primitives.gradients.v[0][0] = 0.0f; + p->primitives.gradients.v[0][1] = 0.0f; + p->primitives.gradients.v[0][2] = 0.0f; + + p->primitives.gradients.v[1][0] = 0.0f; + p->primitives.gradients.v[1][1] = 0.0f; + p->primitives.gradients.v[1][2] = 0.0f; + + p->primitives.gradients.v[2][0] = 0.0f; + p->primitives.gradients.v[2][1] = 0.0f; + p->primitives.gradients.v[2][2] = 0.0f; + + p->primitives.gradients.P[0] = 0.0f; + p->primitives.gradients.P[1] = 0.0f; + p->primitives.gradients.P[2] = 0.0f; + + hydro_slope_limit_cell_init(p); +} + +/** + * @brief Add the gradient estimate for a single quantity due to a particle pair + * to the total gradient for that quantity + * + * This corresponds to one term of equation (21) in Springel (2010). + * + * @param qL Value of the quantity on the left. + * @param qR Value of the quantity on the right. + * @param cLR Vector pointing from the midpoint of the particle pair to the + * geometrical centroid of the face in between the particles. + * @param xLR Vector pointing from the right particle to the left particle. + * @param A Surface area of the face in between the particles. + * @param grad Current value of the gradient for the quantity (is updated). + */ +__attribute__((always_inline)) INLINE void hydro_gradients_single_quantity( + float qL, float qR, float *cLR, float *xLR, float rLR, float A, + float *grad) { + + grad[0] += A * ((qR - qL) * cLR[0] / rLR - 0.5f * (qL + qR) * xLR[0] / rLR); + grad[1] += A * ((qR - qL) * cLR[1] / rLR - 0.5f * (qL + qR) * xLR[1] / rLR); + grad[2] += A * ((qR - qL) * cLR[2] / rLR - 0.5f * (qL + qR) * xLR[2] / rLR); +} + +/** + * @brief Gradient calculations done during the neighbour loop + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_collect( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + float A, midpoint[3]; + + A = voronoi_get_face(&pi->cell, pj->id, midpoint); + if (!A) { + /* particle is not a cell neighbour: do nothing */ + return; + } + + float c[3]; + /* midpoint is relative w.r.t. pi->x, as is dx */ + /* c is supposed to be the vector pointing from the midpoint of pi and pj to + the midpoint of the face between pi and pj: + c = real_midpoint - 0.5*(pi+pj) + = midpoint + pi - 0.5*(2*pi - dx) + = midpoint + 0.5*dx */ + c[0] = midpoint[0] + 0.5f * dx[0]; + c[1] = midpoint[1] + 0.5f * dx[1]; + c[2] = midpoint[2] + 0.5f * dx[2]; + + float r = sqrtf(r2); + hydro_gradients_single_quantity(pi->primitives.rho, pj->primitives.rho, c, dx, + r, A, pi->primitives.gradients.rho); + hydro_gradients_single_quantity(pi->primitives.v[0], pj->primitives.v[0], c, + dx, r, A, pi->primitives.gradients.v[0]); + hydro_gradients_single_quantity(pi->primitives.v[1], pj->primitives.v[1], c, + dx, r, A, pi->primitives.gradients.v[1]); + hydro_gradients_single_quantity(pi->primitives.v[2], pj->primitives.v[2], c, + dx, r, A, pi->primitives.gradients.v[2]); + hydro_gradients_single_quantity(pi->primitives.P, pj->primitives.P, c, dx, r, + A, pi->primitives.gradients.P); + + hydro_slope_limit_cell_collect(pi, pj, r); + + float mindx[3]; + mindx[0] = -dx[0]; + mindx[1] = -dx[1]; + mindx[2] = -dx[2]; + hydro_gradients_single_quantity(pj->primitives.rho, pi->primitives.rho, c, + mindx, r, A, pj->primitives.gradients.rho); + hydro_gradients_single_quantity(pj->primitives.v[0], pi->primitives.v[0], c, + mindx, r, A, pj->primitives.gradients.v[0]); + hydro_gradients_single_quantity(pj->primitives.v[1], pi->primitives.v[1], c, + mindx, r, A, pj->primitives.gradients.v[1]); + hydro_gradients_single_quantity(pj->primitives.v[2], pi->primitives.v[2], c, + mindx, r, A, pj->primitives.gradients.v[2]); + hydro_gradients_single_quantity(pj->primitives.P, pi->primitives.P, c, mindx, + r, A, pj->primitives.gradients.P); + + hydro_slope_limit_cell_collect(pj, pi, r); +} + +/** + * @brief Gradient calculations done during the neighbour loop + * + * @param r2 Squared distance between the two particles. + * @param dx Distance vector (pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void +hydro_gradients_nonsym_collect(float r2, float *dx, float hi, float hj, + struct part *pi, struct part *pj) { + + float A, midpoint[3]; + + A = voronoi_get_face(&pi->cell, pj->id, midpoint); + if (!A) { + /* particle is not a cell neighbour: do nothing */ + return; + } + + float c[3]; + /* midpoint is relative w.r.t. pi->x, as is dx */ + /* c is supposed to be the vector pointing from the midpoint of pi and pj to + the midpoint of the face between pi and pj: + c = real_midpoint - 0.5*(pi+pj) + = midpoint + pi - 0.5*(2*pi - dx) + = midpoint + 0.5*dx */ + c[0] = midpoint[0] + 0.5f * dx[0]; + c[1] = midpoint[1] + 0.5f * dx[1]; + c[2] = midpoint[2] + 0.5f * dx[2]; + + float r = sqrtf(r2); + hydro_gradients_single_quantity(pi->primitives.rho, pj->primitives.rho, c, dx, + r, A, pi->primitives.gradients.rho); + hydro_gradients_single_quantity(pi->primitives.v[0], pj->primitives.v[0], c, + dx, r, A, pi->primitives.gradients.v[0]); + hydro_gradients_single_quantity(pi->primitives.v[1], pj->primitives.v[1], c, + dx, r, A, pi->primitives.gradients.v[1]); + hydro_gradients_single_quantity(pi->primitives.v[2], pj->primitives.v[2], c, + dx, r, A, pi->primitives.gradients.v[2]); + hydro_gradients_single_quantity(pi->primitives.P, pj->primitives.P, c, dx, r, + A, pi->primitives.gradients.P); + + hydro_slope_limit_cell_collect(pi, pj, r); +} + +/** + * @brief Finalize the gradient variables after all data have been collected + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_gradients_finalize( + struct part *p) { + + float volume = p->cell.volume; + + p->primitives.gradients.rho[0] /= volume; + p->primitives.gradients.rho[1] /= volume; + p->primitives.gradients.rho[2] /= volume; + + p->primitives.gradients.v[0][0] /= volume; + p->primitives.gradients.v[0][1] /= volume; + p->primitives.gradients.v[0][2] /= volume; + p->primitives.gradients.v[1][0] /= volume; + p->primitives.gradients.v[1][1] /= volume; + p->primitives.gradients.v[1][2] /= volume; + p->primitives.gradients.v[2][0] /= volume; + p->primitives.gradients.v[2][1] /= volume; + p->primitives.gradients.v[2][2] /= volume; + + p->primitives.gradients.P[0] /= volume; + p->primitives.gradients.P[1] /= volume; + p->primitives.gradients.P[2] /= volume; + + hydro_slope_limit_cell(p); +} diff --git a/src/hydro/Shadowswift/hydro_iact.h b/src/hydro/Shadowswift/hydro_iact.h new file mode 100644 index 0000000000000000000000000000000000000000..63f7bdc6900c8fe9887879f3ff7e6c5eaff1b281 --- /dev/null +++ b/src/hydro/Shadowswift/hydro_iact.h @@ -0,0 +1,365 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "adiabatic_index.h" +#include "hydro_gradients.h" +#include "riemann.h" +#include "voronoi_algorithm.h" + +/** + * @brief Calculate the Voronoi cell by interacting particle pi and pj + * + * This method wraps around voronoi_cell_interact(). + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_density( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + float mindx[3]; + + voronoi_cell_interact(&pi->cell, dx, pj->id); + mindx[0] = -dx[0]; + mindx[1] = -dx[1]; + mindx[2] = -dx[2]; + voronoi_cell_interact(&pj->cell, mindx, pi->id); +} + +/** + * @brief Calculate the Voronoi cell by interacting particle pi with pj + * + * This method wraps around voronoi_cell_interact(). + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_density( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + voronoi_cell_interact(&pi->cell, dx, pj->id); +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j + * + * This method wraps around hydro_gradients_collect, which can be an empty + * method, in which case no gradients are used. + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_gradient( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + hydro_gradients_collect(r2, dx, hi, hj, pi, pj); +} + +/** + * @brief Calculate the gradient interaction between particle i and particle j: + * non-symmetric version + * + * This method wraps around hydro_gradients_nonsym_collect, which can be an + * empty method, in which case no gradients are used. + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_gradient( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + hydro_gradients_nonsym_collect(r2, dx, hi, hj, pi, pj); +} + +/** + * @brief Common part of the flux calculation between particle i and j + * + * Since the only difference between the symmetric and non-symmetric version + * of the flux calculation is in the update of the conserved variables at the + * very end (which is not done for particle j if mode is 0 and particle j is + * active), both runner_iact_force and runner_iact_nonsym_force call this + * method, with an appropriate mode. + * + * This method retrieves the oriented surface area and face midpoint for the + * Voronoi face between pi and pj (if it exists). It uses the midpoint position + * to reconstruct the primitive quantities (if gradients are used) at the face + * and then uses the face quantities to estimate a flux through the face using + * a Riemann solver. + * + * This method also calculates the maximal velocity used to calculate the time + * step. + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_fluxes_common( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj, + int mode) { + + float r = sqrtf(r2); + int k; + float A; + float xij_i[3]; + float vmax, dvdotdx; + float vi[3], vj[3], vij[3]; + float Wi[5], Wj[5]; + float dti, dtj, mindt; + float n_unit[3]; + + A = voronoi_get_face(&pi->cell, pj->id, xij_i); + if (A == 0.0f) { + /* this neighbour does not share a face with the cell, return */ + return; + } + + /* Initialize local variables */ + for (k = 0; k < 3; k++) { + vi[k] = pi->force.v_full[k]; /* particle velocities */ + vj[k] = pj->force.v_full[k]; + } + Wi[0] = pi->primitives.rho; + Wi[1] = pi->primitives.v[0]; + Wi[2] = pi->primitives.v[1]; + Wi[3] = pi->primitives.v[2]; + Wi[4] = pi->primitives.P; + Wj[0] = pj->primitives.rho; + Wj[1] = pj->primitives.v[0]; + Wj[2] = pj->primitives.v[1]; + Wj[3] = pj->primitives.v[2]; + Wj[4] = pj->primitives.P; + + dti = pi->force.dt; + dtj = pj->force.dt; + + /* calculate the maximal signal velocity */ + vmax = 0.0f; + if (Wi[0] > 0.) { + vmax += gas_soundspeed_from_pressure(Wi[0], Wi[4]); + } + + if (Wj[0] > 0.) { + vmax += gas_soundspeed_from_pressure(Wj[0], Wj[4]); + } + + dvdotdx = (Wi[1] - Wj[1]) * dx[0] + (Wi[2] - Wj[2]) * dx[1] + + (Wi[3] - Wj[3]) * dx[2]; + if (dvdotdx > 0.) { + vmax -= dvdotdx / r; + } + + pi->timestepvars.vmax = fmaxf(pi->timestepvars.vmax, vmax); + if (mode == 1) { + pj->timestepvars.vmax = fmaxf(pj->timestepvars.vmax, vmax); + } + + /* The flux will be exchanged using the smallest time step of the two + * particles */ + mindt = fminf(dti, dtj); + + /* compute the normal vector of the interface */ + for (k = 0; k < 3; ++k) { + n_unit[k] = -dx[k] / r; + } + + /* Compute interface velocity */ + float fac = (vi[0] - vj[0]) * (xij_i[0] + 0.5f * dx[0]) + + (vi[1] - vj[1]) * (xij_i[1] + 0.5f * dx[1]) + + (vi[2] - vj[2]) * (xij_i[2] + 0.5f * dx[2]); + fac /= r; + vij[0] = 0.5f * (vi[0] + vj[0]) - fac * dx[0]; + vij[1] = 0.5f * (vi[1] + vj[1]) - fac * dx[1]; + vij[2] = 0.5f * (vi[2] + vj[2]) - fac * dx[2]; + + /* Boost the primitive variables to the frame of reference of the interface */ + /* Note that velocities are indices 1-3 in W */ + Wi[1] -= vij[0]; + Wi[2] -= vij[1]; + Wi[3] -= vij[2]; + Wj[1] -= vij[0]; + Wj[2] -= vij[1]; + Wj[3] -= vij[2]; + + hydro_gradients_predict(pi, pj, hi, hj, dx, r, xij_i, Wi, Wj, mindt); + + /* we don't need to rotate, we can use the unit vector in the Riemann problem + * itself (see GIZMO) */ + + if (Wi[0] < 0.0f || Wj[0] < 0.0f || Wi[4] < 0.0f || Wj[4] < 0.0f) { + printf("mindt: %g\n", mindt); + printf("WL: %g %g %g %g %g\n", pi->primitives.rho, pi->primitives.v[0], + pi->primitives.v[1], pi->primitives.v[2], pi->primitives.P); +#ifdef USE_GRADIENTS + printf("dWL: %g %g %g %g %g\n", dWi[0], dWi[1], dWi[2], dWi[3], dWi[4]); +#endif + printf("gradWL[0]: %g %g %g\n", pi->primitives.gradients.rho[0], + pi->primitives.gradients.rho[1], pi->primitives.gradients.rho[2]); + printf("gradWL[1]: %g %g %g\n", pi->primitives.gradients.v[0][0], + pi->primitives.gradients.v[0][1], pi->primitives.gradients.v[0][2]); + printf("gradWL[2]: %g %g %g\n", pi->primitives.gradients.v[1][0], + pi->primitives.gradients.v[1][1], pi->primitives.gradients.v[1][2]); + printf("gradWL[3]: %g %g %g\n", pi->primitives.gradients.v[2][0], + pi->primitives.gradients.v[2][1], pi->primitives.gradients.v[2][2]); + printf("gradWL[4]: %g %g %g\n", pi->primitives.gradients.P[0], + pi->primitives.gradients.P[1], pi->primitives.gradients.P[2]); + printf("WL': %g %g %g %g %g\n", Wi[0], Wi[1], Wi[2], Wi[3], Wi[4]); + printf("WR: %g %g %g %g %g\n", pj->primitives.rho, pj->primitives.v[0], + pj->primitives.v[1], pj->primitives.v[2], pj->primitives.P); +#ifdef USE_GRADIENTS + printf("dWR: %g %g %g %g %g\n", dWj[0], dWj[1], dWj[2], dWj[3], dWj[4]); +#endif + printf("gradWR[0]: %g %g %g\n", pj->primitives.gradients.rho[0], + pj->primitives.gradients.rho[1], pj->primitives.gradients.rho[2]); + printf("gradWR[1]: %g %g %g\n", pj->primitives.gradients.v[0][0], + pj->primitives.gradients.v[0][1], pj->primitives.gradients.v[0][2]); + printf("gradWR[2]: %g %g %g\n", pj->primitives.gradients.v[1][0], + pj->primitives.gradients.v[1][1], pj->primitives.gradients.v[1][2]); + printf("gradWR[3]: %g %g %g\n", pj->primitives.gradients.v[2][0], + pj->primitives.gradients.v[2][1], pj->primitives.gradients.v[2][2]); + printf("gradWR[4]: %g %g %g\n", pj->primitives.gradients.P[0], + pj->primitives.gradients.P[1], pj->primitives.gradients.P[2]); + printf("WR': %g %g %g %g %g\n", Wj[0], Wj[1], Wj[2], Wj[3], Wj[4]); + error("Negative density or pressure!\n"); + } + + float totflux[5]; + riemann_solve_for_flux(Wi, Wj, n_unit, vij, totflux); + + /* Update conserved variables */ + /* eqn. (16) */ + pi->conserved.flux.mass -= mindt * A * totflux[0]; + pi->conserved.flux.momentum[0] -= mindt * A * totflux[1]; + pi->conserved.flux.momentum[1] -= mindt * A * totflux[2]; + pi->conserved.flux.momentum[2] -= mindt * A * totflux[3]; + pi->conserved.flux.energy -= mindt * A * totflux[4]; + +#ifndef SHADOWFAX_TOTAL_ENERGY + float ekin = 0.5f * (pi->primitives.v[0] * pi->primitives.v[0] + + pi->primitives.v[1] * pi->primitives.v[1] + + pi->primitives.v[2] * pi->primitives.v[2]); + pi->conserved.flux.energy += mindt * A * totflux[1] * pi->primitives.v[0]; + pi->conserved.flux.energy += mindt * A * totflux[2] * pi->primitives.v[1]; + pi->conserved.flux.energy += mindt * A * totflux[3] * pi->primitives.v[2]; + pi->conserved.flux.energy -= mindt * A * totflux[0] * ekin; +#endif + + /* here is how it works: + Mode will only be 1 if both particles are ACTIVE and they are in the same + cell. In this case, this method IS the flux calculation for particle j, and + we HAVE TO UPDATE it. + Mode 0 can mean several things: it can mean that particle j is INACTIVE, in + which case we NEED TO UPDATE it, since otherwise the flux is lost from the + system and the conserved variable is not conserved. + It can also mean that particle j sits in another cell and is ACTIVE. In + this case, the flux exchange for particle j is done TWICE and we SHOULD NOT + UPDATE particle j. + ==> we update particle j if (MODE IS 1) OR (j IS INACTIVE) + */ + if (mode == 1 || pj->force.active == 0) { + pj->conserved.flux.mass += mindt * A * totflux[0]; + pj->conserved.flux.momentum[0] += mindt * A * totflux[1]; + pj->conserved.flux.momentum[1] += mindt * A * totflux[2]; + pj->conserved.flux.momentum[2] += mindt * A * totflux[3]; + pj->conserved.flux.energy += mindt * A * totflux[4]; + +#ifndef SHADOWFAX_TOTAL_ENERGY + ekin = 0.5f * (pj->primitives.v[0] * pj->primitives.v[0] + + pj->primitives.v[1] * pj->primitives.v[1] + + pj->primitives.v[2] * pj->primitives.v[2]); + pj->conserved.flux.energy -= mindt * A * totflux[1] * pj->primitives.v[0]; + pj->conserved.flux.energy -= mindt * A * totflux[2] * pj->primitives.v[1]; + pj->conserved.flux.energy -= mindt * A * totflux[3] * pj->primitives.v[2]; + pj->conserved.flux.energy += mindt * A * totflux[0] * ekin; +#endif + } +} + +/** + * @brief Flux calculation between particle i and particle j + * + * This method calls runner_iact_fluxes_common with mode 1. + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_force( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 1); +} + +/** + * @brief Flux calculation between particle i and particle j: non-symmetric + * version + * + * This method calls runner_iact_fluxes_common with mode 0. + * + * @param r2 Squared distance between particle i and particle j. + * @param dx Distance vector between the particles (dx = pi->x - pj->x). + * @param hi Smoothing length of particle i. + * @param hj Smoothing length of particle j. + * @param pi Particle i. + * @param pj Particle j. + */ +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_force( + float r2, float *dx, float hi, float hj, struct part *pi, struct part *pj) { + + runner_iact_fluxes_common(r2, dx, hi, hj, pi, pj, 0); +} + +//// EMPTY VECTORIZED VERSIONS (gradients methods are missing...) + +__attribute__((always_inline)) INLINE static void runner_iact_vec_density( + float *R2, float *Dx, float *Hi, float *Hj, struct part **pi, + struct part **pj) {} + +__attribute__((always_inline)) INLINE static void +runner_iact_nonsym_vec_density(float *R2, float *Dx, float *Hi, float *Hj, + struct part **pi, struct part **pj) {} + +__attribute__((always_inline)) INLINE static void runner_iact_vec_force( + float *R2, float *Dx, float *Hi, float *Hj, struct part **pi, + struct part **pj) {} + +__attribute__((always_inline)) INLINE static void runner_iact_nonsym_vec_force( + float *R2, float *Dx, float *Hi, float *Hj, struct part **pi, + struct part **pj) {} diff --git a/src/hydro/Shadowswift/hydro_io.h b/src/hydro/Shadowswift/hydro_io.h new file mode 100644 index 0000000000000000000000000000000000000000..de45b5d68c78f96cee3030eadef4b4410e550c22 --- /dev/null +++ b/src/hydro/Shadowswift/hydro_io.h @@ -0,0 +1,183 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "adiabatic_index.h" +#include "equation_of_state.h" +#include "hydro_gradients.h" +#include "hydro_slope_limiters.h" +#include "io_properties.h" +#include "riemann.h" + +/** + * @brief Specifies which particle fields to read from a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to read. + * @param num_fields The number of i/o fields to read. + */ +void hydro_read_particles(struct part* parts, struct io_props* list, + int* num_fields) { + + *num_fields = 8; + + /* List what we want to read */ + list[0] = io_make_input_field("Coordinates", DOUBLE, 3, COMPULSORY, + UNIT_CONV_LENGTH, parts, x); + list[1] = io_make_input_field("Velocities", FLOAT, 3, COMPULSORY, + UNIT_CONV_SPEED, parts, v); + list[2] = io_make_input_field("Masses", FLOAT, 1, COMPULSORY, UNIT_CONV_MASS, + parts, conserved.mass); + list[3] = io_make_input_field("SmoothingLength", FLOAT, 1, COMPULSORY, + UNIT_CONV_LENGTH, parts, h); + list[4] = io_make_input_field("InternalEnergy", FLOAT, 1, COMPULSORY, + UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, + conserved.energy); + list[5] = io_make_input_field("ParticleIDs", ULONGLONG, 1, COMPULSORY, + UNIT_CONV_NO_UNITS, parts, id); + list[6] = io_make_input_field("Accelerations", FLOAT, 3, OPTIONAL, + UNIT_CONV_ACCELERATION, parts, a_hydro); + list[7] = io_make_input_field("Density", FLOAT, 1, OPTIONAL, + UNIT_CONV_DENSITY, parts, primitives.rho); +} + +/** + * @brief Get the internal energy of a particle + * + * @param e #engine. + * @param p Particle. + * @return Internal energy of the particle + */ +float convert_u(struct engine* e, struct part* p) { + if (p->primitives.rho > 0.) { + return gas_internal_energy_from_pressure(p->primitives.rho, + p->primitives.P); + } else { + return 0.; + } +} + +/** + * @brief Get the entropic function of a particle + * + * @param e #engine. + * @param p Particle. + * @return Entropic function of the particle + */ +float convert_A(struct engine* e, struct part* p) { + if (p->primitives.rho > 0.) { + return gas_entropy_from_pressure(p->primitives.rho, p->primitives.P); + } else { + return 0.; + } +} + +/** + * @brief Get the total energy of a particle + * + * @param e #engine. + * @param p Particle. + * @return Total energy of the particle + */ +float convert_Etot(struct engine* e, struct part* p) { +#ifdef SHADOWFAX_TOTAL_ENERGY + return p->conserved.energy; +#else + if (p->conserved.mass > 0.) { + float momentum2; + + momentum2 = p->conserved.momentum[0] * p->conserved.momentum[0] + + p->conserved.momentum[1] * p->conserved.momentum[1] + + p->conserved.momentum[2] * p->conserved.momentum[2]; + + return p->conserved.energy + 0.5f * momentum2 / p->conserved.mass; + } else { + return 0.; + } +#endif +} + +/** + * @brief Specifies which particle fields to write to a dataset + * + * @param parts The particle array. + * @param list The list of i/o properties to write. + * @param num_fields The number of i/o fields to write. + */ +void hydro_write_particles(struct part* parts, struct io_props* list, + int* num_fields) { + + *num_fields = 13; + + /* List what we want to write */ + list[0] = io_make_output_field("Coordinates", DOUBLE, 3, UNIT_CONV_LENGTH, + parts, x); + list[1] = io_make_output_field("Velocities", FLOAT, 3, UNIT_CONV_SPEED, parts, + primitives.v); + list[2] = io_make_output_field("Masses", FLOAT, 1, UNIT_CONV_MASS, parts, + conserved.mass); + list[3] = io_make_output_field("SmoothingLength", FLOAT, 1, UNIT_CONV_LENGTH, + parts, h); + list[4] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1, + UNIT_CONV_ENERGY_PER_UNIT_MASS, + parts, primitives.P, convert_u); + list[5] = io_make_output_field("ParticleIDs", ULONGLONG, 1, + UNIT_CONV_NO_UNITS, parts, id); + list[6] = io_make_output_field("Acceleration", FLOAT, 3, + UNIT_CONV_ACCELERATION, parts, a_hydro); + list[7] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, + primitives.rho); + list[8] = io_make_output_field("Volume", FLOAT, 1, UNIT_CONV_VOLUME, parts, + cell.volume); + list[9] = io_make_output_field("GradDensity", FLOAT, 3, UNIT_CONV_DENSITY, + parts, primitives.gradients.rho); + list[10] = io_make_output_field_convert_part( + "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY, parts, primitives.P, convert_A); + list[11] = io_make_output_field("Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, + parts, primitives.P); + list[12] = + io_make_output_field_convert_part("TotEnergy", FLOAT, 1, UNIT_CONV_ENERGY, + parts, conserved.energy, convert_Etot); +} + +/** + * @brief Writes the current model of SPH to the file + * @param h_grpsph The HDF5 group in which to write + */ +void writeSPHflavour(hid_t h_grpsph) { + /* Gradient information */ + io_write_attribute_s(h_grpsph, "Gradient reconstruction model", + HYDRO_GRADIENT_IMPLEMENTATION); + + /* Slope limiter information */ + io_write_attribute_s(h_grpsph, "Cell wide slope limiter model", + HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION); + io_write_attribute_s(h_grpsph, "Piecewise slope limiter model", + HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION); + + /* Riemann solver information */ + io_write_attribute_s(h_grpsph, "Riemann solver type", + RIEMANN_SOLVER_IMPLEMENTATION); +} + +/** + * @brief Are we writing entropy in the internal energy field ? + * + * @return 1 if entropy is in 'internal energy', 0 otherwise. + */ +int writeEntropyFlag() { return 0; } diff --git a/src/hydro/Shadowswift/hydro_part.h b/src/hydro/Shadowswift/hydro_part.h new file mode 100644 index 0000000000000000000000000000000000000000..43237d9c80a5dfa5bed2fe409281b4f89b6aa172 --- /dev/null +++ b/src/hydro/Shadowswift/hydro_part.h @@ -0,0 +1,185 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk) + * Matthieu Schaller (matthieu.schaller@durham.ac.uk) + * 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "cooling_struct.h" +#include "voronoi_cell.h" + +/* Extra particle data not needed during the computation. */ +struct xpart { + + /* Offset between current position and position at last tree rebuild. */ + float x_diff[3]; + + /* Velocity at the last full step. */ + float v_full[3]; + + /* Additional data used to record cooling information */ + struct cooling_xpart_data cooling_data; + +} SWIFT_STRUCT_ALIGN; + +/* Data of a single particle. */ +struct part { + + /* Particle ID. */ + long long id; + + /* Associated gravitas. */ + struct gpart *gpart; + + /* Particle position. */ + double x[3]; + + /* Particle predicted velocity. */ + float v[3]; + + /* Particle acceleration. */ + float a_hydro[3]; + + /* Particle cutoff radius. */ + float h; + + /* The primitive hydrodynamical variables. */ + struct { + + /* Fluid velocity. */ + float v[3]; + + /* Density. */ + float rho; + + /* Pressure. */ + float P; + + /* Gradients of the primitive variables. */ + struct { + + /* Density gradients. */ + float rho[3]; + + /* Fluid velocity gradients. */ + float v[3][3]; + + /* Pressure gradients. */ + float P[3]; + + } gradients; + + /* Quantities needed by the slope limiter. */ + struct { + + /* Extreme values of the density among the neighbours. */ + float rho[2]; + + /* Extreme values of the fluid velocity among the neighbours. */ + float v[3][2]; + + /* Extreme values of the pressure among the neighbours. */ + float P[2]; + + /* Maximal distance to all neighbouring faces. */ + float maxr; + + } limiter; + + } primitives; + + /* The conserved hydrodynamical variables. */ + struct { + + /* Fluid momentum. */ + float momentum[3]; + + /* Fluid mass (this field already exists outside of this struct as well). */ + float mass; + + /* Fluid thermal energy (not per unit mass!). */ + float energy; + + /* Fluxes. */ + struct { + + /* Mass flux. */ + float mass; + + /* Momentum flux. */ + float momentum[3]; + + /* Energy flux. */ + float energy; + + } flux; + + } conserved; + + /* Variables used for timestep calculation (currently not used). */ + struct { + + /* Maximum fluid velocity among all neighbours. */ + float vmax; + + } timestepvars; + + /* Quantities used during the volume (=density) loop. */ + struct { + + /* Derivative of particle number density. */ + float wcount_dh; + + /* Particle number density. */ + float wcount; + + } density; + + /* Quantities used during the force loop. */ + struct { + + /* Needed to drift the primitive variables. */ + float h_dt; + + /* Physical time step of the particle. */ + float dt; + + /* Active flag. */ + char active; + + /* Actual velocity of the particle. */ + float v_full[3]; + + } force; + + /* Time-step length */ + timebin_t time_bin; + +#ifdef SWIFT_DEBUG_CHECKS + + /* Time of the last drift */ + integertime_t ti_drift; + + /* Time of the last kick */ + integertime_t ti_kick; + +#endif + + /* Voronoi cell. */ + struct voronoi_cell cell; + +} SWIFT_STRUCT_ALIGN; diff --git a/src/hydro/Shadowswift/hydro_slope_limiters.h b/src/hydro/Shadowswift/hydro_slope_limiters.h new file mode 100644 index 0000000000000000000000000000000000000000..a443007b4df3833964b7ec1780e2bc55bf02a03e --- /dev/null +++ b/src/hydro/Shadowswift/hydro_slope_limiters.h @@ -0,0 +1,94 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_HYDRO_SLOPE_LIMITERS_H +#define SWIFT_HYDRO_SLOPE_LIMITERS_H + +#include "dimension.h" +#include "kernel_hydro.h" + +#ifdef SHADOWFAX_SLOPE_LIMITER_PER_FACE + +#define HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION \ + "GIZMO piecewise slope limiter (Hopkins 2015)" +#include "hydro_slope_limiters_face.h" + +#else + +#define HYDRO_SLOPE_LIMITER_FACE_IMPLEMENTATION "No piecewise slope limiter" + +/** + * @brief Slope limit the slopes at the interface between two particles + * + * @param Wi Hydrodynamic variables of particle i. + * @param Wj Hydrodynamic variables of particle j. + * @param dWi Difference between the hydrodynamic variables of particle i at the + * position of particle i and at the interface position. + * @param dWj Difference between the hydrodynamic variables of particle j at the + * position of particle j and at the interface position. + * @param xij_i Relative position vector of the interface w.r.t. particle i. + * @param xij_j Relative position vector of the interface w.r.t. partilce j. + * @param r Distance between particle i and particle j. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_face( + float *Wi, float *Wj, float *dWi, float *dWj, float *xij_i, float *xij_j, + float r) {} + +#endif + +#ifdef SHADOWFAX_SLOPE_LIMITER_CELL_WIDE + +#define HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION \ + "Cell wide slope limiter (Springel 2010)" +#include "hydro_slope_limiters_cell.h" + +#else + +#define HYDRO_SLOPE_LIMITER_CELL_IMPLEMENTATION "No cell wide slope limiter" + +/** + * @brief Initialize variables for the cell wide slope limiter + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell_init( + struct part *p) {} + +/** + * @brief Collect information for the cell wide slope limiter during the + * neighbour loop + * + * @param pi Particle i. + * @param pj Particle j. + * @param r Distance between particle i and particle j. + */ +__attribute__((always_inline)) INLINE static void +hydro_slope_limit_cell_collect(struct part *pi, struct part *pj, float r) {} + +/** + * @brief Slope limit cell gradients + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell( + struct part *p) {} + +#endif + +#endif // SWIFT_HYDRO_SLOPE_LIMITERS_H diff --git a/src/hydro/Shadowswift/hydro_slope_limiters_cell.h b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h new file mode 100644 index 0000000000000000000000000000000000000000..a7b3f7511fa7cd247fd9d2399cd200d8d943630e --- /dev/null +++ b/src/hydro/Shadowswift/hydro_slope_limiters_cell.h @@ -0,0 +1,141 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include <float.h> + +/** + * @brief Initialize variables for the cell wide slope limiter + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell_init( + struct part* p) { + + p->primitives.limiter.rho[0] = FLT_MAX; + p->primitives.limiter.rho[1] = -FLT_MAX; + p->primitives.limiter.v[0][0] = FLT_MAX; + p->primitives.limiter.v[0][1] = -FLT_MAX; + p->primitives.limiter.v[1][0] = FLT_MAX; + p->primitives.limiter.v[1][1] = -FLT_MAX; + p->primitives.limiter.v[2][0] = FLT_MAX; + p->primitives.limiter.v[2][1] = -FLT_MAX; + p->primitives.limiter.P[0] = FLT_MAX; + p->primitives.limiter.P[1] = -FLT_MAX; + + p->primitives.limiter.maxr = -FLT_MAX; +} + +/** + * @brief Collect information for the cell wide slope limiter during the + * neighbour loop + * + * @param pi Particle i. + * @param pj Particle j. + * @param r Distance between particle i and particle j. + */ +__attribute__((always_inline)) INLINE static void +hydro_slope_limit_cell_collect(struct part* pi, struct part* pj, float r) { + + /* basic slope limiter: collect the maximal and the minimal value for the + * primitive variables among the ngbs */ + pi->primitives.limiter.rho[0] = + fmin(pj->primitives.rho, pi->primitives.limiter.rho[0]); + pi->primitives.limiter.rho[1] = + fmax(pj->primitives.rho, pi->primitives.limiter.rho[1]); + + pi->primitives.limiter.v[0][0] = + fmin(pj->primitives.v[0], pi->primitives.limiter.v[0][0]); + pi->primitives.limiter.v[0][1] = + fmax(pj->primitives.v[0], pi->primitives.limiter.v[0][1]); + pi->primitives.limiter.v[1][0] = + fmin(pj->primitives.v[1], pi->primitives.limiter.v[1][0]); + pi->primitives.limiter.v[1][1] = + fmax(pj->primitives.v[1], pi->primitives.limiter.v[1][1]); + pi->primitives.limiter.v[2][0] = + fmin(pj->primitives.v[2], pi->primitives.limiter.v[2][0]); + pi->primitives.limiter.v[2][1] = + fmax(pj->primitives.v[2], pi->primitives.limiter.v[2][1]); + + pi->primitives.limiter.P[0] = + fmin(pj->primitives.P, pi->primitives.limiter.P[0]); + pi->primitives.limiter.P[1] = + fmax(pj->primitives.P, pi->primitives.limiter.P[1]); + + pi->primitives.limiter.maxr = fmax(r, pi->primitives.limiter.maxr); +} + +/** + * @brief Apply the cell wide slope limiter to the gradient of a single quantity + * + * This corresponds to equation (B2) in Hopkins (2015). + * + * @param grad Gradient to slope limit + * @param qval Value of the quantity at the cell generator + * @param qmin Minimal value of the quantity among all cell neighbours + * @param qmax Maximal value of the quantity among all cell neighbours + * @param maxr Maximal distance between the generator and all of its neighbours + */ +__attribute__((always_inline)) INLINE static void +hydro_slope_limit_cell_quantity(float* grad, float qval, float qmin, float qmax, + float maxr) { + + float gradtrue, gradmax, gradmin, alpha; + + gradtrue = sqrtf(grad[0] * grad[0] + grad[1] * grad[1] + grad[2] * grad[2]); + if (gradtrue) { + gradtrue *= maxr; + gradmax = qmax - qval; + gradmin = qval - qmin; + alpha = fmin(1.0f, fmin(gradmax / gradtrue, gradmin / gradtrue)); + grad[0] *= alpha; + grad[1] *= alpha; + grad[2] *= alpha; + } +} + +/** + * @brief Slope limit cell gradients + * + * @param p Particle. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_cell( + struct part* p) { + + hydro_slope_limit_cell_quantity( + p->primitives.gradients.rho, p->primitives.rho, + p->primitives.limiter.rho[0], p->primitives.limiter.rho[1], + p->primitives.limiter.maxr); + + hydro_slope_limit_cell_quantity( + p->primitives.gradients.v[0], p->primitives.v[0], + p->primitives.limiter.v[0][0], p->primitives.limiter.v[0][1], + p->primitives.limiter.maxr); + hydro_slope_limit_cell_quantity( + p->primitives.gradients.v[1], p->primitives.v[1], + p->primitives.limiter.v[1][0], p->primitives.limiter.v[1][1], + p->primitives.limiter.maxr); + hydro_slope_limit_cell_quantity( + p->primitives.gradients.v[2], p->primitives.v[2], + p->primitives.limiter.v[2][0], p->primitives.limiter.v[2][1], + p->primitives.limiter.maxr); + + hydro_slope_limit_cell_quantity( + p->primitives.gradients.P, p->primitives.P, p->primitives.limiter.P[0], + p->primitives.limiter.P[1], p->primitives.limiter.maxr); +} diff --git a/src/hydro/Shadowswift/hydro_slope_limiters_face.h b/src/hydro/Shadowswift/hydro_slope_limiters_face.h new file mode 100644 index 0000000000000000000000000000000000000000..7ae5dd2eb073d9aae8ab6f2efffdf8df15b4bb4a --- /dev/null +++ b/src/hydro/Shadowswift/hydro_slope_limiters_face.h @@ -0,0 +1,121 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +/** + * @brief Slope limit a single quantity at the interface + * + * @param phi_i Value of the quantity at the particle position. + * @param phi_j Value of the quantity at the neighbouring particle position. + * @param phi_mid0 Extrapolated value of the quantity at the interface position. + * @param xij_norm Distance between the particle position and the interface + * position. + * @param r Distance between the particle and its neighbour. + * @return The slope limited difference between the quantity at the particle + * position and the quantity at the interface position. + */ +__attribute__((always_inline)) INLINE static float +hydro_slope_limit_face_quantity(float phi_i, float phi_j, float phi_mid0, + float xij_norm, float r) { + + float delta1, delta2, phimin, phimax, phibar, phiplus, phiminus, phi_mid; + const float psi1 = 0.5f; + const float psi2 = 0.25f; + + if (phi_i == phi_j) { + return 0.0f; + } + + delta1 = psi1 * fabs(phi_i - phi_j); + delta2 = psi2 * fabs(phi_i - phi_j); + + phimin = fmin(phi_i, phi_j); + phimax = fmax(phi_i, phi_j); + + phibar = phi_i + xij_norm / r * (phi_j - phi_i); + + /* if sign(phimax+delta1) == sign(phimax) */ + if ((phimax + delta1) * phimax > 0.0f) { + phiplus = phimax + delta1; + } else { + phiplus = phimax / (1.0f + delta1 / fabs(phimax)); + } + + /* if sign(phimin-delta1) == sign(phimin) */ + if ((phimin - delta1) * phimin > 0.0f) { + phiminus = phimin - delta1; + } else { + phiminus = phimin / (1.0f + delta1 / fabs(phimin)); + } + + if (phi_i < phi_j) { + phi_mid = fmax(phiminus, fmin(phibar + delta2, phi_mid0)); + } else { + phi_mid = fmin(phiplus, fmax(phibar - delta2, phi_mid0)); + } + + return phi_mid - phi_i; +} + +/** + * @brief Slope limit the slopes at the interface between two particles + * + * @param Wi Hydrodynamic variables of particle i. + * @param Wj Hydrodynamic variables of particle j. + * @param dWi Difference between the hydrodynamic variables of particle i at the + * position of particle i and at the interface position. + * @param dWj Difference between the hydrodynamic variables of particle j at the + * position of particle j and at the interface position. + * @param xij_i Relative position vector of the interface w.r.t. particle i. + * @param xij_j Relative position vector of the interface w.r.t. partilce j. + * @param r Distance between particle i and particle j. + */ +__attribute__((always_inline)) INLINE static void hydro_slope_limit_face( + float *Wi, float *Wj, float *dWi, float *dWj, float *xij_i, float *xij_j, + float r) { + + float xij_i_norm, xij_j_norm; + + xij_i_norm = + sqrtf(xij_i[0] * xij_i[0] + xij_i[1] * xij_i[1] + xij_i[2] * xij_i[2]); + + xij_j_norm = + sqrtf(xij_j[0] * xij_j[0] + xij_j[1] * xij_j[1] + xij_j[2] * xij_j[2]); + + dWi[0] = hydro_slope_limit_face_quantity(Wi[0], Wj[0], Wi[0] + dWi[0], + xij_i_norm, r); + dWi[1] = hydro_slope_limit_face_quantity(Wi[1], Wj[1], Wi[1] + dWi[1], + xij_i_norm, r); + dWi[2] = hydro_slope_limit_face_quantity(Wi[2], Wj[2], Wi[2] + dWi[2], + xij_i_norm, r); + dWi[3] = hydro_slope_limit_face_quantity(Wi[3], Wj[3], Wi[3] + dWi[3], + xij_i_norm, r); + dWi[4] = hydro_slope_limit_face_quantity(Wi[4], Wj[4], Wi[4] + dWi[4], + xij_i_norm, r); + + dWj[0] = hydro_slope_limit_face_quantity(Wj[0], Wi[0], Wj[0] + dWj[0], + xij_j_norm, r); + dWj[1] = hydro_slope_limit_face_quantity(Wj[1], Wi[1], Wj[1] + dWj[1], + xij_j_norm, r); + dWj[2] = hydro_slope_limit_face_quantity(Wj[2], Wi[2], Wj[2] + dWj[2], + xij_j_norm, r); + dWj[3] = hydro_slope_limit_face_quantity(Wj[3], Wi[3], Wj[3] + dWj[3], + xij_j_norm, r); + dWj[4] = hydro_slope_limit_face_quantity(Wj[4], Wi[4], Wj[4] + dWj[4], + xij_j_norm, r); +} diff --git a/src/hydro/Shadowswift/voronoi1d_algorithm.h b/src/hydro/Shadowswift/voronoi1d_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..74cc5f1dbf3a2d72df55ce73de0321b5493193a7 --- /dev/null +++ b/src/hydro/Shadowswift/voronoi1d_algorithm.h @@ -0,0 +1,192 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_ALGORITHM_H +#define SWIFT_VORONOIXD_ALGORITHM_H + +#include <math.h> +#include <stdlib.h> +#include "error.h" +#include "inline.h" +#include "voronoi1d_cell.h" + +/** + * @brief Store the extents of the simulation box in the global variables. + * + * @param anchor Corner of the simulation box with the lowest coordinate values. + * @param side Side lengths of the simulation box. + */ +__attribute__((always_inline)) INLINE static void voronoi_set_box( + const float *anchor, const float *side) {} + +/** + * @brief Initialize a 1D Voronoi cell. + * + * Sets the positions of left and right neighbours to very large values, the + * generator position to the given particle position, and all other quantities + * to zero. + * + * @param cell 1D Voronoi cell to initialize. + * @param x Position of the generator of the cell. + * @param anchor Anchor of the simulation box. + * @param side Side lengths of the simulation box. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_init( + struct voronoi_cell *cell, const double *x, const double *anchor, + const double *side) { + cell->x = x[0]; + cell->xL = anchor[0] - cell->x; + cell->xR = anchor[0] + side[0] - cell->x; + cell->idL = 0; + cell->idR = 0; + cell->volume = 0.0f; + cell->centroid = 0.0f; +} + +/** + * @brief Interact a 1D Voronoi cell with a particle with given relative + * position and ID. + * + * This method checks if the given relative position is closer to the cell + * generator than the current left or right neighbour and updates neighbours + * accordingly. + * + * @param cell 1D Voronoi cell. + * @param dx Relative position of the interacting generator w.r.t. the cell + * generator (in fact: dx = generator - neighbour). + * @param id ID of the interacting neighbour. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_interact( + struct voronoi_cell *cell, const float *dx, unsigned long long id) { + + /* Check for stupidity */ + if (dx[0] == 0.0f) { + error("Cannot interact a Voronoi cell generator with itself!"); + } + + if (-dx[0] < 0.0f) { + /* New left neighbour? */ + if (-dx[0] > cell->xL) { + cell->xL = -dx[0]; + cell->idL = id; + } + } else { + /* New right neighbour? */ + if (-dx[0] < cell->xR) { + cell->xR = -dx[0]; + cell->idR = id; + } + } +} + +/** + * @brief Finalize a 1D Voronoi cell. + * + * Calculates the relative positions of the midpoints of the faces (which in + * this case are just the midpoints of the segments connecting the generator + * with the two neighbours) w.r.t. the generator, and the cell volume (length) + * and centroid (midpoint of the segment connecting the midpoints of the faces). + * This function returns the maximal radius at which a particle could still + * change the structure of the cell, i.e. twice the largest distance between + * the cell generator and one of its faces. If the cell has been interacted with + * all neighbours within this radius, we know for sure that the cell is + * complete. + * + * @param cell 1D Voronoi cell. + * @return Maximal radius that could still change the structure of the cell. + */ +__attribute__((always_inline)) INLINE float voronoi_cell_finalize( + struct voronoi_cell *cell) { + + float xL, xR; + float max_radius; + + max_radius = fmax(-cell->xL, cell->xR); + cell->xL = xL = 0.5f * cell->xL; + cell->xR = xR = 0.5f * cell->xR; + + cell->volume = xR - xL; + cell->centroid = cell->x + 0.5f * (xL + xR); + + return max_radius; +} + +/** + * @brief Get the oriented surface area and midpoint of the face between a + * 1D Voronoi cell and the given neighbour. + * + * This function also checks if the given neighbour is in fact a neighbour of + * this cell. Since we perform gradient and flux calculations for all neighbour + * pairs within the smoothing length, which assumes the cell to be spherical, + * it can happen that this is not the case. It is the responsibility of the + * routine that calls this function to check for a zero return value and + * deal with it appropriately. + * + * For this specific case, we simply check if the neighbour is the left or + * right neighbour and set the surface area to 1. The midpoint is set to the + * relative position vector of the appropriate face. + * + * @param cell 1D Voronoi cell. + * @param ngb ID of a particle that is possibly a neighbour of this cell. + * @param midpoint Array to store the relative position of the face in. + * @return 0 if the given neighbour is not a neighbour, surface area 1.0f + * otherwise. + */ +__attribute__((always_inline)) INLINE float voronoi_get_face( + const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) { + + if (ngb != cell->idL && ngb != cell->idR) { + /* this is perfectly possible: we interact with all particles within the + smoothing length, and they do not need to be all neighbours. + If this happens, we return 0, so that the flux method can return */ + return 0.0f; + } + + if (ngb == cell->idL) { + /* Left face */ + midpoint[0] = cell->xL; + } else { + /* Right face */ + midpoint[0] = cell->xR; + } + /* The other components of midpoint are just zero */ + midpoint[1] = 0.0f; + midpoint[2] = 0.0f; + + return 1.0f; +} + +/** + * @brief Get the centroid of a 1D Voronoi cell. + * + * We store only the relevant coordinate of the centroid, but need to return + * a 3D vector. + * + * @param cell 1D Voronoi cell. + * @param centroid Array to store the centroid in. + */ +__attribute__((always_inline)) INLINE void voronoi_get_centroid( + const struct voronoi_cell *cell, float *centroid) { + + centroid[0] = cell->centroid; + centroid[1] = 0.0f; + centroid[2] = 0.0f; +} + +#endif // SWIFT_VORONOIXD_ALGORITHM_H diff --git a/src/hydro/Shadowswift/voronoi1d_cell.h b/src/hydro/Shadowswift/voronoi1d_cell.h new file mode 100644 index 0000000000000000000000000000000000000000..29fff097c4129b1b3ac81f6e1d4291c4efcbce90 --- /dev/null +++ b/src/hydro/Shadowswift/voronoi1d_cell.h @@ -0,0 +1,48 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_CELL_H +#define SWIFT_VORONOIXD_CELL_H + +/* 1D Voronoi cell */ +struct voronoi_cell { + + /* The position of the generator of the cell. */ + double x; + + /* The position of the left neighbour of the cell. */ + double xL; + + /* The position of the right neighbour of the cell. */ + double xR; + + /* The particle ID of the left neighbour. */ + unsigned long long idL; + + /* The particle ID of the right neighbour. */ + unsigned long long idR; + + /* The "volume" of the 1D cell. */ + float volume; + + /* The centroid of the cell. */ + float centroid; +}; + +#endif // SWIFT_VORONOIXD_CELL_H diff --git a/src/hydro/Shadowswift/voronoi2d_algorithm.h b/src/hydro/Shadowswift/voronoi2d_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..947595107a4d3705c52c982da0dc8e54a5c2376e --- /dev/null +++ b/src/hydro/Shadowswift/voronoi2d_algorithm.h @@ -0,0 +1,547 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_ALGORITHM_H +#define SWIFT_VORONOIXD_ALGORITHM_H + +#include <float.h> +#include <math.h> +#include <stdlib.h> +#include "error.h" +#include "inline.h" +#include "minmax.h" +#include "voronoi2d_cell.h" + +/* Check if the number of vertices exceeds the maximal allowed number */ +#define VORONOI_CHECK_SIZE() \ + if (nvert > VORONOI2D_MAXNUMVERT) { \ + error("Too many vertices!"); \ + } + +/* IDs used to keep track of cells neighbouring walls of the simulation box + This will only work if these IDs are never used for actual particles (which + in practice means you want to have less than 2^63-4 (~9e18) particles in your + simulation) */ +#define VORONOI2D_BOX_LEFT 18446744073709551602llu +#define VORONOI2D_BOX_RIGHT 18446744073709551603llu +#define VORONOI2D_BOX_TOP 18446744073709551604llu +#define VORONOI2D_BOX_BOTTOM 18446744073709551605llu + +#define VORONOI2D_TOLERANCE 1.e-6f + +/** + * @brief Initialize a 2D Voronoi cell. + * + * @param cell 2D Voronoi cell to initialize. + * @param x Position of the generator of the cell. + * @param anchor Anchor of the simulation box containing all particles. + * @param side Side lengths of the simulation box containing all particles. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_init( + struct voronoi_cell *cell, const double *x, const double *anchor, + const double *side) { + + /* Set the position of the generator of the cell (for reference) */ + cell->x[0] = x[0]; + cell->x[1] = x[1]; + + /* Initialize the cell as a box with the same extents as the simulation box + (note: all vertex coordinates are relative w.r.t. the cell generator) */ + cell->nvert = 4; + + cell->vertices[0][0] = anchor[0] - cell->x[0]; + cell->vertices[0][1] = anchor[1] - cell->x[1]; + + cell->vertices[1][0] = anchor[0] - cell->x[0]; + cell->vertices[1][1] = anchor[1] + side[1] - cell->x[1]; + + cell->vertices[2][0] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[2][1] = anchor[1] + side[1] - cell->x[1]; + + cell->vertices[3][0] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[3][1] = anchor[1] - cell->x[1]; + + /* The neighbours are ordered such that neighbour i shares the face in between + vertices i and i+1 (with last vertex + 1 = first vertex) + We walk around the cell in clockwise direction */ + cell->ngbs[0] = VORONOI2D_BOX_LEFT; + cell->ngbs[1] = VORONOI2D_BOX_TOP; + cell->ngbs[2] = VORONOI2D_BOX_RIGHT; + cell->ngbs[3] = VORONOI2D_BOX_BOTTOM; + + /* These variables are initialized to zero, we will compute them after the + neighbour iteration has finished */ + cell->volume = 0.0f; + cell->centroid[0] = 0.0f; + cell->centroid[1] = 0.0f; +} + +/** + * @brief Interact a 2D Voronoi cell with a particle with given relative + * position and ID. + * + * @param cell 2D Voronoi cell. + * @param dx Relative position of the interacting generator w.r.t. the cell + * generator (in fact: dx = generator - neighbour). + * @param id ID of the interacting neighbour. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_interact( + struct voronoi_cell *cell, const float *dx, unsigned long long id) { + + /* variables used for geometrical tests */ + float half_dx[2]; + float r2; + /* variables used to store test results */ + float test, b1, b2, a1, a2; + /* general loop index */ + int i; + /* variables used to store indices of intersected edges */ + int index_above1, index_above2; + int index_below1, index_below2; + /* variable used to store directionality in edge traversal */ + int increment; + /* new number of vertices and new vertex coordinates */ + int nvert; + float vertices[VORONOI2D_MAXNUMVERT][2]; + unsigned long long ngbs[VORONOI2D_MAXNUMVERT]; + + /* The process of cutting the current cell with the midline of the generator + and the given relative neighbour position proceeds in two steps: + - first we need to locate an edge of the current cell that is intersected + by this midline. Such an edge does not necessarily exist; in this case + the given neighbour is not an actual neighbour of this cell + - Once we have an intersected edge, we create a new edge starting at the + intersection point. We follow the edges connected to the intersected + edge until we find another intersected edge, and use its intersection + point as end point of the new edge. */ + + /* First, we set up some variables that are used to check if a vertex is above + or below the midplane. */ + + /* we need a vector with half the size of the vector joining generator and + neighbour, pointing to the neighbour */ + half_dx[0] = -0.5f * dx[0]; + half_dx[1] = -0.5f * dx[1]; + + /* we need the squared length of this vector */ + r2 = half_dx[0] * half_dx[0] + half_dx[1] * half_dx[1]; + + /* a vertex v = (vx, vy) is above the midline if + vx*half_dx[0] + vy*half_dx[1] > r2 + i.e., if the length of the projected vertex position is longer than the + length of the vector pointing to the closest point on the midline (both + vectors originate at the position of the generator) + the vertex is below the midline if the projected position vector is shorter + if the projected position vector has the same length, the vertex is on the + midline */ + + /* start testing a random vertex: the first one */ + test = cell->vertices[0][0] * half_dx[0] + cell->vertices[0][1] * half_dx[1] - + r2; + if (test < -VORONOI2D_TOLERANCE) { +/* vertex is below midline */ +#ifdef VORONOI_VERBOSE + message("First vertex is below midline (%g %g --> %g)!", + cell->vertices[0][0] + cell->x[0], + cell->vertices[0][1] + cell->x[1], test); +#endif + + /* store the test result; we might need it to compute the intersection + coordinates */ + b1 = test; + + /* move on until we find a vertex that is above or on the midline */ + i = 1; + test = cell->vertices[i][0] * half_dx[0] + + cell->vertices[i][1] * half_dx[1] - r2; + while (i < cell->nvert && test < 0.) { + /* make sure we always store the latest test result */ + b1 = test; + ++i; + test = cell->vertices[i][0] * half_dx[0] + + cell->vertices[i][1] * half_dx[1] - r2; + } + + /* loop finished, there are two possibilities: + - i == cell->nvert, all vertices lie below the midline and the given + neighbour is not an actual neighbour of this cell + - test >= 0., we found a vertex above (or on) the midline */ + if (i == cell->nvert) { +/* the given neighbour is not an actual neighbour: exit the routine */ +#ifdef VORONOI_VERBOSE + message("Not a neighbour!"); +#endif + return; + } + + /* we have found an intersected edge: i-1 -> i + we store the index of the vertices above and below the midline, make sure + we store the test result for later intersection computation, and set the + increment to positive, so that we look for the other intersected edge in + clockwise direction */ + index_below1 = i - 1; + index_above1 = i; + a1 = test; + increment = 1; + } else { +/* vertex is above or on midline + in the case where it is on the midline, we count that as above as well: + the vertex will be removed, and a new vertex will be created at the same + position */ +#ifdef VORONOI_VERBOSE + message("First vertex is above midline (%g %g --> %g)!", + cell->vertices[0][0] + cell->x[0], + cell->vertices[0][1] + cell->x[1], test); +#endif + + /* store the test result */ + a1 = test; + + /* move on until we find a vertex that is below the midline */ + i = 1; + test = cell->vertices[i][0] * half_dx[0] + + cell->vertices[i][1] * half_dx[1] - r2; + while (i < cell->nvert && test > -VORONOI2D_TOLERANCE) { + /* make sure we always store the most recent test result */ + a1 = test; + ++i; + test = cell->vertices[i][0] * half_dx[0] + + cell->vertices[i][1] * half_dx[1] - r2; + } + + /* loop finished, there are two possibilities: + - i == cell->nvert, all vertices lie above the midline. This should + never happen. + - test <= 0., we found a vertex below (or on) the midline */ + if (i == cell->nvert) { + /* fatal error! */ + error("Could not find a vertex below the midline!"); + } + + /* we have found an intersected edge: i-1 -> i + we store the index of the vertices above and below the midline, make sure + we store the test result for later intersection computation, and set the + increment to negative, so that we look for the other intersected edge in + counterclockwise direction */ + index_below1 = i; + index_above1 = i - 1; + increment = -1; + b1 = test; + } + +#ifdef VORONOI_VERBOSE + message("First intersected edge: %g %g --> %g %g (%i --> %i)", + cell->vertices[index_below1][0] + cell->x[0], + cell->vertices[index_below1][1] + cell->x[1], + cell->vertices[index_above1][0] + cell->x[0], + cell->vertices[index_above1][1] + cell->x[1], index_below1, + index_above1); +#endif + + /* now we need to find the second intersected edge + we start from the vertex above (or on) the midline and search in the + direction opposite to the intersected edge direction until we find a vertex + below the midline */ + + /* we make sure we store the test result for the second vertex above the + midline as well, since we need this for intersection point computations + the second vertex can be equal to the first */ + a2 = a1; + i = index_above1 + increment; + if (i < 0) { + i = cell->nvert - 1; + } + if (i == cell->nvert) { + i = 0; + } + test = cell->vertices[i][0] * half_dx[0] + cell->vertices[i][1] * half_dx[1] - + r2; + /* this loop can never deadlock, as we know there is at least 1 vertex below + the midline */ + while (test > -VORONOI2D_TOLERANCE) { + /* make sure we always store the most recent test result */ + a2 = test; + i += increment; + if (i < 0) { + i = cell->nvert - 1; + } + if (i == cell->nvert) { + i = 0; + } + test = cell->vertices[i][0] * half_dx[0] + + cell->vertices[i][1] * half_dx[1] - r2; + } + + index_below2 = i; + index_above2 = i - increment; + if (index_above2 < 0) { + index_above2 = cell->nvert - 1; + } + if (index_above2 == cell->nvert) { + index_above2 = 0; + } + /* we also store the test result for the second vertex below the midline */ + b2 = test; + + if (index_above1 == index_above2 && index_below1 == index_below2) { + /* There can be only 1 vertex above or below the midline, but we need 2 + intersected edges, so if the vertices above the midline are the same, the + ones below need to be different and vice versa */ + error("Only 1 intersected edge found!"); + } + + /* there is exactly one degenerate case we have not addressed yet: the case + where index_above1 and index_above2 are the same and are on the midline. + In this case we don't want to create 2 new vertices. Instead, we just keep + index_above1, which basically means nothing happens at all and we can just + return */ + if (index_above1 == index_above2 && a1 == 0.) { + return; + } + + /* to make the code below more clear, we make sure index_above1 always holds + the first vertex to remove, and index_above2 the last one, in clockwise + order + This means we need to interchange 1 and 2 if we were searching in counter- + clockwise direction above */ + if (increment < 0) { + i = index_below1; + index_below1 = index_below2; + index_below2 = i; + i = index_above1; + index_above1 = index_above2; + index_above2 = i; + test = b1; + b1 = b2; + b2 = test; + test = a1; + a1 = a2; + a2 = test; + } + +#ifdef VORONOI_VERBOSE + message("First vertex below: %g %g (%i, %g)", + cell->vertices[index_below1][0] + cell->x[0], + cell->vertices[index_below1][1] + cell->x[1], index_below1, b1); + message("First vertex above: %g %g (%i, %g)", + cell->vertices[index_above1][0] + cell->x[0], + cell->vertices[index_above1][1] + cell->x[1], index_above1, a1); + message("Second vertex below: %g %g (%i, %g)", + cell->vertices[index_below2][0] + cell->x[0], + cell->vertices[index_below2][1] + cell->x[1], index_below2, b2); + message("Second vertex above: %g %g (%i, %g)", + cell->vertices[index_above2][0] + cell->x[0], + cell->vertices[index_above2][1] + cell->x[1], index_above2, a2); +#endif + + if (b1 == 0. || b2 == 0.) { + error("Vertex below midline is on midline!"); + } + + /* convert the test results (which correspond to the projected distance + between the vertex and the midline) to the fractions of the intersected + edges above and below the midline */ + test = a1 / (a1 - b1); + a1 = test; + b1 = 1.0f - test; + + test = a2 / (a2 - b2); + a2 = test; + b2 = 1.0f - test; + + /* remove the vertices above the midline, and insert two new vertices, + corresponding to the intersection points of the intersected edges and the + midline + In practice, we just copy all remaining vertices, starting from the first + vertex below the midline (in clockwise order) */ + nvert = 0; + i = index_below2; + while (i != index_above1) { + vertices[nvert][0] = cell->vertices[i][0]; + vertices[nvert][1] = cell->vertices[i][1]; + ngbs[nvert] = cell->ngbs[i]; + ++nvert; + VORONOI_CHECK_SIZE(); + ++i; + if (i == cell->nvert) { + i = 0; + } + } + /* now add the new vertices, they are always last */ + vertices[nvert][0] = a1 * cell->vertices[index_below1][0] + + b1 * cell->vertices[index_above1][0]; + vertices[nvert][1] = a1 * cell->vertices[index_below1][1] + + b1 * cell->vertices[index_above1][1]; + ngbs[nvert] = id; + ++nvert; + VORONOI_CHECK_SIZE(); + vertices[nvert][0] = a2 * cell->vertices[index_below2][0] + + b2 * cell->vertices[index_above2][0]; + vertices[nvert][1] = a2 * cell->vertices[index_below2][1] + + b2 * cell->vertices[index_above2][1]; + ngbs[nvert] = cell->ngbs[index_above2]; + ++nvert; + VORONOI_CHECK_SIZE(); + + /* overwrite the original vertices */ + cell->nvert = nvert; + for (i = 0; i < cell->nvert; ++i) { + cell->vertices[i][0] = vertices[i][0]; + cell->vertices[i][1] = vertices[i][1]; + cell->ngbs[i] = ngbs[i]; + } +} + +/** + * @brief Finalize a 2D Voronoi cell. + * + * @param cell 2D Voronoi cell. + * @return Maximal radius that could still change the structure of the cell. + */ +__attribute__((always_inline)) INLINE float voronoi_cell_finalize( + struct voronoi_cell *cell) { + + int i; + float vertices[VORONOI2D_MAXNUMVERT][2]; + float A, x[2], y[2], r2, r2max; + + /* make a copy of the vertices (they are overwritten when the face midpoints + are computed */ + for (i = 0; i < cell->nvert; ++i) { + vertices[i][0] = cell->vertices[i][0]; + vertices[i][1] = cell->vertices[i][1]; + } + + r2max = 0.0f; + for (i = 0; i < cell->nvert; ++i) { + if (i < cell->nvert - 1) { + x[0] = vertices[i][0]; + y[0] = vertices[i][1]; + x[1] = vertices[i + 1][0]; + y[1] = vertices[i + 1][1]; + } else { + x[0] = vertices[i][0]; + y[0] = vertices[i][1]; + x[1] = vertices[0][0]; + y[1] = vertices[0][1]; + } + A = x[1] * y[0] - x[0] * y[1]; + cell->volume += A; + cell->centroid[0] += (x[0] + x[1]) * A; + cell->centroid[1] += (y[0] + y[1]) * A; + + /* Note that we only need the RELATIVE positions of the midpoints */ + cell->face_midpoints[i][0] = 0.5f * (x[0] + x[1]); + cell->face_midpoints[i][1] = 0.5f * (y[0] + y[1]); + + r2 = x[0] * x[0] + y[0] * y[0]; + r2max = max(r2max, r2); + + x[0] -= x[1]; + y[0] -= y[1]; + cell->face_lengths[i] = sqrtf(x[0] * x[0] + y[0] * y[0]); + } + + cell->volume *= 0.5f; + A = 6 * cell->volume; + cell->centroid[0] /= A; + cell->centroid[1] /= A; + + cell->centroid[0] += cell->x[0]; + cell->centroid[1] += cell->x[1]; + + return 2.0f * sqrtf(r2max); +} + +/** + * @brief Get the oriented surface area and midpoint of the face between a + * 2D Voronoi cell and the given neighbour. + * + * @param cell 2D Voronoi cell. + * @param ngb ID of a particle that is possibly a neighbour of this cell. + * @param midpoint Array to store the relative position of the face in. + * @return 0 if the given neighbour is not a neighbour, surface area otherwise. + */ +__attribute__((always_inline)) INLINE float voronoi_get_face( + const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) { + + /* look up the neighbour */ + int i = 0; + while (i < cell->nvert && cell->ngbs[i] != ngb) { + ++i; + } + + if (i == cell->nvert) { + /* The given cell is not a neighbour. */ + return 0.0f; + } + + midpoint[0] = cell->face_midpoints[i][0]; + midpoint[1] = cell->face_midpoints[i][1]; + midpoint[2] = 0.0f; + + return cell->face_lengths[i]; +} + +/** + * @brief Get the centroid of a 2D Voronoi cell. + * + * @param cell 2D Voronoi cell. + * @param centroid Array to store the centroid in. + */ +__attribute__((always_inline)) INLINE void voronoi_get_centroid( + const struct voronoi_cell *cell, float *centroid) { + + centroid[0] = cell->centroid[0]; + centroid[1] = cell->centroid[1]; + centroid[2] = 0.0f; +} + +/******************************************************************************* + ** EXTRA FUNCTIONS USED FOR DEBUGGING ***************************************** + ******************************************************************************/ + +/** + * @brief Print the given cell to the stdout in a format that can be plotted + * using gnuplot. + * + * @param cell voronoi_cell to print. + */ +__attribute__((always_inline)) INLINE void voronoi_print_cell( + const struct voronoi_cell *cell) { + + int i, ip1; + + /* print cell generator */ + printf("%g %g\n\n", cell->x[0], cell->x[1]); + + /* print cell vertices */ + for (i = 0; i < cell->nvert; ++i) { + ip1 = i + 1; + if (ip1 == cell->nvert) { + ip1 = 0; + } + printf("%g %g\n%g %g\n\n", cell->vertices[i][0] + cell->x[0], + cell->vertices[i][1] + cell->x[1], + cell->vertices[ip1][0] + cell->x[0], + cell->vertices[ip1][1] + cell->x[1]); + } +} + +#endif // SWIFT_VORONOIXD_ALGORITHM_H diff --git a/src/hydro/Shadowswift/voronoi2d_cell.h b/src/hydro/Shadowswift/voronoi2d_cell.h new file mode 100644 index 0000000000000000000000000000000000000000..3c54ea8d0aa9ca1c915da1f245f889ebab2073d3 --- /dev/null +++ b/src/hydro/Shadowswift/voronoi2d_cell.h @@ -0,0 +1,58 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_CELL_H +#define SWIFT_VORONOIXD_CELL_H + +/* Maximal number of vertices (and neighbours) that can be stored in a + voronoi_cell struct. */ +#define VORONOI2D_MAXNUMVERT 100 + +/* 2D Voronoi cell */ +struct voronoi_cell { + + /* The position of the generator of the cell. */ + double x[2]; + + /* The "volume" of the 2D cell. */ + float volume; + + /* The centroid of the cell. */ + float centroid[2]; + + /* Number of cell vertices (and neighbours). */ + int nvert; + + /* We only need to store one of these at the same time. */ + union { + /* The relative positions of the vertices of the cell. */ + float vertices[VORONOI2D_MAXNUMVERT][2]; + + /* The midpoints of the faces. */ + float face_midpoints[VORONOI2D_MAXNUMVERT][2]; + }; + + /* The ids of the neighbouring cells. */ + unsigned long long ngbs[VORONOI2D_MAXNUMVERT]; + + /* The lengths of the faces. */ + float face_lengths[VORONOI2D_MAXNUMVERT]; +}; + +#endif // SWIFT_VORONOIXD_CELL_H diff --git a/src/hydro/Shadowswift/voronoi3d_algorithm.h b/src/hydro/Shadowswift/voronoi3d_algorithm.h new file mode 100644 index 0000000000000000000000000000000000000000..13242ad167f1936d12714af918bce7f41ac77335 --- /dev/null +++ b/src/hydro/Shadowswift/voronoi3d_algorithm.h @@ -0,0 +1,2211 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_ALGORITHM_H +#define SWIFT_VORONOIXD_ALGORITHM_H + +#include <float.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "error.h" +#include "inline.h" +#include "voronoi3d_cell.h" + +/* For debugging purposes */ +//#define LOOP_CHECK 1000 + +#ifdef LOOP_CHECK +/* We need to do the trickery below to get a unique counter for each call to the + macro. This only works if the macro is never called twice on the same line. + */ +#define MERGE(a, b) a##b +#define LOOPCOUNTER_NAME(line) MERGE(loopcount, line) + +/** + * @brief Increase the given counter variable and check if it is still valid. + * + * @param counter Counter to increase. + * @param line_number Line number where the while is called. + * @return 1 if the counter is still valid, 0 otherwise. + */ +__attribute__((always_inline)) INLINE int check_counter(int *counter, + int line_number) { + ++(*counter); + if ((*counter) == LOOP_CHECK) { + error("Number of iterations reached maximum (=%i) in while on line %i!", + LOOP_CHECK, line_number); + } + return 1; +} + +/* safewhile is a wrapper around a while that adds a unique counter variable to + the loop that is increased by 1 for each time the loop is executed, and + causes the code to crash if this number exceeds a given value. + We use this to quickly enable or disable number of iterations checks for a + large number of while loops */ +#define safewhile(condition) \ + int LOOPCOUNTER_NAME(__LINE__) = 0; \ + while (check_counter(&LOOPCOUNTER_NAME(__LINE__), __LINE__) && (condition)) + +#else /* LOOP_CHECK */ + +/* If LOOP_CHECK is not defined, safewhile and while are EXACTLY the same */ +#define safewhile(condition) while (condition) + +#endif /* LOOP_CHECK */ + +/* This flag activates a number of expensive geometrical checks that help + finding bugs. */ +//#define VORONOI3D_EXPENSIVE_CHECKS + +/* Tolerance parameter used to decide when to use more precise geometric + criteria */ +#define VORONOI3D_TOLERANCE 1.e-6f + +/* Box boundary flags used to signal cells neighbouring the box boundary + These values correspond to the top range of possible 64-bit integers, and + we make the strong assumption that there will never be a particle that has + one of these as particle ID. */ +#define VORONOI3D_BOX_FRONT 18446744073709551600llu +#define VORONOI3D_BOX_BACK 18446744073709551601llu +#define VORONOI3D_BOX_TOP 18446744073709551602llu +#define VORONOI3D_BOX_BOTTOM 18446744073709551603llu +#define VORONOI3D_BOX_LEFT 18446744073709551604llu +#define VORONOI3D_BOX_RIGHT 18446744073709551605llu + +/******************************************************************************* + * 3D specific methods + * + * Most of these methods are based on the source code of voro++: + * http://math.lbl.gov/voro++/ + ******************************************************************************/ + +/** + * @brief Print the given cell to the stderr in a format that can be easily + * plotted using gnuplot. + * + * This method prints to the stderr instead of stdout to make it possible to use + * it right before crashing the code. + * + * @param c Voronoi cell to print. + */ +__attribute__((always_inline)) INLINE void voronoi_print_gnuplot_c( + const struct voronoi_cell *c) { + + int i, j, v; + const double *x = c->x; + + fprintf(stderr, "%g\t%g\t%g\n\n", x[0], x[1], x[2]); + + for (i = 0; i < c->nvert; ++i) { + for (j = 0; j < c->orders[i]; ++j) { + v = c->edges[c->offsets[i] + j]; + if (v < 0) { + v = -v - 1; + } + fprintf(stderr, "%g\t%g\t%g\n", c->vertices[3 * i + 0] + x[0], + c->vertices[3 * i + 1] + x[1], c->vertices[3 * i + 2] + x[2]); + fprintf(stderr, "%g\t%g\t%g\n\n", c->vertices[3 * v + 0] + x[0], + c->vertices[3 * v + 1] + x[1], c->vertices[3 * v + 2] + x[2]); + } + } + fprintf(stderr, "\n"); +} + +/** + * @brief Print the contents of a 3D Voronoi cell + * + * @param cell 3D Voronoi cell + */ +__attribute__((always_inline)) INLINE void voronoi_print_cell( + const struct voronoi_cell *cell) { + + int i, j; + + fprintf(stderr, "x: %g %g %g\n", cell->x[0], cell->x[1], cell->x[2]); + fprintf(stderr, "nvert: %i\n", cell->nvert); + + for (i = 0; i < cell->nvert; ++i) { + fprintf(stderr, "%i: %g %g %g (%i)\n", i, cell->vertices[3 * i], + cell->vertices[3 * i + 1], cell->vertices[3 * i + 2], + cell->orders[i]); + for (j = 0; j < cell->orders[i]; ++j) { + fprintf(stderr, "%i (%i)\n", cell->edges[cell->offsets[i] + j], + cell->edgeindices[cell->offsets[i] + j]); + } + } + fprintf(stderr, "\n"); +} + +/** + * @brief Get the index of the vertex pointed to by the given edge of the given + * vertex. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of the cell. + * @param edge Edge of that vertex. + * @return Index of the vertex on the other side of the edge. + */ +__attribute__((always_inline)) INLINE int voronoi_get_edge( + const struct voronoi_cell *c, int vertex, int edge) { + return c->edges[c->offsets[vertex] + edge]; +} + +/** + * @brief Get the index of the given edge in the edge list of the vertex on the + * other side of the edge of the given vertex. + * + * Suppose that the given vertex has edges [edge1, edge2, given_edge], and that + * the vertex on the other side of given_edge has edges [edge1, given_edge, + * edge2], then this method returns 1. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of the cell. + * @param edge Edge of that vertex. + * @return Index of that edge in the edge list of the vertex on the other side + * of the edge. + */ +__attribute__((always_inline)) INLINE int voronoi_get_edgeindex( + const struct voronoi_cell *c, int vertex, int edge) { + return c->edgeindices[c->offsets[vertex] + edge]; +} + +/** + * @brief Set the index of the vertex on the other side of the given edge of the + * given vertex. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of the cell. + * @param edge Edge of that vertex. + * @param value Index of the vertex on the other side of that edge. + */ +__attribute__((always_inline)) INLINE void voronoi_set_edge( + struct voronoi_cell *c, int vertex, int edge, int value) { + c->edges[c->offsets[vertex] + edge] = value; +} + +/** + * @brief Set the index of the given edge in the edge list of the vertex on the + * other side of the edge of the given vertex. + * + * Suppose that the given vertex has edges [edge1, edge2, given_edge], and we + * want to tell this method that the vertex on the other side of given_edge has + * edges [edge1, given_edge, edge2], then we need to pass on a value of 1 to + * this method. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of that cell. + * @param edge Edge of that vertex. + * @param value Index of that edge in the edge list of the vertex on the other + * side of the edge. + */ +__attribute__((always_inline)) INLINE void voronoi_set_edgeindex( + struct voronoi_cell *c, int vertex, int edge, int value) { + c->edgeindices[c->offsets[vertex] + edge] = value; +} + +/** + * @brief Get the neighbour for the given edge of the given vertex. + * + * An edge is shared by two faces, and each face has a neighbour. Luckily, each + * edge also has two endpoints, so we can get away with storing only one + * neighbour per endpoint of an edge. We have complete freedom in choosing which + * neighbour to store in which endpoint, but we need to be consistent about it. + * Here we use the following convention: if we take a vector pointing away from + * the given vertex along the given edge direction, then we store the neighbour + * that corresponds to the face to the right if looking to the cell from the + * outside. This is the face that you encounter first when rotating counter- + * clockwise around that vector, starting from outside the cell. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of that cell. + * @param edge Edge of that vertex. + * @return Index of the neighbour corresponding to that edge and vertex. + */ +__attribute__((always_inline)) INLINE int voronoi_get_ngb( + const struct voronoi_cell *c, int vertex, int edge) { + return c->ngbs[c->offsets[vertex] + edge]; +} + +/** + * @brief Set the neighbour for the given edge of the given vertex. + * + * An edge is shared by two faces, and each face has a neighbour. Luckily, each + * edge also has two endpoints, so we can get away with storing only one + * neighbour per endpoint of an edge. We have complete freedom in choosing which + * neighbour to store in which endpoint, but we need to be consistent about it. + * Here we use the following convention: if we take a vector pointing away from + * the given vertex along the given edge direction, then we store the neighbour + * that corresponds to the face to the right if looking to the cell from the + * outside. This is the face that you encounter first when rotating counter- + * clockwise around that vector, starting from outside the cell. + * + * @param c 3D Voronoi cell. + * @param vertex Index of a vertex of that cell. + * @param edge Edge of that vertex. + * @param value Index of the neighbour corresponding to that edge and vertex. + */ +__attribute__((always_inline)) INLINE void voronoi_set_ngb( + struct voronoi_cell *c, int vertex, int edge, int value) { + c->ngbs[c->offsets[vertex] + edge] = value; +} + +/** + * @brief Check if the 3D Voronoi cell is still consistent. + * + * A cell is consistent if its edges are consistent, i.e. if edge e of vertex v1 + * points to vertex v2, then v2 should have an edge that points to v1 as well, + * and then the edge index of vertex v1 should contain the index of that edge + * in the edge list of v2. We also check if all vertices have orders of at least + * 3, and if all vertices are actually part of the vertex list. + * Oh, and we check if the cell actually has vertices. + * + * @param cell 3D Voronoi cell to check + */ +__attribute__((always_inline)) INLINE void voronoi_check_cell_consistency( + const struct voronoi_cell *c) { + + int i, j, e, l, m; + + if (c->nvert < 4) { + error("Found cell with only %i vertices!", c->nvert); + } + + for (i = 0; i < c->nvert; ++i) { + if (c->orders[i] < 3) { + voronoi_print_cell(c); + error("Found cell with vertex of order %i!", c->orders[i]); + } + for (j = 0; j < c->orders[i]; ++j) { + e = voronoi_get_edge(c, i, j); + if (e >= c->nvert) { + voronoi_print_cell(c); + error("Found cell with edges that lead to non-existing vertices!"); + } + if (e < 0) { + continue; + } + l = voronoi_get_edgeindex(c, i, j); + m = voronoi_get_edge(c, e, l); + if (m != i) { + /* voronoi_print_gnuplot_c(c); */ + voronoi_print_cell(c); + fprintf(stderr, "i: %i, j: %i, e: %i, l: %i, m: %i\n", i, j, e, l, m); + error("Cell inconsistency!"); + } + } + } +} + +/** + * @brief Check if the given vertex is above, below or on the cutting plane + * defined by the given parameters. + * + * @param v Coordinates of a cell vertex, relative w.r.t. the position of the + * generator of the cell. + * @param dx Half of the relative distance vector between the position of the + * generator of the cell and the position of the neighbouring cell that + * generates the cutting plane, pointing from the generator position to the + * cutting plane. + * @param r2 Squared length of dx. + * @param test Variable to store the result of the geometric test in, which + * corresponds to the projected distance between the generator and the vertex + * along dx. + * @param teststack Stack to store the results of the N last tests in (for + * debugging purposes only). + * @param teststack_size Next available field in the teststack, is reset to 0 if + * the teststack is full (so the N+1th results is overwritten; for debugging + * purposes only). + * @return Result of the test: -1 if the vertex is below the cutting plane, +1 + * if it is above, and 0 if it is on the cutting plane. + */ +__attribute__((always_inline)) INLINE int voronoi_test_vertex( + const float *v, const float *dx, float r2, float *test, float *teststack, + int *teststack_size) { + + *test = v[0] * dx[0] + v[1] * dx[1] + v[2] * dx[2] - r2; + + teststack[*teststack_size] = *test; + *teststack_size = *teststack_size + 1; + if (*teststack_size == 2 * VORONOI3D_MAXNUMVERT) { + *teststack_size = 0; + } + + if (*test < -VORONOI3D_TOLERANCE) { + return -1; + } + if (*test > VORONOI3D_TOLERANCE) { + return 1; + } + return 0; +} + +/** + * @brief Initialize the cell as a cube that spans the entire simulation box. + * + * @param c 3D Voronoi cell to initialize. + * @param anchor Anchor of the simulation box. + * @param side Side lengths of the simulation box. + */ +__attribute__((always_inline)) INLINE void voronoi_initialize( + struct voronoi_cell *cell, const double *anchor, const double *side) { + + cell->nvert = 8; + + /* (0, 0, 0) -- 0 */ + cell->vertices[0] = anchor[0] - cell->x[0]; + cell->vertices[1] = anchor[1] - cell->x[1]; + cell->vertices[2] = anchor[2] - cell->x[2]; + + /* (0, 0, 1)-- 1 */ + cell->vertices[3] = anchor[0] - cell->x[0]; + cell->vertices[4] = anchor[1] - cell->x[1]; + cell->vertices[5] = anchor[2] + side[2] - cell->x[2]; + + /* (0, 1, 0) -- 2 */ + cell->vertices[6] = anchor[0] - cell->x[0]; + cell->vertices[7] = anchor[1] + side[1] - cell->x[1]; + cell->vertices[8] = anchor[2] - cell->x[2]; + + /* (0, 1, 1) -- 3 */ + cell->vertices[9] = anchor[0] - cell->x[0]; + cell->vertices[10] = anchor[1] + side[1] - cell->x[1]; + cell->vertices[11] = anchor[2] + side[2] - cell->x[2]; + + /* (1, 0, 0) -- 4 */ + cell->vertices[12] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[13] = anchor[1] - cell->x[1]; + cell->vertices[14] = anchor[2] - cell->x[2]; + + /* (1, 0, 1) -- 5 */ + cell->vertices[15] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[16] = anchor[1] - cell->x[1]; + cell->vertices[17] = anchor[2] + side[2] - cell->x[2]; + + /* (1, 1, 0) -- 6 */ + cell->vertices[18] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[19] = anchor[1] + side[1] - cell->x[1]; + cell->vertices[20] = anchor[2] - cell->x[2]; + + /* (1, 1, 1) -- 7 */ + cell->vertices[21] = anchor[0] + side[0] - cell->x[0]; + cell->vertices[22] = anchor[1] + side[1] - cell->x[1]; + cell->vertices[23] = anchor[2] + side[2] - cell->x[2]; + + cell->orders[0] = 3; + cell->orders[1] = 3; + cell->orders[2] = 3; + cell->orders[3] = 3; + cell->orders[4] = 3; + cell->orders[5] = 3; + cell->orders[6] = 3; + cell->orders[7] = 3; + + /* edges are ordered counterclockwise w.r.t. a vector pointing from the + cell generator to the vertex + (0, 0, 0) corner */ + cell->offsets[0] = 0; + cell->edges[0] = 1; + cell->edges[1] = 2; + cell->edges[2] = 4; + cell->edgeindices[0] = 0; + cell->edgeindices[1] = 2; + cell->edgeindices[2] = 0; + + /* (0, 0, 1) corner */ + cell->offsets[1] = 3; + cell->edges[3] = 0; + cell->edges[4] = 5; + cell->edges[5] = 3; + cell->edgeindices[3] = 0; + cell->edgeindices[4] = 2; + cell->edgeindices[5] = 1; + + /* (0, 1, 0) corner */ + cell->offsets[2] = 6; + cell->edges[6] = 3; + cell->edges[7] = 6; + cell->edges[8] = 0; + cell->edgeindices[6] = 0; + cell->edgeindices[7] = 0; + cell->edgeindices[8] = 1; + + /* (0, 1, 1) corner */ + cell->offsets[3] = 9; + cell->edges[9] = 2; + cell->edges[10] = 1; + cell->edges[11] = 7; + cell->edgeindices[9] = 0; + cell->edgeindices[10] = 2; + cell->edgeindices[11] = 0; + + /* (1, 0, 0) corner */ + cell->offsets[4] = 12; + cell->edges[12] = 0; + cell->edges[13] = 6; + cell->edges[14] = 5; + cell->edgeindices[12] = 2; + cell->edgeindices[13] = 2; + cell->edgeindices[14] = 0; + + /* (1, 0, 1) corner */ + cell->offsets[5] = 15; + cell->edges[15] = 4; + cell->edges[16] = 7; + cell->edges[17] = 1; + cell->edgeindices[15] = 2; + cell->edgeindices[16] = 1; + cell->edgeindices[17] = 1; + + /* (1, 1, 0) corner */ + cell->offsets[6] = 18; + cell->edges[18] = 2; + cell->edges[19] = 7; + cell->edges[20] = 4; + cell->edgeindices[18] = 1; + cell->edgeindices[19] = 2; + cell->edgeindices[20] = 1; + + /* (1, 1, 1) corner */ + cell->offsets[7] = 21; + cell->edges[21] = 3; + cell->edges[22] = 5; + cell->edges[23] = 6; + cell->edgeindices[21] = 2; + cell->edgeindices[22] = 1; + cell->edgeindices[23] = 1; + + /* ngbs[3*i+j] is the neighbour corresponding to the plane clockwise of + edge j of vertex i (when going from edge j to vertex i) + we set them to a ridiculously large value to be able to track faces without + neighbour */ + cell->ngbs[0] = VORONOI3D_BOX_FRONT; /* (000) - (001) */ + cell->ngbs[1] = VORONOI3D_BOX_LEFT; /* (000) - (010) */ + cell->ngbs[2] = VORONOI3D_BOX_BOTTOM; /* (000) - (100) */ + + cell->ngbs[3] = VORONOI3D_BOX_LEFT; /* (001) - (000) */ + cell->ngbs[4] = VORONOI3D_BOX_FRONT; /* (001) - (101) */ + cell->ngbs[5] = VORONOI3D_BOX_TOP; /* (001) - (011) */ + + cell->ngbs[6] = VORONOI3D_BOX_LEFT; /* (010) - (011) */ + cell->ngbs[7] = VORONOI3D_BOX_BACK; /* (010) - (110) */ + cell->ngbs[8] = VORONOI3D_BOX_BOTTOM; /* (010) - (000) */ + + cell->ngbs[9] = VORONOI3D_BOX_BACK; /* (011) - (010) */ + cell->ngbs[10] = VORONOI3D_BOX_LEFT; /* (011) - (001) */ + cell->ngbs[11] = VORONOI3D_BOX_TOP; /* (011) - (111) */ + + cell->ngbs[12] = VORONOI3D_BOX_FRONT; /* (100) - (000) */ + cell->ngbs[13] = VORONOI3D_BOX_BOTTOM; /* (100) - (110) */ + cell->ngbs[14] = VORONOI3D_BOX_RIGHT; /* (100) - (101) */ + + cell->ngbs[15] = VORONOI3D_BOX_FRONT; /* (101) - (100) */ + cell->ngbs[16] = VORONOI3D_BOX_RIGHT; /* (101) - (111) */ + cell->ngbs[17] = VORONOI3D_BOX_TOP; /* (101) - (001) */ + + cell->ngbs[18] = VORONOI3D_BOX_BOTTOM; /* (110) - (010) */ + cell->ngbs[19] = VORONOI3D_BOX_BACK; /* (110) - (111) */ + cell->ngbs[20] = VORONOI3D_BOX_RIGHT; /* (110) - (100) */ + + cell->ngbs[21] = VORONOI3D_BOX_BACK; /* (111) - (011) */ + cell->ngbs[22] = VORONOI3D_BOX_TOP; /* (111) - (101) */ + cell->ngbs[23] = VORONOI3D_BOX_RIGHT; /* (111) - (110) */ +} + +/** + * @brief Find an edge of the voronoi_cell that intersects the cutting plane. + * + * There is a large number of possible paths through this method, each of which + * is covered by a separate unit test in testVoronoi3D. Paths have been numbered + * in the inline comments to help identify them. + * + * @param c 3D Voronoi cell. + * @param dx Vector pointing from pj to the midpoint of the line segment between + * pi and pj. + * @param r2 Squared length of dx. + * @param u Projected distance between the plane and the closest vertex above + * the plane, along dx. + * @param up Index of the closest vertex above the plane. + * @param us Index of the edge of vertex up that intersects the plane. + * @param uw Result of the last test_vertex call for vertex up. + * @param l Projected distance between the plane and the closest vertex below + * the plane, along dx. + * @param lp Index of the closest vertex below the plane. + * @param ls Index of the edge of vertex lp that intersects the plane. + * @param lw Result of the last test_vertex call for vertex lp. + * @param q Projected distance between the plane and a test vertex, along dx. + * @param qp Index of the test vertex. + * @param qs Index of the edge of the test vertex that is connected to up. + * @param qw Result of the last test_vertex call involving qp. + * @return A negative value if an error occurred, 0 if the plane does not + * intersect the cell, 1 if nothing special happened and 2 if we have a + * complicated setup. + */ +__attribute__((always_inline)) INLINE int voronoi_intersect_find_closest_vertex( + struct voronoi_cell *c, const float *dx, float r2, float *u, int *up, + int *us, int *uw, float *l, int *lp, int *ls, int *lw, float *q, int *qp, + int *qs, int *qw) { + + /* stack to store all vertices that have already been tested (debugging + only) */ + float teststack[2 * VORONOI3D_MAXNUMVERT]; + /* size of the used part of the stack */ + int teststack_size = 0; + /* flag signalling a complicated setup */ + int complicated; + + /* test the first vertex: uw = -1 if it is below the plane, 1 if it is above + 0 if it is very close to the plane, and things become complicated... */ + *uw = voronoi_test_vertex(&c->vertices[0], dx, r2, u, teststack, + &teststack_size); + *up = 0; + complicated = 0; + if ((*uw) == 0) { + + /* PATH 0 */ + complicated = 1; + + } else { + + /* two options: either the vertex is above or below the plane */ + + if ((*uw) == 1) { + + /* PATH 1 */ + + /* above: try to find a vertex below + we test all edges of the current vertex stored in up (vertex 0) until + we either find one below the plane or closer to the plane */ + *lp = voronoi_get_edge(c, (*up), 0); + *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, teststack, + &teststack_size); + *us = 1; + /* Not in while: PATH 1.0 */ + /* somewhere in while: PATH 1.1 */ + /* last valid option of while: PATH 1.2 */ + safewhile((*us) < c->orders[(*up)] && (*l) >= (*u)) { + *lp = voronoi_get_edge(c, (*up), (*us)); + *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, teststack, + &teststack_size); + ++(*us); + } + /* we increased us too much, correct this */ + --(*us); + if ((*l) >= (*u)) { + /* PATH 1.3 */ + /* up is the closest vertex to the plane, but is above the plane + since the entire cell is convex, up is the closest vertex of all + vertices of the cell + this means the entire cell is supposedly above the plane, which is + impossible */ + message( + "Cell completely gone! This should not happen. (l >= u, l = %g, u " + "= %g)", + (*l), (*u)); + return -1; + } + /* we know that lp is closer to the plane or below the plane + now find the index of the edge up-lp in the edge list of lp */ + *ls = voronoi_get_edgeindex(c, (*up), (*us)); + + /* if lp is also above the plane, replace up by lp and repeat the process + until lp is below the plane */ + safewhile((*lw) == 1) { + /* PATH 1.4 */ + *u = (*l); + *up = (*lp); + *us = 0; + /* no while: PATH 1.4.0 */ + /* somewhere in while: PATH 1.4.1 */ + /* last valid option of while: PATH 1.4.2 */ + safewhile((*us) < (*ls) && (*l) >= (*u)) { + *lp = voronoi_get_edge(c, (*up), (*us)); + *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, + teststack, &teststack_size); + ++(*us); + } + if ((*l) >= (*u)) { + ++(*us); + /* no while: PATH 1.4.3 */ + /* somewhere in while: PATH 1.4.4 */ + /* last valid option of while: PATH 1.4.5 */ + safewhile((*us) < c->orders[(*up)] && (*l) >= (*u)) { + *lp = voronoi_get_edge(c, (*up), (*us)); + *lw = voronoi_test_vertex(&c->vertices[3 * (*lp)], dx, r2, l, + teststack, &teststack_size); + ++(*us); + } + if ((*l) >= (*u)) { + /* PATH 1.4.6 */ + message( + "Cell completely gone! This should not happen. (l >= u, l = " + "%g, u = %g)", + (*l), (*u)); + return -1; + } + } + --(*us); + *ls = voronoi_get_edgeindex(c, (*up), (*us)); + } + /* if lp is too close to the plane, replace up by lp and proceed to + complicated setup */ + if ((*lw) == 0) { + /* PATH 1.5 */ + *up = (*lp); + complicated = 1; + } + } else { /* if(uw == 1) */ + + /* PATH 2 */ + + /* below: try to find a vertex above + we test all edges of the current vertex stored in up (vertex 0) until + we either find one above the plane or closer to the plane */ + + *qp = voronoi_get_edge(c, (*up), 0); + *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, teststack, + &teststack_size); + *us = 1; + /* not in while: PATH 2.0 */ + /* somewhere in while: PATH 2.1 */ + /* last valid option of while: PATH 2.2 */ + safewhile((*us) < c->orders[(*up)] && (*u) >= (*q)) { + *qp = voronoi_get_edge(c, (*up), (*us)); + *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, teststack, + &teststack_size); + ++(*us); + } + if ((*u) >= (*q)) { + /* PATH 2.3 */ + /* up is the closest vertex to the plane and is below the plane + since the cell is convex, up is the closest vertex of all vertices of + the cell + this means that the entire cell is below the plane + The cell is unaltered. */ + return 0; + } else { + /* the last increase in the loop pushed us too far, correct this */ + --(*us); + } + + /* repeat the above process until qp is closer or above the plane */ + safewhile((*qw) == -1) { + /* PATH 2.4 */ + *qs = voronoi_get_edgeindex(c, (*up), (*us)); + *u = (*q); + *up = (*qp); + *us = 0; + /* no while: PATH 2.4.0 */ + /* somewhere in while: PATH 2.4.1 */ + /* last valid option of while: 2.4.2 */ + safewhile((*us) < (*qs) && (*u) >= (*q)) { + *qp = voronoi_get_edge(c, (*up), (*us)); + *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, + teststack, &teststack_size); + ++(*us); + } + if ((*u) >= (*q)) { + ++(*us); + /* no while: PATH 2.4.3 */ + /* somewhere in while: PATH 2.4.4 */ + /* last valid option of while: PATH 2.4.5 */ + safewhile((*us) < c->orders[(*up)] && (*u) >= (*q)) { + *qp = voronoi_get_edge(c, (*up), (*us)); + *qw = voronoi_test_vertex(&c->vertices[3 * (*qp)], dx, r2, q, + teststack, &teststack_size); + ++(*us); + } + if ((*u) >= (*q)) { + /* PATH 2.4.6 */ + /* cell unaltered */ + return 0; + } + } + --(*us); + } + if ((*qw) == 1) { + /* qp is above the plane: initialize lp to up and replace up by qp */ + *lp = (*up); + *ls = (*us); + *l = (*u); + *up = (*qp); + *us = voronoi_get_edgeindex(c, (*lp), (*ls)); + *u = (*q); + } else { + /* PATH 2.5 */ + /* too close to call: go to complicated setup */ + *up = (*qp); + complicated = 1; + } + + } /* if(uw == 1) */ + + } /* if(uw == 0) */ + + if (complicated) { + return 2; + } else { + return 1; + } +} + +/** + * @brief Intersect the given cell with the midplane between the cell generator + * and a neighbouring cell at the given relative position and with the given ID. + * + * This method is the core of the Voronoi algorithm. If anything goes wrong + * geometrically, it most likely goes wrong somewhere within this method. + * + * @param c 3D Voronoi cell. + * @param odx The original relative distance vector between the cell generator + * and the intersecting neighbour, as it is passed on to runner_iact_density + * (remember: odx = pi->x - pj->x). + * @param ngb ID of the intersecting neighbour (pj->id in runner_iact_density). + */ +__attribute__((always_inline)) INLINE void voronoi_intersect( + struct voronoi_cell *c, const float *odx, unsigned long long ngb) { + + /* vector pointing from pi to the midpoint of the line segment between pi and + pj. This corresponds to -0.5*odx */ + float dx[3]; + /* squared norm of dx */ + float r2; + /* u: distance between the plane and the closest vertex above the plane (up) + l: distance between the plane and the closest vertex below the plane (lp) + q: distance between the plane and the vertex that is currently being + tested (qp) */ + float u = 0.0f, l = 0.0f, q = 0.0f; + /* up: index of the closest vertex above the plane + us: index of the edge of vertex up that intersects the plane + uw: result of the last orientation test involving vertex u + same naming used for vertex l and vertex q */ + int up = -1, us = -1, uw = -1, lp = -1, ls = -1, lw = -1, qp = -1, qs = -1, + qw = -1; + /* auxiliary flag used to capture degeneracies */ + int complicated = -1; + + /* stack to store all vertices that have already been tested (debugging + only) */ + float teststack[2 * VORONOI3D_MAXNUMVERT]; + /* size of the used part of the stack */ + int teststack_size = 0; + +#ifdef VORONOI3D_EXPENSIVE_CHECKS + voronoi_check_cell_consistency(c); +#endif + + /* initialize dx and r2 */ + dx[0] = -0.5f * odx[0]; + dx[1] = -0.5f * odx[1]; + dx[2] = -0.5f * odx[2]; + r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; + + /* find an intersected edge of the cell */ + int result = voronoi_intersect_find_closest_vertex( + c, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + if (result < 0) { + /* the closest_vertex test only found vertices above the intersecting plane + this would mean that the entire cell lies above the midplane of the line + segment connecting a point inside the cell (the generator) and a point + that could be inside or outside the cell (the neighbour). This is + geometrically absurd and should NEVER happen. */ + voronoi_print_gnuplot_c(c); + error("Error while searching intersected edge!"); + } + if (result == 0) { + /* no intersection */ + return; + } + if (result == 2) { + complicated = 1; + } else { + complicated = 0; + } + + /* At this point: + up contains a vertex above the plane + lp contains a vertex below the plane + us and ls contain the index of the edge that connects up and lp, this edge + is intersected by the midplane + u and l contain the projected distances of up and lp to the midplane, + along dx + IF complicated is 1, up contains a vertex that is considered to be on the + plane. All other variables can be considered to be uninitialized in this + case. */ + + int vindex = -1; + int visitflags[VORONOI3D_MAXNUMVERT]; + int dstack[2 * VORONOI3D_MAXNUMVERT]; + int dstack_size = 0; + float r = 0.0f; + int cs = -1, rp = -1; + int double_edge = 0; + int i = -1, j = -1, k = -1; + + /* initialize visitflags */ + for (i = 0; i < VORONOI3D_MAXNUMVERT; ++i) { + visitflags[i] = 0; + } + + if (complicated) { + + /* We've entered the complicated setup, which means that somewhere along the + way we found a vertex that is on or very close to the midplane. The index + of that vertex is stored in up, all other variables are meaningless at + this point. */ + + /* first of all, we need to find a vertex which has edges that extend below + the plane (since the remainder of our algorithm depends on that). This is + not necessarily the case: in principle a vertex can only have edges that + extend inside or above the plane. + we create a stack of vertices to test (we use dstack for this), and add + vertex up. For each vertex on the stack, we then traverse its edges. If + the edge extends above the plane, we ignore it. If it extends below, we + stop. If the edge lies in the plane, we add the vertex on the other end + to the stack. + We make sure that up contains the index of a vertex extending beyond the + plane on exit. */ + dstack[dstack_size] = up; + ++dstack_size; + lw = 0; + j = 0; + safewhile(j < dstack_size && lw != -1) { + up = dstack[j]; + for (i = 0; i < c->orders[up]; ++i) { + lp = voronoi_get_edge(c, up, i); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + if (lw == -1) { + /* jump out of the for loop */ + break; + } + if (lw == 0) { + /* only add each vertex to the stack once */ + k = 0; + safewhile(k < dstack_size && dstack[k] != lp) { ++k; } + if (k == dstack_size) { + dstack[dstack_size] = lp; + ++dstack_size; + } + } + } + ++j; + } + + /* we increased j after lw was calculated, so only the value of lw should be + used to determine whether or not the loop was successful */ + if (lw != -1) { + /* we did not find an edge that extends below the plane. There are two + possible reasons for this: either all vertices of the cell lie above + or inside the midplane of the segment connecting a point inside the + cell (the generator) with a point inside or outside the cell (the + neighbour). This is geometrically absurd. + Another reason might be that somehow all vertices in the midplane only + have edges that extend outwards. This is contradictory to the fact that + a Voronoi cell is convex, and therefore also unacceptable. + We conclude that we should NEVER end up here. */ + voronoi_print_cell(c); + error("Unable to find a vertex below the midplane!"); + } + /* reset the delete stack, we need it later on */ + dstack_size = 0; + + /* the search routine detected a vertex very close to or in the midplane + the index of this vertex is stored in up + we proceed by checking the edges of this vertex */ + + lp = voronoi_get_edge(c, up, 0); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + + /* the first edge can be below, above or on the plane */ + if (lw != -1) { + + /* above or on the plane: we try to find one below the plane */ + + rp = lw; + i = 1; + lp = voronoi_get_edge(c, up, i); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + safewhile(lw != -1) { + ++i; + if (i == c->orders[up]) { + /* none of the edges of up is below the plane. Since the cell is + supposed to be convex, this means the entire cell is above or on + the plane. This should not happen... + Furthermore, we should really NEVER end up here, as in this case + an error should already have be thrown above. */ + voronoi_print_gnuplot_c(c); + error( + "Cell completely gone! This should not happen. (i == " + "c->order[up], i = %d, c->orders[up] = %d, up = %d)\n" + "dx: [%g %g %g]\nv[up]: [%g %g %g]\nx: [%g %g %g]", + i, c->orders[up], up, dx[0], dx[1], dx[2], c->vertices[3 * up], + c->vertices[3 * up + 1], c->vertices[3 * up + 2], c->x[0], + c->x[1], c->x[2]); + } + lp = voronoi_get_edge(c, up, i); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + } + + /* lp, l and lw now contain values corresponding to an edge below the + plane + rp contains the result of test_vertex for the first edge of up, for + reference */ + + /* we go on to the next edge of up, and see if we can find an edge that + does not extend below the plane */ + + j = i + 1; + safewhile(j < c->orders[up] && lw == -1) { + lp = voronoi_get_edge(c, up, j); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + ++j; + } + + if (lw != -1) { + /* the last iteration increased j by 1 too many, correct this */ + --j; + } + + /* j-i now contains the number of edges below the plane. We will replace + up by a new vertex of order this number + 2 (since 2 new edges will be + created inside the plane) + however, we do not do this if there is exactly one edge that lies in + the plane, and all other edges lie below, because in this case we can + just keep vertex up as is */ + + if (j == c->orders[up] && i == 1 && rp == 0) { + /* keep the order of up, and flag this event for later reference */ + k = c->orders[up]; + double_edge = 1; + } else { + /* general case: keep all edges below the plane, and create 2 new ones + in the plane */ + k = j - i + 2; + } + + /* create new order k vertex */ + vindex = c->nvert; + ++c->nvert; + if (c->nvert == VORONOI3D_MAXNUMVERT) { + error("Too many vertices!"); + } + c->orders[vindex] = k; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) { + error("Too many edges!"); + } + + visitflags[vindex] = -vindex; + /* the new vertex adopts the coordinates of the old vertex */ + c->vertices[3 * vindex + 0] = c->vertices[3 * up + 0]; + c->vertices[3 * vindex + 1] = c->vertices[3 * up + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * up + 2]; + + /* us contains the index of the last edge NOT below the plane + note that i is at least 1, so there is no need to wrap in this case */ + us = i - 1; + + /* copy all edges of up below the plane into the new vertex, starting from + edge 1 (edge 0 is reserved to connect to a newly created vertex + below) */ + k = 1; + safewhile(i < j) { + qp = voronoi_get_edge(c, up, i); + qs = voronoi_get_edgeindex(c, up, i); + voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i)); + voronoi_set_edge(c, vindex, k, qp); + voronoi_set_edgeindex(c, vindex, k, qs); + voronoi_set_edge(c, qp, qs, vindex); + voronoi_set_edgeindex(c, qp, qs, k); + /* disconnect up, since this vertex will be removed */ + voronoi_set_edge(c, up, i, -1); + ++i; + ++k; + } + + /* store the index of the first edge not below the plane */ + if (i == c->orders[up]) { + qs = 0; + } else { + qs = i; + } + } else { /* if(lw != -1) */ + + /* the first edge lies below the plane, try to find one that does not */ + + /* we first do a reverse search */ + i = c->orders[up] - 1; + lp = voronoi_get_edge(c, up, i); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + safewhile(lw == -1) { + --i; + if (i == 0) { + /* No edge above or in the plane found: the cell is unaltered */ + return; + } + lp = voronoi_get_edge(c, up, i); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + } + + /* now we do a forward search */ + j = 1; + qp = voronoi_get_edge(c, up, j); + qw = voronoi_test_vertex(&c->vertices[3 * qp], dx, r2, &q, teststack, + &teststack_size); + safewhile(qw == -1) { + ++j; + qp = voronoi_get_edge(c, up, j); + qw = voronoi_test_vertex(&c->vertices[3 * qp], dx, r2, &l, teststack, + &teststack_size); + } + + /* at this point j contains the index of the first edge not below the + plane, i the index of the last edge not below the plane + we use this to compute the number of edges below the plane. up is + replaced by a new vertex that has that number + 2 edges (since 2 new + edges are created inside the plane). We again capture the special event + where there is only one edge not below the plane, which lies inside the + plane. In this case up is copied as is. */ + + if (i == j && qw == 0) { + /* we keep up as is, and flag this event */ + double_edge = 1; + k = c->orders[up]; + } else { + /* (c->orders[up]-1 - i) + j is the number of edges below the plane */ + k = c->orders[up] - i + j + 1; + } + + /* create new order k vertex */ + vindex = c->nvert; + ++c->nvert; + if (c->nvert == VORONOI3D_MAXNUMVERT) { + error("Too many vertices!"); + } + c->orders[vindex] = k; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) { + error("Too many edges!"); + } + + visitflags[vindex] = -vindex; + /* the new vertex is just a copy of vertex up */ + c->vertices[3 * vindex + 0] = c->vertices[3 * up + 0]; + c->vertices[3 * vindex + 1] = c->vertices[3 * up + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * up + 2]; + + /* as above, us stores the index of the last edge NOT below the plane */ + us = i; + + /* copy all edges below the plane into the new vertex, starting from edge + 1 (edge 0 will be connected to a newly created vertex below) + We have to do this in two steps: first we copy the high index edges of + up, then the low index ones (since the edges below the plane are not a + continuous block of indices in this case) */ + k = 1; + ++i; + safewhile(i < c->orders[up]) { + qp = voronoi_get_edge(c, up, i); + qs = voronoi_get_edgeindex(c, up, i); + voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i)); + voronoi_set_edge(c, vindex, k, qp); + voronoi_set_edgeindex(c, vindex, k, qs); + voronoi_set_edge(c, qp, qs, vindex); + voronoi_set_edgeindex(c, qp, qs, k); + /* disconnect up, it will be removed */ + voronoi_set_edge(c, up, i, -1); + ++i; + ++k; + } + i = 0; + safewhile(i < j) { + qp = voronoi_get_edge(c, up, i); + qs = voronoi_get_edgeindex(c, up, i); + voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, i)); + voronoi_set_edge(c, vindex, k, qp); + voronoi_set_edgeindex(c, vindex, k, qs); + voronoi_set_edge(c, qp, qs, vindex); + voronoi_set_edgeindex(c, qp, qs, k); + voronoi_set_edge(c, up, i, -1); + ++i; + ++k; + } + /* qs stores the index of the first edge not below the plane */ + qs = j; + } + + /* at this point, we have created a new vertex that contains all edges of up + below the plane, and two dangling edges: 0 and k + Furthermore, us stores the index of the last edge not below the plane, + qs the index of the first edge not below the plane */ + + /* now set the neighbours for the dangling edge(s) */ + if (!double_edge) { + /* the last edge has the same neighbour as the first edge not below the + plane */ + voronoi_set_ngb(c, vindex, k, voronoi_get_ngb(c, up, qs)); + /* the first edge has the new neighbour as neighbour */ + voronoi_set_ngb(c, vindex, 0, ngb); + } else { + /* up is copied as is, so we also copy its last remaining neighbour */ + voronoi_set_ngb(c, vindex, 0, voronoi_get_ngb(c, up, qs)); + } + + /* add up to the delete stack */ + dstack[dstack_size] = up; + ++dstack_size; + + /* make sure the variables below have the same meaning as they would have + if we had the non complicated setup: + cs contains the index of the last dangling edge of the new vertex + qp and q correspond to the last vertex that has been deleted + qs corresponds to the first edge not below the plane + up and us correspond to the last edge not below the plane, i.e. the edge + that will be the last one to connect to the new vertex + note that the value of i is ignored below, it is just used to temporary + store the new value of up */ + cs = k; + qp = up; + q = u; + i = voronoi_get_edge(c, up, us); + us = voronoi_get_edgeindex(c, up, us); + up = i; + /* we store the index of the newly created vertex in the visitflags of the + last deleted vertex */ + visitflags[qp] = vindex; + } else { /* if(complicated) */ + + if (u == l) { + error("Upper and lower vertex are the same!"); + } + + /* the line joining up and lp has general (vector) equation + x = lp + (up-lp)*t, + with t a parameter ranging from 0 to 1 + we can rewrite this as + x = lp*(1-t) + up*t + the value for t corresponding to the intersection of the line and the + midplane can be found as the ratio of the projected distance between one + of the vertices and the midplane, and the total projected distance + between the two vertices: u-l (remember that u > 0 and l < 0) */ + r = u / (u - l); + l = 1.0f - r; + + if (r > FLT_MAX || r < -FLT_MAX || l > FLT_MAX || l < -FLT_MAX) { + error("Value overflow (r: %g, l: %g)", r, l); + } + + /* create a new order 3 vertex */ + vindex = c->nvert; + ++c->nvert; + if (c->nvert == VORONOI3D_MAXNUMVERT) { + error("Too many vertices!"); + } + c->orders[vindex] = 3; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + if (c->offsets[vindex] + 3 >= VORONOI3D_MAXNUMEDGE) { + error("Too many edges!"); + } + + visitflags[vindex] = -vindex; + c->vertices[3 * vindex + 0] = + c->vertices[3 * lp + 0] * r + c->vertices[3 * up + 0] * l; + c->vertices[3 * vindex + 1] = + c->vertices[3 * lp + 1] * r + c->vertices[3 * up + 1] * l; + c->vertices[3 * vindex + 2] = + c->vertices[3 * lp + 2] * r + c->vertices[3 * up + 2] * l; + + /* add vertex up to the delete stack */ + dstack[dstack_size] = up; + ++dstack_size; + + /* connect the new vertex to lp (and update lp as well) */ + voronoi_set_edge(c, vindex, 1, lp); + voronoi_set_edgeindex(c, vindex, 1, ls); + voronoi_set_edge(c, lp, ls, vindex); + voronoi_set_edgeindex(c, lp, ls, 1); + /* disconnect vertex up, it will be deleted */ + voronoi_set_edge(c, up, us, -1); + /* note that we do not connect edges 0 and 2: edge 2 will be connected to + the next new vertex that we created, while edge 0 will be connected to + the last new vertex */ + + /* set neighbour relations for the new vertex: + - edge 0 will be connected to the next intersection point (below), and + hence has pj as ngb + - edge 1 is connected to lp and has the original neighbour of the + intersected edge corresponding to up as neighbour + - edge 2 has the neighbour on the other side of the original intersected + edge as neighbour, which is the same as the neighbour of the edge + corresponding to lp */ + voronoi_set_ngb(c, vindex, 0, ngb); + voronoi_set_ngb(c, vindex, 1, voronoi_get_ngb(c, up, us)); + voronoi_set_ngb(c, vindex, 2, voronoi_get_ngb(c, lp, ls)); + + qs = us + 1; + if (qs == c->orders[up]) { + qs = 0; + } + qp = up; + q = u; + + cs = 2; + + } /* if(complicated) */ + + /* at this point: + qp corresponds to the last vertex that has been deleted + up corresponds to the last vertex that should be used to connect a new + vertex to the newly created vertex above. In the normal case, qp and up + are the same vertex, but qp and up can be different if the newly created + vertex lies in the midplane + qs contains the index of the edge of qp that is next in line to be tested: + the edge that comes after the intersected edge that was deleted above + us corresponds to the edge of up that was connected to the vertex that is + now connected to the newly created vertex above + q contains the projected distance between qp and the midplane, along dx + cs contains the index of the last dangling edge of the last vertex that + was created above; we still need to connect this edge to a vertex below */ + + /* we have found one intersected edge (or at least an edge that lies inside + the midplane) and created one new vertex that lies in the midplane, with + dangling edges. We now try to find other intersected edges and create other + new vertices that will be connected to the first new vertex. */ + + int cp = -1; + int iqs = -1; + int new_double_edge = -1; + + /* cp and rp both contain the index of the last vertex that was created + cp will be updated if we add more vertices, rp will be kept, as we need it + to link the last new vertex to the first new vertex in the end */ + cp = vindex; + rp = vindex; + /* we traverse connections of the first removed vertex, until we arrive at an + edge that links to this vertex (or its equivalent in the degenerate + case) */ + safewhile(qp != up || qs != us) { + /* test the next edge of qp */ + lp = voronoi_get_edge(c, qp, qs); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + if (lw == 0) { + + /* degenerate case: next vertex lies inside the plane */ + + k = 2; + if (double_edge) { + k = 1; + } + /* store the vertex and edge on the other side of the edge in qp and qs */ + qs = voronoi_get_edgeindex(c, qp, qs); + qp = lp; + + /* move on to the next edge of qp and keep the original edge for + reference */ + iqs = qs; + ++qs; + if (qs == c->orders[qp]) { + qs = 0; + } + + /* test the next edges, and try to find one that does NOT lie below the + plane */ + lp = voronoi_get_edge(c, qp, qs); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + safewhile(lw == -1) { + ++k; + ++qs; + if (qs == c->orders[qp]) { + qs = 0; + } + lp = voronoi_get_edge(c, qp, qs); + lw = voronoi_test_vertex(&c->vertices[3 * lp], dx, r2, &l, teststack, + &teststack_size); + } + + /* qs now contains the next edge NOT below the plane + k contains the order of the new vertex to create: the number of edges + below the plane + 2 (+1 if we have a double edge) */ + + /* if qp (the vertex in the plane) was already visited before, visitflags + will contain the index of the newly created vertex that replaces it */ + j = visitflags[qp]; + + /* we need to find out what the order of the new vertex will be, and if we + are dealing with a new double edge or not */ + if (qp == up && qs == us) { + new_double_edge = 0; + if (j > 0) { + k += c->orders[j]; + } + } else { + if (j > 0) { + k += c->orders[j]; + if (lw == 0) { + i = -visitflags[lp]; + if (i > 0) { + if (voronoi_get_edge(c, i, c->orders[i] - 1) == j) { + new_double_edge = 1; + --k; + } else { + new_double_edge = 0; + } + } else { + if (j == rp && lp == up && voronoi_get_edge(c, qp, qs) == us) { + new_double_edge = 1; + --k; + } else { + new_double_edge = 0; + } + } + } else { + new_double_edge = 0; + } + } else { + if (lw == 0) { + i = -visitflags[lp]; + if (i == cp) { + new_double_edge = 1; + --k; + } else { + new_double_edge = 0; + } + } else { + new_double_edge = 0; + } + } + } + + // if (j > 0) { + // error("Case not handled!"); + // } + + /* create new order k vertex */ + vindex = c->nvert; + ++c->nvert; + if (c->nvert == VORONOI3D_MAXNUMVERT) { + error("Too many vertices!"); + } + c->orders[vindex] = k; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + if (c->offsets[vindex] + k >= VORONOI3D_MAXNUMEDGE) { + error("Too many edges!"); + } + + visitflags[vindex] = -vindex; + c->vertices[3 * vindex + 0] = c->vertices[3 * qp + 0]; + c->vertices[3 * vindex + 1] = c->vertices[3 * qp + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * qp + 2]; + + visitflags[qp] = vindex; + dstack[dstack_size] = qp; + ++dstack_size; + j = vindex; + i = 0; + + if (!double_edge) { + voronoi_set_ngb(c, j, i, ngb); + voronoi_set_edge(c, j, i, cp); + voronoi_set_edgeindex(c, j, i, cs); + voronoi_set_edge(c, cp, cs, j); + voronoi_set_edgeindex(c, cp, cs, i); + ++i; + } + + qs = iqs; + iqs = k - 1; + if (new_double_edge) { + iqs = k; + } + safewhile(i < iqs) { + ++qs; + if (qs == c->orders[qp]) { + qs = 0; + } + lp = voronoi_get_edge(c, qp, qs); + ls = voronoi_get_edgeindex(c, qp, qs); + voronoi_set_ngb(c, j, i, voronoi_get_ngb(c, qp, qs)); + voronoi_set_edge(c, j, i, lp); + voronoi_set_edgeindex(c, j, i, ls); + voronoi_set_edge(c, lp, ls, j); + voronoi_set_edgeindex(c, lp, ls, i); + voronoi_set_edge(c, qp, qs, -1); + ++i; + } + ++qs; + if (qs == c->orders[qp]) { + qs = 0; + } + cs = i; + cp = j; + + if (new_double_edge) { + voronoi_set_ngb(c, j, 0, voronoi_get_ngb(c, qp, qs)); + } else { + voronoi_set_ngb(c, j, cs, voronoi_get_ngb(c, qp, qs)); + } + + double_edge = new_double_edge; + } else { /* if(lw == 0) */ + + /* normal case: next vertex lies below or above the plane */ + + if (lw == 1) { + + /* vertex lies above the plane */ + + /* we just delete the vertex and continue with the next edge of this + vertex */ + + qs = voronoi_get_edgeindex(c, qp, qs) + 1; + if (qs == c->orders[lp]) { + qs = 0; + } + qp = lp; + q = l; + dstack[dstack_size] = qp; + ++dstack_size; + } else { + + /* vertex lies below the plane */ + + /* we have found our next intersected edge: create a new vertex and link + it to the other vertices */ + + if (q == l) { + error("Upper and lower vertex are the same!"); + } + + r = q / (q - l); + l = 1.0f - r; + + if (r > FLT_MAX || r < -FLT_MAX || l > FLT_MAX || l < -FLT_MAX) { + error("Value out of bounds (r: %g, l: %g)!", r, l); + } + + /* create new order 3 vertex */ + vindex = c->nvert; + ++c->nvert; + if (c->nvert == VORONOI3D_MAXNUMVERT) { + error("Too many vertices!"); + } + visitflags[vindex] = -vindex; + c->orders[vindex] = 3; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + if (c->offsets[vindex] + 3 >= VORONOI3D_MAXNUMEDGE) { + error("Too many edges!"); + } + + c->vertices[3 * vindex + 0] = + c->vertices[3 * lp + 0] * r + c->vertices[3 * qp + 0] * l; + c->vertices[3 * vindex + 1] = + c->vertices[3 * lp + 1] * r + c->vertices[3 * qp + 1] * l; + c->vertices[3 * vindex + 2] = + c->vertices[3 * lp + 2] * r + c->vertices[3 * qp + 2] * l; + + /* link the edges: + the first edge is connected to the last edge of the previous new + vertex. The last edge will be connected to the next new vertex, and + is left open for the moment */ + ls = voronoi_get_edgeindex(c, qp, qs); + voronoi_set_edge(c, vindex, 0, cp); + voronoi_set_edge(c, vindex, 1, lp); + voronoi_set_edgeindex(c, vindex, 0, cs); + voronoi_set_edgeindex(c, vindex, 1, ls); + voronoi_set_edge(c, lp, ls, vindex); + voronoi_set_edgeindex(c, lp, ls, 1); + voronoi_set_edge(c, cp, cs, vindex); + voronoi_set_edgeindex(c, cp, cs, 0); + voronoi_set_edge(c, qp, qs, -1); + + voronoi_set_ngb(c, vindex, 0, ngb); + voronoi_set_ngb(c, vindex, 1, voronoi_get_ngb(c, qp, qs)); + voronoi_set_ngb(c, vindex, 2, voronoi_get_ngb(c, lp, ls)); + + /* continue with the next edge of qp (the last vertex above the + midplane */ + ++qs; + if (qs == c->orders[qp]) { + qs = 0; + } + /* store the last newly created vertex and its dangling edge for the + next iteration */ + cp = vindex; + cs = 2; + } /* if(lw == 1) */ + + } /* if(lw == 0) */ + + } /* while() */ + + /* we finished adding new vertices. Now connect the last dangling edge of the + last newly created vertex to the first dangling edge of the first newly + created vertex */ + voronoi_set_edge(c, cp, cs, rp); + voronoi_set_edge(c, rp, 0, cp); + voronoi_set_edgeindex(c, cp, cs, 0); + voronoi_set_edgeindex(c, rp, 0, cs); + + /* now remove the vertices in the delete stack */ + + /* the algorithm above did not necessarily visit all vertices above the plane. + here we scan for vertices that are linked to vertices that are to be + removed and add them to the delete stack if necessary + this only works because we made sure that all deleted vertices no longer + have edges that connect them to vertices that need to stay */ + for (i = 0; i < dstack_size; ++i) { + for (j = 0; j < c->orders[dstack[i]]; ++j) { + if (voronoi_get_edge(c, dstack[i], j) >= 0) { + dstack[dstack_size] = voronoi_get_edge(c, dstack[i], j); + ++dstack_size; + voronoi_set_edge(c, dstack[i], j, -1); + voronoi_set_edgeindex(c, dstack[i], j, -1); + } + } + } + + /* collapse order 1 and 2 vertices: vertices with only 1 edge or 2 edges that + can be created during the plane intersection routine */ + /* first flag them */ + int low_order_stack[VORONOI3D_MAXNUMVERT]; + int low_order_index = 0; + for (i = 0; i < c->nvert; ++i) { + if (voronoi_get_edge(c, i, 0) >= 0 && c->orders[i] < 3) { + low_order_stack[low_order_index] = i; + ++low_order_index; + } + } + + /* now remove them */ + safewhile(low_order_index) { + int v = low_order_stack[low_order_index - 1]; + /* the vertex might already have been deleted by a previous operation */ + if (voronoi_get_edge(c, v, 0) < 0) { + --low_order_index; + continue; + } + if (c->orders[v] == 2) { + int j = voronoi_get_edge(c, v, 0); + int k = voronoi_get_edge(c, v, 1); + int b = voronoi_get_edgeindex(c, v, 1); + int l = 0; + safewhile(l < c->orders[j] && voronoi_get_edge(c, j, l) != k) { ++l; } + if (l == c->orders[j]) { + int a = voronoi_get_edgeindex(c, v, 0); + /* j and k are not joined together. Replace their edges pointing to v + with a new edge pointing from j to k */ + voronoi_set_edge(c, j, a, k); + voronoi_set_edgeindex(c, j, a, b); + voronoi_set_edge(c, k, b, j); + voronoi_set_edgeindex(c, k, b, a); + /* no new elements added to the stack: decrease the counter */ + --low_order_index; + } else { + /* just remove the edges from j to v and from k to v: create two new + vertices */ + /* vertex j */ + vindex = c->nvert; + ++c->nvert; + c->vertices[3 * vindex] = c->vertices[3 * j]; + c->vertices[3 * vindex + 1] = c->vertices[3 * j + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * j + 2]; + c->orders[vindex] = c->orders[j] - 1; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + int m = 0; + for (int n = 0; n < c->orders[j]; ++n) { + int l = voronoi_get_edge(c, j, n); + if (l != v) { + /* make a new edge */ + voronoi_set_edge(c, vindex, m, l); + voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, j, n)); + /* update the other vertex */ + voronoi_set_edge(c, l, voronoi_get_edgeindex(c, j, n), vindex); + voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, j, n), m); + /* copy ngb information */ + voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, j, n)); + ++m; + } + /* remove the old vertex */ + voronoi_set_edge(c, j, n, -1); + voronoi_set_edgeindex(c, j, n, -1); + } + /* vertex k */ + vindex = c->nvert; + ++c->nvert; + c->vertices[3 * vindex] = c->vertices[3 * k]; + c->vertices[3 * vindex + 1] = c->vertices[3 * k + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * k + 2]; + c->orders[vindex] = c->orders[k] - 1; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + m = 0; + for (int n = 0; n < c->orders[k]; ++n) { + int l = voronoi_get_edge(c, k, n); + if (l != v) { + /* make a new edge */ + voronoi_set_edge(c, vindex, m, l); + voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, k, n)); + /* update the other vertex */ + voronoi_set_edge(c, l, voronoi_get_edgeindex(c, k, n), vindex); + voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, k, n), m); + /* copy ngb information */ + /* this one is special: we copy the ngb corresponding to the + deleted edge and skip the one after that */ + if (n == b + 1) { + voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, k, b)); + } else { + voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, k, n)); + } + ++m; + } + /* remove the old vertex */ + voronoi_set_edge(c, k, n, -1); + voronoi_set_edgeindex(c, k, n, -1); + } + /* check if j or k has become an order 2 vertex */ + /* if they have become an order 1 vertex, they were already an order 2 + vertex, and they should already be in the list... */ + if (c->orders[vindex] == 2) { + if (c->orders[vindex - 1] == 2) { + low_order_stack[low_order_index] = vindex - 1; + ++low_order_index; + low_order_stack[low_order_index] = vindex; + /* we do not increase the index here: we want this element to be the + next element that is processed */ + } else { + low_order_stack[low_order_index] = vindex; + } + } else { + if (c->orders[vindex - 1] == 2) { + low_order_stack[low_order_index] = vindex - 1; + } else { + /* no new vertices added to the stack: decrease the counter */ + --low_order_index; + } + } + } + /* Remove the vertex */ + voronoi_set_edge(c, v, 0, -1); + voronoi_set_edgeindex(c, v, 0, -1); + voronoi_set_edge(c, v, 1, -1); + voronoi_set_edgeindex(c, v, 1, -1); + } else if (c->orders[v] == 1) { + int j = voronoi_get_edge(c, v, 0); + /* we have to remove the edge between j and v. We create a new vertex */ + vindex = c->nvert; + ++c->nvert; + c->vertices[3 * vindex] = c->vertices[3 * j]; + c->vertices[3 * vindex + 1] = c->vertices[3 * j + 1]; + c->vertices[3 * vindex + 2] = c->vertices[3 * j + 2]; + c->orders[vindex] = c->orders[j] - 1; + c->offsets[vindex] = c->offsets[vindex - 1] + c->orders[vindex - 1]; + int m = 0; + for (int k = 0; k < c->orders[j]; ++k) { + int l = voronoi_get_edge(c, j, k); + if (l != v) { + /* make a new edge */ + voronoi_set_edge(c, vindex, m, l); + voronoi_set_edgeindex(c, vindex, m, voronoi_get_edgeindex(c, j, k)); + /* update the other vertex */ + voronoi_set_edge(c, l, voronoi_get_edgeindex(c, j, k), vindex); + voronoi_set_edgeindex(c, l, voronoi_get_edgeindex(c, j, k), m); + /* copy ngb information */ + voronoi_set_ngb(c, vindex, m, voronoi_get_ngb(c, j, k)); + ++m; + } + /* remove the old vertex */ + voronoi_set_edge(c, j, k, -1); + voronoi_set_edgeindex(c, j, k, -1); + } + /* if the new vertex is a new order 2 vertex, add it to the stack */ + if (c->orders[vindex] == 2) { + low_order_stack[low_order_index - 1] = vindex; + } else { + --low_order_index; + } + /* remove the order 1 vertex */ + voronoi_set_edge(c, v, 0, -1); + voronoi_set_edgeindex(c, v, 0, -1); + } else { + error("Vertex with order %i. This should not happen!", c->orders[v]); + } + } + + /* remove deleted vertices from all arrays */ + struct voronoi_cell new_cell; + /* make sure the contents of the new cell are the same as for the old cell */ + memcpy(&new_cell, c, sizeof(struct voronoi_cell)); + int m, n; + for (vindex = 0; vindex < c->nvert; ++vindex) { + j = vindex; + /* find next edge that is not deleted */ + safewhile(j < c->nvert && voronoi_get_edge(c, j, 0) < 0) { ++j; } + + if (j == c->nvert) { + /* ready */ + break; + } + + /* copy vertices */ + new_cell.vertices[3 * vindex + 0] = c->vertices[3 * j + 0]; + new_cell.vertices[3 * vindex + 1] = c->vertices[3 * j + 1]; + new_cell.vertices[3 * vindex + 2] = c->vertices[3 * j + 2]; + + /* copy order */ + new_cell.orders[vindex] = c->orders[j]; + + /* set offset */ + if (vindex) { + new_cell.offsets[vindex] = + new_cell.offsets[vindex - 1] + new_cell.orders[vindex - 1]; + } else { + new_cell.offsets[vindex] = 0; + } + + /* copy edges, edgeindices and ngbs */ + for (k = 0; k < c->orders[j]; ++k) { + voronoi_set_edge(&new_cell, vindex, k, voronoi_get_edge(c, j, k)); + voronoi_set_edgeindex(&new_cell, vindex, k, + voronoi_get_edgeindex(c, j, k)); + voronoi_set_ngb(&new_cell, vindex, k, voronoi_get_ngb(c, j, k)); + } + + /* update other edges */ + for (k = 0; k < c->orders[j]; ++k) { + m = voronoi_get_edge(c, j, k); + n = voronoi_get_edgeindex(c, j, k); + if (m < vindex) { + voronoi_set_edge(&new_cell, m, n, vindex); + } else { + voronoi_set_edge(c, m, n, vindex); + } + } + + /* deactivate edge */ + voronoi_set_edge(c, j, 0, -1); + } + new_cell.nvert = vindex; + + new_cell.x[0] = c->x[0]; + new_cell.x[1] = c->x[1]; + new_cell.x[2] = c->x[2]; + new_cell.centroid[0] = c->centroid[0]; + new_cell.centroid[1] = c->centroid[1]; + new_cell.centroid[2] = c->centroid[2]; + new_cell.volume = c->volume; + new_cell.nface = c->nface; + + /* Update the cell values. */ + voronoi3d_cell_copy(&new_cell, c); + +#ifdef VORONOI3D_EXPENSIVE_CHECKS + voronoi_check_cell_consistency(c); +#endif +} + +/** + * @brief Get the volume of the tetrahedron made up by the four given vertices. + * + * The vertices are not expected to be oriented in a specific way. If the input + * happens to be coplanar or colinear, the returned volume will just be zero. + * + * @param v1 First vertex. + * @param v2 Second vertex. + * @param v3 Third vertex. + * @param v4 Fourth vertex. + * @return Volume of the tetrahedron. + */ +__attribute__((always_inline)) INLINE float voronoi_volume_tetrahedron( + const float *v1, const float *v2, const float *v3, const float *v4) { + + float V; + float r1[3], r2[3], r3[3]; + + r1[0] = v2[0] - v1[0]; + r1[1] = v2[1] - v1[1]; + r1[2] = v2[2] - v1[2]; + r2[0] = v3[0] - v1[0]; + r2[1] = v3[1] - v1[1]; + r2[2] = v3[2] - v1[2]; + r3[0] = v4[0] - v1[0]; + r3[1] = v4[1] - v1[1]; + r3[2] = v4[2] - v1[2]; + V = fabs(r1[0] * r2[1] * r3[2] + r1[1] * r2[2] * r3[0] + + r1[2] * r2[0] * r3[1] - r1[2] * r2[1] * r3[0] - + r2[2] * r3[1] * r1[0] - r3[2] * r1[1] * r2[0]); + V /= 6.; + return V; +} + +/** + * @brief Get the centroid of the tetrahedron made up by the four given + * vertices. + * + * The centroid is just the average of four vertex coordinates. + * + * @param centroid Array to store the centroid in. + * @param v1 First vertex. + * @param v2 Second vertex. + * @param v3 Third vertex. + * @param v4 Fourth vertex. + */ +__attribute__((always_inline)) INLINE void voronoi_centroid_tetrahedron( + float *centroid, const float *v1, const float *v2, const float *v3, + const float *v4) { + + centroid[0] = 0.25f * (v1[0] + v2[0] + v3[0] + v4[0]); + centroid[1] = 0.25f * (v1[1] + v2[1] + v3[1] + v4[1]); + centroid[2] = 0.25f * (v1[2] + v2[2] + v3[2] + v4[2]); +} + +/** + * @brief Calculate the volume and centroid of a 3D Voronoi cell. + * + * @param cell 3D Voronoi cell. + */ +__attribute__((always_inline)) INLINE void voronoi_calculate_cell( + struct voronoi_cell *cell) { + + float v1[3], v2[3], v3[3], v4[3]; + int i, j, k, l, m, n; + float tcentroid[3]; + float tvol; + + /* we need to calculate the volume of the tetrahedra formed by the first + vertex and the triangles that make up the other faces + since we do not store faces explicitly, this means keeping track of the + edges that have been processed somehow + we follow the method used in voro++ and "flip" processed edges to + negative values + this also means that we need to process all triangles corresponding to + an edge at once */ + cell->volume = 0.0f; + v1[0] = cell->vertices[0]; + v1[1] = cell->vertices[1]; + v1[2] = cell->vertices[2]; + cell->centroid[0] = 0.0f; + cell->centroid[1] = 0.0f; + cell->centroid[2] = 0.0f; + + /* loop over all vertices (except the first one) */ + for (i = 1; i < cell->nvert; ++i) { + + v2[0] = cell->vertices[3 * i + 0]; + v2[1] = cell->vertices[3 * i + 1]; + v2[2] = cell->vertices[3 * i + 2]; + + /* loop over the edges of the vertex*/ + for (j = 0; j < cell->orders[i]; ++j) { + + k = voronoi_get_edge(cell, i, j); + + if (k >= 0) { + + /* mark the edge as processed */ + voronoi_set_edge(cell, i, j, -k - 1); + + l = voronoi_get_edgeindex(cell, i, j) + 1; + if (l == cell->orders[k]) { + l = 0; + } + v3[0] = cell->vertices[3 * k + 0]; + v3[1] = cell->vertices[3 * k + 1]; + v3[2] = cell->vertices[3 * k + 2]; + m = voronoi_get_edge(cell, k, l); + voronoi_set_edge(cell, k, l, -1 - m); + + int loopcount = 0; + safewhile(m != i) { + if (loopcount == 999) { + voronoi_print_cell(cell); + voronoi_print_gnuplot_c(cell); + } + ++loopcount; + n = voronoi_get_edgeindex(cell, k, l) + 1; + if (n == cell->orders[m]) { + n = 0; + } + v4[0] = cell->vertices[3 * m + 0]; + v4[1] = cell->vertices[3 * m + 1]; + v4[2] = cell->vertices[3 * m + 2]; + tvol = voronoi_volume_tetrahedron(v1, v2, v3, v4); + cell->volume += tvol; + voronoi_centroid_tetrahedron(tcentroid, v1, v2, v3, v4); + cell->centroid[0] += tcentroid[0] * tvol; + cell->centroid[1] += tcentroid[1] * tvol; + cell->centroid[2] += tcentroid[2] * tvol; + k = m; + l = n; + v3[0] = v4[0]; + v3[1] = v4[1]; + v3[2] = v4[2]; + m = voronoi_get_edge(cell, k, l); + voronoi_set_edge(cell, k, l, -1 - m); + } /* while() */ + + } /* if(k >= 0) */ + + } /* for(j) */ + + } /* for(i) */ + + cell->centroid[0] /= cell->volume; + cell->centroid[1] /= cell->volume; + cell->centroid[2] /= cell->volume; + + /* centroid was calculated relative w.r.t. particle position */ + cell->centroid[0] += cell->x[0]; + cell->centroid[1] += cell->x[1]; + cell->centroid[2] += cell->x[2]; + + /* Reset the edges: we still need them for the face calculation */ + for (i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) { + if (cell->edges[i] < 0) { + cell->edges[i] = -1 - cell->edges[i]; + } + } +} + +/** + * @brief Calculate the faces for a 3D Voronoi cell. This reorganizes the + * internal variables of the cell, so no new neighbours can be added after + * this method has been called! + * + * Note that the face midpoints are calculated relative w.r.t. the cell + * generator! + * + * @param cell 3D Voronoi cell. + */ +__attribute__((always_inline)) INLINE void voronoi_calculate_faces( + struct voronoi_cell *cell) { + + int i, j, k, l, m, n; + float area; + float midpoint[3]; + float u[3], v[3], w[3]; + float loc_area; + unsigned long long newngbs[VORONOI3D_MAXNUMEDGE]; + + cell->nface = 0; + for (i = 0; i < cell->nvert; ++i) { + + for (j = 0; j < cell->orders[i]; ++j) { + + k = voronoi_get_edge(cell, i, j); + + if (k >= 0) { + + newngbs[cell->nface] = voronoi_get_ngb(cell, i, j); + area = 0.; + midpoint[0] = 0.; + midpoint[1] = 0.; + midpoint[2] = 0.; + voronoi_set_edge(cell, i, j, -1 - k); + l = voronoi_get_edgeindex(cell, i, j) + 1; + if (l == cell->orders[k]) { + l = 0; + } + m = voronoi_get_edge(cell, k, l); + voronoi_set_edge(cell, k, l, -1 - m); + + safewhile(m != i) { + n = voronoi_get_edgeindex(cell, k, l) + 1; + if (n == cell->orders[m]) { + n = 0; + } + u[0] = cell->vertices[3 * k + 0] - cell->vertices[3 * i + 0]; + u[1] = cell->vertices[3 * k + 1] - cell->vertices[3 * i + 1]; + u[2] = cell->vertices[3 * k + 2] - cell->vertices[3 * i + 2]; + v[0] = cell->vertices[3 * m + 0] - cell->vertices[3 * i + 0]; + v[1] = cell->vertices[3 * m + 1] - cell->vertices[3 * i + 1]; + v[2] = cell->vertices[3 * m + 2] - cell->vertices[3 * i + 2]; + w[0] = u[1] * v[2] - u[2] * v[1]; + w[1] = u[2] * v[0] - u[0] * v[2]; + w[2] = u[0] * v[1] - u[1] * v[0]; + loc_area = sqrtf(w[0] * w[0] + w[1] * w[1] + w[2] * w[2]); + area += loc_area; + midpoint[0] += loc_area * (cell->vertices[3 * k + 0] + + cell->vertices[3 * i + 0] + + cell->vertices[3 * m + 0]); + midpoint[1] += loc_area * (cell->vertices[3 * k + 1] + + cell->vertices[3 * i + 1] + + cell->vertices[3 * m + 1]); + midpoint[2] += loc_area * (cell->vertices[3 * k + 2] + + cell->vertices[3 * i + 2] + + cell->vertices[3 * m + 2]); + k = m; + l = n; + m = voronoi_get_edge(cell, k, l); + voronoi_set_edge(cell, k, l, -1 - m); + } + + cell->face_areas[cell->nface] = 0.5f * area; + cell->face_midpoints[cell->nface][0] = midpoint[0] / area / 3.0f; + cell->face_midpoints[cell->nface][1] = midpoint[1] / area / 3.0f; + cell->face_midpoints[cell->nface][2] = midpoint[2] / area / 3.0f; + ++cell->nface; + + if (cell->nface == VORONOI3D_MAXFACE) { + error("Too many faces!"); + } + + } /* if(k >= 0) */ + + } /* for(j) */ + + } /* for(i) */ + + /* Overwrite the old neighbour array. */ + for (i = 0; i < cell->nface; ++i) { + cell->ngbs[i] = newngbs[i]; + } +} + +/******************************************************************************* + * voronoi_algorithm interface implementations + * + * If you change any function parameters below, you also have to change them in + * the 1D and 2D algorithm! + ******************************************************************************/ + +/** + * @brief Initialize a 3D Voronoi cell. + * + * @param cell 3D Voronoi cell to initialize. + * @param x Position of the generator of the cell. + * @param anchor Anchor of the simulation box. + * @param side Side lengths of the simulation box. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_init( + struct voronoi_cell *cell, const double *x, const double *anchor, + const double *side) { + + cell->x[0] = x[0]; + cell->x[1] = x[1]; + cell->x[2] = x[2]; + + voronoi_initialize(cell, anchor, side); + + cell->volume = 0.0f; + cell->centroid[0] = 0.0f; + cell->centroid[1] = 0.0f; + cell->centroid[2] = 0.0f; + cell->nface = 0; +} + +/** + * @brief Interact a 3D Voronoi cell with a particle with given relative + * position and ID. + * + * @param cell 3D Voronoi cell. + * @param dx Relative position of the interacting generator w.r.t. the cell + * generator (in fact: dx = generator - neighbour). + * @param id ID of the interacting neighbour. + */ +__attribute__((always_inline)) INLINE void voronoi_cell_interact( + struct voronoi_cell *cell, const float *dx, unsigned long long id) { + + voronoi_intersect(cell, dx, id); +} + +/** + * @brief Finalize a 3D Voronoi cell. + * + * @param cell 3D Voronoi cell. + * @return Maximal radius that could still change the structure of the cell. + */ +__attribute__((always_inline)) INLINE float voronoi_cell_finalize( + struct voronoi_cell *cell) { + + int i; + float max_radius, v[3], v2; + + /* Calculate the volume and centroid of the cell. */ + voronoi_calculate_cell(cell); + /* Calculate the faces. */ + voronoi_calculate_faces(cell); + + /* Loop over the vertices and calculate the maximum radius. */ + max_radius = 0.0f; + for (i = 0; i < cell->nvert; ++i) { + v[0] = cell->vertices[3 * i]; + v[1] = cell->vertices[3 * i + 1]; + v[2] = cell->vertices[3 * i + 2]; + v2 = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + max_radius = fmaxf(max_radius, v2); + } + max_radius = sqrtf(max_radius); + + return 2.0f * max_radius; +} + +/** + * @brief Get the surface area and midpoint of the face between a 3D Voronoi + * cell and the given neighbour. + * + * @param cell 3D Voronoi cell. + * @param ngb ID of a particle that is possibly a neighbour of this cell. + * @param midpoint Array to store the relative position of the face in. + * @return 0 if the given neighbour is not a neighbour, the surface area of + * the face otherwise. + */ +__attribute__((always_inline)) INLINE float voronoi_get_face( + const struct voronoi_cell *cell, unsigned long long ngb, float *midpoint) { + + int i = 0; + while (i < cell->nface && cell->ngbs[i] != ngb) { + ++i; + } + if (i == cell->nface) { + /* Ngb not found */ + return 0.0f; + } + + midpoint[0] = cell->face_midpoints[i][0]; + midpoint[1] = cell->face_midpoints[i][1]; + midpoint[2] = cell->face_midpoints[i][2]; + + return cell->face_areas[i]; +} + +/** + * @brief Get the centroid of a 3D Voronoi cell. + * + * @param cell 3D Voronoi cell. + * @param centroid Array to store the centroid in. + */ +__attribute__((always_inline)) INLINE void voronoi_get_centroid( + const struct voronoi_cell *cell, float *centroid) { + + centroid[0] = cell->centroid[0]; + centroid[1] = cell->centroid[1]; + centroid[2] = cell->centroid[2]; +} + +#endif // SWIFT_VORONOIXD_ALGORITHM_H diff --git a/src/hydro/Shadowswift/voronoi3d_cell.h b/src/hydro/Shadowswift/voronoi3d_cell.h new file mode 100644 index 0000000000000000000000000000000000000000..ef43eff1745f48219af14aec2455aaa5e5b0d47a --- /dev/null +++ b/src/hydro/Shadowswift/voronoi3d_cell.h @@ -0,0 +1,143 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOIXD_CELL_H +#define SWIFT_VORONOIXD_CELL_H + +/* Maximal number of neighbours that can be stored in a voronoi_cell struct */ +#define VORONOI3D_MAXNUMNGB 100 +/* Maximal number of vertices that can be stored in a voronoi_cell struct */ +#define VORONOI3D_MAXNUMVERT 500 +/* Maximal number of edges that can be stored in a voronoi_cell struct */ +#define VORONOI3D_MAXNUMEDGE 1500 +/* Maximal number of faces that can be stored in a voronoi_cell struct */ +#define VORONOI3D_MAXFACE 100 + +/* 3D Voronoi cell */ +struct voronoi_cell { + + /* The position of the generator of the cell. */ + double x[3]; + + /* The volume of the 3D cell. */ + float volume; + + /* The centroid of the cell. */ + float centroid[3]; + + /* Number of cell vertices. */ + int nvert; + + /* Vertex coordinates. */ + float vertices[3 * VORONOI3D_MAXNUMVERT]; + + /* Number of edges for every vertex. */ + char orders[VORONOI3D_MAXNUMVERT]; + + /* Offsets of the edges, edgeindices and neighbours corresponding to a + particular vertex in the internal arrays */ + int offsets[VORONOI3D_MAXNUMVERT]; + + /* Edge information. Edges are ordered counterclockwise w.r.t. a vector + pointing from the cell generator to the vertex. */ + int edges[VORONOI3D_MAXNUMEDGE]; + + /* Additional edge information. */ + char edgeindices[VORONOI3D_MAXNUMEDGE]; + + /* Neighbour information. This field is used differently depending on where we + are in the algorithm. During cell construction, it contains, for every edge + of every vertex, the index of the neighbour that generates the face + counterclockwise of the edge w.r.t. a vector pointing from the vertex along + the edge. After cell finalization, it contains a neighbour for every face, + in the same order as the face_areas and face_midpoints arrays. */ + unsigned long long ngbs[VORONOI3D_MAXNUMEDGE]; + + /* Number of faces of the cell. */ + unsigned char nface; + + /* Surface areas of the cell faces. */ + float face_areas[VORONOI3D_MAXFACE]; + + /* Midpoints of the cell faces. */ + float face_midpoints[VORONOI3D_MAXFACE][3]; +}; + +/** + * @brief Copy the contents of the 3D Voronoi cell pointed to by source into the + * 3D Voronoi cell pointed to by destination + * + * @param source Pointer to a 3D Voronoi cell to read from. + * @param destination Pointer to a 3D Voronoi cell to write to. + */ +__attribute__((always_inline)) INLINE void voronoi3d_cell_copy( + struct voronoi_cell *source, struct voronoi_cell *destination) { + + /* Copy the position of the generator of the cell. */ + destination->x[0] = source->x[0]; + destination->x[1] = source->x[1]; + destination->x[2] = source->x[2]; + + /* Copy the volume of the 3D cell. */ + destination->volume = source->volume; + + /* Copy the centroid of the cell. */ + destination->centroid[0] = source->centroid[0]; + destination->centroid[1] = source->centroid[1]; + destination->centroid[2] = source->centroid[2]; + + /* Copy the number of cell vertices. */ + destination->nvert = source->nvert; + + /* Copy the vertex coordinates. We only copy the 3*nvert first coordinates. */ + for (int i = 0; i < 3 * source->nvert; ++i) { + destination->vertices[i] = source->vertices[i]; + } + + /* Copy the number of edges for every vertex. Again, we only copy the nvert + first values. */ + for (int i = 0; i < source->nvert; ++i) { + destination->orders[i] = source->orders[i]; + } + + /* Copy the nvert first values of the offsets. */ + for (int i = 0; i < source->nvert; ++i) { + destination->offsets[i] = source->offsets[i]; + } + + /* Copy the edge information. No idea how many edges we have, so we copy + everything. */ + for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) { + destination->edges[i] = source->edges[i]; + } + + /* Copy all additional edge information. */ + for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) { + destination->edgeindices[i] = source->edgeindices[i]; + } + + /* Copy neighbour information. Since neighbours are stored per edge, the total + number of neighbours in this list is larger than numngb and we copy + everything. */ + for (int i = 0; i < VORONOI3D_MAXNUMEDGE; ++i) { + destination->ngbs[i] = source->ngbs[i]; + } +} + +#endif // SWIFT_VORONOIXD_CELL_H diff --git a/src/sort.h b/src/hydro/Shadowswift/voronoi_algorithm.h similarity index 62% rename from src/sort.h rename to src/hydro/Shadowswift/voronoi_algorithm.h index 62f4eab53d85265f67e3ab9e5d84d862c215b4d6..19ecc723741e6c91b19e85f6457311a80c6faf34 100644 --- a/src/sort.h +++ b/src/hydro/Shadowswift/voronoi_algorithm.h @@ -1,10 +1,6 @@ /******************************************************************************* * This file is part of SWIFT. - * Copyright (c) 2012 Pedro Gonnet (pedro.gonnet@durham.ac.uk) - * Matthieu Schaller (matthieu.schaller@durham.ac.uk) - * 2015 Peter W. Draper (p.w.draper@durham.ac.uk) - * 2016 John A. Regan (john.a.regan@durham.ac.uk) - * Tom Theuns (tom.theuns@durham.ac.uk) + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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 @@ -20,19 +16,18 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. * ******************************************************************************/ -#ifndef SWIFT_SORT_H -#define SWIFT_SORT_H -/** - * @brief Entry in a list of sorted indices. - */ -struct entry { - - /*! Distance on the axis */ - float d; - - /*! Particle index */ - int i; -}; +#ifndef SWIFT_VORONOI_ALGORITHM_H +#define SWIFT_VORONOI_ALGORITHM_H +#if defined(HYDRO_DIMENSION_1D) +#include "voronoi1d_algorithm.h" +#elif defined(HYDRO_DIMENSION_2D) +#include "voronoi2d_algorithm.h" +#elif defined(HYDRO_DIMENSION_3D) +#include "voronoi3d_algorithm.h" +#else +#error "You have to select a dimension for the hydro!" #endif + +#endif // SWIFT_VORONOI_ALGORITHM_H diff --git a/src/hydro/Shadowswift/voronoi_cell.h b/src/hydro/Shadowswift/voronoi_cell.h new file mode 100644 index 0000000000000000000000000000000000000000..30d3e17fdfa76448773c0e45834ddf732989a3a4 --- /dev/null +++ b/src/hydro/Shadowswift/voronoi_cell.h @@ -0,0 +1,33 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#ifndef SWIFT_VORONOI_CELL_H +#define SWIFT_VORONOI_CELL_H + +#if defined(HYDRO_DIMENSION_1D) +#include "voronoi1d_cell.h" +#elif defined(HYDRO_DIMENSION_2D) +#include "voronoi2d_cell.h" +#elif defined(HYDRO_DIMENSION_3D) +#include "voronoi3d_cell.h" +#else +#error "You have to select a dimension for the hydro!" +#endif + +#endif // SWIFT_VORONOI_CELL_H diff --git a/src/hydro_io.h b/src/hydro_io.h index 05ae94ade7b103ff1b584dc2447cbab40479d1fc..202d724f821b570b427210bb48b7070563513458 100644 --- a/src/hydro_io.h +++ b/src/hydro_io.h @@ -32,6 +32,8 @@ #include "./hydro/Default/hydro_io.h" #elif defined(GIZMO_SPH) #include "./hydro/Gizmo/hydro_io.h" +#elif defined(SHADOWFAX_SPH) +#include "./hydro/Shadowswift/hydro_io.h" #else #error "Invalid choice of SPH variant" #endif diff --git a/src/hydro_properties.c b/src/hydro_properties.c index 46785b4b2d5b958f6db3bd9813d139575217d6fe..818c1b6349192ed73b28cd4c3ae771f89a3754cd 100644 --- a/src/hydro_properties.c +++ b/src/hydro_properties.c @@ -44,6 +44,12 @@ void hydro_props_init(struct hydro_props *p, p->target_neighbours = pow_dimension(p->eta_neighbours) * kernel_norm; p->delta_neighbours = parser_get_param_float(params, "SPH:delta_neighbours"); +#ifdef SHADOWFAX_SPH + /* change the meaning of target_neighbours and delta_neighbours */ + p->target_neighbours = 1.0f; + p->delta_neighbours = 0.0f; +#endif + /* Maximal smoothing length */ p->h_max = parser_get_opt_param_float(params, "SPH:h_max", hydro_props_default_h_max); diff --git a/src/hydro_space.c b/src/hydro_space.c new file mode 100644 index 0000000000000000000000000000000000000000..c4a6f9c1495a44050c5477ebfdf0bb76a64bfc51 --- /dev/null +++ b/src/hydro_space.c @@ -0,0 +1,52 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "hydro_space.h" +#include "space.h" + +/** + * @brief Initialize the extra space information needed for some hydro schemes. + * + * @param hs #hydro_space to initialize. + * @param s #space containing the hydro space. + */ +#ifdef SHADOWFAX_SPH +__attribute__((always_inline)) INLINE void hydro_space_init( + struct hydro_space *hs, const struct space *s) { + + if (s->periodic) { + hs->anchor[0] = -0.5f * s->dim[0]; + hs->anchor[1] = -0.5f * s->dim[1]; + hs->anchor[2] = -0.5f * s->dim[2]; + hs->side[0] = 2.0f * s->dim[0]; + hs->side[1] = 2.0f * s->dim[1]; + hs->side[2] = 2.0f * s->dim[2]; + } else { + hs->anchor[0] = 0.0f; + hs->anchor[1] = 0.0f; + hs->anchor[2] = 0.0f; + hs->side[0] = s->dim[0]; + hs->side[1] = s->dim[1]; + hs->side[2] = s->dim[2]; + } +} +#else +__attribute__((always_inline)) INLINE void hydro_space_init( + struct hydro_space *hs, const struct space *s) {} +#endif diff --git a/src/hydro_space.h b/src/hydro_space.h new file mode 100644 index 0000000000000000000000000000000000000000..e64b532d16a5b78526dfed22d99558a768d07400 --- /dev/null +++ b/src/hydro_space.h @@ -0,0 +1,43 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2017 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ +#ifndef SWIFT_HYDRO_SPACE_H +#define SWIFT_HYDRO_SPACE_H + +#include "../config.h" + +struct space; + +/** + * @brief Extra space information that is needed for some hydro schemes. + */ +#ifdef SHADOWFAX_SPH +struct hydro_space { + /*! Anchor of the simulation space. */ + double anchor[3]; + + /*! Side lengths of the simulation space. */ + double side[3]; +}; +#else +struct hydro_space {}; +#endif + +void hydro_space_init(struct hydro_space *hs, const struct space *s); + +#endif /* SWIFT_HYDRO_SPACE_H */ diff --git a/src/kernel_gravity.h b/src/kernel_gravity.h index a1e382a21d04b7354aaf215069e999627e56ee07..a9425c7dda8f864a1d6b9c2a658b308c7e7e0624 100644 --- a/src/kernel_gravity.h +++ b/src/kernel_gravity.h @@ -25,6 +25,7 @@ /* Includes. */ #include "const.h" #include "inline.h" +#include "minmax.h" #include "vector.h" /* The gravity kernel is defined as a degree 6 polynomial in the distance @@ -72,7 +73,7 @@ __attribute__((always_inline)) INLINE static void kernel_grav_eval( float u, float *const W) { /* Pick the correct branch of the kernel */ - const int ind = (int)fminf(u * (float)kernel_grav_ivals, kernel_grav_ivals); + const int ind = (int)min(u * (float)kernel_grav_ivals, kernel_grav_ivals); const float *const coeffs = &kernel_grav_coeffs[ind * (kernel_grav_degree + 1)]; diff --git a/src/kernel_long_gravity.h b/src/kernel_long_gravity.h index 6952681999f833bce7755a72aaee742a7fa0ed22..7b1c5984647c3be232770dc32fc1b112ad8bee94 100644 --- a/src/kernel_long_gravity.h +++ b/src/kernel_long_gravity.h @@ -37,14 +37,15 @@ __attribute__((always_inline)) INLINE static void kernel_long_grav_eval( float u, float *const W) { - const float arg1 = u * 0.5f; - const float arg2 = u * one_over_sqrt_pi; - const float arg3 = -arg1 * arg1; + /* const float arg1 = u * 0.5f; */ + /* const float arg2 = u * one_over_sqrt_pi; */ + /* const float arg3 = -arg1 * arg1; */ - const float term1 = erfcf(arg1); - const float term2 = arg2 * expf(arg3); + /* const float term1 = erfcf(arg1); */ + /* const float term2 = arg2 * expf(arg3); */ - *W = term1 + term2; + /* *W = term1 + term2; */ + *W = 1.f; } #endif // SWIFT_KERNEL_LONG_GRAVITY_H diff --git a/src/multipole.h b/src/multipole.h index b5c9335ee8fabf46740cefe310fcfecbea3fd77e..41613ffe835fbe1e16da5918c8f0684ab6c45b97 100644 --- a/src/multipole.h +++ b/src/multipole.h @@ -20,6 +20,9 @@ #ifndef SWIFT_MULTIPOLE_H #define SWIFT_MULTIPOLE_H +/* Config parameters. */ +#include "../config.h" + /* Some standard headers. */ #include <math.h> #include <string.h> @@ -34,25 +37,104 @@ #include "kernel_gravity.h" #include "minmax.h" #include "part.h" +#include "vector_power.h" #define multipole_align 128 -struct acc_tensor { +struct grav_tensor { - double F_000; -}; + /* 0th order terms */ + float F_000; -struct pot_tensor { +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 - double F_000; + /* 1st order terms */ + float F_100, F_010, F_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* 2nd order terms */ + float F_200, F_020, F_002; + float F_110, F_101, F_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* 3rd order terms */ + float F_300, F_030, F_003; + float F_210, F_201; + float F_120, F_021; + float F_102, F_012; + float F_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + + /* 4th order terms */ + float F_400, F_040, F_004; + float F_310, F_301; + float F_130, F_031; + float F_103, F_013; + float F_220, F_202, F_022; + float F_211, F_121, F_112; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif +#ifdef SWIFT_DEBUG_CHECKS + + /* Total number of gpart this field tensor interacted with */ + long long num_interacted; + +#endif }; struct multipole { - float mass; - - /*! Bulk velocity */ + /* Bulk velocity */ float vel[3]; + + /* 0th order terms */ + float M_000; + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + + /* 1st order terms */ + float M_100, M_010, M_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* 2nd order terms */ + float M_200, M_020, M_002; + float M_110, M_101, M_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* 3rd order terms */ + float M_300, M_030, M_003; + float M_210, M_201; + float M_120, M_021; + float M_102, M_012; + float M_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + + /* 4th order terms */ + float M_400, M_040, M_004; + float M_310, M_301; + float M_130, M_031; + float M_103, M_013; + float M_220, M_202, M_022; + float M_211, M_121, M_112; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + +#ifdef SWIFT_DEBUG_CHECKS + + /* Total number of gpart in this multipole */ + long long num_gpart; + +#endif }; /** @@ -72,24 +154,8 @@ struct gravity_tensors { /*! Multipole mass */ struct multipole m_pole; - /*! Field tensor for acceleration along x */ - struct acc_tensor a_x; - - /*! Field tensor for acceleration along y */ - struct acc_tensor a_y; - - /*! Field tensor for acceleration along z */ - struct acc_tensor a_z; - /*! Field tensor for the potential */ - struct pot_tensor pot; - -#ifdef SWIFT_DEBUG_CHECKS - - /* Total mass this gpart interacted with */ - double mass_interacted; - -#endif + struct grav_tensor pot; }; } SWIFT_STRUCT_ALIGN; @@ -104,15 +170,141 @@ INLINE static void gravity_reset(struct gravity_tensors *m) { bzero(m, sizeof(struct gravity_tensors)); } -INLINE static void gravity_field_tensor_init(struct gravity_tensors *m) { +/** + * @brief Drifts a #multipole forward in time. + * + * @param m The #multipole. + * @param dt The drift time-step. + */ +INLINE static void gravity_drift(struct gravity_tensors *m, double dt) { - bzero(&m->a_x, sizeof(struct acc_tensor)); - bzero(&m->a_y, sizeof(struct acc_tensor)); - bzero(&m->a_z, sizeof(struct acc_tensor)); - bzero(&m->pot, sizeof(struct pot_tensor)); + /* Move the whole thing according to bulk motion */ + m->CoM[0] += m->m_pole.vel[0] * dt; + m->CoM[1] += m->m_pole.vel[1] * dt; + m->CoM[2] += m->m_pole.vel[2] * dt; +} + +INLINE static void gravity_field_tensors_init(struct grav_tensor *l) { + + bzero(l, sizeof(struct grav_tensor)); +} +/** + * @brief Adds field tensrs to other ones (i.e. does la += lb). + * + * @param la The gravity tensors to add to. + * @param lb The gravity tensors to add. + */ +INLINE static void gravity_field_tensors_add(struct grav_tensor *la, + const struct grav_tensor *lb) { #ifdef SWIFT_DEBUG_CHECKS - m->mass_interacted = 0.; + if (lb->num_interacted == 0) error("Adding tensors that did not interact"); + la->num_interacted += lb->num_interacted; +#endif + + /* Add 0th order terms */ + la->F_000 += lb->F_000; + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* Add 1st order terms */ + la->F_100 += lb->F_100; + la->F_010 += lb->F_010; + la->F_001 += lb->F_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + /* Add 2nd order terms */ + la->F_200 += lb->F_200; + la->F_020 += lb->F_020; + la->F_002 += lb->F_002; + la->F_110 += lb->F_110; + la->F_101 += lb->F_101; + la->F_011 += lb->F_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + /* Add 3rd order terms */ + la->F_300 += lb->F_300; + la->F_030 += lb->F_030; + la->F_003 += lb->F_003; + la->F_210 += lb->F_210; + la->F_201 += lb->F_201; + la->F_120 += lb->F_120; + la->F_021 += lb->F_021; + la->F_102 += lb->F_102; + la->F_012 += lb->F_012; + la->F_111 += lb->F_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + /* Add 4th order terms */ + la->F_400 += lb->F_400; + la->F_040 += lb->F_040; + la->F_004 += lb->F_004; + la->F_310 += lb->F_310; + la->F_301 += lb->F_301; + la->F_130 += lb->F_130; + la->F_031 += lb->F_031; + la->F_103 += lb->F_103; + la->F_013 += lb->F_013; + la->F_220 += lb->F_220; + la->F_202 += lb->F_202; + la->F_022 += lb->F_022; + la->F_211 += lb->F_211; + la->F_121 += lb->F_121; + la->F_112 += lb->F_112; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif +} + +/** + * @brief Prints the content of a #grav_tensor to stdout. + * + * Note: Uses directly printf(), not a call to message(). + * + * @param l The #grav_tensor to print. + */ +INLINE static void gravity_field_tensors_print(const struct grav_tensor *l) { + + printf("-------------------------\n"); + printf("F_000= %12.5e\n", l->F_000); +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + printf("-------------------------\n"); + printf("F_100= %12.5e F_010= %12.5e F_001= %12.5e\n", l->F_100, l->F_010, + l->F_001); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + printf("-------------------------\n"); + printf("F_200= %12.5e F_020= %12.5e F_002= %12.5e\n", l->F_200, l->F_020, + l->F_002); + printf("F_110= %12.5e F_101= %12.5e F_011= %12.5e\n", l->F_110, l->F_101, + l->F_011); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + printf("-------------------------\n"); + printf("F_300= %12.5e F_030= %12.5e F_003= %12.5e\n", l->F_300, l->F_030, + l->F_003); + printf("F_210= %12.5e F_201= %12.5e F_120= %12.5e\n", l->F_210, l->F_201, + l->F_120); + printf("F_021= %12.5e F_102= %12.5e F_012= %12.5e\n", l->F_021, l->F_102, + l->F_012); + printf("F_111= %12.5e\n", l->F_111); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + printf("-------------------------\n"); + printf("F_400= %12.5e F_040= %12.5e F_004= %12.5e\n", l->F_400, l->F_040, + l->F_004); + printf("F_310= %12.5e F_301= %12.5e F_130= %12.5e\n", l->F_310, l->F_301, + l->F_130); + printf("F_031= %12.5e F_103= %12.5e F_013= %12.5e\n", l->F_031, l->F_103, + l->F_013); + printf("F_220= %12.5e F_202= %12.5e F_022= %12.5e\n", l->F_220, l->F_202, + l->F_022); + printf("F_211= %12.5e F_121= %12.5e F_112= %12.5e\n", l->F_211, l->F_121, + l->F_112); +#endif + printf("-------------------------\n"); +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" #endif } @@ -125,9 +317,48 @@ INLINE static void gravity_field_tensor_init(struct gravity_tensors *m) { */ INLINE static void gravity_multipole_print(const struct multipole *m) { - // printf("CoM= [%12.5e %12.5e %12.5e\n", m->CoM[0], m->CoM[1], m->CoM[2]); - printf("Mass= %12.5e\n", m->mass); - printf("Vel= [%12.5e %12.5e %12.5e\n", m->vel[0], m->vel[1], m->vel[2]); + printf("Vel= [%12.5e %12.5e %12.5e]\n", m->vel[0], m->vel[1], m->vel[2]); + printf("-------------------------\n"); + printf("M_000= %12.5e\n", m->M_000); +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + printf("-------------------------\n"); + printf("M_100= %12.5e M_010= %12.5e M_001= %12.5e\n", m->M_100, m->M_010, + m->M_001); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + printf("-------------------------\n"); + printf("M_200= %12.5e M_020= %12.5e M_002= %12.5e\n", m->M_200, m->M_020, + m->M_002); + printf("M_110= %12.5e M_101= %12.5e M_011= %12.5e\n", m->M_110, m->M_101, + m->M_011); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + printf("-------------------------\n"); + printf("M_300= %12.5e M_030= %12.5e M_003= %12.5e\n", m->M_300, m->M_030, + m->M_003); + printf("M_210= %12.5e M_201= %12.5e M_120= %12.5e\n", m->M_210, m->M_201, + m->M_120); + printf("M_021= %12.5e M_102= %12.5e M_012= %12.5e\n", m->M_021, m->M_102, + m->M_012); + printf("M_111= %12.5e\n", m->M_111); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + printf("-------------------------\n"); + printf("M_400= %12.5e M_040= %12.5e M_004= %12.5e\n", m->M_400, m->M_040, + m->M_004); + printf("M_310= %12.5e M_301= %12.5e M_130= %12.5e\n", m->M_310, m->M_301, + m->M_130); + printf("M_031= %12.5e M_103= %12.5e M_013= %12.5e\n", m->M_031, m->M_103, + m->M_013); + printf("M_220= %12.5e M_202= %12.5e M_022= %12.5e\n", m->M_220, m->M_202, + m->M_022); + printf("M_211= %12.5e M_121= %12.5e M_112= %12.5e\n", m->M_211, m->M_121, + m->M_112); +#endif + printf("-------------------------\n"); +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif } /** @@ -139,88 +370,335 @@ INLINE static void gravity_multipole_print(const struct multipole *m) { INLINE static void gravity_multipole_add(struct multipole *ma, const struct multipole *mb) { - const float mass = ma->mass + mb->mass; - const float imass = 1.f / mass; + const float M_000 = ma->M_000 + mb->M_000; + const float inv_M_000 = 1.f / M_000; /* Add the bulk velocities */ - ma->vel[0] = (ma->vel[0] * ma->mass + mb->vel[0] * mb->mass) * imass; - ma->vel[1] = (ma->vel[1] * ma->mass + mb->vel[1] * mb->mass) * imass; - ma->vel[2] = (ma->vel[2] * ma->mass + mb->vel[2] * mb->mass) * imass; + ma->vel[0] = (ma->vel[0] * ma->M_000 + mb->vel[0] * mb->M_000) * inv_M_000; + ma->vel[1] = (ma->vel[1] * ma->M_000 + mb->vel[1] * mb->M_000) * inv_M_000; + ma->vel[2] = (ma->vel[2] * ma->M_000 + mb->vel[2] * mb->M_000) * inv_M_000; + + /* Add 0th order terms */ + ma->M_000 = M_000; + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* Add 1st order terms */ + ma->M_100 += mb->M_100; + ma->M_010 += mb->M_010; + ma->M_001 += mb->M_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + /* Add 2nd order terms */ + ma->M_200 += mb->M_200; + ma->M_020 += mb->M_020; + ma->M_002 += mb->M_002; + ma->M_110 += mb->M_110; + ma->M_101 += mb->M_101; + ma->M_011 += mb->M_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + /* Add 3rd order terms */ + ma->M_300 += mb->M_300; + ma->M_030 += mb->M_030; + ma->M_003 += mb->M_003; + ma->M_210 += mb->M_210; + ma->M_201 += mb->M_201; + ma->M_120 += mb->M_120; + ma->M_021 += mb->M_021; + ma->M_102 += mb->M_102; + ma->M_012 += mb->M_012; + ma->M_111 += mb->M_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + /* Add 4th order terms */ + ma->M_400 += mb->M_400; + ma->M_040 += mb->M_040; + ma->M_004 += mb->M_004; + ma->M_310 += mb->M_310; + ma->M_301 += mb->M_301; + ma->M_130 += mb->M_130; + ma->M_031 += mb->M_031; + ma->M_103 += mb->M_103; + ma->M_013 += mb->M_013; + ma->M_220 += mb->M_220; + ma->M_202 += mb->M_202; + ma->M_022 += mb->M_022; + ma->M_211 += mb->M_211; + ma->M_121 += mb->M_121; + ma->M_112 += mb->M_112; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + + // MATTHIEU + ma->M_100 = 0.f; + ma->M_010 = 0.f; + ma->M_001 = 0.f; - /* Add the masses */ - ma->mass = mass; +#ifdef SWIFT_DEBUG_CHECKS + ma->num_gpart += mb->num_gpart; +#endif } /** * @brief Verifies whether two #multipole's are equal or not. * - * @param ma The first #multipole. - * @param mb The second #multipole. - * @param tolerance The maximal allowed difference for the fields. - * @return 1 if the multipoles are equal 0 otherwise. + * @param ga The first #multipole. + * @param gb The second #multipole. + * @param tolerance The maximal allowed relative difference for the fields. + * @return 1 if the multipoles are equal, 0 otherwise */ -INLINE static int gravity_multipole_equal(const struct multipole *ma, - const struct multipole *mb, +INLINE static int gravity_multipole_equal(const struct gravity_tensors *ga, + const struct gravity_tensors *gb, double tolerance) { /* Check CoM */ - /* if (fabs(ma->CoM[0] - mb->CoM[0]) / fabs(ma->CoM[0] + mb->CoM[0]) > - * tolerance) */ - /* return 0; */ - /* if (fabs(ma->CoM[1] - mb->CoM[1]) / fabs(ma->CoM[1] + mb->CoM[1]) > - * tolerance) */ - /* return 0; */ - /* if (fabs(ma->CoM[2] - mb->CoM[2]) / fabs(ma->CoM[2] + mb->CoM[2]) > - * tolerance) */ - /* return 0; */ - - /* Check bulk velocity (if non-zero)*/ - if (fabsf(ma->vel[0] + mb->vel[0]) > 0.f && + if (fabs(ga->CoM[0] - gb->CoM[0]) / fabs(ga->CoM[0] + gb->CoM[0]) > + tolerance) { + message("CoM[0] different"); + return 0; + } + if (fabs(ga->CoM[1] - gb->CoM[1]) / fabs(ga->CoM[1] + gb->CoM[1]) > + tolerance) { + message("CoM[1] different"); + return 0; + } + if (fabs(ga->CoM[2] - gb->CoM[2]) / fabs(ga->CoM[2] + gb->CoM[2]) > + tolerance) { + message("CoM[2] different"); + return 0; + } + + /* Helper pointers */ + const struct multipole *ma = &ga->m_pole; + const struct multipole *mb = &gb->m_pole; + + const double v2 = ma->vel[0] * ma->vel[0] + ma->vel[1] * ma->vel[1] + + ma->vel[2] * ma->vel[2]; + + /* Check bulk velocity (if non-zero and component > 1% of norm)*/ + if (fabsf(ma->vel[0] + mb->vel[0]) > 1e-10 && + (ma->vel[0] * ma->vel[0]) > 0.0001 * v2 && fabsf(ma->vel[0] - mb->vel[0]) / fabsf(ma->vel[0] + mb->vel[0]) > - tolerance) + tolerance) { + message("v[0] different"); return 0; - if (fabsf(ma->vel[1] + mb->vel[1]) > 0.f && + } + if (fabsf(ma->vel[1] + mb->vel[1]) > 1e-10 && + (ma->vel[1] * ma->vel[1]) > 0.0001 * v2 && fabsf(ma->vel[1] - mb->vel[1]) / fabsf(ma->vel[1] + mb->vel[1]) > - tolerance) + tolerance) { + message("v[1] different"); return 0; - if (fabsf(ma->vel[2] + mb->vel[2]) > 0.f && + } + if (fabsf(ma->vel[2] + mb->vel[2]) > 1e-10 && + (ma->vel[2] * ma->vel[2]) > 0.0001 * v2 && fabsf(ma->vel[2] - mb->vel[2]) / fabsf(ma->vel[2] + mb->vel[2]) > - tolerance) + tolerance) { + message("v[2] different"); return 0; + } - /* Check mass */ - if (fabsf(ma->mass - mb->mass) / fabsf(ma->mass + mb->mass) > tolerance) + /* Check 0th order terms */ + if (fabsf(ma->M_000 - mb->M_000) / fabsf(ma->M_000 + mb->M_000) > tolerance) { + message("M_000 term different"); return 0; + } - /* All is good */ - return 1; -} +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* Check 1st order terms */ + if (fabsf(ma->M_100 + mb->M_100) > 1e-6 * ma->M_000 && + fabsf(ma->M_100 - mb->M_100) / fabsf(ma->M_100 + mb->M_100) > tolerance) { + message("M_100 term different"); + return 0; + } + if (fabsf(ma->M_010 + mb->M_010) > 1e-6 * ma->M_000 && + fabsf(ma->M_010 - mb->M_010) / fabsf(ma->M_010 + mb->M_010) > tolerance) { + message("M_010 term different"); + return 0; + } + if (fabsf(ma->M_001 + mb->M_001) > 1e-6 * ma->M_000 && + fabsf(ma->M_001 - mb->M_001) / fabsf(ma->M_001 + mb->M_001) > tolerance) { + message("M_001 term different"); + return 0; + } +#endif -/** - * @brief Drifts a #multipole forward in time. - * - * @param m The #multipole. - * @param dt The drift time-step. - */ -INLINE static void gravity_multipole_drift(struct gravity_tensors *m, - double dt) { +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + /* Check 2nd order terms */ + if (fabsf(ma->M_200 + mb->M_200) > 1e-5 * ma->M_000 && + fabsf(ma->M_200 - mb->M_200) / fabsf(ma->M_200 + mb->M_200) > tolerance) { + message("M_200 term different"); + return 0; + } + if (fabsf(ma->M_020 + mb->M_020) > 1e-5 * ma->M_000 && + fabsf(ma->M_020 - mb->M_020) / fabsf(ma->M_020 + mb->M_020) > tolerance) { + message("M_020 term different"); + return 0; + } + if (fabsf(ma->M_002 + mb->M_002) > 1e-5 * ma->M_000 && + fabsf(ma->M_002 - mb->M_002) / fabsf(ma->M_002 + mb->M_002) > tolerance) { + message("M_002 term different"); + return 0; + } + if (fabsf(ma->M_110 + mb->M_110) > 1e-5 * ma->M_000 && + fabsf(ma->M_110 - mb->M_110) / fabsf(ma->M_110 + mb->M_110) > tolerance) { + message("M_110 term different"); + return 0; + } + if (fabsf(ma->M_101 + mb->M_101) > 1e-5 * ma->M_000 && + fabsf(ma->M_101 - mb->M_101) / fabsf(ma->M_101 + mb->M_101) > tolerance) { + message("M_101 term different"); + return 0; + } + if (fabsf(ma->M_011 + mb->M_011) > 1e-5 * ma->M_000 && + fabsf(ma->M_011 - mb->M_011) / fabsf(ma->M_011 + mb->M_011) > tolerance) { + message("M_011 term different"); + return 0; + } +#endif - /* Move the whole thing according to bulk motion */ - m->CoM[0] += m->m_pole.vel[0]; - m->CoM[1] += m->m_pole.vel[1]; - m->CoM[2] += m->m_pole.vel[2]; -} + tolerance *= 10.; -/** - * @brief Applies the forces due to particles j onto particles i directly. - * - * @param gparts_i The #gpart to update. - * @param gcount_i The number of particles to update. - * @param gparts_j The #gpart that source the gravity field. - * @param gcount_j The number of sources. - */ -INLINE static void gravity_P2P(struct gpart *gparts_i, int gcount_i, - const struct gpart *gparts_j, int gcount_j) {} +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + /* Check 3rd order terms */ + if (fabsf(ma->M_300 + mb->M_300) > 1e-5 * ma->M_000 && + fabsf(ma->M_300 - mb->M_300) / fabsf(ma->M_300 + mb->M_300) > tolerance) { + message("M_300 term different"); + return 0; + } + if (fabsf(ma->M_030 + mb->M_030) > 1e-5 * ma->M_000 && + fabsf(ma->M_030 - mb->M_030) / fabsf(ma->M_030 + mb->M_030) > tolerance) { + message("M_030 term different"); + return 0; + } + if (fabsf(ma->M_003 + mb->M_003) > 1e-5 * ma->M_000 && + fabsf(ma->M_003 - mb->M_003) / fabsf(ma->M_003 + mb->M_003) > tolerance) { + message("M_003 term different"); + return 0; + } + if (fabsf(ma->M_210 + mb->M_210) > 1e-5 * ma->M_000 && + fabsf(ma->M_210 - mb->M_210) / fabsf(ma->M_210 + mb->M_210) > tolerance) { + message("M_210 term different"); + return 0; + } + if (fabsf(ma->M_201 + mb->M_201) > 1e-5 * ma->M_000 && + fabsf(ma->M_201 - mb->M_201) / fabsf(ma->M_201 + mb->M_201) > tolerance) { + message("M_201 term different"); + return 0; + } + if (fabsf(ma->M_120 + mb->M_120) > 1e-5 * ma->M_000 && + fabsf(ma->M_120 - mb->M_120) / fabsf(ma->M_120 + mb->M_120) > tolerance) { + message("M_120 term different"); + return 0; + } + if (fabsf(ma->M_021 + mb->M_021) > 1e-5 * ma->M_000 && + fabsf(ma->M_021 - mb->M_021) / fabsf(ma->M_021 + mb->M_021) > tolerance) { + message("M_021 term different"); + return 0; + } + if (fabsf(ma->M_102 + mb->M_102) > 1e-5 * ma->M_000 && + fabsf(ma->M_102 - mb->M_102) / fabsf(ma->M_102 + mb->M_102) > tolerance) { + message("M_102 term different"); + return 0; + } + if (fabsf(ma->M_012 + mb->M_012) > 1e-5 * ma->M_000 && + fabsf(ma->M_012 - mb->M_012) / fabsf(ma->M_012 + mb->M_012) > tolerance) { + message("M_012 term different"); + return 0; + } + if (fabsf(ma->M_111 + mb->M_111) > 1e-5 * ma->M_000 && + fabsf(ma->M_111 - mb->M_111) / fabsf(ma->M_111 + mb->M_111) > tolerance) { + message("M_111 term different"); + return 0; + } +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + /* Check 4th order terms */ + if (fabsf(ma->M_400 + mb->M_400) > 1e-5 * ma->M_000 && + fabsf(ma->M_400 - mb->M_400) / fabsf(ma->M_400 + mb->M_400) > tolerance) { + message("M_400 term different"); + return 0; + } + if (fabsf(ma->M_040 + mb->M_040) > 1e-5 * ma->M_000 && + fabsf(ma->M_040 - mb->M_040) / fabsf(ma->M_040 + mb->M_040) > tolerance) { + message("M_040 term different"); + return 0; + } + if (fabsf(ma->M_004 + mb->M_004) > 1e-5 * ma->M_000 && + fabsf(ma->M_004 - mb->M_004) / fabsf(ma->M_004 + mb->M_004) > tolerance) { + message("M_003 term different"); + return 0; + } + if (fabsf(ma->M_310 + mb->M_310) > 1e-5 * ma->M_000 && + fabsf(ma->M_310 - mb->M_310) / fabsf(ma->M_310 + mb->M_310) > tolerance) { + message("M_310 term different"); + return 0; + } + if (fabsf(ma->M_301 + mb->M_301) > 1e-5 * ma->M_000 && + fabsf(ma->M_301 - mb->M_301) / fabsf(ma->M_301 + mb->M_301) > tolerance) { + message("M_301 term different"); + return 0; + } + if (fabsf(ma->M_130 + mb->M_130) > 1e-5 * ma->M_000 && + fabsf(ma->M_130 - mb->M_130) / fabsf(ma->M_130 + mb->M_130) > tolerance) { + message("M_130 term different"); + return 0; + } + if (fabsf(ma->M_031 + mb->M_031) > 1e-5 * ma->M_000 && + fabsf(ma->M_031 - mb->M_031) / fabsf(ma->M_031 + mb->M_031) > tolerance) { + message("M_031 term different"); + return 0; + } + if (fabsf(ma->M_103 + mb->M_103) > 1e-5 * ma->M_000 && + fabsf(ma->M_103 - mb->M_103) / fabsf(ma->M_103 + mb->M_103) > tolerance) { + message("M_103 term different"); + return 0; + } + if (fabsf(ma->M_013 + mb->M_013) > 1e-5 * ma->M_000 && + fabsf(ma->M_013 - mb->M_013) / fabsf(ma->M_013 + mb->M_013) > tolerance) { + message("M_013 term different"); + return 0; + } + if (fabsf(ma->M_220 + mb->M_220) > 1e-5 * ma->M_000 && + fabsf(ma->M_220 - mb->M_220) / fabsf(ma->M_220 + mb->M_220) > tolerance) { + message("M_220 term different"); + return 0; + } + if (fabsf(ma->M_202 + mb->M_202) > 1e-5 * ma->M_000 && + fabsf(ma->M_202 - mb->M_202) / fabsf(ma->M_202 + mb->M_202) > tolerance) { + message("M_202 term different"); + return 0; + } + if (fabsf(ma->M_022 + mb->M_022) > 1e-5 * ma->M_000 && + fabsf(ma->M_022 - mb->M_022) / fabsf(ma->M_022 + mb->M_022) > tolerance) { + message("M_022 term different"); + return 0; + } + if (fabsf(ma->M_211 + mb->M_211) > 1e-5 * ma->M_000 && + fabsf(ma->M_211 - mb->M_211) / fabsf(ma->M_211 + mb->M_211) > tolerance) { + message("M_211 term different"); + return 0; + } + if (fabsf(ma->M_121 + mb->M_121) > 1e-5 * ma->M_000 && + fabsf(ma->M_121 - mb->M_121) / fabsf(ma->M_121 + mb->M_121) > tolerance) { + message("M_121 term different"); + return 0; + } + if (fabsf(ma->M_112 + mb->M_112) > 1e-5 * ma->M_000 && + fabsf(ma->M_112 - mb->M_112) / fabsf(ma->M_112 + mb->M_112) > tolerance) { + message("M_112 term different"); + return 0; + } +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + + /* All is good */ + return 1; +} /** * @brief Constructs the #multipole of a bunch of particles around their @@ -235,10 +713,6 @@ INLINE static void gravity_P2P(struct gpart *gparts_i, int gcount_i, INLINE static void gravity_P2M(struct gravity_tensors *m, const struct gpart *gparts, int gcount) { -#if const_gravity_multipole_order >= 2 -#error "Implementation of P2M kernel missing for this order." -#endif - /* Temporary variables */ double mass = 0.0; double com[3] = {0.0, 0.0, 0.0}; @@ -257,16 +731,154 @@ INLINE static void gravity_P2M(struct gravity_tensors *m, vel[2] += gparts[k].v_full[2] * m; } + /* Final operation on CoM */ const double imass = 1.0 / mass; + com[0] *= imass; + com[1] *= imass; + com[2] *= imass; + vel[0] *= imass; + vel[1] *= imass; + vel[2] *= imass; + +/* Prepare some local counters */ +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + float M_100 = 0.f, M_010 = 0.f, M_001 = 0.f; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + float M_200 = 0.f, M_020 = 0.f, M_002 = 0.f; + float M_110 = 0.f, M_101 = 0.f, M_011 = 0.f; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + float M_300 = 0.f, M_030 = 0.f, M_003 = 0.f; + float M_210 = 0.f, M_201 = 0.f, M_120 = 0.f; + float M_021 = 0.f, M_102 = 0.f, M_012 = 0.f; + float M_111 = 0.f; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + float M_400 = 0.f, M_040 = 0.f, M_004 = 0.f; + float M_310 = 0.f, M_301 = 0.f, M_130 = 0.f; + float M_031 = 0.f, M_103 = 0.f, M_013 = 0.f; + float M_220 = 0.f, M_202 = 0.f, M_022 = 0.f; + float M_211 = 0.f, M_121 = 0.f, M_112 = 0.f; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + + /* Construce the higher order terms */ + for (int k = 0; k < gcount; k++) { +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + const float m = gparts[k].mass; + const double dx[3] = {gparts[k].x[0] - com[0], gparts[k].x[1] - com[1], + gparts[k].x[2] - com[2]}; + + 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 + M_200 += m * X_200(dx); + M_020 += m * X_020(dx); + M_002 += m * X_002(dx); + M_110 += m * X_110(dx); + M_101 += m * X_101(dx); + M_011 += m * X_011(dx); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + M_300 += -m * X_300(dx); + M_030 += -m * X_030(dx); + M_003 += -m * X_003(dx); + M_210 += -m * X_210(dx); + M_201 += -m * X_201(dx); + M_120 += -m * X_120(dx); + M_021 += -m * X_021(dx); + M_102 += -m * X_102(dx); + M_012 += -m * X_012(dx); + M_111 += -m * X_111(dx); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + M_400 += m * X_400(dx); + M_040 += m * X_040(dx); + M_004 += m * X_004(dx); + M_310 += m * X_310(dx); + M_301 += m * X_301(dx); + M_130 += m * X_130(dx); + M_031 += m * X_031(dx); + M_103 += m * X_103(dx); + M_013 += m * X_013(dx); + M_220 += m * X_220(dx); + M_202 += m * X_202(dx); + M_022 += m * X_022(dx); + M_211 += m * X_211(dx); + M_121 += m * X_121(dx); + M_112 += m * X_112(dx); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + } + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + M_100 = M_010 = M_001 = 0.f; /* Matthieu */ +#endif /* Store the data on the multipole. */ - m->m_pole.mass = mass; - m->CoM[0] = com[0] * imass; - m->CoM[1] = com[1] * imass; - m->CoM[2] = com[2] * imass; - m->m_pole.vel[0] = vel[0] * imass; - m->m_pole.vel[1] = vel[1] * imass; - m->m_pole.vel[2] = vel[2] * imass; + m->m_pole.M_000 = mass; + m->CoM[0] = com[0]; + m->CoM[1] = com[1]; + m->CoM[2] = com[2]; + m->m_pole.vel[0] = vel[0]; + m->m_pole.vel[1] = vel[1]; + m->m_pole.vel[2] = vel[2]; +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + m->m_pole.M_100 = M_100; + m->m_pole.M_010 = M_010; + m->m_pole.M_001 = M_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + m->m_pole.M_200 = M_200; + m->m_pole.M_020 = M_020; + m->m_pole.M_002 = M_002; + m->m_pole.M_110 = M_110; + m->m_pole.M_101 = M_101; + m->m_pole.M_011 = M_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + m->m_pole.M_300 = M_300; + m->m_pole.M_030 = M_030; + m->m_pole.M_003 = M_003; + m->m_pole.M_210 = M_210; + m->m_pole.M_201 = M_201; + m->m_pole.M_120 = M_120; + m->m_pole.M_021 = M_021; + m->m_pole.M_102 = M_102; + m->m_pole.M_012 = M_012; + m->m_pole.M_111 = M_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 + m->m_pole.M_400 = M_400; + m->m_pole.M_040 = M_040; + m->m_pole.M_004 = M_004; + m->m_pole.M_310 = M_310; + m->m_pole.M_301 = M_301; + m->m_pole.M_130 = M_130; + m->m_pole.M_031 = M_031; + m->m_pole.M_103 = M_103; + m->m_pole.M_013 = M_013; + m->m_pole.M_220 = M_220; + m->m_pole.M_202 = M_202; + m->m_pole.M_022 = M_022; + m->m_pole.M_211 = M_211; + m->m_pole.M_121 = M_121; + m->m_pole.M_112 = M_112; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif + +#ifdef SWIFT_DEBUG_CHECKS + m->m_pole.num_gpart = gcount; +#endif } /** @@ -284,184 +896,406 @@ INLINE static void gravity_M2M(struct multipole *m_a, const struct multipole *m_b, const double pos_a[3], const double pos_b[3], int periodic) { - - m_a->mass = m_b->mass; - + /* Shift bulk velocity */ m_a->vel[0] = m_b->vel[0]; m_a->vel[1] = m_b->vel[1]; m_a->vel[2] = m_b->vel[2]; + + /* Shift 0th order term */ + m_a->M_000 = m_b->M_000; + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + const double dx[3] = {pos_a[0] - pos_b[0], pos_a[1] - pos_b[1], + pos_a[2] - pos_b[2]}; + + /* Shift 1st order term */ + m_a->M_100 = m_b->M_100 + X_100(dx) * m_b->M_000; + m_a->M_010 = m_b->M_010 + X_010(dx) * m_b->M_000; + m_a->M_001 = m_b->M_001 + X_001(dx) * m_b->M_000; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* Shift 2nd order term */ + m_a->M_200 = m_b->M_200 + X_100(dx) * m_b->M_100 + X_200(dx) * m_b->M_000; + m_a->M_020 = m_b->M_020 + X_010(dx) * m_b->M_010 + X_020(dx) * m_b->M_000; + m_a->M_002 = m_b->M_002 + X_001(dx) * m_b->M_001 + X_002(dx) * m_b->M_000; + m_a->M_110 = m_b->M_110 + X_100(dx) * m_b->M_010 + X_010(dx) * m_b->M_100 + + X_110(dx) * m_b->M_000; + m_a->M_101 = m_b->M_101 + X_100(dx) * m_b->M_001 + X_001(dx) * m_b->M_100 + + X_101(dx) * m_b->M_000; + m_a->M_011 = m_b->M_011 + X_010(dx) * m_b->M_001 + X_001(dx) * m_b->M_010 + + X_011(dx) * m_b->M_000; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* Shift 3rd order term */ + m_a->M_300 = m_b->M_300 + X_100(dx) * m_b->M_200 + X_200(dx) * m_b->M_100 + + X_300(dx) * m_b->M_000; + m_a->M_030 = m_b->M_030 + X_010(dx) * m_b->M_020 + X_020(dx) * m_b->M_010 + + X_030(dx) * m_b->M_000; + m_a->M_003 = m_b->M_003 + X_001(dx) * m_b->M_002 + X_002(dx) * m_b->M_001 + + X_003(dx) * m_b->M_000; + m_a->M_210 = m_b->M_210 + X_100(dx) * m_b->M_110 + X_010(dx) * m_b->M_200 + + X_200(dx) * m_b->M_010 + X_110(dx) * m_b->M_100 + + X_210(dx) * m_b->M_000; + m_a->M_201 = m_b->M_201 + X_100(dx) * m_b->M_101 + X_001(dx) * m_b->M_200 + + X_200(dx) * m_b->M_001 + X_101(dx) * m_b->M_100 + + X_201(dx) * m_b->M_000; + m_a->M_120 = m_b->M_120 + X_010(dx) * m_b->M_110 + X_100(dx) * m_b->M_020 + + X_020(dx) * m_b->M_100 + X_110(dx) * m_b->M_010 + + X_120(dx) * m_b->M_000; + m_a->M_021 = m_b->M_021 + X_010(dx) * m_b->M_011 + X_001(dx) * m_b->M_020 + + X_020(dx) * m_b->M_001 + X_011(dx) * m_b->M_010 + + X_021(dx) * m_b->M_000; + m_a->M_102 = m_b->M_102 + X_001(dx) * m_b->M_101 + X_100(dx) * m_b->M_002 + + X_002(dx) * m_b->M_100 + X_101(dx) * m_b->M_001 + + X_102(dx) * m_b->M_000; + m_a->M_012 = m_b->M_012 + X_001(dx) * m_b->M_011 + X_010(dx) * m_b->M_002 + + X_002(dx) * m_b->M_010 + X_011(dx) * m_b->M_001 + + X_012(dx) * m_b->M_000; + m_a->M_111 = m_b->M_111 + X_100(dx) * m_b->M_011 + X_010(dx) * m_b->M_101 + + X_001(dx) * m_b->M_110 + X_110(dx) * m_b->M_001 + + X_101(dx) * m_b->M_010 + X_011(dx) * m_b->M_100 + + X_111(dx) * m_b->M_000; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 +#error "Missing implementation for order >3" +#endif + +#ifdef SWIFT_DEBUG_CHECKS + m_a->num_gpart = m_b->num_gpart; +#endif } + /** * @brief Compute the field tensors due to a multipole. * * Corresponds to equation (28b). * - * @param l_a The field tensor to compute. - * @param m_b The multipole creating the field. - * @param pos_a The position of the field tensor. - * @param pos_b The position of the multipole. + * @param l_b The field tensor to compute. + * @param m_a The multipole creating the field. + * @param pos_b The position of the field tensor. + * @param pos_a The position of the multipole. * @param periodic Is the calculation periodic ? */ -INLINE static void gravity_M2L(struct gravity_tensors *l_a, - const struct multipole *m_b, - const double pos_a[3], const double pos_b[3], +INLINE static void gravity_M2L(struct grav_tensor *l_b, + const struct multipole *m_a, + const double pos_b[3], const double pos_a[3], int periodic) { double dx, dy, dz; if (periodic) { - dx = box_wrap(pos_a[0] - pos_b[0], 0., 1.); - dy = box_wrap(pos_a[1] - pos_b[1], 0., 1.); - dz = box_wrap(pos_a[2] - pos_b[2], 0., 1.); + dx = box_wrap(pos_b[0] - pos_a[0], 0., 1.); + dy = box_wrap(pos_b[1] - pos_a[1], 0., 1.); + dz = box_wrap(pos_b[2] - pos_a[2], 0., 1.); } else { - dx = pos_a[0] - pos_b[0]; - dy = pos_a[1] - pos_b[1]; - dz = pos_a[2] - pos_b[2]; + dx = pos_b[0] - pos_a[0]; + dy = pos_b[1] - pos_a[1]; + dz = pos_b[2] - pos_a[2]; } const double r2 = dx * dx + dy * dy + dz * dz; const double r_inv = 1. / sqrt(r2); - /* 1st order multipole term */ - l_a->a_x.F_000 = D_100(dx, dy, dz, r_inv) * m_b->mass; - l_a->a_y.F_000 = D_010(dx, dy, dz, r_inv) * m_b->mass; - l_a->a_z.F_000 = D_001(dx, dy, dz, r_inv) * m_b->mass; + /* 0th order term */ + l_b->F_000 += m_a->M_000 * D_000(dx, dy, dz, r_inv); + +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* 1st order multipole term (addition to rank 0)*/ + l_b->F_000 += m_a->M_100 * D_100(dx, dy, dz, r_inv) + + m_a->M_010 * D_010(dx, dy, dz, r_inv) + + m_a->M_001 * D_001(dx, dy, dz, r_inv); + + /* 1st order multipole term (addition to rank 1)*/ + l_b->F_100 += m_a->M_000 * D_100(dx, dy, dz, r_inv); + l_b->F_010 += m_a->M_000 * D_010(dx, dy, dz, r_inv); + l_b->F_001 += m_a->M_000 * D_001(dx, dy, dz, r_inv); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* 2nd order multipole term (addition to rank 0)*/ + l_b->F_000 += m_a->M_200 * D_200(dx, dy, dz, r_inv) + + m_a->M_020 * D_020(dx, dy, dz, r_inv) + + m_a->M_002 * D_002(dx, dy, dz, r_inv); + l_b->F_000 += m_a->M_110 * D_110(dx, dy, dz, r_inv) + + m_a->M_101 * D_101(dx, dy, dz, r_inv) + + m_a->M_011 * D_011(dx, dy, dz, r_inv); + + /* 2nd order multipole term (addition to rank 1)*/ + l_b->F_100 += m_a->M_100 * D_200(dx, dy, dz, r_inv) + + m_a->M_010 * D_110(dx, dy, dz, r_inv) + + m_a->M_001 * D_101(dx, dy, dz, r_inv); + l_b->F_010 += m_a->M_100 * D_110(dx, dy, dz, r_inv) + + m_a->M_010 * D_020(dx, dy, dz, r_inv) + + m_a->M_001 * D_011(dx, dy, dz, r_inv); + l_b->F_001 += m_a->M_100 * D_101(dx, dy, dz, r_inv) + + m_a->M_010 * D_011(dx, dy, dz, r_inv) + + m_a->M_001 * D_002(dx, dy, dz, r_inv); + + /* 2nd order multipole term (addition to rank 2)*/ + l_b->F_200 += m_a->M_000 * D_200(dx, dy, dz, r_inv); + l_b->F_020 += m_a->M_000 * D_020(dx, dy, dz, r_inv); + l_b->F_002 += m_a->M_000 * D_002(dx, dy, dz, r_inv); + l_b->F_110 += m_a->M_000 * D_110(dx, dy, dz, r_inv); + l_b->F_101 += m_a->M_000 * D_101(dx, dy, dz, r_inv); + l_b->F_011 += m_a->M_000 * D_011(dx, dy, dz, r_inv); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* 3rd order multipole term (addition to rank 0)*/ + l_b->F_000 += m_a->M_300 * D_300(dx, dy, dz, r_inv) + + m_a->M_030 * D_030(dx, dy, dz, r_inv) + + m_a->M_003 * D_003(dx, dy, dz, r_inv); + l_b->F_000 += m_a->M_210 * D_210(dx, dy, dz, r_inv) + + m_a->M_201 * D_201(dx, dy, dz, r_inv) + + m_a->M_120 * D_120(dx, dy, dz, r_inv); + l_b->F_000 += m_a->M_021 * D_021(dx, dy, dz, r_inv) + + m_a->M_102 * D_102(dx, dy, dz, r_inv) + + m_a->M_012 * D_012(dx, dy, dz, r_inv); + l_b->F_000 += m_a->M_111 * D_111(dx, dy, dz, r_inv); + + /* 3rd order multipole term (addition to rank 1)*/ + l_b->F_100 += m_a->M_200 * D_300(dx, dy, dz, r_inv) + + m_a->M_020 * D_120(dx, dy, dz, r_inv) + + m_a->M_002 * D_102(dx, dy, dz, r_inv); + l_b->F_100 += m_a->M_110 * D_210(dx, dy, dz, r_inv) + + m_a->M_101 * D_201(dx, dy, dz, r_inv) + + m_a->M_011 * D_111(dx, dy, dz, r_inv); + l_b->F_010 += m_a->M_200 * D_210(dx, dy, dz, r_inv) + + m_a->M_020 * D_030(dx, dy, dz, r_inv) + + m_a->M_002 * D_012(dx, dy, dz, r_inv); + l_b->F_010 += m_a->M_110 * D_120(dx, dy, dz, r_inv) + + m_a->M_101 * D_111(dx, dy, dz, r_inv) + + m_a->M_011 * D_021(dx, dy, dz, r_inv); + l_b->F_001 += m_a->M_200 * D_201(dx, dy, dz, r_inv) + + m_a->M_020 * D_021(dx, dy, dz, r_inv) + + m_a->M_002 * D_003(dx, dy, dz, r_inv); + l_b->F_001 += m_a->M_110 * D_111(dx, dy, dz, r_inv) + + m_a->M_101 * D_102(dx, dy, dz, r_inv) + + m_a->M_011 * D_012(dx, dy, dz, r_inv); + + /* 3rd order multipole term (addition to rank 2)*/ + l_b->F_200 += m_a->M_100 * D_300(dx, dy, dz, r_inv) + + m_a->M_010 * D_210(dx, dy, dz, r_inv) + + m_a->M_001 * D_201(dx, dy, dz, r_inv); + l_b->F_020 += m_a->M_100 * D_120(dx, dy, dz, r_inv) + + m_a->M_010 * D_030(dx, dy, dz, r_inv) + + m_a->M_001 * D_021(dx, dy, dz, r_inv); + l_b->F_002 += m_a->M_100 * D_102(dx, dy, dz, r_inv) + + m_a->M_010 * D_012(dx, dy, dz, r_inv) + + m_a->M_001 * D_003(dx, dy, dz, r_inv); + l_b->F_110 += m_a->M_100 * D_210(dx, dy, dz, r_inv) + + m_a->M_010 * D_120(dx, dy, dz, r_inv) + + m_a->M_001 * D_111(dx, dy, dz, r_inv); + l_b->F_101 += m_a->M_100 * D_201(dx, dy, dz, r_inv) + + m_a->M_010 * D_111(dx, dy, dz, r_inv) + + m_a->M_001 * D_102(dx, dy, dz, r_inv); + l_b->F_011 += m_a->M_100 * D_111(dx, dy, dz, r_inv) + + m_a->M_010 * D_021(dx, dy, dz, r_inv) + + m_a->M_001 * D_012(dx, dy, dz, r_inv); + + /* 3rd order multipole term (addition to rank 2)*/ + l_b->F_300 += m_a->M_000 * D_300(dx, dy, dz, r_inv); + l_b->F_030 += m_a->M_000 * D_030(dx, dy, dz, r_inv); + l_b->F_003 += m_a->M_000 * D_003(dx, dy, dz, r_inv); + l_b->F_210 += m_a->M_000 * D_210(dx, dy, dz, r_inv); + l_b->F_201 += m_a->M_000 * D_201(dx, dy, dz, r_inv); + l_b->F_120 += m_a->M_000 * D_120(dx, dy, dz, r_inv); + l_b->F_021 += m_a->M_000 * D_021(dx, dy, dz, r_inv); + l_b->F_102 += m_a->M_000 * D_102(dx, dy, dz, r_inv); + l_b->F_012 += m_a->M_000 * D_012(dx, dy, dz, r_inv); + l_b->F_111 += m_a->M_000 * D_111(dx, dy, dz, r_inv); +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 +#error "Missing implementation for order >3" +#endif #ifdef SWIFT_DEBUG_CHECKS - l_a->mass_interacted += m_b->mass; + + l_b->num_interacted += m_a->num_gpart; #endif } /** - * @brief Creates a copy of #acc_tensor shifted to a new location. + * @brief Creates a copy of #grav_tensor shifted to a new location. * * Corresponds to equation (28e). * - * @param l_a The #acc_tensor copy (content will be overwritten). - * @param l_b The #acc_tensor to shift. + * @param la The #grav_tensor copy (content will be overwritten). + * @param lb The #grav_tensor to shift. * @param pos_a The position to which m_b will be shifted. * @param pos_b The current postion of the multipole to shift. * @param periodic Is the calculation periodic ? */ -INLINE static void gravity_L2L(struct gravity_tensors *l_a, - const struct gravity_tensors *l_b, +INLINE static void gravity_L2L(struct grav_tensor *la, + const struct grav_tensor *lb, const double pos_a[3], const double pos_b[3], - int periodic) {} - -/** - * @brief Applies the #acc_tensor to a set of #gpart. - * - * Corresponds to equation (28a). - */ -INLINE static void gravity_L2P(const struct gravity_tensors *l, - struct gpart *gparts, int gcount) { + int periodic) { - for (int i = 0; i < gcount; ++i) { + /* Initialise everything to zero */ + gravity_field_tensors_init(la); #ifdef SWIFT_DEBUG_CHECKS - struct gpart *gp = &gparts[i]; - - // if(gpart_is_active(gp, e)){ - - gp->mass_interacted += l->mass_interacted; + if (lb->num_interacted == 0) error("Shifting tensors that did not interact"); + la->num_interacted = lb->num_interacted; #endif - //} - } -} -#if 0 + /* Distance to shift by */ + const double dx[3] = {pos_a[0] - pos_b[0], pos_a[1] - pos_b[1], + pos_a[2] - pos_b[2]}; -/* Multipole function prototypes. */ -void multipole_add(struct gravity_tensors *m_sum, const struct gravity_tensors *m_term); -void multipole_init(struct gravity_tensors *m, const struct gpart *gparts, - int gcount); -void multipole_reset(struct gravity_tensors *m); + /* Shift 0th order term */ + la->F_000 += X_000(dx) * lb->F_000; -/* static void multipole_iact_mm(struct multipole *ma, struct multipole *mb, */ -/* double *shift); */ -/* void multipole_addpart(struct multipole *m, struct gpart *p); */ -/* void multipole_addparts(struct multipole *m, struct gpart *p, int N); */ +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* Shift 1st order multipole term (addition to rank 0)*/ + la->F_000 += + X_100(dx) * lb->F_100 + X_010(dx) * lb->F_010 + X_001(dx) * lb->F_001; -/** - * @brief Compute the pairwise interaction between two multipoles. - * - * @param ma The first #multipole. - * @param mb The second #multipole. - * @param shift The periodicity correction. - */ -__attribute__((always_inline)) INLINE static void multipole_iact_mm( - struct gravity_tensors *ma, struct gravity_tensors *mb, double *shift) { - /* float dx[3], ir, r, r2 = 0.0f, acc; */ - /* int k; */ - - /* /\* Compute the multipole distance. *\/ */ - /* for (k = 0; k < 3; k++) { */ - /* dx[k] = ma->x[k] - mb->x[k] - shift[k]; */ - /* r2 += dx[k] * dx[k]; */ - /* } */ - - /* /\* Compute the normalized distance vector. *\/ */ - /* ir = 1.0f / sqrtf(r2); */ - /* r = r2 * ir; */ - - /* /\* Evaluate the gravity kernel. *\/ */ - /* kernel_grav_eval(r, &acc); */ - - /* /\* Scale the acceleration. *\/ */ - /* acc *= const_G * ir * ir * ir; */ - - /* /\* Compute the forces on both multipoles. *\/ */ - /* #if const_gravity_multipole_order == 1 */ - /* float mma = ma->coeffs[0], mmb = mb->coeffs[0]; */ - /* for (k = 0; k < 3; k++) { */ - /* ma->a[k] -= dx[k] * acc * mmb; */ - /* mb->a[k] += dx[k] * acc * mma; */ - /* } */ - /* #else */ - /* #error( "Multipoles of order %i not yet implemented." , - * const_gravity_multipole_order ) - */ - /* #endif */ + /* Shift 1st order multipole term (addition to rank 1)*/ + la->F_100 += X_000(dx) * lb->F_100; + la->F_010 += X_000(dx) * lb->F_010; + la->F_001 += X_000(dx) * lb->F_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* Shift 2nd order multipole term (addition to rank 0)*/ + la->F_000 += + X_200(dx) * lb->F_200 + X_020(dx) * lb->F_020 + X_002(dx) * lb->F_002; + la->F_000 += + X_110(dx) * lb->F_110 + X_101(dx) * lb->F_101 + X_011(dx) * lb->F_011; + + /* Shift 2nd order multipole term (addition to rank 1)*/ + la->F_100 += + X_100(dx) * lb->F_200 + X_010(dx) * lb->F_110 + X_001(dx) * lb->F_101; + la->F_010 += + X_100(dx) * lb->F_110 + X_010(dx) * lb->F_020 + X_001(dx) * lb->F_011; + la->F_001 += + X_100(dx) * lb->F_101 + X_010(dx) * lb->F_011 + X_001(dx) * lb->F_002; + + /* Shift 2nd order multipole term (addition to rank 2)*/ + la->F_200 += X_000(dx) * lb->F_200; + la->F_020 += X_000(dx) * lb->F_020; + la->F_002 += X_000(dx) * lb->F_002; + la->F_110 += X_000(dx) * lb->F_110; + la->F_101 += X_000(dx) * lb->F_101; + la->F_011 += X_000(dx) * lb->F_011; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* Shift 3rd order multipole term (addition to rank 0)*/ + la->F_000 += + X_300(dx) * lb->F_300 + X_030(dx) * lb->F_030 + X_003(dx) * lb->F_003; + la->F_000 += + X_210(dx) * lb->F_210 + X_201(dx) * lb->F_201 + X_120(dx) * lb->F_120; + la->F_000 += + X_021(dx) * lb->F_021 + X_102(dx) * lb->F_102 + X_012(dx) * lb->F_012; + la->F_000 += X_111(dx) * lb->F_111; + + /* Shift 3rd order multipole term (addition to rank 1)*/ + la->F_100 += + X_200(dx) * lb->F_300 + X_020(dx) * lb->F_120 + X_002(dx) * lb->F_102; + la->F_100 += + X_110(dx) * lb->F_210 + X_101(dx) * lb->F_201 + X_011(dx) * lb->F_111; + la->F_010 += + X_200(dx) * lb->F_210 + X_020(dx) * lb->F_030 + X_002(dx) * lb->F_012; + la->F_010 += + X_110(dx) * lb->F_120 + X_101(dx) * lb->F_111 + X_011(dx) * lb->F_021; + la->F_001 += + X_200(dx) * lb->F_201 + X_020(dx) * lb->F_021 + X_002(dx) * lb->F_003; + la->F_001 += + X_110(dx) * lb->F_111 + X_101(dx) * lb->F_102 + X_011(dx) * lb->F_012; + + /* Shift 3rd order multipole term (addition to rank 2)*/ + la->F_200 += + X_100(dx) * lb->F_300 + X_010(dx) * lb->F_210 + X_001(dx) * lb->F_201; + la->F_020 += + X_100(dx) * lb->F_120 + X_010(dx) * lb->F_030 + X_001(dx) * lb->F_021; + la->F_002 += + X_100(dx) * lb->F_102 + X_010(dx) * lb->F_012 + X_001(dx) * lb->F_003; + la->F_110 += + X_100(dx) * lb->F_210 + X_010(dx) * lb->F_120 + X_001(dx) * lb->F_111; + la->F_101 += + X_100(dx) * lb->F_201 + X_010(dx) * lb->F_111 + X_001(dx) * lb->F_102; + la->F_011 += + X_100(dx) * lb->F_111 + X_010(dx) * lb->F_021 + X_001(dx) * lb->F_012; + + /* Shift 3rd order multipole term (addition to rank 2)*/ + la->F_300 += X_000(dx) * lb->F_300; + la->F_030 += X_000(dx) * lb->F_030; + la->F_003 += X_000(dx) * lb->F_003; + la->F_210 += X_000(dx) * lb->F_210; + la->F_201 += X_000(dx) * lb->F_201; + la->F_120 += X_000(dx) * lb->F_120; + la->F_021 += X_000(dx) * lb->F_021; + la->F_102 += X_000(dx) * lb->F_102; + la->F_012 += X_000(dx) * lb->F_012; + la->F_111 += X_000(dx) * lb->F_111; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 +#error "Missing implementation for order >3" +#endif } /** - * @brief Compute the interaction of a multipole on a particle. + * @brief Applies the #grav_tensor to a #gpart. * - * @param m The #multipole. - * @param p The #gpart. - * @param shift The periodicity correction. + * Corresponds to equation (28a). + * + * @param lb The gravity field tensor to apply. + * @param loc The position of the gravity field tensor. + * @param gp The #gpart to update. */ -__attribute__((always_inline)) INLINE static void multipole_iact_mp( - struct gravity_tensors *m, struct gpart *p, double *shift) { - - /* float dx[3], ir, r, r2 = 0.0f, acc; */ - /* int k; */ - - /* /\* Compute the multipole distance. *\/ */ - /* for (k = 0; k < 3; k++) { */ - /* dx[k] = m->x[k] - p->x[k] - shift[k]; */ - /* r2 += dx[k] * dx[k]; */ - /* } */ - - /* /\* Compute the normalized distance vector. *\/ */ - /* ir = 1.0f / sqrtf(r2); */ - /* r = r2 * ir; */ - - /* /\* Evaluate the gravity kernel. *\/ */ - /* kernel_grav_eval(r, &acc); */ - - /* /\* Scale the acceleration. *\/ */ - /* acc *= const_G * ir * ir * ir * m->coeffs[0]; */ - - /* /\* Compute the forces on both multipoles. *\/ */ - /* #if const_gravity_multipole_order == 1 */ - /* for (k = 0; k < 3; k++) p->a_grav[k] += dx[k] * acc; */ - /* #else */ - /* #error( "Multipoles of order %i not yet implemented." , - * const_gravity_multipole_order ) - */ - /* #endif */ -} +INLINE static void gravity_L2P(const struct grav_tensor *lb, + const double loc[3], struct gpart *gp) { + +#ifdef SWIFT_DEBUG_CHECKS + if (lb->num_interacted == 0) error("Interacting with empty field tensor"); + gp->num_interacted += lb->num_interacted; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* Distance to the multipole */ + const double dx[3] = {gp->x[0] - loc[0], gp->x[1] - loc[1], + gp->x[2] - loc[2]}; + + /* 0th order interaction */ + gp->a_grav[0] += X_000(dx) * lb->F_100; + gp->a_grav[1] += X_000(dx) * lb->F_010; + gp->a_grav[2] += X_000(dx) * lb->F_001; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 1 + + /* 1st order interaction */ + gp->a_grav[0] += + X_100(dx) * lb->F_200 + X_010(dx) * lb->F_110 + X_001(dx) * lb->F_101; + gp->a_grav[1] += + X_100(dx) * lb->F_110 + X_010(dx) * lb->F_020 + X_001(dx) * lb->F_011; + gp->a_grav[2] += + X_100(dx) * lb->F_101 + X_010(dx) * lb->F_011 + X_001(dx) * lb->F_002; #endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 2 + + /* 2nd order interaction */ + gp->a_grav[0] += + X_200(dx) * lb->F_300 + X_020(dx) * lb->F_120 + X_002(dx) * lb->F_102; + gp->a_grav[0] += + X_110(dx) * lb->F_210 + X_101(dx) * lb->F_201 + X_011(dx) * lb->F_111; + gp->a_grav[1] += + X_200(dx) * lb->F_210 + X_020(dx) * lb->F_030 + X_002(dx) * lb->F_012; + gp->a_grav[1] += + X_110(dx) * lb->F_120 + X_101(dx) * lb->F_111 + X_011(dx) * lb->F_021; + gp->a_grav[2] += + X_200(dx) * lb->F_201 + X_020(dx) * lb->F_021 + X_002(dx) * lb->F_003; + gp->a_grav[2] += + X_110(dx) * lb->F_111 + X_101(dx) * lb->F_102 + X_011(dx) * lb->F_012; +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 3 +#error "Missing implementation for order >3" +#endif +#if SELF_GRAVITY_MULTIPOLE_ORDER > 4 +#error "Missing implementation for order >4" +#endif +} #endif /* SWIFT_MULTIPOLE_H */ diff --git a/src/part.h b/src/part.h index e9a151f5b6dc6c2670ced942d195e108555c5a4b..1b40aee0db3deb4790e07e3da9807060900d0c55 100644 --- a/src/part.h +++ b/src/part.h @@ -58,6 +58,10 @@ #include "./hydro/Gizmo/hydro_part.h" #define hydro_need_extra_init_loop 0 #define EXTRA_HYDRO_LOOP +#elif defined(SHADOWFAX_SPH) +#include "./hydro/Shadowswift/hydro_part.h" +#define hydro_need_extra_init_loop 0 +#define EXTRA_HYDRO_LOOP #else #error "Invalid choice of SPH variant" #endif diff --git a/src/riemann/riemann_exact.h b/src/riemann/riemann_exact.h index 4a20561def8ac56889d4e2d836dd698e663e8d7e..4a7b40ae78c4c3f1a880539367172894fe298995 100644 --- a/src/riemann/riemann_exact.h +++ b/src/riemann/riemann_exact.h @@ -206,7 +206,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent( float fa, fb, fc, fs; float tmp, tmp2; int mflag; - int i; a = lower_limit; b = upper_limit; @@ -243,7 +242,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent( c = a; fc = fa; mflag = 1; - i = 0; while (!(fb == 0.0f) && (fabs(a - b) > error_tol * 0.5f * (a + b))) { if ((fa != fc) && (fb != fc)) /* Inverse quadratic interpolation */ @@ -286,7 +284,6 @@ __attribute__((always_inline)) INLINE static float riemann_solve_brent( fa = fb; fb = tmp; } - i++; } return b; } diff --git a/src/runner.c b/src/runner.c index 4296a781444aee7e15a2ae607d5c0e9998131da6..7b94168da5bcee5a2c5fde826f6505748a1b5b1b 100644 --- a/src/runner.c +++ b/src/runner.c @@ -55,6 +55,7 @@ #include "minmax.h" #include "runner_doiact_vec.h" #include "scheduler.h" +#include "sort_part.h" #include "sourceterms.h" #include "space.h" #include "stars.h" @@ -62,27 +63,6 @@ #include "timers.h" #include "timestep.h" -/* Orientation of the cell pairs */ -const double runner_shift[13][3] = { - {5.773502691896258e-01, 5.773502691896258e-01, 5.773502691896258e-01}, - {7.071067811865475e-01, 7.071067811865475e-01, 0.0}, - {5.773502691896258e-01, 5.773502691896258e-01, -5.773502691896258e-01}, - {7.071067811865475e-01, 0.0, 7.071067811865475e-01}, - {1.0, 0.0, 0.0}, - {7.071067811865475e-01, 0.0, -7.071067811865475e-01}, - {5.773502691896258e-01, -5.773502691896258e-01, 5.773502691896258e-01}, - {7.071067811865475e-01, -7.071067811865475e-01, 0.0}, - {5.773502691896258e-01, -5.773502691896258e-01, -5.773502691896258e-01}, - {0.0, 7.071067811865475e-01, 7.071067811865475e-01}, - {0.0, 1.0, 0.0}, - {0.0, 7.071067811865475e-01, -7.071067811865475e-01}, - {0.0, 0.0, 1.0}, -}; - -/* Does the axis need flipping ? */ -const char runner_flip[27] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - /* Import the density loop functions. */ #define FUNCTION density #include "runner_doiact.h" @@ -477,6 +457,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) { const int count = c->count; const int gcount = c->gcount; const struct engine *e = r->e; + const struct space *s = e->s; TIMER_TIC; @@ -485,7 +466,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) { /* Reset the gravity acceleration tensors */ if (e->policy & engine_policy_self_gravity) - gravity_field_tensor_init(c->multipole); + gravity_field_tensors_init(&c->multipole->pot); /* Recurse? */ if (c->split) { @@ -502,7 +483,7 @@ void runner_do_init(struct runner *r, struct cell *c, int timer) { if (part_is_active(p, e)) { /* Get ready for a density calculation */ - hydro_init_part(p); + hydro_init_part(p, &s->hs); } } @@ -584,6 +565,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) { struct part *restrict parts = c->parts; struct xpart *restrict xparts = c->xparts; const struct engine *e = r->e; + const struct space *s = e->s; const float hydro_h_max = e->hydro_properties->h_max; const float target_wcount = e->hydro_properties->target_neighbours; const float max_wcount = @@ -664,7 +646,7 @@ void runner_do_ghost(struct runner *r, struct cell *c, int timer) { redo += 1; /* Re-initialise everything */ - hydro_init_part(p); + hydro_init_part(p, &s->hs); /* Off we go ! */ continue; @@ -1359,13 +1341,17 @@ void runner_do_end_force(struct runner *r, struct cell *c, int timer) { } #ifdef SWIFT_DEBUG_CHECKS - if (e->policy & engine_policy_self_gravity) { - gp->mass_interacted += gp->mass; - if (fabs(gp->mass_interacted - e->s->total_mass) > gp->mass) + if (e->policy & engine_policy_self_gravity && gpart_is_active(gp, e)) { + + /* Check that this gpart has interacted with all the other particles + * (via direct or multipoles) in the box */ + gp->num_interacted++; + if (gp->num_interacted != (long long)e->s->nr_gparts) error( - "g-particle did not interact gravitationally with all other " - "particles gp->mass_interacted=%e, total_mass=%e, gp->mass=%e", - gp->mass_interacted, e->s->total_mass, gp->mass); + "g-particle (id=%lld, type=%d) did not interact gravitationally " + "with all other gparts gp->num_interacted=%lld, total_gparts=%zd", + gp->id_or_neg_offset, gp->type, gp->num_interacted, + e->s->nr_gparts); } #endif } @@ -1837,7 +1823,7 @@ void *runner_main(void *data) { // runner_do_grav_mm(r, t->ci, 1); break; case task_type_grav_down: - runner_do_grav_down(r, t->ci); + runner_do_grav_down(r, t->ci, 1); break; case task_type_grav_top_level: // runner_do_grav_top_level(r); diff --git a/src/runner.h b/src/runner.h index 5f175cec5a843ea25429a8433fe8c7061faeffce..49f7cd88a0b345cac1b29b7fd38cf59b21268012 100644 --- a/src/runner.h +++ b/src/runner.h @@ -23,11 +23,11 @@ #ifndef SWIFT_RUNNER_H #define SWIFT_RUNNER_H -#include "cache.h" -#include "sort.h" +/* Config parameters. */ +#include "../config.h" -extern const double runner_shift[13][3]; -extern const char runner_flip[27]; +/* Includes. */ +#include "cache.h" struct cell; struct engine; diff --git a/src/runner_doiact.h b/src/runner_doiact.h index a82f0e3be79f291877059d259d2c892562bb0890..fa8d390956c1c560ee44f87e3b892ea33cf6a37c 100644 --- a/src/runner_doiact.h +++ b/src/runner_doiact.h @@ -3056,7 +3056,7 @@ void DOPAIR1_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) { error("Trying to interact unsorted cells."); #if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH) && (DOPAIR1_BRANCH == runner_dopair1_density_branch) - if(!space_iscorner(sid)) + if(!sort_is_corner(sid)) runner_dopair1_density_vec(r, ci, cj); else DOPAIR1(r, ci, cj); diff --git a/src/runner_doiact_grav.h b/src/runner_doiact_grav.h index 9988b7d553a962ee541f20cab64cc534c991957a..b6c93e72f1c8d31d555a3306bf35098f0125ddf5 100644 --- a/src/runner_doiact_grav.h +++ b/src/runner_doiact_grav.h @@ -26,51 +26,54 @@ #include "part.h" /** - * @brief Compute the recursive upward sweep, i.e. construct the - * multipoles in a cell hierarchy. + * @brief Recursively propagate the multipoles down the tree by applying the + * L2L and L2P kernels. * * @param r The #runner. - * @param c The top-level #cell. + * @param c The #cell we are working on. + * @param timer Are we timing this ? */ -void runner_do_grav_up(struct runner *r, struct cell *c) { +void runner_do_grav_down(struct runner *r, struct cell *c, int timer) { - /* if (c->split) { /\* Regular node *\/ */ - - /* /\* Recurse. *\/ */ - /* for (int k = 0; k < 8; k++) */ - /* if (c->progeny[k] != NULL) runner_do_grav_up(r, c->progeny[k]); */ - - /* /\* Collect the multipoles from the progeny. *\/ */ - /* multipole_reset(&c->multipole); */ - /* for (int k = 0; k < 8; k++) { */ - /* if (c->progeny[k] != NULL) */ - /* multipole_add(&c->multipole, &c->progeny[k]->multipole); */ - /* } */ - - /* } else { /\* Leaf node. *\/ */ - /* /\* Just construct the multipole from the gparts. *\/ */ - /* multipole_init(&c->multipole, c->gparts, c->gcount); */ - /* } */ -} + const struct engine *e = r->e; + const int periodic = e->s->periodic; -void runner_do_grav_down(struct runner *r, struct cell *c) { + TIMER_TIC; if (c->split) { for (int k = 0; k < 8; ++k) { struct cell *cp = c->progeny[k]; - struct gravity_tensors temp; + struct grav_tensor temp; if (cp != NULL) { - gravity_L2L(&temp, c->multipole, cp->multipole->CoM, c->multipole->CoM, - 1); + + /* Shift the field tensor */ + gravity_L2L(&temp, &c->multipole->pot, cp->multipole->CoM, + c->multipole->CoM, 0 * periodic); + /* Add it to this level's tensor */ + gravity_field_tensors_add(&cp->multipole->pot, &temp); + + /* Recurse */ + runner_do_grav_down(r, cp, 0); } } - } else { + } else { /* Leaf case */ + + const struct engine *e = r->e; + struct gpart *gparts = c->gparts; + const int gcount = c->gcount; - gravity_L2P(c->multipole, c->gparts, c->gcount); + /* Apply accelerations to the particles */ + for (int i = 0; i < gcount; ++i) { + struct gpart *gp = &gparts[i]; + if (gpart_is_active(gp, e)) + gravity_L2P(&c->multipole->pot, c->multipole->CoM, gp); + } } + + if (timer) TIMER_TOC(timer_dograv_down); } /** @@ -81,9 +84,8 @@ void runner_do_grav_down(struct runner *r, struct cell *c) { * @param ci The #cell with field tensor to interact. * @param cj The #cell with the multipole. */ -__attribute__((always_inline)) INLINE static void runner_dopair_grav_mm( - const struct runner *r, const struct cell *restrict ci, - const struct cell *restrict cj) { +void runner_dopair_grav_mm(const struct runner *r, struct cell *restrict ci, + struct cell *restrict cj) { const struct engine *e = r->e; const int periodic = e->s->periodic; @@ -94,14 +96,20 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_mm( TIMER_TIC; #ifdef SWIFT_DEBUG_CHECKS - if (multi_j->mass == 0.0) error("Multipole does not seem to have been set."); + if (ci == cj) error("Interacting a cell with itself using M2L"); + + if (multi_j->M_000 == 0.f) error("Multipole does not seem to have been set."); #endif /* Anything to do here? */ if (!cell_is_active(ci, e)) return; - gravity_M2L(ci->multipole, multi_j, ci->multipole->CoM, cj->multipole->CoM, - periodic); + /* Do we need to drift the multipole ? */ + if (cj->ti_old_multipole != e->ti_current) cell_drift_multipole(cj, e); + + /* Let's interact at this level */ + gravity_M2L(&ci->multipole->pot, multi_j, ci->multipole->CoM, + cj->multipole->CoM, periodic * 0); TIMER_TOC(timer_dopair_grav_mm); } @@ -114,65 +122,11 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_mm( * @param ci The #cell with particles to interct. * @param cj The #cell with the multipole. */ -__attribute__((always_inline)) INLINE static void runner_dopair_grav_pm( - const struct runner *r, const struct cell *restrict ci, - const struct cell *restrict cj) { - - const struct engine *e = r->e; - const int gcount = ci->gcount; - struct gpart *restrict gparts = ci->gparts; - const struct gravity_tensors *multi = cj->multipole; - const float a_smooth = e->gravity_properties->a_smooth; - const float rlr_inv = 1. / (a_smooth * ci->super->width[0]); - - TIMER_TIC; - -#ifdef SWIFT_DEBUG_CHECKS - if (gcount == 0) error("Empty cell!"); - - if (multi->m_pole.mass == 0.0) - error("Multipole does not seem to have been set."); -#endif - - /* Anything to do here? */ - if (!cell_is_active(ci, e)) return; - -#if ICHECK > 0 - for (int pid = 0; pid < gcount; pid++) { - - /* Get a hold of the ith part in ci. */ - struct gpart *restrict gp = &gparts[pid]; +void runner_dopair_grav_pm(const struct runner *r, + const struct cell *restrict ci, + const struct cell *restrict cj) { - if (gp->id_or_neg_offset == ICHECK) - message("id=%lld loc=[ %f %f %f ] size= %f count= %d", - gp->id_or_neg_offset, cj->loc[0], cj->loc[1], cj->loc[2], - cj->width[0], cj->gcount); - } -#endif - - /* Loop over every particle in leaf. */ - for (int pid = 0; pid < gcount; pid++) { - - /* Get a hold of the ith part in ci. */ - struct gpart *restrict gp = &gparts[pid]; - - if (!gpart_is_active(gp, e)) continue; - - /* Compute the pairwise distance. */ - const float dx[3] = {multi->CoM[0] - gp->x[0], // x - multi->CoM[1] - gp->x[1], // y - multi->CoM[2] - gp->x[2]}; // z - const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; - - /* Interact !*/ - runner_iact_grav_pm(rlr_inv, r2, dx, gp, &multi->m_pole); - -#ifdef SWIFT_DEBUG_CHECKS - gp->mass_interacted += multi->m_pole.mass; -#endif - } - - TIMER_TOC(timer_dopair_grav_pm); + error("Function should not be called"); } /** @@ -185,8 +139,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pm( * * @todo Use a local cache for the particles. */ -__attribute__((always_inline)) INLINE static void runner_dopair_grav_pp( - struct runner *r, struct cell *ci, struct cell *cj) { +void runner_dopair_grav_pp(struct runner *r, struct cell *ci, struct cell *cj) { const struct engine *e = r->e; const int gcount_i = ci->gcount; @@ -199,7 +152,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp( TIMER_TIC; #ifdef SWIFT_DEBUG_CHECKS - if (ci->width[0] != cj->width[0]) // MATTHIEU sanity check + if (ci->width[0] != cj->width[0]) error("Non matching cell sizes !! h_i=%f h_j=%f", ci->width[0], cj->width[0]); #endif @@ -237,49 +190,55 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp( /* Get a hold of the ith part in ci. */ struct gpart *restrict gpi = &gparts_i[pid]; + if (!gpart_is_active(gpi, e)) continue; + /* Loop over every particle in the other cell. */ for (int pjd = 0; pjd < gcount_j; pjd++) { /* Get a hold of the jth part in cj. */ - struct gpart *restrict gpj = &gparts_j[pjd]; + const struct gpart *restrict gpj = &gparts_j[pjd]; /* Compute the pairwise distance. */ - float dx[3] = {gpi->x[0] - gpj->x[0], // x - gpi->x[1] - gpj->x[1], // y - gpi->x[2] - gpj->x[2]}; // z + const float dx[3] = {gpi->x[0] - gpj->x[0], // x + gpi->x[1] - gpj->x[1], // y + gpi->x[2] - gpj->x[2]}; // z const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; /* Interact ! */ - if (gpart_is_active(gpi, e) && gpart_is_active(gpj, e)) { - - runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj); + runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj); #ifdef SWIFT_DEBUG_CHECKS - gpi->mass_interacted += gpj->mass; - gpj->mass_interacted += gpi->mass; + gpi->num_interacted++; #endif + } + } - } else { + /* Loop over all particles in cj... */ + for (int pjd = 0; pjd < gcount_j; pjd++) { - if (gpart_is_active(gpi, e)) { + /* Get a hold of the ith part in ci. */ + struct gpart *restrict gpj = &gparts_j[pjd]; - runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj); + if (!gpart_is_active(gpj, e)) continue; -#ifdef SWIFT_DEBUG_CHECKS - gpi->mass_interacted += gpj->mass; -#endif - } else if (gpart_is_active(gpj, e)) { + /* Loop over every particle in the other cell. */ + for (int pid = 0; pid < gcount_i; pid++) { - dx[0] = -dx[0]; - dx[1] = -dx[1]; - dx[2] = -dx[2]; - runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi); + /* Get a hold of the ith part in ci. */ + const struct gpart *restrict gpi = &gparts_i[pid]; + + /* Compute the pairwise distance. */ + const float dx[3] = {gpj->x[0] - gpi->x[0], // x + gpj->x[1] - gpi->x[1], // y + gpj->x[2] - gpi->x[2]}; // z + const float r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; + + /* Interact ! */ + runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi); #ifdef SWIFT_DEBUG_CHECKS - gpj->mass_interacted += gpi->mass; + gpj->num_interacted++; #endif - } - } } } @@ -294,8 +253,7 @@ __attribute__((always_inline)) INLINE static void runner_dopair_grav_pp( * * @todo Use a local cache for the particles. */ -__attribute__((always_inline)) INLINE static void runner_doself_grav_pp( - struct runner *r, struct cell *c) { +void runner_doself_grav_pp(struct runner *r, struct cell *c) { const struct engine *e = r->e; const int gcount = c->gcount; @@ -306,8 +264,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp( TIMER_TIC; #ifdef SWIFT_DEBUG_CHECKS - if (c->gcount == 0) // MATTHIEU sanity check - error("Empty cell !"); + if (c->gcount == 0) error("Doing self gravity on an empty cell !"); #endif /* Anything to do here? */ @@ -350,8 +307,8 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp( runner_iact_grav_pp(rlr_inv, r2, dx, gpi, gpj); #ifdef SWIFT_DEBUG_CHECKS - gpi->mass_interacted += gpj->mass; - gpj->mass_interacted += gpi->mass; + gpi->num_interacted++; + gpj->num_interacted++; #endif } else { @@ -361,7 +318,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp( runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpi, gpj); #ifdef SWIFT_DEBUG_CHECKS - gpi->mass_interacted += gpj->mass; + gpi->num_interacted++; #endif } else if (gpart_is_active(gpj, e)) { @@ -372,7 +329,7 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp( runner_iact_grav_pp_nonsym(rlr_inv, r2, dx, gpj, gpi); #ifdef SWIFT_DEBUG_CHECKS - gpj->mass_interacted += gpi->mass; + gpj->num_interacted++; #endif } } @@ -393,8 +350,8 @@ __attribute__((always_inline)) INLINE static void runner_doself_grav_pp( * * @todo Use a local cache for the particles. */ -static void runner_dopair_grav(struct runner *r, struct cell *ci, - struct cell *cj, int gettimer) { +void runner_dopair_grav(struct runner *r, struct cell *ci, struct cell *cj, + int gettimer) { #ifdef SWIFT_DEBUG_CHECKS @@ -402,7 +359,8 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci, const int gcount_j = cj->gcount; /* Early abort? */ - if (gcount_i == 0 || gcount_j == 0) error("Empty cell !"); + if (gcount_i == 0 || gcount_j == 0) + error("Doing pair gravity on an empty cell !"); /* Bad stuff will happen if cell sizes are different */ if (ci->width[0] != cj->width[0]) @@ -468,9 +426,9 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci, } else { - /* Ok, here we can go for particle-multipole interactions */ - runner_dopair_grav_pm(r, ci->progeny[j], cj->progeny[k]); - runner_dopair_grav_pm(r, cj->progeny[k], ci->progeny[j]); + /* Ok, here we can go for multipole-multipole interactions */ + runner_dopair_grav_mm(r, ci->progeny[j], cj->progeny[k]); + runner_dopair_grav_mm(r, cj->progeny[k], ci->progeny[j]); } } } @@ -494,12 +452,12 @@ static void runner_dopair_grav(struct runner *r, struct cell *ci, * * @todo Use a local cache for the particles. */ -static void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) { +void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) { #ifdef SWIFT_DEBUG_CHECKS /* Early abort? */ - if (c->gcount == 0) error("Empty cell !"); + if (c->gcount == 0) error("Doing self gravity on an empty cell !"); #endif TIMER_TIC; @@ -532,8 +490,8 @@ static void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) { if (gettimer) TIMER_TOC(timer_dosub_self_grav); } -static void runner_dosub_grav(struct runner *r, struct cell *ci, - struct cell *cj, int timer) { +void runner_dosub_grav(struct runner *r, struct cell *ci, struct cell *cj, + int timer) { /* Is this a single cell? */ if (cj == NULL) { @@ -551,8 +509,7 @@ static void runner_dosub_grav(struct runner *r, struct cell *ci, } } -static void runner_do_grav_long_range(struct runner *r, struct cell *ci, - int timer) { +void runner_do_grav_long_range(struct runner *r, struct cell *ci, int timer) { #if ICHECK > 0 for (int pid = 0; pid < ci->gcount; pid++) { @@ -567,6 +524,8 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci, } #endif + TIMER_TIC; + /* Recover the list of top-level cells */ const struct engine *e = r->e; struct cell *cells = e->s->cells_top; @@ -579,6 +538,9 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci, /* Anything to do here? */ if (!cell_is_active(ci, e)) return; + /* Drift our own multipole if need be */ + if (ci->ti_old_multipole != e->ti_current) cell_drift_multipole(ci, e); + /* Loop over all the cells and go for a p-m interaction if far enough but not * too far */ for (int i = 0; i < nr_cells; ++i) { @@ -586,16 +548,18 @@ static void runner_do_grav_long_range(struct runner *r, struct cell *ci, struct cell *cj = &cells[i]; if (ci == cj) continue; + if (cj->gcount == 0) continue; /* const double dx[3] = {cj->loc[0] - pos_i[0], // x */ /* cj->loc[1] - pos_i[1], // y */ /* cj->loc[2] - pos_i[2]}; // z */ /* const double r2 = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]; */ - - // if (r2 > max_d2) continue; + /* if (r2 > max_d2) continue; */ if (!cell_are_neighbours(ci, cj)) runner_dopair_grav_mm(r, ci, cj); } + + if (timer) TIMER_TOC(timer_dograv_long_range); } #endif /* SWIFT_RUNNER_DOIACT_GRAV_H */ diff --git a/src/scheduler.c b/src/scheduler.c index 9f0f0fd944ebeb1399591214489272d38e150a72..97300ff7b37d46638a83e143a702d1a33d494956 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -46,6 +46,7 @@ #include "intrinsics.h" #include "kernel_hydro.h" #include "queue.h" +#include "sort_part.h" #include "space.h" #include "task.h" #include "timers.h" @@ -229,7 +230,7 @@ static void scheduler_splittask(struct task *t, struct scheduler *s) { /* Replace by a single sub-task? */ if (scheduler_dosub && ci->count * sid_scale[sid] < space_subsize / cj->count && - sid != 0 && sid != 2 && sid != 6 && sid != 8) { + !sort_is_corner(sid)) { /* Make this task a sub task. */ t->type = task_type_sub_pair; diff --git a/src/sort_part.h b/src/sort_part.h new file mode 100644 index 0000000000000000000000000000000000000000..a243fcdfae8ec0aba606000e26bc18d35601215c --- /dev/null +++ b/src/sort_part.h @@ -0,0 +1,120 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2017 James S. Wills (james.s.willis@durham.ac.uk) + * 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/>. + * + ******************************************************************************/ +#ifndef SWIFT_SORT_PART_H +#define SWIFT_SORT_PART_H + +/** + * @brief Entry in a list of sorted indices. + */ +struct entry { + + /*! Distance on the axis */ + float d; + + /*! Particle index */ + int i; +}; + +/* Orientation of the cell pairs */ +static const double runner_shift[13][3] = { + {5.773502691896258e-01, 5.773502691896258e-01, 5.773502691896258e-01}, + {7.071067811865475e-01, 7.071067811865475e-01, 0.0}, + {5.773502691896258e-01, 5.773502691896258e-01, -5.773502691896258e-01}, + {7.071067811865475e-01, 0.0, 7.071067811865475e-01}, + {1.0, 0.0, 0.0}, + {7.071067811865475e-01, 0.0, -7.071067811865475e-01}, + {5.773502691896258e-01, -5.773502691896258e-01, 5.773502691896258e-01}, + {7.071067811865475e-01, -7.071067811865475e-01, 0.0}, + {5.773502691896258e-01, -5.773502691896258e-01, -5.773502691896258e-01}, + {0.0, 7.071067811865475e-01, 7.071067811865475e-01}, + {0.0, 1.0, 0.0}, + {0.0, 7.071067811865475e-01, -7.071067811865475e-01}, + {0.0, 0.0, 1.0}, +}; + +/* Does the axis need flipping ? */ +static const char runner_flip[27] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +/* Map shift vector to sortlist. */ +static const int sortlistID[27] = { + /* ( -1 , -1 , -1 ) */ 0, + /* ( -1 , -1 , 0 ) */ 1, + /* ( -1 , -1 , 1 ) */ 2, + /* ( -1 , 0 , -1 ) */ 3, + /* ( -1 , 0 , 0 ) */ 4, + /* ( -1 , 0 , 1 ) */ 5, + /* ( -1 , 1 , -1 ) */ 6, + /* ( -1 , 1 , 0 ) */ 7, + /* ( -1 , 1 , 1 ) */ 8, + /* ( 0 , -1 , -1 ) */ 9, + /* ( 0 , -1 , 0 ) */ 10, + /* ( 0 , -1 , 1 ) */ 11, + /* ( 0 , 0 , -1 ) */ 12, + /* ( 0 , 0 , 0 ) */ 0, + /* ( 0 , 0 , 1 ) */ 12, + /* ( 0 , 1 , -1 ) */ 11, + /* ( 0 , 1 , 0 ) */ 10, + /* ( 0 , 1 , 1 ) */ 9, + /* ( 1 , -1 , -1 ) */ 8, + /* ( 1 , -1 , 0 ) */ 7, + /* ( 1 , -1 , 1 ) */ 6, + /* ( 1 , 0 , -1 ) */ 5, + /* ( 1 , 0 , 0 ) */ 4, + /* ( 1 , 0 , 1 ) */ 3, + /* ( 1 , 1 , -1 ) */ 2, + /* ( 1 , 1 , 0 ) */ 1, + /* ( 1 , 1 , 1 ) */ 0}; + +/** + * @brief Determines whether a pair of cells are corner to corner. + * + * @param sid sort ID + * + * @return 1 if corner to corner, 0 otherwise. + */ +__attribute__((always_inline)) INLINE static int sort_is_corner(int sid) { + return (sid == 0 || sid == 2 || sid == 6 || sid == 8); +} + +/** + * @brief Determines whether a pair of cells are edge to edge. + * + * @param sid sort ID + * + * @return 1 if edge to edge, 0 otherwise. + */ +__attribute__((always_inline)) INLINE static int sort_is_edge(int sid) { + return (sid == 1 || sid == 3 || sid == 5 || sid == 7 || sid == 9 || + sid == 11); +} + +/** + * @brief Determines whether a pair of cells are face to face. + * + * @param sid sort ID + * + * @return 1 if face to face, 0 otherwise. + */ +__attribute__((always_inline)) INLINE static int sort_is_face(int sid) { + return (sid == 4 || sid == 10 || sid == 12); +} + +#endif /* SWIFT_SORT_PART_H */ diff --git a/src/space.c b/src/space.c index 75305f47e1bf0828d293e42d76bd87e1152bdbba..432873215c258b987b3c83f87486ade061ea66f0 100644 --- a/src/space.c +++ b/src/space.c @@ -53,6 +53,7 @@ #include "minmax.h" #include "multipole.h" #include "runner.h" +#include "sort_part.h" #include "stars.h" #include "threadpool.h" #include "tools.h" @@ -63,36 +64,6 @@ int space_subsize = space_subsize_default; int space_maxsize = space_maxsize_default; int space_maxcount = space_maxcount_default; -/* Map shift vector to sortlist. */ -const int sortlistID[27] = { - /* ( -1 , -1 , -1 ) */ 0, - /* ( -1 , -1 , 0 ) */ 1, - /* ( -1 , -1 , 1 ) */ 2, - /* ( -1 , 0 , -1 ) */ 3, - /* ( -1 , 0 , 0 ) */ 4, - /* ( -1 , 0 , 1 ) */ 5, - /* ( -1 , 1 , -1 ) */ 6, - /* ( -1 , 1 , 0 ) */ 7, - /* ( -1 , 1 , 1 ) */ 8, - /* ( 0 , -1 , -1 ) */ 9, - /* ( 0 , -1 , 0 ) */ 10, - /* ( 0 , -1 , 1 ) */ 11, - /* ( 0 , 0 , -1 ) */ 12, - /* ( 0 , 0 , 0 ) */ 0, - /* ( 0 , 0 , 1 ) */ 12, - /* ( 0 , 1 , -1 ) */ 11, - /* ( 0 , 1 , 0 ) */ 10, - /* ( 0 , 1 , 1 ) */ 9, - /* ( 1 , -1 , -1 ) */ 8, - /* ( 1 , -1 , 0 ) */ 7, - /* ( 1 , -1 , 1 ) */ 6, - /* ( 1 , 0 , -1 ) */ 5, - /* ( 1 , 0 , 0 ) */ 4, - /* ( 1 , 0 , 1 ) */ 3, - /* ( 1 , 1 , -1 ) */ 2, - /* ( 1 , 1 , 0 ) */ 1, - /* ( 1 , 1 , 1 ) */ 0}; - /** * @brief Interval stack necessary for parallel particle sorting. */ @@ -171,17 +142,6 @@ int space_getsid(struct space *s, struct cell **ci, struct cell **cj, return sid; } -/** - * @brief Determines whether a pair of cells are corner to corner. - * - * @param sort ID - * - * @return True if corner to corner - */ -int space_iscorner(int sid) { - return (sid == 0 || sid == 2 || sid == 6 || sid == 8); -} - /** * @brief Recursively dismantle a cell tree. * @@ -2111,10 +2071,10 @@ void space_split_recursive(struct space *s, struct cell *c, for (int k = 0; k < 8; ++k) { if (c->progeny[k] != NULL) { const struct gravity_tensors *m = c->progeny[k]->multipole; - CoM[0] += m->CoM[0] * m->m_pole.mass; - CoM[1] += m->CoM[1] * m->m_pole.mass; - CoM[2] += m->CoM[2] * m->m_pole.mass; - mass += m->m_pole.mass; + CoM[0] += m->CoM[0] * m->m_pole.M_000; + CoM[1] += m->CoM[1] * m->m_pole.M_000; + CoM[2] += m->CoM[2] * m->m_pole.M_000; + mass += m->m_pole.M_000; } } c->multipole->CoM[0] = CoM[0] / mass; @@ -2703,6 +2663,8 @@ void space_init(struct space *s, const struct swift_params *params, bzero(s->xparts, Npart * sizeof(struct xpart)); } + hydro_space_init(&s->hs, s); + /* Set the particles in a state where they are ready for a run */ space_init_parts(s); space_init_xparts(s); diff --git a/src/space.h b/src/space.h index e110faa68f1dce4cfc4c7ae9dad47fd5d45352d7..d2879d96b9a4ede4e96236ddd5ac19897fbd10cd 100644 --- a/src/space.h +++ b/src/space.h @@ -31,6 +31,7 @@ /* Includes. */ #include "cell.h" +#include "hydro_space.h" #include "lock.h" #include "parser.h" #include "part.h" @@ -55,9 +56,6 @@ extern int space_maxsize; extern int space_subsize; extern int space_maxcount; -/* Map shift vector to sortlist. */ -extern const int sortlistID[27]; - /** * @brief The space in which the cells and particles reside. */ @@ -69,12 +67,12 @@ struct space { /*! Is the space periodic? */ int periodic; + /*! Extra space information needed for some hydro schemes. */ + struct hydro_space hs; + /*! Are we doing gravity? */ int gravity; - /*! Total mass in the system */ - double total_mass; - /*! Width of the top-level cells. */ double width[3]; @@ -169,7 +167,6 @@ void space_gparts_sort(struct space *s, int *ind, size_t N, int min, int max, void space_sparts_sort(struct space *s, int *ind, size_t N, int min, int max, int verbose); void space_getcells(struct space *s, int nr_cells, struct cell **cells); -int space_iscorner(int sid); int space_getsid(struct space *s, struct cell **ci, struct cell **cj, double *shift); void space_init(struct space *s, const struct swift_params *params, diff --git a/src/task.c b/src/task.c index dfc3dc538c75297cbe7e79b7d64c1b7e13a015dc..0ac7bc0c59eb678383adba9587a8026cf872ee6d 100644 --- a/src/task.c +++ b/src/task.c @@ -276,8 +276,8 @@ float task_overlap(const struct task *restrict ta, */ void task_unlock(struct task *t) { - const int type = t->type; - const int subtype = t->subtype; + const enum task_types type = t->type; + const enum task_subtypes subtype = t->subtype; struct cell *ci = t->ci, *cj = t->cj; /* Act based on task type. */ @@ -300,6 +300,7 @@ void task_unlock(struct task *t) { case task_type_sub_self: if (subtype == task_subtype_grav) { cell_gunlocktree(ci); + cell_munlocktree(ci); } else { cell_unlocktree(ci); } @@ -310,15 +311,25 @@ void task_unlock(struct task *t) { if (subtype == task_subtype_grav) { cell_gunlocktree(ci); cell_gunlocktree(cj); + cell_munlocktree(ci); + cell_munlocktree(cj); } else { cell_unlocktree(ci); cell_unlocktree(cj); } break; - case task_type_grav_mm: + case task_type_grav_down: cell_gunlocktree(ci); + cell_munlocktree(ci); + break; + + case task_type_grav_top_level: + case task_type_grav_long_range: + case task_type_grav_mm: + cell_munlocktree(ci); break; + default: break; } @@ -331,8 +342,8 @@ void task_unlock(struct task *t) { */ int task_lock(struct task *t) { - const int type = t->type; - const int subtype = t->subtype; + const enum task_types type = t->type; + const enum task_subtypes subtype = t->subtype; struct cell *ci = t->ci, *cj = t->cj; #ifdef WITH_MPI int res = 0, err = 0; @@ -379,7 +390,14 @@ int task_lock(struct task *t) { case task_type_self: case task_type_sub_self: if (subtype == task_subtype_grav) { - if (cell_glocktree(ci) != 0) return 0; + /* Lock the gparts and the m-pole */ + if (ci->ghold || ci->mhold) return 0; + if (cell_glocktree(ci) != 0) + return 0; + else if (cell_mlocktree(ci) != 0) { + cell_gunlocktree(ci); + return 0; + } } else { if (cell_locktree(ci) != 0) return 0; } @@ -388,13 +406,24 @@ int task_lock(struct task *t) { case task_type_pair: case task_type_sub_pair: if (subtype == task_subtype_grav) { + /* Lock the gparts and the m-pole in both cells */ if (ci->ghold || cj->ghold) return 0; if (cell_glocktree(ci) != 0) return 0; if (cell_glocktree(cj) != 0) { cell_gunlocktree(ci); return 0; + } else if (cell_mlocktree(ci) != 0) { + cell_gunlocktree(ci); + cell_gunlocktree(cj); + return 0; + } else if (cell_mlocktree(cj) != 0) { + cell_gunlocktree(ci); + cell_gunlocktree(cj); + cell_munlocktree(ci); + return 0; } } else { + /* Lock the parts in both cells */ if (ci->hold || cj->hold) return 0; if (cell_locktree(ci) != 0) return 0; if (cell_locktree(cj) != 0) { @@ -404,8 +433,23 @@ int task_lock(struct task *t) { } break; + case task_type_grav_down: + /* Lock the gparts and the m-poles */ + if (ci->ghold || ci->mhold) return 0; + if (cell_glocktree(ci) != 0) + return 0; + else if (cell_mlocktree(ci) != 0) { + cell_gunlocktree(ci); + return 0; + } + break; + + case task_type_grav_top_level: + case task_type_grav_long_range: case task_type_grav_mm: - cell_glocktree(ci); + /* Lock the m-poles */ + if (ci->mhold) return 0; + if (cell_mlocktree(ci) != 0) return 0; break; default: diff --git a/src/timers.h b/src/timers.h index 4cb4d7e0ba60003ba4caefffe257c929c59a8d9e..39bcf30fba0ec36d9209ddcbf3c71035a5851dbb 100644 --- a/src/timers.h +++ b/src/timers.h @@ -49,6 +49,8 @@ enum { timer_dopair_grav_mm, timer_dopair_grav_pp, timer_dograv_external, + timer_dograv_down, + timer_dograv_long_range, timer_dosource, timer_dosub_self_density, timer_dosub_self_gradient, diff --git a/src/tools.c b/src/tools.c index 89ac286fb435c01b361bdea66e62dd2d7f41ee24..73684c82662870d368f7dd360c84635654f06434 100644 --- a/src/tools.c +++ b/src/tools.c @@ -144,7 +144,7 @@ void pairs_single_density(double *dim, long long int pid, p = parts[k]; printf("pairs_single: part[%i].id == %lli.\n", k, pid); - hydro_init_part(&p); + hydro_init_part(&p, NULL); /* Loop over all particle pairs. */ for (k = 0; k < N; k++) { @@ -459,7 +459,7 @@ void engine_single_density(double *dim, long long int pid, p = parts[k]; /* Clear accumulators. */ - hydro_init_part(&p); + hydro_init_part(&p, NULL); /* Loop over all particle pairs (force). */ for (k = 0; k < N; k++) { diff --git a/src/vector_power.h b/src/vector_power.h new file mode 100644 index 0000000000000000000000000000000000000000..5e7c197f8db74f5865ba4d55a79c0e0abaab9baf --- /dev/null +++ b/src/vector_power.h @@ -0,0 +1,413 @@ +/******************************************************************************* + * 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/>. + * + ******************************************************************************/ +#ifndef SWIFT_VECTOR_POWER_H +#define SWIFT_VECTOR_POWER_H + +/** + * @file vector_power.h + * @brief Powers of 3D vectors to a multi-index with factorial. + * + * These expressions are to be used in 3D Taylor series. + * + * We use the notation of Dehnen, Computational Astrophysics and Cosmology, + * 1, 1, pp. 24 (2014), arXiv:1405.2255. + * + * We compute \f$ \frac{1}{\vec{m}!}\vec{v}^{\vec{m}} \f$ for all relevant m. + */ + +/* Config parameters. */ +#include "../config.h" + +/* Some standard headers. */ +#include <math.h> + +/***************************/ +/* 0th order vector powers */ +/***************************/ + +/** + * @brief \f$ \frac{1}{(0,0,0)!}\vec{v}^{(0,0,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_000(const double v[3]) { + + return 1.; +} + +/***************************/ +/* 1st order vector powers */ +/***************************/ + +/** + * @brief \f$ \frac{1}{(1,0,0)!}\vec{v}^{(1,0,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_100(const double v[3]) { + + return v[0]; +} + +/** + * @brief \f$ \frac{1}{(0,1,0)!}\vec{v}^{(0,1,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_010(const double v[3]) { + + return v[1]; +} + +/** + * @brief \f$ \frac{1}{(0,0,1)!}\vec{v}^{(0,0,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_001(const double v[3]) { + + return v[2]; +} + +/***************************/ +/* 2nd order vector powers */ +/***************************/ + +/** + * @brief \f$ \frac{1}{(2,0,0)!}\vec{v}^{(2,0,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_200(const double v[3]) { + + return 0.5 * v[0] * v[0]; +} + +/** + * @brief \f$ \frac{1}{(0,2,0)!}\vec{v}^{(0,2,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_020(const double v[3]) { + + return 0.5 * v[1] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(0,0,2)!}\vec{v}^{(0,0,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_002(const double v[3]) { + + return 0.5 * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,1,0)!}\vec{v}^{(1,1,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_110(const double v[3]) { + + return v[0] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(1,0,1)!}\vec{v}^{(1,0,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_101(const double v[3]) { + + return v[0] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(0,1,1)!}\vec{v}^{(0,1,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_011(const double v[3]) { + + return v[1] * v[2]; +} + +/***************************/ +/* 3rd order vector powers */ +/***************************/ + +/** + * @brief \f$ \frac{1}{(3,0,0)!}\vec{v}^{(3,0,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_300(const double v[3]) { + + return 0.1666666666666667 * v[0] * v[0] * v[0]; +} + +/** + * @brief \f$ \frac{1}{(0,3,0)!}\vec{v}^{(0,3,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_030(const double v[3]) { + + return 0.1666666666666667 * v[1] * v[1] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(0,0,3)!}\vec{v}^{(0,0,3)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_003(const double v[3]) { + + return 0.1666666666666667 * v[2] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(2,1,0)!}\vec{v}^{(2,1,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_210(const double v[3]) { + + return 0.5 * v[0] * v[0] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(2,0,1)!}\vec{v}^{(2,0,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_201(const double v[3]) { + + return 0.5 * v[0] * v[0] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,2,0)!}\vec{v}^{(1,2,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_120(const double v[3]) { + + return 0.5 * v[0] * v[1] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(0,2,1)!}\vec{v}^{(0,2,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_021(const double v[3]) { + + return 0.5 * v[1] * v[1] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,0,2)!}\vec{v}^{(1,0,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_102(const double v[3]) { + + return 0.5 * v[0] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(0,1,2)!}\vec{v}^{(0,1,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_012(const double v[3]) { + + return 0.5 * v[1] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,1,1)!}\vec{v}^{(1,1,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_111(const double v[3]) { + + return v[0] * v[1] * v[2]; +} + +/***************************/ +/* 4th order vector powers */ +/***************************/ + +/** + * @brief \f$ \frac{1}{(4,0,0)!}\vec{v}^{(4,0,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_400(const double v[3]) { + + const double vv = v[0] * v[0]; + return 0.041666666666666667 * vv * vv; +} + +/** + * @brief \f$ \frac{1}{(0,4,0)!}\vec{v}^{(0,4,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_040(const double v[3]) { + + const double vv = v[1] * v[1]; + return 0.041666666666666667 * vv * vv; +} + +/** + * @brief \f$ \frac{1}{(0,0,4)!}\vec{v}^{(0,0,4)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_004(const double v[3]) { + + const double vv = v[2] * v[2]; + return 0.041666666666666667 * vv * vv; +} + +/** + * @brief \f$ \frac{1}{(3,1,0)!}\vec{v}^{(3,1,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_310(const double v[3]) { + + return 0.1666666666666667 * v[0] * v[0] * v[0] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(3,0,1)!}\vec{v}^{(3,0,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_301(const double v[3]) { + + return 0.1666666666666667 * v[0] * v[0] * v[0] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(1,3,0)!}\vec{v}^{(1,3,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_130(const double v[3]) { + + return 0.1666666666666667 * v[0] * v[1] * v[1] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(0,3,1)!}\vec{v}^{(0,3,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_031(const double v[3]) { + + return 0.1666666666666667 * v[1] * v[1] * v[1] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,0,3)!}\vec{v}^{(1,0,3)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_103(const double v[3]) { + + return 0.1666666666666667 * v[0] * v[2] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(0,1,3)!}\vec{v}^{(0,1,3)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_013(const double v[3]) { + + return 0.1666666666666667 * v[1] * v[2] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(2,2,0)!}\vec{v}^{(2,2,0)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_220(const double v[3]) { + + return 0.25 * v[0] * v[0] * v[1] * v[1]; +} + +/** + * @brief \f$ \frac{1}{(2,0,2)!}\vec{v}^{(2,0,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_202(const double v[3]) { + + return 0.25 * v[0] * v[0] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(0,2,2)!}\vec{v}^{(0,2,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_022(const double v[3]) { + + return 0.25 * v[1] * v[1] * v[2] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(2,1,1)!}\vec{v}^{(2,1,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_211(const double v[3]) { + + return 0.5 * v[0] * v[0] * v[1] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,2,1)!}\vec{v}^{(1,2,1)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_121(const double v[3]) { + + return 0.5 * v[0] * v[1] * v[1] * v[2]; +} + +/** + * @brief \f$ \frac{1}{(1,1,2)!}\vec{v}^{(1,1,2)} \f$. + * + * @param v vector (\f$ v \f$). + */ +__attribute__((always_inline)) INLINE static double X_112(const double v[3]) { + + return 0.5 * v[0] * v[1] * v[2] * v[2]; +} + +#endif /* SWIFT_VECTOR_POWER_H */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 0db5c2544433012dcd7f451f535391aa81b1f802..4fb1e3492f95eea4b6019524f939bdc3be2634e7 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -25,7 +25,9 @@ TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetr testPair.sh testPairPerturbed.sh test27cells.sh test27cellsPerturbed.sh \ testParser.sh testSPHStep test125cells.sh testKernelGrav testFFT \ testAdiabaticIndex testRiemannExact testRiemannTRRS testRiemannHLLC \ - testMatrixInversion testThreadpool testDump testLogger + testMatrixInversion testThreadpool testDump testLogger + +#testVoronoi1D testVoronoi2D testVoronoi3D # List of test programs to compile check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \ @@ -33,7 +35,9 @@ check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \ testKernel testKernelGrav testFFT testInteractions testMaths \ testSymmetry testThreadpool benchmarkInteractions \ testAdiabaticIndex testRiemannExact testRiemannTRRS \ - testRiemannHLLC testMatrixInversion testDump testLogger + testRiemannHLLC testMatrixInversion testDump testLogger + +#testVoronoi1D testVoronoi2D testVoronoi3D # Sources for the individual programs testGreetings_SOURCES = testGreetings.c @@ -78,6 +82,12 @@ testRiemannHLLC_SOURCES = testRiemannHLLC.c testMatrixInversion_SOURCES = testMatrixInversion.c +#testVoronoi1D_SOURCES = testVoronoi1D.c + +#testVoronoi2D_SOURCES = testVoronoi2D.c + +#testVoronoi3D_SOURCES = testVoronoi3D.c + testThreadpool_SOURCES = testThreadpool.c testDump_SOURCES = testDump.c diff --git a/tests/benchmarkInteractions.c b/tests/benchmarkInteractions.c index e3f558f88dffbab252bf7c06f9e943ff568b6fff..0f5b3d2eb294c13e3035885b511c702e6f0cd540 100644 --- a/tests/benchmarkInteractions.c +++ b/tests/benchmarkInteractions.c @@ -92,7 +92,7 @@ struct part *make_particles(size_t count, double *offset, double spacing, p->h = h; p->id = ++(*partId); -#if !defined(GIZMO_SPH) +#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH) p->mass = 1.0f; #endif @@ -113,7 +113,7 @@ struct part *make_particles(size_t count, double *offset, double spacing, p->h = h; p->id = ++(*partId); -#if !defined(GIZMO_SPH) +#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH) p->mass = 1.0f; #endif } @@ -125,7 +125,7 @@ struct part *make_particles(size_t count, double *offset, double spacing, */ void prepare_force(struct part *parts, size_t count) { -#if !defined(GIZMO_SPH) +#if !defined(GIZMO_SPH) && !defined(SHADOWFAX_SPH) struct part *p; for (size_t i = 0; i < count; ++i) { p = &parts[i]; @@ -152,18 +152,18 @@ void dump_indv_particle_fields(char *fileName, struct part *p) { "%13e %13e %13e\n", p->id, p->x[0], p->x[1], p->x[2], p->v[0], p->v[1], p->v[2], p->a_hydro[0], p->a_hydro[1], p->a_hydro[2], -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0., 0., #else p->rho, p->density.rho_dh, #endif p->density.wcount, p->density.wcount_dh, p->force.h_dt, -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0., #else p->force.v_sig, #endif -#if defined(MINIMAL_SPH) || defined(GIZMO_SPH) +#if defined(MINIMAL_SPH) || defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0., 0., 0., 0. #else p->density.div_v, p->density.rot_v[0], p->density.rot_v[1], diff --git a/tests/test125cells.c b/tests/test125cells.c index b3895ffa9fc0e4c147bfbf58dc1b5a6301b02763..21fc3f5407f90d9b75485d08bf1077e0a20e88b2 100644 --- a/tests/test125cells.c +++ b/tests/test125cells.c @@ -101,7 +101,7 @@ void set_energy_state(struct part *part, enum pressure_field press, float size, part->u = pressure / (hydro_gamma_minus_one * density); #elif defined(MINIMAL_SPH) part->u = pressure / (hydro_gamma_minus_one * density); -#elif defined(GIZMO_SPH) +#elif defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) part->primitives.P = pressure; #else error("Need to define pressure here !"); @@ -198,8 +198,13 @@ void reset_particles(struct cell *c, enum velocity_field vel, set_velocity(p, vel, size); set_energy_state(p, press, size, density); +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) + float volume = p->conserved.mass / density; #if defined(GIZMO_SPH) - p->geometry.volume = p->conserved.mass / density; + p->geometry.volume = volume; +#else + p->cell.volume = volume; +#endif p->primitives.rho = density; p->primitives.v[0] = p->v[0]; p->primitives.v[1] = p->v[1]; @@ -208,7 +213,7 @@ void reset_particles(struct cell *c, enum velocity_field vel, p->conserved.momentum[1] = p->conserved.mass * p->v[1]; p->conserved.momentum[2] = p->conserved.mass * p->v[2]; p->conserved.energy = - p->primitives.P / hydro_gamma_minus_one * p->geometry.volume + + p->primitives.P / hydro_gamma_minus_one * volume + 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]) / @@ -260,7 +265,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h, part->x[2] = offset[2] + size * (z + 0.5) / (float)n; part->h = size * h / (float)n; -#ifdef GIZMO_SPH +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) part->conserved.mass = density * volume / count; #else part->mass = density * volume / count; @@ -284,7 +289,7 @@ struct cell *make_cell(size_t n, const double offset[3], double size, double h, part->conserved.momentum[1] = part->conserved.mass * part->v[1]; part->conserved.momentum[2] = part->conserved.mass * part->v[2]; part->conserved.energy = - part->primitives.P / hydro_gamma_minus_one * part->geometry.volume + + part->primitives.P / hydro_gamma_minus_one * volume + 0.5f * (part->conserved.momentum[0] * part->conserved.momentum[0] + part->conserved.momentum[1] * part->conserved.momentum[1] + part->conserved.momentum[2] * part->conserved.momentum[2]) / @@ -363,7 +368,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell, main_cell->parts[pid].v[0], main_cell->parts[pid].v[1], main_cell->parts[pid].v[2], main_cell->parts[pid].h, hydro_get_density(&main_cell->parts[pid]), -#ifdef MINIMAL_SPH +#if defined(MINIMAL_SPH) || defined(SHADOWFAX_SPH) 0.f, #else main_cell->parts[pid].density.div_v, diff --git a/tests/test27cells.c b/tests/test27cells.c index 9a8eb1c561a9c68dc6e53b219dc442c0aa1259e1..9e79c097462203465c03ea056569c7f448125d7e 100644 --- a/tests/test27cells.c +++ b/tests/test27cells.c @@ -124,8 +124,15 @@ struct cell *make_cell(size_t n, double *offset, double size, double h, part->h = size * h / (float)n; part->id = ++(*partId); -#ifdef GIZMO_SPH +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) part->conserved.mass = density * volume / count; + +#ifdef SHADOWFAX_SPH + double anchor[3] = {0., 0., 0.}; + double side[3] = {1., 1., 1.}; + voronoi_cell_init(&part->cell, part->x, anchor, side); +#endif + #else part->mass = density * volume / count; #endif @@ -183,7 +190,7 @@ void clean_up(struct cell *ci) { */ void zero_particle_fields(struct cell *c) { for (int pid = 0; pid < c->count; pid++) { - hydro_init_part(&c->parts[pid]); + hydro_init_part(&c->parts[pid], NULL); } } @@ -222,7 +229,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell, main_cell->parts[pid].v[0], main_cell->parts[pid].v[1], main_cell->parts[pid].v[2], hydro_get_density(&main_cell->parts[pid]), -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0.f, #else main_cell->parts[pid].density.rho_dh, @@ -259,7 +266,7 @@ void dump_particle_fields(char *fileName, struct cell *main_cell, cj->parts[pjd].id, cj->parts[pjd].x[0], cj->parts[pjd].x[1], cj->parts[pjd].x[2], cj->parts[pjd].v[0], cj->parts[pjd].v[1], cj->parts[pjd].v[2], hydro_get_density(&cj->parts[pjd]), -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0.f, #else main_cell->parts[pjd].density.rho_dh, diff --git a/tests/testKernelGrav.c b/tests/testKernelGrav.c index 41e085945693efaf658c219d0e5f992ddf023d74..b4a5e4d9f1ff05d8f34840dd19b2a2ccb9ec79b5 100644 --- a/tests/testKernelGrav.c +++ b/tests/testKernelGrav.c @@ -31,26 +31,31 @@ /** * @brief The Gadget-2 gravity kernel function * + * Taken from Gadget-2.0.7's forcetree.c lines 2755-2800 + * * @param r The distance between particles - * @param h The cut-off distance of the kernel + * @param epsilon The cut-off distance of the kernel */ -float gadget(float r, float h) { - float fac; - const float r2 = r * r; - if (r >= h) - fac = 1.0f / (r2 * r); - else { - const float h_inv = 1. / h; - const float h_inv3 = h_inv * h_inv * h_inv; - const float u = r * h_inv; +float gadget(float r, float epsilon) { + + const float h = epsilon; + const float h_inv = 1.f / h; + + const float u = r * h_inv; + + if (u >= 1) { + const float r_inv = 1. / r; + + return r_inv * r_inv * r_inv; + } else { if (u < 0.5) - fac = h_inv3 * (10.666666666667 + u * u * (32.0 * u - 38.4)); + return h_inv * h_inv * h_inv * + (10.666666666667 + u * u * (32.0 * u - 38.4)); else - fac = - h_inv3 * (21.333333333333 - 48.0 * u + 38.4 * u * u - - 10.666666666667 * u * u * u - 0.066666666667 / (u * u * u)); + return h_inv * h_inv * h_inv * + (21.333333333333 - 48.0 * u + 38.4 * u * u - + 10.666666666667 * u * u * u - 0.066666666667 / (u * u * u)); } - return fac; } int main() { @@ -61,20 +66,22 @@ int main() { for (int k = 1; k < numPoints; ++k) { const float r = (r_max * k) / numPoints; - - const float u = r / h; - const float gadget_w = gadget(r, h); + const float h_inv = 1.f / h; + const float h_inv3 = h_inv * h_inv * h_inv; + const float u = r * h_inv; + float swift_w; - if (u < 1.) { - kernel_grav_eval(u, &swift_w); - swift_w *= (1 / (h * h * h)); - } else { + if (r >= h) { swift_w = 1 / (r * r * r); + + } else { + kernel_grav_eval(u, &swift_w); + swift_w *= h_inv3; } - if (fabsf(gadget_w - swift_w) > 2e-7) { + if (fabsf(gadget_w - swift_w) > 1e-5 * fabsf(gadget_w)) { printf("%2d: r= %f h= %f u= %f Wg(r,h)= %f Ws(r,h)= %f\n", k, r, h, u, gadget_w, swift_w); @@ -87,28 +94,30 @@ int main() { printf("\nAll values are consistent\n"); /* Now test the long range function */ - const float a_smooth = 4.5f; + /* const float a_smooth = 4.5f; */ - for (int k = 1; k < numPoints; ++k) { + /* for (int k = 1; k < numPoints; ++k) { */ - const float r = (r_max * k) / numPoints; + /* const float r = (r_max * k) / numPoints; */ - const float u = r / a_smooth; + /* const float u = r / a_smooth; */ - float swift_w; - kernel_long_grav_eval(u, &swift_w); + /* float swift_w; */ + /* kernel_long_grav_eval(u, &swift_w); */ - float gadget_w = erfc(u / 2) + u * exp(-u * u / 4) / sqrt(M_PI); + /* float gadget_w = erfcf(u / 2) + u * expf(-u * u / 4) / sqrtf(M_PI); */ - if (fabsf(gadget_w - swift_w) > 2e-7) { + /* if (fabsf(gadget_w - swift_w) > 1e-4 * fabsf(gadget_w)) { */ - printf("%2d: r= %f r_lr= %f u= %f Ws(r)= %f Wg(r)= %f\n", k, r, a_smooth, - u, swift_w, gadget_w); + /* printf("%2d: r= %f r_lr= %f u= %f Ws(r)= %f Wg(r)= %f\n", k, r, + * a_smooth, */ + /* u, swift_w, gadget_w); */ - printf("Invalid value ! Gadget= %e, SWIFT= %e\n", gadget_w, swift_w); - return 1; - } - } + /* printf("Invalid value ! Gadget= %e, SWIFT= %e\n", gadget_w, swift_w); + */ + /* return 1; */ + /* } */ + /* } */ return 0; } diff --git a/tests/testPair.c b/tests/testPair.c index c734424bca58b7a8ce05bba2c3392ca5c600fa9e..c2533b63b902e3bdc7e7cae6fcbcf50c87dee4af 100644 --- a/tests/testPair.c +++ b/tests/testPair.c @@ -63,7 +63,7 @@ struct cell *make_cell(size_t n, double *offset, double size, double h, part->v[2] = random_uniform(-0.05, 0.05); part->h = size * h / (float)n; part->id = ++(*partId); -#ifdef GIZMO_SPH +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) part->conserved.mass = density * volume / count; #else part->mass = density * volume / count; @@ -116,7 +116,7 @@ void clean_up(struct cell *ci) { */ void zero_particle_fields(struct cell *c) { for (int pid = 0; pid < c->count; pid++) { - hydro_init_part(&c->parts[pid]); + hydro_init_part(&c->parts[pid], NULL); } } @@ -142,7 +142,7 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) { ci->parts[pid].id, ci->parts[pid].x[0], ci->parts[pid].x[1], ci->parts[pid].x[2], ci->parts[pid].v[0], ci->parts[pid].v[1], ci->parts[pid].v[2], hydro_get_density(&ci->parts[pid]), -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0.f, #else ci->parts[pid].density.rho_dh, @@ -166,7 +166,7 @@ void dump_particle_fields(char *fileName, struct cell *ci, struct cell *cj) { cj->parts[pjd].id, cj->parts[pjd].x[0], cj->parts[pjd].x[1], cj->parts[pjd].x[2], cj->parts[pjd].v[0], cj->parts[pjd].v[1], cj->parts[pjd].v[2], hydro_get_density(&cj->parts[pjd]), -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) 0.f, #else cj->parts[pjd].density.rho_dh, diff --git a/tests/testSymmetry.c b/tests/testSymmetry.c index 6469d314fb8b1438cc2c9737669c1a13a97bd803..73c5708a6add174b88f26cc716a716fa2ad81709 100644 --- a/tests/testSymmetry.c +++ b/tests/testSymmetry.c @@ -31,6 +31,13 @@ int main(int argc, char *argv[]) { /* Choke if need be */ feenableexcept(FE_DIVBYZERO | FE_INVALID | FE_OVERFLOW); +#if defined(SHADOWFAX_SPH) + /* Initialize the Voronoi simulation box */ + double box_anchor[3] = {-2.0f, -2.0f, -2.0f}; + double box_side[3] = {6.0f, 6.0f, 6.0f}; +/* voronoi_set_box(box_anchor, box_side);*/ +#endif + /* Create two random particles (don't do this at home !) */ struct part pi, pj; for (size_t i = 0; i < sizeof(struct part) / sizeof(float); ++i) { @@ -46,7 +53,7 @@ int main(int argc, char *argv[]) { pi.id = 1; pj.id = 2; -#if defined(GIZMO_SPH) +#if defined(GIZMO_SPH) || defined(SHADOWFAX_SPH) /* Give the primitive variables sensible values, since the Riemann solver does not like negative densities and pressures */ pi.primitives.rho = random_uniform(0.1f, 1.0f); @@ -93,6 +100,12 @@ int main(int argc, char *argv[]) { /* set time step to reasonable value */ pi.force.dt = 0.001; pj.force.dt = 0.001; + +#ifdef SHADOWFAX_SPH + voronoi_cell_init(&pi.cell, pi.x, box_anchor, box_side); + voronoi_cell_init(&pj.cell, pj.x, box_anchor, box_side); +#endif + #endif /* Make an xpart companion */ diff --git a/tests/testVoronoi1D.c b/tests/testVoronoi1D.c new file mode 100644 index 0000000000000000000000000000000000000000..d16a36d9449d7bfdb2c74408efad61b219b1d7e3 --- /dev/null +++ b/tests/testVoronoi1D.c @@ -0,0 +1,79 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (C) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "hydro/Shadowswift/voronoi1d_algorithm.h" + +int main() { + + double box_anchor[1] = {-0.5}; + double box_side[1] = {2.}; + + /* Create a Voronoi cell */ + double x[1] = {0.5f}; + struct voronoi_cell cell; + voronoi_cell_init(&cell, x, box_anchor, box_side); + + /* Interact with a left and right neighbour */ + float xL[1] = {0.5f}; + float xR[1] = {-0.5f}; + voronoi_cell_interact(&cell, xL, 1); + voronoi_cell_interact(&cell, xR, 2); + + /* Interact with some more neighbours to check if they are properly ignored */ + float x0[1] = {0.6f}; + float x1[1] = {-0.7f}; + voronoi_cell_interact(&cell, x0, 3); + voronoi_cell_interact(&cell, x1, 4); + + /* Finalize cell and check results */ + voronoi_cell_finalize(&cell); + + if (cell.volume != 0.5f) { + error("Wrong volume: %g!", cell.volume); + } + if (cell.centroid != 0.5f) { + error("Wrong centroid: %g!", cell.centroid); + } + if (cell.idL != 1) { + error("Wrong left neighbour: %llu!", cell.idL); + } + if (cell.idR != 2) { + error("Wrong right neighbour: %llu!", cell.idR); + } + + /* Check face method */ + float A; + float midpoint[3]; + A = voronoi_get_face(&cell, 1, midpoint); + if (A != 1.0f) { + error("Wrong surface area returned for left neighbour!"); + } + if (midpoint[0] != -0.25f) { + error("Wrong midpoint returned for left neighbour!"); + } + A = voronoi_get_face(&cell, 2, midpoint); + if (A != 1.0f) { + error("Wrong surface area returned for right neighbour!"); + } + if (midpoint[0] != 0.25f) { + error("Wrong midpoint returned for right neighbour!"); + } + + return 0; +} diff --git a/tests/testVoronoi2D.c b/tests/testVoronoi2D.c new file mode 100644 index 0000000000000000000000000000000000000000..7a35961952079494b0d1567db57abd29fa1df35b --- /dev/null +++ b/tests/testVoronoi2D.c @@ -0,0 +1,210 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (C) 2017 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include "hydro/Shadowswift/voronoi2d_algorithm.h" +#include "tools.h" + +/* Number of cells used to test the 2D interaction algorithm */ +#define TESTVORONOI2D_NUMCELL 100 + +int main() { + + /* initialize simulation box */ + double anchor[3] = {-0.5f, -0.5f, -0.5f}; + double side[3] = {2.0f, 2.0f, 2.0f}; + + /* test initialization and finalization algorithms */ + { + message("Testing initialization and finalization algorithm..."); + + struct voronoi_cell cell; + double x[3] = {0.5, 0.5, 0.5}; + + voronoi_cell_init(&cell, x, anchor, side); + + float maxradius = voronoi_cell_finalize(&cell); + + assert(maxradius == 2.0f * sqrtf(2.0f)); + + assert(cell.volume == 4.0f); + + assert(cell.centroid[0] == 0.5f); + assert(cell.centroid[1] == 0.5f); + + message("Done."); + } + + /* test interaction algorithm: normal case */ + { + message("Testing %i cell grid with random positions...", + TESTVORONOI2D_NUMCELL); + + /* create 100 cells with random positions in [0,1]x[0,1] */ + struct voronoi_cell cells[TESTVORONOI2D_NUMCELL]; + double x[2]; + float dx[2]; + int i, j; + float Atot; + struct voronoi_cell *cell_i, *cell_j; + + for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) { + x[0] = random_uniform(0., 1.); + x[1] = random_uniform(0., 1.); + voronoi_cell_init(&cells[i], x, anchor, side); +#ifdef VORONOI_VERBOSE + message("cell[%i]: %g %g", i, x[0], x[1]); +#endif + } + + /* interact the cells (with periodic boundaries) */ + for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) { + cell_i = &cells[i]; + for (j = 0; j < TESTVORONOI2D_NUMCELL; ++j) { + if (i != j) { + cell_j = &cells[j]; + dx[0] = cell_i->x[0] - cell_j->x[0]; + dx[1] = cell_i->x[1] - cell_j->x[1]; + /* periodic boundaries */ + if (dx[0] >= 0.5f) { + dx[0] -= 1.0f; + } + if (dx[0] < -0.5f) { + dx[0] += 1.0f; + } + if (dx[1] >= 0.5f) { + dx[1] -= 1.0f; + } + if (dx[1] < -0.5f) { + dx[1] += 1.0f; + } +#ifdef VORONOI_VERBOSE + message("Cell %i before:", i); + voronoi_print_cell(&cells[i]); + message("Interacting cell %i with cell %i (%g %g, %g %g", i, j, + cells[i].x[0], cells[i].x[1], cells[j].x[0], cells[j].x[1]); +#endif + voronoi_cell_interact(cell_i, dx, j); + } + } + } + + Atot = 0.0f; + /* print the cells to the stdout */ + for (i = 0; i < TESTVORONOI2D_NUMCELL; ++i) { + printf("Cell %i:\n", i); + voronoi_print_cell(&cells[i]); + voronoi_cell_finalize(&cells[i]); + Atot += cells[i].volume; + } + + /* Check the total surface area */ + assert(fabs(Atot - 1.0f) < 1.e-6); + + /* Check the neighbour relations for an arbitrary cell: cell 44 + We plotted the grid and manually found the correct neighbours and their + order. */ + assert(cells[44].nvert == 7); + assert(cells[44].ngbs[0] == 26); + assert(cells[44].ngbs[1] == 38); + assert(cells[44].ngbs[2] == 3); + assert(cells[44].ngbs[3] == 33); + assert(cells[44].ngbs[4] == 5); + assert(cells[44].ngbs[5] == 90); + assert(cells[44].ngbs[6] == 4); + + message("Done."); + } + + /* test interaction algorithm */ + { + message("Testing 100 cell grid with Cartesian mesh positions..."); + + struct voronoi_cell cells[100]; + double x[2]; + float dx[2]; + int i, j; + float Atot; + struct voronoi_cell *cell_i, *cell_j; + + for (i = 0; i < 10; ++i) { + for (j = 0; j < 10; ++j) { + x[0] = (i + 0.5f) * 0.1; + x[1] = (j + 0.5f) * 0.1; + voronoi_cell_init(&cells[10 * i + j], x, anchor, side); + } + } + + /* interact the cells (with periodic boundaries) */ + for (i = 0; i < 100; ++i) { + cell_i = &cells[i]; + for (j = 0; j < 100; ++j) { + if (i != j) { + cell_j = &cells[j]; + dx[0] = cell_i->x[0] - cell_j->x[0]; + dx[1] = cell_i->x[1] - cell_j->x[1]; + /* periodic boundaries */ + if (dx[0] >= 0.5f) { + dx[0] -= 1.0f; + } + if (dx[0] < -0.5f) { + dx[0] += 1.0f; + } + if (dx[1] >= 0.5f) { + dx[1] -= 1.0f; + } + if (dx[1] < -0.5f) { + dx[1] += 1.0f; + } +#ifdef VORONOI_VERBOSE + message("Cell %i before:", i); + voronoi_print_cell(&cells[i]); + message("Interacting cell %i with cell %i (%g %g, %g %g", i, j, + cells[i].x[0], cells[i].x[1], cells[j].x[0], cells[j].x[1]); +#endif + voronoi_cell_interact(cell_i, dx, j); + } + } + } + + Atot = 0.0f; + /* print the cells to the stdout */ + for (i = 0; i < 100; ++i) { + printf("Cell %i:\n", i); + voronoi_print_cell(&cells[i]); + voronoi_cell_finalize(&cells[i]); + Atot += cells[i].volume; + } + + /* Check the total surface area */ + assert(fabs(Atot - 1.0f) < 1.e-6); + + /* Check the neighbour relations for an arbitrary cell: cell 44 + We plotted the grid and manually found the correct neighbours and their + order. */ + assert(cells[44].nvert == 4); + assert(cells[44].ngbs[0] == 34); + assert(cells[44].ngbs[1] == 45); + assert(cells[44].ngbs[2] == 54); + assert(cells[44].ngbs[3] == 43); + + message("Done."); + } + + return 0; +} diff --git a/tests/testVoronoi3D.c b/tests/testVoronoi3D.c new file mode 100644 index 0000000000000000000000000000000000000000..b4f219a41368bb3ce4e8111ae44c43e7fa1f7441 --- /dev/null +++ b/tests/testVoronoi3D.c @@ -0,0 +1,1518 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (C) 2016 Bert Vandenbroucke (bert.vandenbroucke@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/>. + * + ******************************************************************************/ + +#include <stdlib.h> +#include "error.h" +#include "hydro/Shadowswift/voronoi3d_algorithm.h" +#include "part.h" +#include "tools.h" + +/* Number of random generators to use in the first grid build test */ +#define TESTVORONOI3D_NUMCELL_RANDOM 100 + +/* Number of cartesian generators to use (in one coordinate direction) for the + second grid build test. The total number of generators is the third power of + this number (so be careful with large numbers) */ +#define TESTVORONOI3D_NUMCELL_CARTESIAN_1D 5 + +/* Total number of generators in the second grid build test. Do not change this + value, but change the 1D value above instead. */ +#define TESTVORONOI3D_NUMCELL_CARTESIAN_3D \ + (TESTVORONOI3D_NUMCELL_CARTESIAN_1D * TESTVORONOI3D_NUMCELL_CARTESIAN_1D * \ + TESTVORONOI3D_NUMCELL_CARTESIAN_1D) + +/* Bottom front left corner and side lengths of the large box that contains all + particles and is used as initial cell at the start of the construction */ +#define VORONOI3D_BOX_ANCHOR_X 0.0f +#define VORONOI3D_BOX_ANCHOR_Y 0.0f +#define VORONOI3D_BOX_ANCHOR_Z 0.0f +#define VORONOI3D_BOX_SIDE_X 1.0f +#define VORONOI3D_BOX_SIDE_Y 1.0f +#define VORONOI3D_BOX_SIDE_Z 1.0f + +/** + * @brief Get the volume of the simulation box stored in the global variables. + * + * This method is only used for debugging purposes. + * + * @return Volume of the simulation box as it is stored in the global variables. + */ +float voronoi_get_box_volume() { + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y * VORONOI3D_BOX_SIDE_Z; +} + +/** + * @brief Get the centroid of the simulation box stored in the global variables. + * + * This method is only used for debugging purposes. + * + * @param box_centroid Array to store the resulting coordinates in. + */ +void voronoi_get_box_centroid(float *box_centroid) { + box_centroid[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X; + box_centroid[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y; + box_centroid[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z; +} + +/** + * @brief Get the surface area and coordinates of the face midpoint for the + * face of the simulation box with the given unique ID. + * + * This method is only used for debugging purposes. + * + * @param id Unique ID of one of the 6 faces of the simulation box. + * @param face_midpoint Array to store the coordinates of the requested + * midpoint in. + * @return Surface area of the face, or 0 if the given ID does not correspond to + * a known face of the simulation box. + */ +float voronoi_get_box_face(unsigned long long id, float *face_midpoint) { + + if (id == VORONOI3D_BOX_FRONT) { + face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X; + face_midpoint[1] = VORONOI3D_BOX_ANCHOR_Y; + face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Z; + } + if (id == VORONOI3D_BOX_BACK) { + face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X; + face_midpoint[1] = VORONOI3D_BOX_ANCHOR_Y + VORONOI3D_BOX_SIDE_Y; + face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Z; + } + + if (id == VORONOI3D_BOX_BOTTOM) { + face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X; + face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y; + face_midpoint[2] = VORONOI3D_BOX_ANCHOR_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y; + } + if (id == VORONOI3D_BOX_TOP) { + face_midpoint[0] = 0.5f * VORONOI3D_BOX_SIDE_X + VORONOI3D_BOX_ANCHOR_X; + face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y; + face_midpoint[2] = VORONOI3D_BOX_ANCHOR_Z + VORONOI3D_BOX_SIDE_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y; + } + + if (id == VORONOI3D_BOX_LEFT) { + face_midpoint[0] = VORONOI3D_BOX_ANCHOR_X; + face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y; + face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y; + } + if (id == VORONOI3D_BOX_RIGHT) { + face_midpoint[0] = VORONOI3D_BOX_ANCHOR_X + VORONOI3D_BOX_SIDE_X; + face_midpoint[1] = 0.5f * VORONOI3D_BOX_SIDE_Y + VORONOI3D_BOX_ANCHOR_Y; + face_midpoint[2] = 0.5f * VORONOI3D_BOX_SIDE_Z + VORONOI3D_BOX_ANCHOR_Z; + return VORONOI3D_BOX_SIDE_X * VORONOI3D_BOX_SIDE_Y; + } + + return 0.0f; +} + +/** + * @brief Check if voronoi_volume_tetrahedron() works + */ +void test_voronoi_volume_tetrahedron() { + float v1[3] = {0., 0., 0.}; + float v2[3] = {0., 0., 1.}; + float v3[3] = {0., 1., 0.}; + float v4[3] = {1., 0., 0.}; + + float V = voronoi_volume_tetrahedron(v1, v2, v3, v4); + assert(V == 1.0f / 6.0f); +} + +/** + * @brief Check if voronoi_centroid_tetrahedron() works + */ +void test_voronoi_centroid_tetrahedron() { + float v1[3] = {0., 0., 0.}; + float v2[3] = {0., 0., 1.}; + float v3[3] = {0., 1., 0.}; + float v4[3] = {1., 0., 0.}; + + float centroid[3]; + voronoi_centroid_tetrahedron(centroid, v1, v2, v3, v4); + assert(centroid[0] == 0.25f); + assert(centroid[1] == 0.25f); + assert(centroid[2] == 0.25f); +} + +/** + * @brief Check if voronoi_calculate_cell() works + */ +void test_calculate_cell() { + + double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y, + VORONOI3D_BOX_ANCHOR_Z}; + double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y, + VORONOI3D_BOX_SIDE_Z}; + + struct voronoi_cell cell; + + cell.x[0] = 0.5f; + cell.x[1] = 0.5f; + cell.x[2] = 0.5f; + + /* Initialize the cell to a large cube. */ + voronoi_initialize(&cell, box_anchor, box_side); + /* Calculate the volume and centroid of the large cube. */ + voronoi_calculate_cell(&cell); + /* Calculate the faces. */ + voronoi_calculate_faces(&cell); + + /* Update these values if you ever change to another large cube! */ + assert(cell.volume == voronoi_get_box_volume()); + float box_centroid[3]; + voronoi_get_box_centroid(box_centroid); + assert(cell.centroid[0] = box_centroid[0]); + assert(cell.centroid[1] = box_centroid[1]); + assert(cell.centroid[2] = box_centroid[2]); + + /* Check cell neighbours. */ + assert(cell.nface == 6); + assert(cell.ngbs[0] == VORONOI3D_BOX_FRONT); + assert(cell.ngbs[1] == VORONOI3D_BOX_LEFT); + assert(cell.ngbs[2] == VORONOI3D_BOX_BOTTOM); + assert(cell.ngbs[3] == VORONOI3D_BOX_TOP); + assert(cell.ngbs[4] == VORONOI3D_BOX_BACK); + assert(cell.ngbs[5] == VORONOI3D_BOX_RIGHT); + + /* Check cell faces */ + float face_midpoint[3], face_area; + face_area = voronoi_get_box_face(VORONOI3D_BOX_FRONT, face_midpoint); + assert(cell.face_areas[0] == face_area); + assert(cell.face_midpoints[0][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[0][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[0][2] == face_midpoint[2] - cell.x[2]); + + face_area = voronoi_get_box_face(VORONOI3D_BOX_LEFT, face_midpoint); + assert(cell.face_areas[1] == face_area); + assert(cell.face_midpoints[1][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[1][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[1][2] == face_midpoint[2] - cell.x[2]); + + face_area = voronoi_get_box_face(VORONOI3D_BOX_BOTTOM, face_midpoint); + assert(cell.face_areas[2] == face_area); + assert(cell.face_midpoints[2][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[2][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[2][2] == face_midpoint[2] - cell.x[2]); + + face_area = voronoi_get_box_face(VORONOI3D_BOX_TOP, face_midpoint); + assert(cell.face_areas[3] == face_area); + assert(cell.face_midpoints[3][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[3][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[3][2] == face_midpoint[2] - cell.x[2]); + + face_area = voronoi_get_box_face(VORONOI3D_BOX_BACK, face_midpoint); + assert(cell.face_areas[4] == face_area); + assert(cell.face_midpoints[4][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[4][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[4][2] == face_midpoint[2] - cell.x[2]); + + face_area = voronoi_get_box_face(VORONOI3D_BOX_RIGHT, face_midpoint); + assert(cell.face_areas[5] == face_area); + assert(cell.face_midpoints[5][0] == face_midpoint[0] - cell.x[0]); + assert(cell.face_midpoints[5][1] == face_midpoint[1] - cell.x[1]); + assert(cell.face_midpoints[5][2] == face_midpoint[2] - cell.x[2]); +} + +void test_paths() { + float u, l, q; + int up, us, uw, lp, ls, lw, qp, qs, qw; + float r2, dx[3]; + struct voronoi_cell cell; + + /* PATH 1.0 */ + // the first vertex is above the cutting plane and its first edge is below the + // plane + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -1.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.nvert = 2; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.edges[0] = 1; + cell.edgeindices[0] = 0; + cell.edges[3] = 0; + cell.edgeindices[3] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 0); + assert(us == 0); + assert(uw == 1); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 0); + assert(lw == -1); + assert(l == -0.75f); + } + + /* PATH 1.1 */ + // the first vertex is above the cutting plane and its second edge is below + // the plane + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[6] = 0; + cell.edgeindices[1] = 0; + cell.edgeindices[6] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 0); + assert(us == 1); + assert(uw == 1); + assert(u == 0.25f); + assert(lp == 2); + assert(ls == 0); + assert(lw == -1); + assert(l == -0.75f); + } + + /* PATH 1.2 */ + // the first vertex is above the cutting plane and its second and last edge + // is below the plane + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 2; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 2; + cell.offsets[2] = 5; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[6] = 0; + cell.edgeindices[1] = 0; + cell.edgeindices[5] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 0); + assert(us == 1); + assert(uw == 1); + assert(u == 0.25f); + assert(lp == 2); + assert(ls == 0); + assert(lw == -1); + assert(l == -0.75f); + } + + /* PATH 1.3 */ + // the first vertex is above the cutting plane and is the closest vertex to + // the plane. The code should crash. + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = 2.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.offsets[0] = 0; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[2] = 3; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == -1); + } + + /* PATH 1.4.0 */ + // first vertex is above the plane, second vertex is closer and third vertex + // lies below + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[6] = 1; + cell.edgeindices[0] = 2; + cell.edgeindices[3] = 0; + cell.edgeindices[6] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 0); + assert(u == 0.125f); + assert(lp == 2); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.1 */ + // first vertex is above the plane, second vertex is closer and fourth vertex + // is below + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = -1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.offsets[3] = 9; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[4] = 3; + cell.edges[5] = 0; + cell.edges[6] = 1; + cell.edges[9] = 1; + // this is the only difference between PATH 1.4.1 and PATH 1.4.2 + cell.edgeindices[0] = 3; + cell.edgeindices[3] = 0; + cell.edgeindices[4] = 0; + cell.edgeindices[9] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 1); + assert(u == 0.125f); + assert(lp == 3); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.2 */ + // first vertex is above the plane, second is closer, fourth is below + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = -1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.offsets[3] = 9; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[4] = 3; + cell.edges[5] = 0; + cell.edges[6] = 1; + cell.edges[9] = 1; + // this is the only difference between PATH 1.4.1 and PATH 1.4.2 + cell.edgeindices[0] = 2; + cell.edgeindices[3] = 1; + cell.edgeindices[4] = 0; + cell.edgeindices[9] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 1); + assert(u == 0.125f); + assert(lp == 3); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.3 */ + // first vertex is above the plane, second is closer, third is below + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + // this is the difference between PATH 1.4.0 and this path + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[6] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[6] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 1); + assert(u == 0.125f); + assert(lp == 2); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.4 */ + // first vertex is above the plane, second is closer, fourth is below + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = -1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 4; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 7; + cell.offsets[3] = 10; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[5] = 3; + cell.edges[7] = 1; + cell.edges[10] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[10] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 2); + assert(u == 0.125f); + assert(lp == 3); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.5 */ + // same as 1.4.4, but with an order 3 second vertex + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = -1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.offsets[3] = 9; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[5] = 3; + cell.edges[6] = 1; + cell.edges[9] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[9] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 2); + assert(u == 0.125f); + assert(lp == 3); + assert(ls == 0); + assert(l == -0.75f); + } + + /* PATH 1.4.6 */ + // first vertex is above the plane, second is closer and is the closest + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.75f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 2; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 5; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == -1); + } + + /* PATH 1.5 */ + // first vertex is above the plane, second vertex is too close to call + { + cell.vertices[0] = 1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.nvert = 2; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 2); + assert(up == 1); + } + + /* PATH 2.0 */ + // the first vertex is below the plane and its first edge is above the plane + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 1.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.nvert = 2; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.edges[0] = 1; + cell.edgeindices[0] = 0; + cell.edges[3] = 0; + cell.edgeindices[3] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 1); + assert(us == 0); + assert(uw == -1); + assert(u == 0.25f); + assert(lp == 0); + assert(ls == 0); + assert(qw == 1); + assert(l == -0.75f); + } + + /* PATH 2.1 */ + // the first vertex is below the plane and its second edge is above the plane + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[6] = 0; + cell.edgeindices[1] = 0; + cell.edgeindices[6] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 2); + assert(us == 0); + assert(uw == -1); + assert(u == 0.25f); + assert(lp == 0); + assert(ls == 1); + assert(qw == 1); + assert(l == -0.75f); + } + + /* PATH 2.2 */ + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 2; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 2; + cell.offsets[2] = 5; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[5] = 0; + cell.edgeindices[1] = 0; + cell.edgeindices[5] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 2); + assert(us == 0); + assert(uw == -1); + assert(u == 0.25f); + assert(lp == 0); + assert(ls == 1); + assert(qw == 1); + assert(l == -0.75f); + } + + /* PATH 2.3 */ + // the first vertex is below the plane and is the closest vertex to the plane + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -2.0f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = -2.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.offsets[0] = 0; + cell.edges[0] = 1; + cell.edges[1] = 2; + cell.edges[2] = 3; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 0); + } + + /* PATH 2.4.0 */ + // first vertex is below the plane, second is closer and third is above + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[5] = 0; + cell.edges[6] = 1; + cell.edgeindices[0] = 2; + cell.edgeindices[3] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[6] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 2); + assert(u == 0.25f); + assert(lp == 1); + assert(l == -0.5f); + } + + /* PATH 2.4.1 */ + // first vertex is below, second is closer and fourth is above + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = 1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 4; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 7; + cell.offsets[3] = 10; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[4] = 3; + cell.edges[6] = 0; + cell.edges[10] = 1; + cell.edgeindices[0] = 3; + cell.edgeindices[3] = 0; + cell.edgeindices[4] = 0; + cell.edgeindices[6] = 0; + cell.edgeindices[10] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 3); + assert(us == 0); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 1); + assert(l == -0.5f); + } + + /* PATH 2.4.2 */ + // first vertex is below, second is closer and fourth is above + // same as 2.4.1, but with order 3 second vertex + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = 1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.offsets[3] = 9; + cell.edges[0] = 1; + cell.edges[3] = 2; + cell.edges[4] = 3; + cell.edges[5] = 0; + cell.edges[9] = 1; + cell.edgeindices[0] = 3; + cell.edgeindices[3] = 0; + cell.edgeindices[4] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[9] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 3); + assert(us == 0); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 1); + assert(l == -0.5f); + } + + /* PATH 2.4.3 */ + // first vertex is below, second is closer, third is above + // first vertex is first edge of second + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = 1.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[6] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[4] = 0; + cell.edgeindices[6] = 1; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 2); + assert(us == 0); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 1); + assert(l == -0.5f); + } + + /* PATH 2.4.4 */ + // first vertex is below, second is closer, fourth is above + // first vertex is first edge of second + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = 1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 4; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 7; + cell.offsets[3] = 10; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[5] = 3; + cell.edges[10] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[10] = 2; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 3); + assert(us == 0); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 2); + assert(l == -0.5f); + } + + /* PATH 2.4.5 */ + // first vertex is below, second is closer, fourth is above + // first vertex is first edge of second + // second vertex is order 3 vertex (and not order 4 like 2.4.4) + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.vertices[9] = 1.0f; + cell.vertices[10] = 0.0f; + cell.vertices[11] = 0.0f; + cell.nvert = 4; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.orders[2] = 3; + cell.orders[3] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 6; + cell.offsets[3] = 9; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edges[5] = 3; + cell.edges[9] = 1; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[5] = 0; + cell.edgeindices[9] = 2; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 1); + assert(up == 3); + assert(us == 0); + assert(u == 0.25f); + assert(lp == 1); + assert(ls == 2); + assert(l == -0.5f); + } + + /* PATH 2.4.6 */ + // first vertex is below, second is closer and is closest + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = -0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.vertices[6] = -2.0f; + cell.vertices[7] = 0.0f; + cell.vertices[8] = 0.0f; + cell.nvert = 3; + cell.orders[0] = 3; + cell.orders[1] = 2; + cell.orders[2] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.offsets[2] = 5; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edges[4] = 2; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + cell.edgeindices[4] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 0); + } + + /* PATH 2.5 */ + // first vertex is below, second is too close to call + { + cell.vertices[0] = -1.0f; + cell.vertices[1] = 0.0f; + cell.vertices[2] = 0.0f; + cell.vertices[3] = 0.5f; + cell.vertices[4] = 0.0f; + cell.vertices[5] = 0.0f; + cell.nvert = 2; + cell.orders[0] = 3; + cell.orders[1] = 3; + cell.offsets[0] = 0; + cell.offsets[1] = 3; + cell.edges[0] = 1; + cell.edges[3] = 0; + cell.edgeindices[0] = 0; + cell.edgeindices[3] = 0; + dx[0] = 0.5f; + dx[1] = 0.0f; + dx[2] = 0.0f; + r2 = 0.25f; + int result = voronoi_intersect_find_closest_vertex( + &cell, dx, r2, &u, &up, &us, &uw, &l, &lp, &ls, &lw, &q, &qp, &qs, &qw); + assert(result == 2); + } +} + +#ifdef SHADOWFAX_SPH +void set_coordinates(struct part *p, double x, double y, double z, + unsigned int id) { + + double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y, + VORONOI3D_BOX_ANCHOR_Z}; + double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y, + VORONOI3D_BOX_SIDE_Z}; + + p->x[0] = x; + p->x[1] = y; + p->x[2] = z; + p->id = id; + voronoi_cell_init(&p->cell, p->x, box_anchor, box_side); +} +#endif + +void test_degeneracies() { +#ifdef SHADOWFAX_SPH + int idx = 0; + /* make a small cube */ + struct part particles[100]; + set_coordinates(&particles[idx], 0.1, 0.1, 0.1, idx); + idx++; + set_coordinates(&particles[idx], 0.2, 0.1, 0.1, idx); + idx++; + set_coordinates(&particles[idx], 0.1, 0.2, 0.1, idx); + idx++; + set_coordinates(&particles[idx], 0.1, 0.1, 0.2, idx); + idx++; + /* corner on cutting plane */ + set_coordinates(&particles[idx], 0.2, 0.2, 0.2, idx); + idx++; + /* edge on cutting plane */ + set_coordinates(&particles[idx], 0.2, 0.1, 0.2, idx); + idx++; + set_coordinates(&particles[idx], 0.2, 0.2, 0.1, idx); + idx++; + /* cutting plane is diagonal */ + set_coordinates(&particles[idx], 0.05, 0.1, 0.05, idx); + idx++; + /* order 4 vertex (found after an impressive display of analytical geometry + of which I'm rather proud) */ + float t = 0.5 / 0.0475; + set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.0075 * t + 0.1, + 0.1 - 0.0025 * t, idx); + idx++; + /* order 4 vertex with float edge */ + t = 0.35 / 0.06125; + set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.015 * t + 0.1, + 0.1 - 0.005 * t, idx); + idx++; + /* plane that was already encountered */ + t = 0.5 / 0.0475; + set_coordinates(&particles[idx], 0.0075 * t + 0.1, 0.0075 * t + 0.1, + 0.1 - 0.0025 * t, idx); + idx++; + /* no intersection (just to cover all code) */ + set_coordinates(&particles[idx], 0.3, 0.3, 0.3, idx); + idx++; + set_coordinates(&particles[idx], 0.3, 0.1, 0.3, idx); + idx++; + /* order 5 vertex */ + t = 0.04 / 0.0175; + set_coordinates(&particles[idx], 0.1 - 0.0075 * t, 0.1 + 0.00375 * t, + 0.1 + 0.00625 * t, idx); + idx++; + /* plane with order 5 vertex */ + set_coordinates(&particles[idx], 0.1, 0.2, 0.1, idx); + idx++; + /* edge with order 5 vertex that looses an edge */ + t = -0.1 / 0.095; + set_coordinates(&particles[idx], 0.1 - 0.015 * t, 0.1 + 0.015 * t, + 0.1 - 0.005 * t, idx); + idx++; + for (int i = 1; i < idx; i++) { + float dx[3]; + dx[0] = particles[0].x[0] - particles[i].x[0]; + dx[1] = particles[0].x[1] - particles[i].x[1]; + dx[2] = particles[0].x[2] - particles[i].x[2]; + voronoi_cell_interact(&particles[0].cell, dx, particles[i].id); + } +#endif +} + +int main() { + + /* Set the all enclosing simulation box dimensions */ + double box_anchor[3] = {VORONOI3D_BOX_ANCHOR_X, VORONOI3D_BOX_ANCHOR_Y, + VORONOI3D_BOX_ANCHOR_Z}; + double box_side[3] = {VORONOI3D_BOX_SIDE_X, VORONOI3D_BOX_SIDE_Y, + VORONOI3D_BOX_SIDE_Z}; + + /* Check basic Voronoi cell functions */ + test_voronoi_volume_tetrahedron(); + test_voronoi_centroid_tetrahedron(); + test_calculate_cell(); + + /* Test the different paths */ + test_paths(); + + /* Test the interaction and geometry algorithms */ + { + /* Create a Voronoi cell */ + double x[3] = {0.5f, 0.5f, 0.5f}; + struct voronoi_cell cell; + voronoi_cell_init(&cell, x, box_anchor, box_side); + + /* Interact with neighbours */ + float x0[3] = {0.5f, 0.0f, 0.0f}; + float x1[3] = {-0.5f, 0.0f, 0.0f}; + float x2[3] = {0.0f, 0.5f, 0.0f}; + float x3[3] = {0.0f, -0.5f, 0.0f}; + float x4[3] = {0.0f, 0.0f, 0.5f}; + float x5[3] = {0.0f, 0.0f, -0.5f}; + voronoi_cell_interact(&cell, x0, 1); + voronoi_cell_interact(&cell, x1, 2); + voronoi_cell_interact(&cell, x2, 3); + voronoi_cell_interact(&cell, x3, 4); + voronoi_cell_interact(&cell, x4, 5); + voronoi_cell_interact(&cell, x5, 6); + float expected_midpoints[6][3], expected_areas[6]; + expected_areas[0] = 0.25f; + expected_midpoints[0][0] = 0.25f; + expected_midpoints[0][1] = 0.5f; + expected_midpoints[0][2] = 0.5f; + expected_areas[1] = 0.25f; + expected_midpoints[1][0] = 0.75f; + expected_midpoints[1][1] = 0.5f; + expected_midpoints[1][2] = 0.5f; + expected_areas[2] = 0.25f; + expected_midpoints[2][0] = 0.5f; + expected_midpoints[2][1] = 0.25f; + expected_midpoints[2][2] = 0.5f; + expected_areas[3] = 0.25f; + expected_midpoints[3][0] = 0.5f; + expected_midpoints[3][1] = 0.75f; + expected_midpoints[3][2] = 0.5f; + expected_areas[4] = 0.25f; + expected_midpoints[4][0] = 0.5f; + expected_midpoints[4][1] = 0.5f; + expected_midpoints[4][2] = 0.25f; + expected_areas[5] = 0.25f; + expected_midpoints[5][0] = 0.5f; + expected_midpoints[5][1] = 0.5f; + expected_midpoints[5][2] = 0.75f; + + /* Interact with some more neighbours to check if they are properly + ignored */ + float xE0[3] = {0.6f, 0.0f, 0.1f}; + float xE1[3] = {-0.7f, 0.2f, 0.04f}; + voronoi_cell_interact(&cell, xE0, 7); + voronoi_cell_interact(&cell, xE1, 8); + + /* Finalize cell and check results */ + voronoi_cell_finalize(&cell); + + if (fabs(cell.volume - 0.125f) > 1.e-5) { + error("Wrong volume: %g!", cell.volume); + } + if (fabs(cell.centroid[0] - 0.5f) > 1.e-5f || + fabs(cell.centroid[1] - 0.5f) > 1.e-5f || + fabs(cell.centroid[2] - 0.5f) > 1.e-5f) { + error("Wrong centroid: %g %g %g!", cell.centroid[0], cell.centroid[1], + cell.centroid[2]); + } + + /* Check faces. */ + float A, midpoint[3]; + for (int i = 0; i < 6; ++i) { + A = voronoi_get_face(&cell, i + 1, midpoint); + if (A) { + if (fabs(A - expected_areas[i]) > 1.e-5) { + error("Wrong surface area: %g!", A); + } + if (fabs(midpoint[0] - expected_midpoints[i][0] + cell.x[0]) > 1.e-5 || + fabs(midpoint[1] - expected_midpoints[i][1] + cell.x[1]) > 1.e-5 || + fabs(midpoint[2] - expected_midpoints[i][2] + cell.x[2]) > 1.e-5) { + error("Wrong face midpoint: %g %g %g (should be %g %g %g)!", + midpoint[0], midpoint[1], midpoint[2], expected_midpoints[i][0], + expected_midpoints[i][1], expected_midpoints[i][2]); + } + } else { + error("Neighbour %i not found!", i); + } + } + } + + /* Test degenerate cases */ + test_degeneracies(); + + /* Construct a small random grid */ + { + message("Constructing a small random grid..."); + + int i, j; + double x[3]; + float dx[3]; + float Vtot; + struct voronoi_cell cells[TESTVORONOI3D_NUMCELL_RANDOM]; + struct voronoi_cell *cell_i, *cell_j; + + /* initialize cells with random generator locations */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) { + x[0] = random_uniform(0., 1.); + x[1] = random_uniform(0., 1.); + x[2] = random_uniform(0., 1.); + voronoi_cell_init(&cells[i], x, box_anchor, box_side); + } + + /* interact the cells */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) { + cell_i = &cells[i]; + for (j = 0; j < TESTVORONOI3D_NUMCELL_RANDOM; ++j) { + if (i != j) { + cell_j = &cells[j]; + dx[0] = cell_i->x[0] - cell_j->x[0]; + dx[1] = cell_i->x[1] - cell_j->x[1]; + dx[2] = cell_i->x[2] - cell_j->x[2]; + voronoi_cell_interact(cell_i, dx, j); + } + } + } + + Vtot = 0.0f; + /* print the cells to the stdout */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_RANDOM; ++i) { + /* voronoi_print_gnuplot_c(&cells[i]);*/ + voronoi_cell_finalize(&cells[i]); + Vtot += cells[i].volume; + } + + assert(fabs(Vtot - 1.0f) < 1.e-6); + + message("Done."); + } + + /* Construct a small Cartesian grid full of degeneracies */ + { + message("Constructing a Cartesian grid..."); + + int i, j, k; + double x[3]; + float dx[3]; + float Vtot; + struct voronoi_cell cells[TESTVORONOI3D_NUMCELL_CARTESIAN_3D]; + struct voronoi_cell *cell_i, *cell_j; + + /* initialize cells with Cartesian generator locations */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++i) { + for (j = 0; j < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++j) { + for (k = 0; k < TESTVORONOI3D_NUMCELL_CARTESIAN_1D; ++k) { + x[0] = (i + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D; + x[1] = (j + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D; + x[2] = (k + 0.5f) * 1.0 / TESTVORONOI3D_NUMCELL_CARTESIAN_1D; + voronoi_cell_init(&cells[TESTVORONOI3D_NUMCELL_CARTESIAN_1D * + TESTVORONOI3D_NUMCELL_CARTESIAN_1D * i + + TESTVORONOI3D_NUMCELL_CARTESIAN_1D * j + k], + x, box_anchor, box_side); + } + } + } + + /* interact the cells */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++i) { + cell_i = &cells[i]; + for (j = 0; j < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++j) { + if (i != j) { + cell_j = &cells[j]; + dx[0] = cell_i->x[0] - cell_j->x[0]; + dx[1] = cell_i->x[1] - cell_j->x[1]; + dx[2] = cell_i->x[2] - cell_j->x[2]; + voronoi_cell_interact(cell_i, dx, j); + } + } + } + + Vtot = 0.0f; + /* print the cells to the stdout */ + for (i = 0; i < TESTVORONOI3D_NUMCELL_CARTESIAN_3D; ++i) { + /* voronoi_print_gnuplot_c(&cells[i]);*/ + voronoi_cell_finalize(&cells[i]); + Vtot += cells[i].volume; + } + + message("Vtot: %g (Vtot-1.0f: %g)", Vtot, (Vtot - 1.0f)); + assert(fabs(Vtot - 1.0f) < 2.e-6); + + message("Done."); + } + + return 0; +} diff --git a/theory/Multipoles/multipoles.py b/theory/Multipoles/multipoles.py new file mode 100644 index 0000000000000000000000000000000000000000..06a886e70a7b48bf67d9b88ef28cb486f188a853 --- /dev/null +++ b/theory/Multipoles/multipoles.py @@ -0,0 +1,179 @@ +import numpy as np +import sys + +def factorial(x): + if x == 0: + return 1 + else: + return x * factorial(x-1) + +SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'} +def ordinal(num): + suffix = SUFFIXES.get(num % 10, 'th') + return str(num) + suffix + +# Get the order +order = int(sys.argv[1]) + +print "-------------------------------------------------" +print "Generating code for multipoles of order", order, "(only)." +print "-------------------------------------------------\n" + +print "-------------------------------------------------" +print "Multipole structure:" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d\n"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "float M_%d%d%d;"%(i,j,k) + +if order > 0: + print "#endif" + +print "" +print "-------------------------------------------------" + +print "Field tensor structure:" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d\n"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "float F_%d%d%d;"%(i,j,k) +if order > 0: + print "#endif" + +print "" +print "-------------------------------------------------" + +print "gravity_field_tensors_add():" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "la->F_%d%d%d += lb->F_%d%d%d;"%(i,j,k,i,j,k) +if order > 0: + print "#endif" + +print "" +print "-------------------------------------------------" + +print "gravity_multipole_add():" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "ma->M_%d%d%d += mb->M_%d%d%d;"%(i,j,k,i,j,k) +if order > 0: + print "#endif" + +print "" +print "-------------------------------------------------" + +print "gravity_P2M(): (loop)" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + if order % 2 == 0: + print "M_%d%d%d += m * X_%d%d%d(dx);"%(i,j,k,i,j,k) + else: + print "M_%d%d%d += -m * X_%d%d%d(dx);"%(i,j,k,i,j,k) +if order > 0: + print "#endif" + +print "" +print "-------------------------------------------------" + +print "gravity_P2M(): (storing)" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1) + +print "/* %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "m->m_pole.M_%d%d%d = M_%d%d%d;"%(i,j,k,i,j,k) +if order > 0: + print "#endif" + + +print "" +print "-------------------------------------------------" + +print "gravity_M2M():" +print "-------------------------------------------------\n" + +if order > 0: + print "#if SELF_GRAVITY_MULTIPOLE_ORDER > %d"%(order-1) + +print "/* Shift %s order terms */"%ordinal(order) + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + print "m_a->M_%d%d%d = m_b->M_%d%d%d"%(i,j,k,i,j,k), + + for ii in range(order+1): + for jj in range(order+1): + for kk in range(order+1): + + if not(ii == 0 and jj == 0 and kk == 0): + for iii in range(order+1): + for jjj in range(order+1): + for kkk in range(order+1): + if ii+iii == i and jj + jjj == j and kk+kkk == k: + print "+ X_%d%d%d(dx) * m_b->M_%d%d%d"%(ii, jj, kk, iii, jjj, kkk), + + + print ";" +if order > 0: + print "#endif" + + diff --git a/theory/Multipoles/vector_powers.py b/theory/Multipoles/vector_powers.py new file mode 100644 index 0000000000000000000000000000000000000000..4b1cbd4436f00ff5a1720badd1e8c22bd1762511 --- /dev/null +++ b/theory/Multipoles/vector_powers.py @@ -0,0 +1,55 @@ +import numpy as np +import sys + +def factorial(x): + if x == 0: + return 1 + else: + return x * factorial(x-1) + +SUFFIXES = {1: 'st', 2: 'nd', 3: 'rd'} +def ordinal(num): + suffix = SUFFIXES.get(num % 10, 'th') + return str(num) + suffix + +# Get the order +order = int(sys.argv[1]) + +print "-------------------------------------------------" +print "Generating code for vector powers of order", order, "(only)." +print "-------------------------------------------------\n" + +print "/***************************/" +print "/* %s order vector powers */"%ordinal(order) +print "/***************************/\n" + +# Create all the terms relevent for this order +for i in range(order+1): + for j in range(order+1): + for k in range(order+1): + if i + j + k == order: + fact = factorial(i) * factorial(j) * factorial(k) + print "/**" + print "* @brief \\f$ \\frac{1}{(%d,%d,%d)!}\\vec{v}^{(%d,%d,%d)} \\f$."%(i,j,k,i,j,k) + print "*" + print "* Note \\f$ \\frac{1}{(%d,%d,%d)!} = 1/(%d!*%d!*%d!) = 1/%d! = %e"%(i,j,k,i,j,k, fact, 1./fact) + print "*" + print "* @param v vector (\\f$ v \\f$)." + print "*/" + print "__attribute__((always_inline)) INLINE static double X_%d%d%d(const double v[3]) {"%(i,j,k) + print "" + print " return", + if fact != 1: + print "%12.15e"%(1./fact), + else: + print "1.", + for ii in range(i): + print "* v[0]", + for jj in range(j): + print "* v[1]", + for kk in range(k): + print "* v[2]", + print ";" + print "}" + +