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
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))

View File

@ -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)