From 1ded1f2edd224157427a882e11a8d051fd487d8a Mon Sep 17 00:00:00 2001 From: Corentin Le Molgat Date: Wed, 28 Aug 2024 13:29:25 +0200 Subject: [PATCH] sat: Add symmetry_util_test.cc --- cmake/cpp.cmake | 138 ++++++++++++++++-------------- examples/tests/CMakeLists.txt | 2 +- ortools/sat/BUILD.bazel | 11 +++ ortools/sat/CMakeLists.txt | 14 +++ ortools/sat/symmetry_util_test.cc | 134 +++++++++++++++++++++++++++++ 5 files changed, 232 insertions(+), 67 deletions(-) create mode 100644 ortools/sat/symmetry_util_test.cc diff --git a/cmake/cpp.cmake b/cmake/cpp.cmake index c377066d920..4e86448b10b 100644 --- a/cmake/cpp.cmake +++ b/cmake/cpp.cmake @@ -141,6 +141,78 @@ if(MSVC) ) endif() +################ +## C++ Test ## +################ +# ortools_cxx_test() +# CMake function to generate and build C++ test. +# Parameters: +# FILE_NAME: the C++ filename +# COMPONENT_NAME: name of the ortools/ subdir where the test is located +# note: automatically determined if located in ortools// +# e.g.: +# ortools_cxx_test( +# FILE_NAME +# ${PROJECT_SOURCE_DIR}/ortools/foo/foo_test.cc +# COMPONENT_NAME +# foo +# DEPS +# GTest::gmock +# GTest::gtest_main +# ) +function(ortools_cxx_test) + set(options "") + set(oneValueArgs "FILE_NAME;COMPONENT_NAME") + set(multiValueArgs "DEPS") + cmake_parse_arguments(TEST + "${options}" + "${oneValueArgs}" + "${multiValueArgs}" + ${ARGN} + ) +if(NOT TEST_FILE_NAME) + message(FATAL_ERROR "no FILE_NAME provided") + endif() + get_filename_component(TEST_NAME ${TEST_FILE_NAME} NAME_WE) + + message(STATUS "Configuring test ${TEST_FILE_NAME} ...") + + if(NOT TEST_COMPONENT_NAME) + # test is located in ortools// + get_filename_component(COMPONENT_DIR ${TEST_FILE_NAME} DIRECTORY) + get_filename_component(COMPONENT_NAME ${COMPONENT_DIR} NAME) + else() + set(COMPONENT_NAME ${TEST_COMPONENT_NAME}) + endif() + + add_executable(${TEST_NAME} ${TEST_FILE_NAME}) + target_include_directories(${TEST_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_features(${TEST_NAME} PRIVATE cxx_std_17) + target_link_libraries(${TEST_NAME} PRIVATE + ${PROJECT_NAMESPACE}::ortools + ${TEST_DEPS} + ) + + include(GNUInstallDirs) + if(APPLE) + set_target_properties(${TEST_NAME} PROPERTIES INSTALL_RPATH + "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") + elseif(UNIX) + cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR + BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR} + OUTPUT_VARIABLE libdir_relative_path) + set_target_properties(${TEST_NAME} PROPERTIES + INSTALL_RPATH "$ORIGIN/${libdir_relative_path}") + endif() + + if(BUILD_TESTING) + add_test( + NAME cxx_${COMPONENT_NAME}_${TEST_NAME} + COMMAND ${TEST_NAME}) + endif() + message(STATUS "Configuring test ${TEST_FILE_NAME} ...DONE") +endfunction() + ################## ## PROTO FILE ## ################## @@ -556,72 +628,6 @@ install(DIRECTORY ortools/routing/docs/ PATTERN "*.md") endif() -################ -## C++ Test ## -################ -# add_cxx_test() -# CMake function to generate and build C++ test. -# Parameters: -# FILE_NAME: the C++ filename -# COMPONENT_NAME: name of the ortools/ subdir where the test is located -# note: automatically determined if located in ortools// -# e.g.: -# add_cxx_test( -# FILE_NAME -# ${PROJECT_SOURCE_DIR}/ortools/foo/foo_test.cc -# COMPONENT_NAME -# foo -# ) -function(add_cxx_test) - set(options "") - set(oneValueArgs FILE_NAME COMPONENT_NAME) - set(multiValueArgs "") - cmake_parse_arguments(TEST - "${options}" - "${oneValueArgs}" - "${multiValueArgs}" - ${ARGN} - ) -if(NOT TEST_FILE_NAME) - message(FATAL_ERROR "no FILE_NAME provided") - endif() - get_filename_component(TEST_NAME ${TEST_FILE_NAME} NAME_WE) - - message(STATUS "Configuring test ${TEST_FILE_NAME} ...") - - if(NOT TEST_COMPONENT_NAME) - # test is located in ortools// - get_filename_component(COMPONENT_DIR ${TEST_FILE_NAME} DIRECTORY) - get_filename_component(COMPONENT_NAME ${COMPONENT_DIR} NAME) - else() - set(COMPONENT_NAME ${TEST_COMPONENT_NAME}) - endif() - - add_executable(${TEST_NAME} ${TEST_FILE_NAME}) - target_include_directories(${TEST_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) - target_compile_features(${TEST_NAME} PRIVATE cxx_std_17) - target_link_libraries(${TEST_NAME} PRIVATE ${PROJECT_NAMESPACE}::ortools) - - include(GNUInstallDirs) - if(APPLE) - set_target_properties(${TEST_NAME} PROPERTIES INSTALL_RPATH - "@loader_path/../${CMAKE_INSTALL_LIBDIR};@loader_path") - elseif(UNIX) - cmake_path(RELATIVE_PATH CMAKE_INSTALL_FULL_LIBDIR - BASE_DIRECTORY ${CMAKE_INSTALL_FULL_BINDIR} - OUTPUT_VARIABLE libdir_relative_path) - set_target_properties(${TEST_NAME} PROPERTIES - INSTALL_RPATH "$ORIGIN/${libdir_relative_path}") - endif() - - if(BUILD_TESTING) - add_test( - NAME cxx_${COMPONENT_NAME}_${TEST_NAME} - COMMAND ${TEST_NAME}) - endif() - message(STATUS "Configuring test ${TEST_FILE_NAME} ...DONE") -endfunction() - ################## ## C++ Sample ## ################## diff --git a/examples/tests/CMakeLists.txt b/examples/tests/CMakeLists.txt index 622c576fecd..5d49f347e90 100644 --- a/examples/tests/CMakeLists.txt +++ b/examples/tests/CMakeLists.txt @@ -18,7 +18,7 @@ endif() if(BUILD_CXX_EXAMPLES) file(GLOB CXX_SRCS "*.cc") foreach(FILE_NAME IN LISTS CXX_SRCS) - add_cxx_test(FILE_NAME ${FILE_NAME}) + ortools_cxx_test(FILE_NAME ${FILE_NAME}) endforeach() endif() diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index b0f01e2ec80..4dd84128e98 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -1035,6 +1035,17 @@ cc_library( ], ) +cc_test( + name = "symmetry_util_test", + size = "small", + srcs = ["symmetry_util_test.cc"], + deps = [ + ":symmetry_util", + "//ortools/algorithms:sparse_permutation", + "//ortools/base:gmock_main", + ], +) + cc_library( name = "var_domination", srcs = ["var_domination.cc"], diff --git a/ortools/sat/CMakeLists.txt b/ortools/sat/CMakeLists.txt index 027aa46c6f8..99f2113edb4 100644 --- a/ortools/sat/CMakeLists.txt +++ b/ortools/sat/CMakeLists.txt @@ -12,6 +12,7 @@ # limitations under the License. file(GLOB _SRCS "*.h" "*.cc") +list(FILTER _SRCS EXCLUDE REGEX ".*/.*_test.cc") list(REMOVE_ITEM _SRCS ${CMAKE_CURRENT_SOURCE_DIR}/opb_reader.h ${CMAKE_CURRENT_SOURCE_DIR}/sat_cnf_reader.h @@ -39,6 +40,19 @@ target_link_libraries(${NAME} PRIVATE ${PROJECT_NAMESPACE}::ortools_proto) #add_library(${PROJECT_NAMESPACE}::sat ALIAS ${NAME}) +if(BUILD_TESTING) + file(GLOB _TEST_SRCS "*_test.cc") + foreach(FILE_NAME IN LISTS _TEST_SRCS) + ortools_cxx_test( + FILE_NAME + ${FILE_NAME} + DEPS + GTest::gmock + GTest::gtest_main + ) + endforeach() +endif() + # Sat Runner add_executable(sat_runner) target_sources(sat_runner PRIVATE "sat_runner.cc") diff --git a/ortools/sat/symmetry_util_test.cc b/ortools/sat/symmetry_util_test.cc new file mode 100644 index 00000000000..85a7d674811 --- /dev/null +++ b/ortools/sat/symmetry_util_test.cc @@ -0,0 +1,134 @@ +// Copyright 2010-2024 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ortools/sat/symmetry_util.h" + +#include +#include + +#include "gtest/gtest.h" +#include "ortools/algorithms/sparse_permutation.h" +#include "ortools/base/gmock.h" + +namespace operations_research { +namespace sat { +namespace { + +using ::testing::ElementsAre; + +TEST(GetOrbitsTest, BasicExample) { + const int n = 10; + std::vector> generators; + generators.push_back(std::make_unique(n)); + generators[0]->AddToCurrentCycle(0); + generators[0]->AddToCurrentCycle(1); + generators[0]->AddToCurrentCycle(2); + generators[0]->CloseCurrentCycle(); + generators[0]->AddToCurrentCycle(7); + generators[0]->AddToCurrentCycle(8); + generators[0]->CloseCurrentCycle(); + + generators.push_back(std::make_unique(n)); + generators[1]->AddToCurrentCycle(3); + generators[1]->AddToCurrentCycle(2); + generators[1]->AddToCurrentCycle(7); + generators[1]->CloseCurrentCycle(); + const std::vector orbits = GetOrbits(n, generators); + for (const int i : std::vector{0, 1, 2, 3, 7, 8}) { + EXPECT_EQ(orbits[i], 0); + } + for (const int i : std::vector{4, 5, 6, 9}) { + EXPECT_EQ(orbits[i], -1); + } +} + +// Recover for generators (in a particular form) +// [0, 1, 2] +// [4, 5, 3] +// [8, 7, 6] +TEST(BasicOrbitopeExtractionTest, BasicExample) { + const int n = 10; + std::vector> generators; + + generators.push_back(std::make_unique(n)); + generators[0]->AddToCurrentCycle(0); + generators[0]->AddToCurrentCycle(1); + generators[0]->CloseCurrentCycle(); + generators[0]->AddToCurrentCycle(4); + generators[0]->AddToCurrentCycle(5); + generators[0]->CloseCurrentCycle(); + generators[0]->AddToCurrentCycle(8); + generators[0]->AddToCurrentCycle(7); + generators[0]->CloseCurrentCycle(); + + generators.push_back(std::make_unique(n)); + generators[1]->AddToCurrentCycle(2); + generators[1]->AddToCurrentCycle(1); + generators[1]->CloseCurrentCycle(); + generators[1]->AddToCurrentCycle(5); + generators[1]->AddToCurrentCycle(3); + generators[1]->CloseCurrentCycle(); + generators[1]->AddToCurrentCycle(6); + generators[1]->AddToCurrentCycle(7); + generators[1]->CloseCurrentCycle(); + + const std::vector> orbitope = + BasicOrbitopeExtraction(generators); + ASSERT_EQ(orbitope.size(), 3); + EXPECT_THAT(orbitope[0], ElementsAre(0, 1, 2)); + EXPECT_THAT(orbitope[1], ElementsAre(4, 5, 3)); + EXPECT_THAT(orbitope[2], ElementsAre(8, 7, 6)); +} + +// This one is trickier and is not an orbitope because 8 appear twice. So it +// would be incorrect to "grow" the first two columns with the 3rd one. +// [0, 1, 2] +// [4, 5, 8] +// [8, 7, 9] +TEST(BasicOrbitopeExtractionTest, NotAnOrbitopeBecauseOfDuplicates) { + const int n = 10; + std::vector> generators; + + generators.push_back(std::make_unique(n)); + generators[0]->AddToCurrentCycle(0); + generators[0]->AddToCurrentCycle(1); + generators[0]->CloseCurrentCycle(); + generators[0]->AddToCurrentCycle(4); + generators[0]->AddToCurrentCycle(5); + generators[0]->CloseCurrentCycle(); + generators[0]->AddToCurrentCycle(8); + generators[0]->AddToCurrentCycle(7); + generators[0]->CloseCurrentCycle(); + + generators.push_back(std::make_unique(n)); + generators[1]->AddToCurrentCycle(1); + generators[1]->AddToCurrentCycle(2); + generators[1]->CloseCurrentCycle(); + generators[1]->AddToCurrentCycle(5); + generators[1]->AddToCurrentCycle(8); + generators[1]->CloseCurrentCycle(); + generators[1]->AddToCurrentCycle(6); + generators[1]->AddToCurrentCycle(9); + generators[1]->CloseCurrentCycle(); + + const std::vector> orbitope = + BasicOrbitopeExtraction(generators); + ASSERT_EQ(orbitope.size(), 3); + EXPECT_THAT(orbitope[0], ElementsAre(0, 1)); + EXPECT_THAT(orbitope[1], ElementsAre(4, 5)); + EXPECT_THAT(orbitope[2], ElementsAre(8, 7)); +} + +} // namespace +} // namespace sat +} // namespace operations_research