Revision 4a39e0d97a4df8bd8e0db53b25d5b3d03630b245 authored by Hanno Rein on 13 November 2023, 23:33:59 UTC, committed by Hanno Rein on 13 November 2023, 23:33:59 UTC
1 parent 5c5d887
gravity_fft.c
/**
* @file gravity.c
* @brief Gravity calculation on a grid using an FFT.
* @author Hanno Rein <hanno@hanno-rein.de>, Geoffroy Lesur <geoffroy.lesur@obs.ujf-grenoble.fr>
*
* @details This is a 2D FFT poisson solver for periodic and shearing sheet boxes.
* It has not been well tested yet, so use with caution.
* Furthermore, it is not parallelized yet.
* The number of grid points is set by N_root_x and N_root_y.
*
*
* @section LICENSE
* Copyright (c) 2011 Hanno Rein, Shangfei Liu, Geoffroy Lesur
*
* This file is part of rebound.
*
* rebound is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* rebound 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 General Public License
* along with rebound. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <math.h>
#include <time.h>
#include "particle.h"
#include "rebound.h"
#include "boundaries.h"
#include "integrator.h"
#include <fftw3.h>
#ifdef MPI
#error GRAVITY_FFT not compatible with MPI yet
#endif
unsigned int gravity_ignore_10;
int grid_NX_COMPLEX;
int grid_NY_COMPLEX;
int grid_NCOMPLEX;
double dx,dy; /**< Grid spacing */
double* kx; /**< Wave vector */
double* ky; /**< Wave vector */
double* kxt; /**< Time dependent wave vector (shearing sheet only) */
double* k; /**< Magnitude of wave vector */
double* density; /**< Complex density field */
double* density_r; /**< Real density field */
double* fx; /**< Force in x direction */
double* fy; /**< Force in y direction */
fftw_plan r2cfft; /**< FFT plan real to complex */
fftw_plan c2rfft; /**< FFT plan complex to real */
double* w1d; /**< Temporary 1D arrary for remapping (shearing sheet only) */
fftw_plan for1dfft; /**< FFT plan for remapping (1D, shearing sheet only) */
fftw_plan bac1dfft; /**< FFT plan for remapping (1D, shearing sheet only) */
int gravity_fft_init_done = 0; /**< Flag if arrays and plans are initialized */
void gravity_fft_init(void);
void gravity_fft_grid2p(struct reb_particle* p);
void gravity_fft_p2grid(void);
void gravity_fft_remap(double* wi, const double direction);
double shift_shear = 0;
void reb_calculate_acceleration(void){
// Setting up the grid
if (gravity_fft_init_done==0){
gravity_fft_init();
gravity_fft_init_done=1;
}
#pragma omp parallel for schedule(guided)
for (int i=0; i<N; i++){
particles[i].ax = 0;
particles[i].ay = 0;
particles[i].az = 0;
}
if (integrator == SEI){
struct ghostbox gb = boundaries_get_ghostbox(1,0,0);
shift_shear = gb.y;
}
gravity_fft_p2grid();
if (integrator == SEI){
// Remap in fourier space to deal with shearing sheet boundary conditions.
gravity_fft_remap(density_r, 1);
}
fftw_execute_dft_r2c(r2cfft, density_r, (fftw_complex*)density);
// Inverse Poisson equation
for(int i = 0 ; i < grid_NCOMPLEX ; i++) {
if (integrator == SEI){
// Compute time-dependent wave-vectors
kxt[i] = kx[i] + shift_shear/boxsize.y * ky[i];
k[i] = sqrt( kxt[i]*kxt[i] + ky[i] * ky[i]);
// we will use 1/k, that prevents singularity
// (the k=0 is set to zero by renormalization...)
if ( k[i] == 0.0 ) k[i] = 1.0;
}
double q0 = - 2.0 * M_PI * density[2*i] / (k[i] * N_root_x * N_root_y);
double q1 = - 2.0 * M_PI * density[2*i+1] / (k[i] * N_root_x * N_root_y);
double sinkxt = sin(kxt[i] * dx);
double sinky = sin(ky[i] * dy);
fx[2*i] = q1 * sinkxt / dx; // Real part of Fx
fx[2*i+1] = - q0 * sinkxt / dx; // Imaginary part of Fx
fy[2*i] = q1 * sinky / dy;
fy[2*i+1] = - q0 * sinky / dy;
}
// Transform back the force field
fftw_execute_dft_c2r(c2rfft, (fftw_complex*)fx, fx);
fftw_execute_dft_c2r(c2rfft, (fftw_complex*)fy, fy);
if (integrator == SEI){
// Remap in fourier space to deal with shearing sheet boundary conditions.
gravity_fft_remap(fx, -1);
gravity_fft_remap(fy, -1);
}
for(int i=0;i<N;i++){
gravity_fft_grid2p(&(particles[i]));
}
}
void gravity_fft_init(void) {
// dimension definition
grid_NX_COMPLEX = N_root_x;
grid_NY_COMPLEX = (N_root_y / 2 + 1);
grid_NCOMPLEX = grid_NX_COMPLEX * grid_NY_COMPLEX;
dx = boxsize.x / N_root_x;
dy = boxsize.y / N_root_y;
// Array allocation
kx = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX);
ky = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX);
if (integrator == SEI){
kxt = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX);
w1d = (double *) fftw_malloc( sizeof(double) * N_root_y * 2 );
}else{
kxt = kx; // No time dependent wave vectors.
}
k = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX);
density = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX * 2);
density_r = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX * 2);
fx = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX * 2);
fy = (double *) fftw_malloc( sizeof(double) * grid_NCOMPLEX * 2);
// Init wavevectors
for(int i = 0; i < grid_NX_COMPLEX; i++) {
for(int j =0; j < grid_NY_COMPLEX; j++) {
int IDX2D = i * grid_NY_COMPLEX + j;
kx[IDX2D] = (2.0 * M_PI) / boxsize.x * (
fmod( (double) (i + (grid_NX_COMPLEX/2.0 )), (double) grid_NX_COMPLEX)
- (double) grid_NX_COMPLEX / 2.0 );
ky[IDX2D] = (2.0 * M_PI) / boxsize.y * ((double) j);
if (integrator != SEI){
k[IDX2D] = pow( kx[IDX2D]*kx[IDX2D] + ky[IDX2D] * ky[IDX2D], 0.5);
// we will use 1/k, that prevents singularity
// (the k=0 is set to zero by renormalization...)
if ( k[IDX2D] == 0.0 ) k[IDX2D] = 1.0;
}
}
}
// Init ffts (use in place fourier transform for efficient memory usage)
r2cfft = fftw_plan_dft_r2c_2d( N_root_x, N_root_y, density, (fftw_complex*)density, FFTW_MEASURE);
c2rfft = fftw_plan_dft_c2r_2d( N_root_x, N_root_y, (fftw_complex*)density, density, FFTW_MEASURE);
if (integrator == SEI){
for1dfft = fftw_plan_dft_1d(N_root_y, (fftw_complex*)w1d, (fftw_complex*)w1d, FFTW_FORWARD, FFTW_MEASURE);
bac1dfft = fftw_plan_dft_1d(N_root_y, (fftw_complex*)w1d, (fftw_complex*)w1d, FFTW_BACKWARD, FFTW_MEASURE);
}
}
// Assignement function (TSC Scheme)
// See Hockney and Eastwood (1981), Computer Simulations Using reb_particles
double W(double x){
if (fabs(x)<=0.5) return 0.75 - x*x;
if (fabs(x)>=0.5 && fabs(x)<=3./2.) return 0.5*(3./2.-fabs(x))*(3./2.-fabs(x));
return 0;
}
void gravity_fft_remap(double* wi, const double direction) {
double phase, rew, imw;
for(int i = 0 ; i < N_root_x ; i++) {
for(int j = 0 ; j < N_root_y ; j++) {
w1d[ 2 * j ] = wi[j + (N_root_y + 2) * i]; // w1d is supposed to be a complex array.
w1d[ 2 * j + 1 ] = 0.0;
}
// Transform w1d, which will be stored in w2d
fftw_execute(for1dfft);
for(int j = 0 ; j < N_root_y ; j++) {
// phase = ky * (-shift_shear)
phase = - direction * (2.0 * M_PI) / boxsize.y * ((j + (N_root_y / 2)) % N_root_y - N_root_y / 2) * shift_shear * ((double) i) / ((double) N_root_x);
rew = w1d[2 * j];
imw = w1d[2 * j + 1];
// Real part
w1d[2 * j ] = rew * cos(phase) - imw * sin(phase);
// Imaginary part
w1d[2 * j + 1] = rew * sin(phase) + imw * cos(phase);
// Throw the Nyquist Frequency (should be useless anyway)
if(j==N_root_y/2) {
w1d[2 * j ] =0.0;
w1d[2 * j + 1] = 0.0;
}
}
fftw_execute(bac1dfft);
for(int j = 0 ; j < N_root_y ; j++) {
wi[j + (N_root_y + 2) * i] = w1d[ 2 * j ] / N_root_y;
}
}
}
void gravity_fft_p2grid(void){
// clean the current density
for(int i = 0 ; i < N_root_x * (N_root_y + 2) ; i++) {
density_r[i] = 0.0; // density is used to store the surface density
}
for (int i=0; i<N; i++){
struct reb_particle p = particles[i];
// I'm sorry to say I have to keep these traps. Something's wrong if these traps are called.
int x = (int) floor((p.x / boxsize.x + 0.5) * N_root_x);
int y = (int) floor((p.y / boxsize.y + 0.5) * N_root_y);
// Formally, pos.x is in the interval [-size/2 , size/2 [. Therefore, x and y should be in [0 , grid_NJ-1]
// xp1, xm1... might be out of bound. They are however the relevant coordinates for the interpolation.
int xp1 = x + 1;
int xm1 = x - 1;
int ym1 = y - 1;
int yp1 = y + 1;
// Target according to boundary conditions.
// Although xTarget and yTarget are not relevant here, they will be relevant with shear
// We have to use all these fancy variables since y depends on x because of the shearing path
// Any nicer solution is welcome
int xTarget = x;
int xp1Target = xp1;
int xm1Target = xm1;
int ym1_xm1Target = (ym1 + N_root_y) % N_root_y;
int ym1_xTarget = ym1_xm1Target;
int ym1_xp1Target = ym1_xm1Target;
int y_xm1Target = y % N_root_y;
int y_xTarget = y_xm1Target;
int y_xp1Target = y_xm1Target;
int yp1_xm1Target = yp1 % N_root_y;
int yp1_xTarget = yp1_xm1Target;
int yp1_xp1Target = yp1_xm1Target;
double tx, ty;
double q0 = G* p.m /(dx*dy);
// Shearing patch trick
// This is only an **approximate** mapping
// one should use an exact interpolation scheme here (Fourier like).
if(xp1Target>=N_root_x) {
xp1Target -= N_root_x; // X periodicity
y_xp1Target = y_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
y_xp1Target = (y_xp1Target + N_root_y) % N_root_y; // Y periodicity
yp1_xp1Target = yp1_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
yp1_xp1Target = (yp1_xp1Target + N_root_y) % N_root_y;
ym1_xp1Target = ym1_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
ym1_xp1Target = (ym1_xp1Target + N_root_y) % N_root_y;
}
if(xm1Target<0) {
xm1Target += N_root_x;
y_xm1Target = y_xm1Target - round((shift_shear/boxsize.x) * N_root_y);
y_xm1Target = (y_xm1Target + N_root_y) % N_root_y; // Y periodicity
yp1_xm1Target = yp1_xm1Target - round((shift_shear/boxsize.x) * N_root_y);
yp1_xm1Target = (yp1_xm1Target + N_root_y) % N_root_y;
ym1_xm1Target = ym1_xm1Target - round((shift_shear/boxsize.x) * N_root_y);
ym1_xm1Target = (ym1_xm1Target + N_root_y) % N_root_y;
}
// Distribute density to the 9 nearest cells
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xm1Target + ym1_xm1Target] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xTarget + ym1_xTarget] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xp1Target + ym1_xp1Target] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xm1Target + y_xm1Target ] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xTarget + y_xTarget ] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xp1Target + y_xp1Target ] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xm1Target + yp1_xm1Target] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xTarget + yp1_xTarget] += q0 * W(tx/dx)*W(ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p.x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p.y;
density_r[(N_root_y+2) * xp1Target + yp1_xp1Target] += q0 * W(tx/dx)*W(ty/dy);
}
}
void gravity_fft_grid2p(struct reb_particle* p){
// I'm sorry to say I have to keep these traps. Something's wrong if these traps are called.
int x = (int) floor((p->x / boxsize.x + 0.5) * N_root_x);
int y = (int) floor((p->y / boxsize.y + 0.5) * N_root_y);
// Formally, pos.x is in the interval [-size/2 , size/2 [. Therefore, x and y should be in [0 , grid_NJ-1]
// xp1, xm1... might be out of bound. They are however the relevant coordinates for the interpolation.
int xp1 = x + 1;
int xm1 = x - 1;
int ym1 = y - 1;
int yp1 = y + 1;
// Target according to boundary conditions.
// Although xTarget and yTarget are not relevant here, they will be relevant with shear
// We have to use all these fancy variables since y depends on x because of the shearing path
// Any nicer solution is welcome
int xTarget = x;
int xp1Target = xp1;
int xm1Target = xm1;
int ym1_xm1Target = (ym1 + N_root_y) % N_root_y;
int ym1_xTarget = ym1_xm1Target;
int ym1_xp1Target = ym1_xm1Target;
int y_xm1Target = y % N_root_y;
int y_xTarget = y_xm1Target;
int y_xp1Target = y_xm1Target;
int yp1_xm1Target = yp1 % N_root_y;
int yp1_xTarget = yp1_xm1Target;
int yp1_xp1Target = yp1_xm1Target;
double tx, ty;
// Shearing patch trick
// This is only an **approximate** mapping
// one should use an exact interpolation scheme here (Fourier like).
if(xp1Target>=N_root_x) {
xp1Target -= N_root_x; // X periodicity
y_xp1Target = y_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
y_xp1Target = (y_xp1Target + N_root_y) % N_root_y; // Y periodicity
yp1_xp1Target = yp1_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
yp1_xp1Target = (yp1_xp1Target + N_root_y) % N_root_y;
ym1_xp1Target = ym1_xp1Target + round((shift_shear/boxsize.y) * N_root_y);
ym1_xp1Target = (ym1_xp1Target + N_root_y) % N_root_y;
}
if(xm1Target<0) {
xm1Target += N_root_x;
y_xm1Target = y_xm1Target - round((shift_shear/boxsize.y) * N_root_y);
y_xm1Target = (y_xm1Target + N_root_y) % N_root_y; // Y periodicity
yp1_xm1Target = yp1_xm1Target - round((shift_shear/boxsize.y) * N_root_y);
yp1_xm1Target = (yp1_xm1Target + N_root_y) % N_root_y;
ym1_xm1Target = ym1_xm1Target - round((shift_shear/boxsize.y) * N_root_y);
ym1_xm1Target = (ym1_xm1Target + N_root_y) % N_root_y;
}
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xm1Target + ym1_xm1Target] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xm1Target + ym1_xm1Target] * W(-tx/dx)*W(-ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xTarget + ym1_xTarget] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xTarget + ym1_xTarget] * W(-tx/dx)*W(-ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)ym1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xp1Target + ym1_xp1Target] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xp1Target + ym1_xp1Target] * W(-tx/dx)*W(-ty/dy);
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xm1Target + y_xm1Target ] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xm1Target + y_xm1Target ] * W(-tx/dx)*W(-ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xTarget + y_xTarget ] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xTarget + y_xTarget ] * W(-tx/dx)*W(-ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)y +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xp1Target + y_xp1Target ] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xp1Target + y_xp1Target ] * W(-tx/dx)*W(-ty/dy);
tx = ((double)xm1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xm1Target + yp1_xm1Target] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xm1Target + yp1_xm1Target] * W(-tx/dx)*W(-ty/dy);
tx = ((double)x +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xTarget + yp1_xTarget] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xTarget + yp1_xTarget] * W(-tx/dx)*W(-ty/dy);
tx = ((double)xp1 +0.5) * boxsize.x / N_root_x -0.5*boxsize.x - p->x;
ty = ((double)yp1 +0.5) * boxsize.y / N_root_y -0.5*boxsize.y - p->y;
p->ax += fx[(N_root_y+2) * xp1Target + yp1_xp1Target] * W(-tx/dx)*W(-ty/dy);
p->ay += fy[(N_root_y+2) * xp1Target + yp1_xp1Target] * W(-tx/dx)*W(-ty/dy);
}
void reb_calculate_acceleration_var(void){
// Not yet implemented
}
Computing file changes ...