<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Waqas Younas&apos; blog</title>
    <description>Welcome to Waqas&apos; blog</description>
    <link>wyounas.github.io</link>
    <atom:link href="wyounas.github.io/feed.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title>Finding All Solutions to LinkedIn Queens with a SAT Solver and Model Checkers</title>
        <description>&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/first.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;LinkedIn Queens is a puzzle played on an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n × n&lt;/code&gt; grid with the following rules:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The grid is divided into &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;n&lt;/code&gt;&lt;/em&gt; colored regions.&lt;/li&gt;
  &lt;li&gt;Each row, column, and colored region must contain exactly one queen.&lt;/li&gt;
  &lt;li&gt;No two queens may be adjacent, including diagonally. However, diagonals at a distance are allowed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, we look for all solutions, not just one, using a Python-based SAT solver and Spin, a model checker. We then model the same puzzle in FizzBee, another model checker, where the default output gives a valid counterexample rather than listing every solution. To keep things simple, we will solve the puzzle for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4x4&lt;/code&gt; grid using the SAT solver, but we will solve it both for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4x4&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9x9&lt;/code&gt; using model checkers.&lt;/p&gt;

&lt;p&gt;The goal here is to explore how various computational approaches can tackle the same problem. For one, I believe there is a touch of elegance in how SAT solvers can sometimes reformulate complex problems. I also believe that model checkers, for instance, aren’t just powerful tools for ensuring correctness, but also a great way to learn about nondeterminism.&lt;/p&gt;

&lt;p&gt;All the code in this post is linked in relevant sections, and the repository containing all the code is also linked in the references at the end.&lt;/p&gt;

&lt;h2 id=&quot;solving-the-puzzle-using-a-sat-solver&quot;&gt;Solving the Puzzle using a SAT Solver&lt;/h2&gt;

&lt;p&gt;I first came across this puzzle in a nice &lt;a href=&quot;https://buttondown.com/hillelwayne/archive/solving-linkedin-queens-with-smt/&quot;&gt;blog post&lt;/a&gt; by Hillel Wayne, whose blog I enjoy reading, where he solved it using an SMT solver called Z3.&lt;/p&gt;

&lt;p&gt;In the past, I had used a Python-based SAT solver called PyEDA [1], and I thought it would be interesting to use it to find all solutions to the LinkedIn Queens puzzle. Let’s solve it for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4x4&lt;/code&gt; grid though the code extends to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9x9&lt;/code&gt; grid. The code uses Python 3.13.1 and PyEDA 0.29.0.&lt;/p&gt;

&lt;p&gt;At a high level, PyEDA lets us describe a problem using Boolean variables and rules over those variables, and then searches for settings that satisfy all the rules.&lt;/p&gt;

&lt;p&gt;For example, suppose we create an array of two Boolean variables with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X = exprvars(&apos;x&apos;, 2)&lt;/code&gt;. Here, for example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X[0]&lt;/code&gt; could mean “put a queen in the first cell” and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X[1]&lt;/code&gt; could mean “put a queen in the second cell.” PyEDA’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OneHot()&lt;/code&gt; function says that exactly one variable in a group must be true, so &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OneHot(X[0], X[1])&lt;/code&gt; means exactly one of those two cells must contain a queen. When we ask PyEDA to search for settings that satisfy this rule, it returns only these two valid settings: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{X[0]: 1, X[1]: 0}&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{X[0]: 0, X[1]: 1}&lt;/code&gt;. It does not return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{X[0]: 1, X[1]: 1}&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;{X[0]: 0, X[1]: 0}&lt;/code&gt; because those do not satisfy the rule.&lt;/p&gt;

&lt;p&gt;With that idea in mind, to solve the 4x4 LinkedIn Queens puzzle in PyEDA, we start by representing the board as a two-dimensional array of Boolean variables:&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;exprvars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&apos;x&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We interpret &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X[r, c]&lt;/code&gt; to mean that cell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(r, c)&lt;/code&gt; contains a queen. The solver then searches for True/False assignments to these variables that satisfy all the puzzle rules.&lt;/p&gt;

&lt;p&gt;To represent colored regions, we assign each region a number. For example, cells marked with 1 belong to the red region, 2 to green, 3 to yellow, and 4 to pink. Consider the following layout of four regions in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4×4&lt;/code&gt; grid, where each row is assigned a distinct color:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can map this layout using a Python dictionary as follows:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;n&quot;&gt;regions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Region 1: cells 1-4 (row 0)
&lt;/span&gt;        &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Region 2: row 1
&lt;/span&gt;        &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)],&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Region 3: row 2
&lt;/span&gt;        &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;   &lt;span class=&quot;c1&quot;&gt;# Region 4: row 3
&lt;/span&gt;    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this small example, each region happens to be a whole row, so the region rule does not add anything new here. We are keeping it anyway because it shows how we encode regions. A little later, when we move to the model checkers, we will solve a puzzle with irregular regions where this rule really matters.&lt;/p&gt;

&lt;p&gt;Next, we encode the rules of the puzzle as Boolean constraints. If the solver finds true and false values for the variables that make all of these constraints true at once, then we have a valid board.&lt;/p&gt;

&lt;p&gt;The first constraint says that each row contains exactly one queen. In plain terms, that means one cell in the row must be chosen, and the others must not be. PyEDA’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;OneHot()&lt;/code&gt; gives us exactly that. Here’s how we express this constraint for each row:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;row_vars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;constraints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OneHot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;row_vars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We do the same for columns:&lt;/p&gt;
&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;col_vars&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)]&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;constraints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;OneHot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;col_vars&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we do the same for each colored region.&lt;/p&gt;

&lt;p&gt;Finally, we add the diagonal rule. This puzzle is a little different from the classic N-Queens puzzle: queens may lie on the same diagonal as long as they are not touching diagonally. Since each row and each column already gets exactly one queen, we do not need extra constraints for left-right or up-down neighbors here. The remaining local conflict is two queens on immediately adjacent diagonal cells. For example, a queen at (1, 1) forbids (0, 0), (0, 2), (2, 0), and (2, 2) as shown in the illustration below.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; X  .  X  . 
 .  Q  .  . 
 X  .  X  . 
 .  .  .  . 

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To encode this, it is enough to look only one row down from each cell: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(r+1, c-1)&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(r+1, c+1)&lt;/code&gt;. That covers every touching diagonal pair once. Looking upward as well would only repeat the same restriction.&lt;/p&gt;

&lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# Stop before last row
&lt;/span&gt;        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Below-left diagonal
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;# A constraint like ~(X[1,1] &amp;amp; X[2,0]) means you can&apos;t have 
&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# queens at both (1,1) and (2,0) because ~(a &amp;amp; b) implies both
&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# &apos;a&apos; and &apos;b&apos; can&apos;t be both true at the same time. That means,
&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# the two cells cannot both contain queens.
&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;constraints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;# Below-right diagonal
&lt;/span&gt;            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;# A constraint like ~(X[1,1] &amp;amp; X[2,2]) means you can&apos;t have 
&lt;/span&gt;                &lt;span class=&quot;c1&quot;&gt;# queens at both (1,1) and (2,2)
&lt;/span&gt;                &lt;span class=&quot;n&quot;&gt;constraints&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;append&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;X&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;c&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s pretty much all we need to do to get not just one, but all possible solutions to the puzzle. We combine all the constraints and call PyEDA’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;satisfy_all()&lt;/code&gt;. It returns all choices of true and false values that make the full formula true, which here means all valid boards.&lt;/p&gt;

&lt;p&gt;Below are two valid solutions for the above board with specific regions. Here is the &lt;a href=&quot;https://github.com/wyounas/linkedin_queens/blob/main/queens.py&quot;&gt;complete code&lt;/a&gt; that generated these two solutions.&lt;/p&gt;

&lt;p&gt;Solution 1 - Queens at cells: [3, 5, 12, 14] (Cells are numbered from 1 to 16 (top‑left is 1, bottom‑right is 16)):&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; .  .  Q  . 
 Q  .  .  . 
 .  .  .  Q 
 .  Q  .  . 

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Solution 2 - Queens at cells: [2, 8, 9, 15]:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; .  Q  .  . 
 .  .  .  Q 
 Q  .  .  . 
 .  .  Q  . 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also explore the same idea interactively below, just to get a sense of how Spin steps through the search, backtracks, and finds a solution. Try stepping through the search and watch how invalid branches are blocked and how Spin resumes from an earlier unexplored choice.&lt;/p&gt;

&lt;iframe src=&quot;/assets/widgets/queens-search.html&quot; style=&quot;width: 90%; height: 920px; border: none;&quot; loading=&quot;lazy&quot; title=&quot;Spin search visualization&quot;&gt;
&lt;/iframe&gt;

&lt;p&gt;If the interactive does not load, you can open it directly &lt;a href=&quot;/assets/widgets/queens-search.html&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Now let’s try to solve the puzzle using a model checker, again with the goal of finding all possible solutions, not just one. We’ll start with Spin, which uses Promela as its modeling language.&lt;/p&gt;

&lt;h2 id=&quot;solving-the-puzzle-using-spin&quot;&gt;Solving the Puzzle Using Spin&lt;/h2&gt;

&lt;p&gt;You can use a model checker to check the correctness of a system. You start by first creating a model of that system. A model abstracts the behavior you want to study while leaving out details that are not relevant to the question you are asking. Once you have written the model, you specify correctness properties that should hold in all reachable executions of the model. The model checker then explores those executions and searches for violations of those properties. If it finds a violation, it reports a counterexample that you can study to understand the error and improve the model or the system design.&lt;/p&gt;

&lt;p&gt;Spin is a model checker that works with models written in Promela. It can systematically explore many program behaviors that arise because of nondeterministic choices or concurrency.&lt;/p&gt;

&lt;p&gt;For this puzzle, we will write a Promela model that describes how queens may be placed and which placements are not allowed, so Spin can search for valid solutions. We are not using Spin mainly to check whether the model satisfies a correctness property. Instead, we encode the puzzle as a sequence of choices constrained by the puzzle rules and let Spin explore the resulting executions. Any execution that gets all the way through those constraints corresponds to a valid solution to the puzzle.&lt;/p&gt;

&lt;p&gt;Let’s quickly review Spin’s verification process. Spin analyzes the correctness of a system through verification, which involves three main steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;First, write the model in Promela and save it in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.pml&lt;/code&gt; file.&lt;/li&gt;
  &lt;li&gt;Generate a verifier. Run &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spin -a model.pml&lt;/code&gt;&lt;/em&gt; to generate a C file named &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pan.c&lt;/code&gt;&lt;/em&gt; (the name &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pan&lt;/code&gt;&lt;/em&gt; is historically derived from “protocol analyzer”). Then compile that file with a C compiler, for example &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gcc -o pan pan.c&lt;/code&gt;&lt;/em&gt;. The resulting executable, usually named &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;pan&lt;/code&gt;&lt;/em&gt;, is Spin’s verifier.&lt;/li&gt;
  &lt;li&gt;Run the verifier. One can run it using the shell command &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./pan&lt;/code&gt;&lt;/em&gt;. It reports whether all computations are correct or there exists a computation that contains an error. If an error is found, the verifier produces a trail file which can be used to reconstruct the erroneous computation. We’ll make use of trail files shortly, to extract valid puzzle solutions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s now turn to an example that introduces two Spin concepts, blocking expressions and nondeterminism, which we’ll use to solve the puzzle. Nondeterminism lets Spin try different queen placements, and blocking expressions let Spin discard invalid placements that break the puzzle rules.&lt;/p&gt;

&lt;p&gt;The first concept is a blocking expression that blocks execution when it evaluates to false. In Spin terms, this means the process that was running cannot move on to its next statement. For example, the following statement blocks execution when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; equals 2:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;! (x == 2);
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The statement above lets the process move forward only if the condition is true. The ! symbol means ‘negation,’ so the statement is true when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is not equal to 2. So, the process continues only when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is not 2.&lt;/p&gt;

&lt;p&gt;The second concept is how nondeterminism and error search work in Spin. Consider the following small program (All Spin code in this post was written using Spin version 6.5.2):&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/second.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We wrap our code in a process-like executable unit using the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proctype&lt;/code&gt;&lt;/em&gt; keyword in Promela. When we prefix a &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proctype&lt;/code&gt;&lt;/em&gt; definition with the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;active&lt;/code&gt;&lt;/em&gt; keyword, we are telling the model checker to automatically start that process when Spin begins execution.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;&lt;/em&gt; statement as written above (starting with the keyword &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;&lt;/em&gt; and ending with &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;fi&lt;/code&gt;&lt;/em&gt;) is how nondeterministic choice is expressed in Promela and Spin. Inside an &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;&lt;/em&gt; statement, each option starts with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::&lt;/code&gt;. The first statement after &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;::&lt;/code&gt; is called a guard. If more than one guard statement is executable, Spin chooses one of them nondeterministically. In this case, the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;&lt;/em&gt; block assigns a value to &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; nondeterministically. After this &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt;&lt;/em&gt; statement is executed, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; will have one of the values: 1, 2, 3, 4, or 5.&lt;/p&gt;

&lt;p&gt;To further understand how this nondeterminism will play out, below is an automaton in &lt;em&gt;Figure 1&lt;/em&gt; that represents the possible transitions for this code (generated using a tool called jSpin [2]):&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/three.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The numbers inside the circles represent line numbers. As you can see, there is not just one computation path from start to finish, there are five possible computations, depending on which value is assigned to &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;At the end of the above program, we have an &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert&lt;/code&gt;&lt;/em&gt; that checks whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x == 1&lt;/code&gt;. Will this assertion pass? No, it will fail. Spin explores the executions created by the nondeterministic choice above, and whenever an execution reaches the assertion, it checks whether the condition is true. The assertion passes when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is 1. It fails when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is 3, 4, or 5, so Spin reports those executions as error trails. It does not fail when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is 2, because that execution blocks earlier at &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;!(x == 2)&lt;/code&gt;&lt;/em&gt; and never reaches the assertion.&lt;/p&gt;

&lt;p&gt;Here’s a truncated version of the assertion violation report you would see when you run your model (assuming the above code is saved in a file named &lt;em&gt;atest.pml&lt;/em&gt;):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ spin -a atest.pml
$ gcc -o pan pan.c
$ ./pan -E         
pan:1: assertion violated (x==1) (at depth 1)
pan: wrote atest.pml.trail
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-E&lt;/code&gt; switch tells the verifier not to report invalid end-state errors. Here, this switch keeps the output focused on assertion violations.
As we see in the output above, a trail file named &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;atest.pml.trail&lt;/code&gt;&lt;/em&gt; is generated. We can replay that trail file with Spin’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-t -p&lt;/code&gt; switches, inspect the output, and see how the computation led to the error. The output below, truncated for clarity, shows the sequence of steps that led to the assertion failure.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ spin -t -p atest.pml

  1:    proc  0 (P:1) atest.pml:7 (state 3)     [x = 3]
  2:    proc  0 (P:1) atest.pml:12 (state 8)    [(!((x==2)))]
spin: atest.pml:13, Error: assertion violated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The output above shows that the assertion failed in the computation where &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; was set to 3.  But there are other counterexamples that would also cause the assertion to fail, for example, when &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; is set to 4 or 5. So how do we find all these counterexamples? We can do that using the following command:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;./pan -E -c0 -e    
pan:1: assertion violated (x==1) (at depth 1)
pan: wrote atest.pml1.trail
pan: wrote atest.pml2.trail
pan: wrote atest.pml3.trail
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For each counterexample, Spin generates a separate trail file (e.g., &lt;em&gt;atest.pml1.trail&lt;/em&gt;, etc). We can replay these trail files to reconstruct the exact sequence of steps that led to the assertion failure. For example, let’s inspect the contents of &lt;em&gt;atest.pml3.trail&lt;/em&gt; to see another such erroneous computation:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ spin -p -t -k atest.pml3.trail atest.pml

  1:    proc  0 (P:1) atest.pml:9 (state 5)     [x = 5]
  2:    proc  0 (P:1) atest.pml:12 (state 8)    [(!((x==2)))]
spin: atest.pml:13, Error: assertion violated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We see that when &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt; was set to 5, it refuted our assertion claim. We can similarly replay the trail for other files with commands &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spin -p -t -k atest.pml1.trail atest.pml&lt;/code&gt;&lt;/em&gt; and &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spin -p -t -k atest.pml2.trail atest.pml&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The key takeaway is this: we nondeterministically set &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt;, wrote an assertion, and let the model checker backtrack and explore all possible computations involving the different values &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; could take and the model checker found all computations that violated the assertion and reported them as counterexamples.&lt;/p&gt;

&lt;p&gt;This ability of Spin to take code with nondeterminism and systematically backtrack and explore all possible computations in order to find a counterexample to an assertion is central to how we’ll find all solutions to the puzzle [3].&lt;/p&gt;

&lt;p&gt;So how do we find those valid combinations that are solutions to LinkedIn Queens puzzle?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1&lt;/em&gt; helped me come up with a solution. In that figure, we chose a value for &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;x&lt;/code&gt;&lt;/em&gt; nondeterministically from five options. The model checker then explored all five possible computations, one for each choice. Similarly, if we let the model checker nondeterministically pick one cell from each region, while enforcing the puzzle rules, and then backtrack through all possibilities, we can find all valid solutions.&lt;/p&gt;

&lt;p&gt;To visualize this, I found it helpful to imagine a tree-like structure, as shown in &lt;em&gt;Figure 2&lt;/em&gt; below (If the image appears unclear, please consider opening it in a new tab.). It is of course not ideal, but it helped me think through the problem. For both &lt;em&gt;Figure 2&lt;/em&gt; and &lt;em&gt;Figure 3&lt;/em&gt; below, we use the same board and colored region configuration as in the Python code above—where 1s represent region colored red, 2s represent region colored green, 3s represent region in yellow, and 4s represent region in pink. The board layout looks like this:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In this configuration:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Region 1 includes cells 1, 2, 3, 4&lt;/li&gt;
  &lt;li&gt;Region 2 includes cells 5, 6, 7, 8&lt;/li&gt;
  &lt;li&gt;Region 3 includes cells 9, 10, 11, 12&lt;/li&gt;
  &lt;li&gt;Region 4 includes cells 13, 14, 15, 16&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could now imagine a tree with four levels. At level 0, you see all the cells from Region 1. From each of those cells, there are branches leading to every cell in Region 2.
In the figure, in Region 1, we show a branch going out from cell 1 just to save space. You can imagine similar branches coming from cells 2, 3, and 4 as well, and the same for all cells in level 1 and 2.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/four.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now let’s see what is going on in Figure 2. A queen is first placed in cell 1, and then the search moves on to Region 2, where cell 5 is ignored because of a column constraint and cell 6 is ignored because of a diagonal constraint. It places the queen in cell 7. The board at this point is shown on the right. When it moves to Region 3, it hits a dead-end, there is no cell available where the queen can be placed without violating the constraints.&lt;/p&gt;

&lt;p&gt;At that point, we could imagine Spin backtracking. Let’s look at the following illustration to see what happens next.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/five.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 3&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After hitting a dead-end, Spin backtracks to the most recent nondeterministic choice whose other alternatives have not yet been explored. In the figure, I illustrate that next unexplored choice as a jump back to an earlier region, shown by the red arrow going from 7(Q) back to 2(Q). The red arrow is only a visual aid showing where the search resumes after backtracking; it is not meant to be read as one literal execution step. From there, it places the queen in cell 2 in region 1 (shown as 2(Q), colored in green). After successfully placing a queen there, it moves on to region 2, where it rejects cell 6 due to a column conflict, and cells 5 and 7 due to diagonal clashes. It then selects cell 8. In region 3, given all constraints, it places the queen in cell 9, and finally, in region 4, respecting constraints, it finds a valid position at cell 15, thus completing one valid solution in compliance with the puzzle constraints (the solved board is shown at the right).&lt;/p&gt;

&lt;p&gt;Now let’s begin working toward a solution in Spin, with the goal of finding all possible solutions to the puzzle. We’ll solve it first for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4x4&lt;/code&gt; grid as it is simple, and then the same code, with minor changes, would be used to solve it for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9x9&lt;/code&gt; grid.&lt;/p&gt;

&lt;p&gt;We use the same board and colored regions configuration as given above. We encode the region of 1s as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;inline ChooseRegion1() {
    if
    :: cell = 1   
    :: cell = 2    
    :: cell = 3   
    :: cell = 4  
    fi
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And region 2 as:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;inline ChooseRegion2() {
    if
    :: cell = 5    
    :: cell = 6   
    :: cell = 7    
    :: cell = 8   
    fi
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And so on. Why make the model checker choose cells nondeterministically? We can connect this to &lt;em&gt;Figures 2 and 3&lt;/em&gt; and the explanation that followed. By organizing regions and their cells as shown in these figures you can see how this structure shows how the model checker might systematically explore all combinations.&lt;/p&gt;

&lt;p&gt;We also define a few variables at the top of the model:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define N 4
#define ADJ1(a,b) ((a) == (b) + 1 || (b) == (a) + 1)
byte result[N];    
bool rows[N];      
bool cols[N];           
bool regions[N];
byte qcol[N];
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array stores the final solution. Each &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result[region-1]&lt;/code&gt;&lt;/em&gt; holds the cell where a queen is placed in that region. For example, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result[0] = 2&lt;/code&gt; means the queen is placed in cell 2 in region 1.&lt;/p&gt;

&lt;p&gt;The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt; keeps track of whether a queen has already been placed in a given row. For example, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows[i]&lt;/code&gt;&lt;/em&gt; is true if there is a queen in row &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;i&lt;/code&gt;&lt;/em&gt;. Similarly, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;cols&lt;/code&gt;&lt;/em&gt; keeps track of whether a queen has been placed in a given column.&lt;/p&gt;

&lt;p&gt;The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt; stores one piece of information for each row: if that row already has a queen, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol[row]&lt;/code&gt;&lt;/em&gt; tells us which column that queen is in. For example, if &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol[2] = 0&lt;/code&gt;&lt;/em&gt;, then the queen in row 2 is in column 0.&lt;/p&gt;

&lt;p&gt;This helps because we do not need to store the whole board to check diagonal adjacency. To see whether a new candidate cell would touch an existing queen diagonally, we only need to know two things: whether a neighboring row already has a queen, and if it does, which column that queen is in. The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt; answers the first question. The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt; answers the second.&lt;/p&gt;

&lt;p&gt;The macro &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADJ1(a,b)&lt;/code&gt;&lt;/em&gt; is true when the two column numbers differ by 1. In plain terms, it tells us whether one column is immediately to the left or right of the other. Two queens touch diagonally only if one is in a neighboring row and their columns differ by 1.&lt;/p&gt;

&lt;p&gt;That is why &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt;, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt;, and &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADJ1&lt;/code&gt;&lt;/em&gt; are enough to detect forbidden diagonal adjacency without storing the whole board. This works because a diagonal-touch conflict can only occur with a queen in the row just above or just below the candidate cell, and only when that queen’s column differs by 1. The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt; tells us whether a neighboring row already contains a queen, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt; tells us which column that queen is in, and &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADJ1&lt;/code&gt;&lt;/em&gt; checks whether the two columns differ by 1.&lt;/p&gt;

&lt;p&gt;The general outline of what happens in the program is as follows. As the program processes the board region by region iteratively, from region 1 to 4, it performs these steps in sequence (we’ll go over each in some detail shortly):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Iterate through all regions and do the following in each iteration:
    &lt;ul&gt;
      &lt;li&gt;Choose cells for a region nondeterministically.&lt;/li&gt;
      &lt;li&gt;Checks whether a queen is already placed in the current row and column. If we have, we block the process.&lt;/li&gt;
      &lt;li&gt;Ensures that we are not placing a queen on an adjacent diagonal. If we are, the process is blocked.&lt;/li&gt;
      &lt;li&gt;Mark the row, column, and region as occupied.&lt;/li&gt;
      &lt;li&gt;Places the queen and records its position in the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;After exiting the loop, we deliberately use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assert(false)&lt;/code&gt; so that every computation that successfully exits the loop triggers an assertion violation and produces an error trail. When we replay that trail, we can read the values assigned to the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array; those values are the cells where the queens were placed, so they give us the solution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s quickly go over these one by one.&lt;/p&gt;

&lt;p&gt;We start a loop to process the board region by region, and the first step inside the loop is to choose region cells nondeterministically using the following code:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    if
        :: region == 1 -&amp;gt; ChooseRegion1()
        :: region == 2 -&amp;gt; ChooseRegion2()
        :: region == 3 -&amp;gt; ChooseRegion3()
        :: region == 4 -&amp;gt; ChooseRegion4()
    fi

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The program then checks and blocks if a queen has already been placed in the chosen row. This is done using a blocking expression:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;!rows[row];  
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As we discussed earlier, the execution continues if above is true, meaning the row is still available. It is important to note that, because of these blocking statements, many computations will be blocked, but some will not. To find valid solutions to the puzzle, we will make use of the assertion claim. More on this in a bit.&lt;/p&gt;

&lt;p&gt;After checking the row, we similarly block the placement if a queen has already been placed in the same column. We also block the move if a queen has already been placed in this region.&lt;/p&gt;

&lt;p&gt;For the diagonal rule, the code checks the same thing you would check on the board, but it does so through &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt; and &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt; instead of by storing every cell.&lt;/p&gt;

&lt;p&gt;Suppose, using zero-based indexing, that we are currently considering the candidate cell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(5, 4)&lt;/code&gt;. At this point, earlier region choices may already have placed queens elsewhere on the board. To see whether &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(5, 4)&lt;/code&gt; violates the diagonal rule, we only need to look at the four cells that touch it diagonally: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 3)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 5)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(6, 3)&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(6, 5)&lt;/code&gt;. No other diagonal cells matter, because this puzzle forbids only diagonal touching; longer diagonals are allowed. We could illustrate the relevant rows, columns, and positions like this:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;          col 3   col 4   col 5
row 4       X       .       X
row 5       .       C       .
row 6       X       .       X
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;C&lt;/code&gt;&lt;/em&gt; is the candidate cell &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(5, 4)&lt;/code&gt;. The four &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;X&lt;/code&gt;&lt;/em&gt; cells are the only cells that touch it diagonally: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 3)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(4, 5)&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(6, 3)&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(6, 5)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the code asks: does row &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4&lt;/code&gt;&lt;/em&gt; already contain a queen, and if so, is it in column &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;3&lt;/code&gt;&lt;/em&gt; or &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5&lt;/code&gt;&lt;/em&gt;? It asks the same question for row &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6&lt;/code&gt;&lt;/em&gt;. The array &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rows&lt;/code&gt;&lt;/em&gt; tells us whether those neighboring rows already have queens. If they do, &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;qcol&lt;/code&gt;&lt;/em&gt; tells us the corresponding columns. Then &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ADJ1(qcol[neighboring_row], col)&lt;/code&gt;&lt;/em&gt; checks whether the stored queen column and the candidate column differ by 1. If they do, the candidate would touch an existing queen diagonally, so Spin blocks that execution. For example, if the candidate cell is (5, 4) and there is already a queen at (4, 5), then qcol[4] is 5, ADJ1(5, 4) is true, and Spin blocks that choice.&lt;/p&gt;

&lt;p&gt;We check both neighboring rows because the search proceeds in region order, not row order. By the time we consider a candidate cell, either neighboring row may already have been filled.&lt;/p&gt;

&lt;p&gt;Once a valid cell is found, we mark the row, column, and region as occupied and we store the queen’s position in the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;rows[row] = true;
cols[col] = true;      
result[region-1] = cell;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point, we have finished processing the region, so we move on to the next by restarting the loop. This process repeats until all regions have been processed.&lt;/p&gt;

&lt;p&gt;As you can see, near the end, after the loop, we have added this line:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;assert(false); 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This does not tell Spin to search for solutions directly. Rather, it deliberately turns every execution that gets past all the guards and reaches this point into an assertion violation. That assertion violation makes Spin write an error trail. When we replay the trail, Spin shows the assignments made along that execution, including the final values in the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array. Those values identify the cells that contain queens. So the reported “error” here is not a bad puzzle state; it is the mechanism we use to make Spin emit a trail for each successful placement. &lt;a href=&quot;https://github.com/wyounas/linkedin_queens/blob/main/queenfourbyfour.pml&quot;&gt;Here is the code&lt;/a&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4x4&lt;/code&gt; grid.&lt;/p&gt;

&lt;p&gt;Now to find a solution, we run the model and then run the verifier.&lt;/p&gt;

&lt;p&gt;You will see an assertion violation, along with a message indicating that a trail file has been written:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ spin -a queenfourbyfour.pml
$ gcc -o pan pan.c
$ ./pan -E
pan:1: assertion violated 0 (at depth 79)
pan: wrote queenfourbyfour.pml.trail
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Replay the trail file with command:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ spin -p -t -k queenfourbyfour.pml.trail  queenfourbyfour.pml
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The above will generate output that includes the contents of the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array, our solution to the puzzle. Somewhere near the end of the generated output, you will see the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array printed:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result[0] = 2
result[1] = 8
result[2] = 9
result[3] = 15
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If we plot the above values from the result array onto a board, we get a valid solution to the LinkedIn Queens puzzle:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt; .  Q  .  . 
 .  .  .  Q 
 Q  .  .  . 
 .  .  Q  . 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But this gives us just one solution to the puzzle. How can we find all solutions, ideally without adding any more code to our existing model? As we did earlier, by running &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;./pan -E -c0 -e&lt;/code&gt;&lt;/em&gt;. You will get two solutions to the puzzle in respective trail files (i.e., &lt;em&gt;queenfourbyfour.pml1.trail&lt;/em&gt; and &lt;em&gt;queenfourbyfour.pml2.trail&lt;/em&gt;). Here is the second solution:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result[0] = 3
result[1] = 5
result[2] = 12
result[3] = 14

 .  .  Q  . 
 Q  .  .  . 
 .  .  .  Q 
 .  Q  .  . 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Without changing much code, we can extend these ideas to a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9x9&lt;/code&gt; grid. Here is a LinkedIn Queens puzzle by LinkedIn on a day in July 2025:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/six.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/wyounas/linkedin_queens/blob/main/queenninebynine.pml&quot;&gt;Here is the complete code&lt;/a&gt; that gives us the solution to this puzzle. When I run it, I find the solution:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;result[0] = 46
result[1] = 11
result[2] = 6
result[3] = 26
result[4] = 39
result[5] = 32
result[6] = 63
result[7] = 76
result[8] = 70
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2026-06-06-linkedin-puzzle/seven.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I took the original image and, based on the values in the &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;result&lt;/code&gt;&lt;/em&gt; array, placed queens (marked as “Q” above) in the image. It looks like a valid solution to the puzzle that satisfies all the constraints. (Please ignore the small ‘x’ in the image above, it was just a placeholder in the original image.)&lt;/p&gt;

&lt;p&gt;Just for fun and curiosity, I wanted to find all solutions to the puzzle without the region constraint, that is, to find all valid queen placements where exactly one queen is placed in each row and column, and no two queens are adjacent, including diagonally. &lt;a href=&quot;https://github.com/wyounas/linkedin_queens/blob/main/queens_wo_region.pml&quot;&gt;This code&lt;/a&gt; does that, and it reports 5242 solutions to the puzzle when we drop the region constraint for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;8 x 8&lt;/code&gt; grid (please note that if you run this, it will generate as many trail files as there are solutions).&lt;/p&gt;

&lt;p&gt;Basically, to sum it up, the method we have used to solve the puzzle is this: represent the unknown parts of the problem as nondeterministic choices, and express the rules so that invalid choices are blocked as early as possible. Spin then systematically explores the remaining computations and backtracks whenever it reaches a dead end. Any computation that makes it all the way to the end corresponds to a valid solution. The same method can be used for other problems where the goal is to choose values or positions that satisfy a set of rules.&lt;/p&gt;

&lt;p&gt;In the end, I’d say that Spin’s support for nondeterminism is quite powerful and it can be leveraged to solve certain kinds of problems in an elegant way.&lt;/p&gt;

&lt;h2 id=&quot;solving-the-puzzle-with-fizzbee&quot;&gt;Solving the puzzle with FizzBee&lt;/h2&gt;

&lt;p&gt;There’s another promising model checker on the horizon called &lt;a href=&quot;https://fizzbee.io/&quot;&gt;FizzBee&lt;/a&gt;. Its syntax is close to Python, which makes it approachable for Python programmers.&lt;/p&gt;

&lt;p&gt;To find a solution using FizzBee, I applied similar ideas to those I used with Promela and Spin.&lt;/p&gt;

&lt;p&gt;Just as we wrap the code we want to explore in a &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;proctype&lt;/code&gt;&lt;/em&gt; in Promela to model process-like behavior, in FizzBee we use an &lt;em&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;action&lt;/code&gt;&lt;/em&gt; block to define such executable units.&lt;/p&gt;

&lt;p&gt;We begin with the &lt;em&gt;Init&lt;/em&gt; action, a special action that runs once at the start. It sets up the state. Here is what &lt;em&gt;Init&lt;/em&gt; looks like in our case:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;action Init:
    N = 9  # 9x9 board
    # Define REGIONS as a list of cell IDs; omitted here for brevity
    #Also, we map each cell to (row, col) in the code
    board = [[0 for _ in range(N)] for _ in range(N)]

    row_queen_placed_in = [False for _ in range(N)]
    col_queen_placed_in = [False for _ in range(N)]
    region_queen_placed_in = [False for _ in range(N)]
    diagonals_blocked = [[False for _ in range(N)] for _ in range(N)]
    region_idx = 0
    done = False 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After that, the main search for solutions happens inside the &lt;em&gt;PlaceQueens&lt;/em&gt; action.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PlaceQueens&lt;/em&gt; first checks whether all regions are filled; if so, it sets &lt;em&gt;done&lt;/em&gt; to true and returns. Otherwise, it processes the regions one by one: for each region it picks a candidate cell, checks the constraints, places a queen if allowed, updates the bookkeeping, and then advances to the next region.&lt;/p&gt;

&lt;p&gt;Let’s take a quick look at the FizzBee constructs we use as we process each region in turn.&lt;/p&gt;

&lt;p&gt;We start a loop to iterate over each region. Then to pick a cell, we use the &lt;em&gt;any&lt;/em&gt; keyword. This makes the model checker explore every possible cell in the current region. This allows FizzBee to systematically explore valid combinations of cell placements across all regions.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
atomic action PlaceQueens:
    if all([queen for queen in region_queen_placed_in]):
        done = True 
        return 
    
    while region_idx &amp;lt; N:
        cells_in_region = REGIONS[region_idx]
        cell = any cells_in_region

        row = (cell - 1) // N
        col = (cell - 1) % N
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We then use &lt;em&gt;require&lt;/em&gt; guards to check the puzzle rules for the chosen cell. Each guard ensures that the row, column, and region are free, and that no queen is placed on any of the four immediate diagonals. If a &lt;em&gt;require&lt;/em&gt; fails, FizzBee abandons only that candidate branch; the other choices produced by &lt;em&gt;any&lt;/em&gt; are still explored. If all guards pass, we place the queen, mark the row, column, and region as used, and block the four diagonally adjacent cells so they cannot be used.&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
while region_idx &amp;lt; N:

    cells_in_region = REGIONS[region_idx]
    cell = any cells_in_region
    
    row = (cell - 1) // N
    col = (cell - 1) % N


    require row_queen_placed_in[row] == False  # row must be free
    require col_queen_placed_in[col] == False  # col must be free
    require region_queen_placed_in[region_idx] == False  # region must be free
    require diagonals_blocked[row][col] == False  # cell must not be diagonally blocked
    
    # commit placements
    board[row][col] = 1
    row_queen_placed_in[row] = True
    col_queen_placed_in[col] = True
    region_queen_placed_in[region_idx] = True
    
    # block the four immediate diagonal neighbors
    if row &amp;gt; 0 and col &amp;gt; 0:
        diagonals_blocked[row-1][col-1] = True # upper-left
    if row &amp;gt; 0 and col &amp;lt; N-1:
        diagonals_blocked[row-1][col+1] = True # upper-right
    if row &amp;lt; N-1 and col &amp;gt; 0:
        diagonals_blocked[row+1][col-1] = True # lower-left
    if row &amp;lt; N-1 and col &amp;lt; N-1:
        diagonals_blocked[row+1][col+1] = True # lower-right
    
    region_idx += 1

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;FizzBee also lets us specify safety properties, properties that state a ‘bad thing never happens.’ We write these properties using the &lt;em&gt;always&lt;/em&gt; keyword. For example, to express that a variable &lt;em&gt;temperature&lt;/em&gt; should never be greater than zero, we can write:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;always assertion AlwaysLessThanEqualsZero:
  return temperature &amp;lt;= 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To check this invariant, the model checker looks for counterexamples where &lt;em&gt;temperature&lt;/em&gt; is greater than zero. If no such counterexample exists, the invariant holds and the model is considered correct.&lt;/p&gt;

&lt;p&gt;Here’s a small FizzBee example where we return false if the temperature is greater than zero. Since the temperature is set to 5 in the &lt;em&gt;Init&lt;/em&gt; action, the invariant fails, false is returned, and FizzBee reports this as an invariant violation, with a counterexample trace showing us the state where the invariant did not hold.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# A simple example showing how returning False triggers a counterexample

action Init:
    temperature = 5

always assertion AlwaysLessThanEqualsZero:
    if temperature &amp;lt;= 0:
        return True 
    return False 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When we run the above model, FizzBee reports:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;FAILED: Model checker failed. Invariant:  AlwaysLessThanEqualsZero
------
Init
--
state: {&quot;temperature&quot;:&quot;5&quot;}
------

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In our puzzle, the same logic applies. We define the invariant as an “invalid board.” When that invariant fails, meaning none of the invalid conditions hold, FizzBee reports a counterexample, which is a valid solution to the puzzle.&lt;/p&gt;

&lt;p&gt;An invalid board means these things:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Not all queens are placed,&lt;/li&gt;
  &lt;li&gt;A row with more than one queen,&lt;/li&gt;
  &lt;li&gt;A column with more than one queen,&lt;/li&gt;
  &lt;li&gt;A pair of queens touching diagonally, or&lt;/li&gt;
  &lt;li&gt;A region that doesn’t contain exactly one queen.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of these conditions hold, the board is invalid. A counterexample to this assertion is a valid solution. We check for these assertions in the invariant section that begins with the &lt;em&gt;always assertion&lt;/em&gt; keyword. When none of these invalid-board conditions hold, the invariant returns &lt;em&gt;False&lt;/em&gt;. FizzBee then reports a counterexample, a board that violates the “invalid board” assertion, which represents a valid solution to the puzzle.&lt;/p&gt;

&lt;p&gt;When I run this &lt;a href=&quot;https://github.com/wyounas/linkedin_queens/blob/main/queens.fizz&quot;&gt;code&lt;/a&gt; for the above &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9x9&lt;/code&gt; LinkedIn Queens puzzle, FizzBee returns a counterexample. You can find it by checking the value of the &lt;em&gt;board&lt;/em&gt; variable in the output. And the counterexample corresponds to a valid LinkedIn Queens solution.&lt;/p&gt;

&lt;p&gt;Below is the resulting &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;9×9&lt;/code&gt; board, taken from the &lt;em&gt;board&lt;/em&gt; variable in the counterexample. A value of 1 marks a cell where a queen is placed.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[0, 0, 0, 0, 0, 1, 0, 0, 0]
[0, 1, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 1, 0, 0, 0, 0]
[0, 0, 1, 0, 0, 0, 0, 0, 0]
[1, 0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 0, 0, 0, 0, 1]
[0, 0, 0, 0, 0, 0, 1, 0, 0]
[0, 0, 0, 1, 0, 0, 0, 0, 0]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Unlike Spin with the options used earlier, FizzBee does not directly list every counterexample for this model. So this FizzBee version is best read as a way to find a valid solution. To enumerate solutions, JP (the creator of FizzBee) suggested a workaround: print the board when the assertion reaches a valid completed board, and return &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;True&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;False&lt;/code&gt; so the model checker continues exploring.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please keep in mind that I’m only human, and there’s a chance this post contains errors, even though I’ve done my best to avoid them. If you notice anything off, I’d appreciate a correction. Please feel free to &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;send me an email&lt;/a&gt;. Thanks to JP for reading the draft.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;PyEDA: &lt;a href=&quot;https://pyeda.readthedocs.io/en/latest/overview.html&quot;&gt;https://pyeda.readthedocs.io/en/latest/overview.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;jSpin: &lt;a href=&quot;https://github.com/motib/jspin&quot;&gt;https://github.com/motib/jspin&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Ben Ari’s book introduced me to nondeterminism in Spin. 
&lt;a href=&quot;https://link.springer.com/book/10.1007/978-1-84628-770-1&quot;&gt;Ben-Ari, M. (2008). Principles of the Spin model checker. Springer.&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Repository containing all code in this post: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/linkedin_queens&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/linkedin_queens&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Spin manual, &lt;em&gt;assert&lt;/em&gt;: &lt;a href=&quot;https://spinroot.com/spin/Man/assert.html&quot;&gt;https://spinroot.com/spin/Man/assert.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Spin manual, condition statements: &lt;a href=&quot;https://spinroot.com/spin/Man/condition.html&quot;&gt;https://spinroot.com/spin/Man/condition.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Spin manual, &lt;em&gt;pan&lt;/em&gt; options including &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-E&lt;/code&gt;: &lt;a href=&quot;https://spinroot.com/spin/Man/Pan.html&quot;&gt;https://spinroot.com/spin/Man/Pan.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Spin manual, &lt;em&gt;if&lt;/em&gt;: &lt;a href=&quot;https://spinroot.com/spin/Man/if.html&quot;&gt;https://spinroot.com/spin/Man/if.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;I built the interactive with AI assistance and manually checked the behavior.&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Sat, 06 Jun 2026 00:00:00 -0400</pubDate>
        <link>wyounas.github.io//model-checking/puzzles/2026/06/06/finding-all-solutions-to-linkedin-queens-with-a-sat-solver-and-model-checkers/</link>
        <guid isPermaLink="true">wyounas.github.io//model-checking/puzzles/2026/06/06/finding-all-solutions-to-linkedin-queens-with-a-sat-solver-and-model-checkers/</guid>
      </item>
    
      <item>
        <title>How to Solve Santa Claus Concurrency Puzzle with a Model Checker</title>
        <description>&lt;p&gt;When I enjoy a book, I often look for other work by the same author. While reading a book by Ben-Ari, I looked up his other writing and came across a paper on the Santa Claus concurrency puzzle &lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The puzzle is stated as follows:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Santa Claus sleeps at the North Pole until awakened by either all nine reindeer or a group of three out of ten elves. He performs one of two indivisible actions:&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;If awakened by the group of reindeer, Santa harnesses them to a sleigh, delivers toys, and finally unharnesses the reindeer, who then go on vacation.&lt;/li&gt;
    &lt;li&gt;If awakened by a group of elves, Santa shows them into his office, consults with them on toy R&amp;amp;D, and finally shows them out so they can return to work constructing toys.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;A waiting group of reindeer must be served by Santa before a waiting group of elves. Since Santa’s time is extremely valuable, marshalling the reindeer or elves into a group must not be done by Santa.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The puzzle captures the kind of synchronization challenges that arise whenever multiple processes must coordinate. I wanted to validate its correctness using a model checker. I expected this to be a simple problem. What surprised me is how easy it is to get it wrong.&lt;/p&gt;

&lt;p&gt;I also chose a different path to the solution. Before writing the correct model, I spent some time exploring what incorrect designs might look like. I find failures instructive because they expose unsafe assumptions and make clear what a correct solution must prevent. As a learning exercise, I wrote three small models, each reproducing a different failure scenario, and used correctness properties to catch the bugs. We will look at these failures in a bit. After that, I present the correct model and validate it with a model checker.&lt;/p&gt;

&lt;p&gt;To carry out this analysis, I used the SPIN model checker and wrote the models in Promela, SPIN’s specification language.&lt;/p&gt;

&lt;p&gt;A natural question is why use a model checker instead of simply writing the solution in Python or Go. The answer is coverage: a model checker explores interleavings that tests or experiments might miss, and either proves correctness or produces a counterexample.&lt;/p&gt;

&lt;p&gt;Let’s look at three key puzzle constraints:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Santa must not marshal the groups himself.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;If both reindeer and elves are waiting, Santa must serve the reindeer first (Christmas delivery is critical).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;When Santa serves a group, the entire group must participate together: exactly nine reindeer for delivery, or exactly three elves for consultation. Santa cannot deliver toys with seven reindeer or consult with two elves, for example.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, the problem seems straightforward. You wait until nine reindeer arrive or three elves arrive, wake Santa, do the work, and repeat. It is tempting to think that a few counters or locks are enough to solve it. The difficulty is in the interleavings. A solution that appears correct when reasoning step by step can fail when operations interleave in unexpected ways.&lt;/p&gt;

&lt;p&gt;Let’s now consider three failure scenarios that solve the puzzle incorrectly.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Santa delivers toys even though fewer than nine reindeer are actually ready.&lt;/li&gt;
  &lt;li&gt;Under a subtle interleaving, Santa ends up doing something impossible in the real world: delivering toys &lt;em&gt;and&lt;/em&gt; consulting with elves at the same time.&lt;/li&gt;
  &lt;li&gt;Santa chooses to consult with elves even though nine reindeer are already waiting and ready to go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’ll look at each of these failure scenarios next, and then work toward a solution that satisfies all puzzle constraints. Before that, let’s briefly introduce the Promela concepts we’ll need: channels, options, and guards.&lt;/p&gt;

&lt;p&gt;To communicate between processes, we use channels in Promela. A channel is a data type that supports two operations: send and receive. Channels transfer messages of a specified type from a sender to a receiver.&lt;/p&gt;

&lt;p&gt;SPIN supports two kinds of channels: rendezvous channels and buffered channels. In a rendezvous channel, send and receive synchronize: the sender blocks until the receiver is ready (and vice versa), and the transfer occurs as one atomic handshake. Buffered channels can hold messages temporarily, even if no process is ready to receive them yet. Buffered channels let a sender run ahead of a receiver; rendezvous channels force them to move together.&lt;/p&gt;

&lt;p&gt;A send operation consists of a channel variable followed by an exclamation mark (!) and a sequence of expressions whose number and types must match the channel’s message type. A receive operation, by contrast, consists of a channel variable followed by a question mark (?) and a sequence of variables into which the message is received.&lt;/p&gt;

&lt;p&gt;For example, we can declare a rendezvous channel named &lt;em&gt;request&lt;/em&gt; that carries messages of type &lt;em&gt;byte&lt;/em&gt; as follows (rendezvous channels have capacity of 0 as indicated by [0] syntax):&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;chan request = [0] of { byte };
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;We can send data on this channel as:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;request ! 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and receive data as:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;byte client;
request ? client
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now a few words about options and guards. Options in Promela can be used to do branching in the code. For example, one could write an &lt;em&gt;if&lt;/em&gt; statement in Promela as:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;if
:: (a &amp;lt; 50) -&amp;gt; printf(&quot;a &amp;lt; 50\n&quot;);
:: (a &amp;gt; 50) -&amp;gt; printf(&quot;a &amp;gt; 50\n&quot;);
fi

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each line beginning with :: is an option. An option consists of a guard followed by an action. The guard is the Boolean expression before the arrow (-&amp;gt;). An option is enabled if its guard evaluates to true.&lt;/p&gt;

&lt;p&gt;When the &lt;em&gt;if&lt;/em&gt; statement is reached:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;if exactly one option is enabled, Promela takes that option;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;if multiple options are enabled, SPIN may choose any one of them nondeterministically;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;if no option is enabled, the process blocks at the if.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s look at the first failure scenario in which Santa may deliver toys without all nine reindeer being ready. In the failure models I sometimes shrink the number of elves (e.g., to 3) to keep the state space small; the failure mechanism is the same.&lt;/p&gt;

&lt;p&gt;Here is a code excerpt from the model that simulates the first failure scenario:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;#define NUM_REINDEER 9
#define NUM_ELVES 3

// buffered channels
chan harnessed = [NUM_REINDEER] of { bit };
chan done_consulting = [NUM_ELVES] of { bit };

// Some code omitted for brevity

active [NUM_REINDEER] proctype Reindeer()
{
    do
    ::
        r_arrive ! 1;
        harnessed ? 1;
        actually_harnessed++;
        actually_harnessed--;
    od
}

active proctype Santa()
{
    byte i = 0;
    byte e = 0;
    byte j;
    
    do
    :: (i &amp;lt; NUM_REINDEER) -&amp;gt;
        r_arrive ? 1;
        i++;
        if
        :: (i == NUM_REINDEER) -&amp;gt; reindeer_ready = true
        :: else -&amp;gt; skip
        fi
        
    
    :: (i == NUM_REINDEER) -&amp;gt;
        
        for (j : 1 .. NUM_REINDEER) {
            harnessed ! 1;
        }
        
        delivering = true;
        /*
            simulating delivering toys
        */
        delivering = false;
        reindeer_ready = false;
        i = 0;
    
    // Some code omitted for brevity - complete code linked below
}

// Correctness property 
ltl safety { [] (delivering -&amp;gt; actually_harnessed == NUM_REINDEER) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This Promela model is a small “executable spec” of the Santa problem. The full model and instructions for running it are available in the repository &lt;sup id=&quot;fnref:2&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:2&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;2&lt;/a&gt;&lt;/sup&gt;. To keep things simple, I mainly discuss the Santa–reindeer interaction; elves are present only to keep the overall structure similar.&lt;/p&gt;

&lt;p&gt;The syntax &lt;em&gt;active [N] proctype P(){…}&lt;/em&gt; tells SPIN to start N concurrent copies of process P in the initial state, so here you get 9 Reindeer processes, 3 Elves processes, and 1 Santa process. Each reindeer loops by rendezvous-sending on the &lt;em&gt;r_arrive&lt;/em&gt; channel, so it blocks until Santa receives, then waits for a harness message on &lt;em&gt;harnessed&lt;/em&gt;. Santa loops, counting reindeer arrivals. Once all nine reindeer have arrived, he “harnesses” them by sending nine messages on &lt;em&gt;harnessed&lt;/em&gt; and then briefly sets &lt;em&gt;delivering = true&lt;/em&gt; to represent toy delivery.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-12-23-how-to-help-santa-concurrently/two_one.png&quot; alt=&quot;Santa claus first buggy sequence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The bug is that &lt;em&gt;harnessed&lt;/em&gt; is a buffered channel, so Santa can enqueue all nine harness messages and immediately proceed; nothing forces the reindeer to have actually received them yet. That violates the puzzle constraint that Santa must deliver with the whole group participating together. To check whether this implementation respects the puzzle’s constraints, we write correctness properties in Linear Temporal Logic (LTL).&lt;/p&gt;

&lt;p&gt;In simple terms, an LTL property describes a rule that must hold over time as the system runs. The model checker constructs the system’s state space by enumerating all reachable states, each state being a snapshot of all variables and the current position of each process in its code, under all possible interleavings. You can think of this state space as a graph: each node is a state, and each edge represents a single step the program can take as processes interleave. An LTL property is then checked against this entire state space: when we say a condition must “always” hold, we mean it must be true in every reachable state that SPIN explores.&lt;/p&gt;

&lt;p&gt;The LTL property &lt;em&gt;ltl safety { [] (delivering -&amp;gt; actually_harnessed == NUM_REINDEER) }&lt;/em&gt; encodes that requirement. Here [] means “always,” so the property states that in every reachable state, if delivering is true, then all nine reindeer must already be harnessed.&lt;/p&gt;

&lt;p&gt;When SPIN checks this property, it finds an execution in which Santa sets &lt;em&gt;delivering = true&lt;/em&gt; before all nine reindeer have received their harness signals. In other words, Santa starts delivering toys while some reindeer are still unharnessed. The property fails, and SPIN produces a counterexample showing exactly how this execution occurs. The code shows this model, along with instructions for running it and viewing the trace that contains the counterexample.&lt;/p&gt;

&lt;p&gt;Now let’s look at a second failure scenario, in which Santa may end up doing the seemingly impossible, delivering toys with the reindeer and consulting with the elves at the same time. The complete model that simulates this fault is available here &lt;sup id=&quot;fnref:3&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:3&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;3&lt;/a&gt;&lt;/sup&gt;; an excerpt is shown below.&lt;/p&gt;

&lt;p&gt;To make this bug easy to trigger, I did something silly: I split Santa into two: one responsible for toy delivery and one for consultation. Each process independently waits for its own group to form and then sets the corresponding flag (&lt;em&gt;delivering&lt;/em&gt; or &lt;em&gt;consulting&lt;/em&gt;) to indicate that work has begun. Because these two processes run concurrently and share no mutual exclusion, there exists an interleaving in which both flags become true at the same time.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-12-23-how-to-help-santa-concurrently/two_two.png&quot; alt=&quot;Santa claus second buggy sequence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The code violates a core constraint of the puzzle: Santa must perform only one indivisible action at a time. By adding an assert statement that asserts Santa never delivers and consults simultaneously, SPIN is able to find a concrete counterexample trace showing exactly how this impossible situation arises under a particular interleaving.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
active proctype SantaConsulting()
{

 byte e = 0;
    byte j;
    end:
    do
    
    :: (e &amp;lt; NUM_ELVES) -&amp;gt;
        e_arrive ? 1;
        e++;
    :: (e == NUM_ELVES) -&amp;gt;

        consulting = true;
        
        assert !(consulting &amp;amp;&amp;amp; delivering);
        // We release elves here
        consulting = false;
        e = 0;
    od 

}
active proctype SantaToyDelivery()
{
    byte i = 0;
    byte j=0;
    end:
    do
    :: (i &amp;lt; NUM_REINDEER) -&amp;gt;
        r_arrive ? 1;
        i++;
        
    :: (i == NUM_REINDEER) -&amp;gt;
        
        delivering = true;
        // We release the reindeer here
        delivering = false;
        
        i = 0;
        
    
    od
}

// correctness property

ltl reindeer_precedence {
    [] ( (r_count == NUM_REINDEER) -&amp;gt; ( (!consulting) U delivering ) )
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now let’s examine the final failure scenario, in which Santa may consult with elves even when a full group of nine reindeer is already waiting. The complete model that simulates this fault is available here &lt;sup id=&quot;fnref:4&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:4&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;4&lt;/a&gt;&lt;/sup&gt;; structurally, it is very similar to the previous one.&lt;/p&gt;

&lt;p&gt;In this version, Santa’s consultation logic does not check whether a full reindeer group is ready before proceeding. As a result, there exists an interleaving in which all nine reindeer have arrived and a group of three elves is also ready, yet Santa begins consulting with the elves. This violates a central constraint of the puzzle: when both groups are waiting, reindeer must be served before elves.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-12-23-how-to-help-santa-concurrently/two_three.png&quot; alt=&quot;Santa claus third buggy sequence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I found it tricky to detect this using only the tools discussed so far, and then discovered a precedence property expressed in LTL. Informally, the property states: once a full group of nine reindeer is ready, consulting must not occur before delivery begins. We encode this using the LTL ‘until’ operator (written as ‘U’), which lets us state that consulting must remain false until delivery occurs. When SPIN checks this property, it finds a counterexample in which Santa consults first, demonstrating that the implementation violates the required precedence rule.&lt;/p&gt;

&lt;p&gt;There’s one more rule that all of the models above break: Santa must not do the marshalling himself. In the above models, Santa is directly counting arrivals and releasing reindeer and elves, which makes it easy to mix up group formation with the work itself.&lt;/p&gt;

&lt;p&gt;The fix is to give Santa some help. Following Ben-Ari &lt;sup id=&quot;fnref:1:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;, we introduce rooms. A room’s only job is to collect arrivals and let Santa know when a complete group is ready. Santa never counts arrivals anymore, he simply waits for a “group ready” signal and then does the work.&lt;/p&gt;

&lt;p&gt;To build a correct model, we use one room for the reindeer and another for the elves. The key idea is to separate marshalling (handled by the rooms) from serving. Let’s now look at the modified sequence of events.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-12-23-how-to-help-santa-concurrently/third.png&quot; alt=&quot;Santa claus sequence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When a group of nine reindeer arrives at the reindeer room, the room signals Santa. While this is happening, two elves also arrive. Santa delivers toys and releases the reindeer. When the third elf later arrives, the elf room signals Santa; Santa consults with the elves and then releases them.&lt;/p&gt;

&lt;p&gt;To model this behavior in SPIN, each component is represented as a separate process. SPIN explores all possible interleavings of these processes and checks that specified correctness properties hold in every reachable state.&lt;/p&gt;

&lt;p&gt;We model the system using the following processes:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reindeer&lt;/em&gt;: We run nine reindeer processes concurrently. Each reindeer arrives at the reindeer room and waits there until it is released.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Elves&lt;/em&gt;: We also run ten elf processes concurrently. Each elf arrives at the elf room and waits there until it is released.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reindeer room&lt;/em&gt;: We run a single instance of this process, which simulates the reindeer room. It collects arriving reindeer, signals Santa when a full group of nine has formed, and releases the group after Santa completes delivery.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Elf room&lt;/em&gt;: Similarly, we run a single instance of this process to simulate the elf room. It collects arriving elves, signals Santa when a group of three has formed, and releases the group after Santa completes consultation.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Santa&lt;/em&gt;: A single Santa process waits for “group ready” signals from the rooms. It serves reindeer with priority over elves and notifies the appropriate room when its work is complete.&lt;/p&gt;

&lt;p&gt;To communicate between processes, we use the following rendezvous channels, each carrying messages of type &lt;em&gt;bit&lt;/em&gt;:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;chan r_arrive  = [0] of { bit };
chan e_arrive  = [0] of { bit };
chan r_release = [0] of { bit };
chan e_release = [0] of { bit };
chan r_done    = [0] of { bit };
chan e_done    = [0] of { bit };

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We append an &lt;em&gt;r&lt;/em&gt; to channel names that transfer data for reindeer, and an &lt;em&gt;e&lt;/em&gt; to those for elves. Below are the roles of the main channels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;r_arrive / e_arrive&lt;/em&gt;: These rendezvous channels represent how reindeer and elves enter their rooms. A reindeer tries to enter by sending on &lt;em&gt;r_arrive&lt;/em&gt;; because the channel is rendezvous, it blocks until the room is ready to receive. When the room is full, it stops receiving arrivals, so additional reindeer (or elves) block until the room re-opens.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;r_release / e_release&lt;/em&gt;: These channels represent how a room releases a complete group. After Santa finishes, the room sends on &lt;em&gt;r_release&lt;/em&gt; (or &lt;em&gt;e_release&lt;/em&gt;) once per group member. Because the channel is rendezvous, the room cannot “release ahead”, each send synchronizes with an actual waiting reindeer (or elf).&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;em&gt;r_done / e_done&lt;/em&gt;: These channels represent Santa’s “I’m finished” notification to the room. The room waits for this signal before releasing anyone. For example, after Santa finishes delivering toys, he sends on &lt;em&gt;r_done&lt;/em&gt;; without it, the room would not know when it is safe to release the reindeer.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s look at the reindeer and elf processes, which are quite similar. We’ll start with the reindeer process:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;active [9] proctype Reindeer()
{
    do
    :: r_arrive ! 1;     // enter the room
       r_release ? 1;    // wait to be released
    od
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Because of the &lt;em&gt;active [9]&lt;/em&gt; keyword, SPIN starts nine reindeer processes running concurrently in the initial system state. The statement &lt;em&gt;r_arrive ! 1&lt;/em&gt; indicates that a reindeer is attempting to enter the reindeer room by sending on the &lt;em&gt;r_arrive&lt;/em&gt; channel. Because &lt;em&gt;r_arrive&lt;/em&gt; is a rendezvous channel, the reindeer blocks until the room is ready to accept it. After entering the room, the reindeer waits to be released by blocking on the &lt;em&gt;r_release&lt;/em&gt; channel.&lt;/p&gt;

&lt;p&gt;The code for the elf process is similar:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;active [10] proctype Elf()
{
    do 
    :: e_arrive ! 1;
       e_release ? 1;
    od
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The room is where marshalling and synchronization take place. Let’s now look at the reindeer room.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;active proctype RoomReindeer()
{
    byte waiting = 0;
    byte i = 0;

    do
    /* Collect reindeer until the group is complete (entrance open iff waiting &amp;lt; 9) */
    :: (waiting &amp;lt; NUM_REINDEER) -&amp;gt;
        atomic {
            r_arrive ? 1;
            waiting++;
            reindeer_waiting = waiting;
            if
            :: (waiting == NUM_REINDEER) -&amp;gt; r_request = 1
            :: else -&amp;gt; skip
            fi
        }

    /* Group complete: wait for Santa, then release all 9 and reset */
    :: (waiting == NUM_REINDEER) -&amp;gt;
        atomic { r_done ? 1; i = 0; }

        do
        :: (i &amp;lt; NUM_REINDEER) -&amp;gt;
            atomic { r_release ! 1; i++; }
        :: else -&amp;gt; break
        od;

        atomic {
            waiting = 0;
            reindeer_waiting = 0;
        }
    od
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The repetition structure has two alternatives. When &lt;em&gt;waiting&lt;/em&gt; is less than &lt;em&gt;NUM_REINDEER&lt;/em&gt;, the room accepts arriving reindeer and increments the counter. If the arrival completes the group, the room sets &lt;em&gt;r_request = 1&lt;/em&gt; to notify Santa.&lt;/p&gt;

&lt;p&gt;When &lt;em&gt;waiting&lt;/em&gt; equals &lt;em&gt;NUM_REINDEER&lt;/em&gt;, the room stops accepting arrivals and waits for Santa to message completion on &lt;em&gt;r_done&lt;/em&gt;. After receiving this message, the room releases the reindeer via &lt;em&gt;r_release&lt;/em&gt; and resets its state to accept new arrivals.&lt;/p&gt;

&lt;p&gt;The logic for the elf room follows the same pattern and is omitted here for brevity.&lt;/p&gt;

&lt;p&gt;The Santa process models two kinds of work: toy delivery with the reindeer and consultation with the elves. Its main loop contains two guarded alternatives.&lt;/p&gt;

&lt;p&gt;In the first alternative, guarded by &lt;em&gt;r_request&lt;/em&gt;, Santa performs the toy delivery action, clears &lt;em&gt;r_request&lt;/em&gt;, and signals completion to the reindeer room by sending on &lt;em&gt;r_done&lt;/em&gt;. The corresponding code is shown below.&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;:: r_request -&amp;gt;
        atomic {
            delivering = true;
            r_request = 0;   /* claim the request */
        }

        /* Deliver toys (abstracted). */
        delivering = false;

        /* Notify the Room that delivery is complete; it can release the reindeer. */
        r_done ! 1;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The second alternative is guarded by &lt;em&gt;!r_request &amp;amp;&amp;amp; e_request&lt;/em&gt;. This guard enforces the priority rule: Santa consults with the elves only if no reindeer group is waiting and a group of three elves has formed. Santa then performs the consultation and signals completion to the elf room by sending on &lt;em&gt;e_done&lt;/em&gt;.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;:: (!r_request &amp;amp;&amp;amp; e_request) -&amp;gt;
        atomic {
            consulting = true;
            e_request = 0;   /* claim the request */
        }

        /* Consult (abstracted). */
        consulting = false;

        /* Notify the Room that consultation is complete; it can release the elves. */
        e_done ! 1;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How do we validate that the model is correct?&lt;/p&gt;

&lt;p&gt;We can specify two kinds of correctness properties: safety properties and liveness properties. Let’s look at them one by one.&lt;/p&gt;

&lt;p&gt;Safety properties assert that nothing bad ever happens. We use three safety properties in this model.
First, we ensure that Santa always delivers toys with exactly nine reindeer, that is, a full group of nine reindeer must be present in the room. We specify this as:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ltl safety_delivery { [] (delivering -&amp;gt; reindeer_waiting == 9) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This property states that, at all times, if Santa is delivering toys, then the variable &lt;em&gt;reindeer_waiting&lt;/em&gt; must equal 9.&lt;/p&gt;

&lt;p&gt;Second, we ensure that Santa always consults with exactly three elves. We specify this as:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ltl safety_consult { [] (consulting -&amp;gt; elf_waiting == 3) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we specify a mutual-exclusion property to ensure that Santa never delivers toys and consults at the same time:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ltl mutex_santa { [] !(delivering &amp;amp;&amp;amp; consulting) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You might wonder why the variables delivering and consulting are set to true only briefly. In this model, delivery and consultation are not simulated in detail, they are represented as single abstract steps. The boolean flags simply mark the moment when Santa starts a delivery or consultation, so that we can state and check correctness properties. Their purpose is not to model time passing, but to make illegal overlaps visible to the model checker.&lt;/p&gt;

&lt;p&gt;Now let’s turn to the liveness property. Liveness properties state that something good eventually happens. The liveness property we specify is: always, if a request is pending, then eventually Santa will either be delivering toys or consulting with elves. This rules out executions where requests remain pending forever without Santa ever doing work.&lt;/p&gt;

&lt;p&gt;We specify this property as follows (where [] denotes “always” and &amp;lt;&amp;gt; denotes “eventually”):&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ltl live_progress { [] ((r_request || e_request) -&amp;gt; &amp;lt;&amp;gt; (delivering || consulting)) }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;One important role of a liveness property is to avoid vacuous correctness. For example, a safety property such as &lt;em&gt;[] (delivering -&amp;gt; reindeer_waiting == 9)&lt;/em&gt; can hold even if &lt;em&gt;delivering&lt;/em&gt; is never true (‘-&amp;gt;’ is an implication, and in logic an implication “A implies B” is considered true whenever A is false, regardless of whether B is true or false.). In contrast, this liveness property rules out executions where a request remains pending forever without Santa ever taking a delivery or consultation step, in other words, it checks that some service step eventually occurs whenever a request is pending.&lt;/p&gt;

&lt;p&gt;When we run the model in SPIN, all safety and liveness properties pass. In separate runs, SPIN explored about 5 million states while checking &lt;em&gt;safety_delivery&lt;/em&gt; and &lt;em&gt;mutex_santa&lt;/em&gt;, and about 7 million states while checking the liveness property and found no counterexamples. In a similar fashion, we can also specify and check the precedence property.&lt;/p&gt;

&lt;p&gt;Both the model and the instructions for running it are available in the repository &lt;sup id=&quot;fnref:5&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:5&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;Let’s now look at an example execution to see how these pieces fit together.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-12-23-how-to-help-santa-concurrently/fourth.png&quot; alt=&quot;Santa claus sequence&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Time flows from top to bottom. When the ninth reindeer arrives, the reindeer room sets &lt;em&gt;r_request&lt;/em&gt; and closes the room to further arrivals. While &lt;em&gt;r_request&lt;/em&gt; remains true, Santa serves the reindeer, even if elves arrive, because the elf branch is disabled by the guard &lt;em&gt;!r_request&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;After Santa signals completion by sending on &lt;em&gt;r_done&lt;/em&gt;, the room releases all nine reindeer via rendezvous. When a group of three elves later forms, the model sets &lt;em&gt;e_request&lt;/em&gt;. Since &lt;em&gt;r_request&lt;/em&gt; is now false, Santa serves the elves.&lt;/p&gt;

&lt;p&gt;To transform this model into a computer program, I thought I would try implementing it in Go. I do not know Go particularly well, so I collaborated with an AI to translate the Promela design into a simple, idiomatic Go implementation. I reviewed the code &lt;sup id=&quot;fnref:6&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:6&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;6&lt;/a&gt;&lt;/sup&gt; to keep it faithful to the model, but any mistakes are mine.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please keep in mind that I’m only human, and there’s a chance this post contains errors. If you notice anything off, I’d appreciate a correction. Please feel free to &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;send me an email&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;endnotes&quot;&gt;Endnotes:&lt;/h2&gt;

&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Ben-Ari, M. &lt;em&gt;How to Solve the Santa Claus Problem&lt;/em&gt;. Concurrency: Practice and Experience, 1998. &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt; &lt;a href=&quot;#fnref:1:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:2&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Link to the source code for the first failure scenario: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_deliver_without_full_group.pml&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_deliver_without_full_group.pml&lt;/a&gt; &lt;a href=&quot;#fnref:2&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:3&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Link to the source code for the second failure scenario: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_deliver_and_consult_simultaneously.pml&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_deliver_and_consult_simultaneously.pml&lt;/a&gt; &lt;a href=&quot;#fnref:3&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:4&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Link to the source code for the third failure scenario: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_consult_before_delivery.pml&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_bug_consult_before_delivery.pml&lt;/a&gt; &lt;a href=&quot;#fnref:4&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:5&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Link to the source code for the room-based solution: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_claus.pml&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_claus.pml&lt;/a&gt; &lt;a href=&quot;#fnref:5&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id=&quot;fn:6&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;Link to the Go implementation of the room-based solution: &lt;a href=&quot;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_claus.go&quot;&gt;https://github.com/wyounas/model-checking/blob/main/puzzles/santa_claus/santa_claus.go&lt;/a&gt; &lt;a href=&quot;#fnref:6&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;
</description>
        <pubDate>Sat, 10 Jan 2026 00:00:00 -0500</pubDate>
        <link>wyounas.github.io//puzzles/concurrency/2026/01/10/how-to-help-santa-claus-concurrently/</link>
        <guid isPermaLink="true">wyounas.github.io//puzzles/concurrency/2026/01/10/how-to-help-santa-claus-concurrently/</guid>
      </item>
    
      <item>
        <title>Reproducing the AWS Outage Race Condition with a Model Checker</title>
        <description>&lt;p&gt;AWS published a post-mortem about a recent outage [1]. Big systems like theirs are complex, and when you operate at that scale, things sometimes go wrong. Still, AWS has an impressive record of reliability.&lt;/p&gt;

&lt;p&gt;The post-mortem mentioned a race condition, which caught my eye. I don’t know all the details of AWS’s internal setup, but using the information in the post-mortem and a few assumptions, we can try to reproduce a simplified version of the problem.&lt;/p&gt;

&lt;p&gt;As a small experiment, we’ll use a model checker to see how such a race could happen. Formal verification can’t prevent every failure, but it helps us think more clearly about correctness and reason about subtle concurrency bugs. Of course, writing about it after the incident benefits from hindsight, we already know what went wrong. For this experiment, we’ll use the Spin model checker, which uses the Promela language.&lt;/p&gt;

&lt;p&gt;There’s a lot of detail in the post-mortem, but for simplicity we’ll focus only on the race-condition aspect. The incident was triggered by a defect in DynamoDB’s automated DNS management system. The components of this system involved in the incident were the DNS Planner, DNS Enactor, and Amazon Route 53 service.&lt;/p&gt;

&lt;p&gt;The DNS Planner creates DNS plans, and the DNS Enactors look for new DNS plans and apply them to the Amazon Route 53 service. Three Enactors operate independently in three different availability zones.&lt;/p&gt;

&lt;p&gt;Here is an illustration showing these components (if the images appear small, please open them in a new browser tab):&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-10-30-aws-outage-race-conditions/aws-dns.png&quot; alt=&quot;aws dns management system&quot; /&gt;&lt;/p&gt;

&lt;p&gt;My understanding of how the DNS Enactor works is as follows: it picks up the latest plan and, before applying it, performs a one-time check to ensure the plan is newer than the previously applied one. It then applies the plan and invokes a clean-up process. During the clean-up, it identifies plans significantly older than the one it just applied and deletes them.&lt;/p&gt;

&lt;p&gt;Using the details from the incident report, we could sketch an interleaving that could explain the race condition. Two Enactors running side by side: Enactor 2 applies a new plan and starts cleaning up, while the other, running just a little behind, applies an older plan, making it an active one. When the Enactor 2 finishes its cleanup, it deletes that plan, and the DNS entries disappear. Here’s what that sequence looks like:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-10-30-aws-outage-race-conditions/race-condition-interleaving.png&quot; alt=&quot;aws dns race condition&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s try to uncover this interleaving using a model checker.&lt;/p&gt;

&lt;p&gt;In Promela, you can model each part of the system as its own process. Spin then takes those processes, starts from the initial state, and systematically applies every possible transition, exploring all interleavings to build the set of reachable states [2]. It checks that your invariants hold in each one, and if it finds a violation, it reports a counterexample.&lt;/p&gt;

&lt;p&gt;We’ll create a DNS Planner process that produces plans, and DNS Enactor processes that pick them up. The Enactor will check whether the plan it’s about to apply is newer than the previous one, update the state of certain variables to simulate changes in Route 53, and finally clean up the older plans.&lt;/p&gt;

&lt;p&gt;In our simplified model, we’ll run one DNS Planner process and two concurrent DNS Enactor processes. (AWS appears to run three across zones; we abstract that detail here.) The Planner generates plans, and through Promela channels, these plans are sent to the Enactors for processing.&lt;/p&gt;

&lt;p&gt;Inside each DNS Enactor, we track the key aspects of system state. The Enactor keeps the current plan in &lt;em&gt;current_plan&lt;/em&gt;, and it represents DNS health using &lt;em&gt;dns_valid&lt;/em&gt;. It also records the highest plan applied so far in &lt;em&gt;highest_plan_applied&lt;/em&gt;. The incident report also notes that the clean-up process deletes plans that are “significantly older than the one it just applied.” In our model, we capture this by allowing an Enactor to remove only those plans that are much older than its current plan. To simulate the deletion of an active plan, the Enactor’s clean-up process checks whether &lt;em&gt;current_plan&lt;/em&gt; equals the plan being deleted. If it does, we simulate the resulting DNS failure by setting &lt;em&gt;dns_valid&lt;/em&gt; to false.&lt;/p&gt;

&lt;p&gt;Here’s the code for the DNS Planner:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;active proctype Planner() {
    byte plan = 1;
    
    do
    :: (plan &amp;lt;= MAX_PLAN) -&amp;gt;
        latest_plan = plan;
        plan_channel ! plan; 
        printf(&quot;Planner: Generated Plan v%d\n&quot;, plan);
        plan++;
    :: (plan &amp;gt; MAX_PLAN) -&amp;gt; break;
    od;
    
    printf(&quot;Planner: Completed\n&quot;);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It creates plans and sends them over a channel (&lt;em&gt;plan&lt;/em&gt; is being sent to the channel &lt;em&gt;plan_channel&lt;/em&gt;) to be picked up later by the DNS Enactor.&lt;/p&gt;

&lt;p&gt;We start two concurrent DNS Enactor processes by specifying the number of enactors after the &lt;em&gt;active&lt;/em&gt; keyword.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;active [NUM_ENACTORS] proctype Enactor() 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The DNS Enactor waits for plans and receives them (? opertaor receives a plan from the channel &lt;em&gt;plan_channel&lt;/em&gt;). It then performs a staleness check, updates the state of certain variables to simulate changes in Route 53, and finally cleans up the older plans.&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;:: plan_channel ? my_plan -&amp;gt;
    snapshot_current = current_plan;

    // staleness check    
    if
    :: (my_plan &amp;gt; snapshot_current || snapshot_current == 0) -&amp;gt;

        if
            :: !plan_deleted[my_plan] -&amp;gt;
                /* Apply the plan to Route53 */
                
                current_plan = my_plan;
                dns_valid = true;
                initialized = true;
               /* Track highest plan applied for regression detection */
                if 
                :: (my_plan &amp;gt; highest_plan_applied) -&amp;gt;
                    highest_plan_applied = my_plan;
                fi 
            
            // runs the clean-up process (omitted for brevity, included in the 
            // code linked below)
        fi
    fi

&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;How do we discover the race condition? The idea is this: we express as an invariant what must always be true of the system, and then ask the model checker to confirm that it holds in every possible state. In this case, we can set up an invariant stating that the DNS should never be deleted once a newer plan has been applied. (With more information about the real system, we could simplify or refine this rule further.)&lt;/p&gt;

&lt;p&gt;We specify this invariant formally as follows:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;/*

A quick note on some of the keywords used in the invariant below:

ltl - keyword that declares a temporal property to verify (ltl: linear temporal logic lets you specify properties about all possible executions of your program.)

[] - &quot;always&quot; operator (this must be true at every step forever)

-&amp;gt; - &quot;implies&quot; (if left side is true, then right side must be true)

*/

ltl no_dns_deletion_on_regression {
    [] ( (initialized &amp;amp;&amp;amp; highest_plan_applied &amp;gt; current_plan 
            &amp;amp;&amp;amp; current_plan &amp;gt; 0) -&amp;gt; dns_valid )
}



&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;When we start the model checker, one DNS Planner process begins generating plans and sending them through channels to the DNS Enactors. Two Enactors receive these plans, perform their checks, apply updates, and run their cleanup routines. As these processes interleave, the model checker systematically builds the set of reachable states, allowing the invariant to be checked in each one.&lt;/p&gt;

&lt;p&gt;When we run the model with this invariant in the model checker, it reports a violation. Spin reports one error and writes a trail file that shows, step by step, how the system reached the bad state.&lt;/p&gt;
&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;spin &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; aws-dns-race.pml
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gcc &lt;span class=&quot;nt&quot;&gt;-O2&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; pan pan.c                                                       
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./pan &lt;span class=&quot;nt&quot;&gt;-a&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-N&lt;/span&gt; no_dns_deletion_on_regression  

pan: wrote aws-dns-race.pml.trail

&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;Spin Version 6.5.2 &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; 6 December 2019&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

State-vector 64 byte, depth reached 285, errors: 1
    23201 states, stored
    11239 states, matched
    34440 transitions &lt;span class=&quot;o&quot;&gt;(=&lt;/span&gt; stored+matched&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;truncated &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;brevity....&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The trail file in the repository below shows how the race happens. The trail file shows that two Enactors operate side by side: the faster one applies plan 4 and starts cleaning up. Because cleanup only removes plans much older than the one just applied, it deletes 1 and 2 but skips 3. The slower Enactor then applies plan 3 and makes it active, and when the faster Enactor picks up cleanup again, it deletes 3 and the DNS goes down.&lt;/p&gt;

&lt;p&gt;Here’s an illustration of the interleaving reconstructed from the trail:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-10-30-aws-outage-race-conditions/race-condition-spin-inteleaving.png&quot; alt=&quot;aws dns race condition simulated by spin&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Before publishing, I reread the incident report and noted: “Additionally, because the active plan was deleted, the system was left in an inconsistent state…”. This suggests a direct invariant: the active plan must never be deleted.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;ltl never_delete_active {
    [] ( current_plan &amp;gt; 0 -&amp;gt; !plan_deleted[current_plan] )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Running the model checker with this invariant produces essentially the same counterexample as before: one Enactor advances to newer plans while the other lags and applies an older plan, thereby making it active. When control returns to the faster Enactor, its cleanup deletes that now-active plan, violating the invariant.&lt;/p&gt;

&lt;p&gt;Invariants are invaluable for establishing correctness. If we can show that an invariant holds in the initial state, in every state reachable from it, and in the final state as well, we gain confidence that the system’s logic is sound.&lt;/p&gt;

&lt;p&gt;To fix the code, we execute the problematic statements atomically. You can find both versions of the code, the one with the race and the fixed one, along with the interleaving trail in the accompanying repository [3]. I’ve included detailed comments to make it self-explanatory, as well as instructions on how to run the model and explore the trail.&lt;/p&gt;

&lt;p&gt;Some of the assumptions in this model are necessarily simplified, since I don’t have access to AWS’s internal design details. Without that context, there will naturally be gaps between this abstraction and the real system. This model was created in a short time frame for experimental purposes. With more time and context, one could certainly build a more accurate and refined version.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please keep in mind that I’m only human, and there’s a chance this post contains errors. If you notice anything off, I’d appreciate a correction. Please feel free to &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;send me an email&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;references&quot;&gt;References&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/message/101925/&quot;&gt;AWS Post-Incident Summary — October 2025 Outage&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://wyounas.github.io/concurrency/2024/12/12/how-concurrency-works-a-visual-guide/&quot;&gt;How concurrency works: A visual guide&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/wyounas/aws-dns-outage-oct-2025-modeling/&quot;&gt;Source code repository&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://spinroot.com/&quot;&gt;Spin model checker&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Thu, 30 Oct 2025 00:00:00 -0400</pubDate>
        <link>wyounas.github.io//aws/concurrency/2025/10/30/reproducing-the-aws-outage-race-condition-with-model-checker/</link>
        <guid isPermaLink="true">wyounas.github.io//aws/concurrency/2025/10/30/reproducing-the-aws-outage-race-condition-with-model-checker/</guid>
      </item>
    
      <item>
        <title>When the Simplest Concurrent Program Goes Against All Intuition</title>
        <description>&lt;p&gt;I came across a fascinating and surprising aspect of a seemingly simple concurrent program when run on a model checker. Consider this:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-1-13-concurrency-failing-intuition/one.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If we run P and Q concurrently with ‘n’ initialized to zero, what could be the lowest value of ‘n’ when the two processes finish executing their statements on a model checker? Can a model checker also help us find the extreme interleaving that produces this lowest value of ‘n’?&lt;/p&gt;

&lt;p&gt;I thought the final value of ‘n’ would be between 10 and 20. What do you think? Take a guess.&lt;/p&gt;

&lt;p&gt;I came across this in Ben-Ari’s book on the SPIN model checker [1]. He said he was shocked to discover an extreme interleaving that set the final value of ‘n’ to 2. I was shocked as well—the outcome completely defied my intuition.&lt;/p&gt;

&lt;p&gt;To see this in action, we could write a simple program in SPIN and claim that there is a computation where the value is 2. We can obtain this computation automatically by adding the assertion &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;assertion (n &amp;gt; 2)&lt;/code&gt; at the end of the program and running a verification. SPIN searches the state space, looking for counterexamples.&lt;/p&gt;

&lt;p&gt;Here is the program in PROMELA (a simple language used for writing modes in SPIN) followed by the trail that shows the extreme interleaving (statements in PROMELA are atomic):&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-1-13-concurrency-failing-intuition/two.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When I ran this, the error appeared:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-1-13-concurrency-failing-intuition/three.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can check out the complete interleaving trail in 
&lt;a href=&quot;/files/interleavings.txt&quot; target=&quot;_blank&quot;&gt;this file&lt;/a&gt;. I have never seen an interleaving this extreme.&lt;/p&gt;

&lt;p&gt;I tried to draw the illustration of the trail and here is a rough summary (though you should check out the above trail, it’s not very long):&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2025-1-13-concurrency-failing-intuition/four1.png&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Process 1 sets ‘temp’ to 1, and then Process 2 is scheduled and continues executing until ‘n’ is set to 9. 
At this point, Process 1 takes over and sets ‘n’ to 1. Process 2 is then scheduled again, setting ‘temp’ to 2 
(because Process 1 had just set ‘n’ to 1). Process 1 resumes execution and exhausts the loop. 
Finally, Process 2 executes once more and, using the value it had set for temp (which was 2), sets ‘n’ to 2.&lt;/p&gt;

&lt;p&gt;After seeing this, I wondered: Is it possible to observe a computation in practice where ‘n’ is set to 2? I think it’s highly unlikely to create such a computation in practice. A Go expert shared this following code with me, which limits execution to one thread and explicitly reschedules operations. When you run this Go program, the value of ‘n’ is sometimes 11, sometimes 10, but never less than 10.&lt;/p&gt;

&lt;div class=&quot;language-go highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;runtime&quot;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GOMAXPROCS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

	&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;make&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;chan&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;bool&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
		&lt;span class=&quot;k&quot;&gt;go&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;func&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
			&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;range&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;10&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Gosched&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;:=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;1&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;runtime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Gosched&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
				&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;
			&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
			&lt;span class=&quot;n&quot;&gt;done&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;true&lt;/span&gt;
		&lt;span class=&quot;p&quot;&gt;}()&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;done&lt;/span&gt;
	&lt;span class=&quot;o&quot;&gt;&amp;lt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;done&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;n&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Anyway, I found it not only counterintuitive but fascinating, even though we may not encounter it in practice. So, I thought I should share it.&lt;/p&gt;

&lt;p&gt;I wonder if it’s possible to create this computation in any other way, or a computation with the value of ‘n’ lower than 10?&lt;/p&gt;

&lt;p&gt;This one surprised me. What’s the simplest concurrent program that has surprised you the most?&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References:&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Principles of the Spin Model Checker by Mordechai Ben-Ari.&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

</description>
        <pubDate>Mon, 13 Jan 2025 00:00:00 -0500</pubDate>
        <link>wyounas.github.io//concurrency/2025/01/13/when-a-simple-concurrent-program-goes-against-all-intuition/</link>
        <guid isPermaLink="true">wyounas.github.io//concurrency/2025/01/13/when-a-simple-concurrent-program-goes-against-all-intuition/</guid>
      </item>
    
      <item>
        <title>How concurrency works: A visual guide</title>
        <description>&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/one.png&quot; alt=&quot;concurrency is hard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Concurrent programming is hard.&lt;/p&gt;

&lt;p&gt;Mentally enumerating all the possible states that complex concurrent code might go through is far from easy. Visualizing concurrency can make it easier to understand how these programs operate, especially for those just beginning to learn about concurrency.&lt;/p&gt;

&lt;p&gt;Such visualizations might not always be effective for larger or more complex systems. But even with complex systems, breaking them down into smaller models and visualizing those models can be an excellent way to understand what’s happening behind the scenes.&lt;/p&gt;

&lt;p&gt;Recently, I’ve been exploring model checking and found the mindset it enforces to be not only intriguing but also quite powerful. Leslie Lamport, the renowned researcher in distributed systems and concurrency, has a brilliant quote: &lt;em&gt;If you’re thinking without writing, you only think you’re thinking.&lt;/em&gt; For large and complex distributed or concurrent programs, I believe this principle extends further: &lt;em&gt;If you’re implementing without formally verifying your solution through model checking, you only think you’re implementing it correctly&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Model checking is a powerful tool, and I’ve come across a few resources that can help in understanding concurrency. This inspired me to write about them—not only to deepen my own understanding but also to share what I’ve learned. We’ll begin by exploring how to visualize the execution of a sequential program, then move on to visualizing a concurrent one. Finally, we’ll touch on how to reason about the correctness of concurrent programs.&lt;/p&gt;

&lt;p&gt;Alright, let’s dive in!&lt;/p&gt;

&lt;p&gt;We can visualize how concurrent programs operate by exploring their state space. A few questions arise immediately: What is a state? And what is a state space? Let’s tackle the first question first, then circle back to the second.&lt;/p&gt;

&lt;p&gt;A program’s state is defined by the values of its variables and the location counter (which indicates the next instruction to be executed). Let’s walk through an example using a simple C-like language. We’ll look at the states and demonstrate how states transition during the sequential (non-concurrent) execution of the following program. (We’ll dive into a concurrent program example right after this.)&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/two.png&quot; alt=&quot;concurrency is hard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The program above consists of three instructions, and for clarity, labels are added as location counters on the far left (i.e., &lt;em&gt;1&lt;/em&gt;, &lt;em&gt;2&lt;/em&gt;, &lt;em&gt;3&lt;/em&gt;, and &lt;em&gt;end&lt;/em&gt;). The program includes a single variable, &lt;strong&gt;n&lt;/strong&gt;, which is declared as a byte.&lt;/p&gt;

&lt;p&gt;The state of the program can be represented as a tuple containing the value of the variable &lt;strong&gt;n&lt;/strong&gt; and the location counter: &lt;strong&gt;(n, location counter)&lt;/strong&gt;. For example, when the program starts, its initial state is &lt;strong&gt;(undefined, 1)&lt;/strong&gt;. This is because, at the beginning, none of the program’s statements have been executed, leaving the value of &lt;strong&gt;n&lt;/strong&gt; as “undefined,” while the location counter points to the first instruction, labeled as 1.&lt;/p&gt;

&lt;p&gt;We can visualize this state as follows: the orange arrow represents the location counter, and the current value of &lt;strong&gt;n&lt;/strong&gt; is displayed at the bottom for clarity.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/three.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;To move to the next state, we can advance the location counter. So we advance the location counter to 2 after executing the statement at location counter 1, the next state becomes &lt;strong&gt;(0, 2)&lt;/strong&gt;, indicating that &lt;strong&gt;n&lt;/strong&gt; is now 0 and the location counter points to 2. This state, &lt;strong&gt;(0, 2)&lt;/strong&gt;, reflects that the value of &lt;strong&gt;n&lt;/strong&gt; has been set to 0 as a result of executing the first statement and is visualized as follows:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/four.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The location counter increments again, and the next state becomes &lt;strong&gt;(1, 3)&lt;/strong&gt;. The value of &lt;strong&gt;n&lt;/strong&gt; has changed to 1 because, in the previous state, the location counter was 2, and executing the statement at that location set &lt;strong&gt;n&lt;/strong&gt; to 1. The situation now looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/five.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, the location counter increments one last time to reach the end, marking the completion of execution. At this point, the value of &lt;strong&gt;n&lt;/strong&gt; remains 1.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/six.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A given sequence of states, starting from an initial state and continuing as statements execute, is called a &lt;em&gt;computation&lt;/em&gt;. For example, for the program above, the following is a computation:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/seven.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;As explained earlier, the state of a program is defined by the values of its variables and the location counter (which points at the next instruction to be executed). The state space of a program is simply the set of all possible states that can possibly occur during a computation. Ben-Ari offers a formal definition of state space in [2]: “The state space of a program is a directed graph; each reachable state is a node, and there is an edge from state s1 to state s2 if a transition enabled in s1 moves the computation to s2.”&lt;/p&gt;

&lt;p&gt;Essentially, a state space represents all the states a concurrent program goes through during its execution. By examining the state space, you can understand the full range of program behavior, including unexpected scenarios that might arise from the intricate sequencing of concurrent operations. For this reason, I believe understanding how a state space is generated for a concurrent program can help us grasp how that program works on a more fundamental level.&lt;/p&gt;

&lt;p&gt;In model checking, the state space is also used to reason about the correctness of concurrent programs—another reason to understand how it works. We’ll explore how to reason about correctness at the end.&lt;/p&gt;

&lt;p&gt;We’ll now consider two procedures, running concurrently, to observe how their states transition and how the state space is generated. The program includes a global variable, &lt;strong&gt;n&lt;/strong&gt;, and two procedures, &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt;, each consisting of the following statements:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/eight.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;These are two simple procedures, similar in their operation, each with its own location counter.&lt;/p&gt;

&lt;p&gt;How can we represent a state for the program described above?&lt;/p&gt;

&lt;p&gt;We can represent the state as a triple consisting of the value of the variable &lt;strong&gt;n&lt;/strong&gt; and the location counters of &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt;: &lt;strong&gt;(n, location counter of P, location counter of Q)&lt;/strong&gt;. To make it even clearer, we’ll represent the state as &lt;strong&gt;(n, P: location-counter, Q: location-counter)&lt;/strong&gt;. For example, &lt;strong&gt;(5, P: 1, Q: 2)&lt;/strong&gt; indicates that the value of &lt;strong&gt;n&lt;/strong&gt; is 5, the location counter in &lt;strong&gt;P&lt;/strong&gt; is at 1, and the location counter in &lt;strong&gt;Q&lt;/strong&gt; is at 2.&lt;/p&gt;

&lt;p&gt;For the program above, the initial state could be &lt;strong&gt;(0, P: 1, Q: 2)&lt;/strong&gt;, where the value of &lt;strong&gt;n&lt;/strong&gt; is 0, and the location counters are at the first statements of both &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt;. We can visualize this initiate state with the visualization below, with orange arrows indicating the location counters and the value of &lt;strong&gt;n&lt;/strong&gt; displayed at the bottom:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/nine.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Let’s explore two possible transitions from this initial state.&lt;/p&gt;

&lt;p&gt;We can transition to the next state by incrementing the location counter in &lt;strong&gt;P&lt;/strong&gt;, resulting in &lt;strong&gt;(1, P: end, Q: 2)&lt;/strong&gt;. Here, &lt;strong&gt;n&lt;/strong&gt; becomes 1, reflecting the result of the evaluated expression when the statement at location counter 1 was executed. The state at this point looks like this:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/ten.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The second possible next state from the initial state could be reached by incrementing the location counter in &lt;strong&gt;Q&lt;/strong&gt;. That next state we get by incrementing the location counter in &lt;strong&gt;Q&lt;/strong&gt; is &lt;strong&gt;(2, P: 1, Q: end)&lt;/strong&gt;, and we can visualize it like this:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/eleven.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The location counter of &lt;strong&gt;Q&lt;/strong&gt; reached its end, while &lt;strong&gt;P&lt;/strong&gt; remained at location counter 1, and the value of &lt;strong&gt;n&lt;/strong&gt; is 2.&lt;/p&gt;

&lt;p&gt;So, the transition from the initial state &lt;strong&gt;(0, P: 1, Q: 2)&lt;/strong&gt; to these two states could be represented as state space like this (left arrow points to the next state after an increment in the location counter of &lt;strong&gt;P&lt;/strong&gt; whereas right arrow points to the next state after an increment in the location counter of &lt;strong&gt;Q&lt;/strong&gt;) :&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/twelve.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Next, we’ll write the above program in PROMELA (a C-like language that SPIN [1] uses; SPIN is a software verification tool and a model checker) and then visualize its state space.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/thirteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The code is C-like. The keyword &lt;strong&gt;proctype&lt;/strong&gt; is used to declare a process, while the keyword &lt;strong&gt;active&lt;/strong&gt; activates a process, ensuring that &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt; run concurrently when the program starts.&lt;/p&gt;

&lt;p&gt;As before, we represent the state as a triple: &lt;strong&gt;(n, location counter of P, location counter of Q)&lt;/strong&gt;. In the image above, the line numbers on the far left serve as location counters.&lt;/p&gt;

&lt;p&gt;Below is the state space of the program:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/fourteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The edges in the state space are labeled with orange text, indicating what triggers the transition to the next state. This state space shows that at the end of the program, the value of &lt;strong&gt;n&lt;/strong&gt; could be either 1 or 2. (As an aside: In concurrency, there is also a concept called interleaving, where statements are chosen nondeterministically from processes. For example, a statement from process &lt;strong&gt;P&lt;/strong&gt; could execute first, followed by one from &lt;strong&gt;Q&lt;/strong&gt;, and so on. In the state space above, depending on the interleavings, we could say that at the end, the value of &lt;strong&gt;n&lt;/strong&gt; might be either 1 or 2.)&lt;/p&gt;

&lt;p&gt;Let’s quickly explore how state transitions occur along the right-hand path of the above state space in &lt;em&gt;Figure 1&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Starting with the initial state at the top of the state space, &lt;strong&gt;(0, P: 5, Q: 9)&lt;/strong&gt;,we can represent it as:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/fifteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Starting from the initial state &lt;strong&gt;(0, P: 5, Q: 9)&lt;/strong&gt;, if we increment the location counter in &lt;strong&gt;Q&lt;/strong&gt;, moving it to line 10, the statement at line 9 executes, setting &lt;strong&gt;n&lt;/strong&gt; to 2. This brings us to the next state, &lt;strong&gt;(2, P: 5, Q: 10)&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/sixteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;From this state, we transition to the next state by incrementing the location counter in &lt;strong&gt;P&lt;/strong&gt;, resulting in &lt;strong&gt;(1, P: 6, Q: 10)&lt;/strong&gt;. At this point, the value of &lt;strong&gt;n&lt;/strong&gt; becomes 1 as the statement at location counter 5 executes, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/seventeen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This marks the end of the computation along the right-hand path of the state space shown in &lt;em&gt;Figure 1&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What happens if we extend &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt; by adding more statements? Does the state space expand, or does it shrink? Let’s find out.&lt;/p&gt;

&lt;p&gt;Suppose we add a few more lines to both &lt;strong&gt;P&lt;/strong&gt; and &lt;strong&gt;Q&lt;/strong&gt;—introducing a local variable, assigning it a value, and setting the global variable &lt;strong&gt;total&lt;/strong&gt; to a constant (as shown in &lt;em&gt;Figure 2&lt;/em&gt; below). While these changes might seem minor, they can impact the state space.&lt;/p&gt;

&lt;p&gt;To explore the effect of these changes on the state space, I generated a visualization using &lt;em&gt;jSpin&lt;/em&gt; [3], an excellent educational tool by Ben-Ari built on top of Spin.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/eighteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 2&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The code is displayed on the left side, while the state space is generated as a graph on the right side. In the graph, each box contains three lines: the top line shows the location counter in &lt;strong&gt;P&lt;/strong&gt;, the middle line shows the location counter in &lt;strong&gt;Q&lt;/strong&gt;, and the last line displays the value of the variable &lt;strong&gt;total&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The state space has expanded dramatically compared to the earlier example.&lt;/p&gt;

&lt;p&gt;Now, imagine a concurrent program with dozens of processes, each executing thousands of lines of code with numerous variables. The state space would grow exponentially. Even small programs, if not managed carefully, can generate massive state spaces.&lt;/p&gt;

&lt;p&gt;To further examine the impact of concurrency on state space size, I modified the above program to run five concurrent processes instead of two. As a result, the state space grew so large that, in the final visualization, each state appeared as a tiny dot, rendering the entire image unreadable.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/nineteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This demonstrates just how challenging it can be to debug and test large or complex concurrent programs. You may need to address numerous scenarios, each leading to countless states. While creating visualizations for such large programs might not be practical (at least with jSpin), you can simplify your program into smaller models and generate visualizations from them.&lt;/p&gt;

&lt;p&gt;These simplified visualizations can provide valuable insights into your implementation and serve as an excellent starting point for understanding what’s happening behind the scenes. They’re especially useful as a first step in helping folks grasp the complexities of concurrency.&lt;/p&gt;

&lt;p&gt;State space can help us reason about the correctness of concurrent and distributed systems.&lt;/p&gt;

&lt;p&gt;To increase confidence in a program’s correctness, we can define certain &lt;em&gt;invariants&lt;/em&gt; or &lt;em&gt;correctness properties&lt;/em&gt; that must hold across a program’s state space. Model checkers like SPIN, for one, can verify that these properties are upheld throughout the entire state space. For instance, we might define a property such as &lt;em&gt;“X should always be true,”&lt;/em&gt; meaning that &lt;strong&gt;X&lt;/strong&gt; must not be false in any state.&lt;/p&gt;

&lt;p&gt;When reasoning about large and complex distributed systems, this &lt;em&gt;invariant-based thinking&lt;/em&gt; becomes essential for ensuring correctness. To achieve this, we rely on two key types of properties:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Safety properties&lt;/strong&gt;: Ensuring that nothing bad happens.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Liveness properties&lt;/strong&gt;: Ensuring that something good eventually happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Model checkers like SPIN and TLA+ allow us to verify these properties.&lt;/p&gt;

&lt;p&gt;Let’s try to come up with a safety property for the program shown in &lt;em&gt;Figure 2&lt;/em&gt; above. We want the safety properties to hold for every state in the program’s state space. Since a state is defined by a combination of the location counter and variables, for this example, we’ll focus on defining our property for the variable &lt;strong&gt;total&lt;/strong&gt;. Please note that this is a somewhat contrived example, intended to demonstrate concepts.&lt;/p&gt;

&lt;p&gt;For example purposes, to define a safety property, we can try to come up with an expression for the variable &lt;strong&gt;total&lt;/strong&gt; that holds true in all program states. Consider the following expression as a safety property, where we expect &lt;strong&gt;total&lt;/strong&gt; to be 1 in all states:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;total == 1
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Would this hold true in every state? Let’s revisit the state space, represented as a directed graph on the right side of the code:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-12-10-visualizing-concurrency/eighteen.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Does &lt;strong&gt;total&lt;/strong&gt; equal 1 in all states? No, the above wouldn’t hold in every state because the value of &lt;strong&gt;total&lt;/strong&gt; can be 0, 1, or 2 (as shown at the bottom of each square in the state space). There are states where &lt;strong&gt;total&lt;/strong&gt; is 0 and others where it is 2, so the property &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;total == 1&lt;/code&gt; doesn’t hold universally. Similarly, another property like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;total == 2&lt;/code&gt; wouldn’t hold either, for the same reason.&lt;/p&gt;

&lt;p&gt;How about this instead?&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;total == 1 || total == 2
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Since there are states where the value of &lt;strong&gt;total&lt;/strong&gt; is 0, the previous property wouldn’t hold universally either. However, the following does hold across all states:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;total == 1 || total == 2 || total == 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We almost derived a safety property by exploring the state space—this was just for example purposes. In practice, you may define safety and liveness properties even before writing any models or code.&lt;/p&gt;

&lt;p&gt;As we examine the above expression, a question arises: How can we express it as a safety property and use it to validate correctness in SPIN?&lt;/p&gt;

&lt;p&gt;Expressions like the one above can be expressed as a program’s safety property using &lt;em&gt;Linear Temporal Logic&lt;/em&gt; (LTL). LTL is based on propositional logic and allows formulas to include both logical operators and temporal operators, such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Always ([] in SPIN)&lt;/strong&gt;: Ensures a condition holds in all states.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Eventually (&amp;lt;&amp;gt; in SPIN)&lt;/strong&gt;: Ensures a condition will hold in some future state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As an example, we can express the above expression as a safety property in LTL:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;[] (total == 1 || total == 2 || total == 0)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This reads as: &lt;em&gt;Always, &lt;strong&gt;total&lt;/strong&gt; is either 1, 2, or 0.&lt;/em&gt; We can use this property in SPIN and validate the program’s correctness by ensuring it holds across the state space [4]. Our confidence in concurrent programs increases when we know that a certain property holds across the state space.&lt;/p&gt;

&lt;p&gt;There is much more to LTL, and we’ll dive deeper into it in future articles. Validating safety and liveness properties with LTL is especially useful for large or complex programs.&lt;/p&gt;

&lt;p&gt;In complex concurrent or distributed systems with thousands of possible states, relying on unit tests alone makes it hard to be confident in the solution’s correctness. Most of us would struggle to keep such a vast state space in mind while writing tests. Concurrency bugs are also tough to catch during testing as traditional methods, such as unit or integration tests (though essential), might overlook the issues. The timing and interleaving of events make these bugs hard to find and reproduce. This often leads to dealing with “heisenbugs”—bugs that behave unpredictably and are difficult to track down.&lt;/p&gt;

&lt;p&gt;This all is what makes model checkers excel—they allow you to verify program correctness across a wide range of scenarios. Please stay tuned as I plan to write more about this.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Thanks to Gerard Holzmann, Hillel Wayne, Jack Vanlightly, and Murat Demirbas for reading drafts of this.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;email&lt;/a&gt; with questions, ideas, or corrections.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;More detail and installation instructions are available at https://spinroot.com. And the overview paper The Model Checker Spin,   IEEE Trans. on Software Engineering Vol. 23, No. 5, May 1997, pp. 279-295.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Mathematical Logic for Computer Science, 3rd edition, by Mordechai Ben-Ari.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;For state space visualization, I used https://github.com/motib/jspin.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;You can specify the safety property in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.prp&lt;/code&gt; file and, assuming your PROMELA code is in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;safety.pml&lt;/code&gt;, run the following commands to validate it:&lt;/p&gt;

    &lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;     $ spin -a -F safety.prp  safety.pml
     $ gcc -DSAFETY -o pan pan.c
     $ ./pan
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Top image courtesy of https://geek-and-poke.com/&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</description>
        <pubDate>Thu, 12 Dec 2024 00:00:00 -0500</pubDate>
        <link>wyounas.github.io//concurrency/2024/12/12/how-concurrency-works-a-visual-guide/</link>
        <guid isPermaLink="true">wyounas.github.io//concurrency/2024/12/12/how-concurrency-works-a-visual-guide/</guid>
      </item>
    
      <item>
        <title>How Rendezvous Channels Work in PROMELA (SPIN)</title>
        <description>&lt;p&gt;PROMELA is a language used to write models that can be validated with the SPIN model checker. To facilitate data exchange between processes, PROMELA provides the concept of channels. These channels are essential when creating a model where processes need to communicate and share data.&lt;/p&gt;

&lt;p&gt;Channels allow two processes to exchange information. One process can send data through a channel, and another process can receive it from the same channel. In PROMELA, there are two types of channels:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Rendezvous channels&lt;/em&gt;: These have zero capacity and require both sender and receiver to synchronize.&lt;/li&gt;
  &lt;li&gt;&lt;em&gt;Buffered channels&lt;/em&gt;: These have a capacity greater than zero and can store messages until they are read.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this blog, we’ll focus on rendezvous channels.&lt;/p&gt;

&lt;p&gt;In PROMELA, you can initialize channels using the following syntax:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;chan channelname = [capacity] of {typename}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For example, you can create a rendezvous channel of type &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;byte&lt;/code&gt; like this (meaning the channel can send and receive data of type byte):&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;chan ch = [0] of {byte}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Rendezvous channels have a capacity of zero. I think of a zero-capacity channel as a medium that simply exchanges data without storing it, since it has no capacity to hold messages.&lt;/p&gt;

&lt;p&gt;To send a value on the channel, you use a send statement, which consists of the channel variable followed by an exclamation mark (!), and then the message value. For example, the following statement sends the value 10 on the channel we created earlier:&lt;/p&gt;
&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ch ! 10
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To receive a value, you use a receive statement, which consists of the channel variable followed by a question mark (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;?&lt;/code&gt;), and then the variable where the received value will be stored. For example:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;byte value
ch ? value
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this example, the channel &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ch&lt;/code&gt; facilitates a handshake followed by the transfer of the value. Communication via a rendezvous channel is synchronous, meaning only two processes can engage in the handshake at a time.&lt;/p&gt;

&lt;p&gt;When writing models, one process may send data while another receives it. If a rendezvous channel’s “send” statement is executed but there is no matching “receive” statement, the sending process will block. Similarly, a process with a “receive” statement will block if there is no matching “send” statement.&lt;/p&gt;

&lt;p&gt;Here’s an example where a value is sent but never received:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&quot;language-promela&quot;&gt;chan ch = [0] of {byte}; // Rendezvous channel

active proctype Sender() {
    ch ! 10
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sender&lt;/code&gt; process will block indefinitely because there is no corresponding receive statement. (To run above code, save the code in “file.pml” and then run “spin file.pml” on the command prompt.)&lt;/p&gt;

&lt;p&gt;Now, let’s walk through an example to understand how rendezvous channels work. Imagine we have two processes: a sender and a receiver.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-18-rendezvous-channels/one.png&quot; alt=&quot;rendezvous channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sender&lt;/code&gt; process sends a value on a rendezvous channel named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ch&lt;/code&gt;, while the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Receiver&lt;/code&gt; process receives this value from the channel and stores it in a local variable named &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;value&lt;/code&gt;. The orange arrows represent the location counters, which indicate the next instruction to be executed in each process. The numbers 1, 2, 6, 7, and 8 are the specific locations where the location counter can point. Initially, both counters are assumed to point to the first instructions in their respective processes.&lt;/p&gt;

&lt;p&gt;When the location counter of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sender&lt;/code&gt; process reaches the channel’s send statement, it offers to engage in a rendezvous. If the location counter of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Receiver&lt;/code&gt; process is at the matching channel’s receive statement, the rendezvous is accepted, and the sent value is copied into the local variable. These send and receive statements for rendezvous channels are executed atomically. Once the rendezvous completes, the location counters in both processes advance to their next instructions.&lt;/p&gt;

&lt;p&gt;I know this might feel like a lot of information, don’t worry—we’ll walk through it step by step below.&lt;/p&gt;

&lt;p&gt;Let’s start again. As indicated earlier, this is our code at the start:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-18-rendezvous-channels/one.png&quot; alt=&quot;rendezvous channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Initially, both location counters point to the first instruction. Suppose the location counter in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sender&lt;/code&gt; process reaches the send statement and offers to engage in a rendezvous. At this point, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Sender&lt;/code&gt; process becomes blocked because there is no matching receive statement yet.&lt;/p&gt;

&lt;p&gt;Now, the location counter in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Receiver&lt;/code&gt; process advances to 7:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-18-rendezvous-channels/two.png&quot; alt=&quot;rendezvous channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;At this point, the rendezvous is accepted, and both statements execute atomically. The value 10 is sent on the channel, received by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Receiver&lt;/code&gt; process, and copied into its local variable. Once the statements are executed, the location counters in both processes advance to their next instructions.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-18-rendezvous-channels/three.png&quot; alt=&quot;rendezvous channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We can also send a channel through another channel. This allows one process to pass a channel to another process. Let’s take a look at the following code:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;

chan ch = [0] of {chan}

active proctype P(){ 

    chan localch = [0] of {byte}

    // sends a channel
    ch ! localch 

    byte data

    // waiting to receive data
    localch ? data

    // ensure data is 100 
    assert  data == 100
    printf(&quot;Data = %d \n&quot;, data)
}

active proctype Q(){ 

    chan localch 

    // receives a channel in localch 
    ch ? localch 

    // sends 100 on localch
    localch ! 100

}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We declare a global rendezvous channel, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ch&lt;/code&gt;, which is used to exchange other channels. In process P, we send a channel of type byte on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ch&lt;/code&gt;. After that, P waits for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localch&lt;/code&gt; to receive data into the variable data (as seen at line 15).&lt;/p&gt;

&lt;p&gt;The following visualization helps illustrate what’s happening here:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-18-rendezvous-channels/five.png&quot; alt=&quot;rendezvous channels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Consider the lines with arrows as channels. Process P sends &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localch&lt;/code&gt; on the channel &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ch&lt;/code&gt;, and process Q receives it. Then, Q sends the value 100 on the channel &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localch&lt;/code&gt;, and P receives it.&lt;/p&gt;

&lt;p&gt;One important note: in such scenarios, if the process that instantiated the channel terminates, the corresponding channel also disappears. Any attempt to access it from another process after this will fail and result in an error.&lt;/p&gt;

&lt;p&gt;Hopefully, this provides a general understanding of how rendezvous channels work in PROMELA. We’ll build on these concepts in future articles.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;email&lt;/a&gt; or &lt;a href=&quot;https://x.com/wyounas&quot;&gt;tweet&lt;/a&gt; with questions, ideas, or corrections.&lt;/em&gt;&lt;/p&gt;
</description>
        <pubDate>Mon, 18 Nov 2024 00:00:00 -0500</pubDate>
        <link>wyounas.github.io//spin,/model-checking,/formal-methods/2024/11/18/how-rendezvous-channels-work-in-promela-spin/</link>
        <guid isPermaLink="true">wyounas.github.io//spin,/model-checking,/formal-methods/2024/11/18/how-rendezvous-channels-work-in-promela-spin/</guid>
      </item>
    
      <item>
        <title>How Control Structures Work in PROMELA (SPIN)</title>
        <description>&lt;p&gt;The PROMELA language is used to write models in the SPIN model checker. PROMELA’s syntax is similar to C, but its control statements are inspired by a formalism called “guarded commands,” invented by E.W. Dijkstra, which is particularly well-suited for handling nondeterminism.&lt;/p&gt;

&lt;p&gt;Let’s start by examining the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;if&lt;/code&gt; statement in PROMELA. It begins with the reserved word “if” and ends with “fi.” Within it, there are one or more execution sequences, each starting with a double colon (::), followed by a guard and an arrow (-&amp;gt;). Consider this example:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/one.png&quot; alt=&quot;control structure&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In the program above, we have an “if” statement with three execution sequences (at lines 6, 8, and 10). Each statement after the double colon and before the arrow is a guard; for example, at line 6 the guard is “n &amp;gt; 0,” at line 8 it is “n &amp;lt; 0,” and at line 10 the guard is “n == 0.” An execution sequence runs when its guard evaluates to true. Here, the guard at line 6 evaluates to true, so its execution sequence executes.&lt;/p&gt;

&lt;p&gt;If more than one guard evaluates to true, one of the sequences is selected nondeterministically. If no guard evaluates to true, the process remains blocked until at least one guard becomes selectable.&lt;/p&gt;

&lt;p&gt;Now, let’s first explore how nondeterminism works. Consider:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/two.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In this case, when two or more guards evaluate to true, the statements associated with either guard could be executed. Running the program multiple times will sometimes print “Greater than two” and other times “Greater than three.” To try this out, save the code in a file named file.pml and run it with the command “spin file.pml”. The choice is made nondeterministically.&lt;/p&gt;

&lt;p&gt;Interestingly, this nondeterminism can be used to generate random numbers in PROMELA. In the program below, the value of “n” is determined randomly. Running the program multiple times will show that a different value is chosen for “n” each time.&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/three.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now let’s see what happens when all guards evaluate to false. When all guards evaluate to false, the process will block. This means it cannot proceed until at least one of the guards becomes true. Consider:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/four.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The program above prints “Begin” and then blocks. In a more complex setup with multiple concurrent processes modifying the value of a global variable, a blocked process with an if-statement like the one above may eventually become unblocked if one of the guards evaluates to true.&lt;/p&gt;

&lt;p&gt;Now, let’s take a look at looping structures in PROMELA. One of these is the do-statement, which functions similarly to an if-statement. Consider this program:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/five.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The loop continues to run until the value of ‘n’ reaches 10, at which point it breaks out. The guard selection rules work the same way as they do in an if-statement. If multiple guards in a loop evaluate to true, SPIN will select one nondeterministically. If all guards evaluate to false, the execution process will block.&lt;/p&gt;

&lt;p&gt;The keyword “else” can be used as a guard in a selection or repetition structure, and it defines a condition that is true only if all other guards evaluate to false within the same structure. Let’s look at how it can be used:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/else.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;A for-statement was also introduced in version 6 of SPIN, allowing us to write a similar program using a for-statement as follows:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/2024-11-10-control-structures-promela/six.png&quot; alt=&quot;control structure&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;email&lt;/a&gt; or &lt;a href=&quot;https://x.com/wyounas&quot;&gt;tweet&lt;/a&gt; with questions, ideas, or corrections.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;note&quot;&gt;Note:&lt;/h3&gt;
&lt;p&gt;To run a PROMELA program, save the code in a file with a .pml extension and run the program from the command line using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;spin file.pml&lt;/code&gt;.&lt;/p&gt;
</description>
        <pubDate>Sun, 10 Nov 2024 00:00:00 -0500</pubDate>
        <link>wyounas.github.io//spin,/model-checking,/formal-methods/2024/11/10/how-control-structures-work-in-promela/</link>
        <guid isPermaLink="true">wyounas.github.io//spin,/model-checking,/formal-methods/2024/11/10/how-control-structures-work-in-promela/</guid>
      </item>
    
      <item>
        <title>The Beautiful Simplicity of the Gentzen System</title>
        <description>&lt;p&gt;Gentzen system, created by German mathematician Gerhard Gentzen, is a deductive system which
can be used to prove propositional formulas. I recently learned about it while I was reading Ben-Ari’s fantastic book on mathematical logic [1] and I like its simplicity.&lt;/p&gt;

&lt;p&gt;Should we care about the Gentzen system? Let’s say you’re a programmer, why should you care about logic or mathematical reasoning?&lt;/p&gt;

&lt;p&gt;I recently started learning more about mathematical logic, and I’ve realized that, just as writing can clarify your thoughts, formal mathematical reasoning can bring coherence to your thinking. If parts of your reasoning lack logical soundness, you won’t be able to construct a coherent argument as a whole—mathematical reasoning helps prevent this.&lt;/p&gt;

&lt;p&gt;I’ve been programming for a while now, mostly self-taught, and I’ve observed that while learning mathematical formalism isn’t necessary 
for most programming jobs, it definitely helps you think more carefully about the correctness 
of your code. It helps you reason through problems with greater precision.&lt;/p&gt;

&lt;p&gt;Now let’s dive into the Gentzen system, starting with a scenario.&lt;/p&gt;

&lt;p&gt;Imagine you’re a detective trying to solve a robbery. To prove that a certain person committed the crime, you 
gather and connect key pieces of evidence. You see this person entering the house on a video camera on the day of 
the robbery, and you later find a receipt showing them selling items belonging to the homeowner. While real 
detective work is rarely this clear-cut, this simplified example highlights the deductive process of assembling truths 
and making logical inferences to build a case. In a similar way, a deductive system like the Gentzen system 
starts from basic truths and uses logical rules to reach sound conclusions.&lt;/p&gt;

&lt;p&gt;In a deductive system, you start with basic statements assumed to be true, known as axioms, and you also apply inference rules 
to build a logical case. The Gentzen system stands out for its simplicity: it has just one axiom and a 
few inference rules, yet it can prove complex formulas. There’s a certain beauty in seeing how such a simple 
setup—just one axiom and a few rules—can tackle complex problems with logical precision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gentzen’s Genius: A Single Axiom and a Few Inference Rules&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For me, what makes the Gentzen System elegant is its minimalism. Gentzen system’s approach is surprisingly simple: one axiom and just
 a handful of inference rules. And then you can use it to prove complex propositional formulas.&lt;/p&gt;

&lt;p&gt;Let’s see how Gentzen system can be used to prove a propositional formula 
(Please note that I am teaching myself these concepts by learning from this wonderful book [1]. 
Any errors are my own, and I’m happy to correct anything you find incorrect. I’m writing this to reinforce my own understanding, 
following Confucius’ advice: “I hear and I forget. I see and I remember. I do and I understand.”).&lt;/p&gt;

&lt;p&gt;A little context about propositional logic and formulas: A propositional formula, like $ p \lor q $ (which is 
basically ‘p &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;or&lt;/code&gt; q’, a &lt;a href=&quot;https://en.wikipedia.org/wiki/Logical_disjunction&quot;&gt;logical disjunction&lt;/a&gt;), uses atomic propositions. 
The atomic propositions in $ p \lor q $ are $ p $ and $ q $. The atomic propositions can be assigned values $ \text{true} $ or $ \text{false} $. 
If $ p $ is $ \text{true} $, then $ \neg p $ (not $ p $) is $ \text{false} $. A complementary pair is a pair containing both $ p $ and $ \neg p $.&lt;/p&gt;

&lt;p&gt;And now the axiom and a few words about inference rules.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Axiom&lt;/strong&gt;: Gentzen system starts with only one axiom: a complementary pair, e.g., $ p $ and $ \neg p $. That’s the only axiom in the Gentzen system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Inference Rules&lt;/strong&gt;: The system also provides inference rules. These rules, alongside the one axiom, allow us to prove new things. 
There are two types of inference rules: $ \alpha $ (alpha) and $ \beta $ (beta). Here are some details about each of them:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;$ \alpha $ inference rules: Think of these rules as ways to shape two logical formulas into a propositional formula. 
Let’s say you have two propositional formulas, $ A1 $ and $ A2 $, then you can combine them into a single $ \alpha $ formula. 
There are a few $ \alpha $ rules, but for simplicity, I’m not including them all here—they are all in Ben-Ari’s book. 
Let’s look at two $ \alpha $ inference rules as we will use these both in a proof below.&lt;/p&gt;

    &lt;ul&gt;
      &lt;li&gt;If you have two formulas, $ A1 $ and $ A2 $, then you can write them as $ A1 \lor A2 $.&lt;/li&gt;
      &lt;li&gt;If you have $ \neg A1 $ and $ A2 $, then you can write them as $ A1 \rightarrow A2 $ (A1 implies A2).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;$ \beta $ inference rules: There are also a few $ \beta $ rules. Like $ \alpha $ rules, you can use them to simplify 
two $ \beta $ formulas. Here is one inference rule we’ll use in a proof below.&lt;/p&gt;
    &lt;ul&gt;
      &lt;li&gt;If you have $ \neg B1 $ and $ \neg B2 $, then you can write them as $ \neg (B1 \lor B2) $.&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s try to construct a proof using the above axiom and inference rules.&lt;/p&gt;

&lt;p&gt;We want to prove $ (p \lor q) \rightarrow (q \lor p) $ using the Gentzen system.&lt;/p&gt;

&lt;p&gt;Basically, in such proofs, you proceed step by step, using the outcome of each step to derive the next one, eventually leading to the proof.&lt;/p&gt;

&lt;p&gt;Prove: $ (p \lor q) \rightarrow (q \lor p) $&lt;/p&gt;

&lt;p&gt;Here is the proof (sorry for the extra text due to the added explanation):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;We start by examining the propositional formula we want to prove. Which atomic propositions does it invovle? Well, $ p $ and $ q $. 
For p, we apply the axiom, giving us both $ p $ and $ \neg p $. We then combine this with $ q $, so the outcome of the first step is: $ p, \neg p, q $.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Next, we apply the axiom again, this time for $ q $. This gives us both $ q $ and $ \neg q $. So, after the second step, we have: $ \neg q, q, p $.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now, we can apply a $ \beta $ inference rule to the results of the first two steps. We have $ \neg p $ from the first step and $ \neg q $ 
from the second step. According to the $ \beta $ inference rule, if we have $ \neg B1 $ and $ \neg B2 $, we can combine them into $ \neg (B1 \lor B2) $. 
So, we treat $ \neg p $ as B1 and $ \neg q $ as B2. Applying the rule gives us $ \neg (p \lor q) $. The result of this third step is $ \neg (p \lor q) $, with p and q carrying over from the first two steps. Now, to explain the carryover: when we use $ \neg p $ and $ \neg q $ to 
form $ \neg (p \lor q) $, the remaining elements, $ p $ and $ q $, from both steps are combined with $ \neg (p \lor q) $. Since we are working with sets, 
we only keep unique elements, so the outcome of previous two steps is $ \neg (p \lor q), p, q $.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;In this step, we apply an $ \alpha $ rule. According to this rule, if we have A1 and A2, we can combine them into $ A1 \lor A2 $. 
From step 3, we have $ q $ and $ p $. Applying the alpha rule to these two propositions gives us $ q \lor p $. Importantly,&lt;br /&gt;
 $ \neg (p \lor q) $ is unchanged, so it carries over. The result of this step is: $ \neg (p \lor q), (q \lor p) $.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Now, let’s look at what the result of step 4. Can we apply a rule to get the formula we set out to prove? Yes! We can use another $ \alpha $ rule, 
which says if we have $ \neg A1 $ and $ A2 $, we can form $ A1 \rightarrow A2 $ (A1 implies A2). In step 4, we have $ \neg (p \lor q) $ 
as $ \neg A1 $ and $ (q \lor p) $ as $ A2 $. Using this rule, we arrive at $ (p \lor q) \rightarrow (q \lor p) $, which is exactly what we set out to prove. Voila!&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is a brief summary of the above steps. In the first two steps of the proof below, we start with axioms. Then, in the third step, we apply the $ \beta $ inference rule on the the outcome of the first and second step. Then, on the result of the third step, we apply an $ \alpha $ inference rule. Finally, on the outcome of the fourth step, we apply another $ \alpha $ rule, and we are be able to prove what we set out to prove.&lt;/p&gt;

&lt;p&gt;At first, I didn’t understand this by just reading it—it seemed too clever (as the author also hinted that it seemed clever). I had to solve it a couple of times using pen and paper.&lt;/p&gt;

&lt;p&gt;This can also be presented in the tree form. In the screenshot below, the top nodes are axioms, the internal nodes are inference rules, and the node at the bottom is the formula that needed to be proved:&lt;/p&gt;

&lt;p&gt;&lt;img loading=&quot;lazy&quot; src=&quot;/images/gentzen_tree.png&quot; alt=&quot;gentzen tree&quot; width=&quot;500&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There is an inexplicable beauty in the idea that you start with only one established truth and a few inference rules, and using them, you can prove complex propositional formulas.&lt;/p&gt;

&lt;p&gt;Creating a system with just one axiom and a few inference rules that can prove complex propositional formulas is a sign of elegance. 
The idea that we can take basic truths and use them to prove more complex things has a certain elegance. The idea that we 
can discipline our thinking in steps, with each rule application or inference bringing us closer to our goal while 
maintaining a coherent structure, also embodies elegance. This kind of elegance can inspire us to learn and master complexity in other fields.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Please &lt;a href=&quot;mailto:waqas.younas@gmail.com&quot;&gt;email&lt;/a&gt; or &lt;a href=&quot;https://x.com/wyounas&quot;&gt;tweet&lt;/a&gt; with questions, ideas, or corrections.&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;references&quot;&gt;References:&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Mathematical Logic for Computer Science by Mordechai Ben-Ari, 3rd edition.&lt;/li&gt;
&lt;/ol&gt;
</description>
        <pubDate>Tue, 29 Oct 2024 00:00:00 -0400</pubDate>
        <link>wyounas.github.io//cs/2024/10/29/gentzen-system/</link>
        <guid isPermaLink="true">wyounas.github.io//cs/2024/10/29/gentzen-system/</guid>
      </item>
    
  </channel>
</rss>
