Skip to content

Module airball.core

This module contains the core functionality of AIRBALL. It handles the logic and geometry of adding, integrating, and removing a flyby object in a REBOUND simulation.

There are two main methods for integrating a flyby. The standard method which uses IAS15 for the entire integration and a hybrid method, which switches from WHCKL to IAS15 when the flyby star comes within a specified distance to the outermost object in the simulation.

Info

For most situations, we suggest using IAS15 for the entire integration. For a 3-body system (Sun, Neptune, and flyby star), a typical flyby interaction often takes less than a second of wall-time to integrate. If you're finding that the integration is 'hanging', try setting sim.ri_ias15.adaptive_mode = 2. When the distance between the objects is very large, IAS15 may struggle to converge at each timestep so it defaults to the minimum step size.

The following documentation was automatically generated from the docstrings.

airball.core.flyby(sim, star, **kwargs)

Simulate a stellar flyby to a REBOUND simulation.

Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference. Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False. If any pointers have been assigned to the simulation, then the default overwrite=True is recommended so that the connections between the simulation and the pointers remain intact.

Parameters:

Name Type Description Default
sim Simulation

The simulation (star and planets) that will experience the flyby star.

required
star Star

The star that will flyby the given REBOUND simulation.

required

Other Parameters:

Name Type Description
hybrid bool

Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for hybrid_flyby will be passed to that function.

rmax float

The starting distance of the flyby star in units of AU. Default is \(10^5\) AU.

overwrite bool

Determines whether or not to return a copy of sim (overwrite=False) or integrate using the original sim (overwrite=True). Default is True. If any pointers have been assigned to the simulation, then overwrite=True is recommended.

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or int. Default is None.

hash str

The name for the flyby star. Default is 'flybystar'.

Returns:

Name Type Description
sim Simulation

The simulation after the flyby. This is the same object as the input sim if overwrite=True.

Example
import rebound
import airball

sim = rebound.Simulation()
sim.add(m=1)
sim.add(m=5e-5, a=30)
star = airball.Star(m=1, b=500, v=5)
airball.flyby(sim, star, rmax=4e5, hash="newstar")
Source code in src/airball/core.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
def flyby(sim, star, **kwargs):
    """
    Simulate a stellar flyby to a REBOUND simulation.

    Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference. Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying `overwrite=False`. If any pointers have been assigned to the simulation, then the default `overwrite=True` is recommended so that the connections between the simulation and the pointers remain intact.

    Args:
        sim (Simulation): The simulation (star and planets) that will experience the flyby star.
        star (Star): The star that will flyby the given REBOUND simulation.

    Keyword Args:
        hybrid (bool, optional): Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for `hybrid_flyby` will be passed to that function.
        rmax (float, optional): The starting distance of the flyby star in units of AU. Default is $10^5$ AU.
        overwrite (bool, optional): Determines whether or not to return a copy of sim (`overwrite=False`) or integrate using the original sim (`overwrite=True`). Default is True. If any pointers have been assigned to the simulation, then `overwrite=True` is recommended.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or int. Default is None.
        hash (str, optional): The name for the flyby star. Default is `'flybystar'`.

    Returns:
        sim (Simulation): The simulation after the flyby. This is the same object as the input sim if `overwrite=True`.

    Example:
        ```python
        import rebound
        import airball

        sim = rebound.Simulation()
        sim.add(m=1)
        sim.add(m=5e-5, a=30)
        star = airball.Star(m=1, b=500, v=5)
        airball.flyby(sim, star, rmax=4e5, hash="newstar")
        ```
    """

    if kwargs.get("hybrid", False):
        return hybrid_flyby(sim, star, **kwargs)
    else:
        if sim.integrator == "whfast":
            _warnings.warn(
                "Did you intend to use the hybrid method with WHFast? WHFast may not correctly resolve close encounters.",
                RuntimeWarning,
            )

        overwrite = kwargs.get("overwrite", True)
        if not overwrite:
            sim = sim.copy()
        hash = kwargs.get("hash", "flybystar")
        if "hash" in kwargs:
            del kwargs["hash"]
        add_star_to_sim(sim, star, hash, **kwargs)
        tperi = sim.particles[hash].T - sim.t  # Compute the time to periapsis for the flyby star from the current time.
        sim.integrate(sim.t + 2 * tperi)
        remove_star_from_sim(sim, hash)
        return sim

