projectile-sim 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- projectile_sim-1.1.0/PKG-INFO +4 -0
- projectile_sim-1.1.0/README.md +2 -0
- projectile_sim-1.1.0/projectile/__init__.py +1 -0
- projectile_sim-1.1.0/projectile/projectile_sim.py +51 -0
- projectile_sim-1.1.0/projectile_sim.egg-info/PKG-INFO +4 -0
- projectile_sim-1.1.0/projectile_sim.egg-info/SOURCES.txt +13 -0
- projectile_sim-1.1.0/projectile_sim.egg-info/dependency_links.txt +1 -0
- projectile_sim-1.1.0/projectile_sim.egg-info/requires.txt +1 -0
- projectile_sim-1.1.0/projectile_sim.egg-info/top_level.txt +1 -0
- projectile_sim-1.1.0/pyproject.toml +12 -0
- projectile_sim-1.1.0/setup.cfg +4 -0
- projectile_sim-1.1.0/tests/test_edgecases.py +15 -0
- projectile_sim-1.1.0/tests/test_outputs.py +24 -0
- projectile_sim-1.1.0/tests/test_params.py +9 -0
- projectile_sim-1.1.0/tests/test_postime.py +13 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .projectile_sim import Projectile
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# OOP projectile motion
|
|
2
|
+
import math
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
|
|
6
|
+
class Particle(object):
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.position = [0.0, 0.0]
|
|
9
|
+
|
|
10
|
+
class Projectile(Particle):
|
|
11
|
+
def __init__(self, velocity, angle, initial_height, dt):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.velocity = velocity
|
|
14
|
+
self.angle = angle
|
|
15
|
+
self.initial_height = initial_height
|
|
16
|
+
self.position = [0.0, initial_height]
|
|
17
|
+
self.time = 0.0
|
|
18
|
+
self.dt = dt
|
|
19
|
+
self.g = -9.81
|
|
20
|
+
|
|
21
|
+
def update_position(self, dt):
|
|
22
|
+
self.time += dt
|
|
23
|
+
self.position[0] = self.velocity * math.cos(math.radians(self.angle)) * self.time
|
|
24
|
+
self.position[1] = self.initial_height + (self.velocity * math.sin(math.radians(self.angle)) * self.time) + (0.5 * self.g * self.time ** 2)
|
|
25
|
+
|
|
26
|
+
def get_position(self):
|
|
27
|
+
return tuple(self.position)
|
|
28
|
+
|
|
29
|
+
def get_time(self):
|
|
30
|
+
return self.time
|
|
31
|
+
|
|
32
|
+
def get_max_height(self):
|
|
33
|
+
return self.initial_height + (self.velocity ** 2 * (math.sin(math.radians(self.angle))) ** 2) / (2 * -self.g)
|
|
34
|
+
|
|
35
|
+
if __name__ == "__main__":
|
|
36
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=10.0, dt=0.01)
|
|
37
|
+
print(f"Initial x, y: {ball.get_position()} (m)")
|
|
38
|
+
|
|
39
|
+
x_positions = [ball.get_position()[0]]
|
|
40
|
+
y_positions = [ball.get_position()[1]]
|
|
41
|
+
|
|
42
|
+
while ball.position[1] >= 0:
|
|
43
|
+
ball.update_position(ball.dt)
|
|
44
|
+
x_positions.append(ball.get_position()[0])
|
|
45
|
+
y_positions.append(ball.get_position()[1])
|
|
46
|
+
|
|
47
|
+
print(f"Final x, y: ...")
|
|
48
|
+
|
|
49
|
+
fig, ax = plt.subplots(figsize=(8, 5)) # <-- moves in here
|
|
50
|
+
ax.plot(x_positions, y_positions)
|
|
51
|
+
plt.show()
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
projectile/__init__.py
|
|
4
|
+
projectile/projectile_sim.py
|
|
5
|
+
projectile_sim.egg-info/PKG-INFO
|
|
6
|
+
projectile_sim.egg-info/SOURCES.txt
|
|
7
|
+
projectile_sim.egg-info/dependency_links.txt
|
|
8
|
+
projectile_sim.egg-info/requires.txt
|
|
9
|
+
projectile_sim.egg-info/top_level.txt
|
|
10
|
+
tests/test_edgecases.py
|
|
11
|
+
tests/test_outputs.py
|
|
12
|
+
tests/test_params.py
|
|
13
|
+
tests/test_postime.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
numpy
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
projectile
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from projectile import Projectile
|
|
2
|
+
import math
|
|
3
|
+
def test_90_degree_angle():
|
|
4
|
+
# straight up — x should stay ~0
|
|
5
|
+
ball = Projectile(velocity=10.0, angle=90.0, initial_height=0.0, dt=0.01)
|
|
6
|
+
while ball.position[1] >= 0:
|
|
7
|
+
ball.update_position(ball.dt)
|
|
8
|
+
assert abs(ball.get_position()[0]) < 0.01
|
|
9
|
+
|
|
10
|
+
def test_zero_velocity():
|
|
11
|
+
# with no velocity, ball should just fall from initial height
|
|
12
|
+
ball = Projectile(velocity=0.0, angle=45.0, initial_height=10.0, dt=0.01)
|
|
13
|
+
while ball.position[1] >= 0:
|
|
14
|
+
ball.update_position(ball.dt)
|
|
15
|
+
assert abs(ball.get_position()[0]) < 0.01 # no horizontal motion
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from projectile import Projectile
|
|
2
|
+
import math
|
|
3
|
+
def test_max_height():
|
|
4
|
+
# analytical formula: h_max = h0 + (v * sin(angle))^2 / (2g)
|
|
5
|
+
ball = Projectile(velocity=10.0, angle=90.0, initial_height=0.0, dt=0.01)
|
|
6
|
+
expected = (10.0 ** 2) / (2 * 9.81) # straight up shot, simplifies nicely
|
|
7
|
+
assert abs(ball.get_max_height() - expected) < 0.01
|
|
8
|
+
|
|
9
|
+
def test_horizontal_distance_flat_ground():
|
|
10
|
+
# at angle=45, v=10, h0=0: range = v^2 * sin(2*angle) / g
|
|
11
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=0.0, dt=0.01)
|
|
12
|
+
expected_range = (10.0 ** 2 * math.sin(math.radians(90))) / 9.81
|
|
13
|
+
while ball.position[1] >= 0:
|
|
14
|
+
ball.update_position(ball.dt)
|
|
15
|
+
assert abs(ball.get_position()[0] - expected_range) < 0.1 # tolerance for dt discretization
|
|
16
|
+
|
|
17
|
+
def test_y_is_always_decreasing_after_peak():
|
|
18
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=0.0, dt=0.01)
|
|
19
|
+
positions = []
|
|
20
|
+
while ball.position[1] >= 0:
|
|
21
|
+
ball.update_position(ball.dt)
|
|
22
|
+
positions.append(ball.get_position()[1])
|
|
23
|
+
peak_idx = positions.index(max(positions))
|
|
24
|
+
assert all(positions[i] >= positions[i+1] for i in range(peak_idx, len(positions)-1))
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from projectile import Projectile
|
|
2
|
+
import math
|
|
3
|
+
def test_initial_position():
|
|
4
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=5.0, dt=0.01)
|
|
5
|
+
assert ball.get_position() == (0.0, 5.0)
|
|
6
|
+
|
|
7
|
+
def test_zero_initial_height():
|
|
8
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=0.0, dt=0.01)
|
|
9
|
+
assert ball.get_position()[1] == 0.0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from projectile import Projectile
|
|
2
|
+
import math
|
|
3
|
+
import pytest
|
|
4
|
+
def test_time_increases():
|
|
5
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=0.0, dt=0.01)
|
|
6
|
+
ball.update_position(ball.dt)
|
|
7
|
+
assert ball.get_time() == pytest.approx(0.01)
|
|
8
|
+
|
|
9
|
+
def test_time_accumulates():
|
|
10
|
+
ball = Projectile(velocity=10.0, angle=45.0, initial_height=0.0, dt=0.01)
|
|
11
|
+
for _ in range(5):
|
|
12
|
+
ball.update_position(ball.dt)
|
|
13
|
+
assert ball.get_time() == pytest.approx(0.05)
|