Browse Source

Added utils/lt_builder_exp.py

master
Daniel Walton 2 years 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 @@


- experiment with B-Trees
- unittests
- rename 'nodes' to 'child_nodes' or 'children'
- optimaly load the B-Trees
- support for loading the first two levels in memory and finding the rest on disk

- Once the two bullets above are done we should be able to find a key via
a single seek(). We should also be able to only do one rstrip/split(',')
call, it should only happen when 'if key in key_line' is True.

Today we take ~14s where our time is in:
- 6x6x6 UD edge pairing bulid 70 tables and merge them

- B-Trees...this will benefit all searches

- TPR solver
- how the hell does it work so fast?
- ideas
- before we start IDA we could see which EO edge mapping gives us the
shortest edge heuristic and stick with that one. We would need to go
back to the 0 line dummy table and
LookupTableIDA444TsaiPhase2.experimental_ida_search_complete()

Or we could build out a small lookup table (say 3 moves deep) for that
one edge mapping.

- xyzzy's 5x5x5 solver. It is a variation of the TPR solver so need to understand TPR first.
- he does an EO (edge orientation) step. If all larger solvers that use 5x5x5 for edge
pairing could also do EO then maybe we could toss the edge pairing code...there is a
ton of it and it is hideous.

- bidir IDA search
- I need to understand how the TPR solver works before I spend time on bidir IDA
- would allow us to find slighly shorter solutions than we do today
- may potentially allow us tackle much larger IDA searches...TBD
- first to try is staging all 5x5x5 centers via one phase
- second to try is staging all 5x5x5 centers via one phase
- combine phases 1 and 2 of tsai?
- start from a solved cube and work your way back to the scrambled cube
- requires us to build prune tables for each scrambled cube
- for 4x4x4 staging centers we can do this in XYZ minutes in python
- if we only build it out 4 steps deep we can do it in ~20s
- so this would have to be done in C and even then it would still
take some time. I think this is a strategy that could only be
used in a fewest moves challenge given how long it will take to
produce solutions.



B-Tree notes
============
- optimaly load the B-Trees
- support for loading the first two levels in memory and finding the rest on disk

- Once the two bullets above are done we should be able to find a key via
a single seek(). We should also be able to only do one rstrip/split(',')
call, it should only happen when 'if key in key_line' is True.

Today we take ~14s where our time is in:

974730 0.533 0.000 0.533 0.000 {method 'seek' of '_io.BufferedReader' objects}
1431684 0.721 0.000 0.721 0.000 {method 'decode' of 'bytes' objects}
@@ -22,25 +58,25 @@
195231 3.299 0.000 11.243 0.000 __init__.py:67(disk_get)
ncalls tottime percall cumtime percall filename:lineno(function)

So we should end up with
- seek() : should come down to 195231...should shave about 300ms
So we should end up with
- seek() : should come down to 195231...should shave about 300ms

- decode() : Will 'if key in key_line' work if it is not decoded to utf-8?
If so we can avoid all of these calls....should shave 721ms.
- decode() : Will 'if key in key_line' work if it is not decoded to utf-8?
If so we can avoid all of these calls....should shave 721ms.

- read() : should come down to 195231...should shave about 600ms
- read() : should come down to 195231...should shave about 600ms

- rstrip() : can only call this 'if key in key_line' which should chop 99% of them..should shave 1435ms
- rstrip() : can only call this 'if key in key_line' which should chop 99% of them..should shave 1435ms

- 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
This should shave 1451ms
- 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
This should shave 1451ms

- split() : can only call this 'if key in key_line' which should chop 99% of them..should shave 2557ms
- split() : can only call this 'if key in key_line' which should chop 99% of them..should shave 2557ms

That is about ~7000ms we can shave...would get us from 14s down to 7s which would be awesome
That is about ~7000ms we can shave...would get us from 14s down to 7s which would be awesome


- Test this against slow 6x6x6 UD oblique:
- Test this against slow 6x6x6 UD oblique:

./usr/bin/rubiks-cube-solver.py --cpu-max --state FBDDDFFUDRFBBLFLLURLDLLUFBLRFDUFLBLLFBFLRRBBFDRRDUBUFRBUBRDLUBFDRLBBRLRUFLBRBDUDFFFDBLUDBBLRDFUUDLBBBRRDRUDLBLDFRUDLLFFUUBFBUUFDLRUDUDBRRBBUFFDRRRDBULRRURULFDBRRULDDRUUULBLLFDFRRFDURFFLDUUBRUFDRFUBLDFULFBFDDUDLBLLRBL

@@ -56,12 +92,8 @@ This search takes about 5 million seek() calls when using binary search...the se



- make rotate_xxx use one less function call by using a dictionary?

- tsai
- explore TPR solver to see what tricks it is doing


misc notes
==========
- ida_search() needs to return all of the results at that theshold
Test on this one, 5x5x5-step20-LR-centers-stage finds a solution 12 steps long but I know there is one
that is 10 steps long (I found it via AStar)

+ 11
- 0
rubikscubennnsolver/RubiksCube444.py View File

@@ -610,6 +610,17 @@ class LookupTable444TsaiPhase2Edges(LookupTable):


class LookupTableIDA444TsaiPhase2(LookupTableIDA):
"""
lookup-table-4x4x4-step60.txt
=============================
1 steps has 60 entries (0 percent, 0.00x previous step)
2 steps has 744 entries (0 percent, 12.40x previous step)
3 steps has 11,224 entries (0 percent, 15.09x previous step)
4 steps has 158,608 entries (6 percent, 14.13x previous step)
5 steps has 2,349,908 entries (93 percent, 14.82x previous step)

Total: 2,520,544 entries
"""

def __init__(self, parent):
LookupTableIDA.__init__(

+ 153
- 0
utils/lt_builder_exp.py View File

@@ -0,0 +1,153 @@
#!/usr/bin/env python3

"""
experiment to see how long it takes to build the 4x4x4 centers staging
lookup tables for a scrambled cube. If you only go 4-deep it takes about
20s. Going deeper than that chews through too much memory on the workq.
The workq needs to be stored in a file.

We need to build 6 or 7 deep.
"""

from collections import deque
import logging
import os
import sys
from rubikscubennnsolver import reverse_steps
from rubikscubennnsolver.rotate_xxx import rotate_444
from rubikscubennnsolver.RubiksCube444 import (
moves_4x4x4,
solved_4x4x4,
RubiksCube444,
LookupTableIDA444ULFRBDCentersStage,
LookupTable444UDCentersStage,
LookupTable444LRCentersStage,
LookupTable444FBCentersStage,
)


def build_state_table():
cube = RubiksCube444('DRFDFRUFDURDDLLUFLDLLBLULFBUUFRBLBFLLUDDUFRBURBBRBDLLDURFFBBRUFUFDRFURBUDLDBDUFFBUDRRLDRBLFBRRLB', 'URFDLB')
cube.nuke_corners()
cube.nuke_edges()
original_state = cube.state[:]

log.info("cache start")
for side in (cube.sideU, cube.sideL, cube.sideF, cube.sideR, cube.sideB, cube.sideD):
for pos in side.center_pos:
if cube.state[pos] in ('U', 'D'):
cube.state[pos] = 'U'
elif cube.state[pos] in ('L', 'R'):
cube.state[pos] = 'L'
elif cube.state[pos] in ('F', 'B'):
cube.state[pos] = 'F'

cube.lt_UD_centers_stage = LookupTable444UDCentersStage(cube)
cube.lt_LR_centers_stage = LookupTable444LRCentersStage(cube)
cube.lt_FB_centers_stage = LookupTable444FBCentersStage(cube)
lt_ULFRBD_centers_stage = LookupTableIDA444ULFRBDCentersStage(cube)

workq = deque()
explored = set()
UD_explored = {}
LR_explored = {}
FB_explored = {}
count = 0

for step in moves_4x4x4:
workq.append(([step,], cube.state[:]))

while workq:
(steps, prev_state) = workq.popleft()
cube.state = rotate_444(prev_state, steps[-1])

state = lt_ULFRBD_centers_stage.state()
UD_state = cube.lt_UD_centers_stage.state()
LR_state = cube.lt_LR_centers_stage.state()
FB_state = cube.lt_FB_centers_stage.state()

count += 1

if count % 100000 == 0:
UD_count = len(UD_explored)
LR_count = len(LR_explored)
FB_count = len(FB_explored)

log.info("%d UD states, %d LR state, %d FB states, %d on workq" % (UD_count, LR_count, FB_count, len(workq)))

if UD_count == 735471 and LR_count == 735471 and FB_count == 735471:
break

if state in explored:
continue
else:
explored.add(state)
keep_going = False

if UD_state not in UD_explored:
UD_explored[UD_state] = ' '.join(reverse_steps(steps))
keep_going = True

if LR_state not in LR_explored:
LR_explored[LR_state] = ' '.join(reverse_steps(steps))
keep_going = True

if FB_state not in FB_explored:
FB_explored[FB_state] = ' '.join(reverse_steps(steps))
keep_going = True

if not keep_going:
continue

# Only build the table 4-deep for now
if len(steps) == 4:
continue

prev_step = steps[-1]

for step in moves_4x4x4:

# U2 followed by U2 is a no-op
if step == prev_step and step.endswith("2"):
continue

# U' followed by U is a no-op
if prev_step.endswith("'") and not step.endswith("'") and step == prev_step[0:-1]:
continue

# U followed by U' is a no-op
if not prev_step.endswith("'") and step.endswith("'") and step[0:-1] == prev_step:
continue

workq.append((steps + [step,], cube.state[:]))

log.info("cache end")

log.info("write start")

with open('UD_state.txt', 'w') as fh:
for key in sorted(UD_explored.keys()):
value = UD_explored[key]
fh.write("%s:%s\n" % (key, value))

with open('LR_state.txt', 'w') as fh:
for key in sorted(LR_explored.keys()):
value = LR_explored[key]
fh.write("%s:%s\n" % (key, value))

with open('FB_state.txt', 'w') as fh:
for key in sorted(FB_explored.keys()):
value = FB_explored[key]
fh.write("%s:%s\n" % (key, value))

log.info("write end")


if __name__ == '__main__':

# setup logging
logging.basicConfig(level=logging.INFO,
format='%(asctime)s %(filename)16s %(levelname)8s: %(message)s')
log = logging.getLogger(__name__)

build_state_table()

Loading…
Cancel
Save