Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issues with MERCURIUS and collision handling #475

Closed
Findus23 opened this issue Jan 8, 2021 · 9 comments
Closed

Issues with MERCURIUS and collision handling #475

Findus23 opened this issue Jan 8, 2021 · 9 comments

Comments

@Findus23
Copy link
Contributor

Findus23 commented Jan 8, 2021

Hello,

This time I have stumbled across a bit more complex issue, so this one will be a bit longer (I still tried to break it down to a minimal example).

A bit of background: I'm currently working on my Master's Thesis and my main plan would be to reproduce https://ui.adsabs.harvard.edu/abs/2020A%26A...634A..76B/abstract, but with a bit simplified collision handling (letting rebound do most of the work) and interpolation of a collision catalogue for water retention fractions instead of dedicated SPH simulations.
So the core part would be simulating late planetary formation and tracking water fractions over collisions. As this takes a long time, but also has a lot of collisions MERCURIUS seems like the obvious solution.

So this is the basis of the program that runs the simulation and on an encounter calculates collision speeds and angle

import numpy as np
from numpy import linalg
from rebound import Simulation, Collision

from data import add_particles

sim = Simulation()
sim.integrator = "mercurius"
sim.units = ('yr', 'AU', 'kg')
sim.dt = 0.005
sim.collision = "direct"
sim.ri_mercurius.hillfac = 3.

add_particles(sim)

t_max = 5000
t = 0
print(sim.G)
while t <= t_max:
    print(f"{t / t_max * 100:.2f}% done, N={sim.N}")
    try:
        sim.integrate(t)
        merc = sim.ri_mercurius
        print("dt", sim.dt)
        print("current mode", "ias15" if merc.mode else "whfast", merc.mode)
        t += 10
    except Collision:
        collided = []
        for p in sim.particles:
            if p.lastcollision >= sim.t - sim.dt:
                collided.append(p)
        assert len(collided) == 2
        p1, p2 = collided
        print(collided)
        # Custom resolution
        v1 = np.array(p1.vxyz)
        v2 = np.array(p2.vxyz)
        r1 = np.array(p1.xyz)
        r2 = np.array(p2.xyz)
        vdiff = v2 - v1
        rdiff = r2 - r1
        vdiff_n = linalg.norm(vdiff)
        rdiff_n = linalg.norm(rdiff)
        print("dt", sim.dt)
        merc = sim.ri_mercurius
        print(merc.mode)
        print("current mode", "ias15" if merc.mode else "whfast")

        print("rdiff", rdiff)
        print("vdiff", vdiff)
        print("sum_radii", p1.r + p2.r)
        print("rdiff_n", rdiff_n)
        print("fraction", rdiff_n / (p1.r + p2.r))
        print("vdiff_n", vdiff_n)
        ang = float(np.degrees(np.arccos(np.dot(rdiff, vdiff) / (rdiff_n * vdiff_n))))
        print("angle_deg", ang)
        exit()
uncollapse for data.py

Sorry for the rather verbose file, but I wanted to keep it reproducable independent on any code that reads input files.

from ctypes import c_uint32
from random import randint

from rebound import Simulation, Particle


def random_hash():
    return c_uint32(randint(0, 2 ** 32 - 1))


