How should I reasonably debug and resolve "layout did not converge" warnings?

I have a document which has started giving the layout did not converge within 5 attempts warning.

I’ve been provided the hint to “check if any states or queries are updating themselves”.

Unfortunately I have no idea how I might go about checking this, and cannot see a way to resolve this warning other than randomly removing parts of my document and seeing if it goes away. (Thankfully at least the compilation is fast, so this is a plausible strategy.)

What is the best way to debug and resolve these warnings?

2 Likes

Unfortunately there aren’t really any great ways to debug these kinds of issues at the moment, other than what you’ve already been trying to do.

1 Like

Without seeing the actual document, the only advice is to carefully read State Type – Typst Documentation and focus on things like counter(), state() and context or show rule where you access the value with .get() or update it with .step() or .update(). One thing that comes to mind is updating state/counter by feeding it its previous value.

The general bisecting approach is the most useful for debugging almost everything. Although in some situations, any small change/deletion in any place can remove the bug, so it can potentially very hard to debug. Rewriting from scratch the core logic that you want to have working is also good. This will isolate it from everything else already present in the document. And it’s easier to ask some about a specific code block rather than “here is my entire document, why I have the warning?” (sometimes it’s very easy to detect the problem either way).

Thank you @Enivex and @Andrew for your advice so far.

I am reasonably confident that the issue is in a function I made to assist in double-page layouts, so that I can ensure certain questions appear on a double page by themselves.

I realise I can do #pagebreak(to: "even") in order to accomplish this, but I would also like some text to be on the blank page, indicating that it’s not a printing error (this is standard for the official exams where I live.)

So I have the following code, and I believe that is what is sometimes causing the error. I am not clear on how typst could get stuck in a non-converging situation from this though.

#let cleardoublepage(include_blank_page_info: true) = {
  locate(loc => {
      pagebreak()
      if calc.odd(loc.page()) {
        if include_blank_page_info {
          v(1fr)
          align(center)[*THIS PAGE IS BLANK*]
          v(1fr)
        }

        pagebreak()
      }
    }
  )
}

Can you please provide a MRE? I don’t know how the function is used and how to trigger the warning with it.

Sadly not at the moment, I tried to create one with various bits of #lorem in between and failed. But I’ll post back here if I can create one.

1 Like

Update - I have managed to get a MRE that is under 50 lines with no external dependencies. Unfortunately, almost any change that I make will cause the warning to disappear.

Here is a link to the typst document which has the warning.

Please do not try to interpret the meaning of any of the elements as I’ve replaced things with #lorem and got rid of most of the logic, just to bring it down to a non-converging smaller document.

I’m not sure why Typst would have trouble converging on this, whether it represents a bug in Typst or a fundamental mistake in the way I am doing things.

The document's contents here, for posterity
#let cleardoublepage() = {
  locate(loc => {
      pagebreak()
      if calc.odd(loc.page()) {
        align(center)[*THIS PAGE IS BLANK*]
        pagebreak()
      }
    }
  )
}

#let subquestion(content, answercontainer) = {
  // Step only the sub_question number.
  counter("question").step(level: 2)

  context [
    #let (num, sub) = counter("question").get()

    #grid(
     columns: (1cm, 7fr, 1fr),
     align(left)[#text(weight: "bold")[a.]],
          
     align(left)[#content],
     align(bottom+right)[(3)],
     "",
     align(left)[#v(3mm) #answercontainer],
     ""
    )
  #v(5mm)
  ]
}

#set page(
    header: locate(loc => [ #align(center)[Page #counter(page).display("1")] ])
)


#lorem(2) #parbreak()

*Instructions* #align(left, lorem(10))

*Question* #linebreak()
#box[
#lorem(40)
]
#subquestion()[
#lorem(10)
][
#align(center, rect(width: 100%, height: 5cm))
]

#lorem(380)
#cleardoublepage()

#lorem(10)
#cleardoublepage()

#lorem(10)
#cleardoublepage()

I think the combination of a contextual double page clear and header is too much. In your case, layout divergence occurs when the first page has too much content to fit on a single page.

I had the same issues on my own thesis. I ended up writing the pagebreak more or less manually.

In your document, I have managed to get a working doubleclearpage using the to parameter of pagebreak.

#let cleardoublepage() = {
  pagebreak(to: "odd")
  align(center)[*BLANK*]
  pagebreak()
}

Would this fit your needs?

Thanks for this. This doesn’t quite work for my situation because if I am partway through page 13 and I called #cleardoublepage() I just want to go to start the next thing on page 14.

But I believe the code proposed will go to page 15 (leaving page 14 completely blank) and then put the *BLANK* on page 15, before going to page 16.

That is, it ends up inserting two blank pages, but in fact I just want to ensure that the next content appears at the top of page 14.

So I’d really like to just use pagebreak(to: "even") but have the ability to control what appears on any blank pages that this causes.

Some ideas how this could work have been floating around here, perhaps some of the workarounds listed there work better? You could plug them in and see if that improves the situation, but they’re obviously just workarounds.

The layout engine has undergone a bit of a rework recently, perhaps it’s easier to implement than it was at the time. @laurmaedje, what do you think, have the recent changes made this conceptually easier to implement?

From what I recall, it was less of an implementation and more of a design problem.

Ah, I thought that the Implementation of page made it harder to get show page: set page working. I figured the that show-set would be the preferred way to do it.

Ah right, I skimmed too fast. I’m not sure I really like the show-set thing yet, though I see the elegance. But that’s indeed somewhat tricky to implement and this exact thing hasn’t gotten easier. Other kinds of page show rules have gotten closer to becoming possible, but not show-set because there are no real page elements. It’s all fake. :)

1 Like

I am still not clear on why such a (seemingly) simple document would not be able to converge. Why can’t Typst just look at the page number and see it’s even, then insert the blank page with BLANK?

I could understand why it would go wrong if I were inserting a page at an earlier stage in the document, as that could then change whether a given page number is odd or even, but in my example it should just be able to see the parity of the current page number and converge, shouldn’t it?

Each time this workaround is called it has an effect on subsequent pages, which then require more layout iterations. The compiler cannot really infer what you’re trying to do here and does the safest thing by laying out the content afterwards again if it thinks it needs to.

1 Like

Is it something that could be resolved if I were able to increase the number of iterations? The weird thing also is that if I change just a few elements in my MRE the error goes away, which makes me think that there must be some weird thing going on, hence the original question (top of the post) as to how to debug it.

Sample of what I mean by debugging would be if you could get typst to generate a sequence of PDFs with annotations indicating where Typst currently things everything should go and annotations of what cannot be determined without another iteration.

You can increase the iterations by compiling the compiler with a higher number, not sure if that’s a good idea, if you use a very high number and write something that actually doesn’t converge the compiler would just hang.

Unfortunately there’s no built-in debug mode yet, it’s also not trivial to visualize not fully laid out content easily I imagine. I think right now you’ll have to work with what you have and go over the final document in the end to manually break those pages. :/

1 Like

Since we are on the topic of pagebreak(), I want to say that a lot of times I got a headache when dealing with them. Specifically, I sometimes want to break a page at some point, but what if it already is? It will add a whole blank page in this case. And then I discovered pagebreak.weak. It saved me quite a few times. In some documents, I even do #set pagebreak(weak: true) at the top and then use #pagebreak() normally.

1 Like