import numpy as np

def differentiate(fn, x0, eps = 1.5e-6):
  if np.isscalar(x0):
    sx = (1,)
  else:
    sx = x0.shape

  f0 = fn(x0)

  if np.isscalar(f0):
    sf = (1,)
  else:
    sf = f0.shape

  tshape = sf + sx
  rshape = sx + sf

  D = np.zeros(rshape)

  for index in np.ndindex(sx):
    if np.isscalar(x0):
      diff = np.abs(x0 * eps)
    else:
      diff = np.abs(x0[index] * eps)
    if diff < eps:
      diff = eps
    
    if np.isscalar(x0):
      xp = diff
    else:
      xp = np.zeros(sx)
      xp[index] = diff

    fp = fn(xp+x0)

    D[index] = (fp - f0) / diff
  
  Df = np.zeros(tshape)
  # Reverse
  for ind1 in np.ndindex(sx):
    for ind2 in np.ndindex(sf):
      Df[ind2+ind1] = D[ind1+ind2]
  
  if np.isscalar(x0):
    return Df[0]
  else:
    return Df
