This commit is contained in:
DomNomNomVR 2025-12-20 01:47:30 +13:00
parent 4f792e5026
commit f707876581
3 changed files with 155 additions and 38 deletions

View File

@ -1,16 +1,28 @@
from multiprocessing import Pool, TimeoutError
from math import sqrt from math import sqrt
import shelve import shelve
from collections import deque from collections import deque
from dataclasses import dataclass, replace from dataclasses import dataclass, replace
from functools import reduce from functools import reduce
import random 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 from functools import lru_cache
import pytest import pytest
print() # print()
# twisting the bottom, opposing this color clockwise when this color is facing away # twisting the bottom, opposing this color clockwise when this color is facing away
# RBOG # RBOG
@ -35,7 +47,9 @@ class Corner:
return f"{self.col}{self.rot}" 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) @dataclass(frozen=True)
@ -51,7 +65,7 @@ class Middle:
class Skewb: class Skewb:
"""Represents a Rubics cube variant sold as 'Qiyi Twisty 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] top: Tuple[Corner, Corner, Corner, Corner]
bot: Tuple[Corner, Corner, Corner, Corner] bot: Tuple[Corner, Corner, Corner, Corner]
mids: Tuple[Middle, Middle, Middle, Middle, Middle] mids: Tuple[Middle, Middle, Middle, Middle, Middle]
@ -86,10 +100,10 @@ R1 = Corner("R", 1)
R2 = Corner("R", 2) R2 = Corner("R", 2)
GR = Middle("G", "R") GG = Middle("G", "G")
OR = Middle("O", "R") OG = Middle("O", "G")
BR = Middle("B", "R") BG = Middle("B", "G")
RR = Middle("R", "R") RG = Middle("R", "G")
GO = Middle("G", "O") GO = Middle("G", "O")
OO = Middle("O", "O") OO = Middle("O", "O")
BO = Middle("B", "O") BO = Middle("B", "O")
@ -103,7 +117,7 @@ YY = Middle("Y", "Y")
def rotate_mid_about_W(m: Middle) -> Middle: def rotate_mid_about_W(m: Middle) -> Middle:
return 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( solved_skewb = Skewb(
top=(G0, O0, B0, R0), top=(G0, O0, B0, R0),
bot=(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} 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: def clockwise_twist(s: Skewb, axis: Axis) -> Skewb:
@ -198,7 +215,7 @@ def clockwise_twist(s: Skewb, axis: Axis) -> Skewb:
), ),
Middle( Middle(
(m := s.mids[2]).col, (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", "O",
Skewb( 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 step_count = 0
while q and max_steps > 0: while q and max_steps > 0:
if step_count % 1000 == 0 and step_count: if step_count % 1000 == 0 and step_count:
print(f".", end="", flush=True) # print(f".", end="", flush=True)
if ( if (
bidirectional_fallback_threshold is not None bidirectional_fallback_threshold is not None
and step_count > bidirectional_fallback_threshold and step_count > bidirectional_fallback_threshold
@ -332,8 +349,18 @@ def test_breadth_first_search():
def bidirectional_search( def bidirectional_search(
start: Skewb, max_steps: int, end: Skewb = solved_skewb start: Skewb, max_steps: int, end: Skewb = solved_skewb
) -> list[Twist] | None: ) -> 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: if start == end:
return [] yield []
return
start.assert_valid() start.assert_valid()
q = deque([start]) q = deque([start])
q2 = deque([end]) q2 = deque([end])
@ -367,12 +394,14 @@ def bidirectional_search(
out = out.replace("LLL", "J") out = out.replace("LLL", "J")
return out return out
def on_meet(meet: Skewb) -> list[Twist]: # def on_meet(meet: Skewb) -> list[Twist]:
path = get_path(meet) # path = get_path(meet)
assert apply_twists(start, path) == end # assert apply_twists(start, path) == end
return path # return path
# print(f"{heuristic_list(end)=} {''.join(path)=} {instructions(path)=}") # # print(f"{heuristic_list(end)=} {''.join(path)=} {instructions(path)=}")
# return # # return
last_path = None
seen_paths: set[tuple[Twist, ...]] = set()
while q and max_steps > 0: while q and max_steps > 0:
max_steps -= 1 max_steps -= 1
@ -386,7 +415,20 @@ def bidirectional_search(
continue continue
skewb_to_twist2[child] = twist skewb_to_twist2[child] = twist
if child in skewb_to_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) q2.append(child)
parent = q.popleft() parent = q.popleft()
@ -398,11 +440,23 @@ def bidirectional_search(
skewb_to_twist[child] = twist skewb_to_twist[child] = twist
if child in skewb_to_twist2: 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) q.append(child)
print(f"{len(skewb_to_twist)}") print(f"{len(skewb_to_twist)}")
return None
def heuristic(got: Skewb) -> float: def heuristic(got: Skewb) -> float:
@ -541,7 +595,7 @@ def get_paths_from_heuristic(
# print(f"{mask=} {s=}", end=" ") # print(f"{mask=} {s=}", end=" ")
if sum(mask) == len(heuristic_permutation) - 3: if sum(mask) == len(heuristic_permutation) - 3:
# print("going bidirectional now.") # print("going bidirectional now.")
print("end-bi", end=" ", flush=True) # print("end-bi", end=" ", flush=True)
path = bidirectional_search(s, max_steps=200000) path = bidirectional_search(s, max_steps=200000)
else: else:
path = breadth_first_search( 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))) 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") @shelve_it("skewb_solver.evaluate_permutation.shelve.sqlite")
def evaluate_permutation( def evaluate_permutation(
heuristic_permutation: list[int], seed=4, sample_size: int = 10 heuristic_permutation: list[int], seed=4, sample_size: int = 10
) -> float: ) -> float:
scores = [] scores = []
rng = random.Random(seed) rng = random.Random(seed)
for i in range(sample_size): args = [(rng.randint(0, 2**63), heuristic_permutation) for i in range(sample_size)]
start = random_skewb(rng.randint(0, 2**63)) with Pool(processes=32) as pool:
print(f"{heuristic_permutation=} {start=}", end=" ") for score in pool.starmap(parallel_task, args):
paths = get_paths_from_heuristic(start, heuristic_permutation) print(f"{heuristic_permutation=} {score=}", flush=True)
score = score_fn([len(p) for p in paths])
print(f"{score=}")
scores.append(score) scores.append(score)
return sum(scores) / len(scores) 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] 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 i in range(len(hp)):
for j in range(1, i): for j in range(1, i):
hp[i], hp[j] = hp[j], hp[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=}") print(f"{hp=} {evaluation=}")
hp[i], hp[j] = hp[j], hp[i] 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__": if __name__ == "__main__":
hp = top_bot_mids = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] # 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_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_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(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 = 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)) # start = subtle_invalid = Skewb((O0, B0, R0, G2), (B0, R0, G1, O0), (BY, RY, GY, OY, YY))

View File

@ -57,6 +57,8 @@ hand: List[Card] = [
Card(suit="hearts", rank=10), Card(suit="hearts", rank=10),
] ]
print(hand[0].__str__())
def suit_first(card: Card) -> Tuple: def suit_first(card: Card) -> Tuple:
return (card.suit, card.rank) return (card.suit, card.rank)
@ -70,7 +72,7 @@ def rank_first(card: Card) -> Tuple:
# hand.sort(key=rank_first) # hand.sort(key=rank_first)
# print(hand) # print(hand)
print((1, "clubs", "cinnamon") < (1, "clubs", "vanilla")) # print((1, "clubs", "cinnamon") < (1, "clubs", "vanilla"))
# O(N ^ 2) # O(N ^ 2)