Better equation numbering in LaTeX fragments in org-mode

| categories: emacs, orgmode, latex | tags:

In org-mode we can use LaTeX equations, and toggle an overlay that shows what the rendered equation will look like. One thing that has always bothered me though, is that each fragment is created in isolation. That means numbering is almost always wrong, and typically with each numbered equation starting with (1). Here we look at a way to fix that. Fixing it means we have to find a way to not create each fragment image in isolation; each one needs a context that enables the numbering to be correct. The idea we try here is simple: we just figure out in advance what the numbering for each equation should be, and then figure out how to get that information to the image generation.

See this video of the post in action:

Here are some example equations to see how it works.

This should be numbered (1)

\begin{equation} \int x^2 dx \end{equation}

This is a numbered equation with a custom number. This should have (E1) as the number.

\begin{equation}\tag{E1} \int x^2 dx \end{equation}

But equation* is not numbered

\begin{equation*} \int x^2 dx \end{equation*}

LaTeX align environments are numbered. The first line is (2), the second line is not numbered (because we put \nonumber in the line), and the third line is (3).

\begin{align} a = 5 \\ b=6 \nonumber \\ c = 8 \end{align}

But align* environments are not numbered.

\begin{align*} a = 5 \\ b=6 \end{align*}

This should be numbered (4).

\begin{equation} \int x^3 dx \end{equation}

These should be numbered (5), (6) and (7).

\begin{align} a = 5 \\ b=6 \\ c = 8 \end{align}

This should be numbered with (E2).

\begin{equation}\tag{E2} \int x^2 dx \end{equation}

And this should be numbered (8).

\begin{equation} \int x^2 dx \end{equation}

Note: This will be numbered (1) because it is exactly the same equation as a previous one!

\begin{equation} \int x^2 dx \end{equation}

We can change the numbering of an equation with code like this. After this code, the next equation will be numbered (5).

The only fragments that should be numbered are equation environments, and align environments (these are the main ones that we consider here). The align environment is tricky since there is potentially more than one number in the environment.

So, we get all the fragments, and generate a list of which ones should be numbered, and if they should what the number should be. That means we will need to count the number of numbered equations in an align environment. We will do that by getting the number of line breaks, and subtracting the number of nonumbers.

Here is the code block that does that, using advice again. A downside of this approach is that we generate the list for every fragment, which is not efficient, since it should not change in a synchronous approach to generating them.

(defun org-renumber-environment (orig-func &rest args)
  (let ((results '()) 
        (counter -1)
        (numberp))

    (setq results (loop for (begin .  env) in 
                        (org-element-map (org-element-parse-buffer) 'latex-environment
                          (lambda (env)
                            (cons
                             (org-element-property :begin env)
                             (org-element-property :value env))))
                        collect
                        (cond
                         ((and (string-match "\\\\begin{equation}" env)
                               (not (string-match "\\\\tag{" env)))
                          (incf counter)
                          (cons begin counter))
                         ((string-match "\\\\begin{align}" env)
                          (prog2
                              (incf counter)
                              (cons begin counter)                          
                            (with-temp-buffer
                              (insert env)
                              (goto-char (point-min))
                              ;; \\ is used for a new line. Each one leads to a number
                              (incf counter (count-matches "\\\\$"))
                              ;; unless there are nonumbers.
                              (goto-char (point-min))
                              (decf counter (count-matches "\\nonumber")))))
                         (t
                          (cons begin nil)))))

    (when (setq numberp (cdr (assoc (point) results)))
      (setf (car args)
            (concat
             (format "\\setcounter{equation}{%s}\n" numberp)
             (car args)))))
  
  (apply orig-func args))

(advice-add 'org-create-formula-image :around #'org-renumber-environment)

You can remove the advice like this.

(advice-remove 'org-create-formula-image #'org-renumber-environment)

Copyright (C) 2016 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 9.0

Discuss on Twitter