├── .gitignore ├── tests ├── RunAllTests.cpp ├── MoveSeqTest.cpp ├── CMakeLists.txt ├── test_util.h ├── Cube6Test.cpp ├── EdgeCubeTest.cpp ├── NxPruneTest.cpp └── CubeTest.cpp ├── src ├── alloc.h ├── nxsolve.cpp ├── types.h ├── cube6.h ├── alloc.cpp ├── sse_cube.h ├── util.cpp ├── util.h ├── nxprune.cpp ├── gen-const-cubes.cpp ├── const-cubes.h ├── nxsolve.h ├── nxprune_generator.h ├── cube.cpp ├── avx2_cube.h ├── nxprune.h ├── vc-optimal.cpp └── cube.h ├── CMakeLists.txt ├── test64.txt ├── README.md ├── doc └── speffz.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /vc-optimal 2 | /gen-const-cubes 3 | /tests/check 4 | 5 | CMakeCache.txt 6 | CMakeFiles 7 | CMakeScripts 8 | Testing 9 | Makefile 10 | cmake_install.cmake 11 | install_manifest.txt 12 | compile_commands.json 13 | CTestTestfile.cmake 14 | 15 | .*.swp 16 | *.a 17 | -------------------------------------------------------------------------------- /tests/RunAllTests.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "CppUTest/CommandLineTestRunner.h" 20 | #include "test_util.h" 21 | 22 | decltype(t::rng) t::rng; 23 | 24 | int main(int argc, char **argv) { 25 | t::setup(); 26 | return CommandLineTestRunner::RunAllTests(argc, argv); 27 | } 28 | -------------------------------------------------------------------------------- /tests/MoveSeqTest.cpp: -------------------------------------------------------------------------------- 1 | #include "util.h" 2 | 3 | #include "CppUTest/TestHarness.h" 4 | 5 | using namespace vcube; 6 | 7 | TEST_GROUP(MoveSeq) { 8 | void check_parse(const std::string &s, const moveseq_t &expected) { 9 | CHECK(moveseq_t::parse(s) == expected); 10 | } 11 | }; 12 | 13 | TEST(MoveSeq, Parse) { 14 | check_parse("", {}); 15 | check_parse("URFDLB", { 0, 3, 6, 9, 12, 15 }); 16 | check_parse("U1R1F1D1L1B1", { 0, 3, 6, 9, 12, 15 }); 17 | check_parse("U2R2F2D2L2B2", { 1, 4, 7, 10, 13, 16 }); 18 | check_parse("U'R'F'D'L'B'", { 2, 5, 8, 11, 14, 17 }); 19 | check_parse("UUURRRFFF", { 0, 0, 0, 3, 3, 3, 6, 6, 6 }); 20 | } 21 | 22 | TEST(MoveSeq, ParseLowercase) { 23 | check_parse("urfdlb", { 0, 3, 6, 9, 12, 15 }); 24 | check_parse("u1r1f1d1l1b1", { 0, 3, 6, 9, 12, 15 }); 25 | check_parse("u2r2f2d2l2b2", { 1, 4, 7, 10, 13, 16 }); 26 | check_parse("u'r'f'd'l'b'", { 2, 5, 8, 11, 14, 17 }); 27 | } 28 | 29 | TEST(MoveSeq, ParseDelimited) { 30 | check_parse(" U2?R1,XF2\tD' L B ", { 1, 3, 7, 11, 12, 15 }); 31 | } 32 | 33 | TEST(MoveSeq, ParseMalformedInput) { 34 | check_parse("U2 U 2", { 1, 0 }); 35 | check_parse("U321", { 2 }); 36 | check_parse("1", {}); 37 | check_parse("2", {}); 38 | check_parse("3", {}); 39 | check_parse("'", {}); 40 | } 41 | -------------------------------------------------------------------------------- /src/alloc.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_ALLOC_H 20 | #define VCUBE_ALLOC_H 21 | 22 | #include 23 | #include 24 | 25 | namespace vcube { 26 | 27 | class alloc { 28 | public: 29 | template 30 | static T * huge(size_t n) { 31 | return (T *) huge_impl(n * sizeof(T)); 32 | } 33 | 34 | template 35 | static T * shared(size_t n, uint32_t key, bool rdwr) { 36 | return (T *) shared_impl(n * sizeof(T), key, rdwr); 37 | } 38 | 39 | private: 40 | static void * huge_impl(size_t n); 41 | static void * shared_impl(size_t n, uint32_t key, bool rdwr); 42 | }; 43 | 44 | } 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /src/nxsolve.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include "nxsolve.h" 21 | 22 | using namespace vcube; 23 | using namespace vcube::nx; 24 | 25 | decltype(solver_base::depth4) solver_base::depth4; 26 | 27 | void solver_base::init() { 28 | std::set seen = { cube() }; 29 | std::vector prev = { { cube(), 0, 0xff } }; 30 | 31 | // Find all cubes at depth 4 (there are 43,239 of them) 32 | for (int depth = 0; depth < 4; depth++) { 33 | depth4.clear(); 34 | for (const auto &q : prev) { 35 | for (int m = 0; m < N_MOVES; m++) { 36 | cube6 c6 = q.c6.move(m); 37 | if (seen.insert(c6[0]).second) { 38 | depth4.emplace_back(c6, (q.moves << 8) | m, m / 3); 39 | } 40 | } 41 | } 42 | prev.swap(depth4); 43 | } 44 | 45 | prev.swap(depth4); 46 | } 47 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of vcube. 2 | # 3 | # Copyright (C) 2018 Andrew Skalski 4 | # 5 | # vcube is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # vcube is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with vcube. If not, see . 17 | 18 | if(DEFINED ENV{CPPUTEST_HOME}) 19 | message(STATUS "Using CppUTest found in $ENV{CPPUTEST_HOME}") 20 | set(CPPUTEST_INCLUDE_DIRS $ENV{CPPUTEST_HOME}/include) 21 | set(CPPUTEST_LIBRARIES $ENV{CPPUTEST_HOME}/lib) 22 | set(CPPUTEST_LDFLAGS CppUTest CppUTestExt) 23 | else() 24 | find_package(PkgConfig REQUIRED) 25 | pkg_search_module(CPPUTEST REQUIRED cpputest>=3.8) 26 | message(STATUS "Found CppUTest version ${CPPUTEST_VERSION}") 27 | endif() 28 | 29 | include_directories(${CPPUTEST_INCLUDE_DIRS} ../src/) 30 | link_directories(${CPPUTEST_LIBRARIES}) 31 | 32 | add_executable(check 33 | RunAllTests.cpp 34 | CubeTest.cpp 35 | Cube6Test.cpp 36 | EdgeCubeTest.cpp 37 | MoveSeqTest.cpp 38 | NxPruneTest.cpp 39 | ) 40 | target_link_libraries(check vcube ${CPPUTEST_LDFLAGS}) 41 | add_custom_command(TARGET check COMMAND ./check POST_BUILD) 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of vcube. 2 | # 3 | # Copyright (C) 2018 Andrew Skalski 4 | # 5 | # vcube is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # vcube is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with vcube. If not, see . 17 | 18 | cmake_minimum_required (VERSION 3.5) 19 | 20 | project(vcube) 21 | 22 | # Need -std=c++17 for proper alignment of AVX variables in std::vector 23 | set(CMAKE_BUILD_TYPE Release) 24 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mlzcnt -mbmi -mbmi2 -mavx2 -flax-vector-conversions -std=c++17") 25 | 26 | # The Clang compiler currently produces faster code than GCC for this project 27 | set(CMAKE_C_COMPILER "clang" CACHE STRING "clang compiler" FORCE) 28 | set(CMAKE_CXX_COMPILER "clang++" CACHE STRING "clang++ compiler" FORCE) 29 | 30 | add_executable(gen-const-cubes 31 | src/gen-const-cubes.cpp 32 | ) 33 | 34 | add_library(vcube 35 | src/alloc.cpp 36 | src/cube.cpp 37 | src/nxprune.cpp 38 | src/nxsolve.cpp 39 | src/util.cpp 40 | ) 41 | 42 | add_executable(vc-optimal 43 | src/vc-optimal.cpp 44 | ) 45 | target_link_libraries(vc-optimal vcube pthread) 46 | 47 | add_subdirectory(tests) 48 | -------------------------------------------------------------------------------- /tests/test_util.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "cube.h" 20 | #include 21 | 22 | using namespace vcube; 23 | 24 | struct t { 25 | public: 26 | static void setup() { 27 | rng.seed(rng.default_seed); 28 | } 29 | 30 | static int rand(int max) { 31 | std::uniform_int_distribution<> dis(0, max - 1); 32 | return dis(rng); 33 | } 34 | 35 | static uint8_t * to_array(cube &c) { 36 | return reinterpret_cast(&c); 37 | } 38 | 39 | static const uint8_t * to_array(const cube &c) { 40 | return reinterpret_cast(&c); 41 | } 42 | 43 | static cube random_cube() { 44 | cube c; 45 | c.setEdgePerm(rand(N_EPERM)); 46 | c.setEdgeOrient(rand(N_EORIENT)); 47 | c.setCornerPerm(rand(N_CPERM)); 48 | c.setCornerOrient(rand(N_CORIENT)); 49 | if (c.parity()) { 50 | std::swap(to_array(c)[0], to_array(c)[1]); 51 | } 52 | return c; 53 | } 54 | 55 | private: 56 | static std::mt19937 rng; 57 | }; 58 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_TYPES_H 20 | #define VCUBE_TYPES_H 21 | 22 | #include 23 | 24 | namespace vcube { 25 | 26 | using eorient_t = uint32_t; 27 | static constexpr eorient_t N_EORIENT = 2048; 28 | 29 | using e8orient_t = uint32_t; 30 | static constexpr e8orient_t N_E8ORIENT = 256; 31 | 32 | using e4orient_t = uint32_t; 33 | static constexpr e4orient_t N_E4ORIENT = 16; 34 | 35 | using corient_t = uint32_t; 36 | static constexpr corient_t N_CORIENT = 2187; 37 | 38 | using eperm_t = uint32_t; 39 | static constexpr eperm_t N_EPERM = 479001600; 40 | 41 | using cperm_t = uint32_t; 42 | static constexpr cperm_t N_CPERM = 40320; 43 | 44 | using c4comb_t = uint32_t; 45 | static constexpr c4comb_t N_C4COMB = 70; 46 | 47 | using e4comb_t = uint32_t; 48 | static constexpr e4comb_t N_E4COMB = 495; 49 | 50 | using e4perm_t = uint32_t; 51 | static constexpr e4perm_t N_E4PERM = 24; 52 | 53 | using eud4comb_t = uint32_t; 54 | static constexpr eud4comb_t N_EUD4COMB = 70; 55 | 56 | static constexpr int N_MOVES = 18; 57 | 58 | } 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /src/cube6.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_CUBE6_H 20 | #define VCUBE_CUBE6_H 21 | 22 | #include 23 | #include "cube.h" 24 | 25 | namespace vcube { 26 | 27 | class cube6 { 28 | public: 29 | cube6() : ca() { 30 | } 31 | 32 | cube6(const cube &c) { 33 | cube ci = ~c; 34 | ca = { 35 | c, 36 | S_URF3 * c * S_URF3i, 37 | S_URF3i * c * S_URF3, 38 | ci, 39 | S_URF3 * ci * S_URF3i, 40 | S_URF3i * ci * S_URF3 41 | }; 42 | } 43 | 44 | const cube & operator [] (size_t idx) const { return ca[idx]; } 45 | bool operator == (const cube &c) const { return ca[0] == c; } 46 | bool operator != (const cube &c) const { return ca[0] != c; } 47 | 48 | cube6 operator * (const cube6 &o) const { 49 | return {{ 50 | ca[0] * o.ca[0], 51 | ca[1] * o.ca[1], 52 | ca[2] * o.ca[2], 53 | o.ca[3] * ca[3], 54 | o.ca[4] * ca[4], 55 | o.ca[5] * ca[5] 56 | }}; 57 | } 58 | 59 | cube6 move(uint8_t m0) const { 60 | auto &m = move_sym6[m0]; 61 | return {{ 62 | ca[0].move( m0), 63 | ca[1].move( m[1]), 64 | ca[2].move( m[2]), 65 | ca[3].premove(m[3]), 66 | ca[4].premove(m[4]), 67 | ca[5].premove(m[5]) 68 | }}; 69 | } 70 | 71 | cube6 premove(uint8_t m0) const { 72 | auto &m = move_sym6[m0]; 73 | return {{ 74 | ca[0].premove(m0), 75 | ca[1].premove(m[1]), 76 | ca[2].premove(m[2]), 77 | ca[3].move( m[3]), 78 | ca[4].move( m[4]), 79 | ca[5].move( m[5]) 80 | }}; 81 | } 82 | 83 | private: 84 | std::array ca; 85 | 86 | cube6(const decltype(ca) &ca) 87 | : ca(ca) 88 | { 89 | } 90 | }; 91 | 92 | } 93 | 94 | #endif 95 | -------------------------------------------------------------------------------- /tests/Cube6Test.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | 20 | #include "cube6.h" 21 | 22 | #include "test_util.h" 23 | #include "CppUTest/TestHarness.h" 24 | 25 | using namespace vcube; 26 | 27 | TEST_GROUP(Cube6) { 28 | bool equal(const cube6 &a, const cube6 &b) { 29 | for (int i = 0; i < 6; i++) { 30 | if (a[i] != b[i]) { 31 | return false; 32 | } 33 | } 34 | return true; 35 | } 36 | }; 37 | 38 | TEST(Cube6, CubeConstructor) { 39 | cube c = t::random_cube(); 40 | cube6 c6(c); 41 | 42 | CHECK(c6[0] == c); 43 | CHECK(c6[1] == vcube::S_URF3 * c6[0] * ~vcube::S_URF3); 44 | CHECK(c6[2] == ~vcube::S_URF3 * c6[0] * vcube::S_URF3); 45 | CHECK(c6[3] == ~c); 46 | CHECK(c6[4] == vcube::S_URF3 * c6[3] * ~vcube::S_URF3); 47 | CHECK(c6[5] == ~vcube::S_URF3 * c6[3] * vcube::S_URF3); 48 | } 49 | 50 | TEST(Cube6, Handedness) { 51 | CHECK(cube6(cube::from_moves("U "))[0] == cube::from_moves("U")); 52 | CHECK(cube6(cube::from_moves("R "))[1] == cube::from_moves("U")); 53 | CHECK(cube6(cube::from_moves("F "))[2] == cube::from_moves("U")); 54 | CHECK(cube6(cube::from_moves("U'"))[3] == cube::from_moves("U")); 55 | CHECK(cube6(cube::from_moves("R'"))[4] == cube::from_moves("U")); 56 | CHECK(cube6(cube::from_moves("F'"))[5] == cube::from_moves("U")); 57 | } 58 | 59 | TEST(Cube6, Equality) { 60 | // Unlikely for them to be the same 61 | cube c = t::random_cube(), d = t::random_cube(); 62 | 63 | cube6 c6(c); 64 | 65 | CHECK(c6 == c); 66 | CHECK_FALSE(c6 != c); 67 | 68 | CHECK(c6 != d); 69 | CHECK_FALSE(c6 == d); 70 | } 71 | 72 | TEST(Cube6, Move) { 73 | cube c = t::random_cube(); 74 | for (int m = 0; m < N_MOVES; m++) { 75 | CHECK(equal(c.move(m), cube6(c).move(m))); 76 | CHECK(equal(c.premove(m), cube6(c).premove(m))); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /tests/EdgeCubeTest.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "cube.h" 20 | 21 | #include 22 | #include "test_util.h" 23 | #include "CppUTest/TestHarness.h" 24 | 25 | using namespace vcube; 26 | 27 | TEST_GROUP(EdgeCube) { 28 | bool equivalent(const edgecube &ec, const cube &c) { 29 | return !memcmp(&ec, &c, sizeof(ec)); 30 | } 31 | }; 32 | 33 | TEST(EdgeCube, Constructor) { 34 | LONGS_EQUAL(sizeof(edgecube), 16); 35 | 36 | for (int i = 0; i < 1000; i++) { 37 | cube c = t::random_cube(); 38 | CHECK(equivalent(edgecube(c), c)); 39 | } 40 | } 41 | 42 | TEST(EdgeCube, Compose) { 43 | for (int i = 0; i < 1000; i++) { 44 | cube a = t::random_cube(); 45 | cube b = t::random_cube(); 46 | CHECK(equivalent(edgecube(a).compose(edgecube(b)), a.compose(b))); 47 | } 48 | } 49 | 50 | TEST(EdgeCube, Move) { 51 | for (int m = 0; m < 18; m++) { 52 | cube c = t::random_cube(); 53 | CHECK(equivalent(edgecube(c).move(m), c.move(m))); 54 | CHECK(equivalent(edgecube(c).premove(m), c.premove(m))); 55 | } 56 | } 57 | 58 | TEST(EdgeCube, SymConjugate) { 59 | for (int s = 0; s < 48; s++) { 60 | cube c = t::random_cube(); 61 | CHECK(equivalent(edgecube(c).symConjugate(s), c.symConjugate(s))); 62 | } 63 | } 64 | 65 | TEST(EdgeCube, GetEdgeOrient) { 66 | LONGS_EQUAL(0, edgecube().getEdgeOrientRaw()); 67 | 68 | for (int i = 0; i < 1000; i++) { 69 | cube c = t::random_cube(); 70 | LONGS_EQUAL(edgecube(c).getEdgeOrientRaw(), c.getEdgeOrientRaw()); 71 | } 72 | } 73 | 74 | TEST(EdgeCube, SetEdgeOrient) { 75 | edgecube ec; 76 | for (int i = 0; i < 1000; i++) { 77 | cube c = t::random_cube(); 78 | 79 | auto eo = c.getEdgeOrient(); 80 | auto e8o = c.getEdge8Orient(); 81 | auto e4o = c.getEdge4Orient(); 82 | 83 | c.setEdgeOrient(0); 84 | edgecube ec(c); 85 | 86 | CHECK(equivalent(ec.setEdgeOrient(eo), c.setEdgeOrient(eo))); 87 | CHECK(equivalent(ec.setEdge8Orient(e8o), c.setEdge8Orient(e8o))); 88 | CHECK(equivalent(ec.setEdge4Orient(e4o), c.setEdge4Orient(e4o))); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/alloc.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include "alloc.h" 23 | 24 | // Missing in the header 25 | #ifndef SHM_HUGE_SHIFT 26 | #define SHM_HUGE_SHIFT MAP_HUGE_SHIFT 27 | #endif 28 | 29 | using namespace vcube; 30 | 31 | static size_t num_pages(size_t n, size_t page_size) { 32 | size_t mask = (1LL << page_size) - 1; 33 | return (n >> page_size) + !!(n & mask); 34 | } 35 | 36 | static void * map_huge(size_t n, size_t page_size, int prot, int flags) { 37 | if (page_size) { 38 | n = num_pages(n, page_size); 39 | flags |= MAP_HUGETLB | (page_size << MAP_HUGE_SHIFT); 40 | } 41 | void *mem = mmap(NULL, n << page_size, prot, flags, -1, 0); 42 | return (mem == MAP_FAILED) ? NULL : mem; 43 | } 44 | 45 | void * alloc::huge_impl(size_t n) { 46 | int prot = PROT_READ | PROT_WRITE; 47 | int flags = MAP_PRIVATE | MAP_ANONYMOUS; 48 | 49 | void *mem = map_huge(n, 30, prot, flags); // 1GB pages 50 | if (!mem) { 51 | mem = map_huge(n, 21, prot, flags); // 2MB pages 52 | } 53 | if (!mem) { 54 | mem = map_huge(n, 0, prot, flags); // Standard pages 55 | } 56 | 57 | return mem; 58 | } 59 | 60 | static void * map_shared(size_t n, uint32_t key, bool rdwr, size_t page_size) { 61 | int flags = 0600; 62 | if (rdwr) { 63 | flags |= IPC_CREAT | IPC_EXCL; 64 | } 65 | if (page_size) { 66 | n = num_pages(n, page_size); 67 | flags |= SHM_HUGETLB | (page_size << SHM_HUGE_SHIFT); 68 | } 69 | int shm = shmget(key, n << page_size, flags); 70 | if (shm == -1) { 71 | return NULL; 72 | } 73 | void *mem = shmat(shm, NULL, rdwr ? 0 : SHM_RDONLY); 74 | return (mem == (void *) -1) ? NULL : mem; 75 | } 76 | 77 | void * alloc::shared_impl(size_t n, uint32_t key, bool rdwr) { 78 | void *mem = NULL; 79 | if (rdwr) { 80 | // Huge page setting is relevant only on create 81 | mem = map_shared(n, key, rdwr, 30); // 1GB pages 82 | if (!mem) { 83 | mem = map_shared(n, key, rdwr, 21); // 2MB pages 84 | } 85 | } 86 | if (!mem) { 87 | mem = map_shared(n, key, rdwr, 0); 88 | } 89 | return mem; 90 | } 91 | -------------------------------------------------------------------------------- /src/sse_cube.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_SSE_CUBE_H 20 | #define VCUBE_SSE_CUBE_H 21 | 22 | #include 23 | #include 24 | #include "types.h" 25 | #include "util.h" 26 | 27 | namespace vcube { 28 | 29 | struct sse { 30 | //static const __m128i identity = _mm_set_epi64x( 31 | // 0x0f0e0d0c0b0a0908, 0x0706050403020100); 32 | static constexpr __m128i identity = (__m128i) { 33 | 0x0706050403020100, 0x0f0e0d0c0b0a0908 34 | }; 35 | 36 | static uint32_t bitmask(__m128i v, int b) { 37 | return _mm_movemask_epi8(_mm_slli_epi32(v, 7 - b)); 38 | } 39 | 40 | static bool equals(__m128i a, __m128i b) { 41 | return _mm_movemask_epi8(_mm_cmpeq_epi8(a, b)) == -1; 42 | } 43 | 44 | static bool less_than(__m128i a, __m128i b) { 45 | // See avx2_cube.h 46 | #if 1 47 | uint32_t gt = _mm_movemask_epi8(_mm_cmpgt_epi8(a, b)); 48 | uint32_t lt = _mm_movemask_epi8(_mm_cmpgt_epi8(b, a)); 49 | return gt < lt; 50 | #else 51 | uint32_t eq = _mm_movemask_epi8(_mm_cmpeq_epi8(a, b)); 52 | uint32_t lt = _mm_movemask_epi8(_mm_cmpgt_epi8(b, a)); 53 | uint32_t sum = (lt << 1) + eq; 54 | return sum < lt; 55 | #endif 56 | } 57 | 58 | static __m128i edge_compose(__m128i a, __m128i b) { 59 | __m128i vperm = _mm_shuffle_epi8(a, b); 60 | __m128i vorient = _mm_and_si128(b, _mm_set1_epi8(0xf0)); 61 | return _mm_xor_si128(vperm, vorient); 62 | } 63 | 64 | static __m128i xor_edge_orient(__m128i v, eorient_t eorient) { 65 | __m128i vorient = _mm_shuffle_epi8( 66 | _mm_set1_epi32(eorient), 67 | _mm_set_epi64x(0xffffffff01010101, 0)); 68 | vorient = _mm_or_si128(vorient, _mm_set1_epi64x(~0x8040201008040201)); 69 | vorient = _mm_cmpeq_epi8(vorient, _mm_set1_epi64x(-1)); 70 | vorient = _mm_and_si128(vorient, _mm_set1_epi8(0x10)); 71 | return _mm_xor_si128(v, vorient); 72 | } 73 | 74 | static corient_t corner_orient(__m128i v) { 75 | // Mask the corner orientation bits and convert to 16-bit vector 76 | auto vorient = _mm_and_si128(v, _mm_set1_epi8(0x30)); 77 | vorient = _mm_unpacklo_epi8(vorient, _mm_setzero_si128()); 78 | 79 | // Multiply each corner by its place value, add adjacent pairs 80 | vorient = _mm_madd_epi16(vorient, _mm_set_epi16(729, 243, 81, 27, 9, 3, 1, 0)); 81 | 82 | // Finish the horizontal sum 83 | uint64_t r = _mm_extract_epi64(vorient, 0) + _mm_extract_epi64(vorient, 1); 84 | r += r >> 32; 85 | 86 | // Shift the result into the correct position 87 | return r >> 4; 88 | } 89 | }; 90 | 91 | } 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /src/util.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "util.h" 20 | 21 | using namespace vcube; 22 | 23 | // Parse a move sequence loosely; supports multiple formats such as 24 | // "U R2 F'", "U1R2F3", "URRFFF". Input is not validated, and anything 25 | // unexpected is treated as a delimiter. 26 | moveseq_t moveseq_t::parse(const std::string &s) { 27 | moveseq_t moves; 28 | 29 | int face = -1; 30 | for (auto ch : s) { 31 | int f = -1, power = -1; 32 | switch (ch) { 33 | case 'u': case 'U': f = 0; break; 34 | case 'r': case 'R': f = 3; break; 35 | case 'f': case 'F': f = 6; break; 36 | case 'd': case 'D': f = 9; break; 37 | case 'l': case 'L': f = 12; break; 38 | case 'b': case 'B': f = 15; break; 39 | case '3': case '\'': power = 2; break; 40 | case '2': power = 1; break; 41 | case '1': power = 0; break; 42 | default: power = 0; 43 | } 44 | 45 | if (f != -1) { 46 | if (face != -1) { 47 | moves.push_back(face); 48 | } 49 | face = f; 50 | } else if (power != -1 && face != -1) { 51 | moves.push_back(face + power); 52 | face = -1; 53 | } 54 | } 55 | 56 | if (face != -1) { 57 | moves.push_back(face); 58 | } 59 | 60 | return moves; 61 | } 62 | 63 | moveseq_t moveseq_t::canonical() const { 64 | if (empty()) { 65 | return {}; 66 | } 67 | 68 | moveseq_t canon = *this; 69 | 70 | // Append a dummy move different from the final axis to ensure 71 | // the final moves are flushed 72 | canon.push_back(canon.back() + 3); 73 | 74 | uint8_t last_axis = 0, power[2] = { 0, 0 }; 75 | auto tail = canon.begin(); 76 | for (auto m : canon) { 77 | auto axis = (m / 3) % 3; 78 | if (axis != last_axis) { 79 | for (int pole = 0; pole < 2; pole++) { 80 | if (power[pole] %= 4) { 81 | *tail++ = (last_axis * 3 + pole * 9) + (power[pole] % 4) - 1; 82 | } 83 | power[pole] = 0; 84 | } 85 | last_axis = axis; 86 | } 87 | power[m >= 9] += (m % 3) + 1; 88 | } 89 | 90 | canon.resize(tail - canon.begin()); 91 | 92 | return canon; 93 | } 94 | 95 | std::string moveseq_t::to_string(style_t style) const { 96 | const char *face = "URFDLB"; 97 | const char *power = (style == FIXED) ? "123" : " 2'"; 98 | bool delim = (style != FIXED); 99 | 100 | std::string s; 101 | for (auto m : *this) { 102 | s.push_back(face[m / 3]); 103 | s.push_back(power[m % 3]); 104 | if (delim) { 105 | s.push_back(' '); 106 | } 107 | } 108 | 109 | while (!s.empty() && s.back() == ' ') { 110 | s.pop_back(); 111 | } 112 | 113 | return s; 114 | } 115 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_UTIL_H 20 | #define VCUBE_UTIL_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include "types.h" 26 | 27 | namespace vcube { 28 | 29 | namespace { 30 | template 31 | constexpr auto init_NcR_rank() { 32 | auto tbl = std::array(); 33 | T rank = 0; 34 | for (int32_t i = (1 << N) - 1; i >= 0; i--) { 35 | if (__builtin_popcountl(i) == R) { 36 | tbl[i] = rank++; 37 | } 38 | } 39 | return tbl; 40 | } 41 | 42 | template 43 | constexpr auto init_NcR_unrank() { 44 | auto tbl = std::array(); 45 | T rank = 0; 46 | size_t idx = 0; 47 | for (int32_t i = (1 << N) - 1; i >= 0; i--) { 48 | if (__builtin_popcountl(i) == R) { 49 | tbl[idx++] = i; 50 | } 51 | } 52 | return tbl; 53 | } 54 | 55 | template 56 | extern inline T rank_nCr(T bits) { 57 | static constexpr auto tbl = init_NcR_rank(); 58 | return tbl[bits]; 59 | } 60 | 61 | template 62 | extern inline T unrank_nCr(T idx) { 63 | static constexpr auto tbl = init_NcR_unrank(); 64 | return tbl[idx]; 65 | } 66 | 67 | static constexpr auto init_4perm_rank_oddeven() { 68 | /* To accommodate 'movemask' extraction of the permutation, 69 | * the lookup table index has "odd-even" bit order: 75316420 70 | */ 71 | constexpr int fc[] = { 6, 2, 1 }; 72 | std::array tbl = { }; 73 | for (int perm = 0; perm < 24; perm++) { 74 | uint32_t table = 0x3210; 75 | uint8_t index = 0; 76 | for (int i = 0; i < 3; i++) { 77 | int shift = perm / fc[i] % (4 - i) * 4; 78 | uint8_t value = (table >> shift) & 3; 79 | table ^= (table ^ (table >> 4)) & (0xffffU << shift); 80 | index |= (value & 1) << i; 81 | index |= (value & 2) << (i + 3); 82 | } 83 | index |= (table & 1) << 3; 84 | index |= (table & 2) << 6; 85 | tbl[index] = perm; 86 | } 87 | return tbl; 88 | } 89 | } 90 | 91 | extern inline auto rank_8C4(uint8_t bits) { 92 | return rank_nCr(bits); 93 | } 94 | 95 | extern inline auto rank_12C4(uint16_t bits) { 96 | return rank_nCr(bits); 97 | } 98 | 99 | extern inline auto unrank_8C4(uint8_t idx) { 100 | return unrank_nCr(idx); 101 | } 102 | 103 | extern inline auto unrank_12C4(uint16_t idx) { 104 | return unrank_nCr(idx); 105 | } 106 | 107 | /* Set the orientation of the 12th edge based on parity of the other 11 */ 108 | extern inline eorient_t set_eorient_parity(eorient_t eorient) { 109 | // Faster than popcnt because of instruction latency 110 | eorient_t parity = eorient ^ (eorient << 6); 111 | parity ^= parity << 3; 112 | parity ^= (parity << 2) ^ (parity << 1); 113 | return eorient ^ (parity & 0x800); 114 | } 115 | 116 | extern inline auto rank_4perm_oddeven(uint8_t bits) { 117 | constexpr auto tbl = init_4perm_rank_oddeven(); 118 | return tbl[bits]; 119 | } 120 | 121 | struct moveseq_t : public std::vector { 122 | enum style_t { SINGMASTER, FIXED }; 123 | 124 | using std::vector::vector; 125 | 126 | static moveseq_t parse(const std::string &); 127 | 128 | moveseq_t canonical() const; 129 | std::string to_string(style_t style = SINGMASTER) const; 130 | }; 131 | 132 | } 133 | 134 | #endif 135 | -------------------------------------------------------------------------------- /test64.txt: -------------------------------------------------------------------------------- 1 | B3L3D3F1D3B2L3B1L3F1U1D2F2B2R1L2U3L2D1R1D1F1D2R1B2D2F2R3U3B1L1D1B3L2F2 2 | R3L1D1F2L3U2R1F1U1D2F1L2B1L1B2R1L2D1B3U2B2R2U2L2U2B2U2F2L2D2R3B3D1F2B3 3 | R1L3U2F3D2L2F1L3B3R1B2U2B2D1R1F3B1L2B2D2L3U3L3U2L1U2D2L1D1R3B3L1B2D3F1 4 | F1R3B2R3D1B3L2U3B2U2R2D3B2R3B1L3B2R3F3U1R1L1U3B3R2D1F2L1U3D1F2R1U1F1L3 5 | F3D3B2U1B3D2R3L2D2F3L2D3B3R2U1D2F1L2F2R2B2R3U1D2B3L1B3U1F2R2B3R2U1B3D1 6 | B2R3L2B1D3L2F2D1B2D3R2F3D2L1F3U1B3D2R2B1U3B1R1L1U1D1R2L2D3F2U3L3D2B1R1 7 | U2R2U3B1D3L1F3R1D1B2R1L3B3D3F3U1D3L3U3F1B2U2D1F1B1D1R3U2F1U2B2D1L3B2R1 8 | R2U1L1U3D2B1D3R2D1L3B2U1F3R1F2B1L1B1L1B3R2B2D1R2U2L2U2R3D2R1D1L3F3L2F3 9 | F1D2F3U3R3F1R2B2U2R1F2U2F3R3D1B1L3D3R2U2F3U2B2U2F2B3D1F3U2D2B3L1B1D1R1 10 | D1F2R1L1U1D1B2R2F2B2L1F2R1F1L1B3U2D1R3B1D2F1U3R1U3F2D3R3B3U1L3B2U3R3U3 11 | B2R1L1B3U2F3L2B1L1F1B1L2F2R2D1B3U1L2U2F2D2L3U1D2R1U3F2L1U1B1R3F1D3L1U2 12 | U3F2R2F1D2R2F3U3R2F2L2F2R1U3R2D3F1D2R1U1B2R1F2B3L1U1R2D1L2U2L3D3F3R3U1 13 | B2D2R1B1U2R2F3R3U1B1R1B1L2D1F2U2F3D3R2B3L3F3B3R2F3B2U2L1B1U2L2D1L3U3R3 14 | F3U3B2R3F1L1D1L2U2D1B3L1F3L3D2L3D3B3R2L1B1R1F2D3L3F3D3B2U1B3U1F1R2L2D1 15 | U2R1L1D2L3B2D3R3B2R1D1B1U3F3D3F3B1L3F1U1D2R1B3R2L3B3R2L1U3D3F2D1L2U2F3 16 | U3B3L2F3R1F2L3D1B1D1B3D2B1L1U3D3L3D2B1L3D1B1L3U3B3R1F3D2R2U2R1U3L3U3D2 17 | R3B2R3D2R2L3U3D3B3U2R2B2D3L3F1L2D1R1D3B2U1L3B1L1F3D2R2F3R3D1L1D2R1L3U2 18 | L1U2L1F3B3R3L2D3L1F2D1B3L3B3U1R1U1D3B3R2B3L1D3B3R3U2B3R2L1D2R2B3L2B2D2 19 | B3L2D2F2R1D2F1R1L1U1B3R1D1F2B2L3D1R3L1D3F1L2F3B2L3U2R1U2L1F1D1B1L3U3R1 20 | D3L2D2B1U2B1D3F1B1U2D2R3F2U3D2B2R3L2D3B1D2F1U3D3B3U1F3R1D2F1L3B3U2B2L2 21 | F2D1F2L3B1D1R3F2L1F3U3L3U3R3B2U3F1D3R2L2F1U3F1R2D3L3F1R1L3F3D1F3L3B2D3 22 | D3R1F3U3L1U2R2B3D2F1B1D1B3L1D1F1B2L3F2L3F3B2R1D2F2D3R1B2L2F2B2D1F2B2L2 23 | B3U1L3B2R2L1F2U3D3B2L2D2F3U3F1B1U1R1B2U3L3F1U3R2U1R2L3F2D3L2F2D2L2D1F2 24 | B1D2F2L2U2R1L1D1R1U3R2B3R1U2F3B2D2L3F2R2D2R2U1B1U1R2B3U1D2L2B2R3D2R2F1 25 | U1B2D3L3D2L3F1R3D2L1B1L1F1R2U3F1B3D1F3U2B1D1L3F1L3D1B1D1B3R3L3F2L1F3L3 26 | F3R2U1L1D2R1F1L3B1L1U3F2U3F3L3B1D2L2B3L3U2B2U2B2U1F2B1U3L3U1L2U1D2F2U3 27 | F2L1D3F1B1D2B3U1R3D1B1D3F2R2F1D2F1R3U1L1D3F2B2D3L1U1F1R2L3D2L1D3F2R3F1 28 | D2L3U3R1D3F2B2U3R2D3B2U3R2L3B1U1L2B3R3D3B1L1U3B1U2F3R1B3D1R3U2R3D3B3U2 29 | U2L2U3D1R3F1L2D3B2L1F2U3F3L2F1R2L3F2L1U1D2L2D1F1R3L2U2F2U3D2R1B1R1B2D3 30 | D3B2D3R3F1D3B1D3F3L1B1R3D1B1R2D2R3U3F1D1F3R3U3D1B3R2U1B1L1U3R1D1F2D2F1 31 | L3B1U3L3D2L1B2D2B3L1F3R2L3B2R2D2R2B1D3B2R1D1F2U3R3L3B1D3F2L2U3D1L3U1R2 32 | F3U3L1U2R3U2B1U3B3L1D3L1F3R1B3D3F1R3L1U1D2F2D1L3D2R3B1R1L2F2D2L1D1B3D3 33 | F1L2F2D3B2L3F1B1R2L2U2F1B3U1D3F3R3L2B2R1B3D1F1U1R3D2L1F2L3U2R1L3B1D1B2 34 | U2F3D1L1D2F1R3F3D2B1D3R2F2U2D2B1R2D2B1D1R1F3D1B3U3R2F2R2F3D1F3L3D2B1R2 35 | D1F1B3R3L3B2L1D1B1D3R2B3R2F2U2L1F1D3L3F1B3L1B1D2B1U2D1F3B3U3F2U2L1U3F1 36 | B2D1L2D1R2F2L2U2D2B1L2F2U3B3L1F1U2D2R1L3B3L2U2B2L2F3B3L2U2B1L1F3U3R3F2 37 | D1F2L3B1L1D2L3U3D3R1F3L2D2R2U2R2U2R1L3U2F3L2F2B2L3B3L1D1F1B2U2D1F1U2D3 38 | R3L1U3F3L1B1D1R1F1U1D1L2F2R1D2L3U2F2B2L3F3L1D2L3F2L2D3R1U3F2D3L1B1D2L3 39 | U1L3F1R1F1U3L1B3L2D3L3B2L2U3L1U1L2F3B2U1R1L1U2D1L3B3D1B2R2L1B2U2R1L3U2 40 | D1F2L1U2B2R1D2F1L3F3D2B1R2D1F1D1B2U3F2B3D2F1U3B2U3L2F3L2U2B2R2D1R2D3L3 41 | B3U1L3D2R3B1R3B2D1B1D2F3D1F2L1U1B3U3L2B1D3F3D3F1L2F1B3L3U3B3R2L2F3L3B1 42 | L3F3R2B1L2F2D2B3U3D1B1D3F2L3B1D3R2U3R3U2B3L1D2B3U1F2D1L1B1D3R2L3D2L2F1 43 | U3L2D2B3U3R2D1B3U3L3U2R1F2L1F3L2F2D2B1U1D1R2D1F1D1F3B3U1L1U1F3L3F3L1F1 44 | F3B3L2U1D1R2D1F3U3B1L3F1D3L2U3R3U1B3R2D2F2R3D1R1L1U2L3U3F2U2R2D1B2U1L3 45 | D1L2B2U2B1L3B3D2R1B2D1L3F3L2U3B1U2L3B3R2U2R3D1F2D2L1F2U1L1B3L3D2B2L3D2 46 | B2D2L3D2L2B3D1R1U2F2R2L2U3L1B1U3D3R2F2L2F1U3D3B1R3F2D2L3D3B1U1F3B3R3B3 47 | L1B2L1D2B2L3B3L2B1L2U2R3L1F1D1B2U2R3D1R3F2D3L3B2R2B2R1D2B2D1B3D2R2F1R1 48 | B3U2B2L1D2F3B1R2D1L3B2U3L1F1B2D3L3F2D3L2F1B3D3R1U3D3F3L1F3U2L2B2L2B3U2 49 | F2B2R1B1U3F2R1B3L3D3B1D2F3U1R2F3B1R2B2D1F1U3B1D3B1U2B2D3B1L3D2F1L1B3D1 50 | R1L1F2U1B1U1D2L1F3B1L3F3U2B1U3R3B3L2U3D3B3R2L1D2R3U1B2D1F3R1B1L2F3D3L1 51 | L3U3R3F3D3F2B3R2D3R2D2B3L1U2R2B3D3F2L2D2L1F1B2L3D1B1U1L3D1L1F2R1L1D1R1 52 | D2F3R2D3F2U2B1U3F3U2D1F2B1D1B1D2B2U1D1R1F1L3F3R1L3B3L3F2D3B2U3L2F2R2D1 53 | L3F1L1D2F3L3F1U1L1U3R1D2B1R3D3R2F3U3R2B1U2B2D3B2D3B2R3F3U2B1R3B2R3B2U1 54 | L2D1F3R1U3D3L2F2U2R3F3B1U3L1U1B3U3L2B2R3B1R2B1L2D2L2B2L3B2R2B2L3U2D1B3 55 | R3L1F3B2R2F3B1L2U2F2B2U1F3D3R3F3B2L2D2F2D2B1D2F1L1D1F3D2F3D3R1L3U3B3D1 56 | D3B1U1R3F2B1L2D3R1D3B1U1L3B2L3B1D2B2D1B1L3D1F1U3L3F1U3D3R2L2B2R3F2U3B1 57 | L1F3B2D2L3F2B1U2L1D3L1U2L3U1B2D2R1U3R3D3F2B1D1L2F2R2B3U3L2F2U2F3D2B2R3 58 | F3L2F3R1D1R1D1R1D3B2D2L2U2R2U2L3F1L1D3B3U3L2U1B1D3L1U1D1B3R3D1F1B1R3B2 59 | F2L1B2U1B1D2B2L2F2B2U1B1U1B1D2B1R1B1L1D3R3F1B1R1L3B3D2B1U2D2R2L3D3R1B1 60 | L3F2B1U3B3D2F2R2U3R1U2D1R3L3B3D2L2F2B3U3D2L2B1U3L3D2L3B2L2F3U2L1U2D3R3 61 | B1U3D2B1U3F2R2F3D3L1F1D1L2F3U2L1B3U1D3L2B2L2D3F2L1B1R1B2L3U3D2F2U1R3L2 62 | U3R2D3B2D3B3R1B1U2L3D1R3B2L2U1F2R2B3U1D1F2D3R3B1R2D2L1B3U2L3B2R3L1D2L3 63 | R2D1R3F1D1L2B2U1B1U1F3R3F1L2D2R3B1D3R3L3U3L3F2U3L1B3R2L3U1F3L1B2R2B3R2 64 | B1U3R2D2F3R3B3D3F3R3B3L1B1U1D1R2D1B3R2U2L2D1R3D1F2U1R1U2B1R3D2L2F1D2B3 65 | -------------------------------------------------------------------------------- /src/nxprune.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include "nxprune.h" 25 | #include "alloc.h" 26 | 27 | using namespace vcube::nx; 28 | 29 | prune_base::prune_base(size_t stride) : os_unique(), index(), stride(stride) { 30 | os_unique_t os_tmp; 31 | auto os_next = os_unique.begin(); 32 | decltype(index_t::base) next_symcoord = 0; 33 | uint8_t _ignore_; 34 | 35 | for (corient_t corient = 0; corient < N_CORIENT; corient++) { 36 | // Find the representative corient by symmetry 37 | cube c; 38 | c.setCornerOrient(corient); 39 | auto corient_s = ccoord::rep(c, _ignore_).getCornerOrient(); 40 | 41 | // If this is a new representative, set base to a new coordinate; 42 | // otherwise copy base from the representative 43 | auto &idx = index[corient], &idx_s = index[corient_s]; 44 | idx.base = (corient == corient_s) ? next_symcoord : idx_s.base; 45 | 46 | // Fill in the offsets and symmetries 47 | decltype(offset_sym_t::offset) offset = 0; 48 | for (c4comb_t c4comb = 0; c4comb < N_C4COMB; c4comb++) { 49 | c.setCorner4Comb(c4comb); 50 | c.setCornerOrient(corient); 51 | 52 | auto &os = os_tmp[c4comb]; 53 | auto c4comb_s = ccoord::rep(c, os.sym).getCorner4Comb(); 54 | auto &os_s = idx_s.os[c4comb_s]; 55 | 56 | if (corient != corient_s) { 57 | os.offset = os_s.offset; 58 | } else if (c4comb != c4comb_s) { 59 | os.offset = os_tmp[c4comb_s].offset; 60 | } else { 61 | os.offset = offset++; 62 | } 63 | } 64 | 65 | next_symcoord += offset; 66 | 67 | // Deduplicate 68 | auto os_found = std::find(os_unique.begin(), os_next, os_tmp); 69 | if (os_found == os_next) { 70 | *os_next++ = os_tmp; 71 | } 72 | 73 | idx.os = &(*os_found)[0]; 74 | } 75 | } 76 | 77 | void prune_base::setPrune(decltype(index_t::prune) p) { 78 | for (auto &idx : index) { 79 | idx.prune = p + idx.base * stride; 80 | } 81 | } 82 | 83 | bool prune_base::save(const std::string &filename) const { 84 | auto dir = filename; 85 | (void) mkdir(dirname(dir.data()), 0777); 86 | 87 | auto tmpname = filename + ".tmp"; 88 | FILE *fp = fopen(tmpname.c_str(), "w"); 89 | if (!fp) { 90 | return false; 91 | } 92 | size_t n = fwrite(index[0].prune, stride, N_CORNER_SYM, fp); 93 | if (fclose(fp)) { 94 | return false; 95 | } 96 | if (n != N_CORNER_SYM) { 97 | return false; 98 | } 99 | return rename(tmpname.c_str(), filename.c_str()) == 0; 100 | } 101 | 102 | bool prune_base::load(const std::string &filename) { 103 | size_t sz = stride * N_CORNER_SYM; 104 | 105 | FILE *fp = fopen(filename.c_str(), "r"); 106 | if (!fp) { 107 | return false; 108 | } 109 | 110 | auto mem = alloc::huge(sz); 111 | if (!mem) { 112 | fclose(fp); 113 | return false; 114 | } 115 | 116 | size_t n = fread(mem, stride, N_CORNER_SYM, fp); 117 | if (fclose(fp)) { 118 | return false; 119 | } 120 | 121 | if (n != N_CORNER_SYM) { 122 | return false; 123 | } 124 | 125 | setPrune(mem); 126 | 127 | return true; 128 | } 129 | 130 | bool prune_base::loadShared(uint32_t key, const std::string &filename) { 131 | size_t sz = stride * N_CORNER_SYM; 132 | 133 | auto mem = alloc::shared(sz, key, false); 134 | if (!mem) { 135 | if (filename.empty()) { 136 | return false; 137 | } 138 | 139 | mem = alloc::shared(sz, key, true); 140 | if (!mem) { 141 | return false; 142 | } 143 | 144 | FILE *fp = fopen(filename.c_str(), "r"); 145 | if (!fp) { 146 | return false; 147 | } 148 | 149 | size_t n = fread(mem, stride, N_CORNER_SYM, fp); 150 | if (fclose(fp)) { 151 | return false; 152 | } 153 | 154 | if (n != N_CORNER_SYM) { 155 | return false; 156 | } 157 | } 158 | 159 | setPrune(mem); 160 | 161 | return true; 162 | } 163 | 164 | std::vector prune_base::getCornerRepresentatives() const { 165 | std::vector cv; 166 | for (corient_t corient = 0; corient < N_CORIENT; corient++) { 167 | auto &idx = index[corient]; 168 | for (c4comb_t c4comb = 0; c4comb < N_C4COMB; c4comb++) { 169 | auto &os = idx.os[c4comb]; 170 | if (os.sym == 0) { 171 | cube c; 172 | c.setCorner4Comb(c4comb); 173 | c.setCornerOrient(corient); 174 | cv.push_back(c); 175 | } 176 | } 177 | } 178 | return cv; 179 | } 180 | -------------------------------------------------------------------------------- /src/gen-const-cubes.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #define GENERATE_CONST_CUBES 20 | namespace vcube { 21 | class cube; 22 | static cube *moves = 0; 23 | static cube *sym = 0; 24 | static int *sym_inv = 0; 25 | static int move_sym6[18][8]; 26 | } 27 | 28 | #include 29 | #include 30 | #include "cube.h" 31 | 32 | namespace vcube { 33 | cube S_URF3i, S_F2; 34 | } 35 | 36 | #include "cube6.h" 37 | 38 | using namespace vcube; 39 | 40 | /* Generates the file "const-cubes.h". This setup should be done as a 41 | * constexpr function instead, but compilers currently do not support 42 | * SSE/AVX intrinsics within constexpr functions. 43 | */ 44 | 45 | std::string out(const cube &c) { 46 | std::string s(256, 0); 47 | auto u = reinterpret_cast(&c); 48 | s.resize(sprintf(&s[0], "{0x%016lx,0x%08lx,0x%016lx}", 49 | u[2], u[1] & 0xffffffff, u[0])); 50 | return s; 51 | } 52 | 53 | int main() { 54 | S_URF3i = ~S_URF3; 55 | S_F2 = S_URF3 * S_U4 * S_U4 * S_URF3i; 56 | 57 | /* Symmetries */ 58 | std::array sym; 59 | sym[0] = {}; 60 | sym[1] = S_LR2; 61 | for (int i = 2; i < 4; i++) sym[i] = S_F2 * sym[i - 2]; 62 | for (int i = 4; i < 16; i++) sym[i] = S_U4 * sym[i - 4]; 63 | for (int i = 16; i < 48; i++) sym[i] = S_URF3 * sym[i - 16]; 64 | 65 | /* Find the inverse of all symmetries */ 66 | std::array sym_inv; 67 | for (int i = 0; i < 48; i++) { 68 | for (int j = i; j < 48; j += 2) { 69 | if (sym[i].compose(sym[j], i & 1) == cube()) { 70 | sym_inv[i] = j; 71 | sym_inv[j] = i; 72 | break; 73 | } 74 | } 75 | } 76 | 77 | /* Moves */ 78 | // Generate symmetries that map the U move to U, R, F, D, L, B 79 | std::array face_sym; 80 | face_sym[0] = {}; // U 81 | for (int i = 1; i < 3; i++) face_sym[i] = S_URF3 * face_sym[i - 1]; // R, F 82 | for (int i = 3; i < 6; i++) face_sym[i] = S_F2 * face_sym[i - 3]; // D, L, B 83 | 84 | // Use the symmetries to produce all 18 half-turn metric moves 85 | std::array moves; 86 | for (int face = 0, i = 0; face < 6; face++, i += 3) { 87 | moves[i] = ~face_sym[face] * M_U * face_sym[face]; // M 88 | moves[i + 1] = moves[i] * moves[i]; // M2 89 | moves[i + 2] = moves[i] * moves[i + 1]; // M' 90 | } 91 | 92 | // 6-way move symmetry table (S_URF3 * inverse) 93 | std::map move_number; 94 | for (int m = 0; m < moves.size(); m++) { 95 | move_number[moves[m]] = m; 96 | } 97 | uint8_t move_sym6[18][6]; 98 | for (int m = 0; m < 18; m++) { 99 | cube6 c6 = moves[m]; 100 | for (int s = 0; s < 6; s++) { 101 | move_sym6[m][s] = move_number[c6[s]]; 102 | } 103 | } 104 | 105 | /* Output the generated tables */ 106 | puts("// Code generated by gen-const-cubes - DO NOT EDIT"); 107 | puts(""); 108 | 109 | puts("/* S_F2 - 180-degree rotation on F-B axis (z2) */"); 110 | printf("static constexpr cube S_F2 = %s;\n\n", out(S_F2).c_str()); 111 | 112 | puts("/* Inverse symmetries */"); 113 | printf("static constexpr cube S_URF3i = %s;\n\n", out(S_URF3i).c_str()); 114 | 115 | puts("/* Move table:"); 116 | puts(" * U, U2, U', R, R2, R', F, F2, F', D, D2, D', L, L2, L', B, B2, B'"); 117 | puts(" */"); 118 | printf("static constexpr std::array moves = {{\n", moves.size()); 119 | for (auto &c : moves) { 120 | printf("\t%s,\n", out(c).c_str()); 121 | } 122 | puts("}};"); 123 | puts(""); 124 | 125 | puts("/* Move 6-way symmetries (URF3 * Inverse) */"); 126 | printf("static constexpr uint8_t move_sym6[%ld][8] = {\n", moves.size()); 127 | for (int m = 0; m < 18; m++) { 128 | fputs("\t{", stdout); 129 | for (int i = 0; i < 6; i++) { 130 | printf(" %2d,", move_sym6[m][i]); 131 | } 132 | puts(" },"); 133 | } 134 | puts("};"); 135 | puts(""); 136 | 137 | puts("/* Symmetries (0..47):"); 138 | puts(" * S_LR2 (0, 1)"); 139 | puts(" * S_F2 (0, 2)"); 140 | puts(" * S_U4 (0, 4, 8, 12)"); 141 | puts(" * S_URF3 (0, 16, 32)"); 142 | puts(" */"); 143 | printf("static constexpr std::array sym = {{\n", sym.size()); 144 | for (auto &c : sym) { 145 | printf("\t%s,\n", out(c).c_str()); 146 | } 147 | puts("}};"); 148 | puts(""); 149 | 150 | printf("/* Inverse symmetry map */\n"); 151 | printf("static constexpr std::array sym_inv = {\n", sym_inv.size()); 152 | for (int i = 0; i < sym_inv.size(); i++) { 153 | putchar((i % 16) ? ' ' : '\t'); 154 | printf("%2d,", sym_inv[i]); 155 | if (i % 16 == 15) { 156 | putchar('\n'); 157 | } 158 | } 159 | puts("};"); 160 | 161 | return 0; 162 | } 163 | -------------------------------------------------------------------------------- /src/const-cubes.h: -------------------------------------------------------------------------------- 1 | // Code generated by gen-const-cubes - DO NOT EDIT 2 | 3 | /* S_F2 - 180-degree rotation on F-B axis (z2) */ 4 | static constexpr cube S_F2 = {0x0203000106070405,0x0a0b0809,0x0300010207040506}; 5 | 6 | /* Inverse symmetries */ 7 | static constexpr cube S_URF3i = {0x2516221114271320,0x05070301,0x161a1219141b1018}; 8 | 9 | /* Move table: 10 | * U, U2, U', R, R2, R', F, F2, F', D, D2, D', L, L2, L', B, B2, B' 11 | */ 12 | static constexpr std::array moves = {{ 13 | {0x0706050402010003,0x0b0a0908,0x0706050402010003}, 14 | {0x0706050401000302,0x0b0a0908,0x0706050401000302}, 15 | {0x0706050400030201,0x0b0a0908,0x0706050400030201}, 16 | {0x2306051710020124,0x000a0904,0x0706050b03020108}, 17 | {0x0006050304020107,0x080a090b,0x0706050003020104}, 18 | {0x2406051017020123,0x040a0900,0x070605080302010b}, 19 | {0x0706142003022511,0x0b0a1511,0x0706180403021900}, 20 | {0x0706000103020405,0x0b0a0809,0x0706010403020500}, 21 | {0x0706112503022014,0x0b0a1115,0x0706190403021800}, 22 | {0x0407060503020100,0x0b0a0908,0x0407060503020100}, 23 | {0x0504070603020100,0x0b0a0908,0x0504070603020100}, 24 | {0x0605040703020100,0x0b0a0908,0x0605040703020100}, 25 | {0x0715210403261200,0x0b060208,0x07090504030a0100}, 26 | {0x0701020403050600,0x0b090a08,0x0702050403060100}, 27 | {0x0712260403211500,0x0b020608,0x070a050403090100}, 28 | {0x1622050427130100,0x17130908,0x1a0605041b020100}, 29 | {0x0203050406070100,0x0a0b0908,0x0306050407020100}, 30 | {0x1327050422160100,0x13170908,0x1b0605041a020100}, 31 | }}; 32 | 33 | /* Move 6-way symmetries (URF3 * Inverse) */ 34 | static constexpr uint8_t move_sym6[18][8] = { 35 | { 0, 6, 3, 2, 8, 5, }, 36 | { 1, 7, 4, 1, 7, 4, }, 37 | { 2, 8, 5, 0, 6, 3, }, 38 | { 3, 0, 6, 5, 2, 8, }, 39 | { 4, 1, 7, 4, 1, 7, }, 40 | { 5, 2, 8, 3, 0, 6, }, 41 | { 6, 3, 0, 8, 5, 2, }, 42 | { 7, 4, 1, 7, 4, 1, }, 43 | { 8, 5, 2, 6, 3, 0, }, 44 | { 9, 15, 12, 11, 17, 14, }, 45 | { 10, 16, 13, 10, 16, 13, }, 46 | { 11, 17, 14, 9, 15, 12, }, 47 | { 12, 9, 15, 14, 11, 17, }, 48 | { 13, 10, 16, 13, 10, 16, }, 49 | { 14, 11, 17, 12, 9, 15, }, 50 | { 15, 12, 9, 17, 14, 11, }, 51 | { 16, 13, 10, 16, 13, 10, }, 52 | { 17, 14, 11, 15, 12, 9, }, 53 | }; 54 | 55 | /* Symmetries (0..47): 56 | * S_LR2 (0, 1) 57 | * S_F2 (0, 2) 58 | * S_U4 (0, 4, 8, 12) 59 | * S_URF3 (0, 16, 32) 60 | */ 61 | static constexpr std::array sym = {{ 62 | {0x0706050403020100,0x0b0a0908,0x0706050403020100}, 63 | {0x0607040502030001,0x0a0b0809,0x0704050603000102}, 64 | {0x0203000106070405,0x0a0b0809,0x0300010207040506}, 65 | {0x0302010007060504,0x0b0a0908,0x0302010007060504}, 66 | {0x0605040702010003,0x1a19181b,0x0605040702010003}, 67 | {0x0506070401020300,0x191a1b18,0x0607040502030001}, 68 | {0x0102030005060704,0x191a1b18,0x0203000106070405}, 69 | {0x0201000306050407,0x1a19181b,0x0201000306050407}, 70 | {0x0504070601000302,0x09080b0a,0x0504070601000302}, 71 | {0x0405060700010203,0x08090a0b,0x0506070401020300}, 72 | {0x0001020304050607,0x08090a0b,0x0102030005060704}, 73 | {0x0100030205040706,0x09080b0a,0x0100030205040706}, 74 | {0x0407060500030201,0x181b1a19,0x0407060500030201}, 75 | {0x0704050603000102,0x1b18191a,0x0405060700010203}, 76 | {0x0300010207040506,0x1b18191a,0x0001020304050607}, 77 | {0x0003020104070605,0x181b1a19,0x0003020104070605}, 78 | {0x1226172321152410,0x12161410,0x0a170b1309150811}, 79 | {0x2612231715211024,0x16121014,0x0a130b1709110815}, 80 | {0x1521102426122317,0x16121014,0x091108150a130b17}, 81 | {0x2115241012261723,0x12161410,0x091508110a170b13}, 82 | {0x2617231215241021,0x06040002,0x170b130a15081109}, 83 | {0x1726122324152110,0x04060200,0x170a130b15091108}, 84 | {0x2415211017261223,0x04060200,0x15091108170a130b}, 85 | {0x1524102126172312,0x06040002,0x15081109170b130a}, 86 | {0x1723122624102115,0x14101216,0x0b130a1708110915}, 87 | {0x2317261210241521,0x10141612,0x0b170a1308150911}, 88 | {0x1024152123172612,0x10141612,0x081509110b170a13}, 89 | {0x2410211517231226,0x14101216,0x081109150b130a17}, 90 | {0x2312261710211524,0x00020604,0x130a170b11091508}, 91 | {0x1223172621102415,0x02000406,0x130b170a11081509}, 92 | {0x2110241512231726,0x02000406,0x11081509130b170a}, 93 | {0x1021152423122617,0x00020604,0x11091508130a170b}, 94 | {0x2516221114271320,0x05070301,0x161a1219141b1018}, 95 | {0x1625112227142013,0x07050103,0x1619121a1418101b}, 96 | {0x2714201316251122,0x07050103,0x1418101b1619121a}, 97 | {0x1427132025162211,0x05070301,0x141b1018161a1219}, 98 | {0x1622112527132014,0x17131115,0x1a1219161b101814}, 99 | {0x2216251113271420,0x13171511,0x1a1619121b141810}, 100 | {0x1327142022162511,0x13171511,0x1b1418101a161912}, 101 | {0x2713201416221125,0x17131115,0x1b1018141a121916}, 102 | {0x2211251613201427,0x03010507,0x1219161a1018141b}, 103 | {0x1122162520132714,0x01030705,0x121a1619101b1418}, 104 | {0x2013271411221625,0x01030705,0x101b1418121a1619}, 105 | {0x1320142722112516,0x03010507,0x1018141b1219161a}, 106 | {0x1125162220142713,0x11151713,0x19161a1218141b10}, 107 | {0x2511221614201327,0x15111317,0x19121a1618101b14}, 108 | {0x1420132725112216,0x15111317,0x18101b1419121a16}, 109 | {0x2014271311251622,0x11151713,0x18141b1019161a12}, 110 | }}; 111 | 112 | /* Inverse symmetry map */ 113 | static constexpr std::array sym_inv = { 114 | 0, 1, 2, 3, 12, 5, 6, 15, 8, 9, 10, 11, 4, 13, 14, 7, 115 | 32, 35, 42, 41, 20, 21, 28, 29, 34, 33, 40, 43, 22, 23, 30, 31, 116 | 16, 25, 24, 17, 38, 37, 36, 39, 26, 19, 18, 27, 44, 47, 46, 45, 117 | }; 118 | -------------------------------------------------------------------------------- /tests/NxPruneTest.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "nxprune.h" 20 | 21 | #include "test_util.h" 22 | #include "CppUTest/TestHarness.h" 23 | 24 | using namespace vcube; 25 | 26 | TEST_GROUP(NxPrune) { 27 | }; 28 | 29 | TEST(NxPrune, CCoord) { 30 | cube c; 31 | 32 | LONGS_EQUAL(0, nx::ccoord(c)); 33 | 34 | for (c4comb_t i = 0; i < N_C4COMB; i++) { 35 | c = t::random_cube(); 36 | c.setCorner4Comb(i); 37 | c.setCornerOrient(t::rand(N_CORIENT)); 38 | LONGS_EQUAL(i, nx::ccoord(c) & 0xff); 39 | } 40 | 41 | for (corient_t i = 0; i < N_CORIENT; i++) { 42 | c = t::random_cube(); 43 | c.setCornerOrient(i); 44 | LONGS_EQUAL(c.getCornerOrientRaw(), nx::ccoord(c) >> 8); 45 | } 46 | } 47 | 48 | TEST(NxPrune, CCoordRep) { 49 | cube c; 50 | uint8_t sym; 51 | 52 | /* Rep has the lowest coordinate */ 53 | for (int i = 0; i < 1000; i++) { 54 | c = nx::ccoord::rep(t::random_cube(), sym); 55 | nx::ccoord best(c); 56 | for (int s = 0; s < 16; s++) { 57 | CHECK(best <= nx::ccoord(c.symConjugate(s))); 58 | } 59 | } 60 | 61 | /* Symmetry is set correctly */ 62 | for (int i = 0; i < 1000; i++) { 63 | c = t::random_cube(); 64 | cube rep = nx::ccoord::rep(c, sym); 65 | CHECK(c.symConjugate(sym) == rep); 66 | } 67 | } 68 | 69 | TEST(NxPrune, EP1) { 70 | cube c; 71 | 72 | using ecoord = nx::ecoord; 73 | 74 | LONGS_EQUAL(2, ecoord(c)); 75 | 76 | uint32_t prev = ecoord(c); 77 | for (e4comb_t i = 1; i < N_E4COMB; i++) { 78 | c = t::random_cube(); 79 | c.setEdge4Comb(i); 80 | c.setEdgeOrient(t::rand(N_EORIENT)); 81 | uint32_t ecomb = ecoord(c) & 0x1ff; 82 | CHECK(ecomb % 64 >= 2); // Gaps are in the correct places 83 | CHECK(prev < ecomb); // Coordinate monotonically increases 84 | prev = ecomb; 85 | } 86 | LONGS_EQUAL(prev, N_E4COMB + 2 * ((N_E4COMB + 63) / 64) - 1); 87 | 88 | /* Fuzz test */ 89 | for (int i = 0; i < 1000; i++) { 90 | cube c = t::random_cube(); 91 | c.setEdgeOrient(0); 92 | LONGS_EQUAL(0, ecoord(c) >> 9); 93 | } 94 | } 95 | 96 | TEST(NxPrune, EP2) { 97 | cube c; 98 | 99 | using ecoord = nx::ecoord; 100 | using ecoord14 = nx::ecoord; 101 | 102 | LONGS_EQUAL(2, ecoord(c)); 103 | 104 | /* EP1 field matches */ 105 | for (int i = 0; i < 1000; i++) { 106 | cube c = t::random_cube(); 107 | LONGS_EQUAL(ecoord14(c) & 0x1ff, ecoord(c) & 0x1ff); 108 | } 109 | 110 | /* E4Perm field matches */ 111 | for (e4perm_t i = 0; i < N_E4PERM; i++) { 112 | c.setEdge4Perm(i); 113 | LONGS_EQUAL(i, ecoord(c) >> 13); 114 | } 115 | 116 | /* Fuzz test */ 117 | for (int i = 0; i < 1000; i++) { 118 | cube c = t::random_cube(); 119 | LONGS_EQUAL(c.getEdge4Perm(), ecoord(c) >> 13); 120 | } 121 | } 122 | 123 | TEST(NxPrune, EP3) { 124 | cube c; 125 | 126 | using ecoord = nx::ecoord; 127 | using ecoord14 = nx::ecoord; 128 | 129 | LONGS_EQUAL(2, ecoord(c)); 130 | 131 | /* EP1 field matches */ 132 | for (int i = 0; i < 1000; i++) { 133 | cube c = t::random_cube(); 134 | LONGS_EQUAL(ecoord14(c) & 0x1ff, ecoord(c) & 0x1ff); 135 | } 136 | 137 | /* UD4Comb field matches */ 138 | for (eud4comb_t i = 0; i < N_EUD4COMB; i++) { 139 | c.setEdgeUD4Comb(i); 140 | LONGS_EQUAL(i, ecoord(c) >> 13); 141 | } 142 | 143 | /* Fuzz test */ 144 | for (int i = 0; i < 1000; i++) { 145 | cube c = t::random_cube(); 146 | LONGS_EQUAL(c.getEdgeUD4Comb(), ecoord(c) >> 13); 147 | } 148 | } 149 | 150 | TEST(NxPrune, EP4) { 151 | cube c; 152 | 153 | using ecoord = nx::ecoord; 154 | using ecoord14 = nx::ecoord; 155 | 156 | LONGS_EQUAL(2, ecoord(c)); 157 | 158 | /* EP1 field matches */ 159 | for (int i = 0; i < 1000; i++) { 160 | cube c = t::random_cube(); 161 | LONGS_EQUAL(ecoord14(c) & 0x1ff, ecoord(c) & 0x1ff); 162 | } 163 | 164 | /* E4Perm field matches */ 165 | for (e4perm_t i = 0; i < N_E4PERM; i++) { 166 | c.setEdge4Perm(i); 167 | LONGS_EQUAL(i, (ecoord(c) >> 13) % N_E4PERM); 168 | } 169 | 170 | /* UD4Comb field matches */ 171 | for (eud4comb_t i = 0; i < N_EUD4COMB; i++) { 172 | c.setEdgeUD4Comb(i); 173 | LONGS_EQUAL(i, (ecoord(c) >> 13) / N_E4PERM); 174 | } 175 | 176 | /* Fuzz test */ 177 | for (int i = 0; i < 1000; i++) { 178 | cube c = t::random_cube(); 179 | LONGS_EQUAL(c.getEdge4Perm(), (ecoord(c) >> 13) % N_E4PERM); 180 | LONGS_EQUAL(c.getEdgeUD4Comb(), (ecoord(c) >> 13) / N_E4PERM); 181 | } 182 | } 183 | 184 | TEST(NxPrune, EO4) { 185 | cube c; 186 | 187 | using ecoord = nx::ecoord; 188 | 189 | LONGS_EQUAL(2, ecoord(c)); 190 | 191 | /* Edge4Orient field matches */ 192 | for (e4orient_t i = 0; i < N_E4ORIENT; i++) { 193 | c = t::random_cube(); 194 | c.setEdge4Orient(i); 195 | LONGS_EQUAL(i, (ecoord(c) >> 9) & 0xf); 196 | } 197 | } 198 | 199 | TEST(NxPrune, EO8) { 200 | cube c; 201 | 202 | using ecoord = nx::ecoord; 203 | 204 | LONGS_EQUAL(2, ecoord(c)); 205 | 206 | /* Edge8Orient field matches */ 207 | for (e8orient_t i = 0; i < N_E8ORIENT; i++) { 208 | c = t::random_cube(); 209 | c.setEdge8Orient(i); 210 | LONGS_EQUAL(i, (ecoord(c) >> 9) & 0xff); 211 | } 212 | } 213 | 214 | TEST(NxPrune, EO12) { 215 | cube c; 216 | 217 | using ecoord = nx::ecoord; 218 | 219 | LONGS_EQUAL(2, ecoord(c)); 220 | 221 | /* EdgeOrient field matches */ 222 | for (eorient_t i = 0; i < N_EORIENT; i++) { 223 | c = t::random_cube(); 224 | c.setEdgeOrient(i); 225 | LONGS_EQUAL(i, (ecoord(c) >> 9) & 0x7ff); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/nxsolve.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "cube6.h" 20 | #include 21 | #include 22 | 23 | /* The nxprune table is an inconsistent heuristic, so Bidirectional PathMax 24 | * can offer additional pruning opportunities. The reduction in node 25 | * expansions is very small, however. 26 | */ 27 | #define VCUBE_NX_USE_BPMX 1 28 | 29 | namespace vcube::nx { 30 | 31 | class solver_base { 32 | protected: 33 | struct queue_t { 34 | cube6 c6; 35 | uint32_t moves; 36 | uint8_t last_face; 37 | 38 | queue_t() : c6(), moves(), last_face() { 39 | } 40 | 41 | queue_t(const cube6 &c6, uint32_t moves, uint8_t last_face) : 42 | c6(c6), moves(moves), last_face(last_face) 43 | { 44 | } 45 | }; 46 | 47 | // List of all cubes at depth=4 48 | static std::vector depth4; 49 | 50 | public: 51 | static void init(); 52 | }; 53 | 54 | template 55 | class solver : public solver_base { 56 | uint64_t n_expands; 57 | uint8_t moves[20], *movep; 58 | prune_t &P; 59 | 60 | // Lookup table for expanding a 3-bit axis mask into a move list 61 | static constexpr uint32_t axis_mask_expand[] = { 62 | 0777777, 0770770, 0707707, 0700700, 63 | 0077077, 0070070, 0007007, 0000000 }; 64 | 65 | // Lookup table for the list of canonical next moves following a 66 | // move of a given face. The value 6 denotes no previous move 67 | static constexpr uint32_t last_face_mask[] = { 68 | 0777770, 0777707, 0777077, 0770770, 0707707, 0077077, 0777777 }; 69 | static constexpr uint8_t NO_FACE = 6; 70 | 71 | public: 72 | solver(prune_t &P) : P(P), n_expands(), moves(), movep(moves) { 73 | } 74 | 75 | auto solve(const cube6 &c6, int limit = 20) { 76 | movep = moves; 77 | n_expands = 0; 78 | 79 | uint8_t len = 0xff; 80 | auto limit1 = std::min(limit, prune_t::BASE + 4); 81 | for (int d = P.initial_depth(c6); d <= limit1; d++) { 82 | if (!search(c6, d, NO_FACE, NO_FACE, 0xff, 0)) { 83 | len = d; 84 | break; 85 | } 86 | } 87 | 88 | if (len == 0xff) { 89 | len = queue_search(c6, prune_t::BASE + 5, limit); 90 | if (len == 0xff) { 91 | len = 0; 92 | } 93 | } 94 | 95 | return get_moves(len); 96 | } 97 | 98 | /* Returns the cost of the previous solve */ 99 | uint64_t cost() const { 100 | return n_expands; 101 | } 102 | 103 | private: 104 | uint8_t search(const cube6 &c6, uint8_t max_depth, uint8_t last_face, uint8_t last_face_r, int skip, int val) { 105 | if (max_depth == 0) { 106 | return c6 != cube(); 107 | } 108 | 109 | uint32_t prune_vals; 110 | uint8_t axis_mask; 111 | uint8_t prune = P.lookup(c6, max_depth, prune_vals, skip, val, axis_mask); 112 | if (prune > max_depth) { 113 | return prune; 114 | } 115 | max_depth--; 116 | 117 | n_expands++; 118 | 119 | auto mask_f = axis_mask_expand[axis_mask >> 3] & last_face_mask[last_face]; 120 | auto mask_r = axis_mask_expand[axis_mask & 7] & last_face_mask[last_face_r]; 121 | 122 | // Choose direction with the smaller branching factor 123 | int dir = _popcnt32(mask_r) - _popcnt32(mask_f); 124 | if (dir == 0) { 125 | // Tiebreaker, direction with the larger sum of pruning values 126 | int32_t sum = 127 | ((prune_vals >> 8) & 0xf00f) + 128 | ((prune_vals >> 4) & 0xf00f) + 129 | ((prune_vals >> 0) & 0xf00f); 130 | dir = (sum & 0xfff) - (sum >> 12); 131 | } 132 | 133 | if (dir > 0) { 134 | // Forward 135 | while (mask_f) { 136 | uint8_t m = _tzcnt_u32(mask_f); 137 | mask_f = _blsr_u32(mask_f); 138 | 139 | uint8_t face = _popcnt32(011111 << m >> 15); 140 | uint8_t axis = (face + (face > 2)) & 3; 141 | 142 | // preserve one of the inverse cube pruning values 143 | skip = axis + 3; 144 | val = (prune_vals >> (4 * skip)) & 0xf; 145 | auto sol = search(c6.move(m), max_depth, face, last_face_r, skip, val); 146 | #if VCUBE_NX_USE_BPMX 147 | if (sol > max_depth + 2) { 148 | return sol - 1; 149 | } else if (sol == max_depth + 2) { 150 | #else 151 | if (sol > max_depth + 1) { 152 | #endif 153 | mask_f &= ~7L << (3 * face); 154 | } else if (!sol) { 155 | *movep++ = m; 156 | return 0; 157 | } 158 | } 159 | } else { 160 | // Reverse (pre-move) 161 | while (mask_r) { 162 | uint8_t m = _tzcnt_u32(mask_r); 163 | mask_r = _blsr_u32(mask_r); 164 | 165 | uint8_t face = _popcnt32(011111 << m >> 15); 166 | uint8_t axis = (face + (face > 2)) & 3; 167 | 168 | // preserve one of the forward cube pruning values 169 | skip = axis; 170 | val = (prune_vals >> (4 * skip)) & 0xf; 171 | auto sol = search(c6.premove(m), max_depth, last_face, face, skip, val); 172 | #if VCUBE_NX_USE_BPMX 173 | if (sol > max_depth + 2) { 174 | return sol - 1; 175 | } else if (sol == max_depth + 2) { 176 | #else 177 | if (sol > max_depth + 1) { 178 | #endif 179 | mask_r &= ~7L << (3 * face); 180 | } else if (!sol) { 181 | *movep++ = 0x80 | m; 182 | return 0; 183 | } 184 | } 185 | } 186 | 187 | return prune + !prune; 188 | } 189 | 190 | uint8_t queue_search(const cube6 &c6, uint8_t depth, int limit) { 191 | std::vector queue; 192 | 193 | for (const auto &q : depth4) { 194 | queue.emplace_back(c6 * q.c6, q.moves, q.last_face); 195 | } 196 | 197 | struct order_t { 198 | uint16_t idx; 199 | uint16_t density; 200 | order_t() : idx(), density() { 201 | } 202 | order_t(uint16_t idx, uint16_t density) : idx(idx), density(density) { 203 | } 204 | }; 205 | std::vector order, order_new; 206 | for (int i = 0; i < queue.size(); i++) { 207 | order.emplace_back(i, 0); 208 | } 209 | order_new.resize(order.size()); 210 | 211 | // Histograms for 2-pass radix sort 212 | std::array hist0, hist1; 213 | 214 | for (auto d = depth; d <= limit; d++) { 215 | hist0.fill(0); 216 | hist1.fill(0); 217 | for (auto &o : order) { 218 | auto &q = queue[o.idx]; 219 | auto old_cost = cost(); 220 | auto prune = search(q.c6, d - 4, q.last_face, NO_FACE, -1, 0); 221 | if (!prune) { 222 | auto moves = q.moves; 223 | for (int i = 0; i < 4; i++) { 224 | *movep++ = q.moves; 225 | q.moves >>= 8; 226 | } 227 | return d; 228 | } 229 | 230 | // Search next level in order of decreasing density 231 | // 58206:47525 approximates sqrt(3):sqrt(2) which is the 232 | // ratio of canonical sequences starting with URF vs DLB 233 | constexpr uint64_t ratio[] = { 58206, 47525 }; 234 | float density = (cost() - old_cost) * ratio[q.last_face < 3]; 235 | uint32_t u_density = *(uint32_t *) &density; 236 | o.density = ~(u_density >> 15); 237 | hist0[o.density & 0xff]++; 238 | hist1[o.density >> 8]++; 239 | } 240 | 241 | uint16_t sum0 = 0, sum1 = 0; 242 | for (int i = 0; i < 256; i++) { 243 | std::swap(sum0, hist0[i]); 244 | sum0 += hist0[i]; 245 | std::swap(sum1, hist1[i]); 246 | sum1 += hist1[i]; 247 | } 248 | for (auto &o : order) { 249 | order_new[hist0[o.density & 0xff]++] = o; 250 | } 251 | for (auto &o : order_new) { 252 | order[hist1[o.density >> 8]++] = o; 253 | } 254 | } 255 | 256 | return 0xff; 257 | } 258 | 259 | auto get_moves(uint8_t len) const { 260 | moveseq_t m(len); 261 | auto mi_f = m.begin(); 262 | auto mi_r = m.rbegin(); 263 | for (int i = len - 1; i >= 0; i--) { 264 | if (moves[i] & 0x80) { 265 | *mi_r++ = moves[i] ^ 0x80; 266 | } else { 267 | *mi_f++ = moves[i]; 268 | } 269 | } 270 | return m; 271 | } 272 | }; 273 | 274 | } 275 | -------------------------------------------------------------------------------- /src/nxprune_generator.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "nxprune.h" 26 | #include "alloc.h" 27 | 28 | namespace vcube::nx { 29 | 30 | template 31 | class prune_generator { 32 | struct neighbor_t { 33 | uint16_t first, second; 34 | mutable uint32_t moves, moves_inv; 35 | 36 | neighbor_t(uint16_t first = 0, uint16_t second = 0) 37 | : first(first), second(second), moves(), moves_inv() 38 | { 39 | } 40 | 41 | bool operator < (const neighbor_t &o) const { 42 | return (first == o.first) ? second < o.second : first < o.first; 43 | } 44 | }; 45 | 46 | public: 47 | prune_generator(Prune &P, int n_threads) : 48 | P(P), edge_rep(), corner_prune(), n_threads(), mod3_next_xor(), mod3_mask(), depth_xor() 49 | { 50 | this->n_threads = std::max(1, n_threads); 51 | edge_rep.init(); 52 | } 53 | 54 | void generate() { 55 | // Allocate the pruning table and initialize all to unvisited 56 | auto mem = alloc::huge(16 * Prune::N_EDGE_STRIPE * N_CORNER_SYM); 57 | memset(mem, 0xff, 16 * Prune::N_EDGE_STRIPE * N_CORNER_SYM); 58 | P.init(mem); 59 | 60 | // Neighbor tables 61 | auto corner_rep = P.getCornerRepresentatives(); 62 | auto neighbors = getNeighbors(corner_rep); 63 | 64 | std::vector<__m128i *> prune_row; 65 | for (uint32_t i = 0; i < N_CORNER_SYM; i++) { 66 | prune_row.push_back(reinterpret_cast<__m128i*>(P.getPruneRow(i))); 67 | } 68 | 69 | // Set the identity cube depth to zero 70 | mem[0] = 0xc0; 71 | uint64_t found = 1; 72 | 73 | // Filter to speed up passes where the frontier is sparse 74 | std::vector dirty(N_CORNER_SYM, 255); 75 | dirty[0] = 0; 76 | 77 | for (int depth = 0; depth <= Prune::BASE + 1; depth++) { 78 | // Upon reaching the pruning table base value, zero 79 | // all visited positions. This will leave two distinct 80 | // values in the table, 0 (visited), and 3 (unvisited). 81 | // The final two passes will fill in the 1 and 2 values. 82 | if (depth == Prune::BASE) { 83 | zero_visited(mem); 84 | } 85 | 86 | auto prev_found = found; 87 | 88 | // The initial passes set the values to (depth % 3); the 89 | // final two passes set them to (depth - Prune::BASE) 90 | uint8_t mod3 = (depth < Prune::BASE) ? (depth % 3) : (depth - Prune::BASE); 91 | 92 | // Values in the table are set by xoring with the existing 93 | // unvisited value, which is known to be 3 94 | mod3_next_xor = ((mod3 + 1) % 3) ^ 3; 95 | 96 | // Similarly, the stripe-min values are set by xoring 97 | // with the known unvisited value of 0xf 98 | depth_xor = (depth + 1) ^ 0xf; 99 | 100 | // This mask is used to quickly find values matching 101 | // the current frontier 102 | mod3 |= mod3 << 2; 103 | mod3 |= mod3 << 4; 104 | mod3_mask = _mm_set1_epi8(~mod3); 105 | 106 | auto t0 = std::chrono::steady_clock::now(); 107 | 108 | std::mutex mtx; 109 | std::vector workers; 110 | std::vector busy(N_CORNER_SYM), done(neighbors.size()); 111 | for (int i = 0; i < n_threads; i++) { 112 | workers.push_back(std::thread([&]() { 113 | mtx.lock(); 114 | bool ok; 115 | do { 116 | ok = false; 117 | size_t idx = 0; 118 | 119 | // Scan for and process unfinished neighbor pairs that are not 120 | // currently being processed by another thread 121 | for (auto &n : neighbors) { 122 | if (done[idx] || busy[n.first] || busy[n.second]) { 123 | idx++; 124 | continue; 125 | } 126 | 127 | if (depth < dirty[n.first] && depth < dirty[n.second]) { 128 | done[idx++] = true; 129 | continue; 130 | } else if (depth < dirty[n.first]) { 131 | dirty[n.first] = depth + 1; 132 | } else if (depth < dirty[n.second]) { 133 | dirty[n.second] = depth + 1; 134 | } 135 | 136 | ok = done[idx++] = busy[n.first] = busy[n.second] = true; 137 | mtx.unlock(); 138 | 139 | uint64_t this_found = 0; 140 | auto c = corner_rep[n.first]; 141 | auto v_src = prune_row[n.first], v_dst = prune_row[n.second]; 142 | 143 | // Try all moves that transition from one neighbor to the other 144 | for (auto moves = n.moves; ; moves = n.moves_inv) { 145 | while (moves) { 146 | int m = _tzcnt_u32(moves); 147 | moves = _blsr_u32(moves); 148 | 149 | // Try all self-symmetries of the goal coordinate 150 | auto c_m = c.move(m); 151 | auto goalc = ccoord(c_m.symConjugate(P.get_sym(c_m))); 152 | for (uint8_t sym = 0; sym < 16; sym++) { 153 | if (ccoord(c_m.symConjugate(sym)) == goalc) { 154 | this_found += generateCornerPair(v_src, v_dst, m, sym); 155 | } 156 | } 157 | } 158 | 159 | if (v_src < v_dst) { 160 | c = corner_rep[n.second]; 161 | std::swap(v_src, v_dst); 162 | } else { 163 | break; 164 | } 165 | } 166 | 167 | mtx.lock(); 168 | found += this_found; 169 | busy[n.first] = busy[n.second] = false; 170 | } 171 | } while (ok); 172 | mtx.unlock(); 173 | })); 174 | } 175 | 176 | for (auto &t : workers) { 177 | t.join(); 178 | } 179 | 180 | std::chrono::duration elapsed = std::chrono::steady_clock::now() - t0; 181 | 182 | fprintf(stderr, "depth=%u found=%lu (%.06f)\n", depth + 1, found - prev_found, elapsed.count()); 183 | } 184 | } 185 | 186 | private: 187 | Prune &P; 188 | ecoord_rep edge_rep; 189 | std::vector corner_prune; 190 | int n_threads; 191 | uint8_t mod3_next_xor; 192 | __m128i mod3_mask; 193 | uint8_t depth_xor; 194 | 195 | auto getNeighbors(const std::vector &corner_rep) { 196 | std::set nset; 197 | for (size_t idx0 = 0; idx0 < corner_rep.size(); idx0++) { 198 | for (int m = 0; m < N_MOVES; m++) { 199 | cube c = corner_rep[idx0].move(m); 200 | auto idx1 = P.sym_coord(c); 201 | if (idx0 <= idx1) { 202 | nset.emplace(idx0, idx1).first->moves |= 1 << m; 203 | } else { 204 | nset.emplace(idx1, idx0).first->moves_inv |= 1 << m; 205 | } 206 | } 207 | } 208 | return std::vector(nset.begin(), nset.end()); 209 | } 210 | 211 | void zero_visited(uint8_t *mem) { 212 | /* Zero all visited nodes in the table, but preserve stripe-min field 213 | * This will leave two distinct values in the table: 214 | * 0: Visited; depth <= base 215 | * 3: Unvisited; depth > base 216 | * This prepares for the final two passes which fill in the "1" and "2" values 217 | */ 218 | auto end = (__m128i *) mem + Prune::N_EDGE_STRIPE * N_CORNER_SYM; 219 | for (auto v = (__m128i *) mem; v != end; v++) { 220 | auto &s = *v; 221 | // save the stripe-min field 222 | auto min = _mm_and_si128(s, _mm_set_epi64x(0, 0xf)); 223 | // zero out all non 3 values 224 | s = _mm_and_si128(s, _mm_srli_epi64(s, 1)); 225 | s = _mm_and_si128(s, _mm_set1_epi8(0x55)); 226 | s = _mm_or_si128(s, _mm_slli_epi64(s, 1)); 227 | // restore the stripe-min field 228 | s = _mm_or_si128(s, min); 229 | } 230 | } 231 | 232 | uint64_t generateCornerPair(__m128i *src, __m128i *dst, uint8_t m, uint8_t sym) { 233 | uint64_t found = 0; 234 | uint32_t stripe_idx = 0; 235 | for (auto src_end = src + Prune::N_EDGE_STRIPE; src != src_end; src++, stripe_idx++) { 236 | // Skip untouched stripe (stripe-min is 0xf) 237 | if ((_mm_extract_epi8(*src, 0) & 0xf) == 0xf) { 238 | continue; 239 | } 240 | 241 | auto [ high, low, eo ] = Prune::ecoord::decode(stripe_idx << 6); 242 | 243 | auto cmp = _mm_xor_si128(*src, mod3_mask); 244 | cmp = _mm_and_si128(cmp, _mm_srli_epi64(cmp, 1)); 245 | uint64_t bits = 246 | _pext_u64(_mm_extract_epi64(cmp, 1), 0x5555555555555555) << 32 | 247 | _pext_u64(_mm_extract_epi64(cmp, 0), 0x5555555555555555); 248 | bits &= (low == 448) ? 0x7ffffffffffffffc : 0xfffffffffffffffc; 249 | while (bits) { 250 | int b = _tzcnt_u64(bits); 251 | bits = _blsr_u64(bits); 252 | 253 | edgecube rep = edge_rep.get(high, low + b, eo); 254 | 255 | typename Prune::ecoord coord(rep.move(m), sym); 256 | 257 | auto s_d = (uint8_t *) &dst[coord / 64]; 258 | auto &s_octet = s_d[(coord / 4) % 16]; 259 | auto s_shift = (coord % 4) * 2; 260 | if (((s_octet >> s_shift) & 3) == 3) { 261 | s_octet ^= mod3_next_xor << s_shift; 262 | // set stripe-min if not already set 263 | if ((s_d[0] & 0xf) == 0xf) { 264 | s_d[0] ^= depth_xor; 265 | } 266 | found++; 267 | } 268 | } 269 | } 270 | 271 | return found; 272 | } 273 | }; 274 | 275 | } 276 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vcube 2 | 3 | vcube is a fast optimal half-turn metric Rubik's cube solver that takes 4 | advantage of vector instructions added in Intel's Haswell microarchitecture. 5 | It is a rewrite of the optimal solver behind 6 | [Voltara's Optimal Scrambler](https://voltara.org/cube/) and combines the 7 | best ideas from the original solver, and from Tomas Rokicki's 8 | [nxopt](https://github.com/rokicki/cube20src). 9 | 10 | Using 22 GiB of memory on an Intel Kaby Lake i7-7700K test machine, vcube 11 | achieved an average solution rate of 6.0 cubes/second on a set of 10,000 12 | randomly generated cubes. As a comparison, the original solver found 3.0 13 | solutions/second, and nxopt found 3.8 solutions/second on the same hardware. 14 | 15 | ## Getting started 16 | 17 | ### Prerequisites 18 | 19 | * Intel Haswell or equivalent processor (requires AVX2, BMI, BMI2, and LZCNT) 20 | * 64-bit Linux (developed on Ubuntu 18.04.1 LTS) 21 | * Clang (developed on 6.0.0) 22 | * CMake (developed on 3.10.2) 23 | * Cpputest (developed on 3.8) 24 | 25 | ### Building 26 | 27 | ``` 28 | cmake . 29 | make -j 30 | ``` 31 | 32 | ## Running the tests 33 | 34 | Tests are run automatically during the build, but can also be invoked manually: 35 | 36 | ``` 37 | ./tests/check 38 | ``` 39 | 40 | ## Using the software 41 | 42 | ### Selecting and generating the pruning table 43 | 44 | The `vc-optimal` solver supports pruning tables (pattern databases) of 45 | varying size. Larger tables require more memory, but solve cubes faster 46 | than smaller tables. Run `./vc-optimal --help` to see the list of supported 47 | table coordinates and their memory requirements, and select an appropriate 48 | one for your system. The software was developed on a 32 GiB machine using 49 | coordinate "308" (22 GiB), so that is the default. Additional, larger 50 | tables can be enabled by uncommenting them in `src/vc-optimal.cpp` 51 | (however, they need to be tuned with an appropriate base depth which can 52 | be determined experimentally.) 53 | 54 | Tables are generated automatically the first time they are used. 55 | For example, to use the 7.3 GiB "208" table: 56 | ``` 57 | ./vc-optimal --coord=208 --no-input 58 | ``` 59 | 60 | ### Solving cubes 61 | 62 | To solve cubes, run `vc-optimal` with the desired command-line options, 63 | then feed it cubes on standard input, one cube per line. Several input 64 | formats are supported: 65 | 66 | #### Move sequence 67 | 68 | Cubes can be specified as a move sequence with the `--format=moves` 69 | option (default). 70 | ``` 71 | D' F' D' L B' L' D2 F2 L F U' R U F2 L' 72 | D3F3D3L1B3L3D2F2L1F1U3R1U1F2L3 73 | DDDFFFDDDLBBBLLLDDFFLFUUURUFFLLL 74 | ``` 75 | 76 | #### Michael Reid's solver notation 77 | 78 | The `--format=reid` option allows specifying the cube sticker by sticker. 79 | 80 | Solved cube: 81 | ``` 82 | UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR 83 | ``` 84 | 85 | Cube within a cube pattern: 86 | ``` 87 | UF UR FL FD BR BU DB DL FR RD LU BL UFR FUL FLD FDR BUR BRD DLB BLU 88 | ``` 89 | 90 | #### Speffz 91 | 92 | The [Speffz](https://www.speedsolving.com/wiki/index.php/Speffz) lettering 93 | scheme is helpful for those familiar with blindfolded solving, and can be 94 | used to input manually cubes quickly. Speffz input can be chosen either 95 | by `--format=speffz` (which uses the default corner/edge buffers of A/U), 96 | or `--speffz=CE` where C and E are the desired corner and edge buffers. 97 | 98 | Example input for the cube-within-cube-within-cube pattern (default buffers): 99 | ``` 100 | lopbipJS.teloal 101 | ``` 102 | 103 | In the example, `lopbip` is the permutation of corner stickers, `JS` are 104 | corners twisted in place (and is shorthand for `jcsx`), and `teloal` is 105 | the edge permutation. If there were uppercase edges, they would denote 106 | edges flipped in place. In-place reorientations affect the buffer in the 107 | opposite direction. The notation is written as a blindfolded solver might 108 | memorize a cube, so it reads as a list of steps to *solve* the cube. 109 | 110 | ## Example 111 | 112 | This example uses the 22 GiB "308" table and uses the `--ordered` option 113 | to output solutions in the same order as the input. The solver automatically 114 | spawns one thread per CPU core, in this case 8 threads. 115 | 116 | Each output line includes the input line number (0 through 15), real time 117 | spent, number of moves, and solution. The final output line, which is 118 | printed to standard error, gives the overall amount of real and CPU time 119 | spent solving; this excludes time reading or generating the pruning table. 120 | 121 | ``` 122 | $ head -16 test64.txt | ./vc-optimal -c 308 --ordered 123 | 0 4.787566821 18 F' L2 D2 B U D2 F2 D' L2 U F D R U B' D R L 124 | 1 0.856710460 18 D R U2 F B2 U F2 L' F' D L' U D2 F' R2 D2 B' U2 125 | 2 0.647874028 18 B2 R' U' D' R2 F D' R2 B2 R' U F2 B' R' U R' D2 F' 126 | 3 1.939559155 18 U' D L2 B D F2 L' F2 D2 F' R2 U2 D B R L2 D R' 127 | 4 0.759255970 18 R2 U' L' U2 R' L U' B' U' L F2 D' L' U R2 D' F' L' 128 | 5 0.086163387 17 R2 F D2 R' B' U B U' B' U2 F B R' B2 U2 F2 U2 129 | 6 0.988980881 18 R2 U F2 U D2 L U' L2 U' F R B U' R' F R' F2 L2 130 | 7 1.661477776 18 U' L2 F R' L' F D L2 D L2 U' B2 L2 D' R F' D2 F 131 | 8 0.300311157 17 L' B R2 B2 U F2 R B' R' U D L F2 B R F B 132 | 9 1.012389118 18 L' U' D2 R' U B R2 F' R D L2 D' R2 F2 R2 F' R B2 133 | 10 0.019232801 16 U F U R' D2 F' R2 B D2 R F2 R' D2 F U R2 134 | 11 1.059923421 18 U L' F D2 F' U' B2 U L2 B' U' F' R F B2 D L D' 135 | 12 0.109544175 17 B D2 L B' D2 L2 U2 R' D R' F' L' U F2 R' D R 136 | 13 4.437426858 19 U' R' D F' U B' D' R2 U2 B2 U' R B2 R2 L' D2 L2 B2 L2 137 | 14 0.097405511 17 B R B' D B' L2 U R F2 U2 D2 B D B L B L' 138 | 15 0.064808627 17 R' U2 D2 R F L U' D L' F' L2 U' F' L' F' D' B2 139 | Total time: 5.294330243 real, 18.806913 cpu, 2.350864 cpu/worker 140 | ``` 141 | 142 | ## Tuning your system for speed 143 | 144 | ### Huge pages 145 | 146 | vcube supports 1GB and 2MB huge pages, and will automatically use the 147 | largest page size supported. Huge pages can be enabled by adding 148 | parameters to the kernel command line. For example, to use 1GB pages for 149 | the 21.2 GiB "308" table, add the following kernel parameters (on Ubuntu, 150 | the file is `/etc/default/grub`. The `update-grub` command must be run 151 | afterwards.) 152 | ``` 153 | hugepagesz=1G hugepages=22 154 | ``` 155 | 156 | *Note: Huge pages allocated at boot time are unavailable for ordinary 157 | processes. Be sure to leave enough free memory for the rest of your system 158 | to function.* 159 | 160 | ### Meltdown and Spectre 161 | 162 | **WARNING: Disabling security features is dangerous -- do so at your own risk!** 163 | 164 | The [Meltdown and Spectre](https://meltdownattack.com/) CPU vulnerabilities 165 | required operating system mitigations that negatively impacted performance. 166 | Some workloads are impacted more than others; on the author's i7-7700K 167 | machine, the vc-optimal solver takes 35% longer to run with mitigations 168 | enabled. 169 | 170 | If you are willing to accept the risk, the command line kernel parameter 171 | `nopti` will restore performance. 172 | 173 | **WARNING: Disabling security features is dangerous -- do so at your own risk!** 174 | 175 | ### Overclocking and XMP 176 | 177 | Because vcube relies on random access to a large in-memory table, the main 178 | performance bottleneck is memory access latency. Increasing memory clock 179 | speed and tightening memory timings can improve the speed of the solver. 180 | Memory modules that support XMP (Extreme Memory Profile) makes it easy to 181 | overclock with manufacturer supplied timings. 182 | 183 | ### Shared memory 184 | 185 | Pruning tables can be loaded into shared memory for faster startup times. 186 | Once loaded into shared memory, a table will remain there until reboot. 187 | 188 | To load a table into shared memory: 189 | ``` 190 | ./vc-optimal --coord=308 --no-input --shm 191 | ``` 192 | 193 | Subsequent invocations of `vc-optimal` will use the shared memory copy 194 | of the table (the `--shm` option is only required for the initial load.) 195 | 196 | Use the `ipcs` and `ipcrm` Linux commands to view/remove the shared 197 | memory tables. All vcube tables have keys which start with `0x7663`, 198 | which is ASCII for "vc". The last two hex digits of the key identify 199 | the table coordinate (`38` for `--coord=308`, `2b` for `--coord=212`, 200 | etc.) 201 | ``` 202 | ipcs -m 203 | 204 | ------ Shared Memory Segments -------- 205 | key shmid owner perms bytes nattch status 206 | 0x76630a38 10551301 voltara 600 23622320128 1 207 | ``` 208 | 209 | To remove a table which is not needed anymore (or is corrupt because 210 | it was interrupted while loading): 211 | ``` 212 | # Replace 0x76630a38 with the key for the table you want to remove 213 | ipcrm -M 0x76630a38 214 | ``` 215 | 216 | ## Authors 217 | 218 | * Andrew Skalski ([Voltara](https://github.com/Voltara) on GitHub) 219 | 220 | See also the list of 221 | [contributors](https://github.com/Voltara/vcube/contributors) who 222 | participated in this project. 223 | 224 | ## License 225 | 226 | This project is licensed under the GPLv3 License - see the [LICENSE](LICENSE) 227 | file for details. 228 | 229 | ## Acknowledgments 230 | 231 | Most of what I have learned about Rubik's cube solvers came from the work 232 | of these individuals: 233 | * Herbert Kociemba, [The Mathematics behind Cube Explorer](http://kociemba.org/cube.htm) 234 | * Tomas Rokicki, [nxopt](https://github.com/rokicki/cube20src) and several papers 235 | * Chen Shuang, [min2phase](https://github.com/cs0x7f/min2phase) 236 | -------------------------------------------------------------------------------- /src/cube.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include "cube.h" 22 | 23 | using namespace vcube; 24 | 25 | // Vector of numeric moves 26 | cube cube::from_moveseq(const moveseq_t &v) { 27 | cube c; 28 | for (auto m : v) { 29 | c = c.move(m); 30 | } 31 | return c; 32 | } 33 | 34 | namespace reid { 35 | constexpr uint64_t E_MAP = 0xfab9867452301; 36 | constexpr uint64_t C_MAP = 0xf76541230; 37 | 38 | constexpr char C_LOOKUP[] = "UFRUF ULFUL UBLUB URBUR DRFDR DFLDF DLBDL DBRDB"; 39 | constexpr char E_LOOKUP[] = "URU UFU ULU UBU DRD DFD DLD DBD FRF FLF BLB BRB"; 40 | } 41 | 42 | // Parse a cube position in the notation used by Michael Reid's solver, 43 | // which has the identity: 44 | // UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR 45 | cube cube::from_reid(std::string s) { 46 | using namespace reid; 47 | uint64_t e_map = E_MAP, c_map = C_MAP; 48 | 49 | cube c; 50 | uint8_t *edge = reinterpret_cast(&c.ev()); 51 | uint8_t *corner = reinterpret_cast(&c.cv()); 52 | 53 | for (auto &ch : s) { 54 | ch = toupper(ch); 55 | } 56 | 57 | uint32_t edges_todo = 0xfff, corners_todo = 0xff; 58 | uint8_t eorient_sum = 0, corient_sum = 0; 59 | 60 | const char *ws = " \t\r\n"; 61 | for (char *saveptr, *tok = strtok_r(&s[0], ws, &saveptr); tok; tok = strtok_r(NULL, ws, &saveptr)) { 62 | int len = strlen(tok); 63 | const char *p; 64 | switch (strlen(tok)) { 65 | case 2: 66 | if ((p = strstr(E_LOOKUP, tok))) { 67 | int offset = p - E_LOOKUP; 68 | edge[e_map & 0xf] = (offset >> 2) | ((offset << 4) & 0x10); 69 | e_map >>= 4; 70 | edges_todo ^= 1 << (offset >> 2); 71 | eorient_sum += offset & 1; 72 | } 73 | break; 74 | case 3: 75 | if ((p = strstr(C_LOOKUP, tok))) { 76 | int offset = p - C_LOOKUP; 77 | corner[c_map & 0xf] = (offset >> 3) | ((offset << 4) & 0x30); 78 | c_map >>= 4; 79 | corners_todo ^= 1 << (offset >> 3); 80 | corient_sum += offset & 3; 81 | } 82 | break; 83 | } 84 | } 85 | 86 | // There must be exactly one of each edge and corner cubie 87 | if (edges_todo || corners_todo || e_map != 0xf || c_map != 0xf) { 88 | return cube(); 89 | } 90 | 91 | // Check for valid orientation 92 | if ((eorient_sum & 1) || (corient_sum % 3)) { 93 | return cube(); 94 | } 95 | 96 | // Check for legal parity 97 | if (c.parity()) { 98 | return cube(); 99 | } 100 | 101 | return c; 102 | } 103 | 104 | std::string cube::to_reid() const { 105 | using namespace reid; 106 | 107 | cube tmp = *this; 108 | auto edge = reinterpret_cast(&tmp.ev()); 109 | auto corner = reinterpret_cast(&tmp.cv()); 110 | 111 | uint64_t e_map = E_MAP, c_map = C_MAP; 112 | 113 | std::string s; 114 | 115 | for (int i = 0; i < 12; i++) { 116 | auto e = edge[e_map & 0xf]; 117 | e_map >>= 4; 118 | auto idx = 4 * (e & 0xf) + (e >> 4); 119 | for (int i = 0; i < 2; i++) { 120 | s.push_back(E_LOOKUP[idx++]); 121 | } 122 | s.push_back(' '); 123 | } 124 | 125 | for (int i = 0; i < 8; i++) { 126 | auto c = corner[c_map & 0xf]; 127 | c_map >>= 4; 128 | auto idx = 8 * (c & 0xf) + (c >> 4); 129 | for (int i = 0; i < 3; i++) { 130 | s.push_back(C_LOOKUP[idx++]); 131 | } 132 | s.push_back(' '); 133 | } 134 | 135 | s.pop_back(); 136 | 137 | return s; 138 | } 139 | 140 | // Parse a cube position in Speffz lettering, corners first 141 | cube cube::from_speffz(const std::string &s, int corner_buffer, int edge_buffer) { 142 | constexpr uint8_t c_map[] = { 143 | 2, 3, 0, 1, 2, 1, 5, 6, 1, 0, 4, 5, 0, 3, 7, 4, 3, 2, 6, 7, 5, 4, 7, 6 }; 144 | constexpr uint8_t e_map[] = { 145 | 3, 0, 1, 2, 2, 9, 6, 10, 1, 8, 5, 9, 0, 11, 4, 8, 3, 10, 7, 11, 5, 4, 7, 6 }; 146 | constexpr uint8_t c_ori[] = { 147 | 0, 0, 0, 0, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 0, 0, 0, 0 }; 148 | constexpr uint8_t e_ori[] = { 149 | 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0 }; 150 | 151 | corner_buffer = toupper(corner_buffer) - 'A'; 152 | if (corner_buffer < 0 || corner_buffer >= 24) { 153 | corner_buffer = 0; 154 | } 155 | uint8_t corner_buffer_ori = c_ori[corner_buffer]; 156 | corner_buffer = c_map[corner_buffer]; 157 | 158 | edge_buffer = toupper(edge_buffer) - 'A'; 159 | if (edge_buffer < 0 || edge_buffer >= 24) { 160 | edge_buffer = 0; 161 | } 162 | uint8_t edge_buffer_ori = e_ori[edge_buffer]; 163 | edge_buffer = e_map[edge_buffer]; 164 | 165 | cube c; 166 | uint8_t *edge = reinterpret_cast(&c.ev()); 167 | uint8_t *corner = reinterpret_cast(&c.cv()); 168 | 169 | bool parity_ok = true; 170 | 171 | bool parse_edges = false; 172 | for (auto ch : s) { 173 | if (ch == '.') { 174 | parse_edges = true; 175 | } else if (ch >= 'A' && ch <= 'X') { 176 | // Flip/twist in place 177 | if (parse_edges) { 178 | uint8_t idx = e_map[ch - 'A']; 179 | edge[edge_buffer] ^= 0x10; 180 | edge[idx] ^= 0x10; 181 | } else { 182 | uint8_t idx = c_map[ch - 'A']; 183 | uint8_t ori = c_ori[ch - 'A'] << 4; 184 | corner[corner_buffer] = (corner[corner_buffer] + ori) % 0x30; 185 | corner[idx] = (0x30 + corner[idx] - ori) % 0x30; 186 | } 187 | } else if (ch >= 'a' && ch <= 'x') { 188 | // Cycle with buffer 189 | if (parse_edges) { 190 | uint8_t idx = e_map[ch - 'a']; 191 | uint8_t ori = (e_ori[ch - 'a'] ^ edge_buffer_ori) << 4; 192 | std::swap(edge[edge_buffer], edge[idx]); 193 | edge[edge_buffer] ^= ori; 194 | edge[idx] ^= ori; 195 | parity_ok ^= (idx != edge_buffer); 196 | } else { 197 | uint8_t idx = c_map[ch - 'a']; 198 | int8_t ori = (c_ori[ch - 'a'] - corner_buffer_ori) << 4; 199 | std::swap(corner[corner_buffer], corner[idx]); 200 | corner[idx] = (0x30 + corner[idx] + ori) % 0x30; 201 | corner[corner_buffer] = (0x30 + corner[corner_buffer] - ori) % 0x30; 202 | parity_ok ^= (idx != corner_buffer); 203 | } 204 | } 205 | } 206 | 207 | return parity_ok ? ~c : cube(); 208 | } 209 | 210 | cube & cube::setCornerOrient(corient_t corient) { 211 | /* The scalar and vector versions are close in performance, 212 | * but the vector version is currently faster (tested on i7-7700K) 213 | */ 214 | #if 0 215 | uint64_t tmp, co; 216 | 217 | tmp = (corient / 729); co = tmp; 218 | tmp = (corient / 243) % 3; co = (co << 8) | tmp; 219 | tmp = (corient / 81) % 3; co = (co << 8) | tmp; 220 | tmp = (corient / 27) % 3; co = (co << 8) | tmp; 221 | tmp = (corient / 9) % 3; co = (co << 8) | tmp; 222 | tmp = (corient / 3) % 3; co = (co << 8) | tmp; 223 | tmp = (corient ) % 3; co = (co << 8) | tmp; 224 | 225 | uint64_t sum = co; 226 | sum += sum >> 32; 227 | sum += sum >> 16; 228 | sum += sum >> 8; 229 | 230 | static const uint64_t mod3 = 0x9249249249249244; 231 | uint64_t last_corner = ((mod3 >> sum) | (mod3 << (64 - sum))) & (3LL << 4); 232 | co = (co << 12) | last_corner; 233 | 234 | // Replace old corner orientation 235 | u64()[2] = (u64()[2] & 0x0f0f0f0f0f0f0f0f) | co; 236 | #else 237 | // Replace old corner orientation 238 | u64()[2] = (u64()[2] & 0x0f0f0f0f0f0f0f0f) | avx2::unrank_corner_orient(corient); 239 | #endif 240 | 241 | return *this; 242 | } 243 | 244 | cube & cube::setCorner4Comb(c4comb_t c4comb) { 245 | auto mask = unrank_8C4(c4comb); 246 | 247 | // Set all D-face corners to 4, and U-face corners to 0 248 | uint64_t corners = _pdep_u64(mask, 0x0404040404040404); 249 | 250 | // Create a mask to fill in the low bits (+0 +1 +2 +3) 251 | uint64_t fill_mask = (corners >> 1) | (corners >> 2); 252 | 253 | // Fill in the D-face and U-face low bits 254 | uint64_t fill = 255 | _pdep_u64(0xe4, fill_mask) | 256 | _pdep_u64(0xe4, fill_mask ^ 0x0303030303030303); 257 | 258 | u64()[2] = corners | fill; 259 | 260 | return *this; 261 | } 262 | 263 | cube & cube::setEdgeUD4Comb(eud4comb_t eud4comb) { 264 | auto mask = unrank_8C4(eud4comb); 265 | 266 | // Set all D-face edges to 4, and U-face edges to 0 267 | uint64_t edges = _pdep_u64(mask, 0x0404040404040404); 268 | 269 | // Create a mask to fill in the low bits (+0 +1 +2 +3) 270 | uint64_t fill_mask = (edges >> 1) | (edges >> 2); 271 | 272 | // Fill in the D-face and U-face low bits 273 | uint64_t fill = 274 | _pdep_u64(0xe4, fill_mask) | 275 | _pdep_u64(0xe4, fill_mask ^ 0x0303030303030303); 276 | 277 | u64()[0] = edges | fill; 278 | u64()[1] = 0x0f0e0d0c0b0a0908; 279 | 280 | return *this; 281 | } 282 | 283 | cube & cube::setEdge4Comb(e4comb_t e4comb) { 284 | auto mask = unrank_12C4(e4comb); 285 | 286 | uint64_t edges = _pdep_u64(mask, 0x888888888888); 287 | 288 | uint64_t fill_mask = (edges >> 1) | (edges >> 2) | (edges >> 3); 289 | 290 | uint64_t fill = 291 | _pdep_u64(076543210, fill_mask) | 292 | _pdep_u64(076543210, fill_mask ^ 0x777777777777); 293 | 294 | edges |= fill; 295 | 296 | u64()[0] = _pdep_u64(edges, 0x0f0f0f0f0f0f0f0f); 297 | u64()[1] = _pdep_u64(edges >> 32, 0x0f0f0f0f0f0f0f0f) | 0x0f0e0d0c00000000; 298 | 299 | return *this; 300 | } 301 | -------------------------------------------------------------------------------- /src/avx2_cube.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_AVX2_CUBE_H 20 | #define VCUBE_AVX2_CUBE_H 21 | 22 | #include 23 | #include 24 | #include "types.h" 25 | #include "util.h" 26 | 27 | namespace vcube { 28 | 29 | /* constexpr redeclarations of intrinsics */ 30 | namespace cx { 31 | extern inline constexpr __m256i mm256_set_epi64x( // avx 32 | int64_t a, int64_t b, int64_t c, int64_t d) 33 | { 34 | return (__m256i) { d, c, b, a }; 35 | } 36 | } 37 | 38 | struct avx2 { 39 | static constexpr __m256i identity = cx::mm256_set_epi64x( 40 | 0x0f0e0d0c0b0a0908, 0x0706050403020100, 41 | 0x0f0e0d0c0b0a0908, 0x0706050403020100); 42 | 43 | static constexpr __m256i literal( 44 | uint64_t corners, uint64_t edges_high, uint64_t edges_low) 45 | { 46 | return cx::mm256_set_epi64x( 47 | 0x0f0e0d0c0b0a0908, corners, 48 | 0x0f0e0d0c00000000 | edges_high, edges_low); 49 | }; 50 | 51 | static uint32_t bitmask(__m256i v, int b) { 52 | return _mm256_movemask_epi8(_mm256_slli_epi32(v, 7 - b)); 53 | } 54 | 55 | static bool equals(__m256i a, __m256i b) { 56 | return _mm256_movemask_epi8(_mm256_cmpeq_epi8(a, b)) == -1; 57 | } 58 | 59 | static bool less_than(__m256i a, __m256i b) { 60 | #if 1 61 | // One fewer instruction, except when used together with equals() 62 | uint32_t gt = _mm256_movemask_epi8(_mm256_cmpgt_epi8(a, b)); 63 | uint32_t lt = _mm256_movemask_epi8(_mm256_cmpgt_epi8(b, a)); 64 | return gt < lt; 65 | #else 66 | /* Check that the most-significant 1-bit in "lt" is 67 | * preceded by all 1-bits in "eq" by testing whether 68 | * addition causes a rollover. 69 | */ 70 | uint32_t eq = _mm256_movemask_epi8(_mm256_cmpeq_epi8(a, b)); 71 | uint32_t lt = _mm256_movemask_epi8(_mm256_cmpgt_epi8(b, a)); 72 | // Invariant: (eq & lt) == 0 73 | uint32_t sum = (lt << 1) + eq; 74 | return sum < lt; 75 | #endif 76 | } 77 | 78 | static __m256i compose(__m256i a, __m256i b, bool mirror = false) { 79 | const __m256i vcarry = _mm256_set_epi64x( 80 | 0x3030303030303030, 0x3030303030303030, 81 | 0x2020202020202020, 0x2020202020202020); 82 | 83 | // Permute edges and corners 84 | __m256i vperm = _mm256_shuffle_epi8(a, b); 85 | 86 | // Compose edge and corner orientations 87 | __m256i vorient = _mm256_and_si256(b, _mm256_set1_epi8(0xf0)); 88 | if (mirror) { 89 | // Corner orientations are subtracted 90 | vperm = _mm256_sub_epi8(vperm, vorient); 91 | vperm = _mm256_min_epu8(vperm, _mm256_add_epi8(vperm, vcarry)); 92 | } else { 93 | // Corner orientations are added 94 | vperm = _mm256_add_epi8(vperm, vorient); 95 | vperm = _mm256_min_epu8(vperm, _mm256_sub_epi8(vperm, vcarry)); 96 | } 97 | 98 | return vperm; 99 | } 100 | 101 | static __m256i xor_edge_orient(__m256i v, eorient_t eorient) { 102 | __m256i vorient = _mm256_shuffle_epi8( 103 | _mm256_set1_epi32(eorient), 104 | _mm256_set_epi64x(-1, -1, 0xffffffff01010101, 0)); 105 | vorient = _mm256_or_si256(vorient, _mm256_set1_epi64x(~0x8040201008040201)); 106 | vorient = _mm256_cmpeq_epi8(vorient, _mm256_set1_epi64x(-1)); 107 | vorient = _mm256_and_si256(vorient, _mm256_set1_epi8(0x10)); 108 | return _mm256_xor_si256(v, vorient); 109 | } 110 | 111 | static corient_t corner_orient_raw(__m256i v) { 112 | __m256i vorient = _mm256_unpacklo_epi8( 113 | _mm256_slli_epi32(v, 3), 114 | _mm256_slli_epi32(v, 2)); 115 | return corient_t(_mm256_movemask_epi8(vorient)) >> 16; 116 | } 117 | 118 | static __m256i invert(__m256i v) { 119 | // Split the cube into separate perm and orient vectors 120 | __m256i vperm = _mm256_and_si256(v, _mm256_set1_epi8(0x0f)); 121 | __m256i vorient = _mm256_xor_si256(v, vperm); 122 | 123 | #if 0 124 | // "Brute force" the inverse of the permutation 125 | __m256i vi = _mm256_set_epi64x( 126 | 0x0f0e0d0c00000000, 0x0000000000000000, 127 | 0x0f0e0d0c00000000, 0x0000000000000000); 128 | for (int i = 0; i < 12; i++) { 129 | __m256i vtrial = _mm256_set1_epi8(i); 130 | __m256i vcorrect = _mm256_cmpeq_epi8(identity, _mm256_shuffle_epi8(vperm, vtrial)); 131 | vi = _mm256_or_si256(vi, _mm256_and_si256(vtrial, vcorrect)); 132 | } 133 | #else 134 | /* 27720 (11*9*8*7*5) is the LCM of all possible cycle decompositions, 135 | * so we can invert the permutation by raising it to the 27719th power. 136 | * This method tested faster (9-10%) than the above "brute force". 137 | * The addition chain for 27719 was generated using the calculator 138 | * provided by Achim Flammenkamp on his website: 139 | * http://wwwhomes.uni-bielefeld.de/achim/addition_chain.html 140 | */ 141 | __m256i vp3 = _mm256_shuffle_epi8(vperm, vperm); // 2 142 | vp3 = _mm256_shuffle_epi8(vp3, vperm); // 3 (+1) 143 | __m256i vi = _mm256_shuffle_epi8(vp3, vp3); // 6 144 | vi = _mm256_shuffle_epi8(vi, vi); // 12 145 | vi = _mm256_shuffle_epi8(vi, vi); // 24 146 | vi = _mm256_shuffle_epi8(vi, vp3); // 27 (+3) 147 | vi = _mm256_shuffle_epi8(vi, vi); // 54 148 | vi = _mm256_shuffle_epi8(vi, vi); // 108 149 | vi = _mm256_shuffle_epi8(vi, vi); // 216 150 | vi = _mm256_shuffle_epi8(vi, vi); // 432 151 | vi = _mm256_shuffle_epi8(vi, vperm); // 433 (+1) 152 | vi = _mm256_shuffle_epi8(vi, vi); // 866 153 | vi = _mm256_shuffle_epi8(vi, vi); // 1732 154 | vi = _mm256_shuffle_epi8(vi, vi); // 3464 155 | vi = _mm256_shuffle_epi8(vi, vi); // 6928 156 | vi = _mm256_shuffle_epi8(vi, vi); // 13856 157 | vi = _mm256_shuffle_epi8(vi, vp3); // 13859 (+3) 158 | vi = _mm256_shuffle_epi8(vi, vi); // 27718 159 | vi = _mm256_shuffle_epi8(vi, vperm); // 27719 (+1) 160 | #endif 161 | 162 | // Invert the corner orientations 163 | const __m256i vcarry_corners = _mm256_set_epi64x( 164 | 0x3030303030303030, 0x3030303030303030, 165 | 0x1010101010101010, 0x1010101010101010); 166 | vorient = _mm256_add_epi8(vorient, vorient); 167 | vorient = _mm256_min_epu8(vorient, _mm256_sub_epi8(vorient, vcarry_corners)); 168 | 169 | // Permute the edge and corner orientations 170 | vorient = _mm256_shuffle_epi8(vorient, vi); 171 | 172 | // Combine the new perm and orient 173 | return _mm256_or_si256(vi, vorient); 174 | } 175 | 176 | static uint64_t edges_low(__m256i v) { 177 | return _mm256_extract_epi64(v, 0); 178 | } 179 | 180 | static uint64_t edges_high(__m256i v) { 181 | return _mm256_extract_epi64(v, 1); 182 | } 183 | 184 | static uint64_t corners(__m256i v) { 185 | return _mm256_extract_epi64(v, 2); 186 | } 187 | 188 | static uint64_t unrank_corner_orient(corient_t corient) { 189 | /* 16-bit mulhi is lower latency than 32-bit, but has two disadvantages: 190 | * - Requires two different shift widths 191 | * - The multiplier for the 3^0 place is 65536 192 | */ 193 | static const __m256i vpow3_reciprocal = _mm256_set_epi32( 194 | 1439, 4316, 12946, 38837, 7282, 21846, 0, 0); 195 | static const __m256i vshift = _mm256_set_epi32( 196 | 4, 4, 4, 4, 0, 0, 0, 0); 197 | 198 | // Divide by powers of 3 (1, 3, 9, ..., 729) 199 | __m256i vcorient = _mm256_set1_epi32(corient); 200 | __m256i vco = _mm256_mulhi_epu16(vcorient, vpow3_reciprocal); 201 | vco = _mm256_srlv_epi32(vco, vshift); 202 | 203 | // fixup 3^0 place; reuse vcorient instead of inserting 204 | vco = _mm256_blend_epi32(vco, vcorient, 1 << 1); 205 | 206 | // Compute the remainder mod 3 207 | __m256i div3 = _mm256_mulhi_epu16(vco, _mm256_set1_epi32(21846)); // 21846/65536 ~ 1/3 208 | vco = _mm256_add_epi32(vco, div3); 209 | vco = _mm256_sub_epi32(vco, _mm256_slli_epi32(div3, 2)); 210 | 211 | // Convert the results to a scalar 212 | vco = _mm256_shuffle_epi8(vco, _mm256_set_epi32( 213 | -1, -1, 0x0c080400, -1, 214 | -1, -1, -1, 0x0c080400)); 215 | uint64_t co = 216 | _mm256_extract_epi64(vco, 2) | 217 | _mm256_extract_epi64(vco, 0); 218 | 219 | // Determine the last corner's orientation 220 | uint64_t sum = co + (co >> 32); 221 | sum += sum >> 16; 222 | sum += sum >> 8; 223 | 224 | // Insert the last corner 225 | co |= (0x4924924924924924 >> sum) & 3; 226 | 227 | return co << 4; 228 | } 229 | 230 | /* Return the parity of the edge+corner permutations */ 231 | static bool parity(__m256i v) { 232 | v = _mm256_and_si256(v, _mm256_set1_epi8(0xf)); 233 | 234 | __m256i a, b, c, d, e, f, g, h; 235 | a = _mm256_bslli_epi128(v, 1); // shift left 1 byte 236 | b = _mm256_bslli_epi128(v, 2); // shift left 2 bytes 237 | c = _mm256_bslli_epi128(v, 3); // shift left 3 bytes 238 | d = _mm256_bslli_epi128(v, 4); // shift left 4 bytes 239 | e = _mm256_bslli_epi128(v, 8); // shift left 8 bytes 240 | f = _mm256_alignr_epi8(v, v, 11); // rotate left 5 bytes 241 | g = _mm256_alignr_epi8(v, v, 10); // rotate left 6 bytes 242 | h = _mm256_alignr_epi8(v, v, 9); // rotate left 7 bytes 243 | // Test for inversions in the permutation 244 | a = _mm256_xor_si256(_mm256_cmpgt_epi8(a, v), _mm256_cmpgt_epi8(b, v)); 245 | c = _mm256_xor_si256(_mm256_cmpgt_epi8(c, v), _mm256_cmpgt_epi8(d, v)); 246 | e = _mm256_xor_si256(_mm256_cmpgt_epi8(e, v), _mm256_cmpgt_epi8(f, v)); 247 | // Xor all the tests together 248 | __m256i parity = _mm256_xor_si256(_mm256_xor_si256(a, c), e); 249 | parity = _mm256_xor_si256(parity, _mm256_cmpgt_epi8(g, v)); 250 | parity = _mm256_xor_si256(parity, _mm256_cmpgt_epi8(h, v)); 251 | // The 0x5f corrects for the circular shifts, which cause 252 | // certain pairs of values to be compared out-of-order 253 | return _popcnt32(_mm256_movemask_epi8(parity) ^ 0x5f005f) & 1; 254 | } 255 | }; 256 | 257 | } 258 | 259 | #endif 260 | -------------------------------------------------------------------------------- /doc/speffz.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This document describes the Speffz cycle notation supported by vcube, 4 | intended as an input format for quickly entering a 3x3x3 cube state 5 | manually. 6 | 7 | [Speffz](https://www.speedsolving.com/wiki/index.php/Speffz) is a lettering 8 | scheme commonly used in blindfolded solving. The 24 edge and 24 corner 9 | stickers on the cube are each labeled with the letters `a` through `x`. 10 | 11 | The cube state is decomposed into cycles of corner and edge stickers, 12 | and the sticker letters are written in cycle order. 13 | 14 | ## Sticker lettering 15 | 16 | The sticker lettering scheme is as follows: 17 | 18 | a a b 19 | d[U]b 20 | d c c 21 | 22 | e e f i i j m m n q q r 23 | h[L]f l[F]j p[R]n t[B]r 24 | h g g l k k p o o t s s 25 | 26 | u u v 27 | x[D]v 28 | x w w 29 | 30 | In the above diagram, the bracketed letters `U, R, F, D, L, B` are included 31 | for reference only, and are not part of the lettering scheme. 32 | 33 | ## Buffers 34 | 35 | The notation is written with respect to corner and edge *buffers*. All 36 | maneuvers (exchanges and reorientations) are performed with respect to 37 | a buffer. Unless configured otherwise, the following buffers are assumed: 38 | 39 | Corner: A 40 | Edge: U 41 | 42 | The default buffer choices originate from the blindfolded solving methods 43 | [Classic Pochmann](https://www.speedsolving.com/wiki/index.php/Classic_Pochmann) 44 | for corners and [M2](https://www.speedsolving.com/wiki/index.php/M2/R2) for 45 | edges, but are otherwise arbitrary. 46 | 47 | ## Buffer cubies 48 | 49 | The term *buffer cubie* refers to a cubie which contains a buffer sticker. 50 | The stickers `A`, `E` and `R` comprise the default buffer corner, and the 51 | stickers `K` and `U` comprise the default buffer edge. 52 | 53 | Buffer cubie letters never appear in well-formed notation because they add 54 | no information. 55 | 56 | ## Buffer location 57 | 58 | The term *buffer location* refers to the home position of the buffer. 59 | 60 | # Syntax 61 | 62 | cube-state = sticker-list [ "." sticker-list ] 63 | ; corner stickers first, then edge stickers 64 | 65 | sticker-list = *sticker 66 | 67 | sticker = %x61-78 / %x41-58 68 | ; a-x: sticker to be exchanged with the buffer 69 | ; A-X: sticker to be reoriented in-place 70 | 71 | # Reading the notation 72 | 73 | The notation can be interpreted as a sequence of *exchanges* and in-place 74 | *reorientations* of stickers with respect to the buffer, which when applied 75 | to the cube state described, will restore the cube to the solved state. 76 | When reading the notation, each letter refers to a *location* on the cube. 77 | 78 | ## Exchanges 79 | 80 | A lowercase letter denotes a location to be exchanged with the buffer. 81 | The two cubies are swapped and oriented such that the named location 82 | is effectively exchanged with the buffer location. 83 | 84 | ### Example corner exchange 85 | 86 | The exchange `f` will swap two cubies such that the stickers at locations 87 | `f` and `a` are exchanged. 88 | 89 | Before: 90 | 91 | Location Cubie 92 | -------- ----- 93 | (a)e r (f)d i <- corner buffer 94 | d i(f) m j(c) 95 | 96 | After: 97 | 98 | Location Cubie 99 | -------- ----- 100 | (a)e r (c)m j <- corner buffer 101 | d i(f) d i(f) 102 | 103 | 104 | ## In-place reorientations 105 | 106 | A cubie may be reoriented in-place by making two exchanges. For example, 107 | the pair of corner exchanges `jc` twists the `URF` corner clockwise, with 108 | the side-effect of twisting the buffer (`ULB`) in the opposite direction. 109 | Similarly, the pair of edge exchanges `ci` flips the `UF` edge, with the 110 | side-effect of flipping the buffer (`DF`) as well. 111 | 112 | Uppercase letters are an optional shorthand for these in-place reorientations. 113 | 114 | ### Corners 115 | 116 | Uppercase corner letters twist the cubie at the named location such that 117 | the named sticker ends up on the *up* or *down* face. As a side-effect, 118 | the buffer is twisted in the opposite direction. 119 | 120 | For example, the stickers `cmj` denote the `URF` corner. The reorientation 121 | `M` will twist the cubie at that location counterclockwise (and the buffer 122 | clockwise); `J` will twist the cubie clockwise (and the buffer 123 | counterclockwise.) The uppercase corner `C` has no effect because it 124 | already refers to a location on the *up* face, so it never appears in 125 | well-formed notation. 126 | 127 | ### Edges 128 | 129 | Uppercase edge letters flip the cubie at the named location, and flip the 130 | buffer as a side-effect. Both of an edge cubie's letters are equivalent; 131 | `C` and `I` have the same effect. 132 | 133 | # Writing the notation 134 | 135 | The notation can be written by examining all corners and edges using the 136 | following algorithm: 137 | 138 | procedure TRACE: 139 | Examine the sticker at the current location 140 | Unless the sticker belongs to the buffer cubie: 141 | Write down the letter for that sticker 142 | TRACE the sticker's home location 143 | 144 | TRACE the buffer location 145 | While there are still unexamined cubies: 146 | Choose an unexamined cubie 147 | If the cubie is in its solved position: 148 | Skip 149 | Else 150 | Choose a sticker on that cubie 151 | Write down the letter for that sticker 152 | TRACE the sticker's current location 153 | End 154 | End 155 | 156 | ## Example 157 | 158 | ### Scramble sequence: 159 | 160 | L' B' R F2 D' U' B R D2 U2 B2 F' D B' L U2 L U' 161 | 162 | f i w 163 | t . k 164 | e l m 165 | d n a r f c j u t o c i 166 | m . d e . s w . q a . b 167 | x g l u r b q v p v p h 168 | g h n 169 | x . o 170 | s j k 171 | 172 | ### Corners 173 | 174 | Start at the buffer location. 175 | 176 | (f). w 177 | . . . 178 | e . m 179 | d . a r . c j . t o . i 180 | . . . . . . . . . . . . 181 | x . l u . b q . p v . h 182 | g . n 183 | . . . 184 | s . k 185 | 186 | Write down `f`, move to location `f`. 187 | 188 | * . w 189 | . . . 190 | e . m 191 | * .(a)r . c j . t o . * 192 | . . . . . . . . . . . . 193 | x . l u . b q . p v . h 194 | g . n 195 | . . . 196 | s . k 197 | 198 | Sticker at location `f` is `a`, which is on the buffer cubie, so do not 199 | write it down. The cubie at location `a` has been visited already, so 200 | that ends the first cycle (`f`). 201 | 202 | The cubie at location `bqn` is unvisited, so write down `b` and visit 203 | location `b` next. 204 | 205 | * .(w) 206 | . . . 207 | * . m 208 | * . * * . c j . t o . * 209 | . . . . . . . . . . . . 210 | x . l u . b q . p v . h 211 | g . n 212 | . . . 213 | s . k 214 | 215 | Write down `w`, move to location `w`. 216 | 217 | * . * 218 | . . . 219 | * . m 220 | * . * * . c j . * * . * 221 | . . . . . . . . . . . . 222 | x . l u . b q . p v . h 223 | g . n 224 | . . . 225 | s .(k) 226 | 227 | Write down `k`, move to location `k`. 228 | 229 | * . * 230 | . . . 231 | * . m 232 | * . * * . c j . * * . * 233 | . . . . . . . . . . . . 234 | x . l u .(b)q . * * . h 235 | g . n 236 | . . . 237 | s . * 238 | 239 | Write down `b`. The cubie at location `b` has been visited already, so 240 | that ends the second cycle (`bwkb`). 241 | 242 | The cubie at location `ugl` is unvisited, so write down `u` and visit 243 | location `u` next. 244 | 245 | * . * 246 | . . . 247 | * . m 248 | * . * * . c j . * * . * 249 | . . . . . . . . . . . . 250 | x . l u . * * . * * . h 251 | (g). * 252 | . . . 253 | s . * 254 | 255 | Write down `g`. The cubie at location `g` has been visited already, 256 | so that ends the third cycle (`ug`). Note: This is an in-place twist 257 | and can be shortened to `L`. 258 | 259 | The cubie at location `cmj` is unvisited, so write down `c` and visit 260 | location `c` next. 261 | 262 | * . * 263 | . . . 264 | * .(m) 265 | * . * * . c j . * * . * 266 | . . . . . . . . . . . . 267 | x . * * . * * . * * . h 268 | * . * 269 | . . . 270 | s . * 271 | 272 | Write down `m`. The cubie at location `m` has been visited already, so 273 | that ends the fourth cycle (`cm`). Note: This is an in-place twist 274 | and can be shortened to `J`. 275 | 276 | The cubie at location `xsh` is unvisited, so write down `x` and visit 277 | location `x` next. 278 | 279 | * . * 280 | . . . 281 | * . * 282 | * . * * . * * . * * . * 283 | . . . . . . . . . . . . 284 | x . * * . * * . * * . h 285 | * . * 286 | . . . 287 | (s). * 288 | 289 | Write down `s`. The cubie at location `s` has been visited already, 290 | so that ends the fifth and final cycle (`xs`). Note: This is an in-place 291 | twist and can be shortened to `H`. 292 | 293 | The corner notation is thus: 294 | 295 | fbwkbLJH 296 | 297 | ### Edges 298 | 299 | Start at the buffer location. 300 | 301 | . i . 302 | t . k 303 | . l . 304 | . n . . f . . u . . c . 305 | m . d e . s w . q a . b 306 | . g . . r . . v . . p . 307 | .(h). 308 | x . o 309 | . j . 310 | 311 | Write down `h`, move to location `h`. 312 | 313 | . i . 314 | t . k 315 | . l . 316 | . n . . f . . u . . c . 317 | (m). d e . s w . q a . b 318 | . g . . * . . v . . p . 319 | . * . 320 | x . o 321 | . j . 322 | 323 | Write down `m`, move to location `m`. 324 | 325 | . i . 326 | t . k 327 | . l . 328 | . n . . f . .(u). . c . 329 | * . d e . s w . q a . * 330 | . g . . * . . v . . p . 331 | . * . 332 | x . o 333 | . j . 334 | 335 | Sticker at location `m` is `u`, which is on the buffer cubie, so do not 336 | write it down. The cubie at location `u` has been visited already, so 337 | that ends the first cycle (`hm`). 338 | 339 | The cubie at location `aq` is unvisited, so write down `a` and visit 340 | location `a` next. 341 | 342 | .(i). 343 | t . * 344 | . l . 345 | . n . . f . . * . . c . 346 | * . d e . s w . q a . * 347 | . g . . * . . v . . p . 348 | . * . 349 | x . o 350 | . j . 351 | 352 | Write down `i`, move to location `i`. 353 | 354 | . * . 355 | t . * 356 | . l . 357 | . n . .(f). . * . . * . 358 | * . d e . s w . q a . * 359 | . g . . * . . v . . p . 360 | . * . 361 | x . o 362 | . j . 363 | 364 | Write down `f`, move to location `f`. 365 | 366 | . * . 367 | t . * 368 | . * . 369 | . n . . * . . * . . * . 370 | * .(d)e . s w . q a . * 371 | . g . . * . . v . . p . 372 | . * . 373 | x . o 374 | . j . 375 | 376 | Write down `d`, move to location `d`. 377 | 378 | . * . 379 | (t). * 380 | . * . 381 | . n . . * . . * . . * . 382 | * . * * . s w . q a . * 383 | . g . . * . . v . . p . 384 | . * . 385 | x . o 386 | . j . 387 | 388 | Write down `t`, move to location `t`. 389 | 390 | . * . 391 | * . * 392 | . * . 393 | . * . . * . . * . . * . 394 | * . * * . s w . q(a). * 395 | . g . . * . . v . . p . 396 | . * . 397 | x . o 398 | . j . 399 | 400 | Write down `a`. The cubie at location `a` has been visited already, so 401 | that ends the second cycle (`aifdta`). 402 | 403 | The cubie at location `jp` is unvisited, so write down `j` and visit 404 | location `j` next. 405 | 406 | . * . 407 | * . * 408 | . * . 409 | . * . . * . . * . . * . 410 | * . * * .(s)w . * * . * 411 | . g . . * . . v . . p . 412 | . * . 413 | x . o 414 | . j . 415 | 416 | Write down `s`, move to location `s`. 417 | 418 | . * . 419 | * . * 420 | . * . 421 | . * . . * . . * . . * . 422 | * . * * . * * . * * . * 423 | . g . . * . . v . .(p). 424 | . * . 425 | x . o 426 | . j . 427 | 428 | Write down `p`. The cubie at location `p` has been visited already, 429 | so that ends the third cycle (`jsp`). 430 | 431 | The cubie at location `vo` is unvisited, so write down `v` and visit 432 | location `v` next. 433 | 434 | . * . 435 | * . * 436 | . * . 437 | . * . . * . . * . . * . 438 | * . * * . * * . * * . * 439 | . g . . * . . v . . * . 440 | . * . 441 | x .(o) 442 | . * . 443 | 444 | Write down `o`. The cubie at location `o` has been visited already, 445 | so that ends the fourth cycle (`vo`). Note: This is an in-place flip 446 | and can be shortened to `O` or `V`. 447 | 448 | The only unvisited cubie is `xg` which is already in the solved position, 449 | so we skip it. 450 | 451 | The edge notation is thus: 452 | 453 | hmaifdtajspO 454 | 455 | ### Combined notation 456 | 457 | Combining the corner and edge notation yields a complete description 458 | of the cube state: 459 | 460 | fbwkbLJH.hmaifdtajspO 461 | 462 | With practice, cubes can be input quickly in this manner. 463 | -------------------------------------------------------------------------------- /src/nxprune.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_NXPRUNE_H 20 | #define VCUBE_NXPRUNE_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include "cube.h" 27 | #include "cube6.h" 28 | #include "sse_cube.h" 29 | 30 | /* This implements the pruning tables used by Tomas Rokicki's nxopt. 31 | * See https://github.com/rokicki/cube20src for a better description 32 | * of how it works. 33 | */ 34 | 35 | namespace vcube::nx { 36 | 37 | /* Number of corner sym-coordinates */ 38 | static constexpr uint32_t N_CORNER_SYM = 9930; 39 | 40 | /* EP variants preserve varying information about the edge permutation. 41 | * 42 | * EP1 distinguishes between E-layer and non-E-layer edges 43 | * EP2 is EP1 + preserves the relative position of E-layer edges 44 | * EP3 is EP1 + distinguishes between U-layer and D-layer edges 45 | * EP4 is EP1 + EP2 + EP3 46 | * 47 | * U-layer: UR UF UL UB (up) 48 | * E-layer: FR FL BL BR (equatorial) 49 | * D-layer: DR DF DL DB (down) 50 | * 51 | * DF FR UF BR FL DB UB DR DL BL UL UR 52 | * EP1: -- EE -- EE EE -- -- -- -- EE -- -- 53 | * EP2: -- FR -- BR FL -- -- -- -- BL -- -- 54 | * EP3: DD EE UU EE EE DD UU DD DD EE UU UU 55 | * EP4: DD FR UU BR FL DD UU DD DD BL UU UU 56 | */ 57 | enum EPvariant { 58 | EP1, // 12C4 == 495 59 | EP2, // 12C4 * 4! == 11880 60 | EP3, // 12C4 * 8C4 == 34650 61 | EP4 // 12C4 * 8C4 * 4! == 831600 62 | }; 63 | 64 | /* EO variants preserve varying information about the edge orientation. 65 | * 66 | * EO4 E-layer edges 67 | * EO8 U-layer and D-layer edges 68 | * EO12 All edges 69 | */ 70 | enum EOvariant { 71 | EO4, // 2^4 == 16 72 | EO8, // 2^8 == 256 73 | EO12 // 2^11 == 2048 74 | }; 75 | 76 | /* Edge coordinate. Instantiate this with the desired combination 77 | * of EP and EO variant 78 | */ 79 | template 80 | class ecoord { 81 | static constexpr std::array N_EP = { 1, 24, 70, 1680 }; 82 | static constexpr std::array N_EO = { 16, 256, 2048 }; 83 | public: 84 | static constexpr uint32_t N_ECOORD = N_EP[EP] * N_EO[EO] * 512; 85 | 86 | ecoord() : coord() { 87 | } 88 | 89 | ecoord(const edgecube &ec, int sym = 0) { 90 | __m128i ve = ec.symConjugate(sym); 91 | 92 | // 0x10: edge orient 93 | uint32_t eorient = sse::bitmask(ve, 4); 94 | // 0x08: equatorial layer 95 | uint32_t e_layer = sse::bitmask(ve, 3) & 0xfff; 96 | // 0x04: down layer 97 | uint32_t d_layer = sse::bitmask(ve, 2); 98 | 99 | uint32_t dcomb = rank_8C4(_pext_u32(d_layer, e_layer ^ 0xfff)); 100 | uint32_t ecomb = rank_12C4(e_layer); 101 | 102 | uint32_t e4 = sse::bitmask(ve, 0) ^ (sse::bitmask(ve, 1) << 12) ^ 0xa000; 103 | uint32_t e4perm = rank_4perm_oddeven(_pext_u32(e4, e_layer | (e_layer << 12))); 104 | 105 | /* Insert gaps into the 0..494 numbering to make room for 106 | * the 4-bit min-of-62 pruning values. 107 | * Note: 33/2048 approximates 1/62 efficiently 108 | */ 109 | ecomb += (ecomb + 63) * 33 / 2048 * 2; 110 | 111 | switch (EP) { 112 | case EP1: 113 | coord = 0; 114 | break; 115 | case EP2: 116 | coord = e4perm; 117 | break; 118 | case EP3: 119 | coord = dcomb; 120 | break; 121 | case EP4: 122 | coord = N_E4PERM * dcomb + e4perm; 123 | break; 124 | } 125 | 126 | uint32_t eo; 127 | switch (EO) { 128 | case EO4: 129 | eo = _pext_u32(eorient, e_layer); 130 | coord = (coord << 13) | (eo << 9) | ecomb; 131 | break; 132 | case EO8: 133 | eo = _pext_u32(eorient, e_layer ^ 0xfff); 134 | coord = (coord << 17) | (eo << 9) | ecomb; 135 | break; 136 | case EO12: 137 | eo = eorient & 0x7ff; 138 | coord = (coord << 20) | (eo << 9) | ecomb; 139 | break; 140 | } 141 | } 142 | 143 | operator uint32_t () const { 144 | return coord; 145 | } 146 | 147 | /* Apply a representative edge orientation to a cube */ 148 | static void applyEO(edgecube &c, eorient_t eorient) { 149 | switch (EO) { 150 | case EO4: 151 | c.setEdge4Orient(eorient); 152 | break; 153 | case EO8: 154 | c.setEdge8Orient(eorient); 155 | break; 156 | case EO12: 157 | c.setEdgeOrient(eorient); 158 | break; 159 | } 160 | } 161 | 162 | /* Returns a vector of representative cubes for the permutation 163 | * variant specified in the template parameter. These cubes do not 164 | * include the "ecomb" (equatorial 12C4) part of the coordinate. 165 | */ 166 | static auto getEPnCubes() { 167 | std::vector c_EPn; 168 | switch (EP) { 169 | case EP1: 170 | c_EPn.resize(1); 171 | break; 172 | case EP2: 173 | c_EPn.resize(N_E4PERM); 174 | for (e4perm_t i = 0; i < N_E4PERM; i++) { 175 | c_EPn[i] = cube().setEdge4Perm(i); 176 | } 177 | break; 178 | case EP3: 179 | c_EPn.resize(N_EUD4COMB); 180 | for (eud4comb_t i = 0; i < N_EUD4COMB; i++) { 181 | c_EPn[i] = cube().setEdgeUD4Comb(i); 182 | } 183 | break; 184 | case EP4: 185 | c_EPn.resize(N_EUD4COMB * N_E4PERM); 186 | for (e4perm_t i = 0; i < N_E4PERM; i++) { 187 | c_EPn[i] = cube().setEdge4Perm(i); 188 | } 189 | for (eud4comb_t i = 1; i < N_EUD4COMB; i++) { 190 | uint32_t base = i * N_E4PERM; 191 | c_EPn[base] = cube().setEdgeUD4Comb(i); 192 | for (e4perm_t i = 1; i < N_E4PERM; i++) { 193 | // The e4perm and ud4comb coordinates are orthogonal 194 | c_EPn[base + i] = c_EPn[base] * c_EPn[i]; 195 | } 196 | } 197 | break; 198 | } 199 | return c_EPn; 200 | } 201 | 202 | /* Decodes the high part of the coordinate (shifted right 9 bits) */ 203 | static auto decode(uint32_t coord) { 204 | uint32_t high, low, eo; 205 | low = coord & 0x1ff; 206 | coord >>= 9; 207 | switch (EO) { 208 | case EO4: 209 | eo = coord & 0xf; 210 | high = coord >> 4; 211 | break; 212 | case EO8: 213 | eo = coord & 0xff; 214 | high = coord >> 8; 215 | break; 216 | case EO12: 217 | eo = coord & 0x7ff; 218 | high = coord >> 11; 219 | break; 220 | } 221 | return std::make_tuple(high, low, eo); 222 | } 223 | 224 | private: 225 | uint32_t coord; 226 | }; 227 | 228 | /* Edge coordinate representative cube generator */ 229 | class ecoord_rep { 230 | public: 231 | template 232 | void init() { 233 | for (e4comb_t i = 0; i < N_E4COMB; i++) { 234 | e4comb_t adj = (i + 63) * 33 / 2048 * 2; 235 | c_EP1[i + adj] = cube().setEdge4Comb(i); 236 | } 237 | c_EPn = ECoord::getEPnCubes(); 238 | } 239 | 240 | template 241 | edgecube get(uint32_t high, uint32_t low, uint32_t eo) { 242 | edgecube ec = c_EPn[high] * c_EP1[low]; 243 | ECoord::applyEO(ec, eo); 244 | return ec; 245 | } 246 | 247 | private: 248 | // Representatives for the 512 EP1 coordinates (same for all EP variants) 249 | std::array c_EP1; 250 | // Representatives for the rest of the EP coordinate (varies) 251 | std::vector c_EPn; 252 | }; 253 | 254 | /* Raw corner coordinate */ 255 | class ccoord { 256 | public: 257 | ccoord(const cube &c) { 258 | coord = (c.getCornerOrientRaw() << 8) | c.getCorner4Comb(); 259 | } 260 | 261 | operator uint32_t () const { 262 | return coord; 263 | } 264 | 265 | /* Returns a representative cube for 16-way symmetry, and the 266 | * symmetry index used to make that transformation 267 | */ 268 | static cube rep(const cube &c, uint8_t &sym) { 269 | sym = 0; 270 | cube rep = c; 271 | ccoord best = c; 272 | for (int s = 1; s < 16; s++) { 273 | cube c_s = c.symConjugate(s); 274 | ccoord coord(c_s); 275 | if (coord < best) { 276 | sym = s; 277 | rep = c_s; 278 | best = coord; 279 | } 280 | } 281 | return rep; 282 | } 283 | 284 | private: 285 | uint32_t coord; 286 | }; 287 | 288 | /* The pruning table is indexed first by corner sym-coordinate. 289 | * This class is responsible for the mapping of raw corners to 290 | * sym corners 291 | */ 292 | class prune_base { 293 | struct offset_sym_t { 294 | uint8_t offset; // offset from base sym-coordinate 295 | uint8_t sym; // conjugate -> representative 296 | 297 | bool operator == (const offset_sym_t &o) const { 298 | return offset == o.offset && sym == o.sym; 299 | } 300 | }; 301 | 302 | struct index_t { 303 | uint16_t base; // lowest sym-coordinate for this corient 304 | offset_sym_t *os; // offsets and symmetries for each c4comb 305 | uint8_t *prune; // pointer to pruning table entries 306 | }; 307 | 308 | public: 309 | size_t size() const { 310 | return stride * N_CORNER_SYM; 311 | } 312 | 313 | bool save(const std::string &filename) const; 314 | bool load(const std::string &filename); 315 | bool loadShared(uint32_t key, const std::string &filename = ""); 316 | 317 | protected: 318 | prune_base(size_t stride); 319 | 320 | void setPrune(decltype(index_t::prune) p); 321 | 322 | std::vector getCornerRepresentatives() const; 323 | 324 | uint8_t * getPruneRow(uint32_t corner_sym) { 325 | return index[0].prune + corner_sym * stride; 326 | } 327 | 328 | uint16_t sym_coord(const cube &c) const { 329 | auto &idx = index[c.getCornerOrient()]; 330 | auto &os = idx.os[c.getCorner4Comb()]; 331 | return idx.base + os.offset; 332 | } 333 | 334 | uint8_t get_sym(const cube &c) const { 335 | auto &idx = index[c.getCornerOrient()]; 336 | auto &os = idx.os[c.getCorner4Comb()]; 337 | return os.sym; 338 | } 339 | 340 | /* The offset_sym array, when deduplicated, has 139 distinct patterns */ 341 | using os_unique_t = std::array; 342 | static constexpr uint32_t N_UNIQUE_OFFSET_SYM = 139; 343 | std::array os_unique; 344 | 345 | std::array index; 346 | 347 | size_t stride; 348 | }; 349 | 350 | template 351 | class prune : public prune_base { 352 | template friend class prune_generator; 353 | 354 | struct prefetch_t { 355 | uint32_t edge; 356 | const uint8_t *stripe; 357 | uint8_t fetch() const { 358 | auto &octet = stripe[(edge / 4) % 16]; 359 | auto shift = (edge % 4) * 2; 360 | auto val = (octet >> shift) & 3; 361 | return val ? (BASE + val) : (stripe[0] & 0xf); 362 | } 363 | }; 364 | 365 | prefetch_t prefetch(const cube &c) const { 366 | auto &idx = index[c.getCornerOrient()]; 367 | auto &os = idx.os[c.getCorner4Comb()]; 368 | auto edge = ECoord(c, os.sym); 369 | auto stripe = &idx.prune[16 * (N_EDGE_STRIPE * os.offset + edge / 64)]; 370 | _mm_prefetch(stripe, _MM_HINT_T0); 371 | return { edge, stripe }; 372 | } 373 | 374 | public: 375 | using ecoord = ECoord; 376 | static constexpr uint64_t N_EDGE_STRIPE = ecoord::N_ECOORD / 64; 377 | static constexpr int BASE = Base; 378 | 379 | prune() : prune_base(16 * N_EDGE_STRIPE) { 380 | } 381 | 382 | uint8_t lookup(const cube6 &c6, uint8_t limit, uint32_t &prune_vals, int skip, int val, uint8_t &axis_mask) const { 383 | prefetch_t pre[6]; 384 | if (skip != 0) pre[0] = prefetch(c6[0]); 385 | if (skip != 1) pre[1] = prefetch(c6[1]); 386 | if (skip != 2) pre[2] = prefetch(c6[2]); 387 | if (skip != 3) pre[3] = prefetch(c6[3]); 388 | if (skip != 4) pre[4] = prefetch(c6[4]); 389 | if (skip != 5) pre[5] = prefetch(c6[5]); 390 | 391 | uint8_t prune[6]; 392 | if (skip != 0xff) { 393 | prune[skip] = val; 394 | } 395 | 396 | if (0 != skip) { 397 | prune[0] = pre[0].fetch(); 398 | if (prune[0] > limit) { 399 | return prune[0]; 400 | } 401 | } 402 | if (1 != skip) { 403 | prune[1] = pre[1].fetch(); 404 | if (prune[1] > limit) { 405 | return prune[1]; 406 | } 407 | } 408 | if (2 != skip) { 409 | prune[2] = pre[2].fetch(); 410 | if (prune[2] > limit) { 411 | return prune[2]; 412 | } 413 | } 414 | 415 | prune_vals = (prune[0] << 0) | (prune[1] << 4) | (prune[2] << 8); 416 | if (!prune_vals) { 417 | return 0; 418 | } 419 | 420 | uint32_t prune_cmp0 = (1 << prune[0]) | (1 << prune[1]) | (1 << prune[2]); 421 | prune_cmp0 |= _blsi_u32(prune_cmp0) << 1; 422 | uint8_t prune0 = 31 - _lzcnt_u32(prune_cmp0); 423 | if (prune0 > limit) { 424 | return prune0; 425 | } 426 | 427 | if (3 != skip) { 428 | prune[3] = pre[3].fetch(); 429 | if (prune[3] > limit) { 430 | return prune[3]; 431 | } 432 | } 433 | if (4 != skip) { 434 | prune[4] = pre[4].fetch(); 435 | if (prune[4] > limit) { 436 | return prune[4]; 437 | } 438 | } 439 | if (5 != skip) { 440 | prune[5] = pre[5].fetch(); 441 | if (prune[5] > limit) { 442 | return prune[5]; 443 | } 444 | } 445 | 446 | prune_vals |= (prune[3] << 12) | (prune[4] << 16) | (prune[5] << 20); 447 | 448 | uint32_t prune_cmp1 = (1 << prune[3]) | (1 << prune[4]) | (1 << prune[5]); 449 | prune_cmp1 |= _blsi_u32(prune_cmp1) << 1; 450 | uint8_t prune1 = 31 - _lzcnt_u32(prune_cmp1); 451 | if (prune1 > limit) { 452 | return prune1; 453 | } 454 | 455 | axis_mask = 456 | ((prune[0] == limit) << 0) | 457 | ((prune[1] == limit) << 1) | 458 | ((prune[2] == limit) << 2) | 459 | ((prune[3] == limit) << 3) | 460 | ((prune[4] == limit) << 4) | 461 | ((prune[5] == limit) << 5); 462 | 463 | return (prune0 < prune1) ? prune1 : prune0; 464 | } 465 | 466 | uint8_t initial_depth(const cube6 &c6) const { 467 | uint32_t prune_vals; 468 | uint8_t axis_mask; 469 | return lookup(c6, 0xff, prune_vals, -1, 0, axis_mask); 470 | } 471 | 472 | private: 473 | void init(uint8_t *mem) { 474 | setPrune(mem); 475 | } 476 | }; 477 | 478 | } 479 | 480 | #endif 481 | -------------------------------------------------------------------------------- /src/vc-optimal.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include "nxprune.h" 32 | #include "nxprune_generator.h" 33 | #include "nxsolve.h" 34 | 35 | using namespace vcube; 36 | 37 | enum format_t { 38 | FMT_MOVES, 39 | FMT_REID, 40 | FMT_SPEFFZ, 41 | }; 42 | 43 | /* Configuration */ 44 | static struct { 45 | std::string path; 46 | uint32_t workers; 47 | uint32_t coord; 48 | moveseq_t::style_t style; 49 | std::array speffz_buffer; 50 | format_t format; 51 | bool no_input; 52 | bool ordered; 53 | bool shm; 54 | bool inverse; 55 | uint32_t depth; 56 | } cf; 57 | 58 | static std::string base_path(const char *argv0); 59 | 60 | template 61 | static void solver(const std::string &table_filename, uint32_t shm_key); 62 | static void usage(const char *argv0, int status = EXIT_FAILURE); 63 | 64 | struct solver_variant { 65 | int id; 66 | void (*func)(const std::string &, uint32_t shm_key); 67 | std::string filename; 68 | uint32_t shm_key; 69 | size_t size; 70 | 71 | bool operator < (const solver_variant &o) const { 72 | return size < o.size; 73 | } 74 | 75 | void operator()() { 76 | func(filename, shm_key); 77 | } 78 | 79 | template 80 | static solver_variant S(int id) { 81 | char filename[64]; 82 | sprintf(filename, "tables/nxprune_%d_%02d_%02d.dat", 83 | EP + 1, (EO + 1) * 4, Base); 84 | uint32_t shm_key = 0x76630000 | 85 | (Base << 8) | 86 | ((EP + 1) << 4) | 87 | ((EO + 1) * 4); 88 | return { 89 | id, 90 | solver, 91 | filename, 92 | shm_key, 93 | nx::prune, Base>().size() 94 | }; 95 | } 96 | }; 97 | 98 | /* Some variants are commented out, either because they do not yet have 99 | * a base value chosen (510 GiB and 4 TiB), or because they are too slow 100 | * and unnecessarily increase code size by instantiting templates that 101 | * will see little use. 102 | */ 103 | static std::vector solvers = { 104 | //solver_variant::S(104), 105 | //solver_variant::S(108), 106 | solver_variant::S(112), 107 | 108 | //solver_variant::S(204), 109 | solver_variant::S(208), 110 | solver_variant::S(212), 111 | 112 | solver_variant::S(304), 113 | solver_variant::S(308), 114 | solver_variant::S(312), // base 11 reduces lookups by 0.2% 115 | 116 | solver_variant::S(404), 117 | //solver_variant::S(408), 118 | //solver_variant::S(412), 119 | }; 120 | static constexpr int DEFAULT_VARIANT = 308; 121 | 122 | int main(int argc, char * const *argv) { 123 | cf.path = base_path(argv[0]); 124 | cf.workers = std::max(1U, std::thread::hardware_concurrency()); 125 | cf.coord = DEFAULT_VARIANT; 126 | cf.style = moveseq_t::SINGMASTER; 127 | cf.format = FMT_MOVES; 128 | cf.speffz_buffer = { 'A', 'U' }; 129 | cf.no_input = false; 130 | cf.ordered = false; 131 | cf.inverse = false; 132 | cf.depth = 20; 133 | 134 | for (;;) { 135 | static struct option long_options[] = { 136 | { "coord", required_argument, 0, 'c' }, 137 | { "depth", required_argument, 0, 'd' }, 138 | { "format", required_argument, 0, 'f' }, 139 | { "help", no_argument, 0, 'h' }, 140 | { "inverse", no_argument, 0, 'i' }, 141 | { "no-input", no_argument, 0, 'n' }, 142 | { "ordered", no_argument, 0, 'O' }, 143 | { "shm", no_argument, 0, 'S' }, 144 | { "speffz", optional_argument, 0, 'z' }, 145 | { "style", required_argument, 0, 's' }, 146 | { "workers", required_argument, 0, 'w' }, 147 | { NULL } 148 | }; 149 | 150 | int option_index = 0; 151 | int this_option_optind = optind ? optind : 1; 152 | int c = getopt_long(argc, argv, "c:d:f:hinOSs:w:z::", long_options, &option_index); 153 | if (c == -1) { 154 | break; 155 | } 156 | 157 | int len; 158 | switch (c) { 159 | case 'c': 160 | cf.coord = strtoul(optarg, NULL, 10); 161 | break; 162 | case 'd': 163 | cf.depth = strtoul(optarg, NULL, 10); 164 | break; 165 | case 'f': 166 | len = strlen(optarg); 167 | if (!strncmp(optarg, "moves", len)) { 168 | cf.format = FMT_MOVES; 169 | } else if (!strncmp(optarg, "reid", len)) { 170 | cf.format = FMT_REID; 171 | } else if (!strncmp(optarg, "speffz", len)) { 172 | cf.format = FMT_SPEFFZ; 173 | } else { 174 | fprintf(stderr, "Unsupported input format '%s'\n", optarg); 175 | usage(argv[0]); 176 | } 177 | break; 178 | case 'z': 179 | cf.format = FMT_SPEFFZ; 180 | if (optarg) { 181 | if (optarg[0]) { 182 | cf.speffz_buffer[0] = optarg[0]; // corner 183 | if (optarg[1]) { 184 | cf.speffz_buffer[1] = optarg[1]; // edge 185 | } 186 | } 187 | } 188 | break; 189 | case 'n': 190 | cf.no_input = true; 191 | break; 192 | case 'O': 193 | cf.ordered = true; 194 | break; 195 | case 'S': 196 | cf.shm = true; 197 | break; 198 | case 's': 199 | len = strlen(optarg); 200 | if (!strncmp(optarg, "human", len)) { 201 | cf.style = moveseq_t::SINGMASTER; 202 | } else if (!strncmp(optarg, "fixed", len)) { 203 | cf.style = moveseq_t::FIXED; 204 | } else { 205 | fprintf(stderr, "Unsupported output style '%s'\n", optarg); 206 | usage(argv[0]); 207 | } 208 | break; 209 | case 'i': 210 | cf.inverse = true; 211 | break; 212 | case 'w': 213 | cf.workers = strtoul(optarg, NULL, 10); 214 | break; 215 | default: 216 | usage(argv[0], EXIT_SUCCESS); 217 | } 218 | } 219 | 220 | setbuf(stdout, NULL); 221 | 222 | for (auto &S : solvers) { 223 | if (S.id == cf.coord) { 224 | S(); 225 | return 0; 226 | } 227 | } 228 | 229 | fprintf(stderr, "Unsupported edge coordinate '%d'\n", cf.coord); 230 | exit(EXIT_FAILURE); 231 | } 232 | 233 | std::string format_table_size(size_t n) { 234 | const char suffix[] = " kMGTPE"; 235 | static char s[64]; 236 | int e = (63 - _lzcnt_u64(n)) / 10; 237 | sprintf(s, "%.3f", double(n) / (1LL << (10 * e))); 238 | sprintf(s + 5, " %c%cB", " kMGTPE"[e], "i "[!e]); 239 | return s; 240 | } 241 | 242 | void usage(const char *argv0, int status) { 243 | fprintf(stdout, "Usage: %s [OPTION]...\n", argv0); 244 | fputs( /**********************************************************************/ 245 | "Optimal half-turn metric Rubik's cube solver.\n" 246 | "\n" 247 | "Input cubes are read from standard input, one per line.\n" 248 | "Solutions are output in the order they are found, which may differ from\n" 249 | "the input order. Each output line includes the input sequence number.\n" 250 | "Example output:\n" 251 | " 7 68.926868516 20 U3L3U2F1D1R3L2B1L3U3L2U3F2D3F2R1U3L2F1B1\n" 252 | "The fields are:\n" 253 | " Sequence number, time to solve, solution length, solution\n" 254 | "\n" 255 | "Options:\n" 256 | " -h, --help\n" 257 | " -c, --coord=COORD pruning coordinate variant\n" 258 | " -d, --depth=DEPTH maximum depth to search\n" 259 | " -f, --format=FORMAT input format\n" 260 | " -z, --speffz=[C[E]] speffz buffers (implies -f speffz)\n" 261 | " -n, --no-input load/generate tables and exit\n" 262 | " -O, --ordered output in the same order as input\n" 263 | " -S, --shm load table into shared memory\n" 264 | " -s, --style=STYLE output style\n" 265 | " -i, --inverse output scrambles instead of solutions\n" 266 | " -w, --workers=NUM worker count (default: cpu core count)\n" 267 | "\n" 268 | "Pruning coordinate variants (COORD):\n" 269 | , stdout); 270 | std::sort(solvers.begin(), solvers.end()); 271 | for (auto &S : solvers) { 272 | fprintf(stdout, " %3d (%s)%s\n", 273 | S.id, format_table_size(S.size).c_str(), 274 | S.id == DEFAULT_VARIANT ? " [default]" : ""); 275 | } 276 | fputs( /**********************************************************************/ 277 | "\n" 278 | "Input formats (FORMAT):\n" 279 | " moves [default]\n" 280 | " reid\n" 281 | " speffz (buffers: corner=A, edge=U)\n" 282 | "\n" 283 | "Output styles (STYLE):\n" 284 | " human (U' R F2) [default]\n" 285 | " fixed (U3R1R2)\n" 286 | "\n" 287 | "The input format used by Michael Reid's solver has the following identity:\n" 288 | " UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR\n" 289 | "\n" 290 | "Speffz is a lettering scheme used in blindfolded solving. The 24\n" 291 | "corner and 24 edge stickers are assigned letters A through X.\n" 292 | "This input format looks like \"corneRs.edGes\", and describes a\n" 293 | "sequence of swaps and in-place reorientations that will solve the cube.\n" 294 | "Lowercase letters specify a sticker that will be swapped with the\n" 295 | "buffer. Uppercase edges are flipped in place; uppercase corner\n" 296 | "stickers are twisted toward the up/down face. In place reorientations\n" 297 | "also affect the buffer in the opposite direction.\n" 298 | "\n" 299 | "Example speffz input notation (A/U buffers) for nested cubes pattern:\n" 300 | " olpibpMH.etlaol == U' R D' F' R U2 R2 U' R' U R2 L D' L' F2 D2 R'\n" 301 | , stdout); 302 | exit(status); 303 | } 304 | 305 | std::string base_path(const char *argv0) { 306 | char *tmp = realpath(argv0, NULL); 307 | if (!tmp) { 308 | perror("realpath"); 309 | exit(EXIT_FAILURE); 310 | } 311 | std::string path(dirname(tmp)); 312 | free(tmp); 313 | return path; 314 | } 315 | 316 | class cpu_clock { 317 | public: 318 | using duration = std::chrono::microseconds; 319 | using time_point = std::chrono::time_point;; 320 | static time_point now() noexcept { 321 | struct rusage ru; 322 | getrusage(RUSAGE_SELF, &ru); 323 | return time_point(duration( 324 | (ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) * 1000000 + 325 | (ru.ru_utime.tv_usec + ru.ru_stime.tv_usec))); 326 | } 327 | }; 328 | 329 | template 330 | void solver(const std::string &table_filename, uint32_t shm_key) { 331 | using ECoord = nx::ecoord; 332 | using Prune = nx::prune; 333 | 334 | Prune P; 335 | 336 | std::string table_fullpath = cf.path + "/" + table_filename; 337 | if (!P.loadShared(shm_key)) { 338 | bool ok; 339 | if (cf.shm) { 340 | ok = P.loadShared(shm_key, table_fullpath); 341 | } else { 342 | ok = P.load(table_fullpath); 343 | } 344 | if (!ok) { 345 | nx::prune_generator gen(P, cf.workers); 346 | gen.generate(); 347 | P.save(table_fullpath); 348 | } 349 | } 350 | 351 | if (cf.no_input) { 352 | // generate tables only 353 | return; 354 | } 355 | 356 | nx::solver_base::init(); 357 | 358 | auto t0 = std::chrono::steady_clock::now(); 359 | auto cpu_t0 = cpu_clock::now(); 360 | 361 | std::deque solutions; 362 | uint64_t next_id = 0; 363 | 364 | std::mutex mtx, out_mtx; 365 | std::vector workers; 366 | for (int i = 0; i < cf.workers; i++) { 367 | workers.push_back(std::thread([&mtx, &out_mtx, &P, &next_id, &solutions]() { 368 | char buf[1024]; 369 | decltype(solutions)::iterator solutionp; 370 | nx::solver S(P); 371 | mtx.lock(); 372 | while (!feof(stdin) && fgets(buf, sizeof(buf), stdin)) { 373 | uint64_t solution_id = next_id++; 374 | if (cf.ordered) { 375 | solutionp = solutions.insert(solutions.end(), ""); 376 | } 377 | mtx.unlock(); 378 | 379 | cube c; 380 | switch (cf.format) { 381 | case FMT_MOVES: 382 | c = cube::from_moves(buf); 383 | break; 384 | case FMT_REID: 385 | c = cube::from_reid(buf); 386 | break; 387 | case FMT_SPEFFZ: 388 | c = cube::from_speffz(buf, cf.speffz_buffer[0], cf.speffz_buffer[1]); 389 | break; 390 | } 391 | if (cf.inverse) c = ~c; 392 | 393 | auto t0 = std::chrono::steady_clock::now(); 394 | auto moves = S.solve(c, cf.depth); 395 | std::chrono::duration elapsed = std::chrono::steady_clock::now() - t0; 396 | 397 | moves = moves.canonical(); 398 | std::string solution = moves.to_string(cf.style); 399 | 400 | snprintf(buf, sizeof(buf), "%lu %.9f %lu %s", 401 | solution_id, 402 | elapsed.count(), 403 | moves.size(), 404 | solution.c_str()); 405 | 406 | out_mtx.lock(); 407 | if (cf.ordered) { 408 | *solutionp = buf; 409 | while (!solutions.empty() && !solutions.front().empty()) { 410 | puts(solutions.front().c_str()); 411 | solutions.pop_front(); 412 | } 413 | } else { 414 | puts(buf); 415 | } 416 | out_mtx.unlock(); 417 | 418 | mtx.lock(); 419 | } 420 | mtx.unlock(); 421 | })); 422 | } 423 | 424 | for (auto &t : workers) { 425 | t.join(); 426 | } 427 | 428 | std::chrono::duration elapsed = std::chrono::steady_clock::now() - t0; 429 | std::chrono::duration cpu_elapsed = cpu_clock::now() - cpu_t0; 430 | fprintf(stderr, "Total time: %.9f real, %.6f cpu, %.6f cpu/worker\n", 431 | elapsed.count(), 432 | cpu_elapsed.count(), 433 | cpu_elapsed.count() / cf.workers); 434 | } 435 | -------------------------------------------------------------------------------- /src/cube.h: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #ifndef VCUBE_CUBE_H 20 | #define VCUBE_CUBE_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "sse_cube.h" 28 | #include "avx2_cube.h" 29 | #include "types.h" 30 | #include "util.h" 31 | 32 | namespace vcube { 33 | 34 | template 35 | class cube_base { 36 | public: 37 | /* Test cubes for inequality */ 38 | bool operator != (T o) const { 39 | return !(static_cast(*this) == o); 40 | } 41 | 42 | /* Composition */ 43 | T operator * (T o) const { 44 | return compose(o); 45 | } 46 | 47 | T& operator *= (T o) { 48 | return static_cast(*this) = compose(o); 49 | } 50 | 51 | /* Composition variant for mirrored (S_LR2) left operand. 52 | * Corner twist is subtracted rather than added. 53 | */ 54 | T operator % (T o) const { 55 | return compose(o, true); 56 | } 57 | 58 | T& operator %= (T o) { 59 | return static_cast(*this) = compose(o, true); 60 | } 61 | 62 | /* Get edge orientation without parity reduction 0..4095 */ 63 | eorient_t getEdgeOrientRaw() const { 64 | return edge_bitmask(4); 65 | } 66 | 67 | /* Get edge orientation coordinate 0..2047 */ 68 | eorient_t getEdgeOrient() const { 69 | return getEdgeOrientRaw() & 0x7ff; 70 | } 71 | 72 | /* Get U/D slice edge orientation coordinate 0..255 */ 73 | eorient_t getEdge8Orient() const { 74 | auto e_layer = edge_bitmask(3); 75 | return _pext_u32(getEdgeOrientRaw(), ~e_layer); 76 | } 77 | 78 | /* Get equatorial slice edge orientation coordinate 0..15 */ 79 | eorient_t getEdge4Orient() const { 80 | auto e_layer = edge_bitmask(3); 81 | return _pext_u32(getEdgeOrientRaw(), e_layer); 82 | } 83 | 84 | /* Set edge orientation coordinate 0..2047 */ 85 | T& setEdgeOrient(eorient_t eorient) { 86 | xor_edge_orient(set_eorient_parity(eorient ^ getEdgeOrientRaw())); 87 | return static_cast(*this); 88 | } 89 | 90 | /* Set U/D slice edge orientation from coordinate 0..255 */ 91 | T& setEdge8Orient(e8orient_t e8orient) { 92 | e8orient_t parity = e8orient ^ (e8orient >> 4); 93 | parity = (0x6996 >> (parity & 0xf)) & 1; 94 | auto e_layer = edge_bitmask(3); 95 | auto ori = _pdep_u32(e8orient, ~e_layer) | _pdep_u32(parity, e_layer); 96 | xor_edge_orient(ori ^ getEdgeOrientRaw()); 97 | return static_cast(*this); 98 | } 99 | 100 | /* Set equatorial slice edge orientation from coordinate 0..15 */ 101 | T& setEdge4Orient(e4orient_t e4orient) { 102 | e4orient_t parity = (0x6996 >> e4orient) & 1; 103 | auto e_layer = edge_bitmask(3); 104 | auto ori = _pdep_u32(e4orient, e_layer) | _pdep_u32(parity, ~e_layer); 105 | xor_edge_orient(ori ^ getEdgeOrientRaw()); 106 | return static_cast(*this); 107 | } 108 | 109 | /* Get corner orientation without parity reduction; returns a 16-bit 110 | * packed integer with a 2-bit field for each corner orientation (0..2) 111 | */ 112 | corient_t getCornerOrientRaw() const { 113 | return corner_orient_raw(); 114 | } 115 | 116 | /* Get corner orientation coordinate 0..2186 */ 117 | corient_t getCornerOrient() const { 118 | return corner_orient(); 119 | } 120 | 121 | /* Get the 4! equatorial permutation 0..23 */ 122 | e4perm_t getEdge4Perm() const { 123 | // The 0xf000 and 0xa000 XORs correct for the 0xfedc pseudo-edges 124 | uint32_t e_layer = edge_bitmask(3); 125 | e_layer ^= (e_layer << 12) ^ 0xf000; 126 | uint32_t e = edge_bitmask(0) ^ (edge_bitmask(1) << 12) ^ 0xa000; 127 | return rank_4perm_oddeven(_pext_u32(e, e_layer)); 128 | } 129 | 130 | /* Get the 8C4 U/D face edge combination 0..69 */ 131 | eud4comb_t getEdgeUD4Comb() const { 132 | uint32_t e_layer = edge_bitmask(3); 133 | uint32_t d_layer = edge_bitmask(2); 134 | return rank_8C4(_pext_u32(d_layer, e_layer ^ 0xfff) & 0xff); 135 | } 136 | 137 | private: 138 | T compose(T o, bool mirror = false) const { 139 | return static_cast(this)->compose(o, mirror); 140 | } 141 | 142 | uint32_t edge_bitmask(int bit) const { 143 | return static_cast(this)->edge_bitmask(bit); 144 | } 145 | 146 | uint32_t corner_bitmask(int bit) const { 147 | return static_cast(this)->corner_bitmask(bit); 148 | } 149 | 150 | void xor_edge_orient(eorient_t eorient) { 151 | static_cast(this)->xor_edge_orient(eorient); 152 | } 153 | 154 | corient_t corner_orient_raw() const { 155 | return static_cast(this)->corner_orient_raw(); 156 | } 157 | 158 | corient_t corner_orient() const { 159 | return static_cast(this)->corner_orient(); 160 | } 161 | }; 162 | 163 | class cube : public cube_base { 164 | friend class cube_base; 165 | 166 | public: 167 | constexpr cube() : v(avx2::identity) { 168 | } 169 | 170 | constexpr cube(uint64_t corners, uint64_t edges_high, uint64_t edges_low) : 171 | v(avx2::literal(corners, edges_high, edges_low)) 172 | { 173 | } 174 | 175 | /* Parse a move sequence */ 176 | static cube from_moveseq(const moveseq_t &v); 177 | 178 | static cube from_moves(const std::string &s) { 179 | return from_moveseq(moveseq_t::parse(s)); 180 | } 181 | 182 | /* Parse a cube position in Reid's notation */ 183 | static cube from_reid(std::string s); 184 | 185 | /* Convert a cube to Reid's notation */ 186 | std::string to_reid() const; 187 | 188 | /* Parse a cube position in Speffz cycles, corners first 189 | * Corners and edges are delimited by the "." character. 190 | * Lowercase letters cycle the edge/corner sticker with the buffer, 191 | * and uppercase letters twist the edge/corner in place. 192 | * Uppercase edges always flip (A and Q are equivalent) 193 | * Uppercase corners twist the U/D sticker into the specified position 194 | * (P twists the URF corner clockwise; J twists it counterclockwise) 195 | */ 196 | static cube from_speffz(const std::string &s, int corner_buffer = 'A', int edge_buffer = 'A'); 197 | 198 | /* Test cubes for equality */ 199 | bool operator == (const cube &o) const { 200 | return avx2::equals(v, o.v); 201 | } 202 | 203 | /* Ordering operator for use with std::set, std::map, etc. */ 204 | bool operator < (const cube &o) const { 205 | return avx2::less_than(v, o.v); 206 | } 207 | 208 | /* Convert to __m256i */ 209 | operator const __m256i & () const { 210 | return v; 211 | } 212 | 213 | /* Invert the cube (192M/sec, 243M/sec pipelined) */ 214 | cube operator ~ () const { 215 | return avx2::invert(v); 216 | } 217 | 218 | /* Compose with another cube (1080M/sec, 2517M/sec pipelined) */ 219 | cube compose(const cube &o, bool mirror = false) const { 220 | return avx2::compose(v, o.v, mirror); 221 | } 222 | 223 | /* Parity of the edge+corner permutation */ 224 | bool parity() const { 225 | return avx2::parity(v); 226 | } 227 | 228 | /* Move */ 229 | cube move(int m) const; 230 | 231 | /* Premove */ 232 | cube premove(int m) const; 233 | 234 | /* Conjugate with a symmetry */ 235 | cube symConjugate(int s) const; 236 | 237 | /* Set corner orientation from coordinate 0..2186 */ 238 | cube & setCornerOrient(corient_t corient); 239 | 240 | /* Set full edge permutation coordinate 0..479001599 and reset edge orientation */ 241 | cube & setEdgePerm(eperm_t eperm) { 242 | constexpr uint32_t fc[] = { 39916800, 3628800, 362880, 40320, 5040, 720, 120, 24, 6, 2, 1 }; 243 | uint64_t table = 0xba9876543210; 244 | 245 | uint8_t *edge = reinterpret_cast(&ev()); 246 | 247 | // Special case, first iteration does not need "% 12" 248 | for (int i = 0; i < 1; i++) { 249 | int shift = eperm / fc[i] * 4; 250 | edge[i] = _bextr_u64(table, shift, 4); 251 | table ^= (table ^ (table >> 4)) & (int64_t(-1) << shift); 252 | } 253 | 254 | for (int i = 1; i < 11; i++) { 255 | int shift = eperm / fc[i] % (12 - i) * 4; 256 | edge[i] = _bextr_u64(table, shift, 4); 257 | table ^= (table ^ (table >> 4)) & (int64_t(-1) << shift); 258 | } 259 | 260 | edge[11] = table; 261 | 262 | return *this; 263 | } 264 | 265 | /* Get full edge permutation coordinate 0..479001599 */ 266 | eperm_t getEdgePerm() const { 267 | uint64_t table = 0xba9876543210; 268 | eperm_t eperm = 0; 269 | 270 | uint64_t e = avx2::edges_low(v) << 2; 271 | for (int i = 0; i < 8; i++) { 272 | int shift = e & 0x3c; 273 | eperm = eperm * (12 - i) + _bextr_u64(table, shift, 4); 274 | table -= 0x111111111110LL << shift; 275 | e >>= 8; 276 | } 277 | 278 | e = avx2::edges_high(v) << 2; 279 | for (int i = 8; i < 11; i++) { 280 | int shift = e & 0x3c; 281 | eperm = eperm * (12 - i) + _bextr_u64(table, shift, 4); 282 | table -= 0x111111111110LL << shift; 283 | e >>= 8; 284 | } 285 | 286 | return eperm; 287 | } 288 | 289 | /* Set full corner permutation coordinate 0..40319 and reset corner orientation */ 290 | cube & setCornerPerm(cperm_t cperm) { 291 | constexpr uint32_t fc[] = { 5040, 720, 120, 24, 6, 2, 1 }; 292 | uint32_t table = 0x76543210; 293 | 294 | uint8_t *corner = reinterpret_cast(&cv()); 295 | 296 | for (int i = 0; i < 7; i++) { 297 | int shift = cperm / fc[i] % (8 - i) * 4; 298 | corner[i] = _bextr_u64(table, shift, 4); 299 | table ^= (table ^ (table >> 4)) & (-1L << shift); 300 | } 301 | 302 | corner[7] = table; 303 | 304 | return *this; 305 | } 306 | 307 | /* Get full corner permutation coordinate 0..40319 */ 308 | cperm_t getCornerPerm() const { 309 | uint32_t table = 0x76543210; 310 | cperm_t cperm = 0; 311 | 312 | uint64_t c = avx2::corners(v) << 2; 313 | for (int i = 0; i < 7; i++) { 314 | int shift = c & 0x3c; 315 | cperm = cperm * (8 - i) + _bextr_u64(table, shift, 4); 316 | table -= 0x11111110 << shift; 317 | c >>= 8; 318 | } 319 | 320 | return cperm; 321 | } 322 | 323 | /* Set a representative 8C4 U/D face corner combination 0..69 */ 324 | cube & setCorner4Comb(c4comb_t c4comb); 325 | 326 | /* Get the 8C4 U/D face corner combination 0..69 */ 327 | c4comb_t getCorner4Comb() const { 328 | // SSE tested faster than AVX, and makes it compatible with getCornerOrient 329 | uint32_t d_layer = sse::bitmask(cv(), 2); 330 | return rank_8C4(d_layer & 0xff); 331 | } 332 | 333 | /* Set a representative 12C4 equatorial / non-equatorial combination 0..494 */ 334 | cube & setEdge4Comb(e4comb_t e4comb); 335 | 336 | /* Get the 12C4 equatorial / non-equatorial combination 0..494 */ 337 | e4comb_t getEdge4Comb() const { 338 | uint32_t e_layer = sse::bitmask(ev(), 3); 339 | return rank_12C4(e_layer & 0xfff); 340 | } 341 | 342 | /* Set a representative 4! equatorial permutation 0..23 */ 343 | cube & setEdge4Perm(e4perm_t e4perm) { 344 | return setEdgePerm(e4perm); 345 | } 346 | 347 | /* Set a representative 8C4 U/D face edge combination 0..69 */ 348 | cube & setEdgeUD4Comb(eud4comb_t eud4comb); 349 | 350 | private: 351 | friend class edgecube; 352 | 353 | /* Low 128-bit lane: 354 | * 4 U-face edges 355 | * 4 D-face edges 356 | * 4 E-slice edges "equatorial" 357 | * 4 (unused) 358 | * 359 | * High 128-bit lane: 360 | * 4 U-face corners 361 | * 4 D-face corners 362 | * 8 (unused) 363 | * 364 | * Edge values (8 bits): 365 | * ---OEEEE 366 | * - = unused (zero) 367 | * O = orientation 368 | * E = edge index (0..11) 369 | * 370 | * Corner values (8 bits): 371 | * --OO-CCC 372 | * - = unused (zero) 373 | * O = orientation (0..2) 374 | * C = corner index (0..7) 375 | */ 376 | __m256i v; 377 | 378 | constexpr cube(__m256i v) : v(v) { 379 | } 380 | 381 | __m128i & ev() { 382 | return reinterpret_cast<__m128i *>(&v)[0]; 383 | } 384 | 385 | __m128i ev() const { 386 | return reinterpret_cast(&v)[0]; 387 | } 388 | 389 | __m128i & cv() { 390 | return reinterpret_cast<__m128i *>(&v)[1]; 391 | } 392 | 393 | __m128i cv() const { 394 | return reinterpret_cast(&v)[1]; 395 | } 396 | 397 | uint64_t * u64() { 398 | return reinterpret_cast(&v); 399 | } 400 | 401 | /* cube_base */ 402 | 403 | uint32_t edge_bitmask(int bit) const { 404 | return avx2::bitmask(v, bit) & 0xffff; 405 | } 406 | 407 | uint32_t corner_bitmask(int bit) const { 408 | return avx2::bitmask(v, bit) >> 16; 409 | } 410 | 411 | void xor_edge_orient(eorient_t eorient) { 412 | v = avx2::xor_edge_orient(v, eorient); 413 | } 414 | 415 | corient_t corner_orient() const { 416 | return sse::corner_orient(cv()); 417 | } 418 | 419 | corient_t corner_orient_raw() const { 420 | return avx2::corner_orient_raw(v); 421 | } 422 | }; 423 | 424 | /* Fundamental symmetries: 425 | * S_URF3 - 120-degree clockwise rotation on URF-DBL axis (x y) 426 | * S_U4 - 90-degree clockwise rotation on U-D axis (y) 427 | * S_LR2 - Reflection left to right 428 | */ 429 | static constexpr cube S_URF3 = {0x1226172321152410,0x12161410,0x0a170b1309150811}; 430 | static constexpr cube S_U4 = {0x0605040702010003,0x1a19181b,0x0605040702010003}; 431 | static constexpr cube S_LR2 = {0x0607040502030001,0x0a0b0809,0x0704050603000102}; 432 | 433 | /* Fundamental moves: 434 | * M_U - 90-degree clockwise twist of the U face 435 | */ 436 | static constexpr cube M_U = {0x0706050402010003,0x0b0a0908,0x0706050402010003}; 437 | 438 | #ifndef GENERATE_CONST_CUBES 439 | /* Generated moves and symmetries*/ 440 | #include "const-cubes.h" 441 | #endif 442 | 443 | extern inline cube cube::move(int m) const { 444 | return compose(moves[m]); 445 | } 446 | 447 | extern inline cube cube::premove(int m) const { 448 | return moves[m].compose(*this); 449 | } 450 | 451 | extern inline cube cube::symConjugate(int s) const { 452 | return sym[sym_inv[s]].compose(*this, s & 1).compose(sym[s], s & 1); 453 | } 454 | 455 | /* Edges-only specialization of the cube that can take advantage of faster composition */ 456 | class edgecube : public cube_base { 457 | friend class cube_base; 458 | public: 459 | constexpr edgecube() : v(sse::identity) { 460 | } 461 | 462 | constexpr edgecube(const cube &c) : v(c.ev()) { 463 | } 464 | 465 | /* Convert to __m128i */ 466 | operator const __m128i & () const { 467 | return v; 468 | } 469 | 470 | /* Compose */ 471 | edgecube compose(const edgecube &o, bool = false) const { 472 | return sse::edge_compose(v, o.v); 473 | } 474 | 475 | /* Move */ 476 | edgecube move(int m) const { 477 | auto ec_m = reinterpret_cast(&moves[m]); 478 | return compose(*ec_m); 479 | } 480 | 481 | /* Premove */ 482 | edgecube premove(int m) const { 483 | auto ec_m = reinterpret_cast(&moves[m]); 484 | return ec_m->compose(*this); 485 | } 486 | 487 | /* Conjugate with a symmetry */ 488 | edgecube symConjugate(int s) const { 489 | auto ec_symi = reinterpret_cast(&sym[sym_inv[s]]); 490 | auto ec_sym = reinterpret_cast(&sym[s]); 491 | return ec_symi->compose(*this).compose(*ec_sym); 492 | } 493 | 494 | private: 495 | __m128i v; 496 | 497 | constexpr edgecube(__m128i v) : v(v) { 498 | } 499 | 500 | /* cube_base */ 501 | 502 | uint32_t edge_bitmask(int bit) const { 503 | return sse::bitmask(v, bit); 504 | } 505 | 506 | void xor_edge_orient(eorient_t eorient) { 507 | v = sse::xor_edge_orient(v, eorient); 508 | } 509 | }; 510 | 511 | } 512 | 513 | #endif 514 | -------------------------------------------------------------------------------- /tests/CubeTest.cpp: -------------------------------------------------------------------------------- 1 | /* This file is part of vcube. 2 | * 3 | * Copyright (C) 2018 Andrew Skalski 4 | * 5 | * vcube is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * vcube is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with vcube. If not, see . 17 | */ 18 | 19 | #include "cube.h" 20 | 21 | #include 22 | #include "test_util.h" 23 | #include "CppUTest/TestHarness.h" 24 | 25 | using namespace vcube; 26 | 27 | TEST_GROUP(Cube) { 28 | static void check_edge_orient(const cube &c) { 29 | int eorient_sum = 0; 30 | for (auto eo = c.getEdgeOrientRaw(); eo; eo >>= 1) { 31 | eorient_sum += eo & 1; 32 | } 33 | // The sum of all edge orientations must be divisible by 2 34 | LONGS_EQUAL(0, eorient_sum % 2); 35 | } 36 | 37 | static void check_edge_permutation(const cube &c) { 38 | uint32_t edge_mask = 0; 39 | for (int i = 0; i < 12; i++) { 40 | int edge_idx = t::to_array(c)[i] & 0xf; 41 | edge_mask |= 1 << edge_idx; 42 | } 43 | // Each edge cubie should occur exactly once 44 | LONGS_EQUAL(0xfff, edge_mask); 45 | } 46 | 47 | static void check_corner_orient(const cube &c) { 48 | int corient_sum = 0; 49 | for (auto co = c.getCornerOrientRaw(); co; co >>= 2) { 50 | int corient = co & 3; 51 | // Corners may only be oriented 0, 1, or 2 52 | CHECK(corient < 3); 53 | corient_sum += corient; 54 | } 55 | // The sum of all corner orientations must be divisible by 3 56 | LONGS_EQUAL(0, corient_sum % 3); 57 | } 58 | 59 | static void check_corner_permutation(const cube &c) { 60 | uint32_t corner_mask = 0; 61 | for (int i = 16; i < 24; i++) { 62 | int corner_idx = t::to_array(c)[i] & 0x7; 63 | corner_mask |= 1 << corner_idx; 64 | } 65 | // Each corner cubie should occur exactly once 66 | LONGS_EQUAL(0xff, corner_mask); 67 | } 68 | 69 | static void check_invariants(const cube &c) { 70 | uint8_t edge_bits = 0; 71 | for (int i = 0; i < 12; i++) { 72 | edge_bits |= t::to_array(c)[i]; 73 | } 74 | CHECK((edge_bits & 0xe0) == 0); 75 | 76 | uint8_t corner_bits = 0; 77 | for (int i = 16; i < 24; i++) { 78 | corner_bits |= t::to_array(c)[i]; 79 | } 80 | CHECK((corner_bits & 0xc8) == 0); 81 | 82 | // Check unused edge positions (12..15) 83 | int edge_padding_ok = 0; 84 | for (int i = 12; i < 16; i++) { 85 | edge_padding_ok += (t::to_array(c)[i] == i % 16); 86 | } 87 | CHECK(edge_padding_ok == 4); 88 | 89 | // Check unused corner positions (24..31) 90 | int corner_padding_ok = 0; 91 | for (int i = 24; i < 32; i++) { 92 | corner_padding_ok += (t::to_array(c)[i] == i % 16); 93 | } 94 | CHECK(corner_padding_ok == 8); 95 | 96 | check_corner_orient(c); 97 | check_corner_permutation(c); 98 | check_edge_orient(c); 99 | check_edge_permutation(c); 100 | } 101 | }; 102 | 103 | TEST(Cube, DefaultConstructor) { 104 | LONGS_EQUAL(sizeof(cube), 32); 105 | 106 | cube c; 107 | for (int i = 0; i < 32; i++) { 108 | LONGS_EQUAL(i % 16, t::to_array(c)[i]); 109 | } 110 | 111 | check_invariants(c); 112 | } 113 | 114 | TEST(Cube, EdgeOrient) { 115 | cube c; 116 | 117 | LONGS_EQUAL(0, c.getEdgeOrient()); 118 | 119 | for (int i = 0; i < N_EORIENT; i++) { 120 | c = t::random_cube(); 121 | c.setEdgeOrient(i); 122 | LONGS_EQUAL(i, c.getEdgeOrient()); 123 | check_invariants(c); 124 | } 125 | 126 | /* setEdgeOrient should only affect edge orient */ 127 | for (int i = 0; i < 1000; i++) { 128 | cube old = c = t::random_cube(); 129 | c.setEdgeOrient(c.getEdgeOrient()); 130 | CHECK(c == old); 131 | } 132 | } 133 | 134 | TEST(Cube, Edge4Orient) { 135 | cube c; 136 | 137 | LONGS_EQUAL(0, c.getEdge4Orient()); 138 | 139 | for (int i = 0; i < N_E4ORIENT; i++) { 140 | c = t::random_cube(); 141 | c.setEdge4Orient(i); 142 | LONGS_EQUAL(i, c.getEdge4Orient()); 143 | check_invariants(c); 144 | } 145 | 146 | /* Flipping the other eight edges should not affect the coordinate */ 147 | for (int i = 0; i < 1000; i++) { 148 | c = t::random_cube(); 149 | 150 | /* Flip random e8 edges */ 151 | eorient_t e8flip = t::rand(256); 152 | e8flip ^= _popcnt32(e8flip) & 1; 153 | cube c_e8flip; 154 | c_e8flip.setEdgeOrient(e8flip); 155 | 156 | CHECK(c.getEdge4Orient() == (c_e8flip * c).getEdge4Orient()); 157 | } 158 | 159 | /* setEdge4Orient should only affect edge orient */ 160 | for (int i = 0; i < 1000; i++) { 161 | cube old = c = t::random_cube(); 162 | c.setEdge4Orient(c.getEdge4Orient()); 163 | CHECK(c.getEdge4Orient() == old.getEdge4Orient()); 164 | CHECK(c.getEdgePerm() == old.getEdgePerm()); 165 | CHECK(c.getCornerPerm() == old.getCornerPerm()); 166 | CHECK(c.getCornerOrient() == old.getCornerOrient()); 167 | } 168 | } 169 | 170 | TEST(Cube, Edge8Orient) { 171 | cube c; 172 | 173 | LONGS_EQUAL(0, c.getEdge8Orient()); 174 | 175 | for (int i = 0; i < N_E8ORIENT; i++) { 176 | c = t::random_cube(); 177 | c.setEdge8Orient(i); 178 | LONGS_EQUAL(i, c.getEdge8Orient()); 179 | check_invariants(c); 180 | } 181 | 182 | /* Flipping the other four edges should not affect the coordinate */ 183 | for (int i = 0; i < 1000; i++) { 184 | c = t::random_cube(); 185 | 186 | /* Flip random e4 edges */ 187 | cube c_e4flip; 188 | c_e4flip.setEdgeOrient(t::rand(8) << 8); 189 | 190 | CHECK(c.getEdge8Orient() == (c_e4flip * c).getEdge8Orient()); 191 | } 192 | 193 | /* setEdge8Orient should only affect edge orient */ 194 | for (int i = 0; i < 1000; i++) { 195 | cube old = c = t::random_cube(); 196 | c.setEdge8Orient(c.getEdge8Orient()); 197 | CHECK(c.getEdge8Orient() == old.getEdge8Orient()); 198 | CHECK(c.getEdgePerm() == old.getEdgePerm()); 199 | CHECK(c.getCornerPerm() == old.getCornerPerm()); 200 | CHECK(c.getCornerOrient() == old.getCornerOrient()); 201 | } 202 | } 203 | 204 | TEST(Cube, CornerOrient) { 205 | cube c; 206 | 207 | LONGS_EQUAL(0, c.getCornerOrient()); 208 | 209 | for (int i = 0; i < N_CORIENT; i++) { 210 | c = t::random_cube(); 211 | c.setCornerOrient(i); 212 | LONGS_EQUAL(i, c.getCornerOrient()); 213 | check_invariants(c); 214 | } 215 | 216 | /* setCornerOrient should only affect corner orient */ 217 | for (int i = 0; i < 1000; i++) { 218 | cube old = c = t::random_cube(); 219 | c.setCornerOrient(c.getCornerOrient()); 220 | CHECK(c == old); 221 | } 222 | 223 | /* corner orientations are lexically ordered */ 224 | int prev = -1; 225 | for (int i = 0; i < N_CORIENT; i++) { 226 | c.setCornerOrient(i); 227 | uint16_t cur = c.getCornerOrientRaw(); 228 | CHECK(prev < cur); 229 | prev = cur; 230 | } 231 | } 232 | 233 | TEST(Cube, EdgePerm) { 234 | cube c; 235 | 236 | LONGS_EQUAL(0, c.getEdgePerm()); 237 | 238 | c.setEdgePerm(N_EPERM - 1); 239 | LONGS_EQUAL(N_EPERM - 1, c.getEdgePerm()); 240 | 241 | for (int i = 0; i < 1000; i++) { 242 | eperm_t eperm = t::rand(N_EPERM); 243 | c.setEdgeOrient(t::rand(N_EORIENT)); 244 | c.setEdgePerm(eperm); 245 | LONGS_EQUAL(eperm, c.getEdgePerm()); 246 | LONGS_EQUAL(0, c.getEdgeOrient()); 247 | check_invariants(c); 248 | } 249 | 250 | /* setEdgePerm should only affect edge perm+orient */ 251 | for (int i = 0; i < 1000; i++) { 252 | cube old = c = t::random_cube(); 253 | eorient_t eorient = c.getEdgeOrient(); 254 | c.setEdgePerm(c.getEdgePerm()); 255 | c.setEdgeOrient(eorient); 256 | CHECK(c == old); 257 | } 258 | } 259 | 260 | TEST(Cube, CornerPerm) { 261 | cube c; 262 | 263 | LONGS_EQUAL(0, c.getCornerPerm()); 264 | 265 | c.setCornerPerm(N_CPERM - 1); 266 | LONGS_EQUAL(N_CPERM - 1, c.getCornerPerm()); 267 | 268 | for (int i = 0; i < 1000; i++) { 269 | cperm_t cperm = t::rand(N_CPERM); 270 | c.setCornerOrient(t::rand(N_CORIENT)); 271 | c.setCornerPerm(cperm); 272 | LONGS_EQUAL(cperm, c.getCornerPerm()); 273 | LONGS_EQUAL(0, c.getCornerOrient()); 274 | check_invariants(c); 275 | } 276 | 277 | /* setCornerPerm should only affect corner perm+orient */ 278 | for (int i = 0; i < 1000; i++) { 279 | cube old = c = t::random_cube(); 280 | corient_t corient = c.getCornerOrient(); 281 | c.setCornerPerm(c.getCornerPerm()); 282 | c.setCornerOrient(corient); 283 | CHECK(c == old); 284 | } 285 | } 286 | 287 | TEST(Cube, Parity) { 288 | cube c; 289 | CHECK_EQUAL(c.parity(), false); 290 | 291 | bool expected_parity = false; 292 | for (int i = 0; i < 1000; i++) { 293 | int x = t::rand(12), y = t::rand(12); 294 | std::swap(t::to_array(c)[x], t::to_array(c)[y]); 295 | expected_parity ^= (x != y); 296 | CHECK_EQUAL(c.parity(), expected_parity); 297 | 298 | x = t::rand(8), y = t::rand(8); 299 | std::swap(t::to_array(c)[16 + x], t::to_array(c)[16 + y]); 300 | expected_parity ^= (x != y); 301 | CHECK_EQUAL(c.parity(), expected_parity); 302 | } 303 | 304 | // Orientation should not affect parity 305 | for (int i = 0; i < 1000; i++) { 306 | c = t::random_cube(); 307 | bool parity = c.parity(); 308 | 309 | c.setCornerOrient(t::rand(N_CORIENT)); 310 | CHECK_EQUAL(parity, c.parity()); 311 | 312 | c.setEdgeOrient(t::rand(N_EORIENT)); 313 | CHECK_EQUAL(parity, c.parity()); 314 | } 315 | } 316 | 317 | TEST(Cube, ParitySwap) { 318 | cube c; 319 | for (int i = 0; i < 1000; i++) { 320 | // Edge parity can be fixed by "eperm ^= 1" 321 | eperm_t eperm = t::rand(N_EPERM); 322 | c.setEdgePerm(eperm); 323 | std::swap(t::to_array(c)[10], t::to_array(c)[11]); 324 | CHECK_EQUAL(eperm ^ 1, c.getEdgePerm()); 325 | 326 | // Corner parity can be fixed by "cperm ^= 1" 327 | cperm_t cperm = t::rand(N_CPERM); 328 | c.setCornerPerm(cperm); 329 | std::swap(t::to_array(c)[22], t::to_array(c)[23]); 330 | CHECK_EQUAL(cperm ^ 1, c.getCornerPerm()); 331 | } 332 | } 333 | 334 | TEST(Cube, CubeEquality) { 335 | cube c, d; 336 | 337 | CHECK(c == c); CHECK_FALSE(c != c); 338 | CHECK(c == d); CHECK_FALSE(c != d); 339 | 340 | d = cube(); d.setEdgeOrient(1); 341 | CHECK(c != d); CHECK_FALSE(c == d); 342 | 343 | d = cube(); d.setEdgePerm(1); 344 | CHECK(c != d); CHECK_FALSE(c == d); 345 | 346 | d = cube(); d.setCornerOrient(1); 347 | CHECK(c != d); CHECK_FALSE(c == d); 348 | 349 | d = cube(); d.setCornerPerm(1); 350 | CHECK(c != d); CHECK_FALSE(c == d); 351 | } 352 | 353 | TEST(Cube, CubeLessThan) { 354 | cube c, d = t::random_cube(); 355 | 356 | for (int i = 0; i < 1000; i++) { 357 | c = d; 358 | 359 | CHECK_FALSE_TEXT(c < c, "Cube compared unequal to itself"); 360 | CHECK_FALSE_TEXT(c < d, "c and d are the same cube"); 361 | CHECK_FALSE_TEXT(d < c, "c and d are the same cube"); 362 | 363 | d = t::random_cube(); 364 | 365 | CHECK_TEXT(c != d, "Rolled the same cube twice in a row?"); 366 | CHECK_TEXT((c < d) ^ (d < c), "Exactly one of (c < d) or (d < c) should be true"); 367 | } 368 | 369 | // Identity cube assumptions 370 | c = cube(), d = cube(); 371 | CHECK_FALSE_TEXT(c < d, "Identity cube was less than itself"); 372 | CHECK_TEXT(t::to_array(c)[0] == 0, "Identity cube did not have 0 in position 0"); 373 | CHECK_TEXT(t::to_array(c)[15] == 15, "Identity cube did not have 15 in position 15"); 374 | CHECK_TEXT(t::to_array(c)[16] == 0, "Identity cube did not have 0 in position 16"); 375 | CHECK_TEXT(t::to_array(c)[31] == 15, "Identity cube did not have 15 in position 31"); 376 | 377 | // Least-significant position 378 | c = cube(), d = cube(); 379 | t::to_array(d)[0] = 1; 380 | CHECK_TEXT( c < d, "Position 0: 0 was not less than 1"); 381 | CHECK_FALSE_TEXT(d < c, "Position 0: 1 was less than 0"); 382 | 383 | // Middle position 384 | c = cube(), d = cube(); 385 | t::to_array(d)[16] = 1; 386 | CHECK_TEXT( c < d, "Position 16: 0 was not less than 1"); 387 | CHECK_FALSE_TEXT(d < c, "Position 16: 1 was less than 0"); 388 | 389 | // Higher position takes higher precedence 390 | c = cube(), d = cube(); 391 | t::to_array(c)[0] = 1; // c is greater in position 0 392 | t::to_array(d)[16] = 1; // d is greater in position 16 393 | CHECK_TEXT( c < d, "Position 16: 0 was not less than 1 (precedence)"); 394 | CHECK_FALSE_TEXT(d < c, "Position 16: 1 was less than 0 (precedence)"); 395 | 396 | // Most-significant position 397 | c = cube(), d = cube(); 398 | t::to_array(d)[31] = 16; 399 | CHECK_TEXT( c < d, "Position 31: 15 was not less than 16"); 400 | CHECK_FALSE_TEXT(d < c, "Position 31: 16 was less than 15"); 401 | } 402 | 403 | TEST(Cube, Invert) { 404 | CHECK(cube() == ~cube()); 405 | 406 | for (int i = 0; i < 1000; i++) { 407 | cube c = t::random_cube(); 408 | cube ci = ~c; 409 | CHECK(c * ci == cube()); 410 | check_invariants(ci); 411 | } 412 | } 413 | 414 | TEST(Cube, Symmetry) { 415 | // 48 unique symmetries 416 | std::set s; 417 | for (int i = 0; i < 48; i++) { 418 | s.insert(vcube::sym[i]); 419 | } 420 | CHECK_TEXT(s.size() == 48, "Symmetries are not all unique"); 421 | 422 | // Inverse table is correct 423 | for (int i = 0; i < 48; i++) { 424 | CHECK(vcube::sym[i].compose(vcube::sym[sym_inv[i]], i & 1) == cube()); 425 | } 426 | 427 | // sym[0] is the identity 428 | CHECK(vcube::sym[0] == cube()); 429 | 430 | // S_LR2 is the least significant bit 431 | for (int i = 0; i < 48; i++) { 432 | CHECK(vcube::sym[i] * vcube::S_LR2 == vcube::sym[i ^ 1]); 433 | } 434 | 435 | // S_URF3 is the most significant 436 | for (int i = 0; i < 48; i++) { 437 | CHECK(vcube::S_URF3 * vcube::sym[i] == vcube::sym[(i + 16) % 48]); 438 | } 439 | 440 | // The symmetry group is closed 441 | for (int i = 0; i < 48; i++) { 442 | for (int j = 0; j < 48; j++) { 443 | CHECK(s.find(vcube::sym[i].compose(vcube::sym[j], i & 1)) != s.end()); 444 | } 445 | } 446 | 447 | // Mirroring works as expected 448 | CHECK(vcube::moves[ 0] == vcube::S_LR2 % vcube::moves[ 2] % vcube::S_LR2); 449 | CHECK(vcube::moves[ 3] == vcube::S_LR2 % vcube::moves[14] % vcube::S_LR2); 450 | CHECK(vcube::moves[ 6] == vcube::S_LR2 % vcube::moves[ 8] % vcube::S_LR2); 451 | CHECK(vcube::moves[ 9] == vcube::S_LR2 % vcube::moves[11] % vcube::S_LR2); 452 | CHECK(vcube::moves[12] == vcube::S_LR2 % vcube::moves[ 5] % vcube::S_LR2); 453 | CHECK(vcube::moves[15] == vcube::S_LR2 % vcube::moves[17] % vcube::S_LR2); 454 | } 455 | 456 | TEST(Cube, SymConjugate) { 457 | for (int s = 0; s < 48; s++) { 458 | cube c = t::random_cube(); 459 | CHECK(c.symConjugate(s) == vcube::sym[sym_inv[s]] * c * vcube::sym[s]); 460 | s++; 461 | CHECK(c.symConjugate(s) == vcube::sym[sym_inv[s]] % c % vcube::sym[s]); 462 | } 463 | } 464 | 465 | TEST(Cube, Move) { 466 | // 18 unique moves 467 | std::set s; 468 | for (int i = 0; i < 18; i++) { 469 | s.insert(vcube::moves[i]); 470 | } 471 | CHECK_TEXT(s.size() == 18, "Moves are not all unique"); 472 | 473 | for (int i = 0; i < 18; i += 3) { 474 | CHECK(vcube::moves[i ] * vcube::moves[i] == vcube::moves[i + 1]); 475 | CHECK(vcube::moves[i + 1] * vcube::moves[i] == vcube::moves[i + 2]); 476 | } 477 | } 478 | 479 | TEST(Cube, Compose) { 480 | for (int i = 0; i < 1000; i++) { 481 | cube c = t::random_cube(); 482 | cube d = t::random_cube(); 483 | check_invariants(c * d); 484 | check_invariants(c % d); 485 | } 486 | } 487 | 488 | TEST(Cube, MoveParse) { 489 | CHECK(cube::from_moves("") == cube()); 490 | 491 | cube superflip = cube().setEdgeOrient(N_EORIENT - 1); 492 | 493 | CHECK(cube::from_moves("U R2 F B R B2 R U2 L B2 R U' D' R2 F R' L B2 U2 F2\n") == superflip); 494 | CHECK(cube::from_moves("U1R2F1B1R1B2R1U2L1B2R1U3D3R2F1R3L1B2U2F2") == superflip); 495 | CHECK(cube::from_moves("URRFBRBBRUULBBRUUUDDDRRFRRRLBBUUFF") == superflip); 496 | } 497 | 498 | TEST(Cube, NumericMoves) { 499 | CHECK(cube::from_moveseq({}) == cube()); 500 | CHECK(cube::from_moveseq({0,3,6,9,12,15,1,4,7,10,13,16,2,5,8,11,14,17}) == cube::from_moves("URFDLBU2R2F2D2L2B2U'R'F'D'L'B'")); 501 | } 502 | 503 | TEST(Cube, ReidNotationIdentity) { 504 | const std::string IDENTITY_LOWER = "uf ur ub ul df dr db dl fr fl br bl ufr urb ubl ulf drf dfl dlb dbr"; 505 | const std::string IDENTITY_UPPER = "UF UR UB UL DF DR DB DL FR FL BR BL UFR URB UBL ULF DRF DFL DLB DBR"; 506 | 507 | CHECK(cube::from_reid(IDENTITY_LOWER) == cube()); 508 | CHECK(cube().to_reid() == IDENTITY_UPPER); 509 | } 510 | 511 | TEST(Cube, ReidNotationCubeWithinCube) { 512 | const std::string REID = "UF UR FL FD BR BU DB DL FR RD LU BL UFR FUL FLD FDR BUR BRD DLB BLU"; 513 | 514 | cube c = cube::from_moves("F L F U' R U F2 L2 U' L' B D' B' L2 U"); 515 | CHECK(c.to_reid() == REID); 516 | CHECK(cube::from_reid(REID + "\n") == c); 517 | } 518 | 519 | TEST(Cube, ReidRandom) { 520 | for (int i = 0; i < 5; i++) { 521 | cube c = t::random_cube(); 522 | CHECK(cube::from_reid(c.to_reid()) == c); 523 | } 524 | } 525 | 526 | TEST(Cube, SpeffzParse) { 527 | CHECK(cube::from_speffz("") == cube()); 528 | 529 | // Cube within a cube 530 | cube c = cube::from_moves("F L F U' R U F2 L2 U' L' B D' B' L2 U"); 531 | cube d = cube::from_speffz("lopbip.loteut\n"); 532 | CHECK(c == d); 533 | 534 | // Superflip 535 | c = cube::from_moves("U R2 F B R B2 R U2 L B2 R U' D' R2 F R' L B2 U2 F2"); 536 | d = cube::from_speffz(".qbmcidejpntrhflkuovgxsw"); 537 | CHECK(c == d); 538 | d = cube::from_speffz(".BCDKOSGJTFH"); 539 | CHECK(c == d); 540 | 541 | // Supertwist 542 | c = cube::from_moves("B2 L R2 B2 F2 D2 U2 R' F2 D U2 B2 F2 L2 R2 U'"); 543 | d = cube::from_speffz("bqcjdikplgtosh."); 544 | CHECK(c == d); 545 | d = cube::from_speffz("MNFPOGH."); 546 | CHECK(c == d); 547 | 548 | // Alternate edge buffer 549 | CHECK(cube::from_moves("F") == cube::from_speffz("pcfup.pcf", 'A', 'U')); 550 | 551 | // Alternate edge buffer, flipped 552 | CHECK(cube::from_moves("F") == cube::from_speffz("pcfup.jil", 'A', 'K')); 553 | 554 | // Alternate edge buffer, in-place flip 555 | CHECK(cube::from_speffz(".BU") == cube::from_speffz(".B", 'A', 'U')); 556 | 557 | // Alternate corner buffer 558 | CHECK(cube::from_moves("F") == cube::from_speffz("mdg.jilkj", 'V', 'A')); 559 | 560 | // Alternate corner buffer, twisted 561 | CHECK(cube::from_moves("F") == cube::from_speffz("cfu.jilkj", 'P', 'A')); 562 | 563 | // Alternate edge buffer, in-place twist 564 | CHECK(cube::from_speffz("MP.") == cube::from_speffz("M", 'V', 'A')); 565 | } 566 | 567 | TEST(Cube, Superflip) { 568 | cube c = cube::from_moves("U R2 F B R B2 R U2 L B2 R U' D' R2 F R' L B2 U2 F2"); 569 | LONGS_EQUAL_TEXT(0, c.getEdgePerm(), "bad edge perm"); 570 | LONGS_EQUAL_TEXT(0, c.getCornerPerm(), "bad corner perm"); 571 | LONGS_EQUAL_TEXT(0x7ff, c.getEdgeOrient(), "bad edge orient"); 572 | LONGS_EQUAL_TEXT(0, c.getCornerOrient(), "bad corner orient"); 573 | CHECK(c == ~c); 574 | } 575 | 576 | TEST(Cube, Supertwist) { 577 | cube c = cube::from_moves("B2 L R2 B2 F2 D2 U2 R' F2 D U2 B2 F2 L2 R2 U'"); 578 | LONGS_EQUAL_TEXT(0, c.getEdgePerm(), "bad edge perm"); 579 | LONGS_EQUAL_TEXT(0, c.getCornerPerm(), "bad corner perm"); 580 | LONGS_EQUAL_TEXT(0, c.getEdgeOrient(), "bad edge orient"); 581 | LONGS_EQUAL_TEXT(0x6699, c.getCornerOrientRaw(), "bad corner orient"); 582 | CHECK(c * c == ~c); 583 | } 584 | 585 | TEST(Cube, Corner4Comb) { 586 | cube c; 587 | 588 | LONGS_EQUAL(0, c.getCorner4Comb()); 589 | 590 | for (int i = 0; i < N_C4COMB; i++) { 591 | c = t::random_cube(); 592 | c.setCorner4Comb(i); 593 | LONGS_EQUAL(i, c.getCorner4Comb()); 594 | LONGS_EQUAL(0, c.getCornerOrient()); 595 | check_invariants(c); 596 | } 597 | 598 | /* Cycling the U-face or D-face corners should not affect the coordinate */ 599 | for (int i = 0; i < 1000; i++) { 600 | c = t::random_cube(); 601 | CHECK(c.getCorner4Comb() == c.premove(0).getCorner4Comb()); 602 | CHECK(c.getCorner4Comb() == c.premove(9).getCorner4Comb()); 603 | } 604 | 605 | /* setCorner4Comb should only affect corner perm+orient */ 606 | for (int i = 0; i < 1000; i++) { 607 | c = t::random_cube(); 608 | eperm_t eperm = c.getEdgePerm(); 609 | eorient_t eorient = c.getEdgeOrient(); 610 | c.setCorner4Comb(c.getCorner4Comb()); 611 | LONGS_EQUAL(eperm, c.getEdgePerm()); 612 | LONGS_EQUAL(eorient, c.getEdgeOrient()); 613 | } 614 | } 615 | 616 | TEST(Cube, Edge4Comb) { 617 | cube c; 618 | 619 | LONGS_EQUAL(0, c.getEdge4Comb()); 620 | 621 | for (int i = 0; i < N_E4COMB; i++) { 622 | c = t::random_cube(); 623 | c.setEdge4Comb(i); 624 | LONGS_EQUAL(i, c.getEdge4Comb()); 625 | LONGS_EQUAL(0, c.getEdgeOrient()); 626 | /* Should produce an identity EdgeUD4Comb */ 627 | LONGS_EQUAL(0, c.getEdgeUD4Comb()); 628 | check_invariants(c); 629 | } 630 | 631 | /* Cycling the U-face or D-face edges should not affect the coordinate */ 632 | for (int i = 0; i < 1000; i++) { 633 | c = t::random_cube(); 634 | CHECK(c.getEdge4Comb() == c.premove(0).getEdge4Comb()); // U 635 | CHECK(c.getEdge4Comb() == c.premove(9).getEdge4Comb()); // D 636 | } 637 | 638 | /* Swapping the equatorial slice edges should not affect the coordinate */ 639 | for (int i = 0; i < 1000; i++) { 640 | c = t::random_cube(); 641 | CHECK(c.getEdge4Comb() == c.premove( 4).getEdge4Comb()); // R2 642 | CHECK(c.getEdge4Comb() == c.premove( 7).getEdge4Comb()); // F2 643 | CHECK(c.getEdge4Comb() == c.premove(13).getEdge4Comb()); // L2 644 | CHECK(c.getEdge4Comb() == c.premove(16).getEdge4Comb()); // B2 645 | } 646 | 647 | /* setEdge4Comb should only affect edge perm+orient */ 648 | for (int i = 0; i < 1000; i++) { 649 | c = t::random_cube(); 650 | cperm_t cperm = c.getCornerPerm(); 651 | corient_t corient = c.getCornerOrient(); 652 | c.setEdge4Comb(c.getEdge4Comb()); 653 | LONGS_EQUAL(cperm, c.getCornerPerm()); 654 | LONGS_EQUAL(corient, c.getCornerOrient()); 655 | } 656 | } 657 | 658 | TEST(Cube, Edge4Perm) { 659 | cube c; 660 | 661 | LONGS_EQUAL(0, c.getEdge4Perm()); 662 | 663 | for (int i = 0; i < N_E4PERM; i++) { 664 | c = t::random_cube(); 665 | c.setEdge4Perm(i); 666 | LONGS_EQUAL(i, c.getEdge4Perm()); 667 | LONGS_EQUAL(0, c.getEdge4Comb()); 668 | LONGS_EQUAL(0, c.getEdgeOrient()); 669 | check_invariants(c); 670 | } 671 | 672 | for (int i = 0; i < 1000; i++) { 673 | auto e4comb = t::rand(N_E4COMB); 674 | for (int i = 0; i < N_E4PERM; i++) { 675 | cube d = cube().setEdge4Perm(i) * cube().setEdge4Comb(e4comb); 676 | LONGS_EQUAL(i, d.getEdge4Perm()); 677 | } 678 | } 679 | } 680 | 681 | TEST(Cube, EdgeUD4Comb) { 682 | cube c; 683 | 684 | LONGS_EQUAL(0, c.getEdgeUD4Comb()); 685 | 686 | for (int i = 0; i < N_EUD4COMB; i++) { 687 | c = t::random_cube(); 688 | c.setEdgeUD4Comb(i); 689 | LONGS_EQUAL(i, c.getEdgeUD4Comb()); 690 | LONGS_EQUAL(0, c.getEdgeOrient()); 691 | check_invariants(c); 692 | } 693 | 694 | /* Cycling the U-face or D-face edges should not affect the coordinate */ 695 | for (int i = 0; i < 1000; i++) { 696 | c = t::random_cube(); 697 | CHECK(c.getEdgeUD4Comb() == c.premove(0).getEdgeUD4Comb()); // U 698 | CHECK(c.getEdgeUD4Comb() == c.premove(9).getEdgeUD4Comb()); // D 699 | } 700 | 701 | /* Interleaving E-slice and U/D face edges should not affect the coordinate */ 702 | for (int i = 0; i < N_EUD4COMB; i++) { 703 | cube c_e; 704 | c_e.setEdge4Comb(t::rand(N_E4COMB)); 705 | c.setEdgeUD4Comb(i); 706 | LONGS_EQUAL(i, (c * c_e).getEdgeUD4Comb()); 707 | } 708 | } 709 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | --------------------------------------------------------------------------------