airball.core.flybys(sims, stars, **kwargs)

Run serial flybys in parallel.

This function uses joblib underneath to run the flybys in parallel. This is useful for running multiple flybys at once. Because of how joblib handles memory during parallelization, this function will manually overwrite the necessary data in the simulation objects if overwrite=True, otherwise it will return new simulation objects. Because of these limitations, if any pointers have been assigned to the simulation(s), then the connections between the simulation(s) and the pointers will always be lost. If you require access to pointers during integration then see the Multiple Flybys example for more details.

Parameters:

Name Type Description Default
sims list of Simulations

A list of REBOUND simulations to integrate flybys with. If only one simulation is given, then AIRBALL will duplicate it to match the number of Stars given.

required
stars Stars

The objects that will flyby the given REBOUND simulations.

required

Other Parameters:

Name Type Description
overwrite boolean

Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.

hashes list of str

A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.

rmax float

The starting distance of the flyby object (in units of the REBOUND Simulation). Default is \(10^5\).

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.

inds list of ints

An array of indices to determine which sims and stars to integrate. Default is all of them.

n_jobs int

The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.

verbose int

The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.

hybrid bool

Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for hybrid_flyby will be passed to that function.

crossoverFactor float

For hybrid method. The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.

particle_index int

For hybrid method. The simulation particle index to define the crossoverFactor with respect to. Default is 1.

Returns:

Name Type Description
flybys list of Simulations

The simulations that experienced a flyby.

Source code in src/airball/core.py
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
def flybys(sims, stars, **kwargs):
    """
    Run serial flybys in parallel.

    This function uses `joblib` underneath to run the flybys in parallel. This is useful for running multiple flybys at once. Because of how `joblib` handles memory during parallelization, this function will manually overwrite the necessary data in the simulation objects if `overwrite=True`, otherwise it will return new simulation objects. Because of these limitations, if any pointers have been assigned to the simulation(s), then the connections between the simulation(s) and the pointers will always be lost. If you require access to pointers during integration then see the [Multiple Flybys](../../examples/multiple-flybys/#memory-management-with-pointers-using-joblib) example for more details.

    Args:
        sims (list of Simulations): A list of REBOUND simulations to integrate flybys with. If only one simulation is given, then AIRBALL will duplicate it to match the number of Stars given.
        stars (Stars): The objects that will flyby the given REBOUND simulations.

    Keyword Args:
        overwrite (boolean, optional): Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.
        hashes (list of str, optional): A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.
        rmax (float, optional): The starting distance of the flyby object (in units of the REBOUND Simulation). Default is $10^5$.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.
        inds (list of ints, optional): An array of indices to determine which sims and stars to integrate. Default is all of them.
        n_jobs (int, optional): The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.
        verbose (int, optional): The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.
        hybrid (bool, optional): Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for `hybrid_flyby` will be passed to that function.
        crossoverFactor (float, optional): For hybrid method. The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.
        particle_index (int, optional): For hybrid method. The simulation particle index to define the crossoverFactor with respect to. Default is 1.

    Returns:
        flybys (list of Simulations): The simulations that experienced a flyby.
    """
    Nruns = 0
    try:
        Nruns = len(sims)
        if Nruns != len(stars):
            raise Exception("Sims and stars are unequal lengths")
    except:  # noqa: E722
        Nruns = len(stars)
        sims = [sims.copy() for _ in range(Nruns)]

    try:
        hashes = kwargs["hashes"]
        if not _tools.isList(hashes):
            hashes = Nruns * [hashes]
        elif len(hashes) != Nruns:
            raise Exception("List arguments must be same length.")
    except KeyError:
        hashes = Nruns * ["flybystar"]

    try:
        rmax = kwargs["rmax"]
        rmax = _tools.verify_unit(rmax, _u.au)
        if len(rmax.shape) == 0:
            rmax = _np.array(stars.N * [rmax.value]) << rmax.unit
        elif len(rmax) != stars.N:
            raise Exception("List arguments must be same length.")
    except KeyError:
        rmax = _np.array(Nruns * [1e5]) << _u.au
    if _np.any(stars.b > rmax):
        raise Exception("Some stellar impact parameters are greater than the stellar starting distance, rmax.")

    try:
        crossoverFactor = kwargs["crossoverFactor"]
        if not _tools.isList(crossoverFactor):
            crossoverFactor = Nruns * [crossoverFactor]
        elif len(crossoverFactor) != Nruns:
            raise Exception("List arguments must be same length.")
    except KeyError:
        crossoverFactor = Nruns * [30]

    overwrite = kwargs.get("overwrite", True)
    plane = kwargs.get("plane", None)
    inds = kwargs.get("inds", _np.arange(Nruns))
    n_jobs = kwargs.get("n_jobs", -1)
    verbose = kwargs.get("verbose", 0)
    hybrid = kwargs.get("hybrid", False)
    particle_index = kwargs.get("particle_index", 1)

    sim_results = _joblib.Parallel(n_jobs=n_jobs, verbose=verbose)(
        _joblib.delayed(flyby)(
            sim=sims[int(i)],
            star=stars[i],
            rmax=rmax[i],
            hash=hashes[i],
            overwrite=overwrite,
            plane=plane,
            hybrid=hybrid,
            crossoverFactor=crossoverFactor[i],
            particle_index=particle_index,
        )
        for i in inds
    )
    if overwrite:
        for i in range(Nruns):
            sims[i].t = sim_results[i].t
            sims[i].dt = sim_results[i].dt
            sims[i].ri_whfast.recalculate_coordinates_this_timestep = sim_results[
                i
            ].ri_whfast.recalculate_coordinates_this_timestep
            sims[i].ri_whfast.safe_mode = sim_results[i].ri_whfast.safe_mode
            sims[i].integrator = sim_results[i].integrator
            sims[i].gravity = sim_results[i].gravity
            sims[i].G = sim_results[i].G
            sims[i].walltime = sim_results[i].walltime
            sims[i].walltime_last_steps = sim_results[i].walltime_last_steps
            sims[i].dt_last_done = sim_results[i].dt_last_done
            sims[i].steps_done = sim_results[i].steps_done
            for j in range(sims[i].N):
                sims[i].particles[j] = sim_results[i].particles[j]
        return sims
    else:
        return sim_results

