hannorein / rebound

💫 An open-source multi-purpose N-body code.
https://rebound.readthedocs.io/
GNU General Public License v3.0
849 stars 219 forks source link

Possible timestep bug #615

Closed dmhernan closed 1 year ago

dmhernan commented 2 years ago

Hi, I've been doing some tests, and there's some strange behavior. I was trying to test WHFast's time symmetry. With the C interface, I got the expected behavior. I set up some initial conditions, and integrated for some timestep dt and time t. I reversed the sign of the timestep dt -> -dt, and again integrated for t. The error is close to machine precision. WHFast is time-symmetric, as expected. But then, the behavior was different with the python version. After reversing the timestep, the energy error magnitude didn't decrease to machine precision. I did confirm the time goes back to 0, and used (for C and python) sim.steps() rather than sim.integrate(), to ensure we arrive exactly at the origin. I'll update the thread if I find something new, and happy to post more specific code if that helps.

Also, a question/suggestion: I think an index of all Rebound commands and functions would be helpful. This is probably available somewhere, but I couldn't find it.

damian-666 commented 2 years ago

because this in an inherent issue with floating points numbers , each machine and or language implements it differently, for orbits it will barely ever matter esp if you can wrap the c engline and pass params from python script via s standard serializer, but not execute the integrator in phyton, , but with chaos, black holes, flybys, collisions, fluids- like motion, any sort of jerky interaction, will happen right away regardless.. not deterministic, and not reversible at all. also the timestep is crucial in explicit integrators, and no way to predict the maximum in advance.. one might see.. https://www.josstam.com/reversible where its immediately demonstable... his method is not unconditionally stable, it does require a CFL condition, ( max time step) and it does require using of fixed point., but can be used with existing integrators that use floats, and doesnt need to take snapshots of state , just start reversing with -dt..

this might help fix the issues wihtout overhauling the whole thing... https://arxiv.org/abs/2207.07695 its just a preprint but from Jos Stam... here is an excerpt: ( hope it might help, i his contributioins are legendary, im just a game maker using robotics)

Enter the Fixed Point numbers. This format was popular before floats were implemented in hardware. They work well if your data is bounded. Since our integrators are bounded for reasonable time step sizes h we can assume without loss of generality that our reals lie in the interval [1,−1]. We can then approximate a real number R by the following integer I: I = bR × BIG_INTc, (10) where BIG_INT is a large integer and b·c is the integer part of a real number. The only operation needed in our integration algorithm is addition as shown below in the implementation. To get a real number from a fixed number, we simply “invert” Eq. 10: R = I / BIG_INT. Actually, our implementation is hybrid and uses floating points to compute forces and fixed points when integrating. 4 Implementation In the following C++ code only the integration steps use the fixed format. Both the coordinates and the velocities are represented in fixed format. The time step can be any arbitrary float. The simulation is completely controlled by the force function which uses only floats. Existing simulations can therefore easily be implemented in this framework. We represent our fixed points as 64 bit integers “int64_t”, a type standardized since C++11. t y p e d e f int64_t f i x e d ; c o n s t f i x e d c_max_fixed = 0 x1000000000000000 ; s t a t i c f l o a t x 2 f ( c o n s t f i x e d i_f ){ r e t u r n i_f / ( f l o a t ) c_max_fixed ; 6 } s t a t i c f i x e d f 2 x ( c o n s t f l o a t i_f ) { r e t u r n ( f i x e d ) ( i_f c_max_fixed ) ; } s t a t i c f i x e d x , v ; s t a t i c i n t s i z e ; // d e f i n e your f a v o r i t e f o r c e f i e l d h e r e . s t a t i c f l o a t f o r c e ( c o n s t i n t i , c o n s t f l o a t i_x ) ; v o i d i n t e g r a t e ( c o n s t f l o a t h ) { c o n s t f l o a t h2 = 0 . 5 f h ; f o r ( i n t i = 0 ; i < s i z e ; i++) { x [ i ] += f 2 x ( h2 x 2 f ( v [ i ] ) ) ; } f o r ( i n t i = 0 ; i < s i z e ; i++) { f l o a t f = f o r c e ( i , x 2 f ( x [ i ] ) ) ; v [ i ] += f 2 x ( h f ) ; } f o r ( i n t i = 0 ; i < s i z e ; i++) { x [ i ] += f 2 x ( h2 x 2 f ( v [ i ] ) ) ; } } We also implemented the integrator in JavaScript (JS) to create a web based demo that is easily shared. Ironically, we were faced with the problem that JS does not differentiate between integers and float numbers. It only has the monolithic Number type. We found a solution by treating fixed point numbers as strings. We lost some efficiency of course. The implementation is as follows: v a r XN = 1000000000; f u n c t i o n X2F( x ) { r e t u r n (+x ) / XN; } f u n c t i o n F2X( f ) { ( ( f XN) | 0 ) . t o S t r i n g ( ) ; } f u n c t i o n XADD( x1 , x2 ) { (+x1 + +x2 ) . t o S t r i n g ( ) ; } The code is a bit obfuscated but JS programmers will recognize the hack to get an integer from a Number by taking the bitwise “or” with zero. The “+” operator transforms a string into a Number. See the accompanying HTML file from the link given below for the entire implementation. We would like to hear from readers who have a better solution. Note that this is just a quirk of the JS language. For most typed languages, like in our C++ implementation above,

