alphacube 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 1.2
2
+ Name: alphacube
3
+ Version: 0.1.0
4
+ Summary: A powerful & flexible Rubik's Cube solver
5
+ Home-page: https://alphacube.dev/
6
+ Author: Kyo Takano
7
+ License: MIT
8
+ Project-URL: Documentation, https://alphacube.dev/docs/index.html
9
+ Project-URL: Source, https://github.com/kyo-takano/alphacube
10
+ Description: UNKNOWN
11
+ Platform: UNKNOWN
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.6
18
+ Classifier: Programming Language :: Python :: 3.7
19
+ Classifier: Programming Language :: Python :: 3.8
20
+ Classifier: Programming Language :: Python :: 3.9
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Requires-Python: >= 3.6
@@ -0,0 +1,168 @@
1
+ # AlphaCube
2
+
3
+ AlphaCube is a powerful & flexible Rubik's Cube solver that extends [EfficientCube](https://github.com/kyo-takano/efficientcube).
4
+
5
+ ## App
6
+
7
+ We offer an interactive demo application for you to try out AlphaCube:
8
+ [alphacube.dev](https://alphacube.dev)
9
+
10
+ ## Installation
11
+
12
+ Open a terminal and execute the following command:
13
+
14
+ ```sh
15
+ pip install alphacube
16
+ ```
17
+
18
+ ## Usage
19
+
20
+ ### Basic
21
+
22
+ Using AlphaCube in Python is as simple as this:
23
+
24
+ ```python
25
+ import alphacube
26
+
27
+ # Load a trained DNN
28
+ alphacube.load(model_id="small") # the default model
29
+
30
+ # Solve the cube using a given scramble
31
+ result = alphacube.solve(
32
+ scramble="D U F2 L2 U' B2 F2 D L2 U R' F' D R' F' U L D' F' D R2",
33
+ beam_width=1024,
34
+ )
35
+ print(result)
36
+ ```
37
+
38
+ > **Output**
39
+ > ```python
40
+ > {
41
+ > 'solutions': [
42
+ > "D L D2 R' U2 D B' D' U2 B U2 B' U' B2 D B2 D' B2 F2 U2 F2"
43
+ > ],
44
+ > 'num_nodes': 19744,
45
+ > 'time':1.4068585219999659
46
+ > }
47
+ > ```
48
+
49
+ AlphaCube selects the smallest model by default and takes about a few seconds to find moderately good solution(s).
50
+
51
+ ### Better Solutions
52
+
53
+ If you want even shorter solutions, simply increase the `beam_width` parameter:
54
+
55
+ ```python
56
+ alphacube.load() # model_id="small"
57
+ result = alphacube.solve(
58
+ scramble="D U F2 L2 U' B2 F2 D L2 U R' F' D R' F' U L D' F' D R2",
59
+ beam_width=65536,
60
+ )
61
+ print(result)
62
+ ```
63
+
64
+ > **Output**
65
+ > ```python
66
+ > {
67
+ > 'solutions': [
68
+ > "D' R' D2 F' L2 F' U B F D L D' L B D2 R2 F2 R2 F'",
69
+ > "D2 L2 R' D' B D2 B' D B2 R2 U2 L' U L' D' U2 R' F2 R'"
70
+ > ],
71
+ > 'num_nodes': 968984,
72
+ > 'time': 45.690575091997744
73
+ > }
74
+ > ```
75
+
76
+ You generally get more and better solutions, at the sacrifice of an increased wall-clock time and computational cost.
77
+
78
+ ### Applying Ergonomic Bias
79
+
80
+ ```python
81
+ ergonomic_bias = {
82
+ "U": 0.9, "U'": 0.9, "U2": 0.8,
83
+ "R": 0.8, "R'": 0.8, "R2": 0.75,
84
+ "L": 0.55, "L'": 0.4, "L2": 0.3,
85
+ "F": 0.7, "F'": 0.6, "F2": 0.6,
86
+ "D": 0.3, "D'": 0.3, "D2": 0.2,
87
+ "B": 0.05, "B'": 0.05, "B2": 0.01,
88
+ "u": 0.45, "u'": 0.45, "u2": 0.4,
89
+ "r": 0.3, "r'": 0.3, "r2": 0.25,
90
+ "l": 0.2, "l'": 0.2, "l2": 0.15,
91
+ "f": 0.35, "f'": 0.3, "f2": 0.25,
92
+ "d": 0.15, "d'": 0.15, "d2": 0.1,
93
+ "b": 0.03, "b'": 0.03, "b2": 0.01
94
+ } # Each value represents the desirability score
95
+
96
+ result = alphacube.solve(
97
+ scramble="D U F2 L2 U' B2 F2 D L2 U R' F' D R' F' U L D' F' D R2",
98
+ beam_width=65536,
99
+ ergonomic_bias=ergonomic_bias
100
+ )
101
+ print(result)
102
+ ```
103
+
104
+ > **Output**
105
+ > ```python
106
+ > {
107
+ > 'solutions': [
108
+ > "u' U' f' R2 U2 R' L' F' R D2 f2 R2 U2 R U L' U R L",
109
+ > "u' U' f' R2 U2 R' L' F' R D2 f2 R2 U2 R d F' U f F",
110
+ > "u' U' f' R2 U2 R' L' F' R u2 F2 R2 D2 R u f' l u U"
111
+ > ],
112
+ > 'num_nodes': 1078054,
113
+ > 'time': 56.13087955299852
114
+ > }
115
+ > ```
116
+
117
+ ### GPU Acceleration
118
+
119
+ If you have a GPU, we highly recommend you select the largest model for the best possible performance:
120
+
121
+ ```python
122
+ alphacube.load(model_id="large")
123
+ result = alphacube.solve(
124
+ scramble="D U F2 L2 U' B2 F2 D L2 U R' F' D R' F' U L D' F' D R2",
125
+ beam_width=65536,
126
+ )
127
+ print(result)
128
+ ```
129
+
130
+ > **Output**
131
+ > ```python
132
+ > {
133
+ > 'solutions': ["D F L' F' U2 B2 U F' L R2 B2 U D' F2 U2 R D'"],
134
+ > 'num_nodes': 903448,
135
+ > 'time': 20.46845487099995
136
+ > }
137
+ > ```
138
+
139
+ You will get even better solutions in an order of magnitude smaller amount of time.
140
+
141
+ On the contrary, using a model larger than `"small"` (i.e., `"base"` or `"large"`) *on CPU* will likely result in inferior temporal performance.
142
+
143
+ ### Verbose Mode
144
+
145
+ Additionally, you may call `alphacube.set_verbose()` to keep track of the progress. It will display the current depth of search on your terminal.
146
+
147
+ Please refer to our [documentation](https://alphacube.dev/docs) for more, especially ["Getting Started"](https://alphacube.dev/docs/getting-started/index.html)
148
+
149
+ ## How It Works
150
+
151
+ At the heart of AlphaCube lies a methodology rooted in the paper titled ["Self-Supervision is All You Need for Solving Rubik's Cube" (TMLR'23)](https://openreview.net/forum?id=bnBeNFB27b), the official code of which is also available as a [GitHub repository](https://github.com/kyo-takano/efficientcube).
152
+ This project releases the three largest solvers trained in Half-Turn Metric (HTM) as described in **Section 7. Scaling Law**.
153
+
154
+ In EfficientCube, a Deep Neural Network (DNN) is trained to predict the **probability distribution of the next moves** that would bring a given state one step closer to the goal.
155
+ These predicted moves are then applied sequentially to solve a given scramble.
156
+
157
+ This project releases the three largest, compute-optimally trained solvers as described in **Section 7. Scaling Law**.
158
+
159
+ You may also read ["How It Works"](https://alphacube.dev/docs/how-it-works/index.html) in the documentation for illustrated descriptions.
160
+
161
+ ## Contributing
162
+
163
+ You are more than welcome to collaborate on AlphaCube.
164
+ If you're interested, please read our [CONTRIBUTING](https://github.com/kyo-takano/alphacube/CONTRIBUTING.md) guide to get started.
165
+
166
+ ## License
167
+
168
+ AlphaCube is open-sourced under the [MIT license](https://github.com/kyo-takano/alphacube/LICENSE), granting you the freedom to use and modify it.
@@ -0,0 +1,161 @@
1
+ """
2
+ This package provides functions to load a deep neural Rubik's Cube solver (DNN) and to solve a scrambled Rubik's Cube with it.
3
+ It also offers a command-line utility for solving a Rubik's Cube using AlphaCube.
4
+
5
+ Functions:
6
+
7
+ - ``load(*args, **kwargs)``: Load a trained DNN.
8
+ - ``solve(*args, **kwargs)``: Solve a Rubik's Cube using the loaded solver.
9
+ - ``cli()``: Command-line utility for solving a Rubik's Cube using AlphaCube.
10
+ - ``set_verbose(loglevel=logging.INFO)``: Set the verbosity level of the logger.
11
+
12
+ Example::
13
+
14
+ import alphacube
15
+ alphacube.load(model_id="base")
16
+ solution = alphacube.solve(format='moves', scramble="R U R' U'", beam_width=1024)
17
+
18
+ """
19
+ import logging
20
+ from rich.console import Console
21
+ from rich.logging import RichHandler
22
+
23
+ logger = logging.getLogger("rich")
24
+ logger.propagate = False
25
+ logger.addHandler(RichHandler(console=Console(stderr=True)))
26
+ logger.setLevel(logging.WARNING)
27
+ logargs = dict(extra={"markup": True})
28
+
29
+ # Set up logging level
30
+ def set_verbose(loglevel=logging.INFO):
31
+ """
32
+ Set the verbosity level of the logger.
33
+
34
+ Args:
35
+ loglevel (int): Logging level (e.g., logging.INFO, logging.DEBUG) to control the verbosity.
36
+
37
+ Returns:
38
+ None
39
+ """
40
+ global logger
41
+ logger.setLevel(loglevel)
42
+
43
+ from ._validator import Input
44
+ from .solver import Solver
45
+
46
+ _solver = Solver()
47
+
48
+ def load(*args, **kwargs):
49
+ """
50
+ Load the Rubik's Cube solver model.
51
+
52
+ Args:
53
+ *args, **kwargs: Arguments to configure model loading.
54
+
55
+ Returns:
56
+ None
57
+ """
58
+ _solver.load(*args, **kwargs)
59
+
60
+ def solve(*args, **kwargs):
61
+ """
62
+ Solve a Rubik's Cube puzzle using the loaded solver model.
63
+
64
+ Args:
65
+ *args, **kwargs: Arguments to configure puzzle solving; passed down to ``alphacube.solver.Solver.__call__()`` and ``alphacube.search.beam_search``.
66
+
67
+ Returns:
68
+ dict | None: A dictionary containing solutions and performance metrics. None if failed.
69
+ """
70
+ if _solver.model is None:
71
+ raise ValueError('Model not loaded. Call `load` with appropriate arguments first.')
72
+
73
+ return _solver(*args, **kwargs)
74
+
75
+ # CLI Option
76
+ def cli():
77
+ """
78
+ Command-line utility for solving a Rubik's Cube using AlphaCube.
79
+
80
+ Usage::
81
+
82
+ alphacube [--model_id MODEL_ID] [--format FORMAT] [--scramble SCRAMBLE]
83
+ [--beam_width BEAM_WIDTH] [--extra_depths EXTRA_DEPTHS]
84
+
85
+ Command-line Arguments:
86
+
87
+ * ``--model_id``, ``-m`` (str): Choose a specific model for solving (default: 'small').
88
+ * ``--format``, ``-f`` (str): Specify the input format ('moves' or 'stickers').
89
+ * ``--scramble``, ``-s`` (str): Define the initial cube state using either a sequence of moves or a stringify JSON dictionary.
90
+ * ``--beam_width``, ``-bw`` (int): Set the beam width for search (default: 1024).
91
+ * ``--extra_depths``, ``-ex`` (int): Specify additional depths for exploration (default: 0).
92
+ * ``--verbose``, ``-v``: Enable verbose output for debugging and tracking progress.
93
+
94
+ Example Usages:
95
+
96
+ 1. Solve a cube using default settings::
97
+
98
+ alphacube --scramble "R U R' U'"
99
+
100
+ 2. Solve a cube with custom settings::
101
+
102
+ alphacube --model_id large --beam_width 128 --extra_depths 2 \\
103
+ --scramble "R U2 F' R2 B R' U' L B2 D' U2 R F L"
104
+
105
+ 3. Solve a cube using a sticker representation::
106
+
107
+ alphacube --format stickers \\
108
+ --scramble '{ \\
109
+ "U": [0, 0, 5, 5, 0, 5, 5, 4, 0], \\
110
+ "D": [1, 3, 3, 4, 1, 1, 4, 1, 3], \\
111
+ "L": [4, 5, 1, 0, 2, 2, 0, 1, 4], \\
112
+ "R": [5, 2, 0, 2, 3, 3, 2, 0, 2], \\
113
+ "F": [4, 3, 2, 4, 5, 1, 1, 4, 3], \\
114
+ "B": [1, 5, 5, 0, 4, 3, 3, 2, 2] \\
115
+ }' \\
116
+ --beam_width 64
117
+
118
+ :return: None
119
+ """
120
+ from rich import print
121
+
122
+ import argparse
123
+ parser = argparse.ArgumentParser(description="AlphaCube -- State-of-the-Art Rubik's Cube Solver")
124
+ parser.add_argument("--model_id", "-m", type=str, default="small", help="ID of the model to solve a given scramble with (`small`, `base`, or `large`)")
125
+ parser.add_argument("--format", "-f", type=str, default="moves", help="Format of the input scramble (`moves` or `stickers`)")
126
+ parser.add_argument("--scramble", "-s", type=str, help="Sequence of scramble in HTM (including wide moves) or dictionary of sticker indices by face.")
127
+ parser.add_argument("--beam_width", "-bw", type=int, default=1024, help="Beam width, the parameter strongly correlated with solution optimality and computation time")
128
+ parser.add_argument("--extra_depths", "-ex", type=int, default=0, help="Number of additional depths to explore during the search")
129
+ parser.add_argument('--verbose', '-v', dest="loglevel", action="store_const", const=logging.INFO, help="Enable verbose output for tracking progress")
130
+ args = parser.parse_args()
131
+
132
+ if args.loglevel:
133
+ set_verbose(args.loglevel)
134
+
135
+ # Manual validation
136
+ model_id = args.model_id
137
+ assert model_id in ["small", "base","large"], "Model ID must be either 'base' or 'large'"
138
+
139
+ logger.info(args)
140
+
141
+ # Pydantic validation
142
+ args = Input(
143
+ format=args.format,
144
+ scramble=args.scramble,
145
+ beam_width=args.beam_width,
146
+ extra_depths=args.extra_depths,
147
+ ergonomic_bias=None
148
+ )
149
+
150
+ # Load only once validated
151
+ _solver.load(model_id=model_id)
152
+ # Solve
153
+ solutions = _solver(
154
+ format=args.format,
155
+ scramble=args.scramble,
156
+ beam_width=args.beam_width,
157
+ extra_depths=args.extra_depths
158
+ )
159
+ print(solutions)
160
+
161
+ __all__ = ['load', 'solve', "cli"]
@@ -0,0 +1,149 @@
1
+ """
2
+ Input Validator
3
+
4
+ This module defines the input validator for the AlphaCube application. It includes a Pydantic `Input` class that validates input data for the application's tasks. The input data must adhere to specific format and validation rules.
5
+
6
+ Validation Rules:
7
+
8
+ - ``format`` attribute must be either 'moves' or 'stickers'.
9
+ - For 'moves' format, the `scramble` attribute must consist of valid moves.
10
+ - For 'stickers' format (not implemented), additional validation may be performed.
11
+ - ``beam_width`` attribute must be a positive integer.
12
+ - ``extra_depths`` attribute must be a non-negative integer.
13
+
14
+ Usage Example::
15
+
16
+ input_data = {
17
+ 'format': 'moves',
18
+ 'scramble': "R U R' U'",
19
+ 'beam_width': 256,
20
+ 'extra_depths': 2,
21
+ 'ergonomic_bias': None
22
+ }
23
+ validated_input = Input(**input_data)
24
+
25
+ Note:
26
+
27
+ The ergonomic bias information is optional and not strictly validated.
28
+
29
+ """
30
+
31
+ from pydantic import BaseModel, validator, model_validator
32
+ from typing import Optional
33
+ import json
34
+ import re
35
+
36
+ class Input(BaseModel):
37
+ """
38
+ Input validator (& preprocessor) for AlphaCube.
39
+ """
40
+
41
+ format: str # The format of the input data. Must be either 'moves' or 'stickers'.
42
+ scramble: list # The scramble data in the specified format.
43
+ beam_width: int # The beam width for the task. Must be a positive integer.
44
+ extra_depths: int # The number of extra depths for the task. Must be a non-negative integer.
45
+ ergonomic_bias: Optional[dict] # Optional ergonomic bias information (not strictly validated).
46
+
47
+ @validator('format')
48
+ @classmethod
49
+ def validate_format(cls, value: str) -> str:
50
+ """
51
+ Validate the input format.
52
+
53
+ Args:
54
+ value (str): The format value to be validated.
55
+
56
+ Returns:
57
+ str: The validated format value.
58
+
59
+ Raises:
60
+ ValueError: If the format is not 'moves' or 'stickers'.
61
+ """
62
+ if value not in ["moves", "stickers"]:
63
+ raise ValueError("Invalid input format. Must be either 'moves' or 'stickers'.")
64
+ return value
65
+
66
+ @validator("beam_width")
67
+ @classmethod
68
+ def validate_beam_width(cls, value):
69
+ """
70
+ Validate the beam width.
71
+
72
+ Args:
73
+ value: The beam width value to be validated.
74
+
75
+ Returns:
76
+ int: The validated beam width value.
77
+
78
+ Raises:
79
+ ValueError: If the beam width is not a positive integer.
80
+ """
81
+ if value <= 0:
82
+ raise ValueError("Beam width must be a positive integer.")
83
+ return value
84
+
85
+ @validator("extra_depths")
86
+ @classmethod
87
+ def validate_extra_depths(cls, value):
88
+ """
89
+ Validate the extra depths.
90
+
91
+ Args:
92
+ value: The extra depths value to be validated.
93
+
94
+ Returns:
95
+ int: The validated extra depths value.
96
+
97
+ Raises:
98
+ ValueError: If the extra depths value is negative.
99
+ """
100
+ if value < 0:
101
+ raise ValueError("Extra depths must be a non-negative integer.")
102
+ return value
103
+
104
+ @model_validator(mode='before')
105
+ @classmethod
106
+ def validate_scramble(cls, values):
107
+ """
108
+ Validate & preprocess the scramble data based on the chosen format.
109
+
110
+ Args:
111
+ values (dict): The input values.
112
+
113
+ Returns:
114
+ dict: The input values with validated scramble data.
115
+
116
+ Raises:
117
+ ValueError: If there are invalid moves in 'scramble' for 'moves' format.
118
+ ValueError: If unexpected center-piece configuration in 'scramble' for 'stickers' format (not implemented).
119
+
120
+ Todo:
121
+ Also check for potential orientation, permutation, parity
122
+ """
123
+
124
+ format = values["format"]
125
+ scramble = values["scramble"]
126
+
127
+ if format == "moves":
128
+ if isinstance(scramble, str):
129
+ scramble = scramble.split()
130
+ scramble = [m.replace("2'", "2") for m in scramble]
131
+ invalid_moves = [m for m in scramble if not re.match(r"^[UDLRFBudlrfb'2]{1,2}$", m)] # no wide moves -- yet
132
+ if invalid_moves:
133
+ raise ValueError(f"Invalid move{'s' if len(invalid_moves) > 1 else ''} in `scramble`: {invalid_moves}")
134
+ elif format == "stickers":
135
+ if isinstance(scramble, str):
136
+ scramble = json.loads(scramble.replace("\\\n", ""))
137
+ if isinstance(scramble, dict):
138
+ sticker_colors = [scramble[face] for face in "UDLRBF"]
139
+ # Reset axes if centers are modified
140
+ center_indices = [stickers[4] for stickers in sticker_colors]
141
+ if sorted(center_indices) != list(range(6)):
142
+ raise ValueError('Unexpected center-piece configuration.')
143
+ # Deconstruct a dict of lists to a flat list
144
+ scramble = sum(sticker_colors, [])
145
+
146
+ # Update
147
+ values["scramble"] = scramble
148
+
149
+ return values