r"""Return a LaTeX representation of the given object / expression

    Args:
        expr: Expression to print
        cache (dict or None): dictionary to use for caching
        show_hs_label (bool or str): Whether to a label for the Hilbert space
            of `expr`. By default (``show_hs_label=True``), the label is shown
            as a superscript. It can be shown as a subscript with
            ``show_hs_label='subscript'`` or suppressed entirely
            (``show_hs_label=False``)
        tex_op_macro (str): macro to use for formatting operator symbols.
            Must accept 'name' as a format key.
        tex_textop_macro (str): macro to use for formatting multi-letter
            operator names.
        tex_sop_macro (str): macro to use for formattign super-operator symbols
        tex_textsop_macro (str): macro to use for formatting multi-letter
            super-operator names
        tex_identity_sym (str): macro for the identity symbol
        tex_use_braket (bool): If True, use macros from the
            `braket package
            <https://ctan.org/tex-archive/macros/latex/contrib/braket>`_. Note
            that this will not automatically render in IPython Notebooks, but
            it is recommended when generating latex for a document.
        tex_frac_for_spin_labels (bool): Whether to use '\frac' when printing
            basis state labels for spin Hilbert spaces


    Examples:

        >>> A = OperatorSymbol('A', hs=1); B = OperatorSymbol('B', hs=1)
        >>> latex(A + B)
        '\\hat{A}^{(1)} + \\hat{B}^{(1)}'
        >>> latex(A + B, cache={A: 'A', B: 'B'})
        'A + B'
        >>> latex(A + B, show_hs_label='subscript')
        '\\hat{A}_{(1)} + \\hat{B}_{(1)}'
        >>> latex(A + B, show_hs_label=False)
        '\\hat{A} + \\hat{B}'
        >>> latex(LocalSigma(0, 1, hs=1))
        '\\left\\lvert 0 \\middle\\rangle\\!\\middle\\langle 1 \\right\\rvert^{(1)}'
        >>> latex(LocalSigma(0, 1, hs=1), sig_as_ketbra=False)
        '\\hat{\\sigma}_{0,1}^{(1)}'
        >>> latex(A + B, tex_op_macro=r'\Op{{{name}}}')
        '\\Op{A}^{(1)} + \\Op{B}^{(1)}'
        >>> CNOT = OperatorSymbol('CNOT', hs=1)
        >>> latex(CNOT)
        '\\text{CNOT}^{(1)}'
        >>> latex(CNOT, tex_textop_macro=r'\Op{{{name}}}')
        '\\Op{CNOT}^{(1)}'

        >>> A = SuperOperatorSymbol('A', hs=1)
        >>> latex(A)
        '\\mathrm{A}^{(1)}'
        >>> latex(A, tex_sop_macro=r'\SOp{{{name}}}')
        '\\SOp{A}^{(1)}'
        >>> Lindbladian = SuperOperatorSymbol('Lindbladian', hs=1)
        >>> latex(Lindbladian)
        '\\mathrm{Lindbladian}^{(1)}'
        >>> latex(Lindbladian, tex_textsop_macro=r'\SOp{{{name}}}')
        '\\SOp{Lindbladian}^{(1)}'

        >>> latex(IdentityOperator)
        '\\mathbb{1}'
        >>> latex(IdentityOperator, tex_identity_sym=r'\identity')
        '\\identity'
        >>> latex(LocalSigma(0, 1, hs=1), tex_use_braket=True)
        '\\Ket{0}\\!\\Bra{1}^{(1)}'

        >>> spin = SpinSpace('s', spin=(1, 2))
        >>> up = SpinBasisKet(1, 2, hs=spin)
        >>> latex(up)
        '\\left\\lvert +1/2 \\right\\rangle^{(s)}'
        >>> latex(up, tex_frac_for_spin_labels=True)
        '\\left\\lvert +\\frac{1}{2} \\right\\rangle^{(s)}'

    Note that the accepted parameters and their default values may be changed
    through :func:`init_printing` or :func:`configure_printing`

def latex(expr, cache=None, **settings):
    r"""Return a LaTeX representation of the given object / expression

    Args:
        expr: Expression to print
        cache (dict or None): dictionary to use for caching
        show_hs_label (bool or str): Whether to a label for the Hilbert space
            of `expr`. By default (``show_hs_label=True``), the label is shown
            as a superscript. It can be shown as a subscript with
            ``show_hs_label='subscript'`` or suppressed entirely
            (``show_hs_label=False``)
        tex_op_macro (str): macro to use for formatting operator symbols.
            Must accept 'name' as a format key.
        tex_textop_macro (str): macro to use for formatting multi-letter
            operator names.
        tex_sop_macro (str): macro to use for formattign super-operator symbols
        tex_textsop_macro (str): macro to use for formatting multi-letter
            super-operator names
        tex_identity_sym (str): macro for the identity symbol
        tex_use_braket (bool): If True, use macros from the
            `braket package
            <https://ctan.org/tex-archive/macros/latex/contrib/braket>`_. Note
            that this will not automatically render in IPython Notebooks, but
            it is recommended when generating latex for a document.
        tex_frac_for_spin_labels (bool): Whether to use '\frac' when printing
            basis state labels for spin Hilbert spaces


    Examples:

        >>> A = OperatorSymbol('A', hs=1); B = OperatorSymbol('B', hs=1)
        >>> latex(A + B)
        '\\hat{A}^{(1)} + \\hat{B}^{(1)}'
        >>> latex(A + B, cache={A: 'A', B: 'B'})
        'A + B'
        >>> latex(A + B, show_hs_label='subscript')
        '\\hat{A}_{(1)} + \\hat{B}_{(1)}'
        >>> latex(A + B, show_hs_label=False)
        '\\hat{A} + \\hat{B}'
        >>> latex(LocalSigma(0, 1, hs=1))
        '\\left\\lvert 0 \\middle\\rangle\\!\\middle\\langle 1 \\right\\rvert^{(1)}'
        >>> latex(LocalSigma(0, 1, hs=1), sig_as_ketbra=False)
        '\\hat{\\sigma}_{0,1}^{(1)}'
        >>> latex(A + B, tex_op_macro=r'\Op{{{name}}}')
        '\\Op{A}^{(1)} + \\Op{B}^{(1)}'
        >>> CNOT = OperatorSymbol('CNOT', hs=1)
        >>> latex(CNOT)
        '\\text{CNOT}^{(1)}'
        >>> latex(CNOT, tex_textop_macro=r'\Op{{{name}}}')
        '\\Op{CNOT}^{(1)}'

        >>> A = SuperOperatorSymbol('A', hs=1)
        >>> latex(A)
        '\\mathrm{A}^{(1)}'
        >>> latex(A, tex_sop_macro=r'\SOp{{{name}}}')
        '\\SOp{A}^{(1)}'
        >>> Lindbladian = SuperOperatorSymbol('Lindbladian', hs=1)
        >>> latex(Lindbladian)
        '\\mathrm{Lindbladian}^{(1)}'
        >>> latex(Lindbladian, tex_textsop_macro=r'\SOp{{{name}}}')
        '\\SOp{Lindbladian}^{(1)}'

        >>> latex(IdentityOperator)
        '\\mathbb{1}'
        >>> latex(IdentityOperator, tex_identity_sym=r'\identity')
        '\\identity'
        >>> latex(LocalSigma(0, 1, hs=1), tex_use_braket=True)
        '\\Ket{0}\\!\\Bra{1}^{(1)}'

        >>> spin = SpinSpace('s', spin=(1, 2))
        >>> up = SpinBasisKet(1, 2, hs=spin)
        >>> latex(up)
        '\\left\\lvert +1/2 \\right\\rangle^{(s)}'
        >>> latex(up, tex_frac_for_spin_labels=True)
        '\\left\\lvert +\\frac{1}{2} \\right\\rangle^{(s)}'

    Note that the accepted parameters and their default values may be changed
    through :func:`init_printing` or :func:`configure_printing`
    """
    try:
        if cache is None and len(settings) == 0:
            return latex.printer.doprint(expr)
        else:
            printer = latex._printer_cls(cache, settings)
            return printer.doprint(expr)
    except AttributeError:
        # init_printing was not called. Setting up defaults
        latex._printer_cls = QnetLatexPrinter
        latex.printer = latex._printer_cls()
        return latex(expr, cache, **settings)