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.

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