dmhernan commented 2 years ago

Hi @damian-666 , not sure I fully understood you, but the problem I described is not a rounding error issue (I should have been clearer, I guess). We're talking about, say, 8 orders magnitude difference in error between the C and python case.

damian-666 commented 2 years ago

It could be orders of magnitude offs in very few steps as seen by the pendulum demo of Stam. If u tinker with it on the first JavaScript link a bit you will see.. but without the simulation details I'm not sure if my comment was relevant. Would have to be collision, planet flyby, or some kind of wrong timestep choice I'm only guessing. This kind of error isnt necessarily due to instability as if your simulator exploded, or timestep too large , it's due to apparent chaos because of the nonlinearity in the interactions in even a 2 body pendulum.. so with close orbits or swirling glaxies even slight rounding errors will lead to wildy differing evolutions under those contraints as he shows..

dmhernan commented 2 years ago

@damian-666 I can't say I'm yet able to follow you exactly, but I don't think the error is in the direction you suggest. I think I'd get the exact problem for a regular (e.g., periodic) set up.

damian-666 commented 2 years ago

maybe not, i brought it up in another thread and htere was a collision mentioned, and i think Jos Stams idea might address that class of trouble..... so if you can show it with some simple stable orbits conditions.. its must be another type of issue w the python types or interface or scales, not about hte double precision floating point types.. but in here i added some detaiil from the paper that might show how to fix that class of trouble in existing code bases like this..... anywasy hope it helps in some way STam is very legendary in advancing fluid and N body simulation, and i was exited he published a new paper after a long time ..

hannorein commented 1 year ago

@dmhernan The python version uses the same code as the C version, so I'm not sure what the issue could be. Can you please post a minimal working example that demonstrates this issue.

@damian-666 I don't think what you're describing is related to @dmhernan's issue.

dmhernan commented 1 year ago

Hi Hanno,

Surprisingly, this is completely working now in python. I get small error near machine precision now after reversing time. And I also do with C. I'm not sure what changed. I post a code snippet below where the output is correct. However, I believe @shadden has an example where this didn't work. Will try to followup with him and see if he can post.

Thanks, David

import rebound import os

sim = rebound.Simulation()

k = 0.01720209895 # Gaussian constant sim.G = k*k # Gravitational constant nsteps=int(1000000); dt = 10.0

