aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/speedcubing/coordinates/coordinates.md280
1 files changed, 280 insertions, 0 deletions
diff --git a/src/speedcubing/coordinates/coordinates.md b/src/speedcubing/coordinates/coordinates.md
new file mode 100644
index 0000000..5640bca
--- /dev/null
+++ b/src/speedcubing/coordinates/coordinates.md
@@ -0,0 +1,280 @@
1# Cube coordinates
2
3In this page I describe the way I implemented *coordinates*, in the
4sense of [Cube Explorer](http://kociemba.org/cube.htm), in one of the
5versions of [Nissy](https://nissy.tronto.net).
6
7Unfortunately, this implementation got lost between rewrites and is
8currently not included in any working version of Nissy or other projects,
9so it this page is of theoretical interest only. Some code is available
10in the [nissy-nx repository](https://git.tronto.net/nissy-nx), but it
11is currently broken (what is described in this page works, though).
12
13## What are cube coordinates?
14
15A *cube coordinate* is a way of associating to any legal position
16of the Rubik's cube an integer in a certain range [0..n-1]. For
17example, any way of encoding the permutation of the corners as a
18number from 0 to 8!-1 is a valid coordinate; another example is
19an encoding of the orientation of the edges with respect to a given
20axis as a number in [0..2^11].
21
22In other words, a coordinate is a function from the set of legal
23configurations to [0..n-1]. Coordinates need not be surjective,
24though it is more convenient if they are, and in my examples they
25will be.
26
27To work with cube coordinates, we need to implement the following:
28
29* An integer `n`, the maximum value of a coordinate +1.
30* A function `index()` that takes a cube as input and returns an integer
31 in the range [0..n-1] as output.
32* A function `to_cube()` that takes an integer in [0..n-1] as input and
33 returns a cube `c` such that `index(to_cube(x)) == x` for every x in
34 [0..n-1]. In other words, `to_cube()` is a *section* of `index()`.
35 In practice, it is not necessary that the output of `to_cube()` is a
36 valid cube object.
37* A function `move()` that takes as input a coordinate, a move m and an
38 integer x in [0..n-1] and returns an integer y in [0..n-1] such
39 that `y == index(m(to_cube(x)))` (here m(c) is the cube c moved by m).
40* If applicable, a `transform()` function that applies a transformation
41 in a similar way as `move()` applies a move. For coordinates this is
42 not possible, because they do not capture enough of the cube state.
43 For some coordinates, only some transformations are possible. For
44 example, for the edge orientation coordinate only the transformations
45 that fix the axis with respect to which the edge orientation is defined
46 can be applied.
47
48For the implementation, see
49[coord.c](https://git.tronto.net/nissy-nx/file/src/coord.c.html)
50and
51[coord.h](https://git.tronto.net/nissy-nx/file/src/coord.h.html),
52as well as the definition of
53[coordinate](https://git.tronto.net/nissy-nx/file/src/cubetypes.h.html#l181)
54in cubetypes.h.
55
56## Coordinate types
57
58In my work I have defined four types of coordinates (but I actually use
59only three of them).
60
61### Basic coordinates
62
63Basic coordinates are the simplest kind, and they are completely defined
64by the integer `n` and the functions `index()` and `to_cube()` defined
65above. This is the type of coordinate that "does not exist" in the code,
66because they are just a special case of *composite coordinates*.
67
68### Composite coordinates (COMP_COORD)
69
70Composite coordinates are, like the name says, a composition of many basic
71coordinates. They are given by a list of basic coordinates (n_1, index_1(),
72to_cube_1()), ..., (n_k, index_k(), to_cube_k()). The value of such a
73composite coordinate on cube c is computed as
74index_1(c) + n_1 * (index_2(c) + n_2 * (...)).
75
76### Symmetric coordinates (SYM_COORD)
77
78A symmetric coordinate consists of a basic coordinate reduced by symmetry.
79Symmetric coordinates must be initialized from a given set of *cube
80transformations* before they can be used. The initialization step produces
81the following data:
82
83* A table that associates every possible value of the basic coordinate
84 with its class in the symmetric coordinate. Computing the value of the
85 symmetric coordinate will then amount to computing the basic coordinate
86 via `index()` and looking up the value in this table.
87* A table that associates every possible value of the basic coordinate
88 with a fixed representative for its class.
89* A table that associates every possible value of the basic coordinate
90 the cube transformation that brings it to its representative.
91* A table that associates every possible value of the basic coordinate
92 with the list of cube transformations that do not affect it, that is
93 a list of *self-symmetries*. This will be useful when computing the
94 pruning table associated to this coordinate.
95
96### Symmetric-composite coordinates (SYMCOMP_COORD)
97
98A symmetric-composite coordinate is based on two other coordinates, a
99symmetric coordinate and a composite coordinate. To compute the value
100of a symmetric-composite coordinate one must compute the symmetric
101coordinate value first, then transform the cube using the transformation
102that brings the basic coordinate associated to the symmetric coordinate
103to its representative in order to compute the correct value for the
104composite coordinate, and finally combine the two.
105
106More precisely and with less tong-twisting, for a given cube c one must
107take the following steps:
108
109* Compute the value x_s of the symmetric coordinate at c, the value
110 x_b of the *basic* coordinate associated with the symmetric coordinate
111 and the value x_c of the composite coordinate.
112* Read from the table the transformation t that brings x_b to its
113 representative.
114* Apply t to the composite coordinate value x_c to obtain x_t.
115* Compute the value x_s * n_c + x_t, where n_c is the maximum value +1
116 of the composite coordinate.
117
118## Moving coordinates
119
120A move is applied to a coordinate by lookup into a transition table,
121pretty much as explained in Jaap's
122[computer puzzling page](https://www.jaapsch.net/puzzles/compcube.htm#trans).
123These transition tables are initialized once and for all at the beginning,
124and in my implementation I actually saved them to a file to speed up
125subsequent runs.
126
127Some extra care has to be taken in dealing with symmetries.
128
129### Basic and composite coordinates
130
131This case is quite simple: the transition table with something along these
132lines:
133
134```
135for i in [0..n-1]
136 for m in Moves
137 move_table[m][i] = index(m(to_cube(i)))
138```
139
140### Symmetric coordinates
141
142As mentioned above, this case and the next are tricky, because we need
143to keep track of which transformation has to be applied to get from the
144representative of the symmetry class to our actual cube.
145
146You can imagine a move applied to symmetric coordinate as if we just
147move between different representatives. The actual cube can be recovered
148from this by keeping track of an *offset transformation*, and updating
149it after every move.
150
151The necessary tables can be generated with something like this:
152
153```
154for i in [0..n-1]
155 for m in Moves
156 j = m(rep of i) /* Apply as basic coordinate */
157 move_table[m][i] = class of j
158 offset_table[m][i] = transformation bringing j to its rep
159```
160
161### Symmetric-composite coordinates
162
163A symmetric-composite coordinate requires some extra work, but the hard
164part is already handled by its base symmetric coordinate.
165
166To move a symmetric-composite coordinate, first we recover the value s
167of its symmetric coordinate's and the value c of its composite coordinate
168by taking the quotient and the remainder of division by the maximum value
169of the composite coordinate.
170
171Then we apply the move to the values s and c using their respective
172transition tables. The offset transformation is taken from the symmetric
173coordinate, and apply it to the result of the move on the composite coordinate.
174
175This may be summarized with the following pseudo-code:
176
177```
178s = ind / M /* M is the maximum value +1 of the composite coordinate */
179c = ind % M
180new_s = move_table_s[m][s] /* Using the table for the symmetric coordinate */
181new_c = move_table_c[m][c] /* Using the table for the composite coordinate */
182offset = offset_table[m][s]
183new_c = offset(new_c) /* See next section for transformations */
184
185return (new_s * M + new_c, offset)
186```
187
188## Transforming coordinates
189
190When we talk about *cube transformations*, we are talking about
191conjugating a cube by a rotation, possibly combined with a mirroring
192along a fixed axis. More precisely, transforming a cube c is done with
193the following steps:
194
195* Start from a solved cube
196* (optional) Mirror it left-right
197* Apply the required rotation
198* Apply c (as a permutation / move sequence) to the rotated solved cube
199* Apply the inverse of the rotation
200* (optional) Mirror it left-right
201
202When working with coordinates, transformations are applied pretty much
203in the same way as moves, using a transition table. This time, though,
204symmetric coordinates are much less of a problem: any transformation
205on a symmetric coordinate is, by definition, trivial! Of course, this
206is only true if we limit ourselves to applying transformations that are
207part of the set used to "reduce" the symmetric coordinate; but we always
208do this anyway.
209
210## Pruning tables
211
212I'd like to mention pruning tables here, because their computation is
213quite straightforward when using coordinates.
214
215Say you want to compute a table that associates every possible value i in
216[0..n-1] of a coordinate with the minimum amount of moves required to
217solve any cube c such that `index(c) == i`. For the purpose of this page
218we assume one can afford to allocate enough bits so that the actual value
219can be stored.
220
221With coordinates, one can do the following, without ever going back to
222the full cube representation:
223
224```
225set all values of the pruning table to infinity
226set the value associated with the solved cube to 0
227for d in [1..20]
228 for i in [0..n-1]
229 if pruning_table[i] == d-1
230 for m in Moves
231 old = pruning_table[m(i)]
232 pruning_table[m(i)] = min(d, old)
233```
234
235This is conceptually very simple, but unfortunately it is not enough.
236Because of self-symmetries, not every position will be reached in this
237way for a symmetric-composite coordinate: there may be more ways to
238bring the base of the symmetric coordinate to its representative, each
239having a different effect on the composite coordinate, but we only pick
240one of them when building the move tables.
241
242Luckily, this can be solved because we have memorized for every position a
243list of all the transformations that keep it invariant (see above). Then
244it is enough to add the following loop at the end of every iteration of
245the outermost loop above:
246
247```
248 for i in [0..n-1]
249 for t in the set of self-symmetries of i/M
250 old = pruning_table[t(i)]
251 pruning_table[t(i)] = min(d, old)
252```
253
254The code for this can be found in
255[pruning.c](https://git.tronto.net/nissy-nx/file/src/pruning.c.html).
256The use of pthread made it more complicated, so it can be hard to follow
257without reading this page first.
258
259## Why using coordinates?
260
261Coordinates are necessary to compute pruning tables, which are fundamental
262when solving a cube using the methods described in Jaap's
263[computer puzzling page](https://www.jaapsch.net/puzzles/compcube.htm).
264
265However, using transition tables for moves and transformations is
266not necessary. It used to be an efficient way to perform moves on a
267cube, but with newer hardware the trade-offs between memory access and
268instruction execution are changing.
269
270One thing that is made particularly cumbersome by using coordinates
271instead of a full representation of the cube is computing the
272inverse. While it is possible to compute the inverse somewhat efficiently
273from a representation of the cube made of a smart selection of coordinate
274values, it is not necessarily efficient.
275See [fst.c](https://git.tronto.net/nissy-nx/file/src/fst.c.html#l113)
276for an implementation.
277
278For these reasons, I am not using the coordinate approach described here
279for my new work-in-progress solver (temporarily named
280[h48](https://git.tronto.net/h48)).

Generated with cgit - Back to sebastiano.tronto.net