Public

Prueba del motor (code)

Code written by the AI agents of “Prueba del motor”. Each agent works on its own branch; main follows the lead agent.

Commits

RK4 projectile with quadratic drag, ground-crossing interpolation, no-drag analytic check

Lasques agent 1 (lead, iteration 1) · Oct 24, 2026 · a4e849d6a5f176997b6568f46823059f064890b2

1 file changed

+61−45
main.pyModified+61−45
@@ -1,4 +1,3 @@
1"""RK4 simulation of 2D projectile with quadratic drag a = -g y - (k/m)|v|v."""
21import numpy as np
32import lasques
43import matplotlib
@@ -8,62 +7,79 @@ import matplotlib.pyplot as plt
87p = lasques.params()
98v0 = float(p.get("v0", 30.0))
109angle_deg = float(p.get("angle_deg", 45.0))
10g = float(p.get("g", 9.81))
1111km = float(p.get("km", 0.0))
12dt = float(p.get("dt", 1e-3))
13g = 9.81
12dt = float(p.get("dt", 0.001))
1413
15th = np.radians(angle_deg)
16state = np.array([0.0, 0.0, v0*np.cos(th), v0*np.sin(th)])
14theta = np.radians(angle_deg)
1715
18def deriv(s):
19 x, y, vx, vy = s
16def accel(state, km):
17 x, y, vx, vy = state
2018 sp = np.hypot(vx, vy)
21 return np.array([vx, vy, -km*sp*vx, -g - km*sp*vy])
19 return np.array([vx, vy, -km * sp * vx, -g - km * sp * vy])
2220
21def rk4_step(state, dt, km):
22 k1 = accel(state, km)
23 k2 = accel(state + 0.5 * dt * k1, km)
24 k3 = accel(state + 0.5 * dt * k2, km)
25 k4 = accel(state + dt * k3, km)
26 return state + (dt / 6.0) * (k1 + 2 * k2 + 2 * k3 + k4)
27
28state = np.array([0.0, 0.0, v0 * np.cos(theta), v0 * np.sin(theta)])
2329traj = [state.copy()]
2430t = 0.0
25max_steps = 2_000_000
31max_steps = 10_000_000
2632for i in range(max_steps):
27 k1 = deriv(state)
28 k2 = deriv(state + 0.5*dt*k1)
29 k3 = deriv(state + 0.5*dt*k2)
30 k4 = deriv(state + dt*k3)
31 new = state + dt/6*(k1 + 2*k2 + 2*k3 + k4)
32 # detect ground crossing between state and new
33 if new[1] < 0:
34 # linear interpolation for range
35 f = state[1] / (state[1] - new[1])
36 x_land = state[0] + f*(new[0]-state[0])
37 t_land = t + f*dt
38 state = new
39 traj.append(new)
40 break
41 state = new
42 traj.append(new)
33 prev = state
34 state = rk4_step(state, dt, km)
4335 t += dt
36 traj.append(state.copy())
37 if state[1] < 0.0: # crossed ground: linear interpolation of last step
38 f = prev[1] / (prev[1] - state[1])
39 range_m = prev[0] + f * (state[0] - prev[0])
40 break
41else:
42 range_m = state[0]
4443
4544traj = np.array(traj)
46range_m = x_land
47apex = traj[:,1].max()
48R_analytic_no_drag = v0**2*np.sin(2*th)/g
4945
50lasques.set_metrics({"range_m": range_m, "flight_time_s": t_land,
51 "apex_m": apex, "km": km, "dt": dt,
52 "R_analytic_nodrag": R_analytic_no_drag})
46# analytic no-drag reference
47R_analytic = v0**2 * np.sin(2 * theta) / g
48abs_drop = R_analytic - range_m if km == 0.0 else None
49
50lasques.set_metrics({
51 "range_m": range_m,
52 "range_analytic_m": R_analytic,
53 "km": km,
54 "dt": dt,
55 "angle_deg": angle_deg,
56 "v0": v0,
57})
5358
54if km == 0:
55 rel_err = abs(range_m - R_analytic_no_drag)/R_analytic_no_drag
56 lasques.set_metrics({"nodrag_rel_err": rel_err})
57 lasques.log_metric("nodrag_rel_err", rel_err)
58lasques.log_metric("range", range_m)
59lasques.save_table(traj[::max(1, len(traj)//2000), :], "trajectory")
59if km == 0.0:
60 err_pct = abs(range_m - R_analytic) / R_analytic * 100
61 lasques.set_metrics({"no_drag_error_pct": err_pct})
62 lasques.summary(
63 f"No-drag validation: simulated range {range_m:.4f} m vs analytic "
64 f"{R_analytic:.4f} m (error {err_pct:.4e}%), dt={dt}, v0={v0}, angle={angle_deg}°"
65 )
66else:
67 lasques.summary(
68 f"k/m={km}: range {range_m:.4f} m at {angle_deg}°, v0={v0} m/s, dt={dt}"
69 )
6070
61fig, ax = plt.subplots(figsize=(7,4))
62ax.plot(traj[:,0], traj[:,1], label=f"k/m={km}")
63ax.set_xlabel("x (m)"); ax.set_ylabel("y (m)")
64ax.set_title(f"Projectile, v0={v0} m/s, angle={angle_deg} deg, k/m={km}, dt={dt}")
65ax.legend(); ax.grid(True)
66lasques.save_figure(fig, f"traj_km{km}_dt{dt}")
71# trajectory figure
72fig, ax = plt.subplots(figsize=(7, 4))
73ax.plot(traj[:, 0], traj[:, 1], lw=1.5, label=f"k/m={km}")
74ax.set_xlabel("x (m)")
75ax.set_ylabel("y (m)")
76ax.set_title(f"Trajectory, v0={v0} m/s, θ={angle_deg}°, dt={dt}")
77ax.legend()
78ax.grid(alpha=0.3)
79lasques.save_figure(fig, "trajectory")
6780
68lasques.summary(f"k/m={km}, dt={dt}: range={range_m:.4f} m, apex={apex:.3f} m, flight time={t_land:.3f} s")
69print(f"km={km} dt={dt} range={range_m:.5f} apex={apex:.4f} t={t_land:.4f}")
81lasques.save_table(
82 {"t": np.arange(len(traj)) * dt, "x": traj[:, 0], "y": traj[:, 1],
83 "vx": traj[:, 2], "vy": traj[:, 3]},
84 "trajectory",
85)