docs: def subs(self, *args, **kwargs):
          """Substitute a symbolic expression in ``['bond', 'angle', 'dihedral']``
  
          This is a wrapper around the substitution mechanism of
          `sympy <http://docs.sympy.org/latest/tutorial/basic_operations.html>`_.
          Any symbolic expression in the columns
          ``['bond', 'angle', 'dihedral']`` of ``self`` will be substituted
          with value.
  
          .. note:: This function is not side-effect free.
              If all symbolic expressions are evaluated and are concrete numbers
              and ``perform_checks`` is True, a check for the transformation
              to cartesian coordinates is performed.
              If no :class:`~chemcoord.exceptions.InvalidReference`
              exceptions are raised, the resulting cartesian is written to
              ``self._metadata['last_valid_cartesian']``.
  
          Args:
              symb_expr (sympy expression):
              value :
              perform_checks (bool): If ``perform_checks is True``,
                  it is asserted, that the resulting Zmatrix can be converted
                  to cartesian coordinates.
                  Dummy atoms will be inserted automatically if necessary.
  
          Returns:
              Zmat: Zmatrix with substituted symbolic expressions.
              If all resulting sympy expressions in a column are numbers,
              the column is recasted to 64bit float.
          """
          perform_checks = kwargs.pop('perform_checks', True)
          cols = ['bond', 'angle', 'dihedral']
          out = self.copy()
  
          def get_subs_f(*args):
              def subs_function(x):
                  if hasattr(x, 'subs'):
                      x = x.subs(*args)
                      try:
                          x = float(x)
                      except TypeError:
                          pass
                  return x
              return subs_function
  
          for col in cols:
              if out.loc[:, col].dtype is np.dtype('O'):
                  out.unsafe_loc[:, col] = out.loc[:, col].map(get_subs_f(*args))
                  try:
                      out.unsafe_loc[:, col] = out.loc[:, col].astype('f8')
                  except (SystemError, TypeError):
                      pass
          if perform_checks:
              try:
                  new_cartesian = out.get_cartesian()
              except (AttributeError, TypeError):
                  # Unevaluated symbolic expressions are remaining.
                  pass
              except InvalidReference as e:
                  if out.dummy_manipulation_allowed:
                      out._manipulate_dummies(e, inplace=True)
                  else:
                      raise e
              else:
                  out._metadata['last_valid_cartesian'] = new_cartesian
                  self._metadata['last_valid_cartesian'] = new_cartesian
          return out