[Decider] Halting Segment

After reading the formal proof, I wrote a forward implementation of this decider that uses the Halting Segment graph construction and Remark 5.5. Sadly, I couldn’t find anything to compare my results with.

The official decider used a backward approach that decided ~2% fewer machines. e.g. on 710207, my decider said non-halt, but the official decider said undecided.

Here is my code and my results:

/*
g++ -O2 -std=c++17 "-Wl,--stack,524288000" bb5\5_halting_segment\dec_halting_segment.cpp -o bb5\5_halting_segment\dec_halting_segment.exe

remaining machines: 1538624
N = 1 decided 0 machines in 2 seconds
N = 2 decided 267268 machines in 3 seconds
N = 3 decided 602813 machines in 6 seconds
N = 4 decided 795673 machines in 9 seconds
N = 5 decided 897113 machines in 10 seconds
N = 6 decided 949970 machines in 13 seconds
N = 7 decided 979906 machines in 19 seconds
N = 8 decided 998380 machines in 26 seconds
N = 9 decided 1009465 machines in 41 seconds
N = 10 decided 1016092 machines in 65 seconds
N = 11 decided 1020095 machines in 110 seconds
N = 12 decided 1022536 machines in 198 seconds
N = 13 decided 1024140 machines in 364 seconds
N = 14 decided 1025054 machines in 702 seconds
N = 15 decided 1025665 machines in 1341 seconds

*/
#include <bits/stdc++.h>
using namespace std;
typedef string TM;

struct Tape {
    const int x;

    int get(const int pos) const {
        return (x>>pos)&1;
    }

    Tape set(const int pos, const int val) const {
        if (get(pos)==val) return {x};
        return {x^(1<<pos)};
    }
};

// a configuration consists of:
// state (5 choices)
// head position (N+2 choices)
// tape (2^N choices)
int N;
TM machine;
vector<vector<vector<bool>>> visited; // indices are: state, head position, tape
vector<vector<bool>> haltPos; // indices are: transition (10 choices), head position

void setHaltPos(const int offset, const int head) {
    haltPos.at(offset/3).at(head+1)=1;
}

void dfs(const int state, const int head, const Tape tape) {
    // base case
    if (visited.at(state-1).at(head+1).at(tape.x)) return;
    visited.at(state-1).at(head+1).at(tape.x)=1;
    // recursive case
    if (0<=head && head<N) {
        // middle, follow the transition
        const int offset=(state-1)*6+tape.get(head)*3;
        if (machine[offset+2]==0) setHaltPos(offset, head);
        else {
            const int newHead=head+(machine[offset+1]?-1:1);
            dfs(machine[offset+2],newHead,tape.set(head,machine[offset]));
        }
        return;
    }
    // either -1 or N
    {
        const int offset=(state-1)*6; // follow 0 transition
        if (machine[offset+2]==0) setHaltPos(offset, head);
        else {
            // head position didn't change
            dfs(machine[offset+2],head,tape);
            // head position changed
            const int newHead=head+(machine[offset+1]?-1:1);
            if (0<=newHead && newHead<N) dfs(machine[offset+2],newHead,tape);
        }
    }
    {
        const int offset=(state-1)*6+3; // follow 1 transition
        if (machine[offset+2]==0) setHaltPos(offset, head);
        else {
            // head position didn't change
            dfs(machine[offset+2],head,tape);
            // head position changed
            const int newHead=head+(machine[offset+1]?-1:1);
            if (0<=newHead && newHead<N) dfs(machine[offset+2],newHead,tape);
        }
    }
}

// modify "N" and "machine" before calling
bool decider_halting_segment() {
    visited=vector<vector<vector<bool>>>(5,vector<vector<bool>>(N+2,vector<bool>(1<<N,0)));
    haltPos=vector<vector<bool>>(10,vector<bool>(N+2,0));
    // todo: this is very inefficient, because it traverses the entire graph. optimize it later.
    for (int pos=-1; pos<=N; pos++) dfs(1,pos,{0});
    // count covered and uncovered halting transitions
    int covered=0, uncovered=0;
    for (int i=0; i<10; i++) {
        if (machine[i*3+2]!=0) continue;
        int count=0;
        for (int j=0; j<N+2; j++) {
            if (haltPos.at(i).at(j)) count++;
        }
        if (count==N+2) covered++;
        else uncovered++;
    }
    assert(covered>0 || uncovered>0);
    return covered==0 && uncovered>0; // true = doesn't halt, see remark 5.5
}

// discard tm's from previous deciders
const int LIM=88664064;
char status[LIM];
int remain=LIM;

void discard(const char* filename) {
    FILE* f=fopen(filename,"rb");
    while (1) {
        int a0=fgetc(f);
        if (a0==EOF) break;
        int a1=fgetc(f);
        int a2=fgetc(f);
        int a3=fgetc(f);
        int a=(a0<<24)+(a1<<16)+(a2<<8)+a3;
        assert(status[a]==0);
        status[a]=1;
        remain--;
    }
    fclose(f);
}

int main() {
    discard("../2_cycler/cyclers-run-11c0ef00e9c2-time-1000-space-500-minIndex-0-maxIndex-14322029");
    discard("../3_translated_cycler/translated-cyclers-run-8f5b2539279a-time-1000-space-500-minIndex-14322029-maxIndex-88664064");
    discard("../3_translated_cycler/translated-cyclers-run-725fd45b37eb-time-10000-space-5000-minIndex-14322029-maxIndex-88664064");
    discard("../3_translated_cycler/translated-cyclers-run-85454189d410-time-10000-space-5000-minIndex-14322029-maxIndex-88664064");
    discard("../4_backward_reasoning/backward-reasoning-run-7edeea99bc2d-depth-50-minIndex-0-maxIndex-88664064");
    vector<pair<int,TM>> machines;
    FILE* f=fopen("../all_5_states_undecided_machines_with_global_header","r");
    for (int i=0; i<LIM; i++) {
        if (i==0) fseek(f,(i+1)*30LL,SEEK_SET);
        char ch[30];
        fread(ch,1,30,f);
        if (status[i]==1) continue;
        machines.push_back({i,string(ch,ch+30)});
    }
    fclose(f);
    printf("remaining machines: %d\n",machines.size());
    for (N=1; N<=15; N++) {
        string filename="dec_halting_segment_out_"+to_string(N);
        FILE* f=fopen(filename.c_str(),"wb");
        int decided=0;
        auto time0 = chrono::steady_clock::now();
        for (auto [i,m] : machines) {
            machine=m;
            if (decider_halting_segment()) {
                decided++;
                fputc((i>>24)&255,f);
                fputc((i>>16)&255,f);
                fputc((i>>8)&255,f);
                fputc((i>>0)&255,f);
            }
        }
        auto time1 = chrono::steady_clock::now();
        printf("N = %d decided %d machines in %d seconds\n",N,decided,chrono::duration_cast<std::chrono::seconds>(time1-time0).count());
        fclose(f);
    }
}

Afterwards, I wrote a weaker version that agrees with the official backward decider on N = 7, 9, 11, 13, 15. Here’s a list of changes that I made:

  • If the head position is outside of the segment, don’t keep track of the state.
  • Only run the decider on odd N.
  • For each halting transition, only check the segment’s middle position. (This is Remark 5.5 except weaker)

Here is my code and my results:

/*
g++ -O2 -std=c++17 "-Wl,--stack,524288000" bb5\5_halting_segment\dec_halting_segment_weaker.cpp -o bb5\5_halting_segment\dec_halting_segment_weaker.exe

remaining machines: 1538624
N = 1 decided 0 machines in 2 seconds
N = 3 decided 301723 machines in 5 seconds
N = 5 decided 674458 machines in 8 seconds
N = 7 decided 855186 machines in 14 seconds
N = 9 decided 943633 machines in 29 seconds
N = 11 decided 983961 machines in 75 seconds
N = 13 decided 1002808 machines in 240 seconds
N = 15 decided 1012659 machines in 897 seconds

*/
#include <bits/stdc++.h>
using namespace std;
typedef string TM;

struct Tape {
    const int x;

    int get(const int pos) const {
        return (x>>pos)&1;
    }

    Tape set(const int pos, const int val) const {
        if (get(pos)==val) return {x};
        return {x^(1<<pos)};
    }
};

// a configuration consists of:
// state (5 choices)
// head position (N+2 choices)
// tape (2^N choices)
int N;
TM machine;
vector<vector<vector<bool>>> visited; // indices are: state, head position, tape
vector<vector<bool>> haltPos; // indices are: transition (10 choices), head position

void setHaltPos(const int offset, const int head) {
    haltPos.at(offset/3).at(head+1)=1;
}

void dfs(int state, const int head, const Tape tape) {
    // base case
    if (head==-1 || head==N) state=1; // consolidate states
    if (visited.at(state-1).at(head+1).at(tape.x)) return;
    visited.at(state-1).at(head+1).at(tape.x)=1;
    // recursive case
    if (0<=head && head<N) {
        // middle, follow the transition
        const int offset=(state-1)*6+tape.get(head)*3;
        if (machine[offset+2]==0) setHaltPos(offset, head);
        else {
            const int newHead=head+(machine[offset+1]?-1:1);
            dfs(machine[offset+2],newHead,tape.set(head,machine[offset]));
        }
        return;
    }
    // either -1 or N, follow all transitions
    for (int offset=0; offset<30; offset+=3) {
        if (machine[offset+2]==0) setHaltPos(offset, head);
        else {
            // head position changed
            const int newHead=head+(machine[offset+1]?-1:1);
            if (0<=newHead && newHead<N) dfs(machine[offset+2],newHead,tape);
        }
    }
}

// modify "N" and "machine" before calling
bool decider_halting_segment() {
    visited=vector<vector<vector<bool>>>(5,vector<vector<bool>>(N+2,vector<bool>(1<<N,0)));
    haltPos=vector<vector<bool>>(10,vector<bool>(N+2,0));
    // todo: this is very inefficient, because it traverses the entire graph. optimize it later.
    for (int pos=-1; pos<=N; pos++) dfs(1,pos,{0});
    // count covered and uncovered halting transitions
    int covered=0, uncovered=0;
    for (int i=0; i<10; i++) {
        if (machine[i*3+2]!=0) continue;
        if (haltPos.at(i).at((N+1)/2)) covered++;
        else uncovered++;
    }
    assert(covered>0 || uncovered>0);
    return covered==0 && uncovered>0; // true = doesn't halt
}

// discard tm's from previous deciders
const int LIM=88664064;
char status[LIM];
int remain=LIM;

void discard(const char* filename) {
    FILE* f=fopen(filename,"rb");
    while (1) {
        int a0=fgetc(f);
        if (a0==EOF) break;
        int a1=fgetc(f);
        int a2=fgetc(f);
        int a3=fgetc(f);
        int a=(a0<<24)+(a1<<16)+(a2<<8)+a3;
        assert(status[a]==0);
        status[a]=1;
        remain--;
    }
    fclose(f);
}

int main() {
    discard("../2_cycler/cyclers-run-11c0ef00e9c2-time-1000-space-500-minIndex-0-maxIndex-14322029");
    discard("../3_translated_cycler/translated-cyclers-run-8f5b2539279a-time-1000-space-500-minIndex-14322029-maxIndex-88664064");
    discard("../3_translated_cycler/translated-cyclers-run-725fd45b37eb-time-10000-space-5000-minIndex-14322029-maxIndex-88664064");
    discard("../3_translated_cycler/translated-cyclers-run-85454189d410-time-10000-space-5000-minIndex-14322029-maxIndex-88664064");
    discard("../4_backward_reasoning/backward-reasoning-run-7edeea99bc2d-depth-50-minIndex-0-maxIndex-88664064");
    vector<pair<int,TM>> machines;
    FILE* f=fopen("../all_5_states_undecided_machines_with_global_header","r");
    for (int i=0; i<LIM; i++) {
        if (i==0) fseek(f,(i+1)*30LL,SEEK_SET);
        char ch[30];
        fread(ch,1,30,f);
        if (status[i]==1) continue;
        machines.push_back({i,string(ch,ch+30)});
    }
    fclose(f);
    printf("remaining machines: %d\n",machines.size());
    for (N=1; N<=15; N+=2) {
        string filename="dec_halting_segment_weaker_out_"+to_string(N);
        FILE* f=fopen(filename.c_str(),"wb");
        int decided=0;
        auto time0 = chrono::steady_clock::now();
        for (auto [i,m] : machines) {
            machine=m;
            if (decider_halting_segment()) {
                decided++;
                fputc((i>>24)&255,f);
                fputc((i>>16)&255,f);
                fputc((i>>8)&255,f);
                fputc((i>>0)&255,f);
            }
        }
        auto time1 = chrono::steady_clock::now();
        printf("N = %d decided %d machines in %d seconds\n",N,decided,chrono::duration_cast<std::chrono::seconds>(time1-time0).count());
        fclose(f);
    }
}
1 Like