How to vertically align corresponding factors in the Wallis product fraction

Hi all,
the following image represents the Wallis product:
immagine

following the code in a math object I wrote to build the formula without typing manually each factor. It’s not a so annoying job anyway is interesting the proof of concept on Typst way to do so with its internal scripting language.

Summary
$ pi/2 = #{
  let num = () // numerator start
  let den = () // denumerator start
  // filling
  for n in range(2, 12, step:2) {
    num.push(str(n))
    num.push(str(n))
    den.push(str(n - 1))
    den.push(str(n + 1))
  }
  // ending
  num.push(" dots.c")
  den.push(" dots.c")
  // fraction output
  eval(
      "(" +
      num.join(" dot ") +
      ")/("
      + den.join(" dot ") +
      ")",
    mode: "math")
} $

Each factors of numerator and denominator is not vertically aligned one to one.
Is there a way to write a better code in order to generate series similar to the Wallis product?
Which is the best way to align vertically factors?

Thank you.
R.

1 Like

You can use Measure Function – Typst Documentation to figure out the (maximum) width of each pair of factors and then use Box Function – Typst Documentation to “manually” align the factors.

With the show-set rule for the box alignment you can choose the relative alignment of the individual factors.

#let wallis-product(max) = context {
  show box: set align(right)
  
  let num = ()
  let den = ()
  for n in range(1, max) {
    let c = $#n$
    let c-next = $#(n + 1)$
    let width = calc.max(measure(c).width, measure(c-next).width)
    if calc.even(n) {
      num.push(box(width: width, c))
      den.push(box(width: width, c-next))
    } else {
      den.push(box(width: width, c))
      num.push(box(width: width, c-next))
    }
  }
  num.push($...$)
  den.push($...$)

  $ pi / 2 = #num.join(math.dot) / #den.join(math.dot) $
}

3 Likes

Hi again @Roberto,

Here’s another way to do it without too much refactoring:

$
  pi / 2 = ( #{
  let div = ()
  // filling
  for n in range(2, 12, step:2) {
    div.push(str(n)+"/"+str(n - 1))
    div.push(str(n)+"/"+str(n + 1))
  }
  // ending
  div.push(" dots.c") //(" dots.c / dots.c")
  // fraction output
  eval(
      div.join(" dot "),
    mode: "math")
} )
$

image

EDIT: @janekfleper sent their answer while I was still typing. A more elegant solution, which led me to streamline the code to something simpler and removing the eval:

#let wallis-product(nb: 6) = {
  let div = ()
  for n in range(1, nb) {
    div.push($( #(2*n) / #(2*n - 1) dot #(2*n) / #(2*n + 1) )$)
  }
  div.push($dots.c$)
  div.join($dot$)
}

$ pi / 2 = #wallis-product() $

2 Likes

I think the product should only produce the product, and repeating the same thing 4 times is unnecessarily excessive:

#let wallis-product(max) = context {
  show box: set align(center)

  let num = ()
  let den = ()
  for n in range(1, max) {
    let factors = ($#n$, $#(n + 1)$)
    let width = calc.max(..factors.map(x => measure(x).width))
    let (c, c-next) = factors.map(box.with(width: width))
    if calc.odd(n) {
      den.push(c)
      num.push(c-next)
    } else {
      num.push(c)
      den.push(c-next)
    }
  }
  num.push($dots.c$)
  den.push($dots.c$)

  $num.join(dot) / den.join(dot)$
}

$ pi / 2 = #wallis-product(11) $

Though following the same principle show-set rule is not needed either:

#let wallis-product(max) = context {
  let (num, den) = ((), ())
  for n in range(1, max) {
    let factors = ($#n$, $#(n + 1)$)
    let width = calc.max(..factors.map(x => measure(x).width))
    let (c, c-next) = factors.map(x => box(width: width, align(center, x)))
    if calc.odd(n) {
      den.push(c)
      num.push(c-next)
    } else {
      num.push(c)
      den.push(c-next)
    }
  }
  num.push($dots.c$)
  den.push($dots.c$)
  $num.join(dot) / den.join(dot)$
}

$ pi / 2 = #wallis-product(11) $
1 Like

I really appreciate everyone’s code in the answers to the question. I’m learning something new from every bit of code you’ve shared.
Typst’s scripting language is intuitive and funny to work with, if one have in mind the syntax of modern programming languages.
Thank you @Andrew @janekfleper @vmartel08
R.

2 Likes

Hey everyone,
Sharing a second go at the Wallis product, and this one’s inspired by @janekfleper’s work. The really interesting thing about this v2 is that it only puts a box around one integer – the one that happens to be the narrowest. And get this, the box isn’t aligned with anything on purpose.
I was under the impression that stuff in math formulas was automatically centered. Guess I was wrong!
This new version also has a different way to create the sequences of integers. It uses two starting integers and then increases them by 2, but it alternates between the two.
Thanks!

#let wallis_v2(num_of_frac: 6) = context {
  assert(num_of_frac > 0, message : "Insert a value greater than 0 for 'num_of_frac'")
  let n = 2
  let d = 1
  let num = ()
  let den = ()
  for _ in range(num_of_frac) {
    // insert the current fraction n/d
    let n1 = $#n$
    let d1 = $#d$
    let n1_len = measure(n1).width
    let d1_len = measure(d1).width
    if n1_len > d1_len {
      num.push(n1)
      den.push(box(d1, width:n1_len))
    } else {
      num.push(box(n1, width:d1_len))
      den.push(d1)
    }
    // prepare the next fraction
    if n < d { n += 2 } else { d += 2 }
  }
  num.push($dots.c$)
  den.push($dots.c$)
  // output
  $ pi/2 = #num.join($dot$) / #den.join($dot$) $
}

#wallis_v2(num_of_frac: 14)
2 Likes