diff --git a/skewb_solver.evaluate_permutation.shelve.sqlite b/skewb_solver.evaluate_permutation.shelve.sqlite index 0378669..c469b78 100644 Binary files a/skewb_solver.evaluate_permutation.shelve.sqlite and b/skewb_solver.evaluate_permutation.shelve.sqlite differ diff --git a/skewb_solver.py b/skewb_solver.py index d37bdec..51740b5 100644 --- a/skewb_solver.py +++ b/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,19 +628,22 @@ 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=}") - scores.append(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)) diff --git a/starchild_teaching.py b/starchild_teaching.py index d8c9278..6127d48 100644 --- a/starchild_teaching.py +++ b/starchild_teaching.py @@ -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)