-
-
Notifications
You must be signed in to change notification settings - Fork 46.7k
Add algorithm for N-body simulation - retry #4298
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
Changes from 5 commits
97f9f6c
1812626
719c628
d375b74
56e1dc4
09237cb
3093f52
7a0081d
790277e
9bfafa6
c063ff3
b605977
2612bcb
0368d9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ jobs: | |
machine_learning | ||
networking_flow | ||
neural_network | ||
physics | ||
quantum | ||
scheduling | ||
sorts | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
""" | ||
In physics and astronomy, a gravitational N-body simulation is a simulation of a | ||
dynamical system of particles under the influence of gravity. The system | ||
consists of a number of bodies, each of which exerts a gravitational force on all | ||
other bodies. These forces are calculated using Newton's law of universal | ||
gravitation. The Euler method is used at each time-step to calculate the change in | ||
velocity and position brought about by these forces. Softening is used to prevent | ||
numerical divergences when a particle comes too close to another (and the force | ||
goes to infinity). | ||
(Description adapted from https://en.wikipedia.org/wiki/N-body_simulation ) | ||
(See also http://www.shodor.org/refdesk/Resources/Algorithms/EulersMethod/ ) | ||
""" | ||
|
||
|
||
from __future__ import annotations | ||
|
||
import random | ||
|
||
from matplotlib import animation # type: ignore | ||
from matplotlib import pyplot as plt | ||
|
||
|
||
class Body: | ||
def __init__( | ||
self: Body, | ||
position_x: float, | ||
position_y: float, | ||
velocity_x: float, | ||
velocity_y: float, | ||
mass: float = 1.0, | ||
size: float = 1.0, | ||
color: str = "blue", | ||
) -> None: | ||
""" | ||
The parameters "size" & "color" are not relevant for the simulation itself, | ||
they are only used for plotting. | ||
""" | ||
self.position_x = position_x | ||
self.position_y = position_y | ||
self.velocity_x = velocity_x | ||
self.velocity_y = velocity_y | ||
self.mass = mass | ||
self.size = size | ||
self.color = color | ||
|
||
def update_velocity( | ||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self: Body, force_x: float, force_y: float, delta_time: float | ||
) -> None: | ||
""" | ||
Euler algorithm for velocity | ||
|
||
>>> body = Body(0.,0.,0.,0.) | ||
>>> body.update_velocity(1.,0.,1.) | ||
>>> body.velocity_x | ||
1.0 | ||
""" | ||
self.velocity_x += force_x * delta_time | ||
self.velocity_y += force_y * delta_time | ||
|
||
def update_position(self: Body, delta_time: float) -> None: | ||
""" | ||
Euler algorithm for position | ||
|
||
>>> body = Body(0.,0.,1.,0.) | ||
>>> body.update_position(1.) | ||
>>> body.position_x | ||
1.0 | ||
""" | ||
self.position_x += self.velocity_x * delta_time | ||
self.position_y += self.velocity_y * delta_time | ||
|
||
|
||
class BodySystem: | ||
""" | ||
This class is used to hold the bodies, the gravitation constant, the time | ||
factor and the softening factor. The time factor is used to control the speed | ||
of the simulation. The softening factor is used for softening, a numerical | ||
trick for N-body simulations to prevent numerical divergences when two bodies | ||
get too close to each other. | ||
""" | ||
|
||
def __init__( | ||
self: BodySystem, | ||
bodies: list[Body], | ||
gravitation_constant: float = 1.0, | ||
time_factor: float = 1.0, | ||
softening_factor: float = 0.0, | ||
) -> None: | ||
self.bodies = bodies | ||
self.gravitation_constant = gravitation_constant | ||
self.time_factor = time_factor | ||
self.softening_factor = softening_factor | ||
|
||
def update_system(self: BodySystem, delta_time: float) -> None: | ||
""" | ||
For each body, loop through all other bodies to calculate the total | ||
force they exert on it. Use that force to update the body's velocity. | ||
|
||
>>> body_system = BodySystem([Body(0,0,0,0), Body(10,0,0,0)]) | ||
>>> body_system.update_system(1) | ||
>>> body_system.bodies[0].position_x | ||
0.01 | ||
""" | ||
for body1 in self.bodies: | ||
force_x = 0.0 | ||
force_y = 0.0 | ||
for body2 in self.bodies: | ||
if body1 != body2: | ||
dif_x = body2.position_x - body1.position_x | ||
dif_y = body2.position_y - body1.position_y | ||
|
||
# Calculation of the distance using Pythagoras's theorem | ||
# Extra factor due to the softening technique | ||
distance = (dif_x ** 2 + dif_y ** 2 + self.softening_factor) ** ( | ||
1 / 2 | ||
) | ||
|
||
# Newton's law of universal gravitation. | ||
force_x += ( | ||
self.gravitation_constant * body2.mass * dif_x / distance ** 3 | ||
) | ||
force_y += ( | ||
self.gravitation_constant * body2.mass * dif_y / distance ** 3 | ||
) | ||
|
||
# Update the body's velocity once all the force components have been added | ||
body1.update_velocity(force_x, force_y, delta_time * self.time_factor) | ||
|
||
# Update the positions only after all the velocities have been updated | ||
for body in self.bodies: | ||
body.update_position(delta_time * self.time_factor) | ||
|
||
|
||
def plot( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file |
||
title: str, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file |
||
body_system: BodySystem, | ||
x_start: float = -1, | ||
x_end: float = 1, | ||
y_start: float = -1, | ||
y_end: float = 1, | ||
) -> None: | ||
""" | ||
Utility function to plot how the given body-system evolves over time. | ||
No doctest provided since this function does not have a return value. | ||
""" | ||
|
||
INTERVAL = 20 # Frame rate of the animation | ||
DELTA_TIME = INTERVAL / 1000 # Time between time steps in seconds | ||
|
||
fig = plt.figure() | ||
fig.canvas.set_window_title(title) | ||
|
||
# Set section to be plotted | ||
ax = plt.axes(xlim=(x_start, x_end), ylim=(y_start, y_end)) | ||
|
||
# Each body is drawn as a patch by the plt-function | ||
patches = [] | ||
for body in body_system.bodies: | ||
patches.append( | ||
plt.Circle((body.position_x, body.position_y), body.size, fc=body.color) | ||
) | ||
|
||
# Function called once at the start of the animation | ||
def init() -> list[patches.Circle]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
|
||
axes = plt.gca() | ||
axes.set_aspect("equal") | ||
|
||
for patch in patches: | ||
ax.add_patch(patch) | ||
return patches | ||
|
||
# Function called at each step of the animation | ||
def update(frame: int) -> list[patches.Circle]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As there is no test file in this pull request nor any test function or class in the file
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
dhruvmanila marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Update the positions of the bodies | ||
body_system.update_system(DELTA_TIME) | ||
|
||
# Update the positions of the patches | ||
for patch, body in zip(patches, body_system.bodies): | ||
patch.center = (body.position_x, body.position_y) | ||
return patches | ||
|
||
anim = animation.FuncAnimation( # noqa: F841 | ||
fig, update, init_func=init, interval=INTERVAL, blit=True | ||
) | ||
|
||
plt.show() | ||
|
||
|
||
if __name__ == "__main__": | ||
# Example 1: figure-8 solution to the 3-body-problem | ||
# This example can be seen as a test of the implementation: given the right | ||
# initial conditions, the bodies should move in a figure-8. | ||
# (initial conditions taken from http://www.artcompsci.org/vol_1/v1_web/node56.html) | ||
position_x = 0.9700436 | ||
position_y = -0.24308753 | ||
velocity_x = 0.466203685 | ||
velocity_y = 0.43236573 | ||
|
||
bodies1 = [ | ||
Body(position_x, position_y, velocity_x, velocity_y, size=0.2, color="red"), | ||
Body(-position_x, -position_y, velocity_x, velocity_y, size=0.2, color="green"), | ||
Body(0, 0, -2 * velocity_x, -2 * velocity_y, size=0.2, color="blue"), | ||
] | ||
body_system1 = BodySystem(bodies1, time_factor=3) | ||
plot("Figure-8 solution to the 3-body-problem", body_system1, -2, 2, -2, 2) | ||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Example 2: Moon's orbit around the earth | ||
# This example can be seen as a test of the implementation: given the right | ||
# initial conditions, the moon should orbit around the earth as it actually does. | ||
# (mass, velocity and distance taken from https://en.wikipedia.org/wiki/Earth | ||
# and https://en.wikipedia.org/wiki/Moon) | ||
moon_mass = 7.3476e22 | ||
earth_mass = 5.972e24 | ||
velocity_dif = 1022 | ||
earth_moon_distance = 384399000 | ||
gravitation_constant = 6.674e-11 | ||
|
||
# Calculation of the respective velocities so that total impulse is zero, | ||
# i.e. the two bodies together don't move | ||
moon_velocity = earth_mass * velocity_dif / (earth_mass + moon_mass) | ||
earth_velocity = moon_velocity - velocity_dif | ||
|
||
moon = Body(-earth_moon_distance, 0, 0, moon_velocity, moon_mass, 10000000, "grey") | ||
earth = Body(0, 0, 0, earth_velocity, earth_mass, 50000000, "blue") | ||
body_system2 = BodySystem([earth, moon], gravitation_constant, time_factor=1000000) | ||
plot( | ||
"Moon's orbit around the earth", | ||
body_system2, | ||
-430000000, | ||
430000000, | ||
-430000000, | ||
430000000, | ||
) | ||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# Example 3: Random system with many bodies | ||
bodies = [] | ||
for i in range(10): | ||
velocity_x = random.uniform(-0.5, 0.5) | ||
velocity_y = random.uniform(-0.5, 0.5) | ||
|
||
# Bodies are created pairwise with opposite velocities so that the | ||
# total impulse remains zero | ||
bodies.append( | ||
Body( | ||
random.uniform(-0.5, 0.5), | ||
random.uniform(-0.5, 0.5), | ||
velocity_x, | ||
velocity_y, | ||
size=0.05, | ||
) | ||
) | ||
bodies.append( | ||
Body( | ||
random.uniform(-0.5, 0.5), | ||
random.uniform(-0.5, 0.5), | ||
-velocity_x, | ||
-velocity_y, | ||
size=0.05, | ||
) | ||
) | ||
body_system3 = BodySystem(bodies, 0.01, 10, 0.1) | ||
plot("Random system with many bodies", body_system3, -1.5, 1.5, -1.5, 1.5) | ||
cclauss marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
self
should not be given a type hint. It is evaluated using dynamic dispatch during runtime. Please remove it from other places as well.