airball.core.hybrid_flyby(sim, star, **kwargs)

Simulate a stellar flyby to a REBOUND simulation.

Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference. Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False. This function assumes that you are using the WHCKL integrator with REBOUND and uses IAS15 (instead of WHCKL) for the closest approach if q_star < planet_a * crossoverFactor

Parameters:

Name Type Description Default
sim Simulation

The REBOUND Simulation (star and planets) that will experience the flyby star

required
star Star

An AIRBALL Star object to flyby the given REBOUND simulation.

required

Other Parameters:

Name Type Description
rmax float

The starting distance of the flyby star in units of the REBOUND Simulation. Default is \(10^5\) AU.

crossoverFactor float

The value for when to switch to IAS15 as a multiple of sim.particles[particle_index].a. Default is 100x, i.e. 100 times the largest length scale in the system, calculated as 100 times the semi-major axis of outermost bound particle.

particle_index int

The particle index to consider for the crossoverFactor. Default is 1.

overwrite boolean

Determines whether or not to return a copy of sim (overwrite=False) or integrate using the original sim (overwrite=True). Default is True. overwrite=True is recommended if any pointers have been assigned to the simulation.

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or int. Default is None.

Returns:

Name Type Description
sim Simulation

The simulation after the flyby. This is the same object as the input sim if overwrite=True.

Example
import rebound
import airball

sim = rebound.Simulation()
sim.add(m=1)
sim.add(m=5e-5, a=30)
star = airball.Star(m=1, b=500, v=5)
airball.hybrid_flyby(sim, star, rmax=4e5, hash="newstar", crossoverFactor=40)
Source code in src/airball/core.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
def hybrid_flyby(sim, star, **kwargs):
    """
    Simulate a stellar flyby to a REBOUND simulation.

    Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference.
    Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False.
    This function assumes that you are using the WHCKL integrator with REBOUND and uses IAS15 (instead of WHCKL) for the closest approach if q_star < planet_a * crossoverFactor

    Args:
        sim (Simulation): The REBOUND Simulation (star and planets) that will experience the flyby star
        star (Star): An AIRBALL Star object to flyby the given REBOUND simulation.

    Keyword Args:
        rmax (float, optional): The starting distance of the flyby star in units of the REBOUND Simulation. Default is $10^5$ AU.
        crossoverFactor (float, optional): The value for when to switch to IAS15 as a multiple of sim.particles[particle_index].a. Default is 100x, i.e. 100 times the largest length scale in the system, calculated as 100 times the semi-major axis of outermost bound particle.
        particle_index (int, optional): The particle index to consider for the crossoverFactor. Default is 1.
        overwrite (boolean, optional): Determines whether or not to return a copy of sim (`overwrite=False`) or integrate using the original sim (`overwrite=True`). Default is True. `overwrite=True` is recommended if any pointers have been assigned to the simulation.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or int. Default is None.

    Returns:
        sim (Simulation): The simulation after the flyby. This is the same object as the input sim if overwrite=True.

    Example:
        ```python
        import rebound
        import airball

        sim = rebound.Simulation()
        sim.add(m=1)
        sim.add(m=5e-5, a=30)
        star = airball.Star(m=1, b=500, v=5)
        airball.hybrid_flyby(sim, star, rmax=4e5, hash="newstar", crossoverFactor=40)
        ```
    """

    overwrite = kwargs.get("overwrite", True)
    if not overwrite:
        sim = sim.copy()
    hash = kwargs.get("hash", "flybystar")
    sim_units = _tools.rebound_units(sim)

    if "hash" in kwargs:
        del kwargs["hash"]
    star_vars = add_star_to_sim(sim, star, hash, **kwargs)

    tperi = sim.particles[hash].T - sim.t  # Compute the time to periapsis for the flyby star from the current time.

    # Integrate the flyby. Start at the current time and go to twice the time to periapsis.
    switch, tIAS15 = _time_to_periapsis_from_crossover_point(
        sim,
        sim_units,
        star_elements=star_vars,
        crossoverFactor=kwargs.get("crossoverFactor", None),
        index=kwargs.get("particle_index", None),
    )
    dt = _tools.timestep_for_perihelion_resolution(sim)
    if _np.isnan(dt):
        dt = sim.dt
    if switch:
        t_switch = sim.t + tperi - tIAS15.value
        t_switch_back = sim.t + tperi + tIAS15.value
        t_end = sim.t + 2 * tperi

        _integrate_with_whckl(sim, t_switch, dt)
        _integrate_with_ias15(sim, t_switch_back)
        _integrate_with_whckl(sim, t_end, dt)

    else:
        _integrate_with_whckl(sim, tmax=(sim.t + 2 * tperi), dt=dt)

    # Remove the flyby star.
    remove_star_from_sim(sim, hash=hash)

    return sim

airball.core.hybrid_flybys(sims, stars, **kwargs)

Run serial flybys in parallel.

This function uses joblib underneath to run the flybys in parallel. This is useful for running multiple flybys at once. Because of how joblib handles memory during parallelization, this function will manually overwrite the necessary data in the simulation objects if overwrite=True, otherwise it will return new simulation objects. Because of these limitations, if any pointers have been assigned to the simulation(s), then the connections between the simulation(s) and the pointers will always be lost. If you require access to pointers during integration then see the Multiple Flybys example for more details.

Parameters:

Name Type Description Default
sims list of Simulations

REBOUND simulations to integrate flybys with. If only one simulation is given, then AIRBALL will duplicate it to match the number of Stars given.

required
stars Stars

The objects that will flyby the given REBOUND simulations.

required

Other Parameters:

Name Type Description
crossoverFactor float

The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.

overwrite boolean

Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation object will be returned. This keeps all original pointers attached to it. This only works if a list of Simulations is given.

rmax float

The starting distance of the flyby object (in units of the REBOUND Simulation). Default is \(10^5\).

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.

particle_index int

The simulation particle index to define the crossoverFactor with respect to. Default is 1.

hashes list of str

A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.

inds list of ints

An array of indices to determine which sims and stars to integrate. Default is all of them.

n_jobs int

The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.

verbose int

The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.

Returns:

Name Type Description
hybrid_flybys list of Simulations

The simulations that experienced a flyby.

