When starting out with animations in Jupyter, it is often necessary to restart the kernel. I've found that restarting the kernel often gets rid of
"ERROR! Session/line number was not unique"
errors, hung animations, et cetera. The safest way to restart the kernel is to Restart, and then individually run each cell sequentially.
However, this can get tedious. Restart & Run All saves you the step of individually running the cells. But you may find that Restart & Run All leads to Module Not Found errors for vpython, etc. This is because it takes the computer a long time (by computer standards) to import vpython, so while vpython is loading, Jupyter may skip ahead to the next cell, even if the program isn't ready yet. You can tell if the cells have executed out of order if the numbers in the "In[..]" labels are out of order. If you see an "In[*]" next to a cell, it seems to mean that cell is still being read (that is a guess, though; I am not a Jupyter expert).
If you want to use Restart & Run All in these circumstances, here is a way that works for me:
In the first cell, you have your import statements, your global variables, and one or more function calls. For instance:
import numpy as np # get numpy functions
import matplotlib.pyplot as plt # get matplotlib plot functions
import vpython as vp # get VPython module for animation.
# Initialize global variables (that means variables that you want every function to have access to).
g = 9.8 # gravitational constant
ta,xa,xb,ya,yb = [],[],[],[],[] # declare arrays for plotting and animating.
# The function calls. Every part of the program runs through these.
calculate()
plot()
animate()
Every other cell in the notebook should be a function definition starting with "def", such as:
def plot(): # don't forget the colon
plt.figure() # start a figure
plt.plot(ta,ya, ta,yb,'--') # draw 2nd curve as dashed
plt.xlabel('t (s)') # add labels
plt.ylabel('y (m)')
plt.show() # show figure
plt.figure() # start a figure
plt.plot(ta,xa, ta,xb,'--') # draw 2nd curve as dashed
plt.xlabel('t (s)') # add labels
plt.ylabel('x (m)')
plt.show() # show figure
The reason you do this is that Jupyter won't throw errors if it spots an as-yet undeclared module inside a "def"... It may not know what "plt" is yet, but it's willing to be ignorant as long as you don't actually execute the plot() function before "plt" is defined. This is why the plot() function is called from the first cell, after the import statements...Jupyter does not execute plot() until after it has finished importing matplotlib.pyplot as plt. (Don't forget that .pyplot bit.)
It appear that you shouldn't use my code structure unless the modules you are importing actually do take a long time to load. If they load too quickly, then Jupyter will not have read the "def plot()" cell before the plot() function is called, and that causes an error, too. So use with caution. If you're not comfortable with this kind of thing, just execute the cells individually after every restart.