diff --git a/.clang-format b/.clang-format index a81ea37061..571fc7bd4e 100644 --- a/.clang-format +++ b/.clang-format @@ -30,7 +30,7 @@ ColumnLimit: '100' CompactNamespaces: 'false' ConstructorInitializerAllOnOneLineOrOnePerLine: 'false' ConstructorInitializerIndentWidth: '4' -EmptyLineBeforeAccessModifier: 'Always' +#EmptyLineBeforeAccessModifier: 'Always' FixNamespaceComments: 'true' IncludeBlocks: Regroup IndentCaseLabels: 'false' diff --git a/.gitignore b/.gitignore index afd5e86479..85a8ba5cf7 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,4 @@ cmake-build-debug/ /build/ /wheelhouse/ .mypy_cache/ +/tools/cadical/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 6948a6af5f..a91bf3bdb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Implemented forward-backward abstract interpretation, symbolic bound tightening, interval arithmetic and simulations for all activation functions. - Added the BaBSR heuristic as a new branching strategy for ReLU Splitting - Support Sub of two variables, "Mul" of two constants, Slice, and ConstantOfShape in the python onnx parser + - Added optional CDCL solving for networks with ReLU activations, using CaDiCaL as the backend SAT solver. ## Version 2.0.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index e55a57852a..4f97939a81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.16) +cmake_minimum_required(VERSION 3.16) project(Marabou) set(MARABOU_VERSION 2.0.0) @@ -22,6 +22,7 @@ option(RUN_PYTHON_TEST "Run Python API tests if building with Python" OFF) option(ENABLE_GUROBI "Enable use the Gurobi optimizer" OFF) option(ENABLE_OPENBLAS "Do symbolic bound tighting using blas" ON) # Not available on Windows option(CODE_COVERAGE "Add code coverage" OFF) # Available only in debug mode +option(BUILD_CADICAL "Build the CaDiCaL SAT solver for CDCL solving" ON) ################### ## Git variables ## @@ -30,19 +31,19 @@ option(CODE_COVERAGE "Add code coverage" OFF) # Available only in debug mode # Get the name of the working branch execute_process( - COMMAND git rev-parse --abbrev-ref HEAD - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_BRANCH - OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND git rev-parse --abbrev-ref HEAD + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_BRANCH + OUTPUT_STRIP_TRAILING_WHITESPACE ) add_definitions("-DGIT_BRANCH=\"${GIT_BRANCH}\"") # Get the latest abbreviated commit hash of the working branch execute_process( - COMMAND git log -1 --format=%h - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} - OUTPUT_VARIABLE GIT_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE + COMMAND git log -1 --format=%h + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE GIT_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE ) add_definitions("-DGIT_COMMIT_HASH=\"${GIT_COMMIT_HASH}\"") @@ -60,10 +61,10 @@ set(COMMON_DIR "${SRC_DIR}/common") set(BASIS_DIR "${SRC_DIR}/basis_factorization") if (MSVC) - set(SCRIPT_EXTENSION bat) -else() - set(SCRIPT_EXTENSION sh) -endif() + set(SCRIPT_EXTENSION bat) +else () + set(SCRIPT_EXTENSION sh) +endif () ########## ## CVC4 ## @@ -85,21 +86,21 @@ add_definitions(-DBOOST_NO_CXX98_FUNCTION_BASE) set(BOOST_VERSION 1.84.0) set(BOOST_DIR "${TOOLS_DIR}/boost-${BOOST_VERSION}") if (MSVC) - set(BOOST_ROOT "${BOOST_DIR}/win_installed") - set(Boost_NAMESPACE libboost) + set(BOOST_ROOT "${BOOST_DIR}/win_installed") + set(Boost_NAMESPACE libboost) elseif (${CMAKE_SIZEOF_VOID_P} EQUAL 4 AND NOT MSVC) - set(BOOST_ROOT "${BOOST_DIR}/installed32") -else() - set(BOOST_ROOT "${BOOST_DIR}/installed") -endif() + set(BOOST_ROOT "${BOOST_DIR}/installed32") +else () + set(BOOST_ROOT "${BOOST_DIR}/installed") +endif () set(Boost_USE_DEBUG_RUNTIME FALSE) find_package(Boost ${BOOST_VERSION} COMPONENTS program_options timer chrono thread) # Find boost if (NOT ${Boost_FOUND}) - execute_process(COMMAND ${TOOLS_DIR}/download_boost.${SCRIPT_EXTENSION} ${BOOST_VERSION}) - find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS program_options timer chrono thread regex) -endif() + execute_process(COMMAND ${TOOLS_DIR}/download_boost.${SCRIPT_EXTENSION} ${BOOST_VERSION}) + find_package(Boost ${BOOST_VERSION} REQUIRED COMPONENTS program_options timer chrono thread regex) +endif () set(LIBS_INCLUDES ${Boost_INCLUDE_DIRS}) list(APPEND LIBS ${Boost_LIBRARIES}) @@ -112,17 +113,17 @@ set(PROTOBUF_VERSION 3.19.2) set(PROTOBUF_DEFAULT_DIR "${TOOLS_DIR}/protobuf-${PROTOBUF_VERSION}") if (NOT PROTOBUF_DIR) set(PROTOBUF_DIR ${PROTOBUF_DEFAULT_DIR}) -endif() +endif () -if(NOT EXISTS "${PROTOBUF_DIR}/installed/lib/libprotobuf.a") +if (NOT EXISTS "${PROTOBUF_DIR}/installed/lib/libprotobuf.a") message("Can't find protobuf, installing. If protobuf is installed please use the PROTOBUF_DIR parameter to pass the path") if (${PROTOBUF_DIR} STREQUAL ${PROTOBUF_DEFAULT_DIR}) message("installing protobuf") execute_process(COMMAND ${TOOLS_DIR}/download_protobuf.sh ${PROTOBUF_VERSION}) - else() + else () message(FATAL_ERROR "Can't find protobuf in the supplied directory") - endif() -endif() + endif () +endif () set(PROTOBUF_LIB protobuf) add_library(${PROTOBUF_LIB} SHARED IMPORTED) @@ -139,10 +140,10 @@ list(APPEND LIBS ${PROTOBUF_LIB}) set(ONNX_VERSION 1.15.0) set(ONNX_DIR "${TOOLS_DIR}/onnx-${ONNX_VERSION}") -if(NOT EXISTS "${ONNX_DIR}/onnx.proto3.pb.h") +if (NOT EXISTS "${ONNX_DIR}/onnx.proto3.pb.h") message("generating ONNX protobuf file") execute_process(COMMAND ${TOOLS_DIR}/download_onnx.sh ${ONNX_VERSION} ${PROTOBUF_VERSION}) -endif() +endif () file(GLOB DEPS_ONNX "${ONNX_DIR}/*.cc") include_directories(SYSTEM ${ONNX_DIR}) @@ -151,63 +152,87 @@ include_directories(SYSTEM ${ONNX_DIR}) ############ if (${ENABLE_GUROBI}) - message(STATUS "Using Gurobi for LP relaxation for bound tightening") - if (NOT DEFINED GUROBI_DIR) - set(GUROBI_DIR $ENV{GUROBI_HOME}) - endif() - add_compile_definitions(ENABLE_GUROBI) + message(STATUS "Using Gurobi for LP relaxation for bound tightening") + if (NOT DEFINED GUROBI_DIR) + set(GUROBI_DIR $ENV{GUROBI_HOME}) + endif () + add_compile_definitions(ENABLE_GUROBI) - set(GUROBI_LIB1 "gurobi_c++") - set(GUROBI_LIB2 "gurobi110") + set(GUROBI_LIB1 "gurobi_c++") + set(GUROBI_LIB2 "gurobi110") - add_library(${GUROBI_LIB1} SHARED IMPORTED) - set_target_properties(${GUROBI_LIB1} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi_c++.a) - list(APPEND LIBS ${GUROBI_LIB1}) - target_include_directories(${GUROBI_LIB1} INTERFACE ${GUROBI_DIR}/include/) + add_library(${GUROBI_LIB1} SHARED IMPORTED) + set_target_properties(${GUROBI_LIB1} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi_c++.a) + list(APPEND LIBS ${GUROBI_LIB1}) + target_include_directories(${GUROBI_LIB1} INTERFACE ${GUROBI_DIR}/include/) - add_library(${GUROBI_LIB2} SHARED IMPORTED) + add_library(${GUROBI_LIB2} SHARED IMPORTED) - # MACOSx uses .dylib instead of .so for its Gurobi downloads. - if (APPLE) - set_target_properties(${GUROBI_LIB2} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi110.dylib) - else() - set_target_properties(${GUROBI_LIB2} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi110.so) - endif () + # MACOSx uses .dylib instead of .so for its Gurobi downloads. + if (APPLE) + set_target_properties(${GUROBI_LIB2} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi110.dylib) + else () + set_target_properties(${GUROBI_LIB2} PROPERTIES IMPORTED_LOCATION ${GUROBI_DIR}/lib/libgurobi110.so) + endif () - list(APPEND LIBS ${GUROBI_LIB2}) - target_include_directories(${GUROBI_LIB2} INTERFACE ${GUROBI_DIR}/include/) -endif() + list(APPEND LIBS ${GUROBI_LIB2}) + target_include_directories(${GUROBI_LIB2} INTERFACE ${GUROBI_DIR}/include/) +endif () ############## ## OpenBLAS ## ############## if (NOT MSVC AND ${ENABLE_OPENBLAS}) - set(OPENBLAS_VERSION 0.3.19) - - set(OPENBLAS_LIB openblas) - set(OPENBLAS_DEFAULT_DIR "${TOOLS_DIR}/OpenBLAS-${OPENBLAS_VERSION}") - if (NOT OPENBLAS_DIR) - set(OPENBLAS_DIR ${OPENBLAS_DEFAULT_DIR}) - endif() - - message(STATUS "Using OpenBLAS for matrix multiplication") - add_compile_definitions(ENABLE_OPENBLAS) - if(NOT EXISTS "${OPENBLAS_DIR}/installed/lib/libopenblas.a") - message("Can't find OpenBLAS, installing. If OpenBLAS is installed please use the OPENBLAS_DIR parameter to pass the path") - if (${OPENBLAS_DIR} STREQUAL ${OPENBLAS_DEFAULT_DIR}) - message("Installing OpenBLAS") - execute_process(COMMAND ${TOOLS_DIR}/download_openBLAS.sh ${OPENBLAS_VERSION}) - else() - message(FATAL_ERROR "Can't find OpenBLAS in the supplied directory") - endif() - endif() - - add_library(${OPENBLAS_LIB} SHARED IMPORTED) - set_target_properties(${OPENBLAS_LIB} PROPERTIES IMPORTED_LOCATION ${OPENBLAS_DIR}/installed/lib/libopenblas.a) - list(APPEND LIBS ${OPENBLAS_LIB}) - target_include_directories(${OPENBLAS_LIB} INTERFACE ${OPENBLAS_DIR}/installed/include) -endif() + set(OPENBLAS_VERSION 0.3.19) + + set(OPENBLAS_LIB openblas) + set(OPENBLAS_DEFAULT_DIR "${TOOLS_DIR}/OpenBLAS-${OPENBLAS_VERSION}") + if (NOT OPENBLAS_DIR) + set(OPENBLAS_DIR ${OPENBLAS_DEFAULT_DIR}) + endif () + + message(STATUS "Using OpenBLAS for matrix multiplication") + add_compile_definitions(ENABLE_OPENBLAS) + if (NOT EXISTS "${OPENBLAS_DIR}/installed/lib/libopenblas.a") + message("Can't find OpenBLAS, installing. If OpenBLAS is installed please use the OPENBLAS_DIR parameter to pass the path") + if (${OPENBLAS_DIR} STREQUAL ${OPENBLAS_DEFAULT_DIR}) + message("Installing OpenBLAS") + execute_process(COMMAND ${TOOLS_DIR}/download_openBLAS.sh ${OPENBLAS_VERSION}) + else () + message(FATAL_ERROR "Can't find OpenBLAS in the supplied directory") + endif () + endif () + + add_library(${OPENBLAS_LIB} SHARED IMPORTED) + set_target_properties(${OPENBLAS_LIB} PROPERTIES IMPORTED_LOCATION ${OPENBLAS_DIR}/installed/lib/libopenblas.a) + list(APPEND LIBS ${OPENBLAS_LIB}) + target_include_directories(${OPENBLAS_LIB} INTERFACE ${OPENBLAS_DIR}/installed/include) +endif () + +########## +## CaDiCaL ## +########## + +if (BUILD_CADICAL) + message(STATUS "Using CaDiCaL for CDCL solving") + if (NOT CADICAL_DIR) + set(CADICAL_DIR "${TOOLS_DIR}/cadical") + endif () + add_compile_definitions(BUILD_CADICAL) + + if (NOT EXISTS "${CADICAL_DIR}") + message(STATUS "Building CaDiCaL SAT Solver") + execute_process(COMMAND ${TOOLS_DIR}/download_cadical.sh) + endif () + + set(CADICAL_LIB cadical) + add_library(${CADICAL_LIB} SHARED IMPORTED) + set_property(TARGET ${CADICAL_LIB} PROPERTY POSITION_INDEPENDENT_CODE ON) + set_target_properties(${CADICAL_LIB} PROPERTIES IMPORTED_LOCATION ${CADICAL_DIR}/build/libcadical.a) + target_include_directories(${CADICAL_LIB} INTERFACE ${CADICAL_DIR}/src) + list(APPEND LIBS ${CADICAL_LIB}) +endif () ########### ## Build ## @@ -238,30 +263,30 @@ set(INPUT_PARSERS_DIR input_parsers) include(ProcessorCount) ProcessorCount(CTEST_NTHREADS) -if(CTEST_NTHREADS EQUAL 0) - set(CTEST_NTHREADS 1) -endif() +if (CTEST_NTHREADS EQUAL 0) + set(CTEST_NTHREADS 1) +endif () # --------------- set build type ---------------------------- set(BUILD_TYPES Release Debug MinSizeRel RelWithDebInfo) # Set the default build type to Production -if(NOT CMAKE_BUILD_TYPE) - set(CMAKE_BUILD_TYPE - Release CACHE STRING "Options are: Release Debug MinSizeRel RelWithDebInfo" FORCE) - # Provide drop down menu options in cmake-gui - set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${BUILD_TYPES}) -endif() +if (NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE + Release CACHE STRING "Options are: Release Debug MinSizeRel RelWithDebInfo" FORCE) + # Provide drop down menu options in cmake-gui + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS ${BUILD_TYPES}) +endif () message(STATUS "Building ${CMAKE_BUILD_TYPE} build") #-------------------------set code coverage----------------------------------# # Allow coverage only in debug mode only in gcc -if(CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_BUILD_TYPE MATCHES Debug) - message(STATUS "Building with code coverage") - set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage" CACHE INTERNAL "") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}") - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") -endif() +if (CODE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU" AND CMAKE_BUILD_TYPE MATCHES Debug) + message(STATUS "Building with code coverage") + set(COVERAGE_COMPILER_FLAGS "-g -O0 --coverage" CACHE INTERNAL "") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") +endif () # We build a static library that is the core of the project, the link it to the # API's (executable and python at the moment) @@ -273,40 +298,40 @@ set(MARABOU_EXE Marabou${CMAKE_EXECUTABLE_SUFFIX}) add_executable(${MARABOU_EXE} "${ENGINE_DIR}/main.cpp") set(MARABOU_EXE_PATH "${BIN_DIR}/${MARABOU_EXE}") add_custom_command(TARGET ${MARABOU_EXE} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy $ ${MARABOU_EXE_PATH} ) + COMMAND ${CMAKE_COMMAND} -E copy $ ${MARABOU_EXE_PATH}) set(MPS_PARSER_PATH "${BIN_DIR}/${MPS_PARSER}") if (NOT MSVC) if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(COMPILE_FLAGS -Wall -Wextra -Werror -MMD -Qunused-arguments -Wno-deprecated-declarations -Wno-unused-but-set-variable ) + set(COMPILE_FLAGS -Wall -Wextra -Werror -MMD -Qunused-arguments -Wno-deprecated-declarations -Wno-unused-but-set-variable) elseif (CMAKE_BUILD_TYPE MATCHES "Release") - set(COMPILE_FLAGS -Wall ) - else() - set(COMPILE_FLAGS -Wall -Wextra -Werror -MMD ) #-Wno-deprecated - endif() + set(COMPILE_FLAGS -Wall) + else () + set(COMPILE_FLAGS -Wall -Wextra -Werror -MMD) #-Wno-deprecated + endif () set(RELEASE_FLAGS ${COMPILE_FLAGS} -O3) #-Wno-deprecated -endif() +endif () if (RUN_MEMORY_TEST) - if(NOT MSVC) + if (NOT MSVC) set(MEMORY_FLAGS -fsanitize=address -fno-omit-frame-pointer -O1) - endif() -endif() + endif () +endif () add_definitions(-DRESOURCES_DIR="${RESOURCES_DIR}") if (NOT MSVC) set(DEBUG_FLAGS ${COMPILE_FLAGS} ${MEMORY_FLAGS} -g) - set(CXXTEST_FLAGS ${DEBUG_FLAGS} -Wno-ignored-qualifiers) -else() + set(CXXTEST_FLAGS ${DEBUG_FLAGS} -Wno-ignored-qualifiers) +else () set(DEBUG_FLAGS ${COMPILE_FLAGS} ${MEMORY_FLAGS}) add_definitions(-DNOMINMAX) # remove min max macros -endif() +endif () if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") set(CXXTEST_FLAGS ${CXXTEST_FLAGS} -Wno-terminate) -endif() +endif () # pthread set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -314,11 +339,11 @@ find_package(Threads REQUIRED) list(APPEND LIBS Threads::Threads) if (BUILD_STATIC_MARABOU) - # build a static library - target_link_libraries(${MARABOU_LIB} ${LIBS} -static) -else() - target_link_libraries(${MARABOU_LIB} ${LIBS}) -endif() + # build a static library + target_link_libraries(${MARABOU_LIB} ${LIBS} -static) +else () + target_link_libraries(${MARABOU_LIB} ${LIBS}) +endif () target_include_directories(${MARABOU_LIB} PRIVATE ${LIBS_INCLUDES}) target_compile_options(${MARABOU_LIB} PRIVATE ${RELEASE_FLAGS}) @@ -334,44 +359,44 @@ target_include_directories(${MARABOU_EXE} PRIVATE ${LIBS_INCLUDES}) set(DEFAULT_PYTHON_VERSION "3" CACHE STRING "Default Python version 2/3") set(PYTHON_VERSIONS_SUPPORTED 2 3) list(FIND PYTHON_VERSIONS_SUPPORTED ${DEFAULT_PYTHON_VERSION} index) -if(index EQUAL -1) +if (index EQUAL -1) message(FATAL_ERROR "Python version must be one of ${PYTHON_VERSIONS_SUPPORTED}") -endif() +endif () set(PYTHON_API_DIR "${PROJECT_SOURCE_DIR}/maraboupy") if (NOT PYTHON_LIBRARY_OUTPUT_DIRECTORY) set(PYTHON_LIBRARY_OUTPUT_DIRECTORY "${PYTHON_API_DIR}") -endif() +endif () # Determine if we should build Python set(PYTHON32 FALSE) -if(${BUILD_PYTHON}) +if (${BUILD_PYTHON}) execute_process(COMMAND "${PYTHON_EXECUTABLE}" "-c" - "import struct; print(struct.calcsize('@P'));" - RESULT_VARIABLE _PYTHON_SUCCESS - OUTPUT_VARIABLE PYTHON_SIZEOF_VOID_P - ERROR_VARIABLE _PYTHON_ERROR_VALUE) + "import struct; print(struct.calcsize('@P'));" + RESULT_VARIABLE _PYTHON_SUCCESS + OUTPUT_VARIABLE PYTHON_SIZEOF_VOID_P + ERROR_VARIABLE _PYTHON_ERROR_VALUE) # message("PYTHON SIZEOF VOID p ${PYTHON_SIZEOF_VOID_P}") if (PYTHON_SIZEOF_VOID_P EQUAL 4 AND NOT ${FORCE_PYTHON_BUILD}) set(PYTHON32 TRUE) message(WARNING "Python version is 32-bit, please use build_python.sh in maraboupy folder") - endif() -endif() + endif () +endif () if (${FORCE_PYTHON_BUILD}) set(BUILD_PYTHON ON) -else() +else () if (${BUILD_PYTHON} AND NOT ${PYTHON32}) set(BUILD_PYTHON ON) - else() + else () set(BUILD_PYTHON OFF) - endif() -endif() + endif () +endif () # Actually build Python if (${BUILD_PYTHON}) - set(PYBIND11_VERSION 2.10.4) - set(PYBIND11_DIR "${TOOLS_DIR}/pybind11-${PYBIND11_VERSION}") + set(PYBIND11_VERSION 2.10.4) + set(PYBIND11_DIR "${TOOLS_DIR}/pybind11-${PYBIND11_VERSION}") # This is suppose to set the PYTHON_EXECUTABLE variable # First try to find the default python version @@ -379,12 +404,12 @@ if (${BUILD_PYTHON}) if (NOT EXISTS ${PYTHON_EXECUTABLE}) # If the default didn't work just find any python version find_package(PythonInterp REQUIRED) - endif() + endif () if (NOT EXISTS ${PYBIND11_DIR}) message("didnt find pybind, getting it") - execute_process(COMMAND ${TOOLS_DIR}/download_pybind11.${SCRIPT_EXTENSION} ${PYBIND11_VERSION}) - endif() + execute_process(COMMAND ${TOOLS_DIR}/download_pybind11.${SCRIPT_EXTENSION} ${PYBIND11_VERSION}) + endif () add_subdirectory(${PYBIND11_DIR}) set(MARABOU_PY MarabouCore) @@ -394,11 +419,11 @@ if (${BUILD_PYTHON}) target_include_directories(${MARABOU_PY} PRIVATE ${LIBS_INCLUDES}) set_target_properties(${MARABOU_PY} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${PYTHON_LIBRARY_OUTPUT_DIRECTORY}) - if(NOT MSVC) + LIBRARY_OUTPUT_DIRECTORY ${PYTHON_LIBRARY_OUTPUT_DIRECTORY}) + if (NOT MSVC) target_compile_options(${MARABOU_LIB} PRIVATE -fPIC ${RELEASE_FLAGS}) - endif() -endif() + endif () +endif () ################# ## Build tests ## @@ -407,26 +432,26 @@ endif() set(MARABOU_TEST_LIB MarabouHelperTest) add_library(${MARABOU_TEST_LIB}) -set (TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/tests") +set(TEST_DIR "${CMAKE_CURRENT_BINARY_DIR}/tests") file(MAKE_DIRECTORY ${TEST_DIR}) set(CMAKE_PREFIX_PATH "${TOOLS_DIR}/cxxtest") set(CXXTEST_USE_PYTHON FALSE) find_package(CxxTest) -if(CXXTEST_FOUND) +if (CXXTEST_FOUND) include_directories(${CXXTEST_INCLUDE_DIR}) enable_testing() -endif() +endif () target_link_libraries(${MARABOU_TEST_LIB} ${MARABOU_LIB} ${LIBS}) -target_include_directories(${MARABOU_TEST_LIB} PRIVATE ${LIBS_INCLUDES} ) +target_include_directories(${MARABOU_TEST_LIB} PRIVATE ${LIBS_INCLUDES}) target_compile_options(${MARABOU_TEST_LIB} PRIVATE ${CXXTEST_FLAGS}) add_custom_target(build-tests ALL) add_custom_target(check - COMMAND ctest --output-on-failure -j${CTEST_NTHREADS} $$ARGS - DEPENDS build-tests build_input_parsers ${MARABOU_EXE}) + COMMAND ctest --output-on-failure -j${CTEST_NTHREADS} $$ARGS + DEPENDS build-tests build_input_parsers ${MARABOU_EXE}) # Decide which tests to run and execute set(TESTS_TO_RUN "") @@ -434,50 +459,50 @@ set(TESTS_TO_RUN "") macro(append_tests_to_run new_val) if ("${TESTS_TO_RUN}" STREQUAL "") set(TESTS_TO_RUN ${new_val}) - else() + else () set(TESTS_TO_RUN "${TESTS_TO_RUN}|${new_val}") - endif() + endif () endmacro() if (${RUN_UNIT_TEST}) append_tests_to_run("unit") -endif() +endif () if (${RUN_REGRESS_TEST}) append_tests_to_run("regress[0-5]") -endif() +endif () if (${RUN_SYSTEM_TEST}) append_tests_to_run("system") -endif() +endif () if (NOT ${TESTS_TO_RUN} STREQUAL "") # make ctest verbose set(CTEST_OUTPUT_ON_FAILURE 1) add_custom_command( - TARGET build-tests - POST_BUILD - COMMAND ctest --output-on-failure -L "\"(${TESTS_TO_RUN})\"" -j${CTEST_NTHREADS} $$ARGS + TARGET build-tests + POST_BUILD + COMMAND ctest --output-on-failure -L "\"(${TESTS_TO_RUN})\"" -j${CTEST_NTHREADS} $$ARGS ) -endif() +endif () if (${BUILD_PYTHON} AND ${RUN_PYTHON_TEST}) if (MSVC) add_custom_command( - TARGET build-tests - POST_BUILD - COMMAND cp ${PYTHON_API_DIR}/Release/* ${PYTHON_API_DIR} + TARGET build-tests + POST_BUILD + COMMAND cp ${PYTHON_API_DIR}/Release/* ${PYTHON_API_DIR} ) - endif() + endif () add_custom_command( - TARGET build-tests - POST_BUILD - COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PYTHON_API_DIR}/test + TARGET build-tests + POST_BUILD + COMMAND ${PYTHON_EXECUTABLE} -m pytest ${PYTHON_API_DIR}/test ) -endif() +endif () # Add the input parsers add_custom_target(build_input_parsers) add_dependencies(build_input_parsers ${MPS_PARSER} ${ACAS_PARSER} - ${BERKELEY_PARSER}) + ${BERKELEY_PARSER}) add_subdirectory(${SRC_DIR}) add_subdirectory(${TOOLS_DIR}) diff --git a/COPYING b/COPYING index d923325fcb..5db8fea838 100644 --- a/COPYING +++ b/COPYING @@ -46,5 +46,6 @@ Marabou links (by default) against the following libraries: OpenBlas BSD license (https://www.openblas.net) Marabou can also be linked (however, by default it is not) against the following libraries: - Gurobi (https://www.gurobi.com) + Gurobi (https://www.gurobi.com) + CaDiCaL MIT License (https://github.com/arminbiere/cadical) diff --git a/deps/CVC4/context/cdinsert_hashmap.h b/deps/CVC4/context/cdinsert_hashmap.h index 3ba38ce7f6..4c3d1eec99 100644 --- a/deps/CVC4/context/cdinsert_hashmap.h +++ b/deps/CVC4/context/cdinsert_hashmap.h @@ -42,7 +42,7 @@ #include "base/output.h" #include "context/cdinsert_hashmap_forward.h" #include "context/context.h" -#include "expr/node.h" +//#include "expr/node.h" #pragma once @@ -390,22 +390,22 @@ class CDInsertHashMap : public ContextObj { } };/* class CDInsertHashMap<> */ -template -class CDInsertHashMap : public ContextObj { - /* CDInsertHashMap is challenging to get working with TNode. - * Consider using CDHashMap instead. - * - * Explanation: - * CDInsertHashMap uses keys for deallocation. - * If the key is a TNode and the backing (the hard node reference) - * for the key in another data structure removes the key at the same context - * the ref count could drop to 0. The key would then not be eligible to be - * hashed. Getting the order right with a guarantee is too hard. - */ - - static_assert(sizeof(Data) == 0, - "Cannot create a CDInsertHashMap with TNode keys"); -}; +//template +//class CDInsertHashMap : public ContextObj { +// /* CDInsertHashMap is challenging to get working with TNode. +// * Consider using CDHashMap instead. +// * +// * Explanation: +// * CDInsertHashMap uses keys for deallocation. +// * If the key is a TNode and the backing (the hard node reference) +// * for the key in another data structure removes the key at the same context +// * the ref count could drop to 0. The key would then not be eligible to be +// * hashed. Getting the order right with a guarantee is too hard. +// */ +// +// static_assert(sizeof(Data) == 0, +// "Cannot create a CDInsertHashMap with TNode keys"); +//}; }/* CVC4::context namespace */ }/* CVC4 namespace */ diff --git a/maraboupy/Marabou.py b/maraboupy/Marabou.py index ccaa513922..e7f46ecea9 100644 --- a/maraboupy/Marabou.py +++ b/maraboupy/Marabou.py @@ -108,7 +108,7 @@ def createOptions(numWorkers=1, initialTimeout=5, initialSplits=0, onlineSplits= preprocessorBoundTolerance=0.0000000001, dumpBounds=False, tighteningStrategy="deeppoly", milpTightening="none", milpSolverTimeout=0, numSimulations=10, numBlasThreads=1, performLpTighteningAfterSplit=False, - lpSolver="", produceProofs=False): + lpSolver="", produceProofs=False, cdcl=False): """Create an options object for how Marabou should solve the query Args: @@ -135,6 +135,8 @@ def createOptions(numWorkers=1, initialTimeout=5, initialSplits=0, onlineSplits= numBlasThreads (int, optional): Number of threads to use when using OpenBLAS matrix multiplication (e.g., for DeepPoly analysis), defaults to 1 performLpTighteningAfterSplit (bool, optional): Whether to perform a LP tightening after a case split, defaults to False lpSolver (string, optional): the engine for solving LP (native/gurobi). + produceProofs: Produce proofs of UNSAT and check them + cdcl: Solve the input query with CDCL as the solving procedure Returns: :class:`~maraboupy.MarabouCore.Options` """ @@ -162,4 +164,5 @@ def createOptions(numWorkers=1, initialTimeout=5, initialSplits=0, onlineSplits= options._performLpTighteningAfterSplit = performLpTighteningAfterSplit options._lpSolver = lpSolver options._produceProofs = produceProofs + options._cdcl = cdcl return options diff --git a/maraboupy/MarabouCore.cpp b/maraboupy/MarabouCore.cpp index 8d35646b13..2118954592 100644 --- a/maraboupy/MarabouCore.cpp +++ b/maraboupy/MarabouCore.cpp @@ -311,7 +311,11 @@ struct MarabouOptions , _milpTighteningString( Options::get()->getString( Options::MILP_SOLVER_BOUND_TIGHTENING_TYPE ).ascii() ) , _lpSolverString( Options::get()->getString( Options::LP_SOLVER ).ascii() ) - , _produceProofs( Options::get()->getBool( Options::PRODUCE_PROOFS ) ){}; + , _produceProofs( Options::get()->getBool( Options::PRODUCE_PROOFS ) ) +#ifdef BUILD_CADICAL + , _cdcl( Options::get()->getBool( Options::SOLVE_WITH_CDCL ) ) +#endif + {}; void setOptions() { @@ -323,6 +327,9 @@ struct MarabouOptions Options::get()->setBool( Options::PERFORM_LP_TIGHTENING_AFTER_SPLIT, _performLpTighteningAfterSplit ); Options::get()->setBool( Options::PRODUCE_PROOFS, _produceProofs ); +#ifdef BUILD_CADICAL + Options::get()->setBool( Options::SOLVE_WITH_CDCL, _cdcl ); +#endif // int options Options::get()->setInt( Options::NUM_WORKERS, _numWorkers ); @@ -356,6 +363,7 @@ struct MarabouOptions bool _dumpBounds; bool _performLpTighteningAfterSplit; bool _produceProofs; + bool _cdcl; unsigned _numWorkers; unsigned _numBlasThreads; unsigned _initialTimeout; @@ -376,21 +384,21 @@ struct MarabouOptions }; -std::string exitCodeToString( IEngine::ExitCode code ) +std::string exitCodeToString( ExitCode code ) { switch ( code ) { - case IEngine::UNSAT: + case ExitCode::UNSAT: return "unsat"; - case IEngine::SAT: + case ExitCode::SAT: return "sat"; - case IEngine::ERROR: + case ExitCode::ERROR: return "ERROR"; - case IEngine::UNKNOWN: + case ExitCode::UNKNOWN: return "UNKNOWN"; - case IEngine::TIMEOUT: + case ExitCode::TIMEOUT: return "TIMEOUT"; - case IEngine::QUIT_REQUESTED: + case ExitCode::QUIT_REQUESTED: return "QUIT_REQUESTED"; default: return "UNKNOWN"; @@ -412,6 +420,38 @@ solve( InputQuery &inputQuery, MarabouOptions &options, std::string redirect = " output = redirectOutputToFile( redirect ); try { + if ( options._cdcl ) + { + if ( !options._produceProofs ) + { + options._produceProofs = true; + printf( "Turning produceProofs on to allow proof-based conflict clauses.\n" ); + } + printf( "Please note that producing complete UNSAT proofs while cdcl is on is not yet " + "supported.\n" ); + } + + if ( options._produceProofs ) + { + GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH = false; + printf( "Proof production is not yet supported with DEEPSOI search, turning search " + "off.\n" ); + } + + if ( options._produceProofs && options._snc ) + { + options._snc = false; + printf( "Proof production is not yet supported with snc mode, turning snc off.\n" ); + } + + if ( options._produceProofs && options._solveWithMILP ) + { + options._solveWithMILP = false; + printf( + "Proof production is not yet supported with MILP solvers, turning solveWithMILP " + "off.\n" ); + } + options.setOptions(); bool dnc = Options::get()->getBool( Options::DNC_MODE ); @@ -429,13 +469,13 @@ solve( InputQuery &inputQuery, MarabouOptions &options, std::string redirect = " resultString = dncManager->getResultString().ascii(); switch ( dncManager->getExitCode() ) { - case DnCManager::SAT: + case ExitCode::SAT: { retStats = Statistics(); dncManager->getSolution( ret, inputQuery ); break; } - case DnCManager::TIMEOUT: + case ExitCode::TIMEOUT: { retStats = Statistics(); retStats.timeout(); @@ -449,11 +489,22 @@ solve( InputQuery &inputQuery, MarabouOptions &options, std::string redirect = " else { unsigned timeoutInSeconds = Options::get()->getInt( Options::TIMEOUT ); - engine.solve( timeoutInSeconds ); + if ( engine.shouldSolveWithMILP() ) + engine.solveWithMILPEncoding( timeoutInSeconds ); +#ifdef BUILD_CADICAL + else if ( engine.shouldSolveWithCDCL() ) + engine.solveWithCDCL( timeoutInSeconds ); +#endif + else + { + engine.solve( timeoutInSeconds ); + if ( engine.shouldProduceProofs() && engine.getExitCode() == ExitCode::UNSAT ) + engine.certifyUNSATCertificate(); + } resultString = exitCodeToString( engine.getExitCode() ); - if ( engine.getExitCode() == Engine::SAT ) + if ( engine.getExitCode() == ExitCode::SAT ) { engine.extractSolution( inputQuery ); for ( unsigned int i = 0; i < inputQuery.getNumberOfVariables(); ++i ) @@ -566,7 +617,8 @@ PYBIND11_MODULE( MarabouCore, m ) .def_readwrite( "_numSimulations", &MarabouOptions::_numSimulations ) .def_readwrite( "_performLpTighteningAfterSplit", &MarabouOptions::_performLpTighteningAfterSplit ) - .def_readwrite( "_produceProofs", &MarabouOptions::_produceProofs ); + .def_readwrite( "_produceProofs", &MarabouOptions::_produceProofs ) + .def_readwrite( "_cdcl", &MarabouOptions::_cdcl ); m.def( "maraboupyMain", &maraboupyMain, "Run the Marabou command-line interface" ); m.def( "loadProperty", &loadProperty, "Load a property file into a input query" ); m.def( "createInputQuery", @@ -833,8 +885,8 @@ PYBIND11_MODULE( MarabouCore, m ) .value( "NUM_POPS", Statistics::StatisticsUnsignedAttribute::NUM_POPS ) .value( "CURRENT_DECISION_LEVEL", Statistics::StatisticsUnsignedAttribute::CURRENT_DECISION_LEVEL ) - .value( "NUM_PL_SMT_ORIGINATED_SPLITS", - Statistics::StatisticsUnsignedAttribute::NUM_PL_SMT_ORIGINATED_SPLITS ) + .value( "NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS", + Statistics::StatisticsUnsignedAttribute::NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS ) .value( "NUM_VISITED_TREE_STATES", Statistics::StatisticsUnsignedAttribute::NUM_VISITED_TREE_STATES ) .value( "PP_NUM_TIGHTENING_ITERATIONS", @@ -862,8 +914,8 @@ PYBIND11_MODULE( MarabouCore, m ) py::enum_( m, "StatisticsLongAttribute" ) .value( "NUM_TIGHTENINGS_FROM_EXPLICIT_BASIS", Statistics::StatisticsLongAttribute::NUM_TIGHTENINGS_FROM_EXPLICIT_BASIS ) - .value( "TOTAL_TIME_SMT_CORE_MICRO", - Statistics::StatisticsLongAttribute::TOTAL_TIME_SMT_CORE_MICRO ) + .value( "TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO", + Statistics::StatisticsLongAttribute::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO ) .value( "TOTAL_TIME_PERFORMING_VALID_CASE_SPLITS_MICRO", Statistics::StatisticsLongAttribute::TOTAL_TIME_PERFORMING_VALID_CASE_SPLITS_MICRO ) .value( "PREPROCESSING_TIME_MICRO", diff --git a/regress/CMakeLists.txt b/regress/CMakeLists.txt index 05f385b126..5f09b7206b 100644 --- a/regress/CMakeLists.txt +++ b/regress/CMakeLists.txt @@ -93,6 +93,12 @@ macro(marabou_add_coav_proof_test level net_file result) "${CMAKE_SOURCE_DIR}/resources/properties/builtin_property.txt" "${result}" "--prove-unsat" "coav") endmacro() +macro(marabou_add_coav_cdcl_test level net_file result) + marabou_add_regress_test(${level} + "${CMAKE_SOURCE_DIR}/resources/nnet/coav/${net_file}" + "${CMAKE_SOURCE_DIR}/resources/properties/builtin_property.txt" "${result}" "--cdcl" "coav") +endmacro() + add_subdirectory(regress0) add_subdirectory(regress1) add_subdirectory(regress2) diff --git a/regress/regress1/CMakeLists.txt b/regress/regress1/CMakeLists.txt index 81c936da88..bc8799cfa7 100644 --- a/regress/regress1/CMakeLists.txt +++ b/regress/regress1/CMakeLists.txt @@ -1,6 +1,6 @@ -marabou_add_coav_test(1 "reluBenchmark1.30941200256s_UNSAT.nnet" unsat) -marabou_add_coav_test(1 "reluBenchmark0.94518494606s_SAT.nnet" sat) +marabou_add_coav_test(1 "reluBenchmark1.30941200256s_UNSAT.nnet" unsat) +marabou_add_coav_test(1 "reluBenchmark0.94518494606s_SAT.nnet" sat) marabou_add_acasxu_test(1 "ACASXU_experimental_v2a_1_8" "3" sat) marabou_add_acasxu_test(1 "ACASXU_experimental_v2a_2_9" "3" unsat) marabou_add_acasxu_test(1 "ACASXU_experimental_v2a_1_6" "3" unsat) @@ -120,24 +120,24 @@ marabou_add_coav_test(1 "reluBenchmark0.00601387023926s_UNSAT.nnet" unsat) # Add all input query files in regress1/input_queries/ as tests. file(GLOB sat_ipqs "input_queries/*_sat.ipq") -foreach(file ${sat_ipqs}) - get_filename_component(basename ${file} NAME [CACHE]) - marabou_add_input_query_test(1 ${basename} sat "--verbosity=0" "ipq") - marabou_add_input_query_test(1 ${basename} sat "--num-workers=2+--initial-divides=2+--snc" "ipq") - if (${ENABLE_GUROBI}) - marabou_add_input_query_test(1 ${basename} sat "--num-workers=2+--milp" "ipq") - endif() -endforeach() +foreach (file ${sat_ipqs}) + get_filename_component(basename ${file} NAME [CACHE]) + marabou_add_input_query_test(1 ${basename} sat "--verbosity=0" "ipq") + marabou_add_input_query_test(1 ${basename} sat "--num-workers=2+--initial-divides=2+--snc" "ipq") + if (${ENABLE_GUROBI}) + marabou_add_input_query_test(1 ${basename} sat "--num-workers=2+--milp" "ipq") + endif () +endforeach () file(GLOB unsat_ipqs "input_queries/*_unsat.ipq") -foreach(file ${unsat_ipqs}) - get_filename_component(basename ${file} NAME [CACHE]) - marabou_add_input_query_test(1 ${basename} unsat "--verbosity=0" "ipq") - marabou_add_input_query_test(1 ${basename} unsat "--num-workers=2+--initial-divides=2+--snc" "ipq") - if (${ENABLE_GUROBI}) - marabou_add_input_query_test(1 ${basename} unsat "--num-workers=2+--milp" "ipq") - endif() -endforeach() +foreach (file ${unsat_ipqs}) + get_filename_component(basename ${file} NAME [CACHE]) + marabou_add_input_query_test(1 ${basename} unsat "--verbosity=0" "ipq") + marabou_add_input_query_test(1 ${basename} unsat "--num-workers=2+--initial-divides=2+--snc" "ipq") + if (${ENABLE_GUROBI}) + marabou_add_input_query_test(1 ${basename} unsat "--num-workers=2+--milp" "ipq") + endif () +endforeach () # Proof production tests @@ -167,3 +167,100 @@ marabou_add_input_query_test(1 deep_6_index_5566.ipq unsat "--prove-unsat" "ipq" marabou_add_input_query_test(1 deep_6_index_5567.ipq unsat "--prove-unsat" "ipq") marabou_add_input_query_test(1 deep_6_index_5525.ipq unsat "--prove-unsat" "ipq") marabou_add_input_query_test(1 deep_6_index_7779.ipq unsat "--prove-unsat" "ipq") + +# CDCL +if (${BUILD_CADICAL}) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.453322172165s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.30711388588s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.00512099266052s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.725997209549s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.919886112213s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0352051258087s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.97177696228s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.36359000206s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0990879535675s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.20350694656s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.221790075302s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.048150062561s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.781363010406s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0492730140686s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0494029521942s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.499482154846s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.674989938736s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.15705108643s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0606548786163s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.21319699287s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0064799785614s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0780329704285s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.454473018646s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.00568509101868s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.930528879166s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0380239486694s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.764842987061s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.385080099106s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.07406687737s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0645151138306s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.04137301445s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.505765914917s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.07428097725s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.734488964081s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.326423883438s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.398984909058s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.379405975342s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.479170084s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0386769771576s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.4906899929s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark2.79231500626s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.345348119736s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.780378818512s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.32709693909s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.287177085876s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0374069213867s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0594508647919s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0918869972229s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.983299016953s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0842049121857s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.213617086411s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.666516065598s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.149944067s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.165054798126s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.00636315345764s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0596628189087s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0065929889679s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.30961179733s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0715999603271s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.29426407814s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.294414997101s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.124088048935s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0637600421906s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.00662088394165s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.276684999466s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.271136045456s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.101797103882s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.836799860001s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.907590150833s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.54942297935s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.692045211792s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.04315304756s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.213303089142s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.09928894043s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.21413683891s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.183997154236s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.514003992081s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.047042131424s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0507640838623s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.83825802803s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.262641906738s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.434634923935s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.950109958649s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.62436914444s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.73387503624s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0452961921692s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.828888177872s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.996887922287s_SAT.nnet" sat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.0575940608978s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.213079929352s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark1.81178617477s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.956095933914s_UNSAT.nnet" unsat) + marabou_add_coav_cdcl_test(1 "reluBenchmark0.00601387023926s_UNSAT.nnet" unsat) +endif () \ No newline at end of file diff --git a/regress/run_regression.py b/regress/run_regression.py index 38f97c73b7..feb7d8e1fc 100755 --- a/regress/run_regression.py +++ b/regress/run_regression.py @@ -58,7 +58,7 @@ def analyze_process_result(out, err, exit_status, expected_result): print("Expected unsat, but last line is in output is: ", out_lines[-1]) return False else: - if '\nsat' in out: + if 'sat' in out: return True else: print('expected sat, but \'\\nSAT\' is not in the output. tail of the output:\n', out[-1500:]) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d51713f9e2..b286714149 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -55,3 +55,5 @@ add_subdirectory(query_loader) add_subdirectory(nlr) add_subdirectory(proofs) add_subdirectory(cegar) +add_subdirectory(cdcl) + diff --git a/src/cdcl/CMakeLists.txt b/src/cdcl/CMakeLists.txt new file mode 100644 index 0000000000..7165f2be63 --- /dev/null +++ b/src/cdcl/CMakeLists.txt @@ -0,0 +1,19 @@ +file(GLOB SRCS "*.cpp") +file(GLOB HEADERS "*.h") + +target_sources(${MARABOU_LIB} PRIVATE ${SRCS}) +target_include_directories(${MARABOU_LIB} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + +target_sources(${MARABOU_TEST_LIB} PRIVATE ${SRCS}) +target_include_directories(${MARABOU_TEST_LIB} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") + +set (CDCL_TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/tests") +macro(cdcl_add_unit_test name) + set(USE_MOCK_COMMON TRUE) + set(USE_MOCK_ENGINE TRUE) + marabou_add_test(${CDCL_TESTS_DIR}/Test_${name} cdcl USE_MOCK_COMMON USE_MOCK_ENGINE "unit") +endmacro() + +if (${BUILD_PYTHON}) + target_include_directories(${MARABOU_PY} PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}") +endif() diff --git a/src/cdcl/CadicalWrapper.cpp b/src/cdcl/CadicalWrapper.cpp new file mode 100644 index 0000000000..f861c3d75a --- /dev/null +++ b/src/cdcl/CadicalWrapper.cpp @@ -0,0 +1,119 @@ +/********************* */ +/*! \file CadicalWrapper.cpp + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifdef BUILD_CADICAL +#include "CadicalWrapper.h" + +CadicalWrapper::CadicalWrapper( CaDiCaL::ExternalPropagator *externalPropagator, + CaDiCaL::Terminator *terminator, + CaDiCaL::FixedAssignmentListener *fixedListener ) + : _solver( new CaDiCaL::Solver() ) +{ + _solver->set( "walk", 0 ); + _solver->set( "lucky", 0 ); + _solver->set( "log", 0 ); + + _solver->connect_external_propagator( externalPropagator ); + _solver->connect_terminator( terminator ); + _solver->connect_fixed_listener( fixedListener ); +} + +CadicalWrapper::~CadicalWrapper() +{ + _solver->disconnect_fixed_listener(); + _solver->disconnect_terminator(); + _solver->disconnect_external_propagator(); +} + +void CadicalWrapper::addLiteral( int lit ) +{ + _solver->add( lit ); +} + +void CadicalWrapper::addClause( const Set &clause ) +{ + _solver->clause( std::vector( clause.begin(), clause.end() ) ); +} + +void CadicalWrapper::assume( int lit ) +{ + _solver->assume( lit ); +} + +void CadicalWrapper::phase( int lit ) +{ + _solver->phase( lit ); +} + +int CadicalWrapper::solve() +{ + return _solver->solve(); +} + +int CadicalWrapper::val( int lit ) +{ + return _solver->val( lit ); +} + +void CadicalWrapper::flip( int lit ) +{ + _solver->flip( lit ); +} + +void CadicalWrapper::addObservedVar( int var ) +{ + _solver->add_observed_var( var ); +} + +bool CadicalWrapper::isDecision( int observedVar ) const +{ + return _solver->is_decision( observedVar ); +} + +int CadicalWrapper::vars() +{ + return _solver->vars(); +} + +void CadicalWrapper::forceBacktrack( size_t newLevel ) +{ + _solver->force_backtrack( newLevel ); +} + +Set CadicalWrapper::addExternalNAPClause( const String &externalNAPClauseFilename ) +{ + Set clause; + + if ( externalNAPClauseFilename != "" && File::exists( externalNAPClauseFilename ) ) + { + File externalNAPClauseFile( externalNAPClauseFilename ); + externalNAPClauseFile.open( IFile::MODE_READ ); + + while ( true ) + { + String lit = externalNAPClauseFile.readLine().trim(); + + if ( lit == "" ) + break; + + clause.insert( -atoi( lit.ascii() ) ); + } + + addClause( clause ); + } + + return clause; +} +#endif \ No newline at end of file diff --git a/src/cdcl/CadicalWrapper.h b/src/cdcl/CadicalWrapper.h new file mode 100644 index 0000000000..3b3107ce71 --- /dev/null +++ b/src/cdcl/CadicalWrapper.h @@ -0,0 +1,101 @@ +/********************* */ +/*! \file CadicalWrapper.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __CadicalWrapper_h__ +#define __CadicalWrapper_h__ + +#ifdef BUILD_CADICAL +#include "File.h" +#include "SatSolverWrapper.h" + +#include +#include + + +class CadicalWrapper : public SatSolverWrapper +{ +public: + /* + Constructs and destructs the Cadical Wrapper. + */ + explicit CadicalWrapper( CaDiCaL::ExternalPropagator *externalPropagator, + CaDiCaL::Terminator *terminator, + CaDiCaL::FixedAssignmentListener *fixedListener ); + ~CadicalWrapper() override; + + /* + Add valid literal to clause or zero to terminate clause. + */ + void addLiteral( int lit ) override; + + /* + Add a clause to the solver + */ + void addClause( const Set &clause ) override; + + /* + Assume valid non zero literal for next call to 'solve'. + */ + void assume( int lit ) override; + + /* + Force the default decision phase of a variable to a certain value. + */ + void phase( int lit ) override; + + /* + Try to solve the current formula. + */ + int solve() override; + + /* + Get value (-lit=false, lit=true) of valid non-zero literal. + */ + int val( int lit ) override; + + /* + Try to flip the value of the given literal without falsifying the formula. + */ + void flip( int lit ) override; + + /* + Mark as 'observed' those variables that are relevant to the theory solver. + */ + void addObservedVar( int var ) override; + + /* + Get reason of valid observed literal. + */ + bool isDecision( int observedVar ) const override; + + /* + Return the number of vars; + */ + int vars() override; + + /* + * Forces backtracking to the given level + */ + void forceBacktrack( size_t newLevel ) override; + + Set addExternalNAPClause( const String &externalNAPClauseFilename ) override; + +private: + std::shared_ptr _solver; +}; + + +#endif +#endif // __CadicalWrapper_h__ diff --git a/src/cdcl/CdclCore.cpp b/src/cdcl/CdclCore.cpp new file mode 100644 index 0000000000..eecebf1f36 --- /dev/null +++ b/src/cdcl/CdclCore.cpp @@ -0,0 +1,1384 @@ +/********************* */ +/*! \file CdclCore.cpp + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifdef BUILD_CADICAL +#include "CdclCore.h" + +#include "NetworkLevelReasoner.h" +#include "Options.h" +#include "Query.h" +#include "TimeUtils.h" +#include "TimeoutException.h" + +#include + +CdclCore::CdclCore( IEngine *engine ) + : _engine( engine ) + , _context( _engine->getContext() ) + , _statistics( nullptr ) + , _satSolverWrapper( new CadicalWrapper( this, this, this ) ) + , _cadicalVarToPlc() + , _literalsToPropagate() + , _assignedLiterals( &_context ) + , _reasonClauseLiterals() + , _isReasonClauseInitialized( false ) + , _fixedCadicalVars() + , _timeoutInSeconds( 0 ) + , _numOfClauses( 0 ) + , _satisfiedClauses( &_context ) + , _literalToClauses() + , _vsidsDecayThreshold( 0 ) + , _vsidsDecayCounter( 0 ) + , _restarts( 1 ) + , _restartLimit( 512 * luby( 1 ) ) + , _numOfConflictClauses( 0 ) + , _shouldRestart( false ) + , _initialClauses() + , _scoreTracker( nullptr ) +{ + _cadicalVarToPlc.insert( 0, NULL ); +} + +CdclCore::~CdclCore() +{ + delete _satSolverWrapper; +} + +void CdclCore::initBooleanAbstraction( PiecewiseLinearConstraint *plc ) +{ + struct timespec start = TimeUtils::sampleMicro(); + + plc->booleanAbstraction( _cadicalVarToPlc ); + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +bool CdclCore::isLiteralAssigned( int literal ) const +{ + if ( _assignedLiterals.count( literal ) > 0 ) + { + ASSERT( _cadicalVarToPlc.at( abs( literal ) )->phaseFixed() || + !_cadicalVarToPlc.at( abs( literal ) )->isActive() ) + return true; + } + + return false; +} + +void CdclCore::notify_assignment( const std::vector &lits ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return; + + checkIfShouldExitDueToTimeout(); + + // if ( !_externalClauseToAdd.empty() ) + // { + // SEARCH_TREE_LOG( "Skipping notification due to conflict clause" ) + // return; + // } + + struct timespec start = TimeUtils::sampleMicro(); + + CDCL_LOG( "Notifying assignments:" ) + + for ( int lit : lits ) + { + CDCL_LOG( Stringf( "\tNotified assignment %d; is decision: %d", + lit, + _satSolverWrapper->isDecision( lit ) ) + .ascii() ) + + if ( !isLiteralAssigned( lit ) ) + notifySingleAssignment( lit, false ); + } + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_ASSIGNMENT_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +void CdclCore::notify_new_decision_level() +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return; + + checkIfShouldExitDueToTimeout(); + struct timespec start = TimeUtils::sampleMicro(); + CDCL_LOG( "Notified new decision level" ) + + _engine->preContextPushHook(); + pushContext(); + + if ( _statistics ) + { + _statistics->incUnsignedAttribute( Statistics::NUM_SPLITS ); + + unsigned level = _context.getLevel(); + _statistics->setUnsignedAttribute( Statistics::CURRENT_DECISION_LEVEL, level ); + if ( level > _statistics->getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ) + _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); + _statistics->incUnsignedAttribute( Statistics::NUM_DECISION_LEVELS ); + _statistics->incUnsignedAttribute( Statistics::SUM_DECISION_LEVELS, level ); + + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_NEW_DECISION_LEVEL_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +void CdclCore::notify_backtrack( size_t new_level ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return; + + checkIfShouldExitDueToTimeout(); + struct timespec start = TimeUtils::sampleMicro(); + CDCL_LOG( Stringf( "Backtracking to level %d", new_level ).ascii() ) + + // struct timespec start = TimeUtils::sampleMicro(); + unsigned oldLevel = _context.getLevel(); + + if ( _shouldRestart ) + { + if ( _statistics ) + _statistics->incUnsignedAttribute( Statistics::NUM_RESTARTS ); + + _shouldRestart = false; + _numOfConflictClauses = 0; + _restartLimit = 512 * luby( ++_restarts ); + _engine->restoreInitialEngineState(); + _largestAssignmentSoFar.clear(); + } + + popContextTo( new_level ); + _engine->postContextPopHook(); + + for ( unsigned l = oldLevel; l > new_level; --l ) + _decisionLiterals.erase( l ); + + // Maintain literals to propagate learned before the decision level + List> currentPropagations = _literalsToPropagate; + _literalsToPropagate.clear(); + + for ( const Pair &propagation : currentPropagations ) + if ( propagation.second() <= (int)new_level ) + _literalsToPropagate.append( propagation ); + + for ( int lit : _fixedCadicalVars ) + if ( !isLiteralAssigned( lit ) ) + notifySingleAssignment( lit, true ); + + struct timespec end = TimeUtils::sampleMicro(); + + if ( _statistics ) + { + unsigned jumpSize = oldLevel - new_level; + + _statistics->setUnsignedAttribute( Statistics::CURRENT_DECISION_LEVEL, new_level ); + _statistics->incUnsignedAttribute( Statistics::NUM_DECISION_LEVELS ); + _statistics->incUnsignedAttribute( Statistics::SUM_DECISION_LEVELS, new_level ); + + _statistics->incUnsignedAttribute( Statistics::NUM_BACKJUMPS ); + _statistics->incUnsignedAttribute( Statistics::SUM_BACKJUMPS, jumpSize ); + if ( jumpSize > _statistics->getUnsignedAttribute( Statistics::MAX_BACKJUMP ) ) + _statistics->setUnsignedAttribute( Statistics::MAX_BACKJUMP, jumpSize ); + + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_BACKTRACK_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +bool CdclCore::cb_check_found_model( const std::vector &model ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return false; + + checkIfShouldExitDueToTimeout(); + + if ( _statistics ) + _statistics->incUnsignedAttribute( Statistics::NUM_VISITED_TREE_STATES ); + CDCL_LOG( "Checking model found by SAT solver" ) + ASSERT( _externalClauseToAdd.empty() ) + notify_assignment( model ); + + bool result; + + if ( _engine->getLpSolverType() == LPSolverType::NATIVE ) + { + // Quickly try to notify constraints for bounds, which raises exception in case of + // infeasibility + if ( !_engine->propagateBoundManagerTightenings() ) + return false; + + // If external clause learned, no need to call solve + if ( !_externalClauseToAdd.empty() ) + return false; + + result = _engine->solve( 0 ); + + // In cases where Marabou fails to provide a conflict clause, add the trivial possibility + if ( !result && _externalClauseToAdd.empty() ) + addDecisionBasedConflictClause(); + + CDCL_LOG( Stringf( "\tResult is %u", result ).ascii() ) + result = result && _externalClauseToAdd.empty(); + } + else + result = _engine->solve( 0 ); + + return result; +} + +int CdclCore::cb_decide() +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return 0; + + checkIfShouldExitDueToTimeout(); + struct timespec start = TimeUtils::sampleMicro(); + CDCL_LOG( "Callback for decision:" ) + + if ( _shouldRestart ) + { + _satSolverWrapper->forceBacktrack( 0 ); + return 0; + } + + unsigned decisionVariable = + GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH && _context.getLevel() > 3 + ? decideSplitVarBasedOnPseudoImpactAndVsids() + : decideSplitVarBasedOnPolarityAndVsids(); + + int decisionLiteral = 0; + + if ( decisionVariable ) + decisionLiteral = _cadicalVarToPlc[decisionVariable]->getLiteralForDecision(); + + if ( decisionLiteral ) + { + ASSERT( !isLiteralAssigned( -decisionLiteral ) && !isLiteralAssigned( decisionLiteral ) ) + ASSERT( FloatUtils::abs( decisionLiteral ) <= _satSolverWrapper->vars() ) + CDCL_LOG( Stringf( "Decided literal %d", decisionLiteral ).ascii() ) + + if ( _statistics ) + _statistics->incUnsignedAttribute( Statistics::NUM_MARABOU_DECISIONS ); + } + else + { + CDCL_LOG( "No decision made" ) + if ( _statistics ) + _statistics->incUnsignedAttribute( Statistics::NUM_SAT_SOLVER_DECISIONS ); + } + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_DECIDE_MICRO, + TimeUtils::timePassed( start, end ) ); + } + + return decisionLiteral; +} + +int CdclCore::cb_propagate() +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return 0; + + checkIfShouldExitDueToTimeout(); + + struct timespec start = {}; + struct timespec end = {}; + unsigned long long total = 0; + + if ( _engine->getLpSolverType() == LPSolverType::GUROBI && + GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + if ( _engine->solve( 0 ) ) + { + if ( _statistics ) + start = TimeUtils::sampleMicro(); + + bool allInitialClausesSatisfied = true; + for ( const Set &clause : _initialClauses ) + if ( !_engine->checkAssignmentComplianceWithClause( clause ) ) + { + allInitialClausesSatisfied = false; + break; + } + + if ( _statistics ) + { + end = TimeUtils::sampleMicro(); + total += TimeUtils::timePassed( start, end ); + } + + if ( allInitialClausesSatisfied ) + { + _engine->setExitCode( ExitCode::SAT ); + if ( _statistics ) + { + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + total ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO, total ); + } + return 0; + } + } + } + + // ASSERT( _engine->getLpSolverType() == LPSolverType::NATIVE ) + + if ( _literalsToPropagate.empty() ) + { + if ( _statistics ) + _statistics->incUnsignedAttribute( Statistics::NUM_VISITED_TREE_STATES ); + + // If no literals left to propagate, and no clause already found, attempt solving + if ( _externalClauseToAdd.empty() ) + { + if ( _engine->solve( 0 ) ) + { + if ( _statistics ) + start = TimeUtils::sampleMicro(); + + bool allInitialClausesSatisfied = true; + for ( const Set &clause : _initialClauses ) + if ( !_engine->checkAssignmentComplianceWithClause( clause ) ) + { + allInitialClausesSatisfied = false; + break; + } + + if ( _statistics ) + { + end = TimeUtils::sampleMicro(); + total += TimeUtils::timePassed( start, end ); + } + + if ( allInitialClausesSatisfied ) + { + _engine->setExitCode( ExitCode::SAT ); + if ( _statistics ) + { + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, total ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO, total ); + } + return 0; + } + } + } + + if ( _statistics ) + start = TimeUtils::sampleMicro(); + + // Try learning a conflict clause if possible + if ( _externalClauseToAdd.empty() ) + { + if ( _engine->getLpSolverType() == LPSolverType::NATIVE ) + _engine->propagateBoundManagerTightenings(); + if ( _externalClauseToAdd.empty() ) + { + if ( _assignedLiterals.size() + _literalsToPropagate.size() > + _largestAssignmentSoFar.size() ) + { + _largestAssignmentSoFar.clear(); + + for ( const auto &p : _assignedLiterals ) + { + int lit = p.first; + if ( lit > 0 ) + _largestAssignmentSoFar[lit] = true; + else + _largestAssignmentSoFar[-lit] = false; + } + + for ( const auto &p : _literalsToPropagate ) + { + int lit = p.first(); + if ( lit > 0 ) + _largestAssignmentSoFar[lit] = true; + else + _largestAssignmentSoFar[-lit] = false; + } + } + } + } + + // Add the zero literal at the end + _literalsToPropagate.append( Pair( 0, _context.getLevel() ) ); + + if ( _statistics ) + { + end = TimeUtils::sampleMicro(); + total += TimeUtils::timePassed( start, end ); + } + } + + if ( _statistics ) + start = TimeUtils::sampleMicro(); + + int lit = _literalsToPropagate.popFront().first(); + + // In case of assigned boolean variable with opposite assignment, find a conflict clause and + // terminate propagating + if ( lit ) + if ( isLiteralAssigned( -lit ) ) + { + if ( _externalClauseToAdd.empty() ) + { + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + _engine->explainSimplexFailure(); + else + addDecisionBasedConflictClause(); + } + + ASSERT( !_externalClauseToAdd.empty() ) + _literalsToPropagate.clear(); + _literalsToPropagate.append( Pair( 0, _context.getLevel() ) ); + } + + CDCL_LOG( Stringf( "Propagating literal %d", lit ).ascii() ) + ASSERT( FloatUtils::abs( lit ) <= _satSolverWrapper->vars() ) + + if ( _statistics ) + { + end = TimeUtils::sampleMicro(); + total += TimeUtils::timePassed( start, end ); + + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, total ); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO, total ); + } + return lit; +} + +int CdclCore::cb_add_reason_clause_lit( int propagated_lit ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + { + return 0; + } + + checkIfShouldExitDueToTimeout(); + + ASSERT( _engine->getLpSolverType() == LPSolverType::NATIVE ) + struct timespec start = TimeUtils::sampleMicro(); + ASSERT( propagated_lit ) + ASSERT( !_satSolverWrapper->isDecision( propagated_lit ) ) + + if ( !_isReasonClauseInitialized ) + { + _reasonClauseLiterals.clear(); + if ( _numOfClauses == _vsidsDecayThreshold ) + { + _numOfClauses = 0; + _vsidsDecayThreshold = 512 * luby( ++_vsidsDecayCounter ); + _literalToClauses.clear(); + } + + CDCL_LOG( Stringf( "Adding reason clause for literal %d", propagated_lit ).ascii() ) + + if ( !_fixedCadicalVars.exists( propagated_lit ) ) + { + Set clause; + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + clause = _engine->explainPhaseWithProof( _cadicalVarToPlc[abs( propagated_lit )] ); + else + { + for ( int level = 1; level <= _context.getLevel(); ++level ) + { + ASSERT( _decisionLiterals.exists( level ) ); + int lit = _decisionLiterals[level]; + ASSERT( isDecision( lit ) && lit != propagated_lit ); + + if ( _assignedLiterals[lit] >= _assignedLiterals[propagated_lit] ) + break; + + if ( !_fixedCadicalVars.exists( lit ) ) + clause.insert( lit ); + } + } + + if ( GlobalConfiguration::CDCL_SHORTEN_CLAUSES && + !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = + _engine->getNetworkLevelReasoner(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + + setInputBoundsForLiteralInNLR( -propagated_lit, inputQuery, networkLevelReasoner ); + + if ( !checkIfShouldSkipClauseShortening( clause ) ) + { + Vector> clauseScores; + computeClauseScores( clause, clauseScores ); + reorderByDecisionLevelIfNecessary( clauseScores ); + clause.clear(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + computeShortedClause( clause, clauseScores, propagated_lit ); + } + } + + for ( int lit : clause ) + { + // Make sure all clause literals were fixed before the literal to explain + ASSERT( _cadicalVarToPlc[abs( propagated_lit )]->getPhaseFixingEntry()->id > + _cadicalVarToPlc[abs( lit )]->getPhaseFixingEntry()->id ) + + // Remove fixed literals from clause, as they are redundant + if ( !_fixedCadicalVars.exists( -lit ) ) + { + _reasonClauseLiterals.append( -lit ); + _literalToClauses[-lit].insert( _numOfClauses ); + } + } + } + + ASSERT( !_reasonClauseLiterals.exists( -propagated_lit ) ) + _reasonClauseLiterals.append( propagated_lit ); + _literalToClauses[propagated_lit].insert( _numOfClauses ); + ++_numOfClauses; + _isReasonClauseInitialized = true; + + // Unit clause fixes the propagated literal + if ( _reasonClauseLiterals.size() == 1 ) + _fixedCadicalVars.insert( propagated_lit ); + } + + int lit = 0; + if ( !_reasonClauseLiterals.empty() ) + { + lit = _reasonClauseLiterals.pop(); + ASSERT( FloatUtils::abs( lit ) <= _satSolverWrapper->vars() ) + CDCL_LOG( Stringf( "\tAdding Literal %d for Reason Clause", lit ).ascii() ) + } + else + _isReasonClauseInitialized = false; + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_CB_ADD_REASON_CLAUSE_LIT_MICRO, + TimeUtils::timePassed( start, end ) ); + } + + return lit; +} + +bool CdclCore::cb_has_external_clause( bool & /*is_forgettable*/ ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return false; + + checkIfShouldExitDueToTimeout(); + CDCL_LOG( Stringf( "Checking if there is a Conflict Clause to add: %d", + !_externalClauseToAdd.empty() ) + .ascii() ) + return !_externalClauseToAdd.empty(); +} + +int CdclCore::cb_add_external_clause_lit() +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return 0; + + checkIfShouldExitDueToTimeout(); + struct timespec start = TimeUtils::sampleMicro(); + + ASSERT( !_externalClauseToAdd.empty() ) + + // Add literal from the last conflict clause learned + int lit = _externalClauseToAdd.pop(); + ASSERT( FloatUtils::abs( lit ) <= _satSolverWrapper->vars() ) + CDCL_LOG( Stringf( "\tAdding Literal %d to Conflict Clause", lit ).ascii() ) + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_CB_ADD_EXTERNAL_CLAUSE_LIT_MICRO, + TimeUtils::timePassed( start, end ) ); + } + + return lit; +} + +void CdclCore::addExternalClause( Set &clause ) +{ + CDCL_LOG( "Add External Clause" ) + struct timespec start = TimeUtils::sampleMicro(); + + ASSERT( !clause.exists( 0 ) ) + if ( _numOfClauses == _vsidsDecayThreshold ) + { + _numOfClauses = 0; + _vsidsDecayThreshold = 512 * luby( ++_vsidsDecayCounter ); + _literalToClauses.clear(); + } + + if ( GlobalConfiguration::CDCL_SHORTEN_CLAUSES && + !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + + if ( !checkIfShouldSkipClauseShortening( clause ) ) + { + Vector> clauseScores; + computeClauseScores( clause, clauseScores ); + reorderByDecisionLevelIfNecessary( clauseScores ); + clause.clear(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + computeShortedClause( clause, clauseScores, 0 ); + } + } + + _externalClauseToAdd.append( 0 ); + + // Remove fixed literals as they are redundant + for ( int lit : clause ) + { + _externalClauseToAdd.append( -lit ); + if ( !_fixedCadicalVars.exists( -lit ) ) + _literalToClauses[-lit].insert( _numOfClauses ); + } + + ++_numOfClauses; + + ++_numOfConflictClauses; + if ( _numOfConflictClauses == _restartLimit ) + _shouldRestart = true; + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +const PiecewiseLinearConstraint *CdclCore::getConstraintFromLit( int lit ) const +{ + if ( _cadicalVarToPlc.exists( (unsigned)FloatUtils::abs( lit ) ) ) + return _cadicalVarToPlc.at( (unsigned)FloatUtils::abs( lit ) ); + return nullptr; +} + +bool CdclCore::solveWithCDCL( double timeoutInSeconds ) +{ + try + { + _timeoutInSeconds = timeoutInSeconds; + + // Maybe query detected as UNSAT in processInputQuery + if ( _engine->getExitCode() == ExitCode::UNSAT ) + return false; + + if ( Options::get()->getString( Options::NAP_EXTERNAL_CLAUSE_FILE_PATH ) == "" && + Options::get()->getString( Options::NAP_EXTERNAL_CLAUSE_FILE_PATH2 ) == "" ) + if ( _engine->solve( 0 ) ) + { + _engine->setExitCode( ExitCode::SAT ); + return true; + } + + if ( _engine->getLpSolverType() == LPSolverType::NATIVE ) + _engine->propagateBoundManagerTightenings(); + + // Add the zero literal at the end + if ( !_literalsToPropagate.empty() ) + _literalsToPropagate.append( Pair( 0, _context.getLevel() ) ); + + if ( !_externalClauseToAdd.empty() ) + { + _engine->setExitCode( ExitCode::UNSAT ); + return false; + } + + for ( unsigned var : _cadicalVarToPlc.keys() ) + if ( var != 0 ) + _satSolverWrapper->addObservedVar( (int)var ); + + Set externalClause; + + externalClause = _satSolverWrapper->addExternalNAPClause( + Options::get()->getString( Options::NAP_EXTERNAL_CLAUSE_FILE_PATH ) ); + if ( !externalClause.empty() ) + _initialClauses.append( externalClause ); + + externalClause = _satSolverWrapper->addExternalNAPClause( + Options::get()->getString( Options::NAP_EXTERNAL_CLAUSE_FILE_PATH2 ) ); + if ( !externalClause.empty() ) + _initialClauses.append( externalClause ); + + int result = _satSolverWrapper->solve(); + + if ( _statistics && _engine->getVerbosity() ) + { + printf( "\nCdclCore::Final statistics:\n" ); + _statistics->print(); + } + + if ( result == 0 ) + { + if ( _engine->getExitCode() == ExitCode::SAT ) + return true; + else + return false; + } + else if ( result == 10 ) + { + _engine->setExitCode( ExitCode::SAT ); + return true; + } + else if ( result == 20 ) + { + _engine->setExitCode( ExitCode::UNSAT ); + return false; + } + else + { + ASSERT( false ) + } + } + catch ( const TimeoutException & ) + { + if ( _statistics ) + { + if ( _engine->getVerbosity() > 0 ) + { + printf( "\n\nCdclCore: quitting due to timeout...\n\n" ); + printf( "Final statistics:\n" ); + _statistics->print(); + } + _statistics->timeout(); + } + + _engine->setExitCode( ExitCode::TIMEOUT ); + return false; + } + + return false; +} + +void CdclCore::addLiteralToPropagate( int literal ) +{ + struct timespec start = TimeUtils::sampleMicro(); + + ASSERT( literal ) + if ( !isLiteralAssigned( literal ) && !isLiteralToBePropagated( literal ) ) + { + ASSERT( !isLiteralAssigned( -literal ) && !isLiteralToBePropagated( -literal ) ) + _literalsToPropagate.append( Pair( literal, _context.getLevel() ) ); + } + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +bool CdclCore::isLiteralToBePropagated( int literal ) const +{ + for ( const Pair &pair : _literalsToPropagate ) + if ( pair.first() == literal ) + return true; + + return false; +} + +void CdclCore::addDecisionBasedConflictClause() +{ + struct timespec start = TimeUtils::sampleMicro(); + + Set clause = Set(); + for ( int l = 1; l <= _context.getLevel(); ++l ) + { + ASSERT( _decisionLiterals.exists( l ) ); + int lit = _decisionLiterals[l]; + ASSERT( isDecision( lit ) ); + if ( !_fixedCadicalVars.exists( lit ) ) + clause.insert( lit ); + } + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, + TimeUtils::timePassed( start, end ) ); + } + + addExternalClause( clause ); +} + +void CdclCore::removeLiteralFromPropagations( int literal ) +{ + _literalsToPropagate.erase( Pair( literal, _context.getLevel() ) ); +} + +void CdclCore::phase( int literal ) +{ + CDCL_LOG( Stringf( "Phasing literal %d", literal ).ascii() ) + _satSolverWrapper->phase( literal ); + _fixedCadicalVars.insert( literal ); +} + +void CdclCore::checkIfShouldExitDueToTimeout() +{ + if ( _engine->shouldExitDueToTimeout( _timeoutInSeconds ) ) + { + throw TimeoutException(); + } +} + +bool CdclCore::terminate() +{ + CDCL_LOG( Stringf( "Callback for terminate: %d", _engine->getExitCode() != ExitCode::NOT_DONE ) + .ascii() ) + return _engine->getExitCode() != ExitCode::NOT_DONE; +} + +unsigned CdclCore::getLiteralAssignmentIndex( int literal ) +{ + struct timespec start = TimeUtils::sampleMicro(); + + if ( _assignedLiterals.count( literal ) > 0 ) + return _assignedLiterals[literal].get(); + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, + TimeUtils::timePassed( start, end ) ); + } + + return _assignedLiterals.size(); +} + +bool CdclCore::isLiteralFixed( int literal ) const +{ + return _fixedCadicalVars.exists( literal ); +} + +bool CdclCore::isClauseSatisfied( unsigned int clause ) const +{ + return _satisfiedClauses.contains( clause ); +} + +unsigned int CdclCore::getLiteralVSIDSScore( int literal ) const +{ + unsigned numOfClausesSatisfiedByLiteral = 0; + if ( _literalToClauses.exists( literal ) ) + for ( unsigned clause : _literalToClauses[literal] ) + if ( !isClauseSatisfied( clause ) ) + ++numOfClausesSatisfiedByLiteral; + return numOfClausesSatisfiedByLiteral; +} + +unsigned int CdclCore::getVariableVSIDSScore( unsigned int var ) const +{ + return getLiteralVSIDSScore( (int)var ) + getLiteralVSIDSScore( -(int)var ); +} + +unsigned CdclCore::luby( unsigned int i ) +{ + unsigned k; + for ( k = 1; k < 32; ++k ) + if ( i == (unsigned)( ( 1 << k ) - 1 ) ) + return 1 << ( k - 1 ); + + for ( k = 1;; ++k ) + if ( (unsigned)( 1 << ( k - 1 ) ) <= i && i < (unsigned)( ( 1 << k ) - 1 ) ) + return luby( i - ( 1 << ( k - 1 ) ) + 1 ); +} + +void CdclCore::notify_fixed_assignment( int lit ) +{ + if ( _engine->getExitCode() != ExitCode::NOT_DONE ) + return; + + checkIfShouldExitDueToTimeout(); + struct timespec start = TimeUtils::sampleMicro(); + + CDCL_LOG( Stringf( "Notified fixed assignment: %d", lit ).ascii() ) + if ( !isLiteralAssigned( lit ) ) + notifySingleAssignment( lit, true ); + else + _fixedCadicalVars.insert( lit ); + + if ( _statistics ) + { + struct timespec end = TimeUtils::sampleMicro(); + _statistics->incLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + TimeUtils::timePassed( start, end ) ); + _statistics->incLongAttribute( + Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_FIXED_ASSIGNMENT_MICRO, + TimeUtils::timePassed( start, end ) ); + } +} + +bool CdclCore::hasConflictClause() const +{ + return !_externalClauseToAdd.empty(); +} + +void CdclCore::notifySingleAssignment( int lit, bool isFixed ) +{ + if ( isLiteralToBePropagated( -lit ) || isLiteralAssigned( -lit ) ) + return; + + // Allow notifying a negation of assigned literal only when a conflict is already discovered + ASSERT( !isLiteralAssigned( -lit ) || !_externalClauseToAdd.empty() ) + + if ( isFixed ) + _fixedCadicalVars.insert( lit ); + + if ( isDecision( lit ) ) + _decisionLiterals[_context.getLevel()] = lit; + + // Pick the split to perform + PiecewiseLinearConstraint *plc = _cadicalVarToPlc.at( (unsigned)FloatUtils::abs( lit ) ); + DEBUG( PhaseStatus originalPlcPhase = plc->getPhaseStatus() ); + + plc->propagateLitAsSplit( lit ); + _engine->applyPlcPhaseFixingTightenings( *plc ); + plc->setActiveConstraint( false ); + + ASSERT( !isLiteralAssigned( lit ) ) + + _assignedLiterals.insert( lit, _assignedLiterals.size() ); + for ( unsigned clause : _literalToClauses[lit] ) + if ( !isClauseSatisfied( clause ) ) + _satisfiedClauses.insert( clause ); + + ASSERT( originalPlcPhase == PHASE_NOT_FIXED || plc->getPhaseStatus() == originalPlcPhase ) +} + +void CdclCore::setStatistics( Statistics *statistics ) +{ + _statistics = statistics; +} + +void CdclCore::pushContext() +{ + struct timespec start = TimeUtils::sampleMicro(); + _context.push(); + struct timespec end = TimeUtils::sampleMicro(); + + if ( _statistics ) + { + _statistics->incUnsignedAttribute( Statistics::NUM_CONTEXT_PUSHES ); + _statistics->incLongAttribute( Statistics::TIME_CONTEXT_PUSH, + TimeUtils::timePassed( start, end ) ); + } +} + +void CdclCore::popContextTo( unsigned int level ) +{ + struct timespec start = TimeUtils::sampleMicro(); + unsigned int prevLevel = _context.getLevel(); + _context.popto( (int)level ); + struct timespec end = TimeUtils::sampleMicro(); + + if ( _statistics ) + { + _statistics->incUnsignedAttribute( Statistics::NUM_CONTEXT_POPS, prevLevel - level ); + _statistics->incLongAttribute( Statistics::TIME_CONTEXT_POP, + TimeUtils::timePassed( start, end ) ); + } +} + +void CdclCore::addLiteral( int lit ) +{ + _satSolverWrapper->addLiteral( lit ); +} + +bool CdclCore::isSupported( const PiecewiseLinearConstraint *plc ) +{ + if ( plc->getType() != RELU ) + return false; + + return true; +} + +unsigned CdclCore::decideSplitVarBasedOnPolarityAndVsids() const +{ + unsigned decisionVariable = 0; + + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + ASSERT( networkLevelReasoner ) + + List constraints = + networkLevelReasoner->getConstraintsInTopologicalOrder(); + + Map polarityScoreToConstraint; + for ( auto &plConstraint : constraints ) + { + if ( _largestAssignmentSoFar.exists( plConstraint->getVariableForDecision() ) ) + if ( plConstraint->supportPolarity() && plConstraint->isActive() && + !plConstraint->phaseFixed() ) + { + plConstraint->updateScoreBasedOnPolarity(); + polarityScoreToConstraint[plConstraint->getScore()] = plConstraint; + if ( polarityScoreToConstraint.size() >= + GlobalConfiguration::POLARITY_CANDIDATES_THRESHOLD ) + break; + } + } + + for ( auto &plConstraint : constraints ) + { + if ( plConstraint->supportPolarity() && plConstraint->isActive() && + !plConstraint->phaseFixed() ) + { + plConstraint->updateScoreBasedOnPolarity(); + polarityScoreToConstraint[plConstraint->getScore()] = plConstraint; + if ( polarityScoreToConstraint.size() >= + GlobalConfiguration::POLARITY_CANDIDATES_THRESHOLD ) + break; + } + } + + if ( !polarityScoreToConstraint.empty() ) + { + double maxScore = 0; + for ( double polarityScore : polarityScoreToConstraint.keys() ) + { + unsigned var = polarityScoreToConstraint[polarityScore]->getVariableForDecision(); + double score = ( getVariableVSIDSScore( var ) + 1 ) * polarityScore; + if ( score > maxScore ) + { + decisionVariable = var; + maxScore = score; + } + } + } + + return decisionVariable; +} + +unsigned CdclCore::decideSplitVarBasedOnPseudoImpactAndVsids() const +{ + ASSERT( GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH ) + double maxScore = 0; + unsigned variableWithMaxScore = 0; + + for ( const auto &pair : _cadicalVarToPlc ) + { + unsigned var = pair.first; + if ( var == 0 ) + continue; + + PiecewiseLinearConstraint *plc = pair.second; + if ( plc->isActive() && !plc->phaseFixed() ) + { + ASSERT( !isLiteralAssigned( (int)var ) && !isLiteralAssigned( -(int)var ) ) + double pseudoImpactScore = _scoreTracker->getScore( plc ); + double vsidsScore = getVariableVSIDSScore( var ); + double score = ( vsidsScore + 1 ) * pseudoImpactScore; + if ( score >= maxScore ) + { + maxScore = score; + variableWithMaxScore = var; + } + } + } + return variableWithMaxScore; +} + +void CdclCore::initializeScoreTracker( std::shared_ptr scoreTracker ) +{ + ASSERT( GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH ) + _scoreTracker = std::move( scoreTracker ); +} + +bool CdclCore::isDecision( int lit ) +{ + return _satSolverWrapper->isDecision( lit ); +} + +double CdclCore::computeDecisionScoreForLiteral( int literal ) const +{ + ASSERT( literal != 0 ); + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + + setInputBoundsForLiteralInNLR( literal, inputQuery, networkLevelReasoner ); + runSymbolicBoundTightening( networkLevelReasoner ); + + return getUpperBoundForOutputVariableFromNLR( networkLevelReasoner ); +} + +void CdclCore::setInputBoundsForLiteralInNLR( + int literal, + const std::shared_ptr &inputQuery, + NLR::NetworkLevelReasoner *networkLevelReasoner ) const +{ + const auto &layers = networkLevelReasoner->getLayerIndexToLayer(); + const PiecewiseLinearConstraint *plc = _cadicalVarToPlc[abs( literal )]; + for ( unsigned variable : plc->getParticipatingVariables() ) + { + NLR::NeuronIndex neuronIndex = networkLevelReasoner->variableToNeuron( variable ); + if ( layers[neuronIndex._layer]->getLayerType() == NLR::Layer::RELU ) + { + if ( literal < 0 ) + networkLevelReasoner->setBounds( neuronIndex._layer, neuronIndex._neuron, 0, 0 ); + else + networkLevelReasoner->setBounds( + neuronIndex._layer, + neuronIndex._neuron, + FloatUtils::max( inputQuery->getLowerBound( variable ), 0 ), + inputQuery->getUpperBound( variable ) ); + } + + if ( neuronIndex._layer != 0 || neuronIndex._neuron != 0 ) + break; + } +} + +void CdclCore::runSymbolicBoundTightening( NLR::NetworkLevelReasoner *networkLevelReasoner ) const +{ + if ( _engine->getSymbolicBoundTighteningType() == + SymbolicBoundTighteningType::SYMBOLIC_BOUND_TIGHTENING ) + networkLevelReasoner->symbolicBoundPropagation(); + else if ( _engine->getSymbolicBoundTighteningType() == SymbolicBoundTighteningType::DEEP_POLY ) + networkLevelReasoner->deepPolyPropagation(); +} + +double CdclCore::getUpperBoundForOutputVariableFromNLR( + NLR::NetworkLevelReasoner *networkLevelReasoner ) const +{ + Map, double> outputBounds; + networkLevelReasoner->getOutputBounds( outputBounds ); + + if ( GlobalConfiguration::CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY ) + { + List outputVariables = _engine->getOutputVariables(); + ASSERT( outputVariables.size() == 1 ); + unsigned outputVariable = outputVariables.front(); + + if ( outputBounds.exists( Pair( outputVariable, Tightening::UB ) ) ) + return outputBounds[Pair( outputVariable, Tightening::UB )]; + } + else + { + double outputUb = 0; + std::shared_ptr inputQuery = _engine->getInputQuery(); + const NLR::Layer *outputLayer = + networkLevelReasoner->getLayer( networkLevelReasoner->getNumberOfLayers() - 1 ); + + for ( unsigned neuron = 0; neuron < outputLayer->getSize(); ++neuron ) + { + unsigned variable = outputLayer->neuronToVariable( neuron ); + + double lb; + if ( outputBounds.exists( Pair( variable, Tightening::LB ) ) ) + lb = outputBounds[Pair( variable, Tightening::LB )]; + else + lb = inputQuery->getLowerBound( variable ); + outputUb -= FloatUtils::max( lb, 0 ); + + double ub; + if ( outputBounds.exists( Pair( variable, Tightening::UB ) ) ) + ub = outputBounds[Pair( variable, Tightening::UB )]; + else + ub = inputQuery->getUpperBound( variable ); + outputUb -= FloatUtils::max( -ub, 0 ); + } + + return outputUb; + } + + return FloatUtils::infinity(); +} + +void CdclCore::computeClauseScores( const Set &clause, + Vector> &clauseScores ) +{ + for ( int literal : clause ) + { + if ( !_decisionScores.exists( literal ) ) + _decisionScores[literal] = computeDecisionScoreForLiteral( literal ); + clauseScores.append( Pair( _decisionScores[literal], literal ) ); + } + clauseScores.sort(); +} + +void CdclCore::reorderByDecisionLevelIfNecessary( Vector> &clauseScores ) +{ + if ( !clauseScores.empty() && + clauseScores[0].first() == clauseScores[clauseScores.size() - 1].first() ) + { + double score = clauseScores[0].first(); + clauseScores.clear(); + for ( int level = 1; level <= _context.getLevel(); ++level ) + { + ASSERT( _decisionLiterals.exists( level ) ); + clauseScores.append( Pair( score, _decisionLiterals[level] ) ); + } + } +} + +void CdclCore::computeShortedClause( Set &clause, + const Vector> &clauseScores, + int propagated_lit ) const +{ + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + + if ( GlobalConfiguration::CDCL_SHORTEN_CLAUSES_WITH_QUICKXPLAIN ) + { + clause = quickXplain( Set(), clauseScores, 0, clauseScores.size(), propagated_lit ); + } + else + { + if ( propagated_lit != 0 ) + setInputBoundsForLiteralInNLR( -propagated_lit, inputQuery, networkLevelReasoner ); + + for ( const auto &pair : clauseScores ) + { + double score = pair.first(); + int literal = pair.second(); + + ASSERT( literal != 0 ); + + clause.insert( literal ); + + double outputUb = FloatUtils::infinity(); + if ( clause.size() == 1 ) + outputUb = score; + else + { + setInputBoundsForLiteralInNLR( literal, inputQuery, networkLevelReasoner ); + runSymbolicBoundTightening( networkLevelReasoner ); + outputUb = getUpperBoundForOutputVariableFromNLR( networkLevelReasoner ); + } + + if ( GlobalConfiguration::CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY ) + { + List outputVariables = _engine->getOutputVariables(); + ASSERT( outputVariables.size() == 1 ); + unsigned outputVariable = outputVariables.front(); + if ( FloatUtils::lt( outputUb, inputQuery->getLowerBound( outputVariable ) ) ) + break; + } + else + { + if ( FloatUtils::isNegative( outputUb ) ) + break; + } + } + } +} + +bool CdclCore::checkIfShouldSkipClauseShortening( const Set &clause ) +{ + if ( clause.empty() ) + return true; + + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + + for ( int literal : clause ) + setInputBoundsForLiteralInNLR( literal, inputQuery, networkLevelReasoner ); + + runSymbolicBoundTightening( networkLevelReasoner ); + double outputUb = getUpperBoundForOutputVariableFromNLR( networkLevelReasoner ); + + if ( GlobalConfiguration::CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY ) + { + List outputVariables = _engine->getOutputVariables(); + ASSERT( outputVariables.size() == 1 ); + unsigned outputVariable = outputVariables.front(); + if ( outputUb >= inputQuery->getLowerBound( outputVariable ) ) + return true; + } + else + { + if ( outputUb >= 0 ) + return true; + } + + return false; +} + +Set CdclCore::quickXplain( const Set ¤tClause, + const Vector> &clauseScores, + unsigned int startIdx, + unsigned int endIdx, + int propagated_lit ) const +{ + std::shared_ptr inputQuery = _engine->getInputQuery(); + NLR::NetworkLevelReasoner *networkLevelReasoner = _engine->getNetworkLevelReasoner(); + networkLevelReasoner->obtainCurrentBounds( *inputQuery ); + + if ( propagated_lit != 0 ) + setInputBoundsForLiteralInNLR( -propagated_lit, inputQuery, networkLevelReasoner ); + + for ( int lit : currentClause ) + setInputBoundsForLiteralInNLR( lit, inputQuery, networkLevelReasoner ); + + runSymbolicBoundTightening( networkLevelReasoner ); + double outputUb = getUpperBoundForOutputVariableFromNLR( networkLevelReasoner ); + + if ( !currentClause.empty() && outputUb < 0 ) + return Set(); + + if ( endIdx == startIdx + 1 ) + return Set( { clauseScores[startIdx].second() } ); + + unsigned mid = ( startIdx + endIdx ) / 2; + Set currentClause1; + for ( unsigned i = startIdx; i < mid; ++i ) + currentClause1.insert( clauseScores[i].second() ); + + Set clause1 = quickXplain( currentClause1, clauseScores, mid, endIdx, propagated_lit ); + Set clause2 = quickXplain( clause1, clauseScores, startIdx, mid, propagated_lit ); + + return clause1 + clause2; +} + +#endif \ No newline at end of file diff --git a/src/cdcl/CdclCore.h b/src/cdcl/CdclCore.h new file mode 100644 index 0000000000..1b1085cae9 --- /dev/null +++ b/src/cdcl/CdclCore.h @@ -0,0 +1,321 @@ +/********************* */ +/*! \file CdclCore.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __CdclCore_h__ +#define __CdclCore_h__ + +#ifdef BUILD_CADICAL + +#include "CadicalWrapper.h" +#include "IEngine.h" +#include "InputQuery.h" +#include "PLConstraintScoreTracker.h" +#include "Pair.h" +#include "PiecewiseLinearConstraint.h" +#include "Statistics.h" +#include "context/cdhashmap.h" +#include "context/cdhashset.h" + +#include + +#define CDCL_LOG( x, ... ) LOG( GlobalConfiguration::CDCL_LOGGING, "CDCL: %s\n", x ) + +using CVC4::context::Context; + +typedef Set Clause; + +class CdclCore + : CaDiCaL::ExternalPropagator + , CaDiCaL::Terminator + , CaDiCaL::FixedAssignmentListener +{ +public: + explicit CdclCore( IEngine *engine ); + ~CdclCore() override; + + /* + Have the CDCL core start reporting statistics. + */ + void setStatistics( Statistics *statistics ); + + /* + Initializes the boolean abstraction for a PiecewiseLinearConstraint object + */ + void initBooleanAbstraction( PiecewiseLinearConstraint *plc ); + + /* + Push _context, record statistics + */ + void pushContext(); + + /* + Pop _context to given level, record statistics + */ + void popContextTo( unsigned level ); + + /* + Add valid literal to clause or zero to terminate clause. + */ + void addLiteral( int lit ); + + /* + Calls the solving method combining the SAT solver with Marabou back engine + */ + bool solveWithCDCL( double timeoutInSeconds ); + + /**********************************************************************/ + /* IPASIR-UP functions, for integrating Marabou with the SAT solver */ + /**********************************************************************/ + + /* + Notify Marabou about an assignment of a boolean (abstract) variable + */ + void notify_assignment( const std::vector &lits ) override; + + /* + Notify Marabou about a new decision level + */ + void notify_new_decision_level() override; + + /* + Notify Marabou should backtrack to new_level decision level + */ + void notify_backtrack( size_t new_level ) override; + + /* + Callback from the SAT solver that calls Marabou to check a full assignment of the boolean + (abstract) variables + */ + bool cb_check_found_model( const std::vector &model ) override; + + /* + Callback from the SAT solver that allows Marabou to decide a boolean (abstract) variable to + split on + */ + int cb_decide() override; + + /* + Callback from the SAT solver that enables Marabou propagate literals leanred based on a + partial assignment + */ + int cb_propagate() override; + + /* + Callback from the SAT solver that requires Marabou to explain a propagation. + Returns a literal in the explanation clause one at a time, including the literal to explain. + Ends with 0. + */ + int cb_add_reason_clause_lit( int propagated_lit ) override; + + /* + Check if Marabou has a conflict clause to inform the SAT solver + */ + bool cb_has_external_clause( bool &is_forgettable ) override; + /* + Add conflict clause from Marabou to the SAT solver, one literal at a time. Ends with 0. + */ + int cb_add_external_clause_lit() override; + + /* + Internally adds a conflict clause when learned, later to be informed to the SAT solver + */ + void addExternalClause( Set &clause ); + + /* + Returns the PiecewiseLinearConstraint abstraced by the literal lit + */ + const PiecewiseLinearConstraint *getConstraintFromLit( int lit ) const; + + /* + Internally adds a literal, when learned, later to be informed to the SAT solver + */ + void addLiteralToPropagate( int literal ); + + /* + Adds the decision-based conflict clause (negation of all decisions), except the given literal, + to Marabou, later to be propagated + */ + void addDecisionBasedConflictClause(); + + /* + Remove a literal from the propagation list + */ + void removeLiteralFromPropagations( int literal ); + + /* + Force the default decision phase of a variable to a certain value. + */ + void phase( int literal ); + + /* + Check if the solver should stop due to the requested timeout by the user + */ + void checkIfShouldExitDueToTimeout(); + + /* + Connected terminators are checked for termination regularly. If the + 'terminate' function of the terminator returns true the solver is + terminated synchronously as soon it calls this function. + */ + bool terminate() override; + + /* + Get the index of an assigned literal in the assigned literals list + return the size of the list if element not found + */ + unsigned getLiteralAssignmentIndex( int literal ); + + /* + Return true iff the literal is fixed by the SAT solver + */ + bool isLiteralFixed( int literal ) const; + + /* + Notifying on a fixed literal assignment. + */ + void notify_fixed_assignment( int lit ) override; + + /* + Notify about a single assignment + */ + void notifySingleAssignment( int lit, bool isFixed ); + + /* + Returns true if a conflict clause exists + */ + bool hasConflictClause() const; + + /* + Check if the given piecewise-linear constraint is currently supported by CDCL + */ + static bool isSupported( const PiecewiseLinearConstraint *plc ); + + /* + Initialize score stracker for pseudo-impact based decisions. + */ + void initializeScoreTracker( std::shared_ptr scoreTracker ); + + bool isDecision( int lit ); + +private: + /* + The engine. + */ + IEngine *_engine; + + /* + Context for synchronizing the search. + */ + Context &_context; + + /* + Collect and print various statistics. + */ + Statistics *_statistics; + + /* + SAT solver object + */ + SatSolverWrapper *_satSolverWrapper; + + /* + Boolean abstraction map, from boolean variables to the PiecewiseLinearConstraint they + represent + */ + Map _cadicalVarToPlc; + + /* + Internal data structures to keep track of literals to propagate, assigned and fixed literals; + and reason and conflict clauses + */ + List> _literalsToPropagate; + CVC4::context::CDHashMap _assignedLiterals; + + Vector _reasonClauseLiterals; + bool _isReasonClauseInitialized; + + Vector _externalClauseToAdd; + + Set _fixedCadicalVars; + + double _timeoutInSeconds; + + unsigned _numOfClauses; + CVC4::context::CDHashSet> _satisfiedClauses; + Map> _literalToClauses; + unsigned _vsidsDecayThreshold; + unsigned _vsidsDecayCounter; + + unsigned _restarts; + unsigned _restartLimit; + unsigned _numOfConflictClauses; + bool _shouldRestart; + + HashMap _largestAssignmentSoFar; + + Vector> _initialClauses; + + std::shared_ptr _scoreTracker; + + Map _decisionLiterals; + Map _storedLowerBounds; + Map _storedUpperBounds; + + Map _decisionScores; + + /* + Decision heuristics + */ + unsigned decideSplitVarBasedOnPolarityAndVsids() const; + unsigned decideSplitVarBasedOnPseudoImpactAndVsids() const; + + + /* + Access info in the internal data structures + */ + bool isLiteralAssigned( int literal ) const; + bool isLiteralToBePropagated( int literal ) const; + + bool isClauseSatisfied( unsigned clause ) const; + unsigned int getLiteralVSIDSScore( int literal ) const; + unsigned int getVariableVSIDSScore( unsigned var ) const; + + unsigned luby( unsigned i ); + + double computeDecisionScoreForLiteral( int literal ) const; + void setInputBoundsForLiteralInNLR( int literal, + const std::shared_ptr &inputQuery, + NLR::NetworkLevelReasoner *networkLevelReasoner ) const; + void runSymbolicBoundTightening( NLR::NetworkLevelReasoner *networkLevelReasoner ) const; + + double + getUpperBoundForOutputVariableFromNLR( NLR::NetworkLevelReasoner *networkLevelReasoner ) const; + + void computeClauseScores( const Set &clause, Vector> &clauseScores ); + void reorderByDecisionLevelIfNecessary( Vector> &clauseScores ); + void computeShortedClause( Set &clause, + const Vector> &clauseScores, + int propagated_lit ) const; + bool checkIfShouldSkipClauseShortening( const Set &clause ); + + Set quickXplain( const Set ¤tClause, + const Vector> &clauseScores, + unsigned int startIdx, + unsigned int endIdx, + int propagated_lit ) const; +}; + +#endif +#endif // __CdclCore_h__ diff --git a/src/cdcl/GroundBoundManager.cpp b/src/cdcl/GroundBoundManager.cpp new file mode 100644 index 0000000000..50950fa94b --- /dev/null +++ b/src/cdcl/GroundBoundManager.cpp @@ -0,0 +1,178 @@ +/********************* */ +/*! \file GroundBoundManager.cpp + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#include "GroundBoundManager.h" + +#include "FloatUtils.h" + + +GroundBoundManager::GroundBoundManager( CVC4::context::Context &ctx ) + : _context( ctx ) + , _size( 0 ) + , _upperGroundBounds( 0 ) + , _lowerGroundBounds( 0 ) +{ + _counter = new ( true ) CVC4::context::CDO( &_context, 0 ); +} +GroundBoundManager::~GroundBoundManager() +{ + _counter->deleteSelf(); + for ( unsigned i = 0; i < _size; ++i ) + { + _upperGroundBounds[i]->deleteSelf(); + _lowerGroundBounds[i]->deleteSelf(); + } +} + +void GroundBoundManager::initialize( unsigned size ) +{ + _counter->set( 0 ); + _size = size; + + for ( unsigned i = 0; i < size; ++i ) + { + _upperGroundBounds.append( + new ( true ) CVC4::context::CDList>( &_context ) ); + _lowerGroundBounds.append( + new ( true ) CVC4::context::CDList>( &_context ) ); + } +} + +std::shared_ptr +GroundBoundManager::addGroundBound( unsigned index, + double value, + Tightening::BoundType boundType, + bool isPhaseFixing ) +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + std::shared_ptr groundBoundEntry( + new GroundBoundEntry( _counter->get(), value, nullptr, Set(), isPhaseFixing ) ); + + + if ( !temp[index]->empty() ) + { + ASSERT( boundType == Tightening::UB ? FloatUtils::lte( value, temp[index]->back()->val ) + : FloatUtils::gte( value, temp[index]->back()->val ) ) + } + + temp[index]->push_back( groundBoundEntry ); + _counter->set( _counter->get() + 1 ); + + return temp[index]->back(); +} + +std::shared_ptr +GroundBoundManager::addGroundBound( const std::shared_ptr &lemma, bool isPhaseFixing ) +{ + Tightening::BoundType isUpper = lemma->getAffectedVarBound(); + unsigned index = lemma->getAffectedVar(); + const Vector> *> &temp = + isUpper == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + std::shared_ptr groundBoundEntry( new GroundBoundEntry( + _counter->get(), lemma->getBound(), lemma, Set(), isPhaseFixing ) ); + + if ( !temp[index]->empty() ) + { + ASSERT( isUpper == Tightening::UB + ? FloatUtils::lte( lemma->getBound(), temp[index]->back()->val ) + : FloatUtils::gte( lemma->getBound(), temp[index]->back()->val ) ) + } + + temp[index]->push_back( groundBoundEntry ); + _counter->set( _counter->get() + 1 ); + + return temp[index]->back(); +} + +double GroundBoundManager::getGroundBound( unsigned index, Tightening::BoundType boundType ) const +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + return temp[index]->back()->val; +} + +std::shared_ptr +GroundBoundManager::getGroundBoundEntry( unsigned int index, Tightening::BoundType boundType ) const +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + return temp[index]->back(); +} + +std::shared_ptr +GroundBoundManager::getGroundBoundEntryUpToId( unsigned index, + Tightening::BoundType boundType, + unsigned id ) const +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + + if ( id == 0 ) + return ( *temp[index] )[0]; + + for ( int i = temp[index]->size() - 1; i >= 0; --i ) + { + const std::shared_ptr entry = ( *temp[index] )[i]; + if ( entry->id < id ) + return entry; + } + ASSERT( id <= 2 * temp.size() ); + return ( *temp[index] )[0]; +} + +double GroundBoundManager::getGroundBoundUpToId( unsigned index, + Tightening::BoundType boundType, + unsigned id ) const +{ + return getGroundBoundEntryUpToId( index, boundType, id )->val; +} + +Vector GroundBoundManager::getAllGroundBounds( Tightening::BoundType boundType ) const +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + Vector tops = Vector( 0 ); + + for ( const auto &GBList : temp ) + tops.append( GBList->back()->val ); + + return tops; +} + +unsigned GroundBoundManager::getCounter() const +{ + return _counter->get(); +} + +void GroundBoundManager::addClauseToGroundBoundEntry( + const std::shared_ptr &entry, + const Set &clause ) +{ + entry->clause = clause; +} + +Vector +GroundBoundManager::getAllInitialGroundBounds( Tightening::BoundType boundType ) const +{ + const Vector> *> &temp = + boundType == Tightening::UB ? _upperGroundBounds : _lowerGroundBounds; + Vector bots = Vector( 0 ); + + for ( const auto &GBList : temp ) + bots.append( ( *GBList->begin() )->val ); + + return bots; +} diff --git a/src/cdcl/GroundBoundManager.h b/src/cdcl/GroundBoundManager.h new file mode 100644 index 0000000000..088d641c9e --- /dev/null +++ b/src/cdcl/GroundBoundManager.h @@ -0,0 +1,94 @@ +/********************* */ +/*! \file GroundBoundManager.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __GroundBoundManager_h__ +#define __GroundBoundManager_h__ + +#include "PlcLemma.h" +#include "Set.h" +#include "Tightening.h" +#include "Vector.h" +#include "context/cdlist.h" +#include "context/cdo.h" +#include "context/context.h" + +class GroundBoundManager +{ +public: + struct GroundBoundEntry + { + GroundBoundEntry( unsigned id, + double val, + const std::shared_ptr &lemma, + const Set &clause, + bool isPhaseFixing ) + : id( id ) + , val( val ) + , lemma( lemma ) + , clause( clause ) + , isPhaseFixing( isPhaseFixing ) + { + } + unsigned id; + double val; + const std::shared_ptr lemma; + Set clause; + bool isPhaseFixing; + Set> depList; + }; + + GroundBoundManager( CVC4::context::Context &ctx ); + ~GroundBoundManager(); + + void initialize( unsigned size ); + std::shared_ptr + addGroundBound( unsigned index, + double value, + Tightening::BoundType boundType, + bool isPhaseFixing ); + std::shared_ptr + addGroundBound( const std::shared_ptr &lemma, bool isPhaseFixing ); + + double getGroundBound( unsigned index, Tightening::BoundType boundType ) const; + + std::shared_ptr + getGroundBoundEntry( unsigned index, Tightening::BoundType boundType ) const; + + std::shared_ptr + getGroundBoundEntryUpToId( unsigned index, Tightening::BoundType boundType, unsigned id ) const; + + double + getGroundBoundUpToId( unsigned index, Tightening::BoundType boundType, unsigned id ) const; + Vector getAllGroundBounds( Tightening::BoundType boundType ) const; + Vector getAllInitialGroundBounds( Tightening::BoundType boundType ) const; + + unsigned getCounter() const; + + void + addClauseToGroundBoundEntry( const std::shared_ptr &entry, + const Set &clause ); + +private: + CVC4::context::CDO *_counter; + + CVC4::context::Context &_context; + unsigned _size; + + Vector> *> _upperGroundBounds; + Vector> *> _lowerGroundBounds; +}; + + +#endif // __GroundBoundManager_h__ diff --git a/src/cdcl/SatSolverWrapper.h b/src/cdcl/SatSolverWrapper.h new file mode 100644 index 0000000000..26b7f023df --- /dev/null +++ b/src/cdcl/SatSolverWrapper.h @@ -0,0 +1,86 @@ +/********************* */ +/*! \file SatSolverWrapper.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli, Omri Isac + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2025 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __SatSolverWrapper_h__ +#define __SatSolverWrapper_h__ + +#include "Map.h" +#include "Set.h" + +class SatSolverWrapper +{ +public: + virtual ~SatSolverWrapper() = default; + + /* + Add valid literal to clause or zero to terminate clause. + */ + virtual void addLiteral( int lit ) = 0; + + /* + Add a clause to the solver + */ + virtual void addClause( const Set &clause ) = 0; + + /* + Assume valid non zero literal for next call to 'solve'. + */ + virtual void assume( int lit ) = 0; + + /* + Force the default decision phase of a variable to a certain value. + */ + virtual void phase( int lit ) = 0; + + /* + Try to solve the current formula. + */ + virtual int solve() = 0; + + /* + Get value (-lit=false, lit=true) of valid non-zero literal. + */ + virtual int val( int lit ) = 0; + + /* + Try to flip the value of the given literal without falsifying the formula. + */ + virtual void flip( int lit ) = 0; + + /* + Mark as 'observed' those variables that are relevant to the theory solver. + */ + virtual void addObservedVar( int var ) = 0; + + /* + Get reason of valid observed literal. + */ + virtual bool isDecision( int observedVar ) const = 0; + + /* + Return the number of vars; + */ + virtual int vars() = 0; + + /* + * Forces backtracking to the given level + */ + virtual void forceBacktrack( size_t newLevel ) = 0; + + virtual Set addExternalNAPClause( const String &externalNAPClauseFilename ) = 0; +}; + + +#endif // __SatSolverWrapper_h__ diff --git a/src/cegar/IncrementalLinearization.cpp b/src/cegar/IncrementalLinearization.cpp index 6677285272..7500cb5130 100644 --- a/src/cegar/IncrementalLinearization.cpp +++ b/src/cegar/IncrementalLinearization.cpp @@ -51,6 +51,12 @@ void IncrementalLinearization::solve() 1. _inputQuery contains the assignment in the previous refinement round 2. _timeoutInMicroSeconds is positive */ + if ( !GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH ) + { + printf( "Using incremental linearization requires DeepSOI. Turning it on\n" ); + GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH = true; + } + while ( true ) { struct timespec start = TimeUtils::sampleMicro(); @@ -81,7 +87,7 @@ void IncrementalLinearization::solve() _engine->solve( timeoutInSeconds ); } - if ( _engine->getExitCode() == IEngine::UNKNOWN ) + if ( _engine->getExitCode() == ExitCode::UNKNOWN ) { unsigned long long timePassed = TimeUtils::timePassed( start, TimeUtils::sampleMicro() ); diff --git a/src/cegar/tests/Test_IncrementalLinearization.h b/src/cegar/tests/Test_IncrementalLinearization.h index f6374a903f..de39c6c9ae 100644 --- a/src/cegar/tests/Test_IncrementalLinearization.h +++ b/src/cegar/tests/Test_IncrementalLinearization.h @@ -59,8 +59,8 @@ class IncrementalLinearizationTestSuite : public CxxTest::TestSuite engine.setVerbosity( 2 ); TS_ASSERT_THROWS_NOTHING( engine.processInputQuery( ipq ) ); TS_ASSERT_THROWS_NOTHING( engine.solve() ); - Engine::ExitCode code = engine.getExitCode(); - TS_ASSERT( code == Engine::SAT || code == Engine::UNKNOWN ); + ExitCode code = engine.getExitCode(); + TS_ASSERT( code == ExitCode::SAT || code == ExitCode::UNKNOWN ); } void _test_incremental_linearization_sigmoid() @@ -117,10 +117,9 @@ class IncrementalLinearizationTestSuite : public CxxTest::TestSuite TS_ASSERT( initialEngine->processInputQuery( ipq ) ); TS_ASSERT_THROWS_NOTHING( initialEngine->solve() ); - TS_ASSERT( initialEngine->getExitCode() == Engine::UNKNOWN ); - TS_ASSERT( initialEngine->getExitCode() == Engine::UNKNOWN || - initialEngine->getExitCode() == Engine::SAT ); - if ( initialEngine->getExitCode() == Engine::SAT ) + TS_ASSERT( initialEngine->getExitCode() == ExitCode::UNKNOWN || + initialEngine->getExitCode() == ExitCode::SAT ); + if ( initialEngine->getExitCode() == ExitCode::SAT ) { delete initialEngine; return; @@ -135,7 +134,7 @@ class IncrementalLinearizationTestSuite : public CxxTest::TestSuite std::cout << ipq.getSolutionValue( 0 ) << std::endl; std::cout << ipq.getSolutionValue( 1 ) << std::endl; - TS_ASSERT_EQUALS( afterEngine->getExitCode(), Engine::SAT ); + TS_ASSERT_EQUALS( afterEngine->getExitCode(), ExitCode::SAT ); if ( afterEngine ) delete afterEngine; diff --git a/src/common/GurobiWrapper.cpp b/src/common/GurobiWrapper.cpp index 216e3cf62d..2848ccb884 100644 --- a/src/common/GurobiWrapper.cpp +++ b/src/common/GurobiWrapper.cpp @@ -443,6 +443,44 @@ void GurobiWrapper::log( const String &message ) printf( "GurobiWrapper: %s\n", message.ascii() ); } +void GurobiWrapper::computeIIS( int method ) +{ + try + { + _model->getEnv().set( GRB_IntParam_IISMethod, method ); + _model->computeIIS(); + } + catch ( GRBException e ) + { + throw CommonError( CommonError::GUROBI_EXCEPTION, + Stringf( "Gurobi exception. Gurobi Code: %u, message: %s\n", + e.getErrorCode(), + e.getMessage().c_str() ) + .ascii() ); + } +} + +void GurobiWrapper::extractIIS( Map &bounds, + List &constraints, + const List &constraintNames ) +{ + for ( const auto &variable : _nameToVariable ) + { + if ( variable.second->get( GRB_IntAttr_IISLB ) ) + bounds[variable.first] = IIS_LB; + if ( variable.second->get( GRB_IntAttr_IISUB ) ) + bounds[variable.first] = IIS_UB; + if ( variable.second->get( GRB_IntAttr_IISLB ) && + variable.second->get( GRB_IntAttr_IISUB ) ) + bounds[variable.first] = IIS_BOTH; + } + + for ( const auto &name : constraintNames ) + { + if ( _model->getConstrByName( name.ascii() ).get( GRB_IntAttr_IISConstr ) ) + constraints.append( name ); + } +} #endif // ENABLE_GUROBI // diff --git a/src/common/GurobiWrapper.h b/src/common/GurobiWrapper.h index 00798b8039..f776e6155e 100644 --- a/src/common/GurobiWrapper.h +++ b/src/common/GurobiWrapper.h @@ -31,6 +31,12 @@ class GurobiWrapper INTEGER = 2, }; + enum IISBoundType { + IIS_LB = 0, + IIS_UB = 1, + IIS_BOTH = 2 + }; + /* A term has the form: coefficient * variable */ @@ -212,6 +218,15 @@ class GurobiWrapper // default void dumpModel( String name ); + // Compute the UNSAT core in case of infeasibility + void computeIIS( int method = 0 ); + + // Extract the list of bound participating in an IIS, assuming it is computed + void extractIIS( Map &bounds, + List &constraints, + const List &constraintNames ); + + private: GRBEnv *_environment; GRBModel *_model; @@ -250,6 +265,12 @@ class GurobiWrapper INTEGER = 2, }; + enum IISBoundType { + IIS_LB = 0, + IIS_UB = 1, + IIS_BOTH = 2 + }; + struct Term { Term( double, String ) @@ -263,6 +284,7 @@ class GurobiWrapper GurobiWrapper() { } + ~GurobiWrapper() { } @@ -271,121 +293,185 @@ class GurobiWrapper { (void)type; } - void setLowerBound( String, double ){}; - void setUpperBound( String, double ){}; + + void setLowerBound( String, double ) + { + } + + void setUpperBound( String, double ) + { + } + double getLowerBound( const String & ) { return 0; - }; + } + double getUpperBound( const String & ) { return 0; - }; + } + void addLeqConstraint( const List &, double ) { } + void addGeqConstraint( const List &, double ) { } + void addEqConstraint( const List &, double ) { } + void addPiecewiseLinearConstraint( String, String, unsigned, const double *, const double * ) { } + void addLeqIndicatorConstraint( const String, const int, const List &, double ) { } + void addGeqIndicatorConstraint( const String, const int, const List &, double ) { } + void addEqIndicatorConstraint( const String, const int, const List &, double ) { } + void addBilinearConstraint( const String, const String, const String ) { } + void setCost( const List &, double /* constant */ = 0 ) { } + void setObjective( const List &, double /* constant */ = 0 ) { } + double getOptimalCostOrObjective() { return 0; - }; - void setCutoff( double ){}; + } + + void setCutoff( double ) + { + } + void solve() { } + void extractSolution( Map &, double & ) { } + void reset() { } + void resetModel() { } + bool optimal() { return true; } + bool cutoffOccurred() { return false; - }; + } + bool infeasible() { return false; - }; + } + bool timeout() { return false; - }; + } + bool haveFeasibleSolution() { return true; - }; - void setTimeLimit( double ){}; - void setVerbosity( unsigned ){}; + } + + void setTimeLimit( double ) + { + } + + void setVerbosity( unsigned ) + { + } + bool containsVariable( String /*name*/ ) const { return false; - }; - void setNumberOfThreads( unsigned ){}; - void nonConvex(){}; + } + + void setNumberOfThreads( unsigned ) + { + } + + void nonConvex() + { + } + double getObjectiveBound() { return 0; - }; + } + double getAssignment( const String & ) { return 0; - }; + } + unsigned getNumberOfSimplexIterations() { return 0; - }; + } + unsigned getNumberOfNodes() { return 0; - }; + } + unsigned getStatusCode() { return 0; - }; - void updateModel(){}; + } + + void updateModel() + { + } + bool existsAssignment( const String & ) { return false; - }; + } void dump() { } + static void log( const String & ); + + void computeIIS( int /*method*/ = 0 ) + { + } + + void extractIIS( Map & /*bounds*/, + List & /*constraints*/, + const List & /*constraintNames*/ ) + { + } }; #endif // ENABLE_GUROBI diff --git a/src/common/List.h b/src/common/List.h index 1111d6b6c4..ca2687607d 100644 --- a/src/common/List.h +++ b/src/common/List.h @@ -183,6 +183,17 @@ template class List _container.pop_back(); } + T popFront() + { + if ( empty() ) + throw CommonError( CommonError::LIST_IS_EMPTY ); + + T head = front(); + _container.pop_front(); + + return head; + } + template void removeIf( Predicate p ) { _container.remove_if( p ); diff --git a/src/common/Pair.h b/src/common/Pair.h index 7cf7d46b5e..f4ca7a9d43 100644 --- a/src/common/Pair.h +++ b/src/common/Pair.h @@ -33,6 +33,11 @@ template class Pair { } + Pair( const Pair &other ) + : _container( other._container ) + { + } + L &first() { return _container.first; diff --git a/src/common/Statistics.cpp b/src/common/Statistics.cpp index b9f7e72e52..ea581d7810 100644 --- a/src/common/Statistics.cpp +++ b/src/common/Statistics.cpp @@ -24,10 +24,12 @@ Statistics::Statistics() _unsignedAttributes[NUM_PL_CONSTRAINTS] = 0; _unsignedAttributes[NUM_ACTIVE_PL_CONSTRAINTS] = 0; _unsignedAttributes[NUM_PL_VALID_SPLITS] = 0; - _unsignedAttributes[NUM_PL_SMT_ORIGINATED_SPLITS] = 0; + _unsignedAttributes[NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS] = 0; _unsignedAttributes[NUM_PRECISION_RESTORATIONS] = 0; _unsignedAttributes[CURRENT_DECISION_LEVEL] = 0; _unsignedAttributes[MAX_DECISION_LEVEL] = 0; + _unsignedAttributes[NUM_DECISION_LEVELS] = 1; + _unsignedAttributes[SUM_DECISION_LEVELS] = 0; _unsignedAttributes[NUM_SPLITS] = 0; _unsignedAttributes[NUM_POPS] = 0; _unsignedAttributes[NUM_CONTEXT_PUSHES] = 0; @@ -43,7 +45,14 @@ Statistics::Statistics() _unsignedAttributes[NUM_CERTIFIED_LEAVES] = 0; _unsignedAttributes[NUM_DELEGATED_LEAVES] = 0; _unsignedAttributes[NUM_LEMMAS] = 0; + _unsignedAttributes[NUM_LEMMAS_USED] = 0; _unsignedAttributes[CERTIFIED_UNSAT] = 0; + _unsignedAttributes[MAX_BACKJUMP] = 0; + _unsignedAttributes[NUM_BACKJUMPS] = 0; + _unsignedAttributes[SUM_BACKJUMPS] = 0; + _unsignedAttributes[NUM_SAT_SOLVER_DECISIONS] = 0; + _unsignedAttributes[NUM_MARABOU_DECISIONS] = 0; + _unsignedAttributes[NUM_RESTARTS] = 0; _longAttributes[NUM_MAIN_LOOP_ITERATIONS] = 0; _longAttributes[NUM_SIMPLEX_STEPS] = 0; @@ -82,7 +91,17 @@ Statistics::Statistics() _longAttributes[TOTAL_TIME_PRECISION_RESTORATION] = 0; _longAttributes[TOTAL_TIME_CONSTRAINT_MATRIX_BOUND_TIGHTENING_MICRO] = 0; _longAttributes[TOTAL_TIME_APPLYING_STORED_TIGHTENINGS_MICRO] = 0; - _longAttributes[TOTAL_TIME_SMT_CORE_MICRO] = 0; + _longAttributes[TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_NOTIFY_ASSIGNMENT_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_NOTIFY_NEW_DECISION_LEVEL_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_NOTIFY_BACKTRACK_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_NOTIFY_FIXED_ASSIGNMENT_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_CB_DECIDE_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_CB_ADD_REASON_CLAUSE_LIT_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_CB_ADD_EXTERNAL_CLAUSE_LIT_MICRO] = 0; + _longAttributes[TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO] = 0; _longAttributes[TOTAL_TIME_UPDATING_SOI_PHASE_PATTERN_MICRO] = 0; _longAttributes[NUM_PROPOSED_PHASE_PATTERN_UPDATE] = 0; _longAttributes[NUM_ACCEPTED_PHASE_PATTERN_UPDATE] = 0; @@ -130,7 +149,8 @@ void Statistics::print() seconds = timeMainLoopMicro / 1000000; minutes = seconds / 60; hours = minutes / 60; - printf( "\t\tMain loop: %llu milli (%02u:%02u:%02u)\n", + printf( "\t\t[%.2lf%%] Main loop: %llu milli (%02u:%02u:%02u)\n", + printPercents( timeMainLoopMicro, totalElapsed ), timeMainLoopMicro / 1000, hours, minutes - ( hours * 60 ), @@ -141,18 +161,33 @@ void Statistics::print() seconds = preprocessingTimeMicro / 1000000; minutes = seconds / 60; hours = minutes / 60; - printf( "\t\tPreprocessing time: %llu milli (%02u:%02u:%02u)\n", + printf( "\t\t[%.2lf%%] Preprocessing time: %llu milli (%02u:%02u:%02u)\n", + printPercents( preprocessingTimeMicro, totalElapsed ), preprocessingTimeMicro / 1000, hours, minutes - ( hours * 60 ), seconds - ( minutes * 60 ) ); - unsigned long long totalUnknown = totalElapsed - timeMainLoopMicro - preprocessingTimeMicro; + unsigned long long timeSearchTreeHandlerMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO ); + seconds = timeSearchTreeHandlerMicro / 1000000; + minutes = seconds / 60; + hours = minutes / 60; + printf( "\t\t[%.2lf%%] CDCL Core callbacks time: %llu milli (%02u:%02u:%02u)\n", + printPercents( timeSearchTreeHandlerMicro, totalElapsed ), + timeSearchTreeHandlerMicro / 1000, + hours, + minutes - ( hours * 60 ), + seconds - ( minutes * 60 ) ); + + unsigned long long totalUnknown = + totalElapsed - timeMainLoopMicro - preprocessingTimeMicro - timeSearchTreeHandlerMicro; seconds = totalUnknown / 1000000; minutes = seconds / 60; hours = minutes / 60; - printf( "\t\tUnknown: %llu milli (%02u:%02u:%02u)\n", + printf( "\t\t[%.2lf%%] Unknown: %llu milli (%02u:%02u:%02u)\n", + printPercents( totalUnknown, totalElapsed ), totalUnknown / 1000, hours, minutes - ( hours * 60 ), @@ -208,11 +243,16 @@ void Statistics::print() printf( "\t\t[%.2lf%%] Applying stored bound-tightening: %llu milli\n", printPercents( totalTimeApplyingStoredTighteningsMicro, timeMainLoopMicro ), totalTimeApplyingStoredTighteningsMicro / 1000 ); - unsigned long long totalTimeSmtCoreMicro = - getLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO ); - printf( "\t\t[%.2lf%%] SMT core: %llu milli\n", - printPercents( totalTimeSmtCoreMicro, timeMainLoopMicro ), - totalTimeSmtCoreMicro / 1000 ); + unsigned long long totalTimeSearchTreeHandlerMainLoopMicro = + getLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO ); + printf( "\t\t[%.2lf%%] Search Tree Handler: %llu milli\n", + printPercents( totalTimeSearchTreeHandlerMainLoopMicro, timeMainLoopMicro ), + totalTimeSearchTreeHandlerMainLoopMicro / 1000 ); + unsigned long long totalTimeCdclCoreMainLoopMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO ); + printf( "\t\t[%.2lf%%] CDCL Core: %llu milli\n", + printPercents( totalTimeCdclCoreMainLoopMicro, timeMainLoopMicro ), + totalTimeCdclCoreMainLoopMicro / 1000 ); unsigned long long totalTimePerformingSymbolicBoundTightening = getLongAttribute( Statistics::TOTAL_TIME_PERFORMING_SYMBOLIC_BOUND_TIGHTENING ); printf( "\t\t[%.2lf%%] Symbolic Bound Tightening: %llu milli\n", @@ -234,13 +274,67 @@ void Statistics::print() totalTimePerformingValidCaseSplitsMicro + totalTimeHandlingStatisticsMicro + totalTimeExplicitBasisBoundTighteningMicro + totalTimeDegradationChecking + totalTimePrecisionRestoration + totalTimeConstraintMatrixBoundTighteningMicro + - totalTimeApplyingStoredTighteningsMicro + totalTimeSmtCoreMicro + - totalTimePerformingSymbolicBoundTightening; + totalTimeSearchTreeHandlerMainLoopMicro + totalTimeApplyingStoredTighteningsMicro + + totalTimeCdclCoreMainLoopMicro + totalTimePerformingSymbolicBoundTightening; printf( "\t\t[%.2lf%%] Unaccounted for: %llu milli\n", printPercents( timeMainLoopMicro - total, timeMainLoopMicro ), timeMainLoopMicro > total ? ( timeMainLoopMicro - total ) / 1000 : 0 ); + printf( "\tBreakdown for CDCL Core callbacks:\n" ); + unsigned long long timeNotifyAssignmentMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_ASSIGNMENT_MICRO ); + printf( "\t\t[%.2lf%%] notify_assignment: %llu milli\n", + printPercents( timeNotifyAssignmentMicro, timeSearchTreeHandlerMicro ), + timeNotifyAssignmentMicro / 1000 ); + unsigned long long timeNotifyNewDecisionLevelMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_NEW_DECISION_LEVEL_MICRO ); + printf( "\t\t[%.2lf%%] notify_new_decision_level: %llu milli\n", + printPercents( timeNotifyNewDecisionLevelMicro, timeSearchTreeHandlerMicro ), + timeNotifyNewDecisionLevelMicro / 1000 ); + unsigned long long timeNotifyBacktrackMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_BACKTRACK_MICRO ); + printf( "\t\t[%.2lf%%] notify_backtrack: %llu milli\n", + printPercents( timeNotifyBacktrackMicro, timeSearchTreeHandlerMicro ), + timeNotifyBacktrackMicro / 1000 ); + unsigned long long timeNotifyFixedAssignmentMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_NOTIFY_FIXED_ASSIGNMENT_MICRO ); + printf( "\t\t[%.2lf%%] notify_fixed_assignment: %llu milli\n", + printPercents( timeNotifyFixedAssignmentMicro, timeSearchTreeHandlerMicro ), + timeNotifyFixedAssignmentMicro / 1000 ); + unsigned long long timeCbDecideMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_DECIDE_MICRO ); + printf( "\t\t[%.2lf%%] cb_decide: %llu milli\n", + printPercents( timeCbDecideMicro, timeSearchTreeHandlerMicro ), + timeCbDecideMicro / 1000 ); + unsigned long long timeCbPropagateMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO ); + printf( "\t\t[%.2lf%%] cb_propagate: %llu milli\n", + printPercents( timeCbPropagateMicro, timeSearchTreeHandlerMicro ), + timeCbPropagateMicro / 1000 ); + unsigned long long timeCbAddReasonClauseLitMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_ADD_REASON_CLAUSE_LIT_MICRO ); + printf( "\t\t[%.2lf%%] cb_add_reason_clause_lit: %llu milli\n", + printPercents( timeCbAddReasonClauseLitMicro, timeSearchTreeHandlerMicro ), + timeCbAddReasonClauseLitMicro / 1000 ); + unsigned long long timeCbAddExternalClauseLitMicro = + getLongAttribute( Statistics::TOTAL_TIME_CDCL_CORE_CB_ADD_EXTERNAL_CLAUSE_LIT_MICRO ); + printf( "\t\t[%.2lf%%] cb_add_external_clause_lit: %llu milli\n", + printPercents( timeCbAddExternalClauseLitMicro, timeSearchTreeHandlerMicro ), + timeCbAddExternalClauseLitMicro / 1000 ); + + unsigned long long totalCdclCoreCallbacks = + timeNotifyAssignmentMicro + timeNotifyNewDecisionLevelMicro + timeNotifyBacktrackMicro + + timeNotifyFixedAssignmentMicro + timeCbDecideMicro + timeCbPropagateMicro + + timeCbAddReasonClauseLitMicro + timeCbAddExternalClauseLitMicro; + + printf( "\t\t[%.2lf%%] Unaccounted for: %llu milli\n", + printPercents( timeSearchTreeHandlerMicro - totalCdclCoreCallbacks, + timeSearchTreeHandlerMicro ), + timeSearchTreeHandlerMicro > totalCdclCoreCallbacks + ? ( timeSearchTreeHandlerMicro - totalCdclCoreCallbacks ) / 1000 + : 0 ); + printf( "\t--- Preprocessor Statistics ---\n" ); printf( "\tNumber of preprocessor bound-tightening loop iterations: %u\n", getUnsignedAttribute( Statistics::PP_NUM_TIGHTENING_ITERATIONS ) ); @@ -270,11 +364,11 @@ void Statistics::print() printAverage( timeConstraintFixingStepsMicro / 1000, numConstraintFixingSteps ) ); printf( "\tNumber of active piecewise-linear constraints: %u / %u\n" "\t\tConstraints disabled by valid splits: %u. " - "By SMT-originated splits: %u\n", + "By Search Tree-originated splits: %u\n", getUnsignedAttribute( Statistics::NUM_ACTIVE_PL_CONSTRAINTS ), getUnsignedAttribute( Statistics::NUM_PL_CONSTRAINTS ), getUnsignedAttribute( Statistics::NUM_PL_VALID_SPLITS ), - getUnsignedAttribute( Statistics::NUM_PL_SMT_ORIGINATED_SPLITS ) ); + getUnsignedAttribute( Statistics::NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS ) ); printf( "\tLast reported degradation: %.10lf. Max degradation so far: %.10lf. " "Restorations so far: %u\n", getDoubleAttribute( Statistics::CURRENT_DEGRADATION ), @@ -314,7 +408,7 @@ void Statistics::print() getUnsignedAttribute( Statistics::CURRENT_TABLEAU_M ), getUnsignedAttribute( Statistics::CURRENT_TABLEAU_N ) ); - printf( "\t--- SMT Core Statistics ---\n" ); + printf( "\t--- Search Tree Handler Statistics ---\n" ); printf( "\tTotal depth is %u. Total visited states: %u. Number of splits: %u. Number of pops: %u\n", getUnsignedAttribute( Statistics::CURRENT_DECISION_LEVEL ), @@ -428,6 +522,27 @@ void Statistics::print() printf( "\tNumber of leaves to delegate: %u\n", getUnsignedAttribute( Statistics::NUM_DELEGATED_LEAVES ) ); printf( "\tNumber of lemmas: %u\n", getUnsignedAttribute( Statistics::NUM_LEMMAS ) ); + printf( "\tNumber of lemmas used in proof minimization: %u\n", + getUnsignedAttribute( Statistics::NUM_LEMMAS_USED ) ); + + printf( "\t--- CDCL Statistics ---\n" ); + printf( "\tMaximal depth of the search tree: %u\n", + getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ); + printf( "\tAverage depth of the search tree: %.10lf\n", + getUnsignedAttribute( Statistics::SUM_DECISION_LEVELS ) / + (double)getUnsignedAttribute( Statistics::NUM_DECISION_LEVELS ) ); + printf( "\tNumber of backjumps: %u\n", getUnsignedAttribute( Statistics::NUM_BACKJUMPS ) ); + printf( "\tMaximal jump size across all backjumps: %u\n", + getUnsignedAttribute( Statistics::MAX_BACKJUMP ) ); + printf( "\tAverage jump size across all backjumps: %.10lf\n", + getUnsignedAttribute( Statistics::SUM_BACKJUMPS ) / + (double)getUnsignedAttribute( Statistics::NUM_BACKJUMPS ) ); + printf( "\tNumber of decisions performed by the SAT solver: %u\n", + getUnsignedAttribute( Statistics::NUM_SAT_SOLVER_DECISIONS ) ); + printf( "\tNumber of decisions performed by Marabou: %u\n", + getUnsignedAttribute( Statistics::NUM_MARABOU_DECISIONS ) ); + printf( "\tNumber of restarts of the SAT solver: %u\n", + getUnsignedAttribute( Statistics::NUM_RESTARTS ) ); } unsigned long long Statistics::getTotalTimeInMicro() const diff --git a/src/common/Statistics.h b/src/common/Statistics.h index 4761594b4e..f8216ef228 100644 --- a/src/common/Statistics.h +++ b/src/common/Statistics.h @@ -31,15 +31,21 @@ class Statistics NUM_PL_CONSTRAINTS, NUM_ACTIVE_PL_CONSTRAINTS, NUM_PL_VALID_SPLITS, - NUM_PL_SMT_ORIGINATED_SPLITS, + NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS, // Precision restoration NUM_PRECISION_RESTORATIONS, - // Current and max stack depth in the SMT core + // Current and max depth of the search TREE CURRENT_DECISION_LEVEL, MAX_DECISION_LEVEL, + // Number of time the depth of the search tree was changed + NUM_DECISION_LEVELS, + + // Sum of all depths of the search tree + SUM_DECISION_LEVELS, + // Total number of splits so far NUM_SPLITS, @@ -71,9 +77,26 @@ class Statistics NUM_CERTIFIED_LEAVES, NUM_DELEGATED_LEAVES, NUM_LEMMAS, + NUM_LEMMAS_USED, // 1 if returned UNSAT and proof was certified by proof checker, 0 otherwise. CERTIFIED_UNSAT, + + // Maximal jump size across all backjumps performed + MAX_BACKJUMP, + + // Number of backjumps performed + NUM_BACKJUMPS, + + // Sum of all jump sizes across all backjumps + SUM_BACKJUMPS, + + // Number of decisions performed by Cadical and Marabou + NUM_SAT_SOLVER_DECISIONS, + NUM_MARABOU_DECISIONS, + + // Number of restarts of the SAT solver + NUM_RESTARTS }; enum StatisticsLongAttribute { @@ -188,8 +211,24 @@ class Statistics // Total amount of time spent applying previously stored bound tightenings TOTAL_TIME_APPLYING_STORED_TIGHTENINGS_MICRO, - // Total amount of time spent within the SMT core - TOTAL_TIME_SMT_CORE_MICRO, + // Total amount of time spent within the Search Tree Handler + TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, + + // Total amount of time spent within the CDCL core as a result of CaDiCal callbacks + TOTAL_TIME_CDCL_CORE_CALLBACKS_MICRO, + + // Total amount of time spent within each one of CaDiCal callbacks in SearchTreeHandler + TOTAL_TIME_CDCL_CORE_NOTIFY_ASSIGNMENT_MICRO, + TOTAL_TIME_CDCL_CORE_NOTIFY_NEW_DECISION_LEVEL_MICRO, + TOTAL_TIME_CDCL_CORE_NOTIFY_BACKTRACK_MICRO, + TOTAL_TIME_CDCL_CORE_NOTIFY_FIXED_ASSIGNMENT_MICRO, + TOTAL_TIME_CDCL_CORE_CB_DECIDE_MICRO, + TOTAL_TIME_CDCL_CORE_CB_PROPAGATE_MICRO, + TOTAL_TIME_CDCL_CORE_CB_ADD_REASON_CLAUSE_LIT_MICRO, + TOTAL_TIME_CDCL_CORE_CB_ADD_EXTERNAL_CLAUSE_LIT_MICRO, + + // Total amount of time spent within the CDCL core from Engine main loop + TOTAL_TIME_CDCL_CORE_MAIN_LOOP_MICRO, // Total time heuristically updating the SoI phase pattern TOTAL_TIME_UPDATING_SOI_PHASE_PATTERN_MICRO, diff --git a/src/common/Vector.h b/src/common/Vector.h index 91f4f72c68..0fc747000b 100644 --- a/src/common/Vector.h +++ b/src/common/Vector.h @@ -146,6 +146,17 @@ template class Vector return false; } + int getIndex( const T &value ) const + { + for ( unsigned i = 0; i < size(); ++i ) + { + if ( get( i ) == value ) + return i; + } + + return -1; + } + void erase( const T &value ) { for ( iterator it = _container.begin(); it != _container.end(); ++it ) diff --git a/src/configuration/GlobalConfiguration.cpp b/src/configuration/GlobalConfiguration.cpp index f9a074f076..0f3d02dfb5 100644 --- a/src/configuration/GlobalConfiguration.cpp +++ b/src/configuration/GlobalConfiguration.cpp @@ -27,7 +27,7 @@ const double GlobalConfiguration::EXPONENTIAL_MOVING_AVERAGE_ALPHA = 0.5; // Whether to use SoI instead of Reluplex for local search for satisfying assignments // to non-linear constraint. -bool GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH = true; +bool GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH = false; const double GlobalConfiguration::SCORE_BUMP_FOR_PL_CONSTRAINTS_NOT_IN_SOI = 5; @@ -62,7 +62,7 @@ const unsigned GlobalConfiguration::MAX_SIMPLEX_PIVOT_SEARCH_ITERATIONS = 5; const DivideStrategy GlobalConfiguration::SPLITTING_HEURISTICS = DivideStrategy::ReLUViolation; const unsigned GlobalConfiguration::INTERVAL_SPLITTING_FREQUENCY = 10; const unsigned GlobalConfiguration::INTERVAL_SPLITTING_THRESHOLD = 10; -const unsigned GlobalConfiguration::BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY = 100; +const unsigned GlobalConfiguration::BOUND_TIGHTENING_ON_CONSTRAINT_MATRIX_FREQUENCY = 100; const unsigned GlobalConfiguration::ROW_BOUND_TIGHTENER_SATURATION_ITERATIONS = 20; const double GlobalConfiguration::COST_FUNCTION_ERROR_THRESHOLD = 0.0000000001; @@ -102,6 +102,7 @@ const GlobalConfiguration::ExplicitBasisBoundTighteningType GlobalConfiguration::COMPUTE_INVERTED_BASIS_MATRIX; const bool GlobalConfiguration::EXPLICIT_BOUND_TIGHTENING_UNTIL_SATURATION = false; const double GlobalConfiguration::EXPLICIT_BASIS_BOUND_TIGHTENING_ROUNDING_CONSTANT = 1e-6; +const double GlobalConfiguration::EXPLICIT_BASIS_BOUND_TIGHTENING_PERCENTAGE_THRESHOLD = 1.0; const unsigned GlobalConfiguration::REFACTORIZATION_THRESHOLD = 100; const GlobalConfiguration::BasisFactorizationType GlobalConfiguration::BASIS_FACTORIZATION_TYPE = @@ -119,6 +120,14 @@ const bool GlobalConfiguration::WRITE_JSON_PROOF = false; const unsigned GlobalConfiguration::BACKWARD_BOUND_PROPAGATION_DEPTH = 3; const unsigned GlobalConfiguration::MAX_ROUNDS_OF_BACKWARD_ANALYSIS = 10; +const bool GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES = false; +const bool GlobalConfiguration::MINIMIZE_PROOF_DEPENDENCIES = false; + +const bool GlobalConfiguration::CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY = false; + +const bool GlobalConfiguration::CDCL_SHORTEN_CLAUSES = false; +const bool GlobalConfiguration::CDCL_SHORTEN_CLAUSES_WITH_QUICKXPLAIN = false; + #ifdef ENABLE_GUROBI const unsigned GlobalConfiguration::GUROBI_NUMBER_OF_THREADS = 1; const bool GlobalConfiguration::GUROBI_LOGGING = false; @@ -128,7 +137,7 @@ const bool GlobalConfiguration::GUROBI_LOGGING = false; const bool GlobalConfiguration::DNC_MANAGER_LOGGING = false; const bool GlobalConfiguration::ENGINE_LOGGING = false; const bool GlobalConfiguration::TABLEAU_LOGGING = false; -const bool GlobalConfiguration::SMT_CORE_LOGGING = false; +const bool GlobalConfiguration::SEARCH_TREE_HANDLER_LOGGING = false; const bool GlobalConfiguration::DANTZIGS_RULE_LOGGING = false; const bool GlobalConfiguration::BASIS_FACTORIZATION_LOGGING = false; const bool GlobalConfiguration::PREPROCESSOR_LOGGING = false; @@ -143,6 +152,7 @@ const bool GlobalConfiguration::ONNX_PARSER_LOGGING = false; const bool GlobalConfiguration::SOI_LOGGING = false; const bool GlobalConfiguration::SCORE_TRACKER_LOGGING = false; const bool GlobalConfiguration::CEGAR_LOGGING = false; +const bool GlobalConfiguration::CDCL_LOGGING = false; const bool GlobalConfiguration::USE_SMART_FIX = false; const bool GlobalConfiguration::USE_LEAST_FIX = false; @@ -174,8 +184,8 @@ void GlobalConfiguration::print() printf( " GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD: %.15lf\n", GAUSSIAN_ELIMINATION_PIVOT_SCALE_THRESHOLD ); printf( " MAX_SIMPLEX_PIVOT_SEARCH_ITERATIONS: %u\n", MAX_SIMPLEX_PIVOT_SEARCH_ITERATIONS ); - printf( " BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY: %u\n", - BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY ); + printf( " BOUND_TIGHTENING_ON_CONSTRAINT_MATRIX_FREQUENCY: %u\n", + BOUND_TIGHTENING_ON_CONSTRAINT_MATRIX_FREQUENCY ); printf( " COST_FUNCTION_ERROR_THRESHOLD: %.15lf\n", COST_FUNCTION_ERROR_THRESHOLD ); printf( " USE_HARRIS_RATIO_TEST: %s\n", USE_HARRIS_RATIO_TEST ? "Yes" : "No" ); diff --git a/src/configuration/GlobalConfiguration.h b/src/configuration/GlobalConfiguration.h index 3104edf79d..a267667706 100644 --- a/src/configuration/GlobalConfiguration.h +++ b/src/configuration/GlobalConfiguration.h @@ -139,7 +139,7 @@ class GlobalConfiguration static const unsigned INTERVAL_SPLITTING_THRESHOLD; // How often should we perform full bound tightening, on the entire contraints matrix A. - static const unsigned BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY; + static const unsigned BOUND_TIGHTENING_ON_CONSTRAINT_MATRIX_FREQUENCY; // When the row bound tightener is asked to run until saturation, it can enter an infinite loop // due to tiny increments in bounds. This number limits the number of iterations it can perform. @@ -191,6 +191,10 @@ class GlobalConfiguration // When doing explicit bound tightening, should we repeat until saturation? static const bool EXPLICIT_BOUND_TIGHTENING_UNTIL_SATURATION; + // Percentage of bounds (out of 2*num of variables) learned during basis bound tightening, to be + // used as a threshold to require a split + static const double EXPLICIT_BASIS_BOUND_TIGHTENING_PERCENTAGE_THRESHOLD; + /* Symbolic bound tightening options */ @@ -263,6 +267,23 @@ class GlobalConfiguration */ static const unsigned MAX_ROUNDS_OF_BACKWARD_ANALYSIS; + /* While solving with CDCL, denotes if to use proof-based clauses or not + */ + static const bool ANALYZE_PROOF_DEPENDENCIES; + + /* Minimize the number of lemma dependencies when producing proofs + */ + static const bool MINIMIZE_PROOF_DEPENDENCIES; + + /* Whether to convert the input verification query into a reachability query: + */ + static const bool CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY; + + /* While solving with CDCL, denotes if to try and shorten clauses or not + */ + static const bool CDCL_SHORTEN_CLAUSES; + static const bool CDCL_SHORTEN_CLAUSES_WITH_QUICKXPLAIN; + #ifdef ENABLE_GUROBI /* The number of threads Gurobi spawns @@ -277,7 +298,7 @@ class GlobalConfiguration static const bool DNC_MANAGER_LOGGING; static const bool ENGINE_LOGGING; static const bool TABLEAU_LOGGING; - static const bool SMT_CORE_LOGGING; + static const bool SEARCH_TREE_HANDLER_LOGGING; static const bool DANTZIGS_RULE_LOGGING; static const bool BASIS_FACTORIZATION_LOGGING; static const bool PREPROCESSOR_LOGGING; @@ -292,6 +313,7 @@ class GlobalConfiguration static const bool SOI_LOGGING; static const bool SCORE_TRACKER_LOGGING; static const bool CEGAR_LOGGING; + static const bool CDCL_LOGGING; }; #endif // __GlobalConfiguration_h__ diff --git a/src/configuration/OptionParser.cpp b/src/configuration/OptionParser.cpp index 6864e9fc76..6c4094fc11 100644 --- a/src/configuration/OptionParser.cpp +++ b/src/configuration/OptionParser.cpp @@ -137,6 +137,13 @@ void OptionParser::initialize() boost::program_options::bool_switch( &( ( *_boolOptions )[Options::PRODUCE_PROOFS] ) ) ->default_value( ( *_boolOptions )[Options::PRODUCE_PROOFS] ), "Produce proofs of UNSAT and check them" ) +#ifdef BUILD_CADICAL + ( "cdcl", + boost::program_options::bool_switch( &( ( *_boolOptions )[Options::SOLVE_WITH_CDCL] ) ) + ->default_value( ( *_boolOptions )[Options::SOLVE_WITH_CDCL] ), + "Solve the input query with CDCL as the solving procedure" ) +#endif + #ifdef ENABLE_GUROBI #endif // ENABLE_GUROBI ; @@ -210,11 +217,17 @@ void OptionParser::initialize() &( ( *_intOptions )[Options::CONSTRAINT_VIOLATION_THRESHOLD] ) ) ->default_value( ( *_intOptions )[Options::CONSTRAINT_VIOLATION_THRESHOLD] ), "Max number of tries to repair a relu before splitting when the Reluplex procedure is " - "used." )( "preprocessor-bound-tolerance", - boost::program_options::value( - &( ( *_floatOptions )[Options::PREPROCESSOR_BOUND_TOLERANCE] ) ) - ->default_value( ( *_floatOptions )[Options::PREPROCESSOR_BOUND_TOLERANCE] ), - "epsilon for preprocessor bound tightening comparisons." )( + "used." )( + "vsids-decay-threshold", + boost::program_options::value( &( ( *_intOptions )[Options::VSIDS_DECAY_THRESHOLD] ) ) + ->default_value( ( *_intOptions )[Options::VSIDS_DECAY_THRESHOLD] ), + "(CDCL) The number of clauses on which the literal to decide will be chosen according to " + "the VSIDS branching huristic." )( + "preprocessor-bound-tolerance", + boost::program_options::value( + &( ( *_floatOptions )[Options::PREPROCESSOR_BOUND_TOLERANCE] ) ) + ->default_value( ( *_floatOptions )[Options::PREPROCESSOR_BOUND_TOLERANCE] ), + "epsilon for preprocessor bound tightening comparisons." )( "softmax-bound-type", boost::program_options::value( &( *_stringOptions )[Options::SOFTMAX_BOUND_TYPE] ) @@ -241,7 +254,17 @@ void OptionParser::initialize() &( *_boolOptions )[Options::DO_NOT_MERGE_CONSECUTIVE_WEIGHTED_SUM_LAYERS] ) ->default_value( ( *_boolOptions )[Options::DO_NOT_MERGE_CONSECUTIVE_WEIGHTED_SUM_LAYERS] ), - "Do no merge consecutive weighted-sum layers." ) + "Do no merge consecutive weighted-sum layers." )( + "nap-external-clause", + boost::program_options::value( + &( *_stringOptions )[Options::NAP_EXTERNAL_CLAUSE_FILE_PATH] ) + ->default_value( ( *_stringOptions )[Options::NAP_EXTERNAL_CLAUSE_FILE_PATH] ), + "Filename of external NAP clause" )( + "nap-external-clause2", + boost::program_options::value( + &( *_stringOptions )[Options::NAP_EXTERNAL_CLAUSE_FILE_PATH2] ) + ->default_value( ( *_stringOptions )[Options::NAP_EXTERNAL_CLAUSE_FILE_PATH2] ), + "Filename of external NAP clause" ) #ifdef ENABLE_GUROBI ( "lp-solver", boost::program_options::value( &( ( *_stringOptions )[Options::LP_SOLVER] ) ) diff --git a/src/configuration/Options.cpp b/src/configuration/Options.cpp index 657ddb6b7c..6168437f42 100644 --- a/src/configuration/Options.cpp +++ b/src/configuration/Options.cpp @@ -55,6 +55,9 @@ void Options::initializeDefaultValues() _boolOptions[DEBUG_ASSIGNMENT] = false; _boolOptions[PRODUCE_PROOFS] = false; _boolOptions[DO_NOT_MERGE_CONSECUTIVE_WEIGHTED_SUM_LAYERS] = false; +#ifdef BUILD_CADICAL + _boolOptions[SOLVE_WITH_CDCL] = false; +#endif /* Int options @@ -63,9 +66,10 @@ void Options::initializeDefaultValues() _intOptions[NUM_INITIAL_DIVIDES] = 0; _intOptions[NUM_ONLINE_DIVIDES] = 2; _intOptions[INITIAL_TIMEOUT] = 5; - _intOptions[VERBOSITY] = 2; + _intOptions[VERBOSITY] = 0; _intOptions[TIMEOUT] = 0; _intOptions[CONSTRAINT_VIOLATION_THRESHOLD] = 20; + _intOptions[VSIDS_DECAY_THRESHOLD] = 100000; _intOptions[DEEP_SOI_REJECTION_THRESHOLD] = 2; _intOptions[NUMBER_OF_SIMULATIONS] = 100; _intOptions[SEED] = 1; @@ -100,6 +104,8 @@ void Options::initializeDefaultValues() _stringOptions[SOI_INITIALIZATION_STRATEGY] = "input-assignment"; _stringOptions[LP_SOLVER] = gurobiEnabled() ? "gurobi" : "native"; _stringOptions[SOFTMAX_BOUND_TYPE] = "lse"; + _stringOptions[NAP_EXTERNAL_CLAUSE_FILE_PATH] = ""; + _stringOptions[NAP_EXTERNAL_CLAUSE_FILE_PATH2] = ""; } void Options::parseOptions( int argc, char **argv ) @@ -253,11 +259,6 @@ LPSolverType Options::getLPSolverType() const String solverString = String( _stringOptions.get( Options::LP_SOLVER ) ); if ( solverString == "native" ) return LPSolverType::NATIVE; - else if ( _boolOptions.get( Options::PRODUCE_PROOFS ) ) - { - printf( "Proof-producing mode on, using native LP engine..." ); - return LPSolverType::NATIVE; - } else if ( solverString == "gurobi" ) return LPSolverType::GUROBI; else diff --git a/src/configuration/Options.h b/src/configuration/Options.h index 4dd4f31fd5..9865411249 100644 --- a/src/configuration/Options.h +++ b/src/configuration/Options.h @@ -27,6 +27,7 @@ #include "SoISearchStrategy.h" #include "SoftmaxBoundType.h" #include "SymbolicBoundTighteningType.h" +#include "Vector.h" #include "boost/program_options.hpp" /* @@ -83,6 +84,11 @@ class Options // logically-consecutive weighted sum layers into a single // weighted sum layer, to reduce the number of variables DO_NOT_MERGE_CONSECUTIVE_WEIGHTED_SUM_LAYERS, + +#ifdef BUILD_CADICAL + // Switch the solving procedure to be CDCL-based + SOLVE_WITH_CDCL, +#endif }; enum IntOptions { @@ -100,6 +106,8 @@ class Options CONSTRAINT_VIOLATION_THRESHOLD, + VSIDS_DECAY_THRESHOLD, + // The number of rejected phase pattern proposal allowed before // splitting at a search state. DEEP_SOI_REJECTION_THRESHOLD, @@ -155,7 +163,10 @@ class Options SOI_INITIALIZATION_STRATEGY, // The procedure/solver for solving the LP - LP_SOLVER + LP_SOLVER, + + NAP_EXTERNAL_CLAUSE_FILE_PATH, // TODO: delete or keep for artifact + NAP_EXTERNAL_CLAUSE_FILE_PATH2 // TODO: delete or keep for artifact }; /* diff --git a/src/engine/AbsoluteValueConstraint.cpp b/src/engine/AbsoluteValueConstraint.cpp index a9763980ad..47209377b8 100644 --- a/src/engine/AbsoluteValueConstraint.cpp +++ b/src/engine/AbsoluteValueConstraint.cpp @@ -14,6 +14,9 @@ #include "AbsoluteValueConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "FloatUtils.h" #include "ITableau.h" @@ -21,6 +24,7 @@ #include "MarabouError.h" #include "PiecewiseLinearCaseSplit.h" #include "Query.h" +#include "SearchTreeHandler.h" #include "Statistics.h" AbsoluteValueConstraint::AbsoluteValueConstraint( unsigned b, unsigned f ) @@ -146,13 +150,15 @@ void AbsoluteValueConstraint::notifyLowerBound( unsigned variable, double bound double fUpperBound = FloatUtils::max( -bound, getUpperBound( _b ) ); // If phase is not fixed, both bounds are stored and checker should check the max of // the two - if ( proofs && !phaseFixed() ) + if ( proofs && !phaseFixed() ) // TODO modify explain phase _boundManager->addLemmaExplanationAndTightenBound( _f, fUpperBound, Tightening::UB, { variable, variable }, Tightening::UB, - getType() ); + *this, + false, + fUpperBound ); else if ( proofs && phaseFixed() ) { std::shared_ptr tighteningRow = @@ -226,13 +232,15 @@ void AbsoluteValueConstraint::notifyUpperBound( unsigned variable, double bound double fUpperBound = FloatUtils::max( bound, -getLowerBound( _b ) ); // If phase is not fixed, both bonds are stored and checker should check the max of // the two - if ( proofs && !phaseFixed() ) + if ( proofs && !phaseFixed() ) // TODO modify explain phase _boundManager->addLemmaExplanationAndTightenBound( _f, fUpperBound, Tightening::UB, { variable, variable }, Tightening::UB, - getType() ); + *this, + false, + fUpperBound ); else if ( proofs && phaseFixed() ) { std::shared_ptr tighteningRow = @@ -406,7 +414,7 @@ AbsoluteValueConstraint::getSmartFixes( ITableau * /* tableau */ ) const List AbsoluteValueConstraint::getCaseSplits() const { - ASSERT( _phaseStatus == PhaseStatus::PHASE_NOT_FIXED ); + ASSERT( getPhaseStatus() == PhaseStatus::PHASE_NOT_FIXED ); List splits; splits.append( getNegativeSplit() ); @@ -476,14 +484,14 @@ PiecewiseLinearCaseSplit AbsoluteValueConstraint::getPositiveSplit() const bool AbsoluteValueConstraint::phaseFixed() const { - return _phaseStatus != PhaseStatus::PHASE_NOT_FIXED; + return getPhaseStatus() != PhaseStatus::PHASE_NOT_FIXED; } PiecewiseLinearCaseSplit AbsoluteValueConstraint::getImpliedCaseSplit() const { - ASSERT( _phaseStatus != PHASE_NOT_FIXED ); + ASSERT( getPhaseStatus() != PHASE_NOT_FIXED ); - if ( _phaseStatus == ABS_PHASE_POSITIVE ) + if ( getPhaseStatus() == ABS_PHASE_POSITIVE ) return getPositiveSplit(); return getNegativeSplit(); @@ -512,8 +520,8 @@ void AbsoluteValueConstraint::dump( String &output ) const _f, _b, _constraintActive ? "Yes" : "No", - _phaseStatus, - phaseToString( _phaseStatus ).ascii() ); + getPhaseStatus(), + phaseToString( getPhaseStatus() ).ascii() ); output += Stringf( "b in [%s, %s], ", @@ -826,7 +834,7 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() setPhaseStatus( ABS_PHASE_POSITIVE ); if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _posAux, 0, Tightening::UB, { _b }, Tightening::LB, getType() ); + _posAux, 0, Tightening::UB, { _b }, Tightening::LB, *this, true, 0 ); return; } @@ -836,7 +844,7 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() setPhaseStatus( ABS_PHASE_NEGATIVE ); if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _negAux, 0, Tightening::UB, { _b }, Tightening::UB, getType() ); + _negAux, 0, Tightening::UB, { _b }, Tightening::UB, *this, true, 0 ); return; } @@ -849,8 +857,14 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() { setPhaseStatus( ABS_PHASE_NEGATIVE ); if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _negAux, 0, Tightening::UB, { _b, _f }, Tightening::UB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _negAux, + 0, + Tightening::UB, + { _b, _f }, + Tightening::UB, + *this, + true, + getUpperBound( _b ) ); return; } @@ -860,8 +874,14 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() { setPhaseStatus( ABS_PHASE_POSITIVE ); if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _posAux, 0, Tightening::UB, { _b, _f }, Tightening::LB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _posAux, + 0, + Tightening::UB, + { _b, _f }, + Tightening::LB, + *this, + true, + -getLowerBound( _b ) ); return; } @@ -871,6 +891,9 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() if ( existsUpperBound( _posAux ) && FloatUtils::isZero( getUpperBound( _posAux ) ) ) { setPhaseStatus( ABS_PHASE_POSITIVE ); + if ( proofs ) + _boundManager->addLemmaExplanationAndTightenBound( + _posAux, 0, Tightening::UB, { _posAux }, Tightening::UB, *this, true, 0 ); return; } @@ -878,9 +901,10 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() if ( existsLowerBound( _posAux ) && FloatUtils::isPositive( getLowerBound( _posAux ) ) ) { setPhaseStatus( ABS_PHASE_NEGATIVE ); + // TODO modify code to accept phase fixings without a lemma if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _negAux, 0, Tightening::UB, { _posAux }, Tightening::LB, getType() ); + _negAux, 0, Tightening::UB, { _posAux }, Tightening::LB, *this, true, 0 ); return; } @@ -888,6 +912,9 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() if ( existsUpperBound( _negAux ) && FloatUtils::isZero( getUpperBound( _negAux ) ) ) { setPhaseStatus( ABS_PHASE_NEGATIVE ); + if ( proofs ) + _boundManager->addLemmaExplanationAndTightenBound( + _negAux, 0, Tightening::UB, { _negAux }, Tightening::UB, *this, true, 0 ); return; } @@ -895,12 +922,18 @@ void AbsoluteValueConstraint::fixPhaseIfNeeded() if ( existsLowerBound( _negAux ) && FloatUtils::isPositive( getLowerBound( _negAux ) ) ) { setPhaseStatus( ABS_PHASE_POSITIVE ); + // TODO modify code to accept phase fixings without a lemma if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _posAux, 0, Tightening::UB, { _negAux }, Tightening::LB, getType() ); + _posAux, 0, Tightening::UB, { _negAux }, Tightening::LB, *this, true, 0 ); return; } } + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } String AbsoluteValueConstraint::phaseToString( PhaseStatus phase ) @@ -978,3 +1011,79 @@ void AbsoluteValueConstraint::addTableauAuxVar( unsigned tableauAuxVar, unsigned ASSERT( _tableauAuxVars.front() == tableauAuxVar ); } } + +#ifdef BUILD_CADICAL +void AbsoluteValueConstraint::booleanAbstraction( + Map &cadicalVarToPlc ) +{ + unsigned int idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); +} + +int AbsoluteValueConstraint::propagatePhaseAsLit() const +{ + ASSERT( _cdclVars.size() == 1 ) + if ( getPhaseStatus() == ABS_PHASE_POSITIVE ) + return _cdclVars.back(); + else if ( getPhaseStatus() == ABS_PHASE_NEGATIVE ) + return -_cdclVars.back(); + else + return 0; +} + +void AbsoluteValueConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) ); + + setActiveConstraint( false ); + + if ( lit > 0 ) + setPhaseStatus( ABS_PHASE_POSITIVE ); + else + setPhaseStatus( ABS_PHASE_NEGATIVE ); +} + +int AbsoluteValueConstraint::getLiteralForDecision() const +{ + ASSERT( getPhaseStatus() == PhaseStatus::PHASE_NOT_FIXED ); + + return -(int)_cdclVars.front(); +} + +bool AbsoluteValueConstraint::isBoundFixingPhase( unsigned int var, + double bound, + Tightening::BoundType boundType ) const +{ + if ( getPhaseStatus() == ABS_PHASE_POSITIVE ) + { + if ( var == _b && boundType == Tightening::LB && bound >= 0 ) + return true; + if ( var == _f && boundType == Tightening::LB && bound > -getLowerBound( _b ) ) + return true; + if ( _auxVarsInUse ) + { + if ( var == _posAux && boundType == Tightening::UB && FloatUtils::isZero( bound ) ) + return true; + if ( var == _negAux && boundType == Tightening::LB && FloatUtils::isPositive( bound ) ) + return true; + } + } + else if ( getPhaseStatus() == ABS_PHASE_NEGATIVE ) + { + if ( var == _b && boundType == Tightening::UB && bound <= 0 ) + return true; + if ( var == _f && boundType == Tightening::LB && bound > getUpperBound( _b ) ) + return true; + if ( _auxVarsInUse ) + { + if ( var == _posAux && boundType == Tightening::LB && FloatUtils::isPositive( bound ) ) + return true; + if ( var == _negAux && boundType == Tightening::UB && FloatUtils::isZero( bound ) ) + return true; + } + } + + return false; +} +#endif \ No newline at end of file diff --git a/src/engine/AbsoluteValueConstraint.h b/src/engine/AbsoluteValueConstraint.h index cb78792750..bac998c2bd 100644 --- a/src/engine/AbsoluteValueConstraint.h +++ b/src/engine/AbsoluteValueConstraint.h @@ -217,6 +217,36 @@ class AbsoluteValueConstraint : public PiecewiseLinearConstraint const List getNativeAuxVars() const override; +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; + + /* + Returns on which phase to decide this constraint, as a cadical var + */ + int getLiteralForDecision() const override; + + bool isBoundFixingPhase( unsigned var, + double bound, + Tightening::BoundType boundType ) const override; + +#endif + private: /* The variables that make up this constraint; _f = | _b |. diff --git a/src/engine/BoundManager.cpp b/src/engine/BoundManager.cpp index 5c8c9d1562..9db4126b5f 100644 --- a/src/engine/BoundManager.cpp +++ b/src/engine/BoundManager.cpp @@ -17,7 +17,6 @@ #include "Debug.h" #include "FloatUtils.h" -#include "InfeasibleQueryException.h" #include "MarabouError.h" #include "Tableau.h" #include "Tightening.h" @@ -417,7 +416,9 @@ bool BoundManager::addLemmaExplanationAndTightenBound( unsigned var, Tightening::BoundType affectedVarBound, const List &causingVars, Tightening::BoundType causingVarBound, - PiecewiseLinearFunctionType constraintType ) + PiecewiseLinearConstraint &constraint, + bool isPhaseFixing, + double minTargetBound ) { if ( !shouldProduceProofs() ) return false; @@ -432,12 +433,13 @@ bool BoundManager::addLemmaExplanationAndTightenBound( unsigned var, if ( tightened ) { - if ( constraintType == RELU || constraintType == SIGN || constraintType == LEAKY_RELU ) + if ( constraint.getType() == RELU || constraint.getType() == SIGN || + constraint.getType() == LEAKY_RELU ) { ASSERT( causingVars.size() == 1 ); allExplanations.append( getExplanation( causingVars.front(), causingVarBound ) ); } - else if ( constraintType == ABSOLUTE_VALUE ) + else if ( constraint.getType() == ABSOLUTE_VALUE ) { if ( causingVars.size() == 1 ) allExplanations.append( getExplanation( causingVars.front(), causingVarBound ) ); @@ -456,7 +458,7 @@ bool BoundManager::addLemmaExplanationAndTightenBound( unsigned var, allExplanations.append( getExplanation( causingVars.back(), Tightening::LB ) ); } } - else if ( constraintType == MAX ) + else if ( constraint.getType() == MAX ) for ( const auto &element : causingVars ) allExplanations.append( getExplanation( element, Tightening::UB ) ); else @@ -468,12 +470,26 @@ bool BoundManager::addLemmaExplanationAndTightenBound( unsigned var, causingVarBound, affectedVarBound, allExplanations, - constraintType ); - _engine->addPLCLemma( PLCExpl ); - affectedVarBound == Tightening::UB ? _engine->updateGroundUpperBound( var, value ) - : _engine->updateGroundLowerBound( var, value ); + constraint.getType(), + minTargetBound ); + + if ( !_engine->shouldSolveWithCDCL() ) + _engine->getUNSATCertificateCurrentPointer()->addPLCLemma( PLCExpl ); + + // Add ground bound entry to the GroundBoundManager + std::shared_ptr phaseFixingEntry = + _engine->setGroundBoundFromLemma( PLCExpl, isPhaseFixing ); resetExplanation( var, affectedVarBound ); + + if ( isPhaseFixing ) + { + ASSERT( constraint.getPhaseFixingEntry() == nullptr ); + constraint.setPhaseFixingEntry( phaseFixingEntry ); + } + + _engine->incNumOfLemmas(); } + return true; } diff --git a/src/engine/BoundManager.h b/src/engine/BoundManager.h index 2808e1bbfc..cf04ba05dd 100644 --- a/src/engine/BoundManager.h +++ b/src/engine/BoundManager.h @@ -260,7 +260,9 @@ class BoundManager : public IBoundManager Tightening::BoundType affectedVarBound, const List &causingVars, Tightening::BoundType causingVarBound, - PiecewiseLinearFunctionType constraintType ); + PiecewiseLinearConstraint &constraint, + bool isPhaseFixing, + double minTargetBound ); /* Explainer of all bounds diff --git a/src/engine/CDSmtCore.cpp b/src/engine/CDSearchTreeHandler.cpp similarity index 79% rename from src/engine/CDSmtCore.cpp rename to src/engine/CDSearchTreeHandler.cpp index 6fb9967fed..4287001ccf 100644 --- a/src/engine/CDSmtCore.cpp +++ b/src/engine/CDSearchTreeHandler.cpp @@ -1,5 +1,5 @@ /********************* */ -/*! \file CDSmtCore.cpp +/*! \file CDSearchTreeHandler.cpp ** \verbatim ** Top contributors (to current version): ** Guy Katz, Aleksandar Zeljic, Haoze Wu, Parth Shah @@ -9,10 +9,10 @@ ** All rights reserved. See the file COPYING in the top-level source ** directory for licensing information.\endverbatim ** - ** See the description of the class in CDSmtCore.h. + ** See the description of the class in CDSearchTreeHandler.h. **/ -#include "CDSmtCore.h" +#include "CDSearchTreeHandler.h" #include "Debug.h" #include "DivideStrategy.h" @@ -27,7 +27,7 @@ using namespace CVC4::context; -CDSmtCore::CDSmtCore( IEngine *engine, Context &ctx ) +CDSearchTreeHandler::CDSearchTreeHandler( IEngine *engine, Context &ctx ) : _statistics( NULL ) , _context( ctx ) , _trail( &_context ) @@ -43,11 +43,11 @@ CDSmtCore::CDSmtCore( IEngine *engine, Context &ctx ) { } -CDSmtCore::~CDSmtCore() +CDSearchTreeHandler::~CDSearchTreeHandler() { } -void CDSmtCore::reportViolatedConstraint( PiecewiseLinearConstraint *constraint ) +void CDSearchTreeHandler::reportViolatedConstraint( PiecewiseLinearConstraint *constraint ) { ASSERT( !constraint->phaseFixed() ); @@ -68,7 +68,7 @@ void CDSmtCore::reportViolatedConstraint( PiecewiseLinearConstraint *constraint } } -unsigned CDSmtCore::getViolationCounts( PiecewiseLinearConstraint *constraint ) const +unsigned CDSearchTreeHandler::getViolationCounts( PiecewiseLinearConstraint *constraint ) const { if ( !_constraintToViolationCount.exists( constraint ) ) return 0; @@ -76,7 +76,7 @@ unsigned CDSmtCore::getViolationCounts( PiecewiseLinearConstraint *constraint ) return _constraintToViolationCount[constraint]; } -void CDSmtCore::initializeScoreTrackerIfNeeded( +void CDSearchTreeHandler::initializeScoreTrackerIfNeeded( const List &plConstraints ) { if ( GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH ) @@ -84,11 +84,11 @@ void CDSmtCore::initializeScoreTrackerIfNeeded( _scoreTracker = std::unique_ptr( new PseudoImpactTracker() ); _scoreTracker->initialize( plConstraints ); - SMT_LOG( "\tTracking Pseudo Impact..." ); + CD_SEARCH_TREE_LOG( "\tTracking Pseudo Impact..." ); } } -void CDSmtCore::reportRejectedPhasePatternProposal() +void CDSearchTreeHandler::reportRejectedPhasePatternProposal() { ++_numRejectedPhasePatternProposal; @@ -102,29 +102,30 @@ void CDSmtCore::reportRejectedPhasePatternProposal() } } -bool CDSmtCore::needToSplit() const +bool CDSearchTreeHandler::needToSplit() const { return _needToSplit; } -void CDSmtCore::pushDecision( PiecewiseLinearConstraint *constraint, PhaseStatus decision ) +void CDSearchTreeHandler::pushDecision( PiecewiseLinearConstraint *constraint, + PhaseStatus decision ) { - SMT_LOG( Stringf( "Decision @ %d )", _context.getLevel() + 1 ).ascii() ); + CD_SEARCH_TREE_LOG( Stringf( "Decision @ %d )", _context.getLevel() + 1 ).ascii() ); TrailEntry te( constraint, decision ); applyTrailEntry( te, true ); - SMT_LOG( Stringf( "Decision push @ %d DONE", _context.getLevel() ).ascii() ); + CD_SEARCH_TREE_LOG( Stringf( "Decision push @ %d DONE", _context.getLevel() ).ascii() ); } -void CDSmtCore::pushImplication( PiecewiseLinearConstraint *constraint ) +void CDSearchTreeHandler::pushImplication( PiecewiseLinearConstraint *constraint ) { ASSERT( constraint->isImplication() ); - SMT_LOG( Stringf( "Implication @ %d ... ", _context.getLevel() ).ascii() ); + CD_SEARCH_TREE_LOG( Stringf( "Implication @ %d ... ", _context.getLevel() ).ascii() ); TrailEntry te( constraint, constraint->nextFeasibleCase() ); applyTrailEntry( te, false ); - SMT_LOG( Stringf( "Implication @ %d DONE", _context.getLevel() ).ascii() ); + CD_SEARCH_TREE_LOG( Stringf( "Implication @ %d DONE", _context.getLevel() ).ascii() ); } -void CDSmtCore::applyTrailEntry( TrailEntry &te, bool isDecision ) +void CDSearchTreeHandler::applyTrailEntry( TrailEntry &te, bool isDecision ) { if ( isDecision ) { @@ -136,10 +137,10 @@ void CDSmtCore::applyTrailEntry( TrailEntry &te, bool isDecision ) _engine->applySplit( te.getPiecewiseLinearCaseSplit() ); } -void CDSmtCore::decide() +void CDSearchTreeHandler::decide() { ASSERT( _needToSplit ); - SMT_LOG( "Performing a ReLU split" ); + CD_SEARCH_TREE_LOG( "Performing a ReLU split" ); _numRejectedPhasePatternProposal = 0; // Maybe the constraint has already become inactive - if so, ignore @@ -161,7 +162,7 @@ void CDSmtCore::decide() decideSplit( _constraintForSplitting ); } -void CDSmtCore::decideSplit( PiecewiseLinearConstraint *constraint ) +void CDSearchTreeHandler::decideSplit( PiecewiseLinearConstraint *constraint ) { struct timespec start = TimeUtils::sampleMicro(); @@ -187,51 +188,51 @@ void CDSmtCore::decideSplit( PiecewiseLinearConstraint *constraint ) _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); struct timespec end = TimeUtils::sampleMicro(); - _statistics->incLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO, + _statistics->incLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, TimeUtils::timePassed( start, end ) ); } - SMT_LOG( "Performing a ReLU split - DONE" ); + CD_SEARCH_TREE_LOG( "Performing a ReLU split - DONE" ); } -unsigned CDSmtCore::getDecisionLevel() const +unsigned CDSearchTreeHandler::getDecisionLevel() const { return _decisions.size(); } -bool CDSmtCore::popDecisionLevel( TrailEntry &lastDecision ) +bool CDSearchTreeHandler::popDecisionLevel( TrailEntry &lastDecision ) { // ASSERT( static_cast( _context.getLevel() ) == _decisions.size() ); if ( _decisions.empty() ) return false; - SMT_LOG( "Popping trail ..." ); + CD_SEARCH_TREE_LOG( "Popping trail ..." ); lastDecision = _decisions.back(); _context.pop(); _engine->postContextPopHook(); - SMT_LOG( Stringf( "to %d DONE", _context.getLevel() ).ascii() ); + CD_SEARCH_TREE_LOG( Stringf( "to %d DONE", _context.getLevel() ).ascii() ); return true; } -void CDSmtCore::interruptIfCompliantWithDebugSolution() +void CDSearchTreeHandler::interruptIfCompliantWithDebugSolution() { if ( checkSkewFromDebuggingSolution() ) { - SMT_LOG( "Error! Popping from a compliant stack\n" ); + CD_SEARCH_TREE_LOG( "Error! Popping from a compliant stack\n" ); throw MarabouError( MarabouError::DEBUGGING_ERROR ); } } -PiecewiseLinearCaseSplit CDSmtCore::getDecision( unsigned decisionLevel ) const +PiecewiseLinearCaseSplit CDSearchTreeHandler::getDecision( unsigned decisionLevel ) const { ASSERT( decisionLevel <= getDecisionLevel() ); ASSERT( decisionLevel > 0 ); return _decisions[decisionLevel - 1].getPiecewiseLinearCaseSplit(); } -bool CDSmtCore::backtrackToFeasibleDecision( TrailEntry &lastDecision ) +bool CDSearchTreeHandler::backtrackToFeasibleDecision( TrailEntry &lastDecision ) { - SMT_LOG( "Backtracking to a feasible decision..." ); + CD_SEARCH_TREE_LOG( "Backtracking to a feasible decision..." ); if ( getDecisionLevel() == 0 ) return false; @@ -254,7 +255,7 @@ bool CDSmtCore::backtrackToFeasibleDecision( TrailEntry &lastDecision ) return true; } -bool CDSmtCore::backtrackAndContinueSearch() +bool CDSearchTreeHandler::backtrackAndContinueSearch() { TrailEntry feasibleDecision( nullptr, CONSTRAINT_INFEASIBLE ); struct timespec start = TimeUtils::sampleMicro(); @@ -280,7 +281,7 @@ bool CDSmtCore::backtrackAndContinueSearch() if ( level > _statistics->getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ) _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); struct timespec end = TimeUtils::sampleMicro(); - _statistics->incLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO, + _statistics->incLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, TimeUtils::timePassed( start, end ) ); } @@ -288,14 +289,14 @@ bool CDSmtCore::backtrackAndContinueSearch() return true; } -void CDSmtCore::resetReportedViolations() +void CDSearchTreeHandler::resetReportedViolations() { _constraintToViolationCount.clear(); _numRejectedPhasePatternProposal = 0; _needToSplit = false; } -void CDSmtCore::allSplitsSoFar( List &result ) const +void CDSearchTreeHandler::allSplitsSoFar( List &result ) const { result.clear(); @@ -303,19 +304,19 @@ void CDSmtCore::allSplitsSoFar( List &result ) const result.append( trailEntry.getPiecewiseLinearCaseSplit() ); } -void CDSmtCore::setStatistics( Statistics *statistics ) +void CDSearchTreeHandler::setStatistics( Statistics *statistics ) { _statistics = statistics; } -void CDSmtCore::storeDebuggingSolution( const Map &debuggingSolution ) +void CDSearchTreeHandler::storeDebuggingSolution( const Map &debuggingSolution ) { _debuggingSolution = debuggingSolution; } // Return true if stack is currently compliant, false otherwise // If there is no stored solution, return false --- incompliant. -bool CDSmtCore::checkSkewFromDebuggingSolution() +bool CDSearchTreeHandler::checkSkewFromDebuggingSolution() { if ( _debuggingSolution.empty() ) return false; @@ -372,8 +373,8 @@ bool CDSmtCore::checkSkewFromDebuggingSolution() return true; } -bool CDSmtCore::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, - String &error ) const +bool CDSearchTreeHandler::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, + String &error ) const { // False if the split prevents one of the values in the stored solution, true otherwise. error = ""; @@ -416,12 +417,12 @@ bool CDSmtCore::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split return true; } -void CDSmtCore::setConstraintViolationThreshold( unsigned threshold ) +void CDSearchTreeHandler::setConstraintViolationThreshold( unsigned threshold ) { _constraintViolationThreshold = threshold; } -PiecewiseLinearConstraint *CDSmtCore::chooseViolatedConstraintForFixing( +PiecewiseLinearConstraint *CDSearchTreeHandler::chooseViolatedConstraintForFixing( List &_violatedPlConstraints ) const { ASSERT( !_violatedPlConstraints.empty() ); @@ -455,14 +456,14 @@ PiecewiseLinearConstraint *CDSmtCore::chooseViolatedConstraintForFixing( return candidate; } -bool CDSmtCore::pickSplitPLConstraint() +bool CDSearchTreeHandler::pickSplitPLConstraint() { if ( _needToSplit ) _constraintForSplitting = _engine->pickSplitPLConstraint( _branchingHeuristic ); return _constraintForSplitting != NULL; } -void CDSmtCore::reset() +void CDSearchTreeHandler::reset() { _context.popto( 0 ); _engine->postContextPopHook(); diff --git a/src/engine/CDSmtCore.h b/src/engine/CDSearchTreeHandler.h similarity index 86% rename from src/engine/CDSmtCore.h rename to src/engine/CDSearchTreeHandler.h index 8cbeebc5c0..636ea7f613 100644 --- a/src/engine/CDSmtCore.h +++ b/src/engine/CDSearchTreeHandler.h @@ -1,5 +1,5 @@ /********************* */ -/*! \file CDSmtCore.h +/*! \file CDSearchTreeHandler.h ** \verbatim ** Top contributors (to current version): ** Guy Katz, AleksandarZeljic, Haoze Wu, Parth Shah @@ -9,8 +9,8 @@ ** All rights reserved. See the file COPYING in the top-level source ** directory for licensing information.\endverbatim ** - ** The CDSmtCore class implements a context-dependent SmtCore class. - ** The CDSmtCore distinguishes between: **decisions** and **implications**. + ** The CDSearchTreeHandler class implements a context-dependent SearchTreeHandler class. + ** The CDSearchTreeHandler distinguishes between: **decisions** and **implications**. ** ** Decision is a case of PiecewiseLinearConstraint asserted on the trail. ** A decision is a choice between multiple feasible cases of a @@ -24,7 +24,7 @@ ** by exhausting all other cases) performs an implication. ** ** The overall search state is stored in a distributed way: - ** - CDSmtCore::_trail is the current search state in a chronological order + ** - CDSearchTreeHandler::_trail is the current search state in a chronological order ** - PiecewiseLinearConstraints' infeasible cases enumerate all the explored ** states w.r.t to the chronological order on the _trail. ** @@ -34,7 +34,7 @@ ** _trail and _decisions are both context dependent and will synchronize in ** unison with the _context object. ** - ** When a search state is found to be infeasible, CDSmtCore backtracks to the + ** When a search state is found to be infeasible, CDSearchTreeHandler backtracks to the ** last decision and continues the search. ** ** PushDecision advances the decision level and context level. @@ -63,8 +63,8 @@ ** - Using BoundManager class to store bounds in a context-dependent manner **/ -#ifndef __CDSmtCore_h__ -#define __CDSmtCore_h__ +#ifndef __CDSearchTreeHandler_h__ +#define __CDSearchTreeHandler_h__ #include "Options.h" #include "PLConstraintScoreTracker.h" @@ -76,17 +76,18 @@ #include "context/cdlist.h" #include "context/context.h" -#define SMT_LOG( x, ... ) LOG( GlobalConfiguration::SMT_CORE_LOGGING, "CDSmtCore: %s\n", x ) +#define CD_SEARCH_TREE_LOG( x, ... ) \ + LOG( GlobalConfiguration::SEARCH_TREE_HANDLER_LOGGING, "CDSearchTreeHandler: %s\n", x ) class EngineState; class Engine; class String; -class CDSmtCore +class CDSearchTreeHandler { public: - CDSmtCore( IEngine *engine, CVC4::context::Context &context ); - ~CDSmtCore(); + CDSearchTreeHandler( IEngine *engine, CVC4::context::Context &context ); + ~CDSearchTreeHandler(); /* Clear the stack. @@ -99,7 +100,7 @@ class CDSmtCore void initializeScoreTrackerIfNeeded( const List &plConstraints ); /* - Inform the SMT core that a SoI phase pattern proposal is rejected. + Inform the Search Tree handler that a SoI phase pattern proposal is rejected. */ void reportRejectedPhasePatternProposal(); @@ -121,7 +122,7 @@ class CDSmtCore } /* - Inform the SMT core that a PL constraint is violated. + Inform the Search Tree handler that a PL constraint is violated. */ void reportViolatedConstraint( PiecewiseLinearConstraint *constraint ); @@ -137,7 +138,7 @@ class CDSmtCore void resetReportedViolations(); /* - Returns true iff the SMT core wants to perform a case split. + Returns true iff the Search Tree handler wants to perform a case split. */ bool needToSplit() const; @@ -147,7 +148,7 @@ class CDSmtCore void pushDecision( PiecewiseLinearConstraint *constraint, PhaseStatus decision ); /* - Inform SmtCore of an implied (formerly valid) case split that was discovered. + Inform SearchTreeHandler of an implied (formerly valid) case split that was discovered. */ void pushImplication( PiecewiseLinearConstraint *constraint ); @@ -196,7 +197,7 @@ class CDSmtCore unsigned getDecisionLevel() const; /* - Return a list of all splits performed so far, both SMT-originating and + Return a list of all splits performed so far, both Search Tree-originating and valid ones, in the correct order. */ void allSplitsSoFar( List &result ) const; @@ -218,12 +219,12 @@ class CDSmtCore }; /* - Have the SMT core start reporting statistics. + Have the Search Tree handler start reporting statistics. */ void setStatistics( Statistics *statistics ); /* - Have the SMT core choose, among a set of violated PL constraints, which + Have the Search Tree handler choose, among a set of violated PL constraints, which constraint should be repaired (without splitting) */ PiecewiseLinearConstraint *chooseViolatedConstraintForFixing( @@ -329,4 +330,4 @@ class CDSmtCore unsigned _numRejectedPhasePatternProposal; }; -#endif // __CDSmtCore_h__ +#endif // __CDSearchTreeHandler_h__ diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt index 487d806d3c..da2f4808a2 100644 --- a/src/engine/CMakeLists.txt +++ b/src/engine/CMakeLists.txt @@ -41,10 +41,10 @@ engine_add_unit_test(Query) engine_add_unit_test(ReluConstraint) engine_add_unit_test(RoundConstraint) engine_add_unit_test(RowBoundTightener) +engine_add_unit_test(SearchTreeHandler) engine_add_unit_test(SignConstraint) engine_add_unit_test(SigmoidConstraint) engine_add_unit_test(SoftmaxConstraint) -engine_add_unit_test(SmtCore) engine_add_unit_test(SumOfInfeasibilitiesManager) engine_add_unit_test(Tableau) engine_add_unit_test(BaBsrSplitting) diff --git a/src/engine/DisjunctionConstraint.cpp b/src/engine/DisjunctionConstraint.cpp index c739418b11..e04a6f8d48 100644 --- a/src/engine/DisjunctionConstraint.cpp +++ b/src/engine/DisjunctionConstraint.cpp @@ -14,6 +14,9 @@ #include "DisjunctionConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "InfeasibleQueryException.h" #include "MStringf.h" @@ -25,6 +28,8 @@ DisjunctionConstraint::DisjunctionConstraint( const List &cadicalVarToPlc ) +{ + unsigned int idx; + for ( auto &disjunct : _disjuncts ) + { + idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); + _disjunctsToCadicalVars.insert( &disjunct, idx ); + _cadicalVarsToDisjuncts.insert( idx, &disjunct ); + _cdclCore->addLiteral( (int)idx ); + } + _cdclCore->addLiteral( 0 ); +} + +int DisjunctionConstraint::propagatePhaseAsLit() const +{ + if ( phaseFixed() ) + { + PiecewiseLinearCaseSplit onlyDisjunct = _disjuncts.get( _feasibleDisjuncts.back() ); + return _disjunctsToCadicalVars.at( &onlyDisjunct ); + } + return 0; +} + +void DisjunctionConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) && lit > 0 ); + + setActiveConstraint( false ); + PiecewiseLinearCaseSplit disjunct = *_cadicalVarsToDisjuncts.at( lit ); + PhaseStatus phaseToFix = indToPhaseStatus( _disjuncts.getIndex( disjunct ) ); + + ASSERT( !phaseFixed() || getPhaseStatus() == phaseToFix ); + + setPhaseStatus( phaseToFix ); +} +#endif \ No newline at end of file diff --git a/src/engine/DisjunctionConstraint.h b/src/engine/DisjunctionConstraint.h index 94f90461a9..64baf5eb43 100644 --- a/src/engine/DisjunctionConstraint.h +++ b/src/engine/DisjunctionConstraint.h @@ -189,6 +189,26 @@ class DisjunctionConstraint : public PiecewiseLinearConstraint */ bool addFeasibleDisjunct( const PiecewiseLinearCaseSplit &disjunct ); +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; +#endif + private: /* The disjuncts that form this PL constraint @@ -245,6 +265,9 @@ class DisjunctionConstraint : public PiecewiseLinearConstraint */ double getMinLowerBound( unsigned int var ) const; double getMaxUpperBound( unsigned int var ) const; + + Map _disjunctsToCadicalVars; + Map _cadicalVarsToDisjuncts; }; #endif // __DisjunctionConstraint_h__ diff --git a/src/engine/DnCManager.cpp b/src/engine/DnCManager.cpp index ff8e404c19..d70a4e84dd 100644 --- a/src/engine/DnCManager.cpp +++ b/src/engine/DnCManager.cpp @@ -83,7 +83,7 @@ void DnCManager::dncSolve( WorkerQueue *workload, DnCManager::DnCManager( IQuery *inputQuery ) : _baseQuery( inputQuery ) - , _exitCode( DnCManager::NOT_DONE ) + , _exitCode( ExitCode::NOT_DONE ) , _workload( NULL ) , _timeoutReached( false ) , _numUnsolvedSubQueries( 0 ) @@ -137,7 +137,7 @@ void DnCManager::solve() // Preprocess the input query and create an engine for each of the threads if ( !createEngines( numWorkers ) ) { - _exitCode = DnCManager::UNSAT; + _exitCode = ExitCode::UNSAT; return; } @@ -244,7 +244,7 @@ void DnCManager::solve() return; } -DnCManager::DnCExitCode DnCManager::getExitCode() const +ExitCode DnCManager::getExitCode() const { return _exitCode; } @@ -256,32 +256,32 @@ void DnCManager::updateDnCExitCode() bool hasQuitRequested = false; for ( auto &engine : _engines ) { - Engine::ExitCode result = engine->getExitCode(); - if ( result == Engine::SAT ) + ExitCode result = engine->getExitCode(); + if ( result == ExitCode::SAT ) { _engineWithSATAssignment = engine; hasSat = true; break; } - else if ( result == Engine::ERROR ) + else if ( result == ExitCode::ERROR ) hasError = true; - else if ( result == Engine::QUIT_REQUESTED ) + else if ( result == ExitCode::QUIT_REQUESTED ) hasQuitRequested = true; } if ( hasSat ) - _exitCode = DnCManager::SAT; + _exitCode = ExitCode::SAT; else if ( _timeoutReached ) - _exitCode = DnCManager::TIMEOUT; + _exitCode = ExitCode::TIMEOUT; else if ( _numUnsolvedSubQueries.load() <= 0 ) - _exitCode = DnCManager::UNSAT; + _exitCode = ExitCode::UNSAT; else if ( hasQuitRequested ) - _exitCode = DnCManager::QUIT_REQUESTED; + _exitCode = ExitCode::QUIT_REQUESTED; else if ( hasError ) - _exitCode = DnCManager::ERROR; + _exitCode = ExitCode::ERROR; else { ASSERT( false ); // This should never happen - _exitCode = DnCManager::NOT_DONE; + _exitCode = ExitCode::NOT_DONE; } } @@ -289,17 +289,17 @@ String DnCManager::getResultString() { switch ( _exitCode ) { - case DnCManager::SAT: + case ExitCode::SAT: return "sat"; - case DnCManager::UNSAT: + case ExitCode::UNSAT: return "unsat"; - case DnCManager::ERROR: + case ExitCode::ERROR: return "ERROR"; - case DnCManager::NOT_DONE: + case ExitCode::NOT_DONE: return "NOT_DONE"; - case DnCManager::QUIT_REQUESTED: + case ExitCode::QUIT_REQUESTED: return "QUIT_REQUESTED"; - case DnCManager::TIMEOUT: + case ExitCode::TIMEOUT: return "TIMEOUT"; default: ASSERT( false ); @@ -325,7 +325,7 @@ void DnCManager::printResult() std::cout << std::endl; switch ( _exitCode ) { - case DnCManager::SAT: + case ExitCode::SAT: { std::cout << "sat\n" << std::endl; @@ -352,19 +352,19 @@ void DnCManager::printResult() printf( "\n" ); break; } - case DnCManager::UNSAT: + case ExitCode::UNSAT: std::cout << "unsat" << std::endl; break; - case DnCManager::ERROR: + case ExitCode::ERROR: std::cout << "ERROR" << std::endl; break; - case DnCManager::NOT_DONE: + case ExitCode::NOT_DONE: std::cout << "NOT_DONE" << std::endl; break; - case DnCManager::QUIT_REQUESTED: + case ExitCode::QUIT_REQUESTED: std::cout << "QUIT_REQUESTED" << std::endl; break; - case DnCManager::TIMEOUT: + case ExitCode::TIMEOUT: std::cout << "TIMEOUT" << std::endl; break; default: diff --git a/src/engine/DnCManager.h b/src/engine/DnCManager.h index ee4a55a19d..dda875328e 100644 --- a/src/engine/DnCManager.h +++ b/src/engine/DnCManager.h @@ -32,16 +32,6 @@ class Query; class DnCManager { public: - enum DnCExitCode { - UNSAT = 0, - SAT = 1, - ERROR = 2, - TIMEOUT = 3, - QUIT_REQUESTED = 4, - - NOT_DONE = 999, - }; - DnCManager( IQuery *inputQuery ); ~DnCManager(); @@ -56,7 +46,7 @@ class DnCManager /* Return the DnCExitCode of the DnCManager */ - DnCExitCode getExitCode() const; + ExitCode getExitCode() const; /* Get the string representation of the exitcode @@ -145,7 +135,7 @@ class DnCManager /* The exit code of the DnCManager. */ - DnCExitCode _exitCode; + ExitCode _exitCode; /* Set of subQueries to be solved by workers diff --git a/src/engine/DnCWorker.cpp b/src/engine/DnCWorker.cpp index 4765eae5f1..afa77a12e6 100644 --- a/src/engine/DnCWorker.cpp +++ b/src/engine/DnCWorker.cpp @@ -85,9 +85,9 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) String queryId = subQuery->_queryId; unsigned depth = subQuery->_depth; auto split = std::move( subQuery->_split ); - std::unique_ptr smtState = nullptr; - if ( restoreTreeStates && subQuery->_smtState ) - smtState = std::move( subQuery->_smtState ); + std::unique_ptr searchTreeState = nullptr; + if ( restoreTreeStates && subQuery->_searchTreeState ) + searchTreeState = std::move( subQuery->_searchTreeState ); unsigned timeoutInSeconds = subQuery->_timeoutInSeconds; // Reset the engine state @@ -102,9 +102,9 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) _engine->applySnCSplit( *split, queryId ); bool fullSolveNeeded = true; // denotes whether we need to solve the subquery - if ( restoreTreeStates && smtState ) - fullSolveNeeded = _engine->restoreSmtState( *smtState ); - IEngine::ExitCode result = IEngine::NOT_DONE; + if ( restoreTreeStates && searchTreeState ) + fullSolveNeeded = _engine->restoreSearchTreeState( *searchTreeState ); + ExitCode result = ExitCode::NOT_DONE; if ( fullSolveNeeded ) { _engine->solve( timeoutInSeconds ); @@ -113,13 +113,13 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) else { // UNSAT is proven when replaying stack-entries - result = IEngine::UNSAT; + result = ExitCode::UNSAT; } if ( _verbosity > 0 ) printProgress( queryId, result ); // Switch on the result - if ( result == IEngine::UNSAT ) + if ( result == ExitCode::UNSAT ) { // If UNSAT, continue to solve *_numUnsolvedSubQueries -= 1; @@ -127,7 +127,7 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) *_shouldQuitSolving = true; delete subQuery; } - else if ( result == IEngine::TIMEOUT ) + else if ( result == ExitCode::TIMEOUT ) { // If TIMEOUT, split the current input region and add the // new subQueries to the current queue @@ -136,14 +136,15 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) ? 0 : (unsigned)timeoutInSeconds * _timeoutFactor ); unsigned numNewSubQueries = pow( 2, _onlineDivides ); - std::vector> newSmtStates; + std::vector> newSearchTreeStates; if ( restoreTreeStates ) { - // create |numNewSubQueries| copies of the current SmtState + // create |numNewSubQueries| copies of the current SearchTreeState for ( unsigned i = 0; i < numNewSubQueries; ++i ) { - newSmtStates.push_back( std::unique_ptr( new SmtState() ) ); - _engine->storeSmtState( *( newSmtStates[i] ) ); + newSearchTreeStates.push_back( + std::unique_ptr( new SearchTreeState() ) ); + _engine->storeSearchTreeState( *( newSearchTreeStates[i] ) ); } } @@ -153,10 +154,10 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) unsigned i = 0; for ( auto &newSubQuery : subQueries ) { - // Store the SmtCore state + // Store the SearchTreeHandler state if ( restoreTreeStates ) { - newSubQuery->_smtState = std::move( newSmtStates[i++] ); + newSubQuery->_searchTreeState = std::move( newSearchTreeStates[i++] ); } if ( !_workload->push( std::move( newSubQuery ) ) ) @@ -169,7 +170,7 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) *_numUnsolvedSubQueries -= 1; delete subQuery; } - else if ( result == IEngine::QUIT_REQUESTED ) + else if ( result == ExitCode::QUIT_REQUESTED ) { // If engine was asked to quit, quit std::cout << "Quit requested by manager!" << std::endl; @@ -182,13 +183,13 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) // TIMEOUT. This way, the DnCManager will kill all the DnCWorkers. *_shouldQuitSolving = true; - if ( result == IEngine::SAT ) + if ( result == ExitCode::SAT ) { // case SAT *_numUnsolvedSubQueries -= 1; delete subQuery; } - else if ( result == IEngine::ERROR ) + else if ( result == ExitCode::ERROR ) { // case ERROR std::cout << "Error!" << std::endl; @@ -210,7 +211,7 @@ void DnCWorker::popOneSubQueryAndSolve( bool restoreTreeStates ) } } -void DnCWorker::printProgress( String queryId, IEngine::ExitCode result ) const +void DnCWorker::printProgress( String queryId, ExitCode result ) const { printf( "Worker %d: Query %s %s, %d tasks remaining\n", _threadId, @@ -219,19 +220,19 @@ void DnCWorker::printProgress( String queryId, IEngine::ExitCode result ) const _numUnsolvedSubQueries->load() ); } -String DnCWorker::exitCodeToString( IEngine::ExitCode result ) +String DnCWorker::exitCodeToString( ExitCode result ) { switch ( result ) { - case IEngine::UNSAT: + case ExitCode::UNSAT: return "unsat"; - case IEngine::SAT: + case ExitCode::SAT: return "sat"; - case IEngine::ERROR: + case ExitCode::ERROR: return "ERROR"; - case IEngine::TIMEOUT: + case ExitCode::TIMEOUT: return "TIMEOUT"; - case IEngine::QUIT_REQUESTED: + case ExitCode::QUIT_REQUESTED: return "QUIT_REQUESTED"; default: ASSERT( false ); diff --git a/src/engine/DnCWorker.h b/src/engine/DnCWorker.h index f7c7120d97..3aa6b5e22c 100644 --- a/src/engine/DnCWorker.h +++ b/src/engine/DnCWorker.h @@ -52,12 +52,12 @@ class DnCWorker /* Convert the exitCode to string */ - static String exitCodeToString( IEngine::ExitCode result ); + static String exitCodeToString( ExitCode result ); /* Print the current progress */ - void printProgress( String queryId, IEngine::ExitCode result ) const; + void printProgress( String queryId, ExitCode result ) const; /* The queue of subqueries (shared across threads) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 86f45ecd0c..513448d118 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -28,6 +28,8 @@ #include "PiecewiseLinearConstraint.h" #include "Preprocessor.h" #include "Query.h" +#include "QuitFromPrecisionRestorationException.h" +#include "SearchTreeHandler.h" #include "TableauRow.h" #include "TimeUtils.h" #include "VariableOutOfBoundDuringOptimizationException.h" @@ -36,12 +38,16 @@ #include Engine::Engine() - : _context() + : _exitCode( ExitCode::NOT_DONE ) + , _context() , _boundManager( _context ) , _tableau( _boundManager ) , _preprocessedQuery( nullptr ) , _rowBoundTightener( *_tableau ) - , _smtCore( this ) + , _searchTreeHandler( this ) +#ifdef BUILD_CADICAL + , _cdclCore( this ) +#endif , _numPlConstraintsDisabledByValidSplits( 0 ) , _preprocessingEnabled( false ) , _initialStateStored( false ) @@ -50,7 +56,6 @@ Engine::Engine() , _basisRestorationPerformed( Engine::NO_RESTORATION_PERFORMED ) , _costFunctionManager( _tableau ) , _quitRequested( false ) - , _exitCode( Engine::NOT_DONE ) , _numVisitedStatesAtPreviousRestoration( 0 ) , _networkLevelReasoner( NULL ) , _verbosity( Options::get()->getInt( Options::VERBOSITY ) ) @@ -72,8 +77,17 @@ Engine::Engine() , _produceUNSATProofs( Options::get()->getBool( Options::PRODUCE_PROOFS ) ) , _groundBoundManager( _context ) , _UNSATCertificate( NULL ) -{ - _smtCore.setStatistics( &_statistics ); +#ifdef BUILD_CADICAL + , _solveWithCDCL( Options::get()->getBool( Options::SOLVE_WITH_CDCL ) ) +#else + , _solveWithCDCL( false ) +#endif + , _initialized( false ) +{ + _searchTreeHandler.setStatistics( &_statistics ); +#ifdef BUILD_CADICAL + _cdclCore.setStatistics( &_statistics ); +#endif _tableau->setStatistics( &_statistics ); _rowBoundTightener->setStatistics( &_statistics ); _preprocessor.setStatistics( &_statistics ); @@ -84,7 +98,6 @@ Engine::Engine() setRandomSeed( Options::get()->getInt( Options::SEED ) ); _boundManager.registerEngine( this ); - _groundBoundManager.registerEngine( this ); _statisticsPrintingFrequency = ( _lpSolverType == LPSolverType::NATIVE ) ? GlobalConfiguration::STATISTICS_PRINTING_FREQUENCY : GlobalConfiguration::STATISTICS_PRINTING_FREQUENCY_GUROBI; @@ -137,7 +150,7 @@ void Engine::applySnCSplit( PiecewiseLinearCaseSplit sncSplit, String queryId ) _sncSplit = sncSplit; _queryId = queryId; preContextPushHook(); - _smtCore.pushContext(); + _searchTreeHandler.pushContext(); applySplit( sncSplit ); _boundManager.propagateTightenings(); } @@ -187,7 +200,7 @@ void Engine::exportQueryWithError( String errorMessage ) ipqFileName.ascii() ); } -bool Engine::solve( double timeoutInSeconds ) +void Engine::initializeSolver() { SignalHandler::getInstance()->initialize(); SignalHandler::getInstance()->registerClient( this ); @@ -202,7 +215,14 @@ bool Engine::solve( double timeoutInSeconds ) applyAllValidConstraintCaseSplits(); if ( _solveWithMILP ) - return solveWithMILPEncoding( timeoutInSeconds ); + return; + +#ifdef BUILD_CADICAL + if ( _solveWithCDCL ) + for ( const auto plConstraint : _plConstraints ) + if ( plConstraint->phaseFixed() ) + _cdclCore.phase( plConstraint->propagatePhaseAsLit() ); +#endif updateDirections(); if ( _lpSolverType == LPSolverType::NATIVE ) @@ -225,9 +245,27 @@ bool Engine::solve( double timeoutInSeconds ) _statistics.print(); printf( "\n---\n" ); } +} + +bool Engine::solve( double timeoutInSeconds ) +{ + if ( !_initialized ) + { + initializeSolver(); + _initialized = true; + } bool splitJustPerformed = true; struct timespec mainLoopStart = TimeUtils::sampleMicro(); + if ( _solveWithCDCL ) + { + _searchTreeHandler.resetSplitConditions(); + applyAllBoundTightenings(); + + if ( _lpSolverType == LPSolverType::NATIVE && !propagateBoundManagerTightenings() ) + return false; + } + while ( true ) { struct timespec mainLoopEnd = TimeUtils::sampleMicro(); @@ -244,7 +282,7 @@ bool Engine::solve( double timeoutInSeconds ) _statistics.print(); } - _exitCode = Engine::TIMEOUT; + setExitCode( ExitCode::TIMEOUT ); _statistics.timeout(); return false; } @@ -258,7 +296,7 @@ bool Engine::solve( double timeoutInSeconds ) _statistics.print(); } - _exitCode = Engine::QUIT_REQUESTED; + setExitCode( ExitCode::QUIT_REQUESTED ); return false; } @@ -273,6 +311,22 @@ bool Engine::solve( double timeoutInSeconds ) 0 ) _statistics.print(); + // If solving with CDCL, and _searchTreeHandler demands splitting, stop the loop before + // performing other actions + if ( _solveWithCDCL && _searchTreeHandler.needToSplit() ) + { + if ( std::any_of( + _plConstraints.begin(), + _plConstraints.end(), + []( PiecewiseLinearConstraint *p ) { return !p->phaseFixed(); } ) ) + { + _boundManager.propagateTightenings(); + return false; + } + else + _searchTreeHandler.setNeedToSplit( false ); + } + if ( _lpSolverType == LPSolverType::NATIVE ) { checkOverallProgress(); @@ -289,7 +343,7 @@ bool Engine::solve( double timeoutInSeconds ) } } - // If true, we just entered a new subproblem + // If true, we just entered a new sub-problem if ( splitJustPerformed ) { performBoundTighteningAfterCaseSplit(); @@ -297,10 +351,9 @@ bool Engine::solve( double timeoutInSeconds ) splitJustPerformed = false; } - // Perform any SmtCore-initiated case splits - if ( _smtCore.needToSplit() ) + if ( !_solveWithCDCL && _searchTreeHandler.needToSplit() ) { - _smtCore.performSplit(); + _searchTreeHandler.performSplit(); splitJustPerformed = true; continue; } @@ -327,6 +380,15 @@ bool Engine::solve( double timeoutInSeconds ) { if ( allNonlinearConstraintsHold() ) { + DEBUG( for ( unsigned int v = 0; v < _tableau->getN(); ++v ) { + ASSERT( FloatUtils::areEqual( _tableau->getLowerBound( v ), + _boundManager.getLowerBound( v ) ) ); + + ASSERT( FloatUtils::areEqual( _tableau->getUpperBound( v ), + _boundManager.getUpperBound( v ) ) ); + } ); + + mainLoopEnd = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_MAIN_LOOP_MICRO, @@ -339,12 +401,12 @@ bool Engine::solve( double timeoutInSeconds ) // Allows checking proofs produced for UNSAT leaves of satisfiable query // search tree - if ( _produceUNSATProofs ) + if ( !_solveWithCDCL && _produceUNSATProofs ) { ASSERT( _UNSATCertificateCurrentPointer ); ( **_UNSATCertificateCurrentPointer ).setSATSolutionFlag(); } - _exitCode = Engine::SAT; + setExitCode( ExitCode::SAT ); return true; } else if ( !hasBranchingCandidate() ) @@ -358,13 +420,13 @@ bool Engine::solve( double timeoutInSeconds ) printf( "\nEngine::solve: at leaf node but solving inconclusive\n" ); _statistics.print(); } - _exitCode = Engine::UNKNOWN; + setExitCode( ExitCode::UNKNOWN ); return false; } else { - while ( !_smtCore.needToSplit() ) - _smtCore.reportRejectedPhasePatternProposal(); + while ( !_searchTreeHandler.needToSplit() ) + _searchTreeHandler.reportRejectedPhasePatternProposal(); continue; } } @@ -393,7 +455,7 @@ bool Engine::solve( double timeoutInSeconds ) if ( !handleMalformedBasisException() ) { ASSERT( _lpSolverType == LPSolverType::NATIVE ); - _exitCode = Engine::ERROR; + setExitCode( ExitCode::ERROR ); exportQueryWithError( "Cannot restore tableau" ); mainLoopEnd = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_MAIN_LOOP_MICRO, @@ -407,24 +469,43 @@ bool Engine::solve( double timeoutInSeconds ) // The current query is unsat, and we need to pop. // If we're at level 0, the whole query is unsat. if ( _produceUNSATProofs ) - explainSimplexFailure(); + { + if ( _lpSolverType == LPSolverType::NATIVE ) + explainSimplexFailure(); +#ifdef BUILD_CADICAL + else + explainGurobiFailure(); +#endif + } - if ( !_smtCore.popSplit() ) + if ( !_solveWithCDCL ) { - mainLoopEnd = TimeUtils::sampleMicro(); - _statistics.incLongAttribute( Statistics::TIME_MAIN_LOOP_MICRO, - TimeUtils::timePassed( mainLoopStart, mainLoopEnd ) ); - if ( _verbosity > 0 ) + if ( !_searchTreeHandler.popSplit() ) { - printf( "\nEngine::solve: unsat query\n" ); - _statistics.print(); + mainLoopEnd = TimeUtils::sampleMicro(); + _statistics.incLongAttribute( + Statistics::TIME_MAIN_LOOP_MICRO, + TimeUtils::timePassed( mainLoopStart, mainLoopEnd ) ); + if ( _verbosity > 0 ) + { + printf( "\nEngine::solve: unsat query\n" ); + _statistics.print(); + } + setExitCode( ExitCode::UNSAT ); + return false; + } + else + { + splitJustPerformed = true; } - _exitCode = Engine::UNSAT; - return false; } else { - splitJustPerformed = true; +#ifdef BUILD_CADICAL + if ( !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + _cdclCore.addDecisionBasedConflictClause(); +#endif + return false; } } catch ( const VariableOutOfBoundDuringOptimizationException & ) @@ -432,11 +513,16 @@ bool Engine::solve( double timeoutInSeconds ) _tableau->toggleOptimization( false ); continue; } + catch ( const QuitFromPrecisionRestorationException & ) + { + _tableau->toggleOptimization( false ); + return false; + } catch ( MarabouError &e ) { String message = Stringf( "Caught a MarabouError. Code: %u. Message: %s ", e.getCode(), e.getUserMessage() ); - _exitCode = Engine::ERROR; + setExitCode( ExitCode::ERROR ); exportQueryWithError( message ); mainLoopEnd = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_MAIN_LOOP_MICRO, @@ -445,7 +531,7 @@ bool Engine::solve( double timeoutInSeconds ) } catch ( ... ) { - _exitCode = Engine::ERROR; + setExitCode( ExitCode::ERROR ); exportQueryWithError( "Unknown error" ); mainLoopEnd = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_MAIN_LOOP_MICRO, @@ -467,7 +553,7 @@ void Engine::mainLoopStatistics() _statistics.setUnsignedAttribute( Statistics::NUM_ACTIVE_PL_CONSTRAINTS, activeConstraints ); _statistics.setUnsignedAttribute( Statistics::NUM_PL_VALID_SPLITS, _numPlConstraintsDisabledByValidSplits ); - _statistics.setUnsignedAttribute( Statistics::NUM_PL_SMT_ORIGINATED_SPLITS, + _statistics.setUnsignedAttribute( Statistics::NUM_PL_SEARCH_TREE_ORIGINATED_SPLITS, _plConstraints.size() - activeConstraints - _numPlConstraintsDisabledByValidSplits ); @@ -527,6 +613,7 @@ bool Engine::adjustAssignmentToSatisfyNonLinearConstraints() // and perform any valid case splits. tightenBoundsOnConstraintMatrix(); _boundManager.propagateTightenings(); + // For debugging purposes checkBoundCompliancyWithDebugSolution(); @@ -565,7 +652,7 @@ bool Engine::performPrecisionRestorationIfNeeded() // Restoration is not required _basisRestorationPerformed = Engine::NO_RESTORATION_PERFORMED; - // Possible restoration due to preceision degradation + // Possible restoration due to precision degradation if ( shouldCheckDegradation() && highDegradation() ) { performPrecisionRestoration( PrecisionRestorer::RESTORE_BASICS ); @@ -613,7 +700,7 @@ void Engine::performConstraintFixingStep() // Select a violated constraint as the target selectViolatedPlConstraint(); - // Report the violated constraint to the SMT engine + // Report the violated constraint to the Search Tree engine reportPlViolation(); // Attempt to fix the constraint @@ -961,9 +1048,9 @@ bool Engine::calculateBounds( const IQuery &inputQuery ) _statistics.setLongAttribute( Statistics::CALCULATE_BOUNDS_TIME_MICRO, TimeUtils::timePassed( start, end ) ); - _exitCode = Engine::UNSAT; printf( "unsat\n" ); + _exitCode = ExitCode::UNSAT; return false; } @@ -1000,7 +1087,7 @@ void Engine::invokePreprocessor( const IQuery &inputQuery, bool preprocess ) unsigned infiniteBounds = _preprocessedQuery->countInfiniteBounds(); if ( infiniteBounds != 0 ) { - _exitCode = Engine::ERROR; + _exitCode = ExitCode::ERROR; throw MarabouError( MarabouError::UNBOUNDED_VARIABLES_NOT_YET_SUPPORTED, Stringf( "Error! Have %u infinite bounds", infiniteBounds ).ascii() ); } @@ -1070,7 +1157,7 @@ double *Engine::createConstraintMatrix() { if ( equation._type != Equation::EQ ) { - _exitCode = Engine::ERROR; + _exitCode = ExitCode::ERROR; throw MarabouError( MarabouError::NON_EQUALITY_INPUT_EQUATION_DISCOVERED ); } @@ -1114,7 +1201,7 @@ void Engine::selectInitialVariablesForBasis( const double *constraintMatrix, /* This method permutes rows and columns in the constraint matrix (prior to the addition of auxiliary variables), in order to obtain a set of - column that constitue a lower triangular matrix. The variables + column that constitute a lower triangular matrix. The variables corresponding to the columns of this matrix join the initial basis. (It is possible that not enough variables are obtained this way, in which @@ -1339,7 +1426,7 @@ void Engine::initializeTableau( const double *constraintMatrix, const ListsetConstraintMatrix( constraintMatrix ); _tableau->registerToWatchAllVariables( _rowBoundTightener ); @@ -1489,8 +1576,10 @@ bool Engine::processInputQuery( const IQuery &inputQuery, bool preprocess ) for ( unsigned i = 0; i < n; ++i ) { - _groundBoundManager.setUpperBound( i, _preprocessedQuery->getUpperBound( i ) ); - _groundBoundManager.setLowerBound( i, _preprocessedQuery->getLowerBound( i ) ); + _groundBoundManager.addGroundBound( + i, _preprocessedQuery->getUpperBound( i ), Tightening::UB, false ); + _groundBoundManager.addGroundBound( + i, _preprocessedQuery->getLowerBound( i ), Tightening::LB, false ); } } } @@ -1512,7 +1601,14 @@ bool Engine::processInputQuery( const IQuery &inputQuery, bool preprocess ) } for ( const auto &constraint : _plConstraints ) + { constraint->registerTableau( _tableau ); +#ifdef BUILD_CADICAL + constraint->registerCdclCore( &_cdclCore ); +#endif + if ( !Options::get()->getBool( Options::DNC_MODE ) ) + constraint->initializeCDOs( &_context ); + } for ( const auto &constraint : _nlConstraints ) constraint->registerTableau( _tableau ); @@ -1540,6 +1636,26 @@ bool Engine::processInputQuery( const IQuery &inputQuery, bool preprocess ) // Some variable bounds are invalid, so the query is unsat throw InfeasibleQueryException(); } + +#ifdef BUILD_CADICAL + if ( _solveWithCDCL ) + { + if ( !_nlConstraints.empty() ) + throw MarabouError( MarabouError::FEATURE_NOT_YET_SUPPORTED, + "The network contains constraints currently " + "unsupported by CDCL" ); + + for ( auto *constraint : _plConstraints ) + { + if ( !CdclCore::isSupported( constraint ) ) + throw MarabouError( MarabouError::FEATURE_NOT_YET_SUPPORTED, + "The network contains constraints currently " + "unsupported by CDCL" ); + + _cdclCore.initBooleanAbstraction( constraint ); + } + } +#endif } catch ( const InfeasibleQueryException & ) { @@ -1549,7 +1665,7 @@ bool Engine::processInputQuery( const IQuery &inputQuery, bool preprocess ) _statistics.setLongAttribute( Statistics::PREPROCESSING_TIME_MICRO, TimeUtils::timePassed( start, end ) ); - _exitCode = Engine::UNSAT; + _exitCode = ExitCode::UNSAT; return false; } @@ -1563,7 +1679,7 @@ bool Engine::processInputQuery( const IQuery &inputQuery, bool preprocess ) } } ); - _smtCore.storeDebuggingSolution( _preprocessedQuery->_debuggingSolution ); + _searchTreeHandler.storeDebuggingSolution( _preprocessedQuery->_debuggingSolution ); return true; } @@ -1571,7 +1687,7 @@ void Engine::performMILPSolverBoundedTightening( Query *inputQuery ) { if ( _networkLevelReasoner && Options::get()->gurobiEnabled() ) { - // Obtain from and store bounds into inputquery if it is not null. + // Obtain from and store bounds into inputQuery if it is not null. if ( inputQuery ) _networkLevelReasoner->obtainCurrentBounds( *inputQuery ); else @@ -1803,14 +1919,15 @@ void Engine::selectViolatedPlConstraint() { ASSERT( !_violatedPlConstraints.empty() ); - _plConstraintToFix = _smtCore.chooseViolatedConstraintForFixing( _violatedPlConstraints ); + _plConstraintToFix = + _searchTreeHandler.chooseViolatedConstraintForFixing( _violatedPlConstraints ); ASSERT( _plConstraintToFix ); } void Engine::reportPlViolation() { - _smtCore.reportViolatedConstraint( _plConstraintToFix ); + _searchTreeHandler.reportViolatedConstraint( _plConstraintToFix ); } void Engine::storeState( EngineState &state, TableauStateStorageLevel level ) const @@ -1819,7 +1936,8 @@ void Engine::storeState( EngineState &state, TableauStateStorageLevel level ) co state._tableauStateStorageLevel = level; for ( const auto &constraint : _plConstraints ) - state._plConstraintToState[constraint] = constraint->duplicateConstraint(); + if ( !constraint->getContext() ) + state._plConstraintToState[constraint] = constraint->duplicateConstraint(); state._numPlConstraintsDisabledByValidSplits = _numPlConstraintsDisabledByValidSplits; } @@ -1837,10 +1955,11 @@ void Engine::restoreState( const EngineState &state ) ENGINE_LOG( "\tRestoring constraint states" ); for ( auto &constraint : _plConstraints ) { - if ( !state._plConstraintToState.exists( constraint ) ) + if ( !state._plConstraintToState.exists( constraint ) && !constraint->getContext() ) throw MarabouError( MarabouError::MISSING_PL_CONSTRAINT_STATE ); - constraint->restoreState( state._plConstraintToState[constraint] ); + if ( !constraint->getContext() ) + constraint->restoreState( state._plConstraintToState[constraint] ); } _numPlConstraintsDisabledByValidSplits = state._numPlConstraintsDisabledByValidSplits; @@ -1854,8 +1973,8 @@ void Engine::restoreState( const EngineState &state ) _costFunctionManager->initialize(); } - // Reset the violation counts in the SMT core - _smtCore.resetSplitConditions(); + // Reset the violation counts in the Search Tree handler + _searchTreeHandler.resetSplitConditions(); } void Engine::setNumPlConstraintsDisabledByValidSplits( unsigned numConstraints ) @@ -2065,7 +2184,6 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) { adjustWorkMemorySize(); } - for ( auto &bound : bounds ) { unsigned variable = _tableau->getVariableAfterMerging( bound._variable ); @@ -2078,7 +2196,7 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) FloatUtils::gt( bound._value, _boundManager.getLowerBound( bound._variable ) ) ) { _boundManager.resetExplanation( variable, Tightening::LB ); - updateGroundLowerBound( variable, bound._value ); + _groundBoundManager.addGroundBound( variable, bound._value, Tightening::LB, true ); _boundManager.tightenLowerBound( variable, bound._value ); } else if ( !_produceUNSATProofs ) @@ -2092,7 +2210,7 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) FloatUtils::lt( bound._value, _boundManager.getUpperBound( bound._variable ) ) ) { _boundManager.resetExplanation( variable, Tightening::UB ); - updateGroundUpperBound( variable, bound._value ); + _groundBoundManager.addGroundBound( variable, bound._value, Tightening::UB, true ); _boundManager.tightenUpperBound( variable, bound._value ); } else if ( !_produceUNSATProofs ) @@ -2107,6 +2225,59 @@ void Engine::applySplit( const PiecewiseLinearCaseSplit &split ) ENGINE_LOG( "Done with split\n" ); } +void Engine::applyPlcPhaseFixingTightenings( PiecewiseLinearConstraint &constraint ) +{ + ASSERT( constraint.phaseFixed() ); + ASSERT( constraint.getValidCaseSplit().getEquations().empty() ); + + List bounds = constraint.getValidCaseSplit().getBoundTightenings(); + + for ( auto &bound : bounds ) + { + unsigned variable = _tableau->getVariableAfterMerging( bound._variable ); + + if ( bound._type == Tightening::LB ) + { + ENGINE_LOG( + Stringf( "x%u: lower bound set to %.3lf", variable, bound._value ).ascii() ); + if ( _produceUNSATProofs && + FloatUtils::gt( bound._value, _boundManager.getLowerBound( bound._variable ) ) && + _lpSolverType == LPSolverType::NATIVE ) + { + _boundManager.resetExplanation( variable, Tightening::LB ); + _groundBoundManager.addGroundBound( variable, bound._value, Tightening::LB, true ); + _boundManager.tightenLowerBound( variable, bound._value ); + if ( !constraint.getPhaseFixingEntry() ) + constraint.setPhaseFixingEntry( + getGroundBoundEntry( variable, Tightening::LB ) ); + } + else if ( !_produceUNSATProofs || _lpSolverType == LPSolverType::GUROBI ) + _boundManager.tightenLowerBound( variable, bound._value ); + } + else + { + ENGINE_LOG( + Stringf( "x%u: upper bound set to %.3lf", variable, bound._value ).ascii() ); + if ( _produceUNSATProofs && + FloatUtils::lt( bound._value, _boundManager.getUpperBound( bound._variable ) ) && + _lpSolverType == LPSolverType::NATIVE ) + { + _boundManager.resetExplanation( variable, Tightening::UB ); + _groundBoundManager.addGroundBound( variable, bound._value, Tightening::UB, true ); + _boundManager.tightenUpperBound( variable, bound._value ); + if ( !constraint.getPhaseFixingEntry() ) + constraint.setPhaseFixingEntry( + getGroundBoundEntry( variable, Tightening::UB ) ); + } + else if ( !_produceUNSATProofs || _lpSolverType == LPSolverType::GUROBI ) + _boundManager.tightenUpperBound( variable, bound._value ); + } + } + + DEBUG( _tableau->verifyInvariants() ); + ENGINE_LOG( "Done with split\n" ); +} + void Engine::applyBoundTightenings() { List tightenings; @@ -2171,9 +2342,7 @@ bool Engine::applyValidConstraintCaseSplit( PiecewiseLinearConstraint *constrain .ascii() ); constraint->setActiveConstraint( false ); - PiecewiseLinearCaseSplit validSplit = constraint->getValidCaseSplit(); - _smtCore.recordImpliedValidSplit( validSplit ); - applySplit( validSplit ); + applyPlcPhaseFixingTightenings( *constraint ); if ( _soiManager ) _soiManager->removeCostComponentFromHeuristicCost( constraint ); @@ -2221,7 +2390,7 @@ void Engine::tightenBoundsOnConstraintMatrix() struct timespec start = TimeUtils::sampleMicro(); if ( _statistics.getLongAttribute( Statistics::NUM_MAIN_LOOP_ITERATIONS ) % - GlobalConfiguration::BOUND_TIGHTING_ON_CONSTRAINT_MATRIX_FREQUENCY == + GlobalConfiguration::BOUND_TIGHTENING_ON_CONSTRAINT_MATRIX_FREQUENCY == 0 ) { _rowBoundTightener->examineConstraintMatrix( true ); @@ -2268,7 +2437,7 @@ void Engine::performPrecisionRestoration( PrecisionRestorer::RestoreBasics resto double before = _degradationChecker.computeDegradation( *_tableau ); // - _precisionRestorer.restorePrecision( *this, *_tableau, _smtCore, restoreBasics ); + _precisionRestorer.restorePrecision( *this, *_tableau, _searchTreeHandler, restoreBasics ); struct timespec end = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TOTAL_TIME_PRECISION_RESTORATION, TimeUtils::timePassed( start, end ) ); @@ -2289,7 +2458,7 @@ void Engine::performPrecisionRestoration( PrecisionRestorer::RestoreBasics resto // Try again! start = TimeUtils::sampleMicro(); _precisionRestorer.restorePrecision( - *this, *_tableau, _smtCore, PrecisionRestorer::DO_NOT_RESTORE_BASICS ); + *this, *_tableau, _searchTreeHandler, PrecisionRestorer::DO_NOT_RESTORE_BASICS ); end = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TOTAL_TIME_PRECISION_RESTORATION, TimeUtils::timePassed( start, end ) ); @@ -2317,6 +2486,12 @@ void Engine::storeInitialEngineState() } } +void Engine::restoreInitialEngineState() +{ + if ( _initialStateStored ) + _precisionRestorer.restoreInitialEngineState( *this ); +} + bool Engine::basisRestorationNeeded() const { return _basisRestorationRequired == Engine::STRONG_RESTORATION_NEEDED || @@ -2335,7 +2510,7 @@ Query *Engine::getQuery() void Engine::checkBoundCompliancyWithDebugSolution() { - if ( _smtCore.checkSkewFromDebuggingSolution() ) + if ( _searchTreeHandler.checkSkewFromDebuggingSolution() ) { // The stack is compliant, we should not have learned any non-compliant bounds for ( const auto &var : _preprocessedQuery->_debuggingSolution ) @@ -2372,11 +2547,6 @@ void Engine::quitSignal() _quitRequested = true; } -Engine::ExitCode Engine::getExitCode() const -{ - return _exitCode; -} - std::atomic_bool *Engine::getQuitRequested() { return &_quitRequested; @@ -2430,7 +2600,7 @@ unsigned Engine::performSymbolicBoundTightening( Query *inputQuery ) // Step 1: tell the NLR about the current bounds if ( inputQuery ) { - // Obtain from and store bounds into inputquery if it is not null. + // Obtain from and store bounds into inputQuery if it is not null. _networkLevelReasoner->obtainCurrentBounds( *inputQuery ); } else @@ -2514,8 +2684,6 @@ void Engine::preContextPushHook() { struct timespec start = TimeUtils::sampleMicro(); _boundManager.storeLocalBounds(); - if ( _produceUNSATProofs ) - _groundBoundManager.storeLocalBounds(); struct timespec end = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_CONTEXT_PUSH_HOOK, @@ -2527,9 +2695,11 @@ void Engine::postContextPopHook() struct timespec start = TimeUtils::sampleMicro(); _boundManager.restoreLocalBounds(); - if ( _produceUNSATProofs ) - _groundBoundManager.restoreLocalBounds(); - _tableau->postContextPopHook(); + if ( getLpSolverType() == LPSolverType::NATIVE ) + { + _tableau->postContextPopHook(); + _costFunctionManager->computeCoreCostFunction(); + } struct timespec end = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TIME_CONTEXT_POP_HOOK, @@ -2541,16 +2711,15 @@ void Engine::reset() resetStatistics(); _sncMode = false; clearViolatedPLConstraints(); - resetSmtCore(); + resetSearchTreeHandler(); resetBoundTighteners(); - resetExitCode(); } void Engine::resetStatistics() { Statistics statistics; _statistics = statistics; - _smtCore.setStatistics( &_statistics ); + _searchTreeHandler.setStatistics( &_statistics ); _tableau->setStatistics( &_statistics ); _rowBoundTightener->setStatistics( &_statistics ); _preprocessor.setStatistics( &_statistics ); @@ -2565,15 +2734,14 @@ void Engine::clearViolatedPLConstraints() _plConstraintToFix = NULL; } -void Engine::resetSmtCore() +void Engine::resetSearchTreeHandler() { - _smtCore.reset(); - _smtCore.initializeScoreTrackerIfNeeded( _plConstraints ); -} - -void Engine::resetExitCode() -{ - _exitCode = Engine::NOT_DONE; + _searchTreeHandler.reset(); +#ifdef BUILD_CADICAL + _searchTreeHandler.initializeScoreTrackerIfNeeded( _plConstraints, &_cdclCore ); +#else + _searchTreeHandler.initializeScoreTrackerIfNeeded( _plConstraints ); +#endif } void Engine::resetBoundTighteners() @@ -2680,9 +2848,9 @@ void Engine::decideBranchingHeuristics() _preprocessedQuery->getInputVariables().size() < GlobalConfiguration::INTERVAL_SPLITTING_THRESHOLD ) { - // NOTE: the benefit of input splitting is minimal with abstract intepretation disabled. - // Therefore, since the proof production mode does not currently support that, we do - // not perform input-splitting in proof production mode. + // NOTE: the benefit of input splitting is minimal with abstract interpretation + // disabled. Therefore, since the proof production mode does not currently support that, + // we do not perform input-splitting in proof production mode. divideStrategy = DivideStrategy::LargestInterval; if ( _verbosity >= 2 ) printf( "Branching heuristics set to LargestInterval\n" ); @@ -2697,15 +2865,19 @@ void Engine::decideBranchingHeuristics() } else { - divideStrategy = DivideStrategy::ReLUViolation; + divideStrategy = DivideStrategy::Polarity; if ( _verbosity >= 2 ) - printf( "Branching heuristics set to ReLUViolation\n" ); + printf( "Branching heuristics set to Polarity\n" ); } } } ASSERT( divideStrategy != DivideStrategy::Auto ); - _smtCore.setBranchingHeuristics( divideStrategy ); - _smtCore.initializeScoreTrackerIfNeeded( _plConstraints ); + _searchTreeHandler.setBranchingHeuristics( divideStrategy ); +#ifdef BUILD_CADICAL + _searchTreeHandler.initializeScoreTrackerIfNeeded( _plConstraints, &_cdclCore ); +#else + _searchTreeHandler.initializeScoreTrackerIfNeeded( _plConstraints ); +#endif } PiecewiseLinearConstraint *Engine::pickSplitPLConstraintBasedOnBaBsrHeuristic() @@ -2877,8 +3049,8 @@ PiecewiseLinearConstraint *Engine::pickSplitPLConstraint( DivideStrategy strateg PiecewiseLinearConstraint *candidatePLConstraint = NULL; if ( strategy == DivideStrategy::PseudoImpact ) { - if ( _smtCore.getStackDepth() > 3 ) - candidatePLConstraint = _smtCore.getConstraintsWithHighestScore(); + if ( _context.getLevel() > 3 ) + candidatePLConstraint = _searchTreeHandler.getConstraintsWithHighestScore(); else if ( !_preprocessedQuery->getInputVariables().empty() && _preprocessedQuery->getInputVariables().size() < GlobalConfiguration::INTERVAL_SPLITTING_THRESHOLD ) @@ -2887,7 +3059,7 @@ PiecewiseLinearConstraint *Engine::pickSplitPLConstraint( DivideStrategy strateg { candidatePLConstraint = pickSplitPLConstraintBasedOnPolarity(); if ( candidatePLConstraint == NULL ) - candidatePLConstraint = _smtCore.getConstraintsWithHighestScore(); + candidatePLConstraint = _searchTreeHandler.getConstraintsWithHighestScore(); } } else if ( strategy == DivideStrategy::BaBSR ) @@ -2897,8 +3069,7 @@ PiecewiseLinearConstraint *Engine::pickSplitPLConstraint( DivideStrategy strateg else if ( strategy == DivideStrategy::EarliestReLU ) candidatePLConstraint = pickSplitPLConstraintBasedOnTopology(); else if ( strategy == DivideStrategy::LargestInterval && - ( ( _smtCore.getStackDepth() + 1 ) % - GlobalConfiguration::INTERVAL_SPLITTING_FREQUENCY != + ( ( _context.getLevel() + 1 ) % GlobalConfiguration::INTERVAL_SPLITTING_FREQUENCY != 0 ) ) { // Conduct interval splitting periodically. @@ -2927,17 +3098,18 @@ PiecewiseLinearConstraint *Engine::pickSplitPLConstraintSnC( SnCDivideStrategy s return candidatePLConstraint; } -bool Engine::restoreSmtState( SmtState &smtState ) +bool Engine::restoreSearchTreeState( SearchTreeState &searchTreeState ) { + // TODO: this method should be updated as well, when integrating snc with cdcl try { - ASSERT( _smtCore.getStackDepth() == 0 ); + ASSERT( _searchTreeHandler.getStackDepth() == 0 ); // Step 1: all implied valid splits at root - for ( auto &validSplit : smtState._impliedValidSplitsAtRoot ) + for ( auto &validSplit : searchTreeState._impliedValidSplitsAtRoot ) { applySplit( validSplit ); - _smtCore.recordImpliedValidSplit( validSplit ); + _searchTreeHandler.recordImpliedValidSplit( validSplit ); } tightenBoundsOnConstraintMatrix(); @@ -2949,11 +3121,11 @@ bool Engine::restoreSmtState( SmtState &smtState ) while ( applyAllValidConstraintCaseSplits() ); // Step 2: replay the stack - for ( auto &stackEntry : smtState._stack ) + for ( auto &stackEntry : searchTreeState._stack ) { - _smtCore.replaySmtStackEntry( stackEntry ); + _searchTreeHandler.replaySearchTreeStackEntry( stackEntry ); // Do all the bound propagation, and set ReLU constraints to inactive (at - // least the one corresponding to the _activeSplit applied above. + // least the one corresponding to the _activeSplit applied above). tightenBoundsOnConstraintMatrix(); // For debugging purposes @@ -2969,16 +3141,28 @@ bool Engine::restoreSmtState( SmtState &smtState ) // The current query is unsat, and we need to pop. // If we're at level 0, the whole query is unsat. if ( _produceUNSATProofs ) - explainSimplexFailure(); + { + if ( _lpSolverType == LPSolverType::NATIVE ) + explainSimplexFailure(); +#ifdef BUILD_CADICAL + else + explainGurobiFailure(); +#endif + } + +#ifdef BUILD_CADICAL + if ( _solveWithCDCL && !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + _cdclCore.addDecisionBasedConflictClause(); +#endif - if ( !_smtCore.popSplit() ) + if ( !_searchTreeHandler.popSplit() ) { if ( _verbosity > 0 ) { printf( "\nEngine::solve: UNSAT query\n" ); _statistics.print(); } - _exitCode = Engine::UNSAT; + _exitCode = ExitCode::UNSAT; for ( PiecewiseLinearConstraint *p : _plConstraints ) p->setActiveConstraint( true ); return false; @@ -2987,13 +3171,20 @@ bool Engine::restoreSmtState( SmtState &smtState ) return true; } -void Engine::storeSmtState( SmtState &smtState ) +void Engine::storeSearchTreeState( SearchTreeState &searchTreeState ) { - _smtCore.storeSmtState( smtState ); + _searchTreeHandler.storeSearchTreeState( searchTreeState ); } bool Engine::solveWithMILPEncoding( double timeoutInSeconds ) { + // TODO: this method should be updated when integrating milp with cdcl + if ( !_initialized ) + { + initializeSolver(); + _initialized = true; + } + try { if ( _lpSolverType == LPSolverType::NATIVE && _tableau->basisMatrixAvailable() ) @@ -3010,7 +3201,7 @@ bool Engine::solveWithMILPEncoding( double timeoutInSeconds ) } catch ( const InfeasibleQueryException & ) { - _exitCode = Engine::UNSAT; + _exitCode = ExitCode::UNSAT; return false; } @@ -3033,19 +3224,19 @@ bool Engine::solveWithMILPEncoding( double timeoutInSeconds ) { if ( allNonlinearConstraintsHold() ) { - _exitCode = IEngine::SAT; + _exitCode = ExitCode::SAT; return true; } else { - _exitCode = IEngine::UNKNOWN; + _exitCode = ExitCode::UNKNOWN; return false; } } else if ( _gurobi->infeasible() ) - _exitCode = IEngine::UNSAT; + _exitCode = ExitCode::UNSAT; else if ( _gurobi->timeout() ) - _exitCode = IEngine::TIMEOUT; + _exitCode = ExitCode::TIMEOUT; else throw NLRError( NLRError::UNEXPECTED_RETURN_STATUS_FROM_GUROBI ); return false; @@ -3076,11 +3267,13 @@ bool Engine::performDeepSoILocalSearch() if ( initialPhasePattern.isZero() ) { if ( hasBranchingCandidate() ) - while ( !_smtCore.needToSplit() ) - _smtCore.reportRejectedPhasePatternProposal(); + while ( !_searchTreeHandler.needToSplit() ) + _searchTreeHandler.reportRejectedPhasePatternProposal(); return false; } + if ( _lpSolverType == LPSolverType::NATIVE ) + _costFunctionManager->computeCoreCostFunction(); // TODO ask Andrew minimizeHeuristicCost( initialPhasePattern ); ASSERT( allVarsWithinBounds() ); _soiManager->updateCurrentPhasePatternForSatisfiedPLConstraints(); @@ -3091,7 +3284,7 @@ bool Engine::performDeepSoILocalSearch() double costOfProposedPhasePattern = FloatUtils::infinity(); bool lastProposalAccepted = true; - while ( !_smtCore.needToSplit() ) + while ( !_searchTreeHandler.needToSplit() ) { struct timespec end = TimeUtils::sampleMicro(); _statistics.incLongAttribute( Statistics::TOTAL_TIME_LOCAL_SEARCH_MICRO, @@ -3132,7 +3325,11 @@ bool Engine::performDeepSoILocalSearch() return true; } } - else if ( FloatUtils::isZero( costOfLastAcceptedPhasePattern ) ) + else if ( FloatUtils::isZero( costOfLastAcceptedPhasePattern - + costOfProposedPhasePattern ) ) // TODO: check this fix + // with Andrew, and + // maybe PR into main + // branch of Marabou { // Corner case: the SoI is minimal but there are still some PL // constraints (those not in the SoI) unsatisfied. @@ -3140,8 +3337,8 @@ bool Engine::performDeepSoILocalSearch() // the SoI with the hope to branch on them early. bumpUpPseudoImpactOfPLConstraintsNotInSoI(); if ( hasBranchingCandidate() ) - while ( !_smtCore.needToSplit() ) - _smtCore.reportRejectedPhasePatternProposal(); + while ( !_searchTreeHandler.needToSplit() ) + _searchTreeHandler.reportRejectedPhasePatternProposal(); return false; } } @@ -3170,7 +3367,7 @@ bool Engine::performDeepSoILocalSearch() } else { - _smtCore.reportRejectedPhasePatternProposal(); + _searchTreeHandler.reportRejectedPhasePatternProposal(); lastProposalAccepted = false; } } @@ -3249,7 +3446,7 @@ void Engine::updatePseudoImpactWithSoICosts( double costOfLastAcceptedPhasePatte ASSERT( constraintsUpdated.size() > 0 ); // Update the Pseudo-Impact estimation. for ( const auto &constraint : constraintsUpdated ) - _smtCore.updatePLConstraintScore( constraint, score ); + _searchTreeHandler.updatePLConstraintScore( constraint, score ); } void Engine::bumpUpPseudoImpactOfPLConstraintsNotInSoI() @@ -3259,7 +3456,7 @@ void Engine::bumpUpPseudoImpactOfPLConstraintsNotInSoI() { if ( plConstraint->isActive() && !plConstraint->supportSoI() && !plConstraint->phaseFixed() && !plConstraint->satisfied() ) - _smtCore.updatePLConstraintScore( + _searchTreeHandler.updatePLConstraintScore( plConstraint, GlobalConfiguration::SCORE_BUMP_FOR_PL_CONSTRAINTS_NOT_IN_SOI ); } } @@ -3367,25 +3564,18 @@ Query Engine::buildQueryFromCurrentState() const return query; } -void Engine::updateGroundUpperBound( const unsigned var, const double value ) -{ - ASSERT( var < _tableau->getN() && _produceUNSATProofs ); - if ( FloatUtils::lt( value, _groundBoundManager.getUpperBound( var ) ) ) - _groundBoundManager.setUpperBound( var, value ); -} - -void Engine::updateGroundLowerBound( const unsigned var, const double value ) +double Engine::getGroundBound( unsigned var, bool isUpper ) const { ASSERT( var < _tableau->getN() && _produceUNSATProofs ); - if ( FloatUtils::gt( value, _groundBoundManager.getLowerBound( var ) ) ) - _groundBoundManager.setLowerBound( var, value ); + return _groundBoundManager.getGroundBound( var, isUpper ? Tightening::UB : Tightening::LB ); } -double Engine::getGroundBound( unsigned var, bool isUpper ) const +std::shared_ptr +Engine::getGroundBoundEntry( unsigned var, bool isUpper ) const { ASSERT( var < _tableau->getN() && _produceUNSATProofs ); - return isUpper ? _groundBoundManager.getUpperBound( var ) - : _groundBoundManager.getLowerBound( var ); + return _groundBoundManager.getGroundBoundEntry( var, + isUpper ? Tightening::UB : Tightening::LB ); } bool Engine::shouldProduceProofs() const @@ -3395,8 +3585,10 @@ bool Engine::shouldProduceProofs() const void Engine::explainSimplexFailure() { - ASSERT( _produceUNSATProofs ); - + ASSERT( _produceUNSATProofs && _lpSolverType == LPSolverType::NATIVE ); +#ifdef BUILD_CADICAL + ASSERT( !_solveWithCDCL || !_cdclCore.hasConflictClause() ); +#endif DEBUG( checkGroundBounds() ); unsigned infeasibleVar = _boundManager.getInconsistentVariable(); @@ -3417,17 +3609,64 @@ void Engine::explainSimplexFailure() if ( infeasibleVar == IBoundManager::NO_VARIABLE_FOUND ) { markLeafToDelegate(); +#ifdef BUILD_CADICAL + if ( _solveWithCDCL ) + _cdclCore.addDecisionBasedConflictClause(); +#endif return; } ASSERT( infeasibleVar < _tableau->getN() ); - ASSERT( _UNSATCertificateCurrentPointer && - !( **_UNSATCertificateCurrentPointer ).getContradiction() ); + if ( !_solveWithCDCL ) + ASSERT( _UNSATCertificateCurrentPointer && + !( **_UNSATCertificateCurrentPointer ).getContradiction() ); + _statistics.incUnsignedAttribute( Statistics::NUM_CERTIFIED_LEAVES ); - writeContradictionToCertificate( infeasibleVar ); + Vector leafContradictionVec = computeContradiction( infeasibleVar ); + + if ( !_solveWithCDCL ) + { + writeContradictionToCertificate( leafContradictionVec, infeasibleVar ); + + ( **_UNSATCertificateCurrentPointer ).makeLeaf(); - ( **_UNSATCertificateCurrentPointer ).makeLeaf(); + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + SparseUnsortedList sparseContradictionToAnalyse = SparseUnsortedList(); + leafContradictionVec.empty() + ? sparseContradictionToAnalyse.initializeToEmpty() + : sparseContradictionToAnalyse.initialize( leafContradictionVec.data(), + leafContradictionVec.size() ); + + analyseExplanationDependencies( + sparseContradictionToAnalyse, _groundBoundManager.getCounter(), -1, true, 0 ); + } + + return; + } +#ifdef BUILD_CADICAL + // If both bounds are ground bounds, explanation would be empty and the clause trivial + if ( _boundManager.getUpperBound( infeasibleVar ) == + getGroundBound( infeasibleVar, Tightening::UB ) && + _boundManager.getLowerBound( infeasibleVar ) == + getGroundBound( infeasibleVar, Tightening::LB ) ) + { + _cdclCore.addDecisionBasedConflictClause(); + return; + } + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + SparseUnsortedList sparseContradiction( leafContradictionVec.data(), + leafContradictionVec.size() ); + Set clause = clauseFromContradictionVector( + sparseContradiction, _groundBoundManager.getCounter(), -1, true, 0 ); + + _cdclCore.addExternalClause( clause ); + } + else + _cdclCore.addDecisionBasedConflictClause(); +#endif } bool Engine::certifyInfeasibility( unsigned var ) const @@ -3437,8 +3676,8 @@ bool Engine::certifyInfeasibility( unsigned var ) const Vector contradiction = computeContradiction( var ); if ( contradiction.empty() ) - return FloatUtils::isNegative( _groundBoundManager.getUpperBound( var ) - - _groundBoundManager.getLowerBound( var ) ); + return FloatUtils::isNegative( _groundBoundManager.getGroundBound( var, Tightening::UB ) - + _groundBoundManager.getGroundBound( var, Tightening::LB ) ); SparseUnsortedList sparseContradiction = SparseUnsortedList(); @@ -3448,15 +3687,15 @@ bool Engine::certifyInfeasibility( unsigned var ) const // In case contradiction is a vector of zeros if ( sparseContradiction.empty() ) - return FloatUtils::isNegative( _groundBoundManager.getUpperBound( var ) - - _groundBoundManager.getLowerBound( var ) ); - - double derivedBound = - UNSATCertificateUtils::computeCombinationUpperBound( sparseContradiction, - _tableau->getSparseA(), - _groundBoundManager.getUpperBounds(), - _groundBoundManager.getLowerBounds(), - _tableau->getN() ); + return FloatUtils::isNegative( _groundBoundManager.getGroundBound( var, Tightening::UB ) - + _groundBoundManager.getGroundBound( var, Tightening::LB ) ); + + double derivedBound = UNSATCertificateUtils::computeCombinationUpperBound( + sparseContradiction, + _tableau->getSparseA(), + _groundBoundManager.getAllGroundBounds( Tightening::UB ).data(), + _groundBoundManager.getAllGroundBounds( Tightening::LB ).data(), + _tableau->getN() ); return FloatUtils::isNegative( derivedBound ); } @@ -3470,16 +3709,16 @@ double Engine::explainBound( unsigned var, bool isUpper ) const explanation = _boundManager.getExplanation( var, isUpper ); if ( explanation.empty() ) - return isUpper ? _groundBoundManager.getUpperBound( var ) - : _groundBoundManager.getLowerBound( var ); + return _groundBoundManager.getGroundBound( var, isUpper ? Tightening::UB : Tightening::LB ); - return UNSATCertificateUtils::computeBound( var, - isUpper, - explanation, - _tableau->getSparseA(), - _groundBoundManager.getUpperBounds(), - _groundBoundManager.getLowerBounds(), - _tableau->getN() ); + return UNSATCertificateUtils::computeBound( + var, + isUpper, + explanation, + _tableau->getSparseA(), + _groundBoundManager.getAllGroundBounds( Tightening::UB ).data(), + _groundBoundManager.getAllGroundBounds( Tightening::LB ).data(), + _tableau->getN() ); } bool Engine::validateBounds( unsigned var, double epsilon, bool isUpper ) const @@ -3537,9 +3776,9 @@ bool Engine::checkGroundBounds() const for ( unsigned i = 0; i < _tableau->getN(); ++i ) { - if ( FloatUtils::gt( _groundBoundManager.getLowerBound( i ), + if ( FloatUtils::gt( _groundBoundManager.getGroundBound( i, Tightening::UB ), _boundManager.getLowerBound( i ) ) || - FloatUtils::lt( _groundBoundManager.getUpperBound( i ), + FloatUtils::lt( _groundBoundManager.getGroundBound( i, Tightening::LB ), _boundManager.getUpperBound( i ) ) ) return false; } @@ -3679,7 +3918,8 @@ const UnsatCertificateNode *Engine::getUNSATCertificateRoot() const bool Engine::certifyUNSATCertificate() { - ASSERT( _produceUNSATProofs && _UNSATCertificate && !_smtCore.getStackDepth() ); + ASSERT( _produceUNSATProofs && _UNSATCertificate && !_searchTreeHandler.getStackDepth() && + !_solveWithCDCL ); for ( auto &constraint : _plConstraints ) { @@ -3692,7 +3932,7 @@ bool Engine::certifyUNSATCertificate() return false; } } - + _UNSATCertificateCurrentPointer->get()->deleteUnusedLemmas(); struct timespec certificationStart = TimeUtils::sampleMicro(); _precisionRestorer.restoreInitialEngineState( *this ); @@ -3701,8 +3941,8 @@ bool Engine::certifyUNSATCertificate() for ( unsigned i = 0; i < _tableau->getN(); ++i ) { - groundUpperBounds[i] = _groundBoundManager.getUpperBound( i ); - groundLowerBounds[i] = _groundBoundManager.getLowerBound( i ); + groundUpperBounds[i] = _groundBoundManager.getGroundBound( i, Tightening::UB ); + groundLowerBounds[i] = _groundBoundManager.getGroundBound( i, Tightening::LB ); } if ( GlobalConfiguration::WRITE_JSON_PROOF ) @@ -3760,15 +4000,20 @@ bool Engine::certifyUNSATCertificate() void Engine::markLeafToDelegate() { ASSERT( _produceUNSATProofs ); + UnsatCertificateNode *currentUnsatCertificateNode = NULL; + if ( !_solveWithCDCL ) + { + // Mark leaf with toDelegate Flag + currentUnsatCertificateNode = _UNSATCertificateCurrentPointer->get(); + ASSERT( _UNSATCertificateCurrentPointer && + !currentUnsatCertificateNode->getContradiction() ); + currentUnsatCertificateNode->setDelegationStatus( DelegationStatus::DELEGATE_DONT_SAVE ); + currentUnsatCertificateNode->deletePLCExplanations(); + } - // Mark leaf with toDelegate Flag - UnsatCertificateNode *currentUnsatCertificateNode = _UNSATCertificateCurrentPointer->get(); - ASSERT( _UNSATCertificateCurrentPointer && !currentUnsatCertificateNode->getContradiction() ); - currentUnsatCertificateNode->setDelegationStatus( DelegationStatus::DELEGATE_SAVE ); - currentUnsatCertificateNode->deletePLCExplanations(); _statistics.incUnsignedAttribute( Statistics::NUM_DELEGATED_LEAVES ); - if ( !currentUnsatCertificateNode->getChildren().empty() ) + if ( !_solveWithCDCL && currentUnsatCertificateNode->getChildren().empty() ) currentUnsatCertificateNode->makeLeaf(); } @@ -3802,15 +4047,13 @@ const Vector Engine::computeContradiction( unsigned infeasibleVar ) cons return contradiction; } -void Engine::writeContradictionToCertificate( unsigned infeasibleVar ) const +void Engine::writeContradictionToCertificate( const Vector &contradiction, + unsigned infeasibleVar ) const { ASSERT( _produceUNSATProofs ); - Vector leafContradictionVec = computeContradiction( infeasibleVar ); - - Contradiction *leafContradiction = leafContradictionVec.empty() - ? new Contradiction( infeasibleVar ) - : new Contradiction( leafContradictionVec ); + Contradiction *leafContradiction = contradiction.empty() ? new Contradiction( infeasibleVar ) + : new Contradiction( contradiction ); ( **_UNSATCertificateCurrentPointer ).setContradiction( leafContradiction ); } @@ -3824,9 +4067,34 @@ void Engine::setBoundExplainerContent( BoundExplainer *boundExplainer ) _boundManager.copyBoundExplainerContent( boundExplainer ); } -void Engine::propagateBoundManagerTightenings() +bool Engine::propagateBoundManagerTightenings() { - _boundManager.propagateTightenings(); + ASSERT( _lpSolverType == LPSolverType::NATIVE ); + try + { + _boundManager.propagateTightenings(); + if ( !consistentBounds() ) + { + if ( _produceUNSATProofs ) + explainSimplexFailure(); +#ifdef BUILD_CADICAL + else if ( _solveWithCDCL && !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + _cdclCore.addDecisionBasedConflictClause(); +#endif + } + + return consistentBounds(); + } + catch ( InfeasibleQueryException ) + { + if ( _produceUNSATProofs ) + explainSimplexFailure(); +#ifdef BUILD_CADICAL + else if ( _solveWithCDCL && !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + _cdclCore.addDecisionBasedConflictClause(); +#endif + return false; + } } void Engine::extractBounds( IQuery &inputQuery ) @@ -3871,12 +4139,470 @@ void Engine::extractBounds( IQuery &inputQuery ) } } -void Engine::addPLCLemma( std::shared_ptr &explanation ) +void Engine::incNumOfLemmas() { if ( !_produceUNSATProofs ) return; - ASSERT( explanation && _UNSATCertificate && _UNSATCertificateCurrentPointer ) + ASSERT( _UNSATCertificate && _UNSATCertificateCurrentPointer ) _statistics.incUnsignedAttribute( Statistics::NUM_LEMMAS ); - _UNSATCertificateCurrentPointer->get()->addPLCLemma( explanation ); -} \ No newline at end of file +} + +void Engine::assertEngineBoundsForSplit( const PiecewiseLinearCaseSplit &split ) +{ + for ( const auto &bound : split.getBoundTightenings() ) + { + if ( bound._type == Tightening::UB ) + ASSERT( + FloatUtils::lte( _boundManager.getUpperBound( bound._variable ), bound._value ) ); + + if ( bound._type == Tightening::LB ) + ASSERT( + FloatUtils::gte( _boundManager.getLowerBound( bound._variable ), bound._value ) ); + } +} + +unsigned Engine::getVerbosity() const +{ + return _verbosity; +} + +ExitCode Engine::getExitCode() const +{ + return _exitCode; +} + +void Engine::setExitCode( ExitCode exitCode ) +{ + _exitCode = exitCode; +} + +const List *Engine::getPiecewiseLinearConstraints() const +{ + return &_plConstraints; +} + +LPSolverType Engine::getLpSolverType() const +{ + return _lpSolverType; +} + +NLR::NetworkLevelReasoner *Engine::getNetworkLevelReasoner() const +{ + return _networkLevelReasoner; +} + +bool Engine::shouldSolveWithMILP() const +{ + return _solveWithMILP; +} + +std::shared_ptr +Engine::setGroundBoundFromLemma( const std::shared_ptr lemma, bool isPhaseFixing ) +{ + return _groundBoundManager.addGroundBound( lemma, isPhaseFixing ); +} + +bool Engine::shouldSolveWithCDCL() const +{ + return _solveWithCDCL; +} + +Set> +Engine::analyseExplanationDependencies( const SparseUnsortedList &explanation, + unsigned id, + int explainedVar, + bool isUpper, + double targetBound ) +{ + Vector linearCombination( 0 ); + UNSATCertificateUtils::getExplanationRowCombination( + explanation, linearCombination, _tableau->getSparseA(), _tableau->getN() ); + + if ( explainedVar >= 0 ) + linearCombination[explainedVar]++; + + Set> entries = + Set>(); + + Vector>> + contributions = + Vector>>(); + + Vector gub; + Vector glb; + + if ( GlobalConfiguration::MINIMIZE_PROOF_DEPENDENCIES ) + { + gub = Vector( _tableau->getN(), 0 ); + glb = Vector( _tableau->getN(), 0 ); + + for ( unsigned i = 0; i < _tableau->getN(); ++i ) + { + gub[i] = _groundBoundManager.getGroundBoundUpToId( i, Tightening::UB, id ); + glb[i] = _groundBoundManager.getGroundBoundUpToId( i, Tightening::LB, id ); + } + } + + // Iterate through all lemmas learned, check which participated in the explanation + for ( unsigned var = 0; var < linearCombination.size(); ++var ) + { + if ( !FloatUtils::isZero( linearCombination[var] ) ) + { + Tightening::BoundType btype = ( ( linearCombination[var] > 0 ) && isUpper ) || + ( ( linearCombination[var] < 0 ) && !isUpper ) + ? Tightening::UB + : Tightening::LB; + std::shared_ptr entry = + _groundBoundManager.getGroundBoundEntryUpToId( var, btype, id ); + + entries.insert( entry ); + + if ( GlobalConfiguration::MINIMIZE_PROOF_DEPENDENCIES && entry.get() && entry->lemma && + !entry->lemma->getToCheck() ) + { + double contribution = + ( entry->val - _groundBoundManager.getGroundBoundUpToId( var, btype, 0 ) ) * + linearCombination[var]; + + contributions.append( std::make_tuple( contribution, entry ) ); + } + } + } + + if ( GlobalConfiguration::MINIMIZE_PROOF_DEPENDENCIES ) + { + double explanationBound = + isUpper ? UNSATCertificateUtils::computeCombinationUpperBound( + linearCombination, gub.data(), glb.data(), _tableau->getN() ) + : UNSATCertificateUtils::computeCombinationLowerBound( + linearCombination, gub.data(), glb.data(), _tableau->getN() ); + + std::sort( + contributions.begin(), + contributions.end(), + []( std::tuple> a, + std::tuple> b ) { + return abs( std::get<0>( a ) ) < abs( std::get<0>( b ) ); + } ); + + if ( explainedVar < 0 || ( isUpper && explanationBound <= targetBound ) || + ( !isUpper && explanationBound >= targetBound ) ) + { + double overallContributions = 0; + + for ( const auto &contribution : contributions ) + { + overallContributions += abs( std::get<0>( contribution ) ); + if ( FloatUtils::lt( overallContributions, + abs( explanationBound - targetBound ), + GlobalConfiguration::LEMMA_CERTIFICATION_TOLERANCE ) ) + entries.erase( std::get<1>( contribution ) ); + else + break; + } + } + } + + if ( _solveWithCDCL ) + return entries; + + for ( const auto &entry : entries ) + { + ASSERT( entry->id < id ); + + if ( entry->lemma && !entry->lemma->getExplanations().empty() && + !entry->lemma->getExplanations().front().empty() && !entry->lemma->getToCheck() ) + { + entry->lemma->setToCheck(); + + _statistics.incUnsignedAttribute( Statistics::NUM_LEMMAS_USED ); + std::_List_const_iterator it = entry->lemma->getCausingVars().begin(); + for ( const auto &expl : entry->lemma->getExplanations() ) + { + if ( expl.empty() ) + { + std::advance( it, 1 ); + continue; + } + + analyseExplanationDependencies( expl, + entry->id, + *it, + entry->lemma->getCausingVarBound() == + Tightening::UB, + entry->lemma->getMinTargetBound() ); + + std::advance( it, 1 ); + } + } + } + + return entries; +} +#ifdef BUILD_CADICAL + +bool Engine::solveWithCDCL( double timeoutInSeconds ) +{ + return _cdclCore.solveWithCDCL( timeoutInSeconds ); +} + +Set Engine::clauseFromContradictionVector( const SparseUnsortedList &explanation, + unsigned id, + int explainedVar, + bool isUpper, + double targetBound ) +{ + ASSERT( _solveWithCDCL ); + ASSERT( _nlConstraints.empty() && !explanation.empty() ); + Set clause = Set(); + + Vector linearCombination( 0 ); + UNSATCertificateUtils::getExplanationRowCombination( + explanation, linearCombination, _tableau->getSparseA(), _tableau->getN() ); + + if ( explainedVar >= 0 ) + linearCombination[explainedVar]++; + + int lit; + int decisionCounter = 0; + + // Iterate through all constraints, check whether their phase was involved in the explanation + // Propagate literals accordingly + // Currently works only for ReLU constraints + for ( const auto &constraint : _plConstraints ) + { + lit = 0; + // TODO support max and disjunction + ASSERT( constraint->getType() != DISJUNCTION && constraint->getType() != MAX ); + if ( constraint->getPhaseFixingEntry() && constraint->getPhaseFixingEntry()->id < id ) + { + for ( unsigned var : constraint->getParticipatingVariables() ) + if ( !FloatUtils::isZero( linearCombination[var] ) ) + { + Tightening::BoundType boundTypeParticipating = + ( ( linearCombination[var] > 0 ) && isUpper ) || + ( ( linearCombination[var] < 0 ) && !isUpper ) + ? Tightening::UB + : Tightening::LB; + std::shared_ptr entry = + _groundBoundManager.getGroundBoundEntryUpToId( + var, boundTypeParticipating, id ); + + if ( entry->isPhaseFixing && + constraint->isBoundFixingPhase( + var, entry->val, boundTypeParticipating ) && + !entry->lemma ) + { + ++decisionCounter; + lit = constraint->propagatePhaseAsLit(); + break; + } + } + } + + if ( lit ) + { + ASSERT( !clause.exists( -lit ) ); + ASSERT( constraint->phaseFixed() || !constraint->isActive() ) + clause.insert( lit ); + } + } + + if ( decisionCounter >= _context.getLevel() ) + return clause; + + Set> entries = + analyseExplanationDependencies( explanation, id, explainedVar, isUpper, targetBound ); + + for ( const auto &entry : entries ) + { + ASSERT( entry->id < id ); + Set minorClause; + if ( entry->lemma && !entry->lemma->getExplanations().empty() && + !entry->lemma->getExplanations().front().empty() && entry->clause.empty() ) + { + minorClause = + clauseFromContradictionVector( entry->lemma->getExplanations().back(), + entry->id, + entry->lemma->getCausingVars().back(), + entry->lemma->getCausingVarBound() == Tightening::UB, + entry->lemma->getBound() ); + + _groundBoundManager.addClauseToGroundBoundEntry( entry, minorClause ); + _statistics.incUnsignedAttribute( Statistics::NUM_LEMMAS_USED ); + } + else + minorClause = entry->clause; + + decisionCounter = 0; + for ( int literal : minorClause ) + { + ASSERT( literal && !clause.exists( -literal ) ); + clause.insert( literal ); + if ( _cdclCore.isDecision( literal ) ) + ++decisionCounter; + if ( decisionCounter >= _context.getLevel() ) + return minorClause; + } + + decisionCounter = 0; + for ( const auto &clauseLit : clause ) + if ( _cdclCore.isDecision( clauseLit ) ) + ++decisionCounter; + + if ( decisionCounter >= _context.getLevel() ) + return clause; + } + + return clause; +} + +Set Engine::explainPhaseWithProof( const PiecewiseLinearConstraint *litConstraint ) +{ + ASSERT( _solveWithCDCL && _produceUNSATProofs ); + ASSERT( litConstraint ); + ASSERT( litConstraint->phaseFixed() || !litConstraint->isActive() ); + + // Get corresponding constraints, and its participating variables + std::shared_ptr phaseFixingEntry = + litConstraint->getPhaseFixingEntry(); + + // Return a clause explaining the phase-fixing GroundBound entry + ASSERT( phaseFixingEntry && phaseFixingEntry->lemma && phaseFixingEntry->isPhaseFixing ); + + if ( !phaseFixingEntry->clause.empty() ) + return phaseFixingEntry->clause; + + SparseUnsortedList tempExpl = phaseFixingEntry->lemma->getExplanations().back(); + _statistics.incUnsignedAttribute( Statistics::NUM_LEMMAS_USED ); + Set clause = clauseFromContradictionVector( tempExpl, + phaseFixingEntry->id, + phaseFixingEntry->lemma->getCausingVars().back(), + phaseFixingEntry->lemma->getCausingVarBound(), + phaseFixingEntry->lemma->getBound() ); + + return clause; +} + +void Engine::removeLiteralFromPropagations( int literal ) +{ + ASSERT( _solveWithCDCL ); + _cdclCore.removeLiteralFromPropagations( literal ); +} + +void Engine::explainGurobiFailure() +{ + ASSERT( _lpSolverType == LPSolverType::GUROBI ); + ASSERT( _milpEncoder && _gurobi && _gurobi->infeasible() ); + ASSERT( _produceUNSATProofs ); + + ENGINE_LOG( "Extracting theory explanation..." ); + _gurobi->computeIIS(); + + Map bounds; + List dontCare; + List names; + _gurobi->extractIIS( bounds, dontCare, names ); + + Set clause; + for ( const auto &plc : _plConstraints ) + { + if ( !plc->phaseFixed() ) + continue; + + for ( unsigned variable : plc->getParticipatingVariables() ) + { + String variableName = Stringf( "x%u", variable ); + if ( ( ( bounds[variableName] == GurobiWrapper::IIS_UB || + bounds[variableName] == GurobiWrapper::IIS_BOTH ) && + plc->isBoundFixingPhase( + variable, _boundManager.getUpperBound( variable ), Tightening::UB ) ) || + ( ( bounds[variableName] == GurobiWrapper::IIS_LB || + bounds[variableName] == GurobiWrapper::IIS_BOTH ) && + plc->isBoundFixingPhase( + variable, _boundManager.getLowerBound( variable ), Tightening::LB ) ) ) + { + clause.insert( plc->propagatePhaseAsLit() ); + break; + } + } + } + + if ( _solveWithCDCL ) + _cdclCore.addExternalClause( clause ); + + ENGINE_LOG( Stringf( "Conflict analysis - done, conflict length %u, level %u", + clause.size(), + _context.getLevel() ) + .ascii() ); +} + +bool Engine::checkAssignmentComplianceWithClause( const Set &clause ) const +{ + ASSERT( _solveWithCDCL ); + + bool compliant = false; + for ( const auto &lit : clause ) + { + const PiecewiseLinearConstraint *plc = _cdclCore.getConstraintFromLit( lit ); + PiecewiseLinearCaseSplit litSplit = lit > 0 ? plc->getCaseSplit( RELU_PHASE_ACTIVE ) + : plc->getCaseSplit( RELU_PHASE_INACTIVE ); + bool compliantWithSplit = true; + for ( const auto &tightening : litSplit.getBoundTightenings() ) + { + if ( tightening._type == Tightening::UB ) + { + if ( FloatUtils::gt( _tableau->getValue( tightening._variable ), + tightening._value ) ) + compliantWithSplit = false; + } + else + { + if ( FloatUtils::lt( _tableau->getValue( tightening._variable ), + tightening._value ) ) + compliantWithSplit = false; + } + } + + if ( compliantWithSplit ) + { + compliant = true; + break; + } + } + return compliant; +} + +void Engine::configureForCDCL() +{ + GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH = false; + _solveWithCDCL = true; + _produceUNSATProofs = true; + _solveWithMILP = false; + _sncMode = false; + + _UNSATCertificateCurrentPointer = + new ( true ) CVC4::context::CDO( &_context, NULL ); +} + +SymbolicBoundTighteningType Engine::getSymbolicBoundTighteningType() const +{ + return _symbolicBoundTighteningType; +} + +const IBoundManager *Engine::getBoundManager() const +{ + return &_boundManager; +} + +List Engine::getOutputVariables() const +{ + return _preprocessedQuery->getOutputVariables(); +} + +std::shared_ptr Engine::getInputQuery() const +{ + return _preprocessedQuery; +} +#endif \ No newline at end of file diff --git a/src/engine/Engine.h b/src/engine/Engine.h index a3ea1c22d3..19c93b6740 100644 --- a/src/engine/Engine.h +++ b/src/engine/Engine.h @@ -22,11 +22,15 @@ #include "AutoTableau.h" #include "BlandsRule.h" #include "BoundManager.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Checker.h" #include "DantzigsRule.h" #include "DegradationChecker.h" #include "DivideStrategy.h" #include "GlobalConfiguration.h" +#include "GroundBoundManager.h" #include "GurobiWrapper.h" #include "IEngine.h" #include "IQuery.h" @@ -39,8 +43,8 @@ #include "PrecisionRestorer.h" #include "Preprocessor.h" #include "Query.h" +#include "SearchTreeHandler.h" #include "SignalHandler.h" -#include "SmtCore.h" #include "SmtLibWriter.h" #include "SnCDivideStrategy.h" #include "SparseUnsortedList.h" @@ -79,11 +83,16 @@ class Engine Engine(); ~Engine(); + /* + Required initialization before starting the solving loop. + */ + void initializeSolver() override; + /* Attempt to find a feasible solution for the input within a time limit (a timeout of 0 means no time limit). Returns true if found, false if infeasible. */ - bool solve( double timeoutInSeconds = 0 ); + bool solve( double timeoutInSeconds = 0 ) override; /* Minimize the cost function with respect to the current set of linear constraints. @@ -104,7 +113,7 @@ class Engine bool processInputQuery( const IQuery &inputQuery, bool preprocess ); Query prepareSnCQuery(); - void exportQueryWithError( String errorMessage ); + void exportQueryWithError( String errorMessage ) override; /* Methods for calculating bounds. @@ -125,9 +134,9 @@ class Engine /* Methods for storing and restoring the state of the engine. */ - void storeState( EngineState &state, TableauStateStorageLevel level ) const; - void restoreState( const EngineState &state ); - void setNumPlConstraintsDisabledByValidSplits( unsigned numConstraints ); + void storeState( EngineState &state, TableauStateStorageLevel level ) const override; + void restoreState( const EngineState &state ) override; + void setNumPlConstraintsDisabledByValidSplits( unsigned numConstraints ) override; /* Preprocessor access. @@ -138,7 +147,7 @@ class Engine /* A request from the user to terminate */ - void quitSignal(); + void quitSignal() override; const Statistics *getStatistics() const; @@ -146,11 +155,6 @@ class Engine Query buildQueryFromCurrentState() const; - /* - Get the exit code - */ - Engine::ExitCode getExitCode() const; - /* Get the quitRequested flag */ @@ -159,24 +163,29 @@ class Engine /* Get the list of input variables */ - List getInputVariables() const; + List getInputVariables() const override; /* Add equations and tightenings from a split. */ - void applySplit( const PiecewiseLinearCaseSplit &split ); + void applySplit( const PiecewiseLinearCaseSplit &split ) override; + + /* + Apply tightenings implied from phase fixing of the given piecewise linear constraint; + */ + void applyPlcPhaseFixingTightenings( PiecewiseLinearConstraint &constraint ) override; /* Hooks invoked before/after context push/pop to store/restore/update context independent data. */ - void postContextPopHook(); - void preContextPushHook(); + void postContextPopHook() override; + void preContextPushHook() override; /* Reset the state of the engine, before solving a new query (as part of DnC mode). */ - void reset(); + void reset() override; /* Reset the statistics object @@ -194,41 +203,40 @@ class Engine void setVerbosity( unsigned verbosity ); /* - Apply the stack to the newly created SmtCore, returns false if UNSAT is + Apply the stack to the newly created SearchTreeHandler, returns false if UNSAT is found in this process. */ - bool restoreSmtState( SmtState &smtState ); + bool restoreSearchTreeState( SearchTreeState &searchTreeState ) override; /* - Store the current stack of the smtCore into smtState + Store the current stack of the searchTreeHandler into searchTreeState */ - void storeSmtState( SmtState &smtState ); + void storeSearchTreeState( SearchTreeState &searchTreeState ) override; /* Pick the piecewise linear constraint for splitting */ - PiecewiseLinearConstraint *pickSplitPLConstraint( DivideStrategy strategy ); + PiecewiseLinearConstraint *pickSplitPLConstraint( DivideStrategy strategy ) override; /* Call-back from QueryDividers Pick the piecewise linear constraint for splitting */ - PiecewiseLinearConstraint *pickSplitPLConstraintSnC( SnCDivideStrategy strategy ); + PiecewiseLinearConstraint *pickSplitPLConstraintSnC( SnCDivideStrategy strategy ) override; /* PSA: The following two methods are for DnC only and should be used very cautiously. */ - void resetSmtCore(); - void resetExitCode(); + void resetSearchTreeHandler(); void resetBoundTighteners(); /* Register initial split when in SnC mode */ - void applySnCSplit( PiecewiseLinearCaseSplit sncSplit, String queryId ); + void applySnCSplit( PiecewiseLinearCaseSplit sncSplit, String queryId ) override; - bool inSnCMode() const; + bool inSnCMode() const override; /* Apply bound tightenings stored in the bound manager. @@ -239,71 +247,169 @@ class Engine Apply all bound tightenings (row and matrix-based) in the queue. */ - void applyAllBoundTightenings(); + void applyAllBoundTightenings() override; /* Apply all valid case splits proposed by the constraints. Return true if a valid case split has been applied. */ - bool applyAllValidConstraintCaseSplits(); + bool applyAllValidConstraintCaseSplits() override; void setRandomSeed( unsigned seed ); /* Returns true iff the engine is in proof production mode */ - bool shouldProduceProofs() const; - - /* - Update the ground bounds - */ - void updateGroundUpperBound( unsigned var, double value ); - void updateGroundLowerBound( unsigned var, double value ); + bool shouldProduceProofs() const override; /* Return all ground bounds as a vector */ - double getGroundBound( unsigned var, bool isUpper ) const; + double getGroundBound( unsigned var, bool isUpper ) const override; + std::shared_ptr + getGroundBoundEntry( unsigned var, bool isUpper ) const override; + /* Get the current pointer of the UNSAT certificate */ - UnsatCertificateNode *getUNSATCertificateCurrentPointer() const; + UnsatCertificateNode *getUNSATCertificateCurrentPointer() const override; /* Set the current pointer of the UNSAT certificate */ - void setUNSATCertificateCurrentPointer( UnsatCertificateNode *node ); + void setUNSATCertificateCurrentPointer( UnsatCertificateNode *node ) override; /* Get the pointer to the root of the UNSAT certificate */ - const UnsatCertificateNode *getUNSATCertificateRoot() const; + const UnsatCertificateNode *getUNSATCertificateRoot() const override; /* Certify the UNSAT certificate */ - bool certifyUNSATCertificate(); + bool certifyUNSATCertificate() override; /* Get the boundExplainer */ - const BoundExplainer *getBoundExplainer() const; + const BoundExplainer *getBoundExplainer() const override; /* Set the boundExplainer */ - void setBoundExplainerContent( BoundExplainer *boundExplainer ); + void setBoundExplainerContent( BoundExplainer *boundExplainer ) override; /* Propagate bound tightenings stored in the BoundManager */ - void propagateBoundManagerTightenings(); + bool propagateBoundManagerTightenings() override; + + /* + Returns true if the query should be solved using MILP + */ + bool shouldSolveWithMILP() const override; + + /* + Check whether a timeout value has been provided and exceeded. + */ + bool shouldExitDueToTimeout( double timeout ) const override; + + /* + Returns the verbosity level. + */ + unsigned getVerbosity() const override; + + /* + Returns the exit code from the SearchTreeHandler + */ + ExitCode getExitCode() const override; + + /* + Sets the exit code inside the SmyCore + */ + void setExitCode( ExitCode exitCode ) override; + + /* + Return the piecewise linear constraints list of the engine + */ + const List *getPiecewiseLinearConstraints() const override; + + /* + Returns the type of the LP Solver in use. + */ + LPSolverType getLpSolverType() const override; + + /* + Returns a pointer to the internal NLR object. + */ + NLR::NetworkLevelReasoner *getNetworkLevelReasoner() const override; + + /* + Solve the input query with a MILP solver (Gurobi) + */ + bool solveWithMILPEncoding( double timeoutInSeconds ) override; /* Add lemma to the UNSAT Certificate */ - void addPLCLemma( std::shared_ptr &explanation ); + void incNumOfLemmas() override; + + /* + Add ground bound entry using a lemma + */ + std::shared_ptr + setGroundBoundFromLemma( const std::shared_ptr lemma, bool isPhaseFixing ) override; + + /* + Should solve the input query with CDCL? + */ + bool shouldSolveWithCDCL() const override; + +#ifdef BUILD_CADICAL + /* + Solve the input query with CDCL + */ + bool solveWithCDCL( double timeoutInSeconds = 0 ) override; + + /* + Creates a boolean-abstracted clause explaining a boolean-abstracted literal + */ + Set explainPhaseWithProof( const PiecewiseLinearConstraint *litConstraint ) override; + + /* + Explain infeasibility of gurobi + */ + void explainGurobiFailure() override; + + /* + Returns true if the current assignment complies with the given clause (CDCL). + */ + bool checkAssignmentComplianceWithClause( const Set &clause ) const override; + + /* + Configure the engine to allow solving with CDCL, used for testing only. + */ + void configureForCDCL(); + + List getOutputVariables() const override; + +#endif + + /* + Returns the symbolic bound tightening type in use. + */ + SymbolicBoundTighteningType getSymbolicBoundTighteningType() const override; + + /* + Returns the bound manager + */ + const IBoundManager *getBoundManager() const override; + + /* + Returns the input query. + */ + std::shared_ptr getInputQuery() const override; private: enum BasisRestorationRequired { @@ -325,9 +431,14 @@ class Engine */ void explicitBasisBoundTightening(); + /* + A code indicating how the run terminated. + */ + ExitCode _exitCode; + /* Context is the central object that manages memory and back-tracking - across context-dependent components - SMTCore, + across context-dependent components - SearchTreeHandler, PiecewiseLinearConstraints, BoundManager, etc. */ Context _context; @@ -371,7 +482,7 @@ class Engine /* Preprocessed Query */ - std::unique_ptr _preprocessedQuery; + std::shared_ptr _preprocessedQuery; /* Pivot selection strategies. @@ -387,9 +498,16 @@ class Engine AutoRowBoundTightener _rowBoundTightener; /* - The SMT engine is in charge of case splitting. + The Search Tree engine is in charge of case splitting. */ - SmtCore _smtCore; + SearchTreeHandler _searchTreeHandler; + +#ifdef BUILD_CADICAL + /* + The CDCL core in charge of communicating with the SAT solver. + */ + CdclCore _cdclCore; +#endif /* Number of pl constraints disabled by valid splits. @@ -442,11 +560,6 @@ class Engine */ std::atomic_bool _quitRequested; - /* - A code indicating how the run terminated. - */ - ExitCode _exitCode; - /* The number of visited states when we performed the previous restoration. This field serves as an indication of whether or @@ -542,6 +655,24 @@ class Engine LinearExpression _heuristicCost; + /* + Proof Production data structures + */ + bool _produceUNSATProofs; + GroundBoundManager _groundBoundManager; + UnsatCertificateNode *_UNSATCertificate; + CVC4::context::CDO *_UNSATCertificateCurrentPointer; + + /* + Solve the query with CDCL + */ + bool _solveWithCDCL; + + /* + Is this engine solver initialized + */ + bool _initialized; + /* Perform a simplex step: compute the cost function, pick the entering and leaving variables and perform a pivot. @@ -592,7 +723,7 @@ class Engine void selectViolatedPlConstraint(); /* - Report the violated PL constraint to the SMT engine. + Report the violated PL constraint to the Search Tree engine. */ void reportPlViolation(); @@ -664,6 +795,7 @@ class Engine Restore the tableau from the original version. */ void storeInitialEngineState(); + void restoreInitialEngineState() override; void performPrecisionRestoration( PrecisionRestorer::RestoreBasics restoreBasics ); bool basisRestorationNeeded() const; @@ -696,11 +828,6 @@ class Engine */ void performSimulation(); - /* - Check whether a timeout value has been provided and exceeded. - */ - bool shouldExitDueToTimeout( double timeout ) const; - /* Evaluate the network on legal inputs; obtain the assignment for as many intermediate nodes as possible; and then try @@ -772,11 +899,6 @@ class Engine */ PiecewiseLinearConstraint *pickSplitPLConstraintBasedOnIntervalWidth(); - /* - Solve the input query with a MILP solver (Gurobi) - */ - bool solveWithMILPEncoding( double timeoutInSeconds ); - /* Perform SoI-based stochastic local search */ @@ -817,15 +939,15 @@ class Engine /* Get Context reference */ - Context &getContext() + Context &getContext() override { return _context; } /* - Checks whether the current bounds are consistent. Exposed for the SmtCore. + Checks whether the current bounds are consistent. Exposed for the SearchTreeHandler. */ - bool consistentBounds() const; + bool consistentBounds() const override; /* DEBUG only @@ -833,15 +955,6 @@ class Engine */ void checkGurobiBoundConsistency() const; - /* - Proof Production data structes - */ - - bool _produceUNSATProofs; - BoundManager _groundBoundManager; - UnsatCertificateNode *_UNSATCertificate; - CVC4::context::CDO *_UNSATCertificateCurrentPointer; - /* Returns true iff there is a variable with bounds that can explain infeasibility of the tableau */ @@ -850,7 +963,7 @@ class Engine /* Returns the value of a variable bound, as explained by the BoundExplainer */ - double explainBound( unsigned var, bool isUpper ) const; + double explainBound( unsigned var, bool isUpper ) const override; /* Returns true iff both bounds are epsilon close to their explained bounds @@ -865,7 +978,7 @@ class Engine /* Finds the variable causing failure and updates its bounds explanations */ - void explainSimplexFailure(); + void explainSimplexFailure() override; /* Sanity check for ground bounds, returns true iff all bounds are at least as tight as their @@ -904,7 +1017,35 @@ class Engine /* Writes the details of a contradiction to the UNSAT certificate node */ - void writeContradictionToCertificate( unsigned infeasibleVar ) const; + void writeContradictionToCertificate( const Vector &contradiction, + unsigned infeasibleVar ) const; + + + void assertEngineBoundsForSplit( const PiecewiseLinearCaseSplit &split ) override; + + /* + Analyse dependencies of an explanation vector, resulting in a list of necessary ground bounds + */ + Set> + analyseExplanationDependencies( const SparseUnsortedList &explanation, + unsigned id, + int explainedVar, + bool isUpper, + double targetBound ); +#ifdef BUILD_CADICAL + /* + Creates a boolean-abstracted clause from an explanation + */ + Set clauseFromContradictionVector( const SparseUnsortedList &explanation, + unsigned id, + int explainedVar, + bool isUpper, + double targetBound ) override; + + void removeLiteralFromPropagations( int literal ) override; + + +#endif }; #endif // __Engine_h__ diff --git a/src/engine/EngineState.h b/src/engine/EngineState.h index 3e174bac02..b423173f12 100644 --- a/src/engine/EngineState.h +++ b/src/engine/EngineState.h @@ -42,7 +42,7 @@ class EngineState /* A unique ID allocated to every state that is stored, for - debugging purposes. These are assigned by the SMT core. + debugging purposes. These are assigned by the Search Tree handler. */ unsigned _stateId; }; diff --git a/src/engine/ExitCode.h b/src/engine/ExitCode.h new file mode 100644 index 0000000000..821cad00d6 --- /dev/null +++ b/src/engine/ExitCode.h @@ -0,0 +1,30 @@ +/********************* */ +/*! \file ExitCode.h + ** \verbatim + ** Top contributors (to current version): + ** Guy Katz, Duligur Ibeling + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2024 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __ExitCode_h__ +#define __ExitCode_h__ + +enum ExitCode { + UNSAT = 0, + SAT = 1, + ERROR = 2, + UNKNOWN = 3, + TIMEOUT = 4, + QUIT_REQUESTED = 5, + + NOT_DONE = 999, +}; + +#endif //__ExitCode_h__ diff --git a/src/engine/IBoundManager.h b/src/engine/IBoundManager.h index f2aad70ce5..8731261b26 100644 --- a/src/engine/IBoundManager.h +++ b/src/engine/IBoundManager.h @@ -27,7 +27,6 @@ #define __IBoundManager_h__ #include "List.h" -#include "PiecewiseLinearFunctionType.h" #include "Tightening.h" #include "Vector.h" @@ -38,6 +37,7 @@ class SparseUnsortedList; class TableauRow; class ITableau; class IRowBoundTightener; +class PiecewiseLinearConstraint; class IBoundManager { public: @@ -127,13 +127,14 @@ class IBoundManager Add a lemma to the UNSATCertificateNode object Return true iff adding the lemma was successful */ - virtual bool - addLemmaExplanationAndTightenBound( unsigned var, - double value, - Tightening::BoundType affectedVarBound, - const List &causingVars, - Tightening::BoundType causingVarBound, - PiecewiseLinearFunctionType constraintType ) = 0; + virtual bool addLemmaExplanationAndTightenBound( unsigned var, + double value, + Tightening::BoundType affectedVarBound, + const List &causingVars, + Tightening::BoundType causingVarBound, + PiecewiseLinearConstraint &constraint, + bool isPhaseFixing = false, + double minTargetBound = 0 ) = 0; /* Return the content of the object containing all explanations for variable bounds in the diff --git a/src/engine/IEngine.h b/src/engine/IEngine.h index ea592bb4e5..1a3ed1a479 100644 --- a/src/engine/IEngine.h +++ b/src/engine/IEngine.h @@ -18,9 +18,14 @@ #include "BoundExplainer.h" #include "DivideStrategy.h" +#include "ExitCode.h" +#include "GroundBoundManager.h" +#include "LPSolverType.h" #include "List.h" #include "PlcLemma.h" #include "SnCDivideStrategy.h" +#include "SymbolicBoundTighteningType.h" +#include "TableauState.h" #include "TableauStateStorageLevel.h" #include "Vector.h" #include "context/context.h" @@ -31,11 +36,15 @@ class EngineState; class Equation; +namespace NLR { +class NetworkLevelReasoner; +} class PiecewiseLinearCaseSplit; -class PLCLemma; -class SmtState; +class SearchTreeState; class String; class PiecewiseLinearConstraint; +class PLCLemma; +class Query; class UnsatCertificateNode; class IEngine @@ -43,22 +52,16 @@ class IEngine public: virtual ~IEngine(){}; - enum ExitCode { - UNSAT = 0, - SAT = 1, - ERROR = 2, - UNKNOWN = 3, - TIMEOUT = 4, - QUIT_REQUESTED = 5, - - NOT_DONE = 999, - }; - /* Add equations and apply tightenings from a PL case split. */ virtual void applySplit( const PiecewiseLinearCaseSplit &split ) = 0; + /* + Apply tighetenings implied from phase fixing of the given piecewise linear constraint; + */ + virtual void applyPlcPhaseFixingTightenings( PiecewiseLinearConstraint &constraint ) = 0; + /* Register initial SnC split */ @@ -79,25 +82,25 @@ class IEngine virtual void setNumPlConstraintsDisabledByValidSplits( unsigned numConstraints ) = 0; /* - Store the current stack of the smtCore into smtState + Store the current stack of the searchTreeHandler into searchTreeState */ - virtual void storeSmtState( SmtState &smtState ) = 0; + virtual void storeSearchTreeState( SearchTreeState &searchTreeState ) = 0; /* - Apply the stack to the newly created SmtCore, returns false if UNSAT is + Apply the stack to the newly created SearchTreeHandler, returns false if UNSAT is found in this process. */ - virtual bool restoreSmtState( SmtState &smtState ) = 0; + virtual bool restoreSearchTreeState( SearchTreeState &searchTreeState ) = 0; /* - Solve the encoded query. - */ - virtual bool solve( double timeoutInSeconds ) = 0; + Required initialization before starting the solving loop. + */ + virtual void initializeSolver() = 0; /* - Retrieve the exit code. + Solve the encoded query. */ - virtual ExitCode getExitCode() const = 0; + virtual bool solve( double timeoutInSeconds ) = 0; /* Methods for DnC: reset the engine state for re-use, @@ -121,12 +124,6 @@ class IEngine */ virtual double explainBound( unsigned var, bool isUpper ) const = 0; - /* - * Update the ground bounds - */ - virtual void updateGroundUpperBound( unsigned var, double value ) = 0; - virtual void updateGroundLowerBound( unsigned var, double value ) = 0; - virtual void applyAllBoundTightenings() = 0; virtual bool applyAllValidConstraintCaseSplits() = 0; @@ -146,6 +143,8 @@ class IEngine Get the ground bound of the variable */ virtual double getGroundBound( unsigned var, bool isUpper ) const = 0; + virtual std::shared_ptr + getGroundBoundEntry( unsigned var, bool isUpper ) const = 0; /* Get the current pointer in the UNSAT certificate node @@ -185,12 +184,126 @@ class IEngine /* Propagate bound tightenings stored in the BoundManager */ - virtual void propagateBoundManagerTightenings() = 0; + virtual bool propagateBoundManagerTightenings() = 0; /* Add lemma to the UNSAT Certificate */ - virtual void addPLCLemma( std::shared_ptr &explanation ) = 0; + virtual void incNumOfLemmas() = 0; + + /* + Add ground bound entry using a lemma + */ + virtual std::shared_ptr + setGroundBoundFromLemma( const std::shared_ptr lemma, bool isPhaseFixing ) = 0; + + /* + Returns true if the query should be solved using MILP + */ + virtual bool shouldSolveWithMILP() const = 0; + + virtual void assertEngineBoundsForSplit( const PiecewiseLinearCaseSplit &split ) = 0; + + /* + Check whether a timeout value has been provided and exceeded. + */ + virtual bool shouldExitDueToTimeout( double timeout ) const = 0; + + /* + Returns the verbosity level. + */ + virtual unsigned getVerbosity() const = 0; + + virtual void exportQueryWithError( String ) = 0; + + /* + Returns the exit code. + */ + virtual ExitCode getExitCode() const = 0; + + /* + Sets the exit code. + */ + virtual void setExitCode( ExitCode exitCode ) = 0; + + /* + Return the piecewise linear constraints list of the engine + */ + virtual const List *getPiecewiseLinearConstraints() const = 0; + + /* + Returns the type of the LP Solver in use. + */ + virtual LPSolverType getLpSolverType() const = 0; + + /* + Returns a pointer to the internal NLR object. + */ + virtual NLR::NetworkLevelReasoner *getNetworkLevelReasoner() const = 0; + + virtual void restoreInitialEngineState() = 0; + + /* + Solve the input query with a MILP solver + */ + virtual bool solveWithMILPEncoding( double timeoutInSeconds ) = 0; + + /* + Should solve the input query with CDCL? + */ + virtual bool shouldSolveWithCDCL() const = 0; + + /* + Return the output variables. + */ + virtual List getOutputVariables() const = 0; + + /* + Returns the symbolic bound tightening type in use. + */ + virtual SymbolicBoundTighteningType getSymbolicBoundTighteningType() const = 0; + + /* + Returns the bound manager + */ + virtual const IBoundManager *getBoundManager() const = 0; + + /* + Returns the input query. + */ + virtual std::shared_ptr getInputQuery() const = 0; + +#ifdef BUILD_CADICAL + /* + Solve the input query with CDCL + */ + virtual bool solveWithCDCL( double timeoutInSeconds ) = 0; + + /* + Methods for creating conflict clauses and lemmas, for CDCL. + */ + virtual Set clauseFromContradictionVector( const SparseUnsortedList &explanation, + unsigned id, + int explainedVar, + bool isUpper, + double targetBound ) = 0; + virtual Set explainPhaseWithProof( const PiecewiseLinearConstraint *litConstraint ) = 0; + + /* + Explain infeasibility of Gurobi, for CDCL conflict clauses + */ + virtual void explainGurobiFailure() = 0; + + /* + Returns true if the current assignment complies with the given clause (CDCL). + */ + virtual bool checkAssignmentComplianceWithClause( const Set &clause ) const = 0; + + /* + Remove a literal from the propagation list to the SAT solver, during the CDCL solving. + */ + virtual void removeLiteralFromPropagations( int literal ) = 0; +#endif }; #endif // __IEngine_h__ diff --git a/src/engine/IQuery.h b/src/engine/IQuery.h index 4238ea81d2..156518b7be 100644 --- a/src/engine/IQuery.h +++ b/src/engine/IQuery.h @@ -52,6 +52,7 @@ class IQuery virtual void markInputVariable( unsigned variable, unsigned inputIndex ) = 0; virtual void markOutputVariable( unsigned variable, unsigned outputIndex ) = 0; + virtual void unmarkOutputVariables() = 0; virtual unsigned inputVariableByIndex( unsigned index ) const = 0; virtual unsigned outputVariableByIndex( unsigned index ) const = 0; virtual unsigned getNumInputVariables() const = 0; @@ -59,6 +60,12 @@ class IQuery virtual List getInputVariables() const = 0; virtual List getOutputVariables() const = 0; + virtual void addOutputConstraint( const Equation &equation ) = 0; + virtual const List &getOutputConstraints() const = 0; + + virtual bool isQueryWithDisjunction() const = 0; + virtual void markQueryWithDisjunction() = 0; + /* Methods for setting and getting the solution. */ diff --git a/src/engine/IRowBoundTightener.h b/src/engine/IRowBoundTightener.h index 8d2d658e59..2286bc5d50 100644 --- a/src/engine/IRowBoundTightener.h +++ b/src/engine/IRowBoundTightener.h @@ -37,7 +37,7 @@ class IRowBoundTightener through the tableau. Can also do this until saturation, meaning that we continue until no new bounds are learned. */ - virtual void examineInvertedBasisMatrix( bool untilSaturation ) = 0; + virtual unsigned examineInvertedBasisMatrix( bool untilSaturation ) = 0; /* Derive and enqueue new bounds for all varaibles, implicitly using the @@ -46,7 +46,7 @@ class IRowBoundTightener is performed via FTRANs. Can also do this until saturation, meaning that we continue until no new bounds are learned. */ - virtual void examineImplicitInvertedBasisMatrix( bool untilSaturation ) = 0; + virtual unsigned examineImplicitInvertedBasisMatrix( bool untilSaturation ) = 0; /* Derive and enqueue new bounds for all varaibles, using the diff --git a/src/engine/InputQuery.cpp b/src/engine/InputQuery.cpp index d275646b06..86dc134f28 100644 --- a/src/engine/InputQuery.cpp +++ b/src/engine/InputQuery.cpp @@ -273,6 +273,12 @@ void InputQuery::markOutputVariable( unsigned variable, unsigned outputIndex ) _outputIndexToVariable[outputIndex] = variable; } +void InputQuery::unmarkOutputVariables() +{ + _variableToOutputIndex.clear(); + _outputIndexToVariable.clear(); +} + unsigned InputQuery::inputVariableByIndex( unsigned index ) const { ASSERT( _inputIndexToVariable.exists( index ) ); @@ -377,6 +383,9 @@ Query *InputQuery::generateQuery() const query->markOutputVariable( pair.first, pair.second ); } + for ( const auto &equation : _outputConstraints ) + query->addOutputConstraint( equation ); + for ( const auto &pair : _solution ) { query->setSolutionValue( pair.first, pair.second ); @@ -396,3 +405,23 @@ void InputQuery::dump() const query->dump(); delete query; } + +void InputQuery::addOutputConstraint( const Equation &equation ) +{ + _outputConstraints.append( equation ); +} + +const List &InputQuery::getOutputConstraints() const +{ + return _outputConstraints; +} + +bool InputQuery::isQueryWithDisjunction() const +{ + return _isQueryWithDisjunction; +} + +void InputQuery::markQueryWithDisjunction() +{ + _isQueryWithDisjunction = true; +} \ No newline at end of file diff --git a/src/engine/InputQuery.h b/src/engine/InputQuery.h index 083161ee0d..65460045e4 100644 --- a/src/engine/InputQuery.h +++ b/src/engine/InputQuery.h @@ -69,9 +69,9 @@ class InputQuery : public IQuery InputQuery(); ~InputQuery(); - void setNumberOfVariables( unsigned numberOfVariables ); - unsigned getNumberOfVariables() const; - unsigned getNewVariable(); + void setNumberOfVariables( unsigned numberOfVariables ) override; + unsigned getNumberOfVariables() const override; + unsigned getNewVariable() override; /* The set*Bound methods will overwrite the currently stored bound of the variable. @@ -82,65 +82,72 @@ class InputQuery : public IQuery variable if and only if the new bound is tighter. The methods return true if and if only the bound is tightened. */ - void setLowerBound( unsigned variable, double bound ); - void setUpperBound( unsigned variable, double bound ); - bool tightenLowerBound( unsigned variable, double bound ); - bool tightenUpperBound( unsigned variable, double bound ); + void setLowerBound( unsigned variable, double bound ) override; + void setUpperBound( unsigned variable, double bound ) override; + bool tightenLowerBound( unsigned variable, double bound ) override; + bool tightenUpperBound( unsigned variable, double bound ) override; - double getLowerBound( unsigned variable ) const; - double getUpperBound( unsigned variable ) const; + double getLowerBound( unsigned variable ) const override; + double getUpperBound( unsigned variable ) const override; /* Note: currently there is no API call for removing equations, PLConstraints or NLConstraints, becaues CDList does not support removal. */ - void addEquation( const Equation &equation ); - unsigned getNumberOfEquations() const; + void addEquation( const Equation &equation ) override; + unsigned getNumberOfEquations() const override; ; void getEquations( List &equations ) const; - void addPiecewiseLinearConstraint( PiecewiseLinearConstraint *constraint ); + void addPiecewiseLinearConstraint( PiecewiseLinearConstraint *constraint ) override; void getPiecewiseLinearConstraints( List &constraints ) const; - void addClipConstraint( unsigned b, unsigned f, double floor, double ceiling ); + void addClipConstraint( unsigned b, unsigned f, double floor, double ceiling ) override; - void addNonlinearConstraint( NonlinearConstraint *constraint ); - void getNonlinearConstraints( Vector &constraints ) const; + void addNonlinearConstraint( NonlinearConstraint *constraint ) override; + void getNonlinearConstraints( Vector &constraints ) const override; /* Methods for handling input and output variables */ - void markInputVariable( unsigned variable, unsigned inputIndex ); - void markOutputVariable( unsigned variable, unsigned outputIndex ); - unsigned inputVariableByIndex( unsigned index ) const; - unsigned outputVariableByIndex( unsigned index ) const; - unsigned getNumInputVariables() const; - unsigned getNumOutputVariables() const; + void markInputVariable( unsigned variable, unsigned inputIndex ) override; + void markOutputVariable( unsigned variable, unsigned outputIndex ) override; + void unmarkOutputVariables() override; + unsigned inputVariableByIndex( unsigned index ) const override; + unsigned outputVariableByIndex( unsigned index ) const override; + unsigned getNumInputVariables() const override; + unsigned getNumOutputVariables() const override; void getInputVariables( List &inputVariables ) const; void getOutputVariables( List &outputVariables ) const; - List getInputVariables() const; - List getOutputVariables() const; + List getInputVariables() const override; + List getOutputVariables() const override; + + void addOutputConstraint( const Equation &equation ) override; + const List &getOutputConstraints() const override; + + bool isQueryWithDisjunction() const override; + void markQueryWithDisjunction() override; /* Methods for setting and getting the solution. */ - void setSolutionValue( unsigned variable, double value ); - double getSolutionValue( unsigned variable ) const; + void setSolutionValue( unsigned variable, double value ) override; + double getSolutionValue( unsigned variable ) const override; /* Store a correct possible solution */ - void storeDebuggingSolution( unsigned variable, double value ); + void storeDebuggingSolution( unsigned variable, double value ) override; /* Serializes the query to a file which can then be loaded using QueryLoader. */ - void saveQuery( const String &fileName ); + void saveQuery( const String &fileName ) override; void saveQueryAsSmtLib( const String &filename ) const; /* Generate a non-context-dependent version of the Query */ - Query *generateQuery() const; + Query *generateQuery() const override; void dump() const; @@ -187,6 +194,7 @@ class InputQuery : public IQuery IndexVariableMap _inputIndexToVariable; VariableIndexMap _variableToOutputIndex; IndexVariableMap _outputIndexToVariable; + List _outputConstraints; /* Stores the satisfying assignment. @@ -198,6 +206,13 @@ class InputQuery : public IQuery */ VariableValueMap _debuggingSolution; + /* + * true if the query contains a disjunction constraint, used to check if it is possible to + * convert this verification query into a reachability query. + * TODO: remove this after adding support for converting a query with disjunction constraints + * into a reachability query + */ + bool _isQueryWithDisjunction; /* Free any stored pl constraints. */ diff --git a/src/engine/LeakyReluConstraint.cpp b/src/engine/LeakyReluConstraint.cpp index 28aec9033c..b46e45a395 100644 --- a/src/engine/LeakyReluConstraint.cpp +++ b/src/engine/LeakyReluConstraint.cpp @@ -14,6 +14,9 @@ #include "LeakyReluConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "DivideStrategy.h" #include "FloatUtils.h" @@ -25,6 +28,7 @@ #include "PiecewiseLinearCaseSplit.h" #include "PiecewiseLinearConstraint.h" #include "Query.h" +#include "SearchTreeHandler.h" #include "Statistics.h" #include "TableauRow.h" @@ -149,6 +153,11 @@ void LeakyReluConstraint::checkIfLowerBoundUpdateFixesPhase( unsigned variable, else if ( variable == _inactiveAux && FloatUtils::isPositive( bound ) ) setPhaseStatus( RELU_PHASE_ACTIVE ); } + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } void LeakyReluConstraint::checkIfUpperBoundUpdateFixesPhase( unsigned variable, double bound ) @@ -164,6 +173,11 @@ void LeakyReluConstraint::checkIfUpperBoundUpdateFixesPhase( unsigned variable, else if ( variable == _inactiveAux && FloatUtils::isZero( bound ) ) setPhaseStatus( RELU_PHASE_INACTIVE ); } + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } void LeakyReluConstraint::notifyLowerBound( unsigned variable, double bound ) @@ -193,8 +207,14 @@ void LeakyReluConstraint::notifyLowerBound( unsigned variable, double bound ) { // If we're in the active phase, activeAux should be 0 if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _activeAux, 0, Tightening::UB, { variable }, Tightening::LB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _activeAux, + 0, + Tightening::UB, + { variable }, + Tightening::LB, + *this, + true, + 0 ); else if ( !proofs && _auxVarsInUse ) _boundManager->tightenUpperBound( _activeAux, 0 ); @@ -209,8 +229,14 @@ void LeakyReluConstraint::notifyLowerBound( unsigned variable, double bound ) else if ( variable == _f && FloatUtils::isNegative( bound ) ) { if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _b, bound / _slope, Tightening::LB, { _f }, Tightening::LB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _b, + bound / _slope, + Tightening::LB, + { _f }, + Tightening::LB, + *this, + false, + bound ); else _boundManager->tightenLowerBound( _b, bound / _slope, *_inactiveTighteningRow ); } @@ -221,8 +247,14 @@ void LeakyReluConstraint::notifyLowerBound( unsigned variable, double bound ) { // Inactive phase if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _inactiveAux, 0, Tightening::UB, { _activeAux }, Tightening::LB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _inactiveAux, + 0, + Tightening::UB, + { _activeAux }, + Tightening::LB, + *this, + true, + 0 ); else _boundManager->tightenUpperBound( _inactiveAux, 0 ); } @@ -232,8 +264,14 @@ void LeakyReluConstraint::notifyLowerBound( unsigned variable, double bound ) { // Active phase if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - _activeAux, 0, Tightening::UB, { _inactiveAux }, Tightening::LB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( _activeAux, + 0, + Tightening::UB, + { _inactiveAux }, + Tightening::LB, + *this, + true, + 0 ); else _boundManager->tightenUpperBound( _activeAux, 0 ); } @@ -270,8 +308,14 @@ void LeakyReluConstraint::notifyUpperBound( unsigned variable, double bound ) { unsigned partner = ( variable == _f ) ? _b : _f; if ( proofs ) - _boundManager->addLemmaExplanationAndTightenBound( - partner, bound, Tightening::UB, { variable }, Tightening::UB, getType() ); + _boundManager->addLemmaExplanationAndTightenBound( partner, + bound, + Tightening::UB, + { variable }, + Tightening::UB, + *this, + false, + bound ); else _boundManager->tightenUpperBound( partner, bound ); } @@ -280,7 +324,7 @@ void LeakyReluConstraint::notifyUpperBound( unsigned variable, double bound ) // A negative upper bound of b implies inactive phase if ( proofs && _auxVarsInUse ) _boundManager->addLemmaExplanationAndTightenBound( - _inactiveAux, 0, Tightening::UB, { _b }, Tightening::UB, getType() ); + _inactiveAux, 0, Tightening::UB, { _b }, Tightening::UB, *this, true, 0 ); _boundManager->tightenUpperBound( _f, _slope * bound, *_inactiveTighteningRow ); } @@ -289,7 +333,7 @@ void LeakyReluConstraint::notifyUpperBound( unsigned variable, double bound ) // A negative upper bound of f implies inactive phase as well if ( proofs && _auxVarsInUse ) _boundManager->addLemmaExplanationAndTightenBound( - _inactiveAux, 0, Tightening::UB, { _f }, Tightening::UB, getType() ); + _inactiveAux, 0, Tightening::UB, { _f }, Tightening::UB, *this, true, 0 ); _boundManager->tightenUpperBound( _b, bound / _slope, *_inactiveTighteningRow ); } @@ -403,7 +447,7 @@ List LeakyReluConstraint::getSmartFixes( ITablea List LeakyReluConstraint::getCaseSplits() const { - if ( _phaseStatus != PHASE_NOT_FIXED ) + if ( getPhaseStatus() != PHASE_NOT_FIXED ) throw MarabouError( MarabouError::REQUESTED_CASE_SPLITS_FROM_FIXED_CONSTRAINT ); List splits; @@ -525,14 +569,14 @@ PiecewiseLinearCaseSplit LeakyReluConstraint::getActiveSplit() const bool LeakyReluConstraint::phaseFixed() const { - return _phaseStatus != PHASE_NOT_FIXED; + return getPhaseStatus() != PHASE_NOT_FIXED; } PiecewiseLinearCaseSplit LeakyReluConstraint::getImpliedCaseSplit() const { - ASSERT( _phaseStatus != PHASE_NOT_FIXED ); + ASSERT( getPhaseStatus() != PHASE_NOT_FIXED ); - if ( _phaseStatus == RELU_PHASE_ACTIVE ) + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) return getActiveSplit(); return getInactiveSplit(); @@ -551,8 +595,8 @@ void LeakyReluConstraint::dump( String &output ) const _b, _slope, _constraintActive ? "Yes" : "No", - _phaseStatus, - phaseToString( _phaseStatus ).ascii() ); + getPhaseStatus(), + phaseToString( getPhaseStatus() ).ascii() ); output += Stringf( "b in [%s, %s], ", @@ -625,18 +669,18 @@ void LeakyReluConstraint::eliminateVariable( __attribute__( ( unused ) ) unsigne { if ( FloatUtils::gt( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != RELU_PHASE_INACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_INACTIVE ); } else if ( FloatUtils::lt( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != RELU_PHASE_ACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_ACTIVE ); } } else if ( variable == _activeAux ) { if ( FloatUtils::isPositive( fixedValue ) ) { - ASSERT( _phaseStatus != RELU_PHASE_ACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_ACTIVE ); } } else @@ -644,7 +688,7 @@ void LeakyReluConstraint::eliminateVariable( __attribute__( ( unused ) ) unsigne // This is the inactive aux variable if ( FloatUtils::isPositive( fixedValue ) ) { - ASSERT( _phaseStatus != RELU_PHASE_INACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_INACTIVE ); } } } ); @@ -1007,3 +1051,97 @@ void LeakyReluConstraint::createInactiveTighteningRow() _inactiveTighteningRow->_row[2] = TableauRow::Entry( _tableauAuxVars.back(), 1 ); _inactiveTighteningRow->_scalar = 0; } + +#ifdef BUILD_CADICAL +void LeakyReluConstraint::booleanAbstraction( + Map &cadicalVarToPlc ) +{ + unsigned int idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); +} + +int LeakyReluConstraint::propagatePhaseAsLit() const +{ + ASSERT( _cdclVars.size() == 1 ) + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) + return _cdclVars.back(); + else if ( getPhaseStatus() == RELU_PHASE_INACTIVE ) + return -_cdclVars.back(); + else + return 0; +} + +void LeakyReluConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) ); + setActiveConstraint( false ); + + if ( lit > 0 ) + setPhaseStatus( RELU_PHASE_ACTIVE ); + else + setPhaseStatus( RELU_PHASE_INACTIVE ); +} + +int LeakyReluConstraint::getLiteralForDecision() const +{ + ASSERT( getPhaseStatus() == PHASE_NOT_FIXED ); + + if ( _direction == RELU_PHASE_INACTIVE ) + return -(int)_cdclVars.front(); + if ( _direction == RELU_PHASE_ACTIVE ) + return (int)_cdclVars.front(); + + if ( existsAssignment( _f ) ) + if ( FloatUtils::isPositive( getAssignment( _f ) ) ) + return (int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); +} + +bool LeakyReluConstraint::isBoundFixingPhase( unsigned int var, + double bound, + Tightening::BoundType boundType ) const +{ + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) + { + if ( var == _b && boundType == Tightening::LB && !FloatUtils::isNegative( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::LB && !FloatUtils::isNegative( bound ) ) + return true; + + if ( _auxVarsInUse ) + { + if ( var == _activeAux && boundType == Tightening::UB && FloatUtils::isZero( bound ) ) + return true; + + if ( var == _inactiveAux && boundType == Tightening::LB && + FloatUtils::isPositive( bound ) ) + return true; + } + } + else if ( getPhaseStatus() == RELU_PHASE_INACTIVE ) + { + if ( var == _b && boundType == Tightening::UB && FloatUtils::isNegative( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::UB && FloatUtils::isNegative( bound ) ) + return true; + + if ( _auxVarsInUse ) + { + if ( var == _activeAux && boundType == Tightening::LB && + FloatUtils::isPositive( bound ) ) + return true; + + if ( var == _inactiveAux && boundType == Tightening::UB && FloatUtils::isZero( bound ) ) + return true; + } + } + + return false; +} +#endif \ No newline at end of file diff --git a/src/engine/LeakyReluConstraint.h b/src/engine/LeakyReluConstraint.h index 37ae5374d6..6a05a4b9ab 100644 --- a/src/engine/LeakyReluConstraint.h +++ b/src/engine/LeakyReluConstraint.h @@ -239,6 +239,35 @@ class LeakyReluConstraint : public PiecewiseLinearConstraint void updateScoreBasedOnPolarity() override; +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; + + /* + Returns on which phase to decide this constraint, as a cadical var + */ + int getLiteralForDecision() const override; + + bool isBoundFixingPhase( unsigned var, + double bound, + Tightening::BoundType boundType ) const override; +#endif + private: unsigned _b, _f; double _slope; diff --git a/src/engine/Marabou.cpp b/src/engine/Marabou.cpp index feed4d648b..c0401b7814 100644 --- a/src/engine/Marabou.cpp +++ b/src/engine/Marabou.cpp @@ -221,12 +221,21 @@ void Marabou::solveQuery() unsigned timeoutInSeconds = Options::get()->getInt( Options::TIMEOUT ); if ( _engine->processInputQuery( _inputQuery ) ) { - _engine->solve( timeoutInSeconds ); - if ( _engine->shouldProduceProofs() && _engine->getExitCode() == Engine::UNSAT ) - _engine->certifyUNSATCertificate(); + if ( _engine->shouldSolveWithMILP() ) + _engine->solveWithMILPEncoding( timeoutInSeconds ); +#ifdef BUILD_CADICAL + else if ( _engine->shouldSolveWithCDCL() ) + _engine->solveWithCDCL( timeoutInSeconds ); +#endif + else + { + _engine->solve( timeoutInSeconds ); + if ( _engine->shouldProduceProofs() && _engine->getExitCode() == ExitCode::UNSAT ) + _engine->certifyUNSATCertificate(); + } } - if ( _engine->getExitCode() == Engine::UNKNOWN ) + if ( _engine->getExitCode() == ExitCode::UNKNOWN ) { struct timespec end = TimeUtils::sampleMicro(); unsigned long long totalElapsed = TimeUtils::timePassed( start, end ); @@ -245,21 +254,21 @@ void Marabou::solveQuery() // TODO: update the variable assignment using NLR if possible and double-check that all the // constraints are indeed satisfied. - if ( _engine->getExitCode() == Engine::SAT ) + if ( _engine->getExitCode() == ExitCode::SAT ) _engine->extractSolution( _inputQuery ); } void Marabou::displayResults( unsigned long long microSecondsElapsed ) const { - Engine::ExitCode result = _engine->getExitCode(); + ExitCode result = _engine->getExitCode(); String resultString; - if ( result == Engine::UNSAT ) + if ( result == ExitCode::UNSAT ) { resultString = "unsat"; printf( "unsat\n" ); } - else if ( result == Engine::SAT ) + else if ( result == ExitCode::SAT ) { resultString = "sat"; printf( "sat\n" ); @@ -278,17 +287,17 @@ void Marabou::displayResults( unsigned long long microSecondsElapsed ) const _inputQuery.getSolutionValue( _inputQuery.outputVariableByIndex( i ) ) ); printf( "\n" ); } - else if ( result == Engine::TIMEOUT ) + else if ( result == ExitCode::TIMEOUT ) { resultString = "TIMEOUT"; printf( "Timeout\n" ); } - else if ( result == Engine::ERROR ) + else if ( result == ExitCode::ERROR ) { resultString = "ERROR"; printf( "Error\n" ); } - else if ( result == Engine::UNKNOWN ) + else if ( result == ExitCode::UNKNOWN ) { resultString = "UNKNOWN"; printf( "UNKNOWN\n" ); diff --git a/src/engine/MarabouMain.cpp b/src/engine/MarabouMain.cpp index 0ad1a83a06..114198c0a1 100644 --- a/src/engine/MarabouMain.cpp +++ b/src/engine/MarabouMain.cpp @@ -76,7 +76,29 @@ int marabouMain( int argc, char **argv ) { printVersion(); return 0; - }; + } + +#ifdef BUILD_CADICAL + if ( options->getBool( Options::SOLVE_WITH_CDCL ) ) + { + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + { + if ( !options->getBool( Options::PRODUCE_PROOFS ) ) + { + options->setBool( Options::PRODUCE_PROOFS, true ); + printf( "Turning --prove-unsat on to allow proof-based conflict clauses.\n" ); + } + printf( "Please note that producing complete UNSAT proofs while --cdcl is on is " + "not yet supported.\n" ); + } + + if ( options->getBool( Options::DNC_MODE ) ) + { + options->setBool( Options::DNC_MODE, false ); + printf( "CDCL is not yet supported with snc mode, turning --snc off.\n" ); + } + } +#endif if ( options->getBool( Options::PRODUCE_PROOFS ) ) { @@ -100,14 +122,6 @@ int marabouMain( int argc, char **argv ) "Proof production is not yet supported with MILP solvers, turning --milp off.\n" ); } - if ( options->getBool( Options::PRODUCE_PROOFS ) && - ( options->getLPSolverType() == LPSolverType::GUROBI ) ) - { - options->setString( Options::LP_SOLVER, "native" ); - printf( "Proof production is not yet supported with MILP solvers, using native simplex " - "engine.\n" ); - } - if ( options->getBool( Options::DNC_MODE ) && options->getBool( Options::PARALLEL_DEEPSOI ) ) { diff --git a/src/engine/MaxConstraint.cpp b/src/engine/MaxConstraint.cpp index 13571c44a5..0c62a0c4a6 100644 --- a/src/engine/MaxConstraint.cpp +++ b/src/engine/MaxConstraint.cpp @@ -14,6 +14,9 @@ #include "MaxConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "FloatUtils.h" #include "ITableau.h" @@ -40,6 +43,8 @@ MaxConstraint::MaxConstraint( unsigned f, const Set &elements ) , _maxLowerBound( FloatUtils::negativeInfinity() ) , _haveFeasibleEliminatedPhases( false ) , _maxValueOfEliminatedPhases( FloatUtils::negativeInfinity() ) + , _elementsToCadicalVars() + , _cadicalVarsToElements() { } @@ -186,8 +191,8 @@ void MaxConstraint::notifyLowerBound( unsigned variable, double value ) } if ( phaseFixed() ) - _phaseStatus = ( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED - : variableToPhase( *_elements.begin() ) ); + setPhaseStatus( ( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED + : variableToPhase( *_elements.begin() ) ) ); if ( isActive() && _boundManager ) { @@ -247,8 +252,8 @@ void MaxConstraint::notifyUpperBound( unsigned variable, double value ) } if ( phaseFixed() ) - _phaseStatus = ( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED - : variableToPhase( *_elements.begin() ) ); + setPhaseStatus( ( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED + : variableToPhase( *_elements.begin() ) ) ); // There is no need to recompute the max lower bound and max index here. @@ -481,8 +486,8 @@ void MaxConstraint::updateVariableIndex( unsigned oldIndex, unsigned newIndex ) _elementToAux[newIndex] = auxVar; _auxToElement[auxVar] = newIndex; - if ( _phaseStatus == variableToPhase( oldIndex ) ) - _phaseStatus = variableToPhase( newIndex ); + if ( getPhaseStatus() == variableToPhase( oldIndex ) ) + setPhaseStatus( variableToPhase( newIndex ) ); } else { @@ -533,8 +538,8 @@ void MaxConstraint::eliminateVariable( unsigned var, double value ) } if ( phaseFixed() ) - _phaseStatus = ( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED - : variableToPhase( *_elements.begin() ) ); + setPhaseStatus( _haveFeasibleEliminatedPhases ? MAX_PHASE_ELIMINATED + : variableToPhase( *_elements.begin() ) ); if ( _elements.size() == 0 ) _obsolete = true; @@ -739,7 +744,7 @@ void MaxConstraint::addTableauAuxVar( unsigned tableauAuxVar, unsigned constrain _elementToTighteningRow[element] = nullptr; } -void MaxConstraint::applyTightenings( const List &tightenings ) const +void MaxConstraint::applyTightenings( const List &tightenings ) { bool proofs = _boundManager && _boundManager->shouldProduceProofs(); @@ -781,7 +786,9 @@ void MaxConstraint::applyTightenings( const List &tightenings ) cons Tightening::UB, getElements(), Tightening::UB, - getType() ); + *this, + false, + tightening._value ); else { ASSERT( _elements.exists( tightening._variable ) ); @@ -797,3 +804,45 @@ void MaxConstraint::applyTightenings( const List &tightenings ) cons } } } + +#ifdef BUILD_CADICAL +void MaxConstraint::booleanAbstraction( + Map &cadicalVarToPlc ) +{ + unsigned int idx; + for ( auto &element : _elements ) + { + idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); + _elementsToCadicalVars.insert( element, idx ); + _cadicalVarsToElements.insert( idx, element ); + _cdclCore->addLiteral( (int)idx ); + } + _cdclCore->addLiteral( 0 ); + + // TODO add additional clauses +} + +int MaxConstraint::propagatePhaseAsLit() const +{ + if ( phaseFixed() && !_elements.empty() ) + return _elementsToCadicalVars.at( *_elements.begin() ); + // If no elements exist, then constraint is constant and abstraction is irrelevant + // Assumption - phases are not eliminated in CDCL + + return 0; +} + +void MaxConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) && lit > 0 ); + + setActiveConstraint( false ); + PhaseStatus phaseToFix = variableToPhase( _cadicalVarsToElements.at( lit ) ); + + ASSERT( !phaseFixed() || getPhaseStatus() == phaseToFix ); + + setPhaseStatus( phaseToFix ); +} +#endif \ No newline at end of file diff --git a/src/engine/MaxConstraint.h b/src/engine/MaxConstraint.h index 74445e5cf4..47f3c8d4aa 100644 --- a/src/engine/MaxConstraint.h +++ b/src/engine/MaxConstraint.h @@ -249,6 +249,26 @@ class MaxConstraint : public PiecewiseLinearConstraint : static_cast( phase ) - MAX_VARIABLE_TO_PHASE_OFFSET; } +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; +#endif + private: unsigned _f; Set _elements; @@ -278,12 +298,6 @@ class MaxConstraint : public PiecewiseLinearConstraint bool _haveFeasibleEliminatedPhases; double _maxValueOfEliminatedPhases; - /* - Returns the phase where variable argMax has maximum value. - */ - PiecewiseLinearCaseSplit getSplit( unsigned argMax ) const; - - /* Eliminate the case corresponding to the given input variable to Max. */ @@ -301,7 +315,10 @@ class MaxConstraint : public PiecewiseLinearConstraint /* Apply tightenings in the list, discovered by getEntailedTightenings */ - void applyTightenings( const List &tightenings ) const; + void applyTightenings( const List &tightenings ); + + Map _elementsToCadicalVars; + Map _cadicalVarsToElements; }; #endif // __MaxConstraint_h__ diff --git a/src/engine/NonlinearConstraint.h b/src/engine/NonlinearConstraint.h index 2bf7301540..a9aada447d 100644 --- a/src/engine/NonlinearConstraint.h +++ b/src/engine/NonlinearConstraint.h @@ -85,7 +85,9 @@ class NonlinearConstraint : public ITableau::VariableWatcher Before solving: get additional auxiliary euqations (typically bound-dependent) that this constraint would like to add to the equation pool. */ - virtual void addAuxiliaryEquationsAfterPreprocessing( Query & /* inputQuery */ ){}; + virtual void addAuxiliaryEquationsAfterPreprocessing( Query & /* inputQuery */ ) + { + } /* Returns true iff the assignment satisfies the constraint. @@ -162,7 +164,8 @@ class NonlinearConstraint : public ITableau::VariableWatcher Map _upperBounds; BoundManager *_boundManager; // Pointer to a centralized object to store bounds. - ITableau *_tableau; // Pointer to tableau which simulates CBT until we switch to CDSmtCore + ITableau *_tableau; // Pointer to tableau which simulates CBT until we switch to + // CDSearchTreeHandler /* Statistics collection diff --git a/src/engine/PiecewiseLinearConstraint.cpp b/src/engine/PiecewiseLinearConstraint.cpp index 400d9ba01c..6e1be6a769 100644 --- a/src/engine/PiecewiseLinearConstraint.cpp +++ b/src/engine/PiecewiseLinearConstraint.cpp @@ -31,6 +31,8 @@ PiecewiseLinearConstraint::PiecewiseLinearConstraint() , _statistics( NULL ) , _gurobi( NULL ) , _tableauAuxVars() + , _cdclVars() + , _cdPhaseFixingEntry( nullptr ) { } @@ -47,6 +49,9 @@ PiecewiseLinearConstraint::PiecewiseLinearConstraint( unsigned numCases ) , _score( FloatUtils::negativeInfinity() ) , _statistics( NULL ) , _gurobi( NULL ) + , _tableauAuxVars() + , _cdclVars() + , _cdPhaseFixingEntry( nullptr ) { } @@ -80,6 +85,7 @@ void PiecewiseLinearConstraint::initializeCDOs( CVC4::context::Context *context initializeCDActiveStatus(); initializeCDPhaseStatus(); initializeCDInfeasibleCases(); + initializeCDPhaseFixingEntry(); } void PiecewiseLinearConstraint::initializeCDInfeasibleCases() @@ -103,6 +109,14 @@ void PiecewiseLinearConstraint::initializeCDPhaseStatus() _cdPhaseStatus = new ( true ) CVC4::context::CDO( _context, _phaseStatus ); } +void PiecewiseLinearConstraint::initializeCDPhaseFixingEntry() +{ + ASSERT( _context != nullptr ); + ASSERT( _cdPhaseFixingEntry == nullptr ); + _cdPhaseFixingEntry = new ( true ) + CVC4::context::CDO>( _context ); +} + void PiecewiseLinearConstraint::cdoCleanup() { if ( _cdConstraintActive != nullptr ) @@ -120,6 +134,11 @@ void PiecewiseLinearConstraint::cdoCleanup() _cdInfeasibleCases = nullptr; + if ( _cdPhaseFixingEntry != nullptr ) + _cdPhaseFixingEntry->deleteSelf(); + + _cdPhaseFixingEntry = nullptr; + _context = nullptr; } @@ -157,6 +176,10 @@ void PiecewiseLinearConstraint::initializeDuplicateCDOs( PiecewiseLinearConstrai clone->_cdInfeasibleCases = nullptr; clone->initializeCDInfeasibleCases(); // Does not copy contents + + ASSERT( clone->_cdPhaseFixingEntry != nullptr ) + clone->_cdPhaseFixingEntry = nullptr; + clone->initializeCDPhaseFixingEntry(); } } diff --git a/src/engine/PiecewiseLinearConstraint.h b/src/engine/PiecewiseLinearConstraint.h index 4304baa019..e3d9b5320e 100644 --- a/src/engine/PiecewiseLinearConstraint.h +++ b/src/engine/PiecewiseLinearConstraint.h @@ -48,6 +48,7 @@ #define __PiecewiseLinearConstraint_h__ #include "FloatUtils.h" +#include "GroundBoundManager.h" #include "GurobiWrapper.h" #include "IBoundManager.h" #include "ITableau.h" @@ -63,11 +64,13 @@ #include "context/cdo.h" #include "context/context.h" +class CdclCore; class Equation; class BoundManager; class ITableau; class Query; class String; +class SearchTreeHandler; #define TWO_PHASE_PIECEWISE_LINEAR_CONSTRAINT 2u @@ -204,7 +207,7 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher /* If the constraint's phase has been fixed, get the (valid) case split. Transitioning from Valid to Implied with integration of - context-dependentSMTCore. + context-dependent SearchTreeHandler. */ virtual PiecewiseLinearCaseSplit getValidCaseSplit() const = 0; virtual PiecewiseLinearCaseSplit getImpliedCaseSplit() const = 0; @@ -248,7 +251,9 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher Transform the piecewise linear constraint so that each disjunct contains only bound constraints. */ - virtual void transformToUseAuxVariables( Query & ){}; + virtual void transformToUseAuxVariables( Query & ) + { + } void setStatistics( Statistics *statistics ); @@ -363,6 +368,15 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher { _tableau = tableau; } + + /* + Register the SearchTreeHandler object + */ + inline void registerCdclCore( CdclCore *cdclCore ) + { + _cdclCore = cdclCore; + } + /* Method to set PhaseStatus of the constraint. Encapsulates both context dependent and context-less behavior. Initialized to PHASE_NOT_FIXED. @@ -499,6 +513,63 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher return _tableauAuxVars; } + inline std::shared_ptr getPhaseFixingEntry() const + { + return _cdPhaseFixingEntry->get(); + } + + inline void setPhaseFixingEntry( + const std::shared_ptr &groundBoundEntry ) + { + _cdPhaseFixingEntry->set( groundBoundEntry ); + } + +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + virtual void + booleanAbstraction( Map &cadicalVarToPlc ) = 0; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + virtual int propagatePhaseAsLit() const = 0; + + /* + Fixes a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + virtual void propagateLitAsSplit( int lit ) = 0; + + virtual bool isBoundFixingPhase( unsigned /*var*/, + double /*bound*/, + Tightening::BoundType /*boundType*/ ) const + { + // TODO: remove default implementation after supporting other PLCs in CDCL + return true; + } + + /* + Returns on which phase to decide this constraint, as a cadical var + */ + virtual int getLiteralForDecision() const + { + // TODO: remove default implementation after supporting other PLCs in CDCL + return 0; + } + + /* + Returns a cadical variable of this constraint, for decision + */ + virtual unsigned getVariableForDecision() const + { + // TODO: remove default implementation after supporting other PLCs in CDCL + return 0; + } +#endif + protected: unsigned _numCases; // Number of possible cases/phases for this constraint // (e.g. 2 for ReLU, ABS, SIGN; >=2 for Max and Disjunction ) @@ -509,7 +580,8 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher Map _upperBounds; IBoundManager *_boundManager; // Pointer to a centralized object to store bounds. - ITableau *_tableau; // Pointer to tableau which simulates CBT until we switch to CDSmtCore + ITableau *_tableau; // Pointer to tableau which simulates CBT until we switch to + // CDSearchTreeHandler CVC4::context::Context *_context; CVC4::context::CDO *_cdConstraintActive; @@ -542,12 +614,23 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher */ GurobiWrapper *_gurobi; + /* + The CdclCore object servers as the theory solver of CDCL. + */ + CdclCore *_cdclCore; + + List _tableauAuxVars; + List _cdclVars; + + CVC4::context::CDO> *_cdPhaseFixingEntry; + /* Initialize CDOs. */ void initializeCDActiveStatus(); void initializeCDPhaseStatus(); void initializeCDInfeasibleCases(); + void initializeCDPhaseFixingEntry(); /* Method provided to allow safe copying of the context-dependent members, @@ -643,8 +726,6 @@ class PiecewiseLinearConstraint : public ITableau::VariableWatcher else return _gurobi->getAssignment( Stringf( "x%u", variable ) ); } - - List _tableauAuxVars; }; #endif // __PiecewiseLinearConstraint_h__ diff --git a/src/engine/PrecisionRestorer.cpp b/src/engine/PrecisionRestorer.cpp index c7ed74dff8..898a1fbb54 100644 --- a/src/engine/PrecisionRestorer.cpp +++ b/src/engine/PrecisionRestorer.cpp @@ -19,13 +19,14 @@ #include "FloatUtils.h" #include "MalformedBasisException.h" #include "MarabouError.h" -#include "SmtCore.h" +#include "QuitFromPrecisionRestorationException.h" +#include "SearchTreeHandler.h" #include "TableauStateStorageLevel.h" #include "UnsatCertificateNode.h" void PrecisionRestorer::storeInitialEngineState( const IEngine &engine ) { - engine.storeState( _initialEngineState, TableauStateStorageLevel::STORE_ENTIRE_TABLEAU_STATE ); + engine.storeState( _initialEngineState, TableauStateStorageLevel::STORE_BASICS_ONLY ); } void PrecisionRestorer::restoreInitialEngineState( IEngine &engine ) @@ -35,9 +36,16 @@ void PrecisionRestorer::restoreInitialEngineState( IEngine &engine ) void PrecisionRestorer::restorePrecision( IEngine &engine, ITableau &tableau, - SmtCore &smtCore, + SearchTreeHandler &searchTreeHandler, RestoreBasics restoreBasics ) { + Map> plcStatusBefore; + DEBUG( { + for ( const auto *plc : *engine.getPiecewiseLinearConstraints() ) + plcStatusBefore.insert( + plc, Pair( plc->getPhaseStatus(), plc->isActive() ) ); + } ); + // Store the dimensions, bounds and basic variables in the current tableau, // before restoring it unsigned targetM = tableau.getM(); @@ -49,26 +57,13 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, engine.storeState( targetEngineState, TableauStateStorageLevel::STORE_NONE ); BoundExplainer boundExplainerBackup( targetN, targetM, engine.getContext() ); - Vector groundUpperBoundsBackup; - Vector groundLowerBoundsBackup; Vector upperBoundsBackup = Vector( targetN, 0 ); Vector lowerBoundsBackup = Vector( targetN, 0 ); if ( engine.shouldProduceProofs() ) - { - groundUpperBoundsBackup = Vector( targetN, 0 ); - groundLowerBoundsBackup = Vector( targetN, 0 ); - boundExplainerBackup = *engine.getBoundExplainer(); - for ( unsigned i = 0; i < targetN; ++i ) - { - groundUpperBoundsBackup[i] = engine.getGroundBound( i, Tightening::UB ); - groundLowerBoundsBackup[i] = engine.getGroundBound( i, Tightening::LB ); - } - } - for ( unsigned i = 0; i < targetN; ++i ) { lowerBoundsBackup[i] = tableau.getLowerBound( i ); @@ -77,17 +72,17 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, // Store the case splits performed so far List targetSplits; - smtCore.allSplitsSoFar( targetSplits ); + searchTreeHandler.allSplitsSoFar( targetSplits ); // Restore engine and tableau to their original form - engine.restoreState( _initialEngineState ); + restoreInitialEngineState( engine ); engine.postContextPopHook(); DEBUG( tableau.verifyInvariants() ); // At this point, the tableau has the appropriate dimensions. Restore the // variable bounds and basic variables. Note that if column merging is // enabled, the dimensions may not be precisely those before the - // resotration, because merging sometimes fails - in which case an equation + // restoration, because merging sometimes fails - in which case an equation // is added. If we fail to restore the dimensions, we cannot restore the // basics. @@ -143,8 +138,6 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, tableau.tightenLowerBoundNaively( i, lowerBoundsBackup[i] ); } - engine.propagateBoundManagerTightenings(); - // Restore constraint status for ( const auto &pair : targetEngineState._plConstraintToState ) pair.first->setActiveConstraint( pair.second->isActive() ); @@ -157,22 +150,12 @@ void PrecisionRestorer::restorePrecision( IEngine &engine, ASSERT( GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS || tableau.getN() == targetN ); ASSERT( GlobalConfiguration::USE_COLUMN_MERGING_EQUATIONS || tableau.getM() == targetM ); - // Constraints should be in the same state before and after restoration - for ( const auto &pair : targetEngineState._plConstraintToState ) + for ( const auto *plc : *engine.getPiecewiseLinearConstraints() ) { - ASSERT( pair.second->isActive() == pair.first->isActive() ); - // Only active constraints need to be synchronized - ASSERT( !pair.second->isActive() || - pair.second->phaseFixed() == pair.first->phaseFixed() ); - ASSERT( pair.second->constraintObsolete() == pair.first->constraintObsolete() ); + ASSERT( plc->getPhaseStatus() == plcStatusBefore.get( plc ).first() ); + ASSERT( plc->isActive() == plcStatusBefore.get( plc ).second() ); } - EngineState currentEngineState; - engine.storeState( currentEngineState, TableauStateStorageLevel::STORE_NONE ); - - ASSERT( currentEngineState._numPlConstraintsDisabledByValidSplits == - targetEngineState._numPlConstraintsDisabledByValidSplits ); - tableau.verifyInvariants(); } ); } diff --git a/src/engine/PrecisionRestorer.h b/src/engine/PrecisionRestorer.h index 3fe0c6683b..3f058e1231 100644 --- a/src/engine/PrecisionRestorer.h +++ b/src/engine/PrecisionRestorer.h @@ -18,7 +18,7 @@ #include "EngineState.h" -class SmtCore; +class SearchTreeHandler; class PrecisionRestorer { @@ -33,7 +33,7 @@ class PrecisionRestorer void restorePrecision( IEngine &engine, ITableau &tableau, - SmtCore &smtCore, + SearchTreeHandler &searchTreeHandler, RestoreBasics restoreBasics ); private: diff --git a/src/engine/Preprocessor.cpp b/src/engine/Preprocessor.cpp index f8354575e2..b00b4d112a 100644 --- a/src/engine/Preprocessor.cpp +++ b/src/engine/Preprocessor.cpp @@ -63,6 +63,13 @@ std::unique_ptr Preprocessor::preprocess( const IQuery &query, { _preprocessed = std::unique_ptr( query.generateQuery() ); + /* + First, convert this input query into an equivalent reachability query, if possible. + A reachability query is a query with only one output variable, and the specification indicates + that the single output variable's assignment should be non-negative. + */ + convertToReachabilityQuery(); + informConstraintsOfInitialBounds( *_preprocessed ); /* @@ -1140,3 +1147,95 @@ void Preprocessor::informConstraintsOfInitialBounds( Query &query ) } } } + +void Preprocessor::convertToReachabilityQuery() +{ + if ( !GlobalConfiguration::CONVERT_VERIFICATION_QUERY_INTO_REACHABILITY_QUERY || + _preprocessed->isQueryWithDisjunction() ) + return; + + List newVariables; + // const Map &lowerBounds = _preprocessed->getLowerBounds(); + // const Map &upperBounds = _preprocessed->getUpperBounds(); + // + // // Add new variables and equations for output bounds: + // for ( unsigned outputVariable : _preprocessed->getOutputVariables() ) + // { + // if ( lowerBounds.exists( outputVariable ) && + // FloatUtils::isFinite( lowerBounds[outputVariable] ) ) + // { + // unsigned newVariable = _preprocessed->getNewVariable(); + // newVariables.append( newVariable ); + // Equation newEquation; + // newEquation.addAddend( 1, outputVariable ); + // newEquation.addAddend( 1, newVariable ); + // newEquation.setScalar( lowerBounds[outputVariable] ); + // _preprocessed->addEquation( newEquation ); + // } + // + // if ( upperBounds.exists( outputVariable ) && + // FloatUtils::isFinite( upperBounds[outputVariable] ) ) + // { + // unsigned newVariable = _preprocessed->getNewVariable(); + // newVariables.append( newVariable ); + // Equation newEquation; + // newEquation.addAddend( -1, outputVariable ); + // newEquation.addAddend( 1, newVariable ); + // newEquation.setScalar( -upperBounds[outputVariable] ); + // _preprocessed->addEquation( newEquation ); + // } + // } + + // Add new variables and equations for output constraints + for ( const Equation &equation : _preprocessed->getOutputConstraints() ) + { + bool isTypeEq = ( equation._type == Equation::EQ ); + if ( equation._type == Equation::GE || isTypeEq ) + { + unsigned newVariable = _preprocessed->getNewVariable(); + newVariables.append( newVariable ); + Equation newEquation; + for ( const Equation::Addend addend : equation._addends ) + newEquation.addAddend( addend._coefficient, addend._variable ); + newEquation.addAddend( 1, newVariable ); + newEquation.setScalar( equation._scalar ); + _preprocessed->addEquation( newEquation ); + } + + if ( equation._type == Equation::LE || isTypeEq ) + { + unsigned newVariable = _preprocessed->getNewVariable(); + newVariables.append( newVariable ); + Equation newEquation; + for ( const Equation::Addend addend : equation._addends ) + newEquation.addAddend( -addend._coefficient, addend._variable ); + newEquation.addAddend( 1, newVariable ); + newEquation.setScalar( -equation._scalar ); + _preprocessed->addEquation( newEquation ); + } + } + + // Add new ReLU variables and constraint: + List newReluVariables; + for ( unsigned newVariable : newVariables ) + { + unsigned newReluVariable = _preprocessed->getNewVariable(); + newReluVariables.append( newReluVariable ); + ReluConstraint *newReluConstraint = new ReluConstraint( newVariable, newReluVariable ); + _preprocessed->addPiecewiseLinearConstraint( newReluConstraint ); + } + + // Add new single output variable: + unsigned int newOutputVariable = _preprocessed->getNewVariable(); + + _preprocessed->unmarkOutputVariables(); + _preprocessed->markOutputVariable( newOutputVariable, 0 ); + _preprocessed->setLowerBound( newOutputVariable, 0 ); + + Equation newOutputEquation; + newOutputEquation.addAddend( 1, newOutputVariable ); + for ( unsigned newReluVariable : newReluVariables ) + newOutputEquation.addAddend( 1, newReluVariable ); + newOutputEquation.setScalar( 0 ); + _preprocessed->addEquation( newOutputEquation ); +} diff --git a/src/engine/Preprocessor.h b/src/engine/Preprocessor.h index aa1afb263d..c74881e1e0 100644 --- a/src/engine/Preprocessor.h +++ b/src/engine/Preprocessor.h @@ -73,6 +73,8 @@ class Preprocessor static void informConstraintsOfInitialBounds( Query &query ); + void convertToReachabilityQuery(); + private: void freeMemoryIfNeeded(); diff --git a/src/engine/Query.cpp b/src/engine/Query.cpp index 77b22c9c9f..c458432142 100644 --- a/src/engine/Query.cpp +++ b/src/engine/Query.cpp @@ -34,6 +34,7 @@ Query::Query() : _ensureSameSourceLayerInNLR( Options::get()->getSymbolicBoundTighteningType() == SymbolicBoundTighteningType::DEEP_POLY ) + , _isQueryWithDisjunction( false ) , _networkLevelReasoner( NULL ) { } @@ -624,6 +625,12 @@ void Query::markOutputVariable( unsigned variable, unsigned outputIndex ) _outputIndexToVariable[outputIndex] = variable; } +void Query::unmarkOutputVariables() +{ + _variableToOutputIndex.clear(); + _outputIndexToVariable.clear(); +} + unsigned Query::inputVariableByIndex( unsigned index ) const { ASSERT( _inputIndexToVariable.exists( index ) ); @@ -2049,3 +2056,23 @@ bool Query::constructSoftmaxLayer( NLR::NetworkLevelReasoner *nlr, INPUT_QUERY_LOG( "\tSuccessful!" ); return true; } + +void Query::addOutputConstraint( const Equation &equation ) +{ + _outputConstraints.append( equation ); +} + +const List &Query::getOutputConstraints() const +{ + return _outputConstraints; +} + +bool Query::isQueryWithDisjunction() const +{ + return _isQueryWithDisjunction; +} + +void Query::markQueryWithDisjunction() +{ + _isQueryWithDisjunction = true; +} diff --git a/src/engine/Query.h b/src/engine/Query.h index 48fa1740de..d93e721121 100644 --- a/src/engine/Query.h +++ b/src/engine/Query.h @@ -37,19 +37,19 @@ class Query : public IQuery /* Methods for setting and getting the input part of the query */ - void setNumberOfVariables( unsigned numberOfVariables ); - void setLowerBound( unsigned variable, double bound ); - void setUpperBound( unsigned variable, double bound ); - bool tightenLowerBound( unsigned variable, double bound ); - bool tightenUpperBound( unsigned variable, double bound ); - - void addEquation( const Equation &equation ); - unsigned getNumberOfEquations() const; - - unsigned getNumberOfVariables() const; - unsigned getNewVariable(); - double getLowerBound( unsigned variable ) const; - double getUpperBound( unsigned variable ) const; + void setNumberOfVariables( unsigned numberOfVariables ) override; + void setLowerBound( unsigned variable, double bound ) override; + void setUpperBound( unsigned variable, double bound ) override; + bool tightenLowerBound( unsigned variable, double bound ) override; + bool tightenUpperBound( unsigned variable, double bound ) override; + + void addEquation( const Equation &equation ) override; + unsigned getNumberOfEquations() const override; + + unsigned getNumberOfVariables() const override; + unsigned getNewVariable() override; + double getLowerBound( unsigned variable ) const override; + double getUpperBound( unsigned variable ) const override; const Map &getLowerBounds() const; const Map &getUpperBounds() const; void clearBounds(); @@ -58,15 +58,15 @@ class Query : public IQuery List &getEquations(); void removeEquationsByIndex( const Set indices ); - void addPiecewiseLinearConstraint( PiecewiseLinearConstraint *constraint ); + void addPiecewiseLinearConstraint( PiecewiseLinearConstraint *constraint ) override; const List &getPiecewiseLinearConstraints() const; List &getPiecewiseLinearConstraints(); // Encode a clip constraint using two ReLU constraints - void addClipConstraint( unsigned b, unsigned f, double floor, double ceiling ); + void addClipConstraint( unsigned b, unsigned f, double floor, double ceiling ) override; - void addNonlinearConstraint( NonlinearConstraint *constraint ); - void getNonlinearConstraints( Vector &constraints ) const; + void addNonlinearConstraint( NonlinearConstraint *constraint ) override; + void getNonlinearConstraints( Vector &constraints ) const override; const List &getNonlinearConstraints() const; List &getNonlinearConstraints(); @@ -74,20 +74,27 @@ class Query : public IQuery /* Methods for handling input and output variables */ - void markInputVariable( unsigned variable, unsigned inputIndex ); - void markOutputVariable( unsigned variable, unsigned outputIndex ); - unsigned inputVariableByIndex( unsigned index ) const; - unsigned outputVariableByIndex( unsigned index ) const; - unsigned getNumInputVariables() const; - unsigned getNumOutputVariables() const; - List getInputVariables() const; - List getOutputVariables() const; + void markInputVariable( unsigned variable, unsigned inputIndex ) override; + void markOutputVariable( unsigned variable, unsigned outputIndex ) override; + void unmarkOutputVariables() override; + unsigned inputVariableByIndex( unsigned index ) const override; + unsigned outputVariableByIndex( unsigned index ) const override; + unsigned getNumInputVariables() const override; + unsigned getNumOutputVariables() const override; + List getInputVariables() const override; + List getOutputVariables() const override; + + void addOutputConstraint( const Equation &equation ) override; + const List &getOutputConstraints() const override; + + bool isQueryWithDisjunction() const override; + void markQueryWithDisjunction() override; /* Methods for setting and getting the solution. */ - void setSolutionValue( unsigned variable, double value ); - double getSolutionValue( unsigned variable ) const; + void setSolutionValue( unsigned variable, double value ) override; + double getSolutionValue( unsigned variable ) const override; /* Count the number of infinite bounds in the input query. @@ -118,13 +125,13 @@ class Query : public IQuery /* Store a correct possible solution */ - void storeDebuggingSolution( unsigned variable, double value ); + void storeDebuggingSolution( unsigned variable, double value ) override; Map _debuggingSolution; /* Serializes the query to a file which can then be loaded using QueryLoader. */ - void saveQuery( const String &fileName ); + void saveQuery( const String &fileName ) override; void saveQueryAsSmtLib( const String &fileName ) const; /* @@ -170,7 +177,7 @@ class Query : public IQuery // A map for storing the tableau aux variable assigned to each PLC Map _lastAddendToAux; - Query *generateQuery() const; + Query *generateQuery() const override; private: unsigned _numberOfVariables; @@ -179,6 +186,7 @@ class Query : public IQuery Map _upperBounds; List _plConstraints; List _nlConstraints; + List _outputConstraints; Map _solution; @@ -189,6 +197,14 @@ class Query : public IQuery */ bool _ensureSameSourceLayerInNLR; + /* + * true if the query contains a disjunction constraint, used to check if it is possible to + * convert this verification query into a reachability query. + * TODO: remove this after adding support for converting a query with disjunction constraints + * into a reachability query + */ + bool _isQueryWithDisjunction; + /* Free any stored pl constraints. */ diff --git a/src/engine/QuitFromPrecisionRestorationException.h b/src/engine/QuitFromPrecisionRestorationException.h new file mode 100644 index 0000000000..54401a9a64 --- /dev/null +++ b/src/engine/QuitFromPrecisionRestorationException.h @@ -0,0 +1,32 @@ +/********************* */ +/*! \file QuitFromPrecisionRestorationException.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2024 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __QuitFromPrecisionRestorationException_h__ +#define __QuitFromPrecisionRestorationException_h__ + +class QuitFromPrecisionRestorationException +{ +public: +}; + +#endif // __QuitFromPrecisionRestorationException_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/engine/ReluConstraint.cpp b/src/engine/ReluConstraint.cpp index 3ac962f108..39264b6ac3 100644 --- a/src/engine/ReluConstraint.cpp +++ b/src/engine/ReluConstraint.cpp @@ -14,6 +14,9 @@ #include "ReluConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "DivideStrategy.h" #include "FloatUtils.h" @@ -25,6 +28,7 @@ #include "PiecewiseLinearCaseSplit.h" #include "PiecewiseLinearConstraint.h" #include "Query.h" +#include "SearchTreeHandler.h" #include "Statistics.h" #include "TableauRow.h" @@ -130,6 +134,11 @@ void ReluConstraint::checkIfLowerBoundUpdateFixesPhase( unsigned variable, doubl setPhaseStatus( RELU_PHASE_ACTIVE ); else if ( _auxVarInUse && variable == _aux && FloatUtils::isPositive( bound ) ) setPhaseStatus( RELU_PHASE_INACTIVE ); + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } void ReluConstraint::checkIfUpperBoundUpdateFixesPhase( unsigned variable, double bound ) @@ -139,6 +148,11 @@ void ReluConstraint::checkIfUpperBoundUpdateFixesPhase( unsigned variable, doubl if ( _auxVarInUse && variable == _aux && FloatUtils::isZero( bound ) ) setPhaseStatus( RELU_PHASE_ACTIVE ); + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) @@ -173,7 +187,7 @@ void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) // If we're in the active phase, aux should be 0 if ( proofs && _auxVarInUse ) _boundManager->addLemmaExplanationAndTightenBound( - _aux, 0, Tightening::UB, { variable }, Tightening::LB, getType() ); + _aux, 0, Tightening::UB, { variable }, Tightening::LB, *this, true, 0 ); else if ( !proofs && _auxVarInUse ) _boundManager->tightenUpperBound( _aux, 0 ); @@ -187,7 +201,7 @@ void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) { if ( proofs && _auxVarInUse ) _boundManager->addLemmaExplanationAndTightenBound( - _aux, 0, Tightening::UB, { variable }, Tightening::LB, getType() ); + _aux, 0, Tightening::UB, { variable }, Tightening::LB, *this, true, 0 ); else if ( !proofs && _auxVarInUse ) _boundManager->tightenUpperBound( _aux, 0 ); } @@ -198,7 +212,7 @@ void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) { if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _f, 0, Tightening::UB, { variable }, Tightening::LB, getType() ); + _f, 0, Tightening::UB, { variable }, Tightening::LB, *this, true, 0 ); else _boundManager->tightenUpperBound( _f, 0 ); @@ -212,11 +226,17 @@ void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) if ( proofs ) { // If already inactive, tightening is linear - if ( _phaseStatus == RELU_PHASE_INACTIVE ) + if ( getPhaseStatus() == RELU_PHASE_INACTIVE ) _boundManager->tightenUpperBound( _aux, -bound, *_tighteningRow ); - else if ( _phaseStatus == PHASE_NOT_FIXED ) - _boundManager->addLemmaExplanationAndTightenBound( - _aux, -bound, Tightening::UB, { variable }, Tightening::LB, getType() ); + else if ( getPhaseStatus() == PHASE_NOT_FIXED ) + _boundManager->addLemmaExplanationAndTightenBound( _aux, + -bound, + Tightening::UB, + { variable }, + Tightening::LB, + *this, + false, + bound ); } else _boundManager->tightenUpperBound( _aux, -bound ); @@ -228,7 +248,7 @@ void ReluConstraint::notifyLowerBound( unsigned variable, double newBound ) { if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _f, 0, Tightening::LB, { variable }, Tightening::LB, getType() ); + _f, 0, Tightening::LB, { variable }, Tightening::LB, *this, false, 0 ); else _boundManager->tightenLowerBound( _f, 0 ); } @@ -267,15 +287,21 @@ void ReluConstraint::notifyUpperBound( unsigned variable, double newBound ) { if ( proofs ) { - if ( _phaseStatus != RELU_PHASE_INACTIVE ) + if ( getPhaseStatus() != RELU_PHASE_INACTIVE ) _boundManager->tightenUpperBound( _b, bound, *_tighteningRow ); else { - if ( FloatUtils::isZero( bound ) ) - _boundManager->addLemmaExplanationAndTightenBound( - _b, 0, Tightening::UB, { variable }, Tightening::UB, getType() ); + if ( !FloatUtils::isPositive( bound ) ) + _boundManager->addLemmaExplanationAndTightenBound( _b, + 0, + Tightening::UB, + { variable }, + Tightening::UB, + *this, + true, + 0 ); // Bound cannot be negative if ReLU is inactive - else if ( FloatUtils::isNegative( bound ) ) + if ( FloatUtils::isNegative( bound ) ) throw InfeasibleQueryException(); } } @@ -289,7 +315,7 @@ void ReluConstraint::notifyUpperBound( unsigned variable, double newBound ) // If b has a non-positive upper bound, f's upper bound is 0 if ( proofs ) _boundManager->addLemmaExplanationAndTightenBound( - _f, 0, Tightening::UB, { variable }, Tightening::UB, getType() ); + _f, 0, Tightening::UB, { variable }, Tightening::UB, *this, true, 0 ); else _boundManager->tightenUpperBound( _f, 0 ); @@ -304,15 +330,17 @@ void ReluConstraint::notifyUpperBound( unsigned variable, double newBound ) if ( proofs ) { // If already inactive, tightening is linear - if ( _phaseStatus == RELU_PHASE_ACTIVE ) + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) _boundManager->tightenUpperBound( _f, bound, *_tighteningRow ); - else if ( _phaseStatus == PHASE_NOT_FIXED ) + else if ( getPhaseStatus() == PHASE_NOT_FIXED ) _boundManager->addLemmaExplanationAndTightenBound( _f, bound, Tightening::UB, { variable }, Tightening::UB, - getType() ); + *this, + false, + bound ); } else _boundManager->tightenUpperBound( _f, bound ); @@ -322,15 +350,21 @@ void ReluConstraint::notifyUpperBound( unsigned variable, double newBound ) { if ( proofs ) { - if ( _phaseStatus != RELU_PHASE_ACTIVE ) + if ( getPhaseStatus() != RELU_PHASE_ACTIVE ) _boundManager->tightenLowerBound( _b, -bound, *_tighteningRow ); else { - if ( FloatUtils::isZero( bound ) ) - _boundManager->addLemmaExplanationAndTightenBound( - _b, 0, Tightening::LB, { variable }, Tightening::UB, getType() ); + if ( !FloatUtils::isPositive( bound ) ) + _boundManager->addLemmaExplanationAndTightenBound( _b, + 0, + Tightening::LB, + { variable }, + Tightening::UB, + *this, + true, + 0 ); // Bound cannot be negative if ReLU is active - else if ( FloatUtils::isNegative( bound ) ) + if ( FloatUtils::isNegative( bound ) ) throw InfeasibleQueryException(); } } @@ -554,7 +588,7 @@ List ReluConstraint::getSmartFixes( ITableau *ta List ReluConstraint::getCaseSplits() const { - if ( _phaseStatus != PHASE_NOT_FIXED ) + if ( getPhaseStatus() != PHASE_NOT_FIXED ) throw MarabouError( MarabouError::REQUESTED_CASE_SPLITS_FROM_FIXED_CONSTRAINT ); List splits; @@ -664,14 +698,14 @@ PiecewiseLinearCaseSplit ReluConstraint::getActiveSplit() const bool ReluConstraint::phaseFixed() const { - return _phaseStatus != PHASE_NOT_FIXED; + return getPhaseStatus() != PHASE_NOT_FIXED; } PiecewiseLinearCaseSplit ReluConstraint::getImpliedCaseSplit() const { - ASSERT( _phaseStatus != PHASE_NOT_FIXED ); + ASSERT( getPhaseStatus() != PHASE_NOT_FIXED ); - if ( _phaseStatus == RELU_PHASE_ACTIVE ) + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) return getActiveSplit(); return getInactiveSplit(); @@ -688,8 +722,8 @@ void ReluConstraint::dump( String &output ) const _f, _b, _constraintActive ? "Yes" : "No", - _phaseStatus, - phaseToString( _phaseStatus ).ascii() ); + getPhaseStatus(), + phaseToString( getPhaseStatus() ).ascii() ); output += Stringf( "b in [%s, %s], ", @@ -756,11 +790,11 @@ void ReluConstraint::eliminateVariable( __attribute__( ( unused ) ) unsigned var { if ( FloatUtils::gt( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != RELU_PHASE_INACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_INACTIVE ); } else if ( FloatUtils::lt( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != RELU_PHASE_ACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_ACTIVE ); } } else @@ -768,7 +802,7 @@ void ReluConstraint::eliminateVariable( __attribute__( ( unused ) ) unsigned var // This is the aux variable if ( FloatUtils::isPositive( fixedValue ) ) { - ASSERT( _phaseStatus != RELU_PHASE_ACTIVE ); + ASSERT( getPhaseStatus() != RELU_PHASE_ACTIVE ); } } } ); @@ -1125,6 +1159,95 @@ void ReluConstraint::addTableauAuxVar( unsigned tableauAuxVar, unsigned constrai _tableauAuxVars.append( tableauAuxVar ); } +#ifdef BUILD_CADICAL +void ReluConstraint::booleanAbstraction( + Map &cadicalVarToPlc ) +{ + ASSERT( !cadicalVarToPlc.empty() ); + unsigned int idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); +} + +int ReluConstraint::propagatePhaseAsLit() const +{ + ASSERT( _cdclVars.size() == 1 ) + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) + return _cdclVars.back(); + else if ( getPhaseStatus() == RELU_PHASE_INACTIVE ) + return -_cdclVars.back(); + else + return 0; +} + +void ReluConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) ); + + setActiveConstraint( false ); + + if ( lit > 0 ) + setPhaseStatus( RELU_PHASE_ACTIVE ); + else + setPhaseStatus( RELU_PHASE_INACTIVE ); +} + +bool ReluConstraint::isBoundFixingPhase( unsigned int var, + double bound, + Tightening::BoundType boundType ) const +{ + if ( getPhaseStatus() == RELU_PHASE_ACTIVE ) + { + if ( var == _b && boundType == Tightening::LB && !FloatUtils::isNegative( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::LB && FloatUtils::isPositive( bound ) ) + return true; + + if ( _auxVarInUse && var == _aux && boundType == Tightening::UB && + FloatUtils::isZero( bound ) ) + return true; + } + else if ( getPhaseStatus() == RELU_PHASE_INACTIVE ) + { + if ( var == _b && boundType == Tightening::UB && !FloatUtils::isPositive( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::UB && !FloatUtils::isPositive( bound ) ) + return true; + + if ( _auxVarInUse && var == _aux && boundType == Tightening::LB && + FloatUtils::isPositive( bound ) ) + return true; + } + + return false; +} + +int ReluConstraint::getLiteralForDecision() const +{ + ASSERT( getPhaseStatus() == PHASE_NOT_FIXED ); + + if ( _direction == RELU_PHASE_INACTIVE ) + return -(int)_cdclVars.front(); + if ( _direction == RELU_PHASE_ACTIVE ) + return (int)_cdclVars.front(); + + if ( existsAssignment( _f ) ) + if ( FloatUtils::isPositive( getAssignment( _f ) ) ) + return (int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); +} + +unsigned ReluConstraint::getVariableForDecision() const +{ + return _cdclVars.front(); +} +#endif + // // Local Variables: // compile-command: "make -C ../.. " diff --git a/src/engine/ReluConstraint.h b/src/engine/ReluConstraint.h index 5821813e3a..753ca5ca05 100644 --- a/src/engine/ReluConstraint.h +++ b/src/engine/ReluConstraint.h @@ -269,6 +269,40 @@ class ReluConstraint : public PiecewiseLinearConstraint const List getNativeAuxVars() const override; +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; + + bool isBoundFixingPhase( unsigned var, + double bound, + Tightening::BoundType boundType ) const override; + + /* + Returns on which phase to decide this constraint, as a cadical var + */ + int getLiteralForDecision() const override; + + /* + Returns a cadical variable of this constraint, for decision + */ + unsigned getVariableForDecision() const override; +#endif + private: unsigned _b, _f; NLR::NetworkLevelReasoner *_networkLevelReasoner; diff --git a/src/engine/RowBoundTightener.cpp b/src/engine/RowBoundTightener.cpp index 7ecfdd045f..28bca40e82 100644 --- a/src/engine/RowBoundTightener.cpp +++ b/src/engine/RowBoundTightener.cpp @@ -104,7 +104,7 @@ void RowBoundTightener::freeMemoryIfNeeded() } } -void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation ) +unsigned RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation ) { /* Roughly (the dimensions don't add up): @@ -139,11 +139,13 @@ void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation // The tightening procedure may throw an exception, in which case we need // to release the rows. unsigned newBoundsLearned; + unsigned overallBounds = 0; unsigned maxNumberOfIterations = untilSaturation ? GlobalConfiguration::ROW_BOUND_TIGHTENER_SATURATION_ITERATIONS : 1; do { newBoundsLearned = onePassOverInvertedBasisRows(); + overallBounds += newBoundsLearned; if ( _statistics && ( newBoundsLearned > 0 ) ) _statistics->incLongAttribute( Statistics::NUM_TIGHTENINGS_FROM_EXPLICIT_BASIS, @@ -152,9 +154,11 @@ void RowBoundTightener::examineImplicitInvertedBasisMatrix( bool untilSaturation --maxNumberOfIterations; } while ( ( maxNumberOfIterations != 0 ) && ( newBoundsLearned > 0 ) ); + + return overallBounds; } -void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) +unsigned RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) { /* Roughly (the dimensions don't add up): @@ -167,6 +171,8 @@ void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) const double *b = _tableau.getRightHandSide(); const double *invB = _tableau.getInverseBasisMatrix(); + unsigned overallBounds = 0; + try { for ( unsigned i = 0; i < _m; ++i ) @@ -206,6 +212,7 @@ void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) do { newBoundsLearned = onePassOverInvertedBasisRows(); + overallBounds += newBoundsLearned; if ( _statistics && ( newBoundsLearned > 0 ) ) _statistics->incLongAttribute( Statistics::NUM_TIGHTENINGS_FROM_EXPLICIT_BASIS, @@ -222,6 +229,7 @@ void RowBoundTightener::examineInvertedBasisMatrix( bool untilSaturation ) } delete[] invB; + return overallBounds; } unsigned RowBoundTightener::onePassOverInvertedBasisRows() diff --git a/src/engine/RowBoundTightener.h b/src/engine/RowBoundTightener.h index ea71f0237e..2421760da9 100644 --- a/src/engine/RowBoundTightener.h +++ b/src/engine/RowBoundTightener.h @@ -114,7 +114,7 @@ class RowBoundTightener : public IRowBoundTightener through the tableau. Can also do this until saturation, meaning that we continue until no new bounds are learned. */ - void examineInvertedBasisMatrix( bool untilSaturation ); + unsigned examineInvertedBasisMatrix( bool untilSaturation ); /* Derive and enqueue new bounds for all varaibles, implicitly using the @@ -123,7 +123,7 @@ class RowBoundTightener : public IRowBoundTightener is performed via FTRANs. Can also do this until saturation, meaning that we continue until no new bounds are learned. */ - void examineImplicitInvertedBasisMatrix( bool untilSaturation ); + unsigned examineImplicitInvertedBasisMatrix( bool untilSaturation ); /* Derive and enqueue new bounds for all varaibles, using the diff --git a/src/engine/SmtCore.cpp b/src/engine/SearchTreeHandler.cpp similarity index 83% rename from src/engine/SmtCore.cpp rename to src/engine/SearchTreeHandler.cpp index bdf75aadaa..18f85cef16 100644 --- a/src/engine/SmtCore.cpp +++ b/src/engine/SearchTreeHandler.cpp @@ -1,5 +1,5 @@ /********************* */ -/*! \file SmtCore.cpp +/*! \file SearchTreeHandler.cpp ** \verbatim ** Top contributors (to current version): ** Guy Katz, Parth Shah, Duligur Ibeling @@ -13,22 +13,24 @@ **/ -#include "SmtCore.h" +#include "SearchTreeHandler.h" #include "Debug.h" -#include "DivideStrategy.h" +#include "Engine.h" #include "EngineState.h" #include "FloatUtils.h" #include "GlobalConfiguration.h" #include "IEngine.h" +#include "InputQuery.h" #include "MStringf.h" #include "MarabouError.h" #include "Options.h" +#include "PiecewiseLinearConstraint.h" #include "PseudoImpactTracker.h" -#include "ReluConstraint.h" +#include "TimeUtils.h" #include "UnsatCertificateNode.h" -SmtCore::SmtCore( IEngine *engine ) +SearchTreeHandler::SearchTreeHandler( IEngine *engine ) : _statistics( NULL ) , _engine( engine ) , _context( _engine->getContext() ) @@ -44,12 +46,12 @@ SmtCore::SmtCore( IEngine *engine ) { } -SmtCore::~SmtCore() +SearchTreeHandler::~SearchTreeHandler() { freeMemory(); } -void SmtCore::freeMemory() +void SearchTreeHandler::freeMemory() { for ( const auto &stackEntry : _stack ) { @@ -60,7 +62,7 @@ void SmtCore::freeMemory() _stack.clear(); } -void SmtCore::reset() +void SearchTreeHandler::reset() { _context.popto( 0 ); _engine->postContextPopHook(); @@ -73,7 +75,7 @@ void SmtCore::reset() _numRejectedPhasePatternProposal = 0; } -void SmtCore::reportViolatedConstraint( PiecewiseLinearConstraint *constraint ) +void SearchTreeHandler::reportViolatedConstraint( PiecewiseLinearConstraint *constraint ) { if ( !_constraintToViolationCount.exists( constraint ) ) _constraintToViolationCount[constraint] = 0; @@ -83,14 +85,17 @@ void SmtCore::reportViolatedConstraint( PiecewiseLinearConstraint *constraint ) if ( _constraintToViolationCount[constraint] >= _constraintViolationThreshold ) { _needToSplit = true; - if ( !pickSplitPLConstraint() ) + if ( _engine->shouldSolveWithCDCL() ) + ASSERT( !constraint->phaseFixed() ); + if ( !_engine->shouldSolveWithCDCL() && !pickSplitPLConstraint() ) + // If pickSplitConstraint failed to pick one, use the native // relu-violation based splitting heuristic. _constraintForSplitting = constraint; } } -unsigned SmtCore::getViolationCounts( PiecewiseLinearConstraint *constraint ) const +unsigned SearchTreeHandler::getViolationCounts( PiecewiseLinearConstraint *constraint ) const { if ( !_constraintToViolationCount.exists( constraint ) ) return 0; @@ -98,19 +103,24 @@ unsigned SmtCore::getViolationCounts( PiecewiseLinearConstraint *constraint ) co return _constraintToViolationCount[constraint]; } -void SmtCore::initializeScoreTrackerIfNeeded( - const List &plConstraints ) +void SearchTreeHandler::initializeScoreTrackerIfNeeded( + const List &plConstraints, + CdclCore *cdclCore ) { if ( GlobalConfiguration::USE_DEEPSOI_LOCAL_SEARCH ) { _scoreTracker = std::unique_ptr( new PseudoImpactTracker() ); _scoreTracker->initialize( plConstraints ); +#ifdef BUILD_CADICAL + if ( cdclCore ) + cdclCore->initializeScoreTracker( _scoreTracker ); +#endif - SMT_LOG( "\tTracking Pseudo Impact..." ); + SEARCH_TREE_LOG( "\tTracking Pseudo Impact..." ); } } -void SmtCore::reportRejectedPhasePatternProposal() +void SearchTreeHandler::reportRejectedPhasePatternProposal() { ++_numRejectedPhasePatternProposal; @@ -126,12 +136,12 @@ void SmtCore::reportRejectedPhasePatternProposal() } } -bool SmtCore::needToSplit() const +bool SearchTreeHandler::needToSplit() const { return _needToSplit; } -void SmtCore::performSplit() +void SearchTreeHandler::performSplit() { ASSERT( _needToSplit ); @@ -182,7 +192,7 @@ void SmtCore::performSplit() new UnsatCertificateNode( certificateNode, childSplit ); } - SmtStackEntry *stackEntry = new SmtStackEntry; + SearchTreeStackEntry *stackEntry = new SearchTreeStackEntry; // Perform the first split: add bounds and equations List::iterator split = splits.begin(); ASSERT( split->getEquations().size() == 0 ); @@ -218,21 +228,21 @@ void SmtCore::performSplit() if ( level > _statistics->getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ) _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); struct timespec end = TimeUtils::sampleMicro(); - _statistics->incLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO, + _statistics->incLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, TimeUtils::timePassed( start, end ) ); } _constraintForSplitting = NULL; } -unsigned SmtCore::getStackDepth() const +unsigned SearchTreeHandler::getStackDepth() const { ASSERT( ( _engine->inSnCMode() || _stack.size() == static_cast( _context.getLevel() ) ) ); return _stack.size(); } -void SmtCore::popContext() +void SearchTreeHandler::popContext() { struct timespec start = TimeUtils::sampleMicro(); _context.pop(); @@ -246,7 +256,7 @@ void SmtCore::popContext() } } -void SmtCore::pushContext() +void SearchTreeHandler::pushContext() { struct timespec start = TimeUtils::sampleMicro(); _context.push(); @@ -260,9 +270,9 @@ void SmtCore::pushContext() } } -bool SmtCore::popSplit() +bool SearchTreeHandler::popSplit() { - SMT_LOG( "Performing a pop" ); + SEARCH_TREE_LOG( "Performing a pop" ); if ( _stack.empty() ) return false; @@ -300,6 +310,7 @@ bool SmtCore::popSplit() { UnsatCertificateNode *certificateNode = _engine->getUNSATCertificateCurrentPointer(); + certificateNode->deleteUnusedLemmas(); _engine->setUNSATCertificateCurrentPointer( certificateNode->getParent() ); } @@ -314,14 +325,17 @@ bool SmtCore::popSplit() throw MarabouError( MarabouError::DEBUGGING_ERROR ); } - SmtStackEntry *stackEntry = _stack.back(); + SearchTreeStackEntry *stackEntry = _stack.back(); + + if ( _engine->shouldProduceProofs() && _engine->getUNSATCertificateCurrentPointer() ) + _engine->getUNSATCertificateCurrentPointer()->deleteUnusedLemmas(); popContext(); _engine->postContextPopHook(); // Restore the state of the engine - SMT_LOG( "\tRestoring engine state..." ); + SEARCH_TREE_LOG( "\tRestoring engine state..." ); _engine->restoreState( *( stackEntry->_engineState ) ); - SMT_LOG( "\tRestoring engine state - DONE" ); + SEARCH_TREE_LOG( "\tRestoring engine state - DONE" ); // Apply the new split and erase it from the list auto split = stackEntry->_alternativeSplits.begin(); @@ -339,6 +353,7 @@ bool SmtCore::popSplit() UnsatCertificateNode *splitChild = certificateNode->getChildBySplit( *split ); while ( !splitChild ) { + certificateNode->deleteUnusedLemmas(); certificateNode = certificateNode->getParent(); ASSERT( certificateNode ); splitChild = certificateNode->getChildBySplit( *split ); @@ -348,12 +363,12 @@ bool SmtCore::popSplit() ASSERT( _engine->getUNSATCertificateCurrentPointer()->getSplit() == *split ); } - SMT_LOG( "\tApplying new split..." ); + SEARCH_TREE_LOG( "\tApplying new split..." ); ASSERT( split->getEquations().size() == 0 ); _engine->preContextPushHook(); pushContext(); _engine->applySplit( *split ); - SMT_LOG( "\tApplying new split - DONE" ); + SEARCH_TREE_LOG( "\tApplying new split - DONE" ); stackEntry->_activeSplit = *split; stackEntry->_alternativeSplits.erase( split ); @@ -371,7 +386,7 @@ bool SmtCore::popSplit() if ( level > _statistics->getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ) _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); struct timespec end = TimeUtils::sampleMicro(); - _statistics->incLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO, + _statistics->incLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, TimeUtils::timePassed( start, end ) ); } @@ -380,14 +395,18 @@ bool SmtCore::popSplit() return true; } -void SmtCore::resetSplitConditions() +void SearchTreeHandler::resetSplitConditions() { _constraintToViolationCount.clear(); _numRejectedPhasePatternProposal = 0; +#ifdef BUILD_CADICAL + if ( _engine->shouldSolveWithCDCL() ) + _constraintForSplitting = NULL; +#endif _needToSplit = false; } -void SmtCore::recordImpliedValidSplit( PiecewiseLinearCaseSplit &validSplit ) +void SearchTreeHandler::recordImpliedValidSplit( PiecewiseLinearCaseSplit &validSplit ) { if ( _stack.empty() ) _impliedValidSplitsAtRoot.append( validSplit ); @@ -397,7 +416,7 @@ void SmtCore::recordImpliedValidSplit( PiecewiseLinearCaseSplit &validSplit ) checkSkewFromDebuggingSolution(); } -void SmtCore::allSplitsSoFar( List &result ) const +void SearchTreeHandler::allSplitsSoFar( List &result ) const { result.clear(); @@ -412,19 +431,19 @@ void SmtCore::allSplitsSoFar( List &result ) const } } -void SmtCore::setStatistics( Statistics *statistics ) +void SearchTreeHandler::setStatistics( Statistics *statistics ) { _statistics = statistics; } -void SmtCore::storeDebuggingSolution( const Map &debuggingSolution ) +void SearchTreeHandler::storeDebuggingSolution( const Map &debuggingSolution ) { _debuggingSolution = debuggingSolution; } // Return true if stack is currently compliant, false otherwise // If there is no stored solution, return false --- incompliant. -bool SmtCore::checkSkewFromDebuggingSolution() +bool SearchTreeHandler::checkSkewFromDebuggingSolution() { if ( _debuggingSolution.empty() ) return false; @@ -476,8 +495,8 @@ bool SmtCore::checkSkewFromDebuggingSolution() return true; } -bool SmtCore::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, - String &error ) const +bool SearchTreeHandler::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, + String &error ) const { // False if the split prevents one of the values in the stored solution, true otherwise. error = ""; @@ -520,7 +539,7 @@ bool SmtCore::splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, return true; } -PiecewiseLinearConstraint *SmtCore::chooseViolatedConstraintForFixing( +PiecewiseLinearConstraint *SearchTreeHandler::chooseViolatedConstraintForFixing( List &_violatedPlConstraints ) const { ASSERT( !_violatedPlConstraints.empty() ); @@ -554,7 +573,7 @@ PiecewiseLinearConstraint *SmtCore::chooseViolatedConstraintForFixing( return candidate; } -void SmtCore::replaySmtStackEntry( SmtStackEntry *stackEntry ) +void SearchTreeHandler::replaySearchTreeStackEntry( SearchTreeStackEntry *stackEntry ) { struct timespec start = TimeUtils::sampleMicro(); @@ -585,22 +604,22 @@ void SmtCore::replaySmtStackEntry( SmtStackEntry *stackEntry ) if ( level > _statistics->getUnsignedAttribute( Statistics::MAX_DECISION_LEVEL ) ) _statistics->setUnsignedAttribute( Statistics::MAX_DECISION_LEVEL, level ); struct timespec end = TimeUtils::sampleMicro(); - _statistics->incLongAttribute( Statistics::TOTAL_TIME_SMT_CORE_MICRO, + _statistics->incLongAttribute( Statistics::TOTAL_TIME_SEARCH_TREE_HANDLER_MICRO, TimeUtils::timePassed( start, end ) ); } } -void SmtCore::storeSmtState( SmtState &smtState ) +void SearchTreeHandler::storeSearchTreeState( SearchTreeState &searchTreeState ) { - smtState._impliedValidSplitsAtRoot = _impliedValidSplitsAtRoot; + searchTreeState._impliedValidSplitsAtRoot = _impliedValidSplitsAtRoot; for ( auto &stackEntry : _stack ) - smtState._stack.append( stackEntry->duplicateSmtStackEntry() ); + searchTreeState._stack.append( stackEntry->duplicateSearchTreeStackEntry() ); - smtState._stateId = _stateId; + searchTreeState._stateId = _stateId; } -bool SmtCore::pickSplitPLConstraint() +bool SearchTreeHandler::pickSplitPLConstraint() { if ( _needToSplit ) { @@ -608,3 +627,8 @@ bool SmtCore::pickSplitPLConstraint() } return _constraintForSplitting != NULL; } + +void SearchTreeHandler::setNeedToSplit( bool value ) +{ + _needToSplit = value; +} \ No newline at end of file diff --git a/src/engine/SmtCore.h b/src/engine/SearchTreeHandler.h similarity index 75% rename from src/engine/SmtCore.h rename to src/engine/SearchTreeHandler.h index ad1d61f8e9..769a38c44e 100644 --- a/src/engine/SmtCore.h +++ b/src/engine/SearchTreeHandler.h @@ -1,5 +1,5 @@ /********************* */ -/*! \file SmtCore.h +/*! \file SearchTreeHandler.h ** \verbatim ** Top contributors (to current version): ** Guy Katz, Parth Shah @@ -13,22 +13,27 @@ **/ -#ifndef __SmtCore_h__ -#define __SmtCore_h__ +#ifndef __SearchTreeHandler_h__ +#define __SearchTreeHandler_h__ #include "DivideStrategy.h" +#include "HashMap.h" #include "PLConstraintScoreTracker.h" #include "PiecewiseLinearCaseSplit.h" #include "PiecewiseLinearConstraint.h" -#include "SmtStackEntry.h" -#include "SmtState.h" +#include "SearchTreeStackEntry.h" +#include "SearchTreeState.h" #include "Stack.h" #include "Statistics.h" +#include "TimeoutException.h" +#include "context/cdhashmap.h" +#include "context/cdhashset.h" #include "context/context.h" #include -#define SMT_LOG( x, ... ) LOG( GlobalConfiguration::SMT_CORE_LOGGING, "SmtCore: %s\n", x ) +#define SEARCH_TREE_LOG( x, ... ) \ + LOG( GlobalConfiguration::SEARCH_TREE_HANDLER_LOGGING, "SearchTreeHandler: %s\n", x ) class EngineState; class IEngine; @@ -36,11 +41,11 @@ class String; using CVC4::context::Context; -class SmtCore +class SearchTreeHandler { public: - SmtCore( IEngine *engine ); - ~SmtCore(); + explicit SearchTreeHandler( IEngine *engine ); + ~SearchTreeHandler(); /* Clear the stack. @@ -48,17 +53,18 @@ class SmtCore void freeMemory(); /* - Reset the SmtCore + Reset the SearchTreeHandler */ void reset(); /* Initialize the score tracker with the given list of pl constraints. */ - void initializeScoreTrackerIfNeeded( const List &plConstraints ); + void initializeScoreTrackerIfNeeded( const List &plConstraints, + CdclCore *cdclCore = nullptr ); /* - Inform the SMT core that a SoI phase pattern proposal is rejected. + Inform the Search Tree handler that a SoI phase pattern proposal is rejected. */ void reportRejectedPhasePatternProposal(); @@ -67,7 +73,7 @@ class SmtCore */ inline void updatePLConstraintScore( PiecewiseLinearConstraint *constraint, double score ) { - ASSERT( _scoreTracker != nullptr ); + ASSERT( _scoreTracker != nullptr ) _scoreTracker->updateScore( constraint, score ); } @@ -80,7 +86,7 @@ class SmtCore } /* - Inform the SMT core that a PL constraint is violated. + Inform the Search Tree handler that a PL constraint is violated. */ void reportViolatedConstraint( PiecewiseLinearConstraint *constraint ); @@ -97,7 +103,7 @@ class SmtCore void resetSplitConditions(); /* - Returns true iff the SMT core wants to perform a case split. + Returns true iff the Search Tree handler wants to perform a case split. */ bool needToSplit() const; @@ -123,30 +129,29 @@ class SmtCore */ void pushContext(); - /* The current stack depth. */ unsigned getStackDepth() const; /* - Let the smt core know of an implied valid case split that was discovered. + Let the search tree handler know of an implied valid case split that was discovered. */ void recordImpliedValidSplit( PiecewiseLinearCaseSplit &validSplit ); /* - Return a list of all splits performed so far, both SMT-originating and valid ones, + Return a list of all splits performed so far, both Search Tree-originating and valid ones, in the correct order. */ void allSplitsSoFar( List &result ) const; /* - Have the SMT core start reporting statistics. + Have the Search Tree handler start reporting statistics. */ void setStatistics( Statistics *statistics ); /* - Have the SMT core choose, among a set of violated PL constraints, which + Have the Search Tree handler choose, among a set of violated PL constraints, which constraint should be repaired (without splitting) */ PiecewiseLinearConstraint *chooseViolatedConstraintForFixing( @@ -160,12 +165,12 @@ class SmtCore /* Replay a stackEntry */ - void replaySmtStackEntry( SmtStackEntry *stackEntry ); + void replaySearchTreeStackEntry( SearchTreeStackEntry *stackEntry ); /* - Store the current state of the SmtCore into smtState + Store the current state of the SearchTreeHandler into searchTreeState */ - void storeSmtState( SmtState &smtState ); + void storeSearchTreeState( SearchTreeState &searchTreeState ); /* Pick the piecewise linear constraint for splitting, returns true @@ -180,6 +185,11 @@ class SmtCore bool checkSkewFromDebuggingSolution(); bool splitAllowsStoredSolution( const PiecewiseLinearCaseSplit &split, String &error ) const; + /* + Set the needToSplit flag with the given value; + */ + void setNeedToSplit( bool value ); + private: /* Valid splits that were implied by level 0 of the stack. @@ -194,7 +204,7 @@ class SmtCore /* The case-split stack. */ - List _stack; + List _stack; /* The engine. @@ -205,6 +215,7 @@ class SmtCore Context for synchronizing the search. */ Context &_context; + /* Do we need to perform a split and on which constraint. */ @@ -247,7 +258,7 @@ class SmtCore /* Heap to store the scores of each PLConstraint. */ - std::unique_ptr _scoreTracker; + std::shared_ptr _scoreTracker; /* Number of times the phase pattern proposal has been rejected at the @@ -256,7 +267,7 @@ class SmtCore unsigned _numRejectedPhasePatternProposal; }; -#endif // __SmtCore_h__ +#endif // __SearchTreeHandler_h__ // // Local Variables: diff --git a/src/engine/SmtStackEntry.h b/src/engine/SearchTreeStackEntry.h similarity index 80% rename from src/engine/SmtStackEntry.h rename to src/engine/SearchTreeStackEntry.h index ec9b3f2ce2..a701d9ffd3 100644 --- a/src/engine/SmtStackEntry.h +++ b/src/engine/SearchTreeStackEntry.h @@ -1,5 +1,5 @@ /********************* */ -/*! \file SmtStackEntry.h +/*! \file SearchTreeStackEntry.h ** \verbatim ** Top contributors (to current version): ** Guy Katz, Haoze Wu @@ -13,8 +13,8 @@ **/ -#ifndef __SmtStackEntry_h__ -#define __SmtStackEntry_h__ +#ifndef __SearchTreeStackEntry_h__ +#define __SearchTreeStackEntry_h__ #include "EngineState.h" #include "PiecewiseLinearCaseSplit.h" @@ -24,7 +24,7 @@ the active split, the alternative splits (in case of backtrack), and also any implied splits that were discovered subsequently. */ -struct SmtStackEntry +struct SearchTreeStackEntry { public: PiecewiseLinearCaseSplit _activeSplit; @@ -33,14 +33,14 @@ struct SmtStackEntry EngineState *_engineState; /* - Create a copy of the SmtStackEntry on the stack and returns a pointer to + Create a copy of the SearchTreeStackEntry on the stack and returns a pointer to the copy. We do not copy the engineState for now, since where this method is called, we recreate the engineState by replaying the caseSplits. */ - SmtStackEntry *duplicateSmtStackEntry() + SearchTreeStackEntry *duplicateSearchTreeStackEntry() { - SmtStackEntry *copy = new SmtStackEntry(); + SearchTreeStackEntry *copy = new SearchTreeStackEntry(); copy->_activeSplit = _activeSplit; copy->_impliedValidSplits = _impliedValidSplits; @@ -51,7 +51,7 @@ struct SmtStackEntry } }; -#endif // __SmtStackEntry_h__ +#endif // __SearchTreeStackEntry_h__ // // Local Variables: diff --git a/src/engine/SmtState.h b/src/engine/SearchTreeState.h similarity index 82% rename from src/engine/SmtState.h rename to src/engine/SearchTreeState.h index d513b67b34..11b3d0aa81 100644 --- a/src/engine/SmtState.h +++ b/src/engine/SearchTreeState.h @@ -1,5 +1,5 @@ /********************* */ -/*! \file SmtState.h +/*! \file SearchTreeState.h ** \verbatim ** Top contributors (to current version): ** Guy Katz, Duligur Ibeling @@ -13,15 +13,15 @@ **/ -#ifndef __SmtState_h__ -#define __SmtState_h__ +#ifndef __SearchTreeState_h__ +#define __SearchTreeState_h__ #include "List.h" #include "Map.h" #include "PiecewiseLinearConstraint.h" -#include "SmtStackEntry.h" +#include "SearchTreeStackEntry.h" -class SmtState +class SearchTreeState { public: /* @@ -32,7 +32,7 @@ class SmtState /* The stack. */ - List _stack; + List _stack; /* A unique ID allocated to every state that is stored, for @@ -41,7 +41,7 @@ class SmtState unsigned _stateId; }; -#endif // __SmtState_h__ +#endif // __SearchTreeState_h__ // // Local Variables: diff --git a/src/engine/SignConstraint.cpp b/src/engine/SignConstraint.cpp index 744da2da94..1cd5a41e24 100644 --- a/src/engine/SignConstraint.cpp +++ b/src/engine/SignConstraint.cpp @@ -14,6 +14,9 @@ #include "SignConstraint.h" +#ifdef BUILD_CADICAL +#include "CdclCore.h" +#endif #include "Debug.h" #include "FloatUtils.h" #include "GlobalConfiguration.h" @@ -23,7 +26,7 @@ #include "MarabouError.h" #include "PiecewiseLinearCaseSplit.h" #include "Query.h" -#include "SignConstraint.h" +#include "SearchTreeHandler.h" #include "Statistics.h" #ifdef _WIN32 @@ -123,7 +126,7 @@ bool SignConstraint::satisfied() const List SignConstraint::getCaseSplits() const { - if ( _phaseStatus != PHASE_NOT_FIXED ) + if ( getPhaseStatus() != PHASE_NOT_FIXED ) throw MarabouError( MarabouError::REQUESTED_CASE_SPLITS_FROM_FIXED_CONSTRAINT ); List splits; @@ -168,7 +171,7 @@ List SignConstraint::getCaseSplits() const List SignConstraint::getAllCases() const { - if ( _phaseStatus != PHASE_NOT_FIXED ) + if ( getPhaseStatus() != PHASE_NOT_FIXED ) throw MarabouError( MarabouError::REQUESTED_CASE_SPLITS_FROM_FIXED_CONSTRAINT ); if ( _direction == SIGN_PHASE_NEGATIVE ) @@ -220,7 +223,7 @@ PiecewiseLinearCaseSplit SignConstraint::getPositiveSplit() const bool SignConstraint::phaseFixed() const { - return _phaseStatus != PHASE_NOT_FIXED; + return getPhaseStatus() != PHASE_NOT_FIXED; } void SignConstraint::addAuxiliaryEquationsAfterPreprocessing( Query &inputQuery ) @@ -276,9 +279,9 @@ void SignConstraint::addAuxiliaryEquationsAfterPreprocessing( Query &inputQuery PiecewiseLinearCaseSplit SignConstraint::getImpliedCaseSplit() const { - ASSERT( _phaseStatus != PHASE_NOT_FIXED ); + ASSERT( getPhaseStatus() != PHASE_NOT_FIXED ); - if ( _phaseStatus == PhaseStatus::SIGN_PHASE_POSITIVE ) + if ( getPhaseStatus() == PhaseStatus::SIGN_PHASE_POSITIVE ) return getPositiveSplit(); return getNegativeSplit(); @@ -389,42 +392,49 @@ void SignConstraint::notifyLowerBound( unsigned variable, double bound ) // Otherwise - update bound setLowerBound( variable, bound ); - - if ( variable == _f && FloatUtils::gt( bound, -1 ) ) + if ( !phaseFixed() ) { - setPhaseStatus( PhaseStatus::SIGN_PHASE_POSITIVE ); - - if ( _boundManager != nullptr ) + if ( variable == _f && FloatUtils::gt( bound, -1 ) ) { - if ( _boundManager->shouldProduceProofs() ) - { - // If lb of f is > 1, we have a contradiction - if ( FloatUtils::gt( bound, 1 ) ) - throw InfeasibleQueryException(); - - _boundManager->addLemmaExplanationAndTightenBound( - _f, 1, Tightening::LB, { variable }, Tightening::LB, getType() ); - _boundManager->addLemmaExplanationAndTightenBound( - _b, 0, Tightening::LB, { variable }, Tightening::LB, getType() ); - } - else + setPhaseStatus( PhaseStatus::SIGN_PHASE_POSITIVE ); + + if ( _boundManager != nullptr ) { - _boundManager->tightenLowerBound( _f, 1 ); - _boundManager->tightenLowerBound( _b, 0 ); + if ( _boundManager->shouldProduceProofs() ) + { + // If lb of f is > 1, we have a contradiction + if ( FloatUtils::gt( bound, 1 ) ) + throw InfeasibleQueryException(); + + _boundManager->addLemmaExplanationAndTightenBound( + _f, 1, Tightening::LB, { variable }, Tightening::LB, *this, true, bound ); + _boundManager->addLemmaExplanationAndTightenBound( + _b, 0, Tightening::LB, { variable }, Tightening::LB, *this, false, bound ); + } + else + { + _boundManager->tightenLowerBound( _f, 1 ); + _boundManager->tightenLowerBound( _b, 0 ); + } } } - } - else if ( variable == _b && !FloatUtils::isNegative( bound ) ) - { - setPhaseStatus( PhaseStatus::SIGN_PHASE_POSITIVE ); - if ( _boundManager != nullptr ) + else if ( variable == _b && !FloatUtils::isNegative( bound ) ) { - if ( _boundManager->shouldProduceProofs() ) - _boundManager->addLemmaExplanationAndTightenBound( - _f, 1, Tightening::LB, { variable }, Tightening::LB, getType() ); - else - _boundManager->tightenLowerBound( _f, 1 ); + setPhaseStatus( PhaseStatus::SIGN_PHASE_POSITIVE ); + if ( _boundManager != nullptr ) + { + if ( _boundManager->shouldProduceProofs() ) + _boundManager->addLemmaExplanationAndTightenBound( + _f, 1, Tightening::LB, { variable }, Tightening::LB, *this, true, bound ); + else + _boundManager->tightenLowerBound( _f, 1 ); + } } + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } } @@ -440,41 +450,48 @@ void SignConstraint::notifyUpperBound( unsigned variable, double bound ) // Otherwise - update bound setUpperBound( variable, bound ); - - if ( variable == _f && FloatUtils::lt( bound, 1 ) ) + if ( !phaseFixed() ) { - setPhaseStatus( PhaseStatus::SIGN_PHASE_NEGATIVE ); - if ( _boundManager != nullptr ) + if ( variable == _f && FloatUtils::lt( bound, 1 ) ) { - if ( _boundManager->shouldProduceProofs() ) + setPhaseStatus( PhaseStatus::SIGN_PHASE_NEGATIVE ); + if ( _boundManager != nullptr ) { - // If ub of f is < -1, we have a contradiction - if ( FloatUtils::lt( bound, -1 ) ) - throw InfeasibleQueryException(); - - _boundManager->addLemmaExplanationAndTightenBound( - _f, -1, Tightening::UB, { variable }, Tightening::UB, getType() ); - _boundManager->addLemmaExplanationAndTightenBound( - _b, 0, Tightening::UB, { variable }, Tightening::UB, getType() ); - } - else - { - _boundManager->tightenUpperBound( _f, -1 ); - _boundManager->tightenUpperBound( _b, 0 ); + if ( _boundManager->shouldProduceProofs() ) + { + // If ub of f is < -1, we have a contradiction + if ( FloatUtils::lt( bound, -1 ) ) + throw InfeasibleQueryException(); + + _boundManager->addLemmaExplanationAndTightenBound( + _f, -1, Tightening::UB, { variable }, Tightening::UB, *this, true, bound ); + _boundManager->addLemmaExplanationAndTightenBound( + _b, 0, Tightening::UB, { variable }, Tightening::UB, *this, false, bound ); + } + else + { + _boundManager->tightenUpperBound( _f, -1 ); + _boundManager->tightenUpperBound( _b, 0 ); + } } } - } - else if ( variable == _b && FloatUtils::isNegative( bound ) ) - { - setPhaseStatus( PhaseStatus::SIGN_PHASE_NEGATIVE ); - if ( _boundManager != nullptr ) + else if ( variable == _b && FloatUtils::isNegative( bound ) ) { - if ( _boundManager->shouldProduceProofs() ) - _boundManager->addLemmaExplanationAndTightenBound( - _f, -1, Tightening::UB, { variable }, Tightening::UB, getType() ); - else - _boundManager->tightenUpperBound( _f, -1 ); + setPhaseStatus( PhaseStatus::SIGN_PHASE_NEGATIVE ); + if ( _boundManager != nullptr ) + { + if ( _boundManager->shouldProduceProofs() ) + _boundManager->addLemmaExplanationAndTightenBound( + _f, -1, Tightening::UB, { variable }, Tightening::UB, *this, true, bound ); + else + _boundManager->tightenUpperBound( _f, -1 ); + } } + +#ifdef BUILD_CADICAL + if ( !_cdclVars.empty() && phaseFixed() && isActive() ) + _cdclCore->addLiteralToPropagate( propagatePhaseAsLit() ); +#endif } } @@ -572,22 +589,22 @@ void SignConstraint::eliminateVariable( __attribute__( ( unused ) ) unsigned var if ( FloatUtils::areEqual( fixedValue, 1 ) ) { - ASSERT( _phaseStatus != SIGN_PHASE_NEGATIVE ); + ASSERT( getPhaseStatus() != SIGN_PHASE_NEGATIVE ); } else if ( FloatUtils::areEqual( fixedValue, -1 ) ) { - ASSERT( _phaseStatus != SIGN_PHASE_POSITIVE ); + ASSERT( getPhaseStatus() != SIGN_PHASE_POSITIVE ); } } else if ( variable == _b ) { if ( FloatUtils::gte( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != SIGN_PHASE_NEGATIVE ); + ASSERT( getPhaseStatus() != SIGN_PHASE_NEGATIVE ); } else if ( FloatUtils::lt( fixedValue, 0 ) ) { - ASSERT( _phaseStatus != SIGN_PHASE_POSITIVE ); + ASSERT( getPhaseStatus() != SIGN_PHASE_POSITIVE ); } } } ); @@ -612,8 +629,8 @@ void SignConstraint::dump( String &output ) const _f, _b, _constraintActive ? "Yes" : "No", - _phaseStatus, - phaseToString( _phaseStatus ).ascii() ); + getPhaseStatus(), + phaseToString( getPhaseStatus() ).ascii() ); output += Stringf( "b in [%s, %s], ", @@ -665,3 +682,78 @@ void SignConstraint::addTableauAuxVar( unsigned /* tableauAuxVar */, unsigned /* constraintAuxVar */ ) { } + +#ifdef BUILD_CADICAL +void SignConstraint::booleanAbstraction( + Map &cadicalVarToPlc ) +{ + unsigned int idx = cadicalVarToPlc.size(); + _cdclVars.append( idx ); + cadicalVarToPlc.insert( idx, this ); +} + +int SignConstraint::propagatePhaseAsLit() const +{ + ASSERT( _cdclVars.size() == 1 ) + if ( getPhaseStatus() == SIGN_PHASE_POSITIVE ) + return (int)_cdclVars.back(); + else if ( getPhaseStatus() == SIGN_PHASE_NEGATIVE ) + return -(int)_cdclVars.back(); + else + return 0; +} + +void SignConstraint::propagateLitAsSplit( int lit ) +{ + ASSERT( _cdclVars.exists( FloatUtils::abs( lit ) ) ); + + setActiveConstraint( false ); + + if ( lit > 0 ) + setPhaseStatus( SIGN_PHASE_POSITIVE ); + else + setPhaseStatus( SIGN_PHASE_NEGATIVE ); +} + +int SignConstraint::getLiteralForDecision() const +{ + ASSERT( getPhaseStatus() == PHASE_NOT_FIXED ); + + if ( _direction == SIGN_PHASE_NEGATIVE ) + return -(int)_cdclVars.front(); + if ( _direction == SIGN_PHASE_POSITIVE ) + return (int)_cdclVars.front(); + + if ( existsAssignment( _f ) ) + if ( FloatUtils::isPositive( getAssignment( _f ) ) ) + return (int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); + else + return -(int)_cdclVars.front(); +} + +bool SignConstraint::isBoundFixingPhase( unsigned int var, + double bound, + Tightening::BoundType boundType ) const +{ + if ( getPhaseStatus() == SIGN_PHASE_POSITIVE ) + { + if ( var == _b && boundType == Tightening::LB && !FloatUtils::isNegative( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::LB && FloatUtils::gt( bound, -1 ) ) + return true; + } + else if ( getPhaseStatus() == SIGN_PHASE_NEGATIVE ) + { + if ( var == _b && boundType == Tightening::UB && FloatUtils::isNegative( bound ) ) + return true; + + if ( var == _f && boundType == Tightening::UB && FloatUtils::lt( bound, 1 ) ) + return true; + } + + return false; +} +#endif \ No newline at end of file diff --git a/src/engine/SignConstraint.h b/src/engine/SignConstraint.h index 01544ebe60..f3a8430fca 100644 --- a/src/engine/SignConstraint.h +++ b/src/engine/SignConstraint.h @@ -226,6 +226,34 @@ class SignConstraint : public PiecewiseLinearConstraint void updateScoreBasedOnPolarity() override; +#ifdef BUILD_CADICAL + /* + Creates boolean abstraction of phases and adds abstracted variables to the SAT solver + */ + void + booleanAbstraction( Map &cadicalVarToPlc ) override; + + /* + Returns a literal representing a boolean propagation + Returns 0 if no propagation can be deduced + */ + int propagatePhaseAsLit() const override; + + /* + Returns a phase status corresponding to a literal, + assuming the literal is part of the boolean abstraction + */ + void propagateLitAsSplit( int lit ) override; + + /* + Returns on which phase to decide this constraint, as a cadical var + */ + int getLiteralForDecision() const override; + + bool isBoundFixingPhase( unsigned var, + double bound, + Tightening::BoundType boundType ) const override; +#endif private: unsigned _b, _f; diff --git a/src/engine/SubQuery.h b/src/engine/SubQuery.h index 7f4a12940b..c8239b5869 100644 --- a/src/engine/SubQuery.h +++ b/src/engine/SubQuery.h @@ -19,7 +19,7 @@ #include "List.h" #include "MString.h" #include "PiecewiseLinearCaseSplit.h" -#include "SmtState.h" +#include "SearchTreeState.h" #include #include @@ -33,7 +33,7 @@ struct SubQuery String _queryId; std::unique_ptr _split; - std::unique_ptr _smtState; + std::unique_ptr _searchTreeState; unsigned _timeoutInSeconds; unsigned _depth; }; diff --git a/src/engine/Tableau.cpp b/src/engine/Tableau.cpp index 21fe242875..fafa596f07 100644 --- a/src/engine/Tableau.cpp +++ b/src/engine/Tableau.cpp @@ -1693,6 +1693,32 @@ void Tableau::storeState( TableauState &state, TableauStateStorageLevel level ) // Store the merged variables state._mergedVariables = _mergedVariables; } + else if ( level == TableauStateStorageLevel::STORE_BASICS_ONLY ) + { + // Set the dimensions + state.setDimensionsForBasics( _m, _n, *this ); + + // Basic variables + state._basicVariables = _basicVariables; + + // Store the assignments + memcpy( state._basicAssignment, _basicAssignment, sizeof( double ) * _m ); + memcpy( state._nonBasicAssignment, _nonBasicAssignment, sizeof( double ) * ( _n - _m ) ); + state._basicAssignmentStatus = _basicAssignmentStatus; + + // Store the indices + memcpy( state._basicIndexToVariable, _basicIndexToVariable, sizeof( unsigned ) * _m ); + memcpy( state._nonBasicIndexToVariable, + _nonBasicIndexToVariable, + sizeof( unsigned ) * ( _n - _m ) ); + memcpy( state._variableToIndex, _variableToIndex, sizeof( unsigned ) * _n ); + + // Store the basis factorization + _basisFactorization->storeFactorization( state._basisFactorization ); + + // Store the merged variables + state._mergedVariables = _mergedVariables; + } else { ASSERT( level == TableauStateStorageLevel::STORE_NONE ); @@ -1770,6 +1796,30 @@ void Tableau::restoreState( const TableauState &state, TableauStateStorageLevel _statistics->setUnsignedAttribute( Statistics::CURRENT_TABLEAU_N, _n ); } } + else if ( level == TableauStateStorageLevel::STORE_BASICS_ONLY ) + { + // Basic variables + _basicVariables = state._basicVariables; + + // Restore the assignments + memcpy( _basicAssignment, state._basicAssignment, sizeof( double ) * _m ); + memcpy( _nonBasicAssignment, state._nonBasicAssignment, sizeof( double ) * ( _n - _m ) ); + _basicAssignmentStatus = state._basicAssignmentStatus; + + // Restore the indices + memcpy( _basicIndexToVariable, state._basicIndexToVariable, sizeof( unsigned ) * _m ); + memcpy( _nonBasicIndexToVariable, + state._nonBasicIndexToVariable, + sizeof( unsigned ) * ( _n - _m ) ); + memcpy( _variableToIndex, state._variableToIndex, sizeof( unsigned ) * _n ); + + // Restore the basis factorization + _basisFactorization->restoreFactorization( state._basisFactorization ); + // Restore the merged variables + _mergedVariables = state._mergedVariables; + computeAssignment(); + computeCostFunction(); + } else { ASSERT( false ); @@ -2538,7 +2588,28 @@ void Tableau::getColumnOfBasis( unsigned column, SparseUnsortedList *result ) co void Tableau::refreshBasisFactorization() { - _basisFactorization->obtainFreshBasis(); + try + { + _basisFactorization->obtainFreshBasis(); + } + catch ( const MalformedBasisException & ) + { + ConstraintMatrixAnalyzer analyzer; + analyzer.analyze( (const SparseUnsortedList **)_sparseRowsOfA, _m, _n ); + List independentColumns = analyzer.getIndependentColumns(); + + try + { + initializeTableau( independentColumns ); + } + catch ( MalformedBasisException & ) + { + TABLEAU_LOG( "refreshBasisFactorization failed - could not refactorize basis" ); + throw MarabouError( MarabouError::FAILURE_TO_ADD_NEW_EQUATION ); + } + + computeCostFunction(); + } } unsigned Tableau::getVariableAfterMerging( unsigned variable ) const @@ -2554,6 +2625,10 @@ unsigned Tableau::getVariableAfterMerging( unsigned variable ) const void Tableau::postContextPopHook() { updateVariablesToComplyWithBounds(); +#ifdef BUILD_CADICAL + if ( Options::get()->getBool( Options::SOLVE_WITH_CDCL ) ) + refreshBasisFactorization(); +#endif } void Tableau::mergeColumns( unsigned x1, unsigned x2 ) diff --git a/src/engine/TableauState.cpp b/src/engine/TableauState.cpp index ce4a5c24ce..782c43bdfe 100644 --- a/src/engine/TableauState.cpp +++ b/src/engine/TableauState.cpp @@ -212,6 +212,39 @@ void TableauState::setDimensions( unsigned m, throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::basisFactorization" ); } +void TableauState::setDimensionsForBasics( unsigned m, + unsigned n, + const IBasisFactorization::BasisColumnOracle &oracle ) +{ + _m = m; + _n = n; + + _basicAssignment = new double[m]; + if ( !_basicAssignment ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::assignment" ); + + _nonBasicAssignment = new double[n - m]; + if ( !_nonBasicAssignment ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::nonBasicAssignment" ); + + _basicIndexToVariable = new unsigned[m]; + if ( !_basicIndexToVariable ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::basicIndexToVariable" ); + + _nonBasicIndexToVariable = new unsigned[n - m]; + if ( !_nonBasicIndexToVariable ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, + "TableauState::nonBasicIndexToVariable" ); + + _variableToIndex = new unsigned[n]; + if ( !_variableToIndex ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::variableToIndex" ); + + _basisFactorization = BasisFactorizationFactory::createBasisFactorization( m, oracle ); + if ( !_basisFactorization ) + throw MarabouError( MarabouError::ALLOCATION_FAILED, "TableauState::basisFactorization" ); +} + void TableauState::initializeBounds( unsigned n ) { _lowerBounds = new double[n]; diff --git a/src/engine/TableauState.h b/src/engine/TableauState.h index 5cecc4a400..8fe5756e0a 100644 --- a/src/engine/TableauState.h +++ b/src/engine/TableauState.h @@ -45,6 +45,9 @@ class TableauState void setDimensions( unsigned m, unsigned n, const IBasisFactorization::BasisColumnOracle &oracle ); + void setDimensionsForBasics( unsigned m, + unsigned n, + const IBasisFactorization::BasisColumnOracle &oracle ); /* Just create the bounds array. */ diff --git a/src/engine/TableauStateStorageLevel.h b/src/engine/TableauStateStorageLevel.h index f920bac4ef..51d225fe55 100644 --- a/src/engine/TableauStateStorageLevel.h +++ b/src/engine/TableauStateStorageLevel.h @@ -20,6 +20,7 @@ enum class TableauStateStorageLevel { STORE_NONE = 0, STORE_BOUNDS_ONLY = 1, STORE_ENTIRE_TABLEAU_STATE = 2, + STORE_BASICS_ONLY = 3, }; #endif // __TableauStateStorageLevel_h__ diff --git a/src/engine/TimeoutException.h b/src/engine/TimeoutException.h new file mode 100644 index 0000000000..62553bc3e5 --- /dev/null +++ b/src/engine/TimeoutException.h @@ -0,0 +1,32 @@ +/********************* */ +/*! \file TimeoutException.h + ** \verbatim + ** Top contributors (to current version): + ** Idan Refaeli + ** This file is part of the Marabou project. + ** Copyright (c) 2017-2024 by the authors listed in the file AUTHORS + ** in the top-level source directory) and their institutional affiliations. + ** All rights reserved. See the file COPYING in the top-level source + ** directory for licensing information.\endverbatim + ** + ** [[ Add lengthier description here ]] + +**/ + +#ifndef __TimeoutException_h__ +#define __TimeoutException_h__ + +class TimeoutException +{ +public: +}; + +#endif // __TimeoutException_h__ + +// +// Local Variables: +// compile-command: "make -C ../.. " +// tags-file-name: "../../TAGS" +// c-basic-offset: 4 +// End: +// diff --git a/src/engine/tests/MockBoundManager.h b/src/engine/tests/MockBoundManager.h index 324ccad18e..584ad490c3 100644 --- a/src/engine/tests/MockBoundManager.h +++ b/src/engine/tests/MockBoundManager.h @@ -268,7 +268,9 @@ class MockBoundManager : public IBoundManager Tightening::BoundType /* affectedVarBound */, const List & /* causingVar */, Tightening::BoundType /* causingVarBound */, - PiecewiseLinearFunctionType /* constraintType */ ) + PiecewiseLinearConstraint /* constraintType */ &, + bool /* isPhaseFixing*/, + double /* minTargetBound */ ) { return true; } diff --git a/src/engine/tests/MockEngine.h b/src/engine/tests/MockEngine.h index 4bab581301..afc033e5ba 100644 --- a/src/engine/tests/MockEngine.h +++ b/src/engine/tests/MockEngine.h @@ -20,8 +20,11 @@ #include "List.h" #include "PiecewiseLinearCaseSplit.h" #include "PiecewiseLinearConstraint.h" +#include "SearchTreeHandler.h" #include "context/context.h" +#include + class String; class MockEngine : public IEngine @@ -70,7 +73,7 @@ class MockEngine : public IEngine List lastLowerBounds; List lastUpperBounds; List lastEquations; - void applySplit( const PiecewiseLinearCaseSplit &split ) + void applySplit( const PiecewiseLinearCaseSplit &split ) override { List bounds = split.getBoundTightenings(); auto equations = split.getEquations(); @@ -92,32 +95,43 @@ class MockEngine : public IEngine } } - void postContextPopHook(){}; - void preContextPushHook(){}; + void applyPlcPhaseFixingTightenings( PiecewiseLinearConstraint & /*constraint*/ ) override + { + } + + + void postContextPopHook() override + { + } + + void preContextPushHook() override + { + } mutable EngineState *lastStoredState; - void storeState( EngineState &state, TableauStateStorageLevel /*level*/ ) const + void storeState( EngineState &state, TableauStateStorageLevel /*level*/ ) const override { lastStoredState = &state; } const EngineState *lastRestoredState; - void restoreState( const EngineState &state ) + void restoreState( const EngineState &state ) override { lastRestoredState = &state; } - void setNumPlConstraintsDisabledByValidSplits( unsigned /* numConstraints */ ) + void setNumPlConstraintsDisabledByValidSplits( unsigned /* numConstraints */ ) override { } unsigned _timeToSolve; - IEngine::ExitCode _exitCode; - bool solve( double timeoutInSeconds ) + ExitCode _exitCode; + + bool solve( double timeoutInSeconds ) override { if ( timeoutInSeconds >= _timeToSolve ) - _exitCode = IEngine::TIMEOUT; - return _exitCode == IEngine::SAT; + _exitCode = ExitCode::TIMEOUT; + return _exitCode == ExitCode::SAT; } void setTimeToSolve( unsigned timeToSolve ) @@ -125,17 +139,17 @@ class MockEngine : public IEngine _timeToSolve = timeToSolve; } - void setExitCode( IEngine::ExitCode exitCode ) + ExitCode getExitCode() const override { - _exitCode = exitCode; + return _exitCode; } - IEngine::ExitCode getExitCode() const + void setExitCode( ExitCode exitCode ) override { - return _exitCode; + _exitCode = exitCode; } - void reset() + void reset() override { } @@ -145,7 +159,7 @@ class MockEngine : public IEngine _inputVariables = inputVariables; } - List getInputVariables() const + List getInputVariables() const override { return _inputVariables; } @@ -154,17 +168,17 @@ class MockEngine : public IEngine { } - mutable SmtState *lastRestoredSmtState; - bool restoreSmtState( SmtState &smtState ) + mutable SearchTreeState *lastRestoredSearchTreeState; + bool restoreSearchTreeState( SearchTreeState &searchTreeState ) override { - lastRestoredSmtState = &smtState; + lastRestoredSearchTreeState = &searchTreeState; return true; } - mutable SmtState *lastStoredSmtState; - void storeSmtState( SmtState &smtState ) + mutable SearchTreeState *lastStoredSearchTreeState; + void storeSearchTreeState( SearchTreeState &searchTreeState ) override { - lastStoredSmtState = &smtState; + lastStoredSearchTreeState = &searchTreeState; } List _constraintsToSplit; @@ -173,7 +187,7 @@ class MockEngine : public IEngine _constraintsToSplit.append( constraint ); } - PiecewiseLinearConstraint *pickSplitPLConstraint( DivideStrategy /**/ ) + PiecewiseLinearConstraint *pickSplitPLConstraint( DivideStrategy /**/ ) override { if ( !_constraintsToSplit.empty() ) { @@ -185,7 +199,7 @@ class MockEngine : public IEngine return NULL; } - PiecewiseLinearConstraint *pickSplitPLConstraintSnC( SnCDivideStrategy /**/ ) + PiecewiseLinearConstraint *pickSplitPLConstraintSnC( SnCDivideStrategy /**/ ) override { if ( !_constraintsToSplit.empty() ) { @@ -200,35 +214,37 @@ class MockEngine : public IEngine bool _snc; CVC4::context::Context _context; - void applySnCSplit( PiecewiseLinearCaseSplit /*split*/, String /*queryId*/ ) + void applySnCSplit( PiecewiseLinearCaseSplit /*split*/, String /*queryId*/ ) override { _snc = true; _context.push(); } - bool inSnCMode() const + bool inSnCMode() const override { return _snc; } - void applyAllBoundTightenings(){}; + void applyAllBoundTightenings() override + { + } - bool applyAllValidConstraintCaseSplits() + bool applyAllValidConstraintCaseSplits() override { return false; }; - CVC4::context::Context &getContext() + CVC4::context::Context &getContext() override { return _context; } - bool consistentBounds() const + bool consistentBounds() const override { return true; } - double explainBound( unsigned /* var */, bool /* isUpper */ ) const + double explainBound( unsigned /* var */, bool /* isUpper */ ) const override { return 0.0; } @@ -241,55 +257,183 @@ class MockEngine : public IEngine { } - double getGroundBound( unsigned /*var*/, bool /*isUpper*/ ) const + double getGroundBound( unsigned /*var*/, bool /*isUpper*/ ) const override { return 0; } + std::shared_ptr + getGroundBoundEntry( unsigned /*var*/, bool /*isUpper*/ ) const override + { + return nullptr; + } - UnsatCertificateNode *getUNSATCertificateCurrentPointer() const + + UnsatCertificateNode *getUNSATCertificateCurrentPointer() const override { return NULL; } - void setUNSATCertificateCurrentPointer( UnsatCertificateNode * /* node*/ ) + void setUNSATCertificateCurrentPointer( UnsatCertificateNode * /* node*/ ) override { } - const UnsatCertificateNode *getUNSATCertificateRoot() const + const UnsatCertificateNode *getUNSATCertificateRoot() const override { return NULL; } - bool certifyUNSATCertificate() + bool certifyUNSATCertificate() override { return true; } - void explainSimplexFailure() + void explainSimplexFailure() override { } - const BoundExplainer *getBoundExplainer() const + const BoundExplainer *getBoundExplainer() const override { return NULL; } - void setBoundExplainerContent( BoundExplainer * /*boundExplainer */ ) + void setBoundExplainerContent( BoundExplainer * /*boundExplainer */ ) override { } - void propagateBoundManagerTightenings() + bool propagateBoundManagerTightenings() override { + return false; } - bool shouldProduceProofs() const + bool shouldProduceProofs() const override { return true; } - void addPLCLemma( std::shared_ptr & /*explanation*/ ) + std::shared_ptr + setGroundBoundFromLemma( const std::shared_ptr /*lemma*/, + bool /*isPhaseFixing*/ ) override + { + return nullptr; + } + + bool shouldSolveWithMILP() const override + { + return false; + } + + void initializeSolver() override + { + } + + void assertEngineBoundsForSplit( const PiecewiseLinearCaseSplit & /*split*/ ) override + { + } + + bool shouldExitDueToTimeout( double ) const override + { + return false; + } + + unsigned getVerbosity() const override + { + return 0; + } + + void exportQueryWithError( String ) override + { + } + + const List *getPiecewiseLinearConstraints() const override + { + return NULL; + } + + LPSolverType getLpSolverType() const override { + return LPSolverType::NATIVE; + } + + NLR::NetworkLevelReasoner *getNetworkLevelReasoner() const override + { + return nullptr; + } + + void restoreInitialEngineState() override + { + } + + void incNumOfLemmas() override + { + } + + bool solveWithMILPEncoding( double timeoutInSeconds ) override + { + if ( timeoutInSeconds >= _timeToSolve ) + _exitCode = ExitCode::TIMEOUT; + return _exitCode == ExitCode::SAT; + } + + bool shouldSolveWithCDCL() const override + { + return false; + } + + List getOutputVariables() const override + { + return List(); + } + + SymbolicBoundTighteningType getSymbolicBoundTighteningType() const override + { + return SymbolicBoundTighteningType::NONE; + } + + const IBoundManager *getBoundManager() const override + { + return nullptr; + } + + std::shared_ptr getInputQuery() const + { + return std::shared_ptr( nullptr ); + } + +#ifdef BUILD_CADICAL + bool solveWithCDCL( double timeoutInSeconds ) override + { + if ( timeoutInSeconds >= _timeToSolve ) + _exitCode = ExitCode::TIMEOUT; + return _exitCode == ExitCode::SAT; + } + + Set clauseFromContradictionVector( const SparseUnsortedList &, + unsigned, + int, + bool, + double targetBound ) override + { + return Set(); + } + + Set explainPhaseWithProof( const PiecewiseLinearConstraint * ) override + { + return Set(); + } + + void explainGurobiFailure() override + { + } + + void removeLiteralFromPropagations( int /*literal*/ ) override + { + } + + bool checkAssignmentComplianceWithClause( const Set & /*clause*/ ) const override + { + return true; } +#endif }; #endif // __MockEngine_h__ diff --git a/src/engine/tests/MockRowBoundTightener.h b/src/engine/tests/MockRowBoundTightener.h index 98636607b0..c3d02f8bfa 100644 --- a/src/engine/tests/MockRowBoundTightener.h +++ b/src/engine/tests/MockRowBoundTightener.h @@ -70,9 +70,11 @@ class MockRowBoundTightener : public IRowBoundTightener void notifyUpperBound( unsigned /* variable */, double /* bound */ ) { } - void examineInvertedBasisMatrix( bool /* untilSaturation */ ) + unsigned examineInvertedBasisMatrix( bool /* untilSaturation */ ) { + return 0; } + void examineConstraintMatrix( bool /* untilSaturation */ ) { } @@ -85,8 +87,9 @@ class MockRowBoundTightener : public IRowBoundTightener void setStatistics( Statistics * /* statistics */ ) { } - void examineImplicitInvertedBasisMatrix( bool /* untilSaturation */ ) + unsigned examineImplicitInvertedBasisMatrix( bool /* untilSaturation */ ) { + return 0; } void setBoundsPointers( const double * /* lower */, const double * /* upper */ ) { diff --git a/src/engine/tests/Test_DnCWorker.h b/src/engine/tests/Test_DnCWorker.h index f358f162d6..0c95617ea6 100644 --- a/src/engine/tests/Test_DnCWorker.h +++ b/src/engine/tests/Test_DnCWorker.h @@ -113,7 +113,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite createPlaceHolderSubQuery(); _engine->setTimeToSolve( 10 ); - _engine->setExitCode( IEngine::TIMEOUT ); + _engine->setExitCode( ExitCode::TIMEOUT ); std::atomic_int numUnsolvedSubQueries( 1 ); std::atomic_bool shouldQuitSolving( false ); unsigned threadId = 0; @@ -134,7 +134,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::TIMEOUT ); + TS_ASSERT( _engine->getExitCode() == ExitCode::TIMEOUT ); TS_ASSERT( clearSubQueries() == 4 ); TS_ASSERT( numUnsolvedSubQueries.load() == 4 ); TS_ASSERT( !shouldQuitSolving.load() ); @@ -150,7 +150,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite createPlaceHolderSubQuery(); createPlaceHolderSubQuery(); - _engine->setExitCode( IEngine::UNSAT ); + _engine->setExitCode( ExitCode::UNSAT ); numUnsolvedSubQueries = 2; shouldQuitSolving = false; dncWorker = DnCWorker( _workload, @@ -165,7 +165,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::UNSAT ); + TS_ASSERT( _engine->getExitCode() == ExitCode::UNSAT ); TS_ASSERT( clearSubQueries() == 1 ); TS_ASSERT( numUnsolvedSubQueries.load() == 1 ); TS_ASSERT( !shouldQuitSolving.load() ); @@ -182,7 +182,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite TS_ASSERT( clearSubQueries() == 0 ); createPlaceHolderSubQuery(); - _engine->setExitCode( IEngine::UNSAT ); + _engine->setExitCode( ExitCode::UNSAT ); numUnsolvedSubQueries = 1; shouldQuitSolving = false; @@ -198,7 +198,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::UNSAT ); + TS_ASSERT( _engine->getExitCode() == ExitCode::UNSAT ); TS_ASSERT( clearSubQueries() == 0 ); TS_ASSERT( numUnsolvedSubQueries.load() == 0 ); TS_ASSERT( shouldQuitSolving.load() ); @@ -214,7 +214,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite TS_ASSERT( clearSubQueries() == 0 ); createPlaceHolderSubQuery(); - _engine->setExitCode( IEngine::SAT ); + _engine->setExitCode( ExitCode::SAT ); numUnsolvedSubQueries = ( 1 ); shouldQuitSolving = ( false ); dncWorker = DnCWorker( _workload, @@ -229,7 +229,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::SAT ); + TS_ASSERT( _engine->getExitCode() == ExitCode::SAT ); TS_ASSERT( clearSubQueries() == 0 ); TS_ASSERT( numUnsolvedSubQueries.load() == 0 ); TS_ASSERT( shouldQuitSolving.load() ); @@ -244,7 +244,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite TS_ASSERT( clearSubQueries() == 0 ); createPlaceHolderSubQuery(); - _engine->setExitCode( IEngine::QUIT_REQUESTED ); + _engine->setExitCode( ExitCode::QUIT_REQUESTED ); numUnsolvedSubQueries = 1; shouldQuitSolving = true; dncWorker = DnCWorker( _workload, @@ -258,7 +258,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite verbosity, portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::QUIT_REQUESTED ); + TS_ASSERT( _engine->getExitCode() == ExitCode::QUIT_REQUESTED ); TS_ASSERT( numUnsolvedSubQueries.load() == 1 ); // Pop a subQuery from the workload, set the mock engine's exitCode @@ -272,7 +272,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite TS_ASSERT( clearSubQueries() == 0 ); createPlaceHolderSubQuery(); - _engine->setExitCode( IEngine::ERROR ); + _engine->setExitCode( ExitCode::ERROR ); numUnsolvedSubQueries = 1; shouldQuitSolving = false; @@ -288,7 +288,7 @@ class DnCWorkerTestSuite : public CxxTest::TestSuite portfolio ); dncWorker.popOneSubQueryAndSolve(); - TS_ASSERT( _engine->getExitCode() == IEngine::ERROR ); + TS_ASSERT( _engine->getExitCode() == ExitCode::ERROR ); TS_ASSERT( numUnsolvedSubQueries.load() == 1 ); TS_ASSERT( shouldQuitSolving.load() ); } diff --git a/src/engine/tests/Test_LeakyReluConstraint.h b/src/engine/tests/Test_LeakyReluConstraint.h index 2e3c3bad77..f17c49db77 100644 --- a/src/engine/tests/Test_LeakyReluConstraint.h +++ b/src/engine/tests/Test_LeakyReluConstraint.h @@ -213,8 +213,12 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite { unsigned b = 1; unsigned f = 4; + unsigned activeAux = 10; - LeakyReluConstraint lrelu( b, f, slope ); + Context context; + BoundManager boundManager( context ); + + LeakyReluConstraint lrelu = prepareLeakyRelu( b, f, activeAux, &boundManager ); List splits = lrelu.getCaseSplits(); @@ -226,11 +230,16 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite List::iterator split2 = split1; ++split2; - TS_ASSERT( isActiveSplit( b, f, split1 ) || isActiveSplit( b, f, split2 ) ); - TS_ASSERT( isInactiveSplit( b, f, split1 ) || isInactiveSplit( b, f, split2 ) ); + TS_ASSERT( isActiveSplit( b, f, activeAux, split1 ) || + isActiveSplit( b, f, activeAux, split2 ) ); + TS_ASSERT( isInactiveSplit( b, f, activeAux + 1, split1 ) || + isInactiveSplit( b, f, activeAux + 1, split2 ) ); } - bool isActiveSplit( unsigned b, unsigned f, List::iterator &split ) + bool isActiveSplit( unsigned b, + unsigned f, + unsigned activeAux, + List::iterator &split ) { List bounds = split->getBoundTightenings(); @@ -243,7 +252,7 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite if ( bound1._type != Tightening::LB ) return false; - TS_ASSERT_EQUALS( bounds.size(), 2U ); + TS_ASSERT_EQUALS( bounds.size(), 3U ); ++bound; Tightening bound2 = *bound; @@ -252,27 +261,24 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( bound2._value, 0.0 ); TS_ASSERT_EQUALS( bound2._type, Tightening::LB ); - Equation activeEquation; - auto equations = split->getEquations(); - TS_ASSERT_EQUALS( equations.size(), 1U ); - activeEquation = split->getEquations().front(); - TS_ASSERT_EQUALS( activeEquation._addends.size(), 2U ); - TS_ASSERT_EQUALS( activeEquation._scalar, 0.0 ); - - auto addend = activeEquation._addends.begin(); - TS_ASSERT_EQUALS( addend->_coefficient, 1.0 ); - TS_ASSERT_EQUALS( addend->_variable, b ); + ++bound; + Tightening bound3 = *bound; - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, -1.0 ); - TS_ASSERT_EQUALS( addend->_variable, f ); + TS_ASSERT_EQUALS( bound3._variable, activeAux ); + TS_ASSERT_EQUALS( bound3._value, 0.0 ); + TS_ASSERT_EQUALS( bound3._type, Tightening::UB ); - TS_ASSERT_EQUALS( activeEquation._type, Equation::EQ ); + Equation activeEquation; + auto equations = split->getEquations(); + TS_ASSERT_EQUALS( equations.size(), 0U ); return true; } - bool isInactiveSplit( unsigned b, unsigned f, List::iterator &split ) + bool isInactiveSplit( unsigned b, + unsigned f, + unsigned inactiveAux, + List::iterator &split ) { List bounds = split->getBoundTightenings(); @@ -285,7 +291,7 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite if ( bound1._type != Tightening::UB ) return false; - TS_ASSERT_EQUALS( bounds.size(), 2U ); + TS_ASSERT_EQUALS( bounds.size(), 3U ); ++bound; Tightening bound2 = *bound; @@ -294,22 +300,16 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( bound2._value, 0.0 ); TS_ASSERT_EQUALS( bound2._type, Tightening::UB ); - Equation inactiveEquation; - auto equations = split->getEquations(); - TS_ASSERT_EQUALS( equations.size(), 1U ); - inactiveEquation = split->getEquations().front(); - TS_ASSERT_EQUALS( inactiveEquation._addends.size(), 2U ); - TS_ASSERT_EQUALS( inactiveEquation._scalar, 0.0 ); - - auto addend = inactiveEquation._addends.begin(); - TS_ASSERT_EQUALS( addend->_coefficient, slope ); - TS_ASSERT_EQUALS( addend->_variable, b ); + ++bound; + Tightening bound3 = *bound; - ++addend; - TS_ASSERT_EQUALS( addend->_coefficient, -1.0 ); - TS_ASSERT_EQUALS( addend->_variable, f ); + TS_ASSERT_EQUALS( bound3._variable, inactiveAux ); + TS_ASSERT_EQUALS( bound3._value, 0.0 ); + TS_ASSERT_EQUALS( bound3._type, Tightening::UB ); - TS_ASSERT_EQUALS( inactiveEquation._type, Equation::EQ ); + Equation inactiveEquation; + auto equations = split->getEquations(); + TS_ASSERT_EQUALS( equations.size(), 0U ); return true; } @@ -472,8 +472,12 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite { unsigned b = 1; unsigned f = 4; + unsigned activeAux = 10; - LeakyReluConstraint lrelu( b, f, slope ); + Context context; + BoundManager boundManager( context ); + + LeakyReluConstraint lrelu = prepareLeakyRelu( b, f, activeAux, &boundManager ); TS_ASSERT( !lrelu.phaseFixed() ); TS_ASSERT_THROWS_NOTHING( lrelu.notifyLowerBound( b, 5 ) ); @@ -486,15 +490,19 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite dummy.append( split ); List::iterator split1 = dummy.begin(); - TS_ASSERT( isActiveSplit( b, f, split1 ) ); + TS_ASSERT( isActiveSplit( b, f, activeAux, split1 ) ); } void test_valid_split_relu_phase_fixed_to_inactive() { unsigned b = 1; unsigned f = 4; + unsigned activeAux = 10; - LeakyReluConstraint lrelu( b, f, slope ); + Context context; + BoundManager boundManager( context ); + + LeakyReluConstraint lrelu = prepareLeakyRelu( b, f, activeAux, &boundManager ); TS_ASSERT( !lrelu.phaseFixed() ); TS_ASSERT_THROWS_NOTHING( lrelu.notifyUpperBound( b, -2 ) ); @@ -507,7 +515,7 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite dummy.append( split ); List::iterator split1 = dummy.begin(); - TS_ASSERT( isInactiveSplit( b, f, split1 ) ); + TS_ASSERT( isInactiveSplit( b, f, activeAux + 1, split1 ) ); } void test_leaky_relu_entailed_tightenings() @@ -867,11 +875,13 @@ class LeakyReluConstraintTestSuite : public CxxTest::TestSuite Context context; unsigned b = 1; unsigned f = 4; + unsigned activeAux = 10; - TestLeakyReluConstraint lrelu( b, f, slope ); + BoundManager boundManager( context ); - lrelu.initializeCDOs( &context ); + LeakyReluConstraint lrelu = prepareLeakyRelu( b, f, activeAux, &boundManager ); + lrelu.initializeCDOs( &context ); TS_ASSERT_EQUALS( lrelu.getPhaseStatus(), PHASE_NOT_FIXED ); diff --git a/src/engine/tests/Test_SmtCore.h b/src/engine/tests/Test_SearchTreeHandler.h similarity index 74% rename from src/engine/tests/Test_SmtCore.h rename to src/engine/tests/Test_SearchTreeHandler.h index eae4dea4c0..dd1c606838 100644 --- a/src/engine/tests/Test_SmtCore.h +++ b/src/engine/tests/Test_SearchTreeHandler.h @@ -1,5 +1,5 @@ /********************* */ -/*! \file Test_SmtCore.h +/*! \file Test_SearchTreeHandler.h ** \verbatim ** Top contributors (to current version): ** Guy Katz, Duligur Ibeling, Derek Huang @@ -19,20 +19,20 @@ #include "PiecewiseLinearConstraint.h" #include "Query.h" #include "ReluConstraint.h" -#include "SmtCore.h" +#include "SearchTreeHandler.h" #include #include -class MockForSmtCore +class MockForSearchTreeHandler { public: }; -class SmtCoreTestSuite : public CxxTest::TestSuite +class SearchTreeHandlerTestSuite : public CxxTest::TestSuite { public: - MockForSmtCore *mock; + MockForSearchTreeHandler *mock; MockEngine *engine; class MockConstraint : public PiecewiseLinearConstraint @@ -176,11 +176,25 @@ class SmtCoreTestSuite : public CxxTest::TestSuite void addTableauAuxVar( unsigned /*tableauAuxVar*/, unsigned /*constraintAuxVar*/ ) { } + + void + booleanAbstraction( Map & /*cadicalVarToPlc*/ ) + { + } + + int propagatePhaseAsLit() const + { + return 0; + } + + void propagateLitAsSplit( int /*lit*/ ) + { + } }; void setUp() { - TS_ASSERT( mock = new MockForSmtCore ); + TS_ASSERT( mock = new MockForSearchTreeHandler ); TS_ASSERT( engine = new MockEngine ); } @@ -195,25 +209,25 @@ class SmtCoreTestSuite : public CxxTest::TestSuite ReluConstraint constraint1( 1, 2 ); ReluConstraint constraint2( 3, 4 ); - SmtCore smtCore( engine ); + SearchTreeHandler searchTreeHandler( engine ); for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ) - 1; ++i ) { - smtCore.reportViolatedConstraint( &constraint1 ); - TS_ASSERT( !smtCore.needToSplit() ); - smtCore.reportViolatedConstraint( &constraint2 ); - TS_ASSERT( !smtCore.needToSplit() ); + searchTreeHandler.reportViolatedConstraint( &constraint1 ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); + searchTreeHandler.reportViolatedConstraint( &constraint2 ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); } - smtCore.reportViolatedConstraint( &constraint2 ); - TS_ASSERT( smtCore.needToSplit() ); + searchTreeHandler.reportViolatedConstraint( &constraint2 ); + TS_ASSERT( searchTreeHandler.needToSplit() ); } void test_perform_split() { - SmtCore smtCore( engine ); + SearchTreeHandler searchTreeHandler( engine ); MockConstraint constraint; @@ -247,18 +261,18 @@ class SmtCoreTestSuite : public CxxTest::TestSuite for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &constraint ); + searchTreeHandler.reportViolatedConstraint( &constraint ); engine->lastStoredState = NULL; engine->lastRestoredState = NULL; - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_EQUALS( smtCore.getStackDepth(), 0U ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_EQUALS( searchTreeHandler.getStackDepth(), 0U ); TS_ASSERT( !constraint.setActiveWasCalled ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); TS_ASSERT( constraint.setActiveWasCalled ); - TS_ASSERT( !smtCore.needToSplit() ); - TS_ASSERT_EQUALS( smtCore.getStackDepth(), 1U ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); + TS_ASSERT_EQUALS( searchTreeHandler.getStackDepth(), 1U ); // Check that Split1 was performed and tableau state was stored TS_ASSERT_EQUALS( engine->lastLowerBounds.size(), 1U ); @@ -280,8 +294,8 @@ class SmtCoreTestSuite : public CxxTest::TestSuite // Pop Split1, check that the tableau was restored and that // a Split2 was performed - TS_ASSERT( smtCore.popSplit() ); - TS_ASSERT_EQUALS( smtCore.getStackDepth(), 1U ); + TS_ASSERT( searchTreeHandler.popSplit() ); + TS_ASSERT_EQUALS( searchTreeHandler.getStackDepth(), 1U ); TS_ASSERT_EQUALS( engine->lastRestoredState, originalState ); TS_ASSERT( !engine->lastStoredState ); @@ -304,8 +318,8 @@ class SmtCoreTestSuite : public CxxTest::TestSuite // Pop Split2, check that the tableau was restored and that // a Split3 was performed - TS_ASSERT( smtCore.popSplit() ); - TS_ASSERT_EQUALS( smtCore.getStackDepth(), 1U ); + TS_ASSERT( searchTreeHandler.popSplit() ); + TS_ASSERT_EQUALS( searchTreeHandler.getStackDepth(), 1U ); TS_ASSERT_EQUALS( engine->lastRestoredState, originalState ); TS_ASSERT( !engine->lastStoredState ); @@ -324,14 +338,14 @@ class SmtCoreTestSuite : public CxxTest::TestSuite engine->lastEquations.clear(); // Final pop - TS_ASSERT( !smtCore.popSplit() ); + TS_ASSERT( !searchTreeHandler.popSplit() ); TS_ASSERT( !engine->lastRestoredState ); - TS_ASSERT_EQUALS( smtCore.getStackDepth(), 0U ); + TS_ASSERT_EQUALS( searchTreeHandler.getStackDepth(), 0U ); } void test_perform_split__inactive_constraint() { - SmtCore smtCore( engine ); + SearchTreeHandler searchTreeHandler( engine ); MockConstraint constraint; @@ -365,13 +379,13 @@ class SmtCoreTestSuite : public CxxTest::TestSuite for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &constraint ); + searchTreeHandler.reportViolatedConstraint( &constraint ); constraint.nextIsActive = false; - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); - TS_ASSERT( !smtCore.needToSplit() ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); // Check that no split was performed @@ -383,7 +397,7 @@ class SmtCoreTestSuite : public CxxTest::TestSuite void test_all_splits_so_far() { - SmtCore smtCore( engine ); + SearchTreeHandler searchTreeHandler( engine ); MockConstraint constraint; @@ -410,13 +424,13 @@ class SmtCoreTestSuite : public CxxTest::TestSuite for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &constraint ); + searchTreeHandler.reportViolatedConstraint( &constraint ); constraint.nextIsActive = true; - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); - TS_ASSERT( !smtCore.needToSplit() ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); // Register a valid split @@ -424,7 +438,7 @@ class SmtCoreTestSuite : public CxxTest::TestSuite PiecewiseLinearCaseSplit split3; Tightening bound5( 14, 2.3, Tightening::LB ); - TS_ASSERT_THROWS_NOTHING( smtCore.recordImpliedValidSplit( split3 ) ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.recordImpliedValidSplit( split3 ) ); // Do another real split @@ -445,17 +459,17 @@ class SmtCoreTestSuite : public CxxTest::TestSuite for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &constraint2 ); + searchTreeHandler.reportViolatedConstraint( &constraint2 ); constraint2.nextIsActive = true; - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); - TS_ASSERT( !smtCore.needToSplit() ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); // Check that everything is received in the correct order List allSplitsSoFar; - TS_ASSERT_THROWS_NOTHING( smtCore.allSplitsSoFar( allSplitsSoFar ) ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.allSplitsSoFar( allSplitsSoFar ) ); TS_ASSERT_EQUALS( allSplitsSoFar.size(), 3U ); @@ -469,7 +483,7 @@ class SmtCoreTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( *it, split4 ); } - void test_store_smt_state() + void test_store_search_tree_state() { // ReLU(x0, x1) // ReLU(x2, x3) @@ -484,89 +498,89 @@ class SmtCoreTestSuite : public CxxTest::TestSuite relu1.transformToUseAuxVariables( inputQuery ); relu2.transformToUseAuxVariables( inputQuery ); - SmtCore smtCore( engine ); + SearchTreeHandler searchTreeHandler( engine ); PiecewiseLinearCaseSplit split1; Tightening bound1( 1, 0.5, Tightening::LB ); split1.storeBoundTightening( bound1 ); - TS_ASSERT_THROWS_NOTHING( smtCore.recordImpliedValidSplit( split1 ) ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.recordImpliedValidSplit( split1 ) ); for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &relu1 ); + searchTreeHandler.reportViolatedConstraint( &relu1 ); - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); - TS_ASSERT( !smtCore.needToSplit() ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); PiecewiseLinearCaseSplit split2; Tightening bound2( 3, 2.3, Tightening::UB ); split2.storeBoundTightening( bound2 ); - TS_ASSERT_THROWS_NOTHING( smtCore.recordImpliedValidSplit( split2 ) ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.recordImpliedValidSplit( split2 ) ); for ( unsigned i = 0; i < (unsigned)Options::get()->getInt( Options::CONSTRAINT_VIOLATION_THRESHOLD ); ++i ) - smtCore.reportViolatedConstraint( &relu2 ); + searchTreeHandler.reportViolatedConstraint( &relu2 ); - TS_ASSERT( smtCore.needToSplit() ); - TS_ASSERT_THROWS_NOTHING( smtCore.performSplit() ); - TS_ASSERT( !smtCore.needToSplit() ); + TS_ASSERT( searchTreeHandler.needToSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.performSplit() ); + TS_ASSERT( !searchTreeHandler.needToSplit() ); - SmtState smtState; - smtCore.storeSmtState( smtState ); - TS_ASSERT( smtState._impliedValidSplitsAtRoot.size() == 1 ); - TS_ASSERT( *smtState._impliedValidSplitsAtRoot.begin() == split1 ); + SearchTreeState searchTreeState; + searchTreeHandler.storeSearchTreeState( searchTreeState ); + TS_ASSERT( searchTreeState._impliedValidSplitsAtRoot.size() == 1 ); + TS_ASSERT( *searchTreeState._impliedValidSplitsAtRoot.begin() == split1 ); - TS_ASSERT( smtState._stack.size() == 2 ); + TS_ASSERT( searchTreeState._stack.size() == 2 ); // Examine the first stackEntry - SmtStackEntry *stackEntry = *( smtState._stack.begin() ); + SearchTreeStackEntry *stackEntry = *( searchTreeState._stack.begin() ); TS_ASSERT( stackEntry->_activeSplit == *( relu1.getCaseSplits().begin() ) ); TS_ASSERT( *( stackEntry->_alternativeSplits.begin() ) == *( ++relu1.getCaseSplits().begin() ) ); TS_ASSERT( stackEntry->_impliedValidSplits.size() == 1 ); TS_ASSERT( *( stackEntry->_impliedValidSplits.begin() ) == split2 ); // Examine the second stackEntry - stackEntry = *( ++smtState._stack.begin() ); + stackEntry = *( ++searchTreeState._stack.begin() ); TS_ASSERT( stackEntry->_activeSplit == *( relu2.getCaseSplits().begin() ) ); TS_ASSERT( *( stackEntry->_alternativeSplits.begin() ) == *( ++relu2.getCaseSplits().begin() ) ); TS_ASSERT( stackEntry->_impliedValidSplits.size() == 0 ); - clearSmtState( smtState ); + clearSearchTreeState( searchTreeState ); - TS_ASSERT_THROWS_NOTHING( smtCore.popSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.popSplit() ); - smtCore.storeSmtState( smtState ); - TS_ASSERT( smtState._impliedValidSplitsAtRoot.size() == 1 ); - TS_ASSERT( *smtState._impliedValidSplitsAtRoot.begin() == split1 ); + searchTreeHandler.storeSearchTreeState( searchTreeState ); + TS_ASSERT( searchTreeState._impliedValidSplitsAtRoot.size() == 1 ); + TS_ASSERT( *searchTreeState._impliedValidSplitsAtRoot.begin() == split1 ); - TS_ASSERT( smtState._stack.size() == 2 ); + TS_ASSERT( searchTreeState._stack.size() == 2 ); // Examine the first stackEntry - stackEntry = *( smtState._stack.begin() ); + stackEntry = *( searchTreeState._stack.begin() ); TS_ASSERT( stackEntry->_activeSplit == *( relu1.getCaseSplits().begin() ) ); TS_ASSERT( *( stackEntry->_alternativeSplits.begin() ) == *( ++relu1.getCaseSplits().begin() ) ); TS_ASSERT( stackEntry->_impliedValidSplits.size() == 1 ); TS_ASSERT( *( stackEntry->_impliedValidSplits.begin() ) == split2 ); // Examine the second stackEntry - stackEntry = *( ++smtState._stack.begin() ); + stackEntry = *( ++searchTreeState._stack.begin() ); TS_ASSERT( stackEntry->_activeSplit == *( ++relu2.getCaseSplits().begin() ) ); TS_ASSERT( stackEntry->_alternativeSplits.empty() ); TS_ASSERT( stackEntry->_impliedValidSplits.size() == 0 ); - clearSmtState( smtState ); + clearSearchTreeState( searchTreeState ); - TS_ASSERT_THROWS_NOTHING( smtCore.popSplit() ); + TS_ASSERT_THROWS_NOTHING( searchTreeHandler.popSplit() ); } - void clearSmtState( SmtState &smtState ) + void clearSearchTreeState( SearchTreeState &searchTreeState ) { - for ( const auto &stackEntry : smtState._stack ) + for ( const auto &stackEntry : searchTreeState._stack ) delete stackEntry; - smtState._stack = List(); - smtState._impliedValidSplitsAtRoot = List(); + searchTreeState._stack = List(); + searchTreeState._impliedValidSplitsAtRoot = List(); } void test_todo() diff --git a/src/engine/tests/Test_SignConstraint.h b/src/engine/tests/Test_SignConstraint.h index a284285f00..faabc3d6d6 100644 --- a/src/engine/tests/Test_SignConstraint.h +++ b/src/engine/tests/Test_SignConstraint.h @@ -896,6 +896,7 @@ class SignConstraintTestSuite : public CxxTest::TestSuite TS_ASSERT_EQUALS( tightenings.size(), 1U ); TS_ASSERT( tightenings.exists( Tightening( f, 1, Tightening::LB ) ) ); tightenings.clear(); + sign.setPhaseStatus( PHASE_NOT_FIXED ); sign.notifyUpperBound( f, -0.5 ); boundManager.getTightenings( tightenings ); diff --git a/src/input_parsers/AcasNnet.cpp b/src/input_parsers/AcasNnet.cpp index ac5e104720..84319ff328 100644 --- a/src/input_parsers/AcasNnet.cpp +++ b/src/input_parsers/AcasNnet.cpp @@ -45,63 +45,63 @@ AcasNnet *load_network( const char *filename ) line = fgets( buffer, bufferSize, fstream ); while ( strstr( line, "//" ) != NULL ) line = fgets( buffer, bufferSize, fstream ); // skip header lines - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); nnet->numLayers = atoi( record ); - nnet->inputSize = atoi( strtok( NULL, ",\n" ) ); - nnet->outputSize = atoi( strtok( NULL, ",\n" ) ); - nnet->maxLayerSize = atoi( strtok( NULL, ",\n" ) ); + nnet->inputSize = atoi( strtok( NULL, ",\r\n" ) ); + nnet->outputSize = atoi( strtok( NULL, ",\r\n" ) ); + nnet->maxLayerSize = atoi( strtok( NULL, ",\r\n" ) ); // Allocate space for and read values of the array members of the network nnet->layerSizes = new int[( ( ( nnet->numLayers ) + 1 ) )]; line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); for ( i = 0; i < ( ( nnet->numLayers ) + 1 ); i++ ) { nnet->layerSizes[i] = atoi( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } // Load the symmetric paramter line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); nnet->symmetric = atoi( record ); // Load Min and Max values of inputs nnet->mins = new double[( nnet->inputSize )]; line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); for ( i = 0; i < ( nnet->inputSize ); i++ ) { nnet->mins[i] = atof( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } nnet->maxes = new double[( nnet->inputSize )]; line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); for ( i = 0; i < ( nnet->inputSize ); i++ ) { nnet->maxes[i] = atof( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } // Load Mean and Range of inputs nnet->means = new double[( ( ( nnet->inputSize ) + 1 ) )]; line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); for ( i = 0; i < ( ( nnet->inputSize ) + 1 ); i++ ) { nnet->means[i] = atof( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } nnet->ranges = new double[( ( ( nnet->inputSize ) + 1 ) )]; line = fgets( buffer, bufferSize, fstream ); - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); for ( i = 0; i < ( ( nnet->inputSize ) + 1 ); i++ ) { nnet->ranges[i] = atof( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } // Allocate space for matrix of Neural Network @@ -151,11 +151,11 @@ AcasNnet *load_network( const char *filename ) i = 0; j = 0; } - record = strtok( line, ",\n" ); + record = strtok( line, ",\r\n" ); while ( record != NULL ) { nnet->matrix[layer][param][i][j++] = atof( record ); - record = strtok( NULL, ",\n" ); + record = strtok( NULL, ",\r\n" ); } j = 0; i++; diff --git a/src/input_parsers/MpsParser.cpp b/src/input_parsers/MpsParser.cpp index 45c76ca187..b2f7d8925c 100644 --- a/src/input_parsers/MpsParser.cpp +++ b/src/input_parsers/MpsParser.cpp @@ -101,7 +101,7 @@ void MpsParser::parse( const String &path ) void MpsParser::parseRow( const String &line ) { - List tokens = line.tokenize( "\t\n " ); + List tokens = line.tokenize( "\t\r\n " ); if ( tokens.size() != 2 ) throw InputParserError( InputParserError::UNEXPECTED_INPUT, line.ascii() ); @@ -138,7 +138,7 @@ void MpsParser::parseRow( const String &line ) void MpsParser::parseColumn( const String &line ) { - List tokens = line.tokenize( "\t\n " ); + List tokens = line.tokenize( "\t\r\n " ); // Need an odd number of tokens: row name + pairs if ( tokens.size() % 2 == 0 ) @@ -214,7 +214,7 @@ void MpsParser::parseRhs( const String &line ) void MpsParser::parseBounds( const String &line ) { - List tokens = line.tokenize( "\t\n " ); + List tokens = line.tokenize( "\t\r\n " ); if ( tokens.size() != 4 ) throw InputParserError( InputParserError::UNEXPECTED_INPUT, line.ascii() ); diff --git a/src/input_parsers/PropertyParser.cpp b/src/input_parsers/PropertyParser.cpp index ca254409bc..4812a7290c 100644 --- a/src/input_parsers/PropertyParser.cpp +++ b/src/input_parsers/PropertyParser.cpp @@ -193,6 +193,7 @@ void PropertyParser::processSingleLine( const String &line, IQuery &inputQuery ) // Normal case: add as an equation Equation equation( type ); equation.setScalar( scalar ); + bool isOuputConstraint = false; while ( it != tokens.rend() ) { @@ -225,6 +226,7 @@ void PropertyParser::processSingleLine( const String &line, IQuery &inputQuery ) { ASSERT( justIndex < inputQuery.getNumOutputVariables() ); variable = inputQuery.outputVariableByIndex( justIndex ); + isOuputConstraint = true; } String coefficientString = *subTokens.begin(); @@ -241,6 +243,8 @@ void PropertyParser::processSingleLine( const String &line, IQuery &inputQuery ) } inputQuery.addEquation( equation ); + if ( isOuputConstraint ) + inputQuery.addOutputConstraint( equation ); } } diff --git a/src/input_parsers/VnnLibParser.cpp b/src/input_parsers/VnnLibParser.cpp index c7505a53e2..b95af9209d 100644 --- a/src/input_parsers/VnnLibParser.cpp +++ b/src/input_parsers/VnnLibParser.cpp @@ -236,11 +236,18 @@ int VnnLibParser::parseAssert( int index, const Vector &tokens, IQuery & else { inputQuery.addEquation( eq ); + for ( const Equation::Addend &addend : eq._addends ) + if ( inputQuery.getOutputVariables().exists( addend._variable ) ) + { + inputQuery.addOutputConstraint( eq ); + break; + } } } } else if ( op == "or" ) { + inputQuery.markQueryWithDisjunction(); List disjunctList; ++index; while ( tokens[index] != ")" ) diff --git a/src/input_parsers/tests/Test_VnnLibParser.h b/src/input_parsers/tests/Test_VnnLibParser.h index 06a6136a11..73c38eec71 100644 --- a/src/input_parsers/tests/Test_VnnLibParser.h +++ b/src/input_parsers/tests/Test_VnnLibParser.h @@ -184,7 +184,14 @@ class VnnLibParserTestSuite : public CxxTest::TestSuite engine.setVerbosity( 0 ); TS_ASSERT_THROWS_NOTHING( engine.processInputQuery( *_query ) ); TS_ASSERT_THROWS_NOTHING( engine.solve() ); - TS_ASSERT( engine.getExitCode() == Engine::ExitCode::SAT ) + TS_ASSERT( engine.getExitCode() == ExitCode::SAT ) + + // Engine engineCDCL; + // engineCDCL.configureForCDCL(); + // engineCDCL.setVerbosity( 0 ); + // TS_ASSERT_THROWS_NOTHING( engineCDCL.processInputQuery( *_query ) ); + // TS_ASSERT_THROWS_NOTHING( engineCDCL.solveWithCDCL() ); + // TS_ASSERT( engineCDCL.getExitCode() == ExitCode::SAT ) } void test_unsat_vnncomp() @@ -257,7 +264,14 @@ class VnnLibParserTestSuite : public CxxTest::TestSuite engine.setVerbosity( 0 ); TS_ASSERT_THROWS_NOTHING( engine.processInputQuery( *_query ) ); TS_ASSERT_THROWS_NOTHING( engine.solve() ); - TS_ASSERT( engine.getExitCode() == Engine::ExitCode::UNSAT ) + TS_ASSERT( engine.getExitCode() == ExitCode::UNSAT ) + + // Engine engineCDCL; + // engineCDCL.configureForCDCL(); + // engineCDCL.setVerbosity( 0 ); + // TS_ASSERT_THROWS_NOTHING( engineCDCL.processInputQuery( *_query ) ); + // TS_ASSERT_THROWS_NOTHING( engineCDCL.solveWithCDCL() ); + // TS_ASSERT( engineCDCL.getExitCode() == ExitCode::UNSAT ) } void test_add_const() @@ -406,4 +420,4 @@ class VnnLibParserTestSuite : public CxxTest::TestSuite TS_ASSERT( upperBounds.exists( inputVar ) && upperBounds.get( inputVar ) == 1 ) TS_ASSERT( lowerBounds.exists( outputVar ) && lowerBounds.get( outputVar ) == 0 ) } -}; +}; \ No newline at end of file diff --git a/src/nlr/DeepPolyAnalysis.cpp b/src/nlr/DeepPolyAnalysis.cpp index 6a7ddb09db..c87123dc46 100644 --- a/src/nlr/DeepPolyAnalysis.cpp +++ b/src/nlr/DeepPolyAnalysis.cpp @@ -68,10 +68,10 @@ DeepPolyAnalysis::DeepPolyAnalysis( LayerOwner *layerOwner ) */ unsigned index = pair.first; Layer *layer = pair.second; - log( Stringf( "Creating deeppoly element for layer %u...", index ) ); + // log( Stringf( "Creating deeppoly element for layer %u...", index ) ); DeepPolyElement *deepPolyElement = createDeepPolyElement( layer ); _deepPolyElements[index] = deepPolyElement; - log( Stringf( "Creating deeppoly element for layer %u - done", index ) ); + // log( Stringf( "Creating deeppoly element for layer %u - done", index ) ); } } @@ -139,7 +139,7 @@ void DeepPolyAnalysis::run() Layer *layer = pair.second; ASSERT( _deepPolyElements.exists( index ) ); - log( Stringf( "Running deeppoly analysis for layer %u...", index ) ); + // log( Stringf( "Running deeppoly analysis for layer %u...", index ) ); DeepPolyElement *deepPolyElement = _deepPolyElements[index]; deepPolyElement->execute( _deepPolyElements ); @@ -151,29 +151,35 @@ void DeepPolyAnalysis::run() double lb = deepPolyElement->getLowerBound( j ); if ( layer->getLb( j ) < lb ) { - log( Stringf( "Neuron %u_%u lower-bound updated from %f to %f", - index, - j, - layer->getLb( j ), - lb ) ); + // log( Stringf( "Neuron %u_%u lower-bound updated from %f to %f", + // index, + // j, + // layer->getLb( j ), + // lb ) ); layer->setLb( j, lb ); _layerOwner->receiveTighterBound( Tightening( layer->neuronToVariable( j ), lb, Tightening::LB ) ); + if ( index == _layerOwner->getNumberOfLayers() - 1 ) + _layerOwner->receiveOutputTighterBound( + Tightening( layer->neuronToVariable( j ), lb, Tightening::LB ) ); } double ub = deepPolyElement->getUpperBound( j ); if ( layer->getUb( j ) > ub ) { - log( Stringf( "Neuron %u_%u upper-bound updated from %f to %f", - index, - j, - layer->getUb( j ), - ub ) ); + // log( Stringf( "Neuron %u_%u upper-bound updated from %f to %f", + // index, + // j, + // layer->getUb( j ), + // ub ) ); layer->setUb( j, ub ); _layerOwner->receiveTighterBound( Tightening( layer->neuronToVariable( j ), ub, Tightening::UB ) ); + if ( index == _layerOwner->getNumberOfLayers() - 1 ) + _layerOwner->receiveOutputTighterBound( + Tightening( layer->neuronToVariable( j ), ub, Tightening::UB ) ); } } - log( Stringf( "Running deeppoly analysis for layer %u - done", index ) ); + // log( Stringf( "Running deeppoly analysis for layer %u - done", index ) ); } } diff --git a/src/nlr/DeepPolyInputElement.cpp b/src/nlr/DeepPolyInputElement.cpp index a6aeb245ee..e7b89685a4 100644 --- a/src/nlr/DeepPolyInputElement.cpp +++ b/src/nlr/DeepPolyInputElement.cpp @@ -35,7 +35,7 @@ DeepPolyInputElement::~DeepPolyInputElement() void DeepPolyInputElement::execute( const Map & ) { - log( "Executing..." ); + // log( "Executing..." ); if ( hasPredecessor() ) { throw NLRError( NLRError::INPUT_LAYER_NOT_THE_FIRST_LAYER ); @@ -44,7 +44,7 @@ void DeepPolyInputElement::execute( const Map & ) freeMemoryIfNeeded(); allocateMemory(); getConcreteBounds(); - log( "Executing - done" ); + // log( "Executing - done" ); } void DeepPolyInputElement::symbolicBoundInTermsOfPredecessor( const double *, diff --git a/src/nlr/DeepPolyReLUElement.cpp b/src/nlr/DeepPolyReLUElement.cpp index 678c1edc5c..4aeb9800cd 100644 --- a/src/nlr/DeepPolyReLUElement.cpp +++ b/src/nlr/DeepPolyReLUElement.cpp @@ -33,7 +33,7 @@ DeepPolyReLUElement::~DeepPolyReLUElement() void DeepPolyReLUElement::execute( const Map &deepPolyElementsBefore ) { - log( "Executing..." ); + // log( "Executing..." ); ASSERT( hasPredecessor() ); allocateMemory(); @@ -106,15 +106,15 @@ void DeepPolyReLUElement::execute( const Map &deepP _lb[i] = 0; } } - log( Stringf( "Neuron%u LB: %f b + %f, UB: %f b + %f", - i, - _symbolicLb[i], - _symbolicLowerBias[i], - _symbolicUb[i], - _symbolicUpperBias[i] ) ); - log( Stringf( "Neuron%u LB: %f, UB: %f", i, _lb[i], _ub[i] ) ); + // log( Stringf( "Neuron%u LB: %f b + %f, UB: %f b + %f", + // i, + // _symbolicLb[i], + // _symbolicLowerBias[i], + // _symbolicUb[i], + // _symbolicUpperBias[i] ) ); + // log( Stringf( "Neuron%u LB: %f, UB: %f", i, _lb[i], _ub[i] ) ); } - log( "Executing - done" ); + // log( "Executing - done" ); } void DeepPolyReLUElement::symbolicBoundInTermsOfPredecessor( const double *symbolicLb, @@ -126,8 +126,8 @@ void DeepPolyReLUElement::symbolicBoundInTermsOfPredecessor( const double *symbo unsigned targetLayerSize, DeepPolyElement *predecessor ) { - log( Stringf( "Computing symbolic bounds with respect to layer %u...", - predecessor->getLayerIndex() ) ); + // log( Stringf( "Computing symbolic bounds with respect to layer %u...", + // predecessor->getLayerIndex() ) ); /* We have the symbolic bound of the target layer in terms of the diff --git a/src/nlr/DeepPolyWeightedSumElement.cpp b/src/nlr/DeepPolyWeightedSumElement.cpp index 31a5c297b0..bd92472155 100644 --- a/src/nlr/DeepPolyWeightedSumElement.cpp +++ b/src/nlr/DeepPolyWeightedSumElement.cpp @@ -38,19 +38,19 @@ DeepPolyWeightedSumElement::~DeepPolyWeightedSumElement() void DeepPolyWeightedSumElement::execute( const Map &deepPolyElementsBefore ) { - log( "Executing..." ); + // log( "Executing..." ); ASSERT( hasPredecessor() ); allocateMemory(); getConcreteBounds(); // Compute bounds with back-substitution computeBoundWithBackSubstitution( deepPolyElementsBefore ); - log( "Executing - done" ); + // log( "Executing - done" ); } void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( const Map &deepPolyElementsBefore ) { - log( "Computing bounds with back substitution..." ); + // log( "Computing bounds with back substitution..." ); // Start with the symbolic upper-/lower- bounds of this layer with // respect to its immediate predecessor. @@ -71,7 +71,7 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( predecessorIndex = pair.first; if ( counter < numPredecessors - 1 ) { - log( Stringf( "Adding residual from layer %u...", predecessorIndex ) ); + // log( Stringf( "Adding residual from layer %u...", predecessorIndex ) ); allocateMemoryForResidualsIfNeeded( predecessorIndex, pair.second ); const double *weights = _layer->getWeights( predecessorIndex ); memcpy( @@ -79,11 +79,12 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( memcpy( _residualUb[predecessorIndex], weights, _size * pair.second * sizeof( double ) ); ++counter; - log( Stringf( "Adding residual from layer %u - done", pair.first ) ); + // log( Stringf( "Adding residual from layer %u - done", pair.first ) ); } } - log( Stringf( "Computing symbolic bounds with respect to layer %u...", predecessorIndex ) ); + // log( Stringf( "Computing symbolic bounds with respect to layer %u...", predecessorIndex ) + // ); DeepPolyElement *precedingElement = deepPolyElementsBefore[predecessorIndex]; unsigned sourceLayerSize = precedingElement->getSize(); @@ -102,7 +103,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( _workSymbolicUpperBias, currentElement, deepPolyElementsBefore ); - log( Stringf( "Computing symbolic bounds with respect to layer %u - done", predecessorIndex ) ); + // log( Stringf( "Computing symbolic bounds with respect to layer %u - done", + // predecessorIndex ) ); while ( currentElement->hasPredecessor() || !_residualLayerIndices.empty() ) { @@ -126,7 +128,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( if ( counter < numPredecessors - 1 ) { unsigned predecessorIndex = pair.first; - log( Stringf( "Adding residual from layer %u...", predecessorIndex ) ); + // log( Stringf( "Adding residual from layer %u...", + // predecessorIndex ) ); allocateMemoryForResidualsIfNeeded( predecessorIndex, pair.second ); // Do we need to add bias here? currentElement->symbolicBoundInTermsOfPredecessor( @@ -139,7 +142,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( _size, precedingElement ); ++counter; - log( Stringf( "Adding residual from layer %u - done", pair.first ) ); + // log( Stringf( "Adding residual from layer %u - done", + // pair.first ) ); } } @@ -161,7 +165,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( // in the residualWeights, and remove it from the residual source layers. if ( _residualLayerIndices.exists( predecessorIndex ) ) { - log( Stringf( "merge residual from layer %u...", predecessorIndex ) ); + // log( Stringf( "merge residual from layer %u...", predecessorIndex + // ) ); // Add weights of this residual layer for ( unsigned i = 0; i < _size * precedingElement->getSize(); ++i ) { @@ -173,7 +178,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( _residualLb[predecessorIndex], _size * precedingElement->getSize(), 0 ); std::fill_n( _residualUb[predecessorIndex], _size * precedingElement->getSize(), 0 ); - log( Stringf( "merge residual from layer %u - done", predecessorIndex ) ); + // log( Stringf( "merge residual from layer %u - done", + // predecessorIndex ) ); } double *temp = _work1SymbolicLb; @@ -201,7 +207,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( // Add the current element in the residual element unsigned newCurrentIndex = *_residualLayerIndices.begin(); unsigned residualIndex = currentElement->getLayerIndex(); - log( Stringf( "Adding layer %u to the residual layer\n", residualIndex ).ascii() ); + // log( Stringf( "Adding layer %u to the residual layer\n", residualIndex + // ).ascii() ); ASSERT( residualIndex == 0 ); allocateMemoryForResidualsIfNeeded( residualIndex, currentElement->getSize() ); @@ -214,7 +221,8 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( // Make the first residual element the current element and get ready for the next // back-substitution - log( Stringf( "Making layer %u the current layer\n", newCurrentIndex ).ascii() ); + // log( Stringf( "Making layer %u the current layer\n", newCurrentIndex + // ).ascii() ); currentElement = deepPolyElementsBefore[newCurrentIndex]; @@ -231,7 +239,7 @@ void DeepPolyWeightedSumElement::computeBoundWithBackSubstitution( } } ASSERT( _residualLayerIndices.empty() ); - log( "Computing bounds with back substitution - done" ); + // log( "Computing bounds with back substitution - done" ); } void DeepPolyWeightedSumElement::concretizeSymbolicBound( @@ -242,7 +250,7 @@ void DeepPolyWeightedSumElement::concretizeSymbolicBound( DeepPolyElement *sourceElement, const Map &deepPolyElementsBefore ) { - log( "Concretizing bound..." ); + // log( "Concretizing bound..." ); std::fill_n( _workLb, _size, 0 ); std::fill_n( _workUb, _size, 0 ); @@ -264,11 +272,11 @@ void DeepPolyWeightedSumElement::concretizeSymbolicBound( _lb[i] = _workLb[i]; if ( _ub[i] > _workUb[i] ) _ub[i] = _workUb[i]; - log( Stringf( "Neuron%u working LB: %f, UB: %f", i, _workLb[i], _workUb[i] ) ); - log( Stringf( "Neuron%u LB: %f, UB: %f", i, _lb[i], _ub[i] ) ); + // log( Stringf( "Neuron%u working LB: %f, UB: %f", i, _workLb[i], _workUb[i] ) ); + // log( Stringf( "Neuron%u LB: %f, UB: %f", i, _lb[i], _ub[i] ) ); } - log( "Concretizing bound - done" ); + // log( "Concretizing bound - done" ); } void DeepPolyWeightedSumElement::concretizeSymbolicBoundForSourceLayer( @@ -331,11 +339,11 @@ void DeepPolyWeightedSumElement::concretizeSymbolicBoundForSourceLayer( double sourceUb = sourceElement->getUpperBoundFromLayer( i ) + GlobalConfiguration::SYMBOLIC_TIGHTENING_ROUNDING_CONSTANT; - log( Stringf( "Bounds of neuron%u_%u: [%f, %f]", - sourceElement->getLayerIndex(), - i, - sourceLb, - sourceUb ) ); + // log( Stringf( "Bounds of neuron%u_%u: [%f, %f]", + // sourceElement->getLayerIndex(), + // i, + // sourceLb, + // sourceUb ) ); for ( unsigned j = 0; j < _size; ++j ) { @@ -384,7 +392,8 @@ void DeepPolyWeightedSumElement::symbolicBoundInTermsOfPredecessor( DeepPolyElement *predecessor ) { unsigned predecessorIndex = predecessor->getLayerIndex(); - log( Stringf( "Computing symbolic bounds with respect to layer %u...", predecessorIndex ) ); + // log( Stringf( "Computing symbolic bounds with respect to layer %u...", predecessorIndex ) + // ); unsigned predecessorSize = predecessor->getSize(); double *weights = _layer->getWeights( predecessorIndex ); @@ -411,7 +420,8 @@ void DeepPolyWeightedSumElement::symbolicBoundInTermsOfPredecessor( matrixMultiplication( biases, symbolicLb, symbolicLowerBias, 1, _size, targetLayerSize ); if ( symbolicUpperBias ) matrixMultiplication( biases, symbolicUb, symbolicUpperBias, 1, _size, targetLayerSize ); - log( Stringf( "Computing symbolic bounds with respect to layer %u - done", predecessorIndex ) ); + // log( Stringf( "Computing symbolic bounds with respect to layer %u - done", + // predecessorIndex ) ); } void DeepPolyWeightedSumElement::allocateMemoryForResidualsIfNeeded( unsigned residualLayerIndex, diff --git a/src/nlr/Layer.cpp b/src/nlr/Layer.cpp index 08e6900538..b0dfb1fd95 100644 --- a/src/nlr/Layer.cpp +++ b/src/nlr/Layer.cpp @@ -4249,4 +4249,16 @@ void Layer::dumpBounds() const printf( "\n" ); } +void Layer::setBounds( unsigned int neuron, double lower, double upper ) +{ + ASSERT( neuron < _size ); + _lb[neuron] = lower; + _ub[neuron] = upper; +} + +bool Layer::hasMappingFromVariable( unsigned int variable ) const +{ + return _variableToNeuron.exists( variable ); +} + } // namespace NLR diff --git a/src/nlr/Layer.h b/src/nlr/Layer.h index 900276eda3..1efa9b2a71 100644 --- a/src/nlr/Layer.h +++ b/src/nlr/Layer.h @@ -163,6 +163,9 @@ class Layer bool compareWeights( const Map &map, const Map &mapOfOtherLayer ) const; + void setBounds( unsigned neuron, double lower, double upper ); + bool hasMappingFromVariable( unsigned variable ) const; + private: unsigned _layerIndex; Type _type; diff --git a/src/nlr/LayerOwner.h b/src/nlr/LayerOwner.h index 180a4fdebd..b35888f1b4 100644 --- a/src/nlr/LayerOwner.h +++ b/src/nlr/LayerOwner.h @@ -35,6 +35,7 @@ class LayerOwner virtual const ITableau *getTableau() const = 0; virtual unsigned getNumberOfLayers() const = 0; virtual void receiveTighterBound( Tightening tightening ) = 0; + virtual void receiveOutputTighterBound( Tightening tightening ) = 0; }; } // namespace NLR diff --git a/src/nlr/NetworkLevelReasoner.cpp b/src/nlr/NetworkLevelReasoner.cpp index 8390a0190b..85105d0b18 100644 --- a/src/nlr/NetworkLevelReasoner.cpp +++ b/src/nlr/NetworkLevelReasoner.cpp @@ -200,12 +200,18 @@ void NetworkLevelReasoner::clearConstraintTightenings() void NetworkLevelReasoner::symbolicBoundPropagation() { + _outputBounds.clear(); + _boundTightenings.clear(); + for ( unsigned i = 0; i < _layerIndexToLayer.size(); ++i ) _layerIndexToLayer[i]->computeSymbolicBounds(); } void NetworkLevelReasoner::deepPolyPropagation() { + _outputBounds.clear(); + _boundTightenings.clear(); + if ( _deepPolyAnalysis == nullptr ) _deepPolyAnalysis = std::unique_ptr( new DeepPolyAnalysis( this ) ); _deepPolyAnalysis->run(); @@ -871,4 +877,38 @@ const Map &NetworkLevelReasoner::getLayerIndexToLayer() const return _layerIndexToLayer; } +void NetworkLevelReasoner::setBounds( unsigned layer, + unsigned int neuron, + double lower, + double upper ) +{ + ASSERT( layer < _layerIndexToLayer.size() ); + _layerIndexToLayer[layer]->setBounds( neuron, lower, upper ); +} + +NeuronIndex NetworkLevelReasoner::variableToNeuron( unsigned int variable ) const +{ + for ( const auto pair : _layerIndexToLayer ) + { + unsigned layerIdx = pair.first; + const Layer *layer = pair.second; + if ( layer->hasMappingFromVariable( variable ) ) + return { layerIdx, layer->variableToNeuron( variable ) }; + } + + return { 0, 0 }; +} + +void NetworkLevelReasoner::receiveOutputTighterBound( Tightening tightening ) +{ + _outputBounds[Pair( tightening._variable, tightening._type )] = + tightening._value; +} + +void NetworkLevelReasoner::getOutputBounds( + Map, double> &outputBounds ) +{ + outputBounds = _outputBounds; +} + } // namespace NLR diff --git a/src/nlr/NetworkLevelReasoner.h b/src/nlr/NetworkLevelReasoner.h index 2660795be6..8255c171bc 100644 --- a/src/nlr/NetworkLevelReasoner.h +++ b/src/nlr/NetworkLevelReasoner.h @@ -61,8 +61,8 @@ class NetworkLevelReasoner : public LayerOwner unsigned targetLeyer, unsigned targetNeuron ); - unsigned getNumberOfLayers() const; - const Layer *getLayer( unsigned index ) const; + unsigned getNumberOfLayers() const override; + const Layer *getLayer( unsigned index ) const override; Layer *getLayer( unsigned index ); /* @@ -117,7 +117,7 @@ class NetworkLevelReasoner : public LayerOwner */ void setTableau( const ITableau *tableau ); - const ITableau *getTableau() const; + const ITableau *getTableau() const override; void obtainCurrentBounds( const Query &inputQuery ); void obtainCurrentBounds(); @@ -130,8 +130,10 @@ class NetworkLevelReasoner : public LayerOwner void MILPTighteningForOneLayer( unsigned targetIndex ); void iterativePropagation(); - void receiveTighterBound( Tightening tightening ); + void receiveTighterBound( Tightening tightening ) override; + void receiveOutputTighterBound( Tightening tightening ) override; void getConstraintTightenings( List &tightenings ); + void getOutputBounds( Map, double> &outputBounds ); void clearConstraintTightenings(); /* @@ -196,9 +198,13 @@ class NetworkLevelReasoner : public LayerOwner /* Get the size of the widest layer */ - unsigned getMaxLayerSize() const; + unsigned getMaxLayerSize() const override; - const Map &getLayerIndexToLayer() const; + const Map &getLayerIndexToLayer() const override; + + void setBounds( unsigned layer, unsigned int neuron, double lower, double upper ); + + NeuronIndex variableToNeuron( unsigned variable ) const; private: Map _layerIndexToLayer; @@ -206,7 +212,7 @@ class NetworkLevelReasoner : public LayerOwner // Tightenings discovered by the various layers List _boundTightenings; - + Map, double> _outputBounds; std::unique_ptr _deepPolyAnalysis; diff --git a/src/proofs/Checker.cpp b/src/proofs/Checker.cpp index c153de7feb..8e775f9e75 100644 --- a/src/proofs/Checker.cpp +++ b/src/proofs/Checker.cpp @@ -211,6 +211,11 @@ bool Checker::checkAllPLCExplanations( const UnsatCertificateNode *node, double // NOTE, this will change as PLCLemma will for ( const auto &plcLemma : node->getPLCLemmas() ) { + if ( !plcLemma ) + continue; + DEBUG( + ASSERT( !GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES || plcLemma->getToCheck() ) ); + PiecewiseLinearConstraint *matchedConstraint = NULL; Tightening::BoundType affectedVarBound = plcLemma->getAffectedVarBound(); double explainedBound = affectedVarBound == Tightening::UB ? FloatUtils::infinity() diff --git a/src/proofs/PlcLemma.cpp b/src/proofs/PlcLemma.cpp index c4edcca92d..d1fe334d29 100644 --- a/src/proofs/PlcLemma.cpp +++ b/src/proofs/PlcLemma.cpp @@ -14,19 +14,25 @@ #include "PlcLemma.h" +#include "Debug.h" + + PLCLemma::PLCLemma( const List &causingVars, unsigned affectedVar, double bound, Tightening::BoundType causingVarBound, Tightening::BoundType affectedVarBound, const Vector &explanations, - PiecewiseLinearFunctionType constraintType ) + PiecewiseLinearFunctionType constraintType, + double minTargetBound ) : _causingVars( causingVars ) , _affectedVar( affectedVar ) , _bound( bound ) , _causingVarBound( causingVarBound ) , _affectedVarBound( affectedVarBound ) , _constraintType( constraintType ) + , _toCheck( false ) + , _minTargetBound( minTargetBound ) { if ( explanations.empty() ) _explanations = List(); @@ -96,3 +102,19 @@ PiecewiseLinearFunctionType PLCLemma::getConstraintType() const { return _constraintType; } + +bool PLCLemma::getToCheck() const +{ + return _toCheck; +} + +double PLCLemma::getMinTargetBound() const +{ + return _minTargetBound; +} + + +void PLCLemma::setToCheck() +{ + _toCheck = true; +} diff --git a/src/proofs/PlcLemma.h b/src/proofs/PlcLemma.h index 909752489e..10884888bb 100644 --- a/src/proofs/PlcLemma.h +++ b/src/proofs/PlcLemma.h @@ -15,7 +15,6 @@ #ifndef __PlcExplanation_h__ #define __PlcExplanation_h__ -#include "PiecewiseLinearConstraint.h" #include "PiecewiseLinearFunctionType.h" #include "SparseUnsortedList.h" #include "Tightening.h" @@ -33,7 +32,8 @@ class PLCLemma Tightening::BoundType causingVarBound, Tightening::BoundType affectedVarBound, const Vector &explanation, - PiecewiseLinearFunctionType constraintType ); + PiecewiseLinearFunctionType constraintType, + double minTargetBound ); ~PLCLemma(); @@ -47,6 +47,10 @@ class PLCLemma Tightening::BoundType getAffectedVarBound() const; const List &getExplanations() const; PiecewiseLinearFunctionType getConstraintType() const; + bool getToCheck() const; + double getMinTargetBound() const; + + void setToCheck(); private: const List _causingVars; @@ -56,6 +60,8 @@ class PLCLemma Tightening::BoundType _affectedVarBound; List _explanations; PiecewiseLinearFunctionType _constraintType; + bool _toCheck; + double _minTargetBound; }; #endif //__PlcExplanation_h__ diff --git a/src/proofs/UnsatCertificateNode.cpp b/src/proofs/UnsatCertificateNode.cpp index 2d1c094509..f42c7f3263 100644 --- a/src/proofs/UnsatCertificateNode.cpp +++ b/src/proofs/UnsatCertificateNode.cpp @@ -158,3 +158,11 @@ bool UnsatCertificateNode::isValidNonLeaf() const { return !_contradiction && !_children.empty(); } + +void UnsatCertificateNode::deleteUnusedLemmas() +{ + if ( GlobalConfiguration::ANALYZE_PROOF_DEPENDENCIES ) + for ( auto &lemma : _PLCExplanations ) + if ( lemma && !lemma->getToCheck() ) + lemma = nullptr; +} diff --git a/src/proofs/UnsatCertificateNode.h b/src/proofs/UnsatCertificateNode.h index 42d5048086..9963f85f8c 100644 --- a/src/proofs/UnsatCertificateNode.h +++ b/src/proofs/UnsatCertificateNode.h @@ -128,6 +128,11 @@ class UnsatCertificateNode */ bool isValidNonLeaf() const; + /* + Deletes all unused lemmas + */ + void deleteUnusedLemmas(); + private: List _children; UnsatCertificateNode *_parent; diff --git a/src/proofs/UnsatCertificateUtils.cpp b/src/proofs/UnsatCertificateUtils.cpp index 88f4569451..f540859629 100644 --- a/src/proofs/UnsatCertificateUtils.cpp +++ b/src/proofs/UnsatCertificateUtils.cpp @@ -24,9 +24,6 @@ double UNSATCertificateUtils::computeBound( unsigned var, { ASSERT( var < numberOfVariables ); - double derivedBound = 0; - double temp; - if ( explanation.empty() ) return isUpper ? groundUpperBounds[var] : groundLowerBounds[var]; @@ -47,31 +44,17 @@ double UNSATCertificateUtils::computeBound( unsigned var, UNSATCertificateUtils::getExplanationRowCombination( var, explanation, explanationRowCombination, initialTableau, numberOfVariables ); - // Set the bound derived from the linear combination, using original bounds. - for ( unsigned i = 0; i < numberOfVariables; ++i ) - { - temp = explanationRowCombination[i]; - if ( !FloatUtils::isZero( temp ) ) - { - if ( isUpper ) - temp *= FloatUtils::isPositive( explanationRowCombination[i] ) - ? groundUpperBounds[i] - : groundLowerBounds[i]; - else - temp *= FloatUtils::isPositive( explanationRowCombination[i] ) - ? groundLowerBounds[i] - : groundUpperBounds[i]; - - if ( !FloatUtils::isZero( temp ) ) - derivedBound += temp; - } - } - - return derivedBound; + return isUpper ? computeCombinationUpperBound( explanationRowCombination, + groundUpperBounds, + groundLowerBounds, + numberOfVariables ) + : computeCombinationLowerBound( explanationRowCombination, + groundUpperBounds, + groundLowerBounds, + numberOfVariables ); } -void UNSATCertificateUtils::getExplanationRowCombination( unsigned var, - const SparseUnsortedList &explanation, +void UNSATCertificateUtils::getExplanationRowCombination( const SparseUnsortedList &explanation, Vector &explanationRowCombination, const SparseMatrix *initialTableau, unsigned numberOfVariables ) @@ -100,7 +83,16 @@ void UNSATCertificateUtils::getExplanationRowCombination( unsigned var, if ( FloatUtils::isZero( explanationRowCombination[i] ) ) explanationRowCombination[i] = 0; } +} +void UNSATCertificateUtils::getExplanationRowCombination( unsigned var, + const SparseUnsortedList &explanation, + Vector &explanationRowCombination, + const SparseMatrix *initialTableau, + unsigned numberOfVariables ) +{ + UNSATCertificateUtils::getExplanationRowCombination( + explanation, explanationRowCombination, initialTableau, numberOfVariables ); // Since: 0 = Sum (ci * xi) + c * var = Sum (ci * xi) + (c + 1) * var - var // We have: var = Sum (ci * xi) + (c + 1) * var ++explanationRowCombination[var]; @@ -131,17 +123,31 @@ double UNSATCertificateUtils::computeCombinationUpperBound( const SparseUnsorted } } + return computeCombinationUpperBound( + explanationRowCombination, groundUpperBounds, groundLowerBounds, numberOfVariables ); +} + +const Set UNSATCertificateUtils::getSupportedActivations() +{ + return { RELU, SIGN, ABSOLUTE_VALUE, MAX, DISJUNCTION, LEAKY_RELU }; +} + +double UNSATCertificateUtils::computeCombinationUpperBound( const Vector &combination, + const double *groundUpperBounds, + const double *groundLowerBounds, + unsigned numberOfVariables ) +{ double derivedBound = 0; double temp; // Set the bound derived from the linear combination, using original bounds. for ( unsigned i = 0; i < numberOfVariables; ++i ) { - temp = explanationRowCombination[i]; + temp = combination[i]; if ( !FloatUtils::isZero( temp ) ) { - temp *= FloatUtils::isPositive( explanationRowCombination[i] ) ? groundUpperBounds[i] - : groundLowerBounds[i]; + temp *= FloatUtils::isPositive( combination[i] ) ? groundUpperBounds[i] + : groundLowerBounds[i]; if ( !FloatUtils::isZero( temp ) ) derivedBound += temp; @@ -151,7 +157,27 @@ double UNSATCertificateUtils::computeCombinationUpperBound( const SparseUnsorted return derivedBound; } -const Set UNSATCertificateUtils::getSupportedActivations() +double UNSATCertificateUtils::computeCombinationLowerBound( const Vector &combination, + const double *groundUpperBounds, + const double *groundLowerBounds, + unsigned numberOfVariables ) { - return { RELU, SIGN, ABSOLUTE_VALUE, MAX, DISJUNCTION, LEAKY_RELU }; + double derivedBound = 0; + double temp; + + // Set the bound derived from the linear combination, using original bounds. + for ( unsigned i = 0; i < numberOfVariables; ++i ) + { + temp = combination[i]; + if ( !FloatUtils::isZero( temp ) ) + { + temp *= FloatUtils::isNegative( combination[i] ) ? groundUpperBounds[i] + : groundLowerBounds[i]; + + if ( !FloatUtils::isZero( temp ) ) + derivedBound += temp; + } + } + + return derivedBound; } diff --git a/src/proofs/UnsatCertificateUtils.h b/src/proofs/UnsatCertificateUtils.h index c599758c7c..39446e3638 100644 --- a/src/proofs/UnsatCertificateUtils.h +++ b/src/proofs/UnsatCertificateUtils.h @@ -36,6 +36,13 @@ class UNSATCertificateUtils const double *groundLowerBounds, unsigned numberOfVariables ); + /* + Given a tableau and a column vector, create a linear combination based on the vector + */ + static void getExplanationRowCombination( const SparseUnsortedList &explanation, + Vector &explanationRowCombination, + const SparseMatrix *initialTableau, + unsigned numberOfVariables ); /* Given a var, a tableau and a column vector, create a linear combination used to explain a bound @@ -52,6 +59,16 @@ class UNSATCertificateUtils const double *groundLowerBounds, unsigned numberOfVariables ); + static double computeCombinationUpperBound( const Vector &combination, + const double *groundUpperBounds, + const double *groundLowerBounds, + unsigned numberOfVariables ); + + static double computeCombinationLowerBound( const Vector &combination, + const double *groundUpperBounds, + const double *groundLowerBounds, + unsigned numberOfVariables ); + static const Set getSupportedActivations(); }; diff --git a/src/proofs/tests/Test_UnsatCertificateNode.h b/src/proofs/tests/Test_UnsatCertificateNode.h index 75a1d250a2..ca18bc8608 100644 --- a/src/proofs/tests/Test_UnsatCertificateNode.h +++ b/src/proofs/tests/Test_UnsatCertificateNode.h @@ -83,11 +83,11 @@ class UnsatCertificateNodeTestSuite : public CxxTest::TestSuite Vector emptyVec; auto explanation1 = std::shared_ptr( - new PLCLemma( { 1 }, 1, 0, Tightening::UB, Tightening::UB, emptyVec, RELU ) ); + new PLCLemma( { 1 }, 1, 0, Tightening::UB, Tightening::UB, emptyVec, RELU, 0 ) ); auto explanation2 = std::shared_ptr( - new PLCLemma( { 1 }, 1, -1, Tightening::UB, Tightening::UB, emptyVec, RELU ) ); + new PLCLemma( { 1 }, 1, -1, Tightening::UB, Tightening::UB, emptyVec, RELU, 0 ) ); auto explanation3 = std::shared_ptr( - new PLCLemma( { 1 }, 1, -4, Tightening::UB, Tightening::UB, emptyVec, RELU ) ); + new PLCLemma( { 1 }, 1, -4, Tightening::UB, Tightening::UB, emptyVec, RELU, 0 ) ); TS_ASSERT( root.getPLCLemmas().empty() ); diff --git a/src/query_loader/QueryLoader.cpp b/src/query_loader/QueryLoader.cpp index b73e40d7d5..c7c8d593ba 100644 --- a/src/query_loader/QueryLoader.cpp +++ b/src/query_loader/QueryLoader.cpp @@ -71,6 +71,7 @@ void QueryLoader::loadQuery( const String &fileName, IQuery &inputQuery ) inputQuery.markInputVariable( variable, inputIndex ); } + Set outputVariables; // Output Variables unsigned numOutputVars = atoi( input->readLine().trim().ascii() ); for ( unsigned i = 0; i < numOutputVars; ++i ) @@ -83,6 +84,7 @@ void QueryLoader::loadQuery( const String &fileName, IQuery &inputQuery ) unsigned variable = atoi( it->ascii() ); it++; inputQuery.markOutputVariable( variable, outputIndex ); + outputVariables.insert( variable ); } // Lower Bounds @@ -171,6 +173,7 @@ void QueryLoader::loadQuery( const String &fileName, IQuery &inputQuery ) Equation equation( type ); equation.setScalar( eqScalar ); + bool isOutputConstraint = false; while ( ++it != tokens.end() ) { @@ -182,9 +185,13 @@ void QueryLoader::loadQuery( const String &fileName, IQuery &inputQuery ) QL_LOG( Stringf( "\tVar_no: %i, Coeff: %f\n", varNo, coeff ).ascii() ); equation.addAddend( coeff, varNo ); + if ( outputVariables.exists( varNo ) ) + isOutputConstraint = true; } inputQuery.addEquation( equation ); + if ( isOutputConstraint ) + inputQuery.addOutputConstraint( equation ); } // Non-Linear(Piecewise and Nonlinear) Constraints @@ -235,6 +242,7 @@ void QueryLoader::loadQuery( const String &fileName, IQuery &inputQuery ) { inputQuery.addPiecewiseLinearConstraint( new DisjunctionConstraint( serializeConstraint ) ); + inputQuery.markQueryWithDisjunction(); } else if ( coType == "sigmoid" ) { diff --git a/src/system_tests/Test_incremental.h b/src/system_tests/Test_incremental.h index 61766037ad..2616ce9391 100644 --- a/src/system_tests/Test_incremental.h +++ b/src/system_tests/Test_incremental.h @@ -61,7 +61,7 @@ class IncrementalTestSuite : public CxxTest::TestSuite TS_ASSERT_THROWS_NOTHING( engine.processInputQuery( inputQuery ) ); TS_ASSERT_THROWS_NOTHING( engine.solve() ); - TS_ASSERT_EQUALS( engine.getExitCode(), IEngine::SAT ); + TS_ASSERT_EQUALS( engine.getExitCode(), ExitCode::SAT ); TS_ASSERT_THROWS_NOTHING( engine.extractSolution( inputQuery ) ); inputQuery.push(); } @@ -71,7 +71,7 @@ class IncrementalTestSuite : public CxxTest::TestSuite Engine engine; inputQuery.setLowerBound( 2, 2 ); TS_ASSERT( !engine.processInputQuery( inputQuery ) ); - TS_ASSERT_EQUALS( engine.getExitCode(), IEngine::UNSAT ); + TS_ASSERT_EQUALS( engine.getExitCode(), ExitCode::UNSAT ); inputQuery.pop(); } @@ -82,7 +82,7 @@ class IncrementalTestSuite : public CxxTest::TestSuite TS_ASSERT( engine.processInputQuery( inputQuery ) ); TS_ASSERT_THROWS_NOTHING( engine.solve() ); - TS_ASSERT_EQUALS( engine.getExitCode(), IEngine::SAT ); + TS_ASSERT_EQUALS( engine.getExitCode(), ExitCode::SAT ); TS_ASSERT_THROWS_NOTHING( engine.extractSolution( inputQuery ) ); inputQuery.push(); } diff --git a/src/system_tests/Test_relu.h b/src/system_tests/Test_relu.h index f88c1579ec..1690e0c208 100644 --- a/src/system_tests/Test_relu.h +++ b/src/system_tests/Test_relu.h @@ -220,6 +220,200 @@ class ReluTestSuite : public CxxTest::TestSuite TS_ASSERT( correctSolution ); } + + void test_relu_1_cdcl() + { +#ifdef BUILD_CADICAL + double large = 1000; + + Query inputQuery; + inputQuery.setNumberOfVariables( 9 ); + + inputQuery.setLowerBound( 0, 0 ); + inputQuery.setUpperBound( 0, 1 ); + + inputQuery.setLowerBound( 1, -large ); + inputQuery.setUpperBound( 1, large ); + + inputQuery.setLowerBound( 2, 0 ); + inputQuery.setUpperBound( 2, large ); + + inputQuery.setLowerBound( 3, -large ); + inputQuery.setUpperBound( 3, large ); + + inputQuery.setLowerBound( 4, 0 ); + inputQuery.setUpperBound( 4, large ); + + inputQuery.setLowerBound( 5, 0.5 ); + inputQuery.setUpperBound( 5, 1 ); + + inputQuery.setLowerBound( 6, 0 ); + inputQuery.setUpperBound( 6, 0 ); + inputQuery.setLowerBound( 7, 0 ); + inputQuery.setUpperBound( 7, 0 ); + inputQuery.setLowerBound( 8, 0 ); + inputQuery.setUpperBound( 8, 0 ); + + Equation equation1; + equation1.addAddend( 1, 0 ); + equation1.addAddend( -1, 1 ); + equation1.addAddend( 1, 6 ); + equation1.setScalar( 0 ); + inputQuery.addEquation( equation1 ); + + Equation equation2; + equation2.addAddend( 1, 0 ); + equation2.addAddend( 1, 3 ); + equation2.addAddend( 1, 7 ); + equation2.setScalar( 0 ); + inputQuery.addEquation( equation2 ); + + Equation equation3; + equation3.addAddend( 1, 2 ); + equation3.addAddend( 1, 4 ); + equation3.addAddend( -1, 5 ); + equation3.addAddend( 1, 8 ); + equation3.setScalar( 0 ); + inputQuery.addEquation( equation3 ); + + ReluConstraint *relu1 = new ReluConstraint( 1, 2 ); + ReluConstraint *relu2 = new ReluConstraint( 3, 4 ); + + inputQuery.addPiecewiseLinearConstraint( relu1 ); + inputQuery.addPiecewiseLinearConstraint( relu2 ); + + Engine engineCDCL; + engineCDCL.configureForCDCL(); + TS_ASSERT_THROWS_NOTHING( engineCDCL.processInputQuery( inputQuery ) ); + + TS_ASSERT_THROWS_NOTHING( engineCDCL.solve() ); + + engineCDCL.extractSolution( inputQuery ); + + bool correctSolution = true; + // Sanity test + + double value_x0 = inputQuery.getSolutionValue( 0 ); + double value_x1b = inputQuery.getSolutionValue( 1 ); + double value_x1f = inputQuery.getSolutionValue( 2 ); + double value_x2b = inputQuery.getSolutionValue( 3 ); + double value_x2f = inputQuery.getSolutionValue( 4 ); + double value_x3 = inputQuery.getSolutionValue( 5 ); + + if ( !FloatUtils::areEqual( value_x0, value_x1b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x0, -value_x2b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x3, value_x1f + value_x2f ) ) + correctSolution = false; + + if ( FloatUtils::lt( value_x0, 0 ) || FloatUtils::gt( value_x0, 1 ) || + FloatUtils::lt( value_x1f, 0 ) || FloatUtils::lt( value_x2f, 0 ) || + FloatUtils::lt( value_x3, 0.5 ) || FloatUtils::gt( value_x3, 1 ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x1f ) && !FloatUtils::areEqual( value_x1b, value_x1f ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x2f ) && !FloatUtils::areEqual( value_x2b, value_x2f ) ) + { + correctSolution = false; + } + + TS_ASSERT( correctSolution ); +#endif + } + + void test_relu_2_cdcl() + { +#ifdef BUILD_CADICAL + Query inputQuery; + inputQuery.setNumberOfVariables( 6 ); + + inputQuery.setLowerBound( 0, 0 ); + inputQuery.setUpperBound( 0, 1 ); + + inputQuery.setLowerBound( 5, 0.5 ); + inputQuery.setUpperBound( 5, 1 ); + + Equation equation1; + equation1.addAddend( 1, 0 ); + equation1.addAddend( -1, 1 ); + equation1.setScalar( 0 ); + inputQuery.addEquation( equation1 ); + + Equation equation2; + equation2.addAddend( 1, 0 ); + equation2.addAddend( 1, 3 ); + equation2.setScalar( 0 ); + inputQuery.addEquation( equation2 ); + + Equation equation3; + equation3.addAddend( 1, 2 ); + equation3.addAddend( 1, 4 ); + equation3.addAddend( -1, 5 ); + equation3.setScalar( 0 ); + inputQuery.addEquation( equation3 ); + + ReluConstraint *relu1 = new ReluConstraint( 1, 2 ); + ReluConstraint *relu2 = new ReluConstraint( 3, 4 ); + + inputQuery.addPiecewiseLinearConstraint( relu1 ); + inputQuery.addPiecewiseLinearConstraint( relu2 ); + + Engine engineCDCL; + engineCDCL.configureForCDCL(); + TS_ASSERT_THROWS_NOTHING( engineCDCL.processInputQuery( inputQuery ) ); + + TS_ASSERT_THROWS_NOTHING( engineCDCL.solve() ); + + engineCDCL.extractSolution( inputQuery ); + + bool correctSolution = true; + // Sanity test + + double value_x0 = inputQuery.getSolutionValue( 0 ); + double value_x1b = inputQuery.getSolutionValue( 1 ); + double value_x1f = inputQuery.getSolutionValue( 2 ); + double value_x2b = inputQuery.getSolutionValue( 3 ); + double value_x2f = inputQuery.getSolutionValue( 4 ); + double value_x3 = inputQuery.getSolutionValue( 5 ); + + if ( !FloatUtils::areEqual( value_x0, value_x1b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x0, -value_x2b ) ) + correctSolution = false; + + if ( !FloatUtils::areEqual( value_x3, value_x1f + value_x2f ) ) + correctSolution = false; + + if ( FloatUtils::lt( value_x0, 0 ) || FloatUtils::gt( value_x0, 1 ) || + FloatUtils::lt( value_x1f, 0 ) || FloatUtils::lt( value_x2f, 0 ) || + FloatUtils::lt( value_x3, 0.5 ) || FloatUtils::gt( value_x3, 1 ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x1f ) && !FloatUtils::areEqual( value_x1b, value_x1f ) ) + { + correctSolution = false; + } + + if ( FloatUtils::isPositive( value_x2f ) && !FloatUtils::areEqual( value_x2b, value_x2f ) ) + { + correctSolution = false; + } + + TS_ASSERT( correctSolution ); +#endif + } }; // diff --git a/tools/download_cadical.sh b/tools/download_cadical.sh new file mode 100755 index 0000000000..db46367cfc --- /dev/null +++ b/tools/download_cadical.sh @@ -0,0 +1,18 @@ +#!/bin/bash +curdir=$pwd +mydir="${0%/*}" + +cd $mydir +git clone https://github.com/arminbiere/cadical.git +cd cadical +git checkout d04d77b1ed04f08eebd78aeaff94673bd11330c8 +mkdir build +cd build +for f in ../src/*.cpp; do g++ -O3 -DNDEBUG -DNBUILD -DLOGGING -g -fPIC -c $f; done +ar rc libcadical.a `ls *.o | grep -v ical.o` +g++ -fPIC -o cadical cadical.o -L. -lcadical +g++ -fPIC -o mobical mobical.o -L. -lcadical +#./configure +#make + +cd $curdir