def add_particles(sim: Simulation):
    sim.add(Particle(m=1.9885e+30, hash=random_hash()))
    sim.add(Particle(
        m=1.8986e+27, a=5.20301, e=0.0,
        inc=0.00039742194265462077, omega=4.803,
        Omega=1.75356, M=5.82048,
        simulation=sim,
        hash=random_hash(),
        r=0.0003687524849718172
    ))
    sim.add(Particle(
        m=5.68484e+26, a=9.57882, e=0.0,
        inc=0.0007570435443695485, omega=5.84484,
        Omega=1.98338, M=5.29111,
        simulation=sim,
        hash=random_hash(),
        r=0.0002466966273146204
    ))
    sim.add(Particle(
        m=5.8666e+23, a=0.52, e=0.000223988,
        inc=5.207381809542802e-06, omega=4.95999,
        Omega=0.99878, M=5.96975,
        simulation=sim,
        hash=random_hash(),
        r=2.781466675139965e-05
    ))
    sim.add(Particle(
        m=5.8666e+23, a=0.514974, e=0.000223988,
        inc=5.207381809542802e-06, omega=4.95999,
        Omega=0.99878, M=5.96975,
        simulation=sim,
        hash=random_hash(),
        r=2.492998999385071e-05
    ))
    sim.add(Particle(
        m=5.86954e+23, a=0.545822, e=0.000300669,
        inc=1.3796757923845095e-05, omega=4.32854,
        Omega=2.25764, M=1.81603,
        simulation=sim,
        hash=random_hash(),
        r=2.493415378721156e-05
    ))
    sim.add(Particle(
        m=6.07907e+23, a=0.57872, e=0.000350543,
        inc=1.3277487564791743e-05, omega=1.35398,
        Omega=5.36399, M=0.90169,
        simulation=sim,
        hash=random_hash(),
        r=2.5227390298489546e-05
    ))
    sim.add(Particle(
        m=6.34093e+23, a=0.614068, e=0.000455467,
        inc=1.070206226725639e-05, omega=1.19722,
        Omega=2.29052, M=5.6147,
        simulation=sim,
        hash=random_hash(),
        r=2.5584539111178694e-05
    ))
    sim.add(Particle(
        m=6.62638e+23, a=0.652134, e=0.000668716,
        inc=1.2590176905356374e-05, omega=4.7008,
        Omega=2.48944, M=3.22678,
        simulation=sim,
        hash=random_hash(),
        r=2.5962831658190007e-05
    ))
    sim.add(Particle(
        m=6.93081e+23, a=0.693183, e=4.41476e-05,
        inc=2.167559304636798e-05, omega=0.0134283,
        Omega=4.51607, M=0.353733,
        simulation=sim,
        hash=random_hash(),
        r=2.635448957350133e-05
    ))
    sim.add(Particle(
        m=7.25442e+23, a=0.737501, e=0.000645432,
        inc=1.1687702055735148e-05, omega=5.77891,
        Omega=0.791524, M=1.15504,
        simulation=sim,
        hash=random_hash(),
        r=2.6758443249425374e-05
    ))
    sim.add(Particle(
        m=7.59847e+23, a=0.785404, e=0.000340178,
        inc=2.692187824493773e-05, omega=1.5452,
        Omega=3.03269, M=6.0796,
        simulation=sim,
        hash=random_hash(),
        r=2.7174944300690784e-05
    ))
    sim.add(Particle(
        m=7.96454e+23, a=0.837245, e=0.000879199,
        inc=3.2951118145952145e-05, omega=4.73529,
        Omega=0.929208, M=5.81611,
        simulation=sim,
        hash=random_hash(),
        r=2.76045185788869e-05
    ))
    sim.add(Particle(
        m=8.3544e+23, a=0.893419, e=0.000930815,
        inc=1.0648183953247324e-05, omega=4.4493,
        Omega=3.38601, M=1.12486,
        simulation=sim,
        hash=random_hash(),
        r=2.8047771881572024e-05
    ))
    sim.add(Particle(
        m=8.77001e+23, a=0.954365, e=0.000335656,
        inc=2.103767520476405e-05, omega=1.66953,
        Omega=4.83744, M=2.38779,
        simulation=sim,
        hash=random_hash(),
        r=2.850536727984013e-05
    ))
    sim.add(Particle(
        m=9.21353e+23, a=1.02058, e=0.00155251,
        inc=1.3664898298856924e-05, omega=3.94845,
        Omega=0.672637, M=4.1676,
        simulation=sim,
        hash=random_hash(),
        r=2.8978014783663286e-05
    ))
    sim.add(Particle(
        m=9.68732e+23, a=1.09261, e=0.000751591,
        inc=1.990198946049134e-05, omega=0.88087,
        Omega=1.49139, M=2.3532,
        simulation=sim,
        hash=random_hash(),
        r=2.946645067440372e-05
    ))
    sim.add(Particle(
        m=1.0194e+24, a=1.17108, e=0.000249891,
        inc=2.267461951020953e-05, omega=2.50902,
        Omega=4.98415, M=4.56278,
        simulation=sim,
        hash=random_hash(),
        r=2.9971477624895328e-05
    ))
    sim.add(Particle(
        m=1.07365e+24, a=1.2567, e=0.00047567,
        inc=1.2174701276919126e-05, omega=2.83094,
        Omega=5.07627, M=1.88762,
        simulation=sim,
        hash=random_hash(),
        r=3.049398537708938e-05
    ))
    sim.add(Particle(
        m=1.13179e+24, a=1.35025, e=0.000271617,
        inc=1.5834604358473674e-05, omega=2.86404,
        Omega=1.81951, M=4.93092,
        simulation=sim,
        hash=random_hash(),
        r=3.10347722289999e-05
    ))
    sim.add(Particle(
        m=1.1942e+24, a=1.45263, e=9.9722e-05,
        inc=3.874159700529373e-05, omega=3.65269,
        Omega=0.440055, M=5.47048,
        simulation=sim,
        hash=random_hash(),
        r=3.159504419375006e-05
    ))
    sim.add(Particle(
        m=1.26125e+24, a=1.56486, e=0.000158544,
        inc=1.6893565428828713e-05, omega=2.72206,
        Omega=0.158574, M=0.793716,
        simulation=sim,
        hash=random_hash(),
        r=3.217562596057693e-05
    ))
    sim.add(Particle(
        m=1.33339e+24, a=1.68809, e=0.000975616,
        inc=2.7487190389658696e-05, omega=3.65686,
        Omega=4.42584, M=5.8078,
        simulation=sim,
        hash=random_hash(),
        r=3.277774026203299e-05
    ))
    sim.add(Particle(
        m=1.41111e+24, a=1.82364, e=0.00103516,
        inc=2.214595927978045e-05, omega=1.5047,
        Omega=0.877455, M=6.17712,
        simulation=sim,
        hash=random_hash(),
        r=3.340259706891302e-05
    ))
    sim.add(Particle(
        m=1.49495e+24, a=1.97301, e=0.000293713,
        inc=5.449336803746776e-06, omega=1.75533,
        Omega=3.05701, M=2.65691,
        simulation=sim,
        hash=random_hash(),
        r=3.4051441429393753e-05
    ))
    sim.add(Particle(
        m=1.58554e+24, a=2.13792, e=0.000542054,
        inc=1.3737085116719409e-05, omega=2.14674,
        Omega=2.74039, M=5.82787,
        simulation=sim,
        hash=random_hash(),
        r=3.4748073877552615e-05
    ))
    sim.add(Particle(
        m=1.68356e+24, a=2.32033, e=0.00077591,
        inc=4.1744610516275174e-05, omega=5.2482,
        Omega=5.94733, M=6.15944,
        simulation=sim,
        hash=random_hash(),
        r=3.544986085009149e-05
    ))
    sim.add(Particle(
        m=1.78978e+24, a=2.52253, e=0.000608557,
        inc=7.051775949880329e-06, omega=0.313261,
        Omega=1.67444, M=2.97662,
        simulation=sim,
        hash=random_hash(),
        r=3.729249833371516e-05
    ))
    sim.add(Particle(
        m=1.90507e+24, a=2.74712, e=0.00042454,
        inc=7.411907187736839e-06, omega=0.58756,
        Omega=0.523396, M=0.476308,
        simulation=sim,
        hash=random_hash(),
        r=3.8076636216758454e-05
    ))
    sim.add(Particle(
        m=2.03043e+24, a=2.99715, e=0.000544016,
        inc=6.3588627835460605e-06, omega=0.136097,
        Omega=5.94856, M=4.88277,
        simulation=sim,
        hash=random_hash(),
        r=3.889414861239781e-05
    ))
    sim.add(Particle(
        m=2.16696e+24, a=3.27613, e=0.000404209,
        inc=8.07047227439186e-06, omega=0.386566,
        Omega=3.65507, M=0.505511,
        simulation=sim,
        hash=random_hash(),
        r=3.974707982325106e-05
    ))
    sim.add(Particle(
        m=2.31595e+24, a=3.58817, e=0.00097489,
        inc=4.007083976361261e-05, omega=4.15341,
        Omega=4.53277, M=4.49703,
        simulation=sim,
        hash=random_hash(),
        r=4.063790603111155e-05
    ))
    sim.add(Particle(
        m=2.47882e+24, a=3.93805, e=0.0001383,
        inc=1.4184779523148474e-05, omega=1.74986,
        Omega=2.01533, M=4.7946,
        simulation=sim,
        hash=random_hash(),
        r=4.156903339125982e-05
    ))

