Post

Solving the MIU puzzle with programming

A solution to the MIU puzzle by Douglas Hofstadter using Prolog and Python.

The MIU puzzle, introduced by mathematician and logician Douglas Hofstadter in his book “Gödel, Escher, Bach: An Eternal Golden Braid”, is a classic example of a formal system and has been a topic of interest for logicians, mathematicians, and computer scientists for decades. The game involves starting with the string MI and applying a set of four rules to create new strings of symbols:

RuleFormal ruleInformal explanation
1xIxIUAdd a U to the end of any string ending in I
2MxMxxDouble the string after the M
3xIIIyxUyReplace any III with a U
4xUUyxyRemove any UU

The challenge is to transform the starting string MI into a target string such as MU using only these four rules.

In this blog post, we will explore the MIU puzzle and discuss its implementation in programming.

A decidable criterion for derivability

An arbitrarily given string $x$ can be derived from MI by the above four rules if, and only if, $x$ respects the all three of the following properties:

  1. $x$ is only composed with one M and any number of I and U
  2. $x$ begins with M
  3. the number of I in $x$ is not divisible by 3

(Wikipedia contributors, 2023)

For example, the string MIIIIU is derivable (apply Rule 2 twice and Rule 1 once) but MIIIIII and MU are not.

Implementation of a theorem checker in Prolog

A theorem in a formal system is a valid string that can be formed from the axioms and inference rules. For example MIIII is a theorem in the MIU system but MIT is not.

Based on the criterion for derivability, we can implement a Prolog program to check the derivability of a string.

To be able to do this, we first need to write a few facts/rules:

  1. To count the number of occurrences of an element in a list:
    1
    2
    3
    
    count(_, [], 0).
    count(M, [M |TAIL], N) :- count(M, TAIL, N1), N is N1 + 1.
    count(M, [HEAD|TAIL], N) :- HEAD\= M, count(M, TAIL, N).
    

    For example to count the number of occurrences of i in $[m,i,u,i]$, use the following query:

    1
    2
    
      ?- count(i, [m,i,u,i], Count).
      % Count = 2
    
  2. To check if the first element of the list is m:
    1
    
    starts_with_m([m|_]).
    
  3. To check if all elements of a list belong to the set $\{m, i, u\}$:
    1
    2
    3
    4
    5
    6
    
    valid_alphabet([m]).
    valid_alphabet([i]).
    valid_alphabet([u]).
    valid_alphabet([H|T]):-
    (H == m; H == i; H == u),
    valid_alphabet(T).
    

With the help of the above snippets, we end up with:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
% count number of occurrences of an element in a list
count(_, [], 0).
count(M, [M |TAIL], N) :- count(M, TAIL, N1), N is N1 + 1.
count(M, [HEAD|TAIL], N) :- HEAD\= M, count(M, TAIL, N).

% check if list starts with m
starts_with_m([m|_]).

% check if all elements of a list belong to the set {m, i, u}
valid_alphabet([m]).
valid_alphabet([i]).
valid_alphabet([u]).
valid_alphabet([H|T]):-
    (H == m; H == i; H == u),
    valid_alphabet(T).

% check if a list is a theorem in MIU-system 
theorem([], false).
theorem(List):- 
    valid_alphabet(List),
    count(m, List, Mcount), 
    count(i, List, Icount),
    starts_with_m(List),
    Mcount == 1,
    (Icount mod 3) =\= 0.

/** <examples>
?- theorem([m,u,i,i,u]). -> true
?- theorem([m,i,i,i,i,i,i,i,i,i]). -> false
*/

You can run the Prolog code on an online compiler like SWISH Prolog.

Algorithm to form any given theorem

Now suppose that we are given a theorem $x$ to derive. Since we know that the criteria for derivability are satisfied, we can deduce an algorithm to form the theorem:

Let $N_i$ be the number of $I$s in $x$.

Let $N_u$ be the number of $U$s in $x$.

Assuming $N_i$ is not divisible by 3 (third criterion for derivability is satisfied),

  1. Let $N=N_i+3N_u$.
  2. Find the minimum value of $n$, where $n\in\mathbb Z^+$, such that $2^n\ge N$ and $2^n\mod 3 \equiv N \mod 3$.
  3. Starting from MI, apply Rule 2 $n$ times to form a string with $2^n$ I.

The first $N$ I are used to create our final string and the remaining $2^n-N$ I (highlighted in red) must be eliminated. To achieve this, we need to convert all I in the red region into an even number of U which can then be eliminated with Rule 4.

Image showing what strings looks like after the first 3 steps

  1. If $\lfloor\cfrac{2^n-N}{3}\rfloor$ is odd, apply Rule 1 once.
  2. Using Rule 3 as many times as needed, convert all triples of I in the red region to U .
  3. Eliminate all U in the red region by applying Rule 4 as many times as needed.
  4. Use Rule 3 where needed on the green region to form the desired string.

The logic behind step 1 is the fact that each U in a given theorem is worth 3 I, based on Rule 3. The value of $N$ represents the minimum number of I required to form the theorem.

Step 4 ensures that one of the following cases is satisfied for the red region:

  • There is an even number of triples of I
  • There is an odd number of triples of I and a U.

In both of these cases, all letters in the red region can thus be eliminated.

Here’s a diagrammatic way of representing all the steps required to form MIIUII starting from MI:

$N=7$ and $n=4$. Rule 2 is applied 4 times.

Implementation of a theorem solver in Python

A crude implementation of the above algorithm in Python is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
def print_steps(theorem):
    """Prints the steps required to form a given theorem starting from MI.

    Args:
        theorem (string): String must contain only valid characters and 
        must satisfy the derivability criterion.
    """
        
    theorem.replace(" ", "") .lower()
    print("mi")

    iCount = theorem.count('i')
    uCount = theorem.count('u')
    totalI = iCount + 3*uCount
    
    n = 0
    while((2**n< totalI) or (2**n % 3 != totalI%3)):
        n+=1
        
    print(f'Apply Rule 2 [{n} times]')
    x = "m" +  ("i" * 2**n)
    print(x)
    if(x == theorem):
        return
    
    badU = (2**n - totalI)//3
    if(badU % 2 == 1):
        print('Apply Rule 1')
        x += "u"
        badU+=1
        print(x)
    
    ## get rid of Is in useless region if it exists
    if(2**n-totalI > 0):
        print('Make groups of 3 Is for all Is after the first', totalI, "Is")
        y = 0
        f = ""
        for i in range(totalI+1, len(x)):
            if(y%3==0):
                f+= " "
            f+=(x[i])
            y+=1
        x = "m" + ("i" * totalI) + f
        print(x)
        print("Replace grouped Is with U")
        
        x = "m" + ("i" * totalI)
        for i in range(0, badU):
            x += " u"
        print(x)
        
        print(f"Apply Rule 4 [{badU//2} times]")
        x = "m" + ("i" * totalI)
        print(x)
    
    print("Group Is")
    f = ''
    y = 0
    for i in range (0, len(theorem)):
        if(theorem[i]=='u'):
            f+= " [iii] "
            y+=1
        else:
            f+= theorem[i]
    print(f)
    
    print(f"Apply  Rule 1 [{y} times]")
    print(theorem)

## print_steps('miiuuuii')
## print_steps('miiii')
## print_steps('miiii')
## print_steps('muiiu')

Output for miiuuuii:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
mi
Apply Rule 2 [4 times]
miiiiiiiiiiiiiiii
Apply Rule 1
miiiiiiiiiiiiiiiiu
Make groups of 3 Is for all Is after the first 13 Is
miiiiiiiiiiiii iii u
Replace grouped Is with U
miiiiiiiiiiiii u u
Apply Rule 4 [1 times]
miiiiiiiiiiiii
Group Is
mii [iii]  [iii]  [iii] ii
Apply  Rule 1 [3 times]
miiuuuii

Output for muiiu:

1
2
3
4
5
6
7
mi
Apply Rule 2 [3 times]
miiiiiiii
Group Is
m [iii] ii [iii] 
Apply  Rule 1 [2 times]
muiiu

My program does not check if the theorem passed as parameter is a true theorem. Invalid inputs like mu could cause infinite loops.

References

  1. Wikipedia contributors, 2023. MU puzzle. In Wikipedia, The Free Encyclopedia. Available at: https://en.wikipedia.org/w/index.php?title=MU_puzzle&oldid=1185545164 [Accessed on June 30, 2024].
This post is licensed under CC BY 4.0 by the author.