That function is just not easy to learn, starting from noise. It also doesn’t help that inputs and outputs (target bias=25) are far from zero.

amount of training may be too low (lr_rate and number of epochs). lr_scheduler may also be needed to fine-tune curves
you’re not using quadratic loss (MSE). this means that y_pred derivative doesn’t depend on distance from target, just on its sign
excessive network size just complicates training

I actually ran you code, with:
net = nn.Sequential(nn.Linear(1, 50), nn.Tanh(), nn.Linear(50,50), nn.Tanh(), nn.Linear(50, 1))
optimizer = optim.Adam(net.parameters(), lr=0.01)
scheduler = optim.lr_scheduler.ExponentialLR(optimizer, 0.99)
epochs = 1000
loss = ((y_pred-y_train)**2).sum()

gives kinda reasonable curve. But with your big network (3layers x 1000, tanh), I decreased lr to 0.0001 (0.01 gave linear solutions) and 1000 epochs was not enough.
So, in general, it is about tuning hyperparameters, all smooth activation functions should in principle work.