sim.add( m=1.00000597682, x=-4.06428567034226e-3, y=-6.08813756435987e-3, z=-1.66162304225834e-6, vx=+6.69048890636161e-6, vy=-6.33922479583593e-6, vz=-3.13202145590767e-9) # Sun sim.add( m=1./1047.355, x=+3.40546614227466e+0, y=+3.62978190075864e+0, z=+3.42386261766577e-2, vx=-5.59797969310664e-3, vy=+5.51815399480116e-3, vz=-2.66711392865591e-6) # Jupiter sim.add( m=1./3501.6, x=+6.60801554403466e+0, y=+6.38084674585064e+0, z=-1.36145963724542e-1, vx=-4.17354020307064e-3, vy=+3.99723751748116e-3, vz=+1.67206320571441e-5) # Saturn

sim.move_to_com()

Configure simulation

sim.integrator = "whfast" sim.dt = dt E0 = sim.calculate_energy() sim.steps(nsteps) Ef = sim.calculate_energy() dE = (E0-Ef)/E0 print(dE,sim.t) sim.dt = -dt sim.steps(nsteps) Ef2 = sim.calculate_energy() dE = (E0-Ef2)/E0 print(dE,sim.t)

hannorein commented 1 year ago

Thanks for letting me know. FWIW, there is a unit test which checks for this but it might not check every possible scenario. So it could be that there is a somewhat non-standard case where a problem occurs. But I don't have an idea what the problem might be without being able to reproduce it.

DorianSchuylerAbbot commented 1 year ago

When I run this code, I get the following output:

-4.895028311062055e-09 10000000.0 4.17979946399406e-13 0.0

Unless I am mistaken, this error is larger than machine precision. Do you get something different?

dmhernan commented 1 year ago

Hi @DorianSchuylerAbbot , I think I got something similar. That error is consistent with time symmetry, I think. It's obviously far smaller in magnitude than the initial -5e-9 output.

DorianSchuylerAbbot commented 1 year ago

When I repeat with everything exactly the same but adding this: sim.ri_whfast.safe_mode = 0

I get:

3.001268338118218e-07 10000000.0 -3.838466616454865e-07 0.0

DorianSchuylerAbbot commented 1 year ago

I can't figure out any combination of sim.integrator_synchronize() and sim.move_to_com() that fixes this.

hannorein commented 1 year ago

You need to synchronize at the end of each sim.steps() to complete the step.

sim.integrator = "whfast"
sim.ri_whfast.safe_mode = False
sim.dt = dt
E0 = sim.calculate_energy()
sim.steps(nsteps)
sim.integrator_synchronize()
Ef = sim.calculate_energy()
dE = (E0-Ef)/E0
print(dE,sim.t)
sim.dt = -dt
sim.steps(nsteps)
sim.integrator_synchronize()
Ef2 = sim.calculate_energy()
dE = (E0-Ef2)/E0
print(dE,sim.t)

gives me

-4.895259195222924e-09 10000000.0
-2.3884568365680344e-14 0.0
DorianSchuylerAbbot commented 1 year ago

I tried the synchronize trick yesterday, but it didn't work. I think it is because I also have this line:

sim.ri_whfast.keep_unsynchronized = True # Also keep unsynchronized for speed up.

If I comment that line out it seems to work (I didn't notice this, thanks for pointing it out!). But with that line it doesn't work. Do you reproduce the same behavior?

hannorein commented 1 year ago

Yes, this is the expected behaviour. If you set sim.ri_whfast.keep_unsynchronized = True, then the call to synchronize() will only affect the output, but not the internal variables used for the integrations. The purpose of sim.ri_whfast.keep_unsynchronized is to allow the calculation of synchronized positions and velocities when that is needed for an output, without affecting the variables used for the integration (so you can reproduce an integration exactly -down to the last bit - even when changing the sampling interval).

I've also talked to Sam today and we couldn't reproduce his issue. So I'm tempted to close the issue for now? But please do feel free to reopen it if anything else comes up. WHFast really should be time symmetric and anything else would be a bug.

dmhernan commented 1 year ago

Thanks for the prompt help--- as far as I'm concerned, all set here. Maybe different people simultaneously had programming bugs!