diff --git a/.gitignore b/.gitignore index cc936154b298d48a7661070b35dc7d2f326d56ce..83f22370c4ec8421a9b012a49416569e7bd7a58f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ *~ +*.hdf5 Makefile Makefile.in @@ -23,14 +24,12 @@ doc/Doxyfile examples/swift examples/swift_mpi examples/*/*.xmf -examples/*/*.hdf5 examples/*/*.h5 examples/*/*.png examples/*/*.txt examples/*/*.dot examples/*/used_parameters.yml examples/*/*/*.xmf -examples/*/*/*.hdf5 examples/*/*/*.png examples/*/*/*.txt examples/*/*/*.dot @@ -73,7 +72,6 @@ tests/test_nonsym_force_1_vec.dat tests/test_nonsym_force_2_vec.dat tests/testGreetings tests/testReading -tests/input.hdf5 tests/testSingle tests/testTimeIntegration tests/testSPHStep @@ -107,6 +105,8 @@ tests/testDump tests/testLogger tests/benchmarkInteractions tests/testGravityDerivatives +tests/testPotentialSelf +tests/testPotentialPair theory/latex/swift.pdf theory/SPH/Kernels/kernels.pdf @@ -277,9 +277,14 @@ _minted* *.sympy sympy-plots-for-*.tex/ +# python +*.pyc + # todonotes *.tdo # xindy *.xdy +# macOS +*.DS_Store \ No newline at end of file diff --git a/configure.ac b/configure.ac index 3826441730116b6d54f0c5da5416fa7d5b596e3d..8cdce3e0c72842abdf075213e4a0e6610ac1f441 100644 --- a/configure.ac +++ b/configure.ac @@ -16,7 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # Init the project. -AC_INIT([SWIFT],[0.6.0],[https://gitlab.cosma.dur.ac.uk/swift/swiftsim]) +AC_INIT([SWIFT],[0.7.0],[https://gitlab.cosma.dur.ac.uk/swift/swiftsim]) swift_config_flags="$*" # Need to define this, instead of using fifth argument of AC_INIT, until 2.64. @@ -407,13 +407,47 @@ AC_HEADER_STDC # Check for the libraries we will need. AC_CHECK_LIB(m,sqrt,,AC_MSG_ERROR(something is wrong with the math library!)) -# Check for GSL +# Check for GSL. We test for this in the standard directories by default, +# and only disable if using --with-gsl=no or --without-gsl. When a value +# is given GSL must be found. have_gsl="no" -AC_CHECK_LIB([gslcblas], [cblas_dgemm]) -AC_CHECK_LIB([gsl], [gsl_integration_qag]) -if test "x$ac_cv_lib_gslcblas_cblas_dgemm" != "x"; then - have_gsl="yes" +AC_ARG_WITH([gsl], + [AS_HELP_STRING([--with-gsl=PATH], + [root directory where GSL is installed @<:@yes/no@:>@] + )], + [with_gsl="$withval"], + [with_gsl="test"] +) +if test "x$with_gsl" != "xno"; then + if test "x$with_gsl" != "xyes" -a "x$with_gsl" != "xtest" -a "x$with_gsl" != "x"; then + GSL_LIBS="-L$with_gsl/lib -lgsl -lgslcblas" + GSL_INCS="-I$with_gsl/include" + else + GSL_LIBS="-lgsl -lgslcblas" + GSL_INCS="" + fi + # GSL is not specified, so just check if we have it. + if test "x$with_gsl" = "xtest"; then + AC_CHECK_LIB([gslcblas],[cblas_dgemm],[have_gsl="yes"],[have_gsl="no"],$GSL_LIBS) + if test "x$have_gsl" != "xno"; then + AC_DEFINE([HAVE_LIBGSLCBLAS],1,[The GSL CBLAS library appears to be present.]) + AC_CHECK_LIB([gsl],[gsl_integration_qag], + AC_DEFINE([HAVE_LIBGSL],1,[The GSL library appears to be present.]), + [have_gsl="no"],$GSL_LIBS) + fi + else + AC_CHECK_LIB([gslcblas],[cblas_dgemm], + AC_DEFINE([HAVE_LIBGSLCBLAS],1,[The GSL CBLAS library appears to be present.]), + AC_MSG_ERROR(something is wrong with the GSL CBLAS library!), $GSL_LIBS) + AC_CHECK_LIB([gsl],[gsl_integration_qag], + AC_DEFINE([HAVE_LIBGSL],1,[The GSL library appears to be present.]), + AC_MSG_ERROR(something is wrong with the GSL library!), $GSL_LIBS) + have_gsl="yes" + fi fi +AC_SUBST([GSL_LIBS]) +AC_SUBST([GSL_INCS]) +AM_CONDITIONAL([HAVEGSL],[test -n "$GSL_LIBS"]) # Check for pthreads. AX_PTHREAD([LIBS="$PTHREAD_LIBS $LIBS" CFLAGS="$CFLAGS $PTHREAD_CFLAGS" @@ -443,7 +477,7 @@ AC_ARG_WITH([parmetis], [AS_HELP_STRING([--with-parmetis=PATH], [root directory where ParMETIS is installed @<:@yes/no@:>@] )], - [], + [with_parmetis="$withval"], [with_parmetis="no"] ) @@ -506,13 +540,13 @@ AH_VERBATIM([__STDC_FORMAT_MACROS], #define __STDC_FORMAT_MACROS 1 #endif]) -# Check for grackle. +# Check for grackle. have_grackle="no" AC_ARG_WITH([grackle], [AS_HELP_STRING([--with-grackle=PATH], [root directory where grackle is installed @<:@yes/no@:>@] )], - [], + [with_grackle="$withval"], [with_grackle="no"] ) if test "x$with_grackle" != "xno"; then @@ -1013,7 +1047,7 @@ esac # External potential AC_ARG_WITH([ext-potential], [AS_HELP_STRING([--with-ext-potential=<pot>], - [external potential @<:@none, point-mass, isothermal, softened-isothermal, disc-patch, sine-wave default: none@:>@] + [external potential @<:@none, point-mass, point-mass-ring, point-mass-softened, isothermal, softened-isothermal, disc-patch, sine-wave, default: none@:>@] )], [with_potential="$withval"], [with_potential="none"] @@ -1034,6 +1068,12 @@ case "$with_potential" in sine-wave) AC_DEFINE([EXTERNAL_POTENTIAL_SINE_WAVE], [1], [Sine wave external potential in 1D]) ;; + point-mass-ring) + AC_DEFINE([EXTERNAL_POTENTIAL_POINTMASS_RING], [1], [Point mass potential for Keplerian Ring (Hopkins 2015).]) + ;; + point-mass-softened) + AC_DEFINE([EXTERNAL_POTENTIAL_POINTMASS_SOFT], [1], [Softened point-mass potential with form 1/(r^2 + softening^2).]) + ;; *) AC_MSG_ERROR([Unknown external potential: $with_potential]) ;; diff --git a/examples/EAGLE_100/eagle_100.yml b/examples/EAGLE_100/eagle_100.yml index 4c3caa08f694ba9d40d9278dc7dcc1a339eed2a4..9cf1c9432ed6c79e4b91262cfdd30bad14833558 100644 --- a/examples/EAGLE_100/eagle_100.yml +++ b/examples/EAGLE_100/eagle_100.yml @@ -6,6 +6,15 @@ InternalUnitSystem: UnitCurrent_in_cgs: 1 # Amperes UnitTemp_in_cgs: 1 # Kelvin +# Cosmological parameters +Cosmology: + h: 0.6777 # Reduced Hubble constant + a_begin: 0.9090909 # Initial scale-factor of the simulation + a_end: 1.0 # Final scale factor of the simulation + Omega_m: 0.307 # Matter density parameter + Omega_lambda: 0.693 # Dark-energy density parameter + Omega_b: 0.0455 # Baryon density parameter + # Parameters governing the time integration TimeIntegration: time_begin: 0. # The starting time of the simulation (in internal units). @@ -19,7 +28,7 @@ Scheduler: # Parameters governing the snapshots Snapshots: basename: eagle # Common part of the name of output files - time_first: 0. # Time of the first output (in internal units) + time_first: 1. # Time of the first output (in internal units) delta_time: 1e-3 # Time difference between consecutive outputs (in internal units) # Parameters governing the conserved quantities statistics @@ -29,8 +38,8 @@ Statistics: # Parameters for the self-gravity scheme Gravity: eta: 0.025 # Constant dimensionless multiplier for time integration. - epsilon: 0.0001 # Softening length (in internal units). - theta: 0.7 # Opening angle (Multipole acceptance criterion) + epsilon: 0.001 # Softening length (in internal units). + theta: 0.85 # Opening angle (Multipole acceptance criterion) # Parameters for the hydrodynamics scheme SPH: diff --git a/examples/EAGLE_12/eagle_12.yml b/examples/EAGLE_12/eagle_12.yml index af55da76ff513548bee52e51993fa8b78cfdc9e3..d7aedf591a6988f89ad700f0a6729b3682bb6881 100644 --- a/examples/EAGLE_12/eagle_12.yml +++ b/examples/EAGLE_12/eagle_12.yml @@ -6,13 +6,6 @@ InternalUnitSystem: UnitCurrent_in_cgs: 1 # Amperes UnitTemp_in_cgs: 1 # Kelvin -# Parameters governing the time integration -TimeIntegration: - time_begin: 0. # The starting time of the simulation (in internal units). - time_end: 1e-2 # The end time of the simulation (in internal units). - 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). - # Cosmological parameters Cosmology: h: 0.6777 # Reduced Hubble constant @@ -21,6 +14,13 @@ Cosmology: Omega_m: 0.307 # Matter density parameter Omega_lambda: 0.693 # Dark-energy density parameter Omega_b: 0.0455 # Baryon density parameter + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 1e-2 # The end time of the simulation (in internal units). + 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: max_top_level_cells: 15 @@ -30,6 +30,7 @@ Snapshots: basename: eagle # Common part of the name of output files time_first: 1. # Time of the first output (in internal units) delta_time: 1e-3 # Time difference between consecutive outputs (in internal units) + compression: 4 # Parameters governing the conserved quantities statistics Statistics: diff --git a/examples/EAGLE_25/eagle_25.yml b/examples/EAGLE_25/eagle_25.yml index c01abfe94b98e8366ac05f1e3bce50a3931738c5..24daa13e06c2058d8a93243e9838b6f6b2cfe9bb 100644 --- a/examples/EAGLE_25/eagle_25.yml +++ b/examples/EAGLE_25/eagle_25.yml @@ -6,6 +6,15 @@ InternalUnitSystem: UnitCurrent_in_cgs: 1 # Amperes UnitTemp_in_cgs: 1 # Kelvin +# Cosmological parameters +Cosmology: + h: 0.6777 # Reduced Hubble constant + a_begin: 0.9090909 # Initial scale-factor of the simulation + a_end: 1.0 # Final scale factor of the simulation + Omega_m: 0.307 # Matter density parameter + Omega_lambda: 0.693 # Dark-energy density parameter + Omega_b: 0.0455 # Baryon density parameter + # Parameters governing the time integration TimeIntegration: time_begin: 0. # The starting time of the simulation (in internal units). @@ -19,8 +28,9 @@ Scheduler: # Parameters governing the snapshots Snapshots: basename: eagle # Common part of the name of output files - time_first: 0. # Time of the first output (in internal units) + time_first: 1. # Time of the first output (in internal units) delta_time: 1e-3 # Time difference between consecutive outputs (in internal units) + compression: 4 # Parameters governing the conserved quantities statistics Statistics: diff --git a/examples/EAGLE_50/eagle_50.yml b/examples/EAGLE_50/eagle_50.yml index 0fe1007414e7190ae9cc6f31aa5a16cec75290db..adf64f6963e2a0bc545637757f5826efd0c027a9 100644 --- a/examples/EAGLE_50/eagle_50.yml +++ b/examples/EAGLE_50/eagle_50.yml @@ -6,6 +6,15 @@ InternalUnitSystem: UnitCurrent_in_cgs: 1 # Amperes UnitTemp_in_cgs: 1 # Kelvin +# Cosmological parameters +Cosmology: + h: 0.6777 # Reduced Hubble constant + a_begin: 0.9090909 # Initial scale-factor of the simulation + a_end: 1.0 # Final scale factor of the simulation + Omega_m: 0.307 # Matter density parameter + Omega_lambda: 0.693 # Dark-energy density parameter + Omega_b: 0.0455 # Baryon density parameter + # Parameters governing the time integration TimeIntegration: time_begin: 0. # The starting time of the simulation (in internal units). @@ -19,7 +28,7 @@ Scheduler: # Parameters governing the snapshots Snapshots: basename: eagle # Common part of the name of output files - time_first: 0. # Time of the first output (in internal units) + time_first: 1. # Time of the first output (in internal units) delta_time: 1e-3 # Time difference between consecutive outputs (in internal units) # Parameters governing the conserved quantities statistics diff --git a/examples/EAGLE_6/eagle_6.yml b/examples/EAGLE_6/eagle_6.yml index ab33b79e80e69bcdac6a10f793930eca203cb26f..d0055607d3cd7c1453e3b93ed8b21cd5ff2673d5 100644 --- a/examples/EAGLE_6/eagle_6.yml +++ b/examples/EAGLE_6/eagle_6.yml @@ -6,6 +6,15 @@ InternalUnitSystem: UnitCurrent_in_cgs: 1 # Amperes UnitTemp_in_cgs: 1 # Kelvin +# Cosmological parameters +Cosmology: + h: 0.6777 # Reduced Hubble constant + a_begin: 0.9090909 # Initial scale-factor of the simulation + a_end: 1.0 # Final scale factor of the simulation + Omega_m: 0.307 # Matter density parameter + Omega_lambda: 0.693 # Dark-energy density parameter + Omega_b: 0.0455 # Baryon density parameter + Scheduler: max_top_level_cells: 8 @@ -15,12 +24,13 @@ TimeIntegration: time_end: 1e-2 # The end time of the simulation (in internal units). 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). - + # Parameters governing the snapshots Snapshots: basename: eagle # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 1e-3 # Time difference between consecutive outputs (in internal units) + compression: 4 # Parameters governing the conserved quantities statistics Statistics: diff --git a/examples/EvrardCollapse_3D/evrard.yml b/examples/EvrardCollapse_3D/evrard.yml index 006a22e65d3f674f124ce6c4994e752ba39cd1e1..94cd6e7a95d2671f7021ae3cd2ba40477cd8e7fd 100644 --- a/examples/EvrardCollapse_3D/evrard.yml +++ b/examples/EvrardCollapse_3D/evrard.yml @@ -18,7 +18,8 @@ Snapshots: basename: evrard # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.1 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/GreshoVortex_3D/gresho.yml b/examples/GreshoVortex_3D/gresho.yml index df941450196a7de6cd1471e1d258756ca8c36fb1..f42fcbfd00941e7b9c5c09c0d2e3118f5cc1f57d 100644 --- a/examples/GreshoVortex_3D/gresho.yml +++ b/examples/GreshoVortex_3D/gresho.yml @@ -21,7 +21,8 @@ Snapshots: basename: gresho # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 1e-1 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/KelvinHelmholtzGrowthRate_3D/kelvinHelmholtzGrowthRate.yml b/examples/KelvinHelmholtzGrowthRate_3D/kelvinHelmholtzGrowthRate.yml index 380dc2ab3a530e89b952aa41f425e50709d73ee9..3133e2769e81b80c18760e8258665fc1a6eee6ca 100644 --- a/examples/KelvinHelmholtzGrowthRate_3D/kelvinHelmholtzGrowthRate.yml +++ b/examples/KelvinHelmholtzGrowthRate_3D/kelvinHelmholtzGrowthRate.yml @@ -18,7 +18,8 @@ Snapshots: basename: kelvinHelmholtzGrowthRate # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.04 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/KeplerianRing/README.md b/examples/KeplerianRing/README.md new file mode 100644 index 0000000000000000000000000000000000000000..d486cbfe412c7ff9ca121c87bbc548d0ece078dd --- /dev/null +++ b/examples/KeplerianRing/README.md @@ -0,0 +1,130 @@ +Keplerian Ring Test +=================== + +This test uses the '2D Keplerian Ring' to assess the accuracy of SPH +calculations. For more information on the test, please see Cullen & Dehnen 2009 +(http://arxiv.org/abs/1006.1524) Section 4.3. + +It is key that for this test in particular that the initial conditions are +perfectly arranged (here we use the same methodology as described in the paper +referenced above). + +The test uses: + ++ $M = 10^10$ for a central point mass ++ $r$ up to 5 (particles created to edge of box when relevant) ++ Approximately 16000 particles ++ $c_s = 0.01 \ll v_\phi = 10$, enforced by giving them a mass of 1 unit and an + internal energy of 0.015. + +Please note that the initial condition generator **requires python3 rather than +python2**, as well as the plotting script. + + +Code Setup +---------- + +You will need to recompile SWIFT with an external potential. This can be done +by using + + ./configure --with-ext-potential=point-mass + +in the root directory of the project. We suggest leaving all other code options +as their defaults. + +Please also consider using: + + ./configure --with-ext-potential=point-mass-softened + +if you are running with the initial conditions generated with the script and +using a nonzero softening. + + +Initial Conditions Generation +----------------------------- + +The initial condition generation is handled by ```makeIC.py```, for which +the options are available with + + python3 makeIC.py --help + +however the defaults are very sensible. If you wish to change the central mass, +you will need to change the external point mass in ```keplerian_ring.yml``` as +well. + +There are three main types of generation, handled through `--generationmethod`: + ++ `spiral`: using the original spiral method with a density distribution + created using different mass particles. ++ `gaussian`: a guassian density profile with density distribution changed + through the _distribution_ of equal mass particles. ++ `grid`: a grid with a flat density profile (similar to Hopkins 2013) imposed + on it through different particle masses. + +All of them have their benefits and drawbacks, so give each a go. + +Plotting +-------- + +Once you have ran swift (we suggest that you use the following) + + ../swift -g -S -s -t 16 keplerian_ring.yml 2>&1 | tee output.log + +there will be around 350 ```.hdf5``` files in your directory. To check out +the results of the example use the plotting script: + + python3 plotSolution.py + +which will produce a png file in the directory. Check out `plotSolution.py +--help` for more options. + +You can also try the `make_movie.py` script which should make a movie. + + +Theory +------ + +We use the 'spiral' technique to generate the initial conditions. To do this, +we first calculate +$$ + m_i = \frac{i - \frac{1}{2}}{N} +$$ +for each particle $i$ with $N$ the total number of required particles. Then the +radius of each particle is given by +$$ + r_i = f^{-1}(m_i) +$$ +where for us the PDF $f$ is a Gaussian. + +To choose the radial positions, we then choose an initial angle $\theta_1$, and +from there +$$ + \delta \theta_i = \left[2\pi\left( 1 - \frac{r_{i-1}}{r_i} \right)\right]~. +$$ + +### Inverse CDF of Gaussian + +We need: +$$ + f^{-1}(m_i) = r_i = r_{central} + \sqrt{2}\sigma \mathrm{erf}^{-1}(2m_i - 1), +$$ +and thankfully scipy has an inverse error function built in. + + +Implementation Details +---------------------- + ++ The $m_i$ are generated by ```generate_m_i```. ++ The inverse Gaussian PDF is implemented as ```inverse_gaussian```. ++ The $\theta_i$ are generated by ```generate_theta_i```. + + +Useful Information +------------------ + +$G$ in simulation units (by default using this yaml) is given by: +$$ + 4.300970\times10^{-6} +$$ +This is used to convert the mass of the central potential (used in the +parameterfile) to the $GM$ required in the initial conditions generator. diff --git a/examples/KeplerianRing/keplerian_ring.yml b/examples/KeplerianRing/keplerian_ring.yml new file mode 100644 index 0000000000000000000000000000000000000000..421d1e89255195cd05a66975d838c59a4ad77c72 --- /dev/null +++ b/examples/KeplerianRing/keplerian_ring.yml @@ -0,0 +1,46 @@ +# Define the system of units to use internally. +InternalUnitSystem: + UnitMass_in_cgs: 1 # Grams + UnitLength_in_cgs: 1 # Centimeters + UnitVelocity_in_cgs: 1 # Centimeters per second + UnitCurrent_in_cgs: 1 # Amperes + UnitTemp_in_cgs: 1 # Kelvin + +# Parameters governing the time integration +TimeIntegration: + time_begin: 0. # The starting time of the simulation (in internal units). + time_end: 50 # The end time of the simulation (in internal units). + dt_min: 1e-6 # The minimal time-step size of the simulation (in internal units). + dt_max: 1e-3 # The maximal time-step size of the simulation (in internal units). + +# Parameters governing the snapshots +Snapshots: + basename: keplerian_ring # Common part of the name of output files + time_first: 0. # Time of the first output (in internal units) + delta_time: 0.2 # Time difference between consecutive outputs (in internal units) + +# Parameters governing the conserved quantities statistics +Statistics: + delta_time: 1e-2 # Time between statistics output + +# Parameters for the hydrodynamics scheme +SPH: + resolution_eta: 1.2348 # Target smoothing length in units of the mean inter-particle separation (1.2348 == 48Ngbs with the cubic spline kernel). + delta_neighbours: 0.1 # The tolerance for the targetted number of neighbours. + CFL_condition: 0.1 # Courant-Friedrich-Levy condition for time integration. + +# Parameters related to the initial conditions +InitialConditions: + file_name: initial_conditions.hdf5 # The file to read + shift_x: 0. # A shift to apply to all particles read from the ICs (in internal units). + shift_y: 0. + shift_z: 0. + +# External potential parameters +PointMassPotential: + position_x: 5. # location of external point mass in internal units + position_y: 5. # here just take this to be the centre of the ring + position_z: 5. + mass: 1.498351e7 # mass of external point mass in internal units + timestep_mult: 0.03 # controls time step + softening: 0.05 diff --git a/examples/KeplerianRing/makeIC.py b/examples/KeplerianRing/makeIC.py new file mode 100644 index 0000000000000000000000000000000000000000..e99b7f3b5176f0c7c857e7407634caa78ed6c431 --- /dev/null +++ b/examples/KeplerianRing/makeIC.py @@ -0,0 +1,740 @@ +""" +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2017 +# +# Josh Borrow (joshua.borrow@durham.ac.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################### +""" + + +import numpy as np +import write_gadget as wg +import h5py as h5 + +from scipy.special import erfinv + + +class Particles(object): + """ + Holder class for particle properties. Also contains some methods to e.g. + set their keplerian velocities based on their positions. These properties + are set using the 'generationmethod' functions below. + """ + def __init__(self, meta): + self.gravitymass = meta["gravitymass"] + self.nparts = meta["nparts"]**2 + self.particlemass = meta["particlemass"] + self.softening = meta["softening"] + self.boxsize = meta["boxsize"] + + self.smoothing = np.zeros(self.nparts) + meta["smoothing"] + self.internalenergy = np.zeros(self.nparts) + meta["internalenergy"] + + self.positions = np.array([]) + self.radii = np.array([]) + self.theta = np.array([]) + self.phi = np.array([]) + self.velocities = np.array([]) + self.ids = np.array([]) + self.densities = np.array([]) + self.masses = np.array([]) + + return + + + def calculate_masses(self): + """ + Calculate the individual masses for the particles such that we have + a uniform thickness disk, given their densities. + """ + mass_factor = self.particlemass / self.densities.max() + self.masses = self.densities * mass_factor + + return self.masses + + + def calculate_velocities(self, angle=0): + """ + Calculates keplerian orbit velocities for the, well, keplerian ring. + Requires that radius and phi are set already. + + Please give angle in radians. + """ + force_modifier = np.sqrt(self.gravitymass / (self.radii**2 + self.softening**2)**(3/2)) * self.radii + try: + v_x = np.sin(self.theta) * np.sin(self.phi) * np.cos(angle) + except ValueError: + # Phi are not yet set. Make them and then move on to v_x + if angle: + raise ValueError("Unable to find phi.") + + # If we have angle of inclination 0 we can set phi. + self.phi = np.zeros_like(self.theta) + np.pi / 2 + v_x = np.sin(self.theta) * np.sin(self.phi) * np.cos(angle) + + v_y = np.cos(self.phi) * np.sin(angle) - np.cos(self.theta) * np.sin(self.phi) * np.cos(angle) + v_z = np.sin(self.theta) * np.sin(self.phi) * np.sin(angle) + + self.velocities = (force_modifier * np.array([v_x, v_y, v_z])).T + + return self.velocities + + + def generate_ids(self): + """ + Generate consecutive IDs from 0 based on the number of particles + currently in the object. + """ + self.ids = np.arange(self.nparts) + + return self.ids + + + def convert_polar_to_cartesian(self, centre_of_ring=(5, 5), boxsize=None): + """ + Converts self.radii, self.theta to self.positions. + + If self.phi is set, it also uses that to conver to z. + """ + if len(self.phi) == 0: + x = self.radii * np.cos(self.theta) + centre_of_ring[0] + y = self.radii * np.sin(self.theta) + centre_of_ring[1] + z = np.zeros_like(x) + boxsize/2 + else: + x = self.radii * np.cos(self.theta) * np.sin(self.phi) + centre_of_ring[0] + y = self.radii * np.sin(self.theta) * np.sin(self.phi) + centre_of_ring[1] + try: + z = self.radii * np.cos(self.phi) + centre_of_ring[2] + except AttributeError: + # Just set the central z value to the middle of the box + z = self.radii * np.cos(self.phi) + boxsize/2 + + self.positions = np.array([x, y, z]).T + + return self.positions + + + def convert_cartesian_to_polar(self, centre_of_ring=(5, 5)): + """ + Converts self.positions to self.radii, self.theta, and self.phi. + """ + x, y, z = self.positions.T + + try: + x = x - centre_of_ring[0] + y = y - centre_of_ring[1] + z = z - centre_of_ring[2] + except AttributeError: + raise AttributeError("Invalid array for centre_of_ring provided.") + + xsquare = x*x + ysquare = y*y + zsquare = z*z + + r = np.sqrt(xsquare + ysquare + zsquare) + + # We need to mask over the x = 0 cases (theta = 0). + mask = np.isclose(x, 0, 1e-6) + + masked_x = np.ma.array(x, mask=mask) + theta_for_unmasked = np.arctan2(y, masked_x) + + theta = theta_for_unmasked + mask * 0 + + # Thankfully we have already ensured that r != 0. + phi = np.arccos(z/r) + + + self.radii = r + self.theta = theta + self.phi = phi + + return r, theta, phi + + + def wiggle_positions(self, tol=1e-3): + """ + 'Wiggle' the positions to avoid precision issues. + + Note that this does not touch r, theta, phi. + """ + self.positions += np.random.random(self.positions.shape) * tol + + return + + + def exclude_particles(self, range): + """ + Exclude all particles that are *not* within range (of radii). + """ + mask = np.logical_or( + self.radii < range[0], + self.radii > range[1], + ) + + x = np.ma.array(self.positions[:, 0], mask=mask).compressed() + y = np.ma.array(self.positions[:, 1], mask=mask).compressed() + z = np.ma.array(self.positions[:, 2], mask=mask).compressed() + + self.positions = np.array([x, y, z]).T + + try: + v_x = np.ma.array(self.velocities[:, 0], mask=mask).compressed() + v_y = np.ma.array(self.velocities[:, 1], mask=mask).compressed() + v_z = np.ma.array(self.velocities[:, 2], mask=mask).compressed() + + self.velocities = np.array([v_x, v_y, v_z]) + except IndexError: + # We haven't filled them yet anyway... + pass + + try: + self.ids = np.ma.array(self.ids, mask=mask).compressed() + except np.ma.core.MaskError: + # Not filled yet. + pass + + try: + self.densities = np.ma.array(self.densities, mask=mask).compressed() + except np.ma.core.MaskError: + # Not filled yet. + pass + + # QSP Fix has modified us, so first we need to chop off extras. + # Then, as they are all the same, we don't need to remove 'specific' + # values, and so we can just chop off the ends. + self.smoothing = self.smoothing[:len(x)] + self.internalenergy = self.internalenergy[:len(x)] + + try: + self.masses = np.ma.array(self.masses, mask=mask).compressed() + except np.ma.core.MaskError: + # Not filled yet. + pass + + self.radii = np.ma.array(self.radii, mask=mask).compressed() + self.theta = np.ma.array(self.theta, mask=mask).compressed() + + try: + self.phi = np.ma.array(self.phi, mask=mask).compressed() + except np.ma.core.MaskError: + # We have not allocated our phis, + pass + + self.nparts = len(self.radii) + + return + + + def tilt_particles(self, angle, center=(5, 5, 5)): + """ + Tilts the particles around the x-axis around the point given. + + Assumes that the particles already have set r, theta, phi. + """ + angle_radians = angle * np.pi / 180 + rotation_matrix = np.array( + [ + [ np.cos(angle_radians), 0, np.sin(angle_radians) ], + [ 0, 1, 0 ], + [ -np.sin(angle_radians), 0, np.cos(angle_radians) ] + ] + ) + + self.positions = ( + ( + np.matmul( + rotation_matrix, + (self.positions - np.array(center)).T + ) + ).T + ) + np.array(center) + + self.velocities = ( + ( + np.matmul( + rotation_matrix, + (self.velocities).T + ) + ).T + ) + + self.convert_cartesian_to_polar(center) + + return + + + def save_to_gadget(self, filename, boxsize=10.): + """ + Save the particle data to a GADGET .hdf5 file. + + Uses the internal options, but you must specify a filename. + """ + with h5.File(filename, "w") as handle: + wg.write_header( + handle, + boxsize=boxsize, + flag_entropy=0, + np_total=np.array([self.nparts, 0, 0, 0, 0, 0]), + np_total_hw=np.array([0, 0, 0, 0, 0, 0]), + other={ + "MassTable" : np.array([self.particlemass, 0, 0, 0, 0, 0]), + "Time" : 0, + } + ) + + wg.write_runtime_pars( + handle, + periodic_boundary=1, + ) + + wg.write_units( + handle, + current=1., + length=1., + mass=1, + temperature=1., + time=1., + ) + + wg.write_block( + handle, + 0, # gas + self.positions, + self.velocities, + self.ids, + mass=self.masses, + int_energy=self.internalenergy, + smoothing=self.smoothing, + other={"Density": self.densities}, + ) + + return + + +def __sigma(r): + """ + Density distribution of the ring, this comes directly from Hopkins 2015. + """ + if r < 0.5: + return 0.01 + (r/0.5)**3 + elif r <= 2: + return 1.01 + else: + return 0.01 + (1 + (r-2)/0.1)**(-3) + + +# This is required because of the if, else statement above that does not +# play nicely with our numpy arrays. +sigma = np.vectorize(__sigma) + + +def generate_theta(r, theta_initial=0.): + """ + Generate the theta associated with the particles based on their radii. + This uses the method from The effect of Poisson noise on SPH calculations, + Annabel Cartwright, Dimitris Stamatellos and Anthony P. Whitworth 2009. + + @param: r | float / array-like + - the radii of the particles. + + @param: theta_initial | float | optional + - initial radius to start the generation at. + + --------------------------------------------------------------------------- + + @return: theta_i | numpy.array + - angles associated with the particles in the plane. + """ + radii_fraction = r[:-1] / r[1:] + d_theta_i = np.sqrt(2 * np.pi * (1 - radii_fraction)) + + theta_i = np.empty_like(r) + theta_i[0] = theta_initial + + # Need to do this awful loop because each entry relies on the one before it. + # Unless someone knows how to do this in numpy. + for i in range(len(d_theta_i)): # first is theta_initial + theta_i[i+1] = theta_i[i] + d_theta_i[i] + + return theta_i + + +def QSP_fix(r_i, theta_i): + """ + The start and end of the disk will have the end of the spiral there. That + is no good and introduces some shear forces, so we need to move them to + concentric circles. We'll also add some extra particles on the final + 'layer' of this giant onion to ensure that all of the circles are complete. + + @param: r_i | numpy.array + - the original r_i generated from inverse_gaussian (and perhaps + afterwards masked). + + @param: theta_i | numpy.array + - the original theta_i generated from generate_theta_i. + + --------------------------------------------------------------------------- + + @return r_i_fixed | numpy.array + - the fixed, concentric circle-like r_i. Note that these arrays do not + necessarily have the same length as the r_i, theta_i that are + passed in and you will have to re-calculate the number of particles + in the system. + + @return theta_i_fixed | numpy.array + - the fixed, concentric circle-like theta_i + """ + + # Thankfully, the delta_thetas are not wrapped (i.e. they keep on going + # from 2 pi -- we need to split the arrays into 'circles' by splitting + # the theta_i array every 2 pi. + + rotations = 1 + circles = [] + + these_r_i = [] + these_theta_i = [] + + for radius, theta in zip(r_i, theta_i): + if theta > rotations * 2 * np.pi: + circles.append([these_r_i, these_theta_i]) + these_r_i = [] + these_theta_i = [] + rotations += 1 + + these_r_i.append(radius) + these_theta_i.append(theta) + + # Now we need to do the averaging. + # We want to have all particles in each circle a fixed radius away from the + # centre, as well as having even spacing between each particle. The final + # ring may be a bit dodgy still, but we will see. + + r_i_fixed = [] + theta_i_fixed = [] + + for circle in circles: + n_particles = len(circle[0]) + radius = sum(circle[0]) / n_particles + theta_initial = circle[1][0] + + theta_sep = 2 * np.pi / n_particles + + theta = [t * theta_sep for t in range(n_particles)] + radii = [radius] * n_particles + + r_i_fixed += radii + theta_i_fixed += theta + + return np.array(r_i_fixed), np.array(theta_i_fixed) + + +def gen_particles_grid(meta): + """ + Generates particles on a grid and returns a filled Particles object. + """ + particles = Particles(meta) + range = (0, meta["boxsize"]) + centre_of_ring = [meta["boxsize"]/2.] * 3 + + # Because we are using a uniform grid we actually use the same x and y + # range for the initial particle setup. + step = (range[1] - range[0])/meta["nparts"] + + x_values = np.arange(0, range[1] - range[0], step) + + # These are 2d arrays which isn't actually that helpful. + x, y = np.meshgrid(x_values, x_values) + x = x.flatten() + centre_of_ring[0] - (range[1] - range[0])/2 + y = y.flatten() + centre_of_ring[1] - (range[1] - range[0])/2 + z = np.zeros_like(x) + meta["boxsize"]/2 + + particles.positions = np.array([x, y, z]).T + particles.radii = np.sqrt((x - centre_of_ring[0])**2 + (y - centre_of_ring[1])**2) + particles.theta = np.arctan2(y - centre_of_ring[1], x - centre_of_ring[0]) + particles.exclude_particles((particles.softening, 100.)) + + particles.densities = sigma(particles.radii) + particles.calculate_velocities() + particles.calculate_masses() + + particles.generate_ids() + + if meta["angle"]: + particles.tilt_particles(meta["angle"], centre_of_ring) + + return particles + + +def gen_particles_spiral(meta): + """ + Generates particles on concentric circles and returns a filled Particles + object. Based on Cartwright, Stamatellos & Whitworth (2009). + """ + particles = Particles(meta) + centre_of_ring = (meta["boxsize"]/2., meta["boxsize"]/2., meta["boxsize"]/2.) + max_r = meta["boxsize"]/2. + + m = (np.arange(particles.nparts) + 0.5)/particles.nparts + r = max_r * m + theta = generate_theta(r) + + particles.radii, particles.theta = QSP_fix(r, theta) + # We must do this afterwards as QSP does not always return the same number of parts. + phi = np.zeros_like(particles.theta) + np.pi/2 + particles.phi = phi + + particles.convert_polar_to_cartesian(centre_of_ring, meta["boxsize"]) + particles.nparts = len(particles.radii) + + particles.exclude_particles((particles.softening, 100.)) + + # This way of doing densities does not work for different sized patches. + # Therefore we need to weight by effecitve area. + dtheta = (particles.theta[1:] - particles.theta[:-1]) / 2 + areas = np.square(4 * particles.radii[1:] * np.sin(dtheta)) + areas = np.hstack((np.array([1]), areas)) + + # Now do the 'actual' density calculation. + particles.densities = areas * sigma(particles.radii) + particles.calculate_velocities() + particles.calculate_masses() + + particles.generate_ids() + + if meta["angle"]: + particles.tilt_particles(meta["angle"], centre_of_ring) + + + return particles + + +def gen_particles_gaussian(meta): + """ + Generates particles on concentric circles and returns a filled Particles + object. Based on Cartwright, Stamatellos & Whitworth (2009). + + This generation function uses a Gaussian PDF. + """ + particles = Particles(meta) + centre_of_ring = (meta["boxsize"]/2., meta["boxsize"]/2., meta["boxsize"]/2.) + max_r = meta["boxsize"]/2. + + m = (np.arange(particles.nparts) + 0.5)/particles.nparts + error_function = erfinv(2*m - 1) + r = 2 + 0.5 * error_function + + theta = generate_theta(r) + + particles.radii, particles.theta = QSP_fix(r, theta) + # We must do this afterwards as QSP does not always return the same number of parts. + phi = np.zeros_like(particles.theta) + np.pi/2 + particles.phi = phi + + particles.convert_polar_to_cartesian(centre_of_ring, meta["boxsize"]) + particles.nparts = len(particles.radii) + + particles.exclude_particles((particles.softening, 100.)) + + # This way of doing densities does not work for different sized patches. + particles.densities = np.zeros_like(particles.radii) + particles.calculate_velocities() + particles.masses = np.ones_like(particles.radii) + + particles.generate_ids() + + if meta["angle"]: + particles.tilt_particles(meta["angle"], centre_of_ring) + + + return particles + + +if __name__ == "__main__": + import argparse as ap + + PARSER = ap.ArgumentParser( + description=""" + Initial conditions generator for the Keplerian Ring + example. It has sensible defaults for GIZMO, but if you + wish to run the example with SPH you sould use + --generationmethod spiral. + """ + ) + + PARSER.add_argument( + "-m", + "--gravitymass", + help=""" + GM for the central point mass. Default: 1. + """, + required=False, + default=1., + ) + + PARSER.add_argument( + "-f", + "--filename", + help=""" + Filename for your initial conditions. + Default: initial_conditions.hdf5. + """, + required=False, + default="initial_conditions.hdf5", + ) + + PARSER.add_argument( + "-n", + "--nparts", + help=""" + Square-root of the number of particles, i.e. the default + nparts=128 leads to a ring with 128^2 particles in it. + Note that for the spiral generation method this number is + somewhat approximate. + """, + required=False, + default=128, + ) + + PARSER.add_argument( + "-p", + "--particlemass", + help=""" + Maximum mass of the gas particles. Default: 10. + """, + required=False, + default=10., + ) + + PARSER.add_argument( + "-e", + "--softening", + help=""" + Gravitational softening for the centre of the ring. We also + exclude all particles within this radius from the centre of the + ring. Default: 0.05. + """, + required=False, + default=0.05, + ) + + PARSER.add_argument( + "-s", + "--smoothing", + help=""" + Initial smoothing length for all of the particles. + Default: Boxsize/N + """, + required=False, + default=-1, + ) + + PARSER.add_argument( + "-i", + "--internalenergy", + help=""" + Initial internal energy for all of the particles. Note that this + should be low to ensure that the ring is very cold (see Inviscid + SPH for details). Default: 0.015. + """, + required=False, + default=0.015 + ) + + PARSER.add_argument( + "-g", + "--generationmethod", + help=""" + Generation method for the particles. Choose between grid, spiral, + and gaussian, where spiral ensures that the particles are generated + in a way that minimises the energy in SPH. For more details on + this method see Cartwright, Stamatellos & Whitworth (2009). + Default: grid. + """, + required=False, + default="grid", + ) + + PARSER.add_argument( + "-b", + "--boxsize", + help=""" + The box size. Default: 10. + """, + required=False, + default=10. + ) + + PARSER.add_argument( + "-a", + "--angle", + help=""" + Angle of incline on the disk (degrees). Default: 0. + Note that tilting the ring may cause some issues at the + disk boundary as the 'box' is no longer filled with + particles. + """, + required=False, + default=0. + ) + + + ### --- ### --- Argument Parsing --- ### --- ### + + ARGS = vars(PARSER.parse_args()) + + if ARGS["generationmethod"] == "grid": + gen_particles = gen_particles_grid + elif ARGS["generationmethod"] == "spiral": + gen_particles = gen_particles_spiral + elif ARGS["generationmethod"] == "gaussian": + gen_particles = gen_particles_gaussian + else: + print( + "ERROR: {} is an invalid generation method. Exiting.".format( + ARGS["generationmethod"] + ) + ) + exit(1) + + + if ARGS["smoothing"] == -1: + smoothing = float(ARGS["boxsize"]) / int(ARGS["nparts"]) + else: + smoothing = float(ARGS["smoothing"]) + + + META = { + "gravitymass": float(ARGS["gravitymass"]), + "nparts": int(ARGS["nparts"]), + "particlemass": float(ARGS["particlemass"]), + "smoothing": smoothing, + "softening": float(ARGS["softening"]), + "internalenergy": float(ARGS["internalenergy"]), + "boxsize": float(ARGS["boxsize"]), + "angle" : float(ARGS["angle"]) + } + + PARTICLES = gen_particles(META) + + PARTICLES.save_to_gadget( + filename=ARGS["filename"], + boxsize=ARGS["boxsize"], + ) + diff --git a/examples/KeplerianRing/make_movie.py b/examples/KeplerianRing/make_movie.py new file mode 100644 index 0000000000000000000000000000000000000000..83e783924086377a0b2aa384d2c4634bfd40443f --- /dev/null +++ b/examples/KeplerianRing/make_movie.py @@ -0,0 +1,310 @@ +""" +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2017 +# +# Josh Borrow (joshua.borrow@durham.ac.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# ----------------------------------------------------------------------------- +# +# This program creates test plots for the initial condition generator provided +# for the Keplerian Ring example. +# +############################################################################### +""" + +import matplotlib +matplotlib.use("Agg") + + +import matplotlib.pyplot as plt +import matplotlib.animation as anim +import numpy as np +import h5py as h5 + +from tqdm import tqdm + + +def get_colours(numbers, norm=None, cm_name="viridis"): + if norm is None: + norm = matplotlib.colors.Normalize(vmax=numbers.max(), vmin=numbers.min()) + + cmap = matplotlib.cm.get_cmap(cm_name) + + return cmap(norm(numbers)), norm + + +def load_data(filename, silent=True, extra=None, logextra=True, exclude=None): + if not silent: + print(f"Loading data from {filename}") + + with h5.File(filename, "r") as file_handle: + coords = file_handle['PartType0']['Coordinates'][...] + time = float(file_handle['Header'].attrs['Time']) + boxsize = file_handle['Header'].attrs['BoxSize'] + + # For some old runs we have z=0 + if np.sum(coords[:, 2]) == 0: + centre_of_box = np.array(list(boxsize[:2]/2) + [0]) + else: + centre_of_box = boxsize/2 + + + if exclude is not None: + distance_from_centre = coords - centre_of_box + r2 = np.sum(distance_from_centre * distance_from_centre, 1) + mask = r2 < (exclude * exclude) + + masked = np.ma.array(coords, mask=np.array([mask]*3)) + + coords = masked.compressed() + + + if extra is not None: + other = file_handle['PartType0'][extra][...] + if exclude is not None: + masked = np.ma.array(other, mask=mask) + + other = masked.compressed() + + if logextra: + other = np.log(other) + + return coords, time, other + else: + return coords, time + + +def rms(x): + return np.sqrt(sum(x**2)) + + +def rotation_velocity_at_r(r, params): + """ + Gets the rotation velocity at a given radius r by assuming it is keplerian. + + Assumes we are in cgs units, which may one day be our downfall. + """ + + unit_length = float(params[r"InternalUnitSystem:UnitLength_in_cgs"]) + + if unit_length != 1.: + print(f"Your unit length: {unit_length}") + raise InternalUnitSystemError( + "This function is only created to handle CGS units." + ) + + central_mass = float(params["PointMassPotential:mass"]) + G = 6.674e-8 + + v = np.sqrt( G * central_mass / r) + + return v + + +def get_rotation_period_at_r(r, params): + """ + Gets the rotation period at a given radius r, assuming a keplerian + orbit. + """ + v = rotation_velocity_at_r(r, params) + + return 2*np.pi / v + + +def get_metadata(filename, r=1): + """ The metadata should be extracted from the first snapshot. """ + with h5.File(filename, "r") as file_handle: + header = file_handle['Header'].attrs + code = file_handle['Code'].attrs + hydro = file_handle['HydroScheme'].attrs + params = file_handle['Parameters'].attrs + + period = get_rotation_period_at_r(r, params) + + return_values = { + "header" : dict(header), + "code" : dict(code), + "period" : float(period), + "hydro" : dict(hydro), + "params" : dict(params) + } + + return return_values + + +def plot_single(number, scatter, text, metadata, ax, extra=None, norm=None): + filename = "keplerian_ring_{:04d}.hdf5".format(number) + + if extra is not None: + coordinates, time, other = load_data(filename, extra=extra) + else: + coordinates, time = load_data(filename) + + + text.set_text( + "Time: {:1.2f} | Rotations {:1.2f}".format( + time, + time/metadata['period'], + ) + ) + + data = coordinates[:, 0:2] + scatter.set_offsets(data) + + if extra is not None: + colours, _ = get_colours(other, norm) + scatter.set_color(colours) + + return scatter, + + +if __name__ == "__main__": + import os + import argparse as ap + + parser = ap.ArgumentParser( + description=""" + Plots a movie of the current snapshots in your + directory. Can also colourmap your information. + """ + ) + + parser.add_argument( + "-e", + "--exclude_central", + help=""" + Region from the centre of the ring to exclude from the + vmin/vmax of the colourmap. Note that these particles are + still plotted -- they are just excluded for the purposes + of colourmapping. Default: 1 simulation unit. + """, + default=1., + required=False + ) + + parser.add_argument( + "-c", + "--cmap", + help=""" + The item from the GADGET hdf5 file to clourmap with. + Examples include Density, InternalEnergy. + Default: don't use a colourmap. (Much faster). + """, + required=False, + default=None + ) + + parser.add_argument( + "-m", + "--max", + help=""" + Maximum radii to plot. + Default: 2.5. + """, + required=False, + default=2.5, + ) + + args = vars(parser.parse_args()) + + # Look for the number of files in the directory. + i = 0 + while True: + if os.path.isfile("keplerian_ring_{:04d}.hdf5".format(i)): + i += 1 + else: + break + + if i > 10000: + break + + + + # Now deal with the colourmapping (if present) + if args["cmap"] is not None: + _, _, numbers0 = load_data("keplerian_ring_0000.hdf5", extra=args["cmap"], exclude=float(args["exclude_central"])) + _, _, numbersend = load_data("keplerian_ring_{:04d}.hdf5".format(i-1), extra=args["cmap"], exclude=float(args["exclude_central"])) + vmax = max([numbers0.max(), numbersend.max()]) + vmin = min([numbers0.min(), numbersend.min()]) + + norm = matplotlib.colors.Normalize(vmin=vmin, vmax=vmax) + else: + norm = None + + + # Initial plot setup + + metadata = get_metadata("keplerian_ring_0000.hdf5") + n_particle = metadata['header']['NumPart_Total'][0] + + fig = plt.figure(figsize=(8, 8)) + ax = fig.add_subplot(111) + + scatter = ax.scatter([0]*n_particle, [0]*n_particle, s=0.5, marker="o") + diff = float(args["max"]) + left = metadata['header']['BoxSize'][0]/2 - diff + right = metadata['header']['BoxSize'][0]/2 + diff + ax.set_xlim(left, right) + ax.set_ylim(left, right) + + offset = 0.25 + time_text = ax.text( + offset + left, + offset + left, + "Time: {:1.2f} | Rotations {:1.2f}".format( + 0, + 0/metadata['period'], + ) + ) + + ax.text( + offset + left, + right-offset-0.35, + "Code: {} {} | {} {} \nHydro {}\n$\eta$={:1.4f}".format( + metadata['code']['Git Branch'].decode("utf-8"), + metadata['code']['Git Revision'].decode("utf-8"), + metadata['code']['Compiler Name'].decode("utf-8"), + metadata['code']['Compiler Version'].decode("utf-8"), + metadata['hydro']['Scheme'].decode("utf-8"), + metadata['hydro']['Kernel eta'][0], + ) + ) + + ax.set_title("Keplerian Ring Test") + ax.set_xlabel("$x$ position") + ax.set_ylabel("$y$ position") + + + anim = anim.FuncAnimation( + fig, + plot_single, + tqdm(np.arange(i)), + fargs = [ + scatter, + time_text, + metadata, + ax, + args["cmap"], + norm + ], + interval=50, + repeat_delay=3000, + blit=True, + ) + + anim.save("keplerian_ring.mp4", dpi=int(640/8)) diff --git a/examples/KeplerianRing/plotSolution.py b/examples/KeplerianRing/plotSolution.py new file mode 100644 index 0000000000000000000000000000000000000000..4d6411106f7f5ddd047148927a03fbb4800e0874 --- /dev/null +++ b/examples/KeplerianRing/plotSolution.py @@ -0,0 +1,627 @@ +""" +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2017 +# +# Josh Borrow (joshua.borrow@durham.ac.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +############################################################################### +""" + +# Plotting script for the Keplerian Ring example. +# We use yt for the projection rendering of the ring, +# and then our own density as a function of radius calculation. + +import matplotlib +matplotlib.use("Agg") +matplotlib.rc("text", usetex=True) + +try: + import yt + ytavail = True +except ImportError: + print("yt not found. Falling back on homebrew plots.") + ytavail = False + +import h5py +import os + +import matplotlib.pyplot as plt +import numpy as np +import matplotlib.gridspec as gridspec + +try: + from tqdm import tqdm +except ImportError: + tqdm = lambda x: x + +from make_movie import get_metadata + + +yt.funcs.mylog.setLevel(50) +tqdm.monitor_interval = 0 + + +class InternalUnitSystemError(Exception): + pass + + +def get_axes_grid(figure): + """ + Grab our axes grid. + """ + gs = gridspec.GridSpec(2, 30) + + grid = [] + + grid.append(figure.add_subplot(gs[0, 0:10])) + grid.append(figure.add_subplot(gs[0, 10:20])) + grid.append(figure.add_subplot(gs[0, 20:])) + grid.append(figure.add_subplot(gs[1, 0:9])) + grid.append(figure.add_subplot(gs[1, 11:20])) + grid.append(figure.add_subplot(gs[1, 21:])) + + return grid + + +def get_yt_actual_data(plot, name="density"): + """ + Extracts the image data and colourmap from a yt plot. + + This is used to put on our own grid. + """ + + data = plot.plots[name].image.get_array() + cmap = plot.plots[name].image.cmap + + return data, cmap + + +def chi_square(observed, expected): + """ + The chi squared statistic. + + This also looks for where expected == 0 and masks over those particles to + avoid divide by zero errors and unrealistically high chi squared. + """ + + mask = np.array(expected) != 0 + + masked_expected = np.array(expected)[mask] + masked_observed = np.array(observed)[mask] + + return sum(((masked_observed - masked_expected)**2)/masked_expected**2) + + +def load_data(filename): + """ + Loads the data and extracts the relevant information for + calculating the chi squared statistic and density(r) profiles. + """ + + with h5py.File(filename, "r") as file: + boxsize = np.array(file["Header"].attrs["BoxSize"]) + + # Check if z = 0 for all particles. If so we need to set the cetnre + # to have z = 0 also. + if np.sum(file["PartType0"]["Coordinates"][:, 2]) == 0: + centre = [boxsize[0] / 2., boxsize[0] / 2., 0.] + else: + centre = boxsize / 2. + + radii = np.sqrt(np.sum(((file["PartType0"]["Coordinates"][...] - centre).T)**2, 0)) + masses = file["PartType0"]["Masses"][...] + + return radii, masses + + +def bin_density_r(radii, density, binrange, binnumber): + """ + Bins the density as a funciton of radius. + """ + + bins = np.linspace(*binrange, binnumber) + indicies = np.digitize(radii, bins) + + binned_masses = np.zeros(len(bins) - 1) + + for index, bin in enumerate(indicies): + if bin >= len(bins) - 1: + continue + + binned_masses[bin] += density[index] + + areas = [np.pi * (a**2 - b**2) for a, b in zip(bins[1:], bins[:-1])] + binned_densities = binned_masses/areas + + return bins, binned_densities + + +def get_density_r(snapshot, filename="keplerian_ring", binrange=(0, 5), binnumber=50): + """ + Gets the binned density as a function of radius. + """ + snap = "{:04d}".format(snapshot) + filename = f"{filename}_{snap}.hdf5" + + data = load_data(filename) + + return bin_density_r(*data, binrange, binnumber) + + +def get_mass_outside_inside(snap, radius=2, filename="keplerian_ring"): + """ + Finds the mass outside and inside a radius. This is used to look at mass + flow inside and outside of the ring with a postprocessing routine in + get_derived_data. + """ + snapshot = "{:04d}".format(snap) + filename = f"{filename}_{snapshot}.hdf5" + + radii, masses = load_data(filename) + + below = sum(masses[radii < radius]) + above = sum(masses[radii > radius]) + + return below, above + + +def get_derived_data(minsnap, maxsnap, filename="keplerian_ring", binnumber=50, radius=2): + """ + Gets the derived data from our snapshots, i.e. the + density(r) profile and the chi squared (based on the + difference between the minsnap and the current snapshot). + """ + + initial = get_density_r(minsnap, filename, binnumber=binnumber) + other_densities = [ + get_density_r(snap, binnumber=binnumber)[1] for snap in tqdm( + range(minsnap+1, maxsnap+1), desc="Densities" + ) + ] + densities = [initial[1]] + other_densities + + masses_inside_outside = [ + get_mass_outside_inside(snap, radius=radius, filename=filename)[0] for snap in tqdm( + range(minsnap, maxsnap+1), desc="Mass Flow" + ) + ] + + # Between the initial conditions and the first snapshot we hope that there + # has been no mass flow, hence the [0.] + + mass_flows = [0.] + [ + y - x for x, y in zip( + masses_inside_outside[1:], + masses_inside_outside[:-1] + ) + ] + + cumulative_mass_flows = [ + sum(mass_flows[:x])/masses_inside_outside[0] for x in range(len(mass_flows)) + ] + + chisq = [ + chi_square( + dens[int(0.1*binnumber):int(0.4*binnumber)], + initial[1][int(0.1*binnumber):int(0.4*binnumber)] + ) for dens in tqdm(densities, desc="Chi Squared") + ] + + return initial[0], densities, chisq, cumulative_mass_flows + + +def plot_chisq(ax, minsnap, maxsnap, chisq, filename="keplerian_ring"): + """ + Plot the chisq(rotation). + """ + snapshots = np.arange(minsnap, maxsnap + 1) + rotations = [convert_snapshot_number_to_rotations_at(1, snap, filename) for snap in snapshots] + ax.plot(rotations, np.array(chisq)/max(chisq)) + + ax.set_xlabel("Number of rotations") + ax.set_ylabel("$\chi^2 / \chi^2_{{max}}$ = {:3.5f}".format(max(chisq))) + + return + + +def plot_mass_flow(ax, minsnap, maxsnap, mass_flow, filename="keplerian_ring"): + """ + Plot the mass_flow(rotation). + """ + snapshots = np.arange(minsnap, maxsnap + 1) + rotations = [convert_snapshot_number_to_rotations_at(1, snap, filename) for snap in snapshots] + + ax.plot(rotations, mass_flow) + + ax.set_xlabel("Number of rotations") + ax.set_ylabel(r"Mass flow out of ring ($M_{\rm ring}$)") + + return + + +def plot_density_r(ax, bins, densities, snaplist, filename="keplerian_ring"): + """ + Make the density(r) plots. + + Densities is the _full_ list of density profiles, and + snaplist is the ones that you wish to plot. + """ + radii = [(x + y)/2 for x, y in zip(bins[1:], bins[:-1])] + + for snap in snaplist: + index = snap - snaplist[0] + rotations = convert_snapshot_number_to_rotations_at(1, snap, filename) + ax.plot(radii, densities[index], label="{:2.2f} Rotations".format(rotations)) + + ax.legend() + ax.set_xlabel("Radius") + ax.set_ylabel("Azimuthally Averaged Surface Density") + + return + + +def plot_extra_info(ax, filename): + """ + Plots all of the extra information on the final axis. + + Give it a filename of any of the snapshots. + """ + + metadata = get_metadata(filename) + + git = metadata['code']['Git Revision'].decode("utf-8") + compiler_name = metadata['code']['Compiler Name'].decode("utf-8") + compiler_version = metadata['code']['Compiler Version'].decode("utf-8") + scheme = metadata['hydro']['Scheme'].decode("utf-8") + kernel = metadata['hydro']['Kernel function'].decode("utf-8") + gas_gamma = metadata["hydro"]["Adiabatic index"][0] + neighbors = metadata["hydro"]["Kernel target N_ngb"][0] + eta = metadata["hydro"]["Kernel eta"][0] + + + ax.text(-0.49, 0.9, "Keplerian Ring with $\\gamma={:4.4f}$ in 2/3D".format(gas_gamma), fontsize=11) + ax.text(-0.49, 0.8, f"Compiler: {compiler_name} {compiler_version}", fontsize=10) + ax.text(-0.49, 0.7, "Rotations are quoted at $r=1$", fontsize=10) + ax.plot([-0.49, 0.1], [0.62, 0.62], 'k-', lw=1) + ax.text(-0.49, 0.5, f"$\\textsc{{Swift}}$ {git}", fontsize=10) + ax.text(-0.49, 0.4, scheme, fontsize=10) + ax.text(-0.49, 0.3, kernel, fontsize=10) + ax.text(-0.49, 0.2, "${:2.2f}$ neighbours ($\\eta={:3.3f}$)".format(neighbors, eta), fontsize=10) + + ax.set_axis_off() + ax.set_xlim(-0.5, 0.5) + ax.set_ylim(0, 1) + + return + + +def surface_density_plot_no_yt(ax, snapnum, filename="keplerian_ring", density_limits=None, vlim=None): + """ + Make the surface density plot (sans yt). + + Also returns the max and minimum values for the density so these can + be passed to the next call, as well as vlim which are the colourmap + max/min. + """ + + with h5py.File("{}_{:04d}.hdf5".format(filename, snapnum)) as filehandle: + density = filehandle["PartType0"]["Density"][...] + x, y = filehandle["PartType0"]["Coordinates"][:, 0:2].T + + new_vlim = (density.min(), density.max()) + + if vlim is None: + vlim = new_vlim + + ax.scatter(x, y, c=density, vmin=vlim[0], vmax=vlim[1], s=0.1) + + + metadata = get_metadata("{}_{:04d}.hdf5".format(filename, snapnum)) + period = metadata["period"] + + t = ax.text( + 2.5, + 7.5, + "Snapshot = {:04d}\nRotations = {:1.2f}".format( + snapnum, + float(metadata["header"]["Time"])/period + ), + color='black' + ) + + t.set_bbox(dict(alpha=0.5, color="white")) + + ax.axis("equal") + + ax.set_xlim(2, 8) + ax.set_ylim(2, 8) + + # We now want to remove all of the ticklabels. + + for axis in ['x', 'y']: + ax.tick_params( + axis=axis, + which='both', + bottom='off', + top='off', + left='off', + right='off', + labelleft='off', + labelbottom='off' + ) + + return density_limits, vlim + + + +def surface_density_plot(ax, snapnum, filename="keplerian_ring", density_limits=None, vlim=None): + """ + Make the surface density plot (via yt). + + Also returns the max and minimum values for the density so these can + be passed to the next call, as well as vlim which are the colourmap + max/min. + """ + + unit_base = { + 'length': (1.0, 'cm'), + 'velocity': (1.0, 'cm/s'), + 'mass': (1.0, 'g') + } + + filename = "{}_{:04d}.hdf5".format(filename, snapnum) + + try: + snap = yt.load(filename, unit_base=unit_base, over_refine_factor=2) + except yt.utilities.exceptions.YTOutputNotIdentified: + # Probably the file isn't here because we supplied a too high snapshot + # number. Just return what we're given. + return density_limits, vlim + + projection_plot = yt.ProjectionPlot( + snap, + "z", + ("gas", "cell_mass"), + width=5.5 + ) + + max_density = snap.all_data()[("gas", "cell_mass")].max() + min_density = snap.all_data()[("gas", "cell_mass")].min() + + new_density_limits = (min_density, max_density) + + if density_limits is None: + density_limits = new_density_limits + + projection_plot.set_zlim("cell_mass", *density_limits) + + data = get_yt_actual_data(projection_plot, ("gas", "cell_mass")) + + # Becuase of the way plotting works, we also need a max/min for the colourmap. + + new_vlim = (data[0].min(), data[0].max()) + + if vlim is None: + vlim = new_vlim + + ax.imshow( + data[0], + cmap=data[1], + vmin=vlim[0], + vmax=vlim[1] + ) + + metadata = get_metadata(filename) + period = metadata["period"] + + ax.text( + 20, + 80, + "Snapshot = {:04d}\nRotations = {:1.2f}".format( + snapnum, + float(snap.current_time)/period + ), + color='white' + ) + + # We now want to remove all of the ticklabels. + + for axis in ['x', 'y']: + ax.tick_params( + axis=axis, + which='both', + bottom='off', + top='off', + left='off', + right='off', + labelleft='off', + labelbottom='off' + ) + + return density_limits, vlim + + +def convert_snapshot_number_to_rotations_at(r, snapnum, filename): + """ + Opens the file and extracts metadata to find the number of rotations. + """ + + metadata = get_metadata("{}_{:04d}.hdf5".format(filename, snapnum)) + + t = metadata["period"] + current_time = float(metadata["header"]["Time"]) + + return current_time / t + + +if __name__ == "__main__": + import argparse as ap + + parser = ap.ArgumentParser( + description=""" + Plotting code for the Keplerian Ring test. Uses yt to make + surface density plots. + """ + ) + + parser.add_argument( + "-b", + "--beginning", + help=""" + Initial snapshot to start analysis at. + Default: First snapshot in the folder. + """, + default=-1, + required=False + ) + + parser.add_argument( + "-m", + "--middle", + help=""" + Middle snapshot in the top row of three surface density plots. + Default: (end - beginning) // 2. + """, + default=-1, + required=False + ) + + parser.add_argument( + "-e", + "--end", + help=""" + Snapshot to end the analysis at. + Default: Last snapshot in the folder. + """, + default=-1, + required=False + ) + + parser.add_argument( + "-f", + "--filename", + help=""" + Filename of the output. + Default: plot.png + """, + default="plot.png", + required=False + ) + + parser.add_argument( + "-n", + "--nbins", + help=""" + Number of bins in histogramming (used for the density(r) plots). + Default: 100. + """, + default=100, + required=False + ) + + parser.add_argument( + "-p", + "--plotmassflow", + help=""" + Plot the mass flow instead of several density(r) plots. + Set this to a nonzero number to do this plot. + Default: 0. + """, + default=0, + required=False + ) + + parser.add_argument( + "-s", + "--slug", + help=""" + The first part of the output filename. For example, for snapshots + with the naming scheme keplerian_ring_xxxx.hdf5, this would be the + default value of keplerian_ring. + """, + default="keplerian_ring", + required=False + ) + + parser.add_argument( + "-y", + "--yt", + help=""" + Use yt to do the plots at the top of the page. If set to anything + other than a 'truthy' value, we will use a homebrew plotting + setup. Default: False + """, + default=False, + required=False + ) + + args = vars(parser.parse_args()) + + filename = args["slug"] + + if args["beginning"] == -1: + # We look for the maximum number of snapshots. + numbers = [ + int(x[len(filename)+1:-5]) + for x in os.listdir() if + x[:len(filename)] == filename and x[-1] == "5" + ] + + snapshots = [min(numbers), (max(numbers) - min(numbers)) // 2, max(numbers)] + else: + snapshots = [args["beginning"], args["middle"], args["end"]] + + figure = plt.figure(figsize=(12, 10)) + axes = get_axes_grid(figure) + + density_limits = None + vlim = None + + for snap, ax in zip(snapshots, tqdm(axes[0:3], desc="Images")): + if args["yt"] and ytavail: + density_limits, vlim = surface_density_plot( + ax, + snap, + density_limits=density_limits, + vlim=vlim + ) + + figure.subplots_adjust(hspace=0, wspace=0) + else: + density_limits, vlim = surface_density_plot_no_yt( + ax, + snap, + density_limits=density_limits, + vlim=vlim + ) + + # Now we need to do the density(r) plot. + + # Derived data includes density profiles and chi squared + derived_data = get_derived_data(snapshots[0], snapshots[2], binnumber=int(args["nbins"])) + + if args["plotmassflow"]: + plot_mass_flow(axes[3], snapshots[0], snapshots[2], derived_data[3]) + else: + plot_density_r(axes[3], derived_data[0], derived_data[1], snapshots) + + plot_chisq(axes[4], snapshots[0], snapshots[2], derived_data[2]) + + plot_extra_info(axes[5], "keplerian_ring_0000.hdf5") + + figure.savefig(args["filename"], dpi=300) + + diff --git a/examples/KeplerianRing/run.sh b/examples/KeplerianRing/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..ee8f7b247f38b74a204f9caf2bd543b72c4f94fa --- /dev/null +++ b/examples/KeplerianRing/run.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Generate the initial conditions if they are not present. +if [ ! -e initial_conditions.hdf5 ] +then + echo "Generating initial conditions for the keplerian ring example..." + echo "Please consider choosing your own options before continuing..." + python3 makeIC.py +fi + +rm -rf keplerian_ring_*.hdf5 +../swift -g -s -t 1 -v 1 keplerian_ring.yml 2>&1 | tee output.log diff --git a/examples/KeplerianRing/testplots.py b/examples/KeplerianRing/testplots.py new file mode 100644 index 0000000000000000000000000000000000000000..1de4964a5618636b73657eec229cbf6e83ef0ff9 --- /dev/null +++ b/examples/KeplerianRing/testplots.py @@ -0,0 +1,45 @@ +""" +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2017 +# +# Josh Borrow (joshua.borrow@durham.ac.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# +# ----------------------------------------------------------------------------- +# +# This program creates test plots for the initial condition generator provided +# for the Keplerian Ring example. +# +############################################################################### +""" + + +if __name__ == "__main__": + import yt + + data = yt.load("initial_conditions.hdf5") + + plot = yt.ParticlePlot( + data, + "particle_position_x", + "particle_position_y", + "density" + ) + + plot.save("test.png") + + diff --git a/examples/KeplerianRing/write_gadget.py b/examples/KeplerianRing/write_gadget.py new file mode 100644 index 0000000000000000000000000000000000000000..d2519cdaf4d78d9c7327419283072b8001e70fcb --- /dev/null +++ b/examples/KeplerianRing/write_gadget.py @@ -0,0 +1,309 @@ +""" +############################################################################### +# This file is part of SWIFT. +# Copyright (c) 2017 +# +# Josh Borrow (joshua.borrow@durham.ac.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# ----------------------------------------------------------------------------- +# +# This program is a set of helper functions that write particle data to a +# GADGET .hdf5 file *handle*. It should also function as a piece of +# documentation on the required attributes for SWIFT to function when running +# in GADGET compatibility mode. +# +# Example Usage: +# +# import write_gadget as wg +# import h5py as h5 +$ import numpy as np +# +# +# N_PARTICLES = 1000 +# +# +# with h5.File("test.hdf5", "w") as f: +# wg.write_header( +# f, +# boxsize=100., +# flag_entropy=1, +# np_total=[N_PARTICLES, 0, 0, 0, 0, 0], +# np_total_hw=[0, 0, 0, 0, 0, 0] +# ) +# +# wg.write_runtime_pars( +# f, +# periodic_boundary=1, +# ) +# +# wg.write_units( +# f, +# current=1., +# length=1., +# mass=1., +# temperature=1., +# time=1. +# ) +# +# wg.write_block( +# f, +# part_type=0, +# pos=np.array([np.arange(N_PARTICLES)] * 3).T, +# vel=np.array([np.zeros(N_PARTICLES)] * 3).T, +# ids=np.arange(N_PARTICLES), +# mass=np.ones(N_PARTICLES, +# int_energy=np.zeros(N_PARTICLES), +# smoothing=np.ones(NP_ARTICLES), +# +############################################################################### +""" + + +def write_header(f, boxsize, flag_entropy, np_total, np_total_hw, other=False): + """ Writes the "Header" section of the hdf5 file. The parameters in this + function that are required are the ones that are required for SWIFT + to function; note that the MassTable is **ignored** and that all + particle masses should be placed into the particle data arrays. + + @param: f | file handle + - the file handle of the hdf5 object (use h5py.File(filename, "w") + to open a file handle of the correct type). + + @param boxsize | float / list (2D / 3D) + - the boxsize. If a float is given it is assumed that the box is + the same size in all directions. + + @param flag_entropy | int (0/1) + - sets Flag_Entropy_ICs. This is a historical variable for cross + compatibility with Gadget-3 + + @param np_total | list (6D) + - the total number of particles required of each type. + + Type/Index | Symbolic Type Name + ------------|-------------------- + 0 | Gas + 1 | Halo + 2 | Disk + 3 | Bulge + 4 | Stars + 5 | Bndry + + @param np_total_hw | list (6D) + - the number of high-word particles in the file. + + + @param other | dictionary | optional + - a dictionary with any other parameters that you wish to pass into + the file header. They will be passed such that the key is the + name of the attribute in the hdf5 file. + + """ + + # We'll first build a dictionary to iterate through. + + default_attributes = { + "BoxSize" : boxsize, + "Flag_Entropy_ICs" : flag_entropy, + "NumPart_Total" : np_total, + "NumPart_Total_HighWord" : np_total_hw, + "NumFilesPerSnapshot" : 1, # required for backwards compatibility + "NumPart_ThisFile" : np_total, # Also required for bw compatibility + } + + if other: + attributes = dict(default_attributes, **other) + else: + attributes = default_attributes + + header = f.create_group("Header") + + # For some reason there isn't a direct dictionary interface (at least one + # that is documented, so we are stuck doing this loop... + + for name, value in attributes.items(): + header.attrs[name] = value + + return + + +def write_runtime_pars(f, periodic_boundary, other=False): + """ Writes the "RuntimeParams" section in the hdf5 file. The parameters in + this function that are required are also required for SWIFT to function. + If you wish to pass extra arguments into the runtime parameters you + may do that by providing a dictionary to other. + + @param: f | file handle + - the file handle of the hdf5 object (use h5py.File(filename, "w") + to open a file handle of the correct type). + + @param: periodic_boundary | int (0/1) + - the condition for periodic boundary conditions -- they are 'on' + if this variable is 1, and off if it is 0. Note that SWIFT + currently requires periodic boundary conditions to run (as of + August 2017). + + @param other | dictionary | optional + - a dictionary with any other parameters that you wish to pass into + the RuntimePars. They will be passed such that the key is the + name of the attribute in the hdf5 file. + """ + + # First build the dictionary + + default_attributes = { + "PeriodicBoundariesOn" : periodic_boundary, + } + + if other: + attributes = dict(default_attributes, **other) + else: + attributes = default_attributes + + runtime = f.create_group("RuntimePars") + + for name, value in attributes.items(): + runtime.attrs[name] = value + + return + + +def write_units(f, current, length, mass, temperature, time, other=False): + """ Writes the "RuntimeParams" section in the hdf5 file. The parameters in + this function that are required are also required for SWIFT to function. + If you wish to pass extra arguments into the runtime parameters you + may do that by providing a dictionary to other. + + @param: f | file handle + - the file handle of the hdf5 object (use h5py.File(filename, "w") + to open a file handle of the correct type). + + @param: current | float + - the current conversion factor in cgs units. + + @param: length | float + - the length conversion factor in cgs units. + + @param: mass | float + - the mass conversion factor in cgs units. + + @param: temperature | float + - the temperature conversion factor in cgs units. + + @param: time | float + - the time conversion factor in cgs units. + + @param: other | dictionary | optional + - a dictionary with any other parameters that you wish to pass into + the units attributes. They will be passed such that the key is + the name of the attribute in the hdf5 file. + """ + + # First build the dictionary + + default_attributes = { + "Unit current in cgs (U_I)": current, + "Unit length in cgs (U_L)": length, + "Unit mass in cgs (U_M)": mass, + "Unit temperature in cgs (U_T)": temperature, + "Unit time in cgs (U_t)": time, + } + + if other: + attributes = dict(default_attributes, **other) + else: + attributes = default_attributes + + units = f.create_group("Units") + + for name, value in attributes.items(): + units.attrs[name] = value + + return + + +def write_block(f, part_type, pos, vel, ids, mass, int_energy, smoothing, other=False): + """ Writes a given block of data to PartType{part_type}. The above + required parameters are required for SWIFT to run. + + @param: f | file handle + - the file handle of the hdf5 object. + + @param part_type | int (0-5): + - the identifiying number of the particle type. + + Type/Index | Symbolic Type Name + ------------|-------------------- + 0 | Gas + 1 | Halo + 2 | Disk + 3 | Bulge + 4 | Stars + 5 | Bndry + + @param pos | numpy.array + - the array of particle positions with shape (n_particles, 3). + + @param vel | numpy.array + - the array of particle velocities with shape (n_particles, 3). + + @param ids | numpy.array + - the ids of the particles. Please note that particle IDs in + SWIFT must be strictly positive. Shape (n_particles, 1) + + @param mass | numpy.array + - the masses of the particles. In SWIFT MassTable in the header + is ignored and so particle masses must be provided here. Shape + (n_particles, 1) + + @param int_energy | numpy.array + - the internal energies of the particles. Shape (n_particles, 1) + + @param smoothing | numpy.array + - the smoothing lenghts of the individual particles. Please note + that this cannot be supplied only in the parameterfile and must + be provided on a particle-by-particle basis in SWIFT. Shape + (n_particles, 1) + + @param: other | dictionary | optional + - a dictionary with any other parameters that you wish to pass into + the particle data. They will be passed such that the key is + the name of the dataset in the hdf5 file. + """ + + # Build the dictionary + + default_data = { + "Coordinates" : pos, + "Velocities" : vel, + "ParticleIDs" : ids, + "Masses" : mass, + "InternalEnergy" : int_energy, + "SmoothingLength" : smoothing, + } + + if other: + data = dict(default_data, **other) + else: + data = default_data + + particles = f.create_group(f"PartType{part_type}") + + for name, value in data.items(): + particles.create_dataset(name, data=value) + + return + diff --git a/examples/Makefile.am b/examples/Makefile.am index c5537441d5792f00cac9f71a3c9b99a71b13b8e6..23c7c4a44f1221358066df56ee9f8de8404ad6a3 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -19,12 +19,12 @@ MYFLAGS = # Add the source directory and debug to CFLAGS -AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) +AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) AM_LDFLAGS = $(HDF5_LDFLAGS) # Extra libraries. -EXTRA_LIBS = $(HDF5_LIBS) $(FFTW_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS) $(GRACKLE_LIBS) +EXTRA_LIBS = $(HDF5_LIBS) $(FFTW_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS) $(GRACKLE_LIBS) $(GSL_LIBS) # MPI libraries. MPI_LIBS = $(PARMETIS_LIBS) $(MPI_THREAD_LIBS) diff --git a/examples/Noh_3D/noh.yml b/examples/Noh_3D/noh.yml index 1d126f19babd0c9fe28afff907b3fe8259467a24..88119827501e17cc26742e8cad92a3611e83faa7 100644 --- a/examples/Noh_3D/noh.yml +++ b/examples/Noh_3D/noh.yml @@ -18,7 +18,8 @@ Snapshots: basename: noh # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 5e-2 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 3e-3 # Time between statistics output diff --git a/examples/SedovBlast_3D/sedov.yml b/examples/SedovBlast_3D/sedov.yml index b80be1954491b19234ce7385baed83931d82433f..057e5a18acba2a1e8211d40f15ffde35b2019a9b 100644 --- a/examples/SedovBlast_3D/sedov.yml +++ b/examples/SedovBlast_3D/sedov.yml @@ -18,7 +18,8 @@ Snapshots: basename: sedov # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 1e-2 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-3 # Time between statistics output diff --git a/examples/SodShockSpherical_3D/sodShock.yml b/examples/SodShockSpherical_3D/sodShock.yml index a26ab95b21c782ce83310038432ac08df0e024c3..52c06dd65ec0b37a0ec0707315ccd15356a7b2d6 100644 --- a/examples/SodShockSpherical_3D/sodShock.yml +++ b/examples/SodShockSpherical_3D/sodShock.yml @@ -18,7 +18,8 @@ Snapshots: basename: sodShock # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.1 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/SodShock_3D/sodShock.yml b/examples/SodShock_3D/sodShock.yml index 51a188b6d4537d490cb837a03dab15f74c3b083c..26fbfe4faacf2bbbfd9b077bf9f9c075ce93ef6d 100644 --- a/examples/SodShock_3D/sodShock.yml +++ b/examples/SodShock_3D/sodShock.yml @@ -18,7 +18,8 @@ Snapshots: basename: sodShock # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.2 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/VacuumSpherical_3D/vacuum.yml b/examples/VacuumSpherical_3D/vacuum.yml index 881b155b62c7f1f2af12a1d013ff5c05f1c16a88..92164a18a46404ad7730f3411dc14953139501bb 100644 --- a/examples/VacuumSpherical_3D/vacuum.yml +++ b/examples/VacuumSpherical_3D/vacuum.yml @@ -18,7 +18,8 @@ Snapshots: basename: vacuum # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.05 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/Vacuum_3D/vacuum.yml b/examples/Vacuum_3D/vacuum.yml index 5ef5ce3da68febb086a14ad1a2207711f680d9ff..45a6b73d54de69d71e194ec074ebdd00cd6a57c0 100644 --- a/examples/Vacuum_3D/vacuum.yml +++ b/examples/Vacuum_3D/vacuum.yml @@ -18,7 +18,8 @@ Snapshots: basename: vacuum # Common part of the name of output files time_first: 0. # Time of the first output (in internal units) delta_time: 0.1 # Time difference between consecutive outputs (in internal units) - + compression: 4 + # Parameters governing the conserved quantities statistics Statistics: delta_time: 1e-2 # Time between statistics output diff --git a/examples/main.c b/examples/main.c index 828bf871bb034ce76a1a2454ed7e7dbb43e01443..80e4dfb479172d9e2bca6fb19ee676371d130afe 100644 --- a/examples/main.c +++ b/examples/main.c @@ -606,7 +606,7 @@ int main(int argc, char *argv[]) { cosmology_init(params, &us, &prog_const, &cosmo); else cosmology_init_no_cosmo(&cosmo); - if (with_cosmology) cosmology_print(&cosmo); + if (myrank == 0 && with_cosmology) cosmology_print(&cosmo); /* Initialise the hydro properties */ if (with_hydro) hydro_props_init(&hydro_properties, params); diff --git a/examples/plot_gravity_checks.py b/examples/plot_gravity_checks.py index 41d9d629899f5c34cbb0264b38547a3439c04bf3..de4f37af32cf0d051afb4c5090075654e6fcd65c 100644 --- a/examples/plot_gravity_checks.py +++ b/examples/plot_gravity_checks.py @@ -13,7 +13,7 @@ params = {'axes.labelsize': 14, 'xtick.labelsize': 14, 'ytick.labelsize': 14, 'text.usetex': True, -'figure.figsize': (10, 10), +'figure.figsize': (12, 10), 'figure.subplot.left' : 0.06, 'figure.subplot.right' : 0.99 , 'figure.subplot.bottom' : 0.06 , @@ -60,11 +60,13 @@ data = np.loadtxt('gravity_checks_exact_step%d.dat'%step) exact_ids = data[:,0] exact_pos = data[:,1:4] exact_a = data[:,4:7] +exact_pot = data[:,7] # Sort stuff sort_index = np.argsort(exact_ids) exact_ids = exact_ids[sort_index] exact_pos = exact_pos[sort_index, :] exact_a = exact_a[sort_index, :] +exact_pot = exact_pot[sort_index] exact_a_norm = np.sqrt(exact_a[:,0]**2 + exact_a[:,1]**2 + exact_a[:,2]**2) # Start the plot @@ -166,12 +168,14 @@ for i in range(num_order): ids = data[:,0] pos = data[:,1:4] a_grav = data[:, 4:7] + pot = data[:, 7] # Sort stuff sort_index = np.argsort(ids) ids = ids[sort_index] pos = pos[sort_index, :] a_grav = a_grav[sort_index, :] + pot = pot[sort_index] # Cross-checks if not np.array_equal(exact_ids, ids): @@ -185,6 +189,7 @@ for i in range(num_order): # Compute the error norm diff = exact_a - a_grav + diff_pot = exact_pot - pot norm_diff = np.sqrt(diff[:,0]**2 + diff[:,1]**2 + diff[:,2]**2) @@ -192,73 +197,87 @@ for i in range(num_order): error_x = abs(diff[:,0]) / exact_a_norm error_y = abs(diff[:,1]) / exact_a_norm error_z = abs(diff[:,2]) / exact_a_norm + error_pot = abs(diff_pot) / abs(exact_pot) # 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) + error_pot_hist,_ = np.histogram(error_pot, 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) + median_pot = np.median(error_pot) norm_per99 = np.percentile(norm_error,99) per99_x = np.percentile(error_x,99) per99_y = np.percentile(error_y,99) per99_z = np.percentile(error_z,99) + per99_pot = np.percentile(error_pot, 99) norm_max = np.max(norm_error) max_x = np.max(error_x) max_y = np.max(error_y) max_z = np.max(error_z) + max_pot = np.max(error_pot) print "Order %d ---- "%order[i] print "Norm: median= %f 99%%= %f max= %f"%(norm_median, norm_per99, norm_max) print "X : median= %f 99%%= %f max= %f"%(median_x, per99_x, max_x) print "Y : median= %f 99%%= %f max= %f"%(median_y, per99_y, max_y) print "Z : median= %f 99%%= %f max= %f"%(median_z, per99_z, max_z) + print "Pot : median= %f 99%%= %f max= %f"%(median_pot, per99_pot, max_pot) print "" - plt.subplot(221) - plt.semilogx(bins, norm_error_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) - plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(norm_median, norm_per99), ha="left", va="top", color=cols[i]) - plt.subplot(222) + plt.subplot(231) plt.semilogx(bins, error_x_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_x, per99_x), ha="left", va="top", color=cols[i]) - plt.subplot(223) + plt.subplot(232) plt.semilogx(bins, error_y_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_y, per99_y), ha="left", va="top", color=cols[i]) - plt.subplot(224) + plt.subplot(233) plt.semilogx(bins, error_z_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_z, per99_z), ha="left", va="top", color=cols[i]) + plt.subplot(234) + plt.semilogx(bins, norm_error_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(norm_median, norm_per99), ha="left", va="top", color=cols[i]) + plt.subplot(235) + plt.semilogx(bins, error_pot_hist, color=cols[i],label="SWIFT m-poles order %d"%order[i]) + plt.text(min_error * 1.5, 1.5 - count/10., "$50\\%%\\rightarrow%.4f~~ 99\\%%\\rightarrow%.4f$"%(median_pot, per99_pot), ha="left", va="top", color=cols[i]) count += 1 -plt.subplot(221) -plt.xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$") -#plt.ylabel("Density") -plt.xlim(min_error, max_error) -plt.ylim(0,2.5) -plt.legend(loc="upper left") -plt.subplot(222) +plt.subplot(231) plt.xlabel("$\delta a_x/|\overrightarrow{a}_{exact}|$") #plt.ylabel("Density") plt.xlim(min_error, max_error) plt.ylim(0,1.75) #plt.legend(loc="center left") -plt.subplot(223) +plt.subplot(232) plt.xlabel("$\delta a_y/|\overrightarrow{a}_{exact}|$") #plt.ylabel("Density") plt.xlim(min_error, max_error) plt.ylim(0,1.75) #plt.legend(loc="center left") -plt.subplot(224) +plt.subplot(233) plt.xlabel("$\delta a_z/|\overrightarrow{a}_{exact}|$") #plt.ylabel("Density") plt.xlim(min_error, max_error) plt.ylim(0,1.75) +plt.subplot(234) +plt.xlabel("$|\delta \overrightarrow{a}|/|\overrightarrow{a}_{exact}|$") +#plt.ylabel("Density") +plt.xlim(min_error, max_error) +plt.ylim(0,2.5) +plt.legend(loc="upper left") +plt.subplot(235) +plt.xlabel("$\delta \phi/\phi_{exact}$") +#plt.ylabel("Density") +plt.xlim(min_error, max_error) +plt.ylim(0,1.75) #plt.legend(loc="center left") diff --git a/src/Makefile.am b/src/Makefile.am index 6674dfcfaa11dcaa8b039426c4af66f825835adc..885a019ae4990466b924c03737df76b9472de02c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -16,7 +16,7 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # Add the debug flag to the whole thing -AM_CFLAGS = $(HDF5_CPPFLAGS) +AM_CFLAGS = $(HDF5_CPPFLAGS) $(GSL_INCS) # Assign a "safe" version number AM_LDFLAGS = $(HDF5_LDFLAGS) $(FFTW_LIBS) -version-info 0:0:0 @@ -25,7 +25,7 @@ AM_LDFLAGS = $(HDF5_LDFLAGS) $(FFTW_LIBS) -version-info 0:0:0 GIT_CMD = @GIT_CMD@ # Additional dependencies for shared libraries. -EXTRA_LIBS = $(HDF5_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS) $(GRACKLE_LIB) +EXTRA_LIBS = $(HDF5_LIBS) $(PROFILER_LIBS) $(TCMALLOC_LIBS) $(JEMALLOC_LIBS) $(GRACKLE_LIB) $(GSL_LIBS) # MPI libraries. MPI_LIBS = $(PARMETIS_LIBS) $(MPI_THREAD_LIBS) diff --git a/src/cosmology.c b/src/cosmology.c index 9b301388e08ca0f4aa2a7d5d8f351ab88682c8ba..b1bf71ad25451d2185d911320fb757193e990945 100644 --- a/src/cosmology.c +++ b/src/cosmology.c @@ -667,13 +667,15 @@ void cosmology_struct_dump(const struct cosmology *cosmology, FILE *stream) { * @brief Restore a cosmology struct from the given FILE as a stream of * bytes. * + * @param enabled whether cosmology is enabled. * @param cosmology the struct * @param stream the file stream */ -void cosmology_struct_restore(struct cosmology *cosmology, FILE *stream) { +void cosmology_struct_restore(int enabled, struct cosmology *cosmology, + FILE *stream) { restart_read_blocks((void *)cosmology, sizeof(struct cosmology), 1, stream, NULL, "cosmology function"); - /* Re-initialise the tables */ - cosmology_init_tables(cosmology); + /* Re-initialise the tables if using a cosmology. */ + if (enabled) cosmology_init_tables(cosmology); } diff --git a/src/cosmology.h b/src/cosmology.h index b1b33930bad7a49c0f2abe3a2201d71c9be57305..b541e20fa1dd4f55422f9ff1d0952783d8de0ad7 100644 --- a/src/cosmology.h +++ b/src/cosmology.h @@ -192,6 +192,7 @@ void cosmology_write_model(hid_t h_grp, const struct cosmology *c); /* Dump/restore. */ void cosmology_struct_dump(const struct cosmology *cosmology, FILE *stream); -void cosmology_struct_restore(struct cosmology *cosmology, FILE *stream); +void cosmology_struct_restore(int enabled, struct cosmology *cosmology, + FILE *stream); #endif /* SWIFT_COSMOLOGY_H */ diff --git a/src/engine.c b/src/engine.c index 01c1b54ba12885b21458e403e717c51d80e7e3be..e9badb8643cc6f728feac3b2a0bb493f7ce8bca5 100644 --- a/src/engine.c +++ b/src/engine.c @@ -5821,11 +5821,13 @@ void engine_compute_next_snapshot_time(struct engine *e) { if (e->policy & engine_policy_cosmology) { const float next_snapshot_time = exp(e->ti_nextSnapshot * e->time_base) * e->cosmology->a_begin; - message("Next output time set to a=%e.", next_snapshot_time); + if (e->verbose) + message("Next output time set to a=%e.", next_snapshot_time); } else { const float next_snapshot_time = e->ti_nextSnapshot * e->time_base + e->time_begin; - message("Next output time set to t=%e.", next_snapshot_time); + if (e->verbose) + message("Next output time set to t=%e.", next_snapshot_time); } } } @@ -5924,7 +5926,7 @@ void engine_struct_restore(struct engine *e, FILE *stream) { struct cosmology *cosmo = (struct cosmology *)malloc(sizeof(struct cosmology)); - cosmology_struct_restore(cosmo, stream); + cosmology_struct_restore(e->policy & engine_policy_cosmology, cosmo, stream); e->cosmology = cosmo; #ifdef WITH_MPI diff --git a/src/gravity.c b/src/gravity.c index 05f4f3724414287e5aeaa6e932ff4df7810914d9..0d951b4a9f8babb68d413e1183b1e2b52129abaf 100644 --- a/src/gravity.c +++ b/src/gravity.c @@ -54,6 +54,7 @@ struct exact_force_data { static float fewald_x[Newald + 1][Newald + 1][Newald + 1]; static float fewald_y[Newald + 1][Newald + 1][Newald + 1]; static float fewald_z[Newald + 1][Newald + 1][Newald + 1]; +static float potewald[Newald + 1][Newald + 1][Newald + 1]; /* Factor used to normalize the access to the Ewald table */ float ewald_fac; @@ -83,6 +84,8 @@ void gravity_exact_force_ewald_init(double boxSize) { const float factor_exp1 = 2.f * alpha / sqrt(M_PI); const float factor_exp2 = -M_PI * M_PI / alpha2; const float factor_sin = 2.f * M_PI; + const float factor_cos = 2.f * M_PI; + const float factor_pot = M_PI / alpha2; const float boxSize_inv2 = 1.f / (boxSize * boxSize); /* Ewald factor to access the table */ @@ -92,6 +95,9 @@ void gravity_exact_force_ewald_init(double boxSize) { bzero(fewald_x, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float)); bzero(fewald_y, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float)); bzero(fewald_z, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float)); + bzero(potewald, (Newald + 1) * (Newald + 1) * (Newald + 1) * sizeof(float)); + + potewald[0][0][0] = 2.8372975f; /* Compute the values in one of the octants */ for (int i = 0; i <= Newald; ++i) { @@ -114,6 +120,7 @@ void gravity_exact_force_ewald_init(double boxSize) { float f_x = r_x * r_inv3; float f_y = r_y * r_inv3; float f_z = r_z * r_inv3; + float pot = r_inv + factor_pot; for (int n_i = -4; n_i <= 4; ++n_i) { for (int n_j = -4; n_j <= 4; ++n_j) { @@ -130,15 +137,17 @@ void gravity_exact_force_ewald_init(double boxSize) { const float r_tilde_inv3 = r_tilde_inv * r_tilde_inv * r_tilde_inv; - const float val = - erfcf(alpha * r_tilde) + - factor_exp1 * r_tilde * expf(-alpha2 * r_tilde2); + const float val_pot = erfcf(alpha * r_tilde); + + const float val_f = + val_pot + factor_exp1 * r_tilde * expf(-alpha2 * r_tilde2); /* First correction term */ - const float f = val * r_tilde_inv3; + const float f = val_f * r_tilde_inv3; f_x -= f * d_x; f_y -= f * d_y; f_z -= f * d_z; + pot -= val_pot * r_tilde_inv; } } } @@ -149,16 +158,23 @@ void gravity_exact_force_ewald_init(double boxSize) { const float h2 = h_i * h_i + h_j * h_j + h_k * h_k; + if (h2 == 0.f) continue; + const float h2_inv = 1.f / (h2 + FLT_MIN); const float h_dot_x = h_i * r_x + h_j * r_y + h_k * r_z; - const float val = 2.f * h2_inv * expf(h2 * factor_exp2) * - sinf(factor_sin * h_dot_x); + const float common = h2_inv * expf(h2 * factor_exp2); + + const float val_pot = + (float)M_1_PI * common * cosf(factor_cos * h_dot_x); + + const float val_f = 2.f * common * sinf(factor_sin * h_dot_x); /* Second correction term */ - f_x -= val * h_i; - f_y -= val * h_j; - f_z -= val * h_k; + f_x -= val_f * h_i; + f_y -= val_f * h_j; + f_z -= val_f * h_k; + pot -= val_pot; } } } @@ -167,6 +183,7 @@ void gravity_exact_force_ewald_init(double boxSize) { fewald_x[i][j][k] = f_x; fewald_y[i][j][k] = f_y; fewald_z[i][j][k] = f_z; + potewald[i][j][k] = pot; } } } @@ -196,6 +213,11 @@ void gravity_exact_force_ewald_init(double boxSize) { H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT, &(fewald_z[0][0][0])); H5Dclose(h_data); + h_data = H5Dcreate(h_file, "Ewald_pot", H5T_NATIVE_FLOAT, h_space, + H5P_DEFAULT, H5P_DEFAULT, H5P_DEFAULT); + H5Dwrite(h_data, H5T_NATIVE_FLOAT, h_space, H5S_ALL, H5P_DEFAULT, + &(potewald[0][0][0])); + H5Dclose(h_data); H5Sclose(h_space); H5Fclose(h_file); #endif @@ -207,6 +229,7 @@ void gravity_exact_force_ewald_init(double boxSize) { fewald_x[i][j][k] *= boxSize_inv2; fewald_y[i][j][k] *= boxSize_inv2; fewald_z[i][j][k] *= boxSize_inv2; + potewald[i][j][k] *= boxSize_inv2; } } } @@ -229,11 +252,12 @@ void gravity_exact_force_ewald_init(double boxSize) { * @param rx x-coordinate of distance vector. * @param ry y-coordinate of distance vector. * @param rz z-coordinate of distance vector. - * @param corr (return) The Ewald correction. + * @param corr_f (return) The Ewald correction for the force. + * @param corr_p (return) The Ewald correction for the potential. */ __attribute__((always_inline)) INLINE static void gravity_exact_force_ewald_evaluate(double rx, double ry, double rz, - double corr[3]) { + double corr_f[3], double *corr_p) { const double s_x = (rx < 0.) ? 1. : -1.; const double s_y = (ry < 0.) ? 1. : -1.; @@ -258,40 +282,51 @@ gravity_exact_force_ewald_evaluate(double rx, double ry, double rz, const double tz = 1. - dz; /* Interpolation in X */ - corr[0] = 0.; - corr[0] += fewald_x[i + 0][j + 0][k + 0] * tx * ty * tz; - corr[0] += fewald_x[i + 0][j + 0][k + 1] * tx * ty * dz; - corr[0] += fewald_x[i + 0][j + 1][k + 0] * tx * dy * tz; - corr[0] += fewald_x[i + 0][j + 1][k + 1] * tx * dy * dz; - corr[0] += fewald_x[i + 1][j + 0][k + 0] * dx * ty * tz; - corr[0] += fewald_x[i + 1][j + 0][k + 1] * dx * ty * dz; - corr[0] += fewald_x[i + 1][j + 1][k + 0] * dx * dy * tz; - corr[0] += fewald_x[i + 1][j + 1][k + 1] * dx * dy * dz; - corr[0] *= s_x; + corr_f[0] = 0.; + corr_f[0] += fewald_x[i + 0][j + 0][k + 0] * tx * ty * tz; + corr_f[0] += fewald_x[i + 0][j + 0][k + 1] * tx * ty * dz; + corr_f[0] += fewald_x[i + 0][j + 1][k + 0] * tx * dy * tz; + corr_f[0] += fewald_x[i + 0][j + 1][k + 1] * tx * dy * dz; + corr_f[0] += fewald_x[i + 1][j + 0][k + 0] * dx * ty * tz; + corr_f[0] += fewald_x[i + 1][j + 0][k + 1] * dx * ty * dz; + corr_f[0] += fewald_x[i + 1][j + 1][k + 0] * dx * dy * tz; + corr_f[0] += fewald_x[i + 1][j + 1][k + 1] * dx * dy * dz; + corr_f[0] *= s_x; /* Interpolation in Y */ - corr[1] = 0.; - corr[1] += fewald_y[i + 0][j + 0][k + 0] * tx * ty * tz; - corr[1] += fewald_y[i + 0][j + 0][k + 1] * tx * ty * dz; - corr[1] += fewald_y[i + 0][j + 1][k + 0] * tx * dy * tz; - corr[1] += fewald_y[i + 0][j + 1][k + 1] * tx * dy * dz; - corr[1] += fewald_y[i + 1][j + 0][k + 0] * dx * ty * tz; - corr[1] += fewald_y[i + 1][j + 0][k + 1] * dx * ty * dz; - corr[1] += fewald_y[i + 1][j + 1][k + 0] * dx * dy * tz; - corr[1] += fewald_y[i + 1][j + 1][k + 1] * dx * dy * dz; - corr[1] *= s_y; + corr_f[1] = 0.; + corr_f[1] += fewald_y[i + 0][j + 0][k + 0] * tx * ty * tz; + corr_f[1] += fewald_y[i + 0][j + 0][k + 1] * tx * ty * dz; + corr_f[1] += fewald_y[i + 0][j + 1][k + 0] * tx * dy * tz; + corr_f[1] += fewald_y[i + 0][j + 1][k + 1] * tx * dy * dz; + corr_f[1] += fewald_y[i + 1][j + 0][k + 0] * dx * ty * tz; + corr_f[1] += fewald_y[i + 1][j + 0][k + 1] * dx * ty * dz; + corr_f[1] += fewald_y[i + 1][j + 1][k + 0] * dx * dy * tz; + corr_f[1] += fewald_y[i + 1][j + 1][k + 1] * dx * dy * dz; + corr_f[1] *= s_y; /* Interpolation in Z */ - corr[2] = 0.; - corr[2] += fewald_z[i + 0][j + 0][k + 0] * tx * ty * tz; - corr[2] += fewald_z[i + 0][j + 0][k + 1] * tx * ty * dz; - corr[2] += fewald_z[i + 0][j + 1][k + 0] * tx * dy * tz; - corr[2] += fewald_z[i + 0][j + 1][k + 1] * tx * dy * dz; - corr[2] += fewald_z[i + 1][j + 0][k + 0] * dx * ty * tz; - corr[2] += fewald_z[i + 1][j + 0][k + 1] * dx * ty * dz; - corr[2] += fewald_z[i + 1][j + 1][k + 0] * dx * dy * tz; - corr[2] += fewald_z[i + 1][j + 1][k + 1] * dx * dy * dz; - corr[2] *= s_z; + corr_f[2] = 0.; + corr_f[2] += fewald_z[i + 0][j + 0][k + 0] * tx * ty * tz; + corr_f[2] += fewald_z[i + 0][j + 0][k + 1] * tx * ty * dz; + corr_f[2] += fewald_z[i + 0][j + 1][k + 0] * tx * dy * tz; + corr_f[2] += fewald_z[i + 0][j + 1][k + 1] * tx * dy * dz; + corr_f[2] += fewald_z[i + 1][j + 0][k + 0] * dx * ty * tz; + corr_f[2] += fewald_z[i + 1][j + 0][k + 1] * dx * ty * dz; + corr_f[2] += fewald_z[i + 1][j + 1][k + 0] * dx * dy * tz; + corr_f[2] += fewald_z[i + 1][j + 1][k + 1] * dx * dy * dz; + corr_f[2] *= s_z; + + /* Interpolation for potential */ + *corr_p = 0.; + *corr_p += potewald[i + 0][j + 0][k + 0] * tx * ty * tz; + *corr_p += potewald[i + 0][j + 0][k + 1] * tx * ty * dz; + *corr_p += potewald[i + 0][j + 1][k + 0] * tx * dy * tz; + *corr_p += potewald[i + 0][j + 1][k + 1] * tx * dy * dz; + *corr_p += potewald[i + 1][j + 0][k + 0] * dx * ty * tz; + *corr_p += potewald[i + 1][j + 0][k + 1] * dx * ty * dz; + *corr_p += potewald[i + 1][j + 1][k + 0] * dx * dy * tz; + *corr_p += potewald[i + 1][j + 1][k + 1] * dx * dy * dz; } #endif @@ -383,6 +418,7 @@ void gravity_exact_force_compute_mapper(void *map_data, int nr_gparts, /* Be ready for the calculation */ double a_grav[3] = {0., 0., 0.}; + double pot = 0.; /* Interact it with all other particles in the space.*/ for (int j = 0; j < (int)s->nr_gparts; ++j) { @@ -408,37 +444,42 @@ void gravity_exact_force_compute_mapper(void *map_data, int nr_gparts, const double r_inv = 1. / sqrt(r2); const double r = r2 * r_inv; const double mj = gpj->mass; - double f; + double f, phi; if (r >= hi) { /* Get Newtonian gravity */ f = mj * r_inv * r_inv * r_inv; + phi = -mj * r_inv; } else { const double ui = r * hi_inv; - double W; + double Wf, Wp; - kernel_grav_eval_double(ui, &W); + kernel_grav_eval_force_double(ui, &Wf); + kernel_grav_eval_pot_double(ui, &Wp); /* Get softened gravity */ - f = mj * hi_inv3 * W; + f = mj * hi_inv3 * Wf; + phi = mj * hi_inv * Wp; } a_grav[0] += f * dx; a_grav[1] += f * dy; a_grav[2] += f * dz; + pot += phi; /* Apply Ewald correction for periodic BC */ if (periodic && r > 1e-5 * hi) { - double corr[3]; - gravity_exact_force_ewald_evaluate(dx, dy, dz, corr); + double corr_f[3], corr_pot; + gravity_exact_force_ewald_evaluate(dx, dy, dz, corr_f, &corr_pot); - a_grav[0] += mj * corr[0]; - a_grav[1] += mj * corr[1]; - a_grav[2] += mj * corr[2]; + a_grav[0] += mj * corr_f[0]; + a_grav[1] += mj * corr_f[1]; + a_grav[2] += mj * corr_f[2]; + pot += mj * corr_pot; } } @@ -446,6 +487,7 @@ void gravity_exact_force_compute_mapper(void *map_data, int nr_gparts, 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; + gpi->potential_exact = pot * const_G; counter++; } @@ -529,8 +571,9 @@ void gravity_exact_force_check(struct space *s, const struct engine *e, fprintf(file_swift, "# theta= %16.8e\n", e->gravity_properties->theta_crit); fprintf(file_swift, "# Git Branch: %s\n", git_branch()); fprintf(file_swift, "# Git Revision: %s\n", git_revision()); - fprintf(file_swift, "# %16s %16s %16s %16s %16s %16s %16s\n", "id", "pos[0]", - "pos[1]", "pos[2]", "a_swift[0]", "a_swift[1]", "a_swift[2]"); + fprintf(file_swift, "# %16s %16s %16s %16s %16s %16s %16s %16s\n", "id", + "pos[0]", "pos[1]", "pos[2]", "a_swift[0]", "a_swift[1]", + "a_swift[2]", "potential"); /* Output particle SWIFT accelerations */ for (size_t i = 0; i < s->nr_gparts; ++i) { @@ -541,9 +584,10 @@ 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_swift, "%18lld %16.8e %16.8e %16.8e %16.8e %16.8e %16.8e \n", + fprintf(file_swift, + "%18lld %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[0], gpi->a_grav[1], gpi->a_grav[2]); + gpi->a_grav[0], gpi->a_grav[1], gpi->a_grav[2], gpi->potential); } } @@ -569,9 +613,9 @@ void gravity_exact_force_check(struct space *s, const struct engine *e, fprintf(file_exact, "# periodic= %d\n", s->periodic); fprintf(file_exact, "# Git Branch: %s\n", git_branch()); fprintf(file_exact, "# Git Revision: %s\n", git_revision()); - fprintf(file_exact, "# %16s %16s %16s %16s %16s %16s %16s\n", "id", + fprintf(file_exact, "# %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_exact[2]", "potential"); /* Output particle exact accelerations */ for (size_t i = 0; i < s->nr_gparts; ++i) { @@ -582,10 +626,11 @@ 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_exact, "%18lld %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]); + fprintf(file_exact, + "%18lld %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->potential_exact); } } diff --git a/src/gravity/Default/gravity.h b/src/gravity/Default/gravity.h index bc06b17188f99785404a1162d46384ee26c884ea..099cbcd158436b414b9bc6a912727116b763cff9 100644 --- a/src/gravity/Default/gravity.h +++ b/src/gravity/Default/gravity.h @@ -48,16 +48,29 @@ __attribute__((always_inline)) INLINE static float gravity_get_softening( } /** - * @brief Returns the potential of a particle + * @brief Returns the comoving potential of a particle * * @param gp The particle of interest */ -__attribute__((always_inline)) INLINE static float gravity_get_potential( - const struct gpart* restrict gp) { +__attribute__((always_inline)) INLINE static float +gravity_get_comoving_potential(const struct gpart* restrict gp) { return gp->potential; } +/** + * @brief Returns the physical potential of a particle + * + * @param gp The particle of interest. + * @param cosmo The cosmological model. + */ +__attribute__((always_inline)) INLINE static float +gravity_get_physical_potential(const struct gpart* restrict gp, + const struct cosmology* cosmo) { + + return gp->potential * cosmo->a_inv; +} + /** * @brief Computes the gravity time-step of a given particle due to self-gravity * @@ -136,6 +149,7 @@ __attribute__((always_inline)) INLINE static void gravity_end_force( gp->a_grav[0] *= const_G; gp->a_grav[1] *= const_G; gp->a_grav[2] *= const_G; + gp->potential *= const_G; } /** diff --git a/src/gravity/Default/gravity_iact.h b/src/gravity/Default/gravity_iact.h index 1f4310eea49a1b1b298fe00f00e1ae0802d9b688..3951f77d59e71439d2f621dd2a69fd62d39ba4f0 100644 --- a/src/gravity/Default/gravity_iact.h +++ b/src/gravity/Default/gravity_iact.h @@ -38,9 +38,11 @@ * @param h_inv3 Cube of the inverse of the softening length. * @param mass Mass of the point-mass. * @param f_ij (return) The force intensity. + * @param pot_ij (return) The potential. */ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full( - float r2, float h2, float h_inv, float h_inv3, float mass, float *f_ij) { + float r2, float h2, float h_inv, float h_inv3, float mass, float *f_ij, + float *pot_ij) { /* Get the inverse distance */ const float r_inv = 1.f / sqrtf(r2); @@ -50,17 +52,20 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full( /* Get Newtonian gravity */ *f_ij = mass * r_inv * r_inv * r_inv; + *pot_ij = -mass * r_inv; } else { const float r = r2 * r_inv; const float ui = r * h_inv; - float W_ij; - kernel_grav_eval(ui, &W_ij); + float W_f_ij, W_pot_ij; + kernel_grav_force_eval(ui, &W_f_ij); + kernel_grav_pot_eval(ui, &W_pot_ij); /* Get softened gravity */ - *f_ij = mass * h_inv3 * W_ij; + *f_ij = mass * h_inv3 * W_f_ij; + *pot_ij = mass * h_inv * W_pot_ij; } } @@ -78,10 +83,11 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_full( * @param mass Mass of the point-mass. * @param rlr_inv Inverse of the mesh smoothing scale. * @param f_ij (return) The force intensity. + * @param pot_ij (return) The potential. */ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated( float r2, float h2, float h_inv, float h_inv3, float mass, float rlr_inv, - float *f_ij) { + float *f_ij, float *pot_ij) { /* Get the inverse distance */ const float r_inv = 1.f / sqrtf(r2); @@ -92,23 +98,28 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated( /* Get Newtonian gravity */ *f_ij = mass * r_inv * r_inv * r_inv; + *pot_ij = -mass * r_inv; } else { const float ui = r * h_inv; - float W_ij; + float W_f_ij, W_pot_ij; - kernel_grav_eval(ui, &W_ij); + kernel_grav_force_eval(ui, &W_f_ij); + kernel_grav_pot_eval(ui, &W_pot_ij); /* Get softened gravity */ - *f_ij = mass * h_inv3 * W_ij; + *f_ij = mass * h_inv3 * W_f_ij; + *pot_ij = mass * h_inv * W_pot_ij; } /* Get long-range correction */ const float u_lr = r * rlr_inv; - float corr_lr; - kernel_long_grav_eval(u_lr, &corr_lr); - *f_ij *= corr_lr; + float corr_f_lr, corr_pot_lr; + kernel_long_grav_force_eval(u_lr, &corr_f_lr); + kernel_long_grav_pot_eval(u_lr, &corr_pot_lr); + *f_ij *= corr_f_lr; + *pot_ij *= corr_pot_lr; } /** @@ -127,33 +138,43 @@ __attribute__((always_inline)) INLINE static void runner_iact_grav_pp_truncated( * @param f_x (return) The x-component of the acceleration. * @param f_y (return) The y-component of the acceleration. * @param f_z (return) The z-component of the acceleration. + * @param pot (return) The potential. */ __attribute__((always_inline)) INLINE static void runner_iact_grav_pm( float r_x, float r_y, float r_z, float r2, float h, float h_inv, - const struct multipole *m, float *f_x, float *f_y, float *f_z) { + const struct multipole *m, float *f_x, float *f_y, float *f_z, float *pot) { #if SELF_GRAVITY_MULTIPOLE_ORDER < 3 - runner_iact_grav_pp_full(r2, h * h, h_inv, h_inv3, m->M_000, f_ij); + float f_ij; + runner_iact_grav_pp_full(r2, h * h, h_inv, h_inv * h_inv * h_inv, m->M_000, + &f_ij, pot); + *f_x = -f_ij * r_x; + *f_y = -f_ij * r_y; + *f_z = -f_ij * r_z; #else /* Get the inverse distance */ const float r_inv = 1.f / sqrtf(r2); - struct potential_derivatives_M2P pot; - compute_potential_derivatives_M2P(r_x, r_y, r_z, r2, r_inv, h, h_inv, &pot); + struct potential_derivatives_M2P d; + compute_potential_derivatives_M2P(r_x, r_y, r_z, r2, r_inv, h, h_inv, &d); /* 1st order terms (monopole) */ - *f_x = m->M_000 * pot.D_100; - *f_y = m->M_000 * pot.D_010; - *f_z = m->M_000 * pot.D_001; + *f_x = m->M_000 * d.D_100; + *f_y = m->M_000 * d.D_010; + *f_z = m->M_000 * d.D_001; + *pot = -m->M_000 * d.D_000; /* 3rd order terms (quadrupole) */ - *f_x += m->M_200 * pot.D_300 + m->M_020 * pot.D_120 + m->M_002 * pot.D_102; - *f_y += m->M_200 * pot.D_210 + m->M_020 * pot.D_030 + m->M_002 * pot.D_012; - *f_z += m->M_200 * pot.D_201 + m->M_020 * pot.D_021 + m->M_002 * pot.D_003; - *f_x += m->M_110 * pot.D_210 + m->M_101 * pot.D_201 + m->M_011 * pot.D_111; - *f_y += m->M_110 * pot.D_120 + m->M_101 * pot.D_111 + m->M_011 * pot.D_021; - *f_z += m->M_110 * pot.D_111 + m->M_101 * pot.D_102 + m->M_011 * pot.D_012; + *f_x += m->M_200 * d.D_300 + m->M_020 * d.D_120 + m->M_002 * d.D_102; + *f_y += m->M_200 * d.D_210 + m->M_020 * d.D_030 + m->M_002 * d.D_012; + *f_z += m->M_200 * d.D_201 + m->M_020 * d.D_021 + m->M_002 * d.D_003; + *pot -= m->M_200 * d.D_100 + m->M_020 * d.D_020 + m->M_002 * d.D_002; + + *f_x += m->M_110 * d.D_210 + m->M_101 * d.D_201 + m->M_011 * d.D_111; + *f_y += m->M_110 * d.D_120 + m->M_101 * d.D_111 + m->M_011 * d.D_021; + *f_z += m->M_110 * d.D_111 + m->M_101 * d.D_102 + m->M_011 * d.D_012; + *pot -= m->M_110 * d.D_110 + m->M_101 * d.D_101 + m->M_011 * d.D_011; #endif } diff --git a/src/gravity/Default/gravity_part.h b/src/gravity/Default/gravity_part.h index 33c444e24e4bc9681dc3138298b5fd9301d084d3..4c7a7b5d4af1c26ee96ac797963f5588d3303d77 100644 --- a/src/gravity/Default/gravity_part.h +++ b/src/gravity/Default/gravity_part.h @@ -71,6 +71,8 @@ struct gpart { /* Brute-force particle acceleration. */ double a_grav_exact[3]; + /* Brute-force particle potential. */ + double potential_exact; #endif } SWIFT_STRUCT_ALIGN; diff --git a/src/gravity_cache.h b/src/gravity_cache.h index d46dc3cb1f307aee0b5c7352ea3c5b27b640cfdc..be349cfd4a4583a493219eb020757341c98f540c 100644 --- a/src/gravity_cache.h +++ b/src/gravity_cache.h @@ -59,6 +59,9 @@ struct gravity_cache { /*! #gpart z acceleration. */ float *restrict a_z SWIFT_CACHE_ALIGN; + /*! #gpart potential. */ + float *restrict pot SWIFT_CACHE_ALIGN; + /*! Is this #gpart active ? */ int *restrict active SWIFT_CACHE_ALIGN; @@ -85,6 +88,7 @@ static INLINE void gravity_cache_clean(struct gravity_cache *c) { free(c->a_x); free(c->a_y); free(c->a_z); + free(c->pot); free(c->active); free(c->use_mpole); } @@ -120,6 +124,7 @@ static INLINE void gravity_cache_init(struct gravity_cache *c, int count) { e += posix_memalign((void **)&c->a_x, SWIFT_CACHE_ALIGNMENT, sizeBytesF); e += posix_memalign((void **)&c->a_y, SWIFT_CACHE_ALIGNMENT, sizeBytesF); e += posix_memalign((void **)&c->a_z, SWIFT_CACHE_ALIGNMENT, sizeBytesF); + e += posix_memalign((void **)&c->pot, SWIFT_CACHE_ALIGNMENT, sizeBytesF); e += posix_memalign((void **)&c->active, SWIFT_CACHE_ALIGNMENT, sizeBytesI); e += posix_memalign((void **)&c->use_mpole, SWIFT_CACHE_ALIGNMENT, sizeBytesI); @@ -278,6 +283,7 @@ __attribute__((always_inline)) INLINE void gravity_cache_write_back( swift_declare_aligned_ptr(float, a_x, c->a_x, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(float, a_y, c->a_y, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(float, a_z, c->a_z, SWIFT_CACHE_ALIGNMENT); + swift_declare_aligned_ptr(float, pot, c->pot, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(int, active, c->active, SWIFT_CACHE_ALIGNMENT); /* Write stuff back to the particles */ @@ -286,6 +292,7 @@ __attribute__((always_inline)) INLINE void gravity_cache_write_back( gparts[i].a_grav[0] += a_x[i]; gparts[i].a_grav[1] += a_y[i]; gparts[i].a_grav[2] += a_z[i]; + gparts[i].potential += pot[i]; } } } diff --git a/src/gravity_derivatives.h b/src/gravity_derivatives.h index cf8aa54338b2e87e8bf5f2cc453ad7417eea5804..d698c66e418a120e5e7ebbe1cba0a5a9af8cd1f1 100644 --- a/src/gravity_derivatives.h +++ b/src/gravity_derivatives.h @@ -95,9 +95,16 @@ struct potential_derivatives_M2L { */ struct potential_derivatives_M2P { + /* 0th order terms */ + float D_000; + /* 1st order terms */ float D_100, D_010, D_001; + /* 2nd order terms */ + float D_200, D_020, D_002; + float D_110, D_101, D_011; + /* 3rd order terms */ float D_300, D_030, D_003; float D_210, D_201; @@ -368,11 +375,22 @@ compute_potential_derivatives_M2P(float r_x, float r_y, float r_z, float r2, const float r_y3 = r_y2 * r_y; const float r_z3 = r_z2 * r_z; + /* 0th order derivative */ + pot->D_000 = Dt_1; + /* 1st order derivatives */ pot->D_100 = r_x * Dt_3; pot->D_010 = r_y * Dt_3; pot->D_001 = r_z * Dt_3; + /* 2nd order derivatives */ + pot->D_200 = r_x2 * Dt_5 + Dt_3; + pot->D_020 = r_y2 * Dt_5 + Dt_3; + pot->D_002 = r_z2 * Dt_5 + Dt_3; + pot->D_110 = r_x * r_y * Dt_5; + pot->D_101 = r_x * r_z * Dt_5; + pot->D_011 = r_y * r_z * Dt_5; + /* 3rd order derivatives */ pot->D_300 = r_x3 * Dt_7 + 3.f * r_x * Dt_5; pot->D_030 = r_y3 * Dt_7 + 3.f * r_y * Dt_5; diff --git a/src/hydro/Default/hydro_io.h b/src/hydro/Default/hydro_io.h index 2567545b25f55e03b104844a4142da9cbe397fb5..bfff08dcdf061d86986ffdeabee6e7a84cba1c7b 100644 --- a/src/hydro/Default/hydro_io.h +++ b/src/hydro/Default/hydro_io.h @@ -77,7 +77,7 @@ void convert_part_pos(const struct engine* e, const struct part* p, void hydro_write_particles(struct part* parts, struct io_props* list, int* num_fields) { - *num_fields = 8; + *num_fields = 7; /* List what we want to write */ list[0] = io_make_output_field_convert_part( @@ -92,9 +92,7 @@ void hydro_write_particles(struct part* parts, struct io_props* list, UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, 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] = + list[6] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho); } diff --git a/src/hydro/Gadget2/hydro_iact.h b/src/hydro/Gadget2/hydro_iact.h index 90484c9cee1b384b0540f56e3c845cc4120cdfe0..b7fa7dd4aa00249d009037961888c89abc0d0313 100644 --- a/src/hydro/Gadget2/hydro_iact.h +++ b/src/hydro/Gadget2/hydro_iact.h @@ -659,9 +659,9 @@ runner_iact_nonsym_1_vec_force( vector viz, vector pirho, vector grad_hi, vector piPOrho2, vector balsara_i, vector ci, float *Vjx, float *Vjy, float *Vjz, float *Pjrho, float *Grad_hj, float *PjPOrho2, float *Balsara_j, float *Cj, float *Mj, vector hi_inv, - vector hj_inv, vector *a_hydro_xSum, vector *a_hydro_ySum, - vector *a_hydro_zSum, vector *h_dtSum, vector *v_sigSum, - vector *entropy_dtSum, mask_t mask) { + vector hj_inv, const float a, const float H, vector *a_hydro_xSum, + vector *a_hydro_ySum, vector *a_hydro_zSum, vector *h_dtSum, + vector *v_sigSum, vector *entropy_dtSum, mask_t mask) { #ifdef WITH_VECTORIZATION @@ -687,8 +687,11 @@ runner_iact_nonsym_1_vec_force( const vector balsara_j = vector_load(Balsara_j); const vector cj = vector_load(Cj); - const vector fac_mu = - vector_set1(1.f); /* Will change with cosmological integration */ + /* Cosmological terms */ + const float fac_mu = pow_three_gamma_minus_five_over_two(a); + const float a2_Hubble = a * a * H; + const vector v_fac_mu = vector_set1(fac_mu); + const vector v_a2_Hubble = vector_set1(a2_Hubble); /* Load stuff. */ balsara.v = vec_add(balsara_i.v, balsara_j.v); @@ -718,13 +721,16 @@ runner_iact_nonsym_1_vec_force( dvz.v = vec_sub(viz.v, vjz.v); /* Compute dv dot r. */ - dvdr.v = vec_fma(dvx.v, dx->v, vec_fma(dvy.v, dy->v, vec_mul(dvz.v, dz->v))); + dvdr.v = + vec_fma(dvx.v, dx->v, + vec_fma(dvy.v, dy->v, + vec_fma(dvz.v, dz->v, vec_mul(v_a2_Hubble.v, r2->v)))); /* Compute the relative velocity. (This is 0 if the particles move away from * each other and negative otherwise) */ omega_ij.v = vec_fmin(dvdr.v, vec_setzero()); - mu_ij.v = - vec_mul(fac_mu.v, vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */ + mu_ij.v = vec_mul(v_fac_mu.v, + vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */ /* Compute signal velocity */ v_sig.v = vec_fnma(vec_set1(3.f), mu_ij.v, vec_add(ci.v, cj.v)); @@ -787,9 +793,10 @@ runner_iact_nonsym_2_vec_force( vector viz, vector pirho, vector grad_hi, vector piPOrho2, vector balsara_i, vector ci, float *Vjx, float *Vjy, float *Vjz, float *Pjrho, float *Grad_hj, float *PjPOrho2, float *Balsara_j, float *Cj, float *Mj, vector hi_inv, - float *Hj_inv, vector *a_hydro_xSum, vector *a_hydro_ySum, - vector *a_hydro_zSum, vector *h_dtSum, vector *v_sigSum, - vector *entropy_dtSum, mask_t mask, mask_t mask_2, short mask_cond) { + float *Hj_inv, const float a, const float H, vector *a_hydro_xSum, + vector *a_hydro_ySum, vector *a_hydro_zSum, vector *h_dtSum, + vector *v_sigSum, vector *entropy_dtSum, mask_t mask, mask_t mask_2, + short mask_cond) { #ifdef WITH_VECTORIZATION @@ -853,8 +860,11 @@ runner_iact_nonsym_2_vec_force( const vector hj_inv = vector_load(Hj_inv); const vector hj_inv_2 = vector_load(&Hj_inv[VEC_SIZE]); - const vector fac_mu = - vector_set1(1.f); /* Will change with cosmological integration */ + /* Cosmological terms */ + const float fac_mu = pow_three_gamma_minus_five_over_two(a); + const float a2_Hubble = a * a * H; + const vector v_fac_mu = vector_set1(fac_mu); + const vector v_a2_Hubble = vector_set1(a2_Hubble); /* Find the balsara switch. */ balsara.v = vec_add(balsara_i.v, balsara_j.v); @@ -891,18 +901,22 @@ runner_iact_nonsym_2_vec_force( dvz_2.v = vec_sub(viz.v, vjz_2.v); /* Compute dv dot r. */ - dvdr.v = vec_fma(dvx.v, dx.v, vec_fma(dvy.v, dy.v, vec_mul(dvz.v, dz.v))); - dvdr_2.v = vec_fma(dvx_2.v, dx_2.v, - vec_fma(dvy_2.v, dy_2.v, vec_mul(dvz_2.v, dz_2.v))); + dvdr.v = vec_fma( + dvx.v, dx.v, + vec_fma(dvy.v, dy.v, vec_fma(dvz.v, dz.v, vec_mul(v_a2_Hubble.v, r2.v)))); + dvdr_2.v = vec_fma( + dvx_2.v, dx_2.v, + vec_fma(dvy_2.v, dy_2.v, + vec_fma(dvz_2.v, dz_2.v, vec_mul(v_a2_Hubble.v, r2_2.v)))); /* Compute the relative velocity. (This is 0 if the particles move away from * each other and negative otherwise) */ omega_ij.v = vec_fmin(dvdr.v, vec_setzero()); omega_ij_2.v = vec_fmin(dvdr_2.v, vec_setzero()); - mu_ij.v = - vec_mul(fac_mu.v, vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */ + mu_ij.v = vec_mul(v_fac_mu.v, + vec_mul(ri.v, omega_ij.v)); /* This is 0 or negative */ mu_ij_2.v = vec_mul( - fac_mu.v, vec_mul(ri_2.v, omega_ij_2.v)); /* This is 0 or negative */ + v_fac_mu.v, vec_mul(ri_2.v, omega_ij_2.v)); /* This is 0 or negative */ /* Compute signal velocity */ v_sig.v = vec_fnma(vec_set1(3.f), mu_ij.v, vec_add(ci.v, cj.v)); diff --git a/src/hydro/Gadget2/hydro_io.h b/src/hydro/Gadget2/hydro_io.h index 1d1f5ed453f249191b6853a3a699de847ed501f2..5c329e9d7297109c378e04f0e4931a0717e07278 100644 --- a/src/hydro/Gadget2/hydro_io.h +++ b/src/hydro/Gadget2/hydro_io.h @@ -89,7 +89,7 @@ void convert_part_pos(const struct engine* e, const struct part* p, void hydro_write_particles(const struct part* parts, struct io_props* list, int* num_fields) { - *num_fields = 10; + *num_fields = 9; /* List what we want to write */ list[0] = io_make_output_field_convert_part( @@ -104,14 +104,12 @@ void hydro_write_particles(const struct part* parts, struct io_props* list, "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, parts, entropy); 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] = + list[6] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho); - list[8] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1, + list[7] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1, UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, convert_u); - list[9] = io_make_output_field_convert_part( + list[8] = io_make_output_field_convert_part( "Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, parts, convert_P); #ifdef DEBUG_INTERACTIONS_SPH diff --git a/src/hydro/Minimal/hydro_io.h b/src/hydro/Minimal/hydro_io.h index 5922834e12f6dd74e951b46c2c93f975d7e8b82e..2ff70fd2f7e95344c17ba0f3237c3b2ee131752c 100644 --- a/src/hydro/Minimal/hydro_io.h +++ b/src/hydro/Minimal/hydro_io.h @@ -103,7 +103,7 @@ void convert_part_pos(const struct engine* e, const struct part* p, void hydro_write_particles(struct part* parts, struct io_props* list, int* num_fields) { - *num_fields = 10; + *num_fields = 9; /* List what we want to write */ list[0] = io_make_output_field_convert_part( @@ -118,13 +118,11 @@ void hydro_write_particles(struct part* parts, struct io_props* list, UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, 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] = + list[6] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho); - list[8] = io_make_output_field_convert_part( + list[7] = io_make_output_field_convert_part( "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, parts, convert_S); - list[9] = io_make_output_field_convert_part( + list[8] = io_make_output_field_convert_part( "Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, parts, convert_P); } diff --git a/src/hydro/PressureEntropy/hydro_io.h b/src/hydro/PressureEntropy/hydro_io.h index 454d999b6dcd604e25da4c513d902854c43c781b..ed8e0e45d240ea37ebfb7df6339afd454dc0e4c4 100644 --- a/src/hydro/PressureEntropy/hydro_io.h +++ b/src/hydro/PressureEntropy/hydro_io.h @@ -101,7 +101,7 @@ void convert_part_pos(const struct engine* e, const struct part* p, void hydro_write_particles(struct part* parts, struct io_props* list, int* num_fields) { - *num_fields = 11; + *num_fields = 10; /* List what we want to write */ list[0] = io_make_output_field_convert_part( @@ -116,17 +116,15 @@ void hydro_write_particles(struct part* parts, struct io_props* list, "Entropy", FLOAT, 1, UNIT_CONV_ENTROPY_PER_UNIT_MASS, parts, entropy); 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] = + list[6] = io_make_output_field("Density", FLOAT, 1, UNIT_CONV_DENSITY, parts, rho); - list[8] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1, + list[7] = io_make_output_field_convert_part("InternalEnergy", FLOAT, 1, UNIT_CONV_ENERGY_PER_UNIT_MASS, parts, convert_u); - list[9] = io_make_output_field_convert_part( + list[8] = io_make_output_field_convert_part( "Pressure", FLOAT, 1, UNIT_CONV_PRESSURE, parts, convert_P); - list[10] = io_make_output_field("WeightedDensity", FLOAT, 1, - UNIT_CONV_DENSITY, parts, rho_bar); + list[9] = io_make_output_field("WeightedDensity", FLOAT, 1, UNIT_CONV_DENSITY, + parts, rho_bar); } /** diff --git a/src/kernel_gravity.h b/src/kernel_gravity.h index 799bda85b0c69dd2757f47fb0225006adb6d1432..24d30136c8213dab6db424f2f459766c45ec6b1e 100644 --- a/src/kernel_gravity.h +++ b/src/kernel_gravity.h @@ -27,14 +27,35 @@ #include "minmax.h" /** - * @brief Computes the gravity softening function. + * @brief Computes the gravity softening function for potential. * * This functions assumes 0 < u < 1. * * @param u The ratio of the distance to the softening length $u = x/h$. * @param W (return) The value of the kernel function $W(x,h)$. */ -__attribute__((always_inline)) INLINE static void kernel_grav_eval( +__attribute__((always_inline)) INLINE static void kernel_grav_pot_eval( + float u, float *const W) { + + /* W(u) = 3u^7 - 15u^6 + 28u^5 - 21u^4 + 7u^2 - 3 */ + *W = 3.f * u - 15.f; + *W = *W * u + 28.f; + *W = *W * u - 21.f; + *W = *W * u; + *W = *W * u + 7.f; + *W = *W * u; + *W = *W * u - 3.f; +} + +/** + * @brief Computes the gravity softening function for forces. + * + * This functions assumes 0 < u < 1. + * + * @param u The ratio of the distance to the softening length $u = x/h$. + * @param W (return) The value of the kernel function $W(x,h)$. + */ +__attribute__((always_inline)) INLINE static void kernel_grav_force_eval( float u, float *const W) { /* W(u) = 21u^5 - 90u^4 + 140u^3 - 84u^2 + 14 */ @@ -48,14 +69,37 @@ __attribute__((always_inline)) INLINE static void kernel_grav_eval( #ifdef SWIFT_GRAVITY_FORCE_CHECKS /** - * @brief Computes the gravity softening function in double precision. + * @brief Computes the gravity softening function for potential in double + * precision. + * + * This functions assumes 0 < u < 1. + * + * @param u The ratio of the distance to the softening length $u = x/h$. + * @param W (return) The value of the kernel function $W(x,h)$. + */ +__attribute__((always_inline)) INLINE static void kernel_grav_eval_pot_double( + double u, double *const W) { + + /* W(u) = 3u^7 - 15u^6 + 28u^5 - 21u^4 + 7u^2 - 3 */ + *W = 3. * u - 15.; + *W = *W * u + 28.; + *W = *W * u - 21.; + *W = *W * u; + *W = *W * u + 7.; + *W = *W * u; + *W = *W * u - 3; +} + +/** + * @brief Computes the gravity softening function for forces in double + * precision. * * This functions assumes 0 < u < 1. * * @param u The ratio of the distance to the softening length $u = x/h$. * @param W (return) The value of the kernel function $W(x,h)$. */ -__attribute__((always_inline)) INLINE static void kernel_grav_eval_double( +__attribute__((always_inline)) INLINE static void kernel_grav_eval_force_double( double u, double *const W) { /* W(u) = 21u^5 - 90u^4 + 140u^3 - 84u^2 + 14 */ diff --git a/src/kernel_long_gravity.h b/src/kernel_long_gravity.h index 2e0097ded217769af832af049b6c00b1305ac339..77bc7c17e2ab8038a4860c74daebd19a2d785070 100644 --- a/src/kernel_long_gravity.h +++ b/src/kernel_long_gravity.h @@ -31,12 +31,41 @@ #include <math.h> /** - * @brief Computes the long-range correction term for the FFT calculation. + * @brief Computes the long-range correction term for the potential calculation + * coming from FFT. * * @param u The ratio of the distance to the FFT cell scale \f$u = r/r_s\f$. * @param W (return) The value of the kernel function. */ -__attribute__((always_inline)) INLINE static void kernel_long_grav_eval( +__attribute__((always_inline)) INLINE static void kernel_long_grav_pot_eval( + const float u, float *const W) { + +#ifdef GADGET2_LONG_RANGE_CORRECTION + + const float arg1 = u * 0.5f; + const float term1 = erfcf(arg1); + + *W = term1; +#else + + const float x = 2.f * u; + const float exp_x = expf(x); + const float alpha = 1.f / (1.f + exp_x); + + /* We want 2 - 2 exp(x) * alpha */ + *W = 1.f - alpha * exp_x; + *W *= 2.f; +#endif +} + +/** + * @brief Computes the long-range correction term for the force calculation + * coming from FFT. + * + * @param u The ratio of the distance to the FFT cell scale \f$u = r/r_s\f$. + * @param W (return) The value of the kernel function. + */ +__attribute__((always_inline)) INLINE static void kernel_long_grav_force_eval( float u, float *const W) { #ifdef GADGET2_LONG_RANGE_CORRECTION @@ -54,7 +83,7 @@ __attribute__((always_inline)) INLINE static void kernel_long_grav_eval( #else const float arg = 2.f * u; - const float exp_arg = good_approx_expf(arg); + const float exp_arg = expf(arg); const float term = 1.f / (1.f + exp_arg); *W = arg * exp_arg * term * term - exp_arg * term + 1.f; diff --git a/src/multipole.h b/src/multipole.h index d842081814b6ef7b6f490a1ba47a0152dd84d5a1..852976ad552b3ccf16f3e084612166c9212df2a6 100644 --- a/src/multipole.h +++ b/src/multipole.h @@ -2263,30 +2263,38 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb, /* Local accumulator */ double a_grav[3] = {0., 0., 0.}; + double pot = 0.; -#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 contributions */ + pot -= X_000(dx) * lb->F_000; - /* 0th order interaction */ +#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 + /* 1st order contributions */ a_grav[0] += X_000(dx) * lb->F_100; a_grav[1] += X_000(dx) * lb->F_010; a_grav[2] += X_000(dx) * lb->F_001; + + pot -= X_001(dx) * lb->F_001 + X_010(dx) * lb->F_010 + X_100(dx) * lb->F_100; #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 1 - /* 1st order interaction */ + /* 2nd order contributions */ a_grav[0] += X_100(dx) * lb->F_200 + X_010(dx) * lb->F_110 + X_001(dx) * lb->F_101; a_grav[1] += X_100(dx) * lb->F_110 + X_010(dx) * lb->F_020 + X_001(dx) * lb->F_011; a_grav[2] += X_100(dx) * lb->F_101 + X_010(dx) * lb->F_011 + X_001(dx) * lb->F_002; + + pot -= X_002(dx) * lb->F_002 + X_011(dx) * lb->F_011 + X_020(dx) * lb->F_020 + + X_101(dx) * lb->F_101 + X_110(dx) * lb->F_110 + X_200(dx) * lb->F_200; #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 2 - /* 2nd order interaction */ + /* 3rd order contributions */ a_grav[0] += X_200(dx) * lb->F_300 + X_020(dx) * lb->F_120 + X_002(dx) * lb->F_102; a_grav[0] += @@ -2299,10 +2307,15 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb, X_200(dx) * lb->F_201 + X_020(dx) * lb->F_021 + X_002(dx) * lb->F_003; a_grav[2] += X_110(dx) * lb->F_111 + X_101(dx) * lb->F_102 + X_011(dx) * lb->F_012; + + pot -= X_003(dx) * lb->F_003 + X_012(dx) * lb->F_012 + X_021(dx) * lb->F_021 + + X_030(dx) * lb->F_030 + X_102(dx) * lb->F_102 + X_111(dx) * lb->F_111 + + X_120(dx) * lb->F_120 + X_201(dx) * lb->F_201 + X_210(dx) * lb->F_210 + + X_300(dx) * lb->F_300; #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 3 - /* 3rd order contributions */ + /* 4th order contributions */ a_grav[0] += X_003(dx) * lb->F_103 + X_012(dx) * lb->F_112 + X_021(dx) * lb->F_121 + X_030(dx) * lb->F_130 + X_102(dx) * lb->F_202 + X_111(dx) * lb->F_211 + @@ -2319,10 +2332,16 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb, X_120(dx) * lb->F_121 + X_201(dx) * lb->F_202 + X_210(dx) * lb->F_211 + X_300(dx) * lb->F_301; + pot -= X_004(dx) * lb->F_004 + X_013(dx) * lb->F_013 + X_022(dx) * lb->F_022 + + X_031(dx) * lb->F_031 + X_040(dx) * lb->F_040 + X_103(dx) * lb->F_103 + + X_112(dx) * lb->F_112 + X_121(dx) * lb->F_121 + X_130(dx) * lb->F_130 + + X_202(dx) * lb->F_202 + X_211(dx) * lb->F_211 + X_220(dx) * lb->F_220 + + X_301(dx) * lb->F_301 + X_310(dx) * lb->F_310 + X_400(dx) * lb->F_400; + #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 4 - /* 4th order contributions */ + /* 5th order contributions */ a_grav[0] += X_004(dx) * lb->F_104 + X_013(dx) * lb->F_113 + X_022(dx) * lb->F_122 + X_031(dx) * lb->F_131 + X_040(dx) * lb->F_140 + X_103(dx) * lb->F_203 + @@ -2342,6 +2361,14 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb, X_202(dx) * lb->F_203 + X_211(dx) * lb->F_212 + X_220(dx) * lb->F_221 + X_301(dx) * lb->F_302 + X_310(dx) * lb->F_311 + X_400(dx) * lb->F_401; + pot -= X_005(dx) * lb->F_005 + X_014(dx) * lb->F_014 + X_023(dx) * lb->F_023 + + X_032(dx) * lb->F_032 + X_041(dx) * lb->F_041 + X_050(dx) * lb->F_050 + + X_104(dx) * lb->F_104 + X_113(dx) * lb->F_113 + X_122(dx) * lb->F_122 + + X_131(dx) * lb->F_131 + X_140(dx) * lb->F_140 + X_203(dx) * lb->F_203 + + X_212(dx) * lb->F_212 + X_221(dx) * lb->F_221 + X_230(dx) * lb->F_230 + + X_302(dx) * lb->F_302 + X_311(dx) * lb->F_311 + X_320(dx) * lb->F_320 + + X_401(dx) * lb->F_401 + X_410(dx) * lb->F_410 + X_500(dx) * lb->F_500; + #endif #if SELF_GRAVITY_MULTIPOLE_ORDER > 5 #error "Missing implementation for order >5" @@ -2351,52 +2378,8 @@ INLINE static void gravity_L2P(const struct grav_tensor *lb, gp->a_grav[0] += a_grav[0]; gp->a_grav[1] += a_grav[1]; gp->a_grav[2] += a_grav[2]; + gp->potential += pot; } - -INLINE static void gravity_M2P(const struct multipole *ma, - const struct gravity_props *props, - const double loc[3], struct gpart *gp) { - -#if SELF_GRAVITY_MULTIPOLE_ORDER > 0 - - const float eps2 = props->epsilon2; - const float eps_inv = props->epsilon_inv; - const float eps_inv3 = props->epsilon_inv3; - - /* Distance to the multipole */ - const float dx = gp->x[0] - loc[0]; - const float dy = gp->x[1] - loc[1]; - const float dz = gp->x[2] - loc[2]; - const float r2 = dx * dx + dy * dy + dz * dz; - - /* Get the inverse distance */ - const float r_inv = 1.f / sqrtf(r2); - - float f, W; - - if (r2 >= eps2) { - - /* Get Newtonian gravity */ - f = ma->M_000 * r_inv * r_inv * r_inv; - - } else { - - const float r = r2 * r_inv; - const float u = r * eps_inv; - - kernel_grav_eval(u, &W); - - /* Get softened gravity */ - f = ma->M_000 * eps_inv3 * W; - } - - gp->a_grav[0] -= f * dx; - gp->a_grav[1] -= f * dy; - gp->a_grav[2] -= f * dz; - -#endif -} - /** * @brief Checks whether a cell-cell interaction can be appromixated by a M-M * interaction using the distance and cell radius. diff --git a/src/potential.h b/src/potential.h index bcb3fd284021fba339ba494c90b81c91bd2ce72f..680d4e235fdf7a7666901f34a82f62feda4ae9bb 100644 --- a/src/potential.h +++ b/src/potential.h @@ -38,6 +38,10 @@ #include "./potential/disc_patch/potential.h" #elif defined(EXTERNAL_POTENTIAL_SINE_WAVE) #include "./potential/sine_wave/potential.h" +#elif defined(EXTERNAL_POTENTIAL_POINTMASS_RING) +#include "./potential/point_mass_ring/potential.h" +#elif defined(EXTERNAL_POTENTIAL_POINTMASS_SOFT) +#include "./potential/point_mass_softened/potential.h" #else #error "Invalid choice of external potential" #endif diff --git a/src/potential/point_mass_ring/potential.h b/src/potential/point_mass_ring/potential.h new file mode 100644 index 0000000000000000000000000000000000000000..ebf047ea7c1f946536300f976713893e66295c59 --- /dev/null +++ b/src/potential/point_mass_ring/potential.h @@ -0,0 +1,226 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Tom Theuns (tom.theuns@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_POTENTIAL_POINT_MASS_H +#define SWIFT_POTENTIAL_POINT_MASS_H + +/* Config parameters. */ +#include "../config.h" + +/* Some standard headers. */ +#include <float.h> +#include <math.h> + +/* Local includes. */ +#include "error.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "space.h" +#include "units.h" + +/** + * @brief External Potential Properties - Point mass case + */ +struct external_potential { + + /*! Position of the point mass */ + double x, y, z; + + /*! Mass */ + double mass; + + /*! Time-step condition pre-factor */ + float timestep_mult; +}; + +/** + * @brief Computes the time-step due to the acceleration from a point mass + * based on Hopkins' central potential stuff (i.e. using a 'softened' + * gravitational edge). + * + * We pass in the time for simulations where the potential evolves with time. + * + * @param time The current time. + * @param potential The properties of the external potential. + * @param phys_const The physical constants in internal units. + * @param g Pointer to the g-particle data. + */ +__attribute__((always_inline)) INLINE static float external_gravity_timestep( + double time, const struct external_potential* restrict potential, + const struct phys_const* restrict phys_const, + const struct gpart* restrict g) { + + const float G_newton = phys_const->const_newton_G; + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + const float r = sqrtf(dx * dx + dy * dy + dz * dz); + const float rinv = 1.f / r; + const float rinv2 = rinv * rinv; + const float rinv3 = rinv2 * rinv; + const float drdv = (g->x[0] - potential->x) * (g->v_full[0]) + + (g->x[1] - potential->y) * (g->v_full[1]) + + (g->x[2] - potential->z) * (g->v_full[2]); + float factor; + + if (r < 0.175) { + // We need to flatten gravity as in SWIFT the timestepping is different + // than in GIZMO. This causes particles to become trapped close to the + // central point mass! + factor = 0.; + } else if (r < 0.35) { + factor = (8.16 * r * r) + 1.f - r / 0.35; + } else if (r > 2.1) { + factor = 1.f + (r - 2.1) / 0.1; + } else { + // 0.35 > r > 2.1 + factor = 1.f; + } + + const float dota_x = G_newton * potential->mass * rinv3 * + (-g->v_full[0] + 3.f * rinv2 * drdv * dx) * factor; + const float dota_y = G_newton * potential->mass * rinv3 * + (-g->v_full[1] + 3.f * rinv2 * drdv * dy) * factor; + const float dota_z = G_newton * potential->mass * rinv3 * + (-g->v_full[2] + 3.f * rinv2 * drdv * dz) * factor; + + const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z; + const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] + + g->a_grav[2] * g->a_grav[2]; + + if (fabsf(dota_2) > 0.f) + return potential->timestep_mult * sqrtf(a_2 / dota_2); + else + return FLT_MAX; +} + +/** + * @brief Computes the gravitational acceleration of a particle due to a + * point mass + * + * Note that the accelerations are multiplied by Newton's G constant later + * on. + * + * We pass in the time for simulations where the potential evolves with time. + * + * @param time The current time. + * @param potential The proerties of the external potential. + * @param phys_const The physical constants in internal units. + * @param g Pointer to the g-particle data. + */ +__attribute__((always_inline)) INLINE static void external_gravity_acceleration( + double time, const struct external_potential* restrict potential, + const struct phys_const* restrict phys_const, struct gpart* restrict g) { + + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + const float r = sqrtf(dx * dx + dy * dy + dz * dz); + const float rinv = 1.f / r; + const float rinv2 = rinv * rinv; + const float rinv3 = rinv * rinv2; + float factor = 1.f; + + if (r < 0.175) { + // We need to flatten gravity as in SWIFT the timestepping is different + // than in GIZMO. This causes particles to become trapped close to the + // central point mass! + factor = 0.; + printf("Help me, I'm trapped! (r = %f id = %lld)\n", r, + g->id_or_neg_offset); + } else if (r < 0.35) { + factor = (8.16 * r * r) + 1.f - r / 0.35; + printf("Factor is %f, r is %f \n", factor, r); + } else if (r > 2.1) { + factor = 1.f + (r - 2.1) / 0.1; + } else { + // 0.35 > r > 2.1 + factor = 1.f; + } + + g->a_grav[0] += -potential->mass * dx * rinv3 * factor; + g->a_grav[1] += -potential->mass * dy * rinv3 * factor; + g->a_grav[2] += -potential->mass * dz * rinv3 * factor; +} + +/** + * @brief Computes the gravitational potential energy of a particle in a point + * mass potential. + * + * @param time The current time (unused here). + * @param potential The #external_potential used in the run. + * @param phys_const Physical constants in internal units. + * @param g Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +external_gravity_get_potential_energy( + double time, const struct external_potential* potential, + const struct phys_const* const phys_const, const struct gpart* g) { + + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + const float rinv = 1. / sqrtf(dx * dx + dy * dy + dz * dz); + return -phys_const->const_newton_G * potential->mass * rinv; +} + +/** + * @brief Initialises the external potential properties in the internal system + * of units. + * + * @param parameter_file The parsed parameter file + * @param phys_const Physical constants in internal units + * @param us The current internal system of units + * @param s The #space we run in. + * @param potential The external potential properties to initialize + */ +static INLINE void potential_init_backend( + const struct swift_params* parameter_file, + const struct phys_const* phys_const, const struct unit_system* us, + const struct space* s, struct external_potential* potential) { + + potential->x = + parser_get_param_double(parameter_file, "PointMassPotential:position_x"); + potential->y = + parser_get_param_double(parameter_file, "PointMassPotential:position_y"); + potential->z = + parser_get_param_double(parameter_file, "PointMassPotential:position_z"); + potential->mass = + parser_get_param_double(parameter_file, "PointMassPotential:mass"); + potential->timestep_mult = parser_get_param_float( + parameter_file, "PointMassPotential:timestep_mult"); +} + +/** + * @brief Prints the properties of the external potential to stdout. + * + * @param potential The external potential properties. + */ +static INLINE void potential_print_backend( + const struct external_potential* potential) { + + message( + "External potential is 'Point mass' with properties (x,y,z) = (%e, %e, " + "%e), M = %e timestep multiplier = %e.", + potential->x, potential->y, potential->z, potential->mass, + potential->timestep_mult); +} + +#endif /* SWIFT_POTENTIAL_POINT_MASS_H */ diff --git a/src/potential/point_mass_softened/potential.h b/src/potential/point_mass_softened/potential.h new file mode 100644 index 0000000000000000000000000000000000000000..83a79ea3cddbff37fdd70d58d70afcaf46f7bc0e --- /dev/null +++ b/src/potential/point_mass_softened/potential.h @@ -0,0 +1,215 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (c) 2016 Tom Theuns (tom.theuns@durham.ac.uk) + * Matthieu Schaller (matthieu.schaller@durham.ac.uk) + * Josh Borrow (joshua.borrow@durham.ac.uk) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + ******************************************************************************/ +#ifndef SWIFT_POTENTIAL_POINT_MASS_H +#define SWIFT_POTENTIAL_POINT_MASS_H + +/* Config parameters. */ +#include "../config.h" + +/* Some standard headers. */ +#include <float.h> +#include <math.h> + +/* Local includes. */ +#include "error.h" +#include "parser.h" +#include "part.h" +#include "physical_constants.h" +#include "space.h" +#include "units.h" + +/** + * @brief External Potential Properties - Point mass case + * + * This file contains the softened central potential. This has a 'softening + * factor' that prevents the potential from becoming singular at the centre + * of the potential, i.e. it has the form + * + * $$ \phi \propto 1/(r^2 + \eta^2) $$ + * + * where $\eta$ is some small value. + */ +struct external_potential { + + /*! Position of the point mass */ + double x, y, z; + + /*! Mass */ + double mass; + + /*! Time-step condition pre-factor */ + float timestep_mult; + + /*! Potential softening */ + float softening; +}; + +/** + * @brief Computes the time-step due to the acceleration from a point mass + * + * We pass in the time for simulations where the potential evolves with time. + * + * @param time The current time. + * @param potential The properties of the externa potential. + * @param phys_const The physical constants in internal units. + * @param g Pointer to the g-particle data. + */ +__attribute__((always_inline)) INLINE static float external_gravity_timestep( + double time, const struct external_potential* restrict potential, + const struct phys_const* restrict phys_const, + const struct gpart* restrict g) { + + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + + const float softening2 = potential->softening * potential->softening; + const float r2 = dx * dx + dy * dy + dz * dz; + const float rinv = 1.f / sqrtf(r2); + const float rinv2_softened = 1.f / (r2 + softening2); + + /* G * M / (r^2 + eta^2)^{3/2} */ + const float GMr32 = phys_const->const_newton_G * potential->mass * + sqrtf(rinv2_softened * rinv2_softened * rinv2_softened); + + /* This is actually dr dot v */ + const float drdv = + (dx) * (g->v_full[0]) + (dy) * (g->v_full[1]) + (dz) * (g->v_full[2]); + + /* We want da/dt */ + /* da/dt = -GM/(r^2 + \eta^2)^{3/2} * + * (\vec{v} - 3 \vec{r} \cdot \vec{v} \hat{r} / + * (r^2 + \eta^2)) */ + const float dota_x = + GMr32 * (3.f * drdv * dx * rinv2_softened * rinv - g->v_full[0]); + const float dota_y = + GMr32 * (3.f * drdv * dy * rinv2_softened * rinv - g->v_full[0]); + const float dota_z = + GMr32 * (3.f * drdv * dz * rinv2_softened * rinv - g->v_full[0]); + + const float dota_2 = dota_x * dota_x + dota_y * dota_y + dota_z * dota_z; + const float a_2 = g->a_grav[0] * g->a_grav[0] + g->a_grav[1] * g->a_grav[1] + + g->a_grav[2] * g->a_grav[2]; + + if (fabsf(dota_2) > 0.f) + return potential->timestep_mult * sqrtf(a_2 / dota_2); + else + return FLT_MAX; +} + +/** + * @brief Computes the gravitational acceleration of a particle due to a + * point mass + * + * Note that the accelerations are multiplied by Newton's G constant later + * on. + * + * We pass in the time for simulations where the potential evolves with time. + * + * @param time The current time. + * @param potential The proerties of the external potential. + * @param phys_const The physical constants in internal units. + * @param g Pointer to the g-particle data. + */ +__attribute__((always_inline)) INLINE static void external_gravity_acceleration( + double time, const struct external_potential* restrict potential, + const struct phys_const* restrict phys_const, struct gpart* restrict g) { + + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + const float rinv = 1.f / sqrtf(dx * dx + dy * dy + dz * dz + + potential->softening * potential->softening); + const float rinv3 = rinv * rinv * rinv; + + g->a_grav[0] += -potential->mass * dx * rinv3; + g->a_grav[1] += -potential->mass * dy * rinv3; + g->a_grav[2] += -potential->mass * dz * rinv3; +} + +/** + * @brief Computes the gravitational potential energy of a particle in a point + * mass potential. + * + * @param time The current time (unused here). + * @param potential The #external_potential used in the run. + * @param phys_const Physical constants in internal units. + * @param g Pointer to the particle data. + */ +__attribute__((always_inline)) INLINE static float +external_gravity_get_potential_energy( + double time, const struct external_potential* potential, + const struct phys_const* const phys_const, const struct gpart* g) { + + const float dx = g->x[0] - potential->x; + const float dy = g->x[1] - potential->y; + const float dz = g->x[2] - potential->z; + const float rinv = 1. / sqrtf(dx * dx + dy * dy + dz * dz + + potential->softening * potential->softening); + + return -phys_const->const_newton_G * potential->mass * rinv; +} + +/** + * @brief Initialises the external potential properties in the internal system + * of units. + * + * @param parameter_file The parsed parameter file + * @param phys_const Physical constants in internal units + * @param us The current internal system of units + * @param s The #space we run in. + * @param potential The external potential properties to initialize + */ +static INLINE void potential_init_backend( + const struct swift_params* parameter_file, + const struct phys_const* phys_const, const struct unit_system* us, + const struct space* s, struct external_potential* potential) { + + potential->x = + parser_get_param_double(parameter_file, "PointMassPotential:position_x"); + potential->y = + parser_get_param_double(parameter_file, "PointMassPotential:position_y"); + potential->z = + parser_get_param_double(parameter_file, "PointMassPotential:position_z"); + potential->mass = + parser_get_param_double(parameter_file, "PointMassPotential:mass"); + potential->timestep_mult = parser_get_param_float( + parameter_file, "PointMassPotential:timestep_mult"); + potential->softening = + parser_get_param_float(parameter_file, "PointMassPotential:softening"); +} + +/** + * @brief Prints the properties of the external potential to stdout. + * + * @param potential The external potential properties. + */ +static INLINE void potential_print_backend( + const struct external_potential* potential) { + + message( + "External potential is 'Point mass' with properties (x,y,z) = (%e, %e, " + "%e), M = %e timestep multiplier = %e, softening = %e.", + potential->x, potential->y, potential->z, potential->mass, + potential->timestep_mult, potential->softening); +} + +#endif /* SWIFT_POTENTIAL_POINT_MASS_H */ diff --git a/src/runner_doiact.h b/src/runner_doiact.h index 2ea4cbf24576e50607379d8659106a57a1f80356..2987d205e5005d4618c087bfe7f18a0b3c12fef5 100644 --- a/src/runner_doiact.h +++ b/src/runner_doiact.h @@ -1161,7 +1161,8 @@ void DOPAIR1_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) { p->x[1] * runner_shift[sid][1] + p->x[2] * runner_shift[sid][2]; if (fabsf(d - sort_i[pid].d) - ci->dx_max_sort > - 1.0e-4 * max(fabsf(d), ci->dx_max_sort_old)) + 1.0e-4 * max(fabsf(d), ci->dx_max_sort_old) && + fabsf(d - sort_i[pid].d) - ci->dx_max_sort > ci->width[0] * 1.0e-10) error( "particle shift diff exceeds dx_max_sort in cell ci. ci->nodeID=%d " "cj->nodeID=%d d=%e sort_i[pid].d=%e ci->dx_max_sort=%e " @@ -1174,8 +1175,9 @@ void DOPAIR1_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) { const float d = p->x[0] * runner_shift[sid][0] + p->x[1] * runner_shift[sid][1] + p->x[2] * runner_shift[sid][2]; - if (fabsf(d - sort_j[pjd].d) - cj->dx_max_sort > - 1.0e-4 * max(fabsf(d), cj->dx_max_sort_old)) + if ((fabsf(d - sort_j[pjd].d) - cj->dx_max_sort) > + 1.0e-4 * max(fabsf(d), cj->dx_max_sort_old) && + (fabsf(d - sort_j[pjd].d) - cj->dx_max_sort) > cj->width[0] * 1.0e-10) error( "particle shift diff exceeds dx_max_sort in cell cj. cj->nodeID=%d " "ci->nodeID=%d d=%e sort_j[pjd].d=%e cj->dx_max_sort=%e " @@ -1672,7 +1674,8 @@ void DOPAIR2_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) { p->x[1] * runner_shift[sid][1] + p->x[2] * runner_shift[sid][2]; if (fabsf(d - sort_i[pid].d) - ci->dx_max_sort > - 1.0e-4 * max(fabsf(d), ci->dx_max_sort_old)) + 1.0e-4 * max(fabsf(d), ci->dx_max_sort_old) && + fabsf(d - sort_i[pid].d) - ci->dx_max_sort > ci->width[0] * 1.0e-10) error( "particle shift diff exceeds dx_max_sort in cell ci. ci->nodeID=%d " "cj->nodeID=%d d=%e sort_i[pid].d=%e ci->dx_max_sort=%e " @@ -1686,7 +1689,8 @@ void DOPAIR2_BRANCH(struct runner *r, struct cell *ci, struct cell *cj) { p->x[1] * runner_shift[sid][1] + p->x[2] * runner_shift[sid][2]; if (fabsf(d - sort_j[pjd].d) - cj->dx_max_sort > - 1.0e-4 * max(fabsf(d), cj->dx_max_sort_old)) + 1.0e-4 * max(fabsf(d), cj->dx_max_sort_old) && + fabsf(d - sort_j[pjd].d) - cj->dx_max_sort > cj->width[0] * 1.0e-10) error( "particle shift diff exceeds dx_max_sort in cell cj. cj->nodeID=%d " "ci->nodeID=%d d=%e sort_j[pjd].d=%e cj->dx_max_sort=%e " diff --git a/src/runner_doiact_grav.h b/src/runner_doiact_grav.h index 0bc7b851c05e94974ef6337ba3d8df5e5420c9a1..cec450d84449d2b8cbb6790fcf9a596bb24649be 100644 --- a/src/runner_doiact_grav.h +++ b/src/runner_doiact_grav.h @@ -21,10 +21,12 @@ #define SWIFT_RUNNER_DOIACT_GRAV_H /* Includes. */ +#include "active.h" #include "cell.h" #include "gravity.h" #include "inline.h" #include "part.h" +#include "timers.h" /** * @brief Recursively propagate the multipoles down the tree by applying the @@ -34,7 +36,8 @@ * @param c The #cell we are working on. * @param timer Are we timing this ? */ -void runner_do_grav_down(struct runner *r, struct cell *c, int timer) { +static INLINE void runner_do_grav_down(struct runner *r, struct cell *c, + int timer) { /* Some constants */ const struct engine *e = r->e; @@ -125,8 +128,9 @@ void runner_do_grav_down(struct runner *r, struct cell *c, int timer) { * @param ci The #cell with field tensor to interact. * @param cj The #cell with the multipole. */ -void runner_dopair_grav_mm(const struct runner *r, struct cell *restrict ci, - struct cell *restrict cj) { +static INLINE void runner_dopair_grav_mm(const struct runner *r, + struct cell *restrict ci, + struct cell *restrict cj) { /* Some constants */ const struct engine *e = r->e; @@ -204,8 +208,8 @@ static INLINE void runner_dopair_grav_pp_full(const struct engine *e, const float h_inv_i = 1.f / h_i; const float h_inv3_i = h_inv_i * h_inv_i * h_inv_i; - /* Local accumulators for the acceleration */ - float a_x = 0.f, a_y = 0.f, a_z = 0.f; + /* Local accumulators for the acceleration and potential */ + float a_x = 0.f, a_y = 0.f, a_z = 0.f, pot = 0.f; /* Make the compiler understand we are in happy vectorization land */ swift_align_information(float, cj_cache->x, SWIFT_CACHE_ALIGNMENT); @@ -240,13 +244,15 @@ static INLINE void runner_dopair_grav_pp_full(const struct engine *e, #endif /* Interact! */ - float f_ij; - runner_iact_grav_pp_full(r2, h2_i, h_inv_i, h_inv3_i, mass_j, &f_ij); + float f_ij, pot_ij; + runner_iact_grav_pp_full(r2, h2_i, h_inv_i, h_inv3_i, mass_j, &f_ij, + &pot_ij); /* Store it back */ a_x -= f_ij * dx; a_y -= f_ij * dy; a_z -= f_ij * dz; + pot += pot_ij; #ifdef SWIFT_DEBUG_CHECKS /* Update the interaction counter if it's not a padded gpart */ @@ -258,6 +264,7 @@ static INLINE void runner_dopair_grav_pp_full(const struct engine *e, ci_cache->a_x[pid] = a_x; ci_cache->a_y[pid] = a_y; ci_cache->a_z[pid] = a_z; + ci_cache->pot[pid] = pot; } TIMER_TOC(timer_dopair_grav_pp); @@ -295,8 +302,8 @@ static INLINE void runner_dopair_grav_pp_truncated( const float h_inv_i = 1.f / h_i; const float h_inv3_i = h_inv_i * h_inv_i * h_inv_i; - /* Local accumulators for the acceleration */ - float a_x = 0.f, a_y = 0.f, a_z = 0.f; + /* Local accumulators for the acceleration and potential */ + float a_x = 0.f, a_y = 0.f, a_z = 0.f, pot = 0.f; /* Make the compiler understand we are in happy vectorization land */ swift_align_information(float, cj_cache->x, SWIFT_CACHE_ALIGNMENT); @@ -331,14 +338,15 @@ static INLINE void runner_dopair_grav_pp_truncated( #endif /* Interact! */ - float f_ij; + float f_ij, pot_ij; runner_iact_grav_pp_truncated(r2, h2_i, h_inv_i, h_inv3_i, mass_j, - rlr_inv, &f_ij); + rlr_inv, &f_ij, &pot_ij); /* Store it back */ a_x -= f_ij * dx; a_y -= f_ij * dy; a_z -= f_ij * dz; + pot += pot_ij; #ifdef SWIFT_DEBUG_CHECKS /* Update the interaction counter if it's not a padded gpart */ @@ -350,6 +358,7 @@ static INLINE void runner_dopair_grav_pp_truncated( ci_cache->a_x[pid] = a_x; ci_cache->a_y[pid] = a_y; ci_cache->a_z[pid] = a_z; + ci_cache->pot[pid] = pot; } TIMER_TOC(timer_dopair_grav_pp); @@ -372,6 +381,7 @@ static INLINE void runner_dopair_grav_pm( swift_declare_aligned_ptr(float, a_x, ci_cache->a_x, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(float, a_y, ci_cache->a_y, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(float, a_z, ci_cache->a_z, SWIFT_CACHE_ALIGNMENT); + swift_declare_aligned_ptr(float, pot, ci_cache->pot, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(int, active, ci_cache->active, SWIFT_CACHE_ALIGNMENT); swift_declare_aligned_ptr(int, use_mpole, ci_cache->use_mpole, @@ -407,14 +417,15 @@ static INLINE void runner_dopair_grav_pm( const float r2 = dx * dx + dy * dy + dz * dz; /* Interact! */ - float f_x, f_y, f_z; - runner_iact_grav_pm(dx, dy, dz, r2, h_i, h_inv_i, multi_j, &f_x, &f_y, - &f_z); + float f_x, f_y, f_z, pot_ij; + runner_iact_grav_pm(dx, dy, dz, r2, h_i, h_inv_i, multi_j, &f_x, &f_y, &f_z, + &pot_ij); /* Store it back */ a_x[pid] = f_x; a_y[pid] = f_y; a_z[pid] = f_z; + pot[pid] = pot_ij; #ifdef SWIFT_DEBUG_CHECKS /* Update the interaction counter */ @@ -434,7 +445,8 @@ static INLINE void runner_dopair_grav_pm( * @param ci The first #cell. * @param cj The other #cell. */ -void runner_dopair_grav_pp(struct runner *r, struct cell *ci, struct cell *cj) { +static INLINE void runner_dopair_grav_pp(struct runner *r, struct cell *ci, + struct cell *cj) { const struct engine *e = r->e; @@ -635,7 +647,8 @@ void runner_dopair_grav_pp(struct runner *r, struct cell *ci, struct cell *cj) { * * @todo Use a local cache for the particles. */ -void runner_doself_grav_pp_full(struct runner *r, struct cell *c) { +static INLINE void runner_doself_grav_pp_full(struct runner *r, + struct cell *c) { /* Some constants */ const struct engine *const e = r->e; @@ -683,7 +696,7 @@ void runner_doself_grav_pp_full(struct runner *r, struct cell *c) { const float h_inv3_i = h_inv_i * h_inv_i * h_inv_i; /* Local accumulators for the acceleration */ - float a_x = 0.f, a_y = 0.f, a_z = 0.f; + float a_x = 0.f, a_y = 0.f, a_z = 0.f, pot = 0.f; /* Make the compiler understand we are in happy vectorization land */ swift_align_information(float, ci_cache->x, SWIFT_CACHE_ALIGNMENT); @@ -721,13 +734,15 @@ void runner_doself_grav_pp_full(struct runner *r, struct cell *c) { #endif /* Interact! */ - float f_ij; - runner_iact_grav_pp_full(r2, h2_i, h_inv_i, h_inv3_i, mass_j, &f_ij); + float f_ij, pot_ij; + runner_iact_grav_pp_full(r2, h2_i, h_inv_i, h_inv3_i, mass_j, &f_ij, + &pot_ij); /* Store it back */ a_x -= f_ij * dx; a_y -= f_ij * dy; a_z -= f_ij * dz; + pot += pot_ij; #ifdef SWIFT_DEBUG_CHECKS /* Update the interaction counter if it's not a padded gpart */ @@ -739,6 +754,7 @@ void runner_doself_grav_pp_full(struct runner *r, struct cell *c) { ci_cache->a_x[pid] = a_x; ci_cache->a_y[pid] = a_y; ci_cache->a_z[pid] = a_z; + ci_cache->pot[pid] = pot; } /* Write back to the particles */ @@ -754,7 +770,8 @@ void runner_doself_grav_pp_full(struct runner *r, struct cell *c) { * * @todo Use a local cache for the particles. */ -void runner_doself_grav_pp_truncated(struct runner *r, struct cell *c) { +static INLINE void runner_doself_grav_pp_truncated(struct runner *r, + struct cell *c) { /* Some constants */ const struct engine *const e = r->e; @@ -808,8 +825,8 @@ void runner_doself_grav_pp_truncated(struct runner *r, struct cell *c) { const float h_inv_i = 1.f / h_i; const float h_inv3_i = h_inv_i * h_inv_i * h_inv_i; - /* Local accumulators for the acceleration */ - float a_x = 0.f, a_y = 0.f, a_z = 0.f; + /* Local accumulators for the acceleration and potential */ + float a_x = 0.f, a_y = 0.f, a_z = 0.f, pot = 0.f; /* Make the compiler understand we are in happy vectorization land */ swift_align_information(float, ci_cache->x, SWIFT_CACHE_ALIGNMENT); @@ -847,14 +864,15 @@ void runner_doself_grav_pp_truncated(struct runner *r, struct cell *c) { #endif /* Interact! */ - float f_ij; + float f_ij, pot_ij; runner_iact_grav_pp_truncated(r2, h2_i, h_inv_i, h_inv3_i, mass_j, - rlr_inv, &f_ij); + rlr_inv, &f_ij, &pot_ij); /* Store it back */ a_x -= f_ij * dx; a_y -= f_ij * dy; a_z -= f_ij * dz; + pot += pot_ij; #ifdef SWIFT_DEBUG_CHECKS /* Update the interaction counter if it's not a padded gpart */ @@ -866,6 +884,7 @@ void runner_doself_grav_pp_truncated(struct runner *r, struct cell *c) { ci_cache->a_x[pid] = a_x; ci_cache->a_y[pid] = a_y; ci_cache->a_z[pid] = a_z; + ci_cache->pot[pid] = pot; } /* Write back to the particles */ @@ -879,7 +898,7 @@ void runner_doself_grav_pp_truncated(struct runner *r, struct cell *c) { * @param r The #runner. * @param c The #cell. */ -void runner_doself_grav_pp(struct runner *r, struct cell *c) { +static INLINE void runner_doself_grav_pp(struct runner *r, struct cell *c) { /* Some properties of the space */ const struct engine *e = r->e; @@ -934,8 +953,8 @@ void runner_doself_grav_pp(struct runner *r, struct cell *c) { * * @todo Use a local cache for the particles. */ -void runner_dopair_grav(struct runner *r, struct cell *ci, struct cell *cj, - int gettimer) { +static INLINE void runner_dopair_grav(struct runner *r, struct cell *ci, + struct cell *cj, int gettimer) { /* Some constants */ const struct engine *e = r->e; @@ -1086,7 +1105,8 @@ void runner_dopair_grav(struct runner *r, struct cell *ci, struct cell *cj, * * @todo Use a local cache for the particles. */ -void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) { +static INLINE void runner_doself_grav(struct runner *r, struct cell *c, + int gettimer) { /* Some constants */ const struct engine *e = r->e; @@ -1137,7 +1157,8 @@ void runner_doself_grav(struct runner *r, struct cell *c, int gettimer) { * @param ci The #cell of interest. * @param timer Are we timing this ? */ -void runner_do_grav_long_range(struct runner *r, struct cell *ci, int timer) { +static INLINE void runner_do_grav_long_range(struct runner *r, struct cell *ci, + int timer) { /* Some constants */ const struct engine *e = r->e; diff --git a/src/runner_doiact_vec.c b/src/runner_doiact_vec.c index aa6e6dc3c15f1846462b900076cf131e726cd883..0daea0cf8a8d868ccb14f7d9b27f04aca5a677c8 100644 --- a/src/runner_doiact_vec.c +++ b/src/runner_doiact_vec.c @@ -1111,6 +1111,7 @@ void runner_doself2_force_vec(struct runner *r, struct cell *restrict c) { #if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH) const struct engine *e = r->e; + const struct cosmology *restrict cosmo = e->cosmology; const timebin_t max_active_bin = e->max_active_bin; struct part *restrict parts = c->parts; const int count = c->count; @@ -1139,6 +1140,10 @@ void runner_doself2_force_vec(struct runner *r, struct cell *restrict c) { /* Read the particles from the cell and store them locally in the cache. */ cache_read_force_particles(c, cell_cache); + /* Cosmological terms */ + const float a = cosmo->a; + const float H = cosmo->H; + /* Loop over the particles in the cell. */ for (int pid = 0; pid < count; pid++) { @@ -1262,7 +1267,7 @@ void runner_doself2_force_vec(struct runner *r, struct cell *restrict c) { &cell_cache->vy[pjd], &cell_cache->vz[pjd], &cell_cache->rho[pjd], &cell_cache->grad_h[pjd], &cell_cache->pOrho2[pjd], &cell_cache->balsara[pjd], &cell_cache->soundspeed[pjd], - &cell_cache->m[pjd], v_hi_inv, v_hj_inv, &v_a_hydro_xSum, + &cell_cache->m[pjd], v_hi_inv, v_hj_inv, a, H, &v_a_hydro_xSum, &v_a_hydro_ySum, &v_a_hydro_zSum, &v_h_dtSum, &v_sigSum, &v_entropy_dtSum, v_doi_mask); } @@ -1988,6 +1993,7 @@ void runner_dopair2_force_vec(struct runner *r, struct cell *ci, #if defined(WITH_VECTORIZATION) && defined(GADGET2_SPH) const struct engine *restrict e = r->e; + const struct cosmology *restrict cosmo = e->cosmology; const timebin_t max_active_bin = e->max_active_bin; TIMER_TIC; @@ -2019,6 +2025,10 @@ void runner_dopair2_force_vec(struct runner *r, struct cell *ci, const int active_ci = cell_is_active_hydro(ci, e) && ci_local; const int active_cj = cell_is_active_hydro(cj, e) && cj_local; + /* Cosmological terms */ + const float a = cosmo->a; + const float H = cosmo->H; + #ifdef SWIFT_DEBUG_CHECKS /* Check that particles have been drifted to the current time */ for (int pid = 0; pid < count_i; pid++) @@ -2213,7 +2223,7 @@ void runner_dopair2_force_vec(struct runner *r, struct cell *ci, &cj_cache->grad_h[cj_cache_idx], &cj_cache->pOrho2[cj_cache_idx], &cj_cache->balsara[cj_cache_idx], &cj_cache->soundspeed[cj_cache_idx], &cj_cache->m[cj_cache_idx], - v_hi_inv, v_hj_inv, &v_a_hydro_xSum, &v_a_hydro_ySum, + v_hi_inv, v_hj_inv, a, H, &v_a_hydro_xSum, &v_a_hydro_ySum, &v_a_hydro_zSum, &v_h_dtSum, &v_sigSum, &v_entropy_dtSum, v_doi_mask); } @@ -2349,7 +2359,7 @@ void runner_dopair2_force_vec(struct runner *r, struct cell *ci, &ci_cache->grad_h[ci_cache_idx], &ci_cache->pOrho2[ci_cache_idx], &ci_cache->balsara[ci_cache_idx], &ci_cache->soundspeed[ci_cache_idx], &ci_cache->m[ci_cache_idx], - v_hj_inv, v_hi_inv, &v_a_hydro_xSum, &v_a_hydro_ySum, + v_hj_inv, v_hi_inv, a, H, &v_a_hydro_xSum, &v_a_hydro_ySum, &v_a_hydro_zSum, &v_h_dtSum, &v_sigSum, &v_entropy_dtSum, v_doj_mask); } diff --git a/src/scheduler.c b/src/scheduler.c index 2c0406953581f55a1086ef313da8a389c18a9bd5..756f70c6b02b9e58d0a8a7f1e2b1ad7f11af4ca5 100644 --- a/src/scheduler.c +++ b/src/scheduler.c @@ -1435,7 +1435,7 @@ void scheduler_enqueue(struct scheduler *s, struct task *t) { switch (t->type) { case task_type_self: case task_type_sub_self: - if (t->subtype == task_subtype_grav) + if (t->subtype == task_subtype_grav || t->subtype == task_subtype_external_grav) qid = t->ci->super_gravity->owner; else qid = t->ci->super_hydro->owner; diff --git a/src/serial_io.c b/src/serial_io.c index 3e62bd7fda99ed7703a4fe67340d5a03e5dc45b8..aa8d126e4c6f3e19aed7d205c7ced39f7eded6f8 100644 --- a/src/serial_io.c +++ b/src/serial_io.c @@ -197,13 +197,13 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName, rank = 2; shape[0] = N_total; shape[1] = props.dimension; - chunk_shape[0] = 1 << 16; /* Just a guess...*/ + chunk_shape[0] = 1 << 20; /* Just a guess...*/ chunk_shape[1] = props.dimension; } else { rank = 1; shape[0] = N_total; shape[1] = 0; - chunk_shape[0] = 1 << 16; /* Just a guess...*/ + chunk_shape[0] = 1 << 20; /* Just a guess...*/ chunk_shape[1] = 0; } @@ -211,7 +211,7 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName, if (chunk_shape[0] > N_total) chunk_shape[0] = N_total; /* Change shape of data space */ - hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, NULL); + hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, shape); if (h_err < 0) { error("Error while changing data space shape for field '%s'.", props.name); } @@ -228,11 +228,15 @@ void prepareArray(const struct engine* e, hid_t grp, char* fileName, /* Impose data compression */ if (e->snapshotCompression > 0) { + h_err = H5Pset_shuffle(h_prop); + if (h_err < 0) + error("Error while setting shuffling options for field '%s'.", + props.name); + h_err = H5Pset_deflate(h_prop, e->snapshotCompression); - if (h_err < 0) { + if (h_err < 0) error("Error while setting compression options for field '%s'.", props.name); - } } /* Create dataset */ diff --git a/src/single_io.c b/src/single_io.c index caa39e4a7cf583b210d3e9322abcd53c332cfe70..0fecd724908f7ce2779526792ce8512b9236d58e 100644 --- a/src/single_io.c +++ b/src/single_io.c @@ -204,13 +204,13 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName, rank = 2; shape[0] = N; shape[1] = props.dimension; - chunk_shape[0] = 1 << 16; /* Just a guess...*/ + chunk_shape[0] = 1 << 20; /* Just a guess...*/ chunk_shape[1] = props.dimension; } else { rank = 1; shape[0] = N; shape[1] = 0; - chunk_shape[0] = 1 << 16; /* Just a guess...*/ + chunk_shape[0] = 1 << 20; /* Just a guess...*/ chunk_shape[1] = 0; } @@ -218,7 +218,7 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName, if (chunk_shape[0] > N) chunk_shape[0] = N; /* Change shape of data space */ - hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, NULL); + hid_t h_err = H5Sset_extent_simple(h_space, rank, shape, shape); if (h_err < 0) { error("Error while changing data space shape for field '%s'.", props.name); } @@ -235,11 +235,15 @@ void writeArray(const struct engine* e, hid_t grp, char* fileName, /* Impose data compression */ if (e->snapshotCompression > 0) { + h_err = H5Pset_shuffle(h_prop); + if (h_err < 0) + error("Error while setting shuffling options for field '%s'.", + props.name); + h_err = H5Pset_deflate(h_prop, e->snapshotCompression); - if (h_err < 0) { + if (h_err < 0) error("Error while setting compression options for field '%s'.", props.name); - } } /* Create dataset */ diff --git a/src/statistics.c b/src/statistics.c index ed3197fbd0538394bb1c40d834c16e174a410068..8d8afae0d1441927e001d7d58a3ee5ff0399f5ed 100644 --- a/src/statistics.c +++ b/src/statistics.c @@ -191,7 +191,7 @@ void stats_collect_part_mapper(void *map_data, int nr_parts, void *extra_data) { stats.E_int += m * u_inter; stats.E_rad += cooling_get_radiated_energy(xp); if (gp != NULL) { - stats.E_pot_self += m * gravity_get_potential(gp) * a_inv; + stats.E_pot_self += 0.5f * m * gravity_get_physical_potential(gp, cosmo); stats.E_pot_ext += m * external_gravity_get_potential_energy( time, potential, phys_const, gp); } @@ -292,7 +292,7 @@ void stats_collect_gpart_mapper(void *map_data, int nr_gparts, /* Collect energies. */ stats.E_kin += 0.5f * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) * a_inv2; /* 1/2 m a^2 \dot{r}^2 */ - stats.E_pot_self += m * gravity_get_potential(gp) * a_inv; + stats.E_pot_self += 0.5f * m * gravity_get_physical_potential(gp, cosmo); stats.E_pot_ext += m * external_gravity_get_potential_energy( time, potential, phys_const, gp); } diff --git a/src/vector_power.h b/src/vector_power.h index 6e8377586fdbc072e8f894c0805f43c122f8c54f..ffaa3b3f6cea69626462a9b5f26f94827c6af556 100644 --- a/src/vector_power.h +++ b/src/vector_power.h @@ -415,273 +415,273 @@ __attribute__((always_inline)) INLINE static double X_112(const double v[3]) { /***************************/ /** -* @brief Compute \f$ \frac{1}{(0,0,5)!}\vec{v}^{(0,0,5)} \f$. -* -* Note \f$ \vec{v}^{(0,0,5)} = v_z^5 \f$ -* and \f$ \frac{1}{(0,0,5)!} = 1/(0!*0!*5!) = 1/120 = 8.333333e-03 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,0,5)!}\vec{v}^{(0,0,5)} \f$. + * + * Note \f$ \vec{v}^{(0,0,5)} = v_z^5 \f$ + * and \f$ \frac{1}{(0,0,5)!} = 1/(0!*0!*5!) = 1/120 = 8.333333e-03 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_005(const double v[3]) { return 8.333333333333333e-03 * v[2] * v[2] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(0,1,4)!}\vec{v}^{(0,1,4)} \f$. -* -* Note \f$ \vec{v}^{(0,1,4)} = v_y^1 v_z^4 \f$ -* and \f$ \frac{1}{(0,1,4)!} = 1/(0!*1!*4!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,1,4)!}\vec{v}^{(0,1,4)} \f$. + * + * Note \f$ \vec{v}^{(0,1,4)} = v_y^1 v_z^4 \f$ + * and \f$ \frac{1}{(0,1,4)!} = 1/(0!*1!*4!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_014(const double v[3]) { return 4.166666666666666e-02 * v[1] * v[2] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(0,2,3)!}\vec{v}^{(0,2,3)} \f$. -* -* Note \f$ \vec{v}^{(0,2,3)} = v_y^2 v_z^3 \f$ -* and \f$ \frac{1}{(0,2,3)!} = 1/(0!*2!*3!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,2,3)!}\vec{v}^{(0,2,3)} \f$. + * + * Note \f$ \vec{v}^{(0,2,3)} = v_y^2 v_z^3 \f$ + * and \f$ \frac{1}{(0,2,3)!} = 1/(0!*2!*3!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_023(const double v[3]) { return 8.333333333333333e-02 * v[1] * v[1] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(0,3,2)!}\vec{v}^{(0,3,2)} \f$. -* -* Note \f$ \vec{v}^{(0,3,2)} = v_y^3 v_z^2 \f$ -* and \f$ \frac{1}{(0,3,2)!} = 1/(0!*3!*2!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,3,2)!}\vec{v}^{(0,3,2)} \f$. + * + * Note \f$ \vec{v}^{(0,3,2)} = v_y^3 v_z^2 \f$ + * and \f$ \frac{1}{(0,3,2)!} = 1/(0!*3!*2!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_032(const double v[3]) { return 8.333333333333333e-02 * v[1] * v[1] * v[1] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(0,4,1)!}\vec{v}^{(0,4,1)} \f$. -* -* Note \f$ \vec{v}^{(0,4,1)} = v_y^4 v_z^1 \f$ -* and \f$ \frac{1}{(0,4,1)!} = 1/(0!*4!*1!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,4,1)!}\vec{v}^{(0,4,1)} \f$. + * + * Note \f$ \vec{v}^{(0,4,1)} = v_y^4 v_z^1 \f$ + * and \f$ \frac{1}{(0,4,1)!} = 1/(0!*4!*1!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_041(const double v[3]) { return 4.166666666666666e-02 * v[1] * v[1] * v[1] * v[1] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(0,5,0)!}\vec{v}^{(0,5,0)} \f$. -* -* Note \f$ \vec{v}^{(0,5,0)} = v_y^5 \f$ -* and \f$ \frac{1}{(0,5,0)!} = 1/(0!*5!*0!) = 1/120 = 8.333333e-03 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(0,5,0)!}\vec{v}^{(0,5,0)} \f$. + * + * Note \f$ \vec{v}^{(0,5,0)} = v_y^5 \f$ + * and \f$ \frac{1}{(0,5,0)!} = 1/(0!*5!*0!) = 1/120 = 8.333333e-03 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_050(const double v[3]) { return 8.333333333333333e-03 * v[1] * v[1] * v[1] * v[1] * v[1]; } /** -* @brief Compute \f$ \frac{1}{(1,0,4)!}\vec{v}^{(1,0,4)} \f$. -* -* Note \f$ \vec{v}^{(1,0,4)} = v_x^1 v_z^4 \f$ -* and \f$ \frac{1}{(1,0,4)!} = 1/(1!*0!*4!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(1,0,4)!}\vec{v}^{(1,0,4)} \f$. + * + * Note \f$ \vec{v}^{(1,0,4)} = v_x^1 v_z^4 \f$ + * and \f$ \frac{1}{(1,0,4)!} = 1/(1!*0!*4!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_104(const double v[3]) { return 4.166666666666666e-02 * v[0] * v[2] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(1,1,3)!}\vec{v}^{(1,1,3)} \f$. -* -* Note \f$ \vec{v}^{(1,1,3)} = v_x^1 v_y^1 v_z^3 \f$ -* and \f$ \frac{1}{(1,1,3)!} = 1/(1!*1!*3!) = 1/6 = 1.666667e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(1,1,3)!}\vec{v}^{(1,1,3)} \f$. + * + * Note \f$ \vec{v}^{(1,1,3)} = v_x^1 v_y^1 v_z^3 \f$ + * and \f$ \frac{1}{(1,1,3)!} = 1/(1!*1!*3!) = 1/6 = 1.666667e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_113(const double v[3]) { return 1.666666666666667e-01 * v[0] * v[1] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(1,2,2)!}\vec{v}^{(1,2,2)} \f$. -* -* Note \f$ \vec{v}^{(1,2,2)} = v_x^1 v_y^2 v_z^2 \f$ -* and \f$ \frac{1}{(1,2,2)!} = 1/(1!*2!*2!) = 1/4 = 2.500000e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(1,2,2)!}\vec{v}^{(1,2,2)} \f$. + * + * Note \f$ \vec{v}^{(1,2,2)} = v_x^1 v_y^2 v_z^2 \f$ + * and \f$ \frac{1}{(1,2,2)!} = 1/(1!*2!*2!) = 1/4 = 2.500000e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_122(const double v[3]) { return 2.500000000000000e-01 * v[0] * v[1] * v[1] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(1,3,1)!}\vec{v}^{(1,3,1)} \f$. -* -* Note \f$ \vec{v}^{(1,3,1)} = v_x^1 v_y^3 v_z^1 \f$ -* and \f$ \frac{1}{(1,3,1)!} = 1/(1!*3!*1!) = 1/6 = 1.666667e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(1,3,1)!}\vec{v}^{(1,3,1)} \f$. + * + * Note \f$ \vec{v}^{(1,3,1)} = v_x^1 v_y^3 v_z^1 \f$ + * and \f$ \frac{1}{(1,3,1)!} = 1/(1!*3!*1!) = 1/6 = 1.666667e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_131(const double v[3]) { return 1.666666666666667e-01 * v[0] * v[1] * v[1] * v[1] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(1,4,0)!}\vec{v}^{(1,4,0)} \f$. -* -* Note \f$ \vec{v}^{(1,4,0)} = v_x^1 v_y^4 \f$ -* and \f$ \frac{1}{(1,4,0)!} = 1/(1!*4!*0!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(1,4,0)!}\vec{v}^{(1,4,0)} \f$. + * + * Note \f$ \vec{v}^{(1,4,0)} = v_x^1 v_y^4 \f$ + * and \f$ \frac{1}{(1,4,0)!} = 1/(1!*4!*0!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_140(const double v[3]) { return 4.166666666666666e-02 * v[0] * v[1] * v[1] * v[1] * v[1]; } /** -* @brief Compute \f$ \frac{1}{(2,0,3)!}\vec{v}^{(2,0,3)} \f$. -* -* Note \f$ \vec{v}^{(2,0,3)} = v_x^2 v_z^3 \f$ -* and \f$ \frac{1}{(2,0,3)!} = 1/(2!*0!*3!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(2,0,3)!}\vec{v}^{(2,0,3)} \f$. + * + * Note \f$ \vec{v}^{(2,0,3)} = v_x^2 v_z^3 \f$ + * and \f$ \frac{1}{(2,0,3)!} = 1/(2!*0!*3!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_203(const double v[3]) { return 8.333333333333333e-02 * v[0] * v[0] * v[2] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(2,1,2)!}\vec{v}^{(2,1,2)} \f$. -* -* Note \f$ \vec{v}^{(2,1,2)} = v_x^2 v_y^1 v_z^2 \f$ -* and \f$ \frac{1}{(2,1,2)!} = 1/(2!*1!*2!) = 1/4 = 2.500000e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(2,1,2)!}\vec{v}^{(2,1,2)} \f$. + * + * Note \f$ \vec{v}^{(2,1,2)} = v_x^2 v_y^1 v_z^2 \f$ + * and \f$ \frac{1}{(2,1,2)!} = 1/(2!*1!*2!) = 1/4 = 2.500000e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_212(const double v[3]) { return 2.500000000000000e-01 * v[0] * v[0] * v[1] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(2,2,1)!}\vec{v}^{(2,2,1)} \f$. -* -* Note \f$ \vec{v}^{(2,2,1)} = v_x^2 v_y^2 v_z^1 \f$ -* and \f$ \frac{1}{(2,2,1)!} = 1/(2!*2!*1!) = 1/4 = 2.500000e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(2,2,1)!}\vec{v}^{(2,2,1)} \f$. + * + * Note \f$ \vec{v}^{(2,2,1)} = v_x^2 v_y^2 v_z^1 \f$ + * and \f$ \frac{1}{(2,2,1)!} = 1/(2!*2!*1!) = 1/4 = 2.500000e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_221(const double v[3]) { return 2.500000000000000e-01 * v[0] * v[0] * v[1] * v[1] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(2,3,0)!}\vec{v}^{(2,3,0)} \f$. -* -* Note \f$ \vec{v}^{(2,3,0)} = v_x^2 v_y^3 \f$ -* and \f$ \frac{1}{(2,3,0)!} = 1/(2!*3!*0!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(2,3,0)!}\vec{v}^{(2,3,0)} \f$. + * + * Note \f$ \vec{v}^{(2,3,0)} = v_x^2 v_y^3 \f$ + * and \f$ \frac{1}{(2,3,0)!} = 1/(2!*3!*0!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_230(const double v[3]) { return 8.333333333333333e-02 * v[0] * v[0] * v[1] * v[1] * v[1]; } /** -* @brief Compute \f$ \frac{1}{(3,0,2)!}\vec{v}^{(3,0,2)} \f$. -* -* Note \f$ \vec{v}^{(3,0,2)} = v_x^3 v_z^2 \f$ -* and \f$ \frac{1}{(3,0,2)!} = 1/(3!*0!*2!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(3,0,2)!}\vec{v}^{(3,0,2)} \f$. + * + * Note \f$ \vec{v}^{(3,0,2)} = v_x^3 v_z^2 \f$ + * and \f$ \frac{1}{(3,0,2)!} = 1/(3!*0!*2!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_302(const double v[3]) { return 8.333333333333333e-02 * v[0] * v[0] * v[0] * v[2] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(3,1,1)!}\vec{v}^{(3,1,1)} \f$. -* -* Note \f$ \vec{v}^{(3,1,1)} = v_x^3 v_y^1 v_z^1 \f$ -* and \f$ \frac{1}{(3,1,1)!} = 1/(3!*1!*1!) = 1/6 = 1.666667e-01 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(3,1,1)!}\vec{v}^{(3,1,1)} \f$. + * + * Note \f$ \vec{v}^{(3,1,1)} = v_x^3 v_y^1 v_z^1 \f$ + * and \f$ \frac{1}{(3,1,1)!} = 1/(3!*1!*1!) = 1/6 = 1.666667e-01 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_311(const double v[3]) { return 1.666666666666667e-01 * v[0] * v[0] * v[0] * v[1] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(3,2,0)!}\vec{v}^{(3,2,0)} \f$. -* -* Note \f$ \vec{v}^{(3,2,0)} = v_x^3 v_y^2 \f$ -* and \f$ \frac{1}{(3,2,0)!} = 1/(3!*2!*0!) = 1/12 = 8.333333e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(3,2,0)!}\vec{v}^{(3,2,0)} \f$. + * + * Note \f$ \vec{v}^{(3,2,0)} = v_x^3 v_y^2 \f$ + * and \f$ \frac{1}{(3,2,0)!} = 1/(3!*2!*0!) = 1/12 = 8.333333e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_320(const double v[3]) { return 8.333333333333333e-02 * v[0] * v[0] * v[0] * v[1] * v[1]; } /** -* @brief Compute \f$ \frac{1}{(4,0,1)!}\vec{v}^{(4,0,1)} \f$. -* -* Note \f$ \vec{v}^{(4,0,1)} = v_x^4 v_z^1 \f$ -* and \f$ \frac{1}{(4,0,1)!} = 1/(4!*0!*1!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(4,0,1)!}\vec{v}^{(4,0,1)} \f$. + * + * Note \f$ \vec{v}^{(4,0,1)} = v_x^4 v_z^1 \f$ + * and \f$ \frac{1}{(4,0,1)!} = 1/(4!*0!*1!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_401(const double v[3]) { return 4.166666666666666e-02 * v[0] * v[0] * v[0] * v[0] * v[2]; } /** -* @brief Compute \f$ \frac{1}{(4,1,0)!}\vec{v}^{(4,1,0)} \f$. -* -* Note \f$ \vec{v}^{(4,1,0)} = v_x^4 v_y^1 \f$ -* and \f$ \frac{1}{(4,1,0)!} = 1/(4!*1!*0!) = 1/24 = 4.166667e-02 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(4,1,0)!}\vec{v}^{(4,1,0)} \f$. + * + * Note \f$ \vec{v}^{(4,1,0)} = v_x^4 v_y^1 \f$ + * and \f$ \frac{1}{(4,1,0)!} = 1/(4!*1!*0!) = 1/24 = 4.166667e-02 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_410(const double v[3]) { return 4.166666666666666e-02 * v[0] * v[0] * v[0] * v[0] * v[1]; } /** -* @brief Compute \f$ \frac{1}{(5,0,0)!}\vec{v}^{(5,0,0)} \f$. -* -* Note \f$ \vec{v}^{(5,0,0)} = v_x^5 \f$ -* and \f$ \frac{1}{(5,0,0)!} = 1/(5!*0!*0!) = 1/120 = 8.333333e-03 \f$ -* -* @param v vector (\f$ v \f$). -*/ + * @brief Compute \f$ \frac{1}{(5,0,0)!}\vec{v}^{(5,0,0)} \f$. + * + * Note \f$ \vec{v}^{(5,0,0)} = v_x^5 \f$ + * and \f$ \frac{1}{(5,0,0)!} = 1/(5!*0!*0!) = 1/120 = 8.333333e-03 \f$ + * + * @param v vector (\f$ v \f$). + */ __attribute__((always_inline)) INLINE static double X_500(const double v[3]) { return 8.333333333333333e-03 * v[0] * v[0] * v[0] * v[0] * v[0]; diff --git a/tests/Makefile.am b/tests/Makefile.am index e830aad453653dd2ffa265627953af1c28d8cf59..8a757e0b87ffae4d6daa1a6f2b67cf9548b868d0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -15,9 +15,9 @@ # along with this program. If not, see <http://www.gnu.org/licenses/>. # Add the source directory and debug to CFLAGS -AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) +AM_CFLAGS = -I$(top_srcdir)/src $(HDF5_CPPFLAGS) $(GSL_INCS) -AM_LDFLAGS = ../src/.libs/libswiftsim.a $(HDF5_LDFLAGS) $(HDF5_LIBS) $(FFTW_LIBS) $(GRACKLE_LIBS) +AM_LDFLAGS = ../src/.libs/libswiftsim.a $(HDF5_LDFLAGS) $(HDF5_LIBS) $(FFTW_LIBS) $(GRACKLE_LIBS) $(GSL_LIBS) # List of programs and scripts to run in the test suite TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetry \ @@ -26,7 +26,8 @@ TESTS = testGreetings testMaths testReading.sh testSingle testKernel testSymmetr testAdiabaticIndex testRiemannExact testRiemannTRRS testRiemannHLLC \ testMatrixInversion testThreadpool testDump testLogger testInteractions.sh \ testVoronoi1D testVoronoi2D testVoronoi3D testGravityDerivatives \ - testPeriodicBC.sh testPeriodicBCPerturbed.sh + testPeriodicBC.sh testPeriodicBCPerturbed.sh testPotentialSelf \ + testPotentialPair # List of test programs to compile check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \ @@ -36,7 +37,7 @@ check_PROGRAMS = testGreetings testReading testSingle testTimeIntegration \ testAdiabaticIndex testRiemannExact testRiemannTRRS \ testRiemannHLLC testMatrixInversion testDump testLogger \ testVoronoi1D testVoronoi2D testVoronoi3D testPeriodicBC \ - testGravityDerivatives + testGravityDerivatives testPotentialSelf testPotentialPair # Rebuild tests when SWIFT is updated. $(check_PROGRAMS): ../src/.libs/libswiftsim.a @@ -100,6 +101,10 @@ testLogger_SOURCES = testLogger.c testGravityDerivatives_SOURCES = testGravityDerivatives.c +testPotentialSelf_SOURCES = testPotentialSelf.c + +testPotentialPair_SOURCES = testPotentialPair.c + # Files necessary for distribution EXTRA_DIST = testReading.sh makeInput.py testActivePair.sh \ test27cells.sh test27cellsPerturbed.sh testParser.sh testPeriodicBC.sh \ diff --git a/tests/testInteractions.c b/tests/testInteractions.c index 31b7eb431da6fc12e767bc9053a21dc9ef010598..9c5dd36415970ff2a53220aa56cecba6fc5fe193 100644 --- a/tests/testInteractions.c +++ b/tests/testInteractions.c @@ -610,8 +610,9 @@ void test_force_interactions(struct part test_part, struct part *parts, (viz_vec), rhoi_vec, grad_hi_vec, pOrhoi2_vec, balsara_i_vec, ci_vec, &(vjxq[i]), &(vjyq[i]), &(vjzq[i]), &(rhojq[i]), &(grad_hjq[i]), &(pOrhoj2q[i]), &(balsarajq[i]), &(cjq[i]), - &(mjq[i]), hi_inv_vec, &(hj_invq[i]), &a_hydro_xSum, &a_hydro_ySum, - &a_hydro_zSum, &h_dtSum, &v_sigSum, &entropy_dtSum, mask, mask2, 0); + &(mjq[i]), hi_inv_vec, &(hj_invq[i]), a, H, &a_hydro_xSum, + &a_hydro_ySum, &a_hydro_zSum, &h_dtSum, &v_sigSum, &entropy_dtSum, + mask, mask2, 0); } else { /* Only use one vector for interaction. */ vector my_r2, my_dx, my_dy, my_dz, hj, hj_inv; @@ -626,7 +627,7 @@ void test_force_interactions(struct part test_part, struct part *parts, &my_r2, &my_dx, &my_dy, &my_dz, vix_vec, viy_vec, viz_vec, rhoi_vec, grad_hi_vec, pOrhoi2_vec, balsara_i_vec, ci_vec, &(vjxq[i]), &(vjyq[i]), &(vjzq[i]), &(rhojq[i]), &(grad_hjq[i]), &(pOrhoj2q[i]), - &(balsarajq[i]), &(cjq[i]), &(mjq[i]), hi_inv_vec, hj_inv, + &(balsarajq[i]), &(cjq[i]), &(mjq[i]), hi_inv_vec, hj_inv, a, H, &a_hydro_xSum, &a_hydro_ySum, &a_hydro_zSum, &h_dtSum, &v_sigSum, &entropy_dtSum, mask); } diff --git a/tests/testPotentialPair.c b/tests/testPotentialPair.c new file mode 100644 index 0000000000000000000000000000000000000000..5fbfb04ecd0b4a8c05c07d622dd77bd38af970ae --- /dev/null +++ b/tests/testPotentialPair.c @@ -0,0 +1,367 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (C) 2015 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/>. + * + ******************************************************************************/ +#include "../config.h" + +/* Some standard headers. */ +#include <fenv.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Local headers. */ +#include "runner_doiact_grav.h" +#include "swift.h" + +const int num_tests = 100; +const double eps = 0.1; + +/** + * @brief Check that a and b are consistent (up to some relative error) + * + * @param a First value + * @param b Second value + * @param s String used to identify this check in messages + */ +void check_value(double a, double b, const char *s) { + if (fabs(a - b) / fabs(a + b) > 2e-6 && fabs(a - b) > 1.e-6) + error("Values are inconsistent: %12.15e %12.15e (%s)!", a, b, s); +} + +/* Definitions of the potential and force that match + exactly the theory document */ +double S(double x) { return exp(x) / (1. + exp(x)); } + +double S_prime(double x) { return exp(x) / ((1. + exp(x)) * (1. + exp(x))); } + +double potential(double mass, double r, double H, double rlr) { + + const double u = r / H; + const double x = r / rlr; + double pot; + if (u > 1.) + pot = -mass / r; + else + pot = -mass * + (-3. * u * u * u * u * u * u * u + 15. * u * u * u * u * u * u - + 28. * u * u * u * u * u + 21. * u * u * u * u - 7. * u * u + 3.) / + H; + + return pot * (2. - 2. * S(2. * x)); +} + +double acceleration(double mass, double r, double H, double rlr) { + + const double u = r / H; + const double x = r / rlr; + double acc; + if (u > 1.) + acc = -mass / (r * r * r); + else + acc = -mass * (21. * u * u * u * u * u - 90. * u * u * u * u + + 140. * u * u * u - 84. * u * u + 14.) / + (H * H * H); + + return r * acc * (4. * x * S_prime(2 * x) - 2. * S(2. * x) + 2.); +} + +int main() { + + /* Initialize CPU frequency, this also starts time. */ + unsigned long long cpufreq = 0; + clocks_set_cpufreq(cpufreq); + + /* Initialise a few things to get us going */ + struct engine e; + e.max_active_bin = num_time_bins; + e.time = 0.1f; + e.ti_current = 8; + e.time_base = 1e-10; + + struct space s; + s.periodic = 0; + s.width[0] = 0.2; + s.width[1] = 0.2; + s.width[2] = 0.2; + e.s = &s; + + struct gravity_props props; + props.a_smooth = 1.25; + props.r_cut_min = 0.; + props.theta_crit2 = 0.; + e.gravity_properties = &props; + + struct runner r; + bzero(&r, sizeof(struct runner)); + r.e = &e; + + const double rlr = props.a_smooth * s.width[0] * FLT_MAX; + + /* Init the cache for gravity interaction */ + gravity_cache_init(&r.ci_gravity_cache, num_tests); + gravity_cache_init(&r.cj_gravity_cache, num_tests); + + /* Let's create one cell with a massive particle and a bunch of test particles + */ + struct cell ci, cj; + bzero(&ci, sizeof(struct cell)); + bzero(&cj, sizeof(struct cell)); + + ci.width[0] = 1.; + ci.width[1] = 1.; + ci.width[2] = 1.; + ci.loc[0] = 0.; + ci.loc[1] = 0.; + ci.loc[2] = 0.; + ci.gcount = 1; + ci.ti_old_gpart = 8; + ci.ti_old_multipole = 8; + ci.ti_gravity_end_min = 8; + ci.ti_gravity_end_max = 8; + + cj.width[0] = 1.; + cj.width[1] = 1.; + cj.width[2] = 1.; + cj.loc[0] = 1.; + cj.loc[1] = 0.; + cj.loc[2] = 0.; + cj.gcount = num_tests; + cj.ti_old_gpart = 8; + cj.ti_old_multipole = 8; + cj.ti_gravity_end_min = 8; + cj.ti_gravity_end_max = 8; + + /* Allocate multipoles */ + ci.multipole = malloc(sizeof(struct gravity_tensors)); + cj.multipole = malloc(sizeof(struct gravity_tensors)); + bzero(ci.multipole, sizeof(struct gravity_tensors)); + bzero(cj.multipole, sizeof(struct gravity_tensors)); + + /* Set the multipoles */ + ci.multipole->r_max = 0.1; + cj.multipole->r_max = 0.1; + + /* Allocate the particles */ + if (posix_memalign((void **)&ci.gparts, gpart_align, + ci.gcount * sizeof(struct gpart)) != 0) + error("Error allocating gparts for cell ci"); + bzero(ci.gparts, ci.gcount * sizeof(struct gpart)); + + if (posix_memalign((void **)&cj.gparts, gpart_align, + cj.gcount * sizeof(struct gpart)) != 0) + error("Error allocating gparts for cell ci"); + bzero(cj.gparts, cj.gcount * sizeof(struct gpart)); + + /* Create the mass-less test particles */ + for (int n = 0; n < num_tests; ++n) { + + struct gpart *gp = &cj.gparts[n]; + + gp->x[0] = 1. + (n + 1) / ((double)num_tests); + gp->x[1] = 0.5; + gp->x[2] = 0.5; + gp->mass = 0.; + gp->epsilon = eps; + gp->time_bin = 1; + gp->type = swift_type_dark_matter; + gp->id_or_neg_offset = n + 1; +#ifdef SWIFT_DEBUG_CHECKS + gp->ti_drift = 8; +#endif + } + + /***********************************************/ + /* Let's start by testing the P-P interactions */ + /***********************************************/ + + /* Create the massive particle */ + ci.gparts[0].x[0] = 0.; + ci.gparts[0].x[1] = 0.5; + ci.gparts[0].x[2] = 0.5; + ci.gparts[0].mass = 1.; + ci.gparts[0].epsilon = eps; + ci.gparts[0].time_bin = 1; + ci.gparts[0].type = swift_type_dark_matter; + ci.gparts[0].id_or_neg_offset = 1; +#ifdef SWIFT_DEBUG_CHECKS + ci.gparts[0].ti_drift = 8; +#endif + + /* Now compute the forces */ + runner_dopair_grav_pp(&r, &ci, &cj); + + /* Verify everything */ + for (int n = 0; n < num_tests; ++n) { + const struct gpart *gp = &cj.gparts[n]; + const struct gpart *gp2 = &ci.gparts[0]; + + double pot_true = + potential(ci.gparts[0].mass, gp->x[0] - gp2->x[0], gp->epsilon, rlr); + double acc_true = + acceleration(ci.gparts[0].mass, gp->x[0] - gp2->x[0], gp->epsilon, rlr); + + message("x=%e f=%e f_true=%e pot=%e pot_true=%e", gp->x[0] - gp2->x[0], + gp->a_grav[0], acc_true, gp->potential, pot_true); + + check_value(gp->potential, pot_true, "potential"); + check_value(gp->a_grav[0], acc_true, "acceleration"); + } + + message("\n\t\t P-P interactions all good\n"); + + /* Reset the accelerations */ + for (int n = 0; n < num_tests; ++n) gravity_init_gpart(&cj.gparts[n]); + + /**********************************/ + /* Test the basic PM interactions */ + /**********************************/ + + /* Set an opening angle that allows P-M interactions */ + props.theta_crit2 = 1.; + + ci.gparts[0].mass = 0.; + ci.multipole->CoM[0] = 0.; + ci.multipole->CoM[1] = 0.5; + ci.multipole->CoM[2] = 0.5; + + bzero(&ci.multipole->m_pole, sizeof(struct multipole)); + bzero(&cj.multipole->m_pole, sizeof(struct multipole)); + ci.multipole->m_pole.M_000 = 1.; + + /* Now compute the forces */ + runner_dopair_grav_pp(&r, &ci, &cj); + + /* Verify everything */ + for (int n = 0; n < num_tests; ++n) { + const struct gpart *gp = &cj.gparts[n]; + const struct gravity_tensors *mpole = ci.multipole; + + double pot_true = potential(mpole->m_pole.M_000, gp->x[0] - mpole->CoM[0], + gp->epsilon, rlr * FLT_MAX); + double acc_true = + acceleration(mpole->m_pole.M_000, gp->x[0] - mpole->CoM[0], gp->epsilon, + rlr * FLT_MAX); + + message("x=%e f=%e f_true=%e pot=%e pot_true=%e", gp->x[0] - mpole->CoM[0], + gp->a_grav[0], acc_true, gp->potential, pot_true); + + check_value(gp->potential, pot_true, "potential"); + check_value(gp->a_grav[0], acc_true, "acceleration"); + } + + message("\n\t\t basic P-M interactions all good\n"); + + /* Reset the accelerations */ + for (int n = 0; n < num_tests; ++n) gravity_init_gpart(&cj.gparts[n]); + +/***************************************/ +/* Test the high-order PM interactions */ +/***************************************/ + +#if SELF_GRAVITY_MULTIPOLE_ORDER >= 3 + + /* Let's make ci more interesting */ + free(ci.gparts); + ci.gcount = 8; + if (posix_memalign((void **)&ci.gparts, gpart_align, + ci.gcount * sizeof(struct gpart)) != 0) + error("Error allocating gparts for cell ci"); + bzero(ci.gparts, ci.gcount * sizeof(struct gpart)); + + /* Place particles on a simple cube of side-length 0.2 */ + for (int n = 0; n < 8; ++n) { + if (n & 1) + ci.gparts[n].x[0] = 0.0 - 0.1; + else + ci.gparts[n].x[0] = 0.0 + 0.1; + + if (n & 2) + ci.gparts[n].x[1] = 0.5 - 0.1; + else + ci.gparts[n].x[1] = 0.5 + 0.1; + + if (n & 2) + ci.gparts[n].x[2] = 0.5 - 0.1; + else + ci.gparts[n].x[2] = 0.5 + 0.1; + + ci.gparts[n].mass = 1. / 8.; + + ci.gparts[n].epsilon = eps; + ci.gparts[n].time_bin = 1; + ci.gparts[n].type = swift_type_dark_matter; + ci.gparts[n].id_or_neg_offset = 1; +#ifdef SWIFT_DEBUG_CHECKS + ci.gparts[n].ti_drift = 8; +#endif + } + + /* Now let's make a multipole out of it. */ + gravity_reset(ci.multipole); + gravity_P2M(ci.multipole, ci.gparts, ci.gcount); + + // message("CoM=[%e %e %e]", ci.multipole->CoM[0], ci.multipole->CoM[1], + // ci.multipole->CoM[2]); + gravity_multipole_print(&ci.multipole->m_pole); + + /* Compute the forces */ + runner_dopair_grav_pp(&r, &ci, &cj); + + /* Verify everything */ + for (int n = 0; n < num_tests; ++n) { + const struct gpart *gp = &cj.gparts[n]; + const struct gravity_tensors *mpole = ci.multipole; + + double pot_true = 0, acc_true[3] = {0., 0., 0.}; + + for (int i = 0; i < 8; ++i) { + const struct gpart *gp2 = &ci.gparts[i]; + + const double dx[3] = {gp2->x[0] - gp->x[0], gp2->x[1] - gp->x[1], + gp2->x[2] - gp->x[2]}; + const double d = sqrt(dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]); + + pot_true += potential(gp2->mass, d, gp->epsilon, rlr * FLT_MAX); + acc_true[0] -= + acceleration(gp2->mass, d, gp->epsilon, rlr * FLT_MAX) * dx[0] / d; + acc_true[1] -= + acceleration(gp2->mass, d, gp->epsilon, rlr * FLT_MAX) * dx[1] / d; + acc_true[2] -= + acceleration(gp2->mass, d, gp->epsilon, rlr * FLT_MAX) * dx[2] / d; + } + + message("x=%e f=%e f_true=%e pot=%e pot_true=%e %e %e", + gp->x[0] - mpole->CoM[0], gp->a_grav[0], acc_true[0], gp->potential, + pot_true, acc_true[1], acc_true[2]); + + // check_value(gp->potential, pot_true, "potential"); + // check_value(gp->a_grav[0], acc_true[0], "acceleration"); + } + + message("\n\t\t high-order P-M interactions all good\n"); + +#endif + + free(ci.multipole); + free(cj.multipole); + free(ci.gparts); + free(cj.gparts); + return 0; +} diff --git a/tests/testPotentialSelf.c b/tests/testPotentialSelf.c new file mode 100644 index 0000000000000000000000000000000000000000..33fe574e34bcc49ff8bac6f19f22b55fda19f186 --- /dev/null +++ b/tests/testPotentialSelf.c @@ -0,0 +1,187 @@ +/******************************************************************************* + * This file is part of SWIFT. + * Copyright (C) 2015 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/>. + * + ******************************************************************************/ +#include "../config.h" + +/* Some standard headers. */ +#include <fenv.h> +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +/* Local headers. */ +#include "runner_doiact_grav.h" +#include "swift.h" + +const int num_tests = 100; +const double eps = 0.02; + +/** + * @brief Check that a and b are consistent (up to some relative error) + * + * @param a First value + * @param b Second value + * @param s String used to identify this check in messages + */ +void check_value(double a, double b, const char *s) { + if (fabs(a - b) / fabs(a + b) > 2e-6 && fabs(a - b) > 1.e-6) + error("Values are inconsistent: %12.15e %12.15e (%s)!", a, b, s); +} + +/* Definitions of the potential and force that match + exactly the theory document */ +double S(double x) { return exp(x) / (1. + exp(x)); } + +double S_prime(double x) { return exp(x) / ((1. + exp(x)) * (1. + exp(x))); } + +double potential(double mass, double r, double H, double rlr) { + + const double u = r / H; + const double x = r / rlr; + double pot; + if (u > 1.) + pot = -mass / r; + else + pot = -mass * + (-3. * u * u * u * u * u * u * u + 15. * u * u * u * u * u * u - + 28. * u * u * u * u * u + 21. * u * u * u * u - 7. * u * u + 3.) / + H; + + return pot * (2. - 2. * S(2. * x)); +} + +double acceleration(double mass, double r, double H, double rlr) { + + const double u = r / H; + const double x = r / rlr; + double acc; + if (u > 1.) + acc = -mass / (r * r * r); + else + acc = -mass * (21. * u * u * u * u * u - 90. * u * u * u * u + + 140. * u * u * u - 84. * u * u + 14.) / + (H * H * H); + + return r * acc * (4. * x * S_prime(2 * x) - 2. * S(2. * x) + 2.); +} + +int main() { + + /* Initialize CPU frequency, this also starts time. */ + unsigned long long cpufreq = 0; + clocks_set_cpufreq(cpufreq); + + /* Initialise a few things to get us going */ + struct engine e; + e.max_active_bin = num_time_bins; + e.time = 0.1f; + e.ti_current = 8; + e.time_base = 1e-10; + + struct space s; + s.width[0] = 0.2; + s.width[1] = 0.2; + s.width[2] = 0.2; + e.s = &s; + + struct gravity_props props; + props.a_smooth = 1.25; + e.gravity_properties = &props; + + struct runner r; + bzero(&r, sizeof(struct runner)); + r.e = &e; + + const double rlr = props.a_smooth * s.width[0]; + + /* Init the cache for gravity interaction */ + gravity_cache_init(&r.ci_gravity_cache, num_tests * 2); + + /* Let's create one cell with a massive particle and a bunch of test particles + */ + struct cell c; + bzero(&c, sizeof(struct cell)); + c.width[0] = 1.; + c.width[1] = 1.; + c.width[2] = 1.; + c.loc[0] = 0.; + c.loc[1] = 0.; + c.loc[2] = 0.; + c.gcount = 1 + num_tests; + c.ti_old_gpart = 8; + c.ti_gravity_end_min = 8; + c.ti_gravity_end_max = 8; + + if (posix_memalign((void **)&c.gparts, gpart_align, + c.gcount * sizeof(struct gpart)) != 0) + error("Impossible to allocate memory for the gparts."); + bzero(c.gparts, c.gcount * sizeof(struct gpart)); + + /* Create the massive particle */ + c.gparts[0].x[0] = 0.; + c.gparts[0].x[1] = 0.5; + c.gparts[0].x[2] = 0.5; + c.gparts[0].mass = 1.; + c.gparts[0].epsilon = eps; + c.gparts[0].time_bin = 1; + c.gparts[0].type = swift_type_dark_matter; + c.gparts[0].id_or_neg_offset = 1; +#ifdef SWIFT_DEBUG_CHECKS + c.gparts[0].ti_drift = 8; +#endif + + /* Create the mass-less particles */ + for (int n = 1; n < num_tests + 1; ++n) { + + struct gpart *gp = &c.gparts[n]; + + gp->x[0] = n / ((double)num_tests); + gp->x[1] = 0.5; + gp->x[2] = 0.5; + gp->mass = 0.; + gp->epsilon = eps; + gp->time_bin = 1; + gp->type = swift_type_dark_matter; + gp->id_or_neg_offset = n + 1; +#ifdef SWIFT_DEBUG_CHECKS + gp->ti_drift = 8; +#endif + } + + /* Now compute the forces */ + runner_doself_grav_pp_truncated(&r, &c); + + /* Verify everything */ + for (int n = 1; n < num_tests + 1; ++n) { + const struct gpart *gp = &c.gparts[n]; + + double pot_true = potential(c.gparts[0].mass, gp->x[0], gp->epsilon, rlr); + double acc_true = + acceleration(c.gparts[0].mass, gp->x[0], gp->epsilon, rlr); + + // message("x=%e f=%e f_true=%e", gp->x[0], gp->a_grav[0], acc_true); + + check_value(gp->potential, pot_true, "potential"); + check_value(gp->a_grav[0], acc_true, "acceleration"); + } + + free(c.gparts); + return 0; +} diff --git a/tests/tolerance_27_perturbed_h2.dat b/tests/tolerance_27_perturbed_h2.dat index 781531d6a0d180d58ab74b9ef12efb927ccc733d..882554741734aeb270427b62580d6077907056ad 100644 --- a/tests/tolerance_27_perturbed_h2.dat +++ b/tests/tolerance_27_perturbed_h2.dat @@ -1,4 +1,4 @@ # ID pos_x pos_y pos_z v_x v_y v_z rho rho_dh wcount wcount_dh div_v curl_vx curl_vy curl_vz 0 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 3e-6 1e-4 5e-4 1.5e-2 1.4e-5 3e-6 3e-6 1e-5 - 0 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1.5e-6 2.5e-2 1e-5 5.86e-3 4.96e-4 3e-3 4.5e-3 3e-3 + 0 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1.5e-6 2.5e-2 1e-5 5.86e-3 1.17e-3 3e-3 5.65e-3 3e-3 0 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1e-6 1e0 1e-6 4e-6 4e-6 4e-6