Print a specific value in the Wythoff matrix modulo 2

11

The Wythoff matrix is an infinite matrix consisting of the Grundy numbers of each square on a chessboard in Wythoff's game.

Each entry in this matrix is equal to the smallest nonnegative number that does not appear anywhere above, to the left, or diagonally northwest of the position of the entry.

The top-left 20-by-20 square looks like this:

  0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19
  1  2  0  4  5  3  7  8  6 10 11  9 13 14 12 16 17 15 19 20
  2  0  1  5  3  4  8  6  7 11  9 10 14 12 13 17 15 16 20 18
  3  4  5  6  2  0  1  9 10 12  8  7 15 11 16 18 14 13 21 17
  4  5  3  2  7  6  9  0  1  8 13 12 11 16 15 10 19 18 17 14
  5  3  4  0  6  8 10  1  2  7 12 14  9 15 17 13 18 11 16 21
  6  7  8  1  9 10  3  4  5 13  0  2 16 17 18 12 20 14 15 11
  7  8  6  9  0  1  4  5  3 14 15 13 17  2 10 19 21 12 22 16
  8  6  7 10  1  2  5  3  4 15 16 17 18  0  9 14 12 19 23 24
  9 10 11 12  8  7 13 14 15 16 17  6 19  5  1  0  2  3  4 22
 10 11  9  8 13 12  0 15 16 17 14 18  7  6  2  3  1  4  5 23
 11  9 10  7 12 14  2 13 17  6 18 15  8 19 20 21  4  5  0  1
 12 13 14 15 11  9 16 17 18 19  7  8 10 20 21 22  6 23  3  5
 13 14 12 11 16 15 17  2  0  5  6 19 20  9  7  8 10 22 24  4
 14 12 13 16 15 17 18 10  9  1  2 20 21  7 11 23 22  8 25 26
 15 16 17 18 10 13 12 19 14  0  3 21 22  8 23 20  9 24  7 27
 16 17 15 14 19 18 20 21 12  2  1  4  6 10 22  9 13 25 11 28
 17 15 16 13 18 11 14 12 19  3  4  5 23 22  8 24 25 21 26 10
 18 19 20 21 17 16 15 22 23  4  5  0  3 24 25  7 11 26 12 13
 19 20 18 17 14 21 11 16 24 22 23  1  5  4 26 27 28 10 13 25

There is currently no known efficient algorithm for computing an arbitrary entry in the Wythoff matrix. However, your task in this problem is to try to design a heuristic function that will tell whether the number at a specific coordinate wythoff(x, y) is even or odd.

Your program may not contain more than 64 KB (65,536 bytes) of source code, or use more than 2 MB (2,097,152 bytes) of working memory.

In particular for memory usage, this means that the maximum resident set size of your program may not exceed 2 MB more than the maximum resident set size of an empty program in that language. In the case of an interpreted language, it would be the memory usage of the interpreter / virtual machine itself, and in the case of a compiled language, it would be the memory usage of a program that executes the main method and does nothing.

Your program will be tested on the 10000 x 10000 matrix for row values in 20000 <= x <= 29999 and column values in 20000 <= y <= 29999.

The score of your program is the accuracy rate (number of correct guesses) your program achieves, with shorter code acting as a tiebreaker.

Joe Z.

Posted 2016-10-07T17:33:50.550

Reputation: 30 589

301.R is a 05AB1E that outputs true or false randomly. Let 0 be true and 1 be false, my program will theoretically be correct ~50% of the time. Is this a valid entry? – Magic Octopus Urn – 2016-10-07T17:41:43.463

@carusocomputing Actually, I forgot to mention that randomized solutions are not allowed — your program should produce the same output for the same input every time, although I suspect that the word function implies that. – Joe Z. – 2016-10-07T20:20:10.710

If I fix the seed of my prng on each call, it will produce the same output for identical input, and I know what you mean but you will probably have to word it more specifically somehow. – miles – 2016-10-07T20:57:41.927

I get something very different when I search for Wythoff Matrix

– Linus – 2016-10-08T04:20:43.170

@Linus Would "Wythoff's Game Matrix" be better? I saw that page too. – Joe Z. – 2016-10-08T06:08:13.293

@miles Pseudorandom solutions are fine, just not ones that use R like the 05AB1E solution, which are not guaranteed to return the same value every time. – Joe Z. – 2016-10-08T06:09:48.047

@Vlo For each row, the residuals are eventually periodic, which makes me think that there are at least some heuristics that would work. – Joe Z. – 2016-10-08T06:13:02.210

Let us continue this discussion in chat.

– Joe Z. – 2016-10-10T05:40:05.970

Answers

6

Python; accuracy = 54,074,818; size = 65,526 bytes

Previous scores: 50,227,165; 50,803,687; 50,953,001

#coding=utf-8
d=r'''<65,400 byte string>'''
def f(x,y):
 a=max(x,y)-20000;b=min(x,y)-20000;c=(a*(a+1)//2+b)%523200
 return(ord(d[c//8])>>(c%8))&1

This approach divides all unique entries of the matrix into 523,200 groups and reads the best guess for the group (x, y) from a binary string. You can download the full source code from Google Drive.

I've used @PeterTaylor's parities to generate the string and to compute the accuracy.

Dennis

Posted 2016-10-07T17:33:50.550

Reputation: 196 637

I tried many different, more interesting approaches, but in the end, a simple hardcode outscored all of them... – Dennis – 2016-10-11T03:10:23.807

Hardcoding is also a valid approach — it might turn into, say, which hardcoding scheme returns the best result. – Joe Z. – 2016-10-13T05:18:10.483

Not saying otherwise, but since the distribution of the parities is very obviously non-random, I was hoping to outscore this approach. So far, all my attempts have failed though. – Dennis – 2016-10-13T14:13:35.870

Nah, it's fine. It just means that this problem is too hard to do properly. I've been making a bunch more problems of this style except one-dimensional. They're all in the sandbox if you want to check them out. – Joe Z. – 2016-10-13T16:18:30.397

4

CJam (accuracy 50016828/100000000, 6 bytes)

{+1&!}

(In ALGOL-style pseudocode for non-CJammers: return ((x + y) & 1) == 0).

This performs better than any of the other two dozen simple heuristics I've tried. It's even better than any combination with the next two best ones.


The score assumes that my calculated section of the matrix is correct. Independent verification welcomed. I'm hosting the calculated parity bits at http://cheddarmonk.org/codegolf/PPCG95604-parity.bz2 (8MB download, extracts to 50MB text file: since the matrix is symmetric about the main diagonal, I've only included each line starting from the main diagonal, so you have to offset, transpose, and bitwise OR to get the full square).

The code which I used to calculate it is Java. It uses the definition fairly straightforwardly, but with a set data structure which run-length encodes the ranges so that it's quick to skip to the next permitted value. Further optimisation would be possible, but it runs on my moderately old desktop in about two hours and 1.5GB of heap space.

import java.util.*;

public class PPCG95604Analysis
{
    static final int N = 30000;

    public static void main(String[] args) {
        Indicator[] cols = new Indicator[N];
        Indicator[] diag = new Indicator[N];
        for (int i = 0; i < N; i++) {
            cols[i] = new Indicator();
            diag[i] = new Indicator();
        }

        int maxVal = 0;

        for (int y = 0; y < N; y++) {
            Indicator row = new Indicator(cols[y]);

            for (int x = y; x < N; x++) {
                Indicator col = cols[x];
                Indicator dia = diag[x - y];

                Indicator.Result rr = row.firstCandidate();
                Indicator.Result rc = col.firstCandidate();
                Indicator.Result rd = dia.firstCandidate();

                while (true) {
                    int max = Math.max(Math.max(rr.candidateValue(), rc.candidateValue()), rd.candidateValue());
                    if (rr.candidateValue() == max && rc.candidateValue() == max && rd.candidateValue() == max) break;

                    if (rr.candidateValue() != max) rr = rr.firstCandidateGreaterThan(max - 1);
                    if (rc.candidateValue() != max) rc = rc.firstCandidateGreaterThan(max - 1);
                    if (rd.candidateValue() != max) rd = rd.firstCandidateGreaterThan(max - 1);
                }

                if (y >= 20000 && x >= 20000) System.out.format("%d", rr.candidateValue() & 1);
                maxVal = Math.max(maxVal, rr.candidateValue());
                rr.markUsed();
                rc.markUsed();
                rd.markUsed();
            }
            if (y >= 20000) System.out.println();
        }
    }

    static class Indicator
    {
        private final int INFINITY = (short)0xffff;
        private final int MEMBOUND = 10000;

        private short[] runLengths = new short[MEMBOUND];

        public Indicator() { runLengths[1] = INFINITY; }

        public Indicator(Indicator clone) { System.arraycopy(clone.runLengths, 0, runLengths, 0, MEMBOUND); }

        public Result firstCandidate() {
            // We have a run of used values, followed by a run of unused ones.
            return new Result(1, 0xffff & runLengths[0], 0xffff & runLengths[0]);
        }

        class Result
        {
            private final int runIdx;
            private final int runStart;
            private final int candidateValue;

            Result(int runIdx, int runStart, int candidateValue) {
                this.runIdx = runIdx;
                this.runStart = runStart;
                this.candidateValue = candidateValue;
            }

            public int candidateValue() {
                return candidateValue;
            }

            public Result firstCandidateGreaterThan(int x) {
                if (x < candidateValue) throw new IndexOutOfBoundsException();

                int idx = runIdx;
                int start = runStart;
                while (true) {
                    int end = start + (0xffff & runLengths[idx]) - 1;
                    if (end > x) return new Result(idx, start, x + 1);

                    // Run of excluded
                    start += 0xffff & runLengths[idx];
                    idx++;
                    // Run of included
                    start += 0xffff & runLengths[idx];
                    idx++;

                    if (start > x) return new Result(idx, start, start);
                }
            }

            public void markUsed() {
                if (candidateValue == runStart) {
                    // Transfer one from the start of the run to the previous run
                    runLengths[runIdx - 1]++;
                    if (runLengths[runIdx] != INFINITY) runLengths[runIdx]--;
                    // May need to merge runs
                    if (runLengths[runIdx] == 0) {
                        runLengths[runIdx - 1] += runLengths[runIdx + 1];
                        for (int idx = runIdx; idx < MEMBOUND - 2; idx++) {
                            runLengths[idx] = runLengths[idx + 2];
                            if (runLengths[idx] == INFINITY) break;
                        }
                    }

                    return;
                }

                if (candidateValue == runStart + (0xffff & runLengths[runIdx]) - 1) {
                    // Transfer one from the end of the run to the following run.
                    if (runLengths[runIdx + 1] != INFINITY) runLengths[runIdx + 1]++;
                    if (runLengths[runIdx] != INFINITY) runLengths[runIdx]--;
                    // We never need to merge runs, because if we did we'd have hit the previous case instead
                    return;
                }

                // Need to split the run. From
                //   runIdx: a+1+b
                // to
                //   runIdx: a
                //   runIdx+1: 1
                //   runIdx+2: b
                //   runIdx+3: previous val at runIdx+1
                for (int idx = MEMBOUND - 1; idx > runIdx + 2; idx--) {
                    runLengths[idx] = runLengths[idx - 2];
                }
                runLengths[runIdx + 2] = runLengths[runIdx] == INFINITY ? INFINITY : (short)((0xffff & runLengths[runIdx]) + runStart - 1 - candidateValue);
                runLengths[runIdx + 1] = 1;
                runLengths[runIdx] = (short)(candidateValue - runStart);
            }
        }
    }
}

Peter Taylor

Posted 2016-10-07T17:33:50.550

Reputation: 41 901

3

J, accuracy = 50022668/108 = 50.0227%, 4 bytes

2|*.

Takes the coordinates as two arguments, computes the LCM between them and takes it modulo 2. A 0 means it is even and a 1 means it is odd.

The performance is based on the parity bits provided by @Peter Taylor.

The PRNG version before for 7 bytes, 2|?.@#. had an accuracy of 50010491/108.

Explanation

2|*.  Input: x on LHS, y on RHS
  *.  LCM(x, y)
2|    Modulo 2

miles

Posted 2016-10-07T17:33:50.550

Reputation: 15 654

1The parity of the LCM is the parity of the bitwise AND. Does that save you a byte? The fascinating thing is that it's so obviously a bad heuristic (it gives 1 a mere 25% of the time, when the correct proportion is almost exactly 50%), and yet it does better than many which aren't so obviously bad. – Peter Taylor – 2016-10-09T21:55:36.820

Thanks, but unfortunately bitwise AND in J is literally AND. – miles – 2016-10-09T22:11:31.990

@PeterTaylor That sort of surprising heuristic discovery is what challenges like this one are supposed to be all about. – Joe Z. – 2016-10-10T04:59:19.680