#include "rebound.h"
#include "rebound_internal.h"
#include <math.h>
#include <stdio.h>
#include "particle.h"
#include "boundary.h"
#include "tree.h"
#ifdef MPI
#include "communication_mpi.h"
#endif
static int reb_reb_tree_get_octant_for_particle_in_cell(const struct reb_particle p, struct reb_treecell *node);
static struct reb_treecell *reb_tree_add_particle_to_cell(struct reb_simulation* const r, struct reb_treecell *node, int pt, struct reb_treecell *parent, int o);
void reb_tree_add_particle_to_tree(struct reb_simulation* const r, int pt){
struct reb_particle p = r->particles[pt];
if (!isfinite(p.x) || !isfinite(p.y) || !isfinite(p.z)){
reb_simulation_error(r, "Particle has non-finite coordinates. Cannot add to tree.");
return;
}
int rootbox = reb_get_rootbox_for_particle(r, p);
#ifdef MPI
if (!reb_communication_mpi_rootbox_is_local(r, rootbox)){
reb_simulation_error(r, "Particle has non-local rootbox. Cannot add to tree. Distribute particles before constructing tree.");
return;
}
#endif r->tree_root[rootbox] = reb_tree_add_particle_to_cell(r, r->tree_root[rootbox],pt,NULL,0);
}
static struct reb_treecell *reb_tree_add_particle_to_cell(struct reb_simulation* const r, struct reb_treecell *node, int pt, struct reb_treecell *parent, int o){
struct reb_particle* const particles = r->particles;
if (node == NULL) {
node = calloc(1, sizeof(struct reb_treecell));
struct reb_particle p = particles[pt];
if (parent == NULL){ node->w = r->root_size;
struct reb_vec3d boxsize;
boxsize.x = r->root_size*(double)r->N_root_x;
boxsize.y = r->root_size*(double)r->N_root_y;
boxsize.z = r->root_size*(double)r->N_root_z;
int i = ((int)floor((p.x + boxsize.x/2.)/r->root_size))%r->N_root_x;
int j = ((int)floor((p.y + boxsize.y/2.)/r->root_size))%r->N_root_y;
int k = ((int)floor((p.z + boxsize.z/2.)/r->root_size))%r->N_root_z;
node->x = -boxsize.x/2.+r->root_size*(0.5+(double)i);
node->y = -boxsize.y/2.+r->root_size*(0.5+(double)j);
node->z = -boxsize.z/2.+r->root_size*(0.5+(double)k);
}else{ node->w = parent->w/2.;
node->x = parent->x + node->w/2.*((o>>0)%2==0?1.:-1);
node->y = parent->y + node->w/2.*((o>>1)%2==0?1.:-1);
node->z = parent->z + node->w/2.*((o>>2)%2==0?1.:-1);
}
for (int i=0; i<8; i++){
node->oct[i] = NULL;
}
if (node->w<=0.0){
reb_simulation_error(r, "Tree cell has size zero.");
free(node);
return NULL;
}
node->pt = pt;
return node;
}
if (node->pt >= 0) { int o1 = reb_reb_tree_get_octant_for_particle_in_cell(particles[node->pt], node);
int o2 = reb_reb_tree_get_octant_for_particle_in_cell(particles[pt], node);
if (o1==o2){ if (particles[pt].x == particles[node->pt].x && particles[pt].y == particles[node->pt].y && particles[pt].z == particles[node->pt].z){
reb_simulation_error(r, "Cannot add two particles with the same coordinates to the tree.");
return node;
}
}
node->oct[o1] = reb_tree_add_particle_to_cell(r, node->oct[o1], node->pt, node, o1);
node->oct[o2] = reb_tree_add_particle_to_cell(r, node->oct[o2], pt, node, o2);
node->pt = -2;
}else{ node->pt--;
int o = reb_reb_tree_get_octant_for_particle_in_cell(particles[pt], node);
node->oct[o] = reb_tree_add_particle_to_cell(r, node->oct[o], pt, node, o);
}
return node;
}
static int reb_reb_tree_get_octant_for_particle_in_cell(const struct reb_particle p, struct reb_treecell *node){
int octant = 0;
if (p.x < node->x) octant+=1;
if (p.y < node->y) octant+=2;
if (p.z < node->z) octant+=4;
return octant;
}
static void reb_tree_calculate_gravity_data_in_cell(const struct reb_simulation* const r, struct reb_treecell *node){
#ifdef QUADRUPOLE
node->mxx = 0;
node->mxy = 0;
node->mxz = 0;
node->myy = 0;
node->myz = 0;
node->mzz = 0;
#endif if (node->pt < 0) {
node->m = 0;
node->mx = 0;
node->my = 0;
node->mz = 0;
for (int o=0; o<8; o++) {
struct reb_treecell* d = node->oct[o];
if (d!=NULL){
reb_tree_calculate_gravity_data_in_cell(r, d);
double d_m = d->m;
node->mx += d->mx*d_m;
node->my += d->my*d_m;
node->mz += d->mz*d_m;
node->m += d_m;
}
}
double m_tot = node->m;
if (m_tot>0){
node->mx /= m_tot;
node->my /= m_tot;
node->mz /= m_tot;
}
#ifdef QUADRUPOLE
for (int o=0; o<8; o++) {
struct reb_treecell* d = node->oct[o];
if (d!=NULL){
double d_m = d->m;
double qx = d->mx - node->mx;
double qy = d->my - node->my;
double qz = d->mz - node->mz;
double qr2 = qx*qx + qy*qy + qz*qz;
node->mxx += d->mxx + d_m*(3.*qx*qx - qr2);
node->mxy += d->mxy + d_m*3.*qx*qy;
node->mxz += d->mxz + d_m*3.*qx*qz;
node->myy += d->myy + d_m*(3.*qy*qy - qr2);
node->myz += d->myz + d_m*3.*qy*qz;
}
}
node->mzz = -node->mxx -node->myy;
#endif }else{
struct reb_particle p = r->particles[node->pt];
node->m = p.m;
node->mx = p.x;
node->my = p.y;
node->mz = p.z;
}
}
void reb_tree_calculate_gravity_data(struct reb_simulation* const r){
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
for(size_t i=0;i<N_root;i++){
#ifdef MPI
if (reb_communication_mpi_rootbox_is_local(r, i)){
#endif if (r->tree_root && r->tree_root[i]!=NULL){
reb_tree_calculate_gravity_data_in_cell(r, r->tree_root[i]);
}
#ifdef MPI
}
#endif }
#ifdef MPI
reb_tree_prepare_essential_tree_for_gravity(r);
reb_communication_mpi_distribute_essential_tree_for_gravity(r);
#endif }
static void reb_tree_delete_cell(struct reb_treecell* node){
if (node==NULL){
return;
}
if (node->remote==1){
return;
}
for (int o=0; o<8; o++) {
reb_tree_delete_cell(node->oct[o]);
}
free(node);
}
void reb_tree_delete(struct reb_simulation* const r){
if (r->tree_root!=NULL){
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
for(size_t i=0;i<N_root;i++){
reb_tree_delete_cell(r->tree_root[i]);
r->tree_root[i] = NULL;
}
}
}
void reb_tree_construct(struct reb_simulation* const r){
if (r->root_size<=0.0){
reb_simulation_error(r,"Set root_size to a finite value to use a tree based gravity or collision solver.");
return;
}
if (!r->tree_root){
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
r->tree_root = calloc(N_root,sizeof(struct reb_treecell*));
}
for (size_t i=0;i<r->N;i++){
struct reb_particle p = r->particles[i];
if(fabs(p.x)>r->root_size*(double)r->N_root_x/2. || fabs(p.y)>r->root_size*(double)r->N_root_y/2. || fabs(p.z)>r->root_size*(double)r->N_root_z/2.){
reb_simulation_error(r,"Particle is outside of simulation box. Cannot add to tree.");
return;
}
reb_tree_add_particle_to_tree(r, i);
}
}
static void reb_tree_calculate_acceleration_for_particle_from_cell(const struct reb_simulation* r, const int pt, const struct reb_treecell *node, const struct reb_vec6d gb) {
const double G = r->G;
const double softening2 = r->softening*r->softening;
struct reb_particle* const particles = r->particles;
const double dx = gb.x - node->mx;
const double dy = gb.y - node->my;
const double dz = gb.z - node->mz;
const double r2 = dx*dx + dy*dy + dz*dz;
if ( node->pt < 0 ) { if ( node->w*node->w > r->opening_angle2*r2 ){
for (int o=0; o<8; o++) {
if (node->oct[o] != NULL) {
reb_tree_calculate_acceleration_for_particle_from_cell(r, pt, node->oct[o], gb);
}
}
} else {
double _r = sqrt(r2 + softening2);
double prefact = -G/(_r*_r*_r)*node->m;
#ifdef QUADRUPOLE
double qprefact = G/(_r*_r*_r*_r*_r);
particles[pt].ax += qprefact*(dx*node->mxx + dy*node->mxy + dz*node->mxz);
particles[pt].ay += qprefact*(dx*node->mxy + dy*node->myy + dz*node->myz);
particles[pt].az += qprefact*(dx*node->mxz + dy*node->myz + dz*node->mzz);
double mrr = dx*dx*node->mxx + dy*dy*node->myy + dz*dz*node->mzz
+ 2.*dx*dy*node->mxy + 2.*dx*dz*node->mxz + 2.*dy*dz*node->myz;
qprefact *= -5.0/(2.0*_r*_r)*mrr;
particles[pt].ax += (qprefact + prefact) * dx;
particles[pt].ay += (qprefact + prefact) * dy;
particles[pt].az += (qprefact + prefact) * dz;
#else
particles[pt].ax += prefact*dx;
particles[pt].ay += prefact*dy;
particles[pt].az += prefact*dz;
#endif
}
} else { if (node->remote == 0 && node->pt == pt) return;
double _r = sqrt(r2 + softening2);
double prefact = -G/(_r*_r*_r)*node->m;
particles[pt].ax += prefact*dx;
particles[pt].ay += prefact*dy;
particles[pt].az += prefact*dz;
}
}
void reb_tree_calculate_acceleration_for_particle(const struct reb_simulation* const r, const int pt, const struct reb_vec6d gb) {
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
for(size_t i=0;i<N_root;i++){
struct reb_treecell* node = r->tree_root[i];
if (node!=NULL){
reb_tree_calculate_acceleration_for_particle_from_cell(r, pt, node, gb);
}
}
}
void reb_tree_print(const struct reb_treecell *node, int indent){
for (int o=0; o<8; o++) {
if (node->oct[o] != NULL) {
for (int i=0;i<indent;i++){
printf(" ");
}
printf("%d\n",o);
reb_tree_print(node->oct[o], indent+1);
}
}
if (node->pt >=0){
for (int i=0;i<indent;i++){
printf(" ");
}
printf("pt=%d\n",node->pt);
}
}
#ifdef MPI
int reb_particles_get_rootbox_for_node(struct reb_simulation* const r, struct reb_treecell* node){
int i = ((int)floor((node->x + r->root_size*(double)r->N_root_x/2.)/r->root_size)+r->N_root_x)%r->N_root_x;
int j = ((int)floor((node->y + r->root_size*(double)r->N_root_y/2.)/r->root_size)+r->N_root_y)%r->N_root_y;
int k = ((int)floor((node->z + r->root_size*(double)r->N_root_z/2.)/r->root_size)+r->N_root_z)%r->N_root_z;
int index = (k*r->N_root_y+j)*r->N_root_x+i;
return index;
}
int reb_reb_tree_get_octant_for_cell_in_cell(struct reb_treecell* nnode, struct reb_treecell *node){
int octant = 0;
if (nnode->x < node->x) octant+=1;
if (nnode->y < node->y) octant+=2;
if (nnode->z < node->z) octant+=4;
return octant;
}
void reb_tree_add_essential_node_to_node(struct reb_treecell* nnode, struct reb_treecell* node){
int o = reb_reb_tree_get_octant_for_cell_in_cell(nnode, node);
if (node->oct[o]==NULL){
node->oct[o] = nnode;
}else{
reb_tree_add_essential_node_to_node(nnode, node->oct[o]);
}
}
void reb_tree_add_essential_node(struct reb_simulation* const r, struct reb_treecell* node){
node->remote = 1;
for (int o=0;o<8;o++){
node->oct[o] = NULL;
}
int index = reb_particles_get_rootbox_for_node(r, node);
if (r->tree_root[index]==NULL){
r->tree_root[index] = node;
}else{
reb_tree_add_essential_node_to_node(node, r->tree_root[index]);
}
}
void reb_tree_prepare_essential_tree_for_gravity(struct reb_simulation* const r){
if (!r->tree_root) return;
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
for(size_t i=0;i<N_root;i++){
if (reb_communication_mpi_rootbox_is_local(r, i)==1){
reb_communication_mpi_prepare_essential_tree_for_gravity(r, r->tree_root[i]);
}else{
r->tree_root[i] = NULL;
}
}
}
void reb_tree_prepare_essential_tree_for_collisions(struct reb_simulation* const r){
size_t N_root = r->N_root_x*r->N_root_y*r->N_root_z;
for(size_t i=0;i<N_root;i++){
if (reb_communication_mpi_rootbox_is_local(r, i)==1){
reb_communication_mpi_prepare_essential_tree_for_collisions(r, r->tree_root[i]);
}else{
r->tree_root[i] = NULL;
}
}
}
#endif