John Kerl
kerl at math dot arizona dot edu
2008-07-28

Subroutine index:
* MAIN LOOP
* METROPOLIS SWEEP
* COMPUTE DELTA_N2
* UNIFORM RANDOM PERMUTATION
* COMPUTE RHO_L
* GET CYCLE COUNTS
* COMPUTE RHO_INFTY


================================================================
MAIN LOOP:

Input: Given T, alpha, L, d, N_acc.

Output:  rho_infty(T) which is a real number.

Remark:  During a full simulation suite, this program must be called many
times, with differing values of T, alpha, and L.  This program only computes
one data point.

----------------------------------------------------------------
Allocate an L x L x L matrix of points.
Each point contains the following information:

* Its own i/j/k coordinates on the lattice, each an integer between 0 and L-1

* A forward pointer and a backward pointer.  (A pointer is a RAM address of a
  data structure.  Pointers are an even more efficient implementation of your
  "short vector" concept.  Pointers exist in the C language, but not in
  Fortran.  If they existed in Fortran, presumably your Marseilles
  collaborators would have used them.)

* The squared distance for the forward permutation jump.  This is a value
  that is computed during a Metropolis proposal, so we save it for the
  next step instead of recomputing it.


             k=5  o----o----o----o----o----o
                 /    /    /    /    /    /|
           k=4  o----o----o----o----o----o o
               /    /    /    /    /    /|/|
         k=3  o----o----o----o----o----o o o
             /    /    /    /    /    /|/| |
       k=2  o----o----o----o----o----o o o o
           /    /    /    /    /    /|/| | |
      k=1 o----o----o----o----o----o o o o o
         /    /    /    /    /    /|/| | | |
j=5 k=0 o----o----o----o----o----o o x======> Stored at each node x:
        |    |    |    |    |    |/|/|/|/|/   * Lattice coordinates (i,j,k)
j=4     o----o----o----o----o----o o o o o    * Pointer to pi(x)
        |    |    |    |    |    |/|/|/|/     * Pointer to piinv(x)
j=3     o----o----o----o----o----o o o o      * |x-pi(x)|^2
        |    |    |    |    |    |/|/|/
j=2     o----o----o----o----o----o o o
        |    |    |    |    |    |/|/
j=1     o----o----o----o----o----o o
        |    |    |    |    |    |/
j=0     o----o----o----o----o----o

       i=0  i=1  i=2  i=3  i=4  i=5

At startup, the permutation is either identity, or uniform random.  An
algorithm to generate a uniform-random permutation is well-known; see the
UNIFORM RANDOM PERMUTATION subroutine below.

The initial system energy is computed as follows:
  H := 0.25 / beta * (sum over x of |x-pi(x)|^2) + alpha * number of two cycles.

The initial number of two-cycles is computed as follows:
  N2 := 0
  For i from 0 to L-1:
    For j from 0 to L-1:
      For k from 0 to L-1:
        x := lattice point at (i,j,k)
        pi(x) := x forward pointer
        pi^2(x) := pi(x) forward pointer
        if (x != pi(x) and x == pi^2(x)):
          N2 := N2 + 1
  N2 := N2 / 2.  (Compensate for double-count.)

rho_L = array from 0 to N of real numbers.

(Thermalization loop)
Thermalized := false.
Until thermalized:
  Smooth H over a 200-point window.  Set Thermalized := true if there are
  > 40 turning points in the smoothed energy.  (This smoothing computation
  takes less than 0.01% of the CPU time.  Therefore, the details of the
  smoothing are irrelevant for performance analysis.)

  Call the subroutine METROPOLIS SWEEP. (See below.)

(Accumulation loop)
For i := 1 to N_acc:
  Call the subroutine COMPUTE RHO_L.  (See below.)
  For k := 0 to N:
    rho sum[k] := rho sum[k] + rho_L[k].
  Call the subroutine METROPOLIS SWEEP. (See below.)

(Scale rho_sum to rho_avg)
For k := 0 to N:
  rho_avg[k] := rho_sum[k] / N_acc

Call subroutine COMPUTE RHO_INFTY.  (See below.)


================================================================
SUBROUTINE METROPOLIS SWEEP:

Input:   Lattice points, T, alpha, H (system energy, N2 (number of two cycles).

Output:  Perhaps modify the permutation of the lattice points.
H and N2 are also modified, if the new permutation was accepted.

----------------------------------------------------------------
There are six jump deltas to choose y near pi(x):
  ( 1, 0, 0 ),
  (-1, 0, 0 ),
  ( 0, 1, 0 ),
  ( 0,-1, 0 ),
  ( 0, 0, 1 ),
  ( 0, 0,-1 )

For i from 0 to L-1:
  For j from 0 to L-1:
    For k from 0 to L-1:

      x := lattice point at (i,j,k).

      pi(x) := forward jump pointer from x.

      Choose a pseudorandom integer between 0 and 5.  Select the jump
      delta from the above table of 6 jump deltas.  This gives a point y.

      piinv(y) := backward jump pointer from y.

      |x-pi(x)|^2 and |y-piinv(y)|^2 are already known from the previous
      Metropolis sweep (or from lattice population, if this is the first
      Metropolis sweep).  But, compute |x - y|^2 and |pi(x) - piinv(y)|^2.

      Call subroutine COMPUTE DELTA_N2.  (See below.)

      Delta_H := 0.25 / beta *
        (|x-pi(x)|^2 + |y-piinv(y)|^2 - |x-y|^2 - |pi(x)-piinv(y)|^2)
        + alpha * Delta_N2.

      E := exp(-Delta_H).
      U := uniform random on [0, 1).

      If (U < E) (accept proposed change):
        x      forward  permutation pointer := y.
        y      backward permutation pointer := x.
        piinvy forward  permutation pointer := pi(x).
        pi(x)  backward permutation pointer := piinvy.

        x forward distance squared := |x-y|^2 (save for the next sweep to
        avoid recomputing it).

        piinv(y) forward distance squared := |piinv(y)-pi(x)|^2 (save for the
        next sweep to avoid recomputing it).

        H  := H  + Delta_H.
        N2 := N2 + Delta_N2.


================================================================
SUBROUTINE COMPUTE DELTA_N2:

Input:  x, pi(x), piinv(y), y.

Output:  Change in the number of two-cycles if x were to instead point to y
and piinv(y) were to instead point to pi(x).

Remark:  It took me a while to get this logic correct.  I worked some nice
drawings out on paper, and showed them to you in Tucson, but did not bring them
to Vienna.  I will send them to if you wish.  In the meanwhile, I ask you to
trust that this subroutine has been exhaustively tested.  It is correct.

----------------------------------------------------------------

Find pi^2(x) and pi(y) from the permutation forward pointers of pi(x) and y,
respectively.

if (y == pi(x))                         (Case 0)
  return 0;

if (x == pi(x))                         (Case 1)
  if (piinv(y) == y)
    Delta_N2 :=  1;                        (Case 1a.)
  else if (piinv(y) == piy)
    Delta_N2 := -1;                        (Case 1b.)

else if (piinv(y) == y)                 (Case 2)
  if (x == pi(x))
    Delta_N2 :=  1;                        (Case 2a; same as 1a.)
  else if (x == pi2x)
    Delta_N2 := -1;                        (Case 2b.)

else if (x == y)                        (Case 3)
  if (pi(x) == piinv(y))
    Delta_N2 := -1;                        (Case 3a.)
  else if (pi2x == piinv(y))
    Delta_N2 :=  1;                        (Case 3b.)

else if (pi(x) == piinv(y))             (Case 4)
  if (x == y)
    Delta_N2 :=  -1;                       (Case 4a; same as 3a.)
  else if (piy == x)
    Delta_N2 :=   1;                       (Case 4b.)

else if (pi2x == x)                     (Case 5)
  if (piy == piinv(y))
    Delta_N2 := -2;                        (Case 5a.)
  else
    Delta_N2 := -1;                        (Case 5b.)

else if (piy == piinv(y))               (Case 6)
  if (pi2x == x)
    Delta_N2 :=  2;                        (Case 6a; same as 5a.)
  else
    Delta_N2 := -1;                        (Case 6b.)

else if (pi2x == piinv(y))              (Case 7)
  if (piy == x)
    Delta_N2 :=  2;                        (Case 7a.)
  else
    Delta_N2 :=  1;                        (Case 7b.)

else if (piy == x)                      (Case 8)
  if (pi2x == piinv(y))
    Delta_N2 :=  2;                        (Case 8a.)
  else
    Delta_N2 :=  1;                        (Case 8b.)

Return Delta_N2


================================================================
SUBROUTINE UNIFORM RANDOM PERMUTATION:

Input:  Lattice points with identity permutation.

Output:  Lattice points with a different permutation, all pi in S_n
being equally likely.

----------------------------------------------------------------

N := L^d

For purposes of this subroutine, make a list of N points.  This list of points
is indexed from 0 to N-1.  (This is a flattening of the L x L x L matrix of
points.)

                Example:  lattice points are

                      x6--x7
                    / |  /|
                  /   x4--x5
                /   / /  /
                x2--x3  /
                |/  | /
                x0--x1

                Example:  list is
                +--+--+--+--+--+--+--+--+
                |x0|x1|x2|x3|x4|x5|x6|x7|
                +--+--+--+--+--+--+--+--+

Unused start := 0

For s from 0 to N-1:
  s := 0
  u := uniform random integer between unused start and N-1
  Swap list[u] and list[s]
  Unused start := unused start + 1

                Example:  after three permutation images have been selected (so s=3):
                +--+--+--+--+--+--+--+--+
                |x2|x3|x4|x0|x1|x5|x6|x7|
                +--+--+--+--+--+--+--+--+
                          ^\           /
                          |  Select a point uniformly from this unused range
                          |  and put it here
                          |       |
                          |       v
                          +<------+

For s from 0 to N-1:
  Compute i,j,k from s using lexical order
  Set x's forward permutation pointer to be list[s]
  Set x's forward pointer's backward pointer to be x
  Set x's forward distance squared to be |x-pi(x)|^2.

                Example:  list is
                +--+--+--+--+--+--+--+--+
                |x2|x3|x4|x0|x1|x6|x5|x7|
                +--+--+--+--+--+--+--+--+

                Example:  lattice points are

                      x5--x7
                    / |  /|
                  /   x1--x6
                /   / /  /
                x4--x0  /
                |/  | /
                x2--x3


================================================================
SUBROUTINE COMPUTE RHO_L:

Call the subroutine GET CYCLE COUNTS.

rho_value := 0.0

For k from 0 to N:
  rho_value := rho_value + k * counts[k];
  rho_L[k] := rho_value / N


================================================================
SUBROUTINE GET CYCLE COUNTS:

Input:   Lattice points.
Output:  Occupation numbers for cycle lengths, in an array named counts.

----------------------------------------------------------------

Counts = array from 0 to N of zeroes.

Marks = L x L x L matrix of zeroes.  (This memory is allocated once at program
start and is reused here.)

For i from 0 to L-1:
  For j from 0 to L-1:
    For k from 0 to L-1:

      If (marks[i][j][k] == 1):
        Go on to next (i,j,k) value:  this lattice site was already visited.
        It participates in a cycle that was already enumerated.

      Cycle length := 0
      Cycle start point := points[i][j][k].
      Current point := cycle start point.
      Marks[i][j][k] := 1.

      Loop:
        Cycle length  := cycle length + 1
        Next point    := current point's forward permutation pointer.
        Current point := next point.
        If (next point == cycle start point):
          Break out of loop.
        Mark the next point as visited.
      Counts[cycle length] := counts[cycle length] + 1.


================================================================
SUBROUTINE COMPUTE RHO_INFTY:

Input:  rho_avg

Output: rho_fit and rho_infty

----------------------------------------------------------------

(Find the point of greatest convexity on the rho_avg curve.  Do this by
subtracting off the diagonal and finding the maximum value of rho_avg,
and the index k at which that maximum occurs.)

maxk := 0
maxdev := 0.0

For k from 1 to N:
  dev := rho_avg[k] - k/N
  if (dev > maxdev):
    maxdev := dev
    maxk := k

(Compute the vertical intercept.)
rho_finite_T := rho_avg[maxk] - maxk/N

(Find the distance from the upper left-hand corner to the intercept.)
rho_infty_T  := 1.0 - rho_finite_T

(Compute the linear fit for display purposes.)
for k from 0 to N:
  rho_fit[k] := k/N + rho_finite_T
  if (rho_fit[k] > 1.0)
    rho_fit[k] := 1.0

return rho_infty_T

                Example:

                Horizontal axis runs from 0 to N.
                Vertical   axis runs from 0 to 1.
                Here is rho_avg as computing during Metropolis sweeps:

                +------------------------+
                |         *              |
                |      *                 |
                |    *                   |
                |  *                     |
                |                        |
                |*                       |
                |                        |
                |                        |
                |                        |
                |                        |
                +------------------------+

                Subtract off the diagonal:

                +------------------------+
                |                        |
                |                        |
                |                        |
                |  * * *                 |
                |*   ^    *              |
                |    |      *            |
                |    |        *          |
                |    |          *        |
                |    |            *      |
                |    |              *    |
                +----k----------------*--+
                   Maximum deviation from the diagonal here

                +------------------------+       ---
                |        /*              |        ^
                |      /                 |        | rho_infty(T)
                |    *                   |        |
      Vertical  |  /                     |        v
      intercept >/                       |       ---
                |*                       |        ^
                |                        |        | rho_finite(T)
                |                        |        |
                |                        |        |
                |                        |        v
                +------------------------+       ---