#include "rebound.h"
#include "rebound_internal.h"
#include <stdio.h>
#include <math.h>
#include <string.h>
#ifdef MPI
#include <mpi.h>
#include "communication_mpi.h"
#endif #ifdef OPENMP
#include <omp.h>
#endif #include "tree.h"
#include "particle.h"
#include "simulation.h"
#include "simulationarchive.h"
#include "output.h"
#include "server.h"
#include "display.h"
#include "boundary.h"
#include "binarydata.h"
#include "fmemopen.h"
#include "collision.h"
#include "tools.h"
#include "gravity.h"
#include "integrator_bs.h"
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#endif
#define MAX(a, b) ((a) < (b) ? (b) : (a))
struct reb_thread_info {
struct reb_simulation* r;
double tmax;
};
size_t reb_simulation_struct_size(){
return sizeof(struct reb_simulation);
}
void reb_simulation_stop(struct reb_simulation* const r){
r->status = REB_STATUS_USER;
}
void reb_simulation_warning(struct reb_simulation* const r, const char* const msg){
int save_messages = 0;
if (r != NULL && r->save_messages) save_messages = 1;
reb_message(&r->messages, save_messages, REB_MESSAGE_TYPE_WARNING, msg);
}
void reb_simulation_error(struct reb_simulation* const r, const char* const msg){
int save_messages = 0;
if (r != NULL && r->save_messages) save_messages = 1;
reb_message(&r->messages, save_messages, REB_MESSAGE_TYPE_ERROR, msg);
}
void reb_simulation_info(struct reb_simulation* const r, const char* const msg){
int save_messages = 0;
if (r != NULL && r->save_messages) save_messages = 1;
reb_message(&r->messages, save_messages, REB_MESSAGE_TYPE_INFO, msg);
}
static void reb_simulation_step(struct reb_simulation* const r);
struct reb_simulation* reb_simulation_create(){
struct reb_simulation* r = calloc(1,sizeof(struct reb_simulation));
r->rand_seed = reb_tools_get_rand_seed();
r->G = 1;
r->dt = 0.001;
r->gravity = REB_GRAVITY_BASIC;
r->root_size = -1;
r->opening_angle2 = 0.25;
r->OMEGAZ = -1.0;
r->N_root_x = 1;
r->N_root_y = 1;
r->N_root_z = 1;
r->N_active = SIZE_MAX;
r->N_targets = SIZE_MAX;
r->exact_finish_time = 1;
r->output_timing_last = -1;
r->simulationarchive_version = 5;
r->is_synchronized = 1;
reb_simulation_set_integrator(r, "ias15");
return r;
}
void reb_simulation_free(struct reb_simulation* const r){
if (r->integrator.callbacks.free){
r->integrator.callbacks.free(r->integrator.state);
}
r->gravity = REB_GRAVITY_BASIC; r->gravity_ignore_terms = REB_GRAVITY_IGNORE_TERMS_NONE;
if (r->simulationarchive_filename){
free(r->simulationarchive_filename);
}
if(r->display_settings){
free(r->display_settings);
}
#ifdef OPENGL
if(r->display_data){
if (r->display_data->window){ usleep(100);
}
if (r->display_data->window){ reb_simulation_info(NULL, "Waiting for OpenGL visualization to shut down...");
while(r->display_data->window){
usleep(100);
}
}
pthread_mutex_destroy(&(r->display_data->mutex));
if (r->display_data->r_copy){
reb_simulation_free(r->display_data->r_copy);
}
if (r->display_data->particle_data){
free(r->display_data->particle_data);
}
if (r->display_data->orbit_data){
free(r->display_data->orbit_data);
}
free(r->display_data);
}
#endif #ifdef SERVER
reb_simulation_stop_server(r);
#endif free(r->gravity_cs);
free(r->collisions);
free(r->tree_root);
if(r->free_particle_ap){
for(size_t i=0; i<r->N; i++){
r->free_particle_ap(&r->particles[i]);
}
}
free(r->particles);
free(r->particles_var);
for (size_t i=0;i<r->N_name_list;i++){
free(r->name_list[i]);
}
free(r->name_list);
free(r->name_hash_table);
if (r->messages){
for (size_t i=0;i<reb_messages_max_N;i++){
free(r->messages[i]);
}
}
if (r->messages){
free(r->messages);
}
if (r->extras_cleanup){
r->extras_cleanup(r);
}
if (r->var_config){
free(r->var_config);
}
free(r->odes);
free(r);
}
static void* set_integrator(struct reb_simulation* r, const char* name, struct reb_integrator integrator){
if (r->integrator.callbacks.free && r->integrator.state){
r->integrator.callbacks.free(r->integrator.state);
}
r->integrator.callbacks = integrator;
r->integrator.name = name;
if (r->integrator.callbacks.create){
r->integrator.state = r->integrator.callbacks.create();
}else{
r->integrator.state = NULL;
}
return r->integrator.state; }
void* reb_simulation_set_integrator(struct reb_simulation* r, const char* name){
if (r->integrator.name && !r->is_synchronized){
reb_simulation_warning(r, "Changing integrators while simulation is not synchronized results in undefined behaviour.");
}
#define X(iname) if (strcmp(name, #iname)==0) { return set_integrator(r, #iname, reb_integrator_##iname); }
REB_BUILTIN_INTEGRATORS
#undef X
if (reb_integrator_configurations_custom){
int i=0;
while(reb_integrator_configurations_custom[i].name){ const struct reb_integrator_configuration integrator = reb_integrator_configurations_custom[i];
if (strcmp(integrator.name, name)==0){
return set_integrator(r, integrator.name, integrator.callbacks);
}
i++;
}
}
reb_simulation_error(r, "Integrator not found.");
return NULL;
}
static void run_heartbeat(struct reb_simulation* const r){
if (r->heartbeat){ r->heartbeat(r); } if (r->exit_max_distance){
const double max2 = r->exit_max_distance * r->exit_max_distance;
const struct reb_particle* const particles = r->particles;
const size_t N = r->N;
for (size_t i=0;i<N;i++){
struct reb_particle p = particles[i];
double r2 = p.x*p.x + p.y*p.y + p.z*p.z;
if (r2>max2){
r->status = REB_STATUS_ESCAPE;
}
}
}
if (r->exit_min_distance){
const double min2 = r->exit_min_distance * r->exit_min_distance;
const struct reb_particle* const particles = r->particles;
const size_t N = r->N;
for (size_t i=0;i<N;i++){
struct reb_particle pi = particles[i];
for (size_t j=0;j<i;j++){
struct reb_particle pj = particles[j];
const double x = pi.x-pj.x;
const double y = pi.y-pj.y;
const double z = pi.z-pj.z;
const double r2 = x*x + y*y + z*z;
if (r2<min2){
r->status = REB_STATUS_ENCOUNTER;
}
}
}
}
}
static int error_message_waiting(struct reb_simulation* const r){
if (r->messages){
for (size_t i=0;i<reb_messages_max_N;i++){
if (r->messages[i]!=NULL){
if (r->messages[i][0]=='e'){
return 1;
}
}
}
}
return 0;
}
static int reb_check_exit(struct reb_simulation* const r, const double tmax, double* last_full_dt){
if(r->status <= REB_STATUS_SINGLE_STEP){
if(r->status == REB_STATUS_SINGLE_STEP){
r->status = REB_STATUS_PAUSED;
}else{
r->status++;
}
}
while(r->status == REB_STATUS_PAUSED || r->status == REB_STATUS_SCREENSHOT){
#ifdef __EMSCRIPTEN__
emscripten_sleep(100);
#else
usleep(1000);
#endif
if (reb_sigint){ r->status = REB_STATUS_SIGINT;
}
}
const double dtsign = copysign(1.,r->dt); if (error_message_waiting(r)){
r->status = REB_STATUS_GENERIC_ERROR;
}
if (r->status>=0){
}else if(tmax!=INFINITY){
if(r->exact_finish_time==1){
if ((r->t+r->dt)*dtsign>=tmax*dtsign){ if (r->t==tmax){
r->status = REB_STATUS_SUCCESS;
}else if(r->status == REB_STATUS_LAST_STEP){
double tscale = 1e-12*fabs(tmax); if (tscale<1e-200){ tscale = 1e-12;
}
if (fabs(r->t-tmax)<tscale){
r->status = REB_STATUS_SUCCESS;
}else{
reb_simulation_synchronize(r); r->dt = tmax-r->t;
}
}else{
r->status = REB_STATUS_LAST_STEP; reb_simulation_synchronize(r); if (r->dt_last_done!=0.){ *last_full_dt = r->dt_last_done; }
r->dt = tmax-r->t;
}
}else{
if (r->status == REB_STATUS_LAST_STEP){
r->status = REB_STATUS_RUNNING;
}
}
}else{
if (r->t*dtsign>=tmax*dtsign){ r->status = REB_STATUS_SUCCESS; }
}
}
#ifndef MPI
if (!r->N && !r->N_odes){
reb_simulation_warning(r,"No particles in simulation. Will exit.");
r->status = REB_STATUS_NO_PARTICLES; }
#else
int status_max = 0;
MPI_Allreduce(&(r->status), &status_max, 1, MPI_INT, MPI_MAX, MPI_COMM_WORLD);
if (status_max>=0){
r->status = status_max;
}
#endif return r->status;
}
static void* reb_simulation_integrate_raw(void* args){
reb_sigint = 0;
signal(SIGINT, reb_sigint_handler);
struct reb_thread_info* thread_info = (struct reb_thread_info*)args;
struct reb_simulation* const r = thread_info->r;
if (thread_info->tmax != r->t){
int dt_sign = (thread_info->tmax > r->t) ? 1.0 : -1.0; r->dt = copysign(r->dt, dt_sign);
}
double last_full_dt = r->dt; r->dt_last_done = 0.;
if (r->testparticle_hidewarnings==0 && reb_particle_check_testparticles(r)){
reb_simulation_warning(r,"At least one test particle (type 0) has finite mass. This might lead to unexpected behaviour. Set testparticle_hidewarnings=1 to hide this warning.");
}
if (r->status != REB_STATUS_PAUSED && r->status != REB_STATUS_SCREENSHOT){ r->status = REB_STATUS_RUNNING;
}
run_heartbeat(r);
#ifdef __EMSCRIPTEN__
double t0 = emscripten_performance_now();
#endif
while(reb_check_exit(r,thread_info->tmax,&last_full_dt)<0){
#ifdef __EMSCRIPTEN__
double t1 = emscripten_performance_now();
if (t1-t0>1000./120.){ t0 = t1;
emscripten_sleep(0); }
#endif
#ifdef OPENGL
if (r->display_data){
while (r->display_data->need_copy == 1){
usleep(10);
}
pthread_mutex_lock(&(r->display_data->mutex));
}
#endif #ifdef SERVER
if (r->server_data){
while (r->server_data->need_copy == 1){
usleep(10);
}
#ifdef _WIN32
WaitForSingleObject(r->server_data->mutex, INFINITE);
#else
pthread_mutex_lock(&(r->server_data->mutex));
#endif r->server_data->mutex_locked_by_integrate = 1;
}
#endif if (r->simulationarchive_filename){ reb_simulationarchive_heartbeat(r);}
reb_simulation_step(r);
run_heartbeat(r);
if (reb_sigint){
r->status = REB_STATUS_SIGINT;
}
#ifdef OPENGL
if (r->display_data){
pthread_mutex_unlock(&(r->display_data->mutex));
}
#endif #ifdef SERVER
if (r->server_data){
#ifdef _WIN32
ReleaseMutex(r->server_data->mutex);
#else
pthread_mutex_unlock(&(r->server_data->mutex));
#endif r->server_data->mutex_locked_by_integrate = 0;
}
#endif if (r->usleep > 0){
usleep(r->usleep);
}
}
reb_simulation_synchronize(r);
if(r->exact_finish_time==1){ r->dt = last_full_dt;
}
if (r->simulationarchive_filename){ reb_simulationarchive_heartbeat(r);}
return NULL;
}
enum REB_STATUS reb_simulation_integrate(struct reb_simulation* const r, double tmax){
struct reb_thread_info thread_info = {
.r = r,
.tmax = tmax,
};
#ifdef OPENGL
#ifdef __EMSCRIPTEN__
if (r->display_data==NULL){
r->display_data = calloc(sizeof(struct reb_display_data),1);
r->display_data->r = r;
reb_display_init(r); }
reb_simulation_integrate_raw(&thread_info);
#else
if (r->display_data==NULL){
r->display_data = calloc(sizeof(struct reb_display_data),1);
r->display_data->r = r;
if (pthread_mutex_init(&(r->display_data->mutex), NULL)){
reb_simulation_error(r,"Mutex creation failed.");
}
}
if (pthread_create(&r->display_data->compute_thread,NULL,reb_simulation_integrate_raw,&thread_info)){
reb_simulation_error(r, "Error creating compute thread.");
}
reb_display_init(r); if (pthread_join(r->display_data->compute_thread,NULL)){
reb_simulation_error(r, "Error joining compute thread.");
}
#endif #else
reb_simulation_integrate_raw(&thread_info);
#endif return r->status;
}
void reb_simulation_steps(struct reb_simulation* const r, size_t N_steps){
run_heartbeat(r);
for (size_t i=0;i<N_steps;i++){
if (r->simulationarchive_filename){ reb_simulationarchive_heartbeat(r);}
reb_simulation_step(r);
run_heartbeat(r);
}
reb_simulation_synchronize(r);
if (r->simulationarchive_filename){ reb_simulationarchive_heartbeat(r);}
}
static void reb_simulation_step(struct reb_simulation* const r){
struct reb_timeval time_beginning;
gettimeofday(&time_beginning,NULL);
if (r->pre_timestep_modifications){
reb_simulation_synchronize(r);
r->pre_timestep_modifications(r);
}
PROFILING_START();
if (r->integrator.callbacks.step){
r->integrator.callbacks.step(r, r->integrator.state);
}
if (r->N_odes && strcmp(r->integrator.name, "bs")!=0){
double dt = r->dt_last_done;
double t = r->t - r->dt_last_done; double forward = (dt>0.) ? 1. : -1.;
struct reb_integrator_bs_state* bs = reb_integrator_bs.create();
while(t*forward < r->t*forward && fabs((r->t - t)/(fabs(r->t)+1e-16))>1e-15){
if (reb_sigint > 1){
r->status = REB_STATUS_SIGINT;
return;
}
if (bs->dt_proposed !=0.){
double max_dt = fabs(r->t - t);
dt = fabs(bs->dt_proposed);
if (dt > max_dt){ dt = max_dt;
bs->first_or_last_step = 1;
}
dt *= forward;
}
int success = reb_integrator_bs_step_odes(r, bs, dt);
if (success){
t += dt;
}
}
reb_integrator_bs.free(bs);
}
PROFILING_STOP(PROFILING_CAT_INTEGRATOR);
if (r->post_timestep_modifications){
reb_simulation_synchronize(r);
r->post_timestep_modifications(r);
}
r->did_modify_particles = 0;
if (r->N_var){
reb_simulation_rescale_var(r);
}
PROFILING_START();
reb_boundary_check(r);
PROFILING_STOP(PROFILING_CAT_BOUNDARY);
if (r->collision != REB_COLLISION_NONE){
#ifdef MPI
reb_communication_mpi_distribute_particles(r);
#endif
PROFILING_START();
reb_collision_search(r);
PROFILING_STOP(PROFILING_CAT_COLLISION);
}
struct reb_timeval time_end;
gettimeofday(&time_end,NULL);
r->walltime_last_step = time_end.tv_sec-time_beginning.tv_sec+(time_end.tv_usec-time_beginning.tv_usec)/1e6;
r->walltime_last_steps_sum += r->walltime_last_step;
r->walltime_last_steps_N +=1;
if (r->walltime_last_steps_sum > 0.1){
r->walltime_last_steps = r->walltime_last_steps_sum/r->walltime_last_steps_N;
r->walltime_last_steps_sum =0;
r->walltime_last_steps_N = 0;
}
r->walltime += r->walltime_last_step;
r->steps_done++; }
void reb_simulation_copy_with_messages(struct reb_simulation* r_copy, struct reb_simulation* r, enum REB_BINARYDATA_ERROR_CODE* warnings){
char* bufp;
size_t sizep;
reb_binarydata_simulation_to_stream(r, &bufp,&sizep);
FILE* fin = reb_fmemopen(bufp, sizep, "r");
reb_binarydata_input_fields(r_copy, fin, warnings);
fclose(fin);
free(bufp);
}
char* reb_simulation_diff_char(struct reb_simulation* r1, struct reb_simulation* r2){
char* bufp1;
char* bufp2;
char* bufp;
size_t sizep1, sizep2, size;
reb_binarydata_simulation_to_stream(r1, &bufp1,&sizep1);
reb_binarydata_simulation_to_stream(r2, &bufp2,&sizep2);
reb_binarydata_diff(bufp1, sizep1, bufp2, sizep2, &bufp, &size, REB_BINARYDATA_OUTPUT_BUFFER);
free(bufp1);
free(bufp2);
return bufp;
}
void reb_simulation_synchronize(struct reb_simulation* r){
if (r->integrator.callbacks.synchronize){
r->integrator.callbacks.synchronize(r, r->integrator.state);
}
}
void reb_simulation_update_acceleration(struct reb_simulation* r){
if (r->gravity==REB_GRAVITY_CUSTOM){
if (!r->gravity_custom){
reb_simulation_error(r, "REB_GRAVITY_CUSTOM selected, but r->gravity_custom function pointer not provided.");
}
r->gravity_custom(r);
return;
}
switch (r->gravity){
case REB_GRAVITY_NONE: for (size_t j=0; j<r->N; j++){
r->particles[j].ax = 0;
r->particles[j].ay = 0;
r->particles[j].az = 0;
}
break;
case REB_GRAVITY_TREE:
reb_gravity_tree_calculate_acceleration(r);
break;
case REB_GRAVITY_JACOBI:
reb_gravity_jacobi_calculate_acceleration(r);
break;
case REB_GRAVITY_BASIC:
reb_gravity_basic_calculate_acceleration(r);
break;
case REB_GRAVITY_COMPENSATED:
reb_gravity_compensated_calculate_acceleration(r);
break;
default:
reb_simulation_error(r, "Gravity module not found.");
return;
}
if (r->N_var){
switch (r->gravity){
case REB_GRAVITY_BASIC:
reb_gravity_basic_calculate_acceleration_var(r);
break;
case REB_GRAVITY_NONE:
break;
default:
reb_simulation_error(r, "Variational gravity calculation not implemented in selected gravity module. Please use REB_GRAVITY_BASIC.");
return;
}
}
if (r->additional_forces){
r->additional_forces(r);
}
}
int reb_simulation_diff(struct reb_simulation* r1, struct reb_simulation* r2, int output_option){
if (output_option!=REB_BINARYDATA_OUTPUT_PRINT && output_option!=REB_BINARYDATA_OUTPUT_NONE){
reb_simulation_error(r1, "Invalid output_option in reb_simulation_diff().");
return -1;
}
char* bufp1;
char* bufp2;
size_t sizep1, sizep2;
reb_binarydata_simulation_to_stream(r1, &bufp1,&sizep1);
reb_binarydata_simulation_to_stream(r2, &bufp2,&sizep2);
int ret = reb_binarydata_diff(bufp1, sizep1, bufp2, sizep2, NULL, NULL, output_option);
free(bufp1);
free(bufp2);
return ret;
}
struct reb_simulation* reb_simulation_copy(struct reb_simulation* r){
struct reb_simulation* r_copy = reb_simulation_create();
enum REB_BINARYDATA_ERROR_CODE warnings = REB_BINARYDATA_WARNING_NONE;
reb_simulation_copy_with_messages(r_copy,r,&warnings);
r = reb_binarydata_process_warnings(r, warnings);
return r_copy;
}
void reb_simulation_two_largest_particles(struct reb_simulation* r, size_t* p1, size_t* p2) {
struct reb_particle* particles = r->particles;
*p1 = SIZE_MAX;
*p2 = SIZE_MAX;
double largest1 = -1.0;
double largest2 = -1.0;
#ifdef OPENMP
int num_threads;
struct two_max {
double largest1;
double largest2;
size_t p1;
size_t p2;
};
struct two_max *thread_max;
#pragma omp parallel
{
num_threads = omp_get_num_threads();
#pragma omp master
{
thread_max = (struct two_max *)malloc(num_threads * sizeof(struct two_max));
}
#pragma omp barrier
int thread_id = omp_get_thread_num();
thread_max[thread_id].largest1 = -1.0;
thread_max[thread_id].largest2 = -1.0;
thread_max[thread_id].p1 = SIZE_MAX;
thread_max[thread_id].p2 = SIZE_MAX;
#pragma omp for
for (size_t i=0; i<r->N; i++) {
if (particles[i].r > thread_max[thread_id].largest1) {
thread_max[thread_id].largest2 = thread_max[thread_id].largest1;
thread_max[thread_id].p2 = thread_max[thread_id].p1;
thread_max[thread_id].largest1 = particles[i].r;
thread_max[thread_id].p1 = i;
} else if (particles[i].r > thread_max[thread_id].largest2) {
thread_max[thread_id].largest2 = particles[i].r;
thread_max[thread_id].p2 = i;
}
}
}
for (int i=0; i<num_threads; i++) {
if (thread_max[i].largest1 > largest1) {
largest2 = largest1;
*p2 = *p1;
largest1 = thread_max[i].largest1;
*p1 = thread_max[i].p1;
} else if (thread_max[i].largest1 > largest2) {
largest2 = thread_max[i].largest1;
*p2 = thread_max[i].p1;
}
if (thread_max[i].largest2 > largest2) {
largest2 = thread_max[i].largest2;
*p2 = thread_max[i].p2;
}
}
free(thread_max);
#else
for (size_t i=0; i<r->N; i++) {
if (particles[i].r > largest1) {
largest2 = largest1;
*p2 = *p1;
largest1 = particles[i].r;
*p1 = i;
}else{
if (particles[i].r > largest2) {
largest2 = particles[i].r;
*p2 = i;
}
}
}
#endif }
void reb_simulation_get_serialized_particle_data(struct reb_simulation* r, double* m, double* radius, double (*xyz)[3], double (*vxvyvz)[3], double (*xyzvxvyvz)[6]){
const size_t N = r->N;
struct reb_particle* restrict const particles = r->particles;
for (size_t i=0;i<N;i++){
if (m){
m[i] = particles[i].m;
}
if (radius){
radius[i] = particles[i].r;
}
if (xyz){
xyz[i][0] = particles[i].x;
xyz[i][1] = particles[i].y;
xyz[i][2] = particles[i].z;
}
if (vxvyvz){
vxvyvz[i][0] = particles[i].vx;
vxvyvz[i][1] = particles[i].vy;
vxvyvz[i][2] = particles[i].vz;
}
if (xyzvxvyvz){
xyzvxvyvz[i][0] = particles[i].x;
xyzvxvyvz[i][1] = particles[i].y;
xyzvxvyvz[i][2] = particles[i].z;
xyzvxvyvz[i][3] = particles[i].vx;
xyzvxvyvz[i][4] = particles[i].vy;
xyzvxvyvz[i][5] = particles[i].vz;
}
}
}
void reb_simulation_set_serialized_particle_data(struct reb_simulation* r, double* m, double* radius, double (*xyz)[3], double (*vxvyvz)[3], double (*xyzvxvyvz)[6]){
const size_t N = r->N;
struct reb_particle* restrict const particles = r->particles;
for (size_t i=0;i<N;i++){
if (m){
particles[i].m = m[i];
}
if (radius){
particles[i].r = radius[i] ;
}
if (xyz){
particles[i].x = xyz[i][0];
particles[i].y = xyz[i][1];
particles[i].z = xyz[i][2];
}
if (vxvyvz){
particles[i].vx = vxvyvz[i][0];
particles[i].vy = vxvyvz[i][1];
particles[i].vz = vxvyvz[i][2];
}
if (xyzvxvyvz){
particles[i].x = xyzvxvyvz[i][0];
particles[i].y = xyzvxvyvz[i][1];
particles[i].z = xyzvxvyvz[i][2];
particles[i].vx = xyzvxvyvz[i][3];
particles[i].vy = xyzvxvyvz[i][4];
particles[i].vz = xyzvxvyvz[i][5];
}
}
}
void reb_simulation_add_display_settings(struct reb_simulation*r){
if (r->display_settings){
reb_simulation_error(r,"Simulation already has display settings.");
return;
}
r->display_settings = calloc(1,sizeof(struct reb_display_settings));
reb_display_settings_init(r, r->display_settings);
}