Browse Source

Added utils/lt_builder_exp.py

Daniel Walton 1 year ago
parent
commit
4268df0298
3 changed files with 225 additions and 29 deletions
  1. 61
    29
      TODO.txt
  2. 11
    0
      rubikscubennnsolver/RubiksCube444.py
  3. 153
    0
      utils/lt_builder_exp.py

+ 61
- 29
TODO.txt View File

@@ -1,16 +1,52 @@
1 1
 
2
-
3
-- experiment with B-Trees
4
-    - unittests
5
-    - rename 'nodes' to 'child_nodes' or 'children'
6
-    - optimaly load the B-Trees
7
-    - support for loading the first two levels in memory and finding the rest on disk
8
-
9
-    - Once the two bullets above are done we should be able to find a key via
10
-      a single seek(). We should also be able to only do one rstrip/split(',')
11
-      call, it should only happen when 'if key in key_line' is True.
12
-
13
-      Today we take ~14s where our time is in:
2
+- 6x6x6 UD edge pairing bulid 70 tables and merge them
3
+
4
+- B-Trees...this will benefit all searches
5
+
6
+- TPR solver
7
+    - how the hell does it work so fast?
8
+    - ideas
9
+        - before we start IDA we could see which EO edge mapping gives us the
10
+          shortest edge heuristic and stick with that one.  We would need to go
11
+          back to the 0 line dummy table and
12
+          LookupTableIDA444TsaiPhase2.experimental_ida_search_complete()
13
+
14
+          Or we could build out a small lookup table (say 3 moves deep) for that
15
+          one edge mapping.
16
+
17
+- xyzzy's 5x5x5 solver. It is a variation of the TPR solver so need to understand TPR first.
18
+    - he does an EO (edge orientation) step.  If all larger solvers that use 5x5x5 for edge
19
+      pairing could also do EO then maybe we could toss the edge pairing code...there is a
20
+      ton of it and it is hideous.
21
+
22
+- bidir IDA search
23
+    - I need to understand how the TPR solver works before I spend time on bidir IDA
24
+    - would allow us to find slighly shorter solutions than we do today
25
+    - may potentially allow us tackle much larger IDA searches...TBD
26
+        - first to try is staging all 5x5x5 centers via one phase
27
+        - second to try is staging all 5x5x5 centers via one phase
28
+        - combine phases 1 and 2 of tsai?
29
+    - start from a solved cube and work your way back to the scrambled cube
30
+    - requires us to build prune tables for each scrambled cube
31
+        - for 4x4x4 staging centers we can do this in XYZ minutes in python
32
+        - if we only build it out 4 steps deep we can do it in ~20s
33
+        - so this would have to be done in C and even then it would still
34
+          take some time.  I think this is a strategy that could only be
35
+          used in a fewest moves challenge given how long it will take to
36
+          produce solutions.
37
+
38
+
39
+
40
+B-Tree notes
41
+============
42
+- optimaly load the B-Trees
43
+- support for loading the first two levels in memory and finding the rest on disk
44
+
45
+- Once the two bullets above are done we should be able to find a key via
46
+  a single seek(). We should also be able to only do one rstrip/split(',')
47
+  call, it should only happen when 'if key in key_line' is True.
48
+
49
+  Today we take ~14s where our time is in:
14 50
 
15 51
    974730    0.533    0.000    0.533    0.000 {method 'seek' of '_io.BufferedReader' objects}
16 52
   1431684    0.721    0.000    0.721    0.000 {method 'decode' of 'bytes' objects}
@@ -22,25 +58,25 @@
22 58
    195231    3.299    0.000   11.243    0.000 __init__.py:67(disk_get)
23 59
    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
24 60
 
25
-        So we should end up with
26
-        - seek()   : should come down to 195231...should shave about 300ms
61
+    So we should end up with
62
+    - seek()   : should come down to 195231...should shave about 300ms
27 63
 
28
-        - decode() : Will 'if key in key_line' work if it is not decoded to utf-8?
29
-            If so we can avoid all of these calls....should shave 721ms.
64
+    - decode() : Will 'if key in key_line' work if it is not decoded to utf-8?
65
+        If so we can avoid all of these calls....should shave 721ms.
30 66
 
31
-        - read()   : should come down to 195231...should shave about 600ms
67
+    - read()   : should come down to 195231...should shave about 600ms
32 68
 
33
-        - rstrip() : can only call this 'if key in key_line' which should chop 99% of them..should shave 1435ms
69
+    - rstrip() : can only call this 'if key in key_line' which should chop 99% of them..should shave 1435ms
34 70
 
35
-        - next() : is called today to load the nodes line...we can avoid this if we know that we are already at the leaf node depth
36
-            This should shave 1451ms
71
+    - next() : is called today to load the nodes line...we can avoid this if we know that we are already at the leaf node depth
72
+        This should shave 1451ms
37 73
 
38
-        - split() : can only call this 'if key in key_line' which should chop 99% of them..should shave 2557ms
74
+    - split() : can only call this 'if key in key_line' which should chop 99% of them..should shave 2557ms
39 75
 
40
-        That is about ~7000ms we can shave...would get us from 14s down to 7s which would be awesome
76
+    That is about ~7000ms we can shave...would get us from 14s down to 7s which would be awesome
41 77
 
42 78
 
43
-    - Test this against slow 6x6x6 UD oblique:
79
+- Test this against slow 6x6x6 UD oblique:
44 80
 
45 81
 ./usr/bin/rubiks-cube-solver.py --cpu-max --state FBDDDFFUDRFBBLFLLURLDLLUFBLRFDUFLBLLFBFLRRBBFDRRDUBUFRBUBRDLUBFDRLBBRLRUFLBRBDUDFFFDBLUDBBLRDFUUDLBBBRRDRUDLBLDFRUDLLFFUUBFBUUFDLRUDUDBRRBBUFFDRRRDBULRRURULFDBRRULDDRUUULBLLFDFRRFDURFFLDUUBRUFDRFUBLDFULFBFDDUDLBLLRBL
46 82
 
@@ -56,12 +92,8 @@ This search takes about 5 million seek() calls when using binary search...the se
56 92
 
57 93
 
58 94
 
59
-- make rotate_xxx use one less function call by using a dictionary?
60
-
61
-- tsai
62
-    - explore TPR solver to see what tricks it is doing
63
-
64
-
95
+misc notes
96
+==========
65 97
 - ida_search() needs to return all of the results at that theshold
66 98
 Test on this one, 5x5x5-step20-LR-centers-stage  finds a solution 12 steps long but I know there is one
67 99
 that is 10 steps long (I found it via AStar)

+ 11
- 0
rubikscubennnsolver/RubiksCube444.py View File

@@ -610,6 +610,17 @@ class LookupTable444TsaiPhase2Edges(LookupTable):
610 610
 
611 611
 
612 612
 class LookupTableIDA444TsaiPhase2(LookupTableIDA):
613
+    """
614
+    lookup-table-4x4x4-step60.txt
615
+    =============================
616
+    1 steps has 60 entries (0 percent, 0.00x previous step)
617
+    2 steps has 744 entries (0 percent, 12.40x previous step)
618
+    3 steps has 11,224 entries (0 percent, 15.09x previous step)
619
+    4 steps has 158,608 entries (6 percent, 14.13x previous step)
620
+    5 steps has 2,349,908 entries (93 percent, 14.82x previous step)
621
+
622
+    Total: 2,520,544 entries
623
+    """
613 624
 
614 625
     def __init__(self, parent):
615 626
         LookupTableIDA.__init__(

+ 153
- 0
utils/lt_builder_exp.py View File

@@ -0,0 +1,153 @@
1
+#!/usr/bin/env python3
2
+
3
+"""
4
+experiment to see how long it takes to build the 4x4x4 centers staging
5
+lookup tables for a scrambled cube. If you only go 4-deep it takes about
6
+20s.  Going deeper than that chews through too much memory on the workq.
7
+The workq needs to be stored in a file.
8
+
9
+We need to build 6 or 7 deep.
10
+"""
11
+
12
+from collections import deque
13
+import logging
14
+import os
15
+import sys
16
+from rubikscubennnsolver import reverse_steps
17
+from rubikscubennnsolver.rotate_xxx import rotate_444
18
+from rubikscubennnsolver.RubiksCube444 import (
19
+    moves_4x4x4,
20
+    solved_4x4x4,
21
+    RubiksCube444,
22
+    LookupTableIDA444ULFRBDCentersStage,
23
+    LookupTable444UDCentersStage,
24
+    LookupTable444LRCentersStage,
25
+    LookupTable444FBCentersStage,
26
+)
27
+
28
+
29
+def build_state_table():
30
+    cube = RubiksCube444('DRFDFRUFDURDDLLUFLDLLBLULFBUUFRBLBFLLUDDUFRBURBBRBDLLDURFFBBRUFUFDRFURBUDLDBDUFFBUDRRLDRBLFBRRLB', 'URFDLB')
31
+    cube.nuke_corners()
32
+    cube.nuke_edges()
33
+    original_state = cube.state[:]
34
+
35
+    log.info("cache start")
36
+    for side in (cube.sideU, cube.sideL, cube.sideF, cube.sideR, cube.sideB, cube.sideD):
37
+        for pos in side.center_pos:
38
+            if cube.state[pos] in ('U', 'D'):
39
+                cube.state[pos] = 'U'
40
+            elif cube.state[pos] in ('L', 'R'):
41
+                cube.state[pos] = 'L'
42
+            elif cube.state[pos] in ('F', 'B'):
43
+                cube.state[pos] = 'F'
44
+
45
+    cube.lt_UD_centers_stage = LookupTable444UDCentersStage(cube)
46
+    cube.lt_LR_centers_stage = LookupTable444LRCentersStage(cube)
47
+    cube.lt_FB_centers_stage = LookupTable444FBCentersStage(cube)
48
+    lt_ULFRBD_centers_stage = LookupTableIDA444ULFRBDCentersStage(cube)
49
+
50
+    workq = deque()
51
+    explored = set()
52
+    UD_explored = {}
53
+    LR_explored = {}
54
+    FB_explored = {}
55
+    count = 0
56
+
57
+    for step in moves_4x4x4:
58
+        workq.append(([step,], cube.state[:]))
59
+
60
+    while workq:
61
+        (steps, prev_state) = workq.popleft()
62
+        cube.state = rotate_444(prev_state, steps[-1])
63
+
64
+        state = lt_ULFRBD_centers_stage.state()
65
+        UD_state = cube.lt_UD_centers_stage.state()
66
+        LR_state = cube.lt_LR_centers_stage.state()
67
+        FB_state = cube.lt_FB_centers_stage.state()
68
+
69
+        count += 1
70
+
71
+        if count % 100000 == 0:
72
+            UD_count = len(UD_explored)
73
+            LR_count = len(LR_explored)
74
+            FB_count = len(FB_explored)
75
+
76
+            log.info("%d UD states, %d LR state, %d FB states, %d on workq" % (UD_count, LR_count, FB_count, len(workq)))
77
+
78
+            if UD_count == 735471 and LR_count == 735471 and FB_count == 735471:
79
+                break
80
+
81
+        if state in explored:
82
+            continue
83
+        else:
84
+            explored.add(state)
85
+            keep_going = False
86
+
87
+            if UD_state not in UD_explored:
88
+                UD_explored[UD_state] = ' '.join(reverse_steps(steps))
89
+                keep_going = True
90
+
91
+            if LR_state not in LR_explored:
92
+                LR_explored[LR_state] = ' '.join(reverse_steps(steps))
93
+                keep_going = True
94
+
95
+            if FB_state not in FB_explored:
96
+                FB_explored[FB_state] = ' '.join(reverse_steps(steps))
97
+                keep_going = True
98
+
99
+            if not keep_going:
100
+                continue
101
+
102
+            # Only build the table 4-deep for now
103
+            if len(steps) == 4:
104
+                continue
105
+
106
+            prev_step = steps[-1]
107
+
108
+            for step in moves_4x4x4:
109
+
110
+                # U2 followed by U2 is a no-op
111
+                if step == prev_step and step.endswith("2"):
112
+                    continue
113
+
114
+                # U' followed by U is a no-op
115
+                if prev_step.endswith("'") and not step.endswith("'") and step == prev_step[0:-1]:
116
+                    continue
117
+
118
+                # U followed by U' is a no-op
119
+                if not prev_step.endswith("'") and step.endswith("'") and step[0:-1] == prev_step:
120
+                    continue
121
+
122
+                workq.append((steps + [step,], cube.state[:]))
123
+
124
+    log.info("cache end")
125
+
126
+    log.info("write start")
127
+
128
+    with open('UD_state.txt', 'w') as fh:
129
+        for key in sorted(UD_explored.keys()):
130
+            value = UD_explored[key]
131
+            fh.write("%s:%s\n" % (key, value))
132
+
133
+    with open('LR_state.txt', 'w') as fh:
134
+        for key in sorted(LR_explored.keys()):
135
+            value = LR_explored[key]
136
+            fh.write("%s:%s\n" % (key, value))
137
+
138
+    with open('FB_state.txt', 'w') as fh:
139
+        for key in sorted(FB_explored.keys()):
140
+            value = FB_explored[key]
141
+            fh.write("%s:%s\n" % (key, value))
142
+
143
+    log.info("write end")
144
+
145
+
146
+if __name__ == '__main__':
147
+
148
+    # setup logging
149
+    logging.basicConfig(level=logging.INFO,
150
+                        format='%(asctime)s %(filename)16s %(levelname)8s: %(message)s')
151
+    log = logging.getLogger(__name__)
152
+
153
+    build_state_table()