Source code in src/airball/core.py
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
def hybrid_flybys(sims, stars, **kwargs):
    """
    Run serial flybys in parallel.

    This function uses `joblib` underneath to run the flybys in parallel. This is useful for running multiple flybys at once. Because of how `joblib` handles memory during parallelization, this function will manually overwrite the necessary data in the simulation objects if `overwrite=True`, otherwise it will return new simulation objects. Because of these limitations, if any pointers have been assigned to the simulation(s), then the connections between the simulation(s) and the pointers will always be lost. If you require access to pointers during integration then see the [Multiple Flybys](../../examples/multiple-flybys/#memory-management-with-pointers-using-joblib) example for more details.

    Args:
        sims (list of Simulations): REBOUND simulations to integrate flybys with. If only one simulation is given, then AIRBALL will duplicate it to match the number of Stars given.
        stars (Stars): The objects that will flyby the given REBOUND simulations.

    Keyword Args:
        crossoverFactor (float, optional): The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.
        overwrite (boolean, optional): Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation object will be returned. This keeps all original pointers attached to it. This only works if a list of Simulations is given.
        rmax (float, optional): The starting distance of the flyby object (in units of the REBOUND Simulation). Default is $10^5$.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.
        particle_index (int, optional): The simulation particle index to define the crossoverFactor with respect to. Default is 1.
        hashes (list of str, optional): A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.
        inds (list of ints, optional): An array of indices to determine which sims and stars to integrate. Default is all of them.
        n_jobs (int, optional): The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.
        verbose (int, optional): The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.

    Returns:
        hybrid_flybys (list of Simulations): The simulations that experienced a flyby.
    """
    Nruns = 0
    try:
        Nruns = len(sims)
        if Nruns != len(stars):
            raise Exception("Sims and stars are unequal lengths")
    except:  # noqa: E722
        Nruns = len(stars)
        sims = [sims.copy() for _ in range(Nruns)]

    try:
        crossoverFactor = kwargs["crossoverFactor"]
        if not _tools.isList(crossoverFactor):
            crossoverFactor = Nruns * [crossoverFactor]
        elif len(crossoverFactor) != Nruns:
            raise Exception("List arguments must be same length.")
    except KeyError:
        crossoverFactor = Nruns * [30]

    try:
        rmax = kwargs["rmax"]
        rmax = _tools.verify_unit(rmax, _u.au)
        if len(rmax.shape) == 0:
            rmax = _np.array(stars.N * [rmax.value]) << rmax.unit
        elif len(rmax) != stars.N:
            raise Exception("List arguments must be same length.")
    except KeyError:
        rmax = _np.array(Nruns * [1e5]) << _u.au
    if _np.any(stars.b > rmax):
        raise Exception("Some stellar impact parameters are greater than the stellar starting distance, rmax.")

    try:
        hashes = kwargs["hashes"]
        if not _tools.isList(hashes):
            hashes = Nruns * [hashes]
        elif len(hashes) != Nruns:
            raise Exception("List arguments must be same length.")
    except KeyError:
        hashes = Nruns * ["flybystar"]

    inds = kwargs.get("inds", _np.arange(Nruns))
    overwrite = kwargs.get("overwrite", True)
    n_jobs = kwargs.get("n_jobs", -1)
    verbose = kwargs.get("verbose", 0)
    particle_index = kwargs.get("particle_index", 1)
    plane = kwargs.get("plane", None)

    sim_results = _joblib.Parallel(n_jobs=n_jobs, verbose=verbose)(
        _joblib.delayed(hybrid_flyby)(
            sim=sims[int(i)],
            star=stars[i],
            rmax=rmax[i],
            crossoverFactor=crossoverFactor[i],
            overwrite=overwrite,
            particle_index=particle_index,
            plane=plane,
            hash=hashes[i],
        )
        for i in inds
    )

    if overwrite:
        for i in range(Nruns):
            sims[i].t = sim_results[i].t
            sims[i].dt = sim_results[i].dt
            sims[i].ri_whfast.recalculate_coordinates_this_timestep = sim_results[
                i
            ].ri_whfast.recalculate_coordinates_this_timestep
            sims[i].ri_whfast.safe_mode = sim_results[i].ri_whfast.safe_mode
            sims[i].integrator = sim_results[i].integrator
            sims[i].gravity = sim_results[i].gravity
            sims[i].G = sim_results[i].G
            sims[i].walltime = sim_results[i].walltime
            sims[i].walltime_last_steps = sim_results[i].walltime_last_steps
            sims[i].dt_last_done = sim_results[i].dt_last_done
            sims[i].steps_done = sim_results[i].steps_done
            for j in range(sims[i].N):
                sims[i].particles[j] = sim_results[i].particles[j]
        return sims
    else:
        return sim_results

airball.core.successive_flybys(sim, stars, **kwargs)

Simulate a stellar flyby to a REBOUND simulation.

Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference. Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False.

This function assumes that you are using the WHCKL integrator with REBOUND and uses IAS15 (instead of WHCKL) for the closest approach if q_star < planet_a * crossoverFactor

Parameters:

Name Type Description Default
sim Simulation

REBOUND Simulation that will experience the flyby stars

required
stars Stars

an AIRBALL Stars object. The Stars that will pass by the given REBOUND simulation.

required

Other Parameters:

Name Type Description
snapshots bool

Determines whether or not to return snapshots of the simulation after each flyby. Default is False.

overwrite boolean

Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.

hashes list of str

A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.

rmax float

The starting distance of the flyby object (in units of the REBOUND Simulation). Default is \(10^5\).

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.

inds list of ints

An array of indices to determine which sims and stars to integrate. Default is all of them.

n_jobs int

The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.

verbose int

The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.

hybrid bool

Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for hybrid_flyby will be passed to that function.

crossoverFactor float

For hybrid method. The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.

particle_index int

For hybrid method. The simulation particle index to define the crossoverFactor with respect to. Default is 1.

Returns:

Name Type Description
successive_flybys Simulation or list of Simulations

The simulation that experienced a flyby, or snapshots of the simulation after each flyby if snapshots=True.

Example
import rebound
import airball

sim = rebound.Simulation()
sim.add(m=1)
sim.add(m=5e-5, a=30)
stars = airball.Stars(m=1, b=500, v=5)
airball.successive_flybys(sim, stars, rmax=4e5, hash="newstar")
Source code in src/airball/core.py
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
def successive_flybys(sim, stars, **kwargs):
    """
    Simulate a stellar flyby to a REBOUND simulation.

    Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference.
    Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False.

    This function assumes that you are using the WHCKL integrator with REBOUND and uses IAS15 (instead of WHCKL) for the closest approach if q_star < planet_a * crossoverFactor

    Args:
        sim (Simulation): REBOUND Simulation that will experience the flyby stars
        stars (Stars): an AIRBALL Stars object. The Stars that will pass by the given REBOUND simulation.

    Keyword Args:
        snapshots (bool, optional): Determines whether or not to return snapshots of the simulation after each flyby. Default is False.
        overwrite (boolean, optional): Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.
        hashes (list of str, optional): A list of hash values for adding and removing stars from simulations. Default is 'flybystar'.
        rmax (float, optional): The starting distance of the flyby object (in units of the REBOUND Simulation). Default is $10^5$.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.
        inds (list of ints, optional): An array of indices to determine which sims and stars to integrate. Default is all of them.
        n_jobs (int, optional): The number of jobs per CPU to run in parallel. Default is -1, meaning all CPUs.
        verbose (int, optional): The amount of details to display for the parallel jobs. Default is 0. Range is 0-50.
        hybrid (bool, optional): Determines whether or not to use the hybrid method. Default is False. If True, then any kwargs for `hybrid_flyby` will be passed to that function.
        crossoverFactor (float, optional): For hybrid method. The value for when to switch to IAS15 as a multiple of sim.particles[1].a Default is 30.
        particle_index (int, optional): For hybrid method. The simulation particle index to define the crossoverFactor with respect to. Default is 1.

    Returns:
        successive_flybys (Simulation or list of Simulations): The simulation that experienced a flyby, or snapshots of the simulation after each flyby if `snapshots=True`.

    Example:
        ```python
        import rebound
        import airball

        sim = rebound.Simulation()
        sim.add(m=1)
        sim.add(m=5e-5, a=30)
        stars = airball.Stars(m=1, b=500, v=5)
        airball.successive_flybys(sim, stars, rmax=4e5, hash="newstar")
        ```
    """

    # Do not overwrite given sim.
    overwrite = kwargs.get("overwrite", True)
    if not overwrite:
        sim = sim.copy()
    hashes = kwargs.get("hashes", [f"flybystar{i}" for i in range(stars.N)])
    saveSnapshots = kwargs.get("snapshots", False)
    snapshots = []
    if saveSnapshots:
        snapshots.append(sim.copy())
    for i, star in enumerate(stars):
        if overwrite:
            flyby(sim, star, hash=hashes[i], **kwargs)
        else:
            sim = flyby(sim, star, hash=hashes[i], **kwargs)
        if saveSnapshots:
            snapshots.append(sim.copy())
    if saveSnapshots:
        return snapshots
    else:
        return sim

airball.core.concurrent_flybys(sim, stars, start_times, **kwargs)

Simulate concurrent stellar flybys to a REBOUND simulation.

Warning

Integrating flybys concurrently may give unintuitive results. Use with caution. Hybrid mode is not supported for concurrent flybys.

Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference. Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying overwrite=False.

Parameters:

Name Type Description Default
sim Simulation

a REBOUND Simulation (star and planets) that will experience the flyby star.

required
stars Stars

Multiple stars that will flyby the given REBOUND simulation.

required
start_times list

An array of times for the stars to be added to the sim.

required

Other Parameters:

Name Type Description
overwrite boolean

Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.

hashes list of str

A list of hash values for adding and removing stars from simulations. Default is ['flybystar0', 'flybystar1', ...].

rmax float

The starting distance of the flyby object (in units of the REBOUND Simulation). Default is \(10^5\).

plane str or int

The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.

Example
import rebound
import airball

sim = rebound.Simulation()
sim.add(m=1)
sim.add(m=5e-5, a=30)
oc = airball.OpenCluster()
stars = oc.random_stars(3)
start_times = oc.cumulative_encounter_times(stars.N)
airball.concurrent_flybys(sim, stars, start_times, rmax=4e5)
Source code in src/airball/core.py
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
def concurrent_flybys(sim, stars, start_times, **kwargs):
    """
    Simulate concurrent stellar flybys to a REBOUND simulation.

    !!! warning
        Integrating flybys concurrently may give unintuitive results. Use with caution. Hybrid mode is not supported for concurrent flybys.

    Because REBOUND Simulations are C structs underneath the Python, this function can pass the simulation by reference.
    Meaning, any changes made inside this function to the REBOUND simulation are permanent. This can be avoided by specifying `overwrite=False`.

    Args:
        sim (Simulation): a REBOUND Simulation (star and planets) that will experience the flyby star.
        stars (airball.stars.Stars): Multiple stars that will flyby the given REBOUND simulation.
        start_times (list): An array of times for the stars to be added to the sim.

    Keyword Args:
        overwrite (boolean, optional): Sets whether or not to return new simulation objects or overwrite the given ones. Default is True, meaning the same simulation objects will be returned. This keeps all original pointers attached to it.
        hashes (list of str, optional): A list of hash values for adding and removing stars from simulations. Default is ['flybystar0', 'flybystar1', ...].
        rmax (float, optional): The starting distance of the flyby object (in units of the REBOUND Simulation). Default is $10^5$.
        plane (str or int, optional): The plane defining the orientation of the star, None, 'invariable', 'ecliptic', or Int. Default is None.

    Example:
        ```python
        import rebound
        import airball

        sim = rebound.Simulation()
        sim.add(m=1)
        sim.add(m=5e-5, a=30)
        oc = airball.OpenCluster()
        stars = oc.random_stars(3)
        start_times = oc.cumulative_encounter_times(stars.N)
        airball.concurrent_flybys(sim, stars, start_times, rmax=4e5)
        ```
    """
    message = "Integrating flybys concurrently may give unintuitive results. Use with caution."
    _warnings.warn(message, RuntimeWarning)

    # Do not overwrite given sim.
    overwrite = kwargs.get("overwrite", True)
    if not overwrite:
        sim = sim.copy()
    sim_units = _tools.rebound_units(sim)

    rmax = kwargs.get("rmax", 1e5 * _u.au)
    plane = kwargs.get("plane")
    start_times = _tools.verify_unit(start_times, sim_units["time"]).value
    hashes = kwargs.get("hashes", [f"flybystar{i}" for i in range(stars.N)])

    # Using the sim and the start times, compute the end times for the flyby stars.
    all_times = _np.zeros((stars.N, 2))
    for star_number, star in enumerate(stars):
        tmp_sim = sim.copy()
        hash = hashes[star_number]
        add_star_to_sim(tmp_sim, star, hash=hash, rmax=rmax, plane=plane)
        # Compute the time to periapsis for the flyby star from the current simulation time.
        tperi = tmp_sim.particles[hash].T - tmp_sim.t
        end_time = start_times[star_number] + tmp_sim.t + 2 * tperi
        all_times[star_number] = [start_times[star_number], end_time]

    # Sort the event times sequentially.
    all_times = all_times.flatten()
    event_order = _np.argsort(all_times)
    max_event_number = len(all_times)

    # Integrate the flybys, adding and removing them at the appropriate times.
    event_number = 0
    while event_number < max_event_number:
        event_index = event_order[event_number]
        star_number = event_index // 2
        sim.integrate(all_times[event_index])
        if event_index % 2 == 0:
            add_star_to_sim(
                sim,
                stars[star_number],
                hash=hashes[star_number],
                rmax=rmax,
                plane=plane,
            )
        else:
            remove_star_from_sim(sim, hash=hashes[star_number])
        event_number += 1
    return sim

airball.core.add_star_to_sim(sim, star, hash, **kwargs)

Adds a Star to a REBOUND Simulation in the specified plane. Because REBOUND Simulations are C structs underneath Python, this function passes the simulation by reference and modifies the simulation in place.

Parameters:

Name Type Description Default
sim Simulation

The REBOUND Simulation containing the star and planets that will experience a flyby.

required
star Star

The star that will flyby the given REBOUND simulation.

required
hash str

A string to refer to the Star object by in the REBOUND simulation.

required

Other Parameters:

Name Type Description
rmax float

The starting distance of the flyby star in units of the REBOUND Simulation; if rmax=0, then the star will be placed at perihelion. Default is \(10^5\) AU.

plane str or int

The plane defining the orientation of the star: None, 'invariable', 'ecliptic', or int. Default is None.

Returns:

Name Type Description
orbital_elements dict

The initial conditions of the star in the REBOUND simulation. m, a, e, l are the mass, semi-major axis, eccentricity, and semilatus rectum of the star, respectively.

Example
import rebound
import airball

sim = rebound.Simulation()
sim.add(m=1)
sim.add(m=5e-5, a=30)
star = airball.Star(m=1, b=500, v=5)
add_star_to_sim(sim, star, hash="newstar", rmax=1e5, plane="invariable")
Source code in src/airball/core.py
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
def add_star_to_sim(sim, star, hash, **kwargs):
    """
    Adds a Star to a REBOUND Simulation in the specified plane. Because REBOUND Simulations are C structs underneath Python, this function passes the simulation by reference and modifies the simulation in place.

    Args:
        sim (Simulation): The REBOUND Simulation containing the star and planets that will experience a flyby.
        star (Star): The star that will flyby the given REBOUND simulation.
        hash (str): A string to refer to the Star object by in the REBOUND simulation.

    Keyword Args:
        rmax (float, optional): The starting distance of the flyby star in units of the REBOUND Simulation; if rmax=0, then the star will be placed at perihelion. Default is $10^5$ AU.
        plane (str or int, optional): The plane defining the orientation of the star: None, 'invariable', 'ecliptic', or int. Default is None.

    Returns:
        orbital_elements (dict): The initial conditions of the star in the REBOUND simulation. `m`, `a`, `e`, `l` are the mass, semi-major axis, eccentricity, and semilatus rectum of the star, respectively.

    Example:
        ```python
        import rebound
        import airball

        sim = rebound.Simulation()
        sim.add(m=1)
        sim.add(m=5e-5, a=30)
        star = airball.Star(m=1, b=500, v=5)
        add_star_to_sim(sim, star, hash="newstar", rmax=1e5, plane="invariable")
        ```
    """
    # Because REBOUND Simulations are C structs underneath Python, this function passes the simulation by reference.

    units = _tools.rebound_units(sim)
    rmax = _tools.verify_unit(kwargs.get("rmax", 1e5 * _u.au), units["length"])
    stellar_elements = _tools.hyperbolic_elements(sim, star, rmax, values_only=True)

    plane = kwargs.get("plane")
    if plane is not None:
        rotation = _tools.rotate_into_plane(sim, plane)

    if kwargs.get("helio", False):
        sim.add(**stellar_elements, hash=hash, primary=sim.particles[0])
    else:
        sim.add(**stellar_elements, hash=hash)
    # Because a new particle was added, we need to tell REBOUND to recalculate the coordinates if WHFast is being used.
    if sim.integrator == "whfast":
        sim.ri_whfast.recalculate_coordinates_this_timestep = 1
    sim.synchronize()  # For good measure.

    if plane is not None:
        sim.rotate(rotation.inverse())
    sim.move_to_com()

    # Because REBOUND Simulations are C structs underneath Python, this function passes the simulation by reference.
    return {
        "m": stellar_elements["m"] * units["mass"],
        "a": stellar_elements["a"] * units["length"],
        "e": stellar_elements["e"],
        "l": _tools.semilatus_rectum(**stellar_elements) * units["length"],
    }