Ising model simulation in APL

The APL family of languages

Why APL?

I recently got interested in APL, an array-based programming language. In APL (and derivatives), we try to reason about programs as series of transformations of multi-dimensional arrays. This is exactly the kind of style I like in Haskell and other functional languages, where I also try to use higher-order functions (map, fold, etc) on lists or arrays. A developer only needs to understand these abstractions once, instead of deconstructing each loop or each recursive function encountered in a program.

APL also tries to be a really simple and terse language. This combined with strange Unicode characters for primitive functions and operators, gives it a reputation of unreadability. However, there is only a small number of functions to learn, and you get used really quickly to read them and understand what they do. Some combinations also occur so frequently that you can recognize them instantly (APL programmers call them idioms).

Implementations

APL is actually a family of languages. The classic APL, as created by Ken Iverson, with strange symbols, has many implementations. I initially tried GNU APL, but due to the lack of documentation and proper tooling, I went to Dyalog APL (which is proprietary, but free for personal use). There are also APL derivatives, that often use ASCII symbols: J (free) and Q/kdb+ (proprietary, but free for personal use).

The advantage of Dyalog is that it comes with good tooling (which is necessary for inserting all the symbols!), a large ecosystem, and pretty good documentation. If you want to start, look at Mastering Dyalog APL by Bernard Legrand, freely available online.

The Ising model in APL

I needed a small project to try APL while I was learning. Something array-based, obviously. Since I already implemented a Metropolis-Hastings simulation of the Ising model, which is based on a regular lattice, I decided to reimplement it in Dyalog APL.

It is only a few lines long, but I will try to explain what it does step by step.

The first function simply generates a random lattice filled by elements of \(\{-1,+1\}\).

L←{(2×?⍵ ⍵⍴2)-3}

Let’s deconstruct what is done here:

Sample output:

      ising.L 5
 1 ¯1  1 ¯1  1
 1  1  1 ¯1 ¯1
 1 ¯1 ¯1 ¯1 ¯1
 1  1  1 ¯1 ¯1
¯1 ¯1  1  1  1

Next, we compute the energy variation (for details on the Ising model, see my previous post).

∆E←{
    ⎕IO←0
    (x y)←⍺
    N←⊃⍴⍵
    xn←N|((x-1)y)((x+1)y)
    yn←N|(x(y-1))(x(y+1))
    ⍵[x;y]×+/⍵[xn,yn]
}

Sample output, for site \((3, 3)\) in a random \(5\times 5\) lattice:

      3 3ising.∆E ising.L 5
¯4

Then comes the actual Metropolis-Hastings part:

U←{
    ⎕IO←0
    N←⊃⍴⍵
    (x y)←?N N
    new←⍵
    new[x;y]×←(2×(?0)>*-⍺×x y ∆E ⍵)-1
    new
}

We can now bring everything together for display:

Ising←{' ⌹'[1+1=({10 U ⍵}⍣⍵)L ⍺]}

Final output, with a \(80\times 80\) random lattice, after 50000 update steps:

      80ising.Ising 50000
   ⌹⌹⌹⌹ ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹      ⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹          
   ⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹ ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹           
⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹
⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹             ⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹     ⌹⌹⌹⌹⌹⌹             ⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹ ⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹       ⌹⌹⌹⌹⌹      ⌹       
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹        ⌹⌹⌹⌹      ⌹⌹⌹      
 ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹       ⌹⌹⌹      
 ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹    ⌹⌹⌹⌹⌹⌹      
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹          ⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹
⌹⌹⌹⌹ ⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹           ⌹⌹⌹⌹                ⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹
  ⌹ ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹ ⌹⌹⌹⌹           ⌹⌹⌹⌹                ⌹⌹⌹      ⌹⌹⌹⌹⌹         
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹                ⌹⌹        ⌹           
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹                                     
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹                                    
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹           ⌹                         
 ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹          ⌹                          
⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹          ⌹⌹⌹⌹⌹⌹                          ⌹⌹     ⌹⌹⌹
⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹                  ⌹⌹⌹⌹⌹⌹         ⌹               ⌹⌹⌹     ⌹⌹⌹
⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹                   ⌹⌹⌹⌹⌹⌹                     ⌹⌹⌹⌹⌹⌹     ⌹⌹⌹
⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹             ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹                ⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹                 ⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹ ⌹⌹⌹⌹⌹⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹                            ⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹                               ⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹                                ⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹                   ⌹⌹⌹⌹             ⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹                   ⌹⌹⌹⌹                           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹                  ⌹⌹⌹⌹⌹⌹⌹                ⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹ 
  ⌹⌹⌹⌹                  ⌹⌹⌹⌹⌹⌹⌹              ⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
  ⌹⌹⌹⌹                 ⌹⌹⌹⌹⌹⌹⌹⌹⌹             ⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
  ⌹⌹⌹⌹   ⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
 ⌹⌹⌹⌹⌹   ⌹ ⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
 ⌹⌹⌹⌹⌹   ⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
 ⌹⌹⌹⌹⌹             ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  
⌹⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹
⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹
⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹ ⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹             ⌹          ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹                       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹            ⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹              ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
                    ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹
                       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹⌹⌹⌹  
            ⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹  
           ⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹⌹  
   ⌹⌹⌹ ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹          ⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹           ⌹⌹⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹             ⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹  ⌹⌹⌹⌹   ⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹        ⌹⌹⌹                       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹         ⌹⌹⌹                          ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       ⌹⌹⌹         ⌹⌹⌹    ⌹⌹                      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹     ⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹⌹                      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹          ⌹⌹⌹⌹⌹⌹⌹⌹                      ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹       
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹                         ⌹⌹⌹⌹⌹⌹⌹⌹       
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹⌹⌹⌹                          ⌹⌹⌹⌹⌹⌹⌹       
  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹                          ⌹⌹⌹          
 ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹                        ⌹⌹⌹          
 ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹                        ⌹⌹⌹          
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹                      ⌹⌹⌹⌹          
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹            ⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹       ⌹             ⌹⌹⌹⌹⌹       ⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹ ⌹             ⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹            ⌹⌹⌹⌹⌹⌹     ⌹⌹⌹⌹⌹
⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹             ⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹           ⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹
   ⌹⌹⌹⌹⌹⌹⌹⌹             ⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹ 
   ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         ⌹⌹⌹⌹⌹⌹⌹⌹           
   ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹   ⌹⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         
   ⌹⌹⌹  ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹      ⌹⌹⌹⌹⌹        ⌹⌹⌹⌹⌹⌹⌹    ⌹⌹⌹⌹       ⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹⌹         

Complete code, with the namespace:

:Namespace ising

        L←{(2×?⍵ ⍵⍴2)-3}

        ∆E←{
                ⎕IO←0
                (x y)←⍺
                N←⊃⍴⍵
                xn←N|((x-1)y)((x+1)y)
                yn←N|(x(y-1))(x(y+1))
                ⍵[x;y]×+/⍵[xn,yn]
        }

        U←{
                ⎕IO←0
                N←⊃⍴⍵
                (x y)←?N N
                new←⍵
                new[x;y]×←(2×(?0)>*-⍺×x y ∆E ⍵)-1
                new
        }

        Ising←{' ⌹'[1+1=({10 U ⍵}⍣⍵)L ⍺]}

:EndNamespace

Conclusion

The algorithm is very fast (I think it can be optimized by the interpreter because there is no branching), and is easy to reason about. The whole program fits in a few lines, and you clearly see what each function and each line does. It could probably be optimized further (I don’t know every APL function yet…), and also could probably be golfed to a few lines (at the cost of readability?).

It took me some time to write this, but Dyalog’s tools make it really easy to insert symbols and to look up what they do. Next time, I will look into some ASCII-based APL descendants. J seems to have a good documentation and a tradition of tacit definitions, similar to the point-free style in Haskell. Overall, J seems well-suited to modern functional programming, while APL is still under the influence of its early days when it was more procedural. Another interesting area is K, Q, and their database engine kdb+, which seems to be extremely performant and actually used in production.

Still, Unicode symbols make the code much more readable, mainly because there is a one-to-one link between symbols and functions, which cannot be maintained with only a few ASCII characters.