3

New version of the question

How to detect if a command is being followed by a subscript in a robust manner?

I am using \newcommand*{\command}[1]{\IfStrEq{#1}{_}{...}{...}}, and it works, but it is broken by a package called breqn. Is there a more robust way of making the detection without the xstring package, say by using \@ifnextchar or some other built-in command?


Old version of the question

I have a command for printing probabilities $\Prob{X=3}$ as, say just simply $P(X=3)$ formula, and sometimes I need a long subscript on the $P$, like $${\operatorname*{P}}_{y\sim Y|X}{y=3|x=2}$$:

formula

I would like to use the same macro \Prob both for \Prob{X=3} and \Prob_{y\sim Y|X}{y=3|x=2} to keep code consistency. And I wrote a partially working solution:

\usepackage{xstring}
\newcommand*{\Prob}[1]{\IfStrEq{#1}{_}{\ProbWithSub}{\ProbWithoutSub{#1}}}
\newcommand*{\ProbWithSub}[2]{{\operatorname*{P}_{#1}(#2)}}
\newcommand*{\ProbWithoutSub}[1]{{P(#1)}}

It seems to works but only if the commands are defined after \begin{document}. Otherwise I get the following: formula

Why does this happen, and how should I fix it to define it before \begin{document}?


Minimal working example

While making a minimal working example, I found out that my solution is actually correct but is broken by a package called breqn. Still, I am curious to understand why it breaks, and how to detect the subscript in a robust manner.

\documentclass[conference]{IEEEtran}
\usepackage{amsmath}
\usepackage{xstring}
\usepackage{breqn} % Root of the problem

\newcommand{\custom}[1]{(#1)}

\newcommand*{\Prob}[1]{\IfStrEq{#1}{_}{\ProbWithSub}{\ProbWithoutSub{#1}}}
\newcommand*{\ProbWithSub}[2]{{\operatorname*{P}_{#1}\custom{#2}}}
\newcommand*{\ProbWithoutSub}[1]{{P\custom{#1}}}

\begin{document}
\newcommand*{\XProb}[1]{\IfStrEq{#1}{_}{\XProbWithSub}{\XProbWithoutSub{#1}}}
\newcommand*{\XProbWithSub}[2]{{\operatorname*{P}_{#1}\custom{#2}}}
\newcommand*{\XProbWithoutSub}[1]{{P\custom{#1}}}

These use Prob: $\Prob{X=3}$ and $\Prob_{y\sim Y|X}{y=3|x=5}$.

These use XProb: $\XProb{X=3}$ and $\XProb_{y\sim Y|X}{y=3|x=5}$.
\end{document}

mwe

4
  • You could use xparse to create a command with one mandatory argument and one optional, for example: \NewDocumentCommand{\Prob}{ m o }{\IfValueTF{\ProbWithSub{#1}{#2}}{\ProbWithoutSub{#1}} Aug 19, 2022 at 13:36
  • 1
    Is there any reason the macro has to take arguments at all? Couldn't you just write \Prob(foo) and \Prob_{baz}(foo)? Then \Prob would just have to print the symbol (and set up the correct subscript behavior).
    – schtandard
    Aug 19, 2022 at 14:06
  • 1
    Regarding your literal question: If you provide a complete MWE, people are much more likely to test and try to resolve your problem.
    – schtandard
    Aug 19, 2022 at 14:07
  • @schtandard The macro is that simple just for simplifying the question. My actual macro removes spaces around the equals sign among other features. Aug 19, 2022 at 14:07

3 Answers 3

3

The problem is that some LaTeX packages change the catcode of _ to 12. You can try:

\documentclass[conference]{IEEEtran}
\usepackage{breqn} % Root of the problem

\begin{document}
Catcode: \the\catcode`_ .
\end{document}

and you can see 12. If you remove \usepackage{breqn} then you see 8.

So, the comparison \IfStrEq{#1}{_} fails becase the first argument is _ of catcode 12 and the second argument is _ of catcode 8.

I don't understand LaTeX's \IfStrEq macro, so I suggest to do this by TeX primitive \ifx followed by the \detokenize eTeX primitive which ensures that both following tokens have category 12.

\def\Prob #1{\expandafter\ifx\detokenize{_#1}\expandafter\ProbA \else \ProbB{#1}\fi}
\def\ProbA #1#2{\mathop{P{}}_{#1}(#2)}
\def\ProbB #1{P(#1)}
3
  • 2
    Note that OpTeX sets catcode of _ to 11 and there is no reason to change it during document processing, so \ifx _#1 will always work.
    – wipet
    Aug 19, 2022 at 19:54
  • 1
    Indeed, this answer works with and without the package breqn , and explains the issue. Aug 20, 2022 at 0:10
  • Do you know how to extend your code to detect if the next character is underscore but without consuming it? (like \ifNextIsUnderscore{this}{that}) I would really appreciate it Aug 20, 2022 at 0:16
5

Never ever use $$ in a LaTeX document (it may be used in selected cases to define environments, but that's for experts).

Just define

\DeclareMathOperator*{\Prob}{\mathnormal{P}}

Full code:

\documentclass{article}
\usepackage{amsmath}

\DeclareMathOperator*{\Prob}{\mathnormal{P}}

\begin{document}

\begin{gather*}
\Prob(X=3) \\
\Prob_{y\sim Y\mid X}(y=3\mid x=2)
\end{gather*}

\end{document}

enter image description here

If you want to absorb an argument in order to format it, you can use the e specifier to \NewDocumentCommand.

\documentclass{article}
\usepackage{amsmath}

\NewDocumentCommand{\Prob}{e{_}m}{%
  \operatorname*{\mathnormal{P}}%
  \IfValueT{#1}{_{#1}}%
  (#2)% other settings here
}

\begin{document}

\begin{gather*}
\Prob{X=3} \\
\Prob_{y\sim Y|X}{y=3\mid x=2}
\end{gather*}

\end{document}
2
  • 1
    Thank you, but my actual macro is not just the P(#1). I left it like that for simplicity, but it actually removes spaces around the equals sign among other features. Hence, your solution is useful for this particular case of P(#1), but it does not answer the question of detecting if a command is followed by subscript. Aug 19, 2022 at 14:41
  • @CarlosPinzón Added a different version.
    – egreg
    Aug 19, 2022 at 17:36
3

You can use an e argument type.

enter image description here

\documentclass[conference]{IEEEtran}
\usepackage{amsmath}

\newcommand{\custom}[1]{(#1)}

\NewDocumentCommand\Prob{e{_}m}{\operatorname*{P}\IfNoValueF{#1}{_{#1}}\custom{#2}}

\begin{document}

These use Prob: $\Prob{X=3}$ and $\Prob_{y\sim Y|X}{y=3|x=5}$.


\end{document}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .