diff options
Diffstat (limited to 'doc/h48.md')
| -rw-r--r-- | doc/h48.md | 124 |
1 files changed, 50 insertions, 74 deletions
| @@ -1,9 +1,8 @@ | |||
| 1 | # The H48 optimal solver | 1 | # The H48 optimal solver |
| 2 | 2 | ||
| 3 | This document contains information on the H48 Rubik's Cube optimal solver. | 3 | This document contains information on the H48 Rubik's Cube optimal solver. |
| 4 | The implementation of the solver is still in progress. This document | 4 | This solver is occasionally improved and optimized, and this document |
| 5 | partly describes ideas that have not been implemented yet, and it will | 5 | is updated accordingly to reflect the current implementation. |
| 6 | be updated to reflect the actual implementation. | ||
| 7 | 6 | ||
| 8 | I highly encourage the reader to check out Jaap Scherphuis' | 7 | I highly encourage the reader to check out Jaap Scherphuis' |
| 9 | [Computer Puzzling page](https://www.jaapsch.net/puzzles/compcube.htm) | 8 | [Computer Puzzling page](https://www.jaapsch.net/puzzles/compcube.htm) |
| @@ -215,46 +214,40 @@ stored in a single 32 bit integer, so that the table uses less than 12MB. | |||
| 215 | 214 | ||
| 216 | ### Getting the pruning value | 215 | ### Getting the pruning value |
| 217 | 216 | ||
| 218 | Once we have computed the full h0 coordinate, we can access the correct | 217 | Once we have computed the full h0 coordinate, we can access |
| 219 | entry in the full pruning table. As mentioned above, the pruning table | 218 | the correct entry in the full pruning table. We chose to |
| 220 | can be one of three kinds: | 219 | use 2 bits per entry, as described by Rokicki in the [nxopt |
| 220 | document](https://github.com/rokicki/cube20src/blob/master/nxopt.md). | ||
| 221 | This means that every pruning table needs to have an associated *base | ||
| 222 | value*, that determines the offset to be added to each entry (each entry | ||
| 223 | can only be 0, 1, 2 or 3). If the base value is `b`, a pruning value | ||
| 224 | of 1, 2 or 3 can be used directly as a lower bound of b+1, b+2 and b+3 | ||
| 225 | respectively. However, a value of 0 could mean that the actual lower | ||
| 226 | bound is anything between 0 and b, so we cannot take b as a lower bound. | ||
| 221 | 227 | ||
| 222 | * 4 bits per entry, or `k4`: In this case the pruning value (between 0 | 228 | To be able to still use some sort of pruning value even when we get a |
| 223 | and 15) can be simply read off the table. | 229 | 0 read, we use a **fallback table**. Inspired by nxopt, this table is |
| 224 | * 2 bits per entry, or `k2`: Tables of this kind work as described by | 230 | interleaved with the main table for cache efficiency: every 254 entries |
| 225 | Rokicki in the | 231 | (508 bits), we store in 4 bits the minimum of the pruning values of these |
| 226 | [nxopt document](https://github.com/rokicki/cube20src/blob/master/nxopt.md). | 232 | 254 entries as a number from 0 to 16, without subtracting the table's |
| 227 | In this case the pruning table also has a *base value*, that determines | 233 | base. Since a cache line is 512 bits long, we never get a cache miss |
| 228 | the offset to be added to each entry (each entry can only be 0, 1, 2 or 3). | 234 | when looking up the minimum value in the fallback table after a 0 read. |
| 229 | If the base value is `b`, a pruning value of 1, 2 or 3 can be used directly | 235 | Smaller lines (of 256 or 128 bits) have been tried, but they do not |
| 230 | as a lower bound of b+1, b+2 and b+3 respectively. However, a value of 0 | 236 | give any significant improvement over 512 bit lines. |
| 231 | could mean that the actual lower bound is anything between 0 and b, so we | ||
| 232 | cannot take b as a lower bound. Instead we have to use a pruning value from | ||
| 233 | another table - see the section "Fallback tables" below. | ||
| 234 | * 1 bit per entry, or `k1`: With one bit per entry, the only information we | ||
| 235 | can get from the pruning table is wether or not the current position | ||
| 236 | requires more or fewer moves than a fixed base value b. This can still be | ||
| 237 | valuable if most positions are more or less equally split between two | ||
| 238 | pruning values. | ||
| 239 | (Work in progress - `k1` tables not available in the code yet) | ||
| 240 | 237 | ||
| 241 | ### Fallback tables | 238 | Moreover, as an additional heuristic, in case of a 0 read we also look |
| 239 | up another pruning value in a table that takes into account only the | ||
| 240 | position of the edges. This table is small (around 1MB), so repeated | ||
| 241 | accesses to it are not too slow. In practice, this gives a small speed up | ||
| 242 | of around 5%. More tables could be used to refine the fallback estimate, | ||
| 243 | but each additional table leads to longer lookup times, especially if | ||
| 244 | it is too large to fit in cache. | ||
| 242 | 245 | ||
| 243 | When a pruning table does not store the exact lower bound value, for | 246 | Previous versions of this implementation (up to commit 6c42463, or |
| 244 | example in the case of `k2` table as described above, we need to refine | 247 | to version 0.2) also included the possibility of a table with 4 bits |
| 245 | our estimate using another table, which we call *fallback table*. | 248 | per entry, at least for the `h0` case. Such tables did not require |
| 246 | In the current implementation, we actually use two different tables: | 249 | a fallback lookup, but due to their large size they were not less |
| 247 | 250 | efficient. Therefore, they have been removed. | |
| 248 | * **h0** table: for larger `k2` tables we get a fallback value from the full | ||
| 249 | table for the **h0** coordinate. | ||
| 250 | * edges-only table: to improve the pruning value for certain specific | ||
| 251 | scrambles, namely whose with solved corners such as the superflip, | ||
| 252 | we employ a second table that takes into account the edges of a full | ||
| 253 | **h11** coordinate. This table is small (around 1MB). | ||
| 254 | |||
| 255 | More tables could be used to refine the fallback estimate, but each | ||
| 256 | additional table leads to longer lookup times, especially if it is | ||
| 257 | too large to fit in cache. | ||
| 258 | 251 | ||
| 259 | ### Estimation refinements | 252 | ### Estimation refinements |
| 260 | 253 | ||
| @@ -368,20 +361,20 @@ they are obviously inexistent when looking for *all* optimal solutions. | |||
| 368 | 361 | ||
| 369 | ## Pruning table computation | 362 | ## Pruning table computation |
| 370 | 363 | ||
| 371 | ### 4 bits tables for h0 and h11 | 364 | We first describe how the pruning table would be computed if we stored |
| 365 | each pruning value fully, using 4 bits per entry. This algorithm used | ||
| 366 | to be implemented (up to commit 6c42463), but has since been removed. | ||
| 372 | 367 | ||
| 373 | Computing the pruning table for a "real" h48 coordinate, that **h0** | 368 | ### Full tables for h0 and h11 (not implemented) |
| 374 | or **h11**, using 4 bits per entry is quite simple. The method currently | ||
| 375 | implemented works as follows. | ||
| 376 | 369 | ||
| 377 | First, we set the value of the solved cube's coordinate to 0 and every | 370 | If we are allowed to store the full pruning value for each entry, |
| 378 | other entry to 15, the largest 4-bit integer. Then we iteratively scan | 371 | computing the pruning tables is not that difficult. First, we set the |
| 379 | through the table and compute the coordinates at depth n+1 from those at | 372 | value of the solved cube's coordinate to 0 and every other entry to 15, |
| 380 | depth n as follows: for each coordinate at depth n, we compute a valid | 373 | the largest 4-bit integer. Then we iteratively scan through the table and |
| 381 | representative for it, we apply each of the possible 18 moves to it, and | 374 | compute the coordinates at depth n+1 from those at depth n as follows: |
| 382 | we set their value to n+1. Once the table is filled or we have reached | 375 | for each coordinate at depth n, we compute a valid representative for it, |
| 383 | depth 15, we stop (there are no **h0** coordinates at depth 16 or more, | 376 | we apply each of the possible 18 moves to it, and we set their value to |
| 384 | but I currently don't know if this is the case for **h11**). | 377 | n+1. Once the table is filled or we have reached depth 15, we stop. |
| 385 | 378 | ||
| 386 | Unfortunately what I described above is an oversimplification: one | 379 | Unfortunately what I described above is an oversimplification: one |
| 387 | also must take into account the case where the corner coordinate is | 380 | also must take into account the case where the corner coordinate is |
| @@ -400,20 +393,7 @@ Finally, this algorithm can be easily parallelized by dividing the set | |||
| 400 | of coordinates into separate sections, but one must take into account | 393 | of coordinates into separate sections, but one must take into account |
| 401 | that a coordinate and its neighbors are usually not in the same section. | 394 | that a coordinate and its neighbors are usually not in the same section. |
| 402 | 395 | ||
| 403 | This method is currently implemented only for **h0**, since the 4 bits | 396 | ### 2 bit tables |
| 404 | table for **h11** would require around 115GB of RAM to compute, and I | ||
| 405 | don't have this much memory. | ||
| 406 | |||
| 407 | ### 2 bits tables with for h0 and h11 | ||
| 408 | |||
| 409 | (Work in progress - currently there is no specialized routine for | ||
| 410 | computing 2 bits table for "real" coordinates; instead, an optimized | ||
| 411 | version of the generic method explained below is used) | ||
| 412 | |||
| 413 | ### A generic method for intermediate coordinates (from h1 to h10) | ||
| 414 | |||
| 415 | (Work in progress - this method is quite slow and it may be replaced | ||
| 416 | by a better algorithm in the future) | ||
| 417 | 397 | ||
| 418 | Computing the pruning tables for intermediate coordinates is not as | 398 | Computing the pruning tables for intermediate coordinates is not as |
| 419 | simple. The reason is that these coordinates are not invariant under | 399 | simple. The reason is that these coordinates are not invariant under |
| @@ -441,14 +421,13 @@ map indexed by their **h11** coordinate value. This way we do not have | |||
| 441 | to brute-force our way through from depth 0, and it make this method | 421 | to brute-force our way through from depth 0, and it make this method |
| 442 | considerably faster. Unfortunately, since the number of coordinates | 422 | considerably faster. Unfortunately, since the number of coordinates |
| 443 | at a certain depth increases exponentially with the depth, we are for | 423 | at a certain depth increases exponentially with the depth, we are for |
| 444 | now limited at storing the coordinates at depth 8. (Work in progress - | 424 | now limited at storing the coordinates at depth 8. |
| 445 | we may experiment with depth 9 in the future) | ||
| 446 | 425 | ||
| 447 | This method works for the "real" coordinates **h0** and **h11** as well. | 426 | This method works for the "real" coordinates **h0** and **h11** as well. |
| 448 | Moreover, in this case one can optimize it further by avoiding to repeat | 427 | Moreover, in this case one can optimize it further by avoiding to repeat |
| 449 | the search from a coordinate that has already been visited. (Work | 428 | the search from a coordinate that has already been visited. Further |
| 450 | in progress - this method will be replaced in the future by a more | 429 | optimization are possible for **h0** and **h11**, and we may implement |
| 451 | efficient one) | 430 | them in the future. |
| 452 | 431 | ||
| 453 | ## Possible future improvements | 432 | ## Possible future improvements |
| 454 | 433 | ||
| @@ -458,9 +437,6 @@ notes rather than a description of the solver.* | |||
| 458 | There are some areas where this implementation of the H48 optimal solver | 437 | There are some areas where this implementation of the H48 optimal solver |
| 459 | can be improved: | 438 | can be improved: |
| 460 | 439 | ||
| 461 | * Intertwining fallback tables to the main table. This is a trick that | ||
| 462 | nxopt uses to reduce the number of cache misses, but we have not | ||
| 463 | implemented in H48 yet. | ||
| 464 | * Faster pruning table generation for **h11** and **h0**. Since these | 440 | * Faster pruning table generation for **h11** and **h0**. Since these |
| 465 | two coordinates are "real" coordinates, we can use a different technique | 441 | two coordinates are "real" coordinates, we can use a different technique |
| 466 | to generate their tables faster. This won't affect the solver speed. | 442 | to generate their tables faster. This won't affect the solver speed. |