The program should run for a few seconds and output something like this:

[...]
10.80% done, N=33
dt 0.005
current mode whfast 0
11.00% done, N=33
[<rebound.Particle object, m=6.07907e+23 x=-0.31529722876817773 y=1.2438096592811663 z=-0.00015286847704374062 vx=5.438643933278682 vy=-6.058189301583095 vz=0.00029760553848370456>, <rebound.Particle object, m=6.34093e+23 x=-0.3142968518963612 y=1.2446300211757368 z=-0.00015949439108436036 vx=5.62106564596224 vy=-5.858525947654589 vz=-0.0009334228716780205>]
dt 0.005
0
current mode whfast
rdiff [ 1.00037687e-03  8.20361895e-04 -6.62591404e-06]
vdiff [ 0.18242171  0.19966335 -0.00123103]
sum_radii 5.0811929409668244e-05
rdiff_n 0.0012937509136096217
fraction 25.461558508806657
vdiff_n 0.27045267902413017
angle_deg 8.230132513103468

Now I have a few issues with the program:

  1. It seems like sim.ri_mercurius.mode always returns 0 (so whfast) even in situations where I assume that mercurius has already switched to IAS15. Also sim.dt stays the same (and shows the initial whfast dt even during collision), but I assume this is intentional.
  2. using p.lastcollision == sim.t as shown in CloseEncounters.ipynb doesn't seem to work, but p.lastcollision >= sim.t - sim.dt seems to reliably find the two colliding bodies.
  3. (the main issue) I assumed (but it's quite likely this is wrong as I am not yet completely familiar with all details of mercurius) that when an encounter occurs, IAS15 would reduce the step size a bit and integrate further until the two bodies touch (or at least until the difference in their positions is about the same as the sum of their radii) and then throw the Exception. This seems to be the case when just using IAS15, but when using mercurius as in the program above, the difference of the positions is many times larger than the sum of the radii which in term means that the calculated collision angle is too low.

Am I missing some obvious limitation or am I doing something wrong? I tried increasing sim.ri_mercurius.hillfac but even with a higher value (e.g. 30), but simulation takes a lot longer (which makes sense as the particles are quite close to each others; weirdly the program gets even quite a bit slower than just using IAS15), but I don't think it changes the distance when the simulation halts.

@hannorein
Copy link
Owner

Maybe I'm missing something, but I think what you want to do is implement this as a collision_resolve function. When the exception is thrown, the simulation stops immediately. That's nice for a quick test to see if particles collide, but not really useful if you want to continue with the integration. You'd need to piece together the particle data from some internal arrays, which could be messy. In short, use a collision_resolve function pointer and implement your logic there.

@Findus23
Copy link
Contributor Author

Findus23 commented Jan 8, 2021

Apart from the things I mentioned above (and a few older misunderstandings of mine #418) doing all the collision handling in Python worked really well for me (the part that is calculating the water fractions and then removing and adding the particles afterwards is of course missing in above example). And halting the simulation on collisions, getting some water retention estimations via some external methods and then continuing with the new particles is exactly what I would want to do. With the estimation code already being in Python I assumed it would be most straight-forward (and avoid additional errors in the implementation) to keep the merging code in Python.

@hannorein
Copy link
Owner

That's not a problem! The collision_resolve function pointer can point to a python function!

@Findus23
Copy link
Contributor Author

Findus23 commented Jan 8, 2021

Okay, I totally misunderstood the "pointer".

So instead of

def merge_particles(sim: Simulation):
    ...

try:
     sim.integrate(t)
except Collision:
    merge_particles(sim)

you suggest instead to directly run

def merge_particles(sim: POINTER_REB_SIM, collision: reb_collision)-> int:
    ...

sim.collision_resolve = merge_particles
sim.integrate(t)

?

If I understand it correctly, this means I no longer need the hack to find the two particles as collision.p1 and collision.p2 contain their indices, but I can no longer access the original Python sim to e.g. add multiple particle during the merging and pass other data to merge_particles, correct?

And even the collision.p1 don't seem helpful as I can no longer access sim.particles[collision.p1] (AttributeError: 'LP_Simulation' object has no attribute 'particles').

@hannorein
Copy link
Owner

hannorein commented Jan 8, 2021

You want to access the sim pointer that is given to your function. Because it's a pointer, you need to use the syntax sim.contents.particles...

You can change the two particles involved in the collisions. Depending on the return value of your merge_particle function, you can keep both, one, the other, or neither of the particles. This way, MERCURIUS will make all the necessary changes to the various internal arrays so that the simulation can continue. (But make sure to run a few quick tests)

If you want to do something more complicated, for example adding a whole swarm of new particles when a collisions occurs, you need to dig deep into the code and it can be complicated. If that's what you want to do, it might be best to chat over zoom to brain storm what the best way to implement this would be.

@Findus23
Copy link
Contributor Author

Findus23 commented Jan 8, 2021

Many thanks, I am starting to understand.

So it is more of a matter of

def merge_particles(sim_p: POINTER_REB_SIM, collision: reb_collision) -> int:
    sim: Simulation = sim_p.contents
    p1: Particle = sim.particles[collision.p1], 
    p2: Particle = sim.particles[collision.p2]
    ... # modify p1 and p2 to fit the collision outcome (but I have no way to access some_var for this)
    return 0 # or 1 or 2 to delete one of them

some_var = SomeObject()
sim.collision_resolve = merge_particles
sim.integrate(t)

In theory this should be enough for my use case (there will never be more than two remaining particles). I'll have to think a bit more about if this might work (one issue I still have is that I can't access variables from outside the integration) and do some tests if everything works as expected inside the merge_particles functions (I have only noticed that I can no longer abort it via CTRL+C). But for this it is too late in the evening and I will continue at another time and will get back to you.
Many thanks again for the help. This is getting me further by quite a bit.

But at least this solves the third issue and potentially also another unrelated one (having to remove and reorder all particles after a collision in case a semi-active particle collides with an active one and N_active changes therefore)

@hannorein
Copy link
Owner

You should be able to access variables:

from rebound import Simulation

global_var = 1.23

def collision_resolve(simp, c):
    print(global_var)
    return 0

sim = Simulation()
sim.collision = "direct"    
sim.collision_resolve = collision_resolve
sim.add(r=1,x=0)
sim.add(r=1,x=1)

sim.step()

@Findus23
Copy link
Contributor Author

Findus23 commented Jan 8, 2021

Okay, my issue was that this wouldn't work if collision_resolve was imported from another file, but I missed the most obvious solution for that:

from merge import really_merge_particles


def merge_particles(sim_p: POINTER_REB_SIM, collision: reb_collision) -> int:
    return really_merge_particles(sim_p, collision, some_var)


some_var = SomeObject()
sim.collision_resolve = merge_particles
sim.integrate(t)

One last question before I'll try out all of this in a few days: I assume rebound doesn't use the hash anywhere internally, but rather the pointers to p1 and p2, so it should be fine if their hash changes during the collision, right? (I'll check just in case)

@hannorein
Copy link
Owner

Yes, you can change the hash at any time without consequences.

I'll close this issue for now, but feel free to reopen it if you run into other issues!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants