skerwb
This commit is contained in:
parent
4f792e5026
commit
f707876581
Binary file not shown.
187
skewb_solver.py
187
skewb_solver.py
@ -1,16 +1,28 @@
|
||||
from multiprocessing import Pool, TimeoutError
|
||||
from math import sqrt
|
||||
import shelve
|
||||
from collections import deque
|
||||
from dataclasses import dataclass, replace
|
||||
from functools import reduce
|
||||
import random
|
||||
from typing import TYPE_CHECKING, Callable, Counter, Dict, List, Literal, Set, Tuple
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Callable,
|
||||
Counter,
|
||||
Dict,
|
||||
Iterable,
|
||||
Iterator,
|
||||
List,
|
||||
Literal,
|
||||
Set,
|
||||
Tuple,
|
||||
)
|
||||
from functools import lru_cache
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
print()
|
||||
# print()
|
||||
|
||||
# twisting the bottom, opposing this color clockwise when this color is facing away
|
||||
# RBOG
|
||||
@ -35,7 +47,9 @@ class Corner:
|
||||
return f"{self.col}{self.rot}"
|
||||
|
||||
|
||||
MidRotState = Literal["Y", "O", "R"]
|
||||
MidRotState = Literal[
|
||||
"Y", "O", "G"
|
||||
] # which direction it's pointing in (target middle solved colour)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -51,7 +65,7 @@ class Middle:
|
||||
class Skewb:
|
||||
"""Represents a Rubics cube variant sold as 'Qiyi Twisty Skewb'."""
|
||||
|
||||
# order: vertically below Axis primary color
|
||||
# order: screw down-right from start
|
||||
top: Tuple[Corner, Corner, Corner, Corner]
|
||||
bot: Tuple[Corner, Corner, Corner, Corner]
|
||||
mids: Tuple[Middle, Middle, Middle, Middle, Middle]
|
||||
@ -86,10 +100,10 @@ R1 = Corner("R", 1)
|
||||
R2 = Corner("R", 2)
|
||||
|
||||
|
||||
GR = Middle("G", "R")
|
||||
OR = Middle("O", "R")
|
||||
BR = Middle("B", "R")
|
||||
RR = Middle("R", "R")
|
||||
GG = Middle("G", "G")
|
||||
OG = Middle("O", "G")
|
||||
BG = Middle("B", "G")
|
||||
RG = Middle("R", "G")
|
||||
GO = Middle("G", "O")
|
||||
OO = Middle("O", "O")
|
||||
BO = Middle("B", "O")
|
||||
@ -103,7 +117,7 @@ YY = Middle("Y", "Y")
|
||||
|
||||
def rotate_mid_about_W(m: Middle) -> Middle:
|
||||
return Middle(
|
||||
col=m.col, rot="Y" if m.rot == "Y" else ("R" if m.rot == "O" else "O")
|
||||
col=m.col, rot="Y" if m.rot == "Y" else ("G" if m.rot == "O" else "O")
|
||||
)
|
||||
|
||||
|
||||
@ -122,6 +136,9 @@ def rotate_everything_about_W(s: Skewb) -> Skewb:
|
||||
)
|
||||
|
||||
|
||||
# 0 1 2 3
|
||||
# 3 0 1 2
|
||||
|
||||
solved_skewb = Skewb(
|
||||
top=(G0, O0, B0, R0),
|
||||
bot=(G0, O0, B0, R0),
|
||||
@ -158,7 +175,7 @@ def test_rotation_permutations(p: CornerRotPermutation):
|
||||
assert set(p.values()) == {0, 1, 2}
|
||||
|
||||
|
||||
MID_DIR_INCREMENT: dict[MidRotState, MidRotState] = {"Y": "O", "R": "Y", "O": "R"}
|
||||
MID_DIR_INCREMENT: dict[MidRotState, MidRotState] = {"Y": "O", "G": "Y", "O": "G"}
|
||||
|
||||
|
||||
def clockwise_twist(s: Skewb, axis: Axis) -> Skewb:
|
||||
@ -198,7 +215,7 @@ def clockwise_twist(s: Skewb, axis: Axis) -> Skewb:
|
||||
),
|
||||
Middle(
|
||||
(m := s.mids[2]).col,
|
||||
"Y" if m.col == "Y" else ("O" if m.rot == "Y" else "R"),
|
||||
"Y" if m.col == "Y" else ("O" if m.rot == "Y" else "G"),
|
||||
),
|
||||
),
|
||||
)
|
||||
@ -213,7 +230,7 @@ def clockwise_twist(s: Skewb, axis: Axis) -> Skewb:
|
||||
(
|
||||
"O",
|
||||
Skewb(
|
||||
top=(G0, O0, B0, O2), bot=(G1, R2, B0, R2), mids=(YY, OY, BY, GR, RR)
|
||||
top=(G0, O0, B0, O2), bot=(G1, R2, B0, R2), mids=(YY, OY, BY, GG, RG)
|
||||
),
|
||||
)
|
||||
],
|
||||
@ -299,7 +316,7 @@ def breadth_first_search(
|
||||
step_count = 0
|
||||
while q and max_steps > 0:
|
||||
if step_count % 1000 == 0 and step_count:
|
||||
print(f".", end="", flush=True)
|
||||
# print(f".", end="", flush=True)
|
||||
if (
|
||||
bidirectional_fallback_threshold is not None
|
||||
and step_count > bidirectional_fallback_threshold
|
||||
@ -332,8 +349,18 @@ def test_breadth_first_search():
|
||||
def bidirectional_search(
|
||||
start: Skewb, max_steps: int, end: Skewb = solved_skewb
|
||||
) -> list[Twist] | None:
|
||||
try:
|
||||
return next(bidirectional_search_all(start, max_steps, end))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
|
||||
def bidirectional_search_all(
|
||||
start: Skewb, max_steps: int, end: Skewb = solved_skewb
|
||||
) -> Iterator[list[Twist]]:
|
||||
if start == end:
|
||||
return []
|
||||
yield []
|
||||
return
|
||||
start.assert_valid()
|
||||
q = deque([start])
|
||||
q2 = deque([end])
|
||||
@ -367,12 +394,14 @@ def bidirectional_search(
|
||||
out = out.replace("LLL", "J")
|
||||
return out
|
||||
|
||||
def on_meet(meet: Skewb) -> list[Twist]:
|
||||
path = get_path(meet)
|
||||
assert apply_twists(start, path) == end
|
||||
return path
|
||||
# print(f"{heuristic_list(end)=} {''.join(path)=} {instructions(path)=}")
|
||||
# return
|
||||
# def on_meet(meet: Skewb) -> list[Twist]:
|
||||
# path = get_path(meet)
|
||||
# assert apply_twists(start, path) == end
|
||||
# return path
|
||||
# # print(f"{heuristic_list(end)=} {''.join(path)=} {instructions(path)=}")
|
||||
# # return
|
||||
last_path = None
|
||||
seen_paths: set[tuple[Twist, ...]] = set()
|
||||
|
||||
while q and max_steps > 0:
|
||||
max_steps -= 1
|
||||
@ -386,7 +415,20 @@ def bidirectional_search(
|
||||
continue
|
||||
skewb_to_twist2[child] = twist
|
||||
if child in skewb_to_twist:
|
||||
return on_meet(child)
|
||||
# DUPLICATED
|
||||
path = get_path(child)
|
||||
assert apply_twists(start, path) == end
|
||||
|
||||
# if last_path is not None and len(path) > len(last_path):
|
||||
# return
|
||||
# last_path = path
|
||||
|
||||
if tuple(path) not in seen_paths and (
|
||||
last_path is None or len(path) <= len(last_path)
|
||||
):
|
||||
yield path
|
||||
seen_paths.add(tuple(path))
|
||||
last_path = path
|
||||
q2.append(child)
|
||||
|
||||
parent = q.popleft()
|
||||
@ -398,11 +440,23 @@ def bidirectional_search(
|
||||
|
||||
skewb_to_twist[child] = twist
|
||||
if child in skewb_to_twist2:
|
||||
return on_meet(child)
|
||||
# DUPLICATED
|
||||
path = get_path(child)
|
||||
assert apply_twists(start, path) == end
|
||||
|
||||
# if last_path is not None and len(path) > len(last_path):
|
||||
# return
|
||||
# last_path = path
|
||||
|
||||
if tuple(path) not in seen_paths and (
|
||||
last_path is None or len(path) <= len(last_path)
|
||||
):
|
||||
yield path
|
||||
seen_paths.add(tuple(path))
|
||||
last_path = path
|
||||
q.append(child)
|
||||
|
||||
print(f"{len(skewb_to_twist)}")
|
||||
return None
|
||||
|
||||
|
||||
def heuristic(got: Skewb) -> float:
|
||||
@ -541,7 +595,7 @@ def get_paths_from_heuristic(
|
||||
# print(f"{mask=} {s=}", end=" ")
|
||||
if sum(mask) == len(heuristic_permutation) - 3:
|
||||
# print("going bidirectional now.")
|
||||
print("end-bi", end=" ", flush=True)
|
||||
# print("end-bi", end=" ", flush=True)
|
||||
path = bidirectional_search(s, max_steps=200000)
|
||||
else:
|
||||
path = breadth_first_search(
|
||||
@ -574,18 +628,21 @@ def score_fn(values: list[float]) -> float:
|
||||
return -sum(2**i * v for i, v in enumerate(sorted(values)))
|
||||
|
||||
|
||||
def parallel_task(seed: int, heuristic_permutation: list[int]):
|
||||
paths = get_paths_from_heuristic(random_skewb(seed), heuristic_permutation)
|
||||
return score_fn([len(p) for p in paths])
|
||||
|
||||
|
||||
@shelve_it("skewb_solver.evaluate_permutation.shelve.sqlite")
|
||||
def evaluate_permutation(
|
||||
heuristic_permutation: list[int], seed=4, sample_size: int = 10
|
||||
) -> float:
|
||||
scores = []
|
||||
rng = random.Random(seed)
|
||||
for i in range(sample_size):
|
||||
start = random_skewb(rng.randint(0, 2**63))
|
||||
print(f"{heuristic_permutation=} {start=}", end=" ")
|
||||
paths = get_paths_from_heuristic(start, heuristic_permutation)
|
||||
score = score_fn([len(p) for p in paths])
|
||||
print(f"{score=}")
|
||||
args = [(rng.randint(0, 2**63), heuristic_permutation) for i in range(sample_size)]
|
||||
with Pool(processes=32) as pool:
|
||||
for score in pool.starmap(parallel_task, args):
|
||||
print(f"{heuristic_permutation=} {score=}", flush=True)
|
||||
scores.append(score)
|
||||
return sum(scores) / len(scores)
|
||||
|
||||
@ -598,21 +655,79 @@ def evaluate_all_1_swaps(hp: list[int]):
|
||||
hp[i], hp[j] = hp[j], hp[i]
|
||||
|
||||
|
||||
def evaluate_all_1_swaps_except_first(hp: list[int]):
|
||||
def evaluate_all_1_swaps_except_first(hp: list[int], sample_size: int):
|
||||
for i in range(len(hp)):
|
||||
for j in range(1, i):
|
||||
hp[i], hp[j] = hp[j], hp[i]
|
||||
evaluation = evaluate_permutation(hp, sample_size=100)
|
||||
evaluation = evaluate_permutation(hp, sample_size=sample_size)
|
||||
print(f"{hp=} {evaluation=}")
|
||||
hp[i], hp[j] = hp[j], hp[i]
|
||||
|
||||
|
||||
def print_path_detailed(s: Skewb, paths: list[list[Twist]]):
|
||||
for path in paths:
|
||||
print(s, path)
|
||||
s = apply_twists(s, path)
|
||||
|
||||
|
||||
def reverse_path(path: List[Twist]) -> List[Twist]:
|
||||
return [to_opposite[t] for t in reversed(path)]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
hp = top_bot_mids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
hp = top_down_modified = [0, 1, 2, 3, 8, 9, 10, 11, 12, 4, 5, 6, 7]
|
||||
hp = top_down = [0, 1, 2, 3, 8, 9, 10, 11, 12, 4, 5, 6, 7]
|
||||
# hp = top_bot_mids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
# hp = top_down_modified = [0, 1, 2, 3, 8, 9, 10, 11, 12, 4, 5, 6, 7]
|
||||
# hp = top_down = [0, 1, 2, 3, 8, 9, 10, 11, 12, 4, 5, 6, 7]
|
||||
# hp = local_minimum = [0, 1, 6, 3, 8, 9, 10, 11, 12, 4, 5, 2, 7]
|
||||
# hp = second_1_swap_min = [0, 3, 6, 1, 8, 9, 10, 11, 12, 4, 5, 2, 7]
|
||||
# good_hps = [
|
||||
# [0, 1, 12, 3, 8, 9, 10, 11, 6, 4, 5, 2, 7],
|
||||
# [0, 1, 3, 6, 8, 9, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 1, 6, 3, 11, 9, 10, 8, 12, 4, 5, 2, 7],
|
||||
# [0, 1, 6, 8, 3, 9, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 1, 6, 9, 8, 3, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 1, 8, 3, 6, 9, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 3, 6, 1, 8, 9, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 5, 6, 3, 8, 9, 10, 11, 12, 4, 1, 2, 7],
|
||||
# [0, 8, 6, 3, 1, 9, 10, 11, 12, 4, 5, 2, 7],
|
||||
# [0, 10, 6, 3, 8, 9, 1, 11, 12, 4, 5, 2, 7],
|
||||
# ]
|
||||
# hp = second_1_swap_min_200 = [0, 3, 6, 1, 8, 9, 10, 11, 12, 4, 5, 2, 7]
|
||||
# start = Skewb(
|
||||
# (G0, O0, G2, R0),
|
||||
# (B2, R1, B1, O0),
|
||||
# (GY, OY, RO, YY, BO),
|
||||
# )
|
||||
# print_path_detailed(start, get_paths_from_heuristic(start, hp))
|
||||
|
||||
# start = Skewb(
|
||||
# (G0, O0, G2, R0),
|
||||
# (B2, R1, B1, O0),
|
||||
# (GY, OY, RO, YY, BO),
|
||||
# )
|
||||
|
||||
start = Skewb( # midswap
|
||||
top=(G0, R0, B0, O0),
|
||||
mids=(YY, RY, BY, OY, GO),
|
||||
bot=(G0, R0, B0, O0),
|
||||
)
|
||||
# start = Skewb(
|
||||
# top=(G0, O0, B1, R0),
|
||||
# mids=(YY, OY, GY, RY, BO),
|
||||
# bot=(B2, O2, G0, R0),
|
||||
# )
|
||||
print(bidirectional_search(start, 100000, solved_skewb))
|
||||
print(reverse_path(bidirectional_search(start, 100000, solved_skewb) or []))
|
||||
|
||||
print()
|
||||
for path in bidirectional_search_all(start, 100000, solved_skewb):
|
||||
print(path)
|
||||
# for hp in good_hps:
|
||||
# evaluate_permutation(hp, seed=64, sample_size=200)
|
||||
|
||||
# evaluate_all_1_swaps(top_down)
|
||||
evaluate_all_1_swaps_except_first(hp)
|
||||
# evaluate_all_1_swaps_except_first(hp, 100)
|
||||
# evaluate_all_1_swaps_except_first(hp, sample_size=10)
|
||||
|
||||
# start = hard = Skewb((O0, B0, R0, G2), (B0, R0, G1, O0), (BY, RY, GY, OY, YY))
|
||||
# start = subtle_invalid = Skewb((O0, B0, R0, G2), (B0, R0, G1, O0), (BY, RY, GY, OY, YY))
|
||||
|
||||
@ -57,6 +57,8 @@ hand: List[Card] = [
|
||||
Card(suit="hearts", rank=10),
|
||||
]
|
||||
|
||||
print(hand[0].__str__())
|
||||
|
||||
|
||||
def suit_first(card: Card) -> Tuple:
|
||||
return (card.suit, card.rank)
|
||||
@ -70,7 +72,7 @@ def rank_first(card: Card) -> Tuple:
|
||||
# hand.sort(key=rank_first)
|
||||
# print(hand)
|
||||
|
||||
print((1, "clubs", "cinnamon") < (1, "clubs", "vanilla"))
|
||||
# print((1, "clubs", "cinnamon") < (1, "clubs", "vanilla"))
|
||||
|
||||
# O(N ^ 2)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user