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 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 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 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 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
  • 1
    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 at 19:54
  • 1
    Indeed, this answer works with and without the package breqn , and explains the issue. Aug 20 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 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 at 14:41
  • @CarlosPinzón Added a different version.
    – egreg
    Aug 19 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}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.