How to conditionally enable equation numbering for labeled equations?

In articles it is sometimes helpful to have unnumbered equations appear interlaced with numbered ones, the latter being referenced in the document.

How might I write the #set math.equation(numbering: (params) => output) rule so that only equations with reference tags are numbered? Is this something that would need language support to implement?

2 Likes

I figured out a solution. Create the following function

#let equ(eq, id: none) = {
  let body = if type(id) == none {eq} else if type(id) == label [#eq #id] else [#eq <#id>]
  let numbering = if type(id) != none { "(1)" } else { none }
  set math.equation(numbering: numbering)
  body
}

Then you can control the showing of labels by setting the argument id. For example,

#lorem(50) As shown in @eq:eq1,
#equ($
y = a x + b
$, id: <eq:eq1>)
#lorem(100)
#equ($
E = m c^2
$)
#lorem(50)

image

Later I will try to figure out how to achieve this without using a custom function. But such a function is helpful. You can avoid the math equation breaking paragraphs with this

#let equ(eq, id: none) = {
  let body = if id == none {eq} else if type(id) == label [#eq #id] else [#eq #label(id)]
  let numbering = if id != none { "(1)" } else { none }
  set math.equation(numbering: numbering)
  linebreak()
  box(body, width: 1fr)
  linebreak()
}

In this case, display equations are really “in” the paragraph, not “between” two paragraphs.

1 Like

And here is another solution without the help of a equ function. Put the following code before your document’s main body.

#show: body => {
  for elem in body.children {
    if elem.func() == math.equation and elem.block {
      let numbering = if "label" in elem.fields().keys() { "(1)" } else { none }
      set math.equation(numbering: numbering)
      elem
    } else {
      elem
    }
  }
}

And you can also append some other settings like this comment.

In this case, the following script

#lorem(30)
$ y = a x + b $ <eq:eq2>
#lorem(19)
@eq:eq2 #lorem(20)
$ E = m c^2 $
#lorem(10)

will result in

image

3 Likes

Thank you very much for the extensive options. I particularly like avoiding the custom function.

Hey @Christopher_Marcotte ! I’ve updated your post title to be in accordance with our question title guidelines: How to post in the Questions category

Remember that your title should be a question you’d ask to a friend about Typst. :wink:

1 Like

Hey @Aegon, quick tip! The syntax <#id> produces a label literally containing #id (that is, a hash, the letter “i”, and the letter “d”), not a label with the contents of the variable named id. Rather, you should write label(id) (use the label constructor) if you’d like to construct a label from a dynamic string. Hope this helps in the future!

Thanks! So the corrected version should be

#let equ(eq, id: none) = {
  let body = if id == none {eq} else if type(id) == label [#eq #id] else [#eq #label(id)]
  let numbering = if id != none { "(1)" } else { none }
  set math.equation(numbering: numbering)
  linebreak()
  box(body, width: 1fr)
  linebreak()
}

Right?

Yes (assuming id would always be a string in the last case).

@Christopher_Marcotte Unfortunately, this solution cannot work on equations in lists, enums, etc. To solve this, it is still necessary to define a function which applies the for-loop to its body argument.

Perhaps we can achieve this by using show once the non-recursive show-set is ready.

You may be interested in this package

2 Likes

This appears to precisely solve the issue, very elegantly. Amazing, thank you!

#set math.equation(numbering: "(1)")
#show math.equation: it => {
  if not it.has("label") {
    let fields = it.fields()
    fields.remove("body")
    fields.numbering = none
    return [#counter(math.equation).update(v => v - 1)#math.equation(..fields, it.body)<math-equation-without-label>]
  }
  return it
}

$ x + y $

$ x + y + z $ <with-label>

82ae3879ab4cb384318bc01a3e883fc6

2 Likes

Slightly shorter variant which also ensures correct numbering in presence of inline equations:

#set math.equation(numbering: "(1)")
#show math.equation: it => {
  if it.block and not it.has("label") [
    #counter(math.equation).update(v => v - 1)
    #math.equation(it.body, block: true, numbering: none)#label("")
  ] else {
    it
  }  
}

Explanation

The set rule enables numbering for all equations.
The show rule handles block equations without label specially by displaying a copy of the equation without numbering. To ensure that these equations are not counted, the counter is decremented. To avoid recursion, an empty label is added.

Example

Full code
#set page(width: 100pt, height: auto)


#set math.equation(numbering: "(1)")
#show math.equation: it => {
  if it.block and not it.has("label") [
    #counter(math.equation).update(v => v - 1)
    #math.equation(it.body, block: true, numbering: none)#label("")
  ] else {
    it
  }  
}


$a$ to the one:
$ a^1 $
$b$ to the two:
$ b^2 $ <b>
$c$ to the three:
$ c^3 $
$d$<x> to the four:
$ d^4 $ <d>

Edit: the solution in this post doesn’t work — equation numbers are shown automatically, but equation references don’t work.

Original post

Even shorter still, you can do it with a single show rule:

#show math.equation: it => {
	if it.numbering == none and it.has("label") {
		math.equation(it.body, numbering: "(1)", block: true)
	} else { it }
}

Fortunately, the show rule preserves the label by default.

Shouldn’t the counter update line be this?

#counter(math.equation).update(v => calc.max(v - 1, 0))

Otherwise if the document starts with an unlabeled equation, then the counter goes negative and Typst throws an error.

Otherwise if the document starts with an unlabeled equation, then the counter goes negative and Typst throws an error.

The solution works in either case. At the line in question, the counter is already incremented, i.e. always ≥1. If the document starts with an unlabeled equation, it is 1, and the counter update code sets it back to zero.