hs = np.logspace(-16, 0, 400)
x = torch.tensor(x0, requires_grad=True)
torch.sin(x**2).backward() # automatic differentiation with PyTorch
ad_err = abs(x.grad.item() - df_test(x0))
fig, ax = plt.subplots(figsize=(5.2, 4.2))
for name, rule, col in [("forward difference", forward, "steelblue"), ("centered difference", centered, "forestgreen")]:
err = np.array([abs(rule(f_test, x0, h) - df_test(x0)) for h in hs])
ax.loglog(hs, np.maximum(err, 1e-17), color=col, lw=1, label=name)
ax.loglog(hs, eps / hs, color="k", ls="--", lw=1, label="rounding: $\\varepsilon/h$")
ax.axhline(max(ad_err, eps), color="firebrick", lw=2.5, label="automatic differentiation")
ax.text(1e-15, 4 * eps, f"error {ad_err:.1e}, no $h$ at all", color="firebrick", fontsize=8)
ax.set_ylim(1e-17, 10)
ax.set_xlabel("step size $h$"); ax.set_ylabel("|error| of $f'(1)$")
ax.set_title("$f(x)=\\sin x^2$: the error curve of last class", fontsize=9)
ax.legend(fontsize=7, loc="upper center")
plt.tight_layout()
plt.show()