diff --git a/docs/index.md b/docs/index.md index 154f5656e..f0e6490c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -10,8 +10,8 @@ REBOUND is an N-body integrator, i.e. a software package that can integrate the * No dependencies on external libraries. * Runs natively on Linux, MacOS, and Windows. * Symplectic integrators ([WHFast](integrators/#whfast), [SEI](integrators/#sei), [LEAPFROG](integrators/#leapfrog), [EOS](integrators/#embedded-operator-splitting-method-eos)) -* Hybrid symplectic integrators for planetary dynamics with close encounters ([MERCURIUS](integrators/#mercurius)) * Hybrid reversible integrators for planetary dynamics with arbitrary close encounters ([TRACE](integrators/#trace)) +* Hybrid symplectic integrators for planetary dynamics with close encounters ([MERCURIUS](integrators/#mercurius)) * High order symplectic integrators for integrating planetary systems ([SABA](integrators/#saba), WH Kernel methods) * High accuracy non-symplectic integrator with adaptive time-stepping ([IAS15](integrators/#ias15)) * Can integrate arbitrary user-defined ODEs that are coupled to N-body dynamics for tides, spin, etc diff --git a/docs/integrators.md b/docs/integrators.md index bfabb25ac..8d97a5b74 100644 --- a/docs/integrators.md +++ b/docs/integrators.md @@ -404,7 +404,7 @@ The `reb_integrator_mercurius` structure contains the configuration and data str TRACE is a hybrid time-reversible integrator, based on the algorithm described in [Hernandez & Dehnen 2023](https://ui.adsabs.harvard.edu/abs/2023MNRAS.522.4639H/abstract). It uses WHFast for long term integrations but switches time-reversibly to BS or IAS15 for all close encounters. TRACE is appropriate for systems with a dominant central mass that will occasionally have close encounters. -A paper describing the TRACE implementation is in preparation. +The TRACE implementation is described in [Lu, Hernandez & Rein](https://ui.adsabs.harvard.edu/abs/2024arXiv240503800L/abstract). The following code enables TRACE and sets the critical radius to 4 Hill radii diff --git a/rebound/integrators/trace.py b/rebound/integrators/trace.py index 6f979be7d..e42d746db 100644 --- a/rebound/integrators/trace.py +++ b/rebound/integrators/trace.py @@ -67,7 +67,6 @@ def __repr__(self): ("_current_C", ctypes.c_uint), ("_force_accept", ctypes.c_uint), ] - # To be honest I'm not sure what these do: do we need this? - Tiger @property def S(self): raise AttributeError("You can only set C function pointers from python.") diff --git a/rebound/tests/test_trace_basic.py b/rebound/tests/test_trace_basic.py index 604a1366c..8691e9361 100644 --- a/rebound/tests/test_trace_basic.py +++ b/rebound/tests/test_trace_basic.py @@ -36,6 +36,19 @@ def test_trace_encounter_condition(self): for j in range(i+1,sim.N): self.assertEqual(sim.ri_trace._current_Ks[i*sim.N+j],0) + def test_trace_encounter_prediction(self): + sim = rebound.Simulation() + sim.add(m=1) + sim.add(m=9.55e-4,x=5.2) + sim.add(x=5.3,y=0.36,vy=-7.2) # Non-encounter prediction misses this + sim.integrator = "TRACE" + sim.dt = 0.01 + sim.ri_trace.r_crit_hill = 1 + sim.step() + + self.assertEqual(sim.ri_trace._encounter_N,3) + + if __name__ == "__main__": unittest.main() diff --git a/src/integrator_trace.c b/src/integrator_trace.c index 2194db43e..a93f856cf 100644 --- a/src/integrator_trace.c +++ b/src/integrator_trace.c @@ -41,40 +41,76 @@ int reb_integrator_trace_switch_default(struct reb_simulation* const r, const unsigned int i, const unsigned int j){ struct reb_integrator_trace* const ri_trace = &(r->ri_trace); + const double h2 = r->dt/2.; + + const double dxi = r->particles[i].x; + const double dyi = r->particles[i].y; + const double dzi = r->particles[i].z; + + const double dxj = r->particles[j].x; + const double dyj = r->particles[j].y; + const double dzj = r->particles[j].z; + + const double dx = dxi - dxj; + const double dy = dyi - dyj; + const double dz = dzi - dzj; + const double rp = dx*dx + dy*dy + dz*dz; + double dcriti6 = 0.0; double dcritj6 = 0.0; const double m0 = r->particles[0].m; if (r->particles[i].m != 0){ - const double dxi = r->particles[i].x; // in dh - const double dyi = r->particles[i].y; - const double dzi = r->particles[i].z; const double di2 = dxi*dxi + dyi*dyi + dzi*dzi; const double mr = r->particles[i].m/(3.*m0); dcriti6 = di2*di2*di2*mr*mr; } if (r->particles[j].m != 0){ - const double dxj = r->particles[j].x; // in dh - const double dyj = r->particles[j].y; - const double dzj = r->particles[j].z; const double dj2 = dxj*dxj + dyj*dyj + dzj*dzj; const double mr = r->particles[j].m/(3.*m0); dcritj6 = dj2*dj2*dj2*mr*mr; } - const double dx = r->particles[i].x - r->particles[j].x; - const double dy = r->particles[i].y - r->particles[j].y; - const double dz = r->particles[i].z - r->particles[j].z; - const double d2 = dx*dx + dy*dy + dz*dz; - double r_crit_hill2 = ri_trace->r_crit_hill*ri_trace->r_crit_hill; double dcritmax6 = r_crit_hill2 * r_crit_hill2 * r_crit_hill2 * MAX(dcriti6,dcritj6); - return d2*d2*d2 < dcritmax6; + if (rp*rp*rp < dcritmax6) return 1; + + const double dvx = r->particles[i].vx - r->particles[j].vx; + const double dvy = r->particles[i].vy - r->particles[j].vy; + const double dvz = r->particles[i].vz - r->particles[j].vz; + const double v2 = dvx*dvx + dvy*dvy + dvz*dvz; + + const double qv = dx*dvx + dy*dvy + dz*dvz; + int d; + + if (qv == 0.0){ // Small + // minimum is at present, which is already checked for + return 0; + } + else if (qv < 0){ + d = 1; + } + else{ + d = -1; + } + + double dmin2; + double tmin = -d*qv/v2; + if (tmin < h2){ + // minimum is in the window + dmin2 = rp - qv*qv/v2; + } + else{ + dmin2 = rp + 2*d*qv*h2 + v2*h2*h2; + } + + return dmin2*dmin2*dmin2 < dcritmax6; } + int reb_integrator_trace_switch_peri_distance(struct reb_simulation* const r, const unsigned int j){ const struct reb_integrator_trace* const ri_trace = &(r->ri_trace); const double peri = ri_trace->peri_crit_distance; @@ -519,9 +555,9 @@ void reb_integrator_trace_part1(struct reb_simulation* r){ // These arrays are only used within one timestep. // Can be recreated without loosing bit-wise reproducibility. ri_trace->particles_backup = realloc(ri_trace->particles_backup,sizeof(struct reb_particle)*N); + ri_trace->particles_backup_kepler = realloc(ri_trace->particles_backup_kepler,sizeof(struct reb_particle)*N); ri_trace->current_Ks = realloc(ri_trace->current_Ks,sizeof(int)*N*N); ri_trace->encounter_map = realloc(ri_trace->encounter_map,sizeof(int)*N); - ri_trace->particles_backup_kepler = realloc(ri_trace->particles_backup_kepler,sizeof(struct reb_particle)*N); ri_trace->N_allocated = N; } @@ -842,7 +878,6 @@ void reb_integrator_trace_reset(struct reb_simulation* r){ free(r->ri_trace.particles_backup_additional_forces); r->ri_trace.particles_backup_additional_forces = NULL; - free(r->ri_trace.encounter_map); r->ri_trace.encounter_map = NULL;