Sbwg commited on
Commit
bcf0876
·
verified ·
1 Parent(s): 86f1924

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +137 -0
app.py ADDED
@@ -0,0 +1,137 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ from numpy.random import normal
3
+ import matplotlib.pyplot as plt
4
+
5
+ # ---------------------------
6
+ # Physical constants
7
+ # ---------------------------
8
+ g = 9.81
9
+ rho = 1.2
10
+ Cd = 0.47
11
+ r = 0.02
12
+ A = np.pi * r**2
13
+ m = 0.0027
14
+
15
+ k_drag = 0.5 * rho * Cd * A / m
16
+ k_mag = 4e-4 # Magnus coefficient (tuned)
17
+
18
+ # Table + net
19
+ table_length = 2.74
20
+ x_net = table_length / 2
21
+ h_net = 0.1525
22
+ y_thresh = h_net + r # clearance threshold
23
+
24
+
25
+ # ---------------------------
26
+ # 2D flight ODE
27
+ # ---------------------------
28
+ def f(state, omega):
29
+ x, y, vx, vy = state
30
+ v = np.sqrt(vx**2 + vy**2)
31
+
32
+ ax_drag = -k_drag * v * vx
33
+ ay_drag = -k_drag * v * vy
34
+
35
+ ax_mag = k_mag * omega * vy
36
+ ay_mag = -k_mag * omega * vx
37
+
38
+ return np.array([
39
+ vx,
40
+ vy,
41
+ ax_drag + ax_mag,
42
+ -g + ay_drag + ay_mag
43
+ ])
44
+
45
+
46
+ # ---------------------------
47
+ # Single trajectory (NO BOUNCE)
48
+ # ---------------------------
49
+ def simulate_trajectory(S, theta_deg, omega, dt=0.001, t_max=2.0):
50
+ theta = np.deg2rad(theta_deg)
51
+
52
+ # initial conditions
53
+ vx = S * np.cos(theta)
54
+ vy = S * np.sin(theta)
55
+ x, y = 0.0, 0.3 # contact height ~30 cm
56
+
57
+ prev_x, prev_y = x, y
58
+ cleared_net = False
59
+ hit_net = False
60
+
61
+ for _ in np.arange(0, t_max, dt):
62
+
63
+ # ------------- NET CHECK -------------
64
+ if (prev_x - x_net) * (x - x_net) <= 0 and (x != prev_x):
65
+ tau = (x_net - prev_x) / (x - prev_x)
66
+ y_cross = prev_y + tau * (y - prev_y)
67
+
68
+ if y_cross <= y_thresh:
69
+ return x_net, "net" # hit net
70
+ else:
71
+ cleared_net = True
72
+
73
+ # ------------- GROUND (TABLE) IMPACT -------------
74
+ if y <= 0:
75
+ # Ball lands
76
+ if x < x_net:
77
+ return x, "undershoot" # lands on own side
78
+ elif x <= table_length:
79
+ return x, "valid" # lands on opponent side
80
+ else:
81
+ return x, "overshoot" # lands beyond table
82
+
83
+ # ------------- INTEGRATE (RK4) -------------
84
+ state = np.array([x, y, vx, vy])
85
+ k1 = f(state, omega)
86
+ k2 = f(state + 0.5*dt*k1, omega)
87
+ k3 = f(state + 0.5*dt*k2, omega)
88
+ k4 = f(state + dt*k3, omega)
89
+ state += (dt/6)*(k1 + 2*k2 + 2*k3 + k4)
90
+
91
+ prev_x, prev_y = x, y
92
+ x, y, vx, vy = state
93
+
94
+ # If ball goes far past table without landing → overshoot
95
+ if x > table_length + 0.5:
96
+ return x, "overshoot"
97
+
98
+ return x, "unknown"
99
+
100
+
101
+ # ---------------------------
102
+ # Monte Carlo
103
+ # ---------------------------
104
+ def monte_carlo(n=2000):
105
+ landings = []
106
+ outcomes = {"valid": 0, "net": 0, "undershoot": 0, "overshoot": 0}
107
+
108
+ for _ in range(n):
109
+ # Random shot parameters
110
+ S = normal(8.0, 0.4)
111
+ ang = normal(12.0, 2.0)
112
+ spin = normal(150, 20)
113
+
114
+ x_land, outcome = simulate_trajectory(S, ang, spin)
115
+
116
+ landings.append(x_land)
117
+ outcomes[outcome] += 1
118
+
119
+ return np.array(landings), outcomes
120
+
121
+
122
+ # ---------------------------
123
+ # Run + Plot
124
+ # ---------------------------
125
+ landings, outcomes = monte_carlo(2000)
126
+
127
+ print(outcomes)
128
+ print("Valid shot probability:", outcomes["valid"] / 2000)
129
+
130
+ plt.hist(landings, bins=80, density=True)
131
+ plt.axvline(x_net, color='r', linestyle='--', label='Net')
132
+ plt.axvline(table_length, color='k', linestyle='--', label='End of table")
133
+ plt.title("Landing distribution (PDF approx)")
134
+ plt.xlabel("Landing x-position (m)")
135
+ plt.ylabel("Probability density")
136
+ plt.legend()
137
+ plt.show()