docs: def substitute_all(sp_object, pairs):
      """
      Performs multiple substitutions in an expression
      :param expr: a sympy matrix or expression
      :param pairs: a list of pairs (a,b) where each a_i is to be substituted with b_i
      :return: the substituted expression
      """
      if not isinstance(pairs, dict):
          dict_pairs = dict(pairs)
      else:
          dict_pairs = pairs
  
      # we recurse if the object was a matrix so we apply substitution to all elements
      if isinstance(sp_object, sympy.Matrix):
          return sp_object.applyfunc(lambda x: substitute_all(x, dict_pairs))
  
      try:
          expr = sp_object.xreplace(dict_pairs)
  
      # in sympy 0.7.2, this would not work, so we do it manually
      except Exception:
          expr =  sp_object
          for (a,b) in dict_pairs.items():
              expr = sympy.Subs(expr, a, b)
          expr = expr.doit()
      return expr