def rhs_t(t, h):
h1, h2 = max(h[0], -10), max(h[1], -10)
Q12 = c12*np.sign(h1-h2)*np.sqrt(abs(h1-h2))
Q2 = c2*np.sqrt(abs(h2)) * (1 if h2 >= 0 else -1)
return [qin - Q12, Q12 - Q2]
t_final = 400
t_fine = np.linspace(0, t_final, 2000)
sol_ref = solve_ivp(rhs_t, (0, t_final), [0.0, 0.0], t_eval=t_fine, max_step=0.1)
def euler_fixed(dt):
n = int(t_final/dt)
h = np.zeros((n+1, 2))
t = np.zeros(n+1)
for i in range(n):
h[i+1] = h[i] + dt*np.array(rhs_t(t[i], h[i]))
t[i+1] = t[i] + dt
return t, h
t_e10, h_e10 = euler_fixed(10.0)
t_e20, h_e20 = euler_fixed(20.0)
fig, axes = plt.subplots(1, 2, figsize=(11, 4.5))
axes[0].plot(sol_ref.t, sol_ref.y[1], color='steelblue', lw=2.5, label='RK45 reference')
axes[0].plot(t_e10, h_e10[:,1], 'o-', color='darkorange', ms=3, lw=1, label='Euler, $\\Delta t=10$ s')
axes[0].plot(t_e20, h_e20[:,1], 's-', color='crimson', ms=4, lw=1, label='Euler, $\\Delta t=20$ s')
axes[0].axhline(0, color='k', lw=0.7)
axes[0].axhline(h_eq_nl[1], color='gray', ls=':', lw=1, label=f'$h_{{2,eq}}={h_eq_nl[1]:.1f}$ m')
axes[0].set_xlabel('Time (s)'); axes[0].set_ylabel('$h_2(t)$ (m)')
axes[0].set_title('Coarse Euler drives $h_2$ negative')
axes[0].legend(fontsize=8)
axes[1].plot(sol_ref.y[0], sol_ref.y[1], color='steelblue', lw=2.5, label='RK45 reference')
axes[1].plot(h_e10[:,0], h_e10[:,1], 'o-', color='darkorange', ms=3, lw=1, label='Euler, $\\Delta t=10$ s')
axes[1].plot(h_e20[:,0], h_e20[:,1], 's-', color='crimson', ms=4, lw=1, label='Euler, $\\Delta t=20$ s')
axes[1].plot(h_eq_nl[0], h_eq_nl[1], 'g*', ms=13, label='Equilibrium', zorder=5)
axes[1].set_xlabel('$h_1$ (m)'); axes[1].set_ylabel('$h_2$ (m)')
axes[1].set_title('Phase plane: coarse Euler overshoots the node')
axes[1].legend(fontsize=7.5)
plt.tight_layout()
plt.show()