From f29889d633145f2b96fdc441012ec23e5897d509 Mon Sep 17 00:00:00 2001 From: Benjamin Kaufmann Date: Fri, 14 Feb 2025 10:55:16 +0100 Subject: [PATCH] Switch to new libpotassco. * Enable C++20 for all code. * Modernize code base. * Simplify enum interface. * Switch to clang-format based code formatting. * Switch to "not" instead of operator! * Switch to external catch2. * Switch to pragma once. * Use span instead of iterator pairs. * Adjust URLs. --- .clang-format | 37 + .github/workflows/test.yml | 63 +- .gitignore | 1 + .gitmodules | 2 +- .travis.yml | 63 - CMakeLists.txt | 222 +- LICENSE | 2 +- README.md | 6 +- app/CMakeLists.txt | 30 +- app/main.cpp | 29 +- clasp/asp_preprocessor.h | 163 +- clasp/cb_enumerator.h | 64 +- clasp/clasp_facade.h | 769 +- clasp/claspfwd.h | 27 +- clasp/clause.h | 912 +- clasp/cli/clasp_app.h | 326 +- clasp/cli/clasp_cli_configs.inl | 6 +- clasp/cli/clasp_cli_options.inl | 110 +- clasp/cli/clasp_options.h | 382 +- clasp/cli/clasp_output.h | 443 +- clasp/clingo.h | 381 +- clasp/config.h.in | 112 +- clasp/constraint.h | 931 +- clasp/dependency_graph.h | 876 +- clasp/enumerator.h | 524 +- clasp/heuristics.h | 604 +- clasp/literal.h | 339 +- clasp/logic_program.h | 1285 +- clasp/logic_program_types.h | 1129 +- clasp/lookahead.h | 340 +- clasp/minimize_constraint.h | 968 +- clasp/model_enumerators.h | 122 +- clasp/mt/mutex.h | 54 - clasp/mt/parallel_solve.h | 528 +- clasp/mt/thread.h | 31 +- clasp/parser.h | 243 +- clasp/pod_vector.h | 151 +- clasp/program_builder.h | 392 +- clasp/satelite.h | 260 +- clasp/shared_context.h | 1710 +-- clasp/solve_algorithms.h | 430 +- clasp/solver.h | 2104 +-- clasp/solver_strategies.h | 1120 +- clasp/solver_types.h | 1179 +- clasp/statistics.h | 497 +- clasp/unfounded_check.h | 472 +- clasp/util/hash.h | 48 - clasp/util/indexed_priority_queue.h | 332 +- clasp/util/left_right_sequence.h | 559 +- clasp/util/misc_types.h | 811 +- clasp/util/multi_queue.h | 421 +- clasp/util/pod_vector.h | 1079 +- clasp/util/timer.h | 59 +- clasp/util/type_manip.h | 141 - clasp/weight_constraint.h | 371 +- doc/api/clasp.txt | 2 +- doc/output.md | 10 +- examples/CMakeLists.txt | 12 +- examples/example.h | 13 +- examples/example1.cpp | 166 +- examples/example2.cpp | 87 +- examples/example3.cpp | 64 +- examples/example4.cpp | 54 +- examples/main.cpp | 51 +- libpotassco | 2 +- src/CMakeLists.txt | 259 +- src/asp_preprocessor.cpp | 961 +- src/cb_enumerator.cpp | 570 +- src/clasp_app.cpp | 1310 +- src/clasp_facade.cpp | 1977 +-- src/clasp_options.cpp | 2287 ++-- src/clasp_output.cpp | 2267 ++-- src/clause.cpp | 1982 +-- src/clingo.cpp | 975 +- src/constraint.cpp | 144 +- src/dependency_graph.cpp | 1880 +-- src/enumerator.cpp | 577 +- src/heuristics.cpp | 1848 +-- src/logic_program.cpp | 4214 +++--- src/logic_program_types.cpp | 2409 ++-- src/lookahead.cpp | 667 +- src/minimize_constraint.cpp | 2901 +++-- src/model_enumerators.cpp | 582 +- src/parallel_solve.cpp | 2017 +-- src/parser.cpp | 783 +- src/program_builder.cpp | 675 +- src/satelite.cpp | 1045 +- src/shared_context.cpp | 2012 +-- src/solve_algorithms.cpp | 951 +- src/solver.cpp | 3487 ++--- src/solver_strategies.cpp | 683 +- src/solver_types.cpp | 221 +- src/statistics.cpp | 616 +- src/timer.cpp | 99 +- src/unfounded_check.cpp | 1264 +- src/weight_constraint.cpp | 1011 +- tests/CMakeLists.txt | 35 +- tests/catch.hpp | 17966 -------------------------- tests/clause_creator_test.cpp | 1033 +- tests/clause_test.cpp | 1975 +-- tests/cli_test.cpp | 1577 +-- tests/decision_heuristic_test.cpp | 1252 +- tests/dependency_graph_test.cpp | 645 +- tests/dlp_builder_test.cpp | 819 +- tests/enumerator_test.cpp | 993 +- tests/facade_test.cpp | 5929 ++++----- tests/literal_test.cpp | 356 +- tests/lpcompare.h | 112 +- tests/minimize_test.cpp | 2243 ++-- tests/parser_test.cpp | 2020 +-- tests/program_builder_test.cpp | 4975 ++++--- tests/rule_test.cpp | 1063 +- tests/satelite_test.cpp | 309 +- tests/solver_test.cpp | 4623 ++++--- tests/test_main.cpp | 46 - tests/unfounded_check_test.cpp | 1084 +- tests/weight_constraint_test.cpp | 1451 +-- 117 files changed, 51728 insertions(+), 66173 deletions(-) create mode 100644 .clang-format delete mode 100644 .travis.yml delete mode 100644 clasp/mt/mutex.h delete mode 100644 clasp/util/hash.h delete mode 100644 clasp/util/type_manip.h delete mode 100644 tests/catch.hpp delete mode 100644 tests/test_main.cpp diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..39ed18c --- /dev/null +++ b/.clang-format @@ -0,0 +1,37 @@ +--- +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignConsecutiveAssignments: + Enabled: true + AlignCompound: true +AlignConsecutiveBitFields: + Enabled: true +AlignConsecutiveDeclarations: + Enabled: true +AlignConsecutiveMacros: + Enabled: true +AlignConsecutiveShortCaseStatements: + Enabled: true + AlignCaseColons: true +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + BeforeCatch: true + BeforeElse: true +BreakBeforeBraces: Custom +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +ColumnLimit: 120 +IndentCaseLabels: true +IndentRequiresClause: false +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +ObjCBlockIndentWidth: 4 +PackConstructorInitializers: CurrentLine +PointerAlignment: Left +RequiresClausePosition: WithPreceding +SpaceAfterCStyleCast: true +TabWidth: 4 + diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47137ec..d3abf4f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,40 +28,39 @@ jobs: build_type: 'Debug' steps: - - uses: actions/checkout@v2 - with: - submodules: recursive + - uses: actions/checkout@v4 + with: + submodules: recursive - - name: setup (ubuntu) - if: ${{ matrix.os == 'ubuntu-latest' }} - run: - sudo apt install ninja-build + - name: setup (ubuntu) + if: ${{ matrix.os == 'ubuntu-latest' }} + run: + sudo apt install ninja-build - - name: setup (macos) - if: ${{ matrix.os == 'macos-latest' }} - run: | - brew update - brew install ninja + - name: setup (macos) + if: ${{ matrix.os == 'macos-latest' }} + run: | + brew update + brew install ninja - - name: Configure - run: > - cmake - -G "${{ matrix.generator }}" - -B "${{github.workspace}}/build" - -DCMAKE_BUILD_TYPE="${{matrix.build_type}}" - -DCLASP_BUILD_TESTS="On" - -DCLASP_BUILD_WITH_THREADS="${{matrix.build_threads}}" - -DLIB_POTASSCO_BUILD_TESTS="On" - -DCMAKE_CXX_STANDARD="14" + - name: Configure + run: > + cmake + -G "${{ matrix.generator }}" + -B "${{github.workspace}}/build" + -DCMAKE_BUILD_TYPE="${{matrix.build_type}}" + -DCLASP_BUILD_WITH_THREADS="${{matrix.build_threads}}" + -DCLASP_BUILD_TESTS="On" + -DLIB_POTASSCO_BUILD_TESTS="On" - - name: Build - run: > - cmake - --build "${{github.workspace}}/build" - --config "${{matrix.build_type}}" + - name: Build + run: > + cmake + --build "${{github.workspace}}/build" + --config "${{matrix.build_type}}" - - name: Test - working-directory: ${{github.workspace}}/build - run: > - ctest - -C "${{matrix.build_type}}" + - name: Test + working-directory: ${{github.workspace}}/build + run: > + ctest --rerun-failed --output-on-failure + -C "${{matrix.build_type}}" diff --git a/.gitignore b/.gitignore index 17a5534..8ff4ba2 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build* .vscode* CMakeLists.txt.user +cmake-build* diff --git a/.gitmodules b/.gitmodules index 581ab4d..58e63b1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libpotassco"] path = libpotassco - url = https://github.com/potassco/libpotassco.git + url = ../libpotassco.git diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 3b62046..0000000 --- a/.travis.yml +++ /dev/null @@ -1,63 +0,0 @@ -sudo: false -language: cpp -matrix: - include: - - os: linux - compiler: gcc - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - george-edison55-precise-backports - packages: - - g++-4.9 - - cmake - - cmake-data - env: - - COMPILER='g++-4.9' - - os: linux - compiler: gcc - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - george-edison55-precise-backports - packages: - - g++-5 - - cmake - - cmake-data - env: - - COMPILER='g++-5' - - os: linux - compiler: gcc - addons: - apt: - sources: - - ubuntu-toolchain-r-test - - george-edison55-precise-backports - packages: - - g++-8 - - cmake - - cmake-data - env: - - COMPILER='g++-8' - - os: osx - osx_image: xcode8 - env: - - COMPILER='clang++' - -install: - - export CMAKE=cmake - - export CXX=$COMPILER - - $CMAKE --version - - $CXX --version -script: - - mkdir $CXX-mt && cd $CXX-mt - - $CMAKE -DCMAKE_CXX_COMPILER=$CXX -DCLASP_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-Wall -Wextra" ../ - - make -j3 && make test CTEST_OUTPUT_ON_FAILURE=1 - - cd ../ - - mkdir $CXX-st && cd $CXX-st - - $CMAKE -DCMAKE_CXX_COMPILER=$CXX -DCLASP_BUILD_TESTS=ON -DCLASP_BUILD_WITH_THREADS=OFF -DCMAKE_CXX_FLAGS="-Wall -Wextra" ../ - - make -j3 && make test CTEST_OUTPUT_ON_FAILURE=1 - - cd ../ - diff --git a/CMakeLists.txt b/CMakeLists.txt index 06225bf..09f111c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,82 +1,82 @@ -cmake_minimum_required(VERSION 3.1) -project(CLASP VERSION 3.4.0.4 LANGUAGES CXX) +cmake_minimum_required(VERSION 3.16) +project(CLASP VERSION 4.0.0 LANGUAGES CXX) # Enable folders in IDEs like Visual Studio set_property(GLOBAL PROPERTY USE_FOLDERS ON) -if (POLICY CMP0063) - cmake_policy(SET CMP0063 NEW) +if(POLICY CMP0063) + cmake_policy(SET CMP0063 NEW) endif() -if (NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) - message(STATUS "No build type selected - using 'Release'") - set(CMAKE_BUILD_TYPE "Release") +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "No build type selected - using 'Release'") + set(CMAKE_BUILD_TYPE "Release") endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(GNUInstallDirs) # Configuration options -option(CLASP_BUILD_APP "whether or not to build the clasp application" ON) -option(CLASP_BUILD_STATIC "whether or not to link statically (if supported)" OFF) -option(CLASP_BUILD_TESTS "whether or not to build clasp unit tests" OFF) -option(CLASP_BUILD_EXAMPLES "whether or not to build examples" OFF) -option(CLASP_BUILD_WITH_THREADS "whether or not to build clasp with threading support (requires C++11)" ON) -option(CLASP_INSTALL_LIB "whether or not to install libclasp" OFF) -option(CLASP_INSTALL_VERSIONED "whether to use a versioned install layout" OFF) +option(CLASP_BUILD_APP "whether or not to build the clasp application" ON) +option(CLASP_BUILD_STATIC "whether or not to link statically (if supported)" OFF) +option(CLASP_BUILD_TESTS "whether or not to build clasp unit tests" OFF) +option(CLASP_BUILD_EXAMPLES "whether or not to build examples" OFF) +option(CLASP_BUILD_WITH_THREADS "whether or not to build clasp with threading support (requires C++11)" ON) +option(CLASP_INSTALL_LIB "whether or not to install libclasp" OFF) +option(CLASP_INSTALL_VERSIONED "whether to use a versioned install layout" OFF) option(CLASP_USE_LOCAL_LIB_POTASSCO "whether to use the libpotassco submodule" ON) -if (NOT MSVC) - if (NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) - endif() - if (NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - endif() - if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) - endif() +if(NOT MSVC) + if(NOT CMAKE_ARCHIVE_OUTPUT_DIRECTORY) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) + endif() + if(NOT CMAKE_LIBRARY_OUTPUT_DIRECTORY) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + endif() + if(NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) + endif() else() - set(VC_RELEASE_LINK_OPTIONS /LTCG) - SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") - SET(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") - SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") - SET(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") - if (CLASP_BUILD_STATIC) - # force static runtime - string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") - endif() + set(VC_RELEASE_LINK_OPTIONS /LTCG) + SET(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") + SET(CMAKE_MODULE_LINKER_FLAGS_RELEASE "${CMAKE_MODULE_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") + SET(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") + SET(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} ${VC_RELEASE_LINK_OPTIONS}") + if(CLASP_BUILD_STATIC) + # force static runtime + string(REGEX REPLACE "/MD" "/MT" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + endif() endif() -if (CLASP_INSTALL_VERSIONED) - set(clasp_include_dest "clasp-${CLASP_VERSION}") - set(clasp_library_dest "clasp-${CLASP_VERSION}") - set(cmake_dest "clasp-${CLASP_VERSION}/cmake") +if(CLASP_INSTALL_VERSIONED) + set(clasp_include_dest "clasp-${CLASP_VERSION}") + set(clasp_library_dest "clasp-${CLASP_VERSION}") + set(cmake_dest "clasp-${CLASP_VERSION}/cmake") else() - set(clasp_include_dest ".") - set(clasp_library_dest ".") - set(cmake_dest "cmake/Clasp") + set(clasp_include_dest ".") + set(clasp_library_dest ".") + set(cmake_dest "cmake/Clasp") endif() -if (CLASP_INSTALL_LIB AND NOT CMAKE_INSTALL_LIBDIR) - message(STATUS "LIBDIR no set - using lib") - set(CMAKE_INSTALL_LIBDIR lib) +if(CLASP_INSTALL_LIB AND NOT CMAKE_INSTALL_LIBDIR) + message(STATUS "LIBDIR no set - using lib") + set(CMAKE_INSTALL_LIBDIR lib) endif() - +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_EXTENSIONS ON) +set(CXX_STANDARD_REQUIRED ON) # C++11 is required for building with threads -if (CLASP_BUILD_WITH_THREADS) - set(CMAKE_CXX_STANDARD 11) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_EXTENSIONS ON) - # some versions of findThreads will fail if C is not enabled - enable_language(C) - find_package(Threads REQUIRED) +if(CLASP_BUILD_WITH_THREADS) + + # some versions of findThreads will fail if C is not enabled + enable_language(C) + find_package(Threads REQUIRED) - # Add libatomic if necessary - if (CMAKE_USE_PTHREADS_INIT) - include (CheckCXXSourceCompiles) - set (OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) - set (OLD_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) - list(APPEND CMAKE_REQUIRED_FLAGS "-std=c++11") - list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads) - check_cxx_source_compiles(" + # Add libatomic if necessary + if(CMAKE_USE_PTHREADS_INIT) + include(CheckCXXSourceCompiles) + set(OLD_CMAKE_REQUIRED_FLAGS ${CMAKE_REQUIRED_FLAGS}) + set(OLD_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES}) + list(APPEND CMAKE_REQUIRED_FLAGS "-std=c++20") + list(APPEND CMAKE_REQUIRED_LIBRARIES Threads::Threads) + check_cxx_source_compiles(" #include #include std::atomic x (0); @@ -85,34 +85,40 @@ int main() { return 0; } " CLASP_HAS_WORKING_LIBATOMIC) - set (CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) - set (CMAKE_REQUIRED_LIBRARIES ${OLD_CMAKE_REQUIRED_LIBRARIES}) - if (NOT CLASP_HAS_WORKING_LIBATOMIC) - check_library_exists(atomic __atomic_fetch_add_4 "" CLASP_HAS_LIBATOMIC) - if (CLASP_HAS_LIBATOMIC) - set_property(TARGET Threads::Threads APPEND PROPERTY INTERFACE_LINK_LIBRARIES "atomic") - endif() - endif() - endif() -else() - set(CMAKE_CXX_STANDARD 98) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_EXTENSIONS ON) + set(CMAKE_REQUIRED_FLAGS ${OLD_CMAKE_REQUIRED_FLAGS}) + set(CMAKE_REQUIRED_LIBRARIES ${OLD_CMAKE_REQUIRED_LIBRARIES}) + if(NOT CLASP_HAS_WORKING_LIBATOMIC) + check_library_exists(atomic __atomic_fetch_add_4 "" CLASP_HAS_LIBATOMIC) + if(CLASP_HAS_LIBATOMIC) + set_property(TARGET Threads::Threads APPEND PROPERTY INTERFACE_LINK_LIBRARIES "atomic") + endif() + endif() + endif() endif() # Check for or build external dependency -if (NOT CLASP_USE_LOCAL_LIB_POTASSCO) - find_package(Potassco 1.0 REQUIRED CONFIG) +if(NOT CLASP_USE_LOCAL_LIB_POTASSCO) + find_package(Potassco 1.0 REQUIRED CONFIG) else() - if (NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libpotassco/CMakeLists.txt) - message(STATUS "Potassco is not installed - fetching submodule") - execute_process(COMMAND git submodule update --init WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_QUIET) - else() - message(STATUS "Potassco is not installed - using local copy") - endif() - set(LIB_POTASSCO_BUILD_APP ${CLASP_BUILD_APP} CACHE BOOL "") - set(LIB_POTASSCO_INSTALL_LIB ${CLASP_INSTALL_LIB} CACHE BOOL "") - add_subdirectory(libpotassco) + if(NOT EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/libpotassco/CMakeLists.txt) + message(STATUS "Potassco is not installed - fetching submodule") + execute_process(COMMAND git submodule update --init WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_QUIET) + else() + message(STATUS "Potassco is not installed - using local copy") + endif() + set(LIB_POTASSCO_BUILD_APP ${CLASP_BUILD_APP} CACHE BOOL "") + set(LIB_POTASSCO_INSTALL_LIB ${CLASP_INSTALL_LIB} CACHE BOOL "") + if(CLASP_BUILD_TESTS) + set(LIB_POTASSCO_SETUP_CATCH2 TRUE) + endif() + add_subdirectory(libpotassco) + if(Catch2_SOURCE_DIR) + list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras) + get_directory_property(hasParent PARENT_DIRECTORY) + if(hasParent) + set(CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" PARENT_SCOPE) + endif() + endif() endif() # Build clasp library @@ -120,42 +126,42 @@ add_subdirectory(src) # Build optional targets if(CLASP_BUILD_TESTS) - enable_testing() - add_subdirectory(tests) + enable_testing() + add_subdirectory(tests) endif() # optional doc target find_package(Doxygen) if(DOXYGEN_FOUND) - set(doxyfile "${CMAKE_CURRENT_SOURCE_DIR}/doc/api/clasp.doxy") - add_custom_target(doc_clasp - COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} - WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/doc/api" - COMMENT "Generating documentation..." - VERBATIM) - set_target_properties(doc_clasp PROPERTIES FOLDER doc) + set(doxyfile "${CMAKE_CURRENT_SOURCE_DIR}/doc/api/clasp.doxy") + add_custom_target(doc_clasp + COMMAND ${DOXYGEN_EXECUTABLE} ${doxyfile} + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/doc/api" + COMMENT "Generating documentation..." + VERBATIM) + set_target_properties(doc_clasp PROPERTIES FOLDER doc) endif() if(CLASP_BUILD_APP) - add_subdirectory(app) + add_subdirectory(app) endif() if(CLASP_BUILD_EXAMPLES) - add_subdirectory(examples) + add_subdirectory(examples) endif() # Export -if (CLASP_INSTALL_LIB) - include(CMakePackageConfigHelpers) - configure_package_config_file( - ${PROJECT_SOURCE_DIR}/cmake/ClaspConfig.cmake.in - ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfig.cmake - INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/${cmake_dest}) - write_basic_package_version_file( - ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfigVersion.cmake - COMPATIBILITY SameMajorVersion) - install(FILES - ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfig.cmake - ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfigVersion.cmake - DESTINATION ${CMAKE_INSTALL_LIBDIR}/${cmake_dest}) - install(EXPORT ClaspTargets DESTINATION "${CMAKE_INSTALL_LIBDIR}/${cmake_dest}") +if(CLASP_INSTALL_LIB) + include(CMakePackageConfigHelpers) + configure_package_config_file( + ${PROJECT_SOURCE_DIR}/cmake/ClaspConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/${cmake_dest}) + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfigVersion.cmake + COMPATIBILITY SameMajorVersion) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/ClaspConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/${cmake_dest}) + install(EXPORT ClaspTargets DESTINATION "${CMAKE_INSTALL_LIBDIR}/${cmake_dest}") endif() diff --git a/LICENSE b/LICENSE index bf8b467..a9954c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License -Copyright (c) 2015-2017 Benjamin Kaufmann +Copyright (c) 2015-present Benjamin Kaufmann Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/README.md b/README.md index ce46466..c22957d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ It provides different reasoning modes and other advanced features including: - Different input formats including [smodels][smodels], [aspif][aspif], [dimacs][dimacs] and [opb][opb]. Detailed information (including a User's manual), source code, -and pre-compiled binaries are available at: http://potassco.org/ +and pre-compiled binaries are available at: https://potassco.org/ ## LICENSE clasp is distributed under the MIT License. @@ -68,7 +68,7 @@ and pre-compiled binaries are available at: http://potassco.org/ clasp executable to a directory of your choice. ## DOCUMENTATION - A User's Guide is available from http://potassco.org/ + A User's Guide is available from https://potassco.org/ Source code documentation can be generated with [Doxygen][doxygen]. Either explicitly: @@ -122,7 +122,7 @@ and pre-compiled binaries are available at: http://potassco.org/ [acyc]: https://www.cs.uni-potsdam.de/wv/publications/#DBLP:journals/fuin/BomansonGJKS16 [aspif]: https://www.cs.uni-potsdam.de/wv/publications/#DBLP:conf/iclp/GebserKKOSW16x [smodels]: http://www.tcs.hut.fi/Software/smodels/lparse.ps -[dimacs]: http://www.satcompetition.org/2009/format-benchmarks2009.html +[dimacs]: https://web.archive.org/web/20190325181937/https://www.satcompetition.org/2009/format-benchmarks2009.html [opb]: https://www.cril.univ-artois.fr/PB09/solver_req.html [doxygen]: https://www.stack.nl/~dimitri/doxygen/ [cmake]: https://cmake.org/ diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 976eab7..247bccf 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1,22 +1,20 @@ -set(files - main.cpp) +set(files main.cpp) add_executable(clasp ${files}) set_target_properties(clasp PROPERTIES FOLDER exe) -if (NOT CMAKE_INSTALL_BINDIR) - message(STATUS "BINDIR not set - using bin") - set(CMAKE_INSTALL_BINDIR "bin") +if(NOT CMAKE_INSTALL_BINDIR) + message(STATUS "BINDIR not set - using bin") + set(CMAKE_INSTALL_BINDIR "bin") endif() -if (CLASP_BUILD_STATIC AND UNIX AND NOT APPLE) - if (CLASP_BUILD_WITH_THREADS) - string(CONCAT refs "-Wl,-u,pthread_cancel,-u,pthread_cond_broadcast," - "-u,pthread_cond_destroy,-u,pthread_cond_signal," - "-u,pthread_cond_timedwait,-u,pthread_cond_wait," - "-u,pthread_create,-u,pthread_detach,-u,pthread_join," - "-u,pthread_equal") - target_link_libraries(clasp ${refs}) - endif() - target_link_libraries(clasp "-static") +if(CLASP_BUILD_STATIC AND UNIX AND NOT APPLE) + if(CLASP_BUILD_WITH_THREADS) + string(CONCAT refs "-Wl,-u,pthread_cancel,-u,pthread_cond_broadcast," + "-u,pthread_cond_destroy,-u,pthread_cond_signal," + "-u,pthread_cond_timedwait,-u,pthread_cond_wait," + "-u,pthread_create,-u,pthread_detach,-u,pthread_join," + "-u,pthread_equal") + target_link_libraries(clasp ${refs}) + endif() + target_link_libraries(clasp "-static") endif() target_link_libraries(clasp libclasp) - install(TARGETS clasp EXPORT clasp DESTINATION ${CMAKE_INSTALL_BINDIR}) diff --git a/app/main.cpp b/app/main.cpp index f44176c..4cf2fc3 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -26,22 +26,21 @@ // main - entry point ///////////////////////////////////////////////////////////////////////////////////////// // #define CHECK_HEAP -#if defined (_MSC_VER) && defined(CHECK_HEAP) && _MSC_VER >= 1200 +#if defined(_MSC_VER) && defined(CHECK_HEAP) && _MSC_VER >= 1200 #include #endif int main(int argc, char** argv) { -#if defined (_MSC_VER) && defined (CHECK_HEAP) && _MSC_VER >= 1200 - _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | - CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF | - _CRTDBG_CHECK_ALWAYS_DF); +#if defined(_MSC_VER) && defined(CHECK_HEAP) && _MSC_VER >= 1200 + _CrtSetDbgFlag(_CrtSetDbgFlag(_CRTDBG_REPORT_FLAG) | CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF | + _CRTDBG_CHECK_ALWAYS_DF); - _CrtSetReportMode( _CRT_WARN, _CRTDBG_MODE_FILE ); - _CrtSetReportFile( _CRT_WARN, _CRTDBG_FILE_STDERR ); - _CrtSetReportMode( _CRT_ERROR, _CRTDBG_MODE_FILE ); - _CrtSetReportFile( _CRT_ERROR, _CRTDBG_FILE_STDERR ); - _CrtSetReportMode( _CRT_ASSERT, _CRTDBG_MODE_FILE ); - _CrtSetReportFile( _CRT_ASSERT, _CRTDBG_FILE_STDERR ); + _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ERROR, _CRTDBG_FILE_STDERR); + _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE); + _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); #endif - Clasp::Cli::ClaspApp app; - return app.main(argc, argv); + Clasp::Cli::ClaspApp app; + return app.main(argc, argv); } diff --git a/clasp/asp_preprocessor.h b/clasp/asp_preprocessor.h index 577d045..63e5fba 100644 --- a/clasp/asp_preprocessor.h +++ b/clasp/asp_preprocessor.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,16 +21,10 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_PREPROCESSOR_H_INCLUDED -#define CLASP_PREPROCESSOR_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include -namespace Clasp { namespace Asp { +namespace Clasp::Asp { /** * \addtogroup asp @@ -44,88 +38,81 @@ namespace Clasp { namespace Asp { */ class Preprocessor { public: - Preprocessor() : prg_(0), dfs_(true) {} - //! Possible eq-preprocessing types. - enum EqType { - no_eq, //!< No eq-preprocessing, associate a new var with each supported atom and body. - full_eq //!< Check for all kinds of equivalences between atoms and bodies. - }; + Preprocessor() = default; + Preprocessor(const Preprocessor&) = delete; + Preprocessor& operator=(const Preprocessor&) = delete; + + //! Possible eq-preprocessing types. + enum EqType { + no_eq, //!< No eq-preprocessing, associate a new var with each supported atom and body. + full_eq //!< Check for all kinds of equivalences between atoms and bodies. + }; - const LogicProgram* program() const { return prg_; } - LogicProgram* program() { return prg_; } + [[nodiscard]] const LogicProgram* program() const { return prg_; } + LogicProgram* program() { return prg_; } - //! Starts preprocessing of the logic program. - /*! - * Computes the maximum consequences of prg and associates a variable - * with each supported atom and body. - * \param prg The logic program to preprocess. - * \param t Type of eq-preprocessing. - * \param maxIters If t == full_eq, maximal number of iterations during eq preprocessing. - * \param dfs If t == full_eq, classify in df-order (true) or bf-order (false). - */ - bool preprocess(LogicProgram& prg, EqType t, uint32 maxIters, bool dfs = true) { - prg_ = &prg; - dfs_ = dfs; - type_ = t; - return t == full_eq - ? preprocessEq(maxIters) - : preprocessSimple(); - } + //! Starts preprocessing of the logic program. + /*! + * Computes the maximum consequences of prg and associates a variable + * with each supported atom and body. + * \param prg The logic program to preprocess. + * \param t Type of eq-preprocessing. + * \param maxIters If t == full_eq, maximal number of iterations during eq preprocessing. + * \param dfs If t == full_eq, classify in df-order (true) or bf-order (false). + */ + bool preprocess(LogicProgram& prg, EqType t, uint32_t maxIters, bool dfs = true) { + prg_ = &prg; + dfs_ = dfs; + type_ = t; + return t == full_eq ? preprocessEq(maxIters) : preprocessSimple(); + } + + [[nodiscard]] bool eq() const { return type_ == full_eq; } + [[nodiscard]] Var_t getRootAtom(Literal p) const { + return p.id() < litToNode_.size() ? litToNode_[p.id()] : var_max; + } + void setRootAtom(Literal p, uint32_t atomId) { + if (p.id() >= litToNode_.size()) { + litToNode_.resize(p.id() + 1, var_max); + } + litToNode_[p.id()] = atomId; + } - bool eq() const { return type_ == full_eq; } - Var getRootAtom(Literal p) const { return p.id() < litToNode_.size() ? litToNode_[p.id()] : varMax; } - void setRootAtom(Literal p, uint32 atomId) { - if (p.id() >= litToNode_.size()) litToNode_.resize(p.id()+1, varMax); - litToNode_[p.id()] = atomId; - } private: - Preprocessor(const Preprocessor&); - Preprocessor& operator=(const Preprocessor&); - bool preprocessEq(uint32 maxIters); - bool preprocessSimple(); - // ------------------------------------------------------------------------ - typedef PrgHead* const * HeadIter; - typedef std::pair HeadRange; - // Eq-Preprocessing - struct BodyExtra { - BodyExtra() : known(0), mBody(0), bSeen(0) {} - uint32 known :30; // Number of predecessors already classified, only used for bodies - uint32 mBody : 1; // A flag for marking bodies - uint32 bSeen : 1; // First time we see this body? - }; - bool classifyProgram(const VarVec& supportedBodies); - ValueRep simplifyClassifiedProgram(const HeadRange& atoms, bool more, VarVec& supported); - PrgBody* addBodyVar(uint32 bodyId); - bool addHeadsToUpper(PrgBody* body); - bool addHeadToUpper(PrgHead* head, PrgEdge support); - bool propagateAtomVar(PrgAtom*, PrgEdge source); - bool propagateAtomValue(PrgAtom*, ValueRep val, PrgEdge source); - bool mergeEqBodies(PrgBody* b, Var rootId, bool equalLits); - bool hasRootLiteral(PrgBody* b) const; - bool superfluous(PrgBody* b) const; - ValueRep simplifyHead(PrgHead* h, bool reclassify); - ValueRep simplifyBody(PrgBody* b, bool reclassify, VarVec& supported); - uint32 nextBodyId(VarVec::size_type& idx) { - if (follow_.empty() || idx == follow_.size()) { return varMax; } - if (dfs_) { - uint32 id = follow_.back(); - follow_.pop_back(); - return id; - } - return follow_[idx++];; - } - // ------------------------------------------------------------------------ - typedef PodVector::type BodyData; - LogicProgram* prg_; // program to preprocess - VarVec follow_; // bodies yet to classify - BodyData bodyInfo_; // information about the program nodes - VarVec litToNode_;// the roots of our equivalence classes - uint32 pass_; // current iteration number - uint32 maxPass_; // force stop after maxPass_ iterations - EqType type_; // type of eq-preprocessing - bool dfs_; // classify bodies in DF or BF order + bool preprocessEq(uint32_t maxIters); + bool preprocessSimple(); + // ------------------------------------------------------------------------ + // Eq-Preprocessing + struct BodyExtra { + uint32_t known : 30 {0}; // Number of predecessors already classified, only used for bodies + uint32_t mBody : 1 {0}; // A flag for marking bodies + uint32_t bSeen : 1 {0}; // First time we see this body? + }; + bool classifyProgram(); + Val_t simplifyClassifiedProgram(bool more); + PrgBody* addBodyVar(uint32_t bodyId); + bool addHeadsToUpper(const PrgBody* body); + bool addDisjToUpper(PrgDisj* disj, PrgEdge support); + bool addAtomToUpper(PrgAtom* atom, PrgEdge support); + bool addToUpper(PrgHead* head, PrgEdge support); + bool propagateAtomVar(PrgAtom*, PrgEdge source); + bool propagateAtomValue(PrgAtom*, Val_t val, PrgEdge source); + bool mergeEqBodies(PrgBody* b, uint32_t rootId, bool equalLits); + bool hasRootLiteral(const PrgBody* b) const; + bool superfluous(const PrgBody* b) const; + Val_t simplifyHead(PrgHead* h, bool reclassify); + Val_t simplifyBody(PrgBody* b, bool reclassify, VarVec& supported); + uint32_t popFollow(uint32_t& idx); + // ------------------------------------------------------------------------ + using BodyData = PodVector_t; + LogicProgram* prg_ = nullptr; // program to preprocess + VarVec follow_; // bodies yet to classify + BodyData bodyInfo_; // information about the program nodes + VarVec litToNode_; // the roots of our equivalence classes + uint32_t pass_ = 0; // current iteration number + uint32_t maxPass_ = 0; // force stop after maxPass_ iterations + EqType type_ = no_eq; // type of eq-preprocessing + bool dfs_ = true; // classify bodies in DF or BF order }; //@} -} } -#endif - +} // namespace Clasp::Asp diff --git a/clasp/cb_enumerator.h b/clasp/cb_enumerator.h index 8d74177..c866b32 100644 --- a/clasp/cb_enumerator.h +++ b/clasp/cb_enumerator.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,12 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CB_ENUMERATOR_H -#define CLASP_CB_ENUMERATOR_H - -#ifdef _MSC_VER #pragma once -#endif #include @@ -38,33 +33,34 @@ namespace Clasp { */ class CBConsequences : public Enumerator { public: - enum Type { - Brave = Model::Brave, - Cautious = Model::Cautious, - }; - enum Algo { Default, Query }; - /*! - * \param type Type of consequences to compute. - * \param a Type of algorithm to apply if type is Cautious. - */ - explicit CBConsequences(Type type, Algo a = Default); - ~CBConsequences(); - int modelType() const { return type_; } - bool exhaustive()const { return true; } - bool supportsSplitting(const SharedContext& problem) const; - int unsatType() const; + enum Type { + brave = Model::brave, + cautious = Model::cautious, + }; + enum Algo { def, query }; + /*! + * \param type Type of consequences to compute. + * \param a Type of algorithm to apply if type is Cautious. + */ + explicit CBConsequences(Type type, Algo a = def); + ~CBConsequences() override; + [[nodiscard]] int modelType() const override { return type_; } + [[nodiscard]] bool exhaustive() const override { return true; } + [[nodiscard]] bool supportsSplitting(const SharedContext& problem) const override; + [[nodiscard]] int unsatType() const override; + private: - class CBFinder; - class QueryFinder; - class SharedConstraint; - ConPtr doInit(SharedContext& ctx, SharedMinimizeData* m, int numModels); - void addLit(SharedContext& ctx, Literal p); - void addCurrent(Solver& s, LitVec& con, ValueVec& m, uint32 rootL = 0); - LitVec cons_; - SharedConstraint* shared_; - Type type_; - Algo algo_; + class CBFinder; + class QueryFinder; + class SharedConstraint; + using SharedRef = std::unique_ptr; + ConPtr doInit(SharedContext& ctx, SharedMinimizeData* m, int numModels) override; + void addLit(SharedContext& ctx, Literal p); + void addCurrent(const Solver& s, LitVec& con, ValueVec& m, uint32_t rootL = 0); + LitVec cons_; + SharedRef shared_; + Type type_; + Algo algo_; }; -} -#endif +} // namespace Clasp diff --git a/clasp/clasp_facade.h b/clasp/clasp_facade.h index 70122ff..ce08c09 100644 --- a/clasp/clasp_facade.h +++ b/clasp/clasp_facade.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,30 +21,36 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLASP_FACADE_H_INCLUDED -#define CLASP_CLASP_FACADE_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif #include -#include -#include +#include #include +#include +#include #include -#include #include -#include #if CLASP_HAS_THREADS #include + namespace Clasp { - //! Options for controlling enumeration and solving. - struct SolveOptions : Clasp::mt::ParallelSolveOptions, EnumOptions {}; -} +//! Options for controlling enumeration and solving. +struct SolveOptions + : mt::ParallelSolveOptions + , EnumOptions {}; +} // namespace Clasp #else +#include namespace Clasp { - struct SolveOptions : Clasp::BasicSolveOptions, EnumOptions {}; -} +struct SolveOptions + : BasicSolveOptions + , EnumOptions {}; +} // namespace Clasp +#endif + +#include +#ifndef SIGALRM +#define SIGALRM 14 #endif /*! @@ -69,398 +75,398 @@ namespace Clasp { //! Configuration object for configuring solving via the ClaspFacade. class ClaspConfig : public BasicSatConfig { public: - //! Interface for injecting user-provided configurations. - class Configurator { - public: - virtual ~Configurator(); - virtual void prepare(SharedContext&); - virtual bool applyConfig(Solver& s) = 0; - virtual void unfreeze(SharedContext&); - }; - typedef BasicSatConfig UserConfig; - typedef Solver** SolverIt; - typedef Asp::LogicProgram::AspOptions AspOptions; - ClaspConfig(); - ~ClaspConfig(); - // Base interface - void prepare(SharedContext&); - void reset(); - Configuration* config(const char*); - //! Adds an unfounded set checker to the given solver if necessary. - /*! - * If asp.suppMod is false and the problem in s is a non-tight asp-problem, - * the function adds an unfounded set checker to s. - */ - bool addPost(Solver& s) const; - // own interface - UserConfig* testerConfig() const { return tester_; } - UserConfig* addTesterConfig(); - //! Registers c as additional callback for when addPost() is called. - /*! - * \param c Additional configuration to apply. - * \param ownership If Ownership_t::Acquire, ownership of c is transferred to the configuration object. - * \param once Whether c should be called once in the first step or also in each subsequent step. - */ - void addConfigurator(Configurator* c, Ownership_t::Type ownership = Ownership_t::Retain, bool once = true); - void unfreeze(SharedContext&); - SolveOptions solve; /*!< Options for solve algorithm and enumerator. */ - AspOptions asp; /*!< Options for asp preprocessing. */ - ParserOptions parse; /*!< Options for input parser. */ + //! Interface for injecting user-provided configurations. + class Configurator { + public: + virtual ~Configurator(); + virtual void prepare(SharedContext&); + virtual bool applyConfig(Solver& s) = 0; + virtual void unfreeze(SharedContext&); + }; + using UserConfig = BasicSatConfig; + using AspOptions = Asp::LogicProgram::AspOptions; + ClaspConfig(); + ~ClaspConfig() override; + // Base interface + void prepare(SharedContext&) override; + void reset() override; + Configuration* config(const char*) override; + //! Adds an unfounded set checker to the given solver if necessary. + /*! + * If asp.suppMod is false and the problem in s is a non-tight asp-problem, + * the function adds an unfounded set checker to s. + */ + bool addPost(Solver& s) const override; + // own interface + [[nodiscard]] UserConfig* testerConfig() const { return tester_.get(); } + UserConfig* addTesterConfig(); + //! Registers c as additional callback for when addPost() is called. + /*! + * \param c Additional configuration to apply. + * \param once Whether c should be called once in the first step or also in each subsequent step. + */ + void addConfigurator(Configurator& c, bool once = true); + void unfreeze(SharedContext&); + SolveOptions solve; //!< Options for solve algorithm and enumerator. + AspOptions asp; //!< Options for asp preprocessing. + ParserOptions parse; //!< Options for input parser. private: - struct Impl; - ClaspConfig(const ClaspConfig&); - ClaspConfig& operator=(const ClaspConfig&); - UserConfig* tester_; - Impl* impl_; + struct Impl; + std::unique_ptr tester_; + std::unique_ptr impl_; }; ///////////////////////////////////////////////////////////////////////////////////////// // ClaspFacade ///////////////////////////////////////////////////////////////////////////////////////// //! Result of a solving step. struct SolveResult { - //! Possible solving results. - enum Base { - UNKNOWN = 0, //!< Satisfiability unknown - a given solve limit was hit. - SAT = 1, //!< Problem is satisfiable (a model was found). - UNSAT = 2, //!< Problem is unsatisfiable. - }; - //! Additional flags applicable to a solve result. - enum Ext { - EXT_EXHAUST = 4, //!< Search space is exhausted. - EXT_INTERRUPT= 8, //!< The run was interrupted from outside. - }; - bool sat() const { return *this == SAT; } - bool unsat() const { return *this == UNSAT; } - bool unknown() const { return *this == UNKNOWN; } - bool exhausted() const { return (flags & EXT_EXHAUST) != 0; } - bool interrupted()const { return (flags & EXT_INTERRUPT) != 0; } - operator Base() const { return static_cast(flags & 3u);} - uint8 flags; //!< Set of Base and Ext flags. - uint8 signal; //!< Term signal or 0. + //! Possible solving results. + enum Res { + res_unknown = 0, //!< Satisfiability unknown - a given solve limit was hit. + res_sat = 1, //!< Problem is satisfiable (a model was found). + res_unsat = 2, //!< Problem is unsatisfiable. + }; + //! Additional flags applicable to a solve result. + enum Ext { + ext_exhaust = 4, //!< Search space is exhausted. + ext_interrupt = 8, //!< The run was interrupted from outside. + }; + [[nodiscard]] constexpr bool sat() const { return Potassco::test_any(flags, res_sat); } + [[nodiscard]] constexpr bool unsat() const { return Potassco::test_any(flags, res_unsat); } + [[nodiscard]] constexpr bool unknown() const { return static_cast(*this) == res_unknown; } + [[nodiscard]] constexpr bool exhausted() const { return Potassco::test_any(flags, ext_exhaust); } + [[nodiscard]] constexpr bool interrupted() const { return Potassco::test_any(flags, ext_interrupt); } + constexpr operator Res() const { return static_cast(flags & 3u); } + + uint8_t flags; //!< Set of Base and Ext flags. + uint8_t signal; //!< Term signal or 0. }; //! A bitmask type for representing supported solve modes. -struct SolveMode_t { - //! Named constants. - POTASSCO_ENUM_CONSTANTS(SolveMode_t, - Default = 0, /**< Solve synchronously in current thread. */ - Async = 1, /**< Solve asynchronously in worker thread. */ - Yield = 2, /**< Yield models one by one via handle. */ - AsyncYield); - friend inline SolveMode_t operator|(SolveMode_t::E x, SolveMode_t::E y) { return SolveMode_t(static_cast(x)|static_cast(y)); } - friend inline SolveMode_t operator|(SolveMode_t x, SolveMode_t::E y) { return SolveMode_t(static_cast(x)|static_cast(y)); } - friend inline SolveMode_t operator|(SolveMode_t::E x, SolveMode_t y) { return SolveMode_t(static_cast(x)|static_cast(y)); } +enum class SolveMode : uint32_t { + def = 0, //!< Solve synchronously in current thread. + async = 1, //!< Solve asynchronously in worker thread. + yield = 2, //!< Yield models one by one via handle. + async_yield }; +POTASSCO_ENABLE_BIT_OPS(SolveMode); + //! Provides a simplified interface to the services of the clasp library. -class ClaspFacade : public ModelHandler { - struct SolveData; - struct SolveStrategy; +class ClaspFacade final : public ModelHandler { + struct SolveData; + struct SolveStrategy; + public: - //! A handle to a possibly asynchronously computed SolveResult. - class SolveHandle { - public: - typedef SolveResult Result; - typedef const Model* ModelRef; - typedef const LitVec* CoreRef; - explicit SolveHandle(SolveStrategy*); - SolveHandle(const SolveHandle&); - ~SolveHandle(); - SolveHandle& operator=(SolveHandle temp) { swap(*this, temp); return *this; } - friend void swap(SolveHandle& lhs, SolveHandle& rhs) { std::swap(lhs.strat_, rhs.strat_); } - /*! - * \name Blocking functions - * @{ */ - //! Waits until a result is ready and returns it. - Result get() const; - //! Returns an unsat core if get() returned unsat under assumptions. - CoreRef unsatCore() const; - //! Waits until a result is ready and returns it if it is a model. - /*! - * \note If the corresponding solve operation was not started with - * SolveMode_t::Yield, the function always returns 0. - * \note A call to resume() invalidates the returned model and starts - * the search for the next model. - */ - ModelRef model() const; - //! Waits until a result is ready. - void wait() const; - //! Waits for a result but for at most sec seconds. - bool waitFor(double sec)const; - //! Tries to cancel the active operation. - void cancel() const; - //! Behaves like resume() followed by return model() != 0. - bool next() const; - //@} - /*! - * \name Non-blocking functions - * @{ */ - //! Tests whether a result is ready. - bool ready() const; - //! Tests whether the operation was interrupted and if so returns the interruption signal. - int interrupted() const; - //! Tests whether a result is ready and has a stored exception. - bool error() const; - //! Tests whether the operation is still active. - bool running() const; - //! Releases ownership of the active model and schedules search for the next model. - void resume() const; - //@} - private: - SolveStrategy* strat_; - }; - typedef SolveResult Result; - typedef Potassco::AbstractStatistics AbstractStatistics; - //! Type summarizing one or more solving steps. - struct Summary { - typedef const ClaspFacade* FacadePtr; - void init(ClaspFacade& f); - //! Logic program elements added in the current step or 0 if not an asp problem. - const Asp::LpStats* lpStep() const; - //! Logic program stats or 0 if not an asp problem. - const Asp::LpStats* lpStats() const; - //! Active problem. - const SharedContext& ctx() const { return facade->ctx; } - /*! - * \name Result functions - * Solve and enumeration result - not accumulated. - * @{ - */ - bool sat() const { return result.sat(); } - bool unsat() const { return result.unsat(); } - bool complete() const { return result.exhausted(); } - bool optimum() const { return costs() && (complete() || model()->opt); } - const Model* model() const; - const LitVec* unsatCore() const; - const char* consequences() const; /**< Cautious/brave reasoning active? */ - bool optimize() const; /**< Optimization active? */ - const SumVec* costs() const; /**< Models have associated costs? */ - uint64 optimal() const; /**< Number of optimal models found. */ - bool hasLower() const; - SumVec lower() const; - //@} - //! Visits this summary object. - void accept(StatsVisitor& out) const; - FacadePtr facade; //!< Facade object of this run. - double totalTime; //!< Total wall clock time. - double cpuTime; //!< Total cpu time. - double solveTime; //!< Wall clock time for solving. - double unsatTime; //!< Wall clock time to prove unsat. - double satTime; //!< Wall clock time to first model. - uint64 numEnum; //!< Total models enumerated. - uint64 numOptimal;//!< Optimal models enumerated. - uint32 step; //!< Step number (multishot solving). - Result result; //!< Result of step. - }; - ClaspFacade(); - ~ClaspFacade(); + //! A handle to a possibly asynchronously computed SolveResult. + class SolveHandle { + public: + using Result = SolveResult; + using ModelRef = const Model*; + explicit SolveHandle(SolveStrategy*); + SolveHandle(const SolveHandle&); + ~SolveHandle(); + SolveHandle& operator=(SolveHandle temp) { + swap(*this, temp); + return *this; + } + friend void swap(SolveHandle& lhs, SolveHandle& rhs) noexcept { std::swap(lhs.strat_, rhs.strat_); } + /*! + * \name Blocking functions + * @{ */ + //! Waits until a result is ready and returns it. + [[nodiscard]] Result get() const; + //! Returns an unsat core if `get()` returned unsat under assumptions. + [[nodiscard]] LitView unsatCore() const; + //! Waits until a result is ready and returns it if it is a model. + /*! + * \note If the active solve operation was not started with + * SolveMode_t::yield, the function always returns nullptr. + * \note A call to resume() invalidates the returned model and starts + * the search for the next model. + */ + [[nodiscard]] ModelRef model() const; + //! Waits until a result is ready. + void wait() const; + //! Waits for a result but for at most sec seconds. + [[nodiscard]] bool waitFor(double sec) const; + //! Tries to cancel the active operation. + void cancel() const; + //! Behaves like resume() followed by return model() != nullptr. + [[nodiscard]] bool next() const; + //@} + /*! + * \name Non-blocking functions + * @{ */ + //! Tests whether a result is ready. + [[nodiscard]] bool ready() const; + //! Tests whether the operation was interrupted and if so returns the interruption signal. + [[nodiscard]] int interrupted() const; + //! Tests whether a result is ready and has a stored exception. + [[nodiscard]] bool error() const; + //! Tests whether the operation is still active. + [[nodiscard]] bool running() const; + //! Releases ownership of the active model and schedules search for the next model. + void resume() const; + //@} + private: + SolveStrategy* strat_; + }; + using Result = SolveResult; + using AbstractStatistics = Potassco::AbstractStatistics; + //! Type summarizing one or more solving steps. + struct Summary { + using FacadePtr = const ClaspFacade*; + void init(ClaspFacade& f); + //! Logic program elements added in the current step or nullptr if not an asp problem. + [[nodiscard]] const Asp::LpStats* lpStep() const; + //! Logic program stats or nullptr if not an asp problem. + [[nodiscard]] const Asp::LpStats* lpStats() const; + //! Active problem. + [[nodiscard]] const SharedContext& ctx() const { return facade->ctx; } + /*! + * \name Result functions + * Solve and enumeration result - not accumulated. + * @{ + */ + [[nodiscard]] bool sat() const { return result.sat(); } + [[nodiscard]] bool unsat() const { return result.unsat(); } + [[nodiscard]] bool complete() const { return result.exhausted(); } + [[nodiscard]] bool optimum() const { return hasCosts() && (complete() || model()->opt); } + [[nodiscard]] const Model* model() const; + [[nodiscard]] LitView unsatCore() const; + [[nodiscard]] const char* consequences() const; /**< Cautious/brave reasoning active? */ + [[nodiscard]] bool optimize() const; /**< Optimization active? */ + [[nodiscard]] SumView costs() const; /**< Models have associated costs? */ + [[nodiscard]] uint64_t optimal() const; /**< Number of optimal models found. */ + [[nodiscard]] bool hasCosts() const; + [[nodiscard]] bool hasLower() const; + [[nodiscard]] SumView lower() const; + //@} + //! Visits this summary object. + void accept(StatsVisitor& out) const; + FacadePtr facade; //!< Facade object of this run. + double totalTime; //!< Total wall clock time. + double cpuTime; //!< Total cpu time. + double solveTime; //!< Wall clock time for solving. + double unsatTime; //!< Wall clock time to prove unsat. + double satTime; //!< Wall clock time to first model. + uint64_t numEnum; //!< Total models enumerated. + uint64_t numOptimal; //!< Optimal models enumerated. + uint32_t step; //!< Step number (multishot solving). + Result result; //!< Result of step. + }; + ClaspFacade(); + ~ClaspFacade() override; - /*! - * \name Query functions - * Functions for checking the state of this object. - * @{ */ - //! Returns whether the problem is still valid. - bool ok() const { return program() ? program()->ok() : ctx.ok(); } - //! Returns whether the active step is ready for solving. - bool prepared() const; - //! Returns whether the active step is currently being solved. - bool solving() const; - //! Returns whether the active step has been solved, i.e., has a result. - bool solved() const; - //! Returns whether solving of the active step was interrupted. - bool interrupted() const; - //! Returns the summary of the active step. - const Summary& summary() const { return step_; } - //! Returns the summary of the active (accu = false) or all steps. - const Summary& summary(bool accu) const; - //! Returns solving statistics or throws std::logic_error if solving() is true. - AbstractStatistics*getStats() const; - //! Returns the active configuration. - const ClaspConfig* config() const { return config_;} - //! Returns the current solving step (starts at 0). - int step() const { return (int)step_.step;} - //! Returns the result of the active step (unknown if run is not yet completed). - Result result() const { return step_.result; } - //! Returns the active program or 0 if it was already released. - ProgramBuilder* program() const { return builder_.get(); } - //! Returns whether program updates are enabled. - bool incremental() const; - //! Returns the active enumerator or 0 if there is none. - Enumerator* enumerator() const; - //@} + /*! + * \name Query functions. + * Functions for checking the state of this object. + * @{ */ + //! Returns whether the problem is still valid. + [[nodiscard]] bool ok() const { return program() ? program()->ok() : ctx.ok(); } + //! Returns whether the active step is ready for solving. + [[nodiscard]] bool prepared() const; + //! Returns whether the active step is currently being solved. + [[nodiscard]] bool solving() const; + //! Returns whether the active step has been solved, i.e., has a result. + [[nodiscard]] bool solved() const; + //! Returns whether solving of the active step was interrupted. + [[nodiscard]] bool interrupted() const; + //! Returns the summary of the active step. + [[nodiscard]] const Summary& summary() const { return step_; } + //! Returns the summary of the active (accu = false) or all steps. + [[nodiscard]] const Summary& summary(bool accu) const; + //! Returns solving statistics or throws std::logic_error if solving() is true. + [[nodiscard]] AbstractStatistics* getStats() const; + //! Returns the active configuration. + [[nodiscard]] const ClaspConfig* config() const { return config_; } + //! Returns the current solving step (starts at 0). + [[nodiscard]] int step() const { return static_cast(step_.step); } + //! Returns the result of the active step (unknown if run is not yet completed). + [[nodiscard]] Result result() const { return step_.result; } + //! Returns the active program or nullptr if it was already released. + [[nodiscard]] ProgramBuilder* program() const { return builder_.get(); } + //! Returns the active program if it is of type Asp::LogicProgram. + [[nodiscard]] Asp::LogicProgram* asp() const; + //! Returns whether program updates are enabled. + [[nodiscard]] bool incremental() const; + //! Returns the active enumerator or nullptr if there is none. + [[nodiscard]] Enumerator* enumerator() const; + //@} - //! Event type used to signal that a new step has started. - struct StepStart : Event_t { - explicit StepStart(const ClaspFacade& f) : Event_t(subsystem_facade, verbosity_quiet), facade(&f) {} - const ClaspFacade* facade; - }; - //! Event type used to signal that a solve step has terminated. - struct StepReady : Event_t { - explicit StepReady(const Summary& x) : Event_t(subsystem_facade, verbosity_quiet), summary(&x) {} - const Summary* summary; - }; + //! Event type used to signal that a new step has started. + struct StepStart : Event { + explicit StepStart(const ClaspFacade& f) : Event(this, subsystem_facade, verbosity_quiet), facade(&f) {} + const ClaspFacade* facade; + }; + //! Event type used to signal that a solve step has terminated. + struct StepReady : Event { + explicit StepReady(const Summary& x) : Event(this, subsystem_facade, verbosity_quiet), summary(&x) {} + const Summary* summary; + }; - SharedContext ctx; //!< Context-object used to store problem. + SharedContext ctx; //!< Context-object used to store problem. - /*! - * \name Start functions - * Functions for defining a problem. - * Calling one of the start functions discards any previous problem - * and emits a StepStart event. - * @{ */ - //! Starts definition of an ASP-problem. - Asp::LogicProgram& startAsp(ClaspConfig& config, bool enableProgramUpdates = false); - //! Starts definition of a SAT-problem. - SatBuilder& startSat(ClaspConfig& config); - //! Starts definition of a PB-problem. - PBBuilder& startPB(ClaspConfig& config); - //! Starts definition of a problem of type t. - ProgramBuilder& start(ClaspConfig& config, ProblemType t); - //! Starts definition of a problem given in stream. - ProgramBuilder& start(ClaspConfig& config, std::istream& stream); - //! Enables support for program updates if supported by the program. - /*! - * \pre program() != 0 and not prepared(). - * \return true if program updates are supported. Otherwise, false. - */ - bool enableProgramUpdates(); - //! Enables support for (asynchronous) solve interrupts. - void enableSolveInterrupts(); - //! Disables program disposal in non-incremental mode after problem has been prepared for solving. - /*! - * \pre program() != 0 and not prepared(). - */ - void keepProgram(); - //! Tries to detect the problem type from the given input stream. - static ProblemType detectProblemType(std::istream& str); - //! Tries to read the next program part from the stream passed to start(). - /*! - * \return false if nothing was read because the stream is exhausted, solving was interrupted, - * or the problem is unconditionally unsat. - */ - bool read(); + /*! + * \name Start functions. + * Functions for defining a problem. + * Calling one of the start functions discards any previous problem + * and emits a StepStart event. + * @{ */ + //! Starts definition of an ASP-problem. + Asp::LogicProgram& startAsp(ClaspConfig& config, bool enableProgramUpdates = false); + //! Starts definition of a SAT-problem. + SatBuilder& startSat(ClaspConfig& config); + //! Starts definition of a PB-problem. + PBBuilder& startPB(ClaspConfig& config); + //! Starts definition of a problem of type t. + ProgramBuilder& start(ClaspConfig& config, ProblemType t); + //! Starts definition of a problem given in stream. + ProgramBuilder& start(ClaspConfig& config, std::istream& stream); + //! Enables support for program updates if supported by the program. + /*! + * \pre program() != nullptr and not prepared(). + * \return true if program updates are supported. Otherwise, false. + */ + bool enableProgramUpdates(); + //! Enables support for (asynchronous) solve interrupts. + void enableSolveInterrupts(); + //! Disables program disposal in non-incremental mode after problem has been prepared for solving. + /*! + * \pre program() != nullptr and not prepared(). + */ + void keepProgram(); + //! Tries to detect the problem type from the given input stream. + static ProblemType detectProblemType(std::istream& str); + //! Tries to read the next program part from the stream passed to start(). + /*! + * \return false if nothing was read because the stream is exhausted, solving was interrupted, + * or the problem is unconditionally unsat. + */ + bool read(); - //@} + //@} - /*! - * \name Solve functions - * Functions for solving a problem. - * @{ */ + /*! + * \name Solve functions. + * Functions for solving a problem. + * @{ */ - enum EnumMode { enum_volatile, enum_static }; + enum EnumMode { enum_volatile, enum_static }; - //! Finishes the definition of a problem and prepares it for solving. - /*! - * \pre !solving() - * \post prepared() || !ok() - * \param m Mode to be used for handling enumeration-related knowledge. - * If m is enum_volatile, enumeration knowledge is learnt under an - * assumption that is retracted on program update. Otherwise, - * no special assumption is used and enumeration-related knowledge - * might become unretractable. - * \note If solved() is true, prepare() first starts a new solving step. - */ - void prepare(EnumMode m = enum_volatile); + //! Finishes the definition of a problem and prepares it for solving. + /*! + * \pre !solving() + * \post prepared() || !ok() + * \param m Mode to be used for handling enumeration-related knowledge. + * If m is enum_volatile, enumeration knowledge is learnt under an + * assumption that is retracted on program update. Otherwise, + * no special assumption is used and enumeration-related knowledge + * might become unretractable. + * \note If solved() is true, prepare() first starts a new solving step. + */ + void prepare(EnumMode m = enum_volatile); - //! Solves the current problem. - /*! - * If prepared() is false, the function first calls prepare() to prepare the problem for solving. - * \pre !solving() - * \post solved() - * \param a A list of unit-assumptions under which solving should operate. - * \param eh An optional event handler that is notified on each model and - * once the solve operation has completed. - */ - Result solve(const LitVec& a = LitVec(), EventHandler* eh = 0); - Result solve(EventHandler* eh) { return solve(LitVec(), eh); } + //! Solves the current problem. + /*! + * If prepared() is false, the function first calls prepare() to prepare the problem for solving. + * \pre !solving() + * \post solved() + * \param a A list of unit-assumptions under which solving should operate. + * \param eh An optional event handler that is notified on each model and + * once the solve operation has completed. + */ + Result solve(LitView a = {}, EventHandler* eh = nullptr); + Result solve(EventHandler* eh) { return solve({}, eh); } - //! Solves the current problem using the given solve mode. - /*! - * If prepared() is false, the function first calls prepare() to prepare the problem for solving. - * \pre !solving() - * \param mode The solve mode to use. - * \param a A list of unit-assumptions under which solving should operate. - * \param eh An optional event handler that is notified on each model and - * once the solve operation has completed. - * \throws std::logic_error if mode contains SolveMode_t::Async but thread support is disabled. - * \throws std::runtime_error if mode contains SolveMode_t::Async but solve is unable to start a thread. - * - * \note If mode contains SolveMode_t::Async, the optional event handler is notified in the - * context of the asynchronous thread. - * - * \note If mode contains SolveMode_t::Yield, models are signaled one by one via the - * returned handle object. - * It is the caller's responsibility to finish the solve operation, - * either by extracting models until SolveHandle::model() returns 0, or - * by calling SolveHandle::cancel(). - * - * To iterate over models one by one use a loop like: - * \code - * SolveMode_t p = ... - * for (auto it = facade.solve(p|SolveMode_t::Yield); it.model(); it.resume()) { - * printModel(*it.model()); - * } - * \endcode - */ - SolveHandle solve(SolveMode_t mode, const LitVec& a = LitVec(), EventHandler* eh = 0); + //! Solves the current problem using the given solve mode. + /*! + * If prepared() is false, the function first calls prepare() to prepare the problem for solving. + * \pre !solving() + * \param mode The solve mode to use. + * \param a A list of unit-assumptions under which solving should operate. + * \param eh An optional event handler that is notified on each model and + * once the solve operation has completed. + * \throws std::logic_error if mode contains SolveMode_t::async but thread support is disabled. + * \throws std::runtime_error if mode contains SolveMode_t::async but solve is unable to start a thread. + * + * \note If mode contains SolveMode_t::async, the optional event handler is notified in the + * context of the asynchronous thread. + * + * \note If mode contains SolveMode_t::yield, models are signaled one by one via the + * returned handle object. + * It is the caller's responsibility to finish the solve operation, + * either by extracting models until SolveHandle::model() returns nullptr, or + * by calling SolveHandle::cancel(). + * + * To iterate over models one by one use a loop like: + * \code + * SolveMode_t p = ... + * for (auto it = facade.solve(p|SolveMode_t::yield); it.model(); it.resume()) { + * printModel(*it.model()); + * } + * \endcode + */ + SolveHandle solve(SolveMode mode, LitView a = {}, EventHandler* eh = nullptr); - //! Tries to interrupt the active solve operation. - /*! - * The function sends the given signal to the active solve operation. - * If no solve operation is active (i.e. solving() is false), the signal - * is queued and applied to the next solve operation. - * - * \param sig The signal to raise or 0, to re-raises a previously queued signal. - * \return false if no operation was interrupted, because - * there is no active solve operation, - * or the operation does not support interrupts, - * or sig was 0 and there was no queued signal. - * - * \see enableSolveInterrupts() - */ - bool interrupt(int sig); + //! Tries to interrupt the active solve operation. + /*! + * The function sends the given signal to the active solve operation. + * If no solve operation is active (i.e. solving() is false), the signal + * is queued and applied to the next solve operation. + * + * \param sig The signal to raise or 0, to re-raises a previously queued signal. + * \return false if no operation was interrupted, because + * there is no active solve operation, + * or the operation does not support interrupts, + * or sig was 0 and there was no queued signal. + * + * \see enableSolveInterrupts() + */ + bool interrupt(int sig); - //! Forces termination of the current solving step. - /*! - * \post solved() - * \return summary(true) - */ - const Summary& shutdown(); + //! Forces termination of the current solving step. + /*! + * \post solved() + * \return summary(true) + */ + const Summary& shutdown(); - //! Starts update of the active problem. - /*! - * \pre solving() is false and program updates are enabled (incremental() is true). - * \post !solved() - * \param updateConfig If true, the function applies any configuration changes. - * \param sigQ An action to be performed for any queued signal. - * The default is to apply the signal to the next solve operation, while - * SIGN_IGN can be used to discard queued signals. - */ - ProgramBuilder& update(bool updateConfig, void (*sigQ)(int)); - ProgramBuilder& update(bool updateConfig = false); - //@} + //! Starts update of the active problem. + /*! + * \pre solving() is false and program updates are enabled (incremental() is true). + * \post !solved() + * \param updateConfig If true, the function applies any configuration changes. + * \param sigQ An action to be performed for any queued signal. + * The default is to apply the signal to the next solve operation, while + * SIGN_IGN can be used to discard queued signals. + */ + ProgramBuilder& update(bool updateConfig, void (*sigQ)(int)); + ProgramBuilder& update(bool updateConfig = false) { return update(updateConfig, SIG_DFL); } + //@} private: - struct Statistics; - typedef SingleOwnerPtr BuilderPtr; - typedef SingleOwnerPtr SolvePtr; - typedef SingleOwnerPtr SummaryPtr; - typedef SingleOwnerPtr StatsPtr; - void init(ClaspConfig& cfg, bool discardProblem); - void initBuilder(ProgramBuilder* in); - bool isAsp() const { return program() && type_ == Problem_t::Asp; } - void discardProblem(); - void startStep(uint32 num); - Result stopStep(int signal, bool complete); - void updateStats(); - bool onModel(const Solver& s, const Model& m); - void doUpdate(ProgramBuilder* p, bool updateConfig, void (*sig)(int)); - ProblemType type_; - Summary step_; - LitVec assume_; - ClaspConfig* config_; - BuilderPtr builder_; - SummaryPtr accu_; - StatsPtr stats_; // statistics: only if requested - SolvePtr solve_; // NOTE: last so that it is destroyed first; + struct Statistics; + using SolvePtr = std::unique_ptr; + using BuilderPtr = std::unique_ptr; + using SummaryPtr = std::unique_ptr; + using StatsPtr = std::unique_ptr; + void init(ClaspConfig& cfg, bool discardProblem); + void initBuilder(ProgramBuilder* in); + void discardProblem(); + void startStep(uint32_t num); + Result stopStep(int signal, bool complete); + void updateStats(); + bool onModel(const Solver& s, const Model& m) override; + void doUpdate(ProgramBuilder* p, bool updateConfig, void (*sig)(int)); + ProblemType type_{}; + Summary step_{}; + LitVec assume_; + SumVec lower_; + ClaspConfig* config_ = nullptr; + BuilderPtr builder_; + SummaryPtr accu_; + StatsPtr stats_; // statistics: only if requested + SolvePtr solve_; // NOTE: last so that it is destroyed first; }; /** @@ -473,5 +479,4 @@ class ClaspFacade : public ModelHandler { //!@} -} -#endif +} // namespace Clasp diff --git a/clasp/claspfwd.h b/clasp/claspfwd.h index a8a5ea3..dbdcf6e 100644 --- a/clasp/claspfwd.h +++ b/clasp/claspfwd.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2013-2017 Benjamin Kaufmann +// Copyright (c) 2013-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,8 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLASP_FWD_H_INCLUDED -#define CLASP_CLASP_FWD_H_INCLUDED +#pragma once /*! * \file * \brief Forward declarations of important clasp and potassco types. @@ -32,11 +31,11 @@ namespace Potassco { class TheoryAtom; class TheoryTerm; class TheoryData; -template struct Span; -struct Heuristic_t; +enum class DomModifier : unsigned; class BufferedStream; class AbstractStatistics; -} +class ConstString; +} // namespace Potassco //! Root namespace for all types and functions of libclasp. namespace Clasp { class SharedContext; @@ -48,18 +47,13 @@ class ConstraintInfo; class Solver; struct Model; //! Supported problem types. -struct Problem_t { - enum Type {Sat = 0, Pb = 1, Asp = 2}; -}; -typedef Problem_t::Type ProblemType; +enum class ProblemType { sat = 0, pb = 1, asp = 2 }; class ProgramBuilder; class ProgramParser; class SatBuilder; class PBBuilder; class ExtDepGraph; -class ConstString; -typedef Potassco::Span StrView; -typedef Potassco::Heuristic_t DomModType; +using DomModType = Potassco::DomModifier; //! Namespace for types and functions used to define ASP programs. namespace Asp { class LogicProgram; @@ -72,6 +66,5 @@ class PrgHead; class PrgNode; class PrgDepGraph; struct PrgEdge; -}} - -#endif +} // namespace Asp +} // namespace Clasp diff --git a/clasp/clause.h b/clasp/clause.h index ebc74ae..b458d7a 100644 --- a/clasp/clause.h +++ b/clasp/clause.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,12 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLAUSE_H_INCLUDED -#define CLASP_CLAUSE_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include namespace Clasp { @@ -37,48 +32,47 @@ namespace Clasp { */ class SharedLiterals { public: - //! Creates a shareable (ref-counted) object containing the literals in lits. - /*! - * \note The reference count is set to numRefs. - */ - static SharedLiterals* newShareable(const LitVec& lits, ConstraintType t, uint32 numRefs = 1) { - return newShareable(!lits.empty() ? &lits[0]:0, static_cast(lits.size()), t, numRefs); - } - static SharedLiterals* newShareable(const Literal* lits, uint32 size, ConstraintType t, uint32 numRefs = 1); - - //! Returns a pointer to the beginning of the literal array. - const Literal* begin() const { return lits_; } - //! Returns a pointer to the end of the literal array. - const Literal* end() const { return lits_+size(); } - //! Returns the number of literals in the array. - uint32 size() const { return size_type_ >> 2; } - //! Returns the type of constraint from which the literals originated. - ConstraintType type() const { return ConstraintType( size_type_ & uint32(3) ); } - //! Simplifies the literals w.r.t to the assignment in s. - /*! - * Returns the number of non-false literals in this object or 0 if - * the array contains a true literal. - * \note If the object is currently not shared, simplify() removes - * all false literals from the array. - */ - uint32 simplify(Solver& s); - - void release() { release(1); } - void release(uint32 numRefs); - SharedLiterals* share(); - bool unique() const { return refCount_ <= 1; } - uint32 refCount() const { return refCount_; } + //! Creates a shareable (ref-counted) object containing the literals in lits. + /*! + * \note The reference count is set to numRefs. + */ + static SharedLiterals* newShareable(LitView lits, ConstraintType t, uint32_t numRefs = 1); + SharedLiterals(SharedLiterals&&) = delete; + + //! Returns a pointer to the beginning of the literal array. + [[nodiscard]] const Literal* begin() const { return lits_; } + //! Returns a pointer to the end of the literal array. + [[nodiscard]] const Literal* end() const { return lits_ + size(); } + //! Returns a read-only view of the literal array. + [[nodiscard]] LitView literals() const { return {lits_, size()}; } + //! Returns the number of literals in the array. + [[nodiscard]] uint32_t size() const { return size_type_ >> 2; } + //! Returns the type of constraint from which the literals originated. + [[nodiscard]] ConstraintType type() const { return static_cast(size_type_ & 3u); } + //! Simplifies the literals w.r.t to the assignment in s. + /*! + * Returns the number of non-false literals in this object or 0 if + * the array contains a true literal. + * \note If the object is currently not shared, simplify() removes + * all false literals from the array. + */ + uint32_t simplify(Solver& s); + + void release() { release(1); } + void release(int numRefs); + SharedLiterals* share(); + [[nodiscard]] bool unique() const { return refCount() <= 1u; } + [[nodiscard]] uint32_t refCount() const { return refCount_; } + private: - void destroy(); - SharedLiterals(const Literal* lits, uint32 size, ConstraintType t, uint32 numRefs); - SharedLiterals(const SharedLiterals&); - SharedLiterals& operator=(const SharedLiterals&); - typedef Clasp::Atomic_t::type RCType; - RCType refCount_; - uint32 size_type_; -POTASSCO_WARNING_BEGIN_RELAXED - Literal lits_[0]; -POTASSCO_WARNING_END_RELAXED + SharedLiterals(LitView lits, ConstraintType t, uint32_t numRefs); + ~SharedLiterals() = default; + + RefCount refCount_; + uint32_t size_type_; + POTASSCO_WARNING_BEGIN_RELAXED + Literal lits_[0]; + POTASSCO_WARNING_END_RELAXED }; //! A helper-class for creating/adding clauses. @@ -90,413 +84,432 @@ POTASSCO_WARNING_END_RELAXED */ class ClauseCreator { public: - typedef ConstraintInfo ClauseInfo; - //! Creates a new ClauseCreator object. - /*! - * \param s the Solver in which to store created clauses. - */ - explicit ClauseCreator(Solver* s = 0); - //! Sets the solver in which created clauses are stored. - void setSolver(Solver& s) { solver_ = &s; } - //! Adds additional flags to be applied in end(). - void addDefaultFlags(uint32 f) { flags_ |= f; } - //! Reserve space for a clause of size s. - void reserve(LitVec::size_type s) { literals_.reserve(s); } - //! Discards the current clause. - void clear() { literals_.clear(); } - - //! Status of a clause. - /*! - * For a clause with literals [l1,...,ln], status is one of: - */ - enum Status { - // BASE STATUS - status_open = 0, //!< Clause is neither sat, unsat, or unit. - status_sat = 1, //!< At least one literal is true. - status_unsat = 2, //!< All literals are false. - status_unit = 4, //!< All but one literal false. - // COMPLEX STATUS - status_sat_asserting= 5, //!< Sat but literal is implied on lower dl. - status_asserting = 6, //!< Unsat but literal is implied on second highest dl. - status_subsumed = 9, //!< Sat and one literal is true on level 0. - status_empty = 10, //!< Unsat and all literals are false on level 0. - }; - //! A type for storing the result of a clause insertion operation. - struct Result { - explicit Result(ClauseHead* loc = 0, Status st = status_open) - : local(loc) - , status(st) {} - ClauseHead* local; - Status status; - //! Returns false is clause is conflicting w.r.t current assignment. - bool ok() const { return (status & status_unsat) == 0; } - //! Returns true if the clause implies a literal (possibly after backtracking). - bool unit() const { return (status & status_unit) != 0; } - operator bool() const { return ok(); } - }; - //! Starts the creation of a new clause. - /*! - * \pre s.decisionLevel() == 0 || t != Constraint_t::Static - */ - ClauseCreator& start(ConstraintType t = Constraint_t::Static); - //! Sets the initial activity of the clause under construction. - ClauseCreator& setActivity(uint32 a) { extra_.setActivity(a); return *this; } - //! Sets the initial literal block distance of the clause under construction. - ClauseCreator& setLbd(uint32 lbd) { extra_.setLbd(lbd); return *this; } - //! Adds the literal p to the clause under construction. - ClauseCreator& add(const Literal& p) { literals_.push_back(p); return *this; } - //! Removes subsumed lits and orders first lits w.r.t watch order. - ClauseRep prepare(bool fullSimplify); - //! Returns the current size of the clause under construction. - uint32 size() const { return (uint32)literals_.size(); } - //! Returns the literal at the given idx. - Literal& operator[](uint32 i) { return literals_[i]; } - Literal operator[](uint32 i) const { return literals_[i]; } - //! Returns the literals of the clause under construction. - const LitVec& lits() const { return literals_; } - LitVec& lits() { return literals_; } - //! Returns the clause's type. - ConstraintType type() const { return extra_.type(); } - //! Returns the aux info of the clause under construction. - ConstraintInfo info() const { return extra_; } - //! Creates a new clause object for the clause under construction. - /*! - * \pre The clause does not contain duplicate/complementary literals or - * flags contains clause_force_simplify. - * - * \note If the clause to be added is empty, end() fails and s.hasConflict() is set to true. - * \see Result ClauseCreator::create(Solver& s, LitVec& lits, uint32 flags, const ClauseInfo& info); - */ - Result end(uint32 flags = clause_not_sat | clause_not_conflict); - - /*! - * \name Factory functions - * Functions for creating and integrating clauses. - */ - //@{ - //! Flags controlling clause creation and integration. - enum CreateFlag { - // REPRESENTATION - clause_no_add = 1, //!< Do not add clause to solver db. - clause_explicit = 2, //!< Force creation of explicit clause even if size <= 3. - // STATUS - clause_not_sat = 4, //!< Do not add clause if it is satisfied (but not asserting) w.r.t current assignment. - clause_not_root_sat = 8, //!< Do not add clause if it is satisfied w.r.t the root assignment. - clause_not_conflict = 16, //!< Do not add clause if it is conflicting w.r.t the current assignment. - // INTEGRATE - clause_no_release = 32, //!< Do not call release on shared literals. - clause_int_lbd = 64, //!< Compute lbd when integrating asserting clauses. - // PREPARE - clause_no_prepare = 128,//!< Assume clause is already ordered w.r.t watches. - clause_force_simplify= 256,//!< Call simplify() on create. - clause_no_heuristic = 512,//!< Do not notify heuristic about new clause. - // WATCH MODE - only for problem clauses - clause_watch_first =1024,//!< Watch first free literals. - clause_watch_rand =2048,//!< Watch rand literals. - clause_watch_least =4096,//!< Watch least watched literals. - }; - //! Returns the status of the given clause w.r.t s. - static Status status(const Solver& s, const Literal* clause_begin, const Literal* clause_end); - static Status status(const Solver& s, const ClauseRep& c); - - //! Returns an abstraction of p's decision level that can be used to order literals. - /*! - * The function returns a value, s.th - * order(any true literal) > order(any free literal) > order(any false literal). - * Furthermore, for equally assigned literals p and q, order(p) > order(q), iff - * level(p) > level(q). - */ - static uint32 watchOrder(const Solver& s, Literal p); - - //! Prepares the clause given in lits. - /*! - * A prepared clause [l1...ln] with n >= 2 is a clause that, - * - does not contain any duplicate or complementary literals, and - * - does not contain any subsumed literals (i.e. literals assigned on decision level 0), and - * - is partially ordered w.r.t watchOrder(), i.e., watchOrder(l1) >= watchOrder(l2), and there - * is no lj, j > 2, s.th. watchOrder(lj) > watchOrder(l2) - * . - * - * Removes subsumed literals from lits and reorders lits s.th. - * the first literals are valid watches. Furthermore, - * if flags contains clause_force_simplify, - * duplicate literals are removed from lits and tautologies are - * replaced with the single literal True. - */ - static ClauseRep prepare(Solver& s, LitVec& lits, uint32 flags, const ClauseInfo& info = ClauseInfo()); - - //! Creates a clause from the literals given in lits. - /*! - * \param s The solver to which the clause should be added. - * \param lits The literals of the clause. - * \param flags Flag set controlling creation (see ClauseCreator::CreateFlag). - * \param info Initial information (e.g. type) for the new clause. - * - * \pre !s.hasConflict() and s.decisionLevel() == 0 or extra.learnt() - * \pre lits is fully prepared or flags contains suitable prepare flags. - * - * \note - * If the given clause is unit (or asserting), the unit-resulting literal is - * asserted on the (numerical) lowest level possible but the new information - * is not immediately propagated, i.e. on return queueSize() may be greater than 0. - * - * \note - * The local representation of the clause is always attached to the solver - * but only added to the solver if clause_no_add is not contained in flags. - * Otherwise, the returned clause is owned by the caller - * and it is the caller's responsibility to manage it. Furthermore, - * learnt statistics are *not* updated automatically in that case. - * - * \see prepare() - */ - static Result create(Solver& s, LitVec& lits, uint32 flags, const ClauseInfo& info = ClauseInfo()); - - /*! - * \overload - */ - static Result create(Solver& s, const ClauseRep& rep, uint32 flags); - - //! Integrates the given clause into the current search of s. - /*! - * \pre the assignment in s is not conflicting - * \param s The solver in which the clause should be integrated. - * \param clause The clause to be integrated. - * \param flags A set of flags controlling integration (see ClauseCreator::CreateFlag). - * \param t Constraint type to use for the local representation. - * - * \note - * The function behaves similar to ClauseCreator::create() with the exception that - * it does not add local representations for implicit clauses (i.e. size <= 3) - * unless flags contains clause_explicit. - * In that case, an explicit representation is created. - * Implicit representations can only be created via ClauseCreator::create(). - * - * \note - * The function acts as a sink for the given clause (i.e. it decreases its reference count) - * unless flags contains clause_no_release. - * - * \note integrate() is intended to be called in a post propagator. - * To integrate a set of clauses F, one would use a loop like this: - * \code - * bool MyPostProp::propagate(Solver& s) { - * bool r = true; - * while (!F.empty() && r) { - * SharedLiterals* C = f.pop(); - * r = integrate(s, C, ...).ok; - * } - * return r; - * \endcode - */ - static Result integrate(Solver& s, SharedLiterals* clause, uint32 flags, ConstraintType t); - - /*! - * \overload - */ - static Result integrate(Solver& s, SharedLiterals* clause, uint32 flags); - //@} + enum CreateFlag : uint32_t; + enum Status : uint32_t; + POTASSCO_ENABLE_BIT_OPS(CreateFlag, friend); + + using ClauseInfo = ConstraintInfo; + //! Creates a new ClauseCreator object. + /*! + * \param s the Solver in which to store created clauses. + */ + explicit ClauseCreator(Solver* s = nullptr); + //! Sets the solver in which created clauses are stored. + void setSolver(Solver& s) { solver_ = &s; } + //! Adds additional flags to be applied in end(). + void addDefaultFlags(CreateFlag f) { flags_ |= f; } + //! Reserve space for a clause of size s. + void reserve(uint32_t s) { literals_.reserve(s); } + //! Discards the current clause. + void clear() { literals_.clear(); } + + //! Status of a clause. + /*! + * For a clause with literals [l1,...,ln], status is one of: + */ + enum Status : uint32_t { + // BASE STATUS + status_open = 0u, //!< Clause is neither sat, unsat, or unit. + status_sat = 1u, //!< At least one literal is true. + status_unsat = 2u, //!< All literals are false. + status_unit = 4u, //!< All but one literal false. + // COMPLEX STATUS + status_sat_asserting = 5u, //!< Sat but literal is implied on lower dl. + status_asserting = 6u, //!< Unsat but literal is implied on second-highest dl. + status_subsumed = 9u, //!< Sat and one literal is true on level 0. + status_empty = 10u, //!< Unsat and all literals are false on level 0. + }; + friend constexpr bool unitOrUnsat(Status status) { return (status & status_asserting) != 0u; } + //! A type for storing the result of a clause insertion operation. + struct Result { + explicit Result(ClauseHead* loc = nullptr, Status st = status_open) : local(loc), status(st) {} + ClauseHead* local; + Status status; + //! Returns false is clause is conflicting w.r.t current assignment. + [[nodiscard]] bool ok() const { return not Potassco::test(status, status_unsat); } + //! Returns true if the clause implies a literal (possibly after backtracking). + [[nodiscard]] bool unit() const { return Potassco::test(status, status_unit); } + explicit operator bool() const { return ok(); } + }; + //! Starts the creation of a new clause. + /*! + * \pre s.decisionLevel() == 0 || t != ConstraintType::static_ + */ + ClauseCreator& start(ConstraintType t = ConstraintType::static_); + //! Sets the initial activity of the clause under construction. + ClauseCreator& setActivity(uint32_t a) { + extra_.setActivity(a); + return *this; + } + //! Sets the initial literal block distance of the clause under construction. + ClauseCreator& setLbd(uint32_t lbd) { + extra_.setLbd(lbd); + return *this; + } + //! Adds the literal p to the clause under construction. + ClauseCreator& add(const Literal& p) { + literals_.push_back(p); + return *this; + } + //! Removes subsumed lits and orders first lits w.r.t watch order. + ClauseRep prepare(bool fullSimplify); + //! Returns the current size of the clause under construction. + [[nodiscard]] uint32_t size() const { return size32(literals_); } + //! Returns the literal at the given idx. + Literal operator[](uint32_t i) const { return literals_[i]; } + //! Returns the literals of the clause under construction. + [[nodiscard]] LitView lits() const { return literals_; } + //! Returns the clause's type. + [[nodiscard]] ConstraintType type() const { return extra_.type(); } + //! Returns the aux info of the clause under construction. + [[nodiscard]] ConstraintInfo info() const { return extra_; } + //! Creates a new clause object for the clause under construction. + /*! + * \pre The clause does not contain duplicate/complementary literals or + * flags contains clause_force_simplify. + * + * \note If the clause to be added is empty, end() fails and s.hasConflict() is set to true. + * \see Result ClauseCreator::create(Solver& s, LitVec& lits, uint32_t flags, const ClauseInfo& info); + */ + Result end(CreateFlag flags = clause_not_sat | clause_not_conflict); + + /*! + * \name Factory functions. + * Functions for creating and integrating clauses. + */ + //@{ + //! Flags controlling clause creation and integration. + enum CreateFlag : uint32_t { + // REPRESENTATION + clause_no_add = 1u, //!< Do not add clause to solver db. + clause_explicit = 2u, //!< Force creation of explicit clause even if size <= 3. + // STATUS + clause_not_sat = 4u, //!< Do not add clause if it is satisfied (but not asserting) w.r.t current assignment. + clause_not_root_sat = 8u, //!< Do not add clause if it is satisfied w.r.t the root assignment. + clause_not_conflict = 16u, //!< Do not add clause if it is conflicting w.r.t the current assignment. + // INTEGRATE + clause_no_release = 32u, //!< Do not call release on shared literals. + clause_int_lbd = 64u, //!< Compute lbd when integrating asserting clauses. + // PREPARE + clause_no_prepare = 128u, //!< Assume clause is already ordered w.r.t watches. + clause_force_simplify = 256u, //!< Call simplify() on create. + clause_no_heuristic = 512u, //!< Do not notify heuristic about new clause. + // WATCH MODE - only for problem clauses + clause_watch_first = 1024u, //!< Watch first free literals. + clause_watch_rand = 2048u, //!< Watch rand literals. + clause_watch_least = 4096u, //!< Watch least watched literals. + }; + //! Returns the status of the given clause w.r.t s. + static Status status(const Solver& s, LitView lits); + static Status status(const Solver& s, const ClauseRep& c); + + //! Returns an abstraction of p's decision level that can be used to order literals. + /*! + * The function returns a value, s.th + * order(any true literal) > order(any free literal) > order(any false literal). + * Furthermore, for equally assigned literals p and q, order(p) > order(q), iff + * level(p) > level(q). + */ + static uint32_t watchOrder(const Solver& s, Literal p); + + //! Prepares the clause given in lits. + /*! + * A prepared clause [l1...ln] with n >= 2 is a clause that, + * - does not contain any duplicate or complementary literals, and + * - does not contain any subsumed literals (i.e. literals assigned on decision level 0), and + * - is partially ordered w.r.t watchOrder(), i.e., watchOrder(l1) >= watchOrder(l2), and there + * is no lj, j > 2, s.th. watchOrder(lj) > watchOrder(l2) + * . + * + * Removes subsumed literals from lits and reorders lits s.th. + * the first literals are valid watches. Furthermore, + * if flags contains clause_force_simplify, + * duplicate literals are removed from lits and tautologies are + * replaced with the single literal True. + */ + static ClauseRep prepare(Solver& s, LitVec& lits, CreateFlag flags, const ClauseInfo& info = ClauseInfo()); + + //! Creates a clause from the literals given in lits. + /*! + * \param s The solver to which the clause should be added. + * \param lits The literals of the clause. + * \param flags Flag set controlling creation (see ClauseCreator::CreateFlag). + * \param info Initial information (e.g. type) for the new clause. + * + * \pre !s.hasConflict() and s.decisionLevel() == 0 or extra.learnt() + * \pre lits are fully prepared or flags contains suitable prepare flags. + * + * \note + * If the given clause is unit (or asserting), the unit-resulting literal is + * asserted on the numerical lowest level possible but the new information + * is not immediately propagated, i.e. on return queueSize() may be greater than 0. + * + * \note + * The local representation of the clause is always attached to the solver + * but only added to the solver if clause_no_add is not contained in flags. + * Otherwise, the returned clause is owned by the caller who is also responsible to manage it. + * Furthermore, learnt statistics are \b not updated automatically in that case. + * + * \see prepare() + */ + static Result create(Solver& s, LitVec& lits, CreateFlag flags, const ClauseInfo& info = ClauseInfo()); + + /*! + * \overload + */ + static Result create(Solver& s, const ClauseRep& rep, CreateFlag flags); + + //! Integrates the given clause into the current search of s. + /*! + * \pre the assignment in s is not conflicting + * \param s The solver in which the clause should be integrated. + * \param clause The clause to be integrated. + * \param flags A set of flags controlling integration (see ClauseCreator::CreateFlag). + * \param t Constraint type to use for the local representation. + * + * \note + * The function behaves similar to ClauseCreator::create() with the exception that + * it does not add local representations for implicit clauses (i.e. size <= 3) + * unless flags contains clause_explicit. + * In that case, an explicit representation is created. + * Implicit representations can only be created via ClauseCreator::create(). + * + * \note + * The function acts as a sink for the given clause (i.e. it decreases its reference count) + * unless flags contains clause_no_release. + * + * \note integrate() is intended to be called in a post propagator. + * To integrate a set of clauses F, one would use a loop like this: + * \code + * bool MyPostProp::propagate(Solver& s) { + * bool r = true; + * while (!F.empty() && r) { + * SharedLiterals* C = f.pop(); + * r = integrate(s, C, ...).ok; + * } + * return r; + * } + * \endcode + */ + static Result integrate(Solver& s, SharedLiterals* clause, CreateFlag flags, ConstraintType t); + + /*! + * \overload + */ + static Result integrate(Solver& s, SharedLiterals* clause, CreateFlag flags); + //@} private: - static ClauseRep prepare(Solver& s, const Literal* in, uint32 inSize, const ClauseInfo& e, uint32 flags, Literal* out, uint32 outMax = UINT32_MAX); - static Result create_prepared(Solver& s, const ClauseRep& pc, uint32 flags); - static ClauseHead* newProblemClause(Solver& s, const ClauseRep& clause, uint32 flags); - static ClauseHead* newLearntClause(Solver& s, const ClauseRep& clause, uint32 flags); - static ClauseHead* newUnshared(Solver& s, SharedLiterals* clause, const Literal* w, const ClauseInfo& e); - static bool ignoreClause(const Solver& s, const ClauseRep& cl, Status st, uint32 modeFlags); - Solver* solver_; // solver in which new clauses are stored - LitVec literals_; // literals of the new clause - ClauseInfo extra_; // extra info - uint32 flags_; // default flags to be used in end() + static ClauseRep prepare(Solver& s, LitView in, const ClauseInfo& e, CreateFlag flags, std::span out); + static Result createPrepared(Solver& s, const ClauseRep& pc, CreateFlag flags); + static Status statusPrepared(const Solver& s, const ClauseRep& c); + static ClauseHead* newProblemClause(Solver& s, const ClauseRep& clause, CreateFlag flags); + static ClauseHead* newLearntClause(Solver& s, const ClauseRep& clause, CreateFlag flags); + static ClauseHead* newUnshared(Solver& s, const SharedLiterals* clause, const Literal* w, const ClauseInfo& e); + static bool ignoreClause(const Solver& s, const ClauseRep& cl, Status st, CreateFlag modeFlags); + Solver* solver_; // solver in which new clauses are stored + LitVec literals_; // literals of the new clause + ClauseInfo extra_; // extra info + CreateFlag flags_; // default flags to be used in end() }; //! Class for representing a clause in a solver. /*! * \ingroup constraint */ -class Clause : public ClauseHead { +class Clause final : public ClauseHead { public: - typedef Constraint::PropResult PropResult; - - //! Allocates memory for storing a (learnt) clause with nLits literals. - static void* alloc(Solver& s, uint32 mLits, bool learnt); - - //! Creates a new clause from the clause given in rep. - /*! - * \param s Solver in which the new clause is to be used. - * \param rep The raw representation of the clause. - * - * \pre The clause given in lits is prepared and contains at least two literals. - * \note The clause must be destroyed using Clause::destroy. - * \see ClauseCreator::prepare() - */ - static ClauseHead* newClause(Solver& s, const ClauseRep& rep) { - return newClause(alloc(s, rep.size, rep.info.learnt()), s, rep); - } - //! Creates a new clause object in mem. - /*! - * \pre mem points to a memory block that was allocated via Clause::alloc(). - */ - static ClauseHead* newClause(void* mem, Solver& s, const ClauseRep& rep); - - //! Creates a new contracted clause from the clause given in rep. - /*! - * A contracted clause consists of an active head and a tail of false literals. - * Propagation is restricted to the head. - * The tail is only needed to compute reasons from assignments. - * - * \param s Solver in which the new clause is to be used. - * \param rep The raw representation of the clause. - * \param tailPos The starting index of the tail (first literal that should be temporarily removed from the clause). - * \param extend Extend head part of clause as tail literals become free? - */ - static ClauseHead* newContractedClause(Solver& s, const ClauseRep& rep, uint32 tailPos, bool extend); - - //! Creates a new local surrogate for shared_lits to be used in the given solver. - /*! - * \param s The solver in which this clause will be used. - * \param lits The shared literals of this clause. - * \param e Initial meta data for the new (local) clause. - * \param head Watches and cache literal for the new (local) clause. - * \param addRef Increment ref count of lits. - */ - static ClauseHead* newShared(Solver& s, SharedLiterals* lits, const InfoType& e, const Literal head[3], bool addRef = true); - - // Constraint-Interface - - Constraint* cloneAttach(Solver& other); - - /*! - * For a clause [x y p] the reason for p is ~x and ~y. - * \pre *this previously asserted p - * \note if the clause is a learnt clause, calling reason increases - * the clause's activity. - */ - void reason(Solver& s, Literal p, LitVec& lits); - - bool minimize(Solver& m, Literal p, CCMinRecursive* r); - - bool isReverseReason(const Solver& s, Literal p, uint32 maxL, uint32 maxN); - - //! Returns true if clause is SAT. - /*! - * Removes from the clause all literals that are false. - */ - bool simplify(Solver& s, bool = false); - - //! Destroys the clause and frees its memory. - void destroy(Solver* s = 0, bool detach = false); - - // LearntConstraint interface - - //! Returns type() if the clause is currently not satisfied and t.inSet(type()). - uint32 isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits); - - // clause interface - BoolPair strengthen(Solver& s, Literal p, bool allowToShort); - void detach(Solver&); - uint32 size() const; - void toLits(LitVec& out) const; - bool contracted() const; - bool isSmall() const; - bool strengthened() const; - uint32 computeAllocSize() const; + //! Allocates memory for storing a (learnt) clause with nLits literals. + static void* alloc(Solver& s, uint32_t mLits, bool learnt); + + //! Creates a new clause from the clause given in rep. + /*! + * \param s Solver in which the new clause is to be used. + * \param rep The raw representation of the clause. + * + * \pre The clause given in lits is prepared and contains at least two literals. + * \note The clause must be destroyed using Clause::destroy. + * \see ClauseCreator::prepare() + */ + static ClauseHead* newClause(Solver& s, const ClauseRep& rep) { + return newClause(alloc(s, rep.size, rep.info.learnt()), s, rep); + } + //! Creates a new clause object in mem. + /*! + * \pre mem points to a memory block that was allocated via Clause::alloc(). + */ + static ClauseHead* newClause(void* mem, Solver& s, const ClauseRep& rep); + + //! Creates a new contracted clause from the clause given in rep. + /*! + * A contracted clause consists of an active head and a tail of false literals. + * Propagation is restricted to the head. + * The tail is only needed to compute reasons from assignments. + * + * \param s Solver in which the new clause is to be used. + * \param rep The raw representation of the clause. + * \param tailPos The starting index of the tail (first literal that should be temporarily removed from the clause). + * \param extend Extend head part of clause as tail literals become free? + */ + static ClauseHead* newContractedClause(Solver& s, const ClauseRep& rep, uint32_t tailPos, bool extend); + + //! Creates a new local surrogate for shared_lits to be used in the given solver. + /*! + * \param s The solver in which this clause will be used. + * \param lits The shared literals of this clause. + * \param e Initial metadata for the new (local) clause. + * \param head Watches and cache literal for the new (local) clause. + * \param addRef Increment ref count of lits. + */ + static ClauseHead* newShared(Solver& s, SharedLiterals* lits, const InfoType& e, const Literal head[3], + bool addRef = true); + + // Constraint-Interface + + ClauseHead* cloneAttach(Solver& other) override; + + /*! + * For a clause [x y p] the reason for p is ~x and ~y. + * \pre *this previously asserted p + * \note if the clause is a learnt clause, calling reason increases + * the clause's activity. + */ + void reason(Solver& s, Literal p, LitVec& lits) override; + + bool minimize(Solver& m, Literal p, CCMinRecursive* r) override; + + bool isReverseReason(const Solver& s, Literal p, uint32_t maxL, uint32_t maxN) override; + + //! Returns true if clause is SAT. + /*! + * Removes from the clause all literals that are false. + */ + bool simplify(Solver& s, bool = false) override; + + //! Destroys the clause and frees its memory. + void destroy(Solver* s = nullptr, bool detach = false) override; + + // LearntConstraint interface + + //! Returns type() if the clause is currently not satisfied and t.inSet(type()). + uint32_t isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits) override; + + // clause interface + StrengthenResult strengthen(Solver& s, Literal p, bool allowToShort) override; + void detach(Solver&) override; + [[nodiscard]] uint32_t size() const override; + void toLits(LitVec& out) const override; + [[nodiscard]] bool contracted() const; + [[nodiscard]] bool isSmall() const; + [[nodiscard]] bool strengthened() const; + [[nodiscard]] uint32_t computeAllocSize() const; + private: - Clause(Solver& s, const ClauseRep& rep, uint32 tail = UINT32_MAX, bool extend = false); - Clause(Solver& s, const Clause& other); - typedef std::pair LitRange; - void undoLevel(Solver& s); - bool updateWatch(Solver& s, uint32 pos); - Literal* end() { return head_+local_.size(); } - Literal* removeFromTail(Solver& s, Literal* it, Literal* end); - Literal* small(); - LitRange tail(); + struct Tail { + [[nodiscard]] constexpr Literal* begin() const noexcept { return b; } + [[nodiscard]] constexpr Literal* end() const noexcept { return e; } + [[nodiscard]] constexpr uint32_t size() const noexcept { return static_cast(e - b); } + + Literal *b, *e; + }; + Clause(Solver& s, const ClauseRep& rep, uint32_t tail = UINT32_MAX, bool extend = false); + Clause(Solver& s, const Clause& other); + void undoLevel(Solver& s) override; + bool updateWatch(Solver& s, uint32_t pos) override; + Literal* end() { return head_ + local_.size(); } + Literal* removeFromTail(Solver& s, Literal* it, Literal* end); + Literal* small(); + Tail tail(); }; //! Constraint for Loop-Formulas. /*! * \ingroup constraint * Special purpose constraint for loop formulas of the form: L v B1 v ... v Bm, - * where L is an unfounded set represented as a set of atom literals {~a1, ..., ~an}. + * where L is an unfounded set represented as a set of atom literals {~'a1', ..., ~'an'}. * Representing such a loop formula explicitly as n clauses - * - (1) ~a1 v B1 v ... v Bm + * - (1) ~'a1' v B1 v ... v Bm * - ... - * - (n) ~an v B1 v ... v Bm + * - (n) ~'an' v B1 v ... v Bm * . * is wasteful because each clause contains the same set of bodies. * * The idea behind LoopFormula is to treat L as a "macro-literal" * with the following properties: - * - isTrue(L), iff for all ai isTrue(~ai) - * - isFalse(L), iff for some ai isFalse(~ai) + * - isTrue(L), iff for all 'ai' isTrue(~'ai') + * - isFalse(L), iff for some 'ai' isFalse(~'ai') * - L is watchable, iff not isFalse(L) - * - Watching L means watching all ai. - * - setting L to true means setting all ai to false. + * - Watching L means watching all 'ai'. + * - setting L to true means setting all 'ai' to false. + * * Using this convention the TWL-algo can be implemented as in a clause. * * \par Implementation: * - The literal-array is divided into two parts, an "active clause" part and an atom part. - * - The "active clause" contains one atom and all bodies: [~ai B1 ... Bj] - * - The atom part contains all atoms: [~a1 ... ~an] + * - The "active clause" contains one atom and all bodies: [~'ai' B1 ... Bj] + * - The atom part contains all atoms: [~'a1' ... ~'an'] * - Two of the literals of the "active clause" are watched (again: watching an atom means watching all atoms) * - If a watched atom becomes true, it is copied into the "active clause" and the TWL-algo starts. */ class LoopFormula : public Constraint { public: - //! Creates a new loop-constraint for the given atoms. - /*! - * \param s Solver in which the new constraint is to be used. - * \param c1 First clause of the new constraint. - * \param atoms Set of atoms in the loop. - * \param nAtoms Number of atoms in the loop. - * \param updateHeuristic Whether to notify heuristic about new constraint. - * - * \pre The clause given in c1 is prepared and c1.size > 1 and c1.lits[0] is a literal in atoms. - * \see ClauseCreator::prepare() - */ - static LoopFormula* newLoopFormula(Solver& s, const ClauseRep& c1, const Literal* atoms, uint32 nAtoms, bool updateHeuristic = true); - - //! Returns the number of literals in the loop-formula. - uint32 size() const; - - // Constraint interface - Constraint* cloneAttach(Solver&) { return 0; } - PropResult propagate(Solver& s, Literal p, uint32& data); - void reason(Solver&, Literal p, LitVec& lits); - bool minimize(Solver& s, Literal p, CCMinRecursive* ccMin); - bool simplify(Solver& s, bool = false); - void destroy(Solver* = 0, bool = false); - - // LearntConstraint interface - bool locked(const Solver& s) const; - - uint32 isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits); - - //! Returns the loop-formula's activity. - /*! - * The activity of a loop-formula is increased, whenever reason() is called. - */ - ScoreType activity() const { return act_; } - - //! Halves the loop-formula's activity. - void decreaseActivity() { act_.reduce(); } - void resetActivity() { act_.reset(); } - - //! Returns Constraint_t::Loop. - ConstraintType type() const { return Constraint_t::Loop; } + //! Creates a new loop-constraint for the given atoms. + /*! + * \param s Solver in which the new constraint is to be used. + * \param c1 First clause of the new constraint. + * \param atoms Set of atoms in the loop. + * \param updateHeuristic Whether to notify heuristic about new constraint. + * + * \pre The clause given in c1 is prepared and c1.size > 1 and c1.lits[0] is a literal in atoms. + * \see ClauseCreator::prepare() + */ + static LoopFormula* newLoopFormula(Solver& s, const ClauseRep& c1, LitView atoms, bool updateHeuristic = true); + + //! Returns the number of literals in the loop-formula. + [[nodiscard]] uint32_t size() const; + + // Constraint interface + Constraint* cloneAttach(Solver&) override { return nullptr; } + PropResult propagate(Solver& s, Literal p, uint32_t& data) override; + void reason(Solver&, Literal p, LitVec& lits) override; + bool minimize(Solver& s, Literal p, CCMinRecursive* ccMin) override; + bool simplify(Solver& s, bool = false) override; + void destroy(Solver* = nullptr, bool = false) override; + + // LearntConstraint interface + [[nodiscard]] bool locked(const Solver& s) const override; + + uint32_t isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits) override; + + //! Returns the loop-formula's activity. + /*! + * The activity of a loop-formula is increased, whenever reason() is called. + */ + [[nodiscard]] ScoreType activity() const override { return act_; } + + //! Halves the loop-formula's activity. + void decreaseActivity() override { act_.reduce(); } + void resetActivity() override { act_.reset(); } + + //! Returns ConstraintType::loop. + [[nodiscard]] ConstraintType type() const override { return ConstraintType::loop; } + private: - LoopFormula(Solver& s, const ClauseRep& c1, const Literal* atoms, uint32 nAtoms, bool heu); - void detach(Solver& s); - bool otherIsSat(const Solver& s); - Literal* begin() { return lits_ + 1; } - Literal* xBegin(){ return lits_ + end_ + 1; } - Literal* xEnd() { return lits_ + size_; } - ScoreType act_; // activity of constraint - uint32 end_; // position of second sentinel - uint32 size_:30; // size of lits_ - uint32 str_ : 1; // removed literal(s) during simplify? - uint32 xPos_: 1; // position of ai in lits_ or 0 if no atom - uint32 other_; // stores the position of a literal that was recently true -POTASSCO_WARNING_BEGIN_RELAXED - Literal lits_[0]; // S ai B1...Bm S a1...an -POTASSCO_WARNING_END_RELAXED + LoopFormula(Solver& s, const ClauseRep& c1, LitView atoms, bool heu); + void detach(Solver& s); + bool otherIsSat(const Solver& s); + Literal* begin() { return lits_ + 1; } + Literal* xBegin() { return lits_ + end_ + 1; } + Literal* xEnd() { return lits_ + size_; } + auto xSpan() { return std::span(xBegin(), size_ - (end_ + 1)); } + ScoreType act_; // activity of constraint + uint32_t end_; // position of second sentinel + uint32_t size_ : 30; // size of lits_ + uint32_t str_ : 1; // removed literal(s) during simplify? + uint32_t xPos_ : 1; // position of 'ai' in lits_ or 0 if no atom + uint32_t other_; // stores the position of a literal that was recently true + POTASSCO_WARNING_BEGIN_RELAXED + Literal lits_[0]; // S ai B1...Bm S a1...an + POTASSCO_WARNING_END_RELAXED }; namespace mt { @@ -505,39 +518,40 @@ namespace mt { /*! * \ingroup constraint * The local part of a shared clause consists of a - * clause head and and a pointer to the shared literals. + * clause head and a pointer to the shared literals. * Since the local part is owned by a particular solver * it can be safely modified. Destroying a SharedLitsClause * means destroying the local part and decreasing the * shared literals' reference count. */ -class SharedLitsClause : public ClauseHead { +class SharedLitsClause final : public ClauseHead { public: - //! Creates a new SharedLitsClause to be used in the given solver. - /*! - * \param s The solver in which this clause will be used. - * \param shared_lits The shared literals of this clause. - * \param e Initial meta data for the new (local) clause. - * \param lits Watches and cache literal for the new (local) clause. - * \param addRef Increment ref count of shared_lits. - */ - static ClauseHead* newClause(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits, bool addRef = true); - - Constraint* cloneAttach(Solver& other); - void reason(Solver& s, Literal p, LitVec& out); - bool minimize(Solver& s, Literal p, CCMinRecursive* rec); - bool isReverseReason(const Solver& s, Literal p, uint32 maxL, uint32 maxN); - bool simplify(Solver& s, bool); - void destroy(Solver* s, bool detach); - uint32 isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits); - uint32 size() const; - void toLits(LitVec& out) const; + //! Creates a new SharedLitsClause to be used in the given solver. + /*! + * \param s The solver in which this clause will be used. + * \param shared_lits The shared literals of this clause. + * \param e Initial metadata for the new (local) clause. + * \param lits Watches and cache literal for the new (local) clause. + * \param addRef Increment ref count of shared_lits. + */ + static ClauseHead* newClause(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits, + bool addRef = true); + + ClauseHead* cloneAttach(Solver& other) override; + void reason(Solver& s, Literal p, LitVec& out) override; + bool minimize(Solver& s, Literal p, CCMinRecursive* rec) override; + bool isReverseReason(const Solver& s, Literal p, uint32_t maxL, uint32_t maxN) override; + bool simplify(Solver& s, bool) override; + void destroy(Solver* s, bool detach) override; + uint32_t isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits) override; + [[nodiscard]] uint32_t size() const override; + void toLits(LitVec& out) const override; + private: - SharedLitsClause(Solver& s, SharedLiterals* x, const Literal* lits, const InfoType&, bool addRef); - bool updateWatch(Solver& s, uint32 pos); - BoolPair strengthen(Solver& s, Literal p, bool allowToShort); + SharedLitsClause(Solver& s, SharedLiterals* x, const Literal* lits, const InfoType&, bool addRef); + bool updateWatch(Solver& s, uint32_t pos) override; + StrengthenResult strengthen(Solver& s, Literal p, bool allowToShort) override; }; -} +} // namespace mt -} -#endif +} // namespace Clasp diff --git a/clasp/cli/clasp_app.h b/clasp/cli/clasp_app.h index 0f896eb..6b6c9d5 100644 --- a/clasp/cli/clasp_app.h +++ b/clasp/cli/clasp_app.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,183 +21,188 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLI_CLASP_APP_H_INCLUDED -#define CLASP_CLI_CLASP_APP_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include -#include -#include + #include #include -#include -#include + +#include +#include + #include #include -namespace Potassco { class StringBuilder; } -namespace Clasp { namespace Cli { +#include +#include + +namespace Clasp::Cli { ///////////////////////////////////////////////////////////////////////////////////////// // clasp exit codes ///////////////////////////////////////////////////////////////////////////////////////// enum ExitCode { - E_UNKNOWN = 0, /*!< Satisfiability of problem not known; search not started. */ - E_INTERRUPT = 1, /*!< Run was interrupted. */ - E_SAT = 10, /*!< At least one model was found. */ - E_EXHAUST = 20, /*!< Search-space was completely examined. */ - E_MEMORY = 33, /*!< Run was interrupted by out of memory exception. */ - E_ERROR = 65, /*!< Run was interrupted by internal error. */ - E_NO_RUN = 128 /*!< Search not started because of syntax or command line error.*/ + exit_unknown = 0, /*!< Satisfiability of problem not known; search not started. */ + exit_interrupt = 1, /*!< Run was interrupted. */ + exit_sat = 10, /*!< At least one model was found. */ + exit_exhaust = 20, /*!< Search-space was completely examined. */ + exit_memory = 33, /*!< Run was interrupted by out of memory exception. */ + exit_error = 65, /*!< Run was interrupted by internal error. */ + exit_no_run = 128 /*!< Search not started because of syntax or command line error.*/ }; ///////////////////////////////////////////////////////////////////////////////////////// // clasp app helpers ///////////////////////////////////////////////////////////////////////////////////////// class WriteCnf { public: - WriteCnf(const std::string& outFile); - ~WriteCnf(); - void writeHeader(uint32 numVars, uint32 numCons); - void write(Var maxVar, const ShortImplicationsGraph& g); - void write(ClauseHead* h); - void write(Literal unit); - void close(); - bool unary(Literal, Literal) const; - bool binary(Literal, Literal, Literal) const; + explicit WriteCnf(const std::string& outFile); + ~WriteCnf(); + WriteCnf(WriteCnf&&) = delete; + void writeHeader(uint32_t numVars, uint32_t numCons); + void write(Var_t maxVar, const ShortImplicationsGraph& g); + void write(const ClauseHead* h); + void write(Literal unit); + void close(); + private: - WriteCnf(const WriteCnf&); - WriteCnf& operator=(const WriteCnf&); - FILE* str_; - LitVec lits_; + [[nodiscard]] bool unary(Literal, Literal) const; + [[nodiscard]] bool binary(Literal, Literal, Literal) const; + + FILE* str_; + LitVec lits_; }; class LemmaLogger { public: - struct Options { - Options() : logMax(UINT32_MAX), lbdMax(UINT32_MAX), domOut(false), logText(false) {} - uint32 logMax; // log at most logMax lemmas - uint32 lbdMax; // only log lemmas with lbd <= lbdMax - bool domOut; // only log lemmas that can be expressed over out variables - bool logText; // log lemmas in ground lp format - }; - LemmaLogger(const std::string& outFile, const Options& opts); - ~LemmaLogger(); - void startStep(ProgramBuilder& prg, bool inc); - void add(const Solver& s, const LitVec& cc, const ConstraintInfo& info); - void close(); + struct Options { + uint32_t logMax = UINT32_MAX; // log at most logMax lemmas + uint32_t lbdMax = UINT32_MAX; // only log lemmas with lbd <= lbdMax + bool domOut = false; // only log lemmas that can be expressed over out variables + bool logText = false; // log lemmas in ground lp format + }; + LemmaLogger(const std::string& outFile, const Options& opts); + ~LemmaLogger(); + LemmaLogger(LemmaLogger&&) = delete; + void startStep(const SharedContext& ctx, Asp::LogicProgram* prg, bool inc); + void add(const Solver& s, LitView cc, const ConstraintInfo& info); + void close(); + private: - typedef PodVector::type Var2Idx; - typedef Atomic_t::type Counter; - LemmaLogger(const LemmaLogger&); - LemmaLogger& operator=(const LemmaLogger&); - void formatAspif(const LitVec& cc, uint32 lbd, Potassco::StringBuilder& out) const; - void formatText(const LitVec& cc, const OutputTable& tab, uint32 lbd, Potassco::StringBuilder& out) const; - FILE* str_; - Potassco::LitVec solver2asp_; - Var2Idx solver2NameIdx_; - ProblemType inputType_; - Options options_; - int step_; - Counter logged_; + using Var2Idx = PodVector_t; + using Counter = mt::ThreadSafe; + template + bool formatAspif(LitView cc, uint32_t lbd, S& out) const; + template + bool formatText(LitView cc, const OutputTable& tab, uint32_t lbd, S& out) const; + FILE* str_; + Potassco::LitVec solver2Asp_; + Var2Idx solver2NameIdx_; + bool asp_; + Options options_; + int step_; + Counter logged_; }; ///////////////////////////////////////////////////////////////////////////////////////// // clasp specific application options ///////////////////////////////////////////////////////////////////////////////////////// struct ClaspAppOptions { - typedef LemmaLogger::Options LogOptions; - ClaspAppOptions(); - typedef std::vector StringSeq; - static bool mappedOpts(ClaspAppOptions*, const std::string&, const std::string&); - void initOptions(Potassco::ProgramOptions::OptionContext& root); - bool validateOptions(const Potassco::ProgramOptions::ParsedOptions& parsed); - StringSeq input; // list of input files - only first used! - std::string lemmaLog; // optional file name for writing learnt lemmas - std::string lemmaIn; // optional file name for reading learnt lemmas - std::string hccOut; // optional file name for writing scc programs - std::string outAtom; // optional format string for atoms - uint32 outf; // output format - int compute; // force literal compute to true - LogOptions lemma; // options for lemma logging - char ifs; // output field separator - bool hideAux; // output aux atoms? - uint8 quiet[3]; // configure printing of models, optimization values, and call steps - int8 onlyPre; // run preprocessor and exit - bool printPort; // print portfolio and exit - enum OutputFormat { out_def = 0, out_comp = 1, out_json = 2, out_none = 3 }; + enum OutputFormat { out_def = 0, out_comp = 1, out_json = 2, out_none = 3 }; + static constexpr uint8_t q_def = UINT8_MAX; + using LogOptions = LemmaLogger::Options; + using StringSeq = std::vector; + bool apply(const std::string&, const std::string&); + void initOptions(Potassco::ProgramOptions::OptionContext& root); + bool validateOptions(const Potassco::ProgramOptions::ParsedOptions& parsed); + StringSeq input; // list of input files - only first used! + std::string lemmaLog; // optional file name for writing learnt lemmas + std::string lemmaIn; // optional file name for reading learnt lemmas + std::string hccOut; // optional file name for writing scc programs + std::string outAtom; // optional format string for atoms + uint32_t outf = out_def; // output format + int compute = 0; // force literal compute to true + LogOptions lemma; // options for lemma logging + uint8_t quiet[3] = {q_def, q_def, q_def}; // configure printing of models, optimization values, and call steps + int8_t onlyPre = 0; // run preprocessor and exit + char ifs = ' '; // output field separator + bool hideAux = false; // output aux atoms? + bool printPort = false; // print portfolio and exit }; ///////////////////////////////////////////////////////////////////////////////////////// // clasp application base ///////////////////////////////////////////////////////////////////////////////////////// // Base class for applications using the clasp library. -class ClaspAppBase : public Potassco::Application, public Clasp::EventHandler { +class ClaspAppBase + : public Potassco::Application + , public EventHandler { public: - typedef ClaspFacade::Summary RunSummary; - typedef Potassco::ProgramOptions::PosOption PosOption; + using RunSummary = ClaspFacade::Summary; + protected: - struct TextOptions { - TextOutput::Format format; - unsigned verbosity; - const char* catAtom; - char ifs; - }; - using Potassco::Application::run; - ClaspAppBase(); - ~ClaspAppBase(); - // ------------------------------------------------------------------------------------------- - // Functions to be implemented by subclasses - virtual ProblemType getProblemType() = 0; - virtual void run(ClaspFacade& clasp) = 0; - virtual void storeCommandArgs(const Potassco::ProgramOptions::ParsedValues& values); - virtual Output* createOutput(ProblemType f); - virtual Output* createTextOutput(const TextOptions& options); - virtual Output* createJsonOutput(unsigned verbosity); - // ------------------------------------------------------------------------------------------- - // Helper functions that subclasses might call during run - void handleStartOptions(ClaspFacade& clasp); - bool handlePostGroundOptions(ProgramBuilder& prg); - bool handlePreSolveOptions(ClaspFacade& clasp); - // ------------------------------------------------------------------------------------------- - // Application functions - virtual const int* getSignals() const; - virtual HelpOpt getHelpOption() const { return HelpOpt("Print {1=basic|2=more|3=full} help and exit", 3); } - virtual PosOption getPositional() const { return parsePositional; } - virtual void initOptions(Potassco::ProgramOptions::OptionContext& root); - virtual void validateOptions(const Potassco::ProgramOptions::OptionContext& root, const Potassco::ProgramOptions::ParsedOptions& parsed, const Potassco::ProgramOptions::ParsedValues& values); - virtual void setup(); - virtual void run(); - virtual void shutdown(); - virtual bool onSignal(int); - virtual void printHelp(const Potassco::ProgramOptions::OptionContext& root); - virtual void printVersion(); - static bool parsePositional(const std::string& s, std::string& out); - // ------------------------------------------------------------------------------------------- - // Event handler - virtual void onEvent(const Event& ev); - virtual bool onModel(const Solver& s, const Model& m); - virtual bool onUnsat(const Solver& s, const Model& m); - // ------------------------------------------------------------------------------------------- - // Status information & output - int exitCode(const RunSummary& sol) const; - void printTemplate() const; - void printDefaultConfigs() const; - void printConfig(ConfigKey k) const; - void printLibClaspVersion() const; - void printLicense() const; - std::istream& getStream(bool reopen = false) const; - // ------------------------------------------------------------------------------------------- - // Functions called in handlePreSolveOptions() - void writeNonHcfs(const PrgDepGraph& graph) const; - typedef Potassco::ProgramReader LemmaReader; - typedef SingleOwnerPtr OutPtr; - typedef SingleOwnerPtr ClaspPtr; - typedef SingleOwnerPtr LogPtr; - typedef SingleOwnerPtr LemmaPtr; - ClaspCliConfig claspConfig_; - ClaspAppOptions claspAppOpts_; - ClaspPtr clasp_; - OutPtr out_; - LogPtr logger_; - LemmaPtr lemmaIn_; - unsigned fpuMode_; + struct TextOptions { + TextOutput::Format format; + unsigned verbosity; + const char* catAtom; + char ifs; + }; + using Potassco::Application::run; + ClaspAppBase(); + ~ClaspAppBase() override; + // ------------------------------------------------------------------------------------------- + // Functions to be implemented by subclasses + virtual ProblemType getProblemType() = 0; + virtual void run(ClaspFacade& clasp) = 0; + virtual void storeCommandArgs(const Potassco::ProgramOptions::ParsedValues& values); + virtual Output* createOutput(ProblemType f); + virtual Output* createTextOutput(const TextOptions& options); + virtual Output* createJsonOutput(unsigned verbosity); + // ------------------------------------------------------------------------------------------- + // Helper functions that subclasses might call during run + void handleStartOptions(ClaspFacade& clasp); + bool handlePostGroundOptions(ClaspFacade& clasp); + bool handlePreSolveOptions(ClaspFacade& clasp); + // ------------------------------------------------------------------------------------------- + // Application functions + [[nodiscard]] const int* getSignals() const override; + [[nodiscard]] HelpOpt getHelpOption() const override { return {"Print {1=basic|2=more|3=full} help and exit", 3}; } + [[nodiscard]] const char* getPositional(const std::string& value) const override; + + void initOptions(Potassco::ProgramOptions::OptionContext& root) override; + void validateOptions(const Potassco::ProgramOptions::OptionContext& root, + const Potassco::ProgramOptions::ParsedOptions& parsed, + const Potassco::ProgramOptions::ParsedValues& values) override; + void setup() override; + void run() override; + void shutdown() override; + bool onSignal(int) override; + void flush() override; + void onHelp(const std::string& help, Potassco::ProgramOptions::DescriptionLevel level) override; + void onVersion(const std::string& version) override; + bool onUnhandledException(const char*) override; + // ------------------------------------------------------------------------------------------- + // Event handler + void onEvent(const Event& ev) override; + bool onModel(const Solver& s, const Model& m) override; + bool onUnsat(const Solver& s, const Model& m) override; + // ------------------------------------------------------------------------------------------- + // Status information & output + [[nodiscard]] static int exitCode(const RunSummary& run); + static void printTemplate(); + static void printDefaultConfigs(); + static void printConfig(ConfigKey k); + static void printLibClaspVersion(); + static void printLicense(); + [[nodiscard]] std::istream& getStream(bool reopen = false) const; + // ------------------------------------------------------------------------------------------- + // Functions called in handlePreSolveOptions() + void writeNonHcfs(const PrgDepGraph& graph) const; + struct LemmaReader; + using OutPtr = std::unique_ptr; + using ClaspPtr = std::unique_ptr; + using LogPtr = std::unique_ptr; + using LemmaPtr = std::unique_ptr; + ClaspCliConfig claspConfig_; + ClaspAppOptions claspAppOpts_; + ClaspPtr clasp_; + OutPtr out_; + LogPtr logger_; + LemmaPtr lemmaIn_; + unsigned fpuMode_{}; }; ///////////////////////////////////////////////////////////////////////////////////////// // clasp application @@ -205,21 +210,18 @@ class ClaspAppBase : public Potassco::Application, public Clasp::EventHandler { // Standalone clasp application. class ClaspApp : public ClaspAppBase { public: - ClaspApp(); - const char* getName() const { return "clasp"; } - const char* getVersion() const { return CLASP_VERSION; } - const char* getUsage() const { - return - "[number] [options] [file]\n" - "Compute at most models (0=all) of the instance given in "; - } + ClaspApp(); + [[nodiscard]] const char* getName() const override { return "clasp"; } + [[nodiscard]] const char* getVersion() const override { return CLASP_VERSION; } + [[nodiscard]] const char* getUsage() const override { + return "[number] [options] [file]\n" + "Compute at most models (0=all) of the instance given in "; + } + protected: - virtual ProblemType getProblemType(); - virtual void run(ClaspFacade& clasp); - virtual void printHelp(const Potassco::ProgramOptions::OptionContext& root); -private: - ClaspApp(const ClaspApp&); - ClaspApp& operator=(const ClaspApp&); + using ClaspAppBase::run; + ProblemType getProblemType() override; + void run(ClaspFacade& clasp) override; + void onHelp(const std::string& help, Potassco::ProgramOptions::DescriptionLevel level) override; }; -}} -#endif +} // namespace Clasp::Cli diff --git a/clasp/cli/clasp_cli_configs.inl b/clasp/cli/clasp_cli_configs.inl index 6db3f22..812d8cf 100644 --- a/clasp/cli/clasp_cli_configs.inl +++ b/clasp/cli/clasp_cli_configs.inl @@ -1,7 +1,7 @@ // -// Copyright (c) 2013-2017 Benjamin Kaufmann +// Copyright (c) 2013-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -35,7 +35,7 @@ * . * \note The solver id is used to identify a configuration in the default portfolio. */ - +// clang-format off #if !defined(CONFIG) || (!defined(CLASP_CLI_DEFAULT_CONFIGS) && !defined(CLASP_CLI_AUX_CONFIGS)) #error Invalid include context #endif diff --git a/clasp/cli/clasp_cli_options.inl b/clasp/cli/clasp_cli_options.inl index d1733e0..680161d 100644 --- a/clasp/cli/clasp_cli_options.inl +++ b/clasp/cli/clasp_cli_options.inl @@ -1,7 +1,7 @@ // // Copyright (c) 2013-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,6 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // +// clang-format off /*! * \file * \brief Supermacros for describing clasp's options. @@ -100,8 +101,13 @@ OPTION(share, "!,@1", ARG_EXT(defaultsTo("auto")->state(Value::value_defaulted), MAP("auto", ContextParams::share_auto), MAP("problem", ContextParams::share_problem),\ MAP("learnt", ContextParams::share_learnt))),\ "Configure physical sharing of constraints [%D]\n"\ - " %A: {auto|problem|learnt|all}", FUN(arg) {ContextParams::ShareMode x; return arg>>x && SET(SELF.shareMode, (uint32)x);}, GET((ContextParams::ShareMode)SELF.shareMode)) + " %A: {auto|problem|learnt|all}", FUN(arg) {ContextParams::ShareMode x; return arg>>x && SET(SELF.shareMode, (uint32_t)x);}, GET((ContextParams::ShareMode)SELF.shareMode)) OPTION(learn_explicit, ",@2" , ARG(flag()), "Do not use Short Implication Graph for learning", STORE_FLAG(SELF.shortMode), GET(SELF.shortMode)) +OPTION(short_simp_mode, ",@2" , ARG_EXT(arg("")->defaultsTo("no")->state(Value::value_defaulted), DEFINE_ENUM_MAPPING(ContextParams::ShortSimpMode, \ + MAP("no" , ContextParams::simp_no) , MAP("learnt", ContextParams::simp_learnt))),\ + "Remove duplicate short constraints [%D]\n"\ + " %A: {no|learnt}", FUN(arg) {ContextParams::ShortSimpMode x; return arg>>x && SET(SELF.shortSimp, (uint32_t)x);}\ + , GET((ContextParams::ShortSimpMode)SELF.shortSimp))\ OPTION(sat_prepro , "!,@1", ARG(arg("")->implicit("2")), \ "Run SatELite-like preprocessing (Implicit: %I)\n" \ " %A: [,...]\n" \ @@ -128,9 +134,9 @@ GROUP_END(SELF) #define SELF CLASP_GLOBAL_OPTIONS GROUP_BEGIN(SELF) OPTION(stats, ",s", ARG(implicit("1")->arg("[,]")), "Enable {1=basic|2=full} statistics ( for tester)",\ - FUN(arg) { uint32 s = 0; uint32 t = 0;\ + FUN(arg) { uint32_t s = 0; uint32_t t = 0;\ return (arg.off() || (arg >> s >> opt(t) && s > 0)) - && SET(SELF.stats, s) && ((!SELF.testerConfig() && t == 0) || SET(SELF.addTesterConfig()->stats, t)); + && SET_LEQ(SELF.stats, s, 2) && ((!SELF.testerConfig() && t == 0) || SET(SELF.addTesterConfig()->stats, t)); },\ GET_FUN(str) { ITE(!SELF.testerConfig() || !SELF.testerConfig()->stats, str << SELF.stats, str << SELF.stats << SELF.testerConfig()->stats); }) OPTION(parse_ext, "!", ARG(flag()), "Enable extensions in non-aspif input",\ @@ -188,8 +194,8 @@ OPTION(opt_usc_shrink, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(OptParams:: " min : Linear search for subset minimal core\n" \ " : Limit solve calls to 2^ conflicts [10]",\ FUN(arg) {\ - OptParams::UscTrim t = (OptParams::UscTrim)0; uint32 n = 0; \ - return (arg.off() || arg >> t >> opt(n=10)) && SET(SELF.opt.trim, uint32(t)) && SET(SELF.opt.tLim, uint32(n)); },\ + OptParams::UscTrim t = (OptParams::UscTrim)0; uint32_t n = 0; \ + return (arg.off() || arg >> t >> opt(n=10)) && SET(SELF.opt.trim, uint32_t(t)) && SET(SELF.opt.tLim, uint32_t(n)); },\ GET_IF(SELF.opt.trim, (OptParams::UscTrim)SELF.opt.trim, SELF.opt.tLim)) OPTION(opt_heuristic, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(OptParams::Heuristic, \ MAP("sign", OptParams::heu_sign), MAP("model", OptParams::heu_model))),\ @@ -201,19 +207,19 @@ OPTION(opt_heuristic, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(OptParams:: GET(Set(SELF.opt.heus))) OPTION(restart_on_model, "!", ARG(flag()), "Restart after each model\n", STORE_FLAG(SELF.restartOnModel), GET(SELF.restartOnModel)) OPTION(lookahead , "!", ARG_EXT(implicit("atom"), DEFINE_ENUM_MAPPING(VarType, \ - MAP("atom", Var_t::Atom), MAP("body", Var_t::Body), MAP("hybrid", Var_t::Hybrid))),\ + MAP("atom", VarType::atom), MAP("body", VarType::body), MAP("hybrid", VarType::hybrid))),\ "Configure failed-literal detection (fld)\n" \ " %A: [,] / Implicit: %I\n" \ " : Run fld via {atom|body|hybrid} lookahead\n" \ " : Disable fld after applications ([0]=no limit)\n" \ " --lookahead=atom is default if --no-lookback is used\n", FUN(arg) { \ - VarType type = Var_t::Atom; uint32 limit = (SELF.lookOps = 0u);\ - return ITE(arg.off(), SET(SELF.lookType, 0u), arg>>type>>opt(limit) && SET(SELF.lookType, (uint32)type)) && SET_OR_ZERO(SELF.lookOps, limit);},\ + VarType type = VarType::atom; uint32_t limit = (SELF.lookOps = 0u);\ + return ITE(arg.off(), SET(SELF.lookType, 0u), arg>>type>>opt(limit) && SET(SELF.lookType, (uint32_t)type)) && SET_OR_ZERO(SELF.lookOps, limit);},\ GET_IF(SELF.lookType, (VarType)SELF.lookType, SELF.lookOps)) -OPTION(heuristic, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(Heuristic_t::Type, \ - MAP("berkmin", Heuristic_t::Berkmin), MAP("vmtf" , Heuristic_t::Vmtf), \ - MAP("vsids" , Heuristic_t::Vsids) , MAP("domain", Heuristic_t::Domain), \ - MAP("unit" , Heuristic_t::Unit) , MAP("auto", Heuristic_t::Default), MAP("none" , Heuristic_t::None))), \ +OPTION(heuristic, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(HeuristicType, \ + MAP("berkmin", HeuristicType::berkmin), MAP("vmtf" , HeuristicType::vmtf), \ + MAP("vsids" , HeuristicType::vsids) , MAP("domain", HeuristicType::domain), \ + MAP("unit" , HeuristicType::unit) , MAP("auto", HeuristicType::def), MAP("none" , HeuristicType::none))), \ "Configure decision heuristic\n" \ " %A: {Berkmin|Vmtf|Vsids|Domain|Unit|None}[,]\n" \ " Berkmin: Use BerkMin-like heuristic (Check last nogoods [0]=all)\n" \ @@ -221,9 +227,9 @@ OPTION(heuristic, "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(Heuristic_t::Typ " Vsids : Use Chaff-like heuristic (Use 1.0/0. as decay factor [95])\n"\ " Domain : Use domain knowledge in Vsids-like heuristic\n"\ " Unit : Use Smodels-like heuristic (Default if --no-lookback)\n" \ - " None : Select the first free variable", FUN(arg) { Heuristic_t::Type h = Heuristic_t::Berkmin; uint32 n = 0u; \ - return arg>>h>>opt(n) && SET(SELF.heuId, (uint32)h) && (Heuristic_t::isLookback(h) || !n) && SET_OR_FILL(SELF.heuristic.param, n);},\ - GET((Heuristic_t::Type)SELF.heuId, SELF.heuristic.param)) + " None : Select the first free variable", FUN(arg) { auto h = HeuristicType::berkmin; uint32_t n = 0u; \ + return arg>>h>>opt(n) && SET(SELF.heuId, (uint32_t)h) && (isLookbackHeuristic(h) || !n) && SET_OR_FILL(SELF.heuristic.param, n);},\ + GET((HeuristicType)SELF.heuId, SELF.heuristic.param)) OPTION(init_moms , "!,@2", ARG(flag()) , "Initialize heuristic with MOMS-score", STORE_FLAG(SELF.heuristic.moms), GET(SELF.heuristic.moms)) OPTION(score_res , ",@2" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(HeuParams::Score, \ MAP("auto", HeuParams::score_auto), MAP("min", HeuParams::score_min), MAP("set", HeuParams::score_set), MAP("multiset", HeuParams::score_multi_set))),\ @@ -242,7 +248,7 @@ OPTION(vsids_progress, ",@2", NO_ARG, "Enable dynamic decaying scheme in Vsids/D " : Set initial decay factor to 1.0/0.\n"\ " : Set decay update to /100.0 [1]\n"\ " : Decrease decay every conflicts [5000]", \ - FUN(arg) { uint32 n = 80; uint32 i = 1; uint32 c = 5000; \ + FUN(arg) { uint32_t n = 80; uint32_t i = 1; uint32_t c = 5000; \ return ITE(arg.off(), SET(SELF.heuristic.decay.init, 0), (arg >> n >> opt(i) >> opt(c))\ && SET(SELF.heuristic.decay.init, n) && SET_LEQ(SELF.heuristic.decay.bump, i, 100) && SET(SELF.heuristic.decay.freq, c));}, \ GET_IF(SELF.heuristic.decay.init, SELF.heuristic.decay.init, SELF.heuristic.decay.bump, SELF.heuristic.decay.freq)) @@ -256,8 +262,8 @@ OPTION(dom_mod , ",@1" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(HeuParams: " %A: (no|[,])\n"\ " : Modifier {level|pos|true|neg|false|init|factor}\n"\ " : Apply to (all | ) atoms", \ - FUN(arg) { HeuParams::DomMod modK; unsigned modN = 0; Set k; bool ok = true;\ - if (!arg.off()) { ok = ITE(arg.peek() >= 'A', arg >> modK && SET(modN, uint32(modK)), arg >> modN && modN > 0u && modN < 8u); }\ + FUN(arg) { HeuParams::DomMod modK{}; unsigned modN = 0; Set k; bool ok = true;\ + if (!arg.off()) { ok = ITE(arg.peek() >= 'A', arg >> modK && SET(modN, uint32_t(modK)), arg >> modN && modN > 0u && modN < 8u); }\ return ok && (arg.off() || arg >> opt(k)) && SET(SELF.heuristic.domMod, modN) && SET(SELF.heuristic.domPref, k.value());},\ GET_FUN(str) { Set mod(SELF.heuristic.domMod); Set pick(SELF.heuristic.domPref); \ ITE(mod.value() && pick.value(), str << mod << pick, str << mod); }) @@ -269,7 +275,7 @@ OPTION(update_mode , ",@2", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolverS MAP("propagate", SolverStrategies::update_on_propagate), MAP("conflict", SolverStrategies::update_on_conflict))),\ "Process messages on {propagate|conflict}", STORE_U(SolverStrategies::UpdateMode, SELF.upMode), GET(static_cast(SELF.upMode))) OPTION(acyc_prop, ",@2", ARG(implicit("1")->arg("{0..1}")), "Use backward inference in acyc propagation", \ - FUN(arg) { uint32 x; return arg>>x && SET_LEQ(SELF.acycFwd, (1u-x), 1u); }, GET(1u-SELF.acycFwd)) + FUN(arg) { uint32_t x; return arg>>x && SET_LEQ(SELF.acycFwd, (1u-x), 1u); }, GET(1u-SELF.acycFwd)) OPTION(seed , "" , ARG(arg("")),"Set random number generator's seed to %A", STORE(SELF.seed), GET(SELF.seed)) OPTION(no_lookback , "" , ARG(flag()), "Disable all lookback strategies\n", STORE_FLAG(SELF.search),GET(static_cast(SELF.search == SolverStrategies::no_learning))) OPTION(forget_on_step, "" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolverParams::Forget, \ @@ -287,7 +293,7 @@ OPTION(strengthen , "!" , ARG_EXT(arg(""),\ " : Follow {all|short|binary} antecedents [all]\n" \ " : Bump activities of antecedents [yes]", FUN(arg) {\ SolverStrategies::CCMinType m = SolverStrategies::cc_local; SolverStrategies::CCMinAntes t = SolverStrategies::no_antes; bool b = true; \ - return (arg.off() || arg>>m>>opt(t = SolverStrategies::all_antes)>>opt(b)) && SET(SELF.ccMinAntes, (uint32)t) && SET(SELF.ccMinRec, (uint32)m) && SET(SELF.ccMinKeepAct, uint32(!b)); }, \ + return (arg.off() || arg>>m>>opt(t = SolverStrategies::all_antes)>>opt(b)) && SET(SELF.ccMinAntes, (uint32_t)t) && SET(SELF.ccMinRec, (uint32_t)m) && SET(SELF.ccMinKeepAct, uint32_t(!b)); }, \ GET_IF(SELF.ccMinAntes != SolverStrategies::no_antes, (SolverStrategies::CCMinType)SELF.ccMinRec, (SolverStrategies::CCMinAntes)SELF.ccMinAntes, ITE(SELF.ccMinKeepAct, "no", "yes"))) OPTION(otfs , "" , ARG(implicit("1")->arg("{0..2}")), "Enable {1=partial|2=full} on-the-fly subsumption", STORE_LEQ(SELF.otfs, 2u), GET(SELF.otfs)) OPTION(update_lbd , "!,@2" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolverStrategies::LbdMode, \ @@ -298,8 +304,8 @@ OPTION(update_lbd , "!,@2" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolverSt " glucose: update to X = new LBD iff X+1 < previous LBD\n" \ " pseudo : update to X = new LBD+1 iff X < previous LBD\n" \ " : Protect updated nogoods on next reduce if X <= ",\ - FUN(arg) { SolverStrategies::LbdMode n = SolverStrategies::lbd_fixed; uint32 m = 0; \ - return (arg.off() || (arg >> n >> opt(m) && n > 0)) && SET(SELF.updateLbd, uint32(n)) && SET_LEQ(search->reduce.strategy.protect, m, uint32(Clasp::LBD_MAX));},\ + FUN(arg) { SolverStrategies::LbdMode n = SolverStrategies::lbd_fixed; uint32_t m = 0; \ + return (arg.off() || (arg >> n >> opt(m) && n > 0)) && SET(SELF.updateLbd, uint32_t(n)) && SET_LEQ(search->reduce.strategy.protect, m, Clasp::lbd_max);},\ GET_IF(SELF.updateLbd, (SolverStrategies::LbdMode)SELF.updateLbd, search->reduce.strategy.protect)) OPTION(update_act , ",@2", ARG(flag()), "Enable LBD-based activity bumping", STORE_FLAG(SELF.bumpVarAct), GET(SELF.bumpVarAct)) OPTION(reverse_arcs, "" , ARG(implicit("1")->arg("{0..3}")), "Enable ManySAT-like inverse-arc learning", STORE_LEQ(SELF.reverseArcs, 3u), GET(SELF.reverseArcs)) @@ -308,8 +314,8 @@ OPTION(contraction , "!,@2", ARG_EXT(arg(""),\ "Configure handling of long learnt nogoods\n" " %A: [,]\n"\ " : Contract nogoods if size > (0=disable)\n"\ - " : Nogood replacement {no|decisionSeq|allUIP|dynamic} [no]\n", FUN(arg) { uint32 n = 0; SolverStrategies::CCRepMode r = SolverStrategies::cc_no_replace;\ - return (arg.off() || (arg>>n>>opt(r) && n != 0u)) && SET_OR_FILL(SELF.compress, n) && SET(SELF.ccRepMode, uint32(r));},\ + " : Nogood replacement {no|decisionSeq|allUIP|dynamic} [no]\n", FUN(arg) { uint32_t n = 0; SolverStrategies::CCRepMode r = SolverStrategies::cc_no_replace;\ + return (arg.off() || (arg>>n>>opt(r) && n != 0u)) && SET_OR_FILL(SELF.compress, n) && SET(SELF.ccRepMode, uint32_t(r));},\ GET_IF(SELF.compress, SELF.compress, (SolverStrategies::CCRepMode)SELF.ccRepMode)) OPTION(loops, "" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(DefaultUnfoundedCheck::ReasonStrategy,\ MAP("common" , DefaultUnfoundedCheck::common_reason) , MAP("shared", DefaultUnfoundedCheck::shared_reason), \ @@ -319,7 +325,7 @@ OPTION(loops, "" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(DefaultUnfoundedCh " common : Create loop nogoods for atoms in an unfounded set\n" \ " distinct: Create distinct loop nogood for each atom in an unfounded set\n" \ " shared : Create loop formula for a whole unfounded set\n" \ - " no : Do not learn loop formulas\n", FUN(arg) {DefaultUnfoundedCheck::ReasonStrategy x; return arg>>x && SET(SELF.loopRep, (uint32)x);},\ + " no : Do not learn loop formulas\n", FUN(arg) {DefaultUnfoundedCheck::ReasonStrategy x; return arg>>x && SET(SELF.loopRep, (uint32_t)x);},\ GET(static_cast(SELF.loopRep))) GROUP_END(SELF) #undef CLASP_SOLVER_OPTIONS @@ -337,7 +343,7 @@ OPTION(partial_check, "", ARG(implicit("50")), "Configure partial stability test " %A:

[,] / Implicit: %I\n" \ "

: Partial check skip percentage\n" \ " : Init/update value for high bound ([0]=umax)", FUN(arg) {\ - uint32 p = 0; uint32 h = 0; \ + uint32_t p = 0; uint32_t h = 0; \ return (arg.off() || (arg>>p>>opt(h) && p)) && SET_LEQ(SELF.fwdCheck.highPct, p, 100u) && SET_OR_ZERO(SELF.fwdCheck.highStep, h);},\ GET_IF(SELF.fwdCheck.highPct, SELF.fwdCheck.highPct, SELF.fwdCheck.highStep)) OPTION(sign_def_disj, ",@2", ARG(arg("")), "Default sign for atoms in disjunctions", STORE_U(SolverStrategies::SignHeu, SELF.fwdCheck.signDef), GET((SolverStrategies::SignHeu)SELF.fwdCheck.signDef)) @@ -345,7 +351,7 @@ OPTION(rand_freq, "!", ARG(arg("

")), "Make random decisions with probability double f = 0.0; \ return (arg.off() || arg>>f) && SET_R(SELF.randProb, (float)f, 0.0f, 1.0f);}, GET(SELF.randProb)) OPTION(rand_prob, "", ARG(arg("[,]")), "Do random searches with [=100] conflicts",\ - FUN(arg) { uint32 n1 = 0; uint32 n2 = 100;\ + FUN(arg) { uint32_t n1 = 0; uint32_t n2 = 100;\ return (arg.off() || (arg>>n1>>opt(n2) && n1)) && SET_OR_FILL(SELF.randRuns, n1) && SET_OR_FILL(SELF.randConf, n2);},\ GET_IF(SELF.randRuns, SELF.randRuns,SELF.randConf)) #undef SELF @@ -388,7 +394,7 @@ OPTION(counter_restarts, "" , ARG(arg("")), "Use counter implication rest " %A: ([,] | {0|no})\n" \ " : Interval in number of restarts\n" \ " : Bump factor applied to indegrees", \ - FUN(arg) { uint32 n = 0; uint32 m = SELF.counterBump; \ + FUN(arg) { uint32_t n = 0; uint32_t m = SELF.counterBump; \ return (arg.off() || (arg >> n >> opt(m) && n > 0)) && SET_OR_FILL(SELF.counterRestart, n) && SET_OR_FILL(SELF.counterBump, m); },\ GET_IF(SELF.counterRestart, SELF.counterRestart, SELF.counterBump)) OPTION(block_restarts , "" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(MovingAvg::Type, \ @@ -400,11 +406,11 @@ OPTION(block_restarts , "" , ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(Moving " : Block restart if assignment > average * [1.4]\n" \ " : Disable blocking for the first conflicts [10000]\n" \ " : Type of moving average (see restarts) [e]\n", \ - FUN(arg) { uint32 n = 0; float R = 1.4; uint32 c = 10000; MovingAvg::Type a = MovingAvg::avg_ema; \ + FUN(arg) { uint32_t n = 0; double R = 1.4; uint32_t c = 10000; MovingAvg::Type a = MovingAvg::avg_ema; \ return ITE(arg.off(), (SELF.block=RestartParams::Block(), true), arg>>n>>opt(R)>>opt(c)>>opt(a) && SET_GEQ(SELF.block.window, n, 1) \ - && R >= 1.0 && R <= 5.0 && SET(SELF.block.fscale, static_cast(R*100.0f)) && SET(SELF.block.first, c) && SET(SELF.block.avg, a)); },\ + && R >= 1.0 && R <= 5.0 && SET(SELF.block.fscale, static_cast(R*100.0)) && SET(SELF.block.first, c) && SET(SELF.block.avg, a)); },\ GET_FUN(str) { ITE(!SELF.block.window, str<(SELF.block.avg)); }) -OPTION(shuffle , "!" , ARG(arg(",")), "Shuffle problem after +(*i) restarts\n", FUN(arg) { uint32 n1 = 0; uint32 n2 = 0;\ +OPTION(shuffle , "!" , ARG(arg(",")), "Shuffle problem after +(*i) restarts\n", FUN(arg) { uint32_t n1 = 0; uint32_t n2 = 0;\ return (arg.off() || (arg>>n1>>opt(n2) && n1)) && SET_OR_FILL(SELF.shuffle, n1) && SET_OR_FILL(SELF.shuffleNext, n2);},\ GET_IF(SELF.shuffle, SELF.shuffle, SELF.shuffleNext)) #if defined(NOTIFY_SUBGROUPS) @@ -430,9 +436,9 @@ OPTION(deletion , "!,d", ARG_EXT(defaultsTo("basic,75,activity")->state(Value " : Delete at most %% of nogoods on reduction [75]\n" \ " : Use {activity|lbd|mixed} nogood scores [activity]\n" \ " no : Disable nogood deletion", FUN(arg){\ - ReduceStrategy::Algorithm algo = ReduceStrategy::reduce_linear; uint32 n; ReduceStrategy::Score sc;\ + ReduceStrategy::Algorithm algo = ReduceStrategy::reduce_linear; uint32_t n; ReduceStrategy::Score sc;\ return ITE(arg.off(), (SELF.disable(), true), arg>>algo>>opt(n = 75)>>opt(sc = ReduceStrategy::score_act)\ - && SET(SELF.strategy.algo, (uint32)algo) && SET_R(SELF.strategy.fReduce, n, 1, 100) && SET(SELF.strategy.score, (uint32)sc));},\ + && SET(SELF.strategy.algo, (uint32_t)algo) && SET_R(SELF.strategy.fReduce, n, 1, 100) && SET(SELF.strategy.score, (uint32_t)sc));},\ GET_IF(SELF.strategy.fReduce, (ReduceStrategy::Algorithm)SELF.strategy.algo, SELF.strategy.fReduce,(ReduceStrategy::Score)SELF.strategy.score)) OPTION(del_grow , "!", NO_ARG, "Configure size-based deletion policy\n" \ " %A: [,][,] ( >= 1.0)\n" \ @@ -450,16 +456,16 @@ OPTION(del_cfl , "!", ARG(arg("")), "Configure conflict-based deletio OPTION(del_init , "" , ARG(defaultsTo("3.0")->state(Value::value_defaulted)), "Configure initial deletion limit\n"\ " %A: [,,] ( > 0)\n" \ " : Set initial limit to P=estimated problem size/ [%D]\n" \ - " ,: Clamp initial limit to the range [,+]" , FUN(arg) { double f; uint32 lo = 10; uint32 hi = UINT32_MAX;\ - return arg>>f>>opt(lo)>>opt(hi) && f > 0.0 && (SELF.fInit = float(1.0 / f)) > 0 && SET_OR_FILL(SELF.initRange.lo, lo) && SET_OR_FILL(SELF.initRange.hi, (uint64(hi)+SELF.initRange.lo));},\ + " ,: Clamp initial limit to the range [,+]" , FUN(arg) { double f; uint32_t lo = 10; uint32_t hi = UINT32_MAX;\ + return arg>>f>>opt(lo)>>opt(hi) && f > 0.0 && (SELF.fInit = float(1.0 / f)) > 0 && SET_OR_FILL(SELF.initRange.lo, lo) && SET_OR_FILL(SELF.initRange.hi, (uint64_t(hi)+SELF.initRange.lo));},\ GET_IF(SELF.fInit, 1.0/SELF.fInit, SELF.initRange.lo, SELF.initRange.hi - SELF.initRange.lo)) OPTION(del_estimate, "", ARG(arg("0..3")->implicit("1")), "Use estimated problem complexity in limits", STORE_LEQ(SELF.strategy.estimate, 3u), GET(SELF.strategy.estimate)) -OPTION(del_max , "!", ARG(arg(",")), "Keep at most learnt nogoods taking up to MB", FUN(arg) { uint32 n = UINT32_MAX; uint32 mb = 0; \ +OPTION(del_max , "!", ARG(arg(",")), "Keep at most learnt nogoods taking up to MB", FUN(arg) { uint32_t n = UINT32_MAX; uint32_t mb = 0; \ return (arg.off() || arg>>n>>opt(mb)) && SET_GEQ(SELF.maxRange, n, 1u) && SET(SELF.memMax, mb);}, GET(SELF.maxRange, SELF.memMax)) OPTION(del_glue , "", NO_ARG, "Configure glue clause handling\n" \ " %A: [,]\n" \ " : Do not delete nogoods with LBD <= \n" \ - " : Count (0) or ignore (1) glue clauses in size limit [0]", FUN(arg) {uint32 lbd; uint32 m = 0; \ + " : Count (0) or ignore (1) glue clauses in size limit [0]", FUN(arg) {uint32_t lbd; uint32_t m = 0; \ return arg>>lbd>>opt(m) && SET(SELF.strategy.glue, lbd) && SET(SELF.strategy.noGlue, m);}, GET(SELF.strategy.glue, SELF.strategy.noGlue)) OPTION(del_on_restart, "", ARG(arg("")), "Delete %A%% of learnt nogoods on each restart", STORE_LEQ(SELF.strategy.fRestart, 100u), GET(SELF.strategy.fRestart)) #if defined(NOTIFY_SUBGROUPS) @@ -513,9 +519,9 @@ GROUP_END(SELF) #define SELF CLASP_SOLVE_OPTIONS GROUP_BEGIN(SELF) OPTION(solve_limit , ",@1", ARG(arg("[,]")), "Stop search after conflicts or restarts\n", FUN(arg) {\ - uint32 n = UINT32_MAX; uint32 m = UINT32_MAX;\ + uint32_t n = UINT32_MAX; uint32_t m = UINT32_MAX;\ return ((arg.off() && arg.peek() != '0') || arg>>n>>opt(m)) && (SELF.limit=SolveLimits(n == UINT32_MAX ? UINT64_MAX : n, m == UINT32_MAX ? UINT64_MAX : m), true);},\ - GET((uint32)Range(0u,UINT32_MAX).clamp(SELF.limit.conflicts),(uint32)Range(0u,UINT32_MAX).clamp(SELF.limit.restarts))) + GET((uint32_t)Clasp::clamp(SELF.limit.conflicts, 0u, UINT32_MAX),(uint32_t)Clasp::clamp(SELF.limit.restarts, 0u,UINT32_MAX))) #if CLASP_HAS_THREADS OPTION(parallel_mode, ",t", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolveOptions::Algorithm::SearchMode,\ MAP("compete", SolveOptions::Algorithm::mode_compete), MAP("split", SolveOptions::Algorithm::mode_split))),\ @@ -523,7 +529,7 @@ OPTION(parallel_mode, ",t", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(SolveOptio " %A: [,]\n" \ " : Number of threads to use in search\n"\ " : Run competition or splitting based search [compete]\n", FUN(arg){\ - uint32 n; SolveOptions::Algorithm::SearchMode mode;\ + uint32_t n; SolveOptions::Algorithm::SearchMode mode;\ return arg>>n>>opt(mode = SolveOptions::Algorithm::mode_compete) && SET_R(SELF.algorithm.threads, n, 1u, 64u) && SET(SELF.algorithm.mode, mode);},\ GET(SELF.algorithm.threads, (SolveOptions::Algorithm::SearchMode)SELF.algorithm.mode)) OPTION(global_restarts, ",@1", ARG(arg("")), "Configure global restart policy\n" \ @@ -542,10 +548,10 @@ OPTION(distribute, "!,@1", ARG_EXT(defaultsTo("conflict,global,4"), \ " : Use {global|local} distribution [global]\n" \ " : Distribute only if LBD <= [4]\n" \ " : Distribute only if size <= [-1]", \ - FUN(arg) { Distributor::Policy::Types type; SolveOptions::Distribution::Mode mode = SolveOptions::Distribution::mode_global; uint32 lbd; uint32 size; \ + FUN(arg) { Distributor::Policy::Types type; SolveOptions::Distribution::Mode mode = SolveOptions::Distribution::mode_global; uint32_t lbd; uint32_t size; \ if (arg.off()) { SELF.distribute.policy() = Distributor::Policy(0, 0, 0); return true; } return (arg >> type) && (arg.peek() < 'A' || arg >> opt(mode)) && arg >> opt(lbd = 4) >> opt(size = UINT32_MAX) - && SET(SELF.distribute.types, (uint32)type) && SET(SELF.distribute.mode, (uint32)mode) && SET(SELF.distribute.lbd, lbd) && SET_OR_FILL(SELF.distribute.size, size);},\ + && SET(SELF.distribute.types, (uint32_t)type) && SET(SELF.distribute.mode, (uint32_t)mode) && SET(SELF.distribute.lbd, lbd) && SET_OR_FILL(SELF.distribute.size, size);},\ GET_FUN(str) { ITE(!SELF.distribute.types, str << off,\ str << (Distributor::Policy::Types)SELF.distribute.types << (SolveOptions::Distribution::Mode)SELF.distribute.mode << SELF.distribute.lbd << SELF.distribute.size); }) OPTION(integrate, ",@1", ARG_EXT(defaultsTo("gp")->state(Value::value_defaulted),\ @@ -560,8 +566,8 @@ OPTION(integrate, ",@1", ARG_EXT(defaultsTo("gp")->state(Value::value_defaulted) " : Add {all|unsat|gp(unsat wrt guiding path)|active} nogoods\n" \ " : Always keep at least last integrated nogoods [1024]\n" \ " : Accept nogoods from {all|ring|cube|cubex} peers [all]\n", FUN(arg) {\ - SolveOptions::Integration::Filter pick = SolveOptions::Integration::filter_no; uint32 n; SolveOptions::Integration::Topology topo; - return arg>>pick>>opt(n = 1024)>>opt(topo = SolveOptions::Integration::topo_all) && SET(SELF.integrate.filter, (uint32)pick) && SET_OR_FILL(SELF.integrate.grace, n) && SET(SELF.integrate.topo, (uint32)topo);},\ + SolveOptions::Integration::Filter pick = SolveOptions::Integration::filter_no; uint32_t n; SolveOptions::Integration::Topology topo; + return arg>>pick>>opt(n = 1024)>>opt(topo = SolveOptions::Integration::topo_all) && SET(SELF.integrate.filter, (uint32_t)pick) && SET_OR_FILL(SELF.integrate.grace, n) && SET(SELF.integrate.topo, (uint32_t)topo);},\ GET((SolveOptions::Integration::Filter)SELF.integrate.filter, SELF.integrate.grace, (SolveOptions::Integration::Topology)SELF.integrate.topo)) #endif OPTION(enum_mode , ",e", ARG_EXT(defaultsTo("auto")->state(Value::value_defaulted), DEFINE_ENUM_MAPPING(SolveOptions::EnumType,\ @@ -576,8 +582,8 @@ OPTION(enum_mode , ",e", ARG_EXT(defaultsTo("auto")->state(Value::value_defaul " brave : Compute brave consequences (union of models)\n" \ " cautious: Compute cautious consequences (intersection of models)\n" \ " auto : Use bt for enumeration and record for optimization", STORE(SELF.enumMode), GET(SELF.enumMode)) -OPTION(project, "!", ARG_EXT(arg("")->implicit("auto,3"), DEFINE_ENUM_MAPPING(ProjectMode_t::Mode,\ - MAP("auto", ProjectMode_t::Implicit), MAP("show", ProjectMode_t::Output), MAP("project", ProjectMode_t::Explicit))),\ +OPTION(project, "!", ARG_EXT(arg("")->implicit("auto,3"), DEFINE_ENUM_MAPPING(ProjectMode,\ + MAP("auto", ProjectMode::implicit), MAP("show", ProjectMode::output), MAP("project", ProjectMode::project))),\ "Enable projective solution enumeration\n" \ " %A: {show|project|auto}[,] (Implicit: %I)\n" \ " Project to atoms in show or project directives, or\n" \ @@ -585,13 +591,13 @@ OPTION(project, "!", ARG_EXT(arg("")->implicit("auto,3"), DEFINE_ENUM_MAPPI " : Additional options for enumeration algorithm 'bt'\n" \ " Use activity heuristic (1) when selecting backtracking literal\n" \ " and/or progress saving (2) when retracting solution literals", \ - FUN(arg) { ProjectMode m = ProjectMode_t::Implicit; uint32 p = 0; \ + FUN(arg) { auto m = ProjectMode::implicit; uint32_t p = 0; \ return (arg.off() || (arg.peek() < '9' && arg >> p) || ((arg >> m >> opt(p)) && (p = (p<<1)|1) != 0u)) && SET(SELF.proMode, m) && SET_LEQ(SELF.project, p, 7u);},\ GET_IF(SELF.project, SELF.proMode, SELF.project >> 1)) OPTION(models, ",n", ARG(arg("")), "Compute at most %A models (0 for all)\n", STORE(SELF.numModels), GET(SELF.numModels)) -OPTION(opt_mode , "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(MinimizeMode_t::Mode,\ - MAP("opt" , MinimizeMode_t::optimize), MAP("enum" , MinimizeMode_t::enumerate),\ - MAP("optN", MinimizeMode_t::enumOpt) , MAP("ignore", MinimizeMode_t::ignore))),\ +OPTION(opt_mode , "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(MinimizeMode,\ + MAP("opt" , MinimizeMode::optimize), MAP("enum" , MinimizeMode::enumerate),\ + MAP("optN", MinimizeMode::enum_opt) , MAP("ignore", MinimizeMode::ignore))),\ "Configure optimization algorithm\n"\ " %A: [,...]\n" \ " opt : Find optimal model\n" \ @@ -599,7 +605,7 @@ OPTION(opt_mode , "", ARG_EXT(arg(""), DEFINE_ENUM_MAPPING(MinimizeMode_t " optN : Find optimum, then enumerate optimal models\n"\ " ignore: Ignore optimize statements\n" \ " : Set initial bound for objective function(s)", \ - FUN(arg) { MinimizeMode_t::Mode m = MinimizeMode_t::optimize; SumVec B; return (arg >> m >> opt(B)) && SET(SELF.optMode, m) && (SELF.optBound.swap(B), true); }, \ + FUN(arg) { MinimizeMode m = MinimizeMode::optimize; SumVec B; return (arg >> m >> opt(B)) && SET(SELF.optMode, m) && (SELF.optBound.swap(B), true); }, \ GET_FUN(str) { str << SELF.optMode; if (!SELF.optBound.empty()) str << SELF.optBound; }) OPTION(opt_stop, "", ARG(arg("...")), "Stop optimization on model with cost <= \n", FUN(arg) { SumVec B; return ITE(arg.peek() != '0' && arg.off(), true, arg >> B) && (SELF.optStop = B, true); }, diff --git a/clasp/cli/clasp_options.h b/clasp/cli/clasp_options.h index 47d0dcd..d5442a7 100644 --- a/clasp/cli/clasp_options.h +++ b/clasp/cli/clasp_options.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,28 +21,23 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLI_CLASP_OPTIONS_H_INCLUDED -#define CLASP_CLI_CLASP_OPTIONS_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include -#include -namespace Potassco { namespace ProgramOptions { + +namespace Potassco::ProgramOptions { class OptionContext; class OptionGroup; class ParsedOptions; -}} +} // namespace Potassco::ProgramOptions + /*! * \file * \brief Types and functions for processing command-line options. */ -namespace Clasp { //! Namespace for types and functions used by the command-line interface. -namespace Cli { +namespace Clasp::Cli { /** * \defgroup cli Cli @@ -55,31 +50,33 @@ class ClaspCliConfig; //! Class for iterating over a set of configurations. class ConfigIter { public: - const char* name() const; - const char* base() const; - const char* args() const; - bool valid()const; - bool next(); + [[nodiscard]] const char* name() const; + [[nodiscard]] const char* base() const; + [[nodiscard]] const char* args() const; + [[nodiscard]] bool valid() const; + bool next(); + private: - friend class ClaspCliConfig; - ConfigIter(const char* x); - const char* base_; + friend class ClaspCliConfig; + ConfigIter(const char* x); + const char* base_; }; //! Valid configuration keys. /*! * \see clasp_cli_configs.inl */ enum ConfigKey { -#define CONFIG(id,k,c,s,p) config_##k, +#define CONFIG(id, k, c, s, p) config_##k, #define CLASP_CLI_DEFAULT_CONFIGS config_default = 0, #define CLASP_CLI_AUX_CONFIGS config_default_max_value, #include - config_aux_max_value, - config_many, // default portfolio - config_max_value, - config_asp_default = config_tweety, - config_sat_default = config_trendy, - config_tester_default= config_tester, + + config_aux_max_value, + config_many, // default portfolio + config_max_value, + config_asp_default = config_tweety, + config_sat_default = config_trendy, + config_tester_default = config_tester, }; /*! * \brief Class for storing/processing command-line options. @@ -106,188 +103,191 @@ enum ConfigKey { */ class ClaspCliConfig : public ClaspConfig { public: - //! Returns defaults for the given problem type. - static const char* getDefaults(ProblemType f); - //! Returns the configuration with the given key. - static ConfigIter getConfig(ConfigKey key); - //! Returns the ConfigKey of k or -1 if k is not a known configuration. - static int getConfigKey(const char* k); + //! Returns defaults for the given problem type. + static const char* getDefaults(ProblemType f); + //! Returns the configuration with the given key. + static ConfigIter getConfig(ConfigKey key); + //! Returns the ConfigKey of k or -1 if k is not a known configuration. + static int getConfigKey(const char* k); - ClaspCliConfig(); - ~ClaspCliConfig(); - // Base interface - virtual void prepare(SharedContext&); - virtual void reset(); - virtual Configuration* config(const char*); + ClaspCliConfig(); + ~ClaspCliConfig() override; + // Base interface + void prepare(SharedContext&) override; + void reset() override; + Configuration* config(const char*) override; - /*! - * \name Key-based low-level interface - * - * The functions in this group do not throw exceptions but - * signal logic errors via return values < 0. - * @{ */ + /*! + * \name Key-based low-level interface + * + * The functions in this group do not throw exceptions but + * signal logic errors via return values < 0. + * @{ */ - typedef uint32 KeyType; - static const KeyType KEY_INVALID; //!< Invalid key used to signal errors. - static const KeyType KEY_ROOT; //!< Root key of a configuration, i.e. "." - static const KeyType KEY_TESTER; //!< Root key for tester options, i.e. "tester." - static const KeyType KEY_SOLVER; //!< Root key for (array of) solver options, i.e. "solver." + using KeyType = uint32_t; + static const KeyType key_invalid; //!< Invalid key used to signal errors. + static const KeyType key_root; //!< Root key of a configuration, i.e. "." + static const KeyType key_tester; //!< Root key for tester options, i.e. "tester." + static const KeyType key_solver; //!< Root key for (array of) solver options, i.e. "solver." - //! Returns true if k is a leaf, i.e. has no subkeys. - static bool isLeafKey(KeyType k); + //! Returns true if k is a leaf, i.e. has no subkeys. + static bool isLeafKey(KeyType k); - //! Retrieves a handle to the specified key. - /*! - * \param key A valid handle to a key. - * \param name The name of the subkey to retrieve. - * \return - * - key, if name is 0 or empty. - * - KEY_INVALID, if name is not a subkey of key. - * - A handle to the subkey. - * . - */ - KeyType getKey(KeyType key, const char* name = 0) const; + //! Retrieves a handle to the specified key. + /*! + * \param key A valid handle to a key. + * \param name The name of the subkey to retrieve. + * \return + * - key, if name is 0 or empty. + * - KEY_INVALID, if name is not a subkey of key. + * - A handle to the subkey. + * . + */ + KeyType getKey(KeyType key, const char* name = nullptr) const; - //! Retrieves a handle to the specified element of the given array key. - /*! - * \param arr A valid handle to an array. - * \param element The index of the element to retrieve. - * \return - * - A handle to the requested element, or - * - KEY_INVALID, if arr does not reference an array or element is out of bounds. - * . - */ - KeyType getArrKey(KeyType arr, unsigned element) const; + //! Retrieves a handle to the specified element of the given array key. + /*! + * \param arr A valid handle to an array. + * \param element The index of the element to retrieve. + * \return + * - A handle to the requested element, or + * - KEY_INVALID, if arr does not reference an array or element is out of bounds. + * . + */ + [[nodiscard]] KeyType getArrKey(KeyType arr, unsigned element) const; - //! Retrieves information about the specified key. - /*! - * \param key A valid handle to a key. - * \param[out] nSubkeys The number of subkeys of this key or 0 if key is a leaf. - * \param[out] arrLen If key is an array, the length of the array (can be 0). Otherwise, -1. - * \param[out] help A description of the key. - * \param[out] nValues The number of values the key currently has (0 or 1) or -1 if it can't have values. - * \note All out parameters are optional, i.e. can be 0. - * \return The number of out values or -1 if key is invalid. - */ - int getKeyInfo(KeyType key, int* nSubkeys = 0, int* arrLen = 0, const char** help = 0, int* nValues = 0) const; + //! Retrieves information about the specified key. + /*! + * \param key A valid handle to a key. + * \param[out] nSubkeys The number of subkeys of this key or 0 if key is a leaf. + * \param[out] arrLen If key is an array, the length of the array (can be 0). Otherwise, -1. + * \param[out] help A description of the key. + * \param[out] nValues The number of values the key currently has (0 or 1) or -1 if it can't have values. + * \note All out parameters are optional, i.e. can be 0. + * \return The number of out values or -1 if key is invalid. + */ + int getKeyInfo(KeyType key, int* nSubkeys = nullptr, int* arrLen = nullptr, const char** help = nullptr, + int* nValues = nullptr) const; - //! Returns the name of the i'th subkey of k or 0 if no such subkey exists. - const char* getSubkey(KeyType k, uint32 i) const; + //! Returns the name of the i-th subkey of k or 0 if no such subkey exists. + [[nodiscard]] const char* getSubkey(KeyType k, uint32_t i) const; - //! Creates and returns a string representation of the value of the given key. - /*! - * \param k A valid handle to a key. - * \param[out] value The current value of the key. - * \return The length of value or < 0 if k either has no value (-1) or an error occurred while writing the value (< -1). - */ - int getValue(KeyType k, std::string& value) const; + //! Creates and returns a string representation of the value of the given key. + /*! + * \param k A valid handle to a key. + * \param[out] value The current value of the key. + * \return The length of value or < 0 if k either has no value (-1) or an error occurred while writing the value (< + * -1). + */ + int getValue(KeyType k, std::string& value) const; - //! Writes a null-terminated string representation of the value of the given key into the supplied buffer. - /*! - * \param k A valid handle to a key. - * \param[out] buffer The current value of the key. - * \param bufSize The size of buffer. - * \note Although the number returned can be larger than the bufSize, the function - * never writes more than bufSize bytes into the buffer. - */ - int getValue(KeyType k, char* buffer, std::size_t bufSize) const; + //! Writes a null-terminated string representation of the value of the given key into the supplied buffer. + /*! + * \param k A valid handle to a key. + * \param[out] buffer The current value of the key. + * \param bufSize The size of buffer. + * \note Although the number returned can be larger than the bufSize, the function + * never writes more than bufSize bytes into the buffer. + */ + int getValue(KeyType k, char* buffer, std::size_t bufSize) const; - //! Sets the option identified by the given key. - /*! - * \param key A valid handle to a key. - * \param value The value to set. - * \return - * - > 0: if the value was set. - * - = 0: if value is not a valid value for the given key. - * - < 0: f key does not accept a value (-1), or some error occurred (< -1). - * . - */ - int setValue(KeyType key, const char* value); + //! Sets the option identified by the given key. + /*! + * \param key A valid handle to a key. + * \param value The value to set. + * \return + * - > 0: if the value was set. + * - = 0: if value is not a valid value for the given key. + * - < 0: f key does not accept a value (-1), or some error occurred (< -1). + * . + */ + int setValue(KeyType key, const char* value); - //@} + //@} - /*! - * \name String-based interface - * - * The functions in this group wrap the key-based functions and - * signal logic errors by throwing exceptions. - * @{ */ - //! Returns the value of the option identified by the given key. - std::string getValue(const char* key) const; - //! Returns true if the given key has an associated value. - bool hasValue(const char* key) const; - //! Sets the option identified by the given key. - bool setValue(const char* key, const char* value); - //@} + /*! + * \name String-based interface + * + * The functions in this group wrap the key-based functions and + * signal logic errors by throwing exceptions. + * @{ */ + //! Returns the value of the option identified by the given key. + std::string getValue(const char* key) const; + //! Returns true if the given key has an associated value. + bool hasValue(const char* key) const; + //! Sets the option identified by the given key. + bool setValue(const char* key, const char* value); + //@} - //! Validates this configuration. - bool validate(); + //! Validates this configuration. + bool validate(); - /*! - * \name App interface - * - * Functions for connecting a configuration with the ProgramOptions library. - * @{ */ - //! Adds all available options to root. - /*! - * Once options are added, root can be used with an option source (e.g. the command-line) - * to populate this object. - */ - void addOptions(Potassco::ProgramOptions::OptionContext& root); - //! Adds options that are disabled by the options contained in parsed to parsed. - void addDisabled(Potassco::ProgramOptions::ParsedOptions& parsed); - //! Applies the options in parsed and finalizes and validates this configuration. - bool finalize(const Potassco::ProgramOptions::ParsedOptions& parsed, ProblemType type, bool applyDefaults); - //! Populates this configuration with the options given in [first, last) and finalizes it. - /*! - * \param [first, last) a range of options in argv format. - * \param t Problem type for which this configuration is created. Used to set defaults. - */ - template - bool setConfig(IT first, IT last, ProblemType t) { - std::string args; - while (first != last) { args.append(!args.empty(), ' ').append(*first++); } - return setAppConfig(args, t); - } - //! Releases internal option objects needed for command-line style option processing. - /*! - * \note Subsequent calls to certain functions of this object (e.g. addOptions(), setConfig()) - * recreate the option objects if necessary. - */ - void releaseOptions(); - //@} + /*! + * \name App interface + * + * Functions for connecting a configuration with the ProgramOptions library. + * @{ */ + //! Adds all available options to root. + /*! + * Once options are added, root can be used with an option source (e.g. the command-line) + * to populate this object. + */ + void addOptions(Potassco::ProgramOptions::OptionContext& root); + //! Adds options that are disabled by the options contained in 'parsed' to 'parsed'. + void addDisabled(Potassco::ProgramOptions::ParsedOptions& parsed); + //! Applies the options in parsed and finalizes and validates this configuration. + bool finalize(const Potassco::ProgramOptions::ParsedOptions& parsed, ProblemType type, bool applyDefaults); + //! Populates this configuration with the options given in [first, last) and finalizes it. + /*! + * \param first begin of range of options in argv format. + * \param last end of range of options in argv format. + * \param t Problem type for which this configuration is created. Used to set defaults. + */ + template + bool setConfig(It first, It last, ProblemType t) { + std::string args; + while (first != last) { args.append(not args.empty(), ' ').append(*first++); } + return setAppConfig(args, t); + } + //! Releases internal option objects needed for command-line style option processing. + /*! + * \note Subsequent calls to certain functions of this object (e.g. addOptions(), setConfig()) + * recreate the option objects if necessary. + */ + void releaseOptions(); + //@} private: - struct ParseContext; - class ProgOption; - typedef Potassco::ProgramOptions::OptionContext OptionContext; - typedef Potassco::ProgramOptions::OptionGroup Options; - typedef SingleOwnerPtr OptionsPtr; - typedef Potassco::ProgramOptions::ParsedOptions ParsedOpts; - // Operations on active config and solver - int setOption(int option, uint8 mode, uint32 sId, const char* value); - // App interface impl - bool setAppConfig(const std::string& c, ProblemType t); - int setAppOpt(int o, uint8 mode, const char* value); - bool setAppDefaults(ConfigKey config, uint8 mode, const ParsedOpts& exclude, ProblemType t); - bool finalizeAppConfig(uint8 mode, const ParsedOpts& exclude, ProblemType t, bool defs); - const ParsedOpts& finalizeParsed(uint8 mode, const ParsedOpts& parsed, ParsedOpts& exclude) const; - void createOptions(); - ProgOption* createOption(int o); - const std::string&getOptionName(int key, std::string& mem) const; - bool assignDefaults(const ParsedOpts&); - // Configurations - ConfigIter getConfig(uint8 key, std::string& tempMem) const; - bool setConfig(const char* name, const char* args, uint8 mode, uint32 sId, const ParsedOpts& exclude, ParsedOpts* out); - bool setConfig(const ConfigIter& c, uint8 mode, uint32 sId, const ParsedOpts& exclude, ParsedOpts* out); - // helpers - OptionsPtr opts_; - ParseContext* parseCtx_; - std::string config_[2]; - bool validate_; + struct ParseContext; + class ProgOption; + using OptionContext = Potassco::ProgramOptions::OptionContext; + using Options = Potassco::ProgramOptions::OptionGroup; + using OptionsPtr = std::unique_ptr; + using ParsedOpts = Potassco::ProgramOptions::ParsedOptions; + // Operations on active config and solver + int setOption(int option, uint8_t setMode, uint32_t sId, const char* value); + // App interface impl + bool setAppConfig(const std::string& c, ProblemType t); + int setAppOpt(int o, uint8_t mode, const char* value); + bool setAppDefaults(ConfigKey config, uint8_t mode, const ParsedOpts& exclude, ProblemType t); + bool finalizeAppConfig(uint8_t mode, const ParsedOpts& exclude, ProblemType t, bool defs); + const ParsedOpts& finalizeParsed(uint8_t mode, const ParsedOpts& parsed, ParsedOpts& exclude) const; + void createOptions(); + ProgOption* createOption(int o); + const std::string& getOptionName(int key, std::string& mem) const; + bool assignDefaults(const ParsedOpts&); + // Configurations + ConfigIter getConfig(uint8_t key, std::string& tempMem) const; + bool setConfig(const char* name, const char* args, uint8_t mode, uint32_t sId, const ParsedOpts& exclude, + ParsedOpts* out); + bool setConfig(const ConfigIter& c, uint8_t mode, uint32_t sId, const ParsedOpts& exclude, ParsedOpts* out); + // helpers + OptionsPtr opts_; + ParseContext* parseCtx_; + std::string config_[2]; + bool validate_; }; //! Validates the given solver configuration and returns an error string if invalid. const char* validate(const SolverParams& solver, const SolveParams& search); //@} -}} -#endif +} // namespace Clasp::Cli diff --git a/clasp/cli/clasp_output.h b/clasp/cli/clasp_output.h index 2889ef7..f0903ff 100644 --- a/clasp/cli/clasp_output.h +++ b/clasp/cli/clasp_output.h @@ -1,7 +1,7 @@ // // Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,19 +21,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLI_OUTPUT_H_INCLUDED -#define CLASP_CLI_OUTPUT_H_INCLUDED +#pragma once + #include #include #include + #include -namespace Clasp { namespace Cli { -template -void formatEvent(const T& eventType, S& str); -template -void format(const Clasp::Event_t& ev, S& str) { - formatEvent(static_cast(ev), str); -} + +namespace Clasp::Cli { /*! * \addtogroup cli @@ -44,126 +40,135 @@ void format(const Clasp::Event_t& ev, S& str) { */ class Output : public EventHandler { public: - //! Supported levels for printing models, optimize values, and individual calls. - enum PrintLevel { - print_all = 0, //!< Print all models, optimize values, or calls. - print_best = 1, //!< Only print last model, optimize value, or call. - print_no = 2, //!< Do not print any models, optimize values, or calls. - }; - explicit Output(uint32 verb = 1); - virtual ~Output(); - //! Active verbosity level. - uint32 verbosity() const { return verbose_; } - //! Do not output any models? - bool quiet() const { return modelQ() == 2 && optQ() == 2; } - //! Print level for models. - int modelQ() const { return static_cast(quiet_[0]); } - //! Print level for optimization values. - int optQ() const { return static_cast(quiet_[1]); } - //! Print level for individual (solve) calls. - int callQ() const { return static_cast(quiet_[2]); } + //! Supported levels for printing models, optimize values, and individual calls. + enum PrintLevel { + print_all = 0, //!< Print all models, optimize values, or calls. + print_best = 1, //!< Only print last model, optimize value, or call. + print_no = 2, //!< Do not print any models, optimize values, or calls. + }; + explicit Output(uint32_t verb = 1); + ~Output() override; + Output(Output&&) = delete; + //! Active verbosity level. + [[nodiscard]] uint32_t verbosity() const { return verbose_; } + //! Do not output any models? + [[nodiscard]] bool quiet() const { return modelQ() == 2 && optQ() == 2; } + //! Print level for models. + [[nodiscard]] int modelQ() const { return quiet_[0]; } + //! Print level for optimization values. + [[nodiscard]] int optQ() const { return quiet_[1]; } + //! Print level for individual (solve) calls. + [[nodiscard]] int callQ() const { return quiet_[2]; } - using EventHandler::setVerbosity; - void setVerbosity(uint32 verb); - void setModelQuiet(PrintLevel model); - void setOptQuiet(PrintLevel opt); - void setCallQuiet(PrintLevel call); + using EventHandler::setVerbosity; + void setVerbosity(uint32_t verb); + void setModelQuiet(PrintLevel model); + void setOptQuiet(PrintLevel opt); + void setCallQuiet(PrintLevel call); + + //! Shall be called once on startup. + virtual void run(const char* solver, const char* version, const std::string* begInput, + const std::string* endInput) = 0; + //! Shall be called once on shutdown. + virtual void shutdown(const ClaspFacade::Summary& summary); + virtual void shutdown() = 0; + //! Handles ClaspFacade events by forwarding calls to startStep() and stopStep(). + void onEvent(const Event& ev) override; + //! Checks quiet-levels and forwards to printModel() if appropriate. + bool onModel(const Solver& s, const Model& m) override; + //! Checks quiet-levels and forwards to printLower() if appropriate. + bool onUnsat(const Solver& s, const Model& m) override; + //! Shall print the given model. + virtual void printModel(const OutputTable& out, const Model& m, PrintLevel x) = 0; + //! Called on unsat - may print new info. + virtual void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel); + //! A solving step has started. + virtual void startStep(const ClaspFacade&); + //! A solving step has stopped. + virtual void stopStep(const ClaspFacade::Summary& summary); + //! Shall print the given summary. + virtual void printSummary(const ClaspFacade::Summary& summary, bool final) = 0; + //! Shall print the given statistics. + virtual void printStatistics(const ClaspFacade::Summary& summary, bool final) = 0; - //! Shall be called once on startup. - virtual void run(const char* solver, const char* version, const std::string* begInput, const std::string* endInput) = 0; - //! Shall be called once on shutdown. - virtual void shutdown(const ClaspFacade::Summary& summary); - virtual void shutdown() = 0; - //! Handles ClaspFacade events by forwarding calls to startStep() and stopStep(). - virtual void onEvent(const Event& ev); - //! Checks quiet-levels and forwards to printModel() if appropriate. - virtual bool onModel(const Solver& s, const Model& m); - //! Checks quiet-levels and forwards to printLower() if appropriate. - virtual bool onUnsat(const Solver& s, const Model& m); - //! Shall print the given model. - virtual void printModel(const OutputTable& out, const Model& m, PrintLevel x) = 0; - //! Called on unsat - may print new info. - virtual void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel); - //! A solving step has started. - virtual void startStep(const ClaspFacade&); - //! A solving step has stopped. - virtual void stopStep(const ClaspFacade::Summary& summary); - //! Shall print the given summary. - virtual void printSummary(const ClaspFacade::Summary& summary, bool final) = 0; - //! Shall print the given statistics. - virtual void printStatistics(const ClaspFacade::Summary& summary, bool final) = 0; protected: - typedef std::pair OutPair; - typedef uintp UPtr; - typedef std::pair UPair; - void printWitness(const OutputTable& out, const Model& m, UPtr data); - virtual UPtr doPrint(const OutPair& out, uintp data); - UPair numCons(const OutputTable& out, const Model& m) const; - bool stats(const ClaspFacade::Summary& summary) const; - double elapsedTime() const; - double modelTime() const; + using OutPair = std::pair; + using UPtr = uintptr_t; + using UPair = std::pair; + void printWitness(const OutputTable& out, const Model& m, UPtr data); + virtual UPtr doPrint(const OutPair& out, uintptr_t data); + [[nodiscard]] static UPair numCons(const OutputTable& out, const Model& m); + [[nodiscard]] static bool stats(const ClaspFacade::Summary& summary); + [[nodiscard]] double elapsedTime() const; + [[nodiscard]] double modelTime() const; + private: - Output(const Output&); - Output& operator=(const Output&); - typedef const ClaspFacade::Summary* SumPtr; - double time_; // time of first event - double model_; // elapsed time on last model - SumPtr summary_ ; // summary of last step - uint32 verbose_ ; // verbosity level - uint8 quiet_[3]; // quiet levels for models, optimize, calls - bool last_; // print last model on summary + using SumPtr = const ClaspFacade::Summary*; + double time_; // time of first event + double model_; // elapsed time on last model + SumPtr summary_{nullptr}; // summary of last step + uint32_t verbose_{0}; // verbosity level + uint8_t quiet_[3]{}; // quiet levels for models, optimize, calls + bool last_ = false; // print last model on summary }; //! Prints models and solving statistics in Json-format to stdout. -class JsonOutput : public Output, private StatsVisitor { +class JsonOutput + : public Output + , private StatsVisitor { public: - explicit JsonOutput(uint32 verb); - ~JsonOutput(); - virtual void run(const char* solver, const char* version, const std::string* begInput, const std::string* endInput); - virtual void shutdown(const ClaspFacade::Summary& summary); - virtual void shutdown(); - virtual void printSummary(const ClaspFacade::Summary& summary, bool final); - virtual void printStatistics(const ClaspFacade::Summary& summary, bool final); + explicit JsonOutput(uint32_t verb); + ~JsonOutput() override; + void run(const char* solver, const char* version, const std::string* begInput, + const std::string* endInput) override; + void shutdown(const ClaspFacade::Summary& summary) override; + void shutdown() override; + void printSummary(const ClaspFacade::Summary& summary, bool final) override; + void printStatistics(const ClaspFacade::Summary& summary, bool final) override; + private: - virtual void startStep(const ClaspFacade&); - virtual void stopStep(const ClaspFacade::Summary& summary); - virtual void printModel(const OutputTable& out, const Model& m, PrintLevel x); - virtual void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel); - virtual bool visitThreads(Operation op); - virtual bool visitTester(Operation op); - virtual bool visitHccs(Operation op); + enum ObjType { type_object, type_array }; + void startStep(const ClaspFacade&) override; + void stopStep(const ClaspFacade::Summary& summary) override; + void printModel(const OutputTable& out, const Model& m, PrintLevel x) override; + void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel) override; + bool visitThreads(Operation op) override; + bool visitTester(Operation op) override; + bool visitHccs(Operation op) override; - virtual void visitThread(uint32, const SolverStats& stats); - virtual void visitHcc(uint32, const ProblemStats& p, const SolverStats& s); - virtual void visitLogicProgramStats(const Asp::LpStats& stats); - virtual void visitProblemStats(const ProblemStats& stats); - virtual void visitSolverStats(const SolverStats& stats); - virtual void visitExternalStats(const StatisticObject& stats); - virtual UPtr doPrint(const OutPair& out, UPtr data); - void printChildren(const StatisticObject& s); - enum ObjType { type_object, type_array }; - void pushObject(const char* k = 0, ObjType t = type_object, bool startIndent = false); - char popObject(); - void printKeyValue(const char* k, const char* v) ; - void printKeyValue(const char* k, uint64 v); - void printKeyValue(const char* k, uint32 v); - void printKeyValue(const char* k, double d); - void printKeyValue(const char* k, const StatisticObject& o); - void printString(const char* s, const char* sep); - void printKey(const char* k); - void printCosts(const SumVec& costs, const char* name = "Costs"); - void printSum(const char* name, Potassco::Span sum, const wsum_t* last = 0); - void printCons(const UPair& cons); - void printCoreStats(const CoreStats&); - void printExtStats(const ExtendedStats&, bool generator); - void printJumpStats(const JumpStats&); - void printTime(const char* name, double t); - void startWitness(double time); - void endWitness(); - bool hasWitnesses() const { return !objStack_.empty() && *objStack_.rbegin() == '['; } - uint32 indent() const { return static_cast(objStack_.size() * 2); } - const char* open_; - std::string objStack_; + void visitThread(uint32_t, const SolverStats& stats) override; + void visitHcc(uint32_t, const ProblemStats& p, const SolverStats& s) override; + void visitLogicProgramStats(const Asp::LpStats& stats) override; + void visitProblemStats(const ProblemStats& stats) override; + void visitSolverStats(const SolverStats& stats) override; + void visitExternalStats(const StatisticObject& stats) override; + UPtr doPrint(const OutPair& out, UPtr data) override; + + [[nodiscard]] bool hasWitnesses() const { return not objStack_.empty() && *objStack_.rbegin() == '['; } + [[nodiscard]] uint32_t indent() const { return size32(objStack_) * 2; } + + void printChildren(const StatisticObject& s); + void pushObject(const char* k = nullptr, ObjType t = type_object, bool startIndent = false); + char popObject(); + void printKeyValue(const char* k, const char* v); + void printKeyValue(const char* k, uint64_t v); + void printKeyValue(const char* k, uint32_t v); + void printKeyValue(const char* k, double d); + void printKeyValue(const char* k, const StatisticObject& o); + void printString(const char* s, const char* sep); + void printKey(const char* k); + void printCosts(SumView costs, const char* name = "Costs"); + void printSum(const char* name, SumView sum, const Wsum_t* last = nullptr); + void printCons(const UPair& cons); + void printCoreStats(const CoreStats&); + void printExtStats(const ExtendedStats&, bool generator); + void printJumpStats(const JumpStats&); + void printTime(const char* name, double t); + void startWitness(double time); + void endWitness(); + + const char* open_; + std::string objStack_; }; //! Default clasp format printer. @@ -175,109 +180,123 @@ class JsonOutput : public Output, private StatsVisitor { * - format_pb09 in PB-competition format * . * \see https://www.mat.unical.it/aspcomp2013/ - * \see http://www.satcompetition.org/2009/format-solvers2009.html - * \see http://www.cril.univ-artois.fr/PB09/solver_req.html + * \see https://web.archive.org/web/20170809225851/https://www.satcompetition.org/2009/format-solvers2009.html + * \see https://www.cril.univ-artois.fr/PB09/solver_req.html * */ -class TextOutput : public Output, private StatsVisitor { +class TextOutput + : public Output + , private StatsVisitor { public: - //! Supported text formats. - enum Format { format_asp, format_aspcomp, format_sat09, format_pb09 }; - enum ResultStr { res_unknown = 0, res_sat = 1, res_unsat = 2, res_opt = 3, num_str }; - enum CategoryKey { cat_comment, cat_value, cat_objective, cat_result, cat_value_term, cat_atom_name, cat_atom_var, num_cat }; + //! Supported text formats. + enum Format { format_asp, format_aspcomp, format_sat09, format_pb09 }; + enum ResultStr { res_unknown = 0, res_sat = 1, res_unsat = 2, res_opt = 3, num_str }; + enum CategoryKey { + cat_comment, + cat_value, + cat_objective, + cat_result, + cat_value_term, + cat_atom_name, + cat_atom_var, + num_cat + }; - const char* result[num_str]; //!< Default result strings. - const char* format[num_cat]; //!< Format strings. + const char* result[num_str]; //!< Default result strings. + const char* format[num_cat]; //!< Format strings. - TextOutput(uint32 verbosity, Format f, const char* catAtom = 0, char ifs = ' '); - ~TextOutput(); + TextOutput(uint32_t verbosity, Format fmt, const char* catAtom = nullptr, char ifs = ' '); + ~TextOutput() override; + + //! Prints a (comment) message containing the given solver and input. + void run(const char* solver, const char* version, const std::string* begInput, + const std::string* endInput) override; + using Output::shutdown; + void shutdown() override; + //! Prints the given model. + /*! + * Prints format[cat_value] followed by the elements of the model. Individual + * elements e are printed as format[cat_atom] and separated by the internal field separator. + */ + void printModel(const OutputTable& out, const Model& m, PrintLevel x) override; + //! Prints the given lower bound and upper bounds that are known to be optimal. + void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel) override; + //! Called once a solving step has completed. + /*! + * Always prints "format[cat_result] result[s.result()]". + * Furthermore, if verbosity() > 0, prints a summary consisting of + * - the number of computed models m and whether the search space was exhausted + * - the number of enumerated models e if e != m + * - the state of any optimization and whether the last model was optimal + * - the state of consequence computation and whether the last model corresponded to the consequences + * - timing information + * . + */ + void printSummary(const ClaspFacade::Summary& s, bool final) override; + void printStatistics(const ClaspFacade::Summary& s, bool final) override; + //! Prints progress events (preprocessing/solving) if verbosity() > 1. + void onEvent(const Event& ev) override; + //! A solving step has started. + void startStep(const ClaspFacade&) override; + //! A solving step has finished. + void stopStep(const ClaspFacade::Summary& s) override; + //! Prints a comment message. + void comment(uint32_t v, const char* fmt, ...) const; - //! Prints a (comment) message containing the given solver and input. - virtual void run(const char* solver, const char* version, const std::string* begInput, const std::string* endInput); - virtual void shutdown(); - //! Prints the given model. - /*! - * Prints format[cat_value] followed by the elements of the model. Individual - * elements e are printed as format[cat_atom] and separated by the internal field separator. - */ - virtual void printModel(const OutputTable& out, const Model& m, PrintLevel x); - //! Prints the given lower bound and upper bounds that are known to be optimal. - virtual void printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel); - //! Called once a solving step has completed. - /*! - * Always prints "format[cat_result] result[s.result()]". - * Furthermore, if verbosity() > 0, prints a summary consisting of - * - the number of computed models m and whether the search space was exhausted - * - the number of enumerated models e if e != m - * - the state of any optimization and whether the last model was optimal - * - the state of consequence computation and whether the last model corresponded to the consequences - * - timing information - * . - */ - virtual void printSummary(const ClaspFacade::Summary& s , bool final); - virtual void printStatistics(const ClaspFacade::Summary& s, bool final); - //! Prints progress events (preprocessing/solving) if verbosity() > 1. - virtual void onEvent(const Event& ev); - //! A solving step has started. - virtual void startStep(const ClaspFacade&); - //! A solving step has finished. - virtual void stopStep(const ClaspFacade::Summary& s); - //! Prints a comment message. - void comment(uint32 v, const char* fmt, ...) const; protected: - //! Called on each model to be printed. - /*! - * The default implementation calls printValues(). - */ - virtual void printModelValues(const OutputTable& out, const Model& m); + //! Called on each model to be printed. + /*! + * The default implementation calls printValues(). + */ + virtual void printModelValues(const OutputTable& out, const Model& m); + + bool visitThreads(Operation op) override; + bool visitTester(Operation op) override; - virtual bool visitThreads(Operation op); - virtual bool visitTester(Operation op); + void visitThread(uint32_t, const SolverStats& stats) override; + void visitHcc(uint32_t, const ProblemStats& p, const SolverStats& s) override; + void visitLogicProgramStats(const Asp::LpStats& stats) override; + void visitProblemStats(const ProblemStats& stats) override; + void visitSolverStats(const SolverStats& stats) override; + void visitExternalStats(const StatisticObject& stats) override; - virtual void visitThread(uint32, const SolverStats& stats); - virtual void visitHcc(uint32, const ProblemStats& p, const SolverStats& s); - virtual void visitLogicProgramStats(const Asp::LpStats& stats); - virtual void visitProblemStats(const ProblemStats& stats); - virtual void visitSolverStats(const SolverStats& stats); - virtual void visitExternalStats(const StatisticObject& stats); + UPtr doPrint(const OutPair& out, UPtr data) override; + [[nodiscard]] const char* fieldSeparator() const; + [[nodiscard]] int printSep(CategoryKey c) const; + void printCosts(SumView) const; + void printBounds(SumView lower, SumView upper) const; + void printStats(const SolverStats& stats) const; + void printJumps(const JumpStats&) const; + bool startSection(const char* n) const; + void startObject(const char* n, uint32_t i) const; + void setState(uint32_t state, uint32_t verb, const char* st); + void printSolveProgress(const Event& ev); + void printValues(const OutputTable& out, const Model& m); + void printMeta(const OutputTable& out, const Model& m); + void printChildren(const StatisticObject& s, unsigned level = 0, const char* prefix = nullptr); + int printChildKey(unsigned level, const char* key, uint32_t idx, const char* prefix = nullptr) const; - virtual UPtr doPrint(const OutPair& out, UPtr data); - const char* fieldSeparator() const; - int printSep(CategoryKey c) const; - void printCosts(const SumVec&) const; - void printBounds(const SumVec& upper, const SumVec& lower) const; - void printStats(const SolverStats& stats) const; - void printJumps(const JumpStats&) const; - bool startSection(const char* n) const; - void startObject(const char* n, uint32 i) const; - void setState(uint32 state, uint32 verb, const char* st); - void printSolveProgress(const Event& ev); - void printValues(const OutputTable& out, const Model& m); - void printMeta(const OutputTable& out, const Model& m); - void printChildren(const StatisticObject& s, unsigned level = 0, const char* prefix = 0); - int printChildKey(unsigned level, const char* key, uint32 idx, const char* prefix = 0) const; private: - void printCostsImpl(const SumVec&, char ifs, const char* ifsSuffix = "") const; - const char* getIfsSuffix(char ifs, CategoryKey cat) const; - const char* getIfsSuffix(CategoryKey cat) const; - bool clearProgress(int nLines); - struct SolveProgress { - int lines; - int last; - void clear() { - lines = 0; - last = -1; - } - }; - std::string fmt_; - double stTime_; // time on state enter - SolveProgress progress_;// for printing solve progress - int width_; // output width - uint32 state_; // active state - char ifs_[2]; // field separator - bool accu_; + void printCostsImpl(SumView, char ifs, const char* ifsSuffix = "") const; + [[nodiscard]] const char* getIfsSuffix(char ifs, CategoryKey cat) const; + [[nodiscard]] const char* getIfsSuffix(CategoryKey cat) const; + bool clearProgress(int nLines); + struct SolveProgress { + int lines; + int last; + void clear() { + lines = 0; + last = -1; + } + }; + std::string fmt_; + double stTime_{}; // time on state enter + SolveProgress progress_{}; // for printing solve progress + int width_{0}; // output width + uint32_t state_{0}; // active state + char ifs_[2]{}; // field separator + bool accu_{false}; }; //@} -}} -#endif +} // namespace Clasp::Cli diff --git a/clasp/clingo.h b/clasp/clingo.h index 49c7dbd..a93aa6a 100644 --- a/clasp/clingo.h +++ b/clasp/clingo.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2015-2017 Benjamin Kaufmann +// Copyright (c) 2015-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,15 +21,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CLINGO_H_INCLUDED -#define CLASP_CLINGO_H_INCLUDED +#pragma once /*! * \file * \brief Types for implementing theory propagation from clingo. */ -#include #include #include + +#include namespace Clasp { /*! @@ -38,7 +38,7 @@ namespace Clasp { * \ingroup facade * @{ */ -//! Lock interface called by libclasp during (multi-threaded) theory propagation. +//! Lock interface called by libclasp during (multithreaded) theory propagation. /*! * The interface may be implemented by the application to lock * certain global data structures. For example, in clingo, @@ -46,27 +46,23 @@ namespace Clasp { */ class ClingoPropagatorLock { public: - virtual ~ClingoPropagatorLock(); - virtual void lock() = 0; - virtual void unlock() = 0; + virtual ~ClingoPropagatorLock(); + virtual void lock() = 0; + virtual void unlock() = 0; }; //! Supported check modes for clingo propagators. -struct ClingoPropagatorCheck_t { - enum Type { - No = 0u, //!< Never call AbstractPropagator::check(). - Total = 1u, //!< Call AbstractPropagator::check() only on total assignment. - Fixpoint = 2u, //!< Call AbstractPropagator::check() on every propagation fixpoint. - Both = 3u //!< Call AbstractPropagator::check() on both fixpoints and total assignment. - }; +enum class ClingoPropagatorCheckType { + no = 0u, //!< Never call AbstractPropagator::check(). + total = 1u, //!< Call AbstractPropagator::check() only on total assignment. + fixpoint = 2u, //!< Call AbstractPropagator::check() on every propagation fixpoint. + both = 3u //!< Call AbstractPropagator::check() on every fixpoint and total assignment. }; //! Supported undo modes for clingo propagators. -struct ClingoPropagatorUndo_t { - enum Type { - Default = 0u, //!< Call AbstractPropagator::undo() only on levels with non-empty changelist. - Always = 1u //!< Call AbstractPropagator::undo() on all levels that have been propagated or checked. - }; +enum class ClingoPropagatorUndoType { + def = 0u, //!< Call AbstractPropagator::undo() only on levels with non-empty changelist. + always = 1u //!< Call AbstractPropagator::undo() on all levels that have been propagated or checked. }; //! Initialization adaptor for a Potassco::AbstractPropagator. @@ -77,88 +73,90 @@ struct ClingoPropagatorUndo_t { */ class ClingoPropagatorInit : public ClaspConfig::Configurator { public: - typedef ClingoPropagatorCheck_t::Type CheckType; - typedef ClingoPropagatorUndo_t::Type UndoType; - //! Creates a new adaptor. - /*! - * \param cb The (theory) propagator that should be added to solvers. - * \param lock An optional lock that should be applied during theory propagation. - * - * If lock is not null, calls to cb are wrapped in a lock->lock()/lock->unlock() pair - */ - ClingoPropagatorInit(Potassco::AbstractPropagator& cb, ClingoPropagatorLock* lock = 0, CheckType check = ClingoPropagatorCheck_t::Total); - ~ClingoPropagatorInit(); - // base class - virtual void prepare(SharedContext&); - //! Adds a ClingoPropagator adapting the propagator() to s. - virtual bool applyConfig(Solver& s); - virtual void unfreeze(SharedContext&); + using CheckType = ClingoPropagatorCheckType; + using UndoType = ClingoPropagatorUndoType; + //! Creates a new adaptor. + /*! + * \param cb The (theory) propagator that should be added to solvers. + * \param lock An optional lock that should be applied during theory propagation. + * \param check The check mode that should be used for the propagator. + * + * If lock is not null, calls to cb are wrapped in a lock->lock()/lock->unlock() pair + */ + explicit ClingoPropagatorInit(Potassco::AbstractPropagator& cb, ClingoPropagatorLock* lock = nullptr, + CheckType check = CheckType::total); + ~ClingoPropagatorInit() override; + ClingoPropagatorInit(ClingoPropagatorInit&&) = delete; + // base class + void prepare(SharedContext&) override; + //! Adds a ClingoPropagator adapting the propagator() to s. + bool applyConfig(Solver& s) override; + void unfreeze(SharedContext&) override; + + // for clingo + //! Sets the type of checks to enable during solving. + /*! + * \param checkMode A set of ClingoPropagatorCheckType values. + */ + void enableClingoPropagatorCheck(CheckType checkMode); - // for clingo - //! Sets the type of checks to enable during solving. - /*! - * \param checkMode A set of ClingoPropagatorCheck_t::Type values. - */ - void enableClingoPropagatorCheck(CheckType checkMode); + //! Sets the undo mode to use when checks are enabled. + /*! + * \param undoMode The undo mode to use. + * + * \note By default, AbstractPropagator::undo() is only called for levels on which + * at least one watched literal has been assigned. However, if undoMode is set + * to "Always", AbstractPropagator::undo() is also called for levels L with an + * empty change list if AbstractPropagator::check() has been called on L. + */ + void enableClingoPropagatorUndo(UndoType undoMode); - //! Sets the undo mode to use when checks are enabled. - /*! - * \param undoMode The undo mode to use. - * - * \note By default, AbstractPropagator::undo() is only called for levels on which - * at least one watched literal has been assigned. However, if undoMode is set - * to "Always", AbstractPropagator::undo() is also called for levels L with an - * empty change list if AbstractPropagator::check() has been called on L. - */ - void enableClingoPropagatorUndo(UndoType undoMode); + void enableHistory(bool b); - void enableHistory(bool b); + //! Adds a watch for lit to all solvers and returns encodeLit(lit). + Potassco::Lit_t addWatch(Literal lit); + //! Removes the watch for lit from all solvers. + void removeWatch(Literal lit); + //! Adds a watch for lit to the solver with the given id and returns encodeLit(lit). + Potassco::Lit_t addWatch(uint32_t solverId, Literal lit); + //! Removes the watch for lit from solver with the given id. + void removeWatch(uint32_t solverId, Literal lit); + //! Freezes the given literal making it exempt from Sat-preprocessing. + /*! + * \note Watched literals are automatically frozen. + */ + void freezeLit(Literal lit); - //! Adds a watch for lit to all solvers and returns encodeLit(lit). - Potassco::Lit_t addWatch(Literal lit); - //! Removes the watch for lit from all solvers. - void removeWatch(Literal lit); - //! Adds a watch for lit to the solver with the given id and returns encodeLit(lit). - Potassco::Lit_t addWatch(uint32 solverId, Literal lit); - //! Removes the watch for lit from solver with the given id. - void removeWatch(uint32 solverId, Literal lit); - //! Freezes the given literal making it exempt from Sat-preprocessing. - /*! - * \note Watched literals are automatically frozen. - */ - void freezeLit(Literal lit); + //! Returns the propagator that was given on construction. + [[nodiscard]] Potassco::AbstractPropagator* propagator() const { return prop_; } + [[nodiscard]] ClingoPropagatorLock* lock() const { return lock_; } + [[nodiscard]] [[nodiscard]] CheckType checkMode() const { return check_; } + [[nodiscard]] [[nodiscard]] UndoType undoMode() const { return undo_; } - //! Returns the propagator that was given on construction. - Potassco::AbstractPropagator* propagator() const { return prop_; } - ClingoPropagatorLock* lock() const { return lock_; } - CheckType checkMode() const { return check_; } - UndoType undoMode() const { return undo_; } + uint32_t init(uint32_t lastStep, Potassco::AbstractSolver& s); - uint32 init(uint32 lastStep, Potassco::AbstractSolver& s); private: - typedef Potassco::Lit_t Lit_t; - enum Action { RemoveWatch = 0, AddWatch = 1, FreezeLit = 2 }; - struct History; - struct Change { - Change(Lit_t p, Action a); - Change(Lit_t p, Action a, uint32 sId); - bool operator<(const Change& rhs) const; - void apply(Potassco::AbstractSolver& s) const; - uint64 solverMask() const; - Lit_t lit; - int16 sId; - int16 action; - }; - typedef PodVector::type ChangeList; - ClingoPropagatorInit(const ClingoPropagatorInit&); - ClingoPropagatorInit& operator=(const ClingoPropagatorInit&); - Potassco::AbstractPropagator* prop_; - ClingoPropagatorLock* lock_; - History* history_; - ChangeList changes_; - uint32 step_; - CheckType check_; - UndoType undo_; + using Lit_t = Potassco::Lit_t; + enum Action { remove_watch = 0, add_watch = 1, freeze_lit = 2 }; + struct History; + struct Change { + struct Less; + Change(Lit_t p, Action a); + Change(Lit_t p, Action a, uint32_t sId); + void apply(Potassco::AbstractSolver& s) const; + [[nodiscard]] uint64_t solverMask() const; + Lit_t lit; + int16_t sId; + int16_t action; + }; + using ChangeList = PodVector_t; + Potassco::AbstractPropagator* prop_; + ClingoPropagatorLock* lock_; + History* history_; + ChangeList changes_; + uint32_t step_; + CheckType check_; + UndoType undo_; }; //! Adaptor for a Potassco::AbstractPropagator. @@ -169,121 +167,118 @@ class ClingoPropagatorInit : public ClaspConfig::Configurator { */ class ClingoPropagator : public Clasp::PostPropagator { public: - typedef Potassco::AbstractPropagator::ChangeList ChangeList; - typedef Clasp::PostPropagator::PropResult PPair; + using ChangeList = Potassco::AbstractPropagator::ChangeList; + using PPair = Clasp::PostPropagator::PropResult; - explicit ClingoPropagator(ClingoPropagatorInit* init); + explicit ClingoPropagator(ClingoPropagatorInit* init); + + // PostPropagator + [[nodiscard]] uint32_t priority() const override; + bool init(Solver& s) override; + bool propagateFixpoint(Clasp::Solver& s, Clasp::PostPropagator* ctx) override; + PPair propagate(Solver&, Literal, uint32_t&) override; + bool isModel(Solver& s) override; + void reason(Solver&, Literal, LitVec&) override; + void undoLevel(Solver& s) override; + bool simplify(Solver& s, bool reinit) override; + void destroy(Solver* s, bool detach) override; - // PostPropagator - virtual uint32 priority() const; - virtual bool init(Solver& s); - virtual bool propagateFixpoint(Clasp::Solver& s, Clasp::PostPropagator* ctx); - virtual PPair propagate(Solver&, Literal, uint32&); - virtual bool isModel(Solver& s); - virtual void reason(Solver&, Literal, LitVec&); - virtual void undoLevel(Solver& s); - virtual bool simplify(Solver& s, bool reinit); - virtual void destroy(Solver* s, bool detach); private: - typedef LitVec::size_type size_t; - typedef Potassco::Lit_t Lit_t; - class Control; - enum State { state_ctrl = 1u, state_prop = 2u, state_init = 4u }; - struct ClauseTodo { - bool empty() const { return mem.empty(); } - void clear() { mem.clear(); } - LitVec mem; - ClauseRep clause; - uint32 flags; - }; - typedef PodVector::type AspifVec; - typedef PodVector::type ClauseDB; - typedef ClingoPropagatorInit Propagator; - typedef ClingoPropagatorLock* ClingoLock; - bool addClause(Solver& s, uint32 state); - void toClause(Solver& s, const Potassco::LitSpan& clause, Potassco::Clause_t prop); - void registerUndoCheck(Solver& s); - void registerUndo(Solver& s, uint32 data); - bool inTrail(Literal p) const; - Propagator* call_; // wrapped theory propagator - AspifVec trail_; // assignment trail: watched literals that are true - AspifVec temp_; // temporary buffer used to pass changes to user - VarVec undo_; // offsets into trail marking beginnings of decision levels - ClauseDB db_; // clauses added with flag static - ClauseTodo todo_; // active clause to be added (received from theory propagator) - size_t prop_; // offset into trail: literals [0, prop_) were propagated - size_t epoch_; // number of calls into callback - uint32 level_; // highest undo level - uint32 propL_; // decision level on handling propagate() from theory propagator - int32 front_; // global assignment position for fixpoint checks - uint32 init_; // last time init() was called - Literal aux_; // max active literal + using Lit_t = Potassco::Lit_t; + class Control; + enum State : uint32_t { state_ctrl = 1u, state_prop = 2u, state_init = 4u }; + struct ClauseTodo { + [[nodiscard]] bool empty() const { return mem.empty(); } + void clear() { mem.clear(); } + LitVec mem; + ClauseRep clause; + uint32_t flags; + }; + using AspifVec = PodVector_t; + using ClauseDB = PodVector_t; + using Propagator = ClingoPropagatorInit; + using ClingoLock = ClingoPropagatorLock*; + bool addClause(Solver& s, State state); + void toClause(Solver& s, const Potassco::LitSpan& clause, Potassco::ClauseType prop); + void registerUndoCheck(Solver& s); + void registerUndo(Solver& s, uint32_t data); + [[nodiscard]] bool inTrail(Literal p) const; + Propagator* call_; // wrapped theory propagator + AspifVec trail_; // assignment trail: watched literals that are true + AspifVec temp_; // temporary buffer used to pass changes to user + VarVec undo_; // offsets into trail marking beginnings of decision levels + ClauseDB db_; // clauses added with flag static + ClauseTodo todo_{}; // active clause to be added (received from theory propagator) + uint32_t prop_{0}; // offset into trail: literals [0, prop_) were propagated + uint32_t epoch_{0}; // number of calls into callback + uint32_t level_{0}; // highest undo level + uint32_t propL_{UINT32_MAX}; // decision level on handling propagate() from theory propagator + int32_t front_{-1}; // global assignment position for fixpoint checks + uint32_t init_{0}; // last time init() was called + Literal aux_; // max active literal }; class ClingoAssignment : public Potassco::AbstractAssignment { public: - typedef Potassco::AbstractAssignment BaseType; - typedef BaseType::Value_t Value_t; - typedef BaseType::Lit_t Lit_t; + using BaseType = Potassco::AbstractAssignment; + using Value_t = Potassco::TruthValue; + using Lit_t = Potassco::Lit_t; + + explicit ClingoAssignment(const Solver& s); - ClingoAssignment(const Solver& s); + [[nodiscard]] uint32_t size() const override; + [[nodiscard]] uint32_t unassigned() const override; + [[nodiscard]] bool hasConflict() const override; + [[nodiscard]] uint32_t level() const override; + [[nodiscard]] uint32_t rootLevel() const override; + [[nodiscard]] bool hasLit(Lit_t lit) const override; + [[nodiscard]] Value_t value(Lit_t lit) const override; + [[nodiscard]] uint32_t level(Lit_t lit) const override; + [[nodiscard]] Lit_t decision(uint32_t) const override; + [[nodiscard]] bool isTotal() const override; + [[nodiscard]] uint32_t trailSize() const override; + [[nodiscard]] Lit_t trailAt(uint32_t) const override; + [[nodiscard]] uint32_t trailBegin(uint32_t) const override; - virtual uint32_t size() const; - virtual uint32_t unassigned() const; - virtual bool hasConflict() const; - virtual uint32_t level() const; - virtual uint32_t rootLevel() const; - virtual bool hasLit(Lit_t lit) const; - virtual Value_t value(Lit_t lit) const; - virtual uint32_t level(Lit_t lit) const; - virtual Lit_t decision(uint32_t) const; - virtual bool isTotal() const; - virtual uint32_t trailSize() const; - virtual Lit_t trailAt(uint32_t) const; - virtual uint32_t trailBegin(uint32_t) const; + [[nodiscard]] const Solver& solver() const { return *solver_; } - const Solver& solver() const { return *solver_; } private: - const Solver* solver_; + const Solver* solver_; }; class ClingoHeuristic : public DecisionHeuristic { public: - class Factory : public BasicSatConfig::HeuristicCreator { - public: - /*! - * \param clingoHeuristic The heuristic that should be added to solvers. - * \param lock An optional lock that should be applied during calls to AbstractHeuristic::decide(). - */ - explicit Factory(Potassco::AbstractHeuristic& clingoHeuristic, ClingoPropagatorLock* lock = 0); - DecisionHeuristic* create(Heuristic_t::Type t, const HeuParams& p); - private: - Potassco::AbstractHeuristic* clingo_; - ClingoPropagatorLock* lock_; - }; + // Returns a factory for adding the given heuristic to solvers. + /*! + * \param clingoHeuristic The heuristic that should be added to solvers. + * \param lock An optional lock that should be applied during calls to AbstractHeuristic::decide(). + */ + static BasicSatConfig::HeuristicCreator creator(Potassco::AbstractHeuristic& clingoHeuristic, + ClingoPropagatorLock* lock = nullptr); + + explicit ClingoHeuristic(Potassco::AbstractHeuristic& clingoHeuristic, DecisionHeuristic* claspHeuristic, + ClingoPropagatorLock* lock); + void startInit(const Solver& s) override; + void endInit(Solver& s) override; + void detach(Solver& s) override; + void setConfig(const HeuParams& p) override; + void updateVar(const Solver& s, Var_t v, uint32_t n) override; + void simplify(const Solver& s, LitView) override; + void undo(const Solver& s, LitView undo) override; + void newConstraint(const Solver& s, LitView lits, ConstraintType t) override; + void updateReason(const Solver& s, LitView lits, Literal resolveLit) override; + bool bump(const Solver& s, WeightLitView lits, double adj) override; + Literal doSelect(Solver& s) override; + Literal selectRange(Solver& s, LitView range) override; - explicit ClingoHeuristic(Potassco::AbstractHeuristic& clingoHeuristic, DecisionHeuristic* claspHeuristic, ClingoPropagatorLock* lock); - virtual void startInit(const Solver& s); - virtual void endInit(Solver& s); - virtual void detach(Solver& s); - virtual void setConfig(const HeuParams& p); - virtual void updateVar(const Solver& s, Var v, uint32 n); - virtual void simplify(const Solver& s, LitVec::size_type st); - virtual void undoUntil(const Solver& s, LitVec::size_type st); - virtual void newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t); - virtual void updateReason(const Solver& s, const LitVec& lits, Literal resolveLit); - virtual bool bump(const Solver& s, const WeightLitVec& lits, double adj); - virtual Literal doSelect(Solver& s); - virtual Literal selectRange(Solver& s, const Literal* first, const Literal* last); + [[nodiscard]] DecisionHeuristic* fallback() const; - DecisionHeuristic* fallback() const; private: - typedef SingleOwnerPtr HeuPtr; - Potassco::AbstractHeuristic* clingo_; - HeuPtr clasp_; - ClingoPropagatorLock* lock_; + using HeuPtr = std::unique_ptr; + Potassco::AbstractHeuristic* clingo_; + HeuPtr clasp_; + ClingoPropagatorLock* lock_; }; ///@} -} -#endif +} // namespace Clasp diff --git a/clasp/config.h.in b/clasp/config.h.in index a6a14b6..e2a6859 100644 --- a/clasp/config.h.in +++ b/clasp/config.h.in @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,14 +23,10 @@ // //! \file //! \brief Active configuration. -#ifndef CLASP_CONFIG_H_INCLUDED -#define CLASP_CONFIG_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif //! Library version. -#define CLASP_VERSION "@CLASP_VERSION_STRING@" +#define CLASP_VERSION "@CLASP_VERSION_STRING@" #define CLASP_VERSION_MAJOR @CLASP_VERSION_MAJOR@ #define CLASP_VERSION_MINOR @CLASP_VERSION_MINOR@ #define CLASP_VERSION_PATCH @CLASP_VERSION_PATCH@ @@ -43,44 +39,80 @@ #define CLASP_USE_STD_VECTOR @CLASP_USE_STD_VECTOR@ #include + #if CLASP_HAS_THREADS #include #endif +#include namespace Clasp { -@CLASP_DEFINE_ATOMIC@ -template struct Atomic_t { typedef @CLASP_ATOMIC_TYPE@ type; }; -template -T compare_and_swap(@CLASP_ATOMIC_TYPE@& in, T oldVal, T newVal) { - in.compare_exchange_strong(oldVal, newVal); - return oldVal; -} -typedef uint64_t uint64; -typedef uint32_t uint32; -typedef uint16_t uint16; -typedef uint8_t uint8; -typedef uintptr_t uintp; -typedef int64_t int64; -typedef int32_t int32; -typedef int16_t int16; -typedef int8_t int8; -inline void* alignedAlloc(size_t size, size_t align) { -#if defined(__CYGWIN__) - return memalign(align, size); -#elif defined(_WIN32) || defined(_WIN64) - return _aligned_malloc(size, align); -#else - void* result = 0; - return posix_memalign(&result, align, size) == 0 ? result : static_cast(0); -#endif -} -inline void alignedFree(void* p) { -#if defined(_WIN32) || defined(_WIN64) - _aligned_free(p); +constexpr auto cache_line_size = @CLASP_CACHE_LINE_SIZE@; + +namespace mt { +consteval bool hasThreads() { return CLASP_HAS_THREADS; } +#if CLASP_HAS_THREADS +using std::memory_order_acq_rel; +using std::memory_order_acquire; +using std::memory_order_relaxed; +using std::memory_order_release; +using std::memory_order_seq_cst; +using MemoryOrder = std::memory_order; +template +using AtomicType = std::atomic; #else - free(p); +enum class MemoryOrder {}; +constexpr inline auto memory_order_acq_rel = MemoryOrder{}; +constexpr inline auto memory_order_acquire = MemoryOrder{}; +constexpr inline auto memory_order_relaxed = MemoryOrder{}; +constexpr inline auto memory_order_release = MemoryOrder{}; +constexpr inline auto memory_order_seq_cst = MemoryOrder{}; +template +using AtomicType = void; #endif -} -} // namespace Clasp -#endif +template +class ThreadSafe { + struct SingleThread { + [[nodiscard]] T load(MemoryOrder = {}) const noexcept { return val; } + void store(T xVal, MemoryOrder = {}) noexcept { val = xVal; } + T exchange(T nVal, MemoryOrder = {}) noexcept { return std::exchange(val, nVal); } + T fetch_add(T xVal, MemoryOrder = {}) noexcept { return std::exchange(val, val + xVal); } + T fetch_sub(T xVal, MemoryOrder = {}) noexcept { return std::exchange(val, val - xVal); } + T fetch_or(T xVal, MemoryOrder = {}) noexcept { return std::exchange(val, val | xVal); } + T fetch_and(T xVal, MemoryOrder = {}) noexcept { return std::exchange(val, val & xVal); } + bool compare_exchange_weak(T& oVal, T nVal, MemoryOrder m = {}) noexcept { + return compare_exchange_strong(oVal, nVal, m); + } + bool compare_exchange_strong(T& oVal, T nVal, MemoryOrder = {}) noexcept { + return val == oVal ? (store(nVal), true) : (oVal = val, false); + } + T val{}; + }; + static_assert(not Mt || hasThreads(), "Threads are disabled!"); + using ImplType = std::conditional_t, SingleThread>; + +public: + explicit ThreadSafe(T x) : ref_{x} {} + ThreadSafe() : ref_{} {} + + [[nodiscard]] T load(MemoryOrder m = memory_order_seq_cst) const noexcept { return ref_.load(m); } + operator T() const noexcept { return load(); } + void store(T xVal, MemoryOrder m = memory_order_seq_cst) noexcept { ref_.store(xVal, m); } + T exchange(T nVal, MemoryOrder m = memory_order_seq_cst) noexcept { return ref_.exchange(nVal, m); } + T add(T xVal, MemoryOrder m = memory_order_seq_cst) noexcept { return ref_.fetch_add(xVal, m) + xVal; } + T sub(T xVal, MemoryOrder m = memory_order_seq_cst) noexcept { return ref_.fetch_sub(xVal, m) - xVal; } + + bool compare_exchange_weak(T& oVal, T nVal, MemoryOrder m = memory_order_seq_cst) noexcept { + return ref_.compare_exchange_weak(oVal, nVal, m); + } + bool compare_exchange_strong(T& oVal, T nVal, MemoryOrder m = memory_order_seq_cst) noexcept { + return ref_.compare_exchange_strong(oVal, nVal, m); + } + ImplType& ref() { return ref_; } + +private: + ImplType ref_; +}; +} // namespace mt + +} // namespace Clasp diff --git a/clasp/constraint.h b/clasp/constraint.h index 74c1e52..d61a884 100644 --- a/clasp/constraint.h +++ b/clasp/constraint.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,15 +21,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_CONSTRAINT_H_INCLUDED -#define CLASP_CONSTRAINT_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include // bits stuff + +#include + #include /*! @@ -38,9 +36,9 @@ */ namespace Clasp { -class SharedContext; -class Solver; -class ClauseHead; +class SharedContext; +class Solver; +class ClauseHead; struct CCMinRecursive; /** @@ -49,26 +47,18 @@ struct CCMinRecursive; * @{ */ //! Constraint types distinguished by a Solver. -struct Constraint_t { - enum Type { - Static = 0, //!< An unremovable constraint (e.g. a problem constraint). - Conflict = 1, //!< A removable constraint derived from conflict analysis. - Loop = 2, //!< A removable constraint derived from unfounded set checking. - Other = 3, //!< A removable constraint learnt by some other means. - Type__max = Other - }; - struct Set { - Set() : m(0) {} - bool inSet(Type t) const { return (m & (uint32(1)<; struct ConstraintScore; -class ConstraintInfo; +class ConstraintInfo; //! Base class for (boolean) constraints to be used in a Solver. /*! @@ -80,165 +70,164 @@ class ConstraintInfo; */ class Constraint { public: - //! Type used as return type for Constraint::propagate. - struct PropResult { - explicit PropResult(bool a_ok = true, bool a_keepWatch = true) : ok(a_ok), keepWatch(a_keepWatch) {} - bool ok; //!< true if propagation completes without conflict. - bool keepWatch; //!< true if constraint wants to keep the current watch. - }; - Constraint(); - - /*! - * \name Mandatory functions - * Functions that must be implemented by all constraints. - * @{ */ - - /*! - * Propagate is called for each constraint that watches p. It shall enqueue - * all consequences of p becoming true. - * \pre p is true in s - * \param s The solver in which p became true. - * \param p A literal watched by this constraint that recently became true. - * \param data The data-blob that this constraint passed when the watch for p was registered. - */ - virtual PropResult propagate(Solver& s, Literal p, uint32& data) = 0; - - /*! - * \pre This constraint is the reason for p being true in s. - * \post The literals implying p were added to lits. - */ - virtual void reason(Solver& s, Literal p, LitVec& lits) = 0; - - //! Returns a clone of this and adds necessary watches to the given solver. - /*! - * The function shall create and return a copy of this constraint - * to be used in the given solver. Furthermore, it shall add - * necessary watches to the given solver. - * \note Return 0 to indicate that cloning is not supported. - */ - virtual Constraint* cloneAttach(Solver& other) = 0; - //@} - - /*! - * \name Additional functions - * Functions that can be implemented by constraints. - * @{ */ - //! Called when the given solver removes a decision level watched by this constraint. - virtual void undoLevel(Solver& s); - - /*! - * \brief Simplify this constraint. - * - * \pre s.decisionLevel() == 0 and the current assignment is fully propagated. - * \return - * true if this constraint can be ignored (e.g. is satisfied), - * false otherwise. - * \post - * If simplify returned true, this constraint has previously removed all its watches - * from the solver. - * - * \note The default implementation simply returns false. - */ - virtual bool simplify(Solver& s, bool reinit = false); - - /*! - * \brief Delete this constraint. - * \note The default implementation simply calls delete this. - * \param s The solver in which this constraint is used (can be 0). - * \param detach Whether the constraint shall detach itself from the given solver. - */ - virtual void destroy(Solver* s = 0, bool detach = false); - - //! Shall return whether the constraint is valid (i.e. not conflicting) w.r.t the current assignment in s. - /*! - * \pre The assignment in s is not conflicting and fully propagated. - * \post A changed assignment if the assignment was not valid. - * \note The default implementation always returns true and assumes - * that conflicts are detected by Constraint::propagate(). - */ - virtual bool valid(Solver& s); - - //! Called during minimization of learnt clauses if this is the reason for p being true in s. - /*! - * \return true if p can be removed from the current learnt clause, false otherwise. - * \note The default implementation uses the following inefficient algorithm - * \code - * LitVec temp; - * reason(s, p, temp); - * for each x in temp - * if (!s.ccMinimize(p, rec)) return false; - * return true; - * \endcode - */ - virtual bool minimize(Solver& s, Literal p, CCMinRecursive* rec); - - //! Returns an estimate of the constraint's complexity relative to a clause (complexity = 1). - virtual uint32 estimateComplexity(const Solver& s) const; - - //! Shall return this if constraint is a clause, otherwise 0. - /*! - * The default implementation returns 0. - */ - virtual ClauseHead* clause(); - //@} - - /*! - * \name Functions for learnt constraints - * - * Learnt constraints can be created and deleted dynamically during the search-process and - * are subject to nogood-deletion. - * A learnt constraint shall at least define the methods type() and locked(). - * @{ */ - - typedef ConstraintType Type; - typedef ConstraintScore ScoreType; - typedef ConstraintInfo InfoType; - - //! Returns the type of this (learnt) constraint. - virtual Type type() const; - - /*! - * Shall return true if this constraint can't be deleted because it - * currently implies one or more literals and false otherwise. - * The default implementation returns true. - */ - virtual bool locked(const Solver& s) const; - - //! Returns the activity of the constraint. - /*! - * \note A solver uses the activity-value in order to establish an ordering - * of learnt constraints. Whenever a solver needs to delete some learnt constraints it - * selects from the unlocked ones those with a low activity-value. - * \note The default-implementation always returns the minimal activity. - */ - virtual ScoreType activity() const; - //! Asks the constraint to decrease its activity. - virtual void decreaseActivity(); - //! Asks the constraint to reset its activity. - virtual void resetActivity(); - //! Shall return 0 if either !t.inSet(type()) or this constraint is satisfied w.r.t the current assignment. - /*! - * If this constraint is currently not satisfied and t.inSet(type()), shall return type() - * and freeLits shall contain all currently unassigned literals of this constraint. - * - * The default implementation always returns 0. - */ - virtual uint32 isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits); - //@} + //! Type used as return type for Constraint::propagate. + struct PropResult { + constexpr explicit PropResult(bool a_ok = true, bool a_keepWatch = true) : ok(a_ok), keepWatch(a_keepWatch) {} + bool ok; //!< true if propagation completes without conflict. + bool keepWatch; //!< true if constraint wants to keep the current watch. + }; + Constraint(); + Constraint(const Constraint&) = delete; + Constraint& operator=(const Constraint&) = delete; + + /*! + * \name Mandatory functions. + * Functions that must be implemented by all constraints. + * @{ */ + + /*! + * Propagate is called for each constraint that watches p. It shall enqueue + * all consequences of p becoming true. + * \pre p is true in s + * \param s The solver in which p became true. + * \param p A literal watched by this constraint that recently became true. + * \param data The data-blob that this constraint passed when the watch for p was registered. + */ + virtual PropResult propagate(Solver& s, Literal p, uint32_t& data) = 0; + + /*! + * \pre This constraint is the reason for p being true in s. + * \post The literals implying p were added to lits. + */ + virtual void reason(Solver& s, Literal p, LitVec& lits) = 0; + + //! Returns a clone of this and adds necessary watches to the given solver. + /*! + * The function shall create and return a copy of this constraint + * to be used in the given solver. Furthermore, it shall add + * necessary watches to the given solver. + * \note Return 0 to indicate that cloning is not supported. + */ + virtual Constraint* cloneAttach(Solver& other) = 0; + //@} + + /*! + * \name Additional functions. + * Functions that can be implemented by constraints. + * @{ */ + //! Called when the given solver removes a decision level watched by this constraint. + virtual void undoLevel(Solver& s); + + /*! + * \brief Simplify this constraint. + * + * \pre s.decisionLevel() == 0 and the current assignment is fully propagated. + * \return + * true if this constraint can be ignored (e.g. is satisfied), + * false otherwise. + * \post + * If simplify returned true, this constraint has previously removed all its watches + * from the solver. + * + * \note The default implementation simply returns false. + */ + virtual bool simplify(Solver& s, bool reinit = false); + + /*! + * \brief Delete this constraint. + * \note The default implementation simply calls delete this. + * \param s The solver in which this constraint is used (can be 0). + * \param detach Whether the constraint shall detach itself from the given solver. + */ + virtual void destroy(Solver* s = nullptr, bool detach = false); + + //! Shall return whether the constraint is valid (i.e. not conflicting) w.r.t the current assignment in s. + /*! + * \pre The assignment in s is not conflicting and fully propagated. + * \post A changed assignment if the assignment was not valid. + * \note The default implementation always returns true and assumes + * that conflicts are detected by Constraint::propagate(). + */ + virtual bool valid(Solver& s); + + //! Called during minimization of learnt clauses if this is the reason for p being true in s. + /*! + * \return true if p can be removed from the current learnt clause, false otherwise. + * \note The default implementation uses the following inefficient algorithm + * \code + * LitVec temp; + * reason(s, p, temp); + * for each x in temp + * if (!s.ccMinimize(p, rec)) return false; + * return true; + * \endcode + */ + virtual bool minimize(Solver& s, Literal p, CCMinRecursive* rec); + + //! Returns an estimate of the constraint's complexity relative to a clause (complexity = 1). + [[nodiscard]] virtual uint32_t estimateComplexity(const Solver& s) const; + + //! Shall return this if constraint is a clause, otherwise 0. + /*! + * The default implementation returns 0. + */ + virtual ClauseHead* clause(); + //@} + + /*! + * \name Functions for learnt constraints. + * + * Learnt constraints can be created and deleted dynamically during the search-process and + * are subject to nogood-deletion. + * A learnt constraint shall at least define the methods type() and locked(). + * @{ */ + + using Type = ConstraintType; + using ScoreType = ConstraintScore; + using InfoType = ConstraintInfo; + + //! Returns the type of this (learnt) constraint. + [[nodiscard]] virtual Type type() const; + + /*! + * Shall return true if this constraint can't be deleted because it + * currently implies one or more literals and false otherwise. + * The default implementation returns true. + */ + [[nodiscard]] virtual bool locked(const Solver& s) const; + + //! Returns the activity of the constraint. + /*! + * \note A solver uses the activity-value in order to establish an ordering + * of learnt constraints. Whenever a solver needs to delete some learnt constraints it + * selects from the unlocked ones those with a low activity-value. + * \note The default-implementation always returns the minimal activity. + */ + [[nodiscard]] virtual ScoreType activity() const; + //! Asks the constraint to decrease its activity. + virtual void decreaseActivity(); + //! Asks the constraint to reset its activity. + virtual void resetActivity(); + //! Shall return 0 if either !t.inSet(type()) or this constraint is satisfied w.r.t the current assignment. + /*! + * If this constraint is currently not satisfied and t.inSet(type()), shall return type() + * and freeLits shall contain all currently unassigned literals of this constraint. + * + * The default implementation always returns 0. + */ + virtual uint32_t isOpen(const Solver& s, const TypeSet& t, LitVec& freeLits); + //@} protected: - virtual ~Constraint(); -private: - Constraint(const Constraint&); - Constraint& operator=(const Constraint&); + virtual ~Constraint(); }; //@} /** -* \defgroup propagator Propagators -* \brief Post propagators and related types. -* \ingroup constraint -* @{ -*/ + * \defgroup propagator Propagators + * \brief Post propagators and related types. + * \ingroup constraint + * @{ + */ //! Base class for post propagators. /*! @@ -253,7 +242,8 @@ class Constraint { * the current decision level. That is, these post propagators shall neither * backtrack below the current decision level nor permanently add new decision levels. * Deterministic post propagators are called in priority order. For this, - * the function PostPropagator::priority() is used and shall return a priority in the range: [priority_class_simple, priority_class_general) + * the function PostPropagator::priority() is used and shall return a priority in the range: + * [priority_class_simple, priority_class_general) * - class_general: post propagators that are non-deterministic or those that are not limited to extending * the current decision level shall have a priority of priority_class_general. They are called in FIFO order * after \b all simple post propagators have reached a fixpoint. @@ -266,109 +256,109 @@ class Constraint { */ class PostPropagator : public Constraint { public: - PostPropagator(); - virtual ~PostPropagator(); - using Constraint::propagate; // Enable overloading! - - PostPropagator* next; // main propagation lists of post propagators - //! Default priorities for post propagators. - enum Priority { - priority_class_simple = 0, //!< Starting priority of simple post propagators. - priority_reserved_msg = 0, //!< Reserved priority for message/termination handlers (if any). - priority_reserved_ufs = 10, //!< Reserved priority for the default unfounded set checker (if any). - priority_reserved_look = 1023, //!< Reserved priority for the default lookahead operator (if any). - priority_class_general = 1024, //!< Priority of extended post propagators. - }; - - //! Shall return a value representing the priority of this propagator. - /*! - * The priority is used to order sequences of post propagators and to - * classify post propagators w.r.t the classes: class_simple and class_general. - * \note See class description for an overview of the two priority classes. - */ - virtual uint32 priority() const = 0; - - //! Called during initialization of s. - /*! - * \note During initialization a post propagator may assign variables - * but it must not yet propagate them. - */ - virtual bool init(Solver& s); - - //! Shall enqueue and propagate new assignments implied by this propagator. - /*! - * This function shall enqueue and propagate all assignments currently implied by - * this propagator until a fixpoint is reached w.r.t this post propagator or - * a conflict is detected. - * - * \pre The assignment is fully propagated w.r.t any previous post propagator. - * \param s The solver in which this post propagator is used. - * \param ctx The post propagator from which this post propagator is called or - * 0 if no other post propagator is currently active. - * \post s.queueSize() == 0 || s.hasConflict() - * \return false if propagation led to conflict, true otherwise - * - * \note - * The function shall not call Solver::propagate() - * or any other function that would result in a recursive chain - * of propagate() calls. On the other hand, it shall call - * Solver::propagateUntil(this) after enqueuing any new assignments - * to initiate propagation up to this propagator. - * - * Typically, propagateFixpoint() should implement a loop like this: - * \code - * for (;;) { - * if (!assign_newly_implied_literals(s)){ return false; } - * if (s.queueSize() == 0) { return true; } - * if (!s.propagateUntil(this)) { return false; } - * } - * \endcode - */ - virtual bool propagateFixpoint(Solver& s, PostPropagator* ctx) = 0; - - //! Aborts an active propagation operation. - /*! - * The function reset() is called whenever propagation on the - * current decision level is stopped before a fixpoint is reached. - * In particular, a solver calls reset() when a conflict is detected - * during propagation. - * - * \note The default implementation is a noop. - */ - virtual void reset(); - - //! Is the current total assignment a model? - /*! - * \pre The assignment is total and not conflicting. - * \return - * - true if the assignment is a model w.r.t this post propagator - * - false otherwise - * \post If the function returned true: s.numFreeVars() == 0 && !s.hasConflict(). - * If the function returned false: s.numFreeVars() > 0 || s.hasConflict(). - * \note The default implementation returns Constraint::valid(s); - */ - virtual bool isModel(Solver& s); + PostPropagator(); + ~PostPropagator() override; + PostPropagator(const PostPropagator&) = delete; + PostPropagator& operator=(const PostPropagator&) = delete; + using Constraint::propagate; // Enable overloading! + + PostPropagator* next; // main propagation lists of post propagators + //! Default priorities for post propagators. + enum Priority { + priority_class_simple = 0, //!< Starting priority of simple post propagators. + priority_reserved_msg = 0, //!< Reserved priority for message/termination handlers (if any). + priority_reserved_ufs = 10, //!< Reserved priority for the default unfounded set checker (if any). + priority_reserved_look = 1023, //!< Reserved priority for the default lookahead operator (if any). + priority_class_general = 1024, //!< Priority of extended post propagators. + }; + + //! Shall return a value representing the priority of this propagator. + /*! + * The priority is used to order sequences of post propagators and to + * classify post propagators w.r.t the classes: class_simple and class_general. + * \note See class description for an overview of the two priority classes. + */ + [[nodiscard]] virtual uint32_t priority() const = 0; + + //! Called during initialization of s. + /*! + * \note During initialization a post propagator may assign variables, + * but it must not yet propagate them. + */ + virtual bool init(Solver& s); + + //! Shall enqueue and propagate new assignments implied by this propagator. + /*! + * This function shall enqueue and propagate all assignments currently implied by + * this propagator until a fixpoint is reached w.r.t this post propagator or + * a conflict is detected. + * + * \pre The assignment is fully propagated w.r.t any previous post propagator. + * \param s The solver in which this post propagator is used. + * \param ctx The post propagator from which this post propagator is called or + * 0 if no other post propagator is currently active. + * \post s.queueSize() == 0 || s.hasConflict() + * \return false if propagation led to conflict, true otherwise + * + * \note + * The function shall not call Solver::propagate() + * or any other function that would result in a recursive chain + * of propagate() calls. On the other hand, it shall call + * Solver::propagateUntil(this) after enqueuing any new assignments + * to initiate propagation up to this propagator. + * + * Typically, propagateFixpoint() should implement a loop like this: + * \code + * for (;;) { + * if (!assign_newly_implied_literals(s)){ return false; } + * if (s.queueSize() == 0) { return true; } + * if (!s.propagateUntil(this)) { return false; } + * } + * \endcode + */ + virtual bool propagateFixpoint(Solver& s, PostPropagator* ctx) = 0; + + //! Aborts an active propagation operation. + /*! + * The function reset() is called whenever propagation on the + * current decision level is stopped before a fixpoint is reached. + * In particular, a solver calls reset() when a conflict is detected + * during propagation. + * + * \note The default implementation is a noop. + */ + virtual void reset(); + + //! Is the current total assignment a model? + /*! + * \pre The assignment is total and not conflicting. + * \return + * - true if the assignment is a model w.r.t this post propagator + * - false otherwise + * \post If the function returned true: s.numFreeVars() == 0 && !s.hasConflict(). + * If the function returned false: s.numFreeVars() > 0 || s.hasConflict(). + * \note The default implementation returns Constraint::valid(s); + */ + virtual bool isModel(Solver& s); + protected: - //! Calls reset on post propagators following this. - void cancelPropagation(); - - //! PostPropagators are not cloneable by default. - Constraint* cloneAttach(Solver&) { return 0; } - // Constraint interface - noops - PropResult propagate(Solver&, Literal, uint32&); - void reason(Solver&, Literal, LitVec& ); -private: - PostPropagator(const PostPropagator&); - PostPropagator& operator=(const PostPropagator&); + //! Calls reset on post propagators following this. + void cancelPropagation(); + + //! PostPropagators are not cloneable by default. + Constraint* cloneAttach(Solver&) override { return nullptr; } + // Constraint interface - noops + PropResult propagate(Solver&, Literal, uint32_t&) override; + void reason(Solver&, Literal, LitVec&) override; }; //! A special post propagator used to handle messages and signals. class MessageHandler : public PostPropagator { public: - MessageHandler(); - virtual bool handleMessages() = 0; - virtual uint32 priority()const { return PostPropagator::priority_reserved_msg; } - virtual bool propagateFixpoint(Solver&, PostPropagator*) { return handleMessages(); } + MessageHandler(); + virtual bool handleMessages() = 0; + [[nodiscard]] uint32_t priority() const override { return priority_reserved_msg; } + bool propagateFixpoint(Solver&, PostPropagator*) override { return handleMessages(); } }; //! An intrusive list of post propagators ordered by priority. @@ -377,24 +367,29 @@ class MessageHandler : public PostPropagator { */ class PropagatorList { public: - PropagatorList(); - ~PropagatorList(); - void add(PostPropagator* p); - void remove(PostPropagator* p); - void clear(); - PostPropagator* find(uint32 prio) const; - PostPropagator*const* head() const { return &head_; } - PostPropagator** head() { return &head_; } + PropagatorList(); + ~PropagatorList(); + PropagatorList(PropagatorList&&) = delete; + void add(PostPropagator* p); + void remove(PostPropagator* p); + void clear(); + [[nodiscard]] PostPropagator* find(uint32_t prio) const; + [[nodiscard]] PostPropagator* const* head() const { return &head_; } + PostPropagator** head() { return &head_; } + + // Operations applied on all elements. + bool init(Solver& s); + bool simplify(Solver& s, bool reinit = false); + bool isModel(Solver& s); + private: - PropagatorList(const PropagatorList&); - PropagatorList& operator=(const PropagatorList&); - PostPropagator* head_;// head of pp-list + PostPropagator* head_; // head of pp-list }; //@} /** -* \addtogroup constraint -* @{ */ + * \addtogroup constraint + * @{ */ //! Stores a reference to the constraint that implied a literal. /*! @@ -419,170 +414,220 @@ class PropagatorList { */ class Antecedent { public: - enum Type { Generic = 0, Ternary = 1, Binary = 2}; - //! Creates a null Antecedent. - /*! - * \post: isNull() == true && type == Generic - */ - Antecedent() : data_(0) {} - - //! Creates an Antecedent from the literal p. - /*! - * \post: type() == Binary && firstLiteral() == p - */ - Antecedent(const Literal& p) { - // first lit is stored in high dword - data_ = (((uint64)p.id()) << 33) + Binary; - assert(type() == Binary && firstLiteral() == p); - } - - //! Creates an Antecedent from the literals p and q. - /*! - * \post type() == Ternary && firstLiteral() == p && secondLiteral() == q - */ - Antecedent(const Literal& p, const Literal& q) { - // first lit is stored in high dword - // second lit is stored in low dword - data_ = (((uint64)p.id()) << 33) + (((uint64)q.id()) << 2) + Ternary; - assert(type() == Ternary && firstLiteral() == p && secondLiteral() == q); - } - - //! Creates an Antecedent from the Constraint con. - /*! - * \post type() == Generic && constraint() == con - */ - Antecedent(Constraint* con) : data_((uintp)con) { - static_assert(sizeof(Constraint*) <= sizeof(uint64), "unsupported pointer size"); - assert(type() == Generic && constraint() == con); - } - - //! Returns true if this antecedent does not refer to any constraint. - bool isNull() const { return data_ == 0; } - //! Returns the antecedent's type. - Type type() const { return Type( data_ & 3 ); } - //! Returns true if the antecedent is a learnt nogood. - bool learnt() const { return data_ && (data_ & 3u) == 0 && constraint()->type() != Constraint_t::Static; } - - //! Extracts the constraint-pointer stored in this object. - /*! - * \pre type() == Generic - */ - Constraint* constraint() const { - assert(type() == Generic); - return (Constraint*)(uintp)data_; - } - - //! Extracts the first literal stored in this object. - /*! - * \pre type() != Generic - */ - Literal firstLiteral() const { - assert(type() != Generic); - return Literal::fromId(static_cast(data_ >> 33)); - } - - //! Extracts the second literal stored in this object. - /*! - * \pre type() == Ternary - */ - Literal secondLiteral() const { - assert(type() == Ternary); - return Literal::fromId( static_cast(data_>>1) >> 1 ); - } - - //! Returns the reason for p. - /*! - * \pre !isNull() - */ - void reason(Solver& s, Literal p, LitVec& lits) const { - assert(!isNull()); - Type t = type(); - if (t == Generic) { - constraint()->reason(s, p, lits); - return; - } - lits.push_back(firstLiteral()); - if (t == Ternary) { lits.push_back(secondLiteral()); } - } - template - bool minimize(S& s, Literal p, CCMinRecursive* rec) const { - assert(!isNull()); - Type t = type(); - if (t == Generic) { - return constraint()->minimize(s, p, rec); - } - return s.ccMinimize(firstLiteral(), rec) && (t != Ternary || s.ccMinimize(secondLiteral(), rec)); - } - - //! Returns true iff the antecedent refers to the constraint con. - bool operator==(const Constraint* con) const { - return static_cast(data_) == reinterpret_cast(con); - } - uint64 asUint() const { return data_; } - uint64& asUint() { return data_; } + enum Type { generic = 0, ternary = 1, binary = 2 }; + //! Creates a null Antecedent. + /*! + * \post: isNull() == true && type == Generic + */ + constexpr Antecedent() : data_(0) {} + + //! Creates an Antecedent from the literal p. + /*! + * \post: type() == Binary && firstLiteral() == p + */ + constexpr Antecedent(const Literal& p) { + // first lit is stored in high dword + data_ = (static_cast(p.id()) << 33) + binary; + assert(type() == binary && firstLiteral() == p); + } + + //! Creates an Antecedent from the literals p and q. + /*! + * \post type() == Ternary && firstLiteral() == p && secondLiteral() == q + */ + constexpr Antecedent(const Literal& p, const Literal& q) { + // first lit is stored in high dword + // second lit is stored in low dword + data_ = (static_cast(p.id()) << 33) + (static_cast(q.id()) << 2) + ternary; + assert(type() == ternary && firstLiteral() == p && secondLiteral() == q); + } + + //! Creates an Antecedent from the Constraint con. + /*! + * \post type() == Generic && constraint() == con + */ + Antecedent(Constraint* con) : data_(reinterpret_cast(con)) { + static_assert(sizeof(Constraint*) <= sizeof(uint64_t), "unsupported pointer size"); + assert(type() == generic && constraint() == con); + } + + //! Returns true if this antecedent does not refer to any constraint. + [[nodiscard]] constexpr bool isNull() const { return data_ == 0; } + //! Returns the antecedent's type. + [[nodiscard]] constexpr Type type() const { return static_cast(data_ & 3); } + //! Returns true if the antecedent is a learnt nogood. + [[nodiscard]] constexpr bool learnt() const { + return Potassco::right_most_bit(data_) > binary && constraint()->type() != ConstraintType::static_; + } + + //! Extracts the constraint-pointer stored in this object. + /*! + * \pre type() == Generic + */ + [[nodiscard]] Constraint* constraint() const { + assert(type() == generic); + return reinterpret_cast(static_cast(data_)); + } + + //! Extracts the first literal stored in this object. + /*! + * \pre type() != Generic + */ + [[nodiscard]] constexpr Literal firstLiteral() const { + assert(type() != generic); + return Literal::fromId(static_cast(data_ >> 33)); + } + + //! Extracts the second literal stored in this object. + /*! + * \pre type() == Ternary + */ + [[nodiscard]] constexpr Literal secondLiteral() const { + assert(type() == ternary); + return Literal::fromId(static_cast(data_ >> 1) >> 1); + } + + //! Returns the reason for p. + /*! + * \pre !isNull() + */ + void reason(Solver& s, Literal p, LitVec& lits) const { + assert(not isNull()); + Type t = type(); + if (t == generic) { + constraint()->reason(s, p, lits); + return; + } + lits.push_back(firstLiteral()); + if (t == ternary) { + lits.push_back(secondLiteral()); + } + } + template + bool minimize(S& s, Literal p, CCMinRecursive* rec) const { + assert(not isNull()); + Type t = type(); + if (t == generic) { + return constraint()->minimize(s, p, rec); + } + return s.ccMinimize(firstLiteral(), rec) && (t != ternary || s.ccMinimize(secondLiteral(), rec)); + } + + //! Returns true iff the antecedent refers to the constraint con. + bool operator==(const Constraint* con) const { + return static_cast(data_) == reinterpret_cast(con); + } + [[nodiscard]] constexpr uint64_t asUint() const { return data_; } + constexpr uint64_t& asUint() { return data_; } + private: - uint64 data_; + uint64_t data_; }; -enum { LBD_MAX = 127u, ACT_MAX = (1u << 20) - 1 }; +constexpr uint32_t lbd_max = 127u; //!< highest possible lbd value. +constexpr uint32_t act_max = (1u << 20) - 1; //!< highest possible activity value. //! Type storing a constraint's activity. struct ConstraintScore { - typedef ConstraintScore Score; - enum { LBD_SHIFT = 20, BMP_BIT = 27, BITS_USED = 28, LBD_MASK = LBD_MAX<> LBD_SHIFT) : uint32(LBD_MAX); } - bool hasLbd() const { return (rep & LBD_MASK) != 0; } - bool bumped() const { return test_bit(rep, BMP_BIT); } - void bumpActivity() { if (activity() < uint32(ACT_MAX)) ++rep; } - void bumpLbd(uint32 x) { if (x < lbd()) { rep &= ~uint32(LBD_MASK); rep |= (x << LBD_SHIFT) | bit_mask(BMP_BIT); } } - void clearBumped() { store_clear_bit(rep, BMP_BIT); } - void reduce() { clearBumped(); if (uint32 a = activity()) { rep &= ~uint32(ACT_MAX); rep |= (a>>1); } } - void assign(Score o) { rep &= ~uint32(BITS_MASK); rep |= (o.rep & BITS_MASK); } - uint32 rep; + using Score = ConstraintScore; + static constexpr uint32_t bits_used = 28u; + static constexpr uint32_t bumped_bit = 27; + static constexpr uint32_t lbd_shift = 20u; + static constexpr uint32_t lbd_mask = lbd_max << lbd_shift; + static constexpr uint32_t score_mask = Potassco::bit_max(bits_used); + + explicit constexpr ConstraintScore(uint32_t act = 0, uint32_t lbd = 0) + : rep(std::min(lbd, lbd_max) << lbd_shift | std::min(act, act_max)) {} + + constexpr void reset(uint32_t act = 0, uint32_t lbd = 0) { assign(ConstraintScore(act, lbd)); } + [[nodiscard]] constexpr uint32_t activity() const { return rep & act_max; } + [[nodiscard]] constexpr uint32_t lbd() const { return hasLbd() ? (rep & lbd_mask) >> lbd_shift : lbd_max; } + [[nodiscard]] constexpr bool hasLbd() const { return Potassco::test_any(rep, lbd_mask); } + [[nodiscard]] constexpr bool bumped() const { return Potassco::test_bit(rep, bumped_bit); } + constexpr void bumpActivity() { rep += (activity() < act_max); } + constexpr void bumpLbd(uint32_t x) { + if (x < lbd()) { + Potassco::store_clear_mask(rep, lbd_mask); + Potassco::store_set_mask(rep, (x << lbd_shift) | Potassco::nth_bit(bumped_bit)); + } + } + constexpr void clearBumped() { Potassco::store_clear_bit(rep, bumped_bit); } + constexpr void reduce() { + clearBumped(); + if (uint32_t a = activity()) { + Potassco::store_clear_mask(rep, act_max); + Potassco::store_set_mask(rep, a >> 1); + } + } + constexpr void assign(Score o) { + Potassco::store_clear_mask(rep, score_mask); + Potassco::store_set_mask(rep, o.rep & score_mask); + } + + friend constexpr bool operator==(ConstraintScore lhs, ConstraintScore rhs) noexcept { + return (lhs.rep & score_mask) == (rhs.rep & score_mask); + } + + uint32_t rep; }; -inline ConstraintScore makeScore(uint32 act = 0, uint32 lbd = 0) { - ConstraintScore sc = {0}; sc.reset(act, lbd); - return sc; -} + //! Type storing meta information about a constraint. class ConstraintInfo : private ConstraintScore { public: - typedef ConstraintInfo Info; - typedef ConstraintScore Score; - ConstraintInfo(ConstraintType t = Constraint_t::Static) { - static_assert(ConstraintScore::BITS_USED <= 28, "invalid score"); - rep = uint32(t) << TYPE_SHIFT; - } - using ConstraintScore::lbd; - using ConstraintScore::activity; - ConstraintType type() const { return static_cast( (rep & uint32(TYPE_MASK)) >> TYPE_SHIFT ); } - bool tagged() const { return test_bit(rep, TAG_BIT); } - bool aux() const { return tagged() || test_bit(rep, AUX_BIT); } - bool learnt() const { return type() != Constraint_t::Static; } - const Score& score() const { return *this; } - Score& score() { return *this; } - - Info& setType(ConstraintType t) { rep &= ~uint32(TYPE_MASK); rep |= (uint32(t) << TYPE_SHIFT); return *this; } - Info& setScore(Score sc) { assign(sc); return *this; } - Info& setActivity(uint32 a) { assign(makeScore(a, lbd())); return *this; } - Info& setLbd(uint32 lbd) { assign(makeScore(activity(), lbd)); return *this; } - Info& setTagged(bool b) { if (test_bit(rep, TAG_BIT) != b) store_toggle_bit(rep, TAG_BIT); return *this; } - Info& setAux(bool b) { if (test_bit(rep, AUX_BIT) != b) store_toggle_bit(rep, AUX_BIT); return *this; } + using Info = ConstraintInfo; + using Score = ConstraintScore; + constexpr ConstraintInfo(ConstraintType t = ConstraintType::static_) { + static_assert(ConstraintScore::bits_used <= 28, "invalid score"); + rep = static_cast(t) << type_shift; + } + using ConstraintScore::activity; + using ConstraintScore::lbd; + [[nodiscard]] constexpr ConstraintType type() const { + return static_cast((rep & type_mask) >> type_shift); + } + [[nodiscard]] constexpr bool tagged() const { return Potassco::test_bit(rep, tag_bit); } + [[nodiscard]] constexpr bool aux() const { return tagged() || Potassco::test_bit(rep, aux_bit); } + [[nodiscard]] constexpr bool learnt() const { return type() != ConstraintType::static_; } + [[nodiscard]] constexpr const Score& score() const { return *this; } + constexpr Score& score() { return *this; } + + constexpr Info& setType(ConstraintType t) { + Potassco::store_clear_mask(rep, type_mask); + Potassco::store_set_mask(rep, (static_cast(t) << type_shift)); + return *this; + } + constexpr Info& setScore(Score sc) { + assign(sc); + return *this; + } + constexpr Info& setActivity(uint32_t a) { + assign(ConstraintScore(a, lbd())); + return *this; + } + constexpr Info& setLbd(uint32_t lbd) { + assign(ConstraintScore(activity(), lbd)); + return *this; + } + constexpr Info& setTagged(bool b) { + tagged() == b || Potassco::store_toggle_bit(rep, tag_bit); + return *this; + } + constexpr Info& setAux(bool b) { + aux() == b || Potassco::store_toggle_bit(rep, aux_bit); + return *this; + } + private: - enum { TYPE_SHIFT = 28, AUX_BIT = 30, TAG_BIT = 31, TYPE_MASK = (3u << TYPE_SHIFT) }; + static constexpr uint32_t tag_bit = 31u; + static constexpr uint32_t aux_bit = 30u; + static constexpr uint32_t type_shift = 28u; + static constexpr uint32_t type_mask = 3u << type_shift; }; //@} /** -* \defgroup shared_con Shared -* \brief %Constraint data that can safely be shared between solvers. -* \ingroup constraint -*/ + * \defgroup shared_con Shared + * \brief %Constraint data that can safely be shared between solvers. + * \ingroup constraint + */ -} -#endif +} // namespace Clasp diff --git a/clasp/dependency_graph.h b/clasp/dependency_graph.h index 2134e63..d4b270d 100644 --- a/clasp/dependency_graph.h +++ b/clasp/dependency_graph.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,38 +21,28 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_DEPENDENCY_GRAPH_H_INCLUDED -#define CLASP_DEPENDENCY_GRAPH_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include -#include + namespace Clasp { class Solver; class SharedContext; struct SolverStats; //! Event type used to signal a (partial) check in disjunctive solving. -struct SolveTestEvent : SolveEvent { - SolveTestEvent(const Solver& s, uint32 hcc, bool partial); - int result; //!< -1: before test, 0: unstable, 1: stable - uint32 hcc :31;//!< hcc under test - uint32 partial : 1;//!< partial test? - uint64 confDelta; //!< conflicts before test - uint64 choiceDelta;//!< choices before test - double time; //!< time for test - - uint64 conflicts() const; - uint64 choices() const; -}; -struct LoopReason_t { - enum Type { Explicit = 0u, Implicit = 1u, }; +struct SolveTestEvent : SolveEvent { + SolveTestEvent(const Solver& s, uint32_t hcc, bool partial); + int result; //!< -1: before test, 0: unstable, 1: stable + uint32_t hcc : 31; //!< hcc under test + uint32_t partial : 1; //!< partial test? + uint64_t confDelta; //!< conflicts before test + uint64_t choiceDelta; //!< choices before test + double time; //!< time for test + + [[nodiscard]] uint64_t conflicts() const; + [[nodiscard]] uint64_t choices() const; }; -typedef LoopReason_t::Type LoopType; namespace Asp { //! (Positive) Body-Atom-Dependency Graph. @@ -66,263 +56,339 @@ namespace Asp { */ class PrgDepGraph { public: - enum NonHcfMapType { - map_old = 0, - map_new = 1 - }; - explicit PrgDepGraph(NonHcfMapType m = map_old); - ~PrgDepGraph(); - typedef uint32 NodeId; - //! Type for storing a non head-cycle-free component of a disjunctive program. - class NonHcfComponent { - public: - explicit NonHcfComponent(uint32 id, const PrgDepGraph& dep, SharedContext& generator, Configuration* c, uint32 scc, const VarVec& atoms, const VarVec& bodies); - ~NonHcfComponent(); - void assumptionsFromAssignment(const Solver& generator, LitVec& assumptionsOut) const; - bool test(const Solver& generator, const LitVec& assumptions, VarVec& unfoundedOut) const; - bool simplify(const Solver& generator) const; - const SharedContext& ctx() const { return *prg_; } - void update(const SharedContext& generator); - uint32 id() const { return id_; } - uint32 scc() const { return scc_; } - private: - friend class PrgDepGraph; - NonHcfComponent(const NonHcfComponent&); - NonHcfComponent& operator=(const NonHcfComponent&); - class ComponentMap; - const PrgDepGraph* dep_; - SharedContext* prg_; - ComponentMap* comp_; - uint32 id_; - uint32 scc_; - }; - //! A class for storing statistics on checking of non head-cycle-free components. - class NonHcfStats { - public: - NonHcfStats(PrgDepGraph& g, uint32 level, bool inc); - ~NonHcfStats(); - void accept(StatsVisitor& out, bool final) const; - void startStep(uint32 statsLevel); - void endStep(); - void addTo(StatsMap& problem, StatsMap& solving, StatsMap* accu) const; - private: - friend class PrgDepGraph; - void addHcc(const NonHcfComponent&); - void removeHcc(const NonHcfComponent&); - NonHcfStats(const NonHcfStats&); - NonHcfStats& operator=(const NonHcfStats&); - struct Data; - PrgDepGraph* graph_; - Data* data_; - }; - typedef PodVector::type ComponentVec; - typedef ComponentVec::const_iterator NonHcfIter; - //! Base type for nodes. - struct Node { - Node(Literal l = Literal(0, false), uint32 sc = PrgNode::noScc) - : lit(l), scc(sc), data(0), adj_(0), sep_(0) {} - Literal lit; // literal of this node - uint32 scc : 28;// scc of this node - uint32 data : 4;// additional atom/body data - NodeId* adj_; // list of adjacent nodes - NodeId* sep_; // separates successor/predecessor nodes - }; - //! An atom node. - /*! - * The PBADG stores a node of type AtomNode for each non-trivially connected atom. - * The predecessors of an AtomNode are the bodies that define the atom. Its successors - * are those bodies from the same SCC that contain the atom positively. - */ - struct AtomNode : public Node { - enum Property { property_in_choice = 1u, property_in_disj = 2u, property_in_ext = 4u, property_in_non_hcf = 8u }; - AtomNode() {} - void set(Property p) { data |= (uint32)p; } - void setProperties(uint32 f) { assert(f < 8); data |= f; } - //! Contained in the head of a choice rule? - bool inChoice() const { return (data & property_in_choice) != 0; } - //! Contained in the head of a non-hcf disjunctive rule? - bool inDisjunctive()const { return (data & property_in_disj) != 0; } - //! Contained in an extended body? - bool inExtended() const { return (data& property_in_ext) != 0; } - //! Contained in a non-hcf SCC? - bool inNonHcf() const { return (data & property_in_non_hcf) != 0; } - //! Bodies (i.e. predecessors): bodies from other SCCs precede those from same SCC. - const NodeId* bodies_begin() const { return adj_; } - const NodeId* bodies_end() const { return sep_; } - NodeId body(uint32 i) const { return bodies_begin()[i]; } - //! Successors from same SCC [B1,...Bn, idMax]. - /*! - * \note If extended() is true, the atom is adjacent to some extended body. - * In that case, the returned list looks like this: - * [Bn1, ..., Bnj, idMax, Bext1, pos1, ..., Bextn, posn, idMax], where - * each Bni is a normal body, each Bexti is an extended body and posi is the - * position of this atom in Bexti. - */ - const NodeId* succs() const { return sep_; } - //! Calls the given function object p once for each body containing this atom. - template - void visitSuccessors(const P& p) const { - const NodeId* s = succs(); - for (; *s != idMax; ++s) { p(*s); } - if (inExtended()) { - for (++s; *s != idMax; s += 2) { p(*s, *(s+1)); } - } - } - }; - enum { sentinel_atom = 0u }; - - //! A body node. - /*! - * The PBADG stores a node of type BodyNode for each body that defines - * a non-trivially connected atom. - * The predecessors of a BodyNode are the body's subgoals. - * Its successors are the heads that are defined by the body. - * \note Normal bodies only store the positive subgoals from the same SCC, while - * extended rule bodies store all subgoals. In the latter case, the positive subgoals from - * the same SCC are stored as AtomNodes. All other subgoals are stored as literals. - */ - struct BodyNode : public Node { - enum Flag { flag_has_bound = 1u, flag_has_weights = 2u, flag_has_delta = 4u, flag_seen = 8u }; - explicit BodyNode(PrgBody* b, uint32 scc) : Node(b->literal(), scc) { - if (scc == PrgNode::noScc || b->type() == Body_t::Normal) { - data = 0; - } - else if (b->type() == Body_t::Count){ data = flag_has_bound; } - else if (b->type() == Body_t::Sum) { data = flag_has_bound | flag_has_weights; } - else { assert("UNKNOWN BODY TYPE!\n"); } - } - bool seen() const { return (data & flag_seen) != 0; } - void seen(bool b) { if (b) data |= flag_seen; else data &= ~uint32(flag_seen); } - - //! Heads (i.e. successors): atoms from same SCC precede those from other SCCs. - /*! - * \note Disjunctive heads are stored in flattened atom-lists, where the - * lists are terminated on both ends with the special sentinel atom 0. - * E.g. given - * x :- B. - * y :- B. - * a|b:- B. - * a|c:- B. - * would result in: [x,y,0,a,b,0,0,a,c,0] - */ - const NodeId* heads_begin() const { return adj_; } - const NodeId* heads_end() const { return sep_ - extended(); } - //! Any disjunctive heads? - bool delta() const { return (data & flag_has_delta) != 0; } - //! Predecessors from same SCC [a1,...an, idMax]. - /*! - * \note If extended() is true, the body stores all its subgoals and preds looks - * like this: [a1, [w1], ..., aj, [wj], idMax, l1, [w1], ..., lk, [wk], idMax], where - * each ai is an atom from the same SCC, each li is a literal of a subgoal from - * other SCCs and wi is an optional weight (only for weight rules). - */ - const NodeId* preds() const { assert(scc != PrgNode::noScc); return sep_; } - //! Returns idx of atomId in preds. - uint32 get_pred_idx(NodeId atomId) const { - const uint32 inc = pred_inc(); - uint32 idx = 0; - for (const NodeId* x = preds(); *x != idMax; x += inc, ++idx) { - if (*x == atomId) return idx; - } - return idMax; - } - NodeId get_pred(uint32 idx) const { return *(preds() + (idx*pred_inc())); } - //! Increment to jump from one pred to the next. - uint32 pred_inc() const { return 1 + sum(); } - //! Weight of ith subgoal. - /*! - * \pre i in [0, num_preds()) - */ - uint32 pred_weight(uint32 i, bool ext) const { - return !sum() - ? 1 - : *(preds() + (i*pred_inc()) + (1+uint32(ext))); - } - //! Number of predecessors (counting external subgoals). - uint32 num_preds() const { - if (scc == PrgNode::noScc) return 0; - uint32 p = 0; - const NodeId* x = preds(); - const uint32 inc = pred_inc(); - for (; *x != idMax; x += inc) { ++p; } - x += extended(); - for (; *x != idMax; x += inc) { ++p; } - return p; - } - //! Is the body an extended body? - bool extended()const { return (data & flag_has_bound) != 0u; } - //! Is the body a sum body? - bool sum() const { return (data & flag_has_weights) != 0u; } - //! Bound of extended body. - weight_t ext_bound() const { return sep_[-1]; } - }; - //! Adds SCCs to the graph. - /*! - * \param prg The logic program for which the dependency graph is to be created. - * \param sccAtoms Atoms of the logic program that are strongly connected. - * \param nonHcfs Sorted list of non-hcf sccs - */ - void addSccs(LogicProgram& prg, const AtomList& sccAtoms, const NonHcfSet& nonHcfs); - - //! Removes inactive non-hcfs. - void simplify(const Solver& s); - //! Number of atoms in graph. - uint32 numAtoms() const { return (uint32)atoms_.size(); } - //! Number of bodies in graph. - uint32 numBodies()const { return (uint32)bodies_.size(); } - //! Sum of atoms and bodies. - uint32 nodes() const { return numAtoms()+numBodies(); } - - //! Returns AtomNode of atom with given id. - const AtomNode& getAtom(NodeId atomId) const { - assert(atomId < atoms_.size()); - return atoms_[atomId]; - } - NodeId id(const AtomNode& n) const { return static_cast(&n - &atoms_[0]); } - //! Returns BodyNode of body with given id. - const BodyNode& getBody(NodeId bodyId) const { - assert(bodyId < bodies_.size()); - return bodies_[bodyId]; - } - //! Calls the given function object p once for each body-literal. - template - void visitBodyLiterals(const BodyNode& n, const P& p) const { - const NodeId* x = n.preds(); - const uint32 inc = n.pred_inc(); - uint32 i = 0; - for (; *x != idMax; x += inc, ++i) { p(getAtom(*x).lit, i, false); } - x += n.extended(); - for (; *x != idMax; x += inc, ++i) { p(Literal::fromRep(*x), i, true); } - } - NonHcfIter nonHcfBegin() const { return components_.begin(); } - NonHcfIter nonHcfEnd() const { return components_.end(); } - uint32 numNonHcfs() const { return (uint32)components_.size(); } - NonHcfStats* nonHcfStats() const { return stats_; } - NonHcfStats* enableNonHcfStats(uint32 level, bool incremental); + enum NonHcfMapType { map_old = 0, map_new = 1 }; + explicit PrgDepGraph(NonHcfMapType m = map_old); + ~PrgDepGraph(); + PrgDepGraph(PrgDepGraph&&) = delete; + + using NodeId = uint32_t; + using NodeSpan = SpanView; + //! Type for storing a non head-cycle-free component of a disjunctive program. + class NonHcfComponent { + public: + explicit NonHcfComponent(uint32_t id, const PrgDepGraph& dep, SharedContext& generator, Configuration* c, + uint32_t scc, VarView atoms, VarView bodies); + ~NonHcfComponent(); + NonHcfComponent(NonHcfComponent&&) = delete; + + [[nodiscard]] uint32_t id() const { return id_; } + [[nodiscard]] uint32_t scc() const { return scc_; } + [[nodiscard]] auto ctx() const -> const SharedContext& { return *prg_; } + [[nodiscard]] bool simplify(const Solver& generator) const; + + void assumptionsFromAssignment(const Solver& generator, LitVec& assumptionsOut) const; + bool test(const Solver& generator, LitView assumptions, VarVec& unfoundedOut) const; + void update(const SharedContext& generator); + + private: + friend class PrgDepGraph; + class ComponentMap; + const PrgDepGraph* dep_; + std::unique_ptr prg_; + std::unique_ptr comp_; + uint32_t id_; + uint32_t scc_; + }; + //! A class for storing statistics on checking of non head-cycle-free components. + class NonHcfStats { + public: + NonHcfStats(PrgDepGraph& g, uint32_t level, bool inc); + ~NonHcfStats(); + NonHcfStats(NonHcfStats&&) = delete; + + void accept(StatsVisitor& out, bool final) const; + void startStep(uint32_t statsLevel); + void endStep(); + void addTo(StatsMap& problem, StatsMap& solving, StatsMap* accu) const; + + private: + friend class PrgDepGraph; + void addHcc(const NonHcfComponent&); + void removeHcc(const NonHcfComponent&); + struct Data; + PrgDepGraph* graph_; + std::unique_ptr data_; + }; + using ComponentVec = PodVector_t; + using NonHcfSpan = SpanView; + //! Base type for nodes. + struct Node { + constexpr explicit Node(Literal l = lit_true, uint32_t sc = PrgNode::no_scc) : lit(l), scc(sc), data(0) {} + Literal lit; // literal of this node + uint32_t scc : 28; // scc of this node + uint32_t data : 4; // additional atom/body data + NodeId* adj{nullptr}; // list of adjacent nodes + NodeId* sep{nullptr}; // separates successor/predecessor nodes + }; + + //! Iterator type for iterating over relevant successor/predecessor nodes. + template + class SentIter { + public: + using value_type = DerivedType; + using difference_type = std::ptrdiff_t; + using SmallInt = std::conditional_t; + + constexpr DerivedType& operator++() { + pos += inc; + advance(); + return static_cast(*this); + } + constexpr DerivedType operator++(int) { + DerivedType t(static_cast(*this)); + ++*this; + return t; + } + //! Returns the id of the current successor/predecessor. + [[nodiscard]] constexpr NodeId id() const { return *pos; } + //! Returns whether the current successor/predecessor is from a normal rule. + [[nodiscard]] constexpr bool normal() const { return inExt < 2u; } + + constexpr const value_type& operator*() const { return static_cast(*this); } + constexpr friend bool operator==(const DerivedType& it, std::default_sentinel_t) { return *it.pos == id_max; } + + struct View { + template + constexpr explicit View(Args&&... args) : it(std::forward(args)...) {} + [[nodiscard]] constexpr DerivedType begin() const { return it; } + [[nodiscard]] static constexpr std::default_sentinel_t end() { return std::default_sentinel; } + DerivedType it; + }; + + const NodeId* pos; + SmallInt inc; + SmallInt inExt; + + private: + friend DerivedType; + constexpr SentIter(const NodeId* p, SmallInt baseInc, bool hasExt) + : pos(p) + , inc(baseInc) + , inExt(static_cast(hasExt)) { + advance(); + } + constexpr void advance() { // private: check if we have to advance from normal to extended part + if (*pos == id_max && inExt == 1u) { + inExt = 2u; + ++pos; + inc += AdjustInc; + } + } + }; + + //! An atom node. + /*! + * The PBADG stores a node of type AtomNode for each non-trivially connected atom. + * The predecessors of an AtomNode are the bodies that define the atom. Its successors + * are those bodies from the same SCC that contain the atom positively. + */ + struct AtomNode : Node { + enum Property : uint32_t { + property_in_choice = 1u, + property_in_disj = 2u, + property_in_ext = 4u, + property_in_non_hcf = 8u + }; + constexpr AtomNode() = default; + constexpr void set(Property p) { data |= static_cast(p); } + constexpr void setProperties(uint32_t f) { + assert(f < 8); + data |= f; + } + //! Contained in the head of a choice rule? + [[nodiscard]] constexpr bool inChoice() const { return Potassco::test_mask(data, property_in_choice); } + //! Contained in the head of a non-hcf disjunctive rule? + [[nodiscard]] constexpr bool inDisjunctive() const { return Potassco::test_mask(data, property_in_disj); } + //! Contained in an extended body? + [[nodiscard]] constexpr bool inExtended() const { return Potassco::test_mask(data, property_in_ext); } + //! Contained in a non-hcf SCC? + [[nodiscard]] constexpr bool inNonHcf() const { return Potassco::test_mask(data, property_in_non_hcf); } + //! Bodies (i.e. predecessors): bodies from other SCCs precede those from same SCC. + [[nodiscard]] constexpr NodeSpan bodies() const { return {adj, sep}; } + [[nodiscard]] constexpr NodeId body(uint32_t i) const { return adj[i]; } + + //! Iterator type for iterating over the list of relevant successors (bodies) of an atom. + /*! + * \note The list of successors of an atom looks like this: + * [B1, ..., Bj, id_max, Ext1, pos1, ..., Extn, posn, id_max], where + * each Bi is a normal body, + * each Exti is an extended body, + * and posi is the position of the atom in Exti. + * + * \note The extended part is optional and only exists if the atom appears in at least one extended body. + */ + struct SuccIt : SentIter { + using SentIter::SentIter; + //! Returns the position of this atom in the extended body with id(). + /*! + * \pre normal() == false + */ + [[nodiscard]] constexpr NodeId position() const { return this->pos[1]; } + }; + //! Returns the relevant successors of this atom, i.e. bodies containing this atom. + [[nodiscard]] constexpr auto successors() const { return SuccIt::View{sep, 1u, inExtended()}; } + }; + static constexpr uint32_t sentinel_atom = 0u; + + //! A body node. + /*! + * The PBADG stores a node of type BodyNode for each body that defines a non-trivially connected atom. + * The predecessors of a BodyNode are the body's subgoals. + * Its successors are the heads that are defined by the body. + * \note Normal bodies only store the positive subgoals from the same SCC, while + * extended rule bodies store all subgoals. In the latter case, the positive subgoals from + * the same SCC are stored as AtomNodes. All other subgoals are stored as literals. + */ + struct BodyNode : Node { + enum : uint32_t { flag_has_bound = 1u, flag_has_weights = 2u, flag_has_delta = 4u, flag_seen = 8u }; + constexpr explicit BodyNode(const PrgBody* b, uint32_t ascc) : Node(b->literal(), ascc) { + assert(scc == ascc && data == 0); + if (ascc != PrgNode::no_scc && b->type() != BodyType::normal) { + data = flag_has_bound | (b->type() == BodyType::sum ? flag_has_weights : 0u); + } + } + [[nodiscard]] constexpr bool seen() const { return Potassco::test_mask(data, flag_seen); } + constexpr void seen(bool b) { + if (seen() != b) { + data = Potassco::toggle_mask(data, flag_seen); + } + } + + //! Heads (i.e. successors): atoms from same SCC precede those from other SCCs. + /*! + * \note Disjunctive heads are stored in flattened atom-lists, where the + * lists are terminated on both ends with the special sentinel atom 0. + * E.g. given + * x :- B. + * y :- B. + * a|b:- B. + * a|c:- B. + * would result in: [x,y,0,a,b,0,0,a,c,0] + */ + [[nodiscard]] constexpr NodeSpan heads() const { return {adj, sep - extended()}; } + //! Any disjunctive heads? + [[nodiscard]] constexpr bool delta() const { return Potassco::test_mask(data, flag_has_delta); } + + //! Iterator type for iterating over the list of relevant predecessor atoms of this body. + /*! + * \note The list of predecessors looks like this: + * [a1, [w1], ..., aj, [wj], id_max, l1, [w1], ..., lk, [wk], id_max], where + * each 'ai' is an atom from the same SCC (normal and extended bodies), + * each 'li' is a literal of a subgoal from other SCCs (extended bodies only), + * and each 'wi' is an optional weight (only for weight rules). + * + * \note The extended part is optional and only exists if this body is an extended body (extended() is true). + * \note The extended part stores literal representations (not atom node ids). + */ + struct PredIt : SentIter { + using SentIter::SentIter; + //! Returns the weight of the current subgoal. + [[nodiscard]] constexpr Weight_t weight() const { + return this->inc == 1 ? 1 : static_cast(pos[1]); + } + //! Returns the literal associated with the current subgoal. + [[nodiscard]] constexpr Literal lit(const PrgDepGraph& graph) const { + return normal() ? graph.getAtomLit(*pos) : Literal::fromRep(*pos); + } + //! Returns whether the current subgoal is a literal from another scc (extended bodies only). + [[nodiscard]] constexpr bool ext() const { return not normal(); } + }; + + //! Returns the relevant predecessors of this body. + [[nodiscard]] constexpr auto predecessors(bool internalOnly = false) const { + return PredIt::View{sep, 1u + sum(), not internalOnly && extended()}; + } + //! Number of predecessors (counting external subgoals). + [[nodiscard]] constexpr uint32_t countPreds() const { + return scc == PrgNode::no_scc ? 0u : static_cast(std::ranges::distance(predecessors())); + } + //! Returns idx of atomId in predecessors() or id_max if atom is not found. + [[nodiscard]] constexpr uint32_t findPred(NodeId atomId) const { + for (uint32_t idx = 0; const auto& x : predecessors(true)) { + if (x.id() == atomId) { + return idx; + } + ++idx; + } + return id_max; + } + + //! Weight of ith subgoal. + /*! + * \pre i in [0, countPreds()) + */ + [[nodiscard]] constexpr Weight_t predWeight(uint32_t i, bool ext) const { + return not sum() ? 1 : static_cast(sep[(i << 1) + (1u + ext)]); + } + //! Is the body an extended body? + [[nodiscard]] constexpr bool extended() const { return Potassco::test_mask(data, flag_has_bound); } + //! Is the body a sum body? + [[nodiscard]] constexpr bool sum() const { return Potassco::test_mask(data, flag_has_weights); } + //! Bound of extended body. + [[nodiscard]] constexpr Weight_t extBound() const { + assert(extended()); + const auto* x = sep - 1; + return static_cast(*x); + } + }; + //! Adds SCCs to the graph. + /*! + * \param prg The logic program for which the dependency graph is to be created. + * \param sccAtoms Atoms of the logic program that are strongly connected. + * \param nonHcfs Sorted list of non-hcf sccs + */ + void addSccs(const LogicProgram& prg, const AtomList& sccAtoms, const NonHcfSet& nonHcfs); + + //! Removes inactive non-hcfs. + void simplify(const Solver& s); + //! Number of atoms in graph. + [[nodiscard]] uint32_t numAtoms() const { return size32(atoms_); } + //! Number of bodies in graph. + [[nodiscard]] uint32_t numBodies() const { return size32(bodies_); } + //! Sum of atoms and bodies. + [[nodiscard]] uint32_t nodes() const { return numAtoms() + numBodies(); } + + //! Returns AtomNode of atom with given id. + [[nodiscard]] const AtomNode& getAtom(NodeId atomId) const { + assert(atomId < atoms_.size()); + return atoms_[atomId]; + } + [[nodiscard]] Literal getAtomLit(NodeId atomId) const { return getAtom(atomId).lit; } + [[nodiscard]] NodeId id(const AtomNode& n) const { return static_cast(&n - atoms_.data()); } + //! Returns BodyNode of body with given id. + [[nodiscard]] const BodyNode& getBody(NodeId bodyId) const { + assert(bodyId < bodies_.size()); + return bodies_[bodyId]; + } + + [[nodiscard]] NonHcfSpan nonHcfs() const { return components_; } + [[nodiscard]] uint32_t numNonHcfs() const { return size32(components_); } + [[nodiscard]] NonHcfStats* nonHcfStats() const { return stats_.get(); } + NonHcfStats* enableNonHcfStats(uint32_t level, bool incremental); + private: - typedef PodVector::type AtomVec; - typedef PodVector::type BodyVec; - PrgDepGraph(const PrgDepGraph&); - PrgDepGraph& operator=(const PrgDepGraph&); - inline bool relevantPrgAtom(const Solver& s, PrgAtom* a) const; - inline bool relevantPrgBody(const Solver& s, PrgBody* b) const; - NonHcfMapType nonHcfMapType() const { return static_cast(mapType_); } - NodeId createBody(PrgBody* b, uint32 bScc); - NodeId createAtom(Literal lit, uint32 aScc); - NodeId addBody(const LogicProgram& prg, PrgBody*); - NodeId addDisj(const LogicProgram& prg, PrgDisj*); - uint32 addHeads(const LogicProgram& prg, PrgBody*, VarVec& atoms) const; - uint32 getAtoms(const LogicProgram& prg, PrgDisj*, VarVec& atoms) const; - void addPreds(const LogicProgram& prg, PrgBody*, uint32 bScc, VarVec& preds) const; - void initBody(uint32 id, const VarVec& preds, const VarVec& atHeads); - void initAtom(uint32 id, uint32 prop, const VarVec& adj, uint32 preds); - void addNonHcf(uint32 id, SharedContext& ctx, Configuration* c, uint32 scc); - AtomVec atoms_; - BodyVec bodies_; - ComponentVec components_; - NonHcfStats* stats_; - uint32 seenComponents_ : 31; - uint32 mapType_ : 1; + using AtomVec = PodVector_t; + using BodyVec = PodVector_t; + using StatsPtr = std::unique_ptr; + + [[nodiscard]] auto nonHcfMapType() const -> NonHcfMapType { return static_cast(mapType_); } + NodeId createBody(const PrgBody* b, uint32_t bScc); + NodeId createAtom(Literal lit, uint32_t aScc); + NodeId addBody(const LogicProgram& prg, PrgBody*); + NodeId addDisj(const LogicProgram& prg, PrgDisj*); + static uint32_t addHeads(const LogicProgram& prg, const PrgBody*, VarVec& atoms); + static uint32_t getAtoms(const LogicProgram& prg, const PrgDisj*, VarVec& atoms); + static void addPreds(const LogicProgram& prg, const PrgBody*, uint32_t bScc, VarVec& preds); + void initBody(uint32_t id, VarView preds, VarView atHeads); + void initAtom(uint32_t id, uint32_t prop, VarView adj, uint32_t preds); + void addNonHcf(uint32_t id, SharedContext& ctx, Configuration* c, uint32_t scc); + + AtomVec atoms_; + BodyVec bodies_; + ComponentVec components_; + StatsPtr stats_; + uint32_t seenComponents_ : 31; + uint32_t mapType_ : 1; }; } // namespace Asp @@ -336,69 +402,69 @@ class PrgDepGraph { */ class ExtDepGraph { public: - struct Arc { - Literal lit; - uint32 node[2]; - uint32 tail() const { return node[0]; } - uint32 head() const { return node[1]; } - static Arc create(Literal x, uint32 nodeX, uint32 nodeY) { Arc a = {x, {nodeX, nodeY}}; return a; } - }; - struct Inv { - uint32 tail() const { return rep >> 1; } - Literal lit; - uint32 rep; - }; - template - struct CmpArc { - bool operator()(const Arc& lhs, uint32 n) const { return lhs.node[x] < n; } - bool operator()(uint32 n, const Arc& rhs) const { return n < rhs.node[x]; } - bool operator()(const Arc& lhs, const Arc& rhs) const { - return lhs.node[x] < rhs.node[x] - || (lhs.node[x] == rhs.node[x] && lhs.node[1-x] < rhs.node[1-x]); - } - }; - explicit ExtDepGraph(uint32 numNodeGuess = 0); - ~ExtDepGraph(); - void addEdge(Literal lit, uint32 startNode, uint32 endNode); - void update(); - uint32 finalize(SharedContext& ctx); - bool frozen() const; - uint64 attach(Solver& s, Constraint& p, uint64 genId); - void detach(Solver* s, Constraint& p); - - const Arc& arc(uint32 id) const { return fwdArcs_[id]; } - const Arc* fwdBegin(uint32 n) const { - uint32 X = nodes_[n].fwdOff; - return validOff(X) ? &fwdArcs_[X] : 0; - } - const Arc* fwdNext(const Arc* a)const { assert(a); return a[0].node[0] == a[1].node[0] ? ++a : 0; } - const Inv* invBegin(uint32 n) const { - uint32 X = nodes_[n].invOff; - return validOff(X) ? &invArcs_[X] : 0; - } - const Inv* invNext(const Inv* a)const { assert(a); return (a->rep & 1u) == 1u ? ++a : 0; } - uint32 nodes() const { return maxNode_; } - uint32 edges() const { return comEdge_; } - bool validNode(uint32 n) const { return n < maxNode_; } + struct Arc { + Literal lit; + uint32_t node[2]; + [[nodiscard]] uint32_t tail() const { return node[0]; } + [[nodiscard]] uint32_t head() const { return node[1]; } + [[nodiscard]] const Arc* next() const { return node[0] == (this + 1)->node[0] ? this + 1 : nullptr; } + static Arc create(Literal x, uint32_t nodeX, uint32_t nodeY) { + Arc a = {x, {nodeX, nodeY}}; + return a; + } + }; + struct Inv { + [[nodiscard]] uint32_t tail() const { return rep >> 1; } + [[nodiscard]] const Inv* next() const { return (rep & 1u) != 0u ? this + 1 : nullptr; } + Literal lit; + uint32_t rep{}; + }; + template + struct CmpArc { + bool operator()(const Arc& lhs, uint32_t n) const { return lhs.node[X] < n; } + bool operator()(uint32_t n, const Arc& rhs) const { return n < rhs.node[X]; } + bool operator()(const Arc& lhs, const Arc& rhs) const { + return lhs.node[X] < rhs.node[X] || (lhs.node[X] == rhs.node[X] && lhs.node[1 - X] < rhs.node[1 - X]); + } + }; + explicit ExtDepGraph(uint32_t numNodeGuess = 0); + ~ExtDepGraph(); + ExtDepGraph(ExtDepGraph&&) = delete; + void addEdge(Literal lit, uint32_t startNode, uint32_t endNode); + void update(); + uint32_t finalize(SharedContext& ctx); + [[nodiscard]] bool frozen() const; + uint64_t attach(Solver& s, Constraint& p, uint64_t genId); + void detach(Solver* s, Constraint& p); + + [[nodiscard]] const Arc& arc(uint32_t id) const { return fwdArcs_[id]; } + [[nodiscard]] const Arc* fwdBegin(uint32_t n) const { + uint32_t x = nodes_[n].fwdOff; + return validOff(x) ? &fwdArcs_[x] : nullptr; + } + [[nodiscard]] const Inv* invBegin(uint32_t n) const { + uint32_t x = nodes_[n].invOff; + return validOff(x) ? &invArcs_[x] : nullptr; + } + [[nodiscard]] uint32_t nodes() const { return maxNode_; } + [[nodiscard]] uint32_t edges() const { return comEdge_; } + [[nodiscard]] bool validNode(uint32_t n) const { return n < maxNode_; } + private: - ExtDepGraph(const ExtDepGraph&); - ExtDepGraph& operator=(const ExtDepGraph&); - struct Node { - uint32 fwdOff; - uint32 invOff; - }; - typedef PodVector::type ArcVec; - typedef PodVector::type InvVec; - typedef PodVector::type NodeVec; - bool validOff(uint32 n) const { - return n != UINT32_MAX; - } - ArcVec fwdArcs_; // arcs ordered by node id - InvVec invArcs_; // inverse arcs ordered by node id - NodeVec nodes_; // data for the nodes of this graph - uint32 maxNode_; // nodes have ids in the range [0, maxNode_) - uint32 comEdge_; // number of edges committed - uint32 genCnt_; // generation count (for incremental updates) + [[nodiscard]] static constexpr bool validOff(uint32_t n) { return n != UINT32_MAX; } + struct Node { + uint32_t fwdOff; + uint32_t invOff; + }; + using ArcVec = PodVector_t; + using InvVec = PodVector_t; + using NodeVec = PodVector_t; + ArcVec fwdArcs_; // arcs ordered by node id + InvVec invArcs_; // inverse arcs ordered by node id + NodeVec nodes_; // data for the nodes of this graph + uint32_t maxNode_; // nodes have ids in the range [0, maxNode_) + uint32_t comEdge_; // number of edges committed + uint32_t genCnt_; // generation count (for incremental updates) }; //! Acyclicity checker that operates on a ExtDepGraph. @@ -408,69 +474,71 @@ class ExtDepGraph { */ class AcyclicityCheck : public PostPropagator { public: - enum Strategy { - prop_full = 0, // forward and backward check with clause generation - prop_full_imp = 1, // forward and backward check without clause generation - prop_fwd = 2, // only forward check - }; - enum { PRIO = PostPropagator::priority_reserved_ufs + 1 }; - typedef ExtDepGraph DependencyGraph; - explicit AcyclicityCheck(DependencyGraph* graph); - ~AcyclicityCheck(); - void setStrategy(Strategy p); - void setStrategy(const SolverParams& opts); - // base interface - uint32 priority() const { return uint32(PRIO); } - bool init(Solver&); - void reset(); - bool propagateFixpoint(Solver& s, PostPropagator* ctx); - bool isModel(Solver& s); - bool valid(Solver& s); - void destroy(Solver* s, bool detach); + enum Strategy { + prop_full = 0, // forward and backward check with clause generation + prop_full_imp = 1, // forward and backward check without clause generation + prop_fwd = 2, // only forward check + }; + static constexpr uint32_t prio = priority_reserved_ufs + 1; + using DependencyGraph = ExtDepGraph; + explicit AcyclicityCheck(DependencyGraph* graph); + ~AcyclicityCheck() override; + AcyclicityCheck(AcyclicityCheck&&) = delete; + void setStrategy(Strategy p); + void setStrategy(const SolverParams& opts); + // base interface + [[nodiscard]] uint32_t priority() const override { return prio; } + bool init(Solver&) override; + void reset() override; + bool propagateFixpoint(Solver& s, PostPropagator* ctx) override; + bool isModel(Solver& s) override; + bool valid(Solver& s) override; + void destroy(Solver* s, bool detach) override; + private: - AcyclicityCheck(const AcyclicityCheck&); - AcyclicityCheck& operator=(const AcyclicityCheck&); - struct Parent { - static Parent create(Literal x, uint32 n) { Parent p = {x, n}; return p; } - Literal lit; - uint32 node; - }; - enum { config_bit = 2 }; - struct ReasonStore; - typedef DependencyGraph::Arc Arc; - typedef DependencyGraph::Inv Inv; - typedef PodQueue EdgeQueue; - typedef PodVector::type TagVec; - typedef PodVector::type ParentVec; - bool dfsForward(Solver& s, const Arc& e); - bool dfsBackward(Solver& s, const Arc& e); - void setParent(Var node, const Parent& p){ parent_[node] = p; } - void pushVisit(Var node, uint32 tv) { nStack_.push_back(node); tags_[node] = tv; } - bool visited(Var node, uint32 tv) const { return tags_[node] == tv; } - uint32 startSearch(); - void addClauseLit(Solver& s, Literal p); - void setReason(Literal p, LitVec::const_iterator first, LitVec::const_iterator end); - // ------------------------------------------------------------------------------------------- - // constraint interface - PropResult propagate(Solver&, Literal, uint32& eId) { - todo_.push(graph_->arc(eId)); - return PropResult(true, true); - } - void reason(Solver& s, Literal, LitVec&); - Strategy strategy() const { return static_cast(strat_ & 3u); } - DependencyGraph* graph_; // my graph - Solver* solver_; // my solver - ReasonStore* nogoods_;// stores at most one reason per literal - uint32 strat_; // active propagation strategy - uint32 tagCnt_; // generation counter for searches - EdgeQueue todo_; // edges that recently became enabled - TagVec tags_; // tag for each node - ParentVec parent_; // parents for each node - VarVec nStack_; // node stack for df-search - LitVec reason_; // reason for conflict/implication - uint64 genId_; // generation identifier -}; + struct Parent { + Literal lit; + uint32_t node{}; + }; + static constexpr uint32_t config_bit = 2u; + struct ReasonStore; + using Arc = DependencyGraph::Arc; + using Inv = DependencyGraph::Inv; + using EdgeQueue = PodQueue; + using TagVec = PodVector_t; + using ParentVec = PodVector_t; + using StorePtr = std::unique_ptr; + bool dfsForward(Solver& s, const Arc& e); + bool dfsBackward(Solver& s, const Arc& e); + void setParent(uint32_t node, const Parent& p) { parent_[node] = p; } + void pushVisit(uint32_t node, uint32_t tv) { + nStack_.push_back(node); + tags_[node] = tv; + } + [[nodiscard]] bool visited(uint32_t node, uint32_t tv) const { return tags_[node] == tv; } + uint32_t startSearch(); + void addClauseLit(Solver& s, Literal p); + void setReason(Literal p, LitView reason); + // ------------------------------------------------------------------------------------------- + // constraint interface + PropResult propagate(Solver&, Literal, uint32_t& eId) override { + todo_.push(graph_->arc(eId)); + return PropResult(true, true); + } + void reason(Solver& s, Literal, LitVec&) override; + [[nodiscard]] Strategy strategy() const { return static_cast(strat_ & 3u); } -} -#endif + DependencyGraph* graph_; // my graph + Solver* solver_; // my solver + StorePtr nogoods_; // stores at most one reason per literal + uint32_t strat_; // active propagation strategy + uint32_t tagCnt_; // generation counter for searches + EdgeQueue todo_; // edges that recently became enabled + TagVec tags_; // tag for each node + ParentVec parent_; // parents for each node + VarVec nStack_; // node stack for df-search + LitVec reason_; // reason for conflict/implication + uint64_t genId_; // generation identifier +}; +} // namespace Clasp diff --git a/clasp/enumerator.h b/clasp/enumerator.h index 068f00b..b6398ef 100644 --- a/clasp/enumerator.h +++ b/clasp/enumerator.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,17 +21,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_ENUMERATOR_H_INCLUDED -#define CLASP_ENUMERATOR_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include + #include +#include #include #include +#include + namespace Clasp { class Solver; class SharedContext; @@ -40,33 +38,41 @@ class EnumerationConstraint; //! Type for storing a model. struct Model { - enum Type { Sat = 0u, Brave = 1u, Cautious = 2u, User = 4u }; - enum { cons_mask = 3u, est_pos_mask= 4u, est_neg_mask = 8u }; - static uint8 estMask(Literal p) { return est_pos_mask << ((int)p.sign()); } - bool hasVar(Var v) const { return values && v < values->size(); } - //! True if this model stores current (cautious/brave) consequences. - bool consequences() const { return (type & cons_mask) != 0; } - //! For sat models, value of v in model. Otherwise, undefined. - ValueRep value(Var v) const { assert(hasVar(v)); return (*values)[v] & 3u; } - //! True if p is true in model or part of current consequences. - bool isTrue(Literal p) const { return (value(p.var()) & trueValue(p)) != 0; } - //! True if p is part of a definite answer. - bool isDef(Literal p) const { return isTrue(p) && (def || ((type & Cautious) == 0u) || !isEst(p)); } - //! True if p is part of the current estimate of consequences. - bool isEst(Literal p) const { assert(hasVar(p.var())); return ((*values)[p.var()] & estMask(p)) != 0; } - void reset(); - uint64 num; // running number of this model - const Enumerator* ctx; // ctx in which model was found - const ValueVec* values; // variable assignment or consequences - const SumVec* costs; // associated costs (or 0) - uint32 sId :16;// id of solver that found the model - uint32 type:10;// type of model - uint32 opt : 1;// whether the model is optimal w.r.t costs (0: unknown) - uint32 def : 1;// whether the model is definite w.r.t consequences (0: unknown) - uint32 sym : 1;// whether symmetric models are possible - uint32 up : 1;// whether the model was updated on last unsat - uint32 fin : 1;// final model of the active reasoning step - uint32 res : 1;// reserved + enum Type { sat = 0u, brave = 1u, cautious = 2u, user = 4u }; + enum : uint32_t { cons_mask = 3u, est_pos_mask = 4u, est_neg_mask = 8u }; + static uint8_t estMask(Literal p) { return est_pos_mask << static_cast(p.sign()); } + [[nodiscard]] bool hasVar(Var_t v) const { return v < values.size(); } + [[nodiscard]] bool hasCosts() const { return not costs.empty(); } + //! True if this model stores current (cautious/brave) consequences. + [[nodiscard]] bool consequences() const { return Potassco::test_any(type, cons_mask); } + //! For sat models, value of v in model. Otherwise, undefined. + [[nodiscard]] Val_t value(Var_t v) const { + assert(hasVar(v)); + return values[v] & 3u; + } + //! True if p is true in model or part of current consequences. + [[nodiscard]] bool isTrue(Literal p) const { return Potassco::test_any(value(p.var()), trueValue(p)); } + //! True if p is part of a definite answer. + [[nodiscard]] bool isDef(Literal p) const { + return isTrue(p) && (def || not Potassco::test_any(type, cautious) || not isEst(p)); + } + //! True if p is part of the current estimate of consequences. + [[nodiscard]] bool isEst(Literal p) const { + assert(hasVar(p.var())); + return Potassco::test_any(values[p.var()], estMask(p)); + } + uint64_t num = 0; // running number of this model + const Enumerator* ctx = nullptr; // ctx in which model was found + ValueView values; // variable assignment or consequences + SumView costs; // associated costs (or empty if not applicable) + uint32_t sId : 16 = 0; // id of solver that found the model + uint32_t type : 10 = 0; // type of model + uint32_t opt : 1 = 0; // whether the model is optimal w.r.t costs (0: unknown) + uint32_t def : 1 = 0; // whether the model is definite w.r.t consequences (0: unknown) + uint32_t sym : 1 = 0; // whether symmetric models are possible + uint32_t up : 1 = 0; // whether the model was updated on last unsat + uint32_t fin : 1 = 0; // final model of the active reasoning step + uint32_t res : 1 = 0; // reserved }; /** @@ -77,33 +83,42 @@ struct Model { //! Options for configuring enumeration. struct EnumOptions { - typedef MinimizeMode OptMode; - typedef ProjectMode ProjMode; - enum EnumType { enum_auto = 0, enum_bt = 1, enum_record = 2, enum_dom_record = 3, enum_consequences = 4, enum_brave = 5, enum_cautious = 6, enum_query = 7, enum_user = 8 }; - EnumOptions() : numModels(-1), enumMode(enum_auto), optMode(MinimizeMode_t::optimize), proMode(ProjectMode_t::Implicit), project(0) {} - static Enumerator* createModelEnumerator(const EnumOptions& opts); - static Enumerator* createConsEnumerator(const EnumOptions& opts); - static Enumerator* nullEnumerator(); - static Enumerator* createEnumerator(const EnumOptions& opts); - bool consequences() const { return (enumMode & enum_consequences) != 0; } - bool models() const { return (enumMode < enum_consequences); } - bool optimize() const { return ((optMode & MinimizeMode_t::optimize) != 0); } - int64 numModels; /*!< Number of models to compute. */ - EnumType enumMode; /*!< Enumeration type to use. */ - OptMode optMode; /*!< Optimization mode to use. */ - ProjMode proMode; /*!< Projection mode to use. */ - uint32 project; /*!< Options for projection. */ - SumVec optBound; /*!< Initial bound for optimize statements. */ - SumVec optStop; /*!< Optional stop bound for optimization. */ + using OptMode = MinimizeMode; + using ProjMode = ProjectMode; + enum EnumType { + enum_auto = 0, + enum_bt = 1, + enum_record = 2, + enum_dom_record = 3, + enum_consequences = 4, + enum_brave = 5, + enum_cautious = 6, + enum_query = 7, + enum_user = 8 + }; + EnumOptions() = default; + static Enumerator* createModelEnumerator(const EnumOptions& opts); + static Enumerator* createConsEnumerator(const EnumOptions& opts); + static Enumerator* nullEnumerator(); + static Enumerator* createEnumerator(const EnumOptions& opts); + [[nodiscard]] bool consequences() const { return (enumMode & enum_consequences) != 0; } + [[nodiscard]] bool models() const { return (enumMode < enum_consequences); } + [[nodiscard]] bool optimize() const { return Potassco::test(optMode, MinimizeMode::optimize); } + int64_t numModels{-1}; /*!< Number of models to compute. */ + EnumType enumMode{enum_auto}; /*!< Enumeration type to use. */ + OptMode optMode{MinimizeMode::optimize}; /*!< Optimization mode to use. */ + ProjMode proMode{ProjectMode::implicit}; /*!< Projection mode to use. */ + uint32_t project{0}; /*!< Options for projection. */ + SumVec optBound; /*!< Initial bound for optimize statements. */ + SumVec optStop; /*!< Optional stop bound for optimization. */ }; const char* modelType(const Model& m); - //! Interface for supporting enumeration of models. /*! * Enumerators are global w.r.t a solve algorithm. That is, * even if the solve algorithm itself uses a parallel search, there - * shall be only one enumerator and it is the user's responsibility + * shall be only one enumerator, and it is the user's responsibility * to protect the enumerator with appropriate locking. * * Concrete enumerators must implement a function for preparing a problem for enumeration @@ -114,159 +129,167 @@ const char* modelType(const Model& m); */ class Enumerator { public: - typedef EnumerationConstraint* ConPtr; - typedef EnumerationConstraint& ConRef; - typedef const SharedMinimizeData* Minimizer; - typedef EnumOptions::OptMode OptMode; - class ThreadQueue; - explicit Enumerator(); - virtual ~Enumerator(); + using ConPtr = EnumerationConstraint*; + using ConRef = EnumerationConstraint&; + using Minimizer = const SharedMinimizeData*; + using OptMode = EnumOptions::OptMode; + class SharedQueue; + explicit Enumerator(); + virtual ~Enumerator(); + Enumerator(Enumerator&&) = delete; - void reset(); + void reset(); - //! Prepares the problem for enumeration. - /*! - * The function shall be called once before search is started and before SharedContext::endInit() - * was called. It freezes enumeration-related variables and adds a suitable enumeration constraint - * to the master solver. - * - * \pre The problem is not yet frozen, i.e. SharedContext::endInit() was not yet called. - * \param problem The problem on which this enumerator should work. - * \param opt Minimization mode to apply during enumeration. - * \param limit Optional hint on max number of models to compute. - * - * \note In the incremental setting, init() must be called once for each incremental step. - */ - int init(SharedContext& problem, OptMode opt = MinimizeMode_t::optimize, int limit = 0); + //! Prepares the problem for enumeration. + /*! + * The function shall be called once before search is started and before SharedContext::endInit() + * was called. It freezes enumeration-related variables and adds a suitable enumeration constraint + * to the master solver. + * + * \pre The problem is not yet frozen, i.e. SharedContext::endInit() was not yet called. + * \param problem The problem on which this enumerator should work. + * \param opt Minimization mode to apply during enumeration. + * \param limit Optional hint on max number of models to compute. + * + * \note In the incremental setting, init() must be called once for each incremental step. + */ + int init(SharedContext& problem, OptMode opt = MinimizeMode::optimize, int limit = 0); - //! Prepares the given solver for enumeration under the given path. - /*! - * The function shall be called once before solving is started. It pushes the - * given path to the solver by calling Solver::pushRoot() and prepares s for - * enumeration/optimization. - * \return true if path was added. - */ - bool start(Solver& s, const LitVec& path = LitVec(), bool disjointPath = false) const; + //! Prepares the given solver for enumeration under the given path. + /*! + * The function shall be called once before solving is started. It pushes the + * given path to the solver by calling Solver::pushRoot() and prepares s for + * enumeration/optimization. + * \return true if path was added. + */ + bool start(Solver& s, LitView path = {}, bool disjointPath = false) const; - //! Updates the given solver with enumeration-related information. - /*! - * The function is used to integrate enumeration-related information, - * like minimization bounds or previously committed models, into the search space of s. - * It shall be called after each commit. - * - * \param s The solver to update. - * \note The function is concurrency-safe, i.e. can be called - * concurrently by different solvers. - */ - bool update(Solver& s) const; + //! Updates the given solver with enumeration-related information. + /*! + * The function is used to integrate enumeration-related information, + * like minimization bounds or previously committed models, into the search space of s. + * It shall be called after each commit. + * + * \param s The solver to update. + * \note The function is concurrency-safe, i.e. can be called + * concurrently by different solvers. + */ + bool update(Solver& s) const; - /*! - * \name Commit functions - * Functions for committing enumeration-related information to the enumerator. - * \note The functions in this group are *not* concurrency-safe, i.e. in a parallel search - * at most one solver shall call a commit function at any one time. - */ - //@{ + /*! + * \name Commit functions. + * Functions for committing enumeration-related information to the enumerator. + * \note The functions in this group are *not* concurrency-safe, i.e. in a parallel search + * at most one solver shall call a commit function at any one time. + */ + //@{ - //! Commits the model stored in the given solver. - /*! - * If the model is valid and unique, the function returns true and the - * model can be accessed via a call to Enumerator::lastModel(). - * Otherwise, the function returns false. - * In either case, Enumerator::update(s) shall be called - * in order to continue search for further models. - * - * \pre The solver's assignment is total. - */ - bool commitModel(Solver& s); - //! Expands the next symmetric model if any. - bool commitSymmetric(Solver& s); - //! Commits an unsatisfiable path stored in the given solver. - /*! - * The return value determines how search should proceed. - * If true is returned, the enumerator has relaxed an enumeration constraint - * and search may continue after a call to Enumerator::update(s). - * Otherwise, the search shall be stopped. - */ - bool commitUnsat(Solver& s); - //! Commits the given clause to this enumerator. - bool commitClause(const LitVec& clause) const; - //! Marks current enumeration phase as completed. - /*! - * If the enumerator was initialized with a minimization constraint and - * optimization mode MinimizeMode_t::enumOpt, the optimal bound is committed, - * the enumerator is prepared for enumerating optimal models, and the function - * returns false. Otherwise, the function returns true and search shall be stopped. - */ - bool commitComplete(); - //! Commits the state stored in the given solver. - /*! - * Calls commitModel() or commitUnsat() depending on the state of s. - * The function returns value_true, to signal that s stores a valid and - * unique model, value_false to signal that search shall be stopped, and - * value_free otherwise. - * \see commitModel() - * \see commitUnsat() - */ - uint8 commit(Solver& s); - //@} + //! Commits the model stored in the given solver. + /*! + * If the model is valid and unique, the function returns true and the + * model can be accessed via a call to Enumerator::lastModel(). + * Otherwise, the function returns false. + * In either case, Enumerator::update(s) shall be called + * in order to continue search for further models. + * + * \pre The solver's assignment is total. + */ + bool commitModel(Solver& s); + //! Expands the next symmetric model if any. + bool commitSymmetric(Solver& s); + //! Returns whether there is a next symmetric model not yet committed. + [[nodiscard]] bool hasSymmetric(const Solver& s) const; + //! Commits an unsatisfiable path stored in the given solver. + /*! + * The return value determines how search should proceed. + * If true is returned, the enumerator has relaxed an enumeration constraint + * and search may continue after a call to Enumerator::update(s). + * Otherwise, the search shall be stopped. + */ + bool commitUnsat(Solver& s); + //! Commits the given clause to this enumerator. + bool commitClause(LitView clause) const; // NOLINT(modernize-use-nodiscard) + //! Marks current enumeration phase as completed. + /*! + * If the enumerator was initialized with a minimization constraint and + * optimization mode MinimizeMode::enumOpt, the optimal bound is committed, + * the enumerator is prepared for enumerating optimal models, and the function + * returns false. Otherwise, the function returns true and search shall be stopped. + */ + bool commitComplete(); + //! Commits the state stored in the given solver. + /*! + * Calls commitModel() or commitUnsat() depending on the state of `s`. + * The function returns `value_true`, to signal that `s` stores a valid and + * unique model, `value_false` to signal that search shall be stopped, and + * `value_free` otherwise. + * \see commitModel() + * \see commitUnsat() + */ + Val_t commit(Solver& s); + //@} + + //! Removes from s the path that was passed to start() and any active (minimization) bound. + void end(Solver& s) const; + //! Returns the number of models enumerated so far. + [[nodiscard]] uint64_t enumerated() const { return model_.num; } + //! Returns the last model enumerated. + /*! + * \note If enumerated() is equal to 0, the returned object is in an indeterminate state. + */ + [[nodiscard]] const Model& lastModel() const { return model_; } + //! Returns the most recently updated lower bound (if any). + /*! + * \note If optimize() is false, the returned bound is always inactive. + */ + [[nodiscard]] LowerBound lowerBound() const; + //! Returns whether optimization is active. + [[nodiscard]] bool optimize() const { return mini_ && mini_->mode() != MinimizeMode::enumerate && model_.opt == 0; } + //! Returns whether computed models are still tentative. + [[nodiscard]] bool tentative() const { return mini_ && mini_->mode() == MinimizeMode::enum_opt && model_.opt == 0; } + //! Returns the active minimization constraint if any. + [[nodiscard]] Minimizer minimizer() const { return mini_; } + //! Returns the type of models this enumerator computes. + [[nodiscard]] virtual int modelType() const { return Model::sat; } + enum UnsatType { + unsat_stop = 0u, /*!< First unsat stops search - commitUnsat() always return false. */ + unsat_cont = 1u, /*!< Unsat may be tentative - commitUnsat() may return true. */ + unsat_sync = 3u, /*!< Similar to unsat_cont but additionally requires synchronization among threads. */ + }; + //! Returns whether unsat may be tentative and/or requires synchronization. + [[nodiscard]] virtual int unsatType() const; + //! Returns whether this enumerator supports full restarts once a model was found. + [[nodiscard]] virtual bool supportsRestarts() const { return true; } + //! Returns whether this enumerator supports parallel search. + [[nodiscard]] virtual bool supportsParallel() const { return true; } + //! Returns whether this enumerator supports splitting-based search. + [[nodiscard]] virtual bool supportsSplitting(const SharedContext& problem) const; + //! Returns whether this enumerator requires exhaustive search to produce a definite answer. + [[nodiscard]] virtual bool exhaustive() const { return mini_ && mini_->mode() != MinimizeMode::enumerate; } + //! Sets whether the search path stored in s is disjoint from all others. + void setDisjoint(Solver& s, bool b) const; + //! Sets whether symmetric models should be ignored. + void setIgnoreSymmetric(bool b); + //! Clears the update state of the last model. + void clearUpdate(); - //! Removes from s the path that was passed to start() and any active (minimization) bound. - void end(Solver& s) const; - //! Returns the number of models enumerated so far. - uint64 enumerated() const { return model_.num; } - //! Returns the last model enumerated. - /*! - * \note If enumerated() is equal to 0, the returned object is in an indeterminate state. - */ - const Model& lastModel() const { return model_; } - //! Returns whether optimization is active. - bool optimize() const { return mini_ && mini_->mode() != MinimizeMode_t::enumerate && model_.opt == 0; } - //! Returns whether computed models are still tentative. - bool tentative() const { return mini_ && mini_->mode() == MinimizeMode_t::enumOpt && model_.opt == 0; } - //! Returns the active minimization constraint if any. - Minimizer minimizer() const { return mini_; } - //! Returns the type of models this enumerator computes. - virtual int modelType() const { return Model::Sat; } - enum UnsatType { - unsat_stop = 0u, /*!< First unsat stops search - commitUnsat() always return false. */ - unsat_cont = 1u, /*!< Unsat may be tentative - commitUnsat() may return true. */ - unsat_sync = 3u, /*!< Similar to unsat_cont but additionally requires synchronization among threads. */ - }; - //! Returns whether unsat may be tentative and/or requires synchronization. - virtual int unsatType() const; - //! Returns whether this enumerator supports full restarts once a model was found. - virtual bool supportsRestarts() const { return true; } - //! Returns whether this enumerator supports parallel search. - virtual bool supportsParallel() const { return true; } - //! Returns whether this enumerator supports splitting-based search. - virtual bool supportsSplitting(const SharedContext& problem) const; - //! Returns whether this enumerator requires exhaustive search to produce a definite answer. - virtual bool exhaustive() const { return mini_ && mini_->mode() != MinimizeMode_t::enumerate; } - //! Sets whether the search path stored in s is disjoint from all others. - void setDisjoint(Solver& s, bool b) const; - //! Sets whether symmetric should be ignored. - void setIgnoreSymmetric(bool b); - ConPtr constraint(const Solver& s) const; protected: - //! Shall prepare the enumerator and freeze any enumeration-related variable. - /*! - * \return A prototypical enumeration constraint to be used in a solver. - */ - virtual ConPtr doInit(SharedContext& ctx, SharedMinimizeData* min, int numModels) = 0; - virtual void doReset(); + //! Shall prepare the enumerator and freeze any enumeration-related variable. + /*! + * \return A prototypical enumeration constraint to be used in a solver. + */ + virtual ConPtr doInit(SharedContext& ctx, SharedMinimizeData* min, int numModels) = 0; + virtual void doReset(); + private: - class SharedQueue; - Enumerator(const Enumerator&); - Enumerator& operator=(const Enumerator&); - ConRef constraintRef(const Solver& s) const; - void setModel(Solver& s, bool up); - SharedMinimizeData* mini_; - SharedQueue* queue_; - ValueVec values_; - SumVec costs_; - LitVec sym_; - Model model_; + using QueuePtr = std::unique_ptr; + + SharedMinimizeData* mini_ = nullptr; + QueuePtr queue_; + ValueVec values_; + SumVec costs_; + LitVec sym_; + Model model_{}; }; //! A solver-local (i.e. thread-local) constraint to support enumeration. @@ -276,59 +299,60 @@ class Enumerator { */ class EnumerationConstraint : public Constraint { public: - typedef EnumerationConstraint* ConPtr; - typedef MinimizeConstraint* MinPtr; - typedef Enumerator::ThreadQueue* QueuePtr; - //! Returns true if search-path is disjoint from all others. - bool disjointPath()const { return disjoint_; } - ValueRep state() const { return state_; } - ValueRep resetMode() const { return upMode_; } - //! Returns true if optimization is active. - bool optimize() const; - MinPtr minimizer() const { return mini_; } - // Methods used by enumerator - void init(Solver& s, SharedMinimizeData* min, QueuePtr q); - bool start(Solver& s, const LitVec& path, bool disjoint); - void end(Solver& s); - bool update(Solver& s); - void setDisjoint(bool x); - bool integrateBound(Solver& s); - bool integrateNogoods(Solver& s); - bool commitModel(Enumerator& ctx, Solver& s); - bool commitUnsat(Enumerator& ctx, Solver& s); - void setMinimizer(MinPtr min) { mini_ = min; } - void add(Constraint* c); - void modelHeuristic(Solver& s); + using ConPtr = EnumerationConstraint*; + using MinPtr = MinimizeConstraint*; + using QueuePtr = Enumerator::SharedQueue*; + //! Returns true if search-path is disjoint from all others. + [[nodiscard]] bool disjointPath() const { return disjoint_; } + [[nodiscard]] Val_t state() const { return state_; } + //! Returns true if optimization is active. + [[nodiscard]] bool optimize() const; + [[nodiscard]] MinPtr minimizer() const { return mini_; } + // Methods used by enumerator + void init(Solver& s, SharedMinimizeData* min, QueuePtr q); + bool start(Solver& s, LitView path, bool disjoint); + void end(Solver& s); + bool update(Solver& s); + void setDisjoint(bool x); + bool integrateBound(Solver& s); + bool integrateNogoods(Solver& s); + bool commitModel(Enumerator& ctx, Solver& s); + bool commitUnsat(Enumerator& ctx, Solver& s); + void setMinimizer(MinPtr min) { mini_ = min; } + void add(Constraint* c); + void modelHeuristic(Solver& s); + bool extractModel(Solver& s, ValueVec& out); + protected: - EnumerationConstraint(); - virtual ~EnumerationConstraint(); - // base interface - virtual void destroy(Solver* s, bool detach); - virtual PropResult propagate(Solver&, Literal, uint32&) { return PropResult(true, true); } - virtual void reason(Solver&, Literal, LitVec&) {} - virtual bool simplify(Solver& s, bool reinit); - virtual bool valid(Solver& s); - virtual Constraint* cloneAttach(Solver& s); - // own interface - virtual ConPtr clone() = 0; - virtual bool doUpdate(Solver& s) = 0; - virtual void doCommitModel(Enumerator&, Solver&) {} - virtual void doCommitUnsat(Enumerator&, Solver&) {} - uint32 rootLevel() const { return root_; } + EnumerationConstraint(); + ~EnumerationConstraint() override; + // base interface + void destroy(Solver* s, bool detach) override; + PropResult propagate(Solver&, Literal, uint32_t&) override { return PropResult(true, true); } + void reason(Solver&, Literal, LitVec&) override {} + bool simplify(Solver& s, bool reinit) override; + bool valid(Solver& s) override; + Constraint* cloneAttach(Solver& s) override; + // own interface + virtual ConPtr clone() = 0; + virtual bool doUpdate(Solver& s) = 0; + virtual void doCommitModel(Enumerator&, Solver&) {} + virtual void doCommitUnsat(Enumerator&, Solver&) {} + virtual bool doExtractModel(Solver& s, ValueVec& out, bool sat); + [[nodiscard]] uint32_t rootLevel() const { return root_; } + private: - typedef PodVector::type ConstraintDB; - typedef SingleOwnerPtr QPtr; - MinimizeConstraint* mini_; - QPtr queue_; - ConstraintDB nogoods_; - LitVec next_; - uint32 root_; - ValueRep state_; - ValueRep upMode_; - ValueRep heuristic_; - bool disjoint_; + struct QueueReader; + using ConstraintDB = PodVector_t; + using QPtr = std::unique_ptr; + MinimizeConstraint* mini_ = nullptr; + QPtr queue_; + ConstraintDB nogoods_; + LitVec next_; + uint32_t root_{0}; + Val_t state_{value_free}; + uint8_t heuristic_{0}; + bool disjoint_{false}; }; //@} -} - -#endif +} // namespace Clasp diff --git a/clasp/heuristics.h b/clasp/heuristics.h index 89c1584..afc39ca 100644 --- a/clasp/heuristics.h +++ b/clasp/heuristics.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,25 +21,20 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_HEURISTICS_H_INCLUDED -#define CLASP_HEURISTICS_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif /*! * \file * \brief Defines various decision heuristics to be used in clasp. */ -#include #include +#include #include namespace Clasp { //! Computes a moms-like score for var v. -uint32 momsScore(const Solver& s, Var v); +uint32_t momsScore(const Solver& s, Var_t v); //! A variant of the BerkMin decision heuristic from the BerkMin Sat-Solver. /*! @@ -50,107 +45,108 @@ uint32 momsScore(const Solver& s, Var v); * This version of the BerkMin heuristic varies mostly in the following points from the original BerkMin: * -# it considers loop-nogoods if requested (this is the default) * -# it uses a MOMS-like heuristic as long as there are no conflicts (and therefore no activities) - * -# it uses a MOMS-like score to break ties whenever multiple variables from an unsatisfied learnt constraint have equal activities + * -# it uses a MOMS-like score to break ties whenever multiple variables from an unsatisfied learnt constraint have + * equal activities * -# it uses a lazy decaying scheme that only touches active variables */ class ClaspBerkmin : public DecisionHeuristic { public: - /*! - * \note Checks at most params.param candidates when searching for not yet - * satisfied learnt constraints. If param is 0, all candidates are checked. - */ - explicit ClaspBerkmin(const HeuParams& params = HeuParams()); - void setConfig(const HeuParams& params); - void startInit(const Solver& s); - void endInit(Solver& s); - void newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t); - void updateReason(const Solver& s, const LitVec& lits, Literal resolveLit); - bool bump(const Solver& s, const WeightLitVec& lits, double adj); - void undoUntil(const Solver&, LitVec::size_type); - void updateVar(const Solver& s, Var v, uint32 n); -protected: - Literal doSelect(Solver& s); + /*! + * \note Checks at most params.param candidates when searching for not yet + * satisfied learnt constraints. If param is 0, all candidates are checked. + */ + explicit ClaspBerkmin(const HeuParams& params = HeuParams()); + void setConfig(const HeuParams& params) override; + void startInit(const Solver& s) override; + void endInit(Solver& s) override; + void newConstraint(const Solver& s, LitView lits, ConstraintType t) override; + void updateReason(const Solver& s, LitView lits, Literal resolveLit) override; + bool bump(const Solver& s, WeightLitView lits, double adj) override; + void undo(const Solver&, LitView undo) override; + void updateVar(const Solver& s, Var_t v, uint32_t n) override; + Literal doSelect(Solver& s) override; + Literal selectRange(Solver& s, LitView range) override; + private: - Literal selectLiteral(Solver& s, Var v, bool vsids) const; - Literal selectRange(Solver& s, const Literal* first, const Literal* last); - bool initHuang() const { return order_.score[0].occ == 1; } - void initHuang(bool b) { order_.score[0].occ = b; } - bool hasActivities() const { return order_.score[0].act != 0; } - void hasActivities(bool b) { order_.score[0].act = b; } - Var getMostActiveFreeVar(const Solver& s); - Var getTopMoms(const Solver& s); - bool hasTopUnsat(Solver& s); - // Gathers heuristic information for one variable v. - struct HScore { - HScore(uint32 d = 0) : occ(0), act(0), dec(uint16(d)) {} - void incAct(uint32 gd, bool h, bool sign) { - occ += int(h) * (1 - (2*int(sign))); - decay(gd, h); - ++act; - } - void incOcc(bool sign) { occ += 1 - (2*int(sign)); } - uint32 decay(uint32 gd, bool h) { - if (uint32 x = (gd-dec)) { - // NOTE: shifts might overflow, i.e. - // activity is actually shifted by x%32. - // We deliberately ignore this "logical inaccuracy" - // and treat it as random noise ;) - act >>= x; - dec = uint16(gd); - occ /= (1<<(x*int(h))); - } - return act; - } - int32 occ; - uint16 act; - uint16 dec; - }; - typedef PodVector::type Scores; - typedef VarVec::iterator Pos; + [[nodiscard]] Literal selectLiteral(const Solver& s, Var_t v, bool vsids) const; + [[nodiscard]] bool initHuang() const { return order_.score[0].occ == 1; } + [[nodiscard]] bool hasActivities() const { return order_.score[0].act != 0; } - struct Order { - explicit Order() : decay(0), huang(false), resScore(3u) {} - struct Compare { - explicit Compare(Order* o) : self(o) {} - bool operator()(Var v1, Var v2) const { - return self->decayedScore(v1) > self->decayedScore(v2) - || (self->score[v1].act == self->score[v2].act && v1 < v2); - } - Order* self; - }; - uint32 decayedScore(Var v) { return score[v].decay(decay, huang); } - int32 occ(Var v) const { return score[v].occ; } - void inc(Literal p, bool inNant) { - if (!this->nant || inNant) { - score[p.var()].incAct(decay, huang, p.sign()); - } - } - void incOcc(Literal p) { score[p.var()].incOcc(p.sign()); } - int compare(Var v1, Var v2) { - return int(decayedScore(v1)) - int(decayedScore(v2)); - } - void resetDecay(); - Scores score; // For each var v score_[v] stores heuristic score of v - uint32 decay; // "global" decay counter. Increased every decP_ decisions - bool huang; // Use Huang's scoring scheme (see: Jinbo Huang: "A Case for Simple SAT Solvers") - bool nant; // only score vars v with varInfo(v).nant()? - uint8 resScore; - private: - Order(const Order&); - Order& operator=(const Order&); - } order_; - VarVec cache_; // Caches the most active variables - LitVec freeLits_; // Stores free variables of the last learnt conflict clause that is not sat - LitVec freeOtherLits_; // Stores free variables of the last other learnt nogood that is not sat - uint32 topConflict_; // Index into the array of learnt nogoods used when searching for conflict clauses that are not sat - uint32 topOther_; // Index into the array of learnt nogoods used when searching for other learnt nogoods that are not sat - Var front_; // First variable whose truth-value is not already known - reset on backtracking - Pos cacheFront_; // First unprocessed cache position - reset on backtracking - uint32 cacheSize_; // Cache at most cacheSize_ variables - uint32 numVsids_; // Number of consecutive vsids-based decisions - uint32 maxBerkmin_; // When searching for an open learnt constraint, check at most maxBerkmin_ candidates. - TypeSet types_; // When searching for an open learnt constraint, consider these types of nogoods. - RNG rng_; + void initHuang(bool b) { order_.score[0].occ = b; } + void hasActivities(bool b) { order_.score[0].act = b; } + Var_t getMostActiveFreeVar(const Solver& s); + Var_t getTopMoms(const Solver& s); + bool hasTopUnsat(const Solver& s); + // Gathers heuristic information for one variable v. + struct HScore { + explicit HScore(uint32_t d = 0) : dec(static_cast(d)) {} + void incAct(uint32_t gd, bool h, bool sign) { + occ += static_cast(h) * (1 - (2 * static_cast(sign))); + decay(gd, h); + ++act; + } + void incOcc(bool sign) { occ += 1 - (2 * static_cast(sign)); } + uint32_t decay(uint32_t gd, bool h) { + if (uint32_t x = (gd - dec)) { + // NOTE: shifts might overflow, i.e. + // activity is actually shifted by x%32. + // We deliberately ignore this "logical inaccuracy" + // and treat it as random noise ;) + act >>= x; + dec = static_cast(gd); + occ /= (1 << (x * static_cast(h))); + } + return act; + } + int32_t occ{0}; + uint16_t act{0}; + uint16_t dec; + }; + using Scores = PodVector_t; + using Pos = VarVec::iterator; + + struct Order { + Order() = default; + Order(const Order&) = delete; + Order& operator=(const Order&) = delete; + struct Compare { + explicit Compare(Order* o) : self(o) {} + bool operator()(Var_t v1, Var_t v2) const { + return self->decayedScore(v1) > self->decayedScore(v2) || + (self->score[v1].act == self->score[v2].act && v1 < v2); + } + Order* self; + }; + uint32_t decayedScore(Var_t v) { return score[v].decay(decay, huang); } + [[nodiscard]] int32_t occ(Var_t v) const { return score[v].occ; } + void inc(Literal p, bool inNant) { + if (not this->nant || inNant) { + score[p.var()].incAct(decay, huang, p.sign()); + } + } + void incOcc(Literal p) { score[p.var()].incOcc(p.sign()); } + int compare(Var_t v1, Var_t v2) { + return static_cast(decayedScore(v1)) - static_cast(decayedScore(v2)); + } + void resetDecay(); + Scores score; // For each var v score_[v] stores heuristic score of v + uint32_t decay{0}; // "global" decay counter. Increased every decP_ decisions + bool huang{false}; // Use Huang's scoring scheme (see: Jinbo Huang: "A Case for Simple SAT Solvers") + bool nant{false}; // only score vars v with varInfo(v).nant()? + uint8_t resScore{3u}; + } order_; + VarVec cache_; // Caches the most active variables + LitVec freeLits_; // Stores free variables of the last learnt conflict clause that is not sat + LitVec freeOtherLits_; // Stores free variables of the last other learnt nogood that is not sat + uint32_t topConflict_; // Index into learnt db used when searching for conflict clauses that are not sat + uint32_t topOther_; // Index into learnt db used when searching for other learnt nogoods that are not sat + Var_t front_; // First variable whose truth-value is not already known - reset on backtracking + Pos cacheFront_; // First unprocessed cache position - reset on backtracking + uint32_t cacheSize_; // Cache at most cacheSize_ variables + uint32_t numVsids_; // Number of consecutive vsids-based decisions + uint32_t maxBerkmin_; // When searching for an open learnt constraint, check at most maxBerkmin_ candidates. + TypeSet types_; // When searching for an open learnt constraint, consider these types of nogoods. + Rng rng_; }; //! Variable Move To Front decision strategies inspired by Siege. @@ -165,72 +161,69 @@ class ClaspBerkmin : public DecisionHeuristic { */ class ClaspVmtf : public DecisionHeuristic { public: - /*! - * \note Moves at most params.param literals from constraints used during - * conflict resolution to the front. If params.param is 0, the default is - * to move up to 8 literals. - */ - explicit ClaspVmtf(const HeuParams& params = HeuParams()); - void setConfig(const HeuParams& params); - void startInit(const Solver& s); - void endInit(Solver&); - void newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t); - void updateReason(const Solver& s, const LitVec& lits, Literal resolveLit); - bool bump(const Solver& s, const WeightLitVec& lits, double adj); - void simplify(const Solver&, LitVec::size_type); - void undoUntil(const Solver&, LitVec::size_type); - void updateVar(const Solver& s, Var v, uint32 n); -protected: - Literal doSelect(Solver& s); + /*! + * \note Moves at most params.param literals from constraints used during + * conflict resolution to the front. If params.param is 0, the default is + * to move up to 8 literals. + */ + explicit ClaspVmtf(const HeuParams& params = HeuParams()); + void setConfig(const HeuParams& params) override; + void startInit(const Solver& s) override; + void endInit(Solver&) override; + void newConstraint(const Solver& s, LitView lits, ConstraintType t) override; + void updateReason(const Solver& s, LitView lits, Literal resolveLit) override; + bool bump(const Solver& s, WeightLitView lits, double adj) override; + void simplify(const Solver&, LitView) override; + void undo(const Solver&, LitView undo) override; + void updateVar(const Solver& s, Var_t v, uint32_t n) override; + Literal doSelect(Solver& s) override; + Literal selectRange(Solver& s, LitView range) override; + private: - Literal selectRange(Solver& s, const Literal* first, const Literal* last); - void addToList(Var v); - void removeFromList(Var v); - void moveToFront(Var v); - Var getNext(Var v) const { return score_[v].next_; } - Var getFront() const { return score_[0].next_; } + [[nodiscard]] Var_t getNext(Var_t v) const { return score_[v].next; } + [[nodiscard]] Var_t getFront() const { return score_[0].next; } + + void addToList(Var_t v); + void removeFromList(Var_t v); + void moveToFront(Var_t v); - struct VarInfo { - VarInfo() : prev_(0), next_(0), activity_(0), occ_(0), decay_(0) { } - Var prev_; // link to prev node in intrusive linked list - Var next_; // link to next node in intrusive linked list - uint32 activity_; // activity of var - initially 0 - int32 occ_; // which literal of var occurred more often in learnt constraints? - uint32 decay_; // counter for lazy decaying activity - uint32& activity(uint32 globalDecay) { - if (uint32 x = (globalDecay - decay_)) { - activity_ >>= (x<<1); - decay_ = globalDecay; - } - return activity_; - } - bool inList() const { return prev_ != next_; } - }; - typedef PodVector::type Score; + struct VarInfo { + [[nodiscard]] bool inList() const { return prev != next; } + uint32_t& activity(uint32_t globalDecay) { + if (uint32_t x = globalDecay - decay; x) { + act >>= (x << 1); + decay = globalDecay; + } + return act; + } + Var_t prev{0}; // link to prev node in intrusive linked list + Var_t next{0}; // link to next node in intrusive linked list + uint32_t act{0}; // activity of var - initially 0 + int32_t occ{0}; // which literal of var occurred more often in learnt constraints? + uint32_t decay{0}; // counter for lazy decaying activity + }; + using Score = PodVector_t; - struct LessLevel { - LessLevel(const Solver& s, const Score& sc) : s_(s), sc_(sc) {} - bool operator()(Var v1, Var v2) const { - return s_.level(v1) < s_.level(v2) - || (s_.level(v1) == s_.level(v2) && sc_[v1].activity_ > sc_[v2].activity_); - } - bool operator()(Literal l1, Literal l2) const { - return (*this)(l1.var(), l2.var()); - } - private: - LessLevel& operator=(const LessLevel&); - const Solver& s_; - const Score& sc_; - }; - Score score_; // For each var v score_[v] stores heuristic score of v - VarVec mtf_; // Vars to be moved to the front of vars_ - Var front_; // Current front in var list - reset on backtracking - uint32 decay_; // "Global" decay counter. Increased every 512 decisions - uint32 nMove_; // Limit on number of vars to move - TypeSet types_; // Type of nogoods to score during resolution - uint32 scType_; // Type of scoring - uint32 nList_; // Num vars in vmtf-list - bool nant_; // only move vars v with varInfo(v).nant()? + struct LessLevel { + LessLevel(const Solver& s, const Score& sc) : s_(s), sc_(sc) {} + bool operator()(Var_t v1, Var_t v2) const { + return s_.level(v1) < s_.level(v2) || (s_.level(v1) == s_.level(v2) && sc_[v1].act > sc_[v2].act); + } + bool operator()(Literal l1, Literal l2) const { return (*this)(l1.var(), l2.var()); } + + private: + const Solver& s_; + const Score& sc_; + }; + Score score_; // For each var v score_[v] stores heuristic score of v + VarVec mtf_; // Vars to be moved to the front of vars_ + Var_t front_{0}; // Current front in var list - reset on backtracking + uint32_t decay_{0}; // "Global" decay counter. Increased every 512 decisions + uint32_t nMove_{8}; // Limit on number of vars to move + TypeSet types_; // Type of nogoods to score during resolution + uint32_t scType_{0}; // Type of scoring + uint32_t nList_{0}; // Num vars in vmtf-list + bool nant_{false}; // only move vars v with varInfo(v).nant()? }; //! Score type for VSIDS heuristic. @@ -238,14 +231,16 @@ class ClaspVmtf : public DecisionHeuristic { * \see ClaspVsids */ struct VsidsScore { - typedef VsidsScore SC; - VsidsScore(double sc = 0.0) : value(sc) {} - double get() const { return value; } - bool operator>(const SC& o) const { return value > o.value; } - void set(double f) { value = f; } - template - static double applyFactor(C&, Var, double f) { return f; } - double value; // activity + using Score = VsidsScore; + explicit VsidsScore(double sc = 0.0) : value(sc) {} + [[nodiscard]] double get() const { return value; } + bool operator>(const Score& o) const { return value > o.value; } + void set(double f) { value = f; } + template + static double applyFactor(C&, Var_t, double f) { + return f; + } + double value; // activity }; //! A variable state independent decision heuristic favoring variables that were active in recent conflicts. @@ -257,83 +252,86 @@ struct VsidsScore { * \note By default, the implementation uses the exponential VSIDS scheme from MiniSAT and * applies a MOMs-like score scheme to get an initial var order. */ -template -class ClaspVsids_t : public DecisionHeuristic { +template +class ClaspVsidsBase : public DecisionHeuristic { public: - /*! - * \note Uses params.param to init the decay value d and inc factor 1.0/d. - * If params.param is 0, d is set 0.95. Otherwise, d is set to 0.x, where x is params.param. - */ - explicit ClaspVsids_t(const HeuParams& params = HeuParams()); - virtual void setConfig(const HeuParams& params); - virtual void startInit(const Solver& s); - virtual void endInit(Solver&); - virtual void newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t); - virtual void updateReason(const Solver& s, const LitVec& lits, Literal resolveLit); - virtual bool bump(const Solver& s, const WeightLitVec& lits, double adj); - virtual void undoUntil(const Solver&, LitVec::size_type); - virtual void simplify(const Solver&, LitVec::size_type); - virtual void updateVar(const Solver& s, Var v, uint32 n); + /*! + * \note Uses params.param to init the decay value d and inc factor 1.0/d. + * If params.param is 0, d is set 0.95. Otherwise, d is set to 0.x, where x is params.param. + */ + explicit ClaspVsidsBase(const HeuParams& params = HeuParams()); + void setConfig(const HeuParams& params) override; + void startInit(const Solver& s) override; + void endInit(Solver&) override; + void newConstraint(const Solver& s, LitView lits, ConstraintType t) override; + void updateReason(const Solver& s, LitView lits, Literal resolveLit) override; + bool bump(const Solver& s, WeightLitView lits, double adj) override; + void undo(const Solver&, LitView undo) override; + void simplify(const Solver&, LitView) override; + void updateVar(const Solver& s, Var_t v, uint32_t n) override; + + Literal doSelect(Solver& s) override; + Literal selectRange(Solver& s, LitView range) override; + protected: - virtual Literal doSelect(Solver& s); - virtual Literal selectRange(Solver& s, const Literal* first, const Literal* last); - virtual void initScores(Solver& s, bool moms); - typedef typename PodVector::type ScoreVec; - typedef PodVector::type OccVec; - void updateVarActivity(const Solver& s, Var v, double f = 1.0); - void incOcc(Literal p) { occ_[p.var()] += 1 - (int(p.sign()) << 1); } - int occ(Var v) const { return occ_[v]; } - void normalize(); - struct CmpScore { - explicit CmpScore(const ScoreVec& s) : sc_(s) {} - bool operator()(Var v1, Var v2) const { return sc_[v1] > sc_[v2]; } - private: - CmpScore& operator=(const CmpScore&); - const ScoreVec& sc_; - }; - typedef bk_lib::indexed_priority_queue VarOrder; - struct Decay : Range{ - Decay(double x = 0.0, double y = 0.95, uint32 b = 0, uint32 f = 0) : Range(x, y), bump(b), freq(f), next(f) { - this->df = 1.0 / (freq && lo > 0.0 ? lo : hi); - } - double df; // active decay factor for evsids (>= 1.0) - uint32 bump; - uint32 freq : 16; - uint32 next : 16; - }; - ScoreVec score_; // vsids score for each variable - OccVec occ_; // occurrence balance of each variable - VarOrder vars_; // priority heap of variables - Decay decay_; // (dynamic) decaying strategy - double inc_; // var bump for evsids or conflict index for acids (increased on conflict) - TypeSet types_; // set of constraints to score - uint32 scType_;// score type (one of HeuParams::Score) - bool acids_; // whether to use acids instead if evsids scoring - bool nant_; // whether bumps are restricted to vars v with varInfo(v).nant() + using ScoreVec = PodVector_t; + using OccVec = PodVector_t; + virtual void initScores(Solver& s, bool moms); + + [[nodiscard]] int occ(Var_t v) const { return occ_[v]; } + + void updateVarActivity(const Solver& s, Var_t v, double f = 1.0); + void incOcc(Literal p) { occ_[p.var()] += 1 - (static_cast(p.sign()) << 1); } + void normalize(); + struct CmpScore { + explicit CmpScore(const ScoreVec& s) : sc_(s) {} + bool operator()(Var_t v1, Var_t v2) const { return sc_[v1] > sc_[v2]; } + + private: + const ScoreVec& sc_; + }; + using VarOrder = bk_lib::indexed_priority_queue; + struct DynDecay { + double curr{0.0}; + double stop{0.0}; + uint32_t bump{0}; + uint16_t freq{0}; + uint16_t next{0}; + }; + ScoreVec score_; // vsids score for each variable + OccVec occ_; // occurrence balance of each variable + VarOrder vars_; // priority heap of variables + DynDecay dyn_; // dynamic decaying strategy + double decay_{1.25}; // active decay factor for evsids (>= 1.0) + double inc_{1.0}; // var bump for evsids or conflict index for acids (increased on conflict) + TypeSet types_; // set of constraints to score + uint32_t scType_{0}; // score type (one of HeuParams::Score) + bool acids_{false}; // whether to use acids instead if evsids scoring + bool nant_{false}; // whether bumps are restricted to vars v with varInfo(v).nant() }; -typedef ClaspVsids_t ClaspVsids; +using ClaspVsids = ClaspVsidsBase; //! Score type for DomainHeuristic. /*! * \see DomainHeuristic */ struct DomScore : VsidsScore { - static const uint32 domMax = (1u << 30) - 1; - typedef DomScore SC; - DomScore(double v = 0.0) : VsidsScore(v), level(0), factor(1), domP(domMax), sign(0), init(0) {} - bool operator>(const SC& o) const { return (level > o.level) || (level == o.level && value > o.value); } - bool isDom() const { return domP != domMax; } - void setDom(uint32 key) { domP = key; } - template - static double applyFactor(C& sc, Var v, double f) { - int16 df = sc[v].factor; - return df == 1 ? f : static_cast(df) * f; - } - int16 level; // priority level - int16 factor; // factor used when bumping activity - uint32 domP : 30; // index into dom-table if dom var - uint32 sign : 1; // whether v has a sign modification - uint32 init : 1; // whether value is from init modification + static constexpr uint32_t dom_max = (1u << 30) - 1; + using Score = DomScore; + explicit DomScore(double v = 0.0) : VsidsScore(v) {} + bool operator>(const Score& o) const { return (level > o.level) || (level == o.level && value > o.value); } + [[nodiscard]] bool isDom() const { return domP != dom_max; } + void setDom(uint32_t key) { domP = key; } + template + static double applyFactor(C& sc, Var_t v, double f) { + int16_t df = sc[v].factor; + return df == 1 ? f : static_cast(df) * f; + } + int16_t level{0}; // priority level + int16_t factor{1}; // factor used when bumping activity + uint32_t domP : 30 {dom_max}; // index into dom-table if dom var + uint32_t sign : 1 {0}; // whether v has a sign modification + uint32_t init : 1 {0}; // whether value is from init modification }; //! A VSIDS heuristic supporting additional domain knowledge. @@ -342,67 +340,73 @@ struct DomScore : VsidsScore { * * \see M. Gebser, B. Kaufmann, R. Otero, J. Romero, T. Schaub, P. Wanko: * "Domain-specific Heuristics in Answer Set Programming", - * http://www.cs.uni-potsdam.de/wv/pdfformat/gekaotroscwa13a.pdf + * https://www.cs.uni-potsdam.de/wv/publications/DBLP_conf/aaai/GebserKROSW13.pdf */ -class DomainHeuristic : public ClaspVsids_t - , private Constraint { +class DomainHeuristic + : public ClaspVsidsBase + , private Constraint { public: - typedef ClaspVsids_t BaseType; - explicit DomainHeuristic(const HeuParams& params = HeuParams()); - ~DomainHeuristic(); - void setDefaultMod(HeuParams::DomMod mod, uint32 prefSet); - virtual void setConfig(const HeuParams& params); - virtual void startInit(const Solver& s); - const DomScore& score(Var v) const { return score_[v]; } + using BaseType = ClaspVsidsBase; + explicit DomainHeuristic(const HeuParams& params = HeuParams()); + ~DomainHeuristic() override; + void setDefaultMod(HeuParams::DomMod mod, uint32_t prefSet); + void setConfig(const HeuParams& params) override; + void startInit(const Solver& s) override; + [[nodiscard]] const DomScore& score(Var_t v) const { return score_[v]; } + protected: - // base interface - virtual Literal doSelect(Solver& s); - virtual void initScores(Solver& s, bool moms); - virtual void detach(Solver& s); - // Constraint interface - virtual Constraint* cloneAttach(Solver&) { return 0; } - virtual void reason(Solver&, Literal, LitVec&){} - virtual PropResult propagate(Solver&, Literal, uint32&); - virtual void undoLevel(Solver& s); + // base interface + Literal doSelect(Solver& s) override; + void initScores(Solver& s, bool moms) override; + void detach(Solver& s) override; + // Constraint interface + Constraint* cloneAttach(Solver&) override { return nullptr; } + void reason(Solver&, Literal, LitVec&) override {} + PropResult propagate(Solver&, Literal, uint32_t&) override; + void undoLevel(Solver& s) override; + private: - struct DomAction { - static const uint32 UNDO_NIL = (1u << 31) - 1; - uint32 var:30; // dom var to be modified - uint32 mod: 2; // modification to apply - uint32 undo:31;// next action in undo list - uint32 next: 1;// next action belongs to same condition? - int16 bias; // value to apply - uint16 prio; // prio of modification - }; - struct DomPrio { - void clear() { prio[0] = prio[1] = prio[2] = prio[3] = 0; } - uint16 operator[](unsigned i) const { return prio[i]; } - uint16& operator[](unsigned i) { return prio[i]; } - uint16 prio[4]; - }; - struct Frame { - Frame(uint32 lev, uint32 h) : dl(lev), head(h) {} - uint32 dl; - uint32 head; - }; - typedef PodVector::type ActionVec; - typedef PodVector::type PrioVec; - typedef PodVector::type FrameVec; - typedef DomainTable::ValueType DomMod; - typedef PodVector >::type VarScoreVec; + struct DomAction { + static constexpr uint32_t undo_nil = (1u << 31) - 1; + uint32_t var : 30; // dom var to be modified + uint32_t mod : 2; // modification to apply + uint32_t undo : 31; // next action in undo list + uint32_t next : 1; // next action belongs to same condition? + int16_t bias; // value to apply + uint16_t prio; // prio of modification + }; + struct DomPrio { + void clear() { prio[0] = prio[1] = prio[2] = prio[3] = 0; } + uint16_t operator[](unsigned i) const { return prio[i]; } + uint16_t& operator[](unsigned i) { return prio[i]; } + uint16_t prio[4]; + }; + struct Frame { + Frame(uint32_t lev, uint32_t h) : dl(lev), head(h) {} + uint32_t dl; + uint32_t head; + }; + struct VarScore { + Var_t var{}; + double score{}; + }; + using ActionVec = PodVector_t; + using PrioVec = PodVector_t; + using FrameVec = PodVector_t; + using DomMod = DomainTable::ValueType; + using VarScoreVec = PodVector_t; - uint32 addDomAction(const DomMod& e, Solver& s, VarScoreVec& outInit, Literal& lastW); - void addDefAction(Solver& s, Literal x, int16 lev, uint32 domKey); - void pushUndo(uint32& head, uint32 actionId); - void applyAction(Solver& s, DomAction& act, uint16& oldPrio); - uint16& prio(Var v, uint32 mod) { return prios_[score_[v].domP][mod]; } - PrioVec prios_; // priorities for domain vars - ActionVec actions_; // dynamic modifications - FrameVec frames_; // dynamic undo information - uint32 domSeen_; // offset into domain table - uint32 defMax_; // max var with default modification - uint16 defMod_; // default modifier - uint16 defPref_; // default preferences + uint32_t addDomAction(const DomMod& e, Solver& s, VarScoreVec& outInit, Literal& lastW); + void addDefAction(Solver& s, Literal x, int16_t lev, uint32_t domKey); + void pushUndo(uint32_t& head, uint32_t actionId); + void applyAction(Solver& s, DomAction& act, uint16_t& oldPrio); + uint16_t& prio(Var_t v, uint32_t mod) { return prios_[score_[v].domP][mod]; } + PrioVec prios_; // priorities for domain vars + ActionVec actions_; // dynamic modifications + FrameVec frames_; // dynamic undo information + uint32_t domSeen_; // offset into domain table + uint32_t defMax_; // max var with default modification + uint16_t defMod_; // default modifier + uint16_t defPref_; // default preferences }; -} -#endif +} // namespace Clasp diff --git a/clasp/literal.h b/clasp/literal.h index a514b78..364c67b 100644 --- a/clasp/literal.h +++ b/clasp/literal.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2024 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,16 +21,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_LITERAL_H_INCLUDED -#define CLASP_LITERAL_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include + #include -#include // std::swap -#include + +#include + +#include // std::swap + /*! * \file * \brief Types and functions related to literals and variables. @@ -41,29 +39,18 @@ namespace Clasp { */ //@{ -//! A variable is an integer in the range [0..varMax). -typedef uint32 Var; +//! A variable is an integer in the range [0;var_max). +using Var_t = uint32_t; -//! varMax is not a valid variable, i.e. currently Clasp supports at most 2^30 variables. -const Var varMax = (Var(1) << 30); +//! var_max is not a valid variable, i.e. currently Clasp supports at most 2^30 variables. +constexpr auto var_max = static_cast(1) << 30; //! The variable 0 has a special meaning in the solver. -const Var sentVar = 0; - -//! Literal ids are in the range [0..litIdMax). -const uint32 litIdMax = (Var(1) << 31); +constexpr auto sent_var = static_cast(0); //! Possible types of a variable. -struct Var_t { - enum Type { Atom = 1, Body = 2, Hybrid = Atom | Body}; - static bool isBody(Type t) { - return (static_cast(t) & static_cast(Body)) != 0; - } - static bool isAtom(Type t) { - return (static_cast(t) & static_cast(Atom)) != 0; - } -}; -typedef Var_t::Type VarType; +enum class VarType : uint32_t { atom = 1u, body = 2u, hybrid = atom | body }; +POTASSCO_ENABLE_CMP_OPS(VarType); //! A literal is a variable or its negation. /*! @@ -75,165 +62,192 @@ typedef Var_t::Type VarType; */ class Literal { public: - static const uint32 sign_bit = 2u; - static const uint32 flag_bit = 1u; - - //! The default constructor creates the positive literal of the special sentinel var. - Literal() : rep_(0) { } - - //! Creates a literal of the variable var with sign s. - /*! - * \param var The literal's variable. - * \param sign true if new literal should be negative. - * \pre var < varMax - */ - Literal(Var var, bool sign) : rep_( (var<> sign_bit; } - - //! Returns the sign of the literal. - /*! - * \return true if the literal is negative. Otherwise false. - */ - bool sign() const { return (rep_ & sign_bit) != 0; } - - //! Returns var and sign encoded in a unique id. - /*! - * \note The watch-flag is ignored and thus the id of a literal can be stored in 31-bits. - */ - uint32 id() const { return rep_ >> flag_bit; } - - //! Returns the stored representation of this literal. - uint32& rep() { return rep_; } - uint32 rep() const { return rep_; } - - //! Creates a literal from an id. - static Literal fromId(uint32 id) { - assert(id < litIdMax); - return Literal(id<(sign) << flag_mask)) { + assert(var < var_max); + } + + //! Returns the variable of the literal. + [[nodiscard]] constexpr Var_t var() const noexcept { return rep_ >> sign_mask; } + + //! Returns the sign of the literal. + /*! + * \return true if the literal is negative. Otherwise, false. + */ + [[nodiscard]] constexpr bool sign() const noexcept { return (rep_ & sign_mask) != 0; } + + //! Returns var and sign encoded in a unique id. + /*! + * \note The watch-flag is ignored and thus the id of a literal can be stored in 31-bits. + */ + [[nodiscard]] constexpr uint32_t id() const noexcept { return rep_ >> flag_mask; } + + //! Returns the stored representation of this literal. + constexpr uint32_t& rep() noexcept { return rep_; } + [[nodiscard]] constexpr uint32_t rep() const noexcept { return rep_; } + + //! Creates a literal from an id. + static constexpr Literal fromId(uint32_t id) { + assert(id <= id_max); + return Literal{id << flag_mask}; + } + //! Creates a literal from an unsigned integer. + static constexpr Literal fromRep(uint32_t rep) { return Literal{rep}; } + + constexpr void swap(Literal& other) noexcept { std::swap(rep_, other.rep_); } + + //! Sets the watched-flag of this literal. + constexpr Literal& flag() { + rep_ |= flag_mask; + return *this; + } + + //! Clears the watched-flag of this literal. + constexpr Literal& unflag() { + rep_ &= ~flag_mask; + return *this; + } + + //! Returns true if the watched-flag of this literal is set. + [[nodiscard]] constexpr bool flagged() const noexcept { return (rep_ & flag_mask) != 0; } + + //! Returns the complimentary literal of this literal. + /*! + * The complementary Literal of a Literal is a Literal referring to the + * same variable but with inverted sign. + */ + constexpr Literal operator~() const noexcept { return Literal{(rep_ & ~flag_mask) ^ sign_mask}; } + + friend constexpr auto operator==(Literal lhs, Literal rhs) { return lhs.id() == rhs.id(); } + friend constexpr auto operator<=>(Literal lhs, Literal rhs) { return lhs.id() <=> rhs.id(); } + private: - Literal(uint32 rep) : rep_(rep) {} - uint32 rep_; + static constexpr uint32_t sign_mask = 2u; + static constexpr uint32_t flag_mask = 1u; + static constexpr uint32_t id_max = (1u << 31) - 1; + constexpr explicit Literal(uint32_t rep) : rep_(rep) {} + uint32_t rep_; }; -//! Equality-Comparison for literals. -inline bool operator==(const Literal& lhs, const Literal& rhs) { - return lhs.id() == rhs.id(); -} -//! Defines a strict-weak-ordering for Literals. -inline bool operator<(const Literal& lhs, const Literal& rhs) { - return lhs.id() < rhs.id(); -} -//! Returns !(lhs == rhs). -inline bool operator!=(const Literal& lhs, const Literal& rhs) { - return !(lhs == rhs); -} -inline Literal operator^(Literal lhs, bool sign) { return Literal::fromId( lhs.id() ^ uint32(sign) ); } -inline Literal operator^(bool sign, Literal rhs) { return rhs ^ sign; } -inline void swap(Literal& l, Literal& r) { l.swap(r); } +constexpr Literal operator^(Literal lhs, bool sign) { return Literal::fromId(lhs.id() ^ static_cast(sign)); } +constexpr Literal operator^(bool sign, Literal rhs) { return rhs ^ sign; } +constexpr void swap(Literal& l, Literal& r) noexcept { l.swap(r); } //! Creates the negative literal of variable v. -inline Literal negLit(Var v) { return Literal(v, true); } +constexpr Literal negLit(Var_t v) { return {v, true}; } //! Creates the positive literal of variable v. -inline Literal posLit(Var v) { return Literal(v, false); } +constexpr Literal posLit(Var_t v) { return {v, false}; } //! Returns negLit(abs(p)) if p < 0 and posLit(p) otherwise. -inline Literal toLit(int32 p) { return p < 0 ? negLit(static_cast(-p)) : posLit(static_cast(p)); } +constexpr Literal toLit(int32_t p) { return p < 0 ? negLit(static_cast(-p)) : posLit(static_cast(p)); } //! Converts the given (non-sentinel) literal to a signed integer s.th. p == toLit(toInt(p)). -inline int32 toInt(Literal p) { return p.sign() ? -static_cast(p.var()) : static_cast(p.var()); } +constexpr int32_t toInt(Literal p) { return p.sign() ? -static_cast(p.var()) : static_cast(p.var()); } //! Always-true literal. -// TODO: replace with constant using constexpr ctor once we switch to C++11 -inline Literal lit_true() { return Literal(sentVar, false); } +constexpr auto lit_true = posLit(sent_var); //! Always-false literal. -// TODO: replace with constant using constexpr ctor once we switch to C++11 -inline Literal lit_false() { return Literal(sentVar, true); } +constexpr auto lit_false = negLit(sent_var); +static_assert(lit_true == ~lit_false); +static_assert(lit_true != lit_false); +static_assert(lit_true < lit_false); +static_assert(lit_false > lit_true); +static_assert(posLit(1) < negLit(1)); +static_assert(negLit(1) < posLit(2)); +static_assert((lit_true ^ true) == lit_false); +static_assert((lit_true ^ false) == lit_true); +static_assert(not(~Literal{12, false}.flag()).flagged()); //! Returns true if p represents the special variable 0 -inline bool isSentinel(Literal p) { return p.var() == sentVar; } - +constexpr bool isSentinel(Literal p) { return p.var() == sent_var; } +static_assert(isSentinel(lit_true) && isSentinel(lit_false)); // Low-level conversion between Literals and int literals. // We cannot use toInt() here because it is not defined for the -// sentinel literals lit_true() and lit_false(). -inline int32 encodeLit(Literal x) { return !x.sign() ? static_cast(x.var()+1) : -static_cast(x.var()+1); } -inline Var decodeVar(int32 x) { return static_cast(x >= 0 ? x : -x) - 1; } -inline Literal decodeLit(int32 x) { return Literal(decodeVar(x), x < 0); } - -inline unsigned hashId(unsigned key) { - key = ~key + (key << 15); - key ^= (key >> 11); - key += (key << 3); - key ^= (key >> 5); - key += (key << 10); - key ^= (key >> 16); - return key; +// sentinel literals lit_true and lit_false. +constexpr int32_t encodeLit(Literal x) { + return not x.sign() ? static_cast(x.var() + 1) : -static_cast(x.var() + 1); } -inline uint32 hashLit(Literal p) { return hashId(p.id()); } +constexpr Var_t decodeVar(int32_t x) { return static_cast(x >= 0 ? x : -x) - 1; } +constexpr Literal decodeLit(int32_t x) { return {decodeVar(x), x < 0}; } +static_assert(decodeLit(encodeLit(lit_true)) == lit_true); +static_assert(decodeLit(encodeLit(negLit(2))) == negLit(2)); +constexpr unsigned hashId(unsigned key) { + key = ~key + (key << 15); + key ^= (key >> 11); + key += (key << 3); + key ^= (key >> 5); + key += (key << 10); + key ^= (key >> 16); + return key; +} +constexpr uint32_t hashLit(Literal p) { return hashId(p.id()); } +static_assert(hashLit(lit_true) != hashLit(lit_false)); //! A signed integer type used to represent weights. -typedef int32 weight_t; +using Weight_t = int32_t; //! A signed integer type used to represent sums of weights. -typedef int64 wsum_t; -#define CLASP_WEIGHT_T_MAX ( 2147483647) -#define CLASP_WEIGHT_T_MIN (-2147483647 - 1) -#define CLASP_WEIGHT_SUM_MAX INT64_MAX -#define CLASP_WEIGHT_SUM_MIN INT64_MIN - -typedef PodVector::type VarVec; //!< A vector of variables. -typedef PodVector::type LitVec; //!< A vector of literals. -typedef PodVector::type WeightVec; //!< A vector of weights. -typedef PodVector::type SumVec; //!< A vector of sums of weights. -typedef std::pair WeightLiteral; //!< A literal associated with a weight. -typedef PodVector::type WeightLitVec; //!< A vector of weight-literals. +using Wsum_t = int64_t; + +constexpr Weight_t weight_min = INT32_MIN; +constexpr Weight_t weight_max = INT32_MAX; +constexpr Wsum_t weight_sum_min = INT64_MIN; +constexpr Wsum_t weight_sum_max = INT64_MAX; + +//!< A literal associated with a weight. +struct WeightLiteral { + Literal lit; + Weight_t weight = 1; + + friend constexpr bool operator==(WeightLiteral lhs, WeightLiteral rhs) = default; + friend constexpr auto operator<=>(WeightLiteral lhs, WeightLiteral rhs) = default; +}; +static_assert(std::is_trivially_copyable_v); +template +using SpanView = std::span; +using VarVec = PodVector_t; //!< A vector of variables. +using VarView = SpanView; //!< A read-only view of variables. +using LitVec = PodVector_t; //!< A vector of literals. +using LitView = SpanView; //!< A read-only view of literals. +using WeightVec = PodVector_t; //!< A vector of weights. +using WeightView = SpanView; //!< A read-only view of weights. +using SumVec = PodVector_t; //!< A vector of sums of weights. +using SumView = SpanView; //!< A read-only view of sums of weights. +using WeightLitVec = PodVector_t; //!< A vector of weight-literals. +using WeightLitView = SpanView; //!< A read-only view of weight-literals. /////////////////////////////////////////////////////////////////////////////// // Truth values /////////////////////////////////////////////////////////////////////////////// -typedef uint8 ValueRep; //!< Type of the three value-literals. -const ValueRep value_true = 1; //!< Value used for variables that are true. -const ValueRep value_false = 2; //!< Value used for variables that are false. -const ValueRep value_free = 0; //!< Value used for variables that are unassigned. -typedef PodVector::type ValueVec; +using Val_t = uint8_t; //!< Type of the three value-literals. +using ValueVec = PodVector_t; +using ValueView = SpanView; +constexpr Val_t value_free = 0; //!< Value used for variables that are unassigned. +constexpr Val_t value_true = 1; //!< Value used for variables that are true. +constexpr Val_t value_false = 2; //!< Value used for variables that are false. -//! Returns the value that makes the literal lit true. +//! Returns the value that makes the literal `lit` true. /*! * \param lit The literal for which the true-value should be determined. * \return - * - value_true iff lit is a positive literal + * - value_true iff lit is a positive literal, or * - value_false iff lit is a negative literal. * . */ -inline ValueRep trueValue(const Literal& lit) { return 1 + lit.sign(); } +constexpr Val_t trueValue(Literal lit) { return 1u + lit.sign(); } +static_assert(trueValue(lit_true) == value_true); +static_assert(trueValue(lit_false) == value_false); -//! Returns the value that makes the literal lit false. +//! Returns the value that makes the literal `lit` false. /*! * \param lit The literal for which the false-value should be determined. * \return - * - value_false iff lit is a positive literal + * - value_false iff lit is a positive literal, or * - value_true iff lit is a negative literal. * . */ -inline ValueRep falseValue(const Literal& lit) { return 1 + !lit.sign(); } +constexpr Val_t falseValue(Literal lit) { return 2u - lit.sign(); } +static_assert(falseValue(lit_true) == value_false); +static_assert(falseValue(lit_false) == value_true); //! Returns the sign that matches the value. /*! @@ -241,7 +255,16 @@ inline ValueRep falseValue(const Literal& lit) { return 1 + !lit.sign(); } * - false iff v == value_true * - true otherwise */ -inline bool valSign(ValueRep v) { return v != value_true; } -//@} +constexpr bool valSign(Val_t v) { return v != value_true; } + +template +constexpr Os& operator<<(Os& os, Literal lit) { + return os << toInt(lit); +} +template +constexpr Os& operator<<(Os& os, WeightLiteral lit) { + return os << "(" << lit.lit << ", " << lit.weight << ")"; } -#endif + +//@} +} // namespace Clasp diff --git a/clasp/logic_program.h b/clasp/logic_program.h index baf53ba..dd157a0 100644 --- a/clasp/logic_program.h +++ b/clasp/logic_program.h @@ -1,7 +1,7 @@ // // Copyright (c) 2013-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,18 +21,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_LOGIC_PROGRAM_H_INCLUDED -#define CLASP_LOGIC_PROGRAM_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif + #include #include #include +#include + +#include +#include -namespace Clasp { namespace Asp { +namespace Clasp::Asp { /*! * \file * \defgroup asp Asp @@ -43,86 +42,85 @@ namespace Clasp { namespace Asp { //! A struct for counting program rules and directives. struct RuleStats { - typedef uint32& Ref_t; - typedef uint32 const& CRef_t; - //! Rules and directives counted by this object. - enum Key { - Normal = Head_t::Disjunctive,//!< Normal or disjunctive rules. - Choice = Head_t::Choice, //!< Choice rules. - Minimize , //!< Distinct minimize constraints. - Acyc , //!< Edge directives. - Heuristic , //!< Heuristic directives. - Key__num - }; - //! Returns a string representation of the given key. - static const char* toStr(int k); - //! Returns the number of keys distinguished by this type. - static uint32 numKeys() { return Key__num; } - //! Updates the number of rules of the given type. - void up(Key k, int amount) { key[k] += static_cast(amount); } - //! Returns the number of rules of the given type. - Ref_t operator[](int k) { return key[k]; } - //! @copydoc operator[](int k) - CRef_t operator[](int k) const { return key[k]; } - //! Returns the sum of all rules. - uint32 sum() const; - uint32 key[Key__num]; //!< @private + using Ref_t = uint32_t&; + using CRef_t = uint32_t const&; + //! Rules and directives counted by this object. + enum Key { + normal = static_cast(HeadType::disjunctive), //!< Normal or disjunctive rules. + choice = static_cast(HeadType::choice), //!< Choice rules. + minimize, //!< Distinct minimize constraints. + acyc, //!< Edge directives. + heuristic, //!< Heuristic directives. + key_num + }; + //! Returns a string representation of the given key. + static const char* toStr(unsigned k); + //! Returns the number of keys distinguished by this type. + static uint32_t numKeys() { return key_num; } + //! Updates the number of rules of the given type. + void up(Key k, int amount) { key[k] += static_cast(amount); } + //! Returns the number of rules of the given type. + Ref_t operator[](unsigned k) { return key[k]; } + //! @copydoc Ref_t operator[](unsigned k) + CRef_t operator[](unsigned k) const { return key[k]; } + //! Returns the sum of all rules. + [[nodiscard]] uint32_t sum() const; + uint32_t key[key_num]; //!< @private }; //! A struct for counting distinct program bodies. struct BodyStats { - typedef uint32& Ref_t; - typedef uint32 const& CRef_t; - //! Body types distinguished by this object. - typedef Body_t Key; - //! Returns a string representation of the given key. - static const char* toStr(int k); - //! Returns the number of keys distinguished by this type. - static uint32 numKeys() { return Body_t::eMax + 1; } - //! Updates the number of bodies of the given type. - void up(Key k, int amount) { key[k] += static_cast(amount); } - //! Returns the number of bodies of the given type. - Ref_t operator[](int k) { return key[k]; } - //! @copydoc operator[](int k) - CRef_t operator[](int k) const { return key[k]; } - //! Returns the sum of all bodies. - uint32 sum() const; - uint32 key[Body_t::eMax + 1]; //!< @private + using Ref_t = uint32_t&; + using CRef_t = uint32_t const&; + //! Body types distinguished by this object. + using Key = BodyType; + //! Returns a string representation of the given key. + static const char* toStr(unsigned k); + //! Returns the number of keys distinguished by this type. + static uint32_t numKeys() { return Potassco::enum_count() + 1; } + //! Updates the number of bodies of the given type. + void up(Key k, int amount) { key[static_cast(k)] += static_cast(amount); } + //! Returns the number of bodies of the given type. + Ref_t operator[](unsigned k) { return key[k]; } + //! @copydoc Ref_t operator[](unsigned k) + CRef_t operator[](unsigned k) const { return key[k]; } + //! Returns the sum of all bodies. + [[nodiscard]] uint32_t sum() const; + uint32_t key[Potassco::enum_count() + 1]; //!< @private }; //! A type for maintaining a set of program statistics. class LpStats { public: - LpStats() { reset(); } - void reset(); - //! Returns the sum of all equivalences. - uint32 eqs() const { return eqs(Var_t::Atom) + eqs(Var_t::Body) + eqs(Var_t::Hybrid); } - //! Returns the number of equivalences of the given type. - uint32 eqs(VarType t) const { return eqs_[t-1]; } - //! Increments the number of equivalences of the given type. - void incEqs(VarType t) { ++eqs_[t-1]; } - //! Computes *this += o. - void accu(const LpStats& o); - RuleStats rules[2]; /**< RuleStats (initial, final). */ - BodyStats bodies[2]; /**< BodyStats (initial, final). */ - uint32 atoms; /**< Number of program atoms. */ - uint32 auxAtoms; /**< Number of aux atoms created. */ - uint32 disjunctions[2]; /**< Number of disjunctions (initial, non-hcf). */ - uint32 sccs; /**< How many strongly connected components? */ - uint32 nonHcfs; /**< How many non head-cycle free components?*/ - uint32 gammas; /**< How many non-hcf gamma rules. */ - uint32 ufsNodes; /**< How many nodes in the positive dependency graph? */ - // StatisticObject - static uint32 size(); - static const char* key(uint32 i); - StatisticObject at(const char* k) const; + constexpr LpStats() = default; + //! Returns the sum of all equivalences. + [[nodiscard]] uint32_t eqs() const { return eqs(VarType::atom) + eqs(VarType::body) + eqs(VarType::hybrid); } + //! Returns the number of equivalences of the given type. + [[nodiscard]] uint32_t eqs(VarType t) const { return eqs_[+t - 1]; } + //! Increments the number of equivalences of the given type. + void incEqs(VarType t) { ++eqs_[+t - 1]; } + //! Computes *this += o. + void accu(const LpStats& o); + // StatisticObject + static uint32_t size(); + static const char* key(uint32_t i); + StatisticObject at(const char* k) const; + + RuleStats rules[2]{}; /**< RuleStats (initial, final). */ + BodyStats bodies[2]{}; /**< BodyStats (initial, final). */ + uint32_t atoms{0}; /**< Number of program atoms. */ + uint32_t auxAtoms{0}; /**< Number of aux atoms created. */ + uint32_t disjunctions[2]{}; /**< Number of disjunctions (initial, non-hcf). */ + uint32_t sccs{0}; /**< How many strongly connected components? */ + uint32_t nonHcfs{0}; /**< How many non head-cycle free components?*/ + uint32_t gammas{0}; /**< How many non-hcf gamma rules. */ + uint32_t ufsNodes{0}; /**< How many nodes in the positive dependency graph? */ + private: - uint32 eqs_[3]; + uint32_t eqs_[3]{}; }; using Potassco::TheoryData; -struct MapLit_t { - POTASSCO_ENUM_CONSTANTS(MapLit_t, Raw = 0, Refined = 1); -}; +enum class MapLit { raw = 0, refined = 1 }; //! A class for defining a logic program. /*! @@ -131,552 +129,615 @@ struct MapLit_t { */ class LogicProgram : public ProgramBuilder { public: - LogicProgram(); - ~LogicProgram(); - //! Defines the possible modes for handling extended rules, i.e. choice, cardinality, and weight rules. - enum ExtendedRuleMode { - mode_native = 0, //!< Handle extended rules natively. - mode_transform = 1, //!< Transform extended rules to normal rules. - mode_transform_choice = 2, //!< Transform only choice rules to normal rules. - mode_transform_card = 3, //!< Transform only cardinality rules to normal rules. - mode_transform_weight = 4, //!< Transform cardinality- and weight rules to normal rules. - mode_transform_scc = 5, //!< Transform recursive cardinality- and weight rules to normal rules. - mode_transform_nhcf = 6, //!< Transform cardinality- and weight rules in non-hcf components to normal rules. - mode_transform_integ = 7, //!< Transform cardinality-based integrity constraints. - mode_transform_dynamic= 8 //!< Heuristically decide whether to transform a particular extended rule. - }; - - //! Options for the Asp-Preprocessor. - struct AspOptions { - static const uint32 MAX_EQ_ITERS = static_cast( (1u<<26)-1 ); - typedef ExtendedRuleMode TrMode; - AspOptions() { - static_assert(sizeof(*this) == sizeof(TrMode) + sizeof(uint32), "unexpected alignment"); - std::memset(this, 0, sizeof(AspOptions)); - iters = 5; - } - AspOptions& iterations(uint32 it) { iters = it <= MAX_EQ_ITERS ? it : MAX_EQ_ITERS; return *this;} - AspOptions& depthFirst() { dfOrder = 1; return *this;} - AspOptions& backpropagate() { backprop= 1; return *this;} - AspOptions& noScc() { noSCC = 1; return *this;} - AspOptions& noEq() { iters = 0; return *this;} - AspOptions& disableGamma() { noGamma = 1; return *this;} - AspOptions& ext(ExtendedRuleMode m) { erMode = m; return *this;} - TrMode erMode; //!< How to handle extended rules? - uint32 iters : 26;//!< Number of iterations in eq-preprocessing or 0 to disable. - uint32 noSCC : 1;//!< Disable scc checking? - uint32 suppMod : 1;//!< Disable scc checking and compute supported models. - uint32 dfOrder : 1;//!< Visit nodes in eq-preprocessing in depth-first order? - uint32 backprop : 1;//!< Enable backpropagation during preprocessing? - uint32 oldMap : 1;//!< Use old and larger mapping for disjunctive programs. - uint32 noGamma : 1;//!< Disable creation of (shifted) gamma rules for non-hcf disjunctions? - }; - - /*! - * \name Step control functions - */ - //@{ - - //! Starts the definition of a logic program. - LogicProgram& start(SharedContext& ctx, const AspOptions& opts = AspOptions()) { - startProgram(ctx); - setOptions(opts); - return *this; - } - //! Sets the mode for handling extended rules (default: mode_native). - void setExtendedRuleMode(ExtendedRuleMode m) { opts_.ext(m); } - //! Enable distinct true vars for incremental steps. - void enableDistinctTrue(); - //! Maintain atom output state. - /*! - * \see LogicProgram::getOutputState(Atom_t) const; - */ - void enableOutputState(); - //! Sets preprocessing options. - void setOptions(const AspOptions& opts); - //! Sets the configuration to be used for checker solvers in disjunctive LP solving. - void setNonHcfConfiguration(Configuration* c){ nonHcfs_.config = c; } - - //! Unfreezes a currently frozen program and starts an incremental step. - /*! - * If a program is to be defined incrementally, this function must be called - * exactly once for each step before any new rules or atoms are added. - * \note Program update only works correctly under the following assumptions: - * - Atoms introduced in step i are either: - * - solely defined in step i OR, - * - marked as frozen in step i and solely defined in step i+k OR, - * - forced to false by a compute statement in step 0. - * - * \pre The program is either frozen or at step 0. - * \post The program is no longer frozen and calling program mutating functions is valid again. - * \throws std::logic_error precondition is violated. - * \note The function is an alias for ProgramBuilder::updateProgram(). - */ - bool update() { return updateProgram(); } - - //! Finishes the definition of the logic program (or its current increment). - /*! - * Applies program mutating operations issued in the current step and transforms - * the new program into the solver's internal representation. - * - * \return false if the program is conflicting, true otherwise. - * - * \post - * - If true is returned, the program is considered to be "frozen" and calling - * program mutating functions is invalid until the next call to update(). - * - If false is returned, the state of the object is undefined and start() - * and dispose() are the only remaining valid operations. - * . - * \note The function is an alias for ProgramBuilder::endProgram(). - */ - bool end() { return endProgram(); } - - //! Visits the simplified program by notifying out on its elements. - void accept(Potassco::AbstractProgram& out); - - //! Disposes (parts of) the internal representation of the logic program. - /*! - * \param forceFullDispose If set to true, the whole program is disposed. Otherwise, - * only the rules (of the current step) are disposed but atoms and any incremental - * control data remain. - */ - void dispose(bool forceFullDispose); - - //! Clones the program and adds it to the given ctx. - /* - * \pre The program is currently frozen. - */ - bool clone(SharedContext& ctx); - - //@} - - /*! - * \name Program mutating functions - * - * Functions in this group shall only be called if the program is currently not - * frozen. That is, only between the call to start() (resp. update() if in - * incremental setting) and end(). A std::logic_error is raised if this precondition is violated. - * - */ - //@{ - - //! Adds a new atom to the program and returns the new atom's id. - Atom_t newAtom(); - - //! Sets atomId as the last input atom of the current step. - /*! - * All (new or existing) atoms with a larger id than atomId - * are considered to be auxiliary and automatically removed before - * a new incremental step is started. - * - * \pre atomId >= startAtom() - * \post startAuxAtom() == atomId + 1 - */ - void setMaxInputAtom(uint32 atomId); - - //! Adds a new conjunctive condition to the program. - /*! - * \param cond A (possibly empty) list of atom literals. - * \return The id of the new condition, which can be later passed to - * extractCondition() or getLiteral(). - */ - Id_t newCondition(const Potassco::LitSpan& cond); - - //! Adds the given string to the problem's output table. - /*! - * \param str The string to add. - * \param cond The condition under which str should be considered part of a model. - */ - LogicProgram& addOutput(const ConstString& str, const Potassco::LitSpan& cond); - LogicProgram& addOutput(const ConstString& str, Id_t cond); - - //! Adds the given atoms to the set of projection variables. - LogicProgram& addProject(const Potassco::AtomSpan& atoms); - //! Removes all previously added projection variables from the program. - LogicProgram& removeProject(); - - //! Protects an otherwise undefined atom from preprocessing. - /*! - * If the atom is defined in this or a previous step, the operation has no effect. - * \note If atomId is not yet known, an atom with the given id is implicitly created. - * \note The second parameter defines the assumption that shall hold during solving, i.e. - * - posLit(atomId), if value is value_true, - * - negLit(atomId), if value is value_false, or - * - no assumption, if value is value_free. - * - * \see ProgramBuilder::getAssumptions(LitVec&) const; - */ - LogicProgram& freeze(Atom_t atomId, ValueRep value = value_false); - - //! Removes any protection from the given atom. - /*! - * If the atom is defined in this or a previous step, the operation has no effect. - * \note - * - The effect is logically equivalent to adding a rule atomId :- false. - * - A call to unfreeze() always overwrites a call to freeze() even if the - * latter comes after the former - * . - */ - LogicProgram& unfreeze(Atom_t atomId); - - //! Adds the given rule (or integrity constraint) to the program. - /*! - * \pre The rule does not define an atom from a previous incremental step. - * - * Simplifies the given rule and adds it to the program if it - * is neither tautological (e.g. a :- a) nor contradictory (e.g. a :- b, not b). - * Atoms in the simplified rule that are not yet known are implicitly created. - * - * \throws std::logic_error if the precondition is violated. - * \note If the head of the simplified rule mentions an atom from a previous step, - * that atom shall either be frozen or false. In the former case, - * unfreeze() is implicitly called. In the latter case, the rule is interpreted - * as an integrity constraint. - */ - LogicProgram& addRule(const Rule& rule); - LogicProgram& addRule(Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body); - LogicProgram& addRule(Head_t ht, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& lits); - LogicProgram& addRule(Potassco::RuleBuilder& rb); - //! Adds the given minimize statement. - /*! - * \param prio The priority of the minimize statement. - * \param lits The literals to minimize. - * \note All minimize statements of the same priority are merged into one. - */ - LogicProgram& addMinimize(weight_t prio, const Potassco::WeightLitSpan& lits); - //! Removes all previously added minimize statements from the program. - LogicProgram& removeMinimize(); - - //! Adds an edge to the extended (user-defined) dependency graph. - LogicProgram& addAcycEdge(uint32 n1, uint32 n2, const Potassco::LitSpan& condition) { return addAcycEdge(n1, n2, newCondition(condition)); } - LogicProgram& addAcycEdge(uint32 n1, uint32 n2, Id_t cond); - - //! Adds a conditional domain heuristic directive to the program. - LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio, const Potassco::LitSpan& condition) { return addDomHeuristic(atom, t, bias, prio, newCondition(condition)); } - LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio, Id_t cond); - //! Adds an unconditional domain heuristic directive to the program. - LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio); - - //! Forces the given literals to be true during solving. - /*! - * Assumptions are retracted on the next program update. - */ - LogicProgram& addAssumption(const Potassco::LitSpan& cube); - - //! Adds or updates the given external atom. - /*! - * \see LogicProgram::freeze(Atom_t atomId, ValueRep value); - * \see LogicProgram::unfreeze(Atom_t atomId); - */ - LogicProgram& addExternal(Atom_t atomId, Potassco::Value_t value); - - //! Returns an object for adding theory data to this program. - TheoryData& theoryData(); - //@} - - /*! - * \name Query functions - * - * Functions in this group are useful to query important information - * once the program is frozen, i.e. after end() was called. - * They do not throw exceptions. - */ - //@{ - //! Returns whether the program can be represented in internal smodels format. - bool supportsSmodels() const; - //! Returns whether the program is to be defined incrementally. - bool isIncremental() const { return incData_ != 0; } - //! Returns whether the program contains any minimize statements. - bool hasMinimize() const { return !minimize_.empty(); } - //! Returns whether the program contains any theory data. - bool hasTheoryData() const { return theory_ != 0; } - //! Returns the number of atoms in the logic program. - uint32 numAtoms() const { return (uint32)atoms_.size()-1; } - //! Returns the number of bodies in the current (slice of the) logic program. - uint32 numBodies() const { return (uint32)bodies_.size(); } - //! Returns the number of disjunctive heads. - uint32 numDisjunctions() const { return (uint32)disjunctions_.size(); } - //! Returns the id of the first atom of the current step. - Atom_t startAtom() const { return input_.lo; } - //! Returns an id one past the last valid atom id in the program. - Atom_t endAtom() const { return numAtoms() + 1; } - //! Returns the id of the first atom that is not an input atom or endAtom() if no such atoms exists. - Atom_t startAuxAtom() const; - //! Returns whether a is an atom in the (simplified) program. - bool inProgram(Atom_t a) const; - //! Returns whether a is an external atom, i.e. is frozen in this step. - bool isExternal(Atom_t a) const; - //! Returns whether a occurs in the head of a rule. - bool isDefined(Atom_t a) const; - //! Returns whether a is a fact. - bool isFact(Atom_t a) const; - //! Returns the solver literal that is associated with the given atom or condition. - /*! - * \pre id is the id of a valid atom literal or was previously returned by newCondition(). - * \note Until end() is called, the function returns lit_false() for - * all atoms and conditions defined in the current step. - * \note For an atom literal x with Potassco::atom(x) == a, - * getLiteral(Potassco::id(x)) returns - * getLiteral(a), iff x == a, or - * ~getLiteral(a), iff x == -a. - * - * \note If mode is MapLit_t::Raw, the function simply returns the literal that - * was set during preprocessing. Otherwise, it also considers equivalences - * induced by domain heuristic directives and/or step-local true vars. - * - * \see enableDistinctTrue() - */ - Literal getLiteral(Id_t id, MapLit_t mode = MapLit_t::Raw) const; - //! Returns the atom literals belonging to the given condition. - /*! - * \pre cId was previously returned by newCondition() in the current step. - */ - bool extractCondition(Id_t cId, Potassco::LitVec& lits) const; - - enum OutputState { out_none = 0u, out_shown = 1u, out_projected = 2u, out_all = 3u }; - //! Returns the output state of the given atom or out_none if output state was not enabled. - /*! - * \note If @c mode is MapLit_t::Refined, the function also considers equivalences. - * \return Output state of the given atom, i.e. - * - out_none if atom is neither shown nor projected, - * - out_shown if atom is a shown atom (has an associated name), - * - out_projected if atom occurs in a projection statement, - * - out_all if atom is shown and occurs in a projection statement. - */ - OutputState getOutputState(Atom_t a, MapLit_t mode = MapLit_t::Raw) const; - //! Returns whether a is shown (i.e. has an associated name). - bool isShown(Atom_t a) const { return (getOutputState(a) & out_shown) != 0u; } - //! Returns whether a occurs in a projection statement. - bool isProjected(Atom_t a) const { return (getOutputState(a) & out_projected) != 0u; } - - //! Maps the given unsat core of solver literals to original program assumptions. - /*! - * \param unsatCore An unsat core found when solving under ProgramBuilder::getAssumptions(). - * \param prgLits The given unsat core expressed in terms of program literals. - * \return Whether unsatCore was successfully mapped. - */ - bool extractCore(const LitVec& unsatCore, Potassco::LitVec& prgLits) const; - - LpStats stats; //!< Statistics of the current step. - //@} - - /*! - * \name Implementation functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - */ - //@{ - typedef VarVec::const_iterator VarIter; - typedef PrgHead*const* HeadIter; - typedef std::pair EdgeRange; - typedef std::pair HeadRange; - struct SRule { - SRule() : hash(0), pos(0), bid(varMax) {} - uint32 hash; // hash value of the body - uint32 pos; // positive size of body - uint32 bid; // id of existing body or varMax - }; - const AspOptions& options() const { return opts_; } - bool hasConflict() const { return getTrueAtom()->literal() != lit_true(); } - bool ok() const { return !hasConflict() && ProgramBuilder::ok(); } - PrgAtom* getAtom(Id_t atomId)const { return atoms_[atomId]; } - PrgHead* getHead(PrgEdge it) const { return it.isAtom() ? (PrgHead*)getAtom(it.node()) : (PrgHead*)getDisj(it.node()); } - PrgNode* getSupp(PrgEdge it) const { return it.isBody() ? (PrgNode*)getBody(it.node()) : (PrgNode*)getDisj(it.node()); } - Id_t getRootId(Id_t atom)const { return getEqNode(atoms_, atom); } - PrgAtom* getRootAtom(Id_t a) const { return getAtom(getRootId(a)); } - PrgBody* getBody(Id_t bodyId)const { return bodies_[bodyId]; } - Id_t getEqBody(Id_t b) const { return getEqNode(bodies_, b); } - PrgDisj* getDisj(Id_t disjId)const { return disjunctions_[disjId]; } - HeadIter disj_begin() const { return disjunctions_.empty() ? 0 : reinterpret_cast(&disjunctions_[0]); } - HeadIter disj_end() const { return disj_begin() + numDisjunctions(); } - HeadIter atom_begin() const { return reinterpret_cast(&atoms_[0]); } - HeadIter atom_end() const { return atom_begin() + (numAtoms()+1); } - VarIter unfreeze_begin() const { return incData_?incData_->unfreeze.begin() : propQ_.end(); } - VarIter unfreeze_end() const { return incData_?incData_->unfreeze.end() : propQ_.end(); } - bool validAtom(Id_t aId) const { return aId < (uint32)atoms_.size(); } - bool validBody(Id_t bId) const { return bId < numBodies(); } - bool validDisj(Id_t dId) const { return dId < numDisjunctions(); } - bool isFact(PrgAtom* a) const; - const char*findName(Atom_t x) const; - bool simplifyRule(const Rule& r, Potassco::RuleBuilder& out, SRule& meta); - Atom_t falseAtom(); - VarVec& getSupportedBodies(bool sorted); - uint32 update(PrgBody* b, uint32 oldHash, uint32 newHash); - bool assignValue(PrgAtom* a, ValueRep v, PrgEdge reason); - bool assignValue(PrgHead* h, ValueRep v, PrgEdge reason); - bool propagate(bool backprop); - PrgAtom* mergeEqAtoms(PrgAtom* a, Id_t rootAtom); - PrgBody* mergeEqBodies(PrgBody* b, Id_t rootBody, bool hashEq, bool atomsAssigned); - bool propagate() { return propagate(options().backprop != 0); } - void setConflict() { getTrueAtom()->setLiteral(lit_false()); } - AtomState& atomState() { return atomState_; } - void addMinimize(); - void addOutputState(Atom_t atom, OutputState state); - // ------------------------------------------------------------------------ - // Statistics - void incTrAux(uint32 n) { stats.auxAtoms += n; } - void incEqs(VarType t) { stats.incEqs(t); } - void upStat(RuleStats::Key k, int n = 1){ stats.rules[statsId_].up(k, n); } - void upStat(Body_t k, int n = 1) { stats.bodies[statsId_].up(k, n); } - void upStat(Head_t k, int n = 1) { stats.rules[statsId_].up(static_cast(unsigned(k)), n); } - // ------------------------------------------------------------------------ - //@} + LogicProgram(); + ~LogicProgram() override; + //! Defines the possible modes for handling extended rules, i.e. choice, cardinality, and weight rules. + enum ExtendedRuleMode { + mode_native = 0, //!< Handle extended rules natively. + mode_transform = 1, //!< Transform extended rules to normal rules. + mode_transform_choice = 2, //!< Transform only choice rules to normal rules. + mode_transform_card = 3, //!< Transform only cardinality rules to normal rules. + mode_transform_weight = 4, //!< Transform cardinality- and weight rules to normal rules. + mode_transform_scc = 5, //!< Transform recursive cardinality- and weight rules to normal rules. + mode_transform_nhcf = 6, //!< Transform cardinality- and weight rules in non-hcf components to normal rules. + mode_transform_integ = 7, //!< Transform cardinality-based integrity constraints. + mode_transform_dynamic = 8 //!< Heuristically decide whether to transform a particular extended rule. + }; + + //! Options for the Asp-Preprocessor. + struct AspOptions { + static constexpr uint32_t max_eq_iters = (1u << 26) - 1; + using TrMode = ExtendedRuleMode; + constexpr AspOptions() = default; + constexpr AspOptions& iterations(uint32_t it) { + static_assert(sizeof(*this) == sizeof(TrMode) + sizeof(uint32_t), "unexpected alignment"); + iters = it <= max_eq_iters ? it : max_eq_iters; + return *this; + } + constexpr AspOptions& depthFirst() { + dfOrder = 1; + return *this; + } + constexpr AspOptions& backpropagate() { + backprop = 1; + return *this; + } + constexpr AspOptions& noScc() { + noSCC = 1; + return *this; + } + constexpr AspOptions& noEq() { + iters = 0; + return *this; + } + constexpr AspOptions& disableGamma() { + noGamma = 1; + return *this; + } + constexpr AspOptions& ext(ExtendedRuleMode m) { + erMode = m; + return *this; + } + TrMode erMode = mode_native; //!< How to handle extended rules? + uint32_t iters : 26 = 5; //!< Number of iterations in eq-preprocessing or 0 to disable. + uint32_t noSCC : 1 = 0; //!< Disable scc checking? + uint32_t suppMod : 1 = 0; //!< Disable scc checking and compute supported models. + uint32_t dfOrder : 1 = 0; //!< Visit nodes in eq-preprocessing in depth-first order? + uint32_t backprop : 1 = 0; //!< Enable backpropagation during preprocessing? + uint32_t oldMap : 1 = 0; //!< Use old and larger mapping for disjunctive programs. + uint32_t noGamma : 1 = 0; //!< Disable creation of (shifted) gamma rules for non-hcf disjunctions? + }; + + /*! + * \name Step control functions + */ + //@{ + + //! Starts the definition of a logic program. + LogicProgram& start(SharedContext& ctx, const AspOptions& opts) { + startProgram(ctx); + setOptions(opts); + return *this; + } + LogicProgram& start(SharedContext& ctx) { return start(ctx, {}); } + //! Sets the mode for handling extended rules (default: mode_native). + void setExtendedRuleMode(ExtendedRuleMode m) { opts_.ext(m); } + //! Enable distinct true vars for incremental steps. + void enableDistinctTrue(); + //! Maintain atom output state. + /*! + * \see LogicProgram::getOutputState(Atom_t) const; + */ + void enableOutputState(); + //! Sets preprocessing options. + void setOptions(const AspOptions& opts); + //! Sets the configuration to be used for checker solvers in disjunctive LP solving. + void setNonHcfConfiguration(Configuration* c) { nonHcfs_.config = c; } + + //! Unfreezes a currently frozen program and starts an incremental step. + /*! + * If a program is to be defined incrementally, this function must be called + * exactly once for each step before any new rules or atoms are added. + * \note Program update only works correctly under the following assumptions: + * - Atoms introduced in step 'i' are either: + * - solely defined in step i OR, + * - marked as frozen in step i and solely defined in step i+k OR, + * - forced to false by a compute statement in step 0. + * + * \pre The program is either frozen or at step 0. + * \post The program is no longer frozen and calling program mutating functions is valid again. + * \throws std::logic_error precondition is violated. + * \note The function is an alias for ProgramBuilder::updateProgram(). + */ + bool update() { return updateProgram(); } + + //! Finishes the definition of the logic program (or its current increment). + /*! + * Applies program mutating operations issued in the current step and transforms + * the new program into the solver's internal representation. + * + * \return false if the program is conflicting, true otherwise. + * + * \post + * - If true is returned, the program is considered to be "frozen" and calling + * program mutating functions is invalid until the next call to update(). + * - If false is returned, the state of the object is undefined and start() + * and dispose() are the only remaining valid operations. + * . + * \note The function is an alias for ProgramBuilder::endProgram(). + */ + bool end() { return endProgram(); } + + //! Visits the simplified program by notifying out on its elements. + void accept(Potassco::AbstractProgram& out); + + //! Disposes (parts of) the internal representation of the logic program. + /*! + * \param forceFullDispose If set to true, the whole program is disposed. Otherwise, + * only the rules (of the current step) are disposed but atoms and any incremental + * control data remain. + */ + void dispose(bool forceFullDispose); + + //! Clones the program and adds it to the given ctx. + /* + * \pre The program is currently frozen. + */ + bool clone(SharedContext& ctx); + + //@} + + /*! + * \name Program mutating functions. + * + * Functions in this group shall only be called if the program is currently not + * frozen. That is, only between the call to start() (resp. update() if in + * incremental setting) and end(). A std::logic_error is raised if this precondition is violated. + * + */ + //@{ + + //! Adds a new atom to the program and returns the new atom's id. + Atom_t newAtom(); + + //! Sets atomId as the last input atom of the current step. + /*! + * All (new or existing) atoms with a larger id than atomId + * are considered to be auxiliary and automatically removed before + * a new incremental step is started. + * + * \pre atomId >= startAtom() + * \post startAuxAtom() == atomId + 1 + */ + void setMaxInputAtom(uint32_t atomId); + + //! Adds a new conjunctive condition to the program. + /*! + * \param cond A (possibly empty) list of atom literals. + * \return The id of the new condition, which can be later passed to + * extractCondition() or getLiteral(). + */ + Id_t newCondition(Potassco::LitSpan cond); + + //! Adds the given string to the problem's output table. + /*! + * \param str The string to add. + * \param cond The condition under which str should be considered part of a model. + */ + LogicProgram& addOutput(const Potassco::ConstString& str, Potassco::LitSpan cond); + LogicProgram& addOutput(const Potassco::ConstString& str, Id_t cond); + LogicProgram& addOutput(const char* str, Id_t cond) { + return addOutput(Potassco::ConstString(str, Potassco::ConstString::create_shared), cond); + } + + //! Adds the given atoms to the set of projection variables. + LogicProgram& addProject(Potassco::AtomSpan atoms); + //! Removes all previously added projection variables from the program. + LogicProgram& removeProject(); + + //! Protects an otherwise undefined atom from preprocessing. + /*! + * If the atom is defined in this or a previous step, the operation has no effect. + * \note If atomId is not yet known, an atom with the given id is implicitly created. + * \note The second parameter defines the assumption that shall hold during solving, i.e. + * - posLit(atomId), if value is value_true, + * - negLit(atomId), if value is value_false, or + * - no assumption, if value is value_free. + * + * \see ProgramBuilder::getAssumptions(LitVec&) const; + */ + LogicProgram& freeze(Atom_t atomId, Val_t value = value_false); + + //! Removes any protection from the given atom. + /*! + * If the atom is defined in this or a previous step, the operation has no effect. + * \note + * - The effect is logically equivalent to adding a rule atomId :- false. + * - A call to unfreeze() always overwrites a call to freeze() even if the + * latter comes after the former + * . + */ + LogicProgram& unfreeze(Atom_t atomId); + + //! Adds the given rule (or integrity constraint) to the program. + /*! + * \pre The rule does not define an atom from a previous incremental step. + * + * Simplifies the given rule and adds it to the program if it + * is neither tautological (e.g. a :- a) nor contradictory (e.g. a :- b, not b). + * Atoms in the simplified rule that are not yet known are implicitly created. + * + * \throws std::logic_error if the precondition is violated. + * \note If the head of the simplified rule mentions an atom from a previous step, + * that atom shall either be frozen or false. In the former case, + * unfreeze() is implicitly called. In the latter case, the rule is interpreted + * as an integrity constraint. + */ + LogicProgram& addRule(const Rule& rule); + LogicProgram& addRule(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body) { + return addRule(Rule::normal(ht, head, body)); + } + LogicProgram& addRule(HeadType ht, Potassco::AtomSpan head, Potassco::Weight_t bound, WeightLitSpan lits) { + return addRule(Rule::sum(ht, head, bound, lits)); + } + LogicProgram& addRule(Potassco::RuleBuilder& rb); + //! Adds the given minimize statement. + /*! + * \param prio The priority of the minimize statement. + * \param lits The literals to minimize. + * \note All minimize statements of the same priority are merged into one. + */ + LogicProgram& addMinimize(Weight_t prio, WeightLitSpan lits); + //! Removes all previously added minimize statements from the program. + LogicProgram& removeMinimize(); + + //! Adds an edge to the extended (user-defined) dependency graph. + LogicProgram& addAcycEdge(uint32_t n1, uint32_t n2, Potassco::LitSpan condition) { + return addAcycEdge(n1, n2, newCondition(condition)); + } + LogicProgram& addAcycEdge(uint32_t n1, uint32_t n2, Id_t cond); + + //! Adds a conditional domain heuristic directive to the program. + LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio, Potassco::LitSpan condition) { + return addDomHeuristic(atom, t, bias, prio, newCondition(condition)); + } + LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio, Id_t cond); + //! Adds an unconditional domain heuristic directive to the program. + LogicProgram& addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio); + + //! Forces the given literals to be true during solving. + /*! + * Assumptions are retracted on the next program update. + */ + LogicProgram& addAssumption(Potassco::LitSpan cube); + + //! Adds or updates the given external atom. + /*! + * \see LogicProgram::freeze(Atom_t atomId, TriVal_t value); + * \see LogicProgram::unfreeze(Atom_t atomId); + */ + LogicProgram& addExternal(Atom_t atomId, Potassco::TruthValue value); + + //! Returns an object for adding theory data to this program. + TheoryData& theoryData(); + //@} + + /*! + * \name Query functions. + * + * Functions in this group are useful to query important information + * once the program is frozen, i.e. after end() was called. + * They do not throw exceptions. + */ + //@{ + //! Returns whether the program can be represented in internal smodels format. + [[nodiscard]] bool supportsSmodels(const char** errorOut = nullptr) const; + //! Returns whether the program is to be defined incrementally. + [[nodiscard]] bool isIncremental() const { return incData_ != nullptr; } + //! Returns whether the program contains any minimize statements. + [[nodiscard]] bool hasMinimize() const { return not minimize_.empty(); } + //! Returns whether the program contains any theory data. + [[nodiscard]] bool hasTheoryData() const { return theory_ != nullptr; } + //! Returns the number of atoms in the logic program. + [[nodiscard]] uint32_t numAtoms() const { return size32(atoms_) - 1; } + //! Returns the number of bodies in the current (slice of the) logic program. + [[nodiscard]] uint32_t numBodies() const { return size32(bodies_); } + //! Returns the number of disjunctive heads. + [[nodiscard]] uint32_t numDisjunctions() const { return size32(disjunctions_); } + //! Returns the id of the first atom of the current step. + [[nodiscard]] Atom_t startAtom() const { return input_.lo; } + //! Returns an id one past the last valid atom id in the program. + [[nodiscard]] Atom_t endAtom() const { return numAtoms() + 1; } + //! Returns the id of the first atom that is not an input atom or endAtom() if no such atoms exists. + [[nodiscard]] Atom_t startAuxAtom() const; + //! Returns whether 'a' is an atom in the (simplified) program. + [[nodiscard]] bool inProgram(Atom_t a) const; + //! Returns whether 'a' is an external atom, i.e. is frozen in this step. + [[nodiscard]] bool isExternal(Atom_t a) const; + //! Returns whether 'a' occurs in the head of a rule. + [[nodiscard]] bool isDefined(Atom_t a) const; + //! Returns whether 'a' is a fact. + [[nodiscard]] bool isFact(Atom_t a) const; + //! Returns the solver literal that is associated with the given atom or condition. + /*! + * \pre id is the id of a valid atom literal or was previously returned by newCondition(). + * \note Until end() is called, the function returns \c Clasp::lit_false for + * all atoms and conditions defined in the current step. + * \note For an atom literal 'x' with Potassco::atom(x) == 'a', + * getLiteral(Potassco::id(x)) returns + * - @c getLiteral(a), iff 'x == a', or + * - @c ~getLiteral(a), iff 'x == -a'. + * + * \note If @c mode is @c MapLit::raw, the function simply returns the literal that + * was set during preprocessing. Otherwise, it also considers equivalences + * induced by domain heuristic directives and/or step-local true vars. + * + * \see enableDistinctTrue() + */ + [[nodiscard]] Literal getLiteral(Id_t id, MapLit mode = MapLit::raw) const; + //! Returns the atom literals belonging to the given condition. + /*! + * \pre cId was previously returned by newCondition() in the current step. + */ + bool extractCondition(Id_t cId, Potassco::LitVec& cond) const; + + enum OutputState : uint32_t { out_none = 0u, out_shown = 1u, out_projected = 2u, out_all = 3u }; + //! Returns the output state of the given atom or out_none if output state was not enabled. + /*! + * \note If @c mode is MapLit::refined, the function also considers equivalences. + * \return Output state of the given atom, i.e. + * - out_none if atom is neither shown nor projected, + * - out_shown if atom is a shown atom (has an associated name), + * - out_projected if atom occurs in a projection statement, + * - out_all if atom is shown and occurs in a projection statement. + */ + [[nodiscard]] OutputState getOutputState(Atom_t a, MapLit mode = MapLit::raw) const; + //! Returns whether 'a' is shown (i.e. has an associated name). + [[nodiscard]] bool isShown(Atom_t a) const { return (getOutputState(a) & out_shown) != 0u; } + //! Returns whether 'a' occurs in a projection statement. + [[nodiscard]] bool isProjected(Atom_t a) const { return (getOutputState(a) & out_projected) != 0u; } + + //! Maps the given unsat core of solver literals to original program assumptions. + /*! + * \param unsatCore An unsat core found when solving under ProgramBuilder::getAssumptions(). + * \param prgLits The given unsat core expressed in terms of program literals. + * \return Whether unsatCore was successfully mapped. + */ + bool translateCore(LitView unsatCore, Potassco::LitVec& prgLits) const; + + LpStats stats; //!< Statistics of the current step. + //@} + + /*! + * \name Implementation functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + */ + //@{ + using AtomSpan = SpanView; + using DisjSpan = SpanView; + using BodySpan = SpanView; + using VarSpan = VarView; + struct SRule { + uint32_t hash{0}; // hash value of the body + uint32_t pos{0}; // positive size of body + uint32_t bid{var_max}; // id of existing body or var_max + }; + [[nodiscard]] auto options() const -> const AspOptions& { return opts_; } + [[nodiscard]] bool hasConflict() const { return getTrueAtom()->literal() != lit_true; } + [[nodiscard]] bool ok() const override { return not hasConflict() && ProgramBuilder::ok(); } + [[nodiscard]] auto getAtom(Id_t atomId) const -> PrgAtom* { return atoms_[atomId]; } + [[nodiscard]] auto getHead(PrgEdge it) const -> PrgHead* { + return it.isAtom() ? node_cast(getAtom(it.node())) : node_cast(getDisj(it.node())); + } + [[nodiscard]] auto getSupp(PrgEdge it) const -> PrgNode* { + return it.isBody() ? node_cast(getBody(it.node())) : node_cast(getDisj(it.node())); + } + [[nodiscard]] Id_t getRootId(Id_t atom) const { return getEqNode(atoms_, atom); } + [[nodiscard]] auto getRootAtom(Id_t a) const -> PrgAtom* { return getAtom(getRootId(a)); } + [[nodiscard]] auto getBody(Id_t bodyId) const -> PrgBody* { return bodies_[bodyId]; } + [[nodiscard]] Id_t getEqBody(Id_t b) const { return getEqNode(bodies_, b); } + [[nodiscard]] auto getDisj(Id_t disjId) const -> PrgDisj* { return disjunctions_[disjId]; } + [[nodiscard]] auto bodies() const -> BodySpan { return bodies_; } + [[nodiscard]] auto disjunctions() const -> DisjSpan { return disjunctions_; } + [[nodiscard]] auto atoms(uint32_t off = 1) const -> AtomSpan { return drop(atoms_, off); } + [[nodiscard]] auto stepAtoms() const -> AtomSpan { return drop(atoms_, startAtom()); } + [[nodiscard]] auto oldAtoms() const -> AtomSpan { return {atoms_.data() + 1u, startAtom() - 1u}; } + [[nodiscard]] auto unfreeze() const -> VarSpan { return incData_ ? VarSpan{incData_->unfreeze} : VarSpan{}; } + [[nodiscard]] bool validAtom(Id_t aId) const { return aId < size32(atoms_); } + [[nodiscard]] bool validBody(Id_t bId) const { return bId < numBodies(); } + [[nodiscard]] bool validDisj(Id_t dId) const { return dId < numDisjunctions(); } + [[nodiscard]] bool isFact(const PrgAtom* a) const; + [[nodiscard]] auto findName(Atom_t x) const -> const char*; + bool simplifyRule(const Rule& r, Potassco::RuleBuilder& out, SRule& meta); + Atom_t falseAtom(); + VarVec& getSupportedBodies(bool sorted); + uint32_t update(PrgBody* b, uint32_t oldHash, uint32_t newHash); + bool assignValue(PrgAtom* a, Val_t v, PrgEdge reason); + bool assignValue(PrgHead* h, Val_t v, PrgEdge reason); + bool propagate(bool backprop); + auto mergeEqAtoms(PrgAtom* a, Id_t rootAtom) -> PrgAtom*; + auto mergeEqBodies(PrgBody* b, Id_t rootBody, bool hashEq, bool atomsAssigned) -> PrgBody*; + bool propagate() { return propagate(options().backprop != 0); } + void setConflict() { getTrueAtom()->setLiteral(lit_false); } + auto atomState() -> AtomState& { return atomState_; } + void addMinimize(); + void addOutputState(Atom_t atom, OutputState state); + // ------------------------------------------------------------------------ + // Statistics + void incTrAux(uint32_t n) { stats.auxAtoms += n; } + void incEqs(VarType t) { stats.incEqs(t); } + void upStat(RuleStats::Key k, int n = 1) { stats.rules[statsId_].up(k, n); } + void upStat(BodyType k, int n = 1) { stats.bodies[statsId_].up(k, n); } + void upStat(HeadType k, int n = 1) { stats.rules[statsId_].up(static_cast(k), n); } + // ------------------------------------------------------------------------ + //@} private: - LogicProgram(const LogicProgram&); - LogicProgram& operator=(const LogicProgram&); - struct DlpTr; - struct AcycArc { Id_t cond; uint32 node[2]; }; - struct DomRule { uint32 atom : 29; uint32 type : 3; Id_t cond; int16 bias; uint16 prio; }; - struct Eq { Atom_t var; Literal lit; }; - struct TFilter { bool operator()(const Potassco::TheoryAtom& atom) const; LogicProgram* self; }; - struct Min { weight_t prio; Potassco::WLitVec lits; }; - struct CmpMin { bool operator()(const Min* m1, const Min* m2) const { return m1->prio < m2->prio; } }; - typedef Potassco::RuleBuilder RuleBuilder; - typedef std::pair ShowPair; - typedef PodVector::type ShowVec; - typedef PodVector::type DomRules; - typedef PodVector::type AcycRules; - typedef PodVector::type RuleList; - typedef PodVector::type MinList; - typedef PodVector::type SccMap; - typedef PodVector::type EqVec; - typedef Potassco::WLitVec LpWLitVec; - typedef Potassco::LitVec LpLitVec; - typedef Range AtomRange; - struct IndexData; - struct Aux; - // ------------------------------------------------------------------------ - // virtual overrides - bool doStartProgram(); - bool doUpdateProgram(); - bool doEndProgram(); - void doGetAssumptions(LitVec& out) const; - ProgramParser* doCreateParser(); - int doType() const { return Problem_t::Asp; } - // ------------------------------------------------------------------------ - // Program definition - bool isNew(Atom_t atomId) const { return atomId >= startAtom(); } - PrgAtom* resize(Atom_t atomId); - void pushFrozen(PrgAtom* atom, ValueRep v); - void addRule(const Rule& r, const SRule& meta); - void addFact(const Potassco::AtomSpan& head); - void addIntegrity(const Rule& b, const SRule& meta); - bool handleNatively(const Rule& r) const; - bool transformNoAux(const Rule& r) const; - void freezeTheory(); - void transformExtended(); - void transformIntegrity(uint32 nAtoms, uint32 maxAux); - PrgBody* getBodyFor(const Rule& r, const SRule& m, bool addDeps = true); - PrgBody* getTrueBody(); - PrgDisj* getDisjFor(const Potassco::AtomSpan& heads, uint32 headHash); - PrgBody* assignBodyFor(const Rule& r, const SRule& m, EdgeType x, bool strongSimp); - bool equalLits(const PrgBody& b, const WeightLitSpan& lits) const; - bool simplifyNormal(Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body, RuleBuilder& out, SRule& info); - bool simplifySum(Head_t ht, const Potassco::AtomSpan& head, const Potassco::Sum_t& body, RuleBuilder& out, SRule& info); - bool pushHead(Head_t ht, const Potassco::AtomSpan& head, weight_t slack, RuleBuilder& out); - ValueRep litVal(const PrgAtom* a, bool pos) const; - uint32 findBody(uint32 hash, Body_t type, uint32 size, weight_t bound = -1, Potassco::WeightLit_t* wlits = 0); - uint32 findEqBody(const PrgBody* b, uint32 hash); - uint32 removeBody(PrgBody* b, uint32 oldHash); - Literal getEqAtomLit(Literal lit, const BodyList& supports, Preprocessor& p, const SccMap& x); - bool positiveLoopSafe(PrgBody* b, PrgBody* root) const; - void prepareExternals(); - void updateFrozenAtoms(); - void mergeOutput(VarVec::iterator& hint, Atom_t atom, OutputState state); - template - Id_t getEqNode(C& vec, Id_t id) const { - if (!vec[id]->eq()) return id; - PrgNode* n = vec[id], *r; - Id_t root = n->id(); - for (r = vec[root]; r->eq(); r = vec[root]) { - // n == r and r == r' -> n == r' - assert(root != r->id()); - n->setEq(root = r->id()); - } - return root; - } - bool checkBody(const PrgBody& rhs, Body_t type, uint32 size, weight_t bound) const { - return (rhs.relevant() || (rhs.eq() && getBody(getEqBody(rhs.id()))->relevant())) - && rhs.type() == type && rhs.size() == size && rhs.bound() == bound; - } - // ------------------------------------------------------------------------ - // Nogood creation - void prepareProgram(bool checkSccs); - void prepareOutputTable(); - void finalizeDisjunctions(Preprocessor& p, uint32 numSccs); - void prepareComponents(); - bool addConstraints(); - void addAcycConstraint(); - void addDomRules(); - void freezeAssumptions(); - // ------------------------------------------------------------------------ - void deleteAtoms(uint32 start); - PrgAtom* getTrueAtom() const { return atoms_[0]; } - RuleBuilder rule_; // temporary: active rule - AtomState atomState_; // which atoms appear in the active rule? - IndexData* index_; // additional indices - BodyList bodies_; // all bodies - AtomList atoms_; // all atoms - DisjList disjunctions_;// all (head) disjunctions - MinList minimize_; // list of minimize rules - RuleList extended_; // extended rules to be translated - ShowVec show_; // shown atoms/conditions - VarVec initialSupp_; // bodies that are (initially) supported - VarVec propQ_; // assigned atoms - VarVec frozen_; // list of frozen atoms - LpLitVec assume_; // set of assumptions - NonHcfSet nonHcfs_; // set of non-hcf sccs - TheoryData* theory_; // optional map of theory data - AtomRange input_; // input atoms of current step - int statsId_; // which stats to update (0 or 1) - Aux* auxData_; // additional state for handling extended constructs - struct Incremental { - // first: last atom of step, second: true var - typedef std::pair StepTrue; - typedef PodVector::type TrueVec; - Incremental(); - uint32 startScc; // first valid scc number in this iteration - VarVec unfreeze; // list of atoms to unfreeze in this step - VarVec doms; // list of atom variables with domain modification - TrueVec steps; // map of steps to true var - }* incData_; // additional state for handling incrementally defined programs - AspOptions opts_; // preprocessing + struct DlpTr; + struct AcycArc { + Id_t cond; + uint32_t node[2]; + }; + struct DomRule { + uint32_t atom : 29; + uint32_t type : 3; + Id_t cond; + int16_t bias; + uint16_t prio; + }; + struct Eq { + Atom_t var{}; + Literal lit; + }; + using RuleBuilder = Potassco::RuleBuilder; + using ShowPair = std::pair; + using ShowVec = PodVector_t; + using DomRules = PodVector_t; + using AcycRules = PodVector_t; + using RuleList = PodVector_t; + using SccMap = PodVector_t; + using EqVec = PodVector_t; + using LpWLitVec = Potassco::WLitVec; + using LpLitVec = Potassco::LitVec; + using AtomRange = Range32; + struct IndexData; + struct Aux; + struct Incremental; + using IndexPtr = std::unique_ptr; + using AuxPtr = std::unique_ptr; + using TheoryPtr = std::unique_ptr; + using IncPtr = std::unique_ptr; + // ------------------------------------------------------------------------ + // virtual overrides + bool doStartProgram() override; + bool doUpdateProgram() override; + bool doEndProgram() override; + void doGetAssumptions(LitVec& out) const override; + ProgramParser* doCreateParser() override; + [[nodiscard]] int doType() const override { return static_cast(ProblemType::asp); } + // ------------------------------------------------------------------------ + // Program definition + [[nodiscard]] static bool transformNoAux(const Rule& r); + [[nodiscard]] static bool equalLits(const PrgBody& b, WeightLitSpan lits); + static Val_t litVal(const PrgAtom* a, bool pos); + static bool positiveLoopSafe(const PrgBody* b, const PrgBody* root); + + [[nodiscard]] bool isNew(Atom_t atomId) const { return atomId >= startAtom(); } + PrgAtom* resize(Atom_t atomId); + void pushFrozen(PrgAtom* atom, Val_t v); + void addRule(const Rule& r, const SRule& meta); + void addFact(Atom_t atomId); + void addIntegrity(const Rule& b, const SRule& meta); + [[nodiscard]] bool handleNatively(const Rule& r) const; + void freezeTheory(); + void transformExtended(); + void transformIntegrity(uint32_t nAtoms, uint32_t maxAux); + PrgBody* getBodyFor(const Rule& r, const SRule& m, bool addDeps = true); + PrgBody* getTrueBody(); + PrgDisj* getDisjFor(Potassco::AtomSpan head, uint32_t headHash); + PrgBody* assignBodyFor(const Rule& r, const SRule& m, EdgeType x, bool strongSimp); + bool simplifyNormal(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body, RuleBuilder& out, SRule& meta); + bool simplifySum(HeadType ht, Potassco::AtomSpan head, const Potassco::Sum& body, RuleBuilder& out, SRule& meta); + bool pushHead(HeadType ht, Potassco::AtomSpan head, Weight_t slack, RuleBuilder& out); + uint32_t findBody(uint32_t hash, BodyType type, uint32_t size, Weight_t bound, Potassco::WeightLit* wlits) const; + uint32_t findBody(uint32_t hash, uint32_t size) { + return findBody(hash, BodyType::normal, size, static_cast(size), nullptr); + } + uint32_t findBody(uint32_t hash, BodyType type, Weight_t bound, std::span wLits) { + return findBody(hash, type, size32(wLits), bound, wLits.data()); + } + uint32_t findEqBody(const PrgBody* b, uint32_t hash); + uint32_t removeBody(const PrgBody* b, uint32_t oldHash); + Literal getEqAtomLit(Literal lit, const BodyList& supports, Preprocessor& p, const SccMap& x); + void prepareExternals(); + void updateFrozenAtoms(); + void mergeOutput(VarVec::iterator& hint, Atom_t atom, OutputState state); + template + [[nodiscard]] Id_t getEqNode(C& vec, Id_t id) const { + if (not vec[id]->eq()) { + return id; + } + PrgNode* n = vec[id]; + Id_t root = n->id(); + for (PrgNode* r = vec[root]; r->eq(); r = vec[root]) { + // n == r and r == r' -> n == r' + assert(root != r->id()); + n->setEq(root = r->id()); + } + return root; + } + [[nodiscard]] bool checkBody(const PrgBody& rhs, BodyType type, uint32_t size, Weight_t bound) const { + return (rhs.relevant() || (rhs.eq() && getBody(getEqBody(rhs.id()))->relevant())) && rhs.type() == type && + rhs.size() == size && rhs.bound() == bound; + } + // ------------------------------------------------------------------------ + // Nogood creation + void prepareProgram(bool checkSccs); + void prepareOutputTable(); + void finalizeDisjunctions(Preprocessor& p, uint32_t numSccs); + void prepareComponents(); + bool addConstraints(); + void addAcycConstraint(); + void addDomRules(); + void freezeAssumptions(); + // ------------------------------------------------------------------------ + void deleteAtoms(uint32_t start); + [[nodiscard]] PrgAtom* getTrueAtom() const { return atoms_[0]; } + + RuleBuilder rule_; // temporary: active rule + AtomState atomState_; // which atoms appear in the active rule? + IndexPtr index_; // additional indices + BodyList bodies_; // all bodies + AtomList atoms_; // all atoms + DisjList disjunctions_; // all (head) disjunctions + RuleList minimize_; // list of minimize rules + RuleList extended_; // extended rules to be translated + ShowVec show_; // shown atoms/conditions + VarVec initialSupp_; // bodies that are (initially) supported + VarVec propQ_; // assigned atoms + VarVec frozen_; // list of frozen atoms + LpLitVec assume_; // set of assumptions + NonHcfSet nonHcfs_; // set of non-hcf sccs + TheoryPtr theory_; // optional map of theory data + AtomRange input_; // input atoms of current step + int statsId_; // which stats to update (0 or 1) + AuxPtr auxData_; // additional state for handling extended constructs + struct Incremental { + // first: last atom of step, second: true var + using StepTrue = std::pair; + using TrueVec = PodVector_t; + Incremental(); + uint32_t startScc; // first valid scc number in this iteration + VarVec unfreeze; // list of atoms to unfreeze in this step + VarVec doms; // list of atom variables with domain modification + TrueVec steps; // map of steps to true var + }; + IncPtr incData_; // additional state for handling incrementally defined programs + AspOptions opts_; // preprocessing }; +constexpr Id_t id(Potassco::Lit_t lit) { return static_cast(lit); } +constexpr Id_t id(Potassco::Atom_t a) { return static_cast(a); } //! Returns the internal solver literal that is associated with the given atom literal. /*! * \pre The prg is frozen and atomLit is a known atom in prg. */ inline Literal solverLiteral(const LogicProgram& prg, Potassco::Lit_t atomLit) { - POTASSCO_REQUIRE(prg.frozen() && prg.validAtom(Potassco::atom(atomLit))); - return prg.getLiteral(Potassco::id(atomLit)); + POTASSCO_CHECK_PRE(prg.frozen() && prg.validAtom(Potassco::atom(atomLit))); + return prg.getLiteral(id(atomLit)); } //! Adapts a LogicProgram object to the Potassco::AbstractProgram interface. -class LogicProgramAdapter : public Potassco::AbstractProgram { +class LogicProgramAdapter final : public Potassco::AbstractProgram { public: - LogicProgramAdapter(LogicProgram& prg); - void initProgram(bool inc); - void beginStep(); - void endStep(); - void rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body); - void rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& body); - void minimize(Potassco::Weight_t prio, const Potassco::WeightLitSpan& lits); - void project(const Potassco::AtomSpan& atoms); - void output(const Potassco::StringSpan& str, const Potassco::LitSpan& cond); - void external(Potassco::Atom_t a, Potassco::Value_t v); - void assume(const Potassco::LitSpan& lits); - void heuristic(Potassco::Atom_t a, Potassco::Heuristic_t t, int bias, unsigned prio, const Potassco::LitSpan& cond); - void acycEdge(int s, int t, const Potassco::LitSpan& cond); - void theoryTerm(Potassco::Id_t termId, int number); - void theoryTerm(Potassco::Id_t termId, const Potassco::StringSpan& name); - void theoryTerm(Potassco::Id_t termId, int cId, const Potassco::IdSpan& args); - void theoryElement(Potassco::Id_t elementId, const Potassco::IdSpan& terms, const Potassco::LitSpan& cond); - void theoryAtom(Potassco::Id_t atomOrZero, Potassco::Id_t termId, const Potassco::IdSpan& elements); - void theoryAtom(Potassco::Id_t atomOrZero, Potassco::Id_t termId, const Potassco::IdSpan& elements, Potassco::Id_t op, Potassco::Id_t rhs); -protected: - Asp::LogicProgram* lp_; - bool inc_; + struct Options { + bool removeMinimize = false; + }; + explicit LogicProgramAdapter(LogicProgram& prg, const Options& opts); + explicit LogicProgramAdapter(LogicProgram& prg); + void initProgram(bool inc) override; + void beginStep() override; + void endStep() override; + void rule(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body) override; + void rule(HeadType ht, Potassco::AtomSpan head, Potassco::Weight_t bound, WeightLitSpan body) override; + void minimize(Potassco::Weight_t prio, WeightLitSpan lits) override; + void project(Potassco::AtomSpan atoms) override; + void output(std::string_view str, Potassco::LitSpan cond) override; + void outputAtom(Atom_t, const Potassco::ConstString& n) override; + void external(Atom_t a, Potassco::TruthValue v) override; + void assume(Potassco::LitSpan lits) override; + void heuristic(Atom_t a, Potassco::DomModifier t, int bias, unsigned prio, Potassco::LitSpan cond) override; + void acycEdge(int s, int t, Potassco::LitSpan cond) override; + void theoryTerm(Id_t termId, int number) override; + void theoryTerm(Id_t termId, std::string_view name) override; + void theoryTerm(Id_t termId, int cId, Potassco::IdSpan args) override; + void theoryElement(Id_t elementId, Potassco::IdSpan terms, Potassco::LitSpan cond) override; + void theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements) override; + void theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements, Id_t op, Id_t rhs) override; + +private: + LogicProgram* lp_; + Options opts_; + bool inc_; }; //@} -} } // end namespace Asp -#endif +} // namespace Clasp::Asp diff --git a/clasp/logic_program_types.h b/clasp/logic_program_types.h index 9d740a4..b1aa18b 100644 --- a/clasp/logic_program_types.h +++ b/clasp/logic_program_types.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,34 +21,34 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_LOGIC_PROGRAM_TYPES_H_INCLUDED -#define CLASP_LOGIC_PROGRAM_TYPES_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif + /*! * \file * \brief Basic types for working with a logic program. */ #include #include + +#include #include -#include + +#include + namespace Potassco { - typedef Clasp::PodVector::type LitVec; - typedef Clasp::PodVector::type WLitVec; -} +using LitVec = Clasp::PodVector_t; +using WLitVec = Clasp::PodVector_t; +} // namespace Potassco namespace Clasp { class ClauseCreator; -using Potassco::idMax; +using Potassco::id_max; namespace Asp { -typedef PodVector::type AtomList; -typedef PodVector::type BodyList; -typedef PodVector::type DisjList; -const ValueRep value_weak_true = 3; /**< true but no proof */ +using AtomList = PodVector_t; +using BodyList = PodVector_t; +using DisjList = PodVector_t; using Potassco::Atom_t; using Potassco::Id_t; +constexpr auto value_weak_true = static_cast(3); /**< true but no proof */ /*! * \addtogroup asp */ @@ -60,83 +60,104 @@ using Potassco::Id_t; */ class PrgNode { public: - //! Supported node types. - enum Type { Atom = 0u, Body = 1u, Disj = 2u }; - static const uint32 noScc = (1u << 27)-1; - static const uint32 noNode = (1u << 28)-1; - static const uint32 noLit = 1; - //! Creates a new node that corresponds to a literal that is false. - explicit PrgNode(uint32 id, bool checkScc = true); - //! Is the node still relevant or removed() resp. eq()? - bool relevant() const { return eq_ == 0; } - //! Was the node removed? - bool removed() const { return eq_ != 0 && id_ == noNode; } - //! Ignore the node during scc checking? - bool ignoreScc()const { return noScc_ != 0; } - //! Returns true if this node is equivalent to some other node. - /*! - * If eq() is true, the node is no longer relevant and must not be used any further. - * The only sensible operation is to call id() in order to get the id of the node - * that is equivalent to this node. - */ - bool eq() const { return eq_ != 0 && id_ != noNode; } - bool seen() const { return seen_ != 0; } - //! Returns true if node has an associated variable in a solver. - bool hasVar() const { return litId_ != noLit; } - //! Returns the variable associated with this node or sentVar if no var is associated with this node. - Var var() const { return litId_ >> 1; } - //! Returns the literal associated with this node or a sentinel literal if no var is associated with this node. - Literal literal() const { return Literal::fromId(litId_); } - //! Returns the value currently assigned to this node. - ValueRep value() const { return val_; } - //! Returns the current id of this node. - uint32 id() const { return id_; } - //! Returns the literal that must be true in order to fulfill the truth-value of this node. - Literal trueLit() const { - return value() == value_free - ? lit_true() - : literal() ^ (value() == value_false); - } - - /*! - * \name implementation functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - */ - //@{ - void setLiteral(Literal x) { litId_ = x.id(); } - void clearLiteral(bool clVal){ litId_ = noLit; if (clVal) val_ = value_free; } - void setValue(ValueRep v) { val_ = v; } - void setEq(uint32 eqId) { id_ = eqId; eq_ = 1; seen_ = 1; } - void setIgnoreScc(bool b) { noScc_ = (uint32)b; } - void markRemoved() { if (!eq()) setEq(noNode); } - void setSeen(bool b) { seen_ = uint32(b); } - void resetId(uint32 id, bool seen) { - id_ = id; - eq_ = 0; - seen_ = (uint32)seen; - } - bool assignValueImpl(ValueRep v, bool noWeak) { - if (v == value_weak_true && noWeak) { v = value_true; } - if (value() == value_free || v == value() || (value() == value_weak_true && v == value_true)) { - setValue(v); - return true; - } - return v == value_weak_true && value() == value_true; - } - //@} + //! Supported node types. + enum Type : uint32_t { atom = 0u, body = 1u, disj = 2u }; + static constexpr uint32_t no_scc = (1u << 27) - 1; + static constexpr uint32_t no_node = (1u << 28) - 1; + static constexpr uint32_t no_lit = 1; + //! Creates a new node that corresponds to a literal that is false. + constexpr explicit PrgNode(uint32_t id, bool checkScc = true) + : litId_(no_lit) + , noScc_(not checkScc) + , id_(id) + , val_(value_free) + , eq_(0) + , seen_(0) { + static_assert(sizeof(PrgNode) == sizeof(uint64_t), "Unsupported Alignment"); + POTASSCO_CHECK(id < no_node, EOVERFLOW, "Id out of range"); + } + PrgNode(const PrgNode&) = delete; + PrgNode& operator=(const PrgNode&) = delete; + //! Is the node still relevant or removed() resp. eq()? + [[nodiscard]] constexpr bool relevant() const { return eq_ == 0; } + //! Was the node removed? + [[nodiscard]] constexpr bool removed() const { return eq_ != 0 && id_ == no_node; } + //! Ignore the node during scc checking? + [[nodiscard]] constexpr bool ignoreScc() const { return noScc_ != 0; } + //! Returns true if this node is equivalent to some other node. + /*! + * If eq() is true, the node is no longer relevant and must not be used any further. + * The only sensible operation is to call id() in order to get the id of the node + * that is equivalent to this node. + */ + [[nodiscard]] constexpr bool eq() const { return eq_ != 0 && id_ != no_node; } + [[nodiscard]] constexpr bool seen() const { return seen_ != 0; } + //! Returns true if node has an associated variable in a solver. + [[nodiscard]] constexpr bool hasVar() const { return litId_ != no_lit; } + //! Returns the variable associated with this node or sent_var if no var is associated with this node. + [[nodiscard]] constexpr Var_t var() const { return litId_ >> 1; } + //! Returns the literal associated with this node or a sentinel literal if no var is associated with this node. + [[nodiscard]] constexpr Literal literal() const { return Literal::fromId(litId_); } + //! Returns the value currently assigned to this node. + [[nodiscard]] constexpr Val_t value() const { return val_; } + //! Returns the current id of this node. + [[nodiscard]] constexpr uint32_t id() const { return id_; } + //! Returns the literal that must be true in order to fulfill the truth-value of this node. + [[nodiscard]] constexpr Literal trueLit() const { + return value() == value_free ? lit_true : literal() ^ (value() == value_false); + } + + /*! + * \name implementation functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + */ + //@{ + constexpr void setLiteral(Literal x) { litId_ = x.id(); } + constexpr void clearLiteral(bool clVal) { + litId_ = no_lit; + if (clVal) { + val_ = value_free; + } + } + constexpr void setValue(Val_t v) { val_ = v; } + constexpr void setEq(uint32_t eqId) { + id_ = eqId; + eq_ = 1; + seen_ = 1; + } + constexpr void setIgnoreScc(bool b) { noScc_ = static_cast(b); } + constexpr void markRemoved() { + if (not eq()) { + setEq(no_node); + } + } + constexpr void setSeen(bool b) { seen_ = static_cast(b); } + constexpr void resetId(uint32_t id, bool seen) { + id_ = id; + eq_ = 0; + seen_ = static_cast(seen); + } + constexpr bool assignValueImpl(Val_t v, bool noWeak) { + if (v == value_weak_true && noWeak) { + v = value_true; + } + if (value() == value_free || v == value() || (value() == value_weak_true && v == value_true)) { + setValue(v); + return true; + } + return v == value_weak_true && value() == value_true; + } + //@} protected: - uint32 litId_ : 31; // literal-id in solver - uint32 noScc_ : 1; // ignore during scc checks? - uint32 id_ : 28; // own id/eq-id/root-id/ufs-id - uint32 val_ : 2; // assigned value - uint32 eq_ : 1; // removed or eq to some other node? - uint32 seen_ : 1; // marked as seen? -private: - PrgNode(const PrgNode&); - PrgNode& operator=(const PrgNode&); + uint32_t litId_ : 31; // literal-id in solver + uint32_t noScc_ : 1; // ignore during scc checks? + uint32_t id_ : 28; // own id/eq-id/root-id/ufs-id + uint32_t val_ : 2; // assigned value + uint32_t eq_ : 1; // removed or eq to some other node? + uint32_t seen_ : 1; // marked as seen? }; -typedef PrgNode::Type NodeType; +using NodeType = PrgNode::Type; //! An edge of a program-dependency graph. /*! * Currently, clasp distinguishes four types of edges: @@ -149,125 +170,151 @@ typedef PrgNode::Type NodeType; * The head of a rule is either an atom or a disjunction. */ struct PrgEdge { - //! Type of edge. - enum Type { Normal = 0, Gamma = 1, Choice = 2, GammaChoice = 3 }; - static PrgEdge noEdge() { PrgEdge x; x.rep = UINT32_MAX; return x; } - - template - static PrgEdge newEdge(const NT& n, Type eType) { - // 28-bit node id, 2-bit node type, 2-bit edge type - PrgEdge x = { (n.id() << 4) | (static_cast(n.nodeType()) << 2) | eType }; - return x; - } - //! Returns the id of the adjacent node. - uint32 node() const { return rep >> 4; } - //! Returns the type of this edge. - Type type() const { return Type(rep & 3u); } - //! Returns the type of adjacent node. - NodeType nodeType() const { return NodeType((rep >> 2) & 3u); } - //! Returns true if edge has normal semantic (normal edge or gamma edge). - bool isNormal() const { return (rep & 2u) == 0; } - //! Returns true if edge has choice semantic. - bool isChoice() const { return (rep & 2u) != 0; } - //! Returns true if the edge is a gamma (aux normal) edge. - bool isGamma() const { return (rep & 1u) != 0; } - //! Returns true if the adjacent node is a body. - bool isBody() const { return nodeType() == PrgNode::Body; } - //! Returns true if the adjacent node is an atom. - bool isAtom() const { return nodeType() == PrgNode::Atom; } - //! Returns true if the adjacent node is a disjunction. - bool isDisj() const { return nodeType() == PrgNode::Disj; } - bool operator< (PrgEdge rhs) const { return rep < rhs.rep; } - bool operator==(PrgEdge rhs) const { return rep == rhs.rep; } - uint32 rep; + //! Type of edge. + enum Type : uint32_t { normal = 0, gamma = 1, choice = 2, gamma_choice = 3 }; + static constexpr PrgEdge noEdge() { return {UINT32_MAX}; } + + template + static constexpr PrgEdge newEdge(const NodeT& n, Type eType) { + // 28-bit node id, 2-bit node type, 2-bit edge type + return {(n.id() << 4) | (static_cast(n.nodeType()) << 2) | eType}; + } + //! Returns the id of the adjacent node. + [[nodiscard]] constexpr uint32_t node() const { return rep >> 4; } + //! Returns the type of this edge. + [[nodiscard]] constexpr Type type() const { return static_cast(rep & 3u); } + //! Returns the type of adjacent node. + [[nodiscard]] constexpr NodeType nodeType() const { return static_cast((rep >> 2) & 3u); } + //! Returns true if edge has normal semantic (normal edge or gamma edge). + [[nodiscard]] constexpr bool isNormal() const { return (rep & 2u) == 0; } + //! Returns true if edge has choice semantic. + [[nodiscard]] constexpr bool isChoice() const { return (rep & 2u) != 0; } + //! Returns true if the edge is a gamma (aux normal) edge. + [[nodiscard]] constexpr bool isGamma() const { return (rep & 1u) != 0; } + //! Returns true if the adjacent node is a body. + [[nodiscard]] constexpr bool isBody() const { return nodeType() == PrgNode::body; } + //! Returns true if the adjacent node is an atom. + [[nodiscard]] constexpr bool isAtom() const { return nodeType() == PrgNode::atom; } + //! Returns true if the adjacent node is a disjunction. + [[nodiscard]] constexpr bool isDisj() const { return nodeType() == PrgNode::disj; } + //! Returns true if edge is valid, i.e. is not the special "no edge". + explicit operator bool() const noexcept { return rep != UINT32_MAX; } + constexpr bool operator==(const PrgEdge& rhs) const = default; + constexpr auto operator<=>(const PrgEdge&) const = default; + + uint32_t rep; }; -typedef PrgEdge::Type EdgeType; -typedef const PrgEdge* EdgeIterator; -typedef bk_lib::pod_vector EdgeVec; -inline bool isChoice(EdgeType t) { return t >= PrgEdge::Choice; } +using EdgeType = PrgEdge::Type; +using EdgeVec = bk_lib::pod_vector; +using EdgeSpan = SpanView; +constexpr bool isChoice(EdgeType t) { return t >= PrgEdge::choice; } -using Potassco::Body_t; -using Potassco::Head_t; +using Potassco::BodyType; +using Potassco::HeadType; +using Potassco::Rule; using Potassco::WeightLitSpan; -typedef Potassco::Rule_t Rule; //! A class for translating extended rules to normal rules. class RuleTransform { public: - //! Interface that must be implemented to get the result of a transformation. - struct ProgramAdapter { - virtual Atom_t newAtom() = 0; - virtual void addRule(const Rule& r) = 0; - protected: ~ProgramAdapter() {} - }; - //! Supported transformation strategies. - enum Strategy { strategy_default, strategy_no_aux, strategy_allow_aux }; - RuleTransform(ProgramAdapter& prg); - RuleTransform(LogicProgram& prg); - ~RuleTransform(); - //! Transforms the given (extended) rule to a set of normal rules. - uint32 transform(const Rule& r, Strategy s = strategy_default); + //! Interface that must be implemented to get the result of a transformation. + struct ProgramAdapter { + virtual Atom_t newAtom() = 0; + virtual void addRule(const Rule& r) = 0; + + protected: + ~ProgramAdapter() = default; + }; + //! Supported transformation strategies. + enum Strategy { strategy_default, strategy_no_aux, strategy_allow_aux }; + explicit RuleTransform(ProgramAdapter& prg); + explicit RuleTransform(LogicProgram& prg); + ~RuleTransform(); + RuleTransform(RuleTransform&&) = delete; + //! Transforms the given (extended) rule to a set of normal rules. + uint32_t transform(const Rule& r, Strategy s = strategy_default); + private: - RuleTransform(const RuleTransform&); - RuleTransform& operator=(const RuleTransform&); - struct Impl; - Impl* impl_; + struct Impl; + std::unique_ptr impl_; }; //! A set of flags used during rule simplification. class AtomState { public: - static const uint8 pos_flag = 0x1u; //!< In positive body of active rule - static const uint8 neg_flag = 0x2u; //!< In negative body of active rule - static const uint8 head_flag = 0x4u; //!< In normal head of active rule - static const uint8 choice_flag = 0x8u; //!< In choice head of active rule - static const uint8 disj_flag = 0x10u;//!< In disjunctive head of active rule - static const uint8 rule_mask = 0x1Fu;//!< In active rule - static const uint8 fact_flag = 0x20u;//!< Atom is a fact (sticky) - static const uint8 false_flag = 0x40u;//!< Atom is false (sticky) - static const uint8 simp_mask = 0x7fu;//!< In active rule or assigned - static const uint8 dom_flag = 0x80u;//!< Var of atom is a dom var (sticky) - AtomState() {} - void swap(AtomState& o) { state_.swap(o.state_); } - //! Does t.node() appear in the head of the active rule? - bool inHead(PrgEdge t) const { return isSet(t.node(), headFlag(t)); } - bool inHead(Atom_t atom) const { return isSet(atom, head_flag); } - //! Does p appear in the body of the active rule? - bool inBody(Literal p) const { return isSet(p.var(), pos_flag+p.sign()); } - bool isSet(Var v, uint8 f) const { return v < state_.size() && (state_[v] & f) != 0; } - bool isFact(Var v) const { return isSet(v, fact_flag); } - //! Mark v as a head of the active rule. - void addToHead(Atom_t v) { set(v, head_flag); } - void addToHead(PrgEdge t) { set(t.node(), headFlag(t)); } - //! Mark p as a literal contained in the active rule. - void addToBody(Literal p) { set(p.var(), pos_flag+p.sign()); } - - void set(Var v, uint8 f) { grow(v); state_[v] |= f; } - void clear(Var v, uint8 f) { if (v < state_.size()) { state_[v] &= ~f; } } - void clearRule(Var v) { clear(v, rule_mask); } - void clearHead(PrgEdge t) { clear(t.node(), headFlag(t)); } - void clearBody(Literal p) { clear(p.var(), pos_flag+p.sign()); } - void resize(uint32 sz) { state_.resize(sz); } - - template - bool allMarked(IT first, IT last, uint8 f) const { - for (; first != last; ++first) { - if (!isSet(*first, f)) return false; - } - return true; - } - bool inBody(const Literal* first, const Literal* last) const { - bool all = true; - for (; first != last && (all = inBody(*first)) == true; ++first) { ; } - return all; - } + static constexpr uint8_t pos_flag = 0x1u; //!< In positive body of active rule + static constexpr uint8_t neg_flag = 0x2u; //!< In negative body of active rule + static constexpr uint8_t head_flag = 0x4u; //!< In normal head of active rule + static constexpr uint8_t choice_flag = 0x8u; //!< In choice head of active rule + static constexpr uint8_t disj_flag = 0x10u; //!< In disjunctive head of active rule + static constexpr uint8_t rule_mask = 0x1Fu; //!< In active rule + static constexpr uint8_t fact_flag = 0x20u; //!< Atom is a fact (sticky) + static constexpr uint8_t false_flag = 0x40u; //!< Atom is false (sticky) + static constexpr uint8_t simp_mask = 0x7fu; //!< In active rule or assigned + static constexpr uint8_t dom_flag = 0x80u; //!< Var of atom is a dom var (sticky) + AtomState() = default; + void swap(AtomState& o) noexcept { state_.swap(o.state_); } + //! Does t.node() appear in the head of the active rule? + [[nodiscard]] bool inHead(PrgEdge t) const { return isSet(t.node(), headFlag(t)); } + [[nodiscard]] bool inHead(Atom_t atom) const { return isSet(atom, head_flag); } + //! Does p appear in the body of the active rule? + [[nodiscard]] bool inBody(Literal p) const { return isSet(p.var(), pos_flag + p.sign()); } + [[nodiscard]] bool isSet(Var_t v, uint8_t f) const { return v < state_.size() && (state_[v] & f) != 0; } + [[nodiscard]] bool isFact(Var_t v) const { return isSet(v, fact_flag); } + //! Mark v as a head of the active rule. + void addToHead(Atom_t v) { set(v, head_flag); } + void addToHead(PrgEdge t) { set(t.node(), headFlag(t)); } + //! Mark p as a literal contained in the active rule. + void addToBody(Literal p) { set(p.var(), pos_flag + p.sign()); } + + void addToBody(LitView body) { + for (auto p : body) { addToBody(p); } + } + + void set(Var_t v, uint8_t f) { + grow(v); + state_[v] |= f; + } + void clear(Var_t v, uint8_t f) { + if (v < state_.size()) { + state_[v] &= ~f; + } + } + void clearRule(Var_t v) { clear(v, rule_mask); } + void clearHead(PrgEdge t) { clear(t.node(), headFlag(t)); } + void clearBody(Literal p) { clear(p.var(), pos_flag + p.sign()); } + void resize(uint32_t sz) { state_.resize(sz); } + + template + void clearRule(const R& r, const P& p) { + for (const auto& x : r) { this->clearRule(p(x)); } + } + template + void clearRule(const R& r) { + clearRule(r, [](auto x) { return Potassco::atom(x); }); + } + void clearBody(LitView body) { + for (auto p : body) { clearBody(p); } + } + + [[nodiscard]] constexpr bool allMarked(VarView atoms, uint8_t f) const { + return std::ranges::all_of(atoms, [&](Var_t v) { return isSet(v, f); }); + } + [[nodiscard]] constexpr bool inBody(LitView lits) const { + return std::ranges::all_of(lits, [this](Literal x) { return inBody(x); }); + } + private: - typedef PodVector::type StateVec; - void grow(Var v) { if (v >= state_.size()) { state_.resize(v+1); } } - uint8 headFlag(PrgEdge t) const { - return t.isAtom() ? (head_flag << uint8(t.isChoice())) : disj_flag; - } - StateVec state_; + using StateVec = PodVector_t; + void grow(Var_t v) { + if (v >= state_.size()) { + state_.resize(v + 1); + } + } + [[nodiscard]] static uint8_t headFlag(PrgEdge t) { + return t.isAtom() ? (head_flag << static_cast(t.isChoice())) : disj_flag; + } + StateVec state_; }; //! A head node of a program-dependency graph. @@ -277,56 +324,64 @@ class AtomState { */ class PrgHead : public PrgNode { public: - enum Simplify { no_simplify = 0, force_simplify = 1 }; - typedef EdgeIterator sup_iterator; - - //! Is the head part of the (simplified) program? - bool inUpper() const { return relevant() && upper_ != 0; } - //! Is this head an atom? - bool isAtom() const { return isAtom_ != 0; } - //! Number of supports (rules) for this head. - uint32 supports() const { return supports_.size(); } - sup_iterator supps_begin()const { return supports_.begin(); } - sup_iterator supps_end() const { return supports_.end(); } - //! External atom (or defined in a later incremental step)? - bool frozen() const { return freeze_ != uint32(freeze_no); } - //! If frozen(), value to assume during solving. - ValueRep freezeValue()const { return static_cast(freeze_ - uint32(freeze_ != 0)); } - //! If frozen(), literal to assume during solving. - Literal assumption() const { return freeze_ > uint32(freeze_free) ? literal() ^ (freeze_ == freeze_false) : lit_true(); } - //! Adds r as support edge for this node. - void addSupport(PrgEdge r){ addSupport(r, force_simplify); } - void addSupport(PrgEdge r, Simplify s); - //! Removes r from the head's list of supports. - void removeSupport(PrgEdge r); - void clearSupports(); - void clearSupports(EdgeVec& to) { to.swap(supports_); clearSupports(); } - //! Removes any superfluous/irrelevant supports. - bool simplifySupports(LogicProgram& prg, bool strong, uint32* numDiffSupps = 0); - //! Assigns the value v to this head. - bool assignValue(ValueRep v) { return assignValueImpl(v, ignoreScc() && !frozen()); } - /*! - * \name implementation functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - */ - //@{ - void setInUpper(bool b) { upper_ = (uint32)b; } - void markDirty() { dirty_ = 1; } - void assignVar(LogicProgram& prg, PrgEdge it, bool allowEq = true); - NodeType nodeType() const { return isAtom() ? PrgNode::Atom : PrgNode::Disj; } - //@} + enum Simplify { no_simplify = 0, force_simplify = 1 }; + + //! Is the head part of the (simplified) program? + [[nodiscard]] bool inUpper() const { return relevant() && upper_ != 0; } + //! Is this head an atom? + [[nodiscard]] bool isAtom() const { return isAtom_ != 0; } + //! Number of supports (rules) for this head. + [[nodiscard]] uint32_t numSupports() const { return size32(supports_); } + //! First support for this head or noEdge() if head has no support. + [[nodiscard]] PrgEdge support() const { return not supports_.empty() ? supports_[0] : PrgEdge::noEdge(); } + //! Possible supports for this head. + [[nodiscard]] EdgeSpan supports() const { return supports_; } + //! External atom (or defined in a later incremental step)? + [[nodiscard]] bool frozen() const { return freeze_ != static_cast(freeze_no); } + //! If frozen(), value to assume during solving. + [[nodiscard]] Val_t freezeValue() const { + return static_cast(freeze_ - static_cast(freeze_ != 0)); + } + //! If frozen(), literal to assume during solving. + [[nodiscard]] Literal assumption() const { + return freeze_ > static_cast(freeze_free) ? literal() ^ (freeze_ == freeze_false) : lit_true; + } + //! Adds r as support edge for this node. + void addSupport(PrgEdge r) { addSupport(r, force_simplify); } + void addSupport(PrgEdge r, Simplify s); + //! Removes r from the head's list of supports. + void removeSupport(PrgEdge r); + void clearSupports(); + void clearSupports(EdgeVec& to) { + to.swap(supports_); + clearSupports(); + } + //! Removes any superfluous/irrelevant supports. + bool simplifySupports(LogicProgram& prg, bool strong, uint32_t* numDiffSupps = nullptr); + //! Assigns the value v to this head. + bool assignValue(Val_t v) { return assignValueImpl(v, ignoreScc() && not frozen()); } + /*! + * \name implementation functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + */ + //@{ + void setInUpper(bool b) { upper_ = static_cast(b); } + void markDirty() { dirty_ = 1; } + void assignVar(LogicProgram& prg, PrgEdge it, bool allowEq = true); + [[nodiscard]] NodeType nodeType() const { return isAtom() ? PrgNode::atom : PrgNode::disj; } + //@} protected: - enum FreezeState { freeze_no = 0u, freeze_free = 1u, freeze_true = 2u, freeze_false = 3u }; - //! Creates a new node that corresponds to a literal that is false. - explicit PrgHead(uint32 id, NodeType t, uint32 data = 0, bool checkScc = true); - bool backpropagate(LogicProgram& prg, ValueRep val, bool bpFull); - EdgeVec supports_; // possible supports (body or disjunction) - uint32 data_ : 27; // number of atoms in disjunction or scc of atom - uint32 upper_ : 1; // in (simplified) program? - uint32 dirty_ : 1; // is list of supports dirty? - uint32 freeze_: 2; // incremental freeze state - uint32 isAtom_: 1; // is this head an atom? + enum FreezeState { freeze_no = 0u, freeze_free = 1u, freeze_true = 2u, freeze_false = 3u }; + //! Creates a new node that corresponds to a literal that is false. + explicit PrgHead(uint32_t id, NodeType t, uint32_t data = 0, bool checkScc = true); + bool backpropagate(LogicProgram& prg, Val_t val, bool bpFull); + EdgeVec supports_; // possible supports (body or disjunction) + uint32_t data_ : 27; // number of atoms in disjunction or scc of atom + uint32_t upper_ : 1; // in (simplified) program? + uint32_t dirty_ : 1; // is list of supports dirty? + uint32_t freeze_ : 2; // incremental freeze state + uint32_t isAtom_ : 1; // is this head an atom? }; //! An atom in a logic program. @@ -338,307 +393,337 @@ class PrgHead : public PrgNode { */ class PrgAtom : public PrgHead { public: - enum Dependency { dep_pos = 0, dep_neg = 1, dep_all = 2 }; - typedef LitVec::const_iterator dep_iterator; - explicit PrgAtom(uint32 id, bool checkScc = true); - NodeType nodeType() const { return PrgNode::Atom; } - //! Strongly connected component of this node. - uint32 scc() const { return data_; } - //! If eq(), stores the literal that is eq to this atom. - Literal eqGoal(bool sign) const; - //! Returns true if atom belongs to a disjunctive head. - bool inDisj() const; - /*! - * \name forward dependencies (bodies containing this atom) - */ - //@{ - dep_iterator deps_begin() const { return deps_.begin(); } - dep_iterator deps_end() const { return deps_.end(); } - bool hasDep(Dependency d) const; - void addDep(Id_t bodyId, bool pos); - void removeDep(Id_t bodyId, bool pos); - void clearDeps(Dependency d); - //@} - - /*! - * \name implementation functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - */ - //@{ - void setEqGoal(Literal x); - bool propagateValue(LogicProgram& prg, bool backprop); - bool addConstraints(const LogicProgram& prg, ClauseCreator& c); - void setScc(uint32 scc) { data_ = scc; } - void markFrozen(ValueRep v){ freeze_ = v + freeze_free; } - void clearFrozen() { freeze_ = freeze_no; markDirty(); } - //@} + enum Dependency { dep_pos = 0, dep_neg = 1, dep_all = 2 }; + using DepSpan = LitView; + + explicit PrgAtom(uint32_t id, bool checkScc = true); + [[nodiscard]] static constexpr NodeType nodeType() { return PrgNode::atom; } + //! Strongly connected component of this node. + [[nodiscard]] uint32_t scc() const { return data_; } + //! If eq(), stores the literal that is eq to this atom. + [[nodiscard]] Literal eqGoal(bool sign) const; + //! Returns true if atom belongs to a disjunctive head. + [[nodiscard]] bool inDisj() const; + /*! + * \name forward dependencies (bodies containing this atom) + */ + //@{ + [[nodiscard]] DepSpan deps() const { return deps_; } + [[nodiscard]] bool hasDep(Dependency d) const; + void addDep(Id_t bodyId, bool pos); + void removeDep(Id_t bodyId, bool pos); + void clearDeps(Dependency d); + //@} + + /*! + * \name implementation functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + */ + //@{ + void setEqGoal(Literal x); + bool propagateValue(LogicProgram& prg, bool backprop); + bool addConstraints(const LogicProgram& prg, ClauseCreator& c); + void setScc(uint32_t scc) { data_ = scc; } + void markFrozen(Val_t v) { freeze_ = v + freeze_free; } + void clearFrozen() { + freeze_ = freeze_no; + markDirty(); + } + //@} private: - LitVec deps_; // bodies depending on this atom + LitVec deps_; // bodies depending on this atom }; //! A (rule) body in a logic program. class PrgBody : public PrgNode { public: - typedef EdgeIterator head_iterator; - typedef const Literal* goal_iterator; - - //! Creates a new body node and (optionally) connects it to its predecessors (i.e. atoms). - /*! - * \param prg The program in which the new body is used. - * \param id The id for the new body node. - * \param rule The rule for which a body node is to be created. - * \param pos Positive body size. - * \param addDeps If true, add an edge between each atom subgoal and the new node. - */ - static PrgBody* create(LogicProgram& prg, uint32 id, const Rule& rule, uint32 pos, bool addDeps); - //! Destroys a body node created via create(). - void destroy(); - Body_t type() const { return Body_t(static_cast(type_)); } - //! Returns the number of atoms in the body. - uint32 size() const { return size_; } - bool noScc() const { return size() == 0 || goal(0).sign(); } - //! Returns the bound of this body, or size() if the body is a normal body. - weight_t bound() const { if (type() == Body_t::Normal) return (weight_t)size(); else return hasWeights() ? sumData()->bound : aggData().bound; } - //! Returns the sum of the subgoals weights, or size() if the body is not a sum with weights. - weight_t sumW() const { return static_cast(!hasWeights() ? (weight_t)size() : sumData()->sumW); } - //! Returns the idx'th subgoal as a literal. - Literal goal(uint32 idx) const { assert(idx < size()); return *(goals_begin()+idx); } - //! Returns the weight of the idx'th subgoal. - weight_t weight(uint32 idx)const { assert(idx < size()); return !hasWeights() ? 1 : sumData()->weights[idx]; } - //! Returns true if the body node is supported. - /*! - * A normal body is supported, iff all of its positive subgoals are supported. - * A count/sum body is supported if the sum of the weights of the supported positive + - * the sum of the negative weights is >= lowerBound(). - */ - bool isSupported() const { return unsupp_ <= 0; } - //! Returns true if this body defines any head. - bool hasHeads() const { return isSmallHead() ? head_ != 0 : !largeHead()->empty(); } - bool inRule() const { return hasHeads() || freeze_; } - - head_iterator heads_begin() const { return isSmallHead() ? smallHead() : largeHead()->begin(); } - head_iterator heads_end() const { return isSmallHead() ? smallHead()+head_ : largeHead()->end(); } - goal_iterator goals_begin() const { return const_cast(this)->goals_begin(); } - goal_iterator goals_end() const { return goals_begin() + size(); } - //! Adds a rule edge between this body and the given head. - /*! - * \note - * The function also adds a corresponding back edge to the head. - * \note - * Adding a head invalidates the set property for the heads of this body. - * To restore it, call simplifyHeads() - */ - void addHead(PrgHead* h, EdgeType t); - //! Simplifies the heads of this body and establishes set property. - /*! - * Removes superfluous heads and sets the body to false if for some atom a - * in the head of this body B, Ta -> FB. In that case, all heads atoms are removed, because - * a false body can't define any atom. - * If strong is true, removes head atoms that are not associated with a variable. - * \return - * setValue(value_false) if setting a head of this body to true would - * make the body false (i.e. the body is a selfblocker). Otherwise, true. - */ - bool simplifyHeads(LogicProgram& prg, bool strong); - bool mergeHeads(LogicProgram& prg, PrgBody& heads, bool strong, bool simplify = true); - void removeHead(PrgHead* h, EdgeType t); - bool hasHead(PrgHead* h, EdgeType t) const; - //! Simplifies the body, i.e. its predecessors-lists. - /*! - * - removes true/false atoms from B+/B- resp. - * - removes/merges duplicate subgoals - * - checks whether body must be false (e.g. contains false/true atoms in B+/B- resp. or contains p and ~p) - * - * \pre prg.getBody(id()) == this - * - * \param[in] prg The program containing this body. - * \param[in] strong If true, treats atoms that have no variable associated as false. - * \param[out] eqId The id of a body in prg that is equivalent to this body. - * - * \return - * - true if simplification was successful - * - false if simplification detected a conflict - */ - bool simplifyBody(LogicProgram& prg, bool strong, uint32* eqId = 0); - //! Calls simplifyBody() and/or simplifyHeads() if necessary. - bool simplify(LogicProgram& prg, bool strong, uint32* eqId = 0) { - return simplifyBody(prg, strong, eqId) && simplifyHeads(prg, strong); - } - bool toData(const LogicProgram& prg, Potassco::RuleBuilder& out) const; - //! Notifies the body node about the fact that its positive subgoal v is supported. - /*! - * \return true if the body is now also supported, false otherwise. - */ - bool propagateSupported(Var /* v */); - //! Propagates the assignment of subgoal p. - bool propagateAssigned(LogicProgram& prg, Literal p, ValueRep v); - //! Propagates the assignment of a head. - bool propagateAssigned(LogicProgram& prg, PrgHead* h, EdgeType t); - //! Propagates the value of this body. - bool propagateValue(LogicProgram& prg, bool backprop); - bool propagateValue(LogicProgram& prg); - bool addConstraints(const LogicProgram& prg, ClauseCreator& c); - void markDirty() { sBody_ = 1; } - void markHeadsDirty() { sHead_ = 1; } - void markFrozen() { freeze_= 1; } - void clearHeads(); - bool resetSupported(); - void assignVar(LogicProgram& prg); - bool assignValue(ValueRep v) { return assignValueImpl(v, noScc()); } - uint32 scc(const LogicProgram& prg) const; - bool hasWeights() const { return type() == Body_t::Sum; } - void clearRule(AtomState& rs) const { - for (head_iterator it = heads_begin(), end = heads_end(); it != end; ++it) { - rs.clearRule(it->node()); - } - for (const Literal* it = goals_begin(), *end = it + size(); it != end; ++it) { - rs.clearRule(it->var()); - } - } - NodeType nodeType() const { return PrgNode::Body; } + using GoalSpan = LitView; + + //! Creates a new body node and (optionally) connects it to its predecessors (i.e. atoms). + /*! + * \param prg The program in which the new body is used. + * \param id The id for the new body node. + * \param rule The rule for which a body node is to be created. + * \param pos Positive body size. + * \param addDeps If true, add an edge between each atom subgoal and the new node. + */ + static PrgBody* create(const LogicProgram& prg, uint32_t id, const Rule& rule, uint32_t pos, bool addDeps); + //! Destroys a body node created via create(). + void destroy(); + [[nodiscard]] BodyType type() const { return static_cast(type_); } + //! Returns the number of atoms in the body. + [[nodiscard]] uint32_t size() const { return size_; } + [[nodiscard]] bool noScc() const { return size() == 0 || goal(0).sign(); } + //! Returns the bound of this body, or size() if the body is a normal body. + [[nodiscard]] Weight_t bound() const { + if (type() == BodyType::normal) { + return static_cast(size()); + } + return hasWeights() ? sumData()->bound : aggData().bound; + } + //! Returns the sum of the subgoals weights, or size() if the body is not a sum with weights. + [[nodiscard]] Weight_t sumW() const { return not hasWeights() ? static_cast(size()) : sumData()->sumW; } + //! Returns the idx-th subgoal as a literal. + [[nodiscard]] Literal goal(uint32_t idx) const { + assert(idx < size()); + return *(lits() + idx); + } + //! Returns the weight of the idx-th subgoal. + [[nodiscard]] Weight_t weight(uint32_t idx) const { + assert(idx < size()); + return not hasWeights() ? 1 : sumData()->weights[idx]; + } + //! Returns true if the body node is supported. + /*! + * A normal body is supported, iff all of its positive subgoals are supported. + * A count/sum body is supported if the sum of the weights of the supported positive + + * the sum of the negative weights is >= lowerBound(). + */ + [[nodiscard]] bool isSupported() const { return unsupp_ <= 0; } + //! Returns true if this body defines any head. + [[nodiscard]] bool hasHeads() const { return isSmallHead() ? head_ != 0 : not largeHead()->empty(); } + [[nodiscard]] bool inRule() const { return hasHeads() || freeze_; } + + [[nodiscard]] EdgeSpan heads() const { return isSmallHead() ? EdgeSpan{smallHead(), head_} : *largeHead(); } + [[nodiscard]] GoalSpan goals() const { return {lits(), size()}; } + //! Adds a rule edge between this body and the given head. + /*! + * \note + * The function also adds a corresponding back edge to the head. + * \note + * Adding a head invalidates the set property for the heads of this body. + * To restore it, call simplifyHeads() + */ + void addHead(PrgHead* h, EdgeType t); + //! Simplifies the heads of this body and establishes set property. + /*! + * Removes superfluous heads and sets the body to false if for some atom 'a' + * in the head of this body 'B', Ta -> FB. In that case, all heads atoms are removed, because + * a false body can't define any atom. + * If strong is true, removes head atoms that are not associated with a variable. + * \return + * setValue(value_false) if setting a head of this body to true would + * make the body false (i.e. the body is a selfblocker). Otherwise, true. + */ + bool simplifyHeads(LogicProgram& prg, bool strong); + bool mergeHeads(LogicProgram& prg, PrgBody& heads, bool strong, bool simplify = true); + void removeHead(PrgHead* h, EdgeType t); + bool hasHead(const PrgHead* h, EdgeType t) const; + //! Simplifies the body, i.e. its predecessors-lists. + /*! + * - removes true/false atoms from B+/B- resp. + * - removes/merges duplicate subgoals + * - checks whether body must be false (e.g. contains false/true atoms in B+/B- resp. or contains p and ~p) + * + * \pre prg.getBody(id()) == this + * + * \param[in] prg The program containing this body. + * \param[in] strong If true, treats atoms that have no variable associated as false. + * \param[out] eqId The id of a body in prg that is equivalent to this body. + * + * \return + * - true if simplification was successful + * - false if simplification detected a conflict + */ + bool simplifyBody(LogicProgram& prg, bool strong, uint32_t* eqId = nullptr); + //! Calls simplifyBody() and/or simplifyHeads() if necessary. + bool simplify(LogicProgram& prg, bool strong, uint32_t* eqId = nullptr) { + return simplifyBody(prg, strong, eqId) && simplifyHeads(prg, strong); + } + bool toData(const LogicProgram& prg, Potassco::RuleBuilder& out) const; + //! Notifies the body node about the fact that its positive subgoal v is supported. + /*! + * \return true if the body is now also supported, false otherwise. + */ + bool propagateSupported(Var_t /* v */); + //! Propagates the assignment of subgoal p. + bool propagateAssigned(LogicProgram& prg, Literal p, Val_t v); + //! Propagates the assignment of a head. + bool propagateAssigned(LogicProgram& prg, const PrgHead* h, EdgeType t); + //! Propagates the value of this body. + bool propagateValue(LogicProgram& prg, bool backprop); + bool propagateValue(LogicProgram& prg); + bool addConstraints(const LogicProgram& prg, ClauseCreator& c); + void markDirty() { sBody_ = 1; } + void markHeadsDirty() { sHead_ = 1; } + void markFrozen() { freeze_ = 1; } + void clearHeads(); + bool resetSupported(); + void assignVar(LogicProgram& prg); + bool assignValue(Val_t v) { return assignValueImpl(v, noScc()); } + [[nodiscard]] uint32_t scc(const LogicProgram& prg) const; + [[nodiscard]] bool hasWeights() const { return type() == BodyType::sum; } + void clearRule(AtomState& rs) const { + std::ranges::for_each(heads(), [&rs](PrgEdge e) { rs.clearRule(e.node()); }); + std::ranges::for_each(goals(), [&rs](Literal p) { rs.clearRule(p.var()); }); + } + [[nodiscard]] static constexpr NodeType nodeType() { return PrgNode::body; } + private: - static const uint32 maxSize = (1u<<25)-1; - typedef unsigned char byte_t; -POTASSCO_WARNING_BEGIN_RELAXED - struct SumData { - enum { LIT_OFFSET = sizeof(void*)/sizeof(uint32) }; - static SumData* create(uint32 size, weight_t bnd, weight_t ws); - void destroy(); - weight_t bound; - weight_t sumW; - weight_t weights[0]; - }; - struct Agg { - union { - SumData* sum; - weight_t bound; - }; - Literal lits[0]; - }; - struct Norm { Literal lits[0]; }; - PrgBody(uint32 id, LogicProgram& prg, const Potassco::LitSpan& lits, uint32 pos, bool addDeps); - PrgBody(uint32 id, LogicProgram& prg, const Potassco::Sum_t& sum, bool hasWeights, uint32 pos, bool addDeps); - void init(Body_t t, uint32 sz); - ~PrgBody(); - uint32 findLit(const LogicProgram& prg, Literal p) const; - bool normalize(const LogicProgram& prg, weight_t bound, weight_t sumW, weight_t reachW, uint32& hashOut); - void prepareSimplifyHeads(LogicProgram& prg, AtomState& rs); - bool simplifyHeadsImpl(LogicProgram& prg, PrgBody& target, AtomState& rs, bool strong); - bool superfluousHead(const LogicProgram& prg, const PrgHead* head, PrgEdge it, const AtomState& rs) const; - bool blockedHead(PrgEdge it, const AtomState& rs) const; - void addHead(PrgEdge h); - bool eraseHead(PrgEdge h); - bool isSmallHead() const { return head_ != 3u; } - byte_t* data() const { return const_cast(data_); } - PrgEdge* smallHead() const { return const_cast(headData_.sm); } - EdgeVec* largeHead() const { return headData_.ext; } - SumData* sumData() const { return aggData().sum;} - Agg& aggData() const { return *reinterpret_cast(data()); } - Literal* goals_begin() { return type() == Body_t::Normal ? reinterpret_cast(data())->lits : aggData().lits; } - Literal* goals_end() { return goals_begin() + size(); } - - uint32 size_ : 25; // |B| - uint32 head_ : 2; // simple or extended head? - uint32 type_ : 2; // body type - uint32 sBody_ : 1; // simplify body? - uint32 sHead_ : 1; // simplify head? - uint32 freeze_ : 1; // keep body even if it does not occur in a rule? - weight_t unsupp_; // <= 0 -> body is supported - union Head { - PrgEdge sm[2]; - EdgeVec* ext; - } headData_; // successors of this body - byte_t data_[0]; // empty or one of Agg|Norm -POTASSCO_WARNING_END_RELAXED + static constexpr uint32_t max_size = (1u << 25) - 1; + POTASSCO_WARNING_BEGIN_RELAXED + struct SumData { + static SumData* create(uint32_t size, Weight_t bnd, Weight_t ws); + void destroy(); + Weight_t bound; + Weight_t sumW; + Weight_t weights[0]; + }; + struct Agg { + union { + SumData* sum{}; + Weight_t bound; + }; + Literal lits[0]; + }; + struct Norm { + Literal lits[0]; + }; + PrgBody(uint32_t id, const LogicProgram& prg, Potassco::LitSpan lits, uint32_t pos, bool addDeps); + PrgBody(uint32_t id, const LogicProgram& prg, const Potassco::Sum& sum, bool hasWeights, uint32_t pos, + bool addDeps); + PrgBody(uint32_t id, BodyType, uint32_t sz); + ~PrgBody(); + [[nodiscard]] uint32_t findLit(const LogicProgram& prg, Literal p) const; + bool normalize(const LogicProgram& prg, Weight_t bound, Weight_t sumW, Weight_t reachW, uint32_t& hashOut); + void prepareSimplifyHeads(const LogicProgram& prg, AtomState& rs); + bool simplifyHeadsImpl(const LogicProgram& prg, PrgBody& target, AtomState& rs, bool strong); + bool superfluousHead(const LogicProgram& prg, const PrgHead* head, PrgEdge it, const AtomState& rs) const; + [[nodiscard]] bool blockedHead(PrgEdge it, const AtomState& rs) const; + void addHead(PrgEdge h); + bool eraseHead(PrgEdge h); + [[nodiscard]] bool isSmallHead() const { return head_ != 3u; } + template + [[nodiscard]] T* data() const { + return reinterpret_cast(const_cast(data_)); + } + [[nodiscard]] PrgEdge* smallHead() const { return const_cast(headData_.sm); } + [[nodiscard]] EdgeVec* largeHead() const { return headData_.ext; } + [[nodiscard]] SumData* sumData() const { return aggData().sum; } + [[nodiscard]] Agg& aggData() const { return *data(); } + [[nodiscard]] Literal* lits() const { return type() == BodyType::normal ? data()->lits : data()->lits; } + + uint32_t size_ : 25; // |B| + uint32_t head_ : 2; // simple or extended head? + uint32_t type_ : 2; // body type + uint32_t sBody_ : 1; // simplify body? + uint32_t sHead_ : 1; // simplify head? + uint32_t freeze_ : 1; // keep body even if it does not occur in a rule? + Weight_t unsupp_; // <= 0 -> body is supported + union Head { + PrgEdge sm[2]; + EdgeVec* ext; + } headData_; // successors of this body + char data_[0]; // empty or one of Agg|Norm + POTASSCO_WARNING_END_RELAXED }; //! The head of a disjunctive rule. class PrgDisj : public PrgHead { public: - typedef const Var* atom_iterator; - //! Constructor for disjunctions. - static PrgDisj* create(uint32 id, const Potassco::AtomSpan& head); - //! Destroys a disjunction created via create(). - void destroy(); - void detach(LogicProgram& prg, bool full = true); - //! Number of atoms in disjunction. - uint32 size() const { return data_; } - atom_iterator begin() const { return atoms_; } - atom_iterator end() const { return atoms_ + size(); } - //! Propagates the assignment of an atom in this disjunction. - bool propagateAssigned(LogicProgram& prg, PrgHead* at, EdgeType t); + using AtomSpan = VarView; + //! Constructor for disjunctions. + static PrgDisj* create(uint32_t id, Potassco::AtomSpan head); + //! Destroys a disjunction created via create(). + void destroy(); + void detach(const LogicProgram& prg, bool full = true); + //! Number of atoms in disjunction. + [[nodiscard]] uint32_t size() const { return data_; } + [[nodiscard]] AtomSpan atoms() const { return {atoms_, size()}; } + //! Propagates the assignment of an atom in this disjunction. + bool propagateAssigned(const LogicProgram& prg, PrgHead* at, EdgeType t); + private: - explicit PrgDisj(uint32 id, const Potassco::AtomSpan& head); - ~PrgDisj(); -POTASSCO_WARNING_BEGIN_RELAXED - Var atoms_[0]; // atoms in disjunction -POTASSCO_WARNING_END_RELAXED + explicit PrgDisj(uint32_t id, Potassco::AtomSpan head); + ~PrgDisj(); + POTASSCO_WARNING_BEGIN_RELAXED + Atom_t atoms_[0]; // atoms in disjunction + POTASSCO_WARNING_END_RELAXED }; -inline ValueRep getMergeValue(const PrgNode* lhs, const PrgNode* rhs) { - return static_cast(std::min(static_cast(lhs->value()-1), static_cast(rhs->value()-1)) + 1); +constexpr Val_t getMergeValue(const PrgNode* lhs, const PrgNode* rhs) { + return static_cast(std::min(static_cast(lhs->value() - 1), static_cast(rhs->value() - 1)) + 1); } -template -bool mergeValue(NT* lhs, NT* rhs){ - ValueRep mv = getMergeValue(lhs, rhs); - return (lhs->value() == mv || lhs->assignValue(mv)) - && (rhs->value() == mv || rhs->assignValue(mv)); +template +bool mergeValue(NodeT* lhs, NodeT* rhs) { + auto mv = getMergeValue(lhs, rhs); + return (lhs->value() == mv || lhs->assignValue(mv)) && (rhs->value() == mv || rhs->assignValue(mv)); +} +template To, std::derived_from From> +constexpr auto node_cast(From* node) -> std::conditional_t, const To*, To*> { + using P = std::conditional_t, const To*, To*>; + if constexpr (std::is_same_v, PrgHead>) { + static_assert(std::is_same_v || std::is_same_v); + assert(node->isAtom() == (std::is_same_v) ); + } + return static_cast

(node); +} +template To, typename From> +requires(std::is_pointer_v && std::derived_from, To>) +constexpr auto node_cast(std::span in) -> std::span, To* const, To*>> { + using P = std::add_pointer_t, To* const, To*>>; + return {reinterpret_cast

(in.data()), in.size()}; } //! A class for computing strongly connected components of the positive atom-body dependency graph. class SccChecker { public: - SccChecker(LogicProgram& prg, AtomList& sccAtoms, uint32 startScc); - uint32 sccs() const { return sccs_; } - void visit(PrgBody* body) { visitDfs(body, PrgNode::Body); } - void visit(PrgAtom* atom) { visitDfs(atom, PrgNode::Atom); } - void visit(PrgDisj* disj) { visitDfs(disj, PrgNode::Disj); } + SccChecker(LogicProgram& prg, AtomList& sccAtoms, uint32_t startScc); + [[nodiscard]] uint32_t sccs() const { return sccs_; } + private: - struct Call { - uintp node; - uint32 min; - uint32 next; - }; - typedef PodVector::type CallStack; - typedef PodVector::type NodeStack; - static uintp packNode(PrgNode* n, NodeType t) { return reinterpret_cast(n) + uintp(t); } - static PrgNode* unpackNode(uintp n) { return reinterpret_cast(n & ~uintp(3u));} - static bool isNode(uintp n, NodeType t) { return (n & 3u) == uintp(t); } - bool doVisit(PrgNode* n, bool seen = true) const { return !n->ignoreScc() && n->relevant() && n->hasVar() && (!seen || !n->seen()); } - void visitDfs(PrgNode* n, NodeType t); - bool recurse(Call& c); - bool onNode(PrgNode* n, NodeType t, Call& c, uint32 data); - void addCall(PrgNode* n, NodeType t, uint32 next, uint32 min = 0) { - Call c = {packNode(n, t), min, next}; - callStack_.push_back(c); - } - CallStack callStack_; - NodeStack nodeStack_; - LogicProgram* prg_; - AtomList* sccAtoms_; - uint32 count_; - uint32 sccs_; + struct Call { + uintptr_t node; + uint32_t min; + uint32_t next; + }; + using CallStack = PodVector_t; + using NodeStack = PodVector_t; + static uintptr_t packNode(PrgNode* n, NodeType t) { + return reinterpret_cast(n) + static_cast(t); + } + static PrgNode* unpackNode(uintptr_t n) { return reinterpret_cast(n & ~static_cast(3u)); } + static bool isNode(uintptr_t n, NodeType t) { return (n & 3u) == static_cast(t); } + static bool doVisit(const PrgNode* n, bool seen = true) { + return not n->ignoreScc() && n->relevant() && n->hasVar() && (not seen || not n->seen()); + } + void visitDfs(PrgNode* n, NodeType t); + bool recurse(Call& c); + bool onNode(PrgNode* n, NodeType t, Call& c, uint32_t data); + void addCall(PrgNode* n, NodeType t, uint32_t next, uint32_t min = 0) { + callStack_.push_back({.node = packNode(n, t), .min = min, .next = next}); + } + CallStack callStack_; + NodeStack nodeStack_; + LogicProgram* prg_; + AtomList* sccAtoms_; + uint32_t count_; + uint32_t sccs_; }; //! A set of ids of strongly connected components having at least one head-cycle. -struct NonHcfSet : private PodVector::type { -public: - typedef PodVector::type base_type; - typedef base_type::const_iterator const_iterator; - using base_type::begin; - using base_type::end; - using base_type::size; - NonHcfSet() : config(0) {} - void add(uint32 scc) { - iterator it = std::lower_bound(begin(), end(), scc); - if (it == end() || *it != scc) { insert(it, scc); } - } - bool find(uint32 scc) const { - const_iterator it = scc != PrgNode::noScc ? std::lower_bound(begin(), end(), scc) : end(); - return it != end() && *it == scc; - } - Configuration* config; +struct NonHcfSet : private PodVector_t { + using base_type = PodVector_t; // NOLINT + using const_iterator = base_type::const_iterator; // NOLINT + using base_type::begin; + using base_type::empty; + using base_type::end; + using base_type::size; + NonHcfSet() = default; + void add(uint32_t scc) { + if (auto it = std::ranges::lower_bound(*this, scc); it == end() || *it != scc) { + insert(it, scc); + } + } + [[nodiscard]] bool find(uint32_t scc) const { + auto it = std::ranges::lower_bound(*this, scc); + return it != end() && *it == scc; + } + [[nodiscard]] auto view(std::size_t offset) const -> SpanView { + return drop(static_cast(*this), offset); + } + Configuration* config{nullptr}; }; //@} -} } -#endif +} // namespace Asp +} // namespace Clasp diff --git a/clasp/lookahead.h b/clasp/lookahead.h index 46d4051..b56d257 100644 --- a/clasp/lookahead.h +++ b/clasp/lookahead.h @@ -1,7 +1,7 @@ // // Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,23 +21,18 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_LOOKAHEAD_H_INCLUDED -#define CLASP_LOOKAHEAD_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif /*! * \file * \brief Defines lookahead related types. * * Lookahead can be used as a post propagator (e.g. failed-literal detection) and - * optionally as an heuristic. + * optionally as a heuristic. */ -#include #include +#include namespace Clasp { /*! * \addtogroup propagator @@ -46,93 +41,102 @@ namespace Clasp { //! Type used to store lookahead-information for one variable. struct VarScore { - VarScore() { clear(); } - void clear() { std::memset(this, 0, sizeof(VarScore)); } - //! Mark literal p as dependent. - void setSeen( Literal p ) { seen_ |= uint32(p.sign()) + 1; } - //! Is literal p dependent? - bool seen(Literal p) const { return (seen_ & (uint32(p.sign())+1)) != 0; } - //! Is this var dependent? - bool seen() const { return seen_ != 0; } - //! Mark literal p as tested during lookahead. - void setTested( Literal p ) { tested_ |= uint32(p.sign()) + 1; } - //! Was literal p tested during lookahead? - bool tested(Literal p) const { return (tested_ & (uint32(p.sign())+1)) != 0; } - //! Was some literal of this var tested? - bool tested() const { return tested_ != 0; } - //! Were both literals of this var tested? - bool testedBoth() const { return tested_ == 3; } - - //! Sets the score for literal p to value and marks p as tested. - void setScore(Literal p, LitVec::size_type value) { - if (value > (1U<<14)-1) value = (1U<<14)-1; - if (p.sign()) nVal_ = uint32(value); - else pVal_ = uint32(value); - setTested(p); - } - //! Sets the score of a dependent literal p to min(sc, current score). - void setDepScore(Literal p, uint32 sc) { - if (!seen(p) || score(p) > sc) { - if (sc > (1U<<14)-1) sc = (1U<<14)-1; - if (p.sign()) nVal_ = std::min(uint32(nVal_-(nVal_==0)), sc); - else pVal_ = std::min(uint32(pVal_-(pVal_==0)), sc); - } - } - //! Returns the score for literal p. - uint32 score(Literal p) const { return p.sign() ? nVal_ : pVal_; } - //! Returns the scores of the two literals of a variable. - /*! - * \param[out] mx The maximum score. - * \param[out] mn The minimum score. - */ - void score(uint32& mx, uint32& mn) const { - if (nVal_ > pVal_) { - mx = nVal_; - mn = pVal_; - } - else { - mx = pVal_; - mn = nVal_; - } - } - //! Returns the sign of the literal that has the higher score. - bool prefSign() const { return nVal_ > pVal_; } - - uint32 nVal() const { return nVal_; } - uint32 pVal() const { return pVal_; } + constexpr VarScore() = default; + //! Is literal p dependent? + [[nodiscard]] constexpr bool seen(Literal p) const { return (seen_ & mask(p)) != 0; } + //! Is this var dependent? + [[nodiscard]] constexpr bool seen() const { return seen_ != 0; } + //! Mark literal p as tested during lookahead. + constexpr void setTested(Literal p) { tested_ |= mask(p); } + //! Was literal p tested during lookahead? + [[nodiscard]] constexpr bool tested(Literal p) const { return (tested_ & mask(p)) != 0; } + //! Was some literal of this var tested? + [[nodiscard]] constexpr bool tested() const { return tested_ != 0; } + //! Were both literals of this var tested? + [[nodiscard]] constexpr bool testedBoth() const { return tested_ == 3u; } + + //! Sets the score for literal p to value and marks p as tested. + constexpr void setScore(Literal p, uint32_t value) { + setScoreImpl(p, value); + setTested(p); + } + //! Sets the score of a dependent literal p to min(sc, current score) and mark p as seen. + constexpr void setDepScore(Literal p, uint32_t sc) { + if (not seen(p) || score(p) > sc) { + setScoreImpl(p, sc); + seen_ |= mask(p); + } + } + //! Returns the score for literal p. + [[nodiscard]] constexpr uint32_t score(Literal p) const { return p.sign() ? nVal_ : pVal_; } + //! Returns the scores of the two literals of a variable. + /*! + * \param[out] mx The maximum score. + * \param[out] mn The minimum score. + */ + void score(uint32_t& mx, uint32_t& mn) const { + if (nVal_ > pVal_) { + mx = nVal_; + mn = pVal_; + } + else { + mx = pVal_; + mn = nVal_; + } + } + //! Returns the sign of the literal that has the higher score. + [[nodiscard]] constexpr bool prefSign() const { return nVal_ > pVal_; } + + [[nodiscard]] constexpr uint32_t nVal() const { return nVal_; } + [[nodiscard]] constexpr uint32_t pVal() const { return pVal_; } + private: - uint32 pVal_ : 14; - uint32 nVal_ : 14; - uint32 seen_ : 2; - uint32 tested_: 2; + static constexpr auto max_score = (1u << 14) - 1; + static constexpr uint32_t mask(Literal p) { return static_cast(p.sign()) + 1u; } + + constexpr void setScoreImpl(Literal p, uint32_t value) { + if (value > max_score) { + value = max_score; + } + if (p.sign()) { + nVal_ = value; + } + else { + pVal_ = value; + } + } + + uint32_t pVal_ : 14 = 0; + uint32_t nVal_ : 14 = 0; + uint32_t seen_ : 2 = 0; + uint32_t tested_ : 2 = 0; }; //! A small helper class used to score the result of a lookahead operation. struct ScoreLook { - enum Mode { score_max, score_max_min }; - typedef PodVector::type VarScores; /**< A vector of variable-scores */ - ScoreLook() : best(0), limit(UINT32_MAX), mode(score_max), addDeps(true), nant(false) {} - bool validVar(Var v) const { return v < score.size(); } - void scoreLits(const Solver& s, const Literal* b, const Literal *e); - void clearDeps(); - uint32 countNant(const Solver& s, const Literal* b, const Literal *e) const; - bool greater(Var lhs, Var rhs)const; - bool greaterMax(Var x, uint32 max) const { - return score[x].nVal() > max || score[x].pVal() > max; - } - bool greaterMaxMin(Var lhs, uint32 max, uint32 min) const { - uint32 lhsMin, lhsMax; - score[lhs].score(lhsMax, lhsMin); - return lhsMin > min || (lhsMin == min && lhsMax > max); - } - VarScores score; //!< score[v] stores lookahead score of v - VarVec deps; //!< Tested vars and those that follow from them. - VarType types; //!< Var types to consider. - Var best; //!< Var with best score among those in deps. - uint32 limit; //!< Stop after this number of tests - Mode mode; //!< Score mode to apply. - bool addDeps;//!< Add/score dependent vars? - bool nant; //!< Score only atoms in NegAnte(P)? + enum Mode { score_max, score_max_min }; + using VarScores = PodVector_t; /**< A vector of variable-scores */ + [[nodiscard]] bool validVar(Var_t v) const { return v < score.size(); } + void scoreLits(const Solver& s, LitView lits); + void clearDeps(); + static uint32_t countNant(const Solver& s, LitView lits); + [[nodiscard]] bool greater(Var_t lhs, Var_t rhs) const; + [[nodiscard]] bool greaterMax(Var_t x, uint32_t max) const { + return score[x].nVal() > max || score[x].pVal() > max; + } + [[nodiscard]] bool greaterMaxMin(Var_t lhs, uint32_t max, uint32_t min) const { + uint32_t lhsMin, lhsMax; + score[lhs].score(lhsMax, lhsMin); + return lhsMin > min || (lhsMin == min && lhsMax > max); + } + VarScores score; //!< score[v] stores lookahead score of v + VarVec deps; //!< Tested vars and those that follow from them. + VarType types{VarType::atom}; //!< Var types to consider. + Var_t best{0}; //!< Var with best score among those in deps. + uint32_t limit{UINT32_MAX}; //!< Stop after this number of tests + Mode mode{score_max}; //!< Score mode to apply. + bool addDeps{true}; //!< Add/score dependent vars? + bool nant{false}; //!< Score only atoms in NegAnte(P)? }; class UnitHeuristic; @@ -147,70 +151,84 @@ class UnitHeuristic; */ class Lookahead : public PostPropagator { public: - //! Set of parameters to configure lookahead. - struct Params { - Params(VarType t = Var_t::Atom) : type(t), lim(0), topLevelImps(true), restrictNant(false) {} - Params& lookahead(VarType t){ type = t; return *this; } - Params& addImps(bool b) { topLevelImps = b; return *this; } - Params& nant(bool b) { restrictNant = b; return *this; } - Params& limit(uint32 x) { lim = x; return *this; } - VarType type; - uint32 lim; - bool topLevelImps; - bool restrictNant; - }; - static bool isType(uint32 t) { return t != 0 && t <= Var_t::Hybrid; } - /*! - * \param p Lookahead parameters to use. - */ - explicit Lookahead(const Params& p); - ~Lookahead(); - - bool init(Solver& s); - //! Clears the lookahead list. - void clear(); - //! Returns true if lookahead list is empty. - bool empty() const { return head()->next == head_id; } - //! Adds literal p to the lookahead list. - void append(Literal p, bool testBoth); - //! Executes a single-step lookahead on all vars in the lookahead list. - bool propagateFixpoint(Solver& s, PostPropagator*); - //! Returns PostPropagator::priority_reserved_look. - uint32 priority() const; - void destroy(Solver* s, bool detach); - ScoreLook score; //!< State of last lookahead operation. - //! Returns "best" literal w.r.t scoring of last lookahead or lit_true() if no such literal exists. - Literal heuristic(Solver& s); - void detach(Solver& s); - bool hasLimit() const { return limit_ != 0; } + //! Set of parameters to configure lookahead. + struct Params { + Params(VarType t = VarType::atom) : type(t) {} // NOLINT + Params& lookahead(VarType t) { + type = t; + return *this; + } + Params& addImps(bool b) { + topLevelImps = b; + return *this; + } + Params& nant(bool b) { + restrictNant = b; + return *this; + } + Params& limit(uint32_t x) { + lim = x; + return *this; + } + VarType type; + uint32_t lim{0}; + bool topLevelImps{true}; + bool restrictNant{false}; + }; + static bool isType(uint32_t t) { return t != 0 && t <= VarType::hybrid; } + /*! + * \param p Lookahead parameters to use. + */ + explicit Lookahead(const Params& p); + ~Lookahead() override; + + bool init(Solver& s) override; + //! Clears the lookahead list. + void clear(); + //! Returns true if lookahead list is empty. + [[nodiscard]] bool empty() const { return head()->next == head_id; } + //! Adds literal p to the lookahead list. + void append(Literal p, bool testBoth); + //! Executes a single-step lookahead on all vars in the lookahead list. + bool propagateFixpoint(Solver& s, PostPropagator*) override; + //! Returns PostPropagator::priority_reserved_look. + [[nodiscard]] uint32_t priority() const override; + void destroy(Solver* s, bool detach) override; + ScoreLook score; //!< State of last lookahead operation. + //! Returns "best" literal w.r.t scoring of last lookahead or lit_true() if no such literal exists. + Literal heuristic(Solver& s); + void detach(Solver& s); + [[nodiscard]] bool hasLimit() const { return limit_ != 0; } + protected: - bool propagateLevel(Solver& s); // called by propagate - void undoLevel(Solver& s); - bool test(Solver& s, Literal p); + bool propagateLevel(Solver& s); // called by propagate + void undoLevel(Solver& s) override; + bool test(Solver& s, Literal p); + private: - typedef uint32 NodeId; - enum { head_id = NodeId(0), undo_id = NodeId(1) }; - struct LitNode { - LitNode(Literal x) : lit(x), next(UINT32_MAX) {} - Literal lit; - NodeId next; - }; - typedef PodVector::type UndoStack; - typedef PodVector::type LookList; - typedef UnitHeuristic* HeuPtr; - void splice(NodeId n); - LitNode* node(NodeId n) { return &nodes_[n]; } - LitNode* head() { return &nodes_[head_id]; } // head of circular candidate list - LitNode* undo() { return &nodes_[undo_id]; } // head of undo list - bool checkImps(Solver& s, Literal p); - const LitNode* head() const { return &nodes_[head_id]; } - LookList nodes_; // list of literals to test - UndoStack saved_; // stack of undo lists - LitVec imps_; // additional top-level implications - NodeId last_; // last candidate in list; invariant: node(last_)->next == head_id; - NodeId pos_; // current lookahead start position - uint32 top_; // size of top-level - uint32 limit_; // stop lookahead after this number of applications + using NodeId = uint32_t; + static constexpr auto head_id = static_cast(0); + static constexpr auto undo_id = static_cast(1); + struct LitNode { + explicit LitNode(Literal x) : lit(x) {} + Literal lit; + NodeId next{UINT32_MAX}; + }; + using UndoStack = PodVector_t; + using LookList = PodVector_t; + void splice(NodeId n); + LitNode* node(NodeId n) { return &nodes_[n]; } + LitNode* head() { return &nodes_[head_id]; } // head of circular candidate list + LitNode* undo() { return &nodes_[undo_id]; } // head of undo list + bool checkImps(Solver& s, Literal p); + [[nodiscard]] const LitNode* head() const { return &nodes_[head_id]; } + LookList nodes_; // list of literals to test + UndoStack saved_; // stack of undo lists + LitVec imps_; // additional top-level implications + NodeId last_; // last candidate in list; invariant: node(last_)->next == head_id; + NodeId pos_; // current lookahead start position + uint32_t top_; // size of top-level + uint32_t limit_; // stop lookahead after this number of applications }; //@} @@ -233,12 +251,16 @@ class Lookahead : public PostPropagator { */ class UnitHeuristic : public SelectFirst { public: - UnitHeuristic(); - //! Decorates the heuristic given in other with temporary lookahead. - static UnitHeuristic* restricted(DecisionHeuristic* other); - void endInit(Solver& /* s */); - Literal doSelect(Solver& s); + UnitHeuristic(); + //! Decorates the heuristic given in other with temporary lookahead. + static UnitHeuristic* restricted(DecisionHeuristic* other); + void endInit(Solver& /* s */) override; + Literal doSelect(Solver& s) override; + +private: + static Lookahead* getLookahead(const Solver&); + + class Restricted; }; -} -#endif +} // namespace Clasp diff --git a/clasp/minimize_constraint.h b/clasp/minimize_constraint.h index df431f1..084c520 100644 --- a/clasp/minimize_constraint.h +++ b/clasp/minimize_constraint.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,18 +21,15 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_MINIMIZE_CONSTRAINT_H_INCLUDED -#define CLASP_MINIMIZE_CONSTRAINT_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif + /*! -* \file -* \brief Types and functions for implementing minimization constraints. -*/ + * \file + * \brief Types and functions for implementing minimization constraints. + */ +#include #include #include -#include namespace Clasp { class MinimizeConstraint; @@ -46,15 +43,12 @@ class WeightConstraint; * strictly smaller than all previous solutions. Otherwise, * solutions with costs no greater than a fixed bound are considered valid. */ -struct MinimizeMode_t { - enum Mode { - ignore = 0, //!< Ignore optimize statements during solving. - optimize = 1, //!< Optimize via a decreasing bound. - enumerate = 2, //!< Enumerate models with cost less or equal to a fixed bound. - enumOpt = 3, //!< Enumerate models with cost equal to optimum. - }; +enum class MinimizeMode { + ignore = 0, //!< Ignore optimize statements during solving. + optimize = 1, //!< Optimize via a decreasing bound. + enumerate = 2, //!< Enumerate models with cost less or equal to a fixed bound. + enum_opt = 3, //!< Enumerate models with cost equal to optimum. }; -typedef MinimizeMode_t::Mode MinimizeMode; //! A type holding data (possibly) shared between a set of minimize constraints. /*! @@ -62,143 +56,183 @@ typedef MinimizeMode_t::Mode MinimizeMode; */ class SharedMinimizeData { public: - typedef SharedMinimizeData ThisType; - //! A type to represent a weight at a certain level. - /*! - * Objects of this type are used to create sparse vectors of weights. E.g. - * a weight vector (w1\@L1, w2\@L3, w3\@L5) is represented as \[\\\\], - * where each \-tuple is an object of type LevelWeight. - */ - struct LevelWeight { - LevelWeight(uint32 l, weight_t w) : level(l), next(0), weight(w) {} - uint32 level : 31; //!< The level of this weight. - uint32 next : 1; //!< Does this weight belong to a sparse vector of weights? - weight_t weight; //!< The weight at this level. - }; - //! A type for holding sparse vectors of level weights of a multi-level constraint. - typedef PodVector::type WeightVec; - typedef PodVector::type PrioVec; - explicit SharedMinimizeData(const SumVec& lhsAdjust, MinimizeMode m = MinimizeMode_t::optimize); - //! Increases the reference count of this object. - ThisType* share() { ++count_; return this; } - //! Decreases the object's reference count and destroys it if reference count drops to 0. - void release() { if (--count_ == 0) destroy(); } - //! Number of minimize statements contained in this constraint. - uint32 numRules() const{ return static_cast(adjust_.size()); } - uint32 maxLevel() const{ return numRules()-1; } - static wsum_t maxBound() { return INT64_MAX; } - //! Returns the active minimization mode. - MinimizeMode mode() const{ return mode_; } - //! Returns true if optimization is active. - bool optimize() const{ return optGen_ ? checkNext() : mode_ != MinimizeMode_t::enumerate; } - //! Returns the lower bound of level x. - wsum_t lower(uint32 x) const; - //! Returns the upper bound of level x. - wsum_t upper(uint32 x) const{ return upper()[x]; } - const wsum_t* upper() const{ return &(up_ + (gCount_ & 1u))->front(); } - //! Returns the sum of level x in the most recent model. - wsum_t sum(uint32 x) const{ return sum()[x]; } - const wsum_t* sum() const{ return (mode_ != MinimizeMode_t::enumerate) ? upper() : &up_[1][0]; } - //! Returns the adjustment for level x. - wsum_t adjust(uint32 x) const{ return adjust_[x]; } - const wsum_t* adjust() const{ return &adjust_[0]; } - //! Returns the current (adjusted and possibly tentative) optimum for level x. - wsum_t optimum(uint32 x)const; - //! Returns the highest level of the literal with the given index i. - uint32 level(uint32 i) const{ return numRules() == 1 ? 0 : weights[lits[i].second].level; } - //! Returns the most important weight of the literal with the given index i. - weight_t weight(uint32 i) const{ return numRules() == 1 ? lits[i].second : weights[lits[i].second].weight; } - uint32 generation() const{ return gCount_; } - //! Returns whether minimization should search for solutions with current or next smaller upper bound. - bool checkNext() const{ return mode() != MinimizeMode_t::enumerate && generation() != optGen_; } - /*! - * \name interface for optimization - * If not otherwise specified, the following functions shall not be called concurrently. - */ - //@{ + using ThisType = SharedMinimizeData; + //! A type to represent a weight at a certain level. + /*! + * Objects of this type are used to create sparse vectors of weights. E.g. + * a weight vector (w1\@L1, w2\@L3, w3\@L5) is represented as \[\\\\], + * where each \-tuple is an object of type LevelWeight. + */ + struct LevelWeight { + LevelWeight(uint32_t l, Weight_t w) : level(l), next(0), weight(w) {} + uint32_t level : 31; //!< The level of this weight. + uint32_t next : 1; //!< Does this weight belong to a sparse vector of weights? + Weight_t weight; //!< The weight at this level. + }; + //! A type for holding sparse vectors of level weights of a multi-level constraint. + using WeightVec = PodVector_t; + using PrioVec = PodVector_t; + explicit SharedMinimizeData(SumView lhsAdjust, MinimizeMode m = MinimizeMode::optimize); + //! Increases the reference count of this object. + ThisType* share() { + count_.add(); + return this; + } + //! Decreases the object's reference count and destroys it if reference count drops to 0. + void release() { + if (count_.release()) { + destroy(); + } + } + //! Number of minimize statements contained in this constraint. + [[nodiscard]] uint32_t numRules() const { return size32(adjust_); } + [[nodiscard]] uint32_t maxLevel() const { return numRules() - 1; } + static constexpr Wsum_t maxBound() { return weight_sum_max; } + //! Returns the active minimization mode. + [[nodiscard]] MinimizeMode mode() const { return mode_; } + //! Returns true if optimization is active. + [[nodiscard]] bool optimize() const { return optGen_ ? checkNext() : mode_ != MinimizeMode::enumerate; } + //! Returns the lower bound of level x. + [[nodiscard]] Wsum_t lower(uint32_t x) const; + //! Returns the upper bound of level x. + [[nodiscard]] Wsum_t upper(uint32_t x) const { return upper()[x]; } + [[nodiscard]] const Wsum_t* upper() const { return &(up_ + (gCount_.load() & 1u))->front(); } + //! Returns the sum of level x in the most recent model. + [[nodiscard]] Wsum_t sum(uint32_t x) const { return sum()[x]; } + [[nodiscard]] const Wsum_t* sum() const { return (mode_ != MinimizeMode::enumerate) ? upper() : up_[1].data(); } + //! Returns the adjustment for level x. + [[nodiscard]] Wsum_t adjust(uint32_t x) const { return adjust_[x]; } + [[nodiscard]] const Wsum_t* adjust() const { return adjust_.data(); } + //! Returns the current (adjusted and possibly tentative) optimum for level x. + [[nodiscard]] Wsum_t optimum(uint32_t x) const; + //! Returns the current (adjusted and possibly tentative) most relevant lower bound. + [[nodiscard]] LowerBound lowerBound() const; + //! Returns the highest level of the literal with the given index i. + [[nodiscard]] uint32_t level(uint32_t i) const { return numRules() == 1 ? 0 : lw(lits[i])->level; } + //! Returns the most important weight of the literal with the given index i. + [[nodiscard]] Weight_t weight(uint32_t i) const { return numRules() == 1 ? lits[i].weight : lw(lits[i])->weight; } + [[nodiscard]] uint32_t generation() const { return gCount_.load(); } + //! Returns whether minimization should search for solutions with current or next smaller upper bound. + [[nodiscard]] bool checkNext() const { return mode() != MinimizeMode::enumerate && generation() != optGen_; } + /*! + * \name interface for optimization + * If not otherwise specified, the following functions shall not be called concurrently. + */ + //@{ + + //! Sets the enumeration mode and (optionally) an initial bound. + /*! + * \note If m is MinimizeMode::enumerate, the caller should always + * set a bound. Otherwise, *all* solutions are considered valid. + */ + bool setMode(MinimizeMode m, SumView bound = {}); + void resetBounds(); - //! Sets the enumeration mode and (optionally) an initial bound. - /*! - * \note If m is MinimizeMode::enumerate, the caller should always - * set a bound. Otherwise, *all* solutions are considered valid. - */ - bool setMode(MinimizeMode m, const wsum_t* bound = 0, uint32 boundSize = 0); - bool setMode(MinimizeMode m, const SumVec& bound) { return setMode(m, bound.empty() ? 0 : &bound[0], (uint32)bound.size()); } - void resetBounds(); + //! Attaches a new minimize constraint to this data object. + /*! + * \param s Solver in which the new minimize constraint should apply. + * \param params Parameters to pass to the optimization strategy. + * \param addRef If true, the ref count of the shared object is increased. + * Otherwise, the new minimize constraint inherits the reference to the shared object. + */ + MinimizeConstraint* attach(Solver& s, const OptParams& params, bool addRef = true); - //! Attaches a new minimize constraint to this data object. - /*! - * \param s Solver in which the new minimize constraint should apply. - * \param params Parameters to pass to the optimization strategy. - * \param addRef If true, the ref count of the shared object is increased. - * Otherwise, the new minimize constraint inherits the reference to the shared object. - */ - MinimizeConstraint* attach(Solver& s, const OptParams& params, bool addRef = true); + //! Makes opt the new (tentative) optimum. + /*! + * \pre opt is a pointer to an array of size numRules() + */ + SumView setOptimum(const Wsum_t* opt); + //! Marks the current tentative optimum as the final optimum. + /*! + * \note Once a final optimum is set, further calls to setOptimum() + * are ignored until resetBounds() is called. + */ + void markOptimal(); + //! Sets the lower bound of level lev to low. + void setLower(uint32_t lev, Wsum_t low); + //! Sets the lower bound of level lev to the maximum of low and the existing value lower(lev). + /*! + * \note This function is thread-safe, i.e., can be called safely from multiple threads. + */ + Wsum_t incLower(uint32_t lev, Wsum_t low); + //@} - //! Makes opt the new (tentative) optimum. - /*! - * \pre opt is a pointer to an array of size numRules() - */ - const SumVec* setOptimum(const wsum_t* opt); - //! Marks the current tentative optimum as the final optimum. - /*! - * \note Once a final optimum is set, further calls to setOptimum() - * are ignored until resetBounds() is called. - */ - void markOptimal(); - //! Sets the lower bound of level lev to low. - void setLower(uint32 lev, wsum_t low); - //! Sets the lower bound of level lev to the maximum of low and the existing value lower(lev). - /*! - * \note This function is thread-safe, i.e., can be called safely from multiple threads. - */ - wsum_t incLower(uint32 lev, wsum_t low); - //@} + /*! + * \name Arithmetic functions on weights. + */ + //@{ + //! Computes lhs += weight(lit). + void add(Wsum_t* lhs, const WeightLiteral& lit) const { + if (weights.empty()) { + *lhs += lit.weight; + } + else { + add(lhs, lw(lit)); + } + } + static void add(Wsum_t* lhs, const LevelWeight* w) { + do { lhs[w->level] += w->weight; } while (w++->next); + } + //! Computes lhs -= weight(lit). + void sub(Wsum_t* lhs, const WeightLiteral& lit, uint32_t& aLev) const { + if (weights.empty()) { + *lhs -= lit.weight; + } + else { + sub(lhs, lw(lit), aLev); + } + } + static void sub(Wsum_t* lhs, const LevelWeight* w, uint32_t& aLev); + //! Returns (lhs + weight(lit)) > rhs + bool imp(Wsum_t* lhs, const WeightLiteral& lit, const Wsum_t* rhs, uint32_t& lev) const { + return weights.empty() ? (*lhs + lit.weight) > *rhs : imp(lhs, lw(lit), rhs, lev); + } + bool imp(Wsum_t* lhs, const LevelWeight* w, const Wsum_t* rhs, uint32_t& lev) const; + //! Returns the weight of lit at level lev. + [[nodiscard]] Weight_t weight(const WeightLiteral& lit, uint32_t lev) const { + if (numRules() == 1) { + return lit.weight * (lev == 0); + } + const auto* w = lw(lit); + do { + if (w->level == lev) { + return w->weight; + } + } while (w++->next); + return 0; + } + // NOLINTBEGIN(readability-convert-member-functions-to-static) + struct IterSent {}; + [[nodiscard]] constexpr const WeightLiteral* begin() const noexcept { return lits; } + [[nodiscard]] constexpr IterSent end() const noexcept { return {}; } + friend bool operator==(const WeightLiteral* lhs, IterSent) { return isSentinel(lhs->lit); } + // NOLINTEND(readability-convert-member-functions-to-static) - /*! - * \name Arithmetic functions on weights. - */ - //@{ - //! Computes lhs += weight(lit). - void add(wsum_t* lhs, const WeightLiteral& lit) const { if (weights.empty()) *lhs += lit.second; else add(lhs, &weights[lit.second]); } - void add(wsum_t* lhs, const LevelWeight* w) const { do { lhs[w->level] += w->weight; } while (w++->next); } - //! Computes lhs -= weight(lit). - void sub(wsum_t* lhs, const WeightLiteral& lit, uint32& aLev) const { if (weights.empty()) *lhs -= lit.second; else sub(lhs, &weights[lit.second], aLev); } - void sub(wsum_t* lhs, const LevelWeight* w, uint32& aLev) const; - //! Returns (lhs + weight(lit)) > rhs - bool imp(wsum_t* lhs, const WeightLiteral& lit, const wsum_t* rhs, uint32& lev) const { - return weights.empty() ? (*lhs+lit.second) > *rhs : imp(lhs, &weights[lit.second], rhs, lev); - } - bool imp(wsum_t* lhs, const LevelWeight* w, const wsum_t* rhs, uint32& lev) const; - //! Returns the weight of lit at level lev. - weight_t weight(const WeightLiteral& lit, uint32 lev) const { - if (numRules() == 1) { return lit.second * (lev == 0); } - const LevelWeight* w = &weights[lit.second]; - do { if (w->level == lev) return w->weight; } while (w++->next); - return 0; - } - //@} + //@} private: - typedef Clasp::Atomic_t::type CounterType; - typedef Clasp::Atomic_t::type LowerType; - SumVec adjust_; // initial bound adjustments - SumVec up_[2]; // buffers for update via "double buffering" - LowerType* lower_; // (unadjusted) lower bound of constraint - MinimizeMode mode_; // how to compare assignments? - CounterType count_; // number of refs to this object - CounterType gCount_; // generation count - used when updating optimum - uint32 optGen_; // generation of optimal bound + using CounterType = mt::ThreadSafe; + using LowerType = mt::ThreadSafe; + using LowerPtr = std::unique_ptr; + SumVec adjust_; // initial bound adjustments + SumVec up_[2]; // buffers for update via "double buffering" + LowerPtr lower_; // (unadjusted) lower bound of constraint + CounterType lowPos_; // active lower bound idx + MinimizeMode mode_; // how to compare assignments? + RefCount count_; // number of refs to this object + CounterType gCount_; // generation count - used when updating optimum + uint32_t optGen_; // generation of optimal bound public: - WeightVec weights; // sparse vectors of weights - only used for multi-level constraints - PrioVec prios; // (optional): maps levels to original priorities -POTASSCO_WARNING_BEGIN_RELAXED - WeightLiteral lits[0]; // (shared) literals - terminated with lit_true() -POTASSCO_WARNING_END_RELAXED + WeightVec weights; // sparse vectors of weights - only used for multi-level constraints + PrioVec prios; // (optional): maps levels to original priorities + POTASSCO_WARNING_BEGIN_RELAXED + WeightLiteral lits[0]; // (shared) literals - terminated with lit_true() + POTASSCO_WARNING_END_RELAXED private: - ~SharedMinimizeData(); - void destroy() const; - SharedMinimizeData(const SharedMinimizeData&); - SharedMinimizeData& operator=(const SharedMinimizeData&); + [[nodiscard]] const LevelWeight* lw(const WeightLiteral& wl) const { + return &weights[static_cast(wl.weight)]; + } + ~SharedMinimizeData(); + void destroy() const; }; //! Helper class for creating minimize constraints. /*! @@ -206,49 +240,43 @@ POTASSCO_WARNING_END_RELAXED */ class MinimizeBuilder { public: - typedef SharedMinimizeData SharedData; - MinimizeBuilder(); + using SharedData = SharedMinimizeData; + MinimizeBuilder(); + + MinimizeBuilder& add(Weight_t prio, WeightLitView lits); + MinimizeBuilder& add(Weight_t prio, WeightLiteral lit); + MinimizeBuilder& add(Weight_t prio, Weight_t adjust); + MinimizeBuilder& add(const SharedData& minCon); - MinimizeBuilder& add(weight_t prio, const WeightLitVec& lits); - MinimizeBuilder& add(weight_t prio, WeightLiteral lit); - MinimizeBuilder& add(weight_t prio, weight_t adjust); - MinimizeBuilder& add(const SharedData& minCon); + [[nodiscard]] bool empty() const; - bool empty() const; + //! Creates a new data object from previously added minimize literals. + /*! + * The function creates a new minimize data object from + * the previously added literals to minimize. The returned + * object can be used to attach one or more MinimizeConstraints. + * \param ctx A ctx object to be associated with the new minimize constraint. + * \return A data object representing previously added minimize statements or 0 if empty(). + * \pre !ctx.frozen() + * \post empty() + */ + SharedData* build(SharedContext& ctx); - //! Creates a new data object from previously added minimize literals. - /*! - * The function creates a new minimize data object from - * the previously added literals to minimize. The returned - * object can be used to attach one or more MinimizeConstraints. - * \param ctx A ctx object to be associated with the new minimize constraint. - * \return A data object representing previously added minimize statements or 0 if empty(). - * \pre !ctx.frozen() - * \post empty() - */ - SharedData* build(SharedContext& ctx); + //! Discards any previously added minimize literals. + void clear(); - //! Discards any previously added minimize literals. - void clear(); private: - struct MLit { - MLit(const WeightLiteral& wl, weight_t at) : lit(wl.first), prio(at), weight(wl.second) {} - Literal lit; - weight_t prio; - weight_t weight; - }; - struct CmpPrio { bool operator()(const MLit& lhs, const MLit& rhs) const; }; - struct CmpLit { bool operator()(const MLit& lhs, const MLit& rhs) const; }; - struct CmpWeight{ - CmpWeight(const SharedData::WeightVec* w) : weights(w) {} - bool operator()(const MLit& lhs, const MLit& rhs) const; - const SharedData::WeightVec* weights; - }; - typedef PodVector::type LitVec; - void prepareLevels(const Solver& s, SumVec& adjustOut, WeightVec& priosOut); - void mergeLevels(SumVec& adjust, SharedData::WeightVec& weightsOut); - SharedData* createShared(SharedContext& ctx, const SumVec& adjust, const CmpWeight& cmp); - LitVec lits_; + struct MLit { + MLit(const WeightLiteral& wl, Weight_t at) : lit(wl.lit), prio(at), weight(wl.weight) {} + Literal lit; + Weight_t prio; + Weight_t weight; + }; + using LitVec = PodVector_t; + void prepareLevels(const Solver& s, SumVec& adjustOut, WeightVec& priosOut); + void mergeLevels(SumVec& adjust, SharedData::WeightVec& weightsOut); + SharedData* createShared(SharedContext& ctx, SumView adjust, const SharedData::WeightVec* weights); + LitVec lits_; }; //! Base class for implementing (multi-level) minimize statements. @@ -269,44 +297,44 @@ class MinimizeBuilder { * m0: {a, b} * m1: {c, d} * All models: {a, c,...}, {a, d,...} {b, c,...}, {b, d,...} {a, b,...} - * Mode = optimize: {a, c, ...} (m0 = 1, m1 = 1} + * Mode = optimize: {a, c, ...} (m0 = 1, m1 = 1) * Mode = enumerate and initial opt=1,1: {a, c, ...}, {a, d,...}, {b, c,...}, {b, d,...} * */ class MinimizeConstraint : public Constraint { public: - typedef SharedMinimizeData SharedData; - typedef const SharedData* SharedDataP; - //! Returns a pointer to the shared representation of this constraint. - SharedDataP shared() const { return shared_; } - //! Attaches this object to the given solver. - virtual bool attach(Solver& s) = 0; - //! Shall activate the minimize constraint by integrating bounds stored in the shared data object. - virtual bool integrate(Solver& s) = 0; - //! Shall relax this constraint (i.e. remove any bounds). - /*! - * If reset is true, shall also remove search-path related state. - */ - virtual bool relax(Solver& s, bool reset) = 0; - //! Shall commit the model in s to the shared data object. - /*! - * The return value indicates whether the model is valid w.r.t the - * costs stored in the shared data object. - */ - virtual bool handleModel(Solver& s) = 0; - //! Shall handle the unsatisfiable path in s. - virtual bool handleUnsat(Solver& s, bool upShared, LitVec& restore) = 0; - virtual bool supportsSplitting() const { return true; } - // base interface - void destroy(Solver*, bool); - Constraint* cloneAttach(Solver&) { return 0; } + using SharedData = SharedMinimizeData; + using SharedDataP = const SharedData*; + //! Returns a pointer to the shared representation of this constraint. + [[nodiscard]] SharedDataP shared() const { return shared_; } + //! Attaches this object to the given solver. + virtual bool attach(Solver& s) = 0; + //! Shall activate the minimize constraint by integrating bounds stored in the shared data object. + virtual bool integrate(Solver& s) = 0; + //! Shall relax this constraint (i.e. remove any bounds). + /*! + * If reset is true, shall also remove search-path related state. + */ + virtual bool relax(Solver& s, bool reset) = 0; + //! Shall commit the model in s to the shared data object. + /*! + * The return value indicates whether the model is valid w.r.t the + * costs stored in the shared data object. + */ + virtual bool handleModel(Solver& s) = 0; + //! Shall handle the unsatisfiable path in s. + virtual bool handleUnsat(Solver& s, bool upShared, LitVec& restore) = 0; + [[nodiscard]] virtual bool supportsSplitting() const { return true; } + // base interface + void destroy(Solver*, bool) override; + Constraint* cloneAttach(Solver&) override { return nullptr; } + protected: - MinimizeConstraint(SharedData* s); - ~MinimizeConstraint(); - void reportLower(Solver& s, uint32 level, wsum_t low) const; - bool prepare(Solver& s, bool useTag); - SharedData* shared_; // common shared data - Literal tag_; // (optional) literal for tagging reasons + MinimizeConstraint(SharedData* s); + ~MinimizeConstraint() override; + bool prepare(Solver& s, bool useTag); + SharedData* shared_; // common shared data + Literal tag_; // (optional) literal for tagging reasons }; //! Minimization via branch and bound. @@ -317,113 +345,120 @@ class MinimizeConstraint : public Constraint { */ class DefaultMinimize : public MinimizeConstraint { public: - explicit DefaultMinimize(SharedData* d, const OptParams& params); - // base interface - //! Attaches the constraint to the given solver. - /*! - * \pre s.decisionLevel() == 0 - * \note If either MinimizeMode_t::enumOpt or hierarchical optimization - * is active, s.sharedContext()->tagLiteral() shall be an unassigned literal. - */ - bool attach(Solver& s); - bool integrate(Solver& s) { return integrateBound(s); } - bool relax(Solver&, bool reset) { return relaxBound(reset); } - bool handleModel(Solver& s) { commitUpperBound(s); return true; } - bool handleUnsat(Solver& s, bool up, LitVec& out); - // constraint interface - PropResult propagate(Solver& s, Literal p, uint32& data); - void undoLevel(Solver& s); - void reason(Solver& s, Literal p, LitVec& lits); - bool minimize(Solver& s, Literal p, CCMinRecursive* r); - void destroy(Solver*, bool); - // own interface - bool active() const { return *opt() != SharedData::maxBound(); } - //! Number of minimize statements contained in this constraint. - uint32 numRules()const { return size_; } - //! Tries to integrate the next tentative bound into this constraint. - /*! - * Starting from the current optimum stored in the shared data object, - * the function tries to integrate the next candidate bound into - * this constraint. - * - * \return The function returns true if integration succeeded. Otherwise - * false is returned and s.hasConflict() is true. - * - * \note If integrateBound() failed, the bound of this constraint - * is relaxed. The caller has to resolve the conflict first - * and then integrateBound() shall be called again. - * - * \note The caller has to call s.propagate() to propagate any new information - * from the new bound. - * - * \note If the tag literal (if any) is not true, the minimize constraint first assumes it. - */ - bool integrateBound(Solver& s); + explicit DefaultMinimize(SharedData* d, const OptParams& params); + // base interface + //! Attaches the constraint to the given solver. + /*! + * \pre s.decisionLevel() == 0 + * \note If either MinimizeMode::enumOpt or hierarchical optimization + * is active, s.sharedContext()->tagLiteral() shall be an unassigned literal. + */ + bool attach(Solver& s) override; + bool integrate(Solver& s) override { return integrateBound(s); } + bool relax(Solver&, bool reset) override { return relaxBound(reset); } + bool handleModel(Solver& s) override { + commitUpperBound(s); + return true; + } + bool handleUnsat(Solver& s, bool up, LitVec& out) override; + // constraint interface + PropResult propagate(Solver& s, Literal p, uint32_t& data) override; + void undoLevel(Solver& s) override; + void reason(Solver& s, Literal p, LitVec& lits) override; + bool minimize(Solver& s, Literal p, CCMinRecursive* r) override; + void destroy(Solver*, bool) override; + // own interface + [[nodiscard]] bool active() const { return *opt() != SharedData::maxBound(); } + //! Number of minimize statements contained in this constraint. + [[nodiscard]] uint32_t numRules() const { return size_; } + //! Tries to integrate the next tentative bound into this constraint. + /*! + * Starting from the current optimum stored in the shared data object, + * the function tries to integrate the next candidate bound into + * this constraint. + * + * \return The function returns true if integration succeeded. Otherwise, + * false is returned and s.hasConflict() is true. + * + * \note If integrateBound() failed, the bound of this constraint + * is relaxed. The caller has to resolve the conflict first + * and then integrateBound() shall be called again. + * + * \note The caller has to call s.propagate() to propagate any new information + * from the new bound. + * + * \note If the tag literal (if any) is not true, the minimize constraint first assumes it. + */ + bool integrateBound(Solver& s); + + //! Sets the current local sum as the global optimum (upper bound). + /*! + * commitUpperBound() shall be called whenever the solver finds a model. + * The current local sum is recorded as new optimum in the shared data object. + * Once the local bound is committed, the function integrateBound() has to be + * called in order to continue optimization. + */ + void commitUpperBound(const Solver& s); + //! Sets the current local upper bound as the lower bound of this constraint. + /*! + * commitLowerBound() shall be called on unsat. The function stores + * the local upper bound as new lower bound in this constraint. If upShared is true, + * the lower bound is also copied to the shared data object. + * + * Once the local bound is committed, the function integrateBound() has to be + * called in order to continue optimization. + * \return false if search-space is exceeded w.r.t this constraint. + */ + bool commitLowerBound(Solver& s, bool upShared); - //! Sets the current local sum as the global optimum (upper bound). - /*! - * commitUpperBound() shall be called whenever the solver finds a model. - * The current local sum is recorded as new optimum in the shared data object. - * Once the local bound is committed, the function integrateBound() has to be - * called in order to continue optimization. - */ - void commitUpperBound(const Solver& s); - //! Sets the current local upper bound as the lower bound of this constraint. - /*! - * commitLowerBound() shall be called on unsat. The function stores - * the local upper bound as new lower bound in this constraint. If upShared is true, - * the lower bound is also copied to the shared data object. - * - * Once the local bound is committed, the function integrateBound() has to be - * called in order to continue optimization. - * \return false if search-space is exceeded w.r.t this constraint. - */ - bool commitLowerBound(Solver& s, bool upShared); + //! Removes the local upper bound of this constraint and therefore disables it. + /*! + * If full is true, also removes search-path related state. + */ + bool relaxBound(bool full = false); - //! Removes the local upper bound of this constraint and therefore disables it. - /*! - * If full is true, also removes search-path related state. - */ - bool relaxBound(bool full = false); + [[nodiscard]] bool more() const { return step_.lev != size_; } - bool more() const { return step_.lev != size_; } + // FOR TESTING ONLY! + [[nodiscard]] Wsum_t sum(uint32_t i, bool adjust) const { return sum()[i] + (adjust ? shared_->adjust(i) : 0); } - // FOR TESTING ONLY! - wsum_t sum(uint32 i, bool adjust) const { return sum()[i] + (adjust ? shared_->adjust(i):0); } private: - enum PropMode { propagate_new_sum, propagate_new_opt }; - union UndoInfo; - typedef const WeightLiteral* Iter; - ~DefaultMinimize(); - // bound operations - wsum_t* opt() const { return bounds_; } - wsum_t* sum() const { return bounds_ + size_; } - wsum_t* temp()const { return bounds_ + (size_*2); } - wsum_t* end() const { return bounds_ + (size_*3); } - void assign(wsum_t* lhs, wsum_t* rhs) const; - bool greater(wsum_t* lhs, wsum_t* rhs, uint32 len, uint32& aLev) const; - // propagation & undo - uint32 lastUndoLevel(const Solver& s) const; - bool litSeen(uint32 i) const; - bool propagateImpl(Solver& s, PropMode m); - uint32 computeImplicationSet(const Solver& s, const WeightLiteral& it, uint32& undoPos); - void pushUndo(Solver& s, uint32 litIdx); - bool updateBounds(bool applyStep); - // step - wsum_t& stepLow() const { return *(end() + step_.lev); } - void stepInit(uint32 n); - wsum_t* bounds_; // [upper,sum,temp[,lower]] - Iter pos_; // position of literal to look at next - UndoInfo* undo_; // one "seen" flag for each literal + - uint32 undoTop_; // undo stack holding assigned literals - uint32 posTop_; // stack of saved "look at" positions - const uint32 size_; // number of rules - uint32 actLev_; // first level to look at when comparing bounds - struct Step { // how to reduce next tentative bound - uint32 size; // size of step - uint32 lev : 30; // level on which step is applied - uint32 type: 2; // type of step (one of OptParams::BBAlgo) - } step_; + enum PropMode { propagate_new_sum, propagate_new_opt }; + struct UndoInfo; + using Iter = const WeightLiteral*; + using BoundPtr = std::unique_ptr; + using UndoPtr = std::unique_ptr; + ~DefaultMinimize() override; + // bound operations + [[nodiscard]] Wsum_t* opt() const { return bounds_.get(); } + [[nodiscard]] Wsum_t* sum() const { return opt() + size_; } + [[nodiscard]] Wsum_t* temp() const { return sum() + size_; } + [[nodiscard]] Wsum_t* end() const { return temp() + size_; } + void assign(Wsum_t* lhs, const Wsum_t* rhs) const; + static bool greater(Wsum_t* lhs, Wsum_t* rhs, uint32_t len, uint32_t& aLev); + // propagation & undo + [[nodiscard]] uint32_t lastUndoLevel(const Solver& s) const; + [[nodiscard]] bool litSeen(uint32_t i) const; + bool propagateImpl(Solver& s, PropMode m); + uint32_t computeImplicationSet(const Solver& s, const WeightLiteral& it, uint32_t& undoPos); + void pushUndo(Solver& s, uint32_t litIdx); + [[nodiscard]] auto viewUndo(const Solver& s, Literal p) const -> SpanView; + bool updateBounds(bool applyStep); + // step + [[nodiscard]] Wsum_t& stepLow() const { return *(end() + step_.lev); } + void stepInit(uint32_t n); + BoundPtr bounds_; // [upper,sum,temp[,lower]] + Iter pos_; // position of literal to look at next + UndoPtr undo_; // one "seen" flag for each literal + + uint32_t undoTop_; // undo stack holding assigned literals + uint32_t posTop_; // stack of saved "look at" positions + const uint32_t size_; // number of rules + uint32_t actLev_; // first level to look at when comparing bounds + struct Step { // how to reduce next tentative bound + uint32_t size; // size of step + uint32_t lev : 30; // level on which step is applied + uint32_t type : 2; // type of step (one of OptParams::BBAlgo) + } step_{}; }; //! Minimization via unsat cores. @@ -432,152 +467,157 @@ class DefaultMinimize : public MinimizeConstraint { */ class UncoreMinimize : public MinimizeConstraint { public: - // constraint interface - PropResult propagate(Solver& s, Literal p, uint32& data); - void reason(Solver& s, Literal p, LitVec& lits); - void destroy(Solver*, bool); - bool simplify(Solver& s, bool reinit = false); - // base interface - bool attach(Solver& s); - bool integrate(Solver& s); - bool relax(Solver&, bool reset); - bool valid(Solver& s); - bool handleModel(Solver& s); - bool handleUnsat(Solver& s, bool up, LitVec& out); - bool supportsSplitting() const { return false; } + // constraint interface + PropResult propagate(Solver& s, Literal p, uint32_t& data) override; + void reason(Solver& s, Literal p, LitVec& lits) override; + void destroy(Solver*, bool) override; + bool simplify(Solver& s, bool reinit = false) override; + // base interface + bool attach(Solver& s) override; + bool integrate(Solver& s) override; + bool relax(Solver&, bool reset) override; + bool valid(Solver& s) override; + bool handleModel(Solver& s) override; + bool handleUnsat(Solver& s, bool up, LitVec& out) override; + [[nodiscard]] bool supportsSplitting() const override { return false; } + private: - friend class SharedMinimizeData; - explicit UncoreMinimize(SharedData* d, const OptParams& params); - typedef DefaultMinimize* EnumPtr; - struct LitData { - LitData(weight_t w, bool as, uint32 c) : weight(w), coreId(c), assume((uint32)as), flag(0u) {} - weight_t weight; - uint32 coreId : 30; - uint32 assume : 1; - uint32 flag : 1; - }; - struct LitPair { - LitPair(Literal p, uint32 dataId) : lit(p), id(dataId) {} - Literal lit; - uint32 id; - }; - struct Core { - Core(WeightConstraint* c, weight_t b, weight_t w) : con(c), bound(b), weight(w) {} - uint32 size() const; - Literal at(uint32 i) const; - Literal tag() const; - WeightConstraint* con; - weight_t bound; - weight_t weight; - }; - struct WCTemp { - typedef WeightLitVec WLitVec; - typedef WeightLiteral* Ptr; - void start(weight_t b){ lits.clear(); bound = b; } - void add(Solver& s, Literal p); - bool unsat() const { return bound > 0 && static_cast(bound) > static_cast(lits.size()); } - uint32 size() const { return sizeVec(lits); } - Ptr begin() { return size() ? &lits[0] : 0; } - weight_t bound; - WLitVec lits; - }; - typedef PodVector::type LitTable; - typedef PodVector::type CoreTable; - typedef PodVector::type ConTable; - typedef PodVector::type LitSet; - class Todo { - public: - typedef LitSet::const_iterator const_iterator; - Todo() { clear(); } - const_iterator begin() const { return lits_.begin(); } - const_iterator end() const { return lits_.end(); } - uint32 size() const { return sizeVec(lits_); } - weight_t weight() const { return minW_; } - bool shrink() const { return next_ != 0u; } - void clear(bool resetShrink = true); - void add(const LitPair& x, weight_t w); - void terminate(); - bool shrinkNext(UncoreMinimize& self, ValueRep result); - void shrinkPush(UncoreMinimize& self, Solver& s); - void shrinkReset(); - private: - bool subsetNext(UncoreMinimize& self, ValueRep result); - LitSet lits_; - weight_t minW_; - // shrinking - uint32 last_; - uint32 next_; - uint32 step_; - LitSet core_; - }; - // literal and core management - bool hasCore(const LitData& x) const { return x.coreId != 0; } - bool flagged(uint32 id) const { return litData_[id-1].flag != 0u; } - void setFlag(uint32 id, bool f) { litData_[id-1].flag = uint32(f); } - LitData& getData(uint32 id) { return litData_[id-1];} - Core& getCore(const LitData& x) { return open_[x.coreId-1]; } - LitPair newAssumption(Literal p, weight_t w); - Literal newLit(Solver& s); - void releaseLits(); - bool addCore(Solver& s, const LitPair* lits, uint32 size, weight_t w, bool updateLower); - uint32 allocCore(WeightConstraint* con, weight_t bound, weight_t weight, bool open); - bool closeCore(Solver& s, LitData& x, bool sat); - bool addOll(Solver& s, const LitPair* lits, uint32 size, weight_t w); - bool addOllCon(Solver& s, const WCTemp& wc, weight_t w); - bool addK(Solver& s, uint32 K, const LitPair* lits, uint32 size, weight_t w); - enum CompType { comp_disj = 0, comp_conj = 1 }; - bool addPmr(Solver& s, const LitPair* lits, uint32 size, weight_t w); - bool addPmrCon(CompType t, Solver& s, Literal head, Literal body1, Literal body2); - bool addConstraint(Solver& s, WeightLiteral* lits, uint32 size, weight_t bound); - bool addImplication(Solver& s, Literal a, Literal b, bool concise); - // algorithm - void init(); - uint32 initRoot(Solver& s); - bool initLevel(Solver& s); - uint32 analyze(Solver& s); - bool addNext(Solver& s, bool allowInit = true); - bool pushPath(Solver& s); - bool popPath(Solver& s, uint32 dl); - bool fixLit(Solver& s, Literal p); - bool fixLevel(Solver& s); - void detach(Solver* s, bool b); - bool pushTrim(Solver& s); - void resetTrim(Solver& s); - bool push(Solver& s, Literal p, uint32 id); - wsum_t* computeSum(const Solver& s) const; - bool validLowerBound() const { - wsum_t cmp = lower_ - upper_; - return cmp < 0 || (cmp == 0 && level_ == shared_->maxLevel() && !shared_->checkNext()); - } - // data - EnumPtr enum_; // for supporting (optimal) model enumeration in parallel mode - wsum_t* sum_; // costs of active model - LitTable litData_; // data for active literals (tag lits for cores + lits from active minimize) - CoreTable open_; // open cores, i.e. relaxable and referenced by an assumption - ConTable closed_; // closed cores represented as weight constraints - LitSet assume_; // current set of assumptions - Todo todo_; // core(s) not yet represented as constraint - LitVec fix_; // set of fixed literals - LitVec conflict_; // temporary: conflicting set of assumptions - WCTemp temp_; // temporary: used for creating weight constraints - wsum_t lower_; // lower bound of active level - wsum_t upper_; // upper bound of active level - uint32 auxInit_; // number of solver aux vars on attach - uint32 auxAdd_; // number of aux vars added for cores - uint32 gen_; // active generation - uint32 level_ : 28;// active level - uint32 next_ : 1;// update because of model - uint32 disj_ : 1;// preprocessing active? - uint32 path_ : 1;// push path? - uint32 init_ : 1;// init constraint? - weight_t actW_; // active weight limit (only weighted minimization with stratification) - weight_t nextW_; // next weight limit (only weighted minimization with stratification) - uint32 eRoot_; // saved root level of solver (initial gp) - uint32 aTop_; // saved assumption level (added by us) - uint32 freeOpen_; // head of open core free list - OptParams options_; // active options + friend class SharedMinimizeData; + explicit UncoreMinimize(SharedData* d, const OptParams& params); + using EnumPtr = DefaultMinimize*; + using BoundPtr = std::unique_ptr; + struct LitData { + LitData(Weight_t w, bool as, uint32_t c) : weight(w), coreId(c), assume(static_cast(as)), flag(0u) {} + Weight_t weight; + uint32_t coreId : 30; + uint32_t assume : 1; + uint32_t flag : 1; + }; + struct LitPair { + LitPair(Literal p, uint32_t dataId) : lit(p), id(dataId) {} + Literal lit; + uint32_t id; + }; + struct Core { + Core(WeightConstraint* c, Weight_t b, Weight_t w) : con(c), bound(b), weight(w) {} + [[nodiscard]] uint32_t size() const; + [[nodiscard]] Literal at(uint32_t i) const; + [[nodiscard]] Literal tag() const; + WeightConstraint* con; + Weight_t bound; + Weight_t weight; + }; + struct WCTemp { + using WLitVec = WeightLitVec; + using Ptr = WeightLiteral*; + void start(Weight_t b) { + lits.clear(); + bound = b; + } + void add(const Solver& s, Literal p); + [[nodiscard]] bool unsat() const { return bound > 0 && static_cast(bound) > size32(lits); } + [[nodiscard]] uint32_t size() const { return size32(lits); } + Ptr data() { return lits.data(); } + Weight_t bound; + WLitVec lits; + }; + using LitTable = PodVector_t; + using CoreTable = PodVector_t; + using ConTable = PodVector_t; + using LitSet = PodVector_t; + using LitView = SpanView; + class Todo { + public: + Todo() = default; + [[nodiscard]] uint32_t size() const { return size32(lits_); } + [[nodiscard]] LitView view() const { return lits_; } + [[nodiscard]] LitView last(uint32_t n) const { return {lits_.data() + (lits_.size() - n), n}; } + [[nodiscard]] Weight_t weight() const { return minW_; } + [[nodiscard]] bool shrink() const { return next_ != 0u; } + void clear(bool resetShrink = true); + void add(const LitPair& x, Weight_t w); + void terminate(); + bool shrinkNext(UncoreMinimize& self, Val_t result); + void shrinkPush(UncoreMinimize& self, Solver& s); + void shrinkReset(); + + private: + bool subsetNext(UncoreMinimize& self, Val_t result); + LitSet lits_; + Weight_t minW_ = weight_min; + // shrinking + uint32_t last_ = 0; + uint32_t next_ = 0; + uint32_t step_ = 0; + LitSet core_; + }; + // literal and core management + [[nodiscard]] static bool hasCore(const LitData& x) { return x.coreId != 0; } + [[nodiscard]] bool flagged(uint32_t id) const { return litData_[id - 1].flag != 0u; } + void setFlag(uint32_t id, bool f) { litData_[id - 1].flag = static_cast(f); } + LitData& getData(uint32_t id) { return litData_[id - 1]; } + + Core& getCore(const LitData& x) { return open_[x.coreId - 1]; } + LitPair newAssumption(Literal p, Weight_t w); + Literal newLit(Solver& s); + void releaseLits(); + bool addCore(Solver& s, LitView lits, Weight_t w, bool updateLower); + uint32_t allocCore(WeightConstraint* con, Weight_t bound, Weight_t weight, bool open); + bool closeCore(Solver& s, LitData& x, bool sat); + bool addOll(Solver& s, LitView lits, Weight_t w); + bool addOllCon(Solver& s, WCTemp& wc, Weight_t w); + bool addK(Solver& s, uint32_t k, LitView lits, Weight_t w); + enum CompType { comp_disj = 0, comp_conj = 1 }; + bool addPmr(Solver& s, LitView lits, Weight_t w); + bool addPmrCon(CompType t, Solver& s, Literal head, Literal body1, Literal body2); + bool addConstraint(Solver& s, WeightLiteral* lits, uint32_t size, Weight_t bound); + bool addImplication(Solver& s, Literal a, Literal b, bool concise); + // algorithm + void init(); + uint32_t initRoot(const Solver& s); + bool initLevel(Solver& s); + uint32_t analyze(Solver& s); + bool addNext(Solver& s, bool allowInit = true); + bool pushPath(Solver& s); + bool popPath(Solver& s, uint32_t dl); + bool fixLit(Solver& s, Literal p); + bool fixLevel(Solver& s); + void detach(Solver* s, bool b); + bool pushTrim(Solver& s); + void resetTrim(Solver& s); + bool push(Solver& s, Literal p, uint32_t id); + Wsum_t* computeSum(const Solver& s) const; // NOLINT(modernize-use-nodiscard) + [[nodiscard]] bool validLowerBound() const { + Wsum_t cmp = lower_ - upper_; + return cmp < 0 || (cmp == 0 && level_ == shared_->maxLevel() && not shared_->checkNext()); + } + // data + EnumPtr enum_; // for supporting (optimal) model enumeration in parallel mode + BoundPtr sum_; // costs of active model + LitTable litData_; // data for active literals (tag lits for cores + lits from active minimize) + CoreTable open_; // open cores, i.e. relaxable and referenced by an assumption + ConTable closed_; // closed cores represented as weight constraints + LitSet assume_; // current set of assumptions + Todo todo_; // core(s) not yet represented as constraint + LitVec fix_; // set of fixed literals + LitVec conflict_; // temporary: conflicting set of assumptions + WCTemp temp_; // temporary: used for creating weight constraints + Wsum_t lower_; // lower bound of active level + Wsum_t upper_; // upper bound of active level + uint32_t auxInit_; // number of solver aux vars on attach + uint32_t auxAdd_; // number of aux vars added for cores + uint32_t gen_; // active generation + uint32_t level_ : 28; // active level + uint32_t next_ : 1; // update because of model + uint32_t disj_ : 1; // preprocessing active? + uint32_t path_ : 1; // push path? + uint32_t init_ : 1; // init constraint? + Weight_t actW_; // active weight limit (only weighted minimization with stratification) + Weight_t nextW_; // next weight limit (only weighted minimization with stratification) + uint32_t eRoot_; // saved root level of solver (initial gp) + uint32_t aTop_; // saved assumption level (added by us) + uint32_t freeOpen_; // head of open core free list + OptParams options_; // active options }; } // end namespace Clasp - -#endif diff --git a/clasp/model_enumerators.h b/clasp/model_enumerators.h index 1f6def9..484ae0a 100644 --- a/clasp/model_enumerators.h +++ b/clasp/model_enumerators.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,15 +23,9 @@ // //! \file //! \brief Model enumeration with minimization and projection. -#ifndef CLASP_MODEL_ENUMERATORS_H -#define CLASP_MODEL_ENUMERATORS_H - -#ifdef _MSC_VER #pragma once -#endif #include -#include namespace Clasp { @@ -56,7 +50,7 @@ namespace Clasp { * based on the problem at hand. It uses strategy_record, if one of the following holds: * - optimization is active, or * - only one model is requested, or - * - both parallel search as well as projection are active + * - both parallel search and projection are active * . * In all other cases, strategy_auto selects strategy_backtrack. * @@ -64,60 +58,64 @@ namespace Clasp { */ class ModelEnumerator : public Enumerator { public: - //! Enumeration algorithms. - enum Strategy { - strategy_auto = 0, //!< Use strategy best suited to problem. - strategy_backtrack = 1, //!< Use backtrack-based enumeration. - strategy_record = 2 //!< Use nogood-based enumeration. - }; - //! Projective solution enumeration and options. - enum ProjectOptions { - project_enable_simple = 1, //!< Enable projective solution enumeration. - project_use_heuristic = 2, //!< Use heuristic when selecting a literal from a projection nogood. - project_save_progress = 4, //!< Enable progress saving after the first solution was found. - project_enable_full = 6, //!< Enable projective solution enumeration with heuristic and progress saving. - project_dom_lits = 8, //!< In strategy record, project only on true domain literals. - }; - /*! - * \param st Enumeration strategy to apply. - */ - explicit ModelEnumerator(Strategy st = strategy_auto); - ~ModelEnumerator(); + //! Enumeration algorithms. + enum Strategy { + strategy_auto = 0, //!< Use strategy best suited to problem. + strategy_backtrack = 1, //!< Use backtrack-based enumeration. + strategy_record = 2 //!< Use nogood-based enumeration. + }; + //! Projective solution enumeration and options. + enum ProjectOptions { + project_enable_simple = 1, //!< Enable projective solution enumeration. + project_use_heuristic = 2, //!< Use heuristic when selecting a literal from a projection nogood. + project_save_progress = 4, //!< Enable progress saving after the first solution was found. + project_enable_full = 6, //!< Enable projective solution enumeration with heuristic and progress saving. + project_dom_lits = 8, //!< In strategy record, project only on true domain literals. + }; + /*! + * \param st Enumeration strategy to apply. + */ + explicit ModelEnumerator(Strategy st = strategy_auto); + ~ModelEnumerator() override; + + //! Configure strategy. + /*! + * \param st Enumeration strategy to use. + * \param projection The set of ProjectOptions to be applied or 0 to disable projective enumeration. + * \param filter Ignore output predicates starting with filter in projective enumeration. + */ + void setStrategy(Strategy st = strategy_auto, uint32_t projection = 0, char filter = '_'); + [[nodiscard]] bool projectionEnabled() const { return projectOpts() != 0; } + [[nodiscard]] bool domRec() const { return Potassco::test_any(projectOpts(), project_dom_lits); } + [[nodiscard]] Strategy strategy() const { return static_cast(opts_.algo); } + [[nodiscard]] bool project(Var_t v) const; - //! Configure strategy. - /*! - * \param st Enumeration strategy to use. - * \param projection The set of ProjectOptions to be applied or 0 to disable projective enumeration. - * \param filter Ignore output predicates starting with filter in projective enumeration. - */ - void setStrategy(Strategy st = strategy_auto, uint32 projection = 0, char filter = '_'); - bool projectionEnabled()const { return projectOpts() != 0; } - bool domRec() const { return (projectOpts() & project_dom_lits) != 0; } - Strategy strategy() const { return static_cast(opts_.algo); } - bool project(Var v) const; protected: - bool supportsRestarts() const { return optimize() || strategy() == strategy_record; } - bool supportsParallel() const { return !projectionEnabled() || strategy() != strategy_backtrack; } - bool supportsSplitting(const SharedContext& problem) const { - return (strategy() == strategy_backtrack || !domRec()) && Enumerator::supportsSplitting(problem); - } - ConPtr doInit(SharedContext& ctx, SharedMinimizeData* m, int numModels); + [[nodiscard]] bool supportsRestarts() const override { return optimize() || strategy() == strategy_record; } + [[nodiscard]] bool supportsParallel() const override { + return not projectionEnabled() || strategy() != strategy_backtrack; + } + [[nodiscard]] bool supportsSplitting(const SharedContext& problem) const override { + return (strategy() == strategy_backtrack || not domRec()) && Enumerator::supportsSplitting(problem); + } + ConPtr doInit(SharedContext& ctx, SharedMinimizeData* m, int numModels) override; + private: - class ModelFinder; - class BacktrackFinder; - class RecordFinder; - typedef PodVector::type WordVec; - void initProjection(SharedContext& ctx); - void addProject(SharedContext& ctx, Var v); - uint32 projectOpts() const { return opts_.proj; } - bool trivial() const { return trivial_; } - WordVec project_; - char filter_; - struct Options { - uint8 proj : 4; - uint8 algo : 2; - } opts_, saved_; - bool trivial_; + class ModelFinder; + class BacktrackFinder; + class RecordFinder; + using Set = Potassco::DynamicBitset; + void initProjection(SharedContext& ctx); + bool initDomRec(SharedContext& ctx); + void addProject(SharedContext& ctx, Var_t v); + [[nodiscard]] uint32_t projectOpts() const { return opts_.proj; } + [[nodiscard]] bool trivial() const { return trivial_; } + Set project_; + char filter_{'_'}; + struct Options { + uint8_t proj : 4; + uint8_t algo : 2; + } opts_{}, saved_{}; + bool trivial_{false}; }; -} -#endif +} // namespace Clasp diff --git a/clasp/mt/mutex.h b/clasp/mt/mutex.h deleted file mode 100644 index ade899c..0000000 --- a/clasp/mt/mutex.h +++ /dev/null @@ -1,54 +0,0 @@ -// -// Copyright (c) 2012-2017 Benjamin Kaufmann -// -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// - -#ifndef CLASP_UTIL_MUTEX_H_INCLUDED -#define CLASP_UTIL_MUTEX_H_INCLUDED - -#include -#include - -namespace Clasp { namespace mt { -using std::mutex; -using std::lock_guard; -using std::unique_lock; -using std::swap; -using std::defer_lock_t; -struct condition_variable : private std::condition_variable { - typedef std::condition_variable base_type; - using base_type::notify_one; - using base_type::notify_all; - using base_type::wait; - - template - inline auto native_handle() -> typename X::native_handle_type { - return X::native_handle(); - } - - inline bool wait_for(unique_lock& lock, double timeInSecs) { - return base_type::wait_for(lock, std::chrono::duration_cast(std::chrono::duration(timeInSecs))) - == std::cv_status::no_timeout; - } -}; -}} -#endif diff --git a/clasp/mt/parallel_solve.h b/clasp/mt/parallel_solve.h index 4d7fb57..e93352c 100644 --- a/clasp/mt/parallel_solve.h +++ b/clasp/mt/parallel_solve.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,30 +21,24 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_PARALLEL_SOLVE_H_INCLUDED -#define CLASP_PARALLEL_SOLVE_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif + #include #if !CLASP_HAS_THREADS #error Thread support required for parallel solving #endif -#include #include +#include #include +#include #include -#include -#include /*! * \file - * \brief Defines classes controlling multi-threaded parallel solving. + * \brief Defines classes controlling multithreaded parallel solving. */ -namespace Clasp { -//! Namespace for types and functions needed for implementing multi-threaded parallel solving. -namespace mt { +//! Namespace for types and functions needed for implementing multithreaded parallel solving. +namespace Clasp::mt { /** * \defgroup mt Multi-threading @@ -58,313 +52,285 @@ class ParallelSolve; //! Options for controlling parallel solving. struct ParallelSolveOptions : BasicSolveOptions { - //! Nogood distribution options. - struct Distribution : Distributor::Policy { - enum Mode { mode_global = 0, mode_local = 1 }; - Distribution(Mode m = mode_global) : Distributor::Policy(), mode(m) {} - Distributor::Policy& policy() { return *this; } - uint32 mode; - }; - ParallelSolveOptions() {} - //! Algorithm options. - struct Algorithm { - //! Possible search strategies. - enum SearchMode { mode_split = 0, mode_compete = 1 }; - Algorithm() : threads(1), mode(mode_compete) {} - uint32 threads; - SearchMode mode; - }; - //! Nogood integration options. - struct Integration { - static const uint32 GRACE_MAX = (1u<<28)-1; - Integration() : grace(1024), filter(filter_gp), topo(topo_all) {} - enum Filter { filter_no = 0, filter_gp = 1, filter_sat = 2, filter_heuristic = 3 }; - enum Topology { topo_all = 0, topo_ring = 1, topo_cube = 2, topo_cubex = 3 }; - uint32 grace : 28; /**< Lower bound on number of shared nogoods to keep. */ - uint32 filter: 2; /**< Filter for integrating shared nogoods (one of Filter). */ - uint32 topo : 2; /**< Integration topology */ - }; - //! Global restart options. - struct GRestarts { - GRestarts():maxR(0) {} - uint32 maxR; - ScheduleStrategy sched; - }; - Integration integrate; //!< Nogood integration options to apply during search. - Distribution distribute;//!< Nogood distribution options to apply during search. - GRestarts restarts; //!< Global restart strategy to apply during search. - Algorithm algorithm; //!< Parallel algorithm to use. - //! Allocates a new solve object. - SolveAlgorithm* createSolveObject() const; - //! Returns the number of threads that can run concurrently on the current hardware. - static uint32 recommendedSolvers() { return Clasp::mt::thread::hardware_concurrency(); } - //! Returns number of maximal number of supported threads. - static uint32 supportedSolvers() { return 64; } - //! Returns the peers of the solver with the given id assuming the given topology. - static uint64 initPeerMask(uint32 sId, Integration::Topology topo, uint32 numThreads); - uint32 numSolver() const { return algorithm.threads; } - void setSolvers(uint32 i) { algorithm.threads = std::max(uint32(1), i); } - bool defaultPortfolio() const { return algorithm.mode == Algorithm::mode_compete; } + //! Nogood distribution options. + struct Distribution : Distributor::Policy { + enum Mode { mode_global = 0, mode_local = 1 }; + Distribution(Mode m = mode_global) : mode(m) {} + Policy& policy() { return *this; } + uint32_t mode; + }; + ParallelSolveOptions() = default; + //! Algorithm options. + struct Algorithm { + //! Possible search strategies. + enum SearchMode { mode_split = 0, mode_compete = 1 }; + uint32_t threads{1}; + SearchMode mode{mode_compete}; + }; + //! Nogood integration options. + struct Integration { + static constexpr uint32_t grace_max = (1u << 28) - 1; + enum Filter { filter_no = 0, filter_gp = 1, filter_sat = 2, filter_heuristic = 3 }; + enum Topology { topo_all = 0, topo_ring = 1, topo_cube = 2, topo_cubex = 3 }; + uint32_t grace : 28 {1024}; /**< Lower bound on number of shared nogoods to keep. */ + uint32_t filter : 2 {filter_gp}; /**< Filter for integrating shared nogoods (one of Filter). */ + uint32_t topo : 2 {topo_all}; /**< Integration topology */ + }; + //! Global restart options. + struct GRestarts { + uint32_t maxR{0}; + ScheduleStrategy sched; + }; + Integration integrate; //!< Nogood integration options to apply during search. + Distribution distribute; //!< Nogood distribution options to apply during search. + GRestarts restarts; //!< Global restart strategy to apply during search. + Algorithm algorithm; //!< Parallel algorithm to use. + //! Allocates a new solve object. + [[nodiscard]] SolveAlgorithm* createSolveObject() const; + //! Returns the number of threads that can run concurrently on the current hardware. + static uint32_t recommendedSolvers() { return Clasp::mt::thread::hardware_concurrency(); } + //! Returns number of maximal number of supported threads. + static uint32_t supportedSolvers() { return 64; } + //! Returns the peers of the solver with the given id assuming the given topology. + static SolverSet initPeerSet(uint32_t sId, Integration::Topology topo, uint32_t numThreads); + //! Returns all peers of the solver with the given id. + static constexpr SolverSet fullPeerSet(uint32_t sId, uint32_t numThreads) { + return SolverSet::fromRep(Potassco::toggle_bit(Potassco::bit_max(numThreads), sId)); + } + [[nodiscard]] uint32_t numSolver() const { return algorithm.threads; } + void setSolvers(uint32_t i) { algorithm.threads = std::max(1u, i); } + [[nodiscard]] bool defaultPortfolio() const { return algorithm.mode == Algorithm::mode_compete; } }; -//! A parallel algorithm for multi-threaded solving with and without search-space splitting. +//! A parallel algorithm for multithreaded solving with and without search-space splitting. /*! - * The class adapts clasp's basic solve algorithm - * to a parallel solve algorithm that solves - * a problem using a given number of threads. + * The class adapts clasp's basic solve algorithm to a parallel solve algorithm + * that solves a problem using a given number of threads. * It supports guiding path based solving, portfolio based solving, as well * as a combination of these two approaches. */ class ParallelSolve : public SolveAlgorithm { public: - explicit ParallelSolve(const ParallelSolveOptions& opts); - ~ParallelSolve(); - // base interface - virtual bool interrupted() const; - virtual void resetSolve(); - virtual void enableInterrupts(); - // own interface - //! Returns the number of active threads. - uint32 numThreads() const; - bool integrateUseHeuristic() const { return test_bit(intFlags_, 31); } - uint32 integrateGrace() const { return intGrace_; } - uint32 integrateFlags() const { return intFlags_; } - uint64 hasErrors() const; - //! Requests a global restart. - void requestRestart(); - bool handleMessages(Solver& s); - bool integrateModels(Solver& s, uint32& mCount); - void pushWork(LitVec* gp); - bool commitModel(Solver& s); - bool commitUnsat(Solver& s); - enum GpType { gp_none = 0, gp_split = 1, gp_fixed = 2 }; + explicit ParallelSolve(const ParallelSolveOptions& opts); + ~ParallelSolve() override; + ParallelSolve(ParallelSolve&&) = delete; + // base interface + [[nodiscard]] bool interrupted() const override; + void resetSolve() override; + void enableInterrupts() override; + // own interface + //! Returns the number of active threads. + [[nodiscard]] uint32_t numThreads() const; + [[nodiscard]] bool integrateUseHeuristic() const { return Potassco::test_bit(intFlags_, 31); } + [[nodiscard]] uint32_t integrateGrace() const { return intGrace_; } + [[nodiscard]] uint32_t integrateFlags() const { return intFlags_; } + [[nodiscard]] uint64_t hasErrors() const; + //! Requests a global restart. + void requestRestart(); + bool handleMessages(Solver& s); + bool integrateModels(Solver& s, uint32_t& mCount); + void pushWork(LitView path); + bool commitModel(Solver& s); + bool commitUnsat(Solver& s); + enum GpType { gp_none = 0, gp_split = 1, gp_fixed = 2 }; + private: - ParallelSolve(const ParallelSolve&); - ParallelSolve& operator=(const ParallelSolve&); - typedef SingleOwnerPtr PathPtr; - enum { masterId = 0 }; - // ------------------------------------------------------------------------------------------- - // Thread setup - void destroyThread(uint32 id); - void allocThread(uint32 id, Solver& s); - int joinThreads(); - // ------------------------------------------------------------------------------------------- - // Algorithm steps - void setIntegrate(uint32 grace, uint8 filter); - void setRestarts(uint32 maxR, const ScheduleStrategy& rs); - bool beginSolve(SharedContext& ctx, const LitVec& assume); - bool doSolve(SharedContext& ctx, const LitVec& assume); - void doStart(SharedContext& ctx, const LitVec& assume); - int doNext(int last); - void doStop(); - void doDetach(); - bool doInterrupt(); - void solveParallel(uint32 id); - void initQueue(); - bool requestWork(Solver& s, PathPtr& out); - void terminate(Solver& s, bool complete); - bool waitOnSync(Solver& s); - void exception(uint32 id, PathPtr& path, int err, const char* what); - void reportProgress(const Event& ev) const; - void reportProgress(const Solver& s, const char* msg) const; - // ------------------------------------------------------------------------------------------- - typedef ParallelSolveOptions::Distribution Distribution; - struct SharedData; - // SHARED DATA - SharedData* shared_; // Shared control data - ParallelHandler** thread_; // Thread-local control data - // READ ONLY - Distribution distribution_; // distribution options - uint32 maxRestarts_; // disable global restarts once reached - uint32 intGrace_ : 30;// grace period for clauses to integrate - uint32 intTopo_ : 2;// integration topology - uint32 intFlags_; // bitset controlling clause integration - bool modeSplit_; + // ------------------------------------------------------------------------------------------- + // Thread setup + void destroyThread(uint32_t id); + void allocThread(uint32_t id, Solver& s); + int joinThreads(); + // ------------------------------------------------------------------------------------------- + // Algorithm steps + void setIntegrate(uint32_t grace, uint8_t filter); + void setRestarts(uint32_t maxR, const ScheduleStrategy& rs); + bool beginSolve(SharedContext& ctx, LitView assume); + bool doSolve(SharedContext& ctx, LitView assume) override; + void doStart(SharedContext& ctx, LitView assume) override; + auto doNext(Val_t last) -> Val_t override; + void doStop() override; + void doDetach() override; + bool doInterrupt() override; + void solveParallel(uint32_t id); + void initQueue(); + bool requestWork(Solver& s, Path& out); + void terminate(const Solver& s, bool complete); + bool waitOnSync(const Solver& s); + void exception(uint32_t id, Path path, int err, const char* what); + void reportProgress(const Event& ev) const; + void reportProgress(const Solver& s, const char* msg) const; + // ------------------------------------------------------------------------------------------- + using Distribution = ParallelSolveOptions::Distribution; + struct SharedData; + using DataPtr = std::unique_ptr; + using HandlerPtr = std::unique_ptr; + using ControlPtr = std::unique_ptr; + // SHARED DATA + DataPtr shared_; // Shared control data + ControlPtr thread_; // Thread-local control data + // READ ONLY + Distribution distribution_; // distribution options + uint32_t maxRestarts_; // disable global restarts once reached + uint32_t intGrace_ : 30; // grace period for clauses to integrate + uint32_t intTopo_ : 2; // integration topology + uint32_t intFlags_; // bitset controlling clause integration + bool modeSplit_; }; //! An event type for debugging messages sent between threads. -struct MessageEvent : SolveEvent { - enum Action { sent, received, completed }; - MessageEvent(const Solver& s, const char* message, Action a, double t = 0.0) - : SolveEvent(s, verbosity_high), msg(message), time(t) { op = (uint32)a; } - const char* msg; // name of message - double time; // only for action completed +struct MessageEvent : SolveEvent { + enum Action { sent, received, completed }; + MessageEvent(const Solver& s, const char* message, Action a, double t = 0.0) + : SolveEvent(this, s, verbosity_high) + , msg(message) + , time(t) { + op = static_cast(a); + } + const char* msg; // name of message + double time; // only for action completed }; //! A per-solver (i.e. thread) class that implements message handling and knowledge integration. /*! - * The class adds itself as a post propagator to the given solver. During propagation + * The class adds itself as a post propagator to the given solver. During propagation, * it checks for new messages and lemmas to integrate. */ -class ParallelHandler : public MessageHandler { +class ParallelHandler final : public MessageHandler { public: - typedef ParallelSolve::GpType GpType; + using GpType = ParallelSolve::GpType; + + //! Creates a new parallel handler to be used in the given solve group. + /*! + * \param ctrl The object controlling the parallel solve operation. + * \param s The solver that is to be controlled by this object. + */ + explicit ParallelHandler(ParallelSolve& ctrl, Solver& s); + ~ParallelHandler() override; + //! Attaches the object's solver to ctx and adds this object as a post propagator. + bool attach(SharedContext& ctx); + //! Removes this object from the list of post propagators of its solver and detaches the solver from ctx. + void detach(SharedContext& ctx, bool fastExit); - //! Creates a new parallel handler to be used in the given solve group. - /*! - * \param ctrl The object controlling the parallel solve operation. - * \param s The solver that is to be controlled by this object. - */ - explicit ParallelHandler(ParallelSolve& ctrl, Solver& s); - ~ParallelHandler(); - //! Attaches the object's solver to ctx and adds this object as a post propagator. - bool attach(SharedContext& ctx); - //! Removes this object from the list of post propagators of its solver and detaches the solver from ctx. - void detach(SharedContext& ctx, bool fastExit); + bool setError(int e); + [[nodiscard]] int error() const; + void setWinner(); + [[nodiscard]] bool winner() const; - bool setError(int e); - int error() const { return (int)error_; } - void setWinner() { win_ = 1; } - bool winner() const { return win_ != 0; } - void setThread(Clasp::mt::thread& x) { assert(!joinable()); x.swap(thread_); assert(joinable()); } + //! True if *this has an associated thread of execution, false otherwise. + [[nodiscard]] bool joinable() const; + void setThread(Clasp::mt::thread x); + //! Waits for the thread of execution associated with *this to finish. + int join(); - //! True if *this has an associated thread of execution, false otherwise. - bool joinable() const { return thread_.joinable(); } - //! Waits for the thread of execution associated with *this to finish. - int join() { if (joinable()) { thread_.join(); } return error(); } + // overridden methods - // overridden methods + //! Integrates new information. + bool propagateFixpoint(Solver& s, PostPropagator*) override; + bool handleMessages() override; + void reset() override; + bool simplify(Solver& s, bool shuffle) override; + //! Checks whether new information has invalidated current model. + bool isModel(Solver& s) override; - //! Integrates new information. - bool propagateFixpoint(Solver& s, PostPropagator*); - bool handleMessages() { return ctrl_->handleMessages(solver()); } - void reset() { up_ = 1; } - bool simplify(Solver& s, bool re); - //! Checks whether new information has invalidated current model. - bool isModel(Solver& s); + // own interface + bool isModelLocked(Solver& s); - // own interface - bool isModelLocked(Solver& s); + //! Returns true if handler has a guiding path. + [[nodiscard]] bool hasPath() const; + [[nodiscard]] bool disjointPath() const; + void setGpType(GpType t); - // TODO: make functions virtual once necessary + //! Entry point for solving the given guiding path. + /*! + * \param solve The object used for solving. + * \param type The guiding path's type. + * \param restart Request restart after restart number of conflicts. + */ + Val_t solveGP(BasicSolve& solve, GpType type, uint64_t restart); - //! Returns true if handler's guiding path is disjoint from all others. - bool disjointPath() const { return gp_.type == ParallelSolve::gp_split; } - //! Returns true if handler has a guiding path. - bool hasPath() const { return gp_.type != ParallelSolve::gp_none; } - void setGpType(GpType t) { gp_.type = t; } + /*! + * \name Message handlers + * \note + * Message handlers are intended as callbacks for ParallelSolve::handleMessages(). + * They shall not change the assignment of the solver object. + */ + //@{ - //! Entry point for solving the given guiding path. - /*! - * \param solve The object used for solving. - * \param type The guiding path's type. - * \param restart Request restart after restart number of conflicts. - */ - ValueRep solveGP(BasicSolve& solve, GpType type, uint64 restart); + //! Algorithm is about to terminate. + /*! + * Removes this object from the solver's list of post propagators. + */ + void handleTerminateMessage(); - /*! - * \name Message handlers - * \note - * Message handlers are intended as callbacks for ParallelSolve::handleMessages(). - * They shall not change the assignment of the solver object. - */ - //@{ + //! Request for split. + /*! + * Splits off a new guiding path and adds it to the control object. + * \pre The guiding path of this object is "splittable" + */ + void handleSplitMessage(); - //! Algorithm is about to terminate. - /*! - * Removes this object from the solver's list of post propagators. - */ - void handleTerminateMessage(); + //! Request for (global) restart. + /*! + * \return true if restart is valid, else false. + */ + bool handleRestartMessage(); - //! Request for split. - /*! - * Splits off a new guiding path and adds it to the control object. - * \pre The guiding path of this object is "splittable" - */ - void handleSplitMessage(); + Solver& solver(); + //@} - //! Request for (global) restart. - /*! - * \return true if restart is valid, else false. - */ - bool handleRestartMessage(); + void* operator new(std::size_t count); + void operator delete(void* ptr, std::size_t sz); - Solver& solver() { return *solver_; } - //@} private: - void add(ClauseHead* h); - void clearDB(Solver* s); - bool integrate(Solver& s); - typedef PodVector::type ClauseDB; - typedef SharedLiterals** RecBuffer; - struct GP { - uint64 restart; // don't give up before restart number of conflicts - uint32 modCount; // integration counter for synchronizing models - GpType type; // type of gp - void reset(uint64 r = UINT64_MAX, GpType t = ParallelSolve::gp_none) { - restart = r; - modCount = 0; - type = t; - } - }; - enum { RECEIVE_BUFFER_SIZE = 32 }; - Clasp::mt::thread thread_; // active thread or empty for master - GP gp_; // active guiding path - ParallelSolve* ctrl_; // message source - Solver* solver_; // associated solver - RecBuffer received_; // received clauses not yet integrated - ClauseDB integrated_; // integrated clauses - uint32 recEnd_; // where to put next received clause - uint32 intEnd_; // where to put next clause - uint32 error_:28; // error code or 0 if ok - uint32 win_ : 1; // 1 if thread was the first to terminate the search - uint32 up_ : 1; // 1 if next propagate should check for new lemmas/models - uint32 act_ : 1; // 1 if gp is active - uint32 lbd_ : 1; // 1 if integrate should compute lbds + using ClauseDB = PodVector_t; + using RecBuffer = std::unique_ptr; + void add(ClauseHead* h); + void clearDB(Solver* s); + bool integrate(Solver& s); + struct GP { + uint64_t restart = UINT64_MAX; // don't give up before restart number of conflicts + uint32_t modCount = 0; // integration counter for synchronizing models + GpType type = ParallelSolve::gp_none; // type of gp + }; + thread thread_; // active thread or empty for master + GP gp_; // active guiding path + LitVec temp_; // buffer for splitting + ParallelSolve* ctrl_; // message source + Solver* solver_; // associated solver + RecBuffer received_; // received clauses not yet integrated + ClauseDB integrated_; // integrated clauses + uint32_t recEnd_; // where to put next received clause + uint32_t intEnd_; // where to put next clause + uint32_t error_ : 28; // error code or 0 if ok + uint32_t win_ : 1; // 1 if thread was the first to terminate the search + uint32_t up_ : 1; // 1 if next propagate should check for new lemmas/models + uint32_t act_ : 1; // 1 if gp is active + uint32_t lbd_ : 1; // 1 if integrate should compute lbds }; //! A class that uses a global list to exchange nogoods between threads. -class GlobalDistribution : public Distributor { +class GlobalDistribution final : public Distributor { public: - explicit GlobalDistribution(const Policy& p, uint32 maxShare, uint32 topo); - ~GlobalDistribution(); - uint32 receive(const Solver& in, SharedLiterals** out, uint32 maxOut); - void publish(const Solver& source, SharedLiterals* n); + explicit GlobalDistribution(const Policy& p, uint32_t maxShare, uint32_t topo); + ~GlobalDistribution() override; + uint32_t receive(const Solver& in, SharedLiterals** out, uint32_t maxOut) override; + void publish(const Solver& source, SharedLiterals* n) override; + private: - void release(); - struct ClausePair { - ClausePair(uint32 sId = UINT32_MAX, SharedLiterals* x = 0) : sender(sId), lits(x) {} - uint32 sender; - SharedLiterals* lits; - }; - class Queue : public MultiQueue { - public: - typedef MultiQueue base_type; - using base_type::publish; - Queue(uint32 m) : base_type(m) {} - }; - struct ThreadInfo { - uint64 peerMask; - union { - Queue::ThreadId id; - uint64 rep; - }; - char pad[64 - (sizeof(uint64)*2)]; - }; - Queue::ThreadId& getThreadId(uint32 sId) const { return threadId_[sId].id; } - uint64 getPeerMask(uint32 sId) const { return threadId_[sId].peerMask; } - Queue* queue_; - ThreadInfo* threadId_; + class Queue; + std::unique_ptr queue_; }; //! A class that uses thread-local lists to exchange nogoods between threads. -class LocalDistribution : public Distributor { +class LocalDistribution final : public Distributor { public: - explicit LocalDistribution(const Policy& p, uint32 maxShare, uint32 topo); - ~LocalDistribution(); - uint32 receive(const Solver& in, SharedLiterals** out, uint32 maxOut); - void publish(const Solver& source, SharedLiterals* n); + explicit LocalDistribution(const Policy& p, uint32_t maxShare, uint32_t topo); + ~LocalDistribution() override; + uint32_t receive(const Solver& in, SharedLiterals** out, uint32_t maxOut) override; + void publish(const Solver& source, SharedLiterals* n) override; + private: - typedef Detail::RawStack RawStack; - typedef MPSCPtrQueue::Node QNode; - QNode* allocNode(uint32 tId, SharedLiterals* clause); - void freeNode(uint32 tId, QNode* n) const; - struct ThreadData { - MPSCPtrQueue received; // queue holding received clauses - uint64 peers; // set of peers from which this thread receives clauses - QNode sentinel; // sentinel node for simplifying queue impl - QNode* free; // local free list - only accessed by this thread - }** thread_; // one entry for each thread - RawStack blocks_; // allocated node blocks - uint32 numThread_; // number of threads, i.e. size of array thread_ + struct ThreadData; + using ThreadPtr = std::unique_ptr; + using ThreadArray = std::unique_ptr; + ThreadArray thread_; // one entry for each thread + uint32_t numThread_; // number of threads, i.e. size of array thread_ }; //@} -} } -#endif - +} // namespace Clasp::mt diff --git a/clasp/mt/thread.h b/clasp/mt/thread.h index 1a4b13f..23a60c7 100644 --- a/clasp/mt/thread.h +++ b/clasp/mt/thread.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2010-2017 Benjamin Kaufmann +// Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,13 +21,26 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // +#pragma once -#ifndef CLASP_UTIL_THREAD_H_INCLUDED -#define CLASP_UTIL_THREAD_H_INCLUDED - +#include +#include #include -namespace Clasp { namespace mt { + +namespace Clasp::mt { +using std::condition_variable; +using std::defer_lock_t; +using std::lock_guard; +using std::mutex; +using std::swap; using std::thread; -namespace this_thread { using std::this_thread::yield; } -}} -#endif +using std::unique_lock; + +constexpr std::chrono::milliseconds toMillis(double seconds) { + return std::chrono::duration_cast(std::chrono::duration(seconds)); +} + +namespace this_thread { +using std::this_thread::yield; +} +} // namespace Clasp::mt diff --git a/clasp/parser.h b/clasp/parser.h index 5dbd7ac..761d953 100644 --- a/clasp/parser.h +++ b/clasp/parser.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2014-2017 Benjamin Kaufmann +// Copyright (c) 2014-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,19 +21,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_PARSER_H_INCLUDED -#define CLASP_PARSER_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include -#include #include #include -#include + #include + /*! * \file * \brief Defines parsers for supported input formats. @@ -52,67 +46,70 @@ ProblemType detectProblemType(std::istream& prg); //! Parse additional information in symbol table/comments. struct ParserOptions { - //! Supported parser extensions. - enum Extension { - parse_heuristic = 1u, //!< Parse heuristic info in smodels, dimacs, and pb format. - parse_acyc_edge = 2u, //!< Parse acyc info in smodels, dimacs, and pb format. - parse_minimize = 4u, //!< Parse cost function in dimacs format. - parse_project = 8u, //!< Parse project directive in dimacs and pb format. - parse_assume = 16u,//!< Parse assumption directive in dimacs and pb format. - parse_output = 32u,//!< Parse output directive in dimacs and pb format. - parse_full = 63u, - parse_maxsat = 128u//!< Parse dimacs as MaxSAT problem - }; - ParserOptions() : set(0) {} - ParserOptions& enableHeuristic() { set |= parse_heuristic; return *this; } - ParserOptions& enableAcycEdges() { set |= parse_acyc_edge; return *this; } - ParserOptions& enableMinimize() { set |= parse_minimize; return *this; } - ParserOptions& enableProject() { set |= parse_project; return *this; } - ParserOptions& enableAssume() { set |= parse_assume; return *this; } - ParserOptions& enableOutput() { set |= parse_output; return *this; } - ParserOptions& assign(uint8 f, bool b) { - if (b) { set |= f; } - else { set &= ~f; } - return *this; - } - bool isEnabled(Extension e) const { return (set & static_cast(e)) != 0u; } - bool anyOf(uint8 f) const { return (set & f) != 0u; } - uint8 set; + //! Supported parser extensions. + enum Extension : uint8_t { + parse_heuristic = 1u, //!< Parse heuristic info in smodels, dimacs, and pb format. + parse_acyc_edge = 2u, //!< Parse acyc info in smodels, dimacs, and pb format. + parse_minimize = 4u, //!< Parse cost function in dimacs format. + parse_project = 8u, //!< Parse project directive in dimacs and pb format. + parse_assume = 16u, //!< Parse assumption directive in dimacs and pb format. + parse_output = 32u, //!< Parse output directive in dimacs and pb format. + parse_full = 63u, + parse_maxsat = 128u //!< Parse dimacs as MaxSAT problem + }; + constexpr ParserOptions() = default; + [[nodiscard]] bool isEnabled(Extension e) const { return Potassco::test_mask(set, Potassco::to_underlying(e)); } + [[nodiscard]] bool anyOf(uint8_t f) const { return Potassco::test_any(set, f); } + + ParserOptions& enableHeuristic() { return enable(parse_heuristic); } + ParserOptions& enableAcycEdges() { return enable(parse_acyc_edge); } + ParserOptions& enableMinimize() { return enable(parse_minimize); } + ParserOptions& enableProject() { return enable(parse_project); } + ParserOptions& enableAssume() { return enable(parse_assume); } + ParserOptions& enableOutput() { return enable(parse_output); } + ParserOptions& assign(uint8_t f, bool b) { + b ? Potassco::store_set_mask(set, f) : Potassco::store_clear_mask(set, f); + return *this; + } + ParserOptions& enable(Extension e) { + Potassco::store_set_mask(set, Potassco::to_underlying(e)); + return *this; + } + uint8_t set{0}; }; //! Base class for parsers. class ProgramParser { public: - typedef Potassco::ProgramReader StrategyType; - static const Var VAR_MAX = varMax - 1; - ProgramParser(); - virtual ~ProgramParser(); - bool accept(std::istream& str, const ParserOptions& o = ParserOptions()); - bool incremental() const; - bool isOpen() const; - bool parse(); - bool more(); - void reset(); + using StrategyType = Potassco::ProgramReader; + ProgramParser(); + virtual ~ProgramParser(); + bool accept(std::istream& str, const ParserOptions& o = ParserOptions()); + [[nodiscard]] bool incremental() const; + [[nodiscard]] bool isOpen() const; + bool parse(); + bool more(); + void reset(); + private: - virtual StrategyType* doAccept(std::istream& str, const ParserOptions& o) = 0; - StrategyType* strat_; + virtual StrategyType* doAccept(std::istream& str, const ParserOptions& o) = 0; + StrategyType* strat_; }; //! Parser for logic programs in smodels-internal or aspif format. -class AspParser : public ProgramParser { +class AspParser final : public ProgramParser { public: - static bool accept(char c); - explicit AspParser(Asp::LogicProgram& prg); - ~AspParser(); - enum Format { format_smodels = -1, format_aspif = 1 }; - static void write(Asp::LogicProgram& prg, std::ostream& os); - static void write(Asp::LogicProgram& prg, std::ostream& os, Format f); -protected: - virtual StrategyType* doAccept(std::istream& str, const ParserOptions& o); + static bool accept(char c); + explicit AspParser(Asp::LogicProgram& prg); + enum Format { format_smodels = -1, format_aspif = 1 }; + static void write(Asp::LogicProgram& prg, std::ostream& os); + static void write(Asp::LogicProgram& prg, std::ostream& os, Format f); + private: - struct SmAdapter; - Asp::LogicProgram* lp_; - StrategyType* in_; - Potassco::AbstractProgram* out_; + StrategyType* doAccept(std::istream& str, const ParserOptions& o) override; + + Asp::LogicProgram* lp_; + std::unique_ptr in_; + std::unique_ptr out_; }; ///////////////////////////////////////////////////////////////////////////////////////// // SAT parsing @@ -120,81 +117,87 @@ class AspParser : public ProgramParser { //! Base class for dimacs and opb parser. class SatReader : public Potassco::ProgramReader { public: - SatReader(); - ParserOptions options; + SatReader(); + ParserOptions options; + protected: - bool skipLines(char start); - void parseExt(const char* pre, uint32 maxVar, SharedContext& ctx); - // ::= { } - void parseProject(uint32 maxVar, SharedContext& ctx); - // ::= { } - void parseAssume(uint32 maxVar); - // ::= - void parseHeuristic(uint32 maxVar, SharedContext& ctx); - // ::= "range" - // | - void parseOutput(uint32 maxVar, SharedContext& ctx); - void parseGraph(uint32 maxVar, const char* pre, ExtDepGraph& graph); - virtual void addObjective(const WeightLitVec& vec) = 0; - virtual void addAssumption(Literal x) = 0; + bool skipLines(char start); + bool skipMatch(const std::string_view& word); + void parseExt(const char* pre, SharedContext& ctx); + Wsum_t matchWeightSum(Wsum_t min, const char* what); + virtual void addObjective(WeightLitView) = 0; + virtual void addAssumption(Literal x) = 0; + private: - Literal matchLit(Var max); + // ::= { } + void parseProject(SharedContext& ctx); + // ::= { } + void parseAssume(); + // ::= + void parseHeuristic(SharedContext& ctx); + // ::= "range" + // | + void parseOutput(SharedContext& ctx); + void parseGraph(const char* pre, ExtDepGraph& graph); + Literal matchExtLit(); }; //! Parser for (extended) dimacs format. -class DimacsReader : public SatReader { +class DimacsReader final : public SatReader { public: - static bool accept(char c) { return c == 'c' || c == 'p'; } - DimacsReader(SatBuilder&); -protected: - virtual bool doAttach(bool& inc); - virtual bool doParse(); - virtual void addObjective(const WeightLitVec& vec); - virtual void addAssumption(Literal x); + static bool accept(char c) { return c == 'c' || c == 'p'; } + explicit DimacsReader(SatBuilder&); + private: - void parsePbConstraint(WeightLitVec& scratch, int64_t maxV); - void parseConstraintRhs(WeightLitVec& lhs); - SatBuilder* program_; - Var numVar_; - bool wcnf_; - bool plus_; + bool doAttach(bool& inc) override; + bool doParse() override; + void addObjective(WeightLitView) override; + void addAssumption(Literal x) override; + void parsePbConstraint(WeightLitVec& scratch); + void parseConstraintRhs(WeightLitVec& lhs); + + SatBuilder* program_{nullptr}; + Var_t numVar_{0}; + bool wcnf_{false}; + bool plus_{false}; }; //! Parser for opb format. -class OpbReader : public SatReader { +class OpbReader final : public SatReader { public: - OpbReader(PBBuilder&); - static bool accept(char c) { return c == '*'; } -protected: - virtual bool doAttach(bool& inc); - virtual bool doParse(); - virtual void addObjective(const WeightLitVec& vec); - virtual void addAssumption(Literal x); - void parseOptObjective(); - void parseConstraint(); - void parseSum(); - void parseTerm(); + explicit OpbReader(PBBuilder&); + static bool accept(char c) { return c == '*'; } + private: - PBBuilder* program_; - weight_t minCost_; - weight_t maxCost_; - struct Temp { - WeightLitVec lits; - LitVec term; - weight_t bound; - bool eq; - } active_; + bool doAttach(bool& inc) override; + bool doParse() override; + void addObjective(WeightLitView vec) override; + void addAssumption(Literal x) override; + void parseOptObjective(); + void parseConstraint(); + void parseSum(); + void parseTerm(); + + PBBuilder* program_{nullptr}; + Weight_t minCost_{0}; + Weight_t maxCost_{0}; + struct Temp { + WeightLitVec lits; + LitVec term; + Weight_t bound; + bool eq; + } active_{}; }; //! Parser for SAT or PB problems. class SatParser : public ProgramParser { public: - explicit SatParser(SatBuilder& prg); - explicit SatParser(PBBuilder& prg); - ~SatParser(); + explicit SatParser(SatBuilder& prg); + explicit SatParser(PBBuilder& prg); + protected: - virtual StrategyType* doAccept(std::istream& str, const ParserOptions& o); + StrategyType* doAccept(std::istream& str, const ParserOptions& o) override; + private: - SatReader* reader_; + std::unique_ptr reader_; }; //@} -} -#endif +} // namespace Clasp diff --git a/clasp/pod_vector.h b/clasp/pod_vector.h index 19783c6..0ba4180 100644 --- a/clasp/pod_vector.h +++ b/clasp/pod_vector.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,87 +21,116 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // +#pragma once -#ifndef CLASP_POD_VECTOR_H_INCLUDED -#define CLASP_POD_VECTOR_H_INCLUDED #include #include -#include + #include +#include +#include namespace Clasp { #if CLASP_USE_STD_VECTOR - template - struct PodVector { - typedef std::vector type; - static void destruct(type& t) {t.clear();} - }; +template +struct PodVector { + using type = std::vector; + static void destruct(type& t) { t.clear(); } +}; +using std::erase; +using std::erase_if; #else - //! Type selector for a vector type optimized for storing POD-types. - template - struct PodVector { - typedef bk_lib::pod_vector type; - static void destruct(type& t) { - for (typename type::size_type i = 0, end = t.size(); i != end; ++i) { - t[i].~Type(); - } - t.clear(); - } - }; +//! Type selector for a vector type optimized for storing POD-types. +template +struct PodVector { + using type = bk_lib::pod_vector; + static void destruct(type& t) { + std::destroy(t.begin(), t.end()); + t.clear(); + } +}; #endif -inline uint32 toU32(std::size_t x) { - assert(sizeof(std::size_t) <= sizeof(uint32) || x <= static_cast(UINT32_MAX)); - return static_cast(x); +template +using PodVector_t = typename PodVector::type; + +constexpr uint32_t toU32(std::size_t x) { + assert(std::in_range(x)); + return static_cast(x); +} +template +POTASSCO_ATTR_INLINE constexpr uint32_t size32(const T& c) { + if constexpr (std::is_same_v) { + return c.size(); + } + else { + return toU32(c.size()); + } } -template -inline uint32 sizeVec(const T& c) { return toU32(c.size()); } -template -inline void releaseVec(T& t) { - T().swap(t); +template +inline void discardVec(T& t) { + T().swap(t); } -template +template inline void shrinkVecTo(T& t, typename T::size_type j) { - t.erase(t.begin()+j, t.end()); + t.erase(t.begin() + static_cast(j), t.end()); } -template +template inline void growVecTo(T& vec, typename T::size_type j, const typename T::value_type& val = typename T::value_type()) { - if (vec.size() < j) { - if (vec.capacity() < j) { vec.reserve(j + j / 2); } - vec.resize(j, val); - } + if (vec.size() < j) { + if (vec.capacity() < j) { + vec.reserve(j + j / 2); + } + vec.resize(j, val); + } } -template +template void moveDown(T& t, typename T::size_type from, typename T::size_type to) { - for (typename T::size_type end = t.size(); from != end;) { - t[to++] = t[from++]; - } - shrinkVecTo(t, to); + for (typename T::size_type end = t.size(); from != end;) { t[to++] = t[from++]; } + shrinkVecTo(t, to); +} + +template +constexpr auto contains(It first, It last, const V& v) -> decltype(std::find(first, last, v) != last) { + return std::find(first, last, v) != last; } + +template +constexpr auto contains(const R& range, const V& v) -> decltype(contains(range.begin(), range.end(), v)) { + return contains(range.begin(), range.end(), v); +} + +template +constexpr auto drop(R&& range, std::size_t offset) { + assert(offset <= range.size()); + return std::span(range.data() + offset, range.size() - offset); +} + //! A simple vector-based fifo queue for storing POD-types. -template +template struct PodQueue { - typedef typename PodVector::type vec_type; - typedef typename vec_type::size_type size_type; - PodQueue() : qFront(0) {} - bool empty() const { return qFront == vec.size(); } - size_type size() const { return vec.size() - qFront; } - const T& front() const { return vec[qFront]; } - const T& back() const { return vec.back(); } - T& front() { return vec[qFront]; } - T& back() { return vec.back(); } - void push(const T& x){ vec.push_back(x); } - void pop() { ++qFront; } - T pop_ret() { return vec[qFront++]; } - void clear() { vec.clear(); qFront = 0; } - void rewind() { qFront = 0; } - vec_type vec; // the underlying vector holding the items - size_type qFront; // front position + using vec_type = PodVector_t; + using size_type = typename vec_type::size_type; + PodQueue() : qFront(0) {} + [[nodiscard]] bool empty() const { return qFront == vec.size(); } + size_type size() const { return vec.size() - qFront; } + const T& front() const { return vec[qFront]; } + const T& back() const { return vec.back(); } + T& front() { return vec[qFront]; } + T& back() { return vec.back(); } + void push(const T& x) { vec.push_back(x); } + void pop() { ++qFront; } + T pop_ret() { return vec[qFront++]; } + void clear() { + vec.clear(); + qFront = 0; + } + void rewind() { qFront = 0; } + vec_type vec; // the underlying vector holding the items + size_type qFront; // front position }; -} - -#endif +} // namespace Clasp diff --git a/clasp/program_builder.h b/clasp/program_builder.h index 9bcafd6..badc35f 100644 --- a/clasp/program_builder.h +++ b/clasp/program_builder.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,18 +21,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_PROGRAM_BUILDER_H_INCLUDED -#define CLASP_PROGRAM_BUILDER_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include -#include #include + #include namespace Clasp { @@ -47,209 +41,215 @@ namespace Clasp { //! Interface for defining an input program. class ProgramBuilder { public: - typedef SharedMinimizeData SharedMinimize; - typedef SingleOwnerPtr MinPtr; - - ProgramBuilder(); - virtual ~ProgramBuilder(); - //! Starts the definition of a program. - /*! - * This function shall be called exactly once before a new program is defined. - * It discards any previously added program. - * - * \param ctx The context object in which the program should be stored. - */ - bool startProgram(SharedContext& ctx); - //! Parses the given stream as a program of type() and adds it to this object. - bool parseProgram(std::istream& prg); - //! Unfreezes a currently frozen program. - bool updateProgram(); - //! Loads the program into the shared context passed to startProgram(). - bool endProgram(); - //! Returns any assumptions that shall hold during solving. - /*! - * \pre frozen() - */ - void getAssumptions(LitVec& out) const; - //! Returns bounds that shall hold during minimization. - void getWeakBounds(SumVec& out) const; - //! Returns the type of program that is created by this builder. - int type() const { return doType(); } - //! Returns true if the program is currently frozen. - bool frozen() const { return frozen_; } - //! Returns true if the program is not conflicting. - virtual bool ok() const; - //! Returns the stored context object. - SharedContext* ctx() const { return ctx_; } - //! Returns a parser for this type of program associated with this object. - ProgramParser& parser(); + ProgramBuilder(); + virtual ~ProgramBuilder(); + ProgramBuilder(const ProgramBuilder&) = delete; + ProgramBuilder& operator=(ProgramBuilder&) = delete; + + //! Starts the definition of a program. + /*! + * This function shall be called exactly once before a new program is defined. + * It discards any previously added program. + * + * \param ctx The context object in which the program should be stored. + */ + bool startProgram(SharedContext& ctx); + //! Parses the given stream as a program of type() and adds it to this object. + bool parseProgram(std::istream& prg); + //! Unfreezes a currently frozen program. + bool updateProgram(); + //! Loads the program into the shared context passed to startProgram(). + bool endProgram(); + //! Returns any assumptions that shall hold during solving. + /*! + * \pre frozen() + */ + void getAssumptions(LitVec& out) const; + //! Returns bounds that shall hold during minimization. + void getWeakBounds(SumVec& out) const; + //! Returns the type of program that is created by this builder. + [[nodiscard]] int type() const { return doType(); } + //! Returns true if the program is currently frozen. + [[nodiscard]] bool frozen() const { return frozen_; } + //! Returns true if the program is not conflicting. + [[nodiscard]] virtual bool ok() const; + //! Returns the stored context object. + [[nodiscard]] SharedContext* ctx() const { return ctx_; } + //! Returns a parser for this type of program associated with this object. + ProgramParser& parser(); + protected: - void addMinLit(weight_t prio, WeightLiteral x); - void setFrozen(bool frozen) { frozen_ = frozen; } - void setCtx(SharedContext* x){ ctx_ = x; } - void markOutputVariables() const; + void addMinLit(Weight_t prio, WeightLiteral x); + void setFrozen(bool frozen) { frozen_ = frozen; } + void setCtx(SharedContext* x) { ctx_ = x; } + void markOutputVariables() const; + private: - typedef SingleOwnerPtr ParserPtr; - ProgramBuilder(const ProgramBuilder&); - ProgramBuilder& operator=(ProgramBuilder&); - virtual bool doStartProgram() = 0; - virtual bool doUpdateProgram() = 0; - virtual bool doEndProgram() = 0; - virtual void doGetWeakBounds(SumVec& out) const; - virtual void doGetAssumptions(LitVec& out) const = 0; - virtual int doType() const = 0; - virtual ProgramParser* doCreateParser() = 0; - SharedContext* ctx_; - ParserPtr parser_; - bool frozen_; + virtual bool doStartProgram() = 0; + virtual bool doUpdateProgram() = 0; + virtual bool doEndProgram() = 0; + virtual void doGetWeakBounds(SumVec& out) const; + virtual void doGetAssumptions(LitVec& out) const = 0; + [[nodiscard]] virtual int doType() const = 0; + virtual ProgramParser* doCreateParser() = 0; + SharedContext* ctx_; + std::unique_ptr parser_; + bool frozen_; }; //! A class for defining a SAT-problem in CNF. -class SatBuilder : public ProgramBuilder { +class SatBuilder final : public ProgramBuilder { public: - explicit SatBuilder(); - // program definition - - //! Creates necessary variables and prepares the problem. - /*! - * \param numVars Number of variables to create. - * \param hardClauseWeight Weight identifying hard clauses (0 means no weight). - * Clauses added with a weight != hardClauseWeight are - * considered soft clauses (see addClause()). - * \param clauseHint A hint on how many clauses will be added. - */ - void prepareProblem(uint32 numVars, wsum_t hardClauseWeight = 0, uint32 clauseHint = 100); - //! Returns the number of variables in the problem. - Var numVars() const { return vars_; } - //! Adds the given clause to the problem. - /*! - * The SatBuilder supports the creation of (weighted) MaxSAT problems - * via the creation of "soft clauses". For this, clauses - * added to this object have an associated weight cw. If cw - * does not equal hardClauseWeight (typically 0), the clause is a - * soft clause and not satisfying it results in a penalty of cw. - * - * \pre v <= numVars(), for all variables v occurring in clause. - * \pre cw >= 0. - * \param clause The clause to add. - * \param cw The weight associated with the clause. - */ - bool addClause(LitVec& clause, wsum_t cw = 0); - //! Adds the given PB-constraint (sum(lits) >= bound) to the problem. - bool addConstraint(WeightLitVec& lits, weight_t bound); - //! Adds min as an objective function to the problem. - bool addObjective(const WeightLitVec& min); - //! Adds v to the set of projection vars. - void addProject(Var v); - //! Adds x to the set of initial assumptions. - void addAssumption(Literal x); + explicit SatBuilder() = default; + // program definition + + //! Creates necessary variables and prepares the problem. + /*! + * \param numVars Number of variables to create. + * \param hardClauseWeight Weight identifying hard clauses (0 means no weight). + * Clauses added with a weight != hardClauseWeight are + * considered soft clauses (see addClause()). + * \param clauseHint A hint on how many clauses will be added. + */ + void prepareProblem(uint32_t numVars, Wsum_t hardClauseWeight = 0, uint32_t clauseHint = 100); + //! Returns the number of variables in the problem. + [[nodiscard]] Var_t numVars() const { return vars_; } + //! Adds the given clause to the problem. + /*! + * The SatBuilder supports the creation of (weighted) MaxSAT problems + * via the creation of "soft clauses". For this, clauses + * added to this object have an associated weight cw. If cw + * does not equal hardClauseWeight (typically 0), the clause is a + * soft clause and not satisfying it results in a penalty of cw. + * + * \pre v <= numVars(), for all variables v occurring in clause. + * \pre cw >= 0. + * \param clause The clause to add. + * \param cw The weight associated with the clause. + */ + bool addClause(LitVec& clause, Wsum_t cw = 0); + //! Adds the given PB-constraint (sum(lits) >= bound) to the problem. + bool addConstraint(WeightLitVec& lits, Weight_t bound); + //! Adds min as an objective function to the problem. + bool addObjective(WeightLitView min); + //! Adds v to the set of projection vars. + void addProject(Var_t v); + //! Adds x to the set of initial assumptions. + void addAssumption(Literal x); + private: - typedef PodVector::type VarState; - bool doStartProgram(); - ProgramParser* doCreateParser(); - int doType() const { return Problem_t::Sat; } - bool doUpdateProgram() { return !frozen(); } - void doGetAssumptions(LitVec& a) const { a.insert(a.end(), assume_.begin(), assume_.end()); } - bool doEndProgram(); - bool satisfied(LitVec& clause); - bool markAssigned(); - void markLit(Literal x) { varState_[x.var()] |= 1 + x.sign(); } - void markOcc(Literal x) { varState_[x.var()] |= (trueValue(x) << 2u); } - VarState varState_; - LitVec softClauses_; - LitVec assume_; - wsum_t hardWeight_; - Var vars_; - uint32 pos_; + using VarState = PodVector_t; + bool doStartProgram() override; + ProgramParser* doCreateParser() override; + [[nodiscard]] int doType() const override { return static_cast(ProblemType::sat); } + bool doUpdateProgram() override { return not frozen(); } + void doGetAssumptions(LitVec& a) const override { a.insert(a.end(), assume_.begin(), assume_.end()); } + bool doEndProgram() override; + bool satisfied(LitVec& clause); + bool markAssigned(); + void markLit(Literal x) { varState_[x.var()] |= trueValue(x); } + void markOcc(Literal x) { varState_[x.var()] |= static_cast(trueValue(x) << 2u); } + VarState varState_; + LitVec softClauses_; + LitVec assume_; + Wsum_t hardWeight_ = 0; + Var_t vars_ = 0; + uint32_t pos_ = 0; }; //! A class for defining a PB-problem. -class PBBuilder : public ProgramBuilder { +class PBBuilder final : public ProgramBuilder { public: - PBBuilder(); - ~PBBuilder(); - - // program definition - //! Creates necessary variables and prepares the problem. - /*! - * \param numVars Number of problem variables to create. - * \param maxProduct Max number of products in the problem. - * \param maxSoft Max number of soft constraints in the problem. - * \param constraintHint A hint on how many clauses will be added. - */ - void prepareProblem(uint32 numVars, uint32 maxProduct, uint32 maxSoft, uint32 constraintHint = 100); - //! Returns the number of variables in the problem. - uint32 numVars() const { return auxVar_ - 1; } - //! Adds the given PB-constraint to the problem. - /*! - * A PB-constraint consists of a list of weighted Boolean literals (lhs), - * a comparison operator (either >= or =), and an integer bound (rhs). - * - * \pre v <= numVars(), for all variables v occurring in lits. - * - * \param lits The lhs of the PB-constraint. - * \param bound The rhs of the PB-constraint. - * \param eq If true, use '=' instead of '>=' as comparison operator. - * \param cw If > 0, treat constraint as soft constraint with weight cw. - */ - bool addConstraint(WeightLitVec& lits, weight_t bound, bool eq = false, weight_t cw = 0); - //! Adds the given product to the problem. - /*! - * The function creates the equality x == l1 && ... && ln, where x is a new - * literal and each li is a literal in lits. - * \pre The number of products added so far is < maxProduct that was given in prepareProblem(). - */ - Literal addProduct(LitVec& lits); - //! Adds min as an objective function to the problem. - bool addObjective(const WeightLitVec& min); - //! Adds v to the set of projection vars. - void addProject(Var v); - //! Adds x to the set of initial assumptions. - void addAssumption(Literal x); - //! Only allow solutions where the sum of violated soft constraint is less than bound. - bool setSoftBound(wsum_t bound); + PBBuilder(); + ~PBBuilder() override; + + // program definition + //! Creates necessary variables and prepares the problem. + /*! + * \param numVars Number of problem variables to create. + * \param maxProduct Max number of products in the problem. + * \param maxSoft Max number of soft constraints in the problem. + * \param constraintHint A hint on how many clauses will be added. + */ + void prepareProblem(uint32_t numVars, uint32_t maxProduct, uint32_t maxSoft, uint32_t constraintHint = 100); + //! Returns the number of variables in the problem. + [[nodiscard]] uint32_t numVars() const { return auxVar_ - 1; } + //! Adds the given PB-constraint to the problem. + /*! + * A PB-constraint consists of a list of weighted Boolean literals (lhs), + * a comparison operator (either >= or =), and an integer bound (rhs). + * + * \pre v <= numVars(), for all variables v occurring in lits. + * + * \param lits The lhs of the PB-constraint. + * \param bound The rhs of the PB-constraint. + * \param eq If true, use '=' instead of '>=' as comparison operator. + * \param cw If > 0, treat constraint as soft constraint with weight cw. + */ + bool addConstraint(WeightLitVec& lits, Weight_t bound, bool eq = false, Weight_t cw = 0); + //! Adds the given product to the problem. + /*! + * The function creates the equality x == l1 && ... && ln, where x is a new + * literal and each li is a literal in lits. + * \pre The number of products added so far is < maxProduct that was given in prepareProblem(). + */ + Literal addProduct(LitVec& lits); + //! Adds min as an objective function to the problem. + bool addObjective(WeightLitView min); + //! Adds v to the set of projection vars. + void addProject(Var_t v); + //! Adds x to the set of initial assumptions. + void addAssumption(Literal x); + //! Only allow solutions where the sum of violated soft constraint is less than bound. + bool setSoftBound(Wsum_t bound); + private: - struct PKey { - LitVec lits; - std::size_t operator()(const PKey& k) const { return k.lits[0].rep(); } - bool operator()(const PKey& lhs, const PKey& rhs) const { return lhs.lits == rhs.lits; } - }; - struct ProductIndex; - typedef SingleOwnerPtr ProductIndexPtr; - - bool doStartProgram(); - void doGetWeakBounds(SumVec& out) const; - int doType() const { return Problem_t::Pb; } - bool doUpdateProgram() { return !frozen(); } - void doGetAssumptions(LitVec& a) const { a.insert(a.end(), assume_.begin(), assume_.end()); } - ProgramParser* doCreateParser(); - bool doEndProgram(); - bool productSubsumed(LitVec& lits, PKey& prod); - void addProductConstraints(Literal eqLit, LitVec& lits); - Var getAuxVar(); - ProductIndexPtr products_; - PKey prod_; - LitVec assume_; - uint32 auxVar_; - uint32 endVar_; - wsum_t soft_; + struct PKey { + LitVec lits; + std::size_t operator()(const PKey& k) const { return k.lits[0].rep(); } + bool operator()(const PKey& lhs, const PKey& rhs) const { return lhs.lits == rhs.lits; } + }; + struct ProductIndex; + using ProductIndexPtr = std::unique_ptr; + + bool doStartProgram() override; + void doGetWeakBounds(SumVec& out) const override; + [[nodiscard]] int doType() const override { return static_cast(ProblemType::pb); } + bool doUpdateProgram() override { return not frozen(); } + void doGetAssumptions(LitVec& a) const override { a.insert(a.end(), assume_.begin(), assume_.end()); } + ProgramParser* doCreateParser() override; + bool doEndProgram() override; + bool productSubsumed(LitVec& lits, PKey& prod); + void addProductConstraints(Literal eqLit, LitVec& lits); + Var_t nextAuxVar(); + ProductIndexPtr products_; + PKey prod_; + LitVec assume_; + uint32_t auxVar_{1}; + uint32_t endVar_{0}; + Wsum_t soft_{0}; }; //! Adapts a Sat or PB builder to the Potassco::AbstractProgram interface. -class BasicProgramAdapter : public Potassco::AbstractProgram { +class BasicProgramAdapter final : public Potassco::AbstractProgram { public: - BasicProgramAdapter(ProgramBuilder& prg); - void initProgram(bool inc); - void beginStep(); - void rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body); - void rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& body); - void minimize(Potassco::Weight_t prio, const Potassco::WeightLitSpan& lits); -protected: - ProgramBuilder* prg_; - LitVec clause_; - WeightLitVec constraint_; - bool inc_; + explicit BasicProgramAdapter(ProgramBuilder& prg); + void initProgram(bool inc) override; + void beginStep() override; + void rule(Potassco::HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body) override; + void rule(Potassco::HeadType ht, Potassco::AtomSpan head, Potassco::Weight_t bound, + Potassco::WeightLitSpan body) override; + void minimize(Potassco::Weight_t prio, Potassco::WeightLitSpan lits) override; + +private: + template + void withPrg(C&& call) const; + + ProgramBuilder* prg_; + LitVec clause_; + WeightLitVec constraint_; + bool sat_; + bool inc_; }; -} -#endif +} // namespace Clasp diff --git a/clasp/satelite.h b/clasp/satelite.h index f15cf6f..e75a73a 100644 --- a/clasp/satelite.h +++ b/clasp/satelite.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,12 +23,7 @@ // //! \file //! \brief Types and functions for SAT-based preprocessing. -#ifndef CLASP_SATELITE_H_INCLUDED -#define CLASP_SATELITE_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include #include @@ -47,128 +42,137 @@ namespace Clasp { * available under the MIT licence from http://minisat.se/MiniSat.html * . */ -class SatElite : public Clasp::SatPreprocessor { +class SatElite : public SatPreprocessor { public: - SatElite(); - ~SatElite(); - Clasp::SatPreprocessor* clone(); - //! Event type for providing information on preprocessing progress. - struct Progress : public Event_t { - enum EventOp { event_algorithm = '*', event_bce = 'B', event_var_elim = 'E', event_subsumption = 'S', }; - Progress(SatElite* p, EventOp o, uint32 i, uint32 m) : Event_t(Event::subsystem_prepare, Event::verbosity_high), self(p), cur(i), max(m) { - op = (uint32)o; - } - SatElite* self; - uint32 cur; - uint32 max; - }; + SatElite(); + ~SatElite() override; + SatElite(SatElite&&) = delete; + SatPreprocessor* clone() override; + //! Event type for providing information on preprocessing progress. + struct Progress : Event { + enum EventOp { + event_algorithm = '*', + event_bce = 'B', + event_var_elim = 'E', + event_subsumption = 'S', + }; + Progress(SatElite* p, EventOp o, uint32_t i, uint32_t m) + : Event(this, subsystem_prepare, verbosity_high) + , self(p) + , cur(i) + , max(m) { + op = static_cast(o); + } + SatElite* self; + uint32_t cur; + uint32_t max; + }; + protected: - bool initPreprocess(Options& opts); - void reportProgress(Progress::EventOp, uint32 curr, uint32 max); - bool doPreprocess(); - void doExtendModel(ValueVec& m, LitVec& open); - void doCleanUp(); + bool initPreprocess(Options& opts) override; + void reportProgress(Progress::EventOp, uint32_t curr, uint32_t max); + bool doPreprocess() override; + void doExtendModel(ValueVec& m, LitVec& open) override; + void doCleanUp() override; + private: - typedef PodVector::type TouchedList; - typedef bk_lib::left_right_sequence ClWList; - typedef ClWList::left_iterator ClIter; - typedef ClWList::right_iterator WIter; - typedef std::pair ClRange; - SatElite(const SatElite&); - const SatElite& operator=(const SatElite&); - // For each var v - struct OccurList { - OccurList() : pos(0), bce(0), dirty(0), neg(0), litMark(0) {} - ClWList refs; // left : ids of clauses containing v or ~v (var() == id, sign() == v or ~v) - // right: ids of clauses watching v or ~v (literal 0 is the watched literal) - uint32 pos:30; // number of *relevant* clauses containing v - uint32 bce:1; // in BCE queue? - uint32 dirty:1; // does clauses contain removed clauses? - uint32 neg:30; // number of *relevant* clauses containing v - uint32 litMark:2; // 00: no literal of v marked, 01: v marked, 10: ~v marked - uint32 numOcc() const { return pos + neg; } - uint32 cost() const { return pos * neg; } - ClRange clauseRange() const { return ClRange(const_cast(refs).left_begin(), const_cast(refs).left_end()); } - void clear() { - this->~OccurList(); - new (this) OccurList(); - } - void addWatch(uint32 clId) { refs.push_right(clId); } - void removeWatch(uint32 clId) { refs.erase_right(std::find(refs.right_begin(), refs.right_end(), clId)); } - void add(uint32 id, bool sign){ - pos += uint32(!sign); - neg += uint32(sign); - refs.push_left(Literal(id, sign)); - } - void remove(uint32 id, bool sign, bool updateClauseList) { - pos -= uint32(!sign); - neg -= uint32(sign); - if (updateClauseList) { - refs.erase_left(std::find(refs.left_begin(), refs.left_end(), Literal(id, sign))); - } - else { dirty = 1; } - } - // note: only one literal of v shall be marked at a time - bool marked(bool sign) const { return (litMark & (1+int(sign))) != 0; } - void mark(bool sign) { litMark = (1+int(sign)); } - void unmark() { litMark = 0; } - }; - struct LessOccCost { - explicit LessOccCost(OccurList*& occ) : occ_(occ) {} - bool operator()(Var v1, Var v2) const { return occ_[v1].cost() < occ_[v2].cost(); } - private: - const LessOccCost& operator=(LessOccCost&); - OccurList*& occ_; - }; - typedef bk_lib::indexed_priority_queue ElimHeap; - Clause* peekSubQueue() const { - assert(qFront_ < queue_.size()); - return (Clause*)clause( queue_[qFront_] ); - } - inline Clause* popSubQueue(); - inline void addToSubQueue(uint32 clauseId); - void updateHeap(Var v) { - assert(ctx_); - if (!ctx_->varInfo(v).frozen() && !ctx_->eliminated(v)) { - elimHeap_.update(v); - if (occurs_[v].bce == 0 && occurs_[0].bce != 0) { - occurs_[0].addWatch(v); - occurs_[v].bce = 1; - } - } - } - inline uint32 findUnmarkedLit(const Clause& c, uint32 x) const; - void attach(uint32 cId, bool initialClause); - void detach(uint32 cId); - void bceVeRemove(uint32 cId, bool freeId, Var v, bool blocked); - bool propagateFacts(); - bool backwardSubsume(); - Literal subsumes(const Clause& c, const Clause& other, Literal res) const; - bool strengthenClause(uint32 clauseId, Literal p); - bool subsumed(LitVec& cl); - bool eliminateVars(); - bool bce(); - bool bceVe(Var v, uint32 maxCnt); - ClRange splitOcc(Var v, bool mark); - bool trivialResolvent(const Clause& c2, Var v) const; - void markAll(const Literal* lits, uint32 size) const; - void unmarkAll(const Literal* lits, uint32 size) const; - bool addResolvent(uint32 newId, const Clause& c1, const Clause& c2); - bool cutoff(Var v) const { - return opts_->occLimit(occurs_[v].pos, occurs_[v].neg) - || (occurs_[v].cost() == 0 && ctx_->preserveModels()); - } - bool timeout() const { return time(0) > timeout_; } - enum OccSign { pos = 0, neg = 1}; - OccurList* occurs_; // occur list for each variable - ElimHeap elimHeap_; // candidates for variable elimination; ordered by increasing occurrence-cost - VarVec occT_[2]; // temporary clause lists used in eliminateVar - ClauseList resCands_; // pairs of clauses to be resolved - LitVec resolvent_; // temporary, used in addResolvent - VarVec queue_; // indices of clauses waiting for subsumption-check - uint32 qFront_; // front of queue_, i.e. [queue_.begin()+qFront_, queue.end()) is the subsumption queue - uint32 facts_; // [facts_, solver.trail.size()): new top-level facts - std::time_t timeout_; // stop once time > timeout_ + using ClWList = bk_lib::left_right_sequence; + using ClIter = ClWList::left_iterator; + using WIter = ClWList::right_iterator; + using ClRange = std::span; + using IdQueue = PodQueue; + // For each var v + struct OccurList { + [[nodiscard]] uint32_t numOcc() const { return pos + neg; } + [[nodiscard]] uint32_t cost() const { return pos * neg; } + [[nodiscard]] ClRange clauseRange() const { + return {const_cast(refs).left_begin(), refs.left_size()}; + } + void clear() { + this->~OccurList(); + new (this) OccurList(); + } + void addWatch(uint32_t clId) { refs.push_right(clId); } + void removeWatch(uint32_t clId) { refs.erase_right(std::find(refs.right_begin(), refs.right_end(), clId)); } + void add(uint32_t id, bool sign) { + pos += static_cast(not sign); + neg += static_cast(sign); + refs.push_left(Literal(id, sign)); + } + void remove(uint32_t id, bool sign, bool updateClauseList) { + pos -= static_cast(not sign); + neg -= static_cast(sign); + if (updateClauseList) { + refs.erase_left(std::find(refs.left_begin(), refs.left_end(), Literal(id, sign))); + } + else { + dirty = 1; + } + } + // note: only one literal of v shall be marked at a time + static constexpr uint32_t mask(bool s) { return 1u + s; } + [[nodiscard]] bool marked(bool sign) const { return Potassco::test_any(litMark, mask(sign)); } + void mark(bool sign) { litMark = mask(sign); } + void unmark() { litMark = 0; } + + ClWList refs; // left : ids of clauses containing v or ~v (var() == id, sign() == v or ~v) + // right: ids of clauses watching v or ~v (literal 0 is the watched literal) + uint32_t pos : 30 = 0; // number of *relevant* clauses containing v + uint32_t bce : 1 = 0; // in BCE queue? + uint32_t dirty : 1 = 0; // does clauses contain removed clauses? + uint32_t neg : 30 = 0; // number of *relevant* clauses containing v + uint32_t litMark : 2 = 0; // 00: no literal of v marked, 01: v marked, 10: ~v marked + }; + using OccurLists = std::unique_ptr; + struct LessOccCost { + explicit LessOccCost(OccurLists& occ) : occ_(occ) {} + bool operator()(Var_t v1, Var_t v2) const { return occ_[v1].cost() < occ_[v2].cost(); } + + private: + OccurLists& occ_; + }; + using ElimHeap = bk_lib::indexed_priority_queue; + Clause* popSubQueue(); + void addToSubQueue(uint32_t clauseId); + void updateHeap(Var_t v) { + assert(ctx_); + if (not ctx_->varInfo(v).frozen() && not ctx_->eliminated(v)) { + elimHeap_.update(v); + if (occurs_[v].bce == 0 && occurs_[0].bce != 0) { + occurs_[0].addWatch(v); + occurs_[v].bce = 1; + } + } + } + [[nodiscard]] uint32_t findUnmarkedLit(const Clause& c, uint32_t x) const; + void attach(uint32_t cId, bool initialClause); + void detach(uint32_t cId); + void bceVeRemove(uint32_t cId, bool freeId, Var_t v, bool blocked); + bool propagateFacts(); + bool backwardSubsume(); + [[nodiscard]] Literal subsumes(const Clause& c, const Clause& other, Literal res) const; + bool strengthenClause(uint32_t clauseId, Literal p); + bool subsumed(LitVec& cl); + bool eliminateVars(); + bool bce(); + bool bceVe(Var_t v, uint32_t maxCnt); + ClRange splitOcc(Var_t v, bool mark); + [[nodiscard]] bool trivialResolvent(const Clause& c2, Var_t v) const; + void markAll(LitView lits) const; + void unmarkAll(LitView lits) const; + bool addResolvent(uint32_t newId, const Clause& c1, const Clause& c2); + [[nodiscard]] bool cutoff(Var_t v) const { + return opts_->occLimit(occurs_[v].pos, occurs_[v].neg) || (occurs_[v].cost() == 0 && ctx_->preserveModels()); + } + [[nodiscard]] bool timeout() const { return time(nullptr) > timeout_; } + enum OccSign { occ_pos = 0, occ_neg = 1 }; + OccurLists occurs_; // occur list for each variable + ElimHeap elimHeap_; // candidates for variable elimination; ordered by increasing occurrence-cost + VarVec occT_[2]; // temporary clause lists used in eliminateVar + ClauseList resCands_; // pairs of clauses to be resolved + LitVec resolvent_; // temporary, used in addResolvent + IdQueue queue_; // indices of clauses waiting for subsumption-check + uint32_t facts_; // [facts_, solver.trail.size()): new top-level facts + std::time_t timeout_; // stop once time > timeout_ }; -} -#endif +} // namespace Clasp diff --git a/clasp/shared_context.h b/clasp/shared_context.h index f331b31..a6c8e20 100644 --- a/clasp/shared_context.h +++ b/clasp/shared_context.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,16 +21,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SHARED_CONTEXT_H_INCLUDED -#define CLASP_SHARED_CONTEXT_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif + #include -#include #include -#include +#include #include +#include + +#include + /*! * \file * \brief Contains common types shared between different solvers. @@ -40,31 +40,12 @@ class Assignment; class SharedLiterals; struct SolverStats; class StatisticObject; -typedef Asp::PrgDepGraph PrgDepGraph; -//! An immutable string with efficient copying. -/*! - * \ingroup misc - */ -class ConstString { -public: - //! Creates a string from str. - ConstString(const char* str = ""); - //! Creates a copy of str. - ConstString(const StrView& str); - ConstString(const ConstString& other); - ~ConstString(); - ConstString& operator=(const ConstString& rhs); - const char* c_str() const; - operator const char* () const { return c_str(); } - void swap(ConstString& o); -private: - typedef uint64 RefType; - RefType ref_; -}; +using PrgDepGraph = Asp::PrgDepGraph; + /** -* \defgroup shared SharedContext -* \brief %SharedContext and related types. -*/ + * \defgroup shared SharedContext + * \brief %SharedContext and related types. + */ //! Base class for event handlers. /*! @@ -72,42 +53,54 @@ class ConstString { */ class EventHandler : public ModelHandler { public: - //! Creates a handler for events with given verbosity or lower. - explicit EventHandler(Event::Verbosity verbosity = Event::verbosity_quiet); - virtual ~EventHandler(); - //! Sets the verbosity for the given event source. - /*! - * Events with higher verbosity are not dispatched to this handler. - */ - void setVerbosity(Event::Subsystem sys, Event::Verbosity verb); - //! Sets the active event source to sys if sys is not yet active. - bool setActive(Event::Subsystem sys); - Event::Subsystem active() const; - uint32 verbosity(Event::Subsystem sys) const { return (uint32(verb_) >> (uint32(sys)<(ev.system))) { onEvent(ev); } } - virtual void onEvent(const Event& /* ev */) {} - virtual bool onModel(const Solver&, const Model&) { return true; } + //! Creates a handler for events with given verbosity or lower. + explicit EventHandler(Event::Verbosity verbosity = Event::verbosity_quiet); + EventHandler(const EventHandler&) = delete; + EventHandler& operator=(const EventHandler&) = delete; + + //! Sets the verbosity for the given event source. + /*! + * Events with higher verbosity are not dispatched to this handler. + */ + void setVerbosity(Event::Subsystem sys, Event::Verbosity verb); + //! Sets the active event source to sys if sys is not yet active. + bool setActive(Event::Subsystem sys); + [[nodiscard]] Event::Subsystem active() const; + [[nodiscard]] uint32_t verbosity(Event::Subsystem sys) const { + return (static_cast(verb_) >> (static_cast(sys) << verb_shift)) & verb_mask; + } + //! Calls onEvent(ev) if ev has acceptable verbosity. + void dispatch(const Event& ev) { + if (ev.verb <= verbosity(static_cast(ev.system))) { + onEvent(ev); + } + } + virtual void onEvent(const Event& /* ev */) {} + bool onModel(const Solver&, const Model&) override { return true; } + private: - enum { VERB_SHIFT = 2u, VERB_MAX = 15u }; - EventHandler(const EventHandler&); - EventHandler& operator=(const EventHandler&); - uint16 verb_; - uint16 sys_; + static constexpr auto verb_mask = 15u; + static constexpr auto verb_shift = 2u; + + uint16_t verb_; + uint16_t sys_; }; //! Event type for log or warning messages. /*! * \ingroup enumerator */ -struct LogEvent : Event_t { - enum Type { Message = 'M', Warning = 'W' }; - LogEvent(Subsystem sys, Verbosity verb, Type t, const Solver* s, const char* what) : Event_t(sys, verb), solver(s), msg(what) { - op = static_cast(t); - } - bool isWarning() const { return op == static_cast(Warning); } - const Solver* solver; - const char* msg; +struct LogEvent : Event { + enum Type { message = 'M', warning = 'W' }; + LogEvent(Subsystem sys, Verbosity v, Type t, const Solver* s, const char* what) + : Event(this, sys, v) + , solver(s) + , msg(what) { + op = static_cast(t); + } + [[nodiscard]] bool isWarning() const { return op == static_cast(warning); } + const Solver* solver; + const char* msg; }; //! Base class for preprocessors working on clauses only. @@ -116,106 +109,108 @@ struct LogEvent : Event_t { */ class SatPreprocessor { public: - //! A clause class optimized for preprocessing. - class Clause { - public: - static Clause* newClause(const Literal* lits, uint32 size); - static uint64 abstractLit(Literal p) { return uint64(1) << ((p.var()-1) & 63); } - uint32 size() const { return size_; } - const Literal& operator[](uint32 x) const { return lits_[x]; } - bool inQ() const { return inQ_ != 0; } - uint64 abstraction() const { return data_.abstr; } - Clause* next() const { return data_.next; } - bool marked() const { return marked_ != 0;} - Literal& operator[](uint32 x) { return lits_[x]; } - void setInQ(bool b) { inQ_ = (uint32)b;} - void setMarked(bool b) { marked_ = (uint32)b;} - uint64& abstraction() { return data_.abstr; } - Clause* linkRemoved(Clause* next) { data_.next = next; return this; } - void strengthen(Literal p); - void simplify(Solver& s); - void destroy(); - private: - Clause(const Literal* lits, uint32 size); - union { - uint64 abstr; // abstraction of literals - Clause* next; // next removed clause - } data_; - uint32 size_ : 30; // size of the clause - uint32 inQ_ : 1; // in todo-queue? - uint32 marked_ : 1; // a marker flag - Literal lits_[1]; // literals of the clause: [lits_[0], lits_[size_]) - }; - - SatPreprocessor(); - virtual ~SatPreprocessor(); - - //! Creates a clone of this preprocessor. - /*! - * \note The function does not clone any clauses already added to *this. - */ - virtual SatPreprocessor* clone() = 0; - - uint32 numClauses() const { return (uint32)clauses_.size(); } - //! Adds a clause to the preprocessor. - /*! - * \pre clause is not a tautology (i.e. does not contain both l and ~l) - * \pre clause is a set (i.e. does not contain l more than once) - * \return true if clause was added. False if adding the clause makes the problem UNSAT - */ - bool addClause(const LitVec& cl) { return addClause(!cl.empty() ? &cl[0] : 0, sizeVec(cl)); } - bool addClause(const Literal* clause, uint32 size); - //! Runs the preprocessor on all clauses that were previously added. - /*! - * \pre A ctx.startAddConstraint() was called and has variables for all added clauses. - */ - bool preprocess(SharedContext& ctx, SatPreParams& opts); - bool preprocess(SharedContext& ctx); - - //! Force removal of state & clauses. - void cleanUp(bool discardEliminated = false); - - //! Extends the model in m with values for any eliminated variables. - void extendModel(ValueVec& m, LitVec& open); - struct Stats { - Stats() : clRemoved(0), clAdded(0), litsRemoved(0) {} - uint32 clRemoved; - uint32 clAdded; - uint32 litsRemoved; - } stats; - typedef SatPreParams Options; + //! A clause class optimized for preprocessing. + class Clause { + public: + static Clause* newClause(LitView lits); + static uint64_t abstractLit(Literal p) { return static_cast(1) << ((p.var() - 1) & 63); } + [[nodiscard]] uint32_t size() const { return size_; } + const Literal& operator[](uint32_t x) const { return lits_[x]; } + [[nodiscard]] bool inQ() const { return inQ_ != 0; } + [[nodiscard]] uint64_t abstraction() const { return data_.abstr; } + [[nodiscard]] Clause* next() const { return data_.next; } + [[nodiscard]] bool marked() const { return marked_ != 0; } + [[nodiscard]] auto lits() const -> LitView { return {lits_, size_}; } + Literal& operator[](uint32_t x) { return lits_[x]; } + void setInQ(bool b) { inQ_ = static_cast(b); } + void setMarked(bool b) { marked_ = static_cast(b); } + uint64_t& abstraction() { return data_.abstr; } + Clause* linkRemoved(Clause* next) { + data_.next = next; + return this; + } + void strengthen(Literal p); + void simplify(Solver& s); + void destroy(); + + private: + Clause(const Literal* lits, uint32_t size); + union { + uint64_t abstr{}; // abstraction of literals + Clause* next; // next removed clause + } data_; + uint32_t size_ : 30; // size of the clause + uint32_t inQ_ : 1; // in todo-queue? + uint32_t marked_ : 1; // a marker flag + Literal lits_[1]; // literals of the clause: [lits_[0], lits_[size_]) + }; + + SatPreprocessor(); + virtual ~SatPreprocessor(); + SatPreprocessor(SatPreprocessor&&) = delete; + + //! Creates a clone of this preprocessor. + /*! + * \note The function does not clone any clauses already added to *this. + */ + virtual SatPreprocessor* clone() = 0; + + [[nodiscard]] uint32_t numClauses() const { return size32(clauses_); } + //! Adds a clause to the preprocessor. + /*! + * \pre clause is not a tautology (i.e. does not contain both l and ~l) + * \pre clause is a set (i.e. does not contain l more than once) + * \return true if `clause` was added. False if adding the clause makes the problem UNSAT + */ + bool addClause(LitView clause); + //! Runs the preprocessor on all clauses that were previously added. + /*! + * \pre A ctx.startAddConstraint() was called and has variables for all added clauses. + */ + bool preprocess(SharedContext& ctx, SatPreParams& opts); + bool preprocess(SharedContext& ctx); + + //! Force removal of state & clauses. + void cleanUp(bool discardEliminated = false); + + //! Extends the model in m with values for any eliminated variables. + void extendModel(ValueVec& m, LitVec& open); + struct Stats { + uint32_t clRemoved{0}; + uint32_t clAdded{0}; + uint32_t litsRemoved{0}; + } stats; + using Options = SatPreParams; + protected: - typedef PodVector::type ClauseList; - virtual bool initPreprocess(SatPreParams& opts) = 0; - virtual bool doPreprocess() = 0; - virtual void doExtendModel(ValueVec& m, LitVec& open) = 0; - virtual void doCleanUp() = 0; - Clause* clause(uint32 clId) { return clauses_[clId]; } - const Clause* clause(uint32 clId) const { return clauses_[clId]; } - void freezeSeen(); - void discardClauses(bool discardEliminated); - void setClause(uint32 clId, const LitVec& cl) { - clauses_[clId] = Clause::newClause(&cl[0], (uint32)cl.size()); - } - void destroyClause(uint32 clId){ - clauses_[clId]->destroy(); - clauses_[clId] = 0; - ++stats.clRemoved; - } - void eliminateClause(uint32 id){ - elimTop_ = clauses_[id]->linkRemoved(elimTop_); - clauses_[id] = 0; - ++stats.clRemoved; - } - SharedContext* ctx_; // current context - const Options* opts_; // active options - Clause* elimTop_; // stack of blocked/eliminated clauses + using ClauseList = PodVector_t; + + virtual bool initPreprocess(SatPreParams& opts) = 0; + virtual bool doPreprocess() = 0; + virtual void doExtendModel(ValueVec& m, LitVec& open) = 0; + virtual void doCleanUp() = 0; + Clause* clause(uint32_t clId) { return clauses_[clId]; } + [[nodiscard]] const Clause* clause(uint32_t clId) const { return clauses_[clId]; } + void freezeSeen(); + void discardClauses(bool discardEliminated); + void setClause(uint32_t clId, LitView cl) { clauses_[clId] = Clause::newClause(cl); } + void destroyClause(uint32_t clId) { + clauses_[clId]->destroy(); + clauses_[clId] = nullptr; + ++stats.clRemoved; + } + void eliminateClause(uint32_t id) { + elimTop_ = clauses_[id]->linkRemoved(elimTop_); + clauses_[id] = nullptr; + ++stats.clRemoved; + } + SharedContext* ctx_; // current context + const Options* opts_; // active options + Clause* elimTop_; // stack of blocked/eliminated clauses private: - SatPreprocessor(const SatPreprocessor&); - SatPreprocessor& operator=(const SatPreprocessor&); - ClauseList clauses_; // initial non-unit clauses - LitVec units_; // initial unit clauses - Range32 seen_; // vars seen in previous step + ClauseList clauses_; // initial non-unit clauses + LitVec units_; // initial unit clauses + Range32 seen_; // vars seen in previous step }; /////////////////////////////////////////////////////////////////////////////// @@ -227,79 +222,85 @@ class SatPreprocessor { */ //! A struct for aggregating basic problem statistics. struct ProblemStats { - ProblemStats() { reset(); } - struct { uint32 num, eliminated, frozen; } vars; - struct { uint32 other, binary, ternary; } constraints; - uint32 acycEdges; - uint32 complexity; - void reset() { std::memset(this, 0, sizeof(*this)); } - uint32 numConstraints() const { return constraints.other + constraints.binary + constraints.ternary; } - void diff(const ProblemStats& o) { - vars.num = std::max(vars.num, o.vars.num)-std::min(vars.num, o.vars.num); - vars.eliminated = std::max(vars.eliminated, o.vars.eliminated)-std::min(vars.eliminated, o.vars.eliminated); - vars.frozen = std::max(vars.frozen, o.vars.frozen)-std::min(vars.frozen, o.vars.frozen); - constraints.other = std::max(constraints.other, o.constraints.other) - std::min(constraints.other, o.constraints.other); - constraints.binary = std::max(constraints.binary, o.constraints.binary) - std::min(constraints.binary, o.constraints.binary); - constraints.ternary= std::max(constraints.ternary, o.constraints.ternary) - std::min(constraints.ternary, o.constraints.ternary); - acycEdges = std::max(acycEdges, o.acycEdges) - std::min(acycEdges, o.acycEdges); - } - void accu(const ProblemStats& o) { - vars.num += o.vars.num; - vars.eliminated += o.vars.eliminated; - vars.frozen += o.vars.frozen; - constraints.other += o.constraints.other; - constraints.binary += o.constraints.binary; - constraints.ternary += o.constraints.ternary; - acycEdges += o.acycEdges; - } - // StatisticObject - static uint32 size(); - static const char* key(uint32 i); - StatisticObject at(const char* k) const; + constexpr ProblemStats() = default; + struct { + uint32_t num, eliminated, frozen; + } vars{}; + struct { + uint32_t other, binary, ternary; + } constraints{}; + uint32_t acycEdges{}; + uint32_t complexity{}; + [[nodiscard]] uint32_t numConstraints() const { + return constraints.other + constraints.binary + constraints.ternary; + } + void diff(const ProblemStats& o) { + vars.num = std::max(vars.num, o.vars.num) - std::min(vars.num, o.vars.num); + vars.eliminated = std::max(vars.eliminated, o.vars.eliminated) - std::min(vars.eliminated, o.vars.eliminated); + vars.frozen = std::max(vars.frozen, o.vars.frozen) - std::min(vars.frozen, o.vars.frozen); + constraints.other = + std::max(constraints.other, o.constraints.other) - std::min(constraints.other, o.constraints.other); + constraints.binary = + std::max(constraints.binary, o.constraints.binary) - std::min(constraints.binary, o.constraints.binary); + constraints.ternary = + std::max(constraints.ternary, o.constraints.ternary) - std::min(constraints.ternary, o.constraints.ternary); + acycEdges = std::max(acycEdges, o.acycEdges) - std::min(acycEdges, o.acycEdges); + } + void accu(const ProblemStats& o) { + vars.num += o.vars.num; + vars.eliminated += o.vars.eliminated; + vars.frozen += o.vars.frozen; + constraints.other += o.constraints.other; + constraints.binary += o.constraints.binary; + constraints.ternary += o.constraints.ternary; + acycEdges += o.acycEdges; + } + // StatisticObject + static uint32_t size(); + static const char* key(uint32_t i); + StatisticObject at(const char* k) const; }; //! Stores static information about a variable. struct VarInfo { - //! Possible flags. - enum Flag { - Mark_p = 0x1u, //!< Mark for positive literal. - Mark_n = 0x2u, //!< Mark for negative literal. - Input = 0x4u, //!< Is this var an input variable? - Body = 0x8u, //!< Is this var representing a body? - Eq = 0x10u, //!< Is this var representing both a body and an atom? - Nant = 0x20u, //!< Is this var in NAnt(P)? - Frozen = 0x40u, //!< Is the variable frozen? - Output = 0x80u //!< Is the variable an output variable? - }; - static uint8 flags(VarType t) { - if (t == Var_t::Body) { return VarInfo::Body; } - if (t == Var_t::Hybrid){ return VarInfo::Eq; } - return 0; - } - explicit VarInfo(uint8 r = 0) : rep(r) { } - //! Returns the type of the variable (or Var_t::Hybrid if variable was created with parameter eq=true). - VarType type() const { return has(VarInfo::Eq) ? Var_t::Hybrid : VarType(Var_t::Atom + has(VarInfo::Body)); } - //! Returns whether var is part of negative antecedents (occurs negatively or in the head of a choice rule). - bool nant() const { return has(VarInfo::Nant); } - //! Returns true if var is excluded from variable elimination. - bool frozen() const { return has(VarInfo::Frozen); } - //! Returns true if var is an input variable. - bool input() const { return has(VarInfo::Input); } - //! Returns true if var is marked as output variable. - bool output() const { return has(VarInfo::Output); } - //! Returns the preferred sign of this variable w.r.t its type. - /*! - * \return false (i.e no sign) if var originated from body, otherwise true. - */ - bool preferredSign() const { return !has(VarInfo::Body); } - - bool has(Flag f) const { return (rep & flag(f)) != 0; } - bool has(uint32 f) const { return (rep & f) != 0; } - bool hasAll(uint32 f)const { return (rep & f) == f; } - void set(Flag f) { rep |= flag(f); } - void toggle(Flag f) { rep ^= flag(f); } - static uint8 flag(Flag x) { return uint8(x); } - uint8 rep; + //! Possible flags. + enum Flag : uint32_t { + flag_pos = 0x1u, //!< Mark for positive literal. + flag_neg = 0x2u, //!< Mark for negative literal. + flag_input = 0x4u, //!< Is this var an input variable? + flag_body = 0x8u, //!< Is this var representing a body? + flag_eq = 0x10u, //!< Is this var representing both a body and an atom? + flag_nant = 0x20u, //!< Is this var in NAnt(P)? + flag_frozen = 0x40u, //!< Is the variable frozen? + flag_output = 0x80u //!< Is the variable an output variable? + }; + constexpr explicit VarInfo(uint8_t r = 0) : rep(r) {} + //! Returns the type of the variable (or VarType::hybrid if variable was created with parameter eq=true). + [[nodiscard]] constexpr VarType type() const { + return has(flag_eq) ? VarType::hybrid : static_cast(+VarType::atom + has(flag_body)); + } + //! Returns whether variable is an atom (or hybrid). + [[nodiscard]] constexpr bool atom() const { return Potassco::test(type(), VarType::atom); } + //! Returns whether var is part of negative antecedents (occurs negatively or in the head of a choice rule). + [[nodiscard]] constexpr bool nant() const { return has(flag_nant); } + //! Returns true if var is excluded from variable elimination. + [[nodiscard]] constexpr bool frozen() const { return has(flag_frozen); } + //! Returns true if var is an input variable. + [[nodiscard]] constexpr bool input() const { return has(flag_input); } + //! Returns true if var is marked as output variable. + [[nodiscard]] constexpr bool output() const { return has(flag_output); } + //! Returns the preferred sign of this variable w.r.t its type. + /*! + * \return false (i.e. no sign) if var originated from body, otherwise true. + */ + [[nodiscard]] constexpr bool preferredSign() const { return not has(flag_body); } + + [[nodiscard]] constexpr bool has(Flag f) const { return Potassco::test_mask(rep, f); } + [[nodiscard]] constexpr bool has(uint32_t f) const { return Potassco::test_any(rep, f); } + [[nodiscard]] constexpr bool hasAll(uint32_t f) const { return Potassco::test_mask(rep, f); } + constexpr void set(Flag f) { rep = Potassco::set_mask(rep, f); } + constexpr void toggle(Flag f) { rep = Potassco::toggle_mask(rep, f); } + uint8_t rep; }; //! A class for efficiently storing and propagating binary and ternary clauses. @@ -308,283 +309,308 @@ struct VarInfo { */ class ShortImplicationsGraph { public: - ShortImplicationsGraph(); - ~ShortImplicationsGraph(); - enum ImpType { binary_imp = 2, ternary_imp = 3 }; - - //! Makes room for nodes number of nodes. - void resize(uint32 nodes); - //! Mark the instance as shared/unshared. - /*! - * A shared instance adds learnt binary/ternary clauses - * to specialized shared blocks of memory. - */ - void markShared(bool b) { shared_ = b; } - //! Adds the given constraint to the implication graph. - /*! - * \return true iff a new implication was added. - */ - bool add(ImpType t, bool learnt, const Literal* lits); - - //! Removes p and its implications. - /*! - * \pre s.isTrue(p) - */ - void removeTrue(const Solver& s, Literal p); - - //! Propagates consequences of p following from binary and ternary clauses. - /*! - * \pre s.isTrue(p) - */ - bool propagate(Solver& s, Literal p) const; - //! Propagates immediate consequences of p following from binary clauses only. - bool propagateBin(Assignment& out, Literal p, uint32 dl) const; - //! Checks whether there is a reverse arc implying p and if so returns it in out. - bool reverseArc(const Solver& s, Literal p, uint32 maxLev, Antecedent& out) const; - - uint32 numBinary() const { return bin_[0]; } - uint32 numTernary()const { return tern_[0]; } - uint32 numLearnt() const { return bin_[1] + tern_[1]; } - uint32 numEdges(Literal p) const; - uint32 size() const { return sizeVec(graph_); } - //! Applies op on all unary- and binary implications following from p. - /*! - * OP must provide two functions: - * - bool unary(Literal, Literal) - * - bool binary(Literal, Literal, Literal) - * The first argument will be p, the second (resp. third) the unary - * (resp. binary) clause implied by p. - * \note For learnt imps, at least one literal has its watch-flag set. - */ - template - bool forEach(Literal p, const OP& op) const { - const ImplicationList& x = graph_[p.id()]; - if (x.empty()) return true; - ImplicationList::const_right_iterator rEnd = x.right_end(); // prefetch - for (ImplicationList::const_left_iterator it = x.left_begin(), end = x.left_end(); it != end; ++it) { - if (!op.unary(p, *it)) { return false; } - } - for (ImplicationList::const_right_iterator it = x.right_begin(); it != rEnd; ++it) { - if (!op.binary(p, it->first, it->second)) { return false; } - } + ShortImplicationsGraph() = default; + ~ShortImplicationsGraph(); + ShortImplicationsGraph(ShortImplicationsGraph&&) = delete; + + //! Makes room for nodes number of nodes. + void resize(uint32_t nodes); + //! Mark the instance as shared/unshared. + /*! + * A shared instance adds learnt binary/ternary clauses + * to specialized shared blocks of memory. + */ + void markShared(bool b) { shared_ = b; } + //! Check and avoid duplicates when simplifying ternary clauses + void setSimpMode(ContextParams::ShortSimpMode x) { simp_ = x; } + //! Adds the given constraint to the implication graph. + /*! + * \return true iff a new implication was added. + */ + bool add(LitView lits, bool learnt); + + //! Removes p and its implications. + /*! + * \pre s.isTrue(p) + */ + void removeTrue(const Solver& s, Literal p); + + //! Propagates consequences of p following from binary and ternary clauses. + /*! + * \pre s.isTrue(p) + */ + bool propagate(Solver& s, Literal p) const; + //! Propagates immediate consequences of p following from binary clauses only. + bool propagateBin(Assignment& out, Literal p, uint32_t dl) const; + //! Checks whether there is a reverse arc implying p and if so returns it in out. + bool reverseArc(const Solver& s, Literal p, uint32_t maxLev, Antecedent& out) const; + + [[nodiscard]] uint32_t numBinary() const { return bin_[0]; } + [[nodiscard]] uint32_t numTernary() const { return tern_[0]; } + [[nodiscard]] uint32_t numLearnt() const { return bin_[1] + tern_[1]; } + [[nodiscard]] uint32_t numEdges(Literal p) const; + [[nodiscard]] uint32_t size() const { return size32(graph_); } + [[nodiscard]] auto simpMode() const -> ContextParams::ShortSimpMode { + return static_cast(simp_); + } + //! Applies op on all unary- and binary implications following from p. + /*! + * Op must be a callable with two signatures: + * - (Literal, Literal) -> bool + * - (Literal, Literal, Literal) -> bool + * The first argument will be p, the second (resp. third) the unary (resp. binary) clause implied by p. + * \note For learnt implications, at least one literal has its watch-flag set. + */ + template + bool forEach(Literal p, const Op& op) const { + const auto& x = graph_[p.id()]; + if (x.empty()) { + return true; + } + auto rEnd = x.right_end(); // prefetch + for (auto it = x.left_begin(), end = x.left_end(); it != end; ++it) { + if (not op(p, *it)) { + return false; + } + } + for (auto it = x.right_begin(); it != rEnd; ++it) { + if (const auto& t = *it; not op(p, t[0], t[1])) { + return false; + } + } #if CLASP_HAS_THREADS - for (Block* b = (x).learnt; b ; b = b->next) { - p.flag(); bool r = true; - for (Block::const_iterator imp = b->begin(), endOf = b->end(); imp != endOf; ) { - if (!imp->flagged()) { r = op.binary(p, imp[0], imp[1]); imp += 2; } - else { r = op.unary(p, imp[0]); imp += 1; } - if (!r) { return false; } - } - } + return x.forEachLearnt(p, op); +#else + return true; #endif - return true; - } + } + private: - ShortImplicationsGraph(const ShortImplicationsGraph&); - ShortImplicationsGraph& operator=(ShortImplicationsGraph&); - struct Propagate; - struct ReverseArc; + using Tern = std::array; #if CLASP_HAS_THREADS - struct Block { - typedef Clasp::mt::atomic atomic_size; - typedef Clasp::mt::atomic atomic_ptr; - typedef const Literal* const_iterator; - typedef Literal* iterator; - enum { block_cap = (64 - (sizeof(atomic_size)+sizeof(atomic_ptr)))/sizeof(Literal) }; - explicit Block(); - const_iterator begin() const { return data; } - const_iterator end() const { return data+size(); } - iterator end() { return data+size(); } - uint32 size() const { return size_lock >> 1; } - bool tryLock(uint32& lockedSize); - void addUnlock(uint32 lockedSize, const Literal* x, uint32 xs); - atomic_ptr next; - atomic_size size_lock; - Literal data[block_cap]; - }; - typedef Block::atomic_ptr SharedBlockPtr; - typedef bk_lib::left_right_sequence, 64-sizeof(SharedBlockPtr)> ImpListBase; - struct ImplicationList : public ImpListBase { - ImplicationList() : ImpListBase() { learnt = 0; } - ImplicationList(const ImplicationList& other) : ImpListBase(other), learnt() { - learnt = static_cast(other.learnt); - } - ImplicationList& operator=(const ImplicationList& other) { - ImpListBase::operator=(other); - learnt = static_cast(other.learnt); - return *this; - } - ~ImplicationList(); - bool hasLearnt(Literal q, Literal r = lit_false()) const; - void addLearnt(Literal q, Literal r = lit_false()); - void simplifyLearnt(const Solver& s); - bool empty() const { return ImpListBase::empty() && learnt == static_cast(0); } - void move(ImplicationList& other); - void clear(bool b); - SharedBlockPtr learnt; - }; + class Block { + public: + using const_iterator = const Literal*; // NOLINT + using iterator = Literal*; // NOLINT + explicit Block(Block* n, const Literal* x, uint32_t xs); + [[nodiscard]] const_iterator begin() const { return data_; } + [[nodiscard]] const_iterator end() const { return data_ + size(); } + [[nodiscard]] uint32_t size() const { return sizeLock_.load(std::memory_order_acquire) >> size_shift; } + [[nodiscard]] Block* next() const { return next_; } + bool tryLock(uint32_t& lockedSize); + bool addUnlock(uint32_t lockedSize, const Literal* x, uint32_t xs); + + private: + static constexpr auto block_cap = 13u; + static constexpr auto lock_mask = 1u; + static constexpr auto size_shift = 1u; + using SizeLock = std::atomic; + Block* next_; + SizeLock sizeLock_; + Literal data_[block_cap]; + }; + using SharedBlockPtr = std::atomic; + using ImpListBase = bk_lib::left_right_sequence; + struct ImplicationList : public ImpListBase { + ImplicationList() = default; + ImplicationList(const ImplicationList& other) : ImpListBase(other), learnt(other.learnt.load()) {} + ImplicationList(ImplicationList&& other) noexcept + : ImpListBase(static_cast(other)) + , learnt(other.learnt.exchange(nullptr)) {} + ImplicationList& operator=(const ImplicationList& other) = delete; + ImplicationList& operator=(ImplicationList&& other) noexcept { + if (this != &other) { + resetLearnt(); + ImpListBase::operator=(static_cast(other)); + learnt = other.learnt.exchange(nullptr); + } + return *this; + } + ~ImplicationList(); + [[nodiscard]] bool hasLearnt(Literal q, Literal r = lit_false) const; + void addLearnt(Literal q, Literal r = lit_false); + void reset(); + void resetLearnt(); + [[nodiscard]] bool empty() const { return ImpListBase::empty() && learnt == static_cast(nullptr); } + template + bool forEachLearnt(Literal p, const Op& op) const { + for (Block* b = learnt; b; b = b->next()) { + for (auto imp = b->begin(), endOf = b->end(); imp != endOf;) { + auto sz = 2u - imp->flagged(); + if (not(sz == 1 ? op(p, imp[0]) : op(p, imp[0], imp[1]))) { + return false; + } + imp += sz; + } + } + return true; + } + SharedBlockPtr learnt = nullptr; + }; #else - typedef bk_lib::left_right_sequence, 64> ImplicationList; + using ImplicationList = bk_lib::left_right_sequence; #endif - ImplicationList& getList(Literal p) { return graph_[p.id()]; } - void remove_bin(ImplicationList& w, Literal p); - void remove_tern(ImplicationList& w, Literal p); - typedef PodVector::type ImpLists; - ImpLists graph_; // one implication list for each literal - uint32 bin_[2]; // number of binary constraints (0: problem / 1: learnt) - uint32 tern_[2]; // number of ternary constraints(0: problem / 1: learnt) - bool shared_; + using ImpLists = PodVector_t; + auto getList(Literal p) -> ImplicationList& { return graph_[p.id()]; } + void removeTern(const Solver& s, const Tern& t, Literal p); + void removeBin(Literal other, Literal sat); + ImpLists graph_; // one implication list for each literal + uint32_t bin_[2]{}; // number of binary constraints (0: problem / 1: learnt) + uint32_t tern_[2]{}; // number of ternary constraints(0: problem / 1: learnt) + bool shared_{false}; // shared between multiple threads? + uint8_t simp_{0}; // check duplicates during simplification }; //! Base class for distributing learnt knowledge between solvers. class Distributor { public: - struct Policy { - enum Types { - no = 0, - conflict = Constraint_t::Conflict, - loop = Constraint_t::Loop, - all = conflict | loop, - implicit = all + 1 - }; - Policy(uint32 a_sz = 0, uint32 a_lbd = 0, uint32 a_type = 0) : size(a_sz), lbd(a_lbd), types(a_type) {} - uint32 size : 22; /*!< Allow distribution up to this size only. */ - uint32 lbd : 7; /*!< Allow distribution up to this lbd only. */ - uint32 types : 3; /*!< Restrict distribution to these types. */ - }; - static uint64 mask(uint32 i) { return uint64(1) << i; } - static uint64 initSet(uint32 sz) { return sz < 64 ? (uint64(1) << sz) - 1 : UINT64_MAX; } - static bool inSet(uint64 s, uint32 id) { return (s & mask(id)) != 0; } - explicit Distributor(const Policy& p); - virtual ~Distributor(); - bool isCandidate(uint32 size, uint32 lbd, uint32 type) const { - return size <= policy_.size && lbd <= policy_.lbd && ((type & policy_.types) != 0); - } - virtual void publish(const Solver& source, SharedLiterals* lits) = 0; - virtual uint32 receive(const Solver& in, SharedLiterals** out, uint32 maxOut) = 0; + struct Policy { + enum Types : uint32_t { + no = 0, + conflict = static_cast(ConstraintType::conflict), + loop = static_cast(ConstraintType::loop), + all = conflict | loop, + implicit = all + 1 + }; + Policy(uint32_t a_sz = 0, uint32_t a_lbd = 0, uint32_t a_type = 0) : size(a_sz), lbd(a_lbd), types(a_type) {} + uint32_t size : 22; /*!< Allow distribution up to this size only. */ + uint32_t lbd : 7; /*!< Allow distribution up to this lbd only. */ + uint32_t types : 3; /*!< Restrict distribution to these types. */ + }; + explicit Distributor(const Policy& p); + virtual ~Distributor(); + Distributor(Distributor&&) = delete; + + [[nodiscard]] bool isCandidate(uint32_t size, uint32_t lbd, uint32_t type) const { + return size <= policy_.size && lbd <= policy_.lbd && Potassco::test_any(policy_.types, type); + } + [[nodiscard]] bool isCandidate(uint32_t size, uint32_t lbd, ConstraintType type) const { + return isCandidate(size, lbd, +type); + } + [[nodiscard]] bool isCandidate(uint32_t size, const ConstraintInfo& extra) const { + return size <= 3u || isCandidate(size, extra.lbd(), extra.type()); + } + virtual void publish(const Solver& source, SharedLiterals* lits) = 0; + virtual uint32_t receive(const Solver& in, SharedLiterals** out, uint32_t maxOut) = 0; + private: - Distributor(const Distributor&); - Distributor& operator=(const Distributor&); - Policy policy_; + Policy policy_; }; //! Output table that contains predicates to be output on model. class OutputTable { public: - typedef ConstString NameType; - typedef Range32 RangeType; - struct PredType { - NameType name; - Literal cond; - uint32 user; - }; - typedef PodVector::type FactVec; - typedef PodVector::type PredVec; - typedef FactVec::const_iterator fact_iterator; - typedef PredVec::const_iterator pred_iterator; - typedef num_iterator range_iterator; - typedef LitVec::const_iterator lit_iterator; - - OutputTable(); - ~OutputTable(); - //! Ignore predicates starting with c. - void setFilter(char c); - //! Adds a fact. - bool add(const NameType& fact); - //! Adds an output predicate, i.e. n is output if c is true. - bool add(const NameType& n, Literal c, uint32 u = 0); - //! Sets a range of output variables. - void setVarRange(const RangeType& r); - void setProjectMode(ProjectMode m); - - //! Returns whether n would be filtered out. - bool filter(const NameType& n) const; - - fact_iterator fact_begin() const { return facts_.begin(); } - fact_iterator fact_end() const { return facts_.end(); } - pred_iterator pred_begin() const { return preds_.begin(); } - pred_iterator pred_end() const { return preds_.end(); } - range_iterator vars_begin() const { return range_iterator(vars_.lo); } - range_iterator vars_end() const { return range_iterator(vars_.hi); } - RangeType vars_range() const { return vars_; } - - ProjectMode projectMode()const { return projMode_ ? static_cast(projMode_) : hasProject() ? ProjectMode_t::Explicit : ProjectMode_t::Output; } - bool hasProject() const { return !proj_.empty(); } - lit_iterator proj_begin() const { return proj_.begin(); } - lit_iterator proj_end() const { return proj_.end(); } - void addProject(Literal x); - void clearProject(); - - //! Returns the number of output elements, counting each element in a var range. - uint32 size() const; - uint32 numFacts() const { return static_cast(facts_.size()); } - uint32 numPreds() const { return static_cast(preds_.size()); } - uint32 numVars() const { return static_cast(vars_.hi - vars_.lo); } - - //! An optional callback for getting additional theory output. - class Theory { - public: - virtual ~Theory(); - //! Called once on new model m. Shall return 0 to indicate no output. - virtual const char* first(const Model& m) = 0; - //! Shall return 0 to indicate no output. - virtual const char* next() = 0; - }* theory; + using NameType = Potassco::ConstString; + struct PredType { + NameType name; + Literal cond; + uint32_t user; + }; + using FactVec = PodVector_t; + using PredVec = PodVector_t; + using FactSpan = SpanView; + using PredSpan = SpanView; + + OutputTable(); + ~OutputTable(); + //! Ignore predicates starting with c. + void setFilter(char c); + //! Adds a fact. + bool add(const NameType& fact); + bool add(const std::string_view& fact); + //! Adds an output predicate, i.e. n is output if c is true. + bool add(const NameType& n, Literal c, uint32_t u = 0); + bool add(const std::string_view& n, Literal c, uint32_t u = 0); + + //! Sets a range of output variables. + void setVarRange(const Range32& r); + void setProjectMode(ProjectMode m); + + //! Returns whether n would be filtered out. + [[nodiscard]] bool filter(const NameType& n) const; + [[nodiscard]] bool filter(const std::string_view& n) const; + + [[nodiscard]] FactSpan fact_range() const { return facts_; } + [[nodiscard]] PredSpan pred_range() const { return preds_; } + [[nodiscard]] auto vars_range() const { return irange(vars_.lo, vars_.hi); } + + [[nodiscard]] ProjectMode projectMode() const { + return projMode_ != ProjectMode::implicit ? projMode_ + : hasProject() ? ProjectMode::project + : ProjectMode::output; + } + [[nodiscard]] bool hasProject() const { return not proj_.empty(); } + [[nodiscard]] LitView proj_range() const { return proj_; } + void addProject(Literal x); + void clearProject(); + + //! Returns the number of output elements, counting each element in a var range. + [[nodiscard]] uint32_t size() const; + [[nodiscard]] uint32_t numFacts() const { return size32(facts_); } + [[nodiscard]] uint32_t numPreds() const { return size32(preds_); } + [[nodiscard]] uint32_t numVars() const { return vars_.hi - vars_.lo; } + + //! An optional callback for getting additional theory output. + class Theory { + public: + virtual ~Theory(); + //! Called once on new model m. Shall return 0 to indicate no output. + virtual const char* first(const Model& m) = 0; + //! Shall return 0 to indicate no output. + virtual const char* next() = 0; + }* theory; + private: - FactVec facts_; - PredVec preds_; - LitVec proj_; - Range32 vars_; - int projMode_; - char hide_; + FactVec facts_; + PredVec preds_; + LitVec proj_; + Range32 vars_; + ProjectMode projMode_; + char hide_; }; //! A type for storing information to be used in clasp's domain heuristic. class DomainTable { public: - DomainTable(); - //! A type storing a single domain modification for a variable. - class ValueType { - public: - ValueType(Var v, DomModType t, int16 bias, uint16 prio, Literal cond); - bool hasCondition() const { return cond_ != 0; } - Literal cond() const { return Literal::fromId(cond_); } - Var var() const { return var_; } - DomModType type() const; - int16 bias() const { return bias_; } - uint16 prio() const { return prio_; } - bool comp() const { return comp_ != 0; } - private: - uint32 cond_ : 31; - uint32 comp_ : 1; - uint32 var_ : 30; - uint32 type_ : 2; - int16 bias_; - uint16 prio_; - }; - typedef PodVector::type DomVec; - typedef DomVec::const_iterator iterator; - - void add(Var v, DomModType t, int16 bias, uint16 prio, Literal cond); - uint32 simplify(); - void reset(); - bool empty() const; - uint32 size() const; - iterator begin() const; - iterator end() const; - LitVec* assume; - - class DefaultAction { - public: - virtual ~DefaultAction(); - virtual void atom(Literal p, HeuParams::DomPref, uint32 strat) = 0; - }; - static void applyDefault(const SharedContext& ctx, DefaultAction& action, uint32 prefSet = 0); + DomainTable(); + //! A type storing a single domain modification for a variable. + class ValueType { + public: + ValueType(Var_t v, DomModType t, int16_t bias, uint16_t prio, Literal cond); + [[nodiscard]] bool hasCondition() const { return cond_ != 0; } + [[nodiscard]] Literal cond() const { return Literal::fromId(cond_); } + [[nodiscard]] Var_t var() const { return var_; } + [[nodiscard]] DomModType type() const; + [[nodiscard]] int16_t bias() const { return bias_; } + [[nodiscard]] uint16_t prio() const { return prio_; } + [[nodiscard]] bool comp() const { return comp_ != 0; } + + private: + uint32_t cond_ : 31; + uint32_t comp_ : 1; + uint32_t var_ : 30; + uint32_t type_ : 2; + int16_t bias_; + uint16_t prio_; + }; + using DomVec = PodVector_t; + using iterator = DomVec::const_iterator; // NOLINT + + void add(Var_t v, DomModType t, int16_t bias, uint16_t prio, Literal cond); + uint32_t simplify(); + void reset(); + [[nodiscard]] bool empty() const; + [[nodiscard]] uint32_t size() const; + [[nodiscard]] iterator begin() const; + [[nodiscard]] iterator end() const; + [[nodiscard]] auto dropView(uint32_t offset = 0u) const { return drop(entries_, offset); } + LitVec* assume; + + using DefaultAction = std::function; + static void applyDefault(const SharedContext& ctx, const DefaultAction& action, uint32_t prefSet = 0); + private: - static bool cmp(const ValueType& lhs, const ValueType& rhs) { - return lhs.cond() < rhs.cond() || (lhs.cond() == rhs.cond() && lhs.var() < rhs.var()); - } - DomVec entries_; - uint32 seen_; // size of domain table after last simplify + DomVec entries_; + uint32_t seen_; // size of domain table after last simplify }; //! Aggregates information to be shared between solver objects. @@ -601,351 +627,383 @@ class DomainTable { */ class SharedContext { public: - typedef PodVector::type SolverVec; - typedef SingleOwnerPtr SccGraph; - typedef SingleOwnerPtr ExtGraph; - typedef Configuration* ConfigPtr; - typedef SingleOwnerPtr DistrPtr; - typedef const ProblemStats& StatsCRef; - typedef DomainTable DomTab; - typedef OutputTable Output; - typedef LitVec::size_type size_type; - typedef ShortImplicationsGraph ImpGraph; - typedef const ImpGraph& ImpGraphRef; - typedef EventHandler* LogPtr; - typedef SingleOwnerPtrSatPrePtr; - typedef SharedMinimizeData* MinPtr; - enum ResizeMode { resize_reserve = 0u, resize_push = 1u, resize_pop = 2u, resize_resize = 3u}; - enum PreproMode { prepro_preserve_models = 1u, prepro_preserve_shown = 2u, prepro_preserve_heuristic = 4u }; - enum ReportMode { report_default = 0u, report_conflict = 1u }; - enum SolveMode { solve_once = 0u, solve_multi = 1u }; - /*! - * \name Configuration - * \brief Functions for creating and configuring a shared context. - * @{ */ - //! Creates a new object for sharing variables and the binary and ternary implication graph. - explicit SharedContext(); - ~SharedContext(); - //! Resets this object to the state after default construction. - void reset(); - //! Enables event reporting via the given event handler. - void setEventHandler(LogPtr r, ReportMode m = report_default) { progress_ = r; share_.report = uint32(m); } - //! Sets solve mode, which can be used by other objects to query whether multi-shot solving is active. - void setSolveMode(SolveMode m); - //! Sets how to handle physical sharing of constraints. - void setShareMode(ContextParams::ShareMode m); - //! Sets whether the short implication graph should be used for storing short learnt constraints. - void setShortMode(ContextParams::ShortMode m); - //! Sets maximal number of solvers sharing this object. - void setConcurrency(uint32 numSolver, ResizeMode m = resize_reserve); - //! If b is true, sets preprocessing mode to model-preserving operations only. - void setPreserveModels(bool b = true) { setPreproMode(prepro_preserve_models, b); } - //! If b is true, excludes all shown variables from variable elimination. - void setPreserveShown(bool b = true) { setPreproMode(prepro_preserve_shown, b); } - //! If b is true, excludes all variables with domain heuristic modifications from variable elimination. - void setPreserveHeuristic(bool b = true) { setPreproMode(prepro_preserve_heuristic, b); } - - //! Adds an additional solver to this object and returns it. - Solver& pushSolver(); - //! Configures the statistic object of attached solvers. - /*! - * The level determines the amount of extra statistics. - * \see ExtendedStats - * \see JumpStats - */ - void enableStats(uint32 level); - //! Sets the configuration for this object and its attached solvers. - /*! - * \note If ownership is Ownership_t::Acquire, ownership of c is transferred. - */ - void setConfiguration(Configuration* c, Ownership_t::Type ownership); - SatPrePtr satPrepro; /*!< Preprocessor for simplifying the problem. */ - SccGraph sccGraph; /*!< Program dependency graph - only used for ASP-problems. */ - ExtGraph extGraph; /*!< External dependency graph - given by user. */ - - //! Returns the current configuration used in this object. - ConfigPtr configuration() const { return config_.get(); } - //! Returns the active event handler or 0 if none was set. - LogPtr eventHandler() const { return progress_; } - //! Returns whether this object seeds the RNG of new solvers. - bool seedSolvers() const { return share_.seed != 0; } - //! Returns the number of solvers that can share this object. - uint32 concurrency() const { return share_.count; } - bool preserveModels() const { return (share_.satPreM & prepro_preserve_models) != 0; } - bool preserveShown() const { return (share_.satPreM & prepro_preserve_shown) != 0; } - bool preserveHeuristic() const { return (share_.satPreM & prepro_preserve_heuristic) != 0; } - uint32 defaultDomPref() const; - //! Returns whether physical sharing is enabled for constraints of type t. - bool physicalShare(ConstraintType t) const { return (share_.shareM & (1 + (t != Constraint_t::Static))) != 0; } - //! Returns whether physical sharing of problem constraints is enabled. - bool physicalShareProblem() const { return (share_.shareM & ContextParams::share_problem) != 0; } - //! Returns whether short constraints of type t can be stored in the short implication graph. - bool allowImplicit(ConstraintType t) const { return t != Constraint_t::Static ? share_.shortM != ContextParams::short_explicit : !isShared(); } - //! Returns the configured solve mode. - SolveMode solveMode() const { return static_cast(share_.solveM); } - //@} - - /*! - * \name Problem introspection - * \brief Functions for querying information about the problem. - */ - //@{ - //! Returns true unless the master has an unresolvable top-level conflict. - bool ok() const; - //! Returns whether the problem is currently frozen and therefore ready for being solved. - bool frozen() const { return share_.frozen;} - //! Returns whether more than one solver is actively working on the problem. - bool isShared() const { return frozen() && concurrency() > 1; } - //! Returns whether the problem is more than a simple CNF. - bool isExtended() const { return stats_.vars.frozen != 0; } - //! Returns whether this object has a solver associated with the given id. - bool hasSolver(uint32 id) const { return id < solvers_.size(); } - //! Returns the master solver associated with this object. - Solver* master() const { return solver(0); } - //! Returns the solver with the given id. - Solver* solver(uint32 id) const { return solvers_[id]; } - - //! Returns the number of problem variables. - /*! - * \note The special sentinel-var 0 is not counted, i.e. numVars() returns - * the number of problem-variables. - * To iterate over all problem variables use a loop like: - * \code - * for (Var i = 1; i <= numVars(); ++i) {...} - * \endcode - */ - uint32 numVars() const { return static_cast(varInfo_.size() - 1); } - //! Returns the number of eliminated vars. - uint32 numEliminatedVars() const { return stats_.vars.eliminated; } - //! Returns true if var represents a valid variable in this problem. - /*! - * \note The range of valid variables is [1..numVars()]. The variable 0 - * is a special sentinel variable. - */ - bool validVar(Var var) const { return var < static_cast(varInfo_.size()); } - //! Returns information about the given variable. - VarInfo varInfo(Var v) const { assert(validVar(v)); return varInfo_[v]; } - //! Returns true if v is currently eliminated, i.e. no longer part of the problem. - bool eliminated(Var v) const; - bool marked(Literal p) const { return varInfo(p.var()).has(VarInfo::Mark_p + p.sign()); } - //! Returns the number of problem constraints. - uint32 numConstraints() const; - //! Returns the number of binary constraints. - uint32 numBinary() const { return btig_.numBinary(); } - //! Returns the number of ternary constraints. - uint32 numTernary() const { return btig_.numTernary(); } - //! Returns the number of unary constraints. - uint32 numUnary() const { return lastTopLevel_; } - //! Returns an estimate of the problem complexity based on the number and type of constraints. - uint32 problemComplexity() const; - //! Returns whether the problem contains minimize (i.e. weak) constraints. - bool hasMinimize() const; - StatsCRef stats() const { return stats_; } - //@} - - /*! - * \name Problem setup - * \brief Functions for specifying the problem. - * - * Problem specification is a four-stage process: - * -# Add variables to the SharedContext object. - * -# Call startAddConstraints(). - * -# Add problem constraints. - * -# Call endInit() to finish the initialization process. - * . - * \note After endInit() was called, other solvers can be attached to this object. - * \note In incremental setting, the process must be repeated for each incremental step. - * - * \note Problem specification is *not* thread-safe, i.e. during initialization no other thread shall - * access the context. - * - * \note !frozen() is a precondition for all functions in this group! - * @{ */ - //! Unfreezes a frozen program and prepares it for updates. - /*! - * The function also triggers forgetting of volatile knowledge and removes - * any auxiliary variables. - * \see requestStepVar() - * \see Solver::popAuxVar() - */ - bool unfreeze(); - - //! Adds a new variable and returns its numerical id. - /*! - * \param type Type of variable. - * \param flags Additional information associated with the new variable. - * \note Problem variables are numbered from 1 onwards! - */ - Var addVar(VarType type, uint8 flags = VarInfo::Nant | VarInfo::Input) { return addVars(1, type, flags); } - Var addVars(uint32 nVars, VarType type, uint8 flags = VarInfo::Nant | VarInfo::Input); - //! Removes the n most recently added problem variables. - /*! - * \pre The variables have either not yet been committed by a call to startAddConstraints() - * or they do not occur in any constraint. - */ - void popVars(uint32 n = 1); - //! Freezes/defreezes a variable (a frozen var is exempt from Sat-preprocessing). - void setFrozen(Var v, bool b); - //! Marks/unmarks v as input variable. - void setInput(Var v, bool b) { set(v, VarInfo::Input, b); } - //! Marks/unmarks v as output variable. - void setOutput(Var v, bool b) { set(v, VarInfo::Output, b); } - //! Marks/unmarks v as part of negative antecedents. - void setNant(Var v, bool b) { set(v, VarInfo::Nant, b); } - void setVarEq(Var v, bool b) { set(v, VarInfo::Eq, b); } - void set(Var v, VarInfo::Flag f, bool b) { if (b != varInfo(v).has(f)) varInfo_[v].toggle(f); } - void mark(Literal p) { assert(validVar(p.var())); varInfo_[p.var()].rep |= (VarInfo::Mark_p + p.sign()); } - void unmark(Literal p) { assert(validVar(p.var())); varInfo_[p.var()].rep &= ~(VarInfo::Mark_p + p.sign()); } - void unmark(Var v) { assert(validVar(v)); varInfo_[v].rep &= ~(VarInfo::Mark_p|VarInfo::Mark_n); } - //! Eliminates the variable v from the problem. - /*! - * \pre v must not occur in any constraint and frozen(v) == false and value(v) == value_free - */ - void eliminate(Var v); - - //! Prepares the master solver so that constraints can be added. - /*! - * Must be called to publish previously added variables to master solver - * and before constraints over these variables can be added. - * \return The master solver associated with this object. - */ - Solver& startAddConstraints(uint32 constraintGuess = 100); - - //! A convenience method for adding facts to the master. - bool addUnary(Literal x); - //! A convenience method for adding binary clauses. - bool addBinary(Literal x, Literal y); - //! A convenience method for adding ternary clauses. - bool addTernary(Literal x, Literal y, Literal z); - //! A convenience method for adding constraints to the master. - void add(Constraint* c); - //! Add weak constraint :~ x.first \[x.second\@p\]. - void addMinimize(WeightLiteral x, weight_t p); - //! Returns a pointer to an optimized representation of all minimize constraints in this problem. - MinPtr minimize(); - //! List of output predicates and/or variables. - Output output; - //! Set of heuristic modifications. - DomTab heuristic; - //! Requests a special variable for tagging volatile knowledge in multi-shot solving. - /*! - * The step variable is created on the next call to endInit() and removed on the next - * call to unfreeze(). - * Once the step variable S is set, learnt constraints containing ~S are - * considered to be "volatile" and removed on the next call to unfreeze(). - * For this to work correctly, S shall be a root assumption during search. - */ - void requestStepVar(); - //! Finishes initialization of the master solver. - /*! - * The function must be called once before search is started. After endInit() - * was called, previously added solvers can be attached to the - * shared context and learnt constraints may be added to solver. - * \param attachAll If true, also calls attach() for all solvers that were added to this object. - * \return If the constraints are initially conflicting, false. Otherwise, true. - * \note - * The master solver can't recover from top-level conflicts, i.e. if endInit() - * returned false, the solver is in an unusable state. - * \post frozen() - */ - bool endInit(bool attachAll = false); - //@} - - /*! - * \name (Parallel) solving - * Functions to be called during (parallel) solving. - * - * \note If not otherwise noted, the functions in this group can be safely called - * from multiple threads. - * @{ */ - //! Returns the active step literal (see requestStepVar()). - Literal stepLiteral() const { return step_; } - //! Attaches the solver with the given id to this object. - /*! - * \note It is safe to attach multiple solvers concurrently - * but the master solver shall not change during the whole operation. - * - * \pre hasSolver(id) - */ - bool attach(uint32 id) { return attach(*solver(id)); } - bool attach(Solver& s); - - //! Detaches the solver with the given id from this object. - /*! - * The function removes any tentative constraints from s. - * Shall be called once after search has stopped. - * \note The function is concurrency-safe w.r.t to different solver objects, - * i.e. in a parallel search different solvers may call detach() - * concurrently. - */ - void detach(uint32 id, bool reset = false) { return detach(*solver(id), reset); } - void detach(Solver& s, bool reset = false); - - DistrPtr distributor;/*!< Distributor object to use for distribution of learnt constraints.*/ - - uint32 winner() const { return share_.winner; } - void setWinner(uint32 sId) { share_.winner = std::min(sId, concurrency()); } - - //! Simplifies the problem constraints w.r.t the master's assignment. - void simplify(LitVec::size_type trailStart, bool shuffle); - //! Removes the constraint with the given idx from the master's db. - void removeConstraint(uint32 idx, bool detach); - //! Removes all minimize constraints from this object. - void removeMinimize(); - - //! Adds the given short implication to the short implication graph if possible. - /*! - * \return - * - > 0 if implication was added. - * - < 0 if implication can't be added because allowImplicit() is false for ct. - * - = 0 if implication is subsumed by some constraint in the short implication graph. - */ - int addImp(ImpGraph::ImpType t, const Literal* lits, ConstraintType ct); - //! Returns the number of learnt short implications. - uint32 numLearntShort() const { return btig_.numLearnt(); } - ImpGraphRef shortImplications() const { return btig_; } - void report(const Event& ev) const { if (progress_) progress_->dispatch(ev); } - bool report(const Solver& s, const Model& m) const { return !progress_ || progress_->onModel(s, m); } - void report(const char* what, const Solver* s = 0) const; - void report(Event::Subsystem sys) const; - void warn(const char* what) const; - ReportMode reportMode() const { return static_cast(share_.report); } - void initStats(Solver& s) const; - SolverStats& solverStats(uint32 sId) const; // stats of solver i - const SolverStats& accuStats(SolverStats& out) const; // accumulates all solver stats in out - MinPtr minimizeNoCreate() const; - //@} + using SolverVec = PodVector_t; + using SccGraph = std::unique_ptr; + using ExtGraph = std::unique_ptr; + using ConfigPtr = Configuration*; + using DistrPtr = std::unique_ptr; + using StatsCRef = const ProblemStats&; + using DomTab = DomainTable; + using Output = OutputTable; + using ImpGraph = ShortImplicationsGraph; + using ImpGraphRef = const ImpGraph&; + using LogPtr = EventHandler*; + using SatPrePtr = std::unique_ptr; + using MinPtr = SharedMinimizeData*; + enum ResizeMode { resize_reserve = 0u, resize_push = 1u, resize_pop = 2u, resize_resize = 3u }; + enum PreproMode { prepro_preserve_models = 1u, prepro_preserve_shown = 2u, prepro_preserve_heuristic = 4u }; + enum ReportMode { report_default = 0u, report_conflict = 1u }; + enum SolveMode { solve_once = 0u, solve_multi = 1u }; + static constexpr auto markMask(Literal x) { return VarInfo::flag_pos + x.sign(); } + /*! + * \name Configuration + * \brief Functions for creating and configuring a shared context. + * @{ */ + //! Creates a new object for sharing variables and the binary and ternary implication graph. + explicit SharedContext(); + ~SharedContext(); + SharedContext(SharedContext&&) = delete; + //! Resets this object to the state after default construction. + void reset(); + //! Enables event reporting via the given event handler. + void setEventHandler(LogPtr r, ReportMode m = report_default) { + progress_ = r; + share_.report = static_cast(m); + } + //! Sets solve mode, which can be used by other objects to query whether multi-shot solving is active. + void setSolveMode(SolveMode m); + //! Sets how to handle physical sharing of constraints. + void setShareMode(ContextParams::ShareMode m); + //! Sets whether the short implication graph should be used for storing short learnt constraints. + void setShortMode(ContextParams::ShortMode m, ContextParams::ShortSimpMode x = ContextParams::simp_no); + //! Sets maximal number of solvers sharing this object. + void setConcurrency(uint32_t numSolver, ResizeMode m = resize_reserve); + //! If b is true, sets preprocessing mode to model-preserving operations only. + void setPreserveModels(bool b = true) { setPreproMode(prepro_preserve_models, b); } + //! If b is true, excludes all shown variables from variable elimination. + void setPreserveShown(bool b = true) { setPreproMode(prepro_preserve_shown, b); } + //! If b is true, excludes all variables with domain heuristic modifications from variable elimination. + void setPreserveHeuristic(bool b = true) { setPreproMode(prepro_preserve_heuristic, b); } + + //! Adds a solver to this object and returns it. + Solver& pushSolver(); + //! Configures the statistic object of attached solvers. + /*! + * The level determines the amount of extra statistics. + * \see ExtendedStats + * \see JumpStats + */ + void enableStats(uint32_t level); + //! Sets the configuration for this object and its attached solvers. + /*! + * \param c Configuration to use or nullptr to set default configuration. + */ + void setConfiguration(Configuration* c); + + SatPrePtr satPrepro; /*!< Preprocessor for simplifying the problem. */ + SccGraph sccGraph; /*!< Program dependency graph - only used for ASP-problems. */ + ExtGraph extGraph; /*!< External dependency graph - given by user. */ + + //! Returns the current configuration used in this object. + [[nodiscard]] ConfigPtr configuration() const { return config_; } + //! Returns the active event handler or 0 if none was set. + [[nodiscard]] LogPtr eventHandler() const { return progress_; } + //! Returns whether this object seeds the RNG of new solvers. + [[nodiscard]] bool seedSolvers() const { return share_.seed != 0; } + //! Returns the number of solvers that can share this object. + [[nodiscard]] uint32_t concurrency() const { return share_.count; } + [[nodiscard]] bool preserveModels() const { return (share_.satPreM & prepro_preserve_models) != 0; } + [[nodiscard]] bool preserveShown() const { return (share_.satPreM & prepro_preserve_shown) != 0; } + [[nodiscard]] bool preserveHeuristic() const { return (share_.satPreM & prepro_preserve_heuristic) != 0; } + [[nodiscard]] uint32_t defaultDomPref() const; + //! Returns whether physical sharing is enabled for constraints of type t. + [[nodiscard]] bool physicalShare(ConstraintType t) const { + return (share_.shareM & (1 + (t != ConstraintType::static_))) != 0; + } + //! Returns whether physical sharing of problem constraints is enabled. + [[nodiscard]] bool physicalShareProblem() const { return (share_.shareM & ContextParams::share_problem) != 0; } + //! Returns whether short constraints of type t can be stored in the short implication graph. + [[nodiscard]] bool allowImplicit(ConstraintType t) const { + return t != ConstraintType::static_ ? share_.shortM != ContextParams::short_explicit : not isShared(); + } + //! Returns the configured solve mode. + [[nodiscard]] SolveMode solveMode() const { return static_cast(share_.solveM); } + //@} + + /*! + * \name Problem introspection + * \brief Functions for querying information about the problem. + */ + //@{ + //! Returns true unless the master has an unresolvable top-level conflict. + [[nodiscard]] bool ok() const; + //! Returns whether the problem is currently frozen and therefore ready for being solved. + [[nodiscard]] bool frozen() const { return share_.frozen; } + //! Returns whether more than one solver is actively working on the problem. + [[nodiscard]] bool isShared() const { return frozen() && concurrency() > 1; } + //! Returns whether the problem is more than a simple CNF. + [[nodiscard]] bool isExtended() const { return stats_.vars.frozen != 0; } + //! Returns whether this object has a solver associated with the given id. + [[nodiscard]] bool hasSolver(uint32_t id) const { return id < solvers_.size(); } + //! Returns the master solver associated with this object. + [[nodiscard]] Solver* master() const { return solver(0); } + //! Returns the solver with the given id. + [[nodiscard]] Solver* solver(uint32_t id) const { return solvers_[id]; } + + //! Returns the number of problem variables. + /*! + * \note The special sentinel-var 0 is not counted, i.e. numVars() returns + * the number of problem-variables. + * To iterate over all problem variables use a loop like: + * \code + * for (auto v : vars()) {...} + * \endcode + */ + [[nodiscard]] uint32_t numVars() const { return size32(varInfo_) - 1; } + //! Returns the problem variables as an iterable view. + [[nodiscard]] auto vars() const { return irange(1u, numVars() + 1); } + //! Returns the number of eliminated vars. + [[nodiscard]] uint32_t numEliminatedVars() const { return stats_.vars.eliminated; } + //! Returns true if var represents a valid variable in this problem. + /*! + * \note The range of valid variables is [1;numVars()]. The variable 0 + * is a special sentinel variable. + */ + [[nodiscard]] bool validVar(Var_t var) const { return var < size32(varInfo_); } + //! Returns information about the given variable. + [[nodiscard]] VarInfo varInfo(Var_t v) const { + assert(validVar(v)); + return varInfo_[v]; + } + //! Returns true if v is currently eliminated, i.e. no longer part of the problem. + [[nodiscard]] bool eliminated(Var_t v) const; + [[nodiscard]] bool marked(Literal p) const { return varInfo(p.var()).has(markMask(p)); } + //! Returns the number of problem constraints. + [[nodiscard]] uint32_t numConstraints() const; + //! Returns the number of binary constraints. + [[nodiscard]] uint32_t numBinary() const { return btig_.numBinary(); } + //! Returns the number of ternary constraints. + [[nodiscard]] uint32_t numTernary() const { return btig_.numTernary(); } + //! Returns the number of unary constraints. + [[nodiscard]] uint32_t numUnary() const { return lastTopLevel_; } + //! Returns an estimate of the problem complexity based on the number and type of constraints. + [[nodiscard]] uint32_t problemComplexity() const; + //! Returns whether the problem contains minimize (i.e. weak) constraints. + [[nodiscard]] bool hasMinimize() const; + [[nodiscard]] StatsCRef stats() const { return stats_; } + //@} + + /*! + * \name Problem setup + * \brief Functions for specifying the problem. + * + * Problem specification is a four-stage process: + * -# Add variables to the SharedContext object. + * -# Call startAddConstraints(). + * -# Add problem constraints. + * -# Call endInit() to finish the initialization process. + * . + * \note After endInit() was called, other solvers can be attached to this object. + * \note In incremental setting, the process must be repeated for each incremental step. + * + * \note Problem specification is *not* thread-safe, i.e. during initialization no other thread shall + * access the context. + * + * \note !frozen() is a precondition for all functions in this group! + * @{ */ + //! Unfreezes a frozen program and prepares it for updates. + /*! + * The function also triggers forgetting of volatile knowledge and removes + * any auxiliary variables. + * \see requestStepVar() + * \see Solver::popAuxVar() + */ + bool unfreeze(); + + //! Adds a new variable and returns its numerical id. + /*! + * \param type Type of variable. + * \param flags Additional information associated with the new variable. + * \note Problem variables are numbered from 1 onward! + */ + Var_t addVar(VarType type, uint8_t flags = VarInfo::flag_nant | VarInfo::flag_input) { + return addVars(1, type, flags); + } + Var_t addVars(uint32_t nVars, VarType type, uint8_t flags = VarInfo::flag_nant | VarInfo::flag_input); + //! Removes the n most recently added problem variables. + /*! + * \pre The variables have either not yet been committed by a call to startAddConstraints() + * or they do not occur in any constraint. + */ + void popVars(uint32_t n = 1); + //! Freezes/defreezes a variable (a frozen var is exempt from Sat-preprocessing). + void setFrozen(Var_t v, bool b); + //! Marks/unmarks v as input variable. + void setInput(Var_t v, bool b) { set(v, VarInfo::flag_input, b); } + //! Marks/unmarks v as output variable. + void setOutput(Var_t v, bool b) { set(v, VarInfo::flag_output, b); } + //! Marks/unmarks v as part of negative antecedents. + void setNant(Var_t v, bool b) { set(v, VarInfo::flag_nant, b); } + void setVarEq(Var_t v, bool b) { set(v, VarInfo::flag_eq, b); } + void set(Var_t v, VarInfo::Flag f, bool b) { + if (b != varInfo(v).has(f)) { + varInfo_[v].toggle(f); + } + } + void mark(Literal p) { + assert(validVar(p.var())); + Potassco::store_set_mask(varInfo_[p.var()].rep, markMask(p)); + } + void unmark(Literal p) { + assert(validVar(p.var())); + Potassco::store_clear_mask(varInfo_[p.var()].rep, markMask(p)); + } + void unmark(Var_t v) { + assert(validVar(v)); + Potassco::store_clear_mask(varInfo_[v].rep, VarInfo::flag_pos | VarInfo::flag_neg); + } + //! Eliminates the variable v from the problem. + /*! + * \pre v must not occur in any constraint and frozen(v) == false and value(v) == value_free + */ + void eliminate(Var_t v); + + //! Prepares the master solver so that constraints can be added. + /*! + * Must be called to publish previously added variables to master solver + * and before constraints over these variables can be added. + * \return The master solver associated with this object. + */ + Solver& startAddConstraints(uint32_t constraintGuess = 100); + + //! A convenience method for adding facts to the master. + bool addUnary(Literal x); + //! A convenience method for adding binary clauses. + bool addBinary(Literal x, Literal y); + //! A convenience method for adding ternary clauses. + bool addTernary(Literal x, Literal y, Literal z); + //! A convenience method for adding constraints to the master. + void add(Constraint* c); + //! Add weak constraint :~ x.first \[x.second\@p\]. + void addMinimize(WeightLiteral x, Weight_t p); + //! Returns a pointer to an optimized representation of all minimize constraints in this problem. + MinPtr minimize(); + //! List of output predicates and/or variables. + Output output; + //! Set of heuristic modifications. + DomTab heuristic; + //! Requests a special variable for tagging volatile knowledge in multi-shot solving. + /*! + * The step variable is created on the next call to endInit() and removed on the next + * call to unfreeze(). + * Once the step variable S is set, learnt constraints containing ~S are + * considered to be "volatile" and removed on the next call to unfreeze(). + * For this to work correctly, S shall be a root assumption during search. + */ + void requestStepVar(); + //! Finishes initialization of the master solver. + /*! + * The function must be called once before search is started. After endInit() + * was called, previously added solvers can be attached to the + * shared context and learnt constraints may be added to solver. + * \param attachAll If true, also calls attach() for all solvers that were added to this object. + * \return If the constraints are initially conflicting, false. Otherwise, true. + * \note + * The master solver can't recover from top-level conflicts, i.e. if endInit() + * returned false, the solver is in an unusable state. + * \post frozen() + */ + bool endInit(bool attachAll = false); + //@} + + /*! + * \name (Parallel) solving + * Functions to be called during (parallel) solving. + * + * \note If not otherwise noted, the functions in this group can be safely called + * from multiple threads. + * @{ */ + //! Returns the active step literal (see requestStepVar()). + [[nodiscard]] Literal stepLiteral() const { return step_; } + //! Attaches the solver with the given id to this object. + /*! + * \note It is safe to attach multiple solvers concurrently + * but the master solver shall not change during the whole operation. + * + * \pre hasSolver(id) + */ + bool attach(uint32_t id) { return attach(*solver(id)); } + bool attach(Solver& s); + + //! Detaches the solver with the given id from this object. + /*! + * The function removes any tentative constraints from s. + * Shall be called once after search has stopped. + * \note The function is concurrency-safe w.r.t to different solver objects, + * i.e. in a parallel search different solvers may call detach() + * concurrently. + */ + void detach(uint32_t id, bool reset = false) { return detach(*solver(id), reset); } + void detach(Solver& s, bool reset = false); + + DistrPtr distributor; /*!< Distributor object to use for distribution of learnt constraints.*/ + + [[nodiscard]] uint32_t winner() const { return share_.winner; } + void setWinner(uint32_t sId) { share_.winner = std::min(sId, concurrency()); } + + //! Simplifies the problem constraints w.r.t the master's assignment. + void simplify(LitView assigned, bool shuffle); + //! Removes the constraint with the given idx from the master's db. + void removeConstraint(uint32_t idx, bool detach); + //! Removes all minimize constraints from this object. + void removeMinimize(); + + //! Adds the given short implication to the short implication graph if possible. + /*! + * \return + * - > 0 if implication was added. + * - < 0 if implication can't be added because allowImplicit() is false for ct. + * - = 0 if implication is subsumed by some constraint in the short implication graph. + */ + int addImp(LitView lits, ConstraintType ct); + //! Returns the number of learnt short implications. + [[nodiscard]] uint32_t numLearntShort() const { return btig_.numLearnt(); } + [[nodiscard]] ImpGraphRef shortImplications() const { return btig_; } + void report(const Event& ev) const { + if (progress_) { + progress_->dispatch(ev); + } + } + [[nodiscard]] bool report(const Solver& s, const Model& m) const { + return not progress_ || progress_->onModel(s, m); + } + void report(const char* what, const Solver* s = nullptr) const; + void report(Event::Subsystem sys) const; + void warn(const char* what) const; + void warnFmt(const char* fmt, ...) const POTASSCO_ATTRIBUTE_FORMAT(2, 3); + [[nodiscard]] ReportMode reportMode() const { return static_cast(share_.report); } + void initStats(Solver& s) const; + [[nodiscard]] SolverStats& solverStats(uint32_t sId) const; // stats of solver i + const SolverStats& accuStats(SolverStats& out) const; // accumulates all solver stats in out + [[nodiscard]] MinPtr minimizeNoCreate() const; + //@} private: - SharedContext(const SharedContext&); - SharedContext& operator=(const SharedContext&); - bool unfreezeStep(); - Literal addStepLit(); - typedef SingleOwnerPtr Config; - typedef PodVector::type VarVec; - void setPreproMode(uint32 m, bool b); - struct Minimize; - ProblemStats stats_; // problem statistics - VarVec varInfo_; // varInfo[v] stores info about variable v - ImpGraph btig_; // binary-/ternary implication graph - Config config_; // active configuration - SolverVec solvers_; // solvers associated with this context - Minimize* mini_; // pointer to set of weak constraints - LogPtr progress_; // event handler or 0 if not used - Literal step_; // literal for tagging enumeration/step constraints - uint32 lastTopLevel_; // size of master's top-level after last init - struct Share { // Additional data - uint32 count :10; // max number of objects sharing this object - uint32 winner :10; // id of solver that terminated the search - uint32 shareM : 3; // physical sharing mode - uint32 shortM : 1; // short clause mode - uint32 solveM : 1; // solve mode - uint32 frozen : 1; // is adding of problem constraints allowed? - uint32 seed : 1; // set seed of new solvers - uint32 satPreM : 3; // preprocessing mode - uint32 report : 2; // report mode - Share() : count(1), winner(0), shareM((uint32)ContextParams::share_auto), shortM(0), solveM(0), frozen(0), seed(0), satPreM(0), report(0) {} - } share_; + bool unfreezeStep(); + Literal addStepLit(); + using VarVec = PodVector_t; + void setPreproMode(uint32_t m, bool b); + struct Minimize; + using MiniPtr = std::unique_ptr; + ProblemStats stats_; // problem statistics + VarVec varInfo_; // varInfo[v] stores info about variable v + ImpGraph btig_; // binary-/ternary implication graph + ConfigPtr config_; // active configuration + SolverVec solvers_; // solvers associated with this context + MiniPtr mini_; // pointer to set of weak constraints + LogPtr progress_; // event handler or 0 if not used + Literal step_; // literal for tagging enumeration/step constraints + uint32_t lastTopLevel_; // size of master's top-level after last init + struct Share { // Additional data + uint32_t count : 10 = 1; // max number of objects sharing this object + uint32_t winner : 10 = 0; // id of solver that terminated the search + uint32_t shareM : 3 = ContextParams::share_auto; // physical sharing mode + uint32_t shortM : 1 = 0; // short clause mode + uint32_t solveM : 1 = 0; // solve mode + uint32_t frozen : 1 = 0; // is adding of problem constraints allowed? + uint32_t seed : 1 = 0; // set seed of new solvers + uint32_t satPreM : 3 = 0; // preprocessing mode + uint32_t report : 2 = 0; // report mode + } share_; }; //@} -} -#endif +} // namespace Clasp diff --git a/clasp/solve_algorithms.h b/clasp/solve_algorithms.h index 319432c..b669f18 100644 --- a/clasp/solve_algorithms.h +++ b/clasp/solve_algorithms.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,12 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SOLVE_ALGORITHMS_H_INCLUDED -#define CLASP_SOLVE_ALGORITHMS_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include /*! @@ -40,14 +35,11 @@ namespace Clasp { //! Type for holding global solve limits. struct SolveLimits { - explicit SolveLimits(uint64 conf = UINT64_MAX, uint64 r = UINT64_MAX) - : conflicts(conf) - , restarts(r) { - } - bool reached() const { return conflicts == 0 || restarts == 0; } - bool enabled() const { return conflicts != UINT64_MAX || restarts != UINT64_MAX; } - uint64 conflicts; /*!< Number of conflicts. */ - uint64 restarts; /*!< Number of restarts. */ + explicit SolveLimits(uint64_t conf = UINT64_MAX, uint64_t r = UINT64_MAX) : conflicts(conf), restarts(r) {} + [[nodiscard]] bool reached() const { return conflicts == 0 || restarts == 0; } + [[nodiscard]] bool enabled() const { return conflicts != UINT64_MAX || restarts != UINT64_MAX; } + uint64_t conflicts; /*!< Number of conflicts. */ + uint64_t restarts; /*!< Number of restarts. */ }; /////////////////////////////////////////////////////////////////////////////// @@ -56,72 +48,77 @@ struct SolveLimits { //! Basic (sequential) solving using given solving options. class BasicSolve { public: - //! Creates a new object for solving with the given solver using the given solving options. - /*! - * If an optional solve limit is given, solving stops once this limit is reached. - * \pre s is attached to a problem (SharedContext). - */ - BasicSolve(Solver& s, const SolveParams& p, const SolveLimits& lim = SolveLimits()); - BasicSolve(Solver& s, const SolveLimits& lim = SolveLimits()); - ~BasicSolve(); + //! Creates a new object for solving with the given solver using the given solving options. + /*! + * If an optional solve limit is given, solving stops once this limit is reached. + * \pre s is attached to a problem (SharedContext). + */ + BasicSolve(Solver& s, const SolveParams& p, const SolveLimits& lim = SolveLimits()); + explicit BasicSolve(Solver& s, const SolveLimits& lim = SolveLimits()); + ~BasicSolve(); + BasicSolve(BasicSolve&&) = delete; + + [[nodiscard]] bool hasLimit() const { return limits_.enabled(); } + + //! Enables solving under the given assumptions. + /*! + * The use of assumptions allows for incremental solving. Literals contained + * in assumptions are assumed to be true during search but can be undone thereafter. + * + * \param assumptions A list of unit assumptions to be assumed true. + * \return false if assumptions are conflicting. + */ + bool assume(LitView assumptions); - bool hasLimit() const { return limits_.enabled(); } + //! Solves the path stored in the given solver using the given solving options. + /*! + * \return + * - value_true if search stopped on a model. + * - value_false if the search-space was completely examined. + * - value_free if the given solve limit was hit. + * + * \note + * The function maintains the current solving state (number of restarts, learnt limits, ...) + * between calls. + */ + Val_t solve(); + //! Returns whether the given problem is satisfiable under the given assumptions. + /*! + * Calls assume(assumptions) followed by solve() but does not maintain any solving state. + * \param assumptions Possibly empty set of assumptions to apply before solving. + * \param init Call InitParams::randomize() before starting search? + */ + [[nodiscard]] bool satisfiable(LitView assumptions, bool init) const; - //! Enables solving under the given assumptions. - /*! - * The use of assumptions allows for incremental solving. Literals contained - * in assumptions are assumed to be true during search but can be undone afterwards. - * - * \param assumptions A list of unit assumptions to be assumed true. - * \return false if assumptions are conflicting. - */ - bool assume(const LitVec& assumptions); + //! Replaces *this with BasicSolve(s, p). + void reset(Solver& s, const SolveParams& p, const SolveLimits& lim = SolveLimits()); + //! Resets the internal solving state while keeping the solver and the solving options. + void reset(); - //! Solves the path stored in the given solver using the given solving options. - /*! - * \return - * - value_true if search stopped on a model. - * - value_false if the search-space was completely examined. - * - value_free if the given solve limit was hit. - * - * \note - * The function maintains the current solving state (number of restarts, learnt limits, ...) - * between calls. - */ - ValueRep solve(); - //! Returns whether the given problem is satisfiable under the given assumptions. - /*! - * Calls assume(assumptions) followed by solve() but does not maintain any solving state. - * \param assumptions Possibly empty set of assumptions to apply before solving. - * \param init Call InitParams::randomize() before starting search? - */ - bool satisfiable(const LitVec& assumptions, bool init); + Solver& solver() { return *solver_; } - //! Resets the internal solving state while keeping the solver and the solving options. - void reset(bool reinit = false); - //! Replaces *this with BasicSolve(s, p). - void reset(Solver& s, const SolveParams& p, const SolveLimits& lim = SolveLimits()); - Solver& solver() { return *solver_; } private: - BasicSolve(const BasicSolve&); - BasicSolve& operator=(const BasicSolve&); - typedef const SolveParams Params; - typedef SolveLimits Limits; - struct State; - Solver* solver_; // active solver - Params* params_; // active solving options - Limits limits_; // active solving limits - State* state_; // internal solving state + struct State; + using Params = const SolveParams; + using Limits = SolveLimits; + using StatePtr = std::unique_ptr; + Solver* solver_; // active solver + Params* params_; // active solving options + Limits limits_; // active solving limits + StatePtr state_; // internal solving state }; //! Event type for reporting basic solve events like restarts or deletion. -struct BasicSolveEvent : SolveEvent { - //! Type of operation that emitted the event. - enum EventOp { event_none = 0, event_deletion = 'D', event_exit = 'E', event_grow = 'G', event_restart = 'R' }; - BasicSolveEvent(const Solver& s, EventOp a_op, uint64 cLim, uint32 lLim) : SolveEvent(s, verbosity_max), cLimit(cLim), lLimit(lLim) { - op = a_op; - } - uint64 cLimit; //!< Next conflict limit - uint32 lLimit; //!< Next learnt limit +struct BasicSolveEvent : SolveEvent { + //! Type of operation that emitted the event. + enum EventOp { event_none = 0, event_deletion = 'D', event_exit = 'E', event_grow = 'G', event_restart = 'R' }; + BasicSolveEvent(const Solver& s, EventOp a_op, uint64_t cLim, uint32_t lLim) + : SolveEvent(this, s, verbosity_max) + , cLimit(cLim) + , lLimit(lLim) { + op = a_op; + } + uint64_t cLimit; //!< Next conflict limit + uint32_t lLimit; //!< Next learnt limit }; /////////////////////////////////////////////////////////////////////////////// // General solve @@ -135,157 +132,182 @@ class Enumerator; */ class SolveAlgorithm { public: - /*! - * \param limit An optional solve limit applied in solve(). - */ - explicit SolveAlgorithm(const SolveLimits& limit = SolveLimits()); - virtual ~SolveAlgorithm(); + class Path { + public: + using trivially_relocatable = std::true_type; // NOLINT + constexpr Path() = default; + static Path acquire(LitView path); + static Path borrow(LitView path); + ~Path(); + Path(Path&& other) noexcept; + Path(const Path&) = delete; + Path& operator=(const Path& other) = delete; + Path& operator=(Path&& other) noexcept; + + [[nodiscard]] const Literal* begin() const { return lits_.get(); } + [[nodiscard]] const Literal* end() const { return lits_.get() + size_; } + [[nodiscard]] operator LitView() const { return {lits_.get(), size_}; } + [[nodiscard]] bool owner() const { return lits_.test<0>(); } + + private: + using Ptr = TaggedPtr; + Path(Ptr p, std::size_t sz) : lits_(p), size_(sz) {} + Ptr lits_{}; + std::size_t size_{0}; + }; + + /*! + * \param limit An optional solve limit applied in solve(). + */ + explicit SolveAlgorithm(const SolveLimits& limit = SolveLimits()); + virtual ~SolveAlgorithm(); + SolveAlgorithm(const SolveAlgorithm&) = delete; + SolveAlgorithm& operator=(const SolveAlgorithm&) = delete; - const Enumerator* enumerator() const { return enum_.get(); } - const SolveLimits& limits() const { return limits_; } - virtual bool interrupted()const = 0; - const Model& model() const; - const LitVec* unsatCore() const; + [[nodiscard]] const SolveLimits& limits() const { return limits_; } + [[nodiscard]] virtual bool interrupted() const = 0; + [[nodiscard]] const Model& model() const; + [[nodiscard]] LitView unsatCore() const; - void setEnumerator(Enumerator& e); - void setEnumLimit(uint64 m) { enumLimit_= m; } - void setLimits(const SolveLimits& x) { limits_ = x; } - void setOptLimit(const SumVec& bound); - //! If set to false, SharedContext::report() is not called for models. - /*! - * \note The default is true, i.e. models are reported via SharedContext::report(). - */ - void setReportModels(bool report) { reportM_ = report; } + void setEnumLimit(uint64_t m) { enumLimit_ = m; } + void setOptLimit(SumView bound); + void setLimits(const SolveLimits& x) { limits_ = x; } + //! If set to false, SharedContext::report() is not called for models. + /*! + * \note The default is true, i.e. models are reported via SharedContext::report(). + */ + void setReportModels(bool report) { reportM_ = report; } - //! Runs the solve algorithm. - /*! - * \param ctx A context object containing the problem. - * \param assume A list of initial unit-assumptions. - * \param onModel Optional handler to be called on each model. - * - * \return - * - true: if the search stopped before the search-space was exceeded. - * - false: if the search-space was completely examined. - * - * \note - * The use of assumptions allows for incremental solving. Literals contained - * in assumptions are assumed to be true during search but are undone before solve returns. - * - * \note - * Conceptually, solve() behaves as follows: - * \code - * start(ctx, assume); - * while (next()) { - * if (!report(model()) || enum_limit_reached()) { stop(); } - * } - * return more(); - * \endcode - * where report() notifies all registered model handlers. - */ - bool solve(SharedContext& ctx, const LitVec& assume = LitVec(), ModelHandler* onModel = 0); + //! Runs the solve algorithm. + /*! + * \param en A fully initialized enumerator. + * \param ctx A context object containing the problem. + * \param assume A list of initial unit-assumptions. + * \param onModel Optional handler to be called on each model. + * + * \return + * - true: if the search stopped before the search-space was exceeded. + * - false: if the search-space was completely examined. + * + * \note + * The use of assumptions allows for incremental solving. Literals contained + * in assumptions are assumed to be true during search but are undone before solve returns. + * + * \note + * Conceptually, solve() behaves as follows: + * \code + * start(en, ctx, assume); + * while (next()) { + * if (!report(model()) || enum_limit_reached()) { stop(); } + * } + * return more(); + * \endcode + * where report() notifies all registered model handlers. + */ + bool solve(Enumerator& en, SharedContext& ctx, LitView assume = {}, ModelHandler* onModel = nullptr); - //! Prepares the solve algorithm for enumerating models. - /*! - * \pre The algorithm is not yet active. - */ - void start(SharedContext& ctx, const LitVec& assume = LitVec(), ModelHandler* onModel = 0); - //! Searches for the next model and returns whether such a model was found. - /*! - * \pre start() was called. - */ - bool next(); - //! Stops the algorithms. - void stop(); - //! Returns whether the last search completely exhausted the search-space. - bool more(); + //! Prepares the solve algorithm for enumerating models. + /*! + * \pre The algorithm is not yet active. + */ + void start(Enumerator& en, SharedContext& ctx, LitView assume = {}, ModelHandler* onModel = nullptr); + //! Searches for the next model and returns whether such a model was found. + /*! + * \pre start() was called. + */ + bool next(); + //! Stops the algorithms. + void stop(); + //! Returns whether the last search completely exhausted the search-space. + [[nodiscard]] bool more() const; - //! Resets solving state and sticky messages like terminate. - /*! - * \note The function must be called between successive calls to solve(). - */ - virtual void resetSolve() = 0; + //! Resets solving state and sticky messages like terminate. + /*! + * \note The function must be called between successive calls to solve(). + */ + virtual void resetSolve() = 0; - //! Prepares the algorithm for handling (asynchronous) calls to SolveAlgorithm::interrupt(). - virtual void enableInterrupts() = 0; + //! Prepares the algorithm for handling (asynchronous) calls to SolveAlgorithm::interrupt(). + virtual void enableInterrupts() = 0; + + //! Tries to terminate the current solve process. + /*! + * \note If enableInterrupts() was not called, SolveAlgorithm::interrupt() may return false + * to signal that (asynchronous) termination is not supported. + */ + bool interrupt(); - //! Tries to terminate the current solve process. - /*! - * \note If enableInterrupts() was not called, SolveAlgorithm::interrupt() may return false - * to signal that (asynchronous) termination is not supported. - */ - bool interrupt(); protected: - SolveAlgorithm(const SolveAlgorithm&); - SolveAlgorithm& operator=(const SolveAlgorithm&); - //! The actual solve algorithm. - virtual bool doSolve(SharedContext& ctx, const LitVec& assume) = 0; - //! Shall return true if termination is supported, otherwise false. - virtual bool doInterrupt() = 0; + //! The actual solve algorithm. + virtual bool doSolve(SharedContext& ctx, LitView assume) = 0; + //! Shall return true if termination is supported, otherwise false. + virtual bool doInterrupt() = 0; + + virtual void doStart(SharedContext& ctx, LitView assume); + virtual auto doNext(Val_t last) -> Val_t; + virtual void doStop(); + virtual void doDetach() = 0; - virtual void doStart(SharedContext& ctx, const LitVec& assume); - virtual int doNext(int last); - virtual void doStop(); - virtual void doDetach() = 0; + bool reportModel(Solver& s) const; + bool reportUnsat(Solver& s) const; + [[nodiscard]] Enumerator& enumerator() const { return *enum_; } + [[nodiscard]] SharedContext& ctx() const { return *ctx_; } + [[nodiscard]] const Path& path() const { return path_; } + [[nodiscard]] uint64_t maxModels() const { return enumLimit_; } + [[nodiscard]] bool moreModels(const Solver& s) const; + [[nodiscard]] bool hasLimit(const Model& m) const; - bool reportModel(Solver& s) const; - bool reportUnsat(Solver& s) const; - Enumerator& enumerator() { return *enum_; } - SharedContext& ctx() const { return *ctx_; } - const LitVec& path() const { return *path_; } - uint64 maxModels() const { return enumLimit_; } - bool moreModels(const Solver& s) const; private: - typedef SingleOwnerPtr EnumPtr; - typedef SingleOwnerPtr PathPtr; - typedef SingleOwnerPtr CorePtr; - enum { value_stop = value_false|value_true }; - bool attach(SharedContext& ctx, ModelHandler* onModel); - void detach(); - bool hasLimit(const Model& m) const; - bool reportModel(Solver& s, bool sym) const; - SolveLimits limits_; - SharedContext* ctx_; - EnumPtr enum_; - ModelHandler* onModel_; - PathPtr path_; - CorePtr core_; - uint64 enumLimit_; - SumVec optLimit_; - double time_; - int last_; - bool reportM_; + bool reportModel(Solver& s, bool sym) const; + bool attach(Enumerator& en, SharedContext& ctx, ModelHandler* onModel); + void detach(); + + SolveLimits limits_; + SharedContext* ctx_; + Enumerator* enum_; + ModelHandler* onModel_; + Path path_; + LitVec core_; + uint64_t enumLimit_; + SumVec optLimit_; + double time_; + Val_t last_; + bool reportM_; }; //! A class that implements clasp's sequential solving algorithm. class SequentialSolve : public SolveAlgorithm { public: - explicit SequentialSolve(const SolveLimits& limit = SolveLimits()); - virtual bool interrupted() const; - virtual void resetSolve(); - virtual void enableInterrupts(); + explicit SequentialSolve(const SolveLimits& limit = SolveLimits()); + [[nodiscard]] bool interrupted() const override; + void resetSolve() override; + void enableInterrupts() override; + protected: - virtual bool doSolve(SharedContext& ctx, const LitVec& assume); - virtual bool doInterrupt(); - virtual void doStart(SharedContext& ctx, const LitVec& assume); - virtual int doNext(int last); - virtual void doStop(); - virtual void doDetach(); + bool doSolve(SharedContext& ctx, LitView assume) override; + bool doInterrupt() override; + void doStart(SharedContext& ctx, LitView assume) override; + auto doNext(Val_t last) -> Val_t override; + void doStop() override; + void doDetach() override; + bool restart(Solver& s, LitView assume); + private: - typedef SingleOwnerPtr SolvePtr; - SolvePtr solve_; - volatile int term_; + using SolvePtr = std::unique_ptr; + SolvePtr solve_; + volatile int term_; }; //! Options for controlling solving. struct BasicSolveOptions { - SolveLimits limit; //!< Solve limit (disabled by default). - SolveAlgorithm* createSolveObject() const { return new SequentialSolve(limit); } - static uint32 supportedSolvers() { return 1; } - static uint32 recommendedSolvers() { return 1; } - uint32 numSolver() const { return 1; } - void setSolvers(uint32) {} - bool defaultPortfolio() const { return false; } + [[nodiscard]] SolveAlgorithm* createSolveObject() const { return new SequentialSolve(limit); } + static uint32_t supportedSolvers() { return 1; } + static uint32_t recommendedSolvers() { return 1; } + [[nodiscard]] uint32_t numSolver() const { return 1; } + void setSolvers(uint32_t) {} + [[nodiscard]] bool defaultPortfolio() const { return false; } + + SolveLimits limit; //!< Solve limit (disabled by default). }; //@} -} -#endif +} // namespace Clasp diff --git a/clasp/solver.h b/clasp/solver.h index 47fc402..2a77ef7 100644 --- a/clasp/solver.h +++ b/clasp/solver.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,15 +21,11 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SOLVER_H_INCLUDED -#define CLASP_SOLVER_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif -#include -#include #include +#include +#include namespace Clasp { @@ -67,869 +63,972 @@ namespace Clasp { */ class Solver { public: - typedef PodVector::type ConstraintDB; - typedef const ConstraintDB& DBRef; - typedef SingleOwnerPtr HeuristicPtr; + using ConstraintDB = PodVector_t; + using DBRef = const ConstraintDB&; + private: - friend class SharedContext; - // Creates an empty solver object with all strategies set to their default value. - Solver(SharedContext* ctx, uint32 id); - ~Solver(); - // Resets a solver object to the state it had after construction. - void reset(); - void resetConfig(); - void startInit(uint32 constraintGuess, const SolverParams& params); - void updateVars(); - bool cloneDB(const ConstraintDB& db); - bool preparePost(); - bool endInit(); - bool endStep(uint32 top, const SolverParams& params); + friend class SharedContext; + // Creates an empty solver object with all strategies set to their default value. + Solver(SharedContext* ctx, uint32_t id); + ~Solver(); + // Resets a solver object to the state it had after construction. + void reset(); + void resetConfig(); + void startInit(uint32_t constraintGuess, const SolverParams& params); + void updateVars(); + bool cloneDB(const ConstraintDB& db); + bool preparePost(); + bool endInit(); + bool endStep(uint32_t top, const SolverParams& params); + public: - typedef SolverStrategies::SearchStrategy SearchMode; - typedef SolverStrategies::UpdateMode UpdateMode; - typedef SolverStrategies::WatchInit WatchInitMode; - //! Returns a pointer to the shared context object of this solver. - const SharedContext* sharedContext() const { return shared_; } - //! Returns a pointer to the sat-preprocessor used by this solver. - SatPreprocessor* satPrepro() const; - //! Returns the solver's solve parameters. - const SolveParams& searchConfig() const; - SearchMode searchMode() const { return static_cast(strategy_.search); } - UpdateMode updateMode() const { return static_cast(strategy_.upMode); } - WatchInitMode watchInitMode() const { return static_cast(strategy_.initWatches); } - uint32 compressLimit() const { return strategy_.compress ? strategy_.compress : UINT32_MAX; } - bool restartOnModel()const { return strategy_.restartOnModel; } - DecisionHeuristic* heuristic() const { return heuristic_.get();} - uint32 id() const { return strategy_.id; } - VarInfo varInfo(Var v) const { return shared_->validVar(v) ? shared_->varInfo(v) : VarInfo(); } - const OutputTable& outputTable() const { return shared_->output; } - Literal tagLiteral() const { return tag_; } - bool isMaster() const { return this == sharedContext()->master(); } - /*! - * \name Setup functions - * Functions in this group are typically used before a search is started. - * @{ */ - //! Adds the problem constraint c to the solver. - /*! - * Problem constraints shall only be added to the master solver of - * a SharedContext object and only during the setup phase. - * \pre this == sharedContext()->master() && !sharedContext()->frozen(). - */ - void add(Constraint* c); - //! Adds a suitable representation of the given clause to the solver. - /*! - * Depending on the type and size of the given clause, the function - * either adds a (learnt) constraint to this solver or an implication - * to the shared implication graph. - * \note If c is a problem clause, the precondition of add(Constraint* c) applies. - */ - bool add(const ClauseRep& c, bool isNew = true); - //! Returns whether c can be stored in the shared short implication graph. - bool allowImplicit(const ClauseRep& c) const { - return c.isImp() - ? shared_->allowImplicit(c.info.type()) && !c.info.aux() && (c.prep == 1 || (!auxVar(c.lits[0].var()) && !auxVar(c.lits[1].var()) && (c.size == 2 || !auxVar(c.lits[2].var())))) - : c.size <= 1; - } - //! Returns the post propagator with the given priority or 0 if no such post propagator exists. - PostPropagator* getPost(uint32 prio) const; - //! Adds p as post propagator to this solver. - /*! - * \pre p was not added previously and is not part of any other solver. - * \note Post propagators are stored and called in priority order. - * \see PostPropagator::priority() - */ - bool addPost(PostPropagator* p); - //! Removes p from the solver's list of post propagators. - /*! - * \note The function shall not be called during propagation of any other post propagator. - */ - void removePost(PostPropagator* p); - - //! Adds path to the current root-path and adjusts the root-level accordingly. - bool pushRoot(const LitVec& path, bool pushStep = false); - bool pushRoot(Literal p); - void setEnumerationConstraint(Constraint* c); - //! Requests a special aux variable for tagging conditional knowledge. - /*! - * Once a tag variable t is set, learnt clauses containing ~t are - * tagged as "conditional". Conditional clauses are removed once t becomes - * unassigned or Solver::removeConditional() is called. Furthermore, calling - * Solver::strengthenConditional() removes ~t from conditional clauses and - * transforms them to unconditional knowledge. - * - * \note Typically, the tag variable is a root assumption and hence true during - * the whole search. - */ - Var pushTagVar(bool pushToRoot); - //@} - - /*! - * \name CDNL functions - * Top level functions that are important to the CDNL algorithm. - * @{ */ - - //! Searches for a model as long as the given limit is not reached. - /*! - * The function searches for a model as long as none of the limits given by limit - * is reached. The limits are updated during search. - * - * \param limit Imposed limit on conflicts and number of learnt constraints. - * \param randf Pick next decision variable randomly with a probability of randf. - * \return - * - value_true: if a model was found. - * - value_false: if the problem is unsatisfiable (under assumptions, if any). - * - value_free: if search was stopped because limit was reached. - * . - * - * \note search treats the root level as top-level, i.e. it will never backtrack below that level. - */ - ValueRep search(SearchLimits& limit, double randf = 0.0); - ValueRep search(uint64 maxC, uint32 maxL, bool local = false, double rp = 0.0); - - //! Moves the root-level i levels down (i.e. away from the top-level). - /*! - * The root-level is similar to the top-level in that it cannot be - * undone during search, i.e. the solver will not resolve conflicts that are on or - * above the root-level. - */ - void pushRootLevel(uint32 i = 1) { - levels_.root = std::min(decisionLevel(), levels_.root+i); - levels_.flip = std::max(levels_.flip, levels_.root); - } - - //! Moves the root-level i levels up (i.e. towards the top-level). - /*! - * The function removes all levels between the new root level and the current decision level, - * resets the current backtrack-level, and re-assigns any implied literals. - * \param i Number of root decisions to pop. - * \param[out] popped Optional storage for popped root decisions. - * \param aux Whether or not aux variables should be added to popped. - * \post decisionLevel() == rootLevel() - * \note The function first calls clearStopConflict() to remove any stop conflicts. - * \note The function *does not* propagate any asserted literals. It is - * the caller's responsibility to call propagate() after the function returned. - */ - bool popRootLevel(uint32 i = 1, LitVec* popped = 0, bool aux = true); - - //! Removes a previously set stop conflict and restores the root level. - void clearStopConflict(); - - //! Returns the current root level. - uint32 rootLevel() const { return levels_.root; } - - //! Removes any implications made between the top-level and the root-level. - /*! - * The function also resets the current backtrack-level and re-assigns learnt facts. - * \note - * Equivalent to popRootLevel(rootLevel()) followed by simplify(). - */ - bool clearAssumptions(); - - //! Adds c as a learnt constraint to the solver. - void addLearnt(Constraint* c, uint32 size, ConstraintType type) { - learnts_.push_back(c); - stats.addLearnt(size, type); - } - void addLearnt(Constraint* c, uint32 size) { addLearnt(c, size, c->type()); } - //! Tries to receive at most maxOut clauses. - /*! - * The function queries the distributor object for new clauses to be delivered to - * this solver. Clauses are stored in out. - * \return The number of clauses received. - */ - uint32 receive(SharedLiterals** out, uint32 maxOut) const; - //! Distributes the clause in lits via the distributor. - /*! - * The function first calls the distribution strategy - * to decides whether the clause is a valid candidate for distribution. - * If so and a distributor was set, it distributes the clause and returns a handle to the - * now shared literals of the clause. Otherwise, it returns 0. - * - * \param lits The literals of the clause. - * \param size The number of literals in the clause. - * \param extra Additional information about the clause. - * \note - * If the return value is not null, it is the caller's - * responsibility to release the returned handle (i.e. by calling release()). - * \note If the clause contains aux vars, it is not distributed. - */ - SharedLiterals* distribute(const Literal* lits, uint32 size, const ConstraintInfo& extra); - - - //! Returns to the maximum of rootLevel() and backtrackLevel() and increases the number of restarts. - void restart(); - - enum UndoMode { undo_default = 0u, undo_pop_bt_level = 1u, undo_pop_proj_level = 2u, undo_save_phases = 4u }; - //! Sets the backtracking level to dl. - /*! - * Depending on mode, the backtracking level either applies - * to normal or projective solution enumeration. - * \see "Solution Enumeration for Projected Boolean Search Problems". - */ - void setBacktrackLevel(uint32 dl, UndoMode mode = undo_pop_bt_level) { - if (uint32(mode) >= levels_.mode) { - levels_.flip = std::max(std::min(dl, decisionLevel()), rootLevel()); - levels_.mode = std::max(uint32(mode & 3u), uint32(undo_pop_bt_level)); - } - } - //! Returns the current backtracking level. - uint32 backtrackLevel() const { return levels_.flip; } - //! Returns the backjump level during an undo operation. - uint32 jumpLevel() const { return decisionLevel() - levels_.jump; } - - //! Returns whether the solver can split-off work. - bool splittable() const; - //! Notifies the solver about a split request. - /*! - * \return splittable() - */ - bool requestSplit(); - //! Clears last split request and returns true if there was an open request. - bool clearSplitRequest(); - - //! Tries to split-off disjoint work from the solver's current guiding path and returns it in out. - /*! - * On split, any open split request is also cleared. - * \return splittable() - */ - bool split(LitVec& out); - - //! Copies the solver's current guiding path to gp. - /*! - * \note The solver's guiding path consists of: - * - the decisions from levels [1, rootLevel()] - * - any literals that are implied on a level <= rootLevel() because of newly learnt - * information. This particularly includes literals that were flipped during model enumeration. - * - * \param[out] out Where to store the guiding path. - */ - void copyGuidingPath(LitVec& out); - - //! If called on top-level, removes SAT-clauses + Constraints for which Constraint::simplify returned true. - /*! - * \note If this method is called on a decision-level > 0, it is a noop and will - * simply return true. - * \return false, if a top-level conflict is detected. Otherwise, true. - */ - bool simplify(); - //! Shuffle constraints upon next simplification. - void shuffleOnNextSimplify(){ shufSimp_ = 1; } - - - //! Removes all conditional knowledge, i.e. all previously tagged learnt clauses. - /*! - * \see Solver::pushTagVar() - */ - void removeConditional(); - - //! Resolves all tagged clauses with the tag literal and thereby strengthens the learnt db. - /*! - * \see Solver::pushTagVar() - */ - void strengthenConditional(); - - //! Sets the literal p to true and schedules p for propagation. - /*! - * Setting a literal p to true means assigning the appropriate value to - * p's variable. That is: value_false if p is a negative literal and value_true - * if p is a positive literal. - * \param p The literal that should become true. - * \param a The reason for the literal to become true or 0 if no reason exists. - * - * \return - * - false if p is already false - * - otherwise true. - * - * \pre hasConflict() == false - * \pre a.isNull() == false || decisionLevel() <= rootLevel() || searchMode() == no_learning - * \post - * p.var() == trueValue(p) || p.var() == falseValue(p) && hasConflict() == true - * - * \note if setting p to true leads to a conflict, the nogood that caused the - * conflict can be requested using the conflict() function. - */ - bool force(const Literal& p, const Antecedent& a) { - assert(!hasConflict() || isTrue(p)); - if (assign_.assign(p, decisionLevel(), a)) return true; - setConflict(p, a, UINT32_MAX); - return false; - } - /*! - * \overload bool Solver::force(const Literal&, const Antecedent&) - */ - bool force(const Literal& p, const Antecedent& a, uint32 data) { - return data != UINT32_MAX - ? assign_.assign(p, decisionLevel(), a.constraint(), data) || (setConflict(p, a, data), false) - : force(p, a); - } - - //! Assigns p at dl because of r. - /*! - * \pre dl <= decisionLevel() - * \note - * If dl < ul = max(rootLevel(), backtrackLevel()), p is actually assigned - * at ul but the solver stores enough information to reassign - * p on backtracking. - */ - bool force(Literal p, uint32 dl, const Antecedent& r, uint32 d = UINT32_MAX) { - return dl == decisionLevel() ? force(p, r, d) : force(ImpliedLiteral(p, dl, r, d)); - } - //! Assigns p as a fact at decision level 0. - bool force(Literal p) { return force(p, 0, Antecedent(lit_true())); } - - //! Assumes the literal p if possible. - /*! - * If p is currently unassigned, sets p to true and starts a new decision level. - * \pre validVar(p.var()) == true - * \param p The literal to assume. - * \return !isFalse(p) - */ - bool assume(const Literal& p); - - //! Selects and assumes the next branching literal by calling the installed decision heuristic. - /*! - * \pre queueSize() == 0 - * \note The next decision literal will be selected randomly with probability f. - * \return - * - true if the assignment is not total and a literal was assumed (or forced). - * - false otherwise - * . - * \see DecisionHeuristic - */ - bool decideNextBranch(double f = 0.0); - - //! Sets a conflict that forces the solver to terminate its search. - /*! - * \pre !hasConflict() - * \post hasConflict() - * - * \note - * To prevent the solver from resolving the stop conflict, the - * function sets the root level to the current decision level. - * Call clearStopConflict() to remove the conflict and to restore - * the previous root-level. - */ - void setStopConflict(); - - /*! - * Propagates all enqueued literals. If a conflict arises during propagation - * propagate returns false and the current conflict (as a set of literals) - * is stored in the solver's conflict variable. - * \pre !hasConflict() - * \see Solver::force - * \see Solver::assume - * \note Shall not be called recursively. - */ - bool propagate(); - - /*! - * Does unit propagation and calls x->propagateFixpoint(*this) - * for all post propagators x up to but not including p. - * \note The function is meant to be called only in the context of p. - * \pre p is a post propagator of this solver, i.e. was previously added via addPost(). - * \pre Post propagators are active, i.e. the solver is fully initialized. - */ - bool propagateUntil(PostPropagator* p); - - /*! - * Calls x->propagateFixpoint(*this) for all post propagators x starting from and including p. - * \note The function is meant to be called only in the context of p. - * \pre p is a post propagator of this solver, i.e. was previously added via addPost(). - * \pre Post propagators are active, i.e. the solver is fully initialized. - * \pre Assignment is fully (unit) propagated up to p. - */ - bool propagateFrom(PostPropagator* p); - - //! Executes a one-step lookahead on p. - /*! - * Assumes p and propagates this assumption. If propagations leads to - * a conflict, false is returned. Otherwise, the assumption is undone and - * the function returns true. - * \param p The literal to test. - * \param c The constraint that wants to test p (can be 0). - * \pre p is free - * \note If c is not null and testing p does not lead to a conflict, - * c->undoLevel() is called *before* p is undone. Hence, the - * range [s.levelStart(s.decisionLevel()), s.assignment().size()) - * contains p followed by all literals that were forced because of p. - * \note propagateUntil(c) is used to propagate p. - */ - bool test(Literal p, PostPropagator* c); - - //! Estimates the number of assignments following from setting p to true. - /*! - * \note For the estimate only binary clauses are considered. - */ - uint32 estimateBCP(const Literal& p, int maxRecursionDepth = 5) const; - - //! Computes the number of in-edges for each assigned literal. - /*! - * \pre !hasConflict() - * \note For a literal p assigned on level(p), only in-edges from - * levels < level(p) are counted. - * \return The maximum number of in-edges. - */ - uint32 inDegree(WeightLitVec& out); - - struct DBInfo { uint32 size; uint32 locked; uint32 pinned; }; - //! Removes upto remMax percent of the learnt nogoods. - /*! - * \param remMax Fraction of nogoods to remove ([0.0,1.0]). - * \param rs Strategy to apply during nogood deletion. - * \return The number of locked and active/glue clauses currently exempt from deletion. - * \note - * Nogoods that are the reason for a literal to be in the assignment - * are said to be locked and won't be removed. - */ - DBInfo reduceLearnts(float remMax, const ReduceStrategy& rs = ReduceStrategy()); - - //! Resolves the active conflict using the selected strategy. - /*! - * If searchMode() is set to learning, resolveConflict implements - * First-UIP learning and backjumping. Otherwise, it simply applies - * chronological backtracking. - * \pre hasConflict() - * \return - * - true if the conflict was successfully resolved - * - false otherwise - * \note - * If decisionLevel() == rootLevel() false is returned. - */ - bool resolveConflict(); - - //! Backtracks the last decision and updates the backtrack-level if necessary. - /*! - * \return - * - true if backtracking was possible - * - false if decisionLevel() == rootLevel() - */ - bool backtrack(); - - //! Undoes all assignments up to (but not including) decision level dl. - /*! - * \post decision level == max(min(decisionLevel(), dl), max(rootLevel(), backtrackLevel())) - * \return The decision level after undoing assignments. - * \note - * undoUntil() stops at the current backtrack level unless undoMode includes the mode - * that was used when setting the backtrack level. - * \note - * If undoMode contains undo_save_phases, the functions saves the values of variables that are undone. - * Otherwise, phases are only saved if indicated by the active strategy. - */ - uint32 undoUntil(uint32 dl, uint32 undoMode); - //! Behaves like undoUntil(dl, undo_default). - uint32 undoUntil(uint32 dl) { return undoUntilImpl(dl, false); } - //! Returns whether undoUntil(decisionLevel()-1) is valid and would remove decisionLevel(). - bool isUndoLevel() const; - - //! Adds a new auxiliary variable to this solver. - /*! - * Auxiliary variables are local to one solver and are not considered - * as part of the problem. They shall be added/used only during solving, i.e. - * after problem setup is completed. - */ - Var pushAuxVar(); - //! Pops the num most recently added auxiliary variables and destroys all constraints in auxCons. - void popAuxVar(uint32 num = UINT32_MAX, ConstraintDB* auxCons = 0); - //@} - - /*! - * \name State inspection - * Functions for inspecting the state of the solver & search. - * \note validVar(v) is a precondition for all functions that take a variable as - * parameter. - * @{ */ - //! Returns the number of problem variables. - uint32 numProblemVars() const { return shared_->numVars(); } - //! Returns the number of active solver-local aux variables. - uint32 numAuxVars() const { return numVars() - numProblemVars(); } - //! Returns the number of solver variables, i.e. numProblemVars() + numAuxVars() - uint32 numVars() const { return assign_.numVars() - 1; } - //! Returns true if var represents a valid variable in this solver. - bool validVar(Var var) const { return var <= numVars(); } - //! Returns true if var is a solver-local aux var. - bool auxVar(Var var) const { return shared_->numVars() < var; } - //! Returns the number of assigned variables. - uint32 numAssignedVars() const { return assign_.assigned(); } - //! Returns the number of free variables. - /*! - * The number of free variables is the number of vars that are neither - * assigned nor eliminated. - */ - uint32 numFreeVars() const { return assign_.free()-1; } - //! Returns the value of v w.r.t the current assignment. - ValueRep value(Var v) const { assert(validVar(v)); return assign_.value(v); } - //! Returns the value of v w.r.t the top level. - ValueRep topValue(Var v) const { return level(v) == 0 ? value(v) : value_free; } - //! Returns the set of preferred values of v. - ValueSet pref(Var v) const { assert(validVar(v)); return assign_.pref(v);} - //! Returns true if p is true w.r.t the current assignment. - bool isTrue(Literal p) const { assert(validVar(p.var())); return assign_.value(p.var()) == trueValue(p); } - //! Returns true if p is false w.r.t the current assignment. - bool isFalse(Literal p) const { assert(validVar(p.var())); return assign_.value(p.var()) == falseValue(p); } - //! Returns the literal of v being true in the current assignment. - /*! - * \pre v is assigned a value in the current assignment - */ - Literal trueLit(Var v) const { assert(value(v) != value_free); return Literal(v, valSign(value(v))); } - Literal defaultLit(Var v) const; - //! Returns the decision level on which v was assigned. - /*! - * \note The returned value is only meaningful if value(v) != value_free. - */ - uint32 level(Var v) const { assert(validVar(v)); return assign_.level(v); } - //! Returns true if v is currently marked as seen. - /*! - * Note: variables assigned on level 0 are always marked. - */ - bool seen(Var v) const { assert(validVar(v)); return assign_.seen(v, 3u); } - //! Returns true if the literal p is currently marked as seen. - bool seen(Literal p) const { assert(validVar(p.var())); return assign_.seen(p.var(), uint8(1+p.sign())); } - //! Returns the current decision level. - uint32 decisionLevel() const { return (uint32)levels_.size(); } - bool validLevel(uint32 dl) const { return dl != 0 && dl <= decisionLevel(); } - //! Returns the starting position of decision level dl in the trail. - /*! - * \pre validLevel(dl) - */ - uint32 levelStart(uint32 dl) const { assert(validLevel(dl)); return levels_[dl-1].trailPos; } - //! Returns the decision literal of the decision level dl. - /*! - * \pre validLevel(dl) - */ - Literal decision(uint32 dl) const { assert(validLevel(dl)); return assign_.trail[ levels_[dl-1].trailPos ]; } - //! Returns true, if the current assignment is conflicting. - bool hasConflict() const { return !conflict_.empty(); } - bool hasStopConflict() const { return hasConflict() && conflict_[0] == lit_false(); } - //! Returns the number of (unprocessed) literals in the propagation queue. - uint32 queueSize() const { return (uint32) assign_.qSize(); } - //! Number of problem constraints in this solver. - uint32 numConstraints() const; - //! Returns the number of constraints that are currently in the solver's learnt database. - uint32 numLearntConstraints() const { return (uint32)learnts_.size(); } - //! Returns the reason for p being true. - /*! - * \pre p is true w.r.t the current assignment - */ - const Antecedent& reason(Literal p) const { assert(isTrue(p)); return assign_.reason(p.var()); } - //! Returns the additional reason data associated with p. - uint32 reasonData(Literal p) const { return assign_.data(p.var()); } - //! Returns the current (partial) assignment as a set of true literals. - /*! - * \note Although the special var 0 always has a value it is not considered to be - * part of the assignment. - */ - const LitVec& trail() const { return assign_.trail; } - const Assignment& assignment() const { return assign_; } - //! Returns the current conflict as a set of literals. - const LitVec& conflict() const { return conflict_; } - //! Returns the most recently derived conflict clause. - const LitVec& conflictClause() const { return cc_; } - //! Returns the set of eliminated literals that are unconstrained w.r.t the last model. - const LitVec& symmetric() const { return temp_; } - //! Returns the enumeration constraint set by the enumerator used. - Constraint* enumerationConstraint() const { return enum_; } - DBRef constraints() const { return constraints_; } - //! Returns the idx'th learnt constraint. - /*! - * \pre idx < numLearntConstraints() - */ - Constraint& getLearnt(uint32 idx) const { - assert(idx < numLearntConstraints()); - return *learnts_[ idx ]; - } - - mutable RNG rng; //!< Random number generator for this object. - ValueVec model; //!< Stores the last model (if any). - LowerBound lower; //!< Stores the last lower bound found (if any). - SolverStats stats; //!< Stores statistics about the solving process. - //@} - - /*! - * \name Watch management - * Functions for setting/removing watches. - * \pre validVar(v) - * @{ */ - //! Returns the number of constraints watching the literal p. - uint32 numWatches(Literal p) const; - //! Returns true if the constraint c watches the literal p. - bool hasWatch(Literal p, Constraint* c) const; - bool hasWatch(Literal p, ClauseHead* c) const; - //! Returns c's watch-structure associated with p. - /*! - * \note returns 0, if hasWatch(p, c) == false - */ - GenericWatch* getWatch(Literal p, Constraint* c) const; - //! Adds c to the watch-list of p. - /*! - * When p becomes true, c->propagate(p, data, *this) is called. - * \post hasWatch(p, c) == true - */ - void addWatch(Literal p, Constraint* c, uint32 data = 0) { - assert(validWatch(p)); - watches_[p.id()].push_right(GenericWatch(c, data)); - } - //! Adds w to the clause watch-list of p. - void addWatch(Literal p, const ClauseWatch& w) { - assert(validWatch(p)); - watches_[p.id()].push_left(w); - } - //! Removes c from p's watch-list. - /*! - * \post hasWatch(p, c) == false - */ - void removeWatch(const Literal& p, Constraint* c); - void removeWatch(const Literal& p, ClauseHead* c); - //! Adds c to the watch-list of decision-level dl. - /*! - * Constraints in the watch-list of a decision level are - * notified when that decision level is about to be backtracked. - * \pre validLevel(dl) - */ - void addUndoWatch(uint32 dl, Constraint* c) { - assert(validLevel(dl)); - if (levels_[dl-1].undo != 0) { - levels_[dl-1].undo->push_back(c); - } - else { - levels_[dl-1].undo = allocUndo(c); - } - } - //! Removes c from the watch-list of the decision level dl. - bool removeUndoWatch(uint32 dl, Constraint* c); - //@} - - /*! - * \name Misc functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - * @{ */ - bool addPost(PostPropagator* p, bool init); - //! Updates the reason for p being true. - /*! - * \pre p is true and x is a valid reason for p - */ - bool setReason(Literal p, const Antecedent& x, uint32 data = UINT32_MAX) { - assert(isTrue(p) || shared_->eliminated(p.var())); - assign_.setReason(p.var(), x); - if (data != UINT32_MAX) { assign_.setData(p.var(), data); } - return true; - } - void setPref(Var v, ValueSet::Value which, ValueRep to) { - assert(validVar(v) && to <= value_false); - assign_.requestPrefs(); - assign_.setPref(v, which, to); - } - void resetPrefs() { assign_.resetPrefs(); } - void resetLearntActivities(); - //! Returns the reason for p being true as a set of literals. - void reason(Literal p, LitVec& out) { assert(isTrue(p)); out.clear(); return assign_.reason(p.var()).reason(*this, p, out); } - - //! Helper function for updating antecedent scores during conflict resolution. - /*! - * \param sc The current score of the active antecedent. - * \param p The literal implied by the active antecedent. - * \param lits The literals of the active antecedent. - * \return true if a score was updated. - * - * \note Depending on the active solver strategies, the function - * increases the activity and/or updates the lbd of the given antecedent. - * - * \note If SolverStrategies::bumpVarAct is active, p's activity - * is increased if the new lbd is smaller than the lbd of the - * conflict clause that is currently being derived. - */ - bool updateOnReason(ConstraintScore& sc, Literal p, const LitVec& lits) { - // update only during conflict resolution - if (&lits != &conflict_) { return false; } - sc.bumpActivity(); - uint32 up = strategy_.updateLbd; - if (up != SolverStrategies::lbd_fixed && !lits.empty()) { - uint32 lbd = sc.lbd(); - uint32 inc = uint32(up != SolverStrategies::lbd_updated_less); - uint32 nLbd = countLevels(&lits[0], &lits[0] + lits.size(), lbd - inc); - if ((nLbd + inc) < lbd) { - sc.bumpLbd(nLbd + uint32(up == SolverStrategies::lbd_update_pseudo)); - } - } - if (strategy_.bumpVarAct && isTrue(p)) { bumpAct_.push_back(WeightLiteral(p, sc.lbd())); } - return true; - } - - //! Helper function for increasing antecedent activities during conflict clause minimization. - bool updateOnMinimize(ConstraintScore& sc) const { - return !strategy_.ccMinKeepAct && (sc.bumpActivity(), true); - } - - //! Helper function for antecedents to be called during conflict clause minimization. - bool ccMinimize(Literal p, CCMinRecursive* rec) const { - return seen(p.var()) || (rec && hasLevel(level(p.var())) && ccMinRecurse(*rec, p)); - } - - //! Allocates a small block (32-bytes) from the solver's small block pool. - void* allocSmall() { return smallAlloc_.allocate(); } - //! Frees a small block previously allocated from the solver's small block pool. - void freeSmall(void* m) { smallAlloc_.free(m); } - - void addLearntBytes(uint32 bytes) { memUse_ += bytes; } - void freeLearntBytes(uint64 bytes) { memUse_ -= (bytes < memUse_) ? bytes : memUse_; } - - bool restartReached(const SearchLimits& limit) const; - bool reduceReached(const SearchLimits& limit) const; - - //! simplifies cc and returns finalizeConflictClause(cc, info); - uint32 simplifyConflictClause(LitVec& cc, ConstraintInfo& info, ClauseHead* rhs); - uint32 finalizeConflictClause(LitVec& cc, ConstraintInfo& info, uint32 ccRepMode = 0); - uint32 countLevels(const Literal* first, const Literal* last, uint32 maxLevels = Clasp::LBD_MAX); - bool hasLevel(uint32 dl) const { assert(validLevel(dl)); return levels_[dl-1].marked != 0; } - bool frozenLevel(uint32 dl) const { assert(validLevel(dl)); return levels_[dl-1].freeze != 0; } - bool inputVar(Literal p) const { return varInfo(p.var()).input(); } - void markLevel(uint32 dl) { assert(validLevel(dl)); levels_[dl-1].marked = 1; } - void freezeLevel(uint32 dl) { assert(validLevel(dl)); levels_[dl-1].freeze = 1; } - void unmarkLevel(uint32 dl) { assert(validLevel(dl)); levels_[dl-1].marked = 0; } - void unfreezeLevel(uint32 dl){ assert(validLevel(dl)); levels_[dl-1].freeze = 0; } - void markSeen(Var v) { assert(validVar(v)); assign_.setSeen(v, 3u); } - void markSeen(Literal p) { assert(validVar(p.var())); assign_.setSeen(p.var(), uint8(1+p.sign())); } - void clearSeen(Var v) { assert(validVar(v)); assign_.clearSeen(v); } - void setHeuristic(DecisionHeuristic* h, Ownership_t::Type own); - void destroyDB(ConstraintDB& db); - SolverStrategies& strategies() { return strategy_; } - bool resolveToFlagged(const LitVec& conflictClause, uint8 vflag, LitVec& out, uint32& lbd) const; - void resolveToCore(LitVec& out); - void acquireProblemVar(Var var); - void acquireProblemVars() { acquireProblemVar(numProblemVars()); } - //@} + using SearchMode = SolverStrategies::SearchStrategy; + using UpdateMode = SolverStrategies::UpdateMode; + using WatchInitMode = SolverStrategies::WatchInit; + //! Returns a pointer to the shared context object of this solver. + const SharedContext* sharedContext() const { return shared_; } + //! Returns a pointer to the sat-preprocessor used by this solver. + SatPreprocessor* satPrepro() const; + //! Returns the solver's solve parameters. + const SolveParams& searchConfig() const; + SearchMode searchMode() const { return static_cast(strategy_.search); } + UpdateMode updateMode() const { return static_cast(strategy_.upMode); } + WatchInitMode watchInitMode() const { return static_cast(strategy_.initWatches); } + uint32_t compressLimit() const { return strategy_.compress ? strategy_.compress : UINT32_MAX; } + bool restartOnModel() const { return strategy_.restartOnModel; } + DecisionHeuristic* heuristic() const { return heuristic_; } + uint32_t id() const { return strategy_.id; } + VarInfo varInfo(Var_t v) const { return shared_->validVar(v) ? shared_->varInfo(v) : VarInfo(); } + const OutputTable& outputTable() const { return shared_->output; } + Literal tagLiteral() const { return tag_; } + bool isMaster() const { return this == sharedContext()->master(); } + /*! + * \name Setup functions. + * Functions in this group are typically used before a search is started. + * @{ */ + //! Adds the problem constraint c to the solver. + /*! + * Problem constraints shall only be added to the master solver of + * a SharedContext object and only during the setup phase. + * \pre this == sharedContext()->master() && !sharedContext()->frozen(). + */ + void add(Constraint* c); + //! Adds a suitable representation of the given clause to the solver. + /*! + * Depending on the type and size of the given clause, the function + * either adds a (learnt) constraint to this solver or an implication + * to the shared implication graph. + * \note If c is a problem clause, the precondition of add(Constraint* c) applies. + */ + bool add(const ClauseRep& c, bool isNew = true); + //! Returns whether c can be stored in the shared short implication graph. + bool allowImplicit(const ClauseRep& c) const { + return c.isImp() ? shared_->allowImplicit(c.info.type()) && not c.info.aux() && + (c.prep == 1 || (not auxVar(c.lits[0].var()) && not auxVar(c.lits[1].var()) && + (c.size == 2 || not auxVar(c.lits[2].var())))) + : c.size <= 1; + } + //! Returns the post propagator with the given priority or 0 if no such post propagator exists. + PostPropagator* getPost(uint32_t prio) const; + //! Adds p as post propagator to this solver. + /*! + * \pre p was not added previously and is not part of any other solver. + * \note Post propagators are stored and called in priority order. + * \see PostPropagator::priority() + */ + bool addPost(PostPropagator* p); + //! Removes p from the solver's list of post propagators. + /*! + * \note The function shall not be called during propagation of any other post propagator. + */ + void removePost(PostPropagator* p); + + //! Adds path to the current root-path and adjusts the root-level accordingly. + bool pushRoot(LitView path, bool pushStep = false); + bool pushRoot(Literal p); + void setEnumerationConstraint(Constraint* c); + //! Requests a special aux variable for tagging conditional knowledge. + /*! + * Once a tag variable t is set, learnt clauses containing ~t are + * tagged as "conditional". Conditional clauses are removed once t becomes + * unassigned or Solver::removeConditional() is called. Furthermore, calling + * Solver::strengthenConditional() removes ~t from conditional clauses and + * transforms them to unconditional knowledge. + * + * \note Typically, the tag variable is a root assumption and hence true during + * the whole search. + */ + Var_t pushTagVar(bool pushToRoot); + //@} + + /*! + * \name CDNL functions + * Top level functions that are important to the CDNL algorithm. + * @{ */ + + //! Searches for a model as long as the given limit is not reached. + /*! + * The function searches for a model as long as none of the limits given by limit + * is reached. The limits are updated during search. + * + * \param limit Imposed limit on conflicts and number of learnt constraints. + * \param randf Pick next decision variable randomly with a probability of randf. + * \return + * - value_true: if a model was found. + * - value_false: if the problem is unsatisfiable (under assumptions, if any). + * - value_free: if search was stopped because limit was reached. + * . + * + * \note search treats the root level as top-level, i.e. it will never backtrack below that level. + */ + Val_t search(SearchLimits& limit, double randf = 0.0); + Val_t search(uint64_t maxC = UINT64_MAX, uint32_t maxL = UINT32_MAX, bool local = false, double rp = 0.0); + + //! Moves the root-level 'n' levels down (i.e. away from the top-level). + /*! + * The root-level is similar to the top-level in that it cannot be + * undone during search, i.e. the solver will not resolve conflicts that are on or + * above the root-level. + */ + void pushRootLevel(uint32_t n = 1) { + levels_.root = std::min(decisionLevel(), levels_.root + n); + levels_.flip = std::max(levels_.flip, levels_.root); + } + + //! Moves the root-level 'n' levels up (i.e. towards the top-level). + /*! + * The function removes all levels between the new root level and the current decision level, + * resets the current backtrack-level, and re-assigns any implied literals. + * \param n Number of root decisions to pop. + * \param[out] popped Optional storage for popped root decisions. + * \param aux Whether aux variables should be added to @c popped. + * \post decisionLevel() == rootLevel() + * \note The function first calls clearStopConflict() to remove any stop conflicts. + * \note The function *does not* propagate any asserted literals. It is + * the caller's responsibility to call propagate() after the function returned. + */ + bool popRootLevel(uint32_t n = 1, LitVec* popped = nullptr, bool aux = true); + + //! Removes a previously set stop conflict and restores the root level. + void clearStopConflict(); + + //! Returns the current root level. + uint32_t rootLevel() const { return levels_.root; } + + //! Removes any implications made between the top-level and the root-level. + /*! + * The function also resets the current backtrack-level and re-assigns learnt facts. + * \note + * Equivalent to popRootLevel(rootLevel()) followed by simplify(). + */ + bool clearAssumptions(); + + //! Adds c as a learnt constraint to the solver. + void addLearnt(Constraint* c, uint32_t size, ConstraintType type) { + learnts_.push_back(c); + stats.addLearnt(size, type); + } + void addLearnt(Constraint* c, uint32_t size) { addLearnt(c, size, c->type()); } + //! Tries to receive at most maxOut clauses. + /*! + * The function queries the distributor object for new clauses to be delivered to + * this solver. Clauses are stored in out. + * \return The number of clauses received. + */ + uint32_t receive(SharedLiterals** out, uint32_t maxOut) const; + //! Distributes the clause in lits via the distributor. + /*! + * The function first calls the distribution strategy + * to decides whether the clause is a valid candidate for distribution. + * If so and a distributor was set, it distributes the clause and returns a handle to the + * now shared literals of the clause. Otherwise, it returns 0. + * + * \param lits The literals of the clause. + * \param extra Additional information about the clause. + * \note + * If the return value is not null, it is the caller's + * responsibility to release the returned handle (i.e. by calling release()). + * \note If the clause contains aux vars, it is not distributed. + */ + SharedLiterals* distribute(LitView lits, const ConstraintInfo& extra); + + //! Returns to the maximum of rootLevel() and backtrackLevel() and increases the number of restarts. + void restart(); + + enum UndoMode : uint32_t { + undo_default = 0u, + undo_pop_bt_level = 1u, + undo_pop_proj_level = 2u, + undo_save_phases = 4u + }; + //! Sets the backtracking level to dl. + /*! + * Depending on mode, the backtracking level either applies + * to normal or projective solution enumeration. + * \see "Solution Enumeration for Projected Boolean Search Problems". + */ + void setBacktrackLevel(uint32_t dl, UndoMode mode = undo_pop_bt_level) { + if (Potassco::to_underlying(mode) >= levels_.mode) { + levels_.flip = std::max(std::min(dl, decisionLevel()), rootLevel()); + levels_.mode = std::max(mode & 3u, Potassco::to_underlying(undo_pop_bt_level)); + } + } + //! Returns the current backtracking level. + uint32_t backtrackLevel() const { return levels_.flip; } + //! Returns the backjump level during an undo operation. + uint32_t jumpLevel() const { return decisionLevel() - levels_.jump; } + + //! Returns whether the solver can split-off work. + bool splittable() const; + //! Notifies the solver about a split request. + /*! + * \return splittable() + */ + bool requestSplit(); + //! Clears last split request and returns true if there was an open request. + bool clearSplitRequest(); + + //! Tries to split-off disjoint work from the solver's current guiding path and returns it in out. + /*! + * On split, any open split request is also cleared. + * \return splittable() + */ + bool split(LitVec& out); + + //! Copies the solver's current guiding path to gp. + /*! + * \note The solver's guiding path consists of: + * - the decisions from levels [1, rootLevel()] + * - any literals that are implied on a level <= rootLevel() because of newly learnt + * information. This particularly includes literals that were flipped during model enumeration. + * + * \param[out] out Where to store the guiding path. + */ + void copyGuidingPath(LitVec& out); + + //! If called on top-level, removes SAT-clauses + Constraints for which Constraint::simplify returned true. + /*! + * \note If this method is called on a decision-level > 0, it is a noop and will + * simply return true. + * \return false, if a top-level conflict is detected. Otherwise, true. + */ + bool simplify(); + //! Shuffle constraints upon next simplification. + void shuffleOnNextSimplify() { shufSimp_ = 1; } + + //! Removes all conditional knowledge, i.e. all previously tagged learnt clauses. + /*! + * \see Solver::pushTagVar() + */ + void removeConditional(); + + //! Resolves all tagged clauses with the tag literal and thereby strengthens the learnt db. + /*! + * \see Solver::pushTagVar() + */ + void strengthenConditional(); + + //! Sets the literal p to true and schedules p for propagation. + /*! + * Setting a literal p to true means assigning the appropriate value to + * p's variable. That is: value_false if p is a negative literal and value_true + * if p is a positive literal. + * \param p The literal that should become true. + * \param a The reason for the literal to become true or 0 if no reason exists. + * + * \return + * - false if p is already false + * - otherwise true. + * + * \pre hasConflict() == false + * \pre a.isNull() == false || decisionLevel() <= rootLevel() || searchMode() == no_learning + * \post + * p.var() == trueValue(p) || p.var() == falseValue(p) && hasConflict() == true + * + * \note if setting p to true leads to a conflict, the nogood that caused the + * conflict can be requested using the conflict() function. + */ + bool force(const Literal& p, const Antecedent& a) { + assert(not hasConflict() || isTrue(p)); + if (assign_.assign(p, decisionLevel(), a)) { + return true; + } + setConflict(p, a, UINT32_MAX); + return false; + } + /*! + * \overload bool Solver::force(const Literal&, const Antecedent&) + */ + bool force(const Literal& p, const Antecedent& a, uint32_t data) { + return data != UINT32_MAX ? assign_.assign(p, decisionLevel(), a, data) || (setConflict(p, a, data), false) + : force(p, a); + } + + //! Assigns p at dl because of r. + /*! + * \pre dl <= decisionLevel() + * \note + * If dl < ul = max(rootLevel(), backtrackLevel()), p is actually assigned + * at ul but the solver stores enough information to reassign + * p on backtracking. + */ + bool force(Literal p, uint32_t dl, const Antecedent& r, uint32_t d = UINT32_MAX) { + return dl == decisionLevel() ? force(p, r, d) : force(ImpliedLiteral(p, dl, r, d)); + } + //! Assigns p as a fact at decision level 0. + bool force(Literal p) { return force(p, 0, Antecedent(lit_true)); } + + //! Assumes the literal p if possible. + /*! + * If p is currently unassigned, sets p to true and starts a new decision level. + * \pre validVar(p.var()) == true + * \param p The literal to assume. + * \return !isFalse(p) + */ + bool assume(const Literal& p); + + //! Selects and assumes the next branching literal by calling the installed decision heuristic. + /*! + * \pre queueSize() == 0 + * \note The next decision literal will be selected randomly with probability f. + * \return + * - true if the assignment is not total and a literal was assumed (or forced). + * - false otherwise + * . + * \see DecisionHeuristic + */ + bool decideNextBranch(double f = 0.0); + + //! Sets a conflict that forces the solver to terminate its search. + /*! + * \pre !hasConflict() + * \post hasConflict() + * + * \note + * To prevent the solver from resolving the stop conflict, the + * function sets the root level to the current decision level. + * Call clearStopConflict() to remove the conflict and to restore + * the previous root-level. + */ + void setStopConflict(); + + /*! + * Propagates all enqueued literals. If a conflict arises during propagation + * propagate returns false and the current conflict (as a set of literals) + * is stored in the solver's conflict variable. + * \pre !hasConflict() + * \see Solver::force + * \see Solver::assume + * \note Shall not be called recursively. + */ + bool propagate(); + + /*! + * Does unit propagation and calls x->propagateFixpoint(*this) + * for all post propagators x up to but not including p. + * \note The function is meant to be called only in the context of p. + * \pre p is a post propagator of this solver, i.e. was previously added via addPost(). + * \pre Post propagators are active, i.e. the solver is fully initialized. + */ + bool propagateUntil(PostPropagator* p); + + /*! + * Calls x->propagateFixpoint(*this) for all post propagators x starting from and including p. + * \note The function is meant to be called only in the context of p. + * \pre p is a post propagator of this solver, i.e. was previously added via addPost(). + * \pre Post propagators are active, i.e. the solver is fully initialized. + * \pre Assignment is fully (unit) propagated up to p. + */ + bool propagateFrom(const PostPropagator* p); + + //! Executes a one-step lookahead on p. + /*! + * Assumes p and propagates this assumption. If propagations leads to + * a conflict, false is returned. Otherwise, the assumption is undone and + * the function returns true. + * \param p The literal to test. + * \param c The constraint that wants to test p (can be 0). + * \pre p is free + * \note If c is not null and testing p does not lead to a conflict, + * c->undoLevel() is called *before* p is undone. Hence, the + * range [s.levelStart(s.decisionLevel()), s.assignment().size()) + * contains p followed by all literals that were forced because of p. + * \note propagateUntil(c) is used to propagate p. + */ + bool test(Literal p, PostPropagator* c); + + //! Estimates the number of assignments following from setting p to true. + /*! + * \note For the estimate only binary clauses are considered. + */ + uint32_t estimateBCP(Literal p, int maxRecursionDepth = 5) const; + + //! Computes the number of in-edges for each assigned literal. + /*! + * \pre !hasConflict() + * \note For a literal p assigned on level(p), only in-edges from + * levels < level(p) are counted. + * \return The maximum number of in-edges. + */ + uint32_t inDegree(WeightLitVec& out); + //! Bumps var activities of assigned variables based on their in-degree. + /*! + * Let `vMax` be the assigned variable with the highest in-degree `maxIn = inDegree(vMax)`. + * Bumps all assigned variables `v` by `inDegree(v) * bump/maxIn`. + */ + void counterBumpVars(uint32_t bump); + + struct DBInfo { + uint32_t size; + uint32_t locked; + uint32_t pinned; + }; + //! Removes upto remMax percent of the learnt nogoods. + /*! + * \param remMax Fraction of nogoods to remove ([0.0,1.0]). + * \param rs Strategy to apply during nogood deletion. + * \return The number of locked and active/glue clauses currently exempt from deletion. + * \note + * Nogoods that are the reason for a literal to be in the assignment + * are said to be locked and won't be removed. + */ + DBInfo reduceLearnts(double remMax, const ReduceStrategy& rs = ReduceStrategy()); + + //! Resolves the active conflict using the selected strategy. + /*! + * If searchMode() is set to learning, resolveConflict implements + * First-UIP learning and backjumping. Otherwise, it simply applies + * chronological backtracking. + * \pre hasConflict() + * \return + * - true if the conflict was successfully resolved + * - false otherwise + * \note + * If decisionLevel() == rootLevel() false is returned. + */ + bool resolveConflict(); + + //! Backtracks the last decision and updates the backtrack-level if necessary. + /*! + * \return + * - true if backtracking was possible + * - false if decisionLevel() == rootLevel() + */ + bool backtrack(); + + //! Undoes all assignments up to (but not including) decision level dl. + /*! + * \post decision level == max(min(decisionLevel(), dl), max(rootLevel(), backtrackLevel())) + * \return The decision level after undoing assignments. + * \note + * undoUntil() stops at the current backtrack level unless undoMode includes the mode + * that was used when setting the backtrack level. + * \note + * If undoMode contains undo_save_phases, the functions saves the values of variables that are undone. + * Otherwise, phases are only saved if indicated by the active strategy. + */ + uint32_t undoUntil(uint32_t dl, uint32_t undoMode); + //! Behaves like undoUntil(dl, undo_default). + uint32_t undoUntil(uint32_t dl) { return undoUntilImpl(dl, false); } + //! Returns whether undoUntil(decisionLevel()-1) is valid and would remove decisionLevel(). + bool isUndoLevel() const; + + //! Adds a new auxiliary variable to this solver. + /*! + * Auxiliary variables are local to one solver and are not considered + * as part of the problem. They shall be added/used only during solving, i.e. + * after problem setup is completed. + */ + Var_t pushAuxVar(); + //! Pops the num most recently added auxiliary variables and destroys all constraints in auxCons. + void popAuxVar(uint32_t num = UINT32_MAX, ConstraintDB* auxCons = nullptr); + //@} + + /*! + * \name State inspection + * Functions for inspecting the state of the solver & search. + * \note validVar(v) is a precondition for all functions that take a variable as + * parameter. + * @{ */ + //! Returns the number of problem variables. + uint32_t numProblemVars() const { return shared_->numVars(); } + //! Returns the number of active solver-local aux variables. + uint32_t numAuxVars() const { return numVars() - numProblemVars(); } + //! Returns the number of solver variables, i.e. numProblemVars() + numAuxVars() + uint32_t numVars() const { return assign_.numVars() - 1; } + //! Returns the solver variables as an iterable view. + auto vars(uint32_t off = 1u) const { return irange(off, numVars() + 1); } + //! Returns the problem variables as an iterable view. + auto problemVars(uint32_t off = 1u) const { return irange(off, numProblemVars() + 1); } + //! Returns true if var represents a valid variable in this solver. + bool validVar(Var_t var) const { return var <= numVars(); } + //! Returns true if var is a solver-local aux var. + bool auxVar(Var_t var) const { return shared_->numVars() < var; } + //! Returns the number of assigned variables. + uint32_t numAssignedVars() const { return assign_.assigned(); } + //! Returns the number of free variables. + /*! + * The number of free variables is the number of vars that are neither + * assigned nor eliminated. + */ + uint32_t numFreeVars() const { return assign_.free() - 1; } + //! Returns the value of v w.r.t the current assignment. + Val_t value(Var_t v) const { + assert(validVar(v)); + return assign_.value(v); + } + //! Returns the value of v w.r.t the top level. + Val_t topValue(Var_t v) const { return level(v) == 0 ? value(v) : value_free; } + //! Returns the set of preferred values of v. + ValueSet pref(Var_t v) const { + assert(validVar(v)); + return assign_.pref(v); + } + //! Returns true if p is true w.r.t the current assignment. + bool isTrue(Literal p) const { + assert(validVar(p.var())); + return assign_.value(p.var()) == trueValue(p); + } + //! Returns true if p is false w.r.t the current assignment. + bool isFalse(Literal p) const { + assert(validVar(p.var())); + return assign_.value(p.var()) == falseValue(p); + } + //! Returns the literal of v being true in the current assignment. + /*! + * \pre v is assigned a value in the current assignment + */ + Literal trueLit(Var_t v) const { + assert(value(v) != value_free); + return {v, valSign(value(v))}; + } + Literal defaultLit(Var_t v) const; + //! Returns the decision level on which v was assigned. + /*! + * \note The returned value is only meaningful if value(v) != value_free. + */ + uint32_t level(Var_t v) const { + assert(validVar(v)); + return assign_.level(v); + } + //! Returns true if v is currently marked as seen. + /*! + * Note: variables assigned on level 0 are always marked. + */ + bool seen(Var_t v) const { + assert(validVar(v)); + return assign_.seen(v); + } + //! Returns true if the literal p is currently marked as seen. + bool seen(Literal p) const { + assert(validVar(p.var())); + return assign_.seen(p); + } + //! Returns the current decision level. + uint32_t decisionLevel() const { return size32(levels_); } + bool validLevel(uint32_t dl) const { return dl != 0 && dl <= decisionLevel(); } + //! Returns the starting position of decision level dl in the trail. + /*! + * \pre validLevel(dl) + */ + uint32_t levelStart(uint32_t dl) const { + assert(validLevel(dl)); + return levels_[dl - 1].trailPos; + } + //! Returns the decision literal of the decision level dl. + /*! + * \pre validLevel(dl) + */ + Literal decision(uint32_t dl) const { + assert(validLevel(dl)); + return assign_.trail[levels_[dl - 1].trailPos]; + } + //! Returns true, if the current assignment is conflicting. + bool hasConflict() const { return not conflict_.empty(); } + bool hasStopConflict() const { return hasConflict() && conflict_[0] == lit_false; } + //! Returns the number of (unprocessed) literals in the propagation queue. + uint32_t queueSize() const { return assign_.qSize(); } + //! Number of problem constraints in this solver. + uint32_t numConstraints() const; + //! Returns the number of constraints that are currently in the solver's learnt database. + uint32_t numLearntConstraints() const { return size32(learnts_); } + //! Returns the reason for p being true. + /*! + * \pre p is true w.r.t the current assignment + */ + const Antecedent& reason(Literal p) const { + assert(isTrue(p)); + return assign_.reason(p.var()); + } + //! Returns the additional reason data associated with p. + uint32_t reasonData(Literal p) const { return assign_.data(p.var()); } + //! Returns the current (partial) assignment as a set of true literals. + /*! + * \note Although the special var 0 always has a value it is not considered to be + * part of the assignment. + */ + LitView trailView(uint32_t offset = 0) const { + return {assign_.trail.data() + offset, size32(assign_.trail) - offset}; + } + const Assignment& assignment() const { return assign_; } + //! Returns the current conflict as a set of literals. + LitView conflict() const { return conflict_; } + //! Returns the most recently derived conflict clause. + LitView conflictClause() const { return cc_; } + //! Returns the enumeration constraint set by the enumerator used. + Constraint* enumerationConstraint() const { return enum_; } + DBRef constraints() const { return constraints_; } + //! Returns the idx-th learnt constraint. + /*! + * \pre idx < numLearntConstraints() + */ + Constraint& getLearnt(uint32_t idx) const { + assert(idx < numLearntConstraints()); + return *learnts_[idx]; + } + + mutable Rng rng; //!< Random number generator for this object. + SolverStats stats; //!< Stores statistics about the solving process. + //@} + + /*! + * \name Watch management + * Functions for setting/removing watches. + * \pre validVar(v) + * @{ */ + //! Returns the number of constraints watching the literal p. + uint32_t numWatches(Literal p) const; + //! Returns true if the constraint c watches the literal p. + bool hasWatch(Literal p, Constraint* c) const; + bool hasWatch(Literal p, ClauseHead* c) const; + //! Returns c's watch-structure associated with p. + /*! + * \note returns 0, if hasWatch(p, c) == false + */ + GenericWatch* getWatch(Literal p, Constraint* c) const; + //! Adds c to the watch-list of p. + /*! + * When p becomes true, c->propagate(p, data, *this) is called. + * \post hasWatch(p, c) == true + */ + void addWatch(Literal p, Constraint* c, uint32_t data = 0) { + assert(validWatch(p)); + watches_[p.id()].push_right(GenericWatch(c, data)); + } + //! Adds w to the clause watch-list of p. + void addWatch(Literal p, const ClauseWatch& w) { + assert(validWatch(p)); + watches_[p.id()].push_left(w); + } + //! Removes c from p's watch-list. + /*! + * \post hasWatch(p, c) == false + */ + void removeWatch(const Literal& p, Constraint* c); + void removeWatch(const Literal& p, ClauseHead* c); + //! Adds c to the watch-list of decision-level dl. + /*! + * Constraints in the watch-list of a decision level are + * notified when that decision level is about to be backtracked. + * \pre validLevel(dl) + */ + void addUndoWatch(uint32_t dl, Constraint* c) { + assert(validLevel(dl)); + if (levels_[dl - 1].undo != nullptr) { + levels_[dl - 1].undo->push_back(c); + } + else { + levels_[dl - 1].undo = allocUndo(c); + } + } + //! Removes c from the watch-list of the decision level dl. + bool removeUndoWatch(uint32_t dl, Constraint* c); + //@} + + /*! + * \name Misc functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + * @{ */ + bool addPost(PostPropagator* p, bool init); + //! Updates the reason for p being true. + /*! + * \pre p is true and x is a valid reason for p + */ + bool setReason(Literal p, const Antecedent& x, uint32_t data = UINT32_MAX) { + assert(isTrue(p) || shared_->eliminated(p.var())); + assign_.setReason(p.var(), x); + if (data != UINT32_MAX) { + assign_.setData(p.var(), data); + } + return true; + } + void setPref(Var_t v, ValueSet::Value which, Val_t to) { + assert(validVar(v) && to <= value_false); + assign_.requestPrefs(); + assign_.setPref(v, which, to); + } + void resetPrefs() { assign_.resetPrefs(); } + void resetLearntActivities(); + //! Returns the reason for p being true as a set of literals. + void reason(Literal p, LitVec& out) { + assert(isTrue(p)); + out.clear(); + return assign_.reason(p.var()).reason(*this, p, out); + } + + //! Helper function for updating antecedent scores during conflict resolution. + /*! + * \param sc The current score of the active antecedent. + * \param p The literal implied by the active antecedent. + * \param lits The literals of the active antecedent. + * \return true if a score was updated. + * + * \note Depending on the active solver strategies, the function + * increases the activity and/or updates the lbd of the given antecedent. + * + * \note If SolverStrategies::bumpVarAct is active, p's activity + * is increased if the new lbd is smaller than the lbd of the + * conflict clause that is currently being derived. + */ + bool updateOnReason(ConstraintScore& sc, Literal p, const LitVec& lits) { + // update only during conflict resolution + if (&lits != &conflict_) { + return false; + } + sc.bumpActivity(); + if (uint32_t up = strategy_.updateLbd; up != SolverStrategies::lbd_fixed && not lits.empty()) { + uint32_t lbd = sc.lbd(); + auto inc = static_cast(up != SolverStrategies::lbd_updated_less); + uint32_t nLbd = countLevels(lits, lbd - inc); + if ((nLbd + inc) < lbd) { + sc.bumpLbd(nLbd + static_cast(up == SolverStrategies::lbd_update_pseudo)); + } + } + if (strategy_.bumpVarAct && isTrue(p)) { + bumpAct_.push_back(WeightLiteral{p, static_cast(sc.lbd())}); + } + return true; + } + + //! Helper function for increasing antecedent activities during conflict clause minimization. + bool updateOnMinimize(ConstraintScore& sc) const { return not strategy_.ccMinKeepAct && (sc.bumpActivity(), true); } + + //! Helper function for antecedents to be called during conflict clause minimization. + bool ccMinimize(Literal p, CCMinRecursive* rec) const { + return seen(p.var()) || (rec && hasLevel(level(p.var())) && ccMinRecurse(*rec, p)); + } + + //! Allocates a small block (32-bytes) from the solver's small block pool. + void* allocSmall() { return smallAlloc_.allocate(); } + //! Frees a small block previously allocated from the solver's small block pool. + void freeSmall(void* m) { smallAlloc_.free(m); } + + void addLearntBytes(uint32_t bytes) { memUse_ += bytes; } + void freeLearntBytes(uint64_t bytes) { memUse_ -= (bytes < memUse_) ? bytes : memUse_; } + + bool restartReached(const SearchLimits& limit) const; + bool reduceReached(const SearchLimits& limit) const; + + //! simplifies cc and returns finalizeConflictClause(cc, info); + uint32_t simplifyConflictClause(LitVec& cc, ConstraintInfo& info, ClauseHead* rhs); + uint32_t finalizeConflictClause(LitVec& cc, ConstraintInfo& info, uint32_t ccRepMode = 0); + uint32_t countLevels(LitView lits, uint32_t maxLevels = Clasp::lbd_max); + bool hasLevel(uint32_t dl) const { + assert(validLevel(dl)); + return levels_[dl - 1].marked != 0; + } + bool frozenLevel(uint32_t dl) const { + assert(validLevel(dl)); + return levels_[dl - 1].freeze != 0; + } + bool inputVar(Literal p) const { return varInfo(p.var()).input(); } + void markLevel(uint32_t dl) { + assert(validLevel(dl)); + levels_[dl - 1].marked = 1; + } + void freezeLevel(uint32_t dl) { + assert(validLevel(dl)); + levels_[dl - 1].freeze = 1; + } + void unmarkLevel(uint32_t dl) { + assert(validLevel(dl)); + levels_[dl - 1].marked = 0; + } + void unfreezeLevel(uint32_t dl) { + assert(validLevel(dl)); + levels_[dl - 1].freeze = 0; + } + void markSeen(Var_t v) { + assert(validVar(v)); + assign_.setSeen(v); + } + void markSeen(Literal p) { + assert(validVar(p.var())); + assign_.setSeen(p); + } + void clearSeen(Var_t v) { + assert(validVar(v)); + assign_.clearSeen(v); + } + void values(ValueVec& out) const { assign_.values(out); } + void setHeuristic(DecisionHeuristic* h); + void destroyDB(ConstraintDB& db); + auto strategies() -> SolverStrategies& { return strategy_; } + bool resolveToFlagged(LitView conflictClause, uint8_t vflag, LitVec& out, uint32_t& lbd) const; + void resolveToCore(LitVec& out); + void acquireProblemVar(Var_t var); + void acquireProblemVars() { acquireProblemVar(numProblemVars()); } + auto trailLit(uint32_t pos) const -> Literal { return assign_.trail[pos]; } + LitView levelLits(uint32_t dl) const { + auto start = levelStart(dl); + return {assign_.trail.data() + start, + (dl < decisionLevel() ? levelStart(dl + 1) : size32(assign_.trail)) - start}; + } + //@} private: - struct DLevel { - explicit DLevel(uint32 pos = 0, ConstraintDB* u = 0) - : trailPos(pos) - , marked(0) - , freeze(0) - , undo(u) {} - uint32 trailPos : 30; - uint32 marked : 1; - uint32 freeze : 1; - ConstraintDB* undo; - }; - struct DecisionLevels : public PodVector::type { - DecisionLevels() : root(0), flip(0), mode(0), jump(0) {} - uint32 root; // root level - uint32 flip : 30; // backtrack level - uint32 mode : 2; // type of backtrack level - uint32 jump; // length of active undo - }; - typedef PodVector::type ReasonVec; - typedef PodVector::type Watches; - struct Dirty; - struct CmpScore { - typedef std::pair ViewPair; - CmpScore(const ConstraintDB& learnts, ReduceStrategy::Score sc, uint32 g, uint32 f = 0) : db(learnts), rs(sc), glue(g), freeze(f) {} - uint32 score(const ConstraintScore& act) const { return ReduceStrategy::asScore(rs, act); } - bool operator()(uint32 lhsId, uint32 rhsId) const { return (*this)(db[lhsId], db[rhsId]); } - bool operator()(const ViewPair& lhs, const ViewPair& rhs) const { return this->operator()(lhs.second, rhs.second); } - bool operator()(ConstraintScore lhs, ConstraintScore rhs) const { return ReduceStrategy::compare(rs, lhs, rhs) < 0;} - bool operator()(const Constraint* lhs, const Constraint* rhs) const { return this->operator()(lhs->activity(), rhs->activity()); } - bool isFrozen(const ConstraintScore& a) const { return a.bumped() && a.lbd() <= freeze; } - bool isGlue(const ConstraintScore& a) const { return a.lbd() <= glue; } - const ConstraintDB& db; - ReduceStrategy::Score rs; - uint32 glue; - uint32 freeze; - private: CmpScore& operator=(const CmpScore&); - }; - bool validWatch(Literal p) const { return p.id() < (uint32)watches_.size(); } - void freeMem(); - void resetHeuristic(Solver* detach, DecisionHeuristic* h = 0, Ownership_t::Type own = Ownership_t::Acquire); - bool simplifySAT(); - bool unitPropagate(); - bool postPropagate(PostPropagator** start, PostPropagator* stop); - void cancelPropagation(); - uint32 undoUntilImpl(uint32 dl, bool sp); - void undoLevel(bool sp); - uint32 analyzeConflict(); - bool isModel(); - bool resolveToFlagged(const LitVec& conflictClause, uint8 vf, LitVec& out, uint32& lbd); - void otfs(Antecedent& lhs, const Antecedent& rhs, Literal p, bool final); - ClauseHead* otfsRemove(ClauseHead* c, const LitVec* newC); - uint32 ccMinimize(LitVec& cc, LitVec& removed, uint32 antes, CCMinRecursive* ccMin); - void ccMinRecurseInit(CCMinRecursive& ccMin); - bool ccMinRecurse(CCMinRecursive& ccMin, Literal p) const; - bool ccRemovable(Literal p, uint32 antes, CCMinRecursive* ccMin); - Antecedent ccHasReverseArc(Literal p, uint32 maxLevel, uint32 maxNew); - void ccResolve(LitVec& cc, uint32 pos, const LitVec& reason); - void undoFree(ConstraintDB* x); - void setConflict(Literal p, const Antecedent& a, uint32 data); - bool force(const ImpliedLiteral& p); - void updateBranch(uint32 n); - uint32 incEpoch(uint32 size, uint32 inc = 1); - DBInfo reduceLinear(uint32 maxR, const CmpScore& cmp); - DBInfo reduceSort(uint32 maxR, const CmpScore& cmp); - DBInfo reduceSortInPlace(uint32 maxR, const CmpScore& cmp, bool onlyPartialSort); - Literal popVars(uint32 num, bool popLearnt, ConstraintDB* popAux); - ConstraintDB* allocUndo(Constraint* c); - SharedContext* shared_; // initialized by master thread - otherwise read-only! - SolverStrategies strategy_; // strategies used by this object - HeuristicPtr heuristic_; // active decision heuristic - CCMinRecursive* ccMin_; // additional data for supporting recursive strengthen - PostPropagator** postHead_; // head of post propagator list to propagate - ConstraintDB* undoHead_; // free list of undo DBs - Constraint* enum_; // enumeration constraint - set by enumerator - uint64 memUse_; // memory used by learnt constraints (estimate) - Dirty* lazyRem_; // set of watch lists that contain invalid constraints - DynamicLimit* dynLimit_; // active dynamic limit - SmallClauseAlloc smallAlloc_; // allocator object for small clauses - Assignment assign_; // three-valued assignment. - DecisionLevels levels_; // information (e.g. position in trail) on each decision level - ConstraintDB constraints_; // problem constraints - ConstraintDB learnts_; // learnt constraints - PropagatorList post_; // (possibly empty) list of post propagators - Watches watches_; // for each literal p: list of constraints watching p - LitVec conflict_; // conflict-literals for later analysis - LitVec cc_; // temporary: conflict clause within analyzeConflict - LitVec temp_; // temporary: redundant literals in simplifyConflictClause - WeightLitVec bumpAct_; // temporary: lits from current dl whose activity might get an extra bump - VarVec epoch_; // temporary vector for computing LBD - VarVec cflStamp_; // temporary vector for computing number of conflicts in branch - ImpliedList impliedLits_; // lits that were asserted on current dl but are logically implied earlier - ConstraintInfo ccInfo_; // temporary: information about conflict clause cc_ - Literal tag_; // aux literal for tagging learnt constraints - uint32 dbIdx_; // position of first new problem constraint in master db - uint32 lastSimp_ :30;// number of top-level assignments on last call to simplify - uint32 shufSimp_ : 1;// shuffle db on next simplify? - uint32 initPost_ : 1;// initialize new post propagators? - bool splitReq_; // unhandled split request? + struct DLevel { + explicit DLevel(uint32_t pos = 0, ConstraintDB* u = nullptr) : trailPos(pos), marked(0), freeze(0), undo(u) {} + uint32_t trailPos : 30; + uint32_t marked : 1; + uint32_t freeze : 1; + ConstraintDB* undo; + }; + struct DecisionLevels : PodVector_t { + uint32_t root = 0; // root level + uint32_t flip : 30 = 0; // backtrack level + uint32_t mode : 2 = 0; // type of backtrack level + uint32_t jump = 0; // length of active undo + }; + using ReasonVec = PodVector_t; + using Watches = PodVector_t; + using CCMinRecPtr = std::unique_ptr; + struct Dirty; + struct CmpScore { + using ViewPair = std::pair; + CmpScore(const ConstraintDB& learnts, ReduceStrategy::Score sc, uint32_t g, uint32_t f = 0) + : db(learnts) + , rs(sc) + , glue(g) + , freeze(f) {} + [[nodiscard]] uint32_t score(const ConstraintScore& act) const { return ReduceStrategy::asScore(rs, act); } + bool operator()(uint32_t lhsId, uint32_t rhsId) const { return (*this)(db[lhsId], db[rhsId]); } + bool operator()(const ViewPair& lhs, const ViewPair& rhs) const { + return this->operator()(lhs.second, rhs.second); + } + bool operator()(ConstraintScore lhs, ConstraintScore rhs) const { + return ReduceStrategy::compare(rs, lhs, rhs) < 0; + } + bool operator()(const Constraint* lhs, const Constraint* rhs) const { + return this->operator()(lhs->activity(), rhs->activity()); + } + [[nodiscard]] bool isFrozen(const ConstraintScore& a) const { return a.bumped() && a.lbd() <= freeze; } + [[nodiscard]] bool isGlue(const ConstraintScore& a) const { return a.lbd() <= glue; } + const ConstraintDB& db; + ReduceStrategy::Score rs; + uint32_t glue; + uint32_t freeze; + }; + bool validWatch(Literal p) const { return p.id() < size32(watches_); } + void freeMem(); + void resetHeuristic(Solver* detach, DecisionHeuristic* h = nullptr); + bool simplifySat(); + bool unitPropagate(); + bool postPropagate(PostPropagator** start, PostPropagator* stop); + void cancelPropagation(); + uint32_t undoUntilImpl(uint32_t dl, bool sp); + void undoLevel(bool sp); + uint32_t analyzeConflict(); + bool isModel(); + bool resolveToFlagged(LitView conflictClause, uint8_t vf, LitVec& out, uint32_t& lbd); + void otfs(Antecedent& lhs, const Antecedent& rhs, Literal p, bool final); + ClauseHead* otfsRemove(ClauseHead* c, const LitVec* newC); + uint32_t ccMinimize(LitVec& cc, LitVec& removed, uint32_t antes, CCMinRecursive* ccMin); + void ccMinRecurseInit(CCMinRecursive& ccMin); + bool ccMinRecurse(CCMinRecursive& ccMin, Literal p) const; + bool ccRemovable(Literal p, uint32_t antes, CCMinRecursive* ccMin); + Antecedent ccHasReverseArc(Literal p, uint32_t maxLevel, uint32_t maxNew); + void ccResolve(LitVec& cc, uint32_t pos, const LitVec& reason); + void undoFree(ConstraintDB* x); + void setConflict(Literal p, const Antecedent& a, uint32_t data); + bool force(const ImpliedLiteral& p); + void updateBranch(uint32_t n); + uint32_t incEpoch(uint32_t size, uint32_t inc = 1); + DBInfo reduceLinear(uint32_t maxR, const CmpScore& cmp); + DBInfo reduceSort(uint32_t maxR, const CmpScore& cmp); + DBInfo reduceSortInPlace(uint32_t maxR, const CmpScore& cmp, bool onlyPartialSort); + Literal popVars(uint32_t num, bool popLearnt, ConstraintDB* popAux); + ConstraintDB* allocUndo(Constraint* c); + SharedContext* shared_; // initialized by master thread - otherwise read-only! + SolverStrategies strategy_; // strategies used by this object + DecisionHeuristic* heuristic_; // active decision heuristic + CCMinRecPtr ccMin_; // additional data for supporting recursive strengthen + PostPropagator** postHead_; // head of post propagator list to propagate + ConstraintDB* undoHead_; // free list of undo DBs + Constraint* enum_; // enumeration constraint - set by enumerator + uint64_t memUse_; // memory used by learnt constraints (estimate) + Dirty* lazyRem_; // set of watch lists that contain invalid constraints + DynamicLimit* dynLimit_; // active dynamic limit + SmallClauseAlloc smallAlloc_; // allocator object for small clauses + Assignment assign_; // three-valued assignment. + DecisionLevels levels_; // information (e.g. position in trail) on each decision level + ConstraintDB constraints_; // problem constraints + ConstraintDB learnts_; // learnt constraints + PropagatorList post_; // (possibly empty) list of post propagators + Watches watches_; // for each literal p: list of constraints watching p + LitVec conflict_; // conflict-literals for later analysis + LitVec cc_; // temporary: conflict clause within analyzeConflict + LitVec temp_; // temporary: redundant literals in simplifyConflictClause + WeightLitVec bumpAct_; // temporary: lits from current dl whose activity might get an extra bump + VarVec epoch_; // temporary vector for computing LBD + VarVec cflStamp_; // temporary vector for computing number of conflicts in branch + ImpliedList impliedLits_; // lits that were asserted on current dl but are logically implied earlier + ConstraintInfo ccInfo_; // temporary: information about conflict clause cc_ + Literal tag_; // aux literal for tagging learnt constraints + uint32_t dbIdx_; // position of first new problem constraint in master db + uint32_t lastSimp_ : 30; // number of top-level assignments on last call to simplify + uint32_t shufSimp_ : 1; // shuffle db on next simplify? + uint32_t initPost_ : 1; // initialize new post propagators? + bool splitReq_; // unhandled split request? }; -inline bool isRevLit(const Solver& s, Literal p, uint32 maxL) { - return s.isFalse(p) && (s.seen(p) || s.level(p.var()) < maxL); +inline bool isRevLit(const Solver& s, Literal p, uint32_t maxL) { + return s.isFalse(p) && (s.seen(p) || s.level(p.var()) < maxL); } //! Simplifies the constraints in db and removes those that are satisfied. -template +template void simplifyDB(Solver& s, C& db, bool shuffle) { - typename C::size_type j = 0; - for (typename C::size_type i = j, end = db.size(); i != end; ++i) { - Constraint* c = db[i]; - if (c->simplify(s, shuffle)){ c->destroy(&s, false); } - else { db[j++] = c; } - } - shrinkVecTo(db, j); + typename C::size_type j = 0; + for (Constraint* c : db) { + if (c->simplify(s, shuffle)) { + c->destroy(&s, false); + } + else { + db[j++] = c; + } + } + shrinkVecTo(db, j); } //! Destroys (and optionally detaches) all constraints in db. void destroyDB(Solver::ConstraintDB& db, Solver* s, bool detach); //! Returns the default decision literal of the given variable. -inline Literal Solver::defaultLit(Var v) const { - switch(strategy_.signDef) { - default: // - case SolverStrategies::sign_atom: return Literal(v, !varInfo(v).has(VarInfo::Body)); - case SolverStrategies::sign_pos : return posLit(v); - case SolverStrategies::sign_neg : return negLit(v); - case SolverStrategies::sign_rnd : return Literal(v, rng.drand() < 0.5); - } +inline Literal Solver::defaultLit(Var_t v) const { + switch (strategy_.signDef) { + default: // + case SolverStrategies::sign_atom: return {v, not varInfo(v).has(VarInfo::flag_body)}; + case SolverStrategies::sign_pos : return posLit(v); + case SolverStrategies::sign_neg : return negLit(v); + case SolverStrategies::sign_rnd : return {v, rng.drand() < 0.5}; + } } //! Event type optionally emitted after a conflict. -struct NewConflictEvent : SolveEvent { - NewConflictEvent(const Solver& s, const LitVec& c, const ConstraintInfo& i) : SolveEvent(s, verbosity_quiet), learnt(&c), info(i) {} - const LitVec* learnt; //!< Learnt conflict clause. - ConstraintInfo info; //!< Additional information associated with the conflict clause. +struct NewConflictEvent : SolveEvent { + template + NewConflictEvent(const Solver& s, const C& c, const ConstraintInfo& i) + : SolveEvent(this, s, verbosity_quiet) + , learnt(c) + , info(i) {} + LitView learnt; //!< Learnt conflict clause. + ConstraintInfo info; //!< Additional information associated with the conflict clause. }; //@} @@ -947,148 +1046,161 @@ struct NewConflictEvent : SolveEvent { */ class DecisionHeuristic { public: - DecisionHeuristic() {} - virtual ~DecisionHeuristic(); - /*! - * Called once after all problem variables are known to the solver. - * The default-implementation is a noop. - * \param s The solver in which this heuristic is used. - */ - virtual void startInit(const Solver& s) { (void)s; } - - /*! - * Called once after all problem constraints are known to the solver - * and the problem was simplified. - * The default-implementation is a noop. - * \param s The solver in which this heuristic is used. - */ - virtual void endInit(Solver& s) { (void)s; } - - //! Called once if s switches to a different heuristic. - virtual void detach(Solver& s) { (void)s; } - - //! Called if configuration has changed. - virtual void setConfig(const HeuParams& p) { (void)p; } - - /*! - * Called if the state of one or more variables changed. - * A state change is one of: - * - A previously eliminated variable is resurrected. - * - A new aux variable was added. - * - An aux variable was removed. - * . - * \param s Solver in which the state change occurred. - * \param v The first variable affected by the change. - * \param n The range of variables affected, i.e. [v, v+n). - * \note Use s.validVar(v) and s.auxVar(v) to determine the reason for the update. - */ - virtual void updateVar(const Solver& s, Var v, uint32 n) = 0; - - /*! - * Called on decision level 0. Variables that are assigned on this level - * may be removed from any decision heuristic. - * \note Whenever the solver returns to dl 0, simplify is only called again - * if the solver learnt new facts since the last call to simplify. - * - * \param s The solver that reached decision level 0. - * \param st The position in the trail of the first new learnt fact. - */ - virtual void simplify(const Solver& s, LitVec::size_type st) { (void)s; (void)st; } - - /*! - * Called whenever the solver backtracks. - * Literals in the range [s.trail()[st], s.trail().size()) are subject to backtracking. - * The default-implementation is a noop. - * \param s The solver that is about to backtrack. - * \param st Position in the trail of the first literal that will be backtracked. - */ - virtual void undoUntil(const Solver& s, LitVec::size_type st) { (void)s; (void)st; } - - /*! - * Called whenever a new constraint is added to the solver s. - * The default-implementation is a noop. - * \param s The solver to which the constraint is added. - * \param first First literal of the new constraint. - * \param size Size of the new constraint. - * \param t Type of the new constraint. - * \note first points to an array of size size. - */ - virtual void newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t) { (void)s; (void)first; (void)size; (void)t; } - - /*! - * Called for each new reason-set that is traversed during conflict analysis. - * The default-implementation is a noop. - * \param s The solver in which the conflict is analyzed. - * \param lits The current reason-set under inspection. - * \param resolveLit The literal that is currently resolved. - * \note When a conflict is detected, the solver passes the conflicting literals - * in lits during the first call to updateReason. On that first call resolveLit - * is the sentinel-literal. - */ - virtual void updateReason(const Solver& s, const LitVec& lits, Literal resolveLit) { (void)s; (void)lits; (void)resolveLit; } - - //! Shall bump the activity of literals in lits by lits.second * adj. - /*! - * The default-implementation is a noop and always returns false. - * \return true if heuristic supports activities, false otherwise. - */ - virtual bool bump(const Solver& s, const WeightLitVec& lits, double adj) { (void)s; (void)lits; (void)adj; return false; } - - /*! - * Called whenever the solver must pick a new variable to branch on. - * \param s The solver that needs a new decision variable. - * \return - * - true : if the decision heuristic assumed a literal - * - false : if no decision could be made because assignment is total or there is a conflict - * . - * \post - * If true is returned, the heuristic has asserted a literal. - */ - bool select(Solver& s) { return s.numFreeVars() != 0 && s.assume(doSelect(s)); } - - //! Implements the actual selection process. - /*! - * \pre s.numFreeVars() > 0, i.e. there is at least one variable to branch on. - * \return - * - a literal that is currently free or - * - a sentinel literal. In that case, the heuristic shall have asserted a literal! - */ - virtual Literal doSelect(Solver& s) = 0; - - /*! - * Shall select one of the literals in the range [first, last). - * \param s The solver that needs a new decision variable. - * \param first Pointer to first literal in range. - * \param last Pointer to the end of the range. - * \pre [first, last) is not empty and all literals in the range are currently unassigned. - * \note The default implementation returns *first. - */ - virtual Literal selectRange(Solver& s, const Literal* first, const Literal* last) { - (void)s; (void)last; - return *first; - } - static Literal selectLiteral(const Solver& s, Var v, int signScore) { - ValueSet prefs = s.pref(v); - if (signScore != 0 && !prefs.has(ValueSet::user_value | ValueSet::saved_value | ValueSet::pref_value)) { - return Literal(v, signScore < 0); - } - else if (!prefs.empty()) { - return Literal(v, prefs.sign()); - } - return s.defaultLit(v); - } -private: - DecisionHeuristic(const DecisionHeuristic&); - DecisionHeuristic& operator=(const DecisionHeuristic&); + DecisionHeuristic() = default; + virtual ~DecisionHeuristic(); + DecisionHeuristic(DecisionHeuristic&&) = delete; + + /*! + * Called once after all problem variables are known to the solver. + * The default-implementation is a noop. + * \param s The solver in which this heuristic is used. + */ + virtual void startInit(const Solver& s) { static_cast(s); } + + /*! + * Called once after all problem constraints are known to the solver + * and the problem was simplified. + * The default-implementation is a noop. + * \param s The solver in which this heuristic is used. + */ + virtual void endInit(Solver& s) { static_cast(s); } + + //! Called once if s switches to a different heuristic. + virtual void detach(Solver& s) { static_cast(s); } + + //! Called if configuration has changed. + virtual void setConfig(const HeuParams& p) { static_cast(p); } + + /*! + * Called if the state of one or more variables changed. + * A state change is one of: + * - A previously eliminated variable is resurrected. + * - A new aux variable was added. + * - An aux variable was removed. + * . + * \param s Solver in which the state change occurred. + * \param v The first variable affected by the change. + * \param n The range of variables affected, i.e. [v, v+n). + * \note Use s.validVar(v) and s.auxVar(v) to determine the reason for the update. + */ + virtual void updateVar(const Solver& s, Var_t v, uint32_t n) = 0; + + /*! + * Called on decision level 0. Variables that are assigned on this level + * may be removed from any decision heuristic. + * \note Whenever the solver returns to dl 0, simplify is only called again + * if the solver learnt new facts since the last call to simplify. + * + * \param s The solver that reached decision level 0. + * \param newFacts List of newly derived facts. + */ + virtual void simplify(const Solver& s, LitView newFacts) { + static_cast(s); + static_cast(newFacts); + } + + /*! + * Called whenever the solver backtracks. + * Literals in 'undo' are subject to backtracking. + * The default-implementation is a noop. + * \param s The solver that is about to backtrack. + * \param undo Literals that will be backtracked. + */ + virtual void undo(const Solver& s, LitView undo) { + static_cast(s); + static_cast(undo); + } + + /*! + * Called whenever a new constraint is added to the given solver. + * The default-implementation is a noop. + * \param s The solver to which the constraint is added. + * \param lits Literals of the new constraint. + * \param t Type of the new constraint. + */ + virtual void newConstraint(const Solver& s, LitView lits, ConstraintType t) { + static_cast(s); + static_cast(lits); + static_cast(t); + } + + /*! + * Called for each new reason-set that is traversed during conflict analysis. + * The default-implementation is a noop. + * \param s The solver in which the conflict is analyzed. + * \param lits The current reason-set under inspection. + * \param resolveLit The literal that is currently resolved. + * \note When a conflict is detected, the solver passes the conflicting literals + * in lits during the first call to updateReason. On that first call resolveLit + * is the sentinel-literal. + */ + virtual void updateReason(const Solver& s, LitView lits, Literal resolveLit) { + static_cast(s); + static_cast(lits); + static_cast(resolveLit); + } + + //! Shall bump the activity of literals `l` in `lits` by `l.weight * adj`. + /*! + * The default-implementation is a noop and always returns false. + * \return true if heuristic supports activities, false otherwise. + */ + virtual bool bump(const Solver& s, WeightLitView lits, double adj) { + static_cast(s); + static_cast(lits); + static_cast(adj); + return false; + } + + /*! + * Called whenever the solver must pick a new variable to branch on. + * \param s The solver that needs a new decision variable. + * \return + * - true : if the decision heuristic assumed a literal + * - false : if no decision could be made because assignment is total or there is a conflict + * . + * \post + * If true is returned, the heuristic has asserted a literal. + */ + bool select(Solver& s) { return s.numFreeVars() != 0 && s.assume(doSelect(s)); } + + //! Implements the actual selection process. + /*! + * \pre s.numFreeVars() > 0, i.e. there is at least one variable to branch on. + * \return + * - a literal that is currently free or + * - a sentinel literal. In that case, the heuristic shall have asserted a literal! + */ + virtual Literal doSelect(Solver& s) = 0; + + /*! + * Shall select one of the literals in the given non-empty range. + * \param s The solver that needs a new decision variable. + * \param range Range of literals to select from. + * \pre lits is a non-empty range of currently unassigned literals. + * \note The default implementation returns the first literal in lits. + */ + virtual Literal selectRange(Solver& s, LitView range) { + static_cast(s); + static_cast(range); + return range[0]; + } + static Literal selectLiteral(const Solver& s, Var_t v, int signScore) { + auto prefs = s.pref(v); + if (signScore != 0 && not prefs.has(ValueSet::user_value | ValueSet::saved_value | ValueSet::pref_value)) { + return {v, signScore < 0}; + } + if (not prefs.empty()) { + return {v, prefs.sign()}; + } + return s.defaultLit(v); + } }; //! Selects the first free literal w.r.t to the initial variable order. class SelectFirst : public DecisionHeuristic { public: - void updateVar(const Solver&, Var, uint32) {} -protected: - Literal doSelect(Solver& s); + void updateVar(const Solver&, Var_t, uint32_t) override {} + Literal doSelect(Solver& s) override; }; //@} -} -#endif - +} // namespace Clasp diff --git a/clasp/solver_strategies.h b/clasp/solver_strategies.h index c955e17..41e91b4 100644 --- a/clasp/solver_strategies.h +++ b/clasp/solver_strategies.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,24 +21,22 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SOLVER_STRATEGIES_H_INCLUDED -#define CLASP_SOLVER_STRATEGIES_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif #include #include +#include + #if !defined(CLASP_ALIGN_BITFIELD) -# if defined(EMSCRIPTEN) +#if defined(EMSCRIPTEN) // Force alignment of bitfield to T in order to prevent // code-generation bug in emcc // see: https://github.com/kripken/emscripten/issues/4540 -# define CLASP_ALIGN_BITFIELD(T) T : 0; -# else -# define CLASP_ALIGN_BITFIELD(T) -# endif +#define CLASP_ALIGN_BITFIELD(T) T : 0; +#else +#define CLASP_ALIGN_BITFIELD(T) +#endif #endif /*! @@ -66,231 +64,241 @@ namespace Clasp { * */ struct ScheduleStrategy { -public: - //! Supported strategies. - enum Type { Geometric = 0, Arithmetic = 1, Luby = 2 }; - - ScheduleStrategy(Type t = Geometric, uint32 b = 100, double g = 1.5, uint32 o = 0); - //! Creates luby's sequence with unit-length unit and optional outer limit. - static ScheduleStrategy luby(uint32 unit, uint32 limit = 0) { return ScheduleStrategy(Luby, unit, 0, limit); } - //! Creates geometric sequence base * (grow^k) with optional outer limit. - static ScheduleStrategy geom(uint32 base, double grow, uint32 limit = 0) { return ScheduleStrategy(Geometric, base, grow, limit); } - //! Creates arithmetic sequence base + (add*k) with optional outer limit. - static ScheduleStrategy arith(uint32 base, double add, uint32 limit = 0) { return ScheduleStrategy(Arithmetic, base, add, limit); } - //! Creates fixed sequence with length base. - static ScheduleStrategy fixed(uint32 base) { return ScheduleStrategy(Arithmetic, base, 0, 0); } - static ScheduleStrategy none() { return ScheduleStrategy(Geometric, 0); } - static ScheduleStrategy def() { return ScheduleStrategy(Arithmetic, 0); } - uint64 current() const; - bool disabled() const { return base == 0; } - bool defaulted()const { return base == 0 && type == Arithmetic; } - void reset() { idx = 0; } - uint64 next(); - void advanceTo(uint32 idx); - uint32 base : 30; // base of sequence (n1) - uint32 type : 2; // type of basic sequence - uint32 idx; // current index into sequence - uint32 len; // length of sequence (0 if infinite) (once reached, sequence is repeated and len increased) - float grow; // update parameter n2 + //! Supported strategies. + enum Type { sched_geom = 0, sched_arith = 1, sched_luby = 2 }; + + ScheduleStrategy(Type t = sched_geom, uint32_t b = 100, double g = 1.5, uint32_t o = 0); + //! Creates luby's sequence with unit-length unit and optional outer limit. + static ScheduleStrategy luby(uint32_t unit, uint32_t limit = 0) { return {sched_luby, unit, 0, limit}; } + //! Creates geometric sequence base * (grow^k) with optional outer limit. + static ScheduleStrategy geom(uint32_t base, double grow, uint32_t limit = 0) { + return {sched_geom, base, grow, limit}; + } + //! Creates arithmetic sequence base + (add*k) with optional outer limit. + static ScheduleStrategy arith(uint32_t base, double add, uint32_t limit = 0) { + return {sched_arith, base, add, limit}; + } + //! Creates fixed sequence with length base. + static ScheduleStrategy fixed(uint32_t base) { return {sched_arith, base, 0, 0}; } + static ScheduleStrategy none() { return {sched_geom, 0}; } + static ScheduleStrategy def() { return {sched_arith, 0}; } + [[nodiscard]] uint64_t current() const; + [[nodiscard]] bool disabled() const { return base == 0; } + [[nodiscard]] bool defaulted() const { return base == 0 && type == sched_arith; } + void reset() { idx = 0; } + uint64_t next(); + void advanceTo(uint32_t idx); + uint32_t base : 30; // base of sequence (n1) + uint32_t type : 2; // type of basic sequence + uint32_t idx; // current index into sequence + uint32_t len; // length of sequence (0 if infinite) (once reached, sequence is repeated and len increased) + float grow; // update parameter n2 }; -//! Returns the idx'th value of the luby sequence. -uint32 lubyR(uint32 idx); -//! Returns the idx'th value of the geometric sequence with the given growth factor. -double growR(uint32 idx, double g); -//! Returns the idx'th value of the arithmetic sequence with the given addend. -double addR(uint32 idx, double a); +//! Returns the idx-th value of the luby sequence. +uint32_t lubyR(uint32_t idx); +//! Returns the idx-th value of the geometric sequence with the given growth factor. +double growR(uint32_t idx, double g); +//! Returns the idx-th value of the arithmetic sequence with the given addend. +double addR(uint32_t idx, double a); class DecisionHeuristic; //! Parameter-Object for grouping solver strategies. struct SolverStrategies { - //! Clasp's two general search strategies. - enum SearchStrategy { - use_learning = 0, //!< Analyze conflicts and learn First-1-UIP-clause. - no_learning = 1 //!< Don't analyze conflicts - chronological backtracking. - }; - //! Default sign heuristic. - enum SignHeu { - sign_atom = 0, //!< Prefer negative literal for atoms. - sign_pos = 1, //!< Prefer positive literal. - sign_neg = 2, //!< Prefer negative literal. - sign_rnd = 3, //!< Prefer random literal. - }; - //! Conflict clause minimization strategy. - enum CCMinType { - cc_local = 0, //!< Basic algorithm. - cc_recursive = 1, //!< Extended algorithm. - }; - //! Antecedents to consider during conflict clause minimization. - enum CCMinAntes { - all_antes = 0, //!< Consider all antecedents. - short_antes = 1, //!< Consider only short antecedents. - binary_antes = 2, //!< Consider only binary antecedents. - no_antes = 3, //!< Don't minimize conflict clauses. - }; - //! Simplifications for long conflict clauses. - enum CCRepMode { - cc_no_replace = 0,//!< Don't replace literals in conflict clauses. - cc_rep_decision= 1,//!< Replace conflict clause with decision sequence. - cc_rep_uip = 2,//!< Replace conflict clause with all uip clause. - cc_rep_dynamic = 3,//!< Dynamically select between cc_rep_decision and cc_rep_uip. - }; - //! Strategy for initializing watched literals in clauses. - enum WatchInit { watch_rand = 0, watch_first = 1, watch_least = 2 }; - //! Strategy for integrating new information in parallel solving. - enum UpdateMode { update_on_propagate = 0, update_on_conflict = 1 }; - enum LbdMode { lbd_fixed = 0, lbd_updated_less = 1, lbd_update_glucose = 2, lbd_update_pseudo = 3 }; - - SolverStrategies(); - void prepare(); - //----- 32 bit ------------ - uint32 compress : 16; /*!< If > 0, enable compression for learnt clauses of size > compress. */ - uint32 saveProgress : 16; /*!< Enable progress saving if > 0. */ - //----- 32 bit ------------ - uint32 heuId : 3; /*!< Type of decision heuristic. */ - uint32 reverseArcs : 2; /*!< Use "reverse-arcs" during learning if > 0. */ - uint32 otfs : 2; /*!< Enable "on-the-fly" subsumption if > 0. */ - uint32 updateLbd : 2; /*!< Update lbds of antecedents during conflict analysis (one of LbdMode). */ - uint32 ccMinAntes : 2; /*!< Antecedents to look at during conflict clause minimization. */ - uint32 ccRepMode : 2; /*!< One of CCRepMode. */ - uint32 ccMinRec : 1; /*!< If 1, use more expensive recursive nogood minimization. */ - uint32 ccMinKeepAct : 1; /*!< Do not increase nogood activities during nogood minimization? */ - uint32 initWatches : 2; /*!< Initialize watches randomly in clauses. */ - uint32 upMode : 1; /*!< One of UpdateMode. */ - uint32 bumpVarAct : 1; /*!< Bump activities of vars implied by learnt clauses with small lbd. */ - uint32 search : 1; /*!< Current search strategy. */ - uint32 restartOnModel: 1; /*!< Do a restart after each model. */ - uint32 signDef : 2; /*!< Default sign heuristic. */ - uint32 signFix : 1; /*!< Disable all sign heuristics and always use default sign. */ - uint32 reserved : 1; - uint32 hasConfig : 1; /*!< Config applied to solver? */ - uint32 id : 6; /*!< Solver id - SHALL ONLY BE SET BY Shared Context! */ + //! Clasp's two general search strategies. + enum SearchStrategy { + use_learning = 0, //!< Analyze conflicts and learn First-1-UIP-clause. + no_learning = 1 //!< Don't analyze conflicts - chronological backtracking. + }; + //! Default sign heuristic. + enum SignHeu { + sign_atom = 0, //!< Prefer negative literal for atoms. + sign_pos = 1, //!< Prefer positive literal. + sign_neg = 2, //!< Prefer negative literal. + sign_rnd = 3, //!< Prefer random literal. + }; + //! Conflict clause minimization strategy. + enum CCMinType { + cc_local = 0, //!< Basic algorithm. + cc_recursive = 1, //!< Extended algorithm. + }; + //! Antecedents to consider during conflict clause minimization. + enum CCMinAntes { + all_antes = 0, //!< Consider all antecedents. + short_antes = 1, //!< Consider only short antecedents. + binary_antes = 2, //!< Consider only binary antecedents. + no_antes = 3, //!< Don't minimize conflict clauses. + }; + //! Simplifications for long conflict clauses. + enum CCRepMode { + cc_no_replace = 0, //!< Don't replace literals in conflict clauses. + cc_rep_decision = 1, //!< Replace conflict clause with decision sequence. + cc_rep_uip = 2, //!< Replace conflict clause with all uip clause. + cc_rep_dynamic = 3, //!< Dynamically select between cc_rep_decision and cc_rep_uip. + }; + //! Strategy for initializing watched literals in clauses. + enum WatchInit { watch_rand = 0, watch_first = 1, watch_least = 2 }; + //! Strategy for integrating new information in parallel solving. + enum UpdateMode { update_on_propagate = 0, update_on_conflict = 1 }; + enum LbdMode { lbd_fixed = 0, lbd_updated_less = 1, lbd_update_glucose = 2, lbd_update_pseudo = 3 }; + + void prepare(); + //----- 32 bit ------------ + uint32_t compress : 16 = 0; /*!< If > 0, enable compression for learnt clauses of size > compress. */ + uint32_t saveProgress : 16 = 0; /*!< Enable progress saving if > 0. */ + //----- 32 bit ------------ + uint32_t heuId : 3 = 0; /*!< Type of decision heuristic. */ + uint32_t reverseArcs : 2 = 0; /*!< Use "reverse-arcs" during learning if > 0. */ + uint32_t otfs : 2 = 0; /*!< Enable "on-the-fly" subsumption if > 0. */ + uint32_t updateLbd : 2 = 0; /*!< Update lbds of antecedents during conflict analysis (one of LbdMode). */ + uint32_t ccMinAntes : 2 = 0; /*!< Antecedents to look at during conflict clause minimization. */ + uint32_t ccRepMode : 2 = 0; /*!< One of CCRepMode. */ + uint32_t ccMinRec : 1 = 0; /*!< If 1, use more expensive recursive nogood minimization. */ + uint32_t ccMinKeepAct : 1 = 0; /*!< Do not increase nogood activities during nogood minimization? */ + uint32_t initWatches : 2 = 0; /*!< Initialize watches randomly in clauses. */ + uint32_t upMode : 1 = 0; /*!< One of UpdateMode. */ + uint32_t bumpVarAct : 1 = 0; /*!< Bump activities of vars implied by learnt clauses with small lbd. */ + uint32_t search : 1 = 0; /*!< Current search strategy. */ + uint32_t restartOnModel : 1 = 0; /*!< Do a restart after each model. */ + uint32_t resetOnModel : 1 = 0; /*!< Reset solving state like e.g. restarts on model? */ + uint32_t signDef : 2 = 0; /*!< Default sign heuristic. */ + uint32_t signFix : 1 = 0; /*!< Disable all sign heuristics and always use default sign. */ + uint32_t hasConfig : 1 = 0; /*!< Config applied to solver? */ + uint32_t id : 6 = 0; /*!< Solver id - SHALL ONLY BE SET BY Shared Context! */ }; //! Parameter-Object for grouping additional heuristic options. struct HeuParams { - //! Strategy for scoring clauses not learnt by conflict analysis. - enum ScoreOther { other_auto = 0u, other_no = 1u, other_loop = 2u, other_all = 3u }; - //! Strategy for scoring during conflict analysis. - enum Score { score_auto = 0u, score_min = 1u, score_set = 2u, score_multi_set = 3u }; - //! Global preference for domain heuristic. - enum DomPref { pref_atom = 0u, pref_scc = 1u, pref_hcc = 2u, pref_disj = 4u, pref_min = 8u, pref_show = 16u }; - //! Global modification for domain heuristic. - enum DomMod { mod_none = 0u, mod_level = 1u, mod_spos = 2u, mod_true = 3u, mod_sneg = 4u, mod_false = 5u, mod_init = 6u, mod_factor = 7u }; - //! Values for dynamic decaying scheme. - struct VsidsDecay { - uint32 init: 10; /*!< Starting decay factor: 1/0.\. */ - uint32 bump: 7; /*!< Decay decrease value : \/100. */ - uint32 freq: 15; /*!< Update decay factor every \ conflicts. */ - }; - HeuParams(); - uint32 param : 16; /*!< Extra parameter for heuristic with meaning depending on type. */ - uint32 score : 2; /*!< Type of scoring during resolution. */ - uint32 other : 2; /*!< Consider other learnt nogoods in heuristic. */ - uint32 moms : 1; /*!< Use MOMS-score as top-level heuristic. */ - uint32 nant : 1; /*!< Prefer elements in NegAnte(P). */ - uint32 huang : 1; /*!< Only for Berkmin. */ - uint32 acids : 1; /*!< Only for Vsids/Dom. */ - uint32 domPref : 5; /*!< Default pref for domain heuristic (set of DomPref). */ - uint32 domMod : 3; /*!< Default mod for domain heuristic (one of DomMod). */ - union { - uint32 extra; - VsidsDecay decay; /*!< Only for Vsids/Dom. */ - }; + //! Strategy for scoring clauses not learnt by conflict analysis. + enum ScoreOther { other_auto = 0u, other_no = 1u, other_loop = 2u, other_all = 3u }; + //! Strategy for scoring during conflict analysis. + enum Score { score_auto = 0u, score_min = 1u, score_set = 2u, score_multi_set = 3u }; + //! Global preference for domain heuristic. + enum DomPref { pref_atom = 0u, pref_scc = 1u, pref_hcc = 2u, pref_disj = 4u, pref_min = 8u, pref_show = 16u }; + //! Global modification for domain heuristic. + enum DomMod { + mod_none = 0u, + mod_level = 1u, + mod_spos = 2u, + mod_true = 3u, + mod_sneg = 4u, + mod_false = 5u, + mod_init = 6u, + mod_factor = 7u + }; + //! Values for dynamic decaying scheme. + struct VsidsDecay { + uint32_t init : 10; /*!< Starting decay factor: 1/0.\. */ + uint32_t bump : 7; /*!< Decay decrease value : \/100. */ + uint32_t freq : 15; /*!< Update decay factor every \ conflicts. */ + }; + uint32_t param : 16 = 0; /*!< Extra parameter for heuristic with meaning depending on type. */ + uint32_t score : 2 = 0; /*!< Type of scoring during resolution. */ + uint32_t other : 2 = 0; /*!< Consider other learnt nogoods in heuristic. */ + uint32_t moms : 1 = 1; /*!< Use MOMS-score as top-level heuristic. */ + uint32_t nant : 1 = 0; /*!< Prefer elements in NegAnte(P). */ + uint32_t huang : 1 = 0; /*!< Only for Berkmin. */ + uint32_t acids : 1 = 0; /*!< Only for Vsids/Dom. */ + uint32_t domPref : 5 = 0; /*!< Default pref for domain heuristic (set of DomPref). */ + uint32_t domMod : 3 = 0; /*!< Default mod for domain heuristic (one of DomMod). */ + union { + uint32_t extra = 0; + VsidsDecay decay; /*!< Only for Vsids/Dom. */ + }; }; struct OptParams { - //! Strategy to use for optimization. - enum Type { - type_bb = 0, //!< Branch and bound based (model-guided) optimization. - type_usc= 1, //!< Unsatisfiable-core based (core-guided) optimization. - }; - //! Algorithm for model-guided optimization. - enum BBAlgo { - bb_lin = 0u, //!< Linear branch and bound with fixed step of size 1. - bb_hier = 1u, //!< Hierarchical branch and bound with fixed step of size 1. - bb_inc = 2u, //!< Hierarchical branch and bound with increasing steps. - bb_dec = 3u, //!< Hierarchical branch and bound with decreasing steps. - }; - //! Algorithm for core-guided optimization. - enum UscAlgo { - usc_oll = 0u, //!< OLL with possibly multiple cardinality constraints per core. - usc_one = 1u, //!< ONE with one cardinality constraints per core. - usc_k = 2u, //!< K with bounded cardinality constraints of size 2 * (K+1). - usc_pmr = 3u, //!< PMRES with clauses. - }; - //! Additional tactics for core-guided optimization. - enum UscOption { - usc_disjoint = 1u, //!< Enable (disjoint) preprocessing. - usc_succinct = 2u, //!< Do not add redundant constraints. - usc_stratify = 4u, //!< Enable stratification for weighted optimization. - }; - //! Strategy for unsatisfiable-core shrinking. - enum UscTrim { - usc_trim_lin = 1u, //!< Shrinking with linear search SAT->UNSAT. - usc_trim_inv = 2u, //!< Shrinking with inverse linear search UNSAT->SAT. - usc_trim_bin = 3u, //!< Shrinking with binary search SAT->UNSAT. - usc_trim_rgs = 4u, //!< Shrinking with repeated geometric sequence until UNSAT. - usc_trim_exp = 5u, //!< Shrinking with exponential search until UNSAT. - usc_trim_min = 6u, //!< Shrinking with linear search for subset minimal core. - }; - //! Heuristic options common to all optimization strategies. - enum Heuristic { - heu_sign = 1, //!< Use optimize statements in sign heuristic. - heu_model = 2, //!< Apply model heuristic when optimizing. - }; - OptParams(Type st = type_bb); - bool supportsSplitting() const { return type != type_usc; } - bool hasOption(UscOption o) const { return (opts & uint32(o)) != 0u; } - bool hasOption(Heuristic h) const { return (heus & uint32(h)) != 0u; } - uint32 type : 1; /*!< Optimization strategy (see Type).*/ - uint32 heus : 2; /*!< Set of Heuristic values. */ - uint32 algo : 2; /*!< Optimization algorithm (see BBAlgo/UscAlgo). */ - uint32 trim : 3; /*!< Unsatisfiable-core shrinking (0=no shrinking). */ - uint32 opts : 4; /*!< Set of usc options. */ - uint32 tLim : 5; /*!< Limit core shrinking to 2^tLim conflicts (0=no limit). */ - uint32 kLim :15; /*!< Limit for algorithm K (0=dynamic limit). */ + //! Strategy to use for optimization. + enum Type { + type_bb = 0, //!< Branch and bound based (model-guided) optimization. + type_usc = 1, //!< Unsatisfiable-core based (core-guided) optimization. + }; + //! Algorithm for model-guided optimization. + enum BBAlgo { + bb_lin = 0u, //!< Linear branch and bound with fixed step of size 1. + bb_hier = 1u, //!< Hierarchical branch and bound with fixed step of size 1. + bb_inc = 2u, //!< Hierarchical branch and bound with increasing steps. + bb_dec = 3u, //!< Hierarchical branch and bound with decreasing steps. + }; + //! Algorithm for core-guided optimization. + enum UscAlgo { + usc_oll = 0u, //!< OLL with possibly multiple cardinality constraints per core. + usc_one = 1u, //!< ONE with one cardinality constraints per core. + usc_k = 2u, //!< K with bounded cardinality constraints of size 2 * (K+1). + usc_pmr = 3u, //!< PMRES with clauses. + }; + //! Additional tactics for core-guided optimization. + enum UscOption { + usc_disjoint = 1u, //!< Enable (disjoint) preprocessing. + usc_succinct = 2u, //!< Do not add redundant constraints. + usc_stratify = 4u, //!< Enable stratification for weighted optimization. + }; + //! Strategy for unsatisfiable-core shrinking. + enum UscTrim { + usc_trim_lin = 1u, //!< Shrinking with linear search SAT->UNSAT. + usc_trim_inv = 2u, //!< Shrinking with inverse linear search UNSAT->SAT. + usc_trim_bin = 3u, //!< Shrinking with binary search SAT->UNSAT. + usc_trim_rgs = 4u, //!< Shrinking with repeated geometric sequence until UNSAT. + usc_trim_exp = 5u, //!< Shrinking with exponential search until UNSAT. + usc_trim_min = 6u, //!< Shrinking with linear search for subset minimal core. + }; + //! Heuristic options common to all optimization strategies. + enum Heuristic { + heu_sign = 1, //!< Use optimize statements in sign heuristic. + heu_model = 2, //!< Apply model heuristic when optimizing. + }; + constexpr OptParams(Type st = type_bb) : type(st) {} + [[nodiscard]] bool supportsSplitting() const { return type != type_usc; } + [[nodiscard]] bool hasOption(UscOption o) const { return (opts & static_cast(o)) != 0u; } + [[nodiscard]] bool hasOption(Heuristic h) const { return (heus & static_cast(h)) != 0u; } + uint32_t type : 1 = type_bb; /*!< Optimization strategy (see Type).*/ + uint32_t heus : 2 = 0; /*!< Set of Heuristic values. */ + uint32_t algo : 2 = 0; /*!< Optimization algorithm (see BBAlgo/UscAlgo). */ + uint32_t trim : 3 = 0; /*!< Unsatisfiable-core shrinking (0=no shrinking). */ + uint32_t opts : 4 = 0; /*!< Set of usc options. */ + uint32_t tLim : 5 = 0; /*!< Limit core shrinking to 2^tLim conflicts (0=no limit). */ + uint32_t kLim : 15 = 0; /*!< Limit for algorithm K (0=dynamic limit). */ }; //! Parameter-Object for configuring a solver. -struct SolverParams : SolverStrategies { - //! Supported forget options. - enum Forget { forget_heuristic = 1u, forget_signs = 2u, forget_activities = 4u, forget_learnts = 8u }; - SolverParams(); - uint32 prepare(); - inline bool forgetHeuristic() const { return (forgetSet & uint32(forget_heuristic)) != 0; } - inline bool forgetSigns() const { return (forgetSet & uint32(forget_signs)) != 0; } - inline bool forgetActivities()const { return (forgetSet & uint32(forget_activities)) != 0; } - inline bool forgetLearnts() const { return (forgetSet & uint32(forget_learnts)) != 0; } - SolverParams& setId(uint32 sId) { id = sId; return *this; } - HeuParams heuristic; /*!< Parameters for decision heuristic. */ - OptParams opt; /*!< Parameters for optimization. */ - // 64-bit - uint32 seed; /*!< Seed for the random number generator. */ - uint32 lookOps : 16; /*!< Max. number of lookahead operations (0: no limit). */ - uint32 lookType : 2; /*!< Type of lookahead operations. */ - uint32 loopRep : 2; /*!< How to represent loops? */ - uint32 acycFwd : 1; /*!< Disable backward propagation in acyclicity checker. */ - uint32 forgetSet : 4; /*!< What to forget on (incremental step). */ - uint32 reserved : 7; +struct SolverParams : SolverStrategies { + //! Supported forget options. + enum Forget { forget_heuristic = 1u, forget_signs = 2u, forget_activities = 4u, forget_learnts = 8u }; + uint32_t prepare(); + [[nodiscard]] bool forgetHeuristic() const { return Potassco::test_mask(forgetSet, forget_heuristic); } + [[nodiscard]] bool forgetSigns() const { return Potassco::test_mask(forgetSet, forget_signs); } + [[nodiscard]] bool forgetActivities() const { return Potassco::test_mask(forgetSet, forget_activities); } + [[nodiscard]] bool forgetLearnts() const { return Potassco::test_mask(forgetSet, forget_learnts); } + SolverParams& setId(uint32_t sId) { + id = sId; + return *this; + } + HeuParams heuristic; /*!< Parameters for decision heuristic. */ + OptParams opt; /*!< Parameters for optimization. */ + // 64-bit + uint32_t seed = 1; /*!< Seed for the random number generator. */ + uint32_t lookOps : 16 = 0; /*!< Max. number of lookahead operations (0: no limit). */ + uint32_t lookType : 2 = 0; /*!< Type of lookahead operations. */ + uint32_t loopRep : 2 = 0; /*!< How to represent loops? */ + uint32_t acycFwd : 1 = 0; /*!< Disable backward propagation in acyclicity checker. */ + uint32_t forgetSet : 4 = 0; /*!< What to forget on (incremental step). */ + uint32_t reserved : 7 = 0; }; -typedef Range Range32; - struct RestartSchedule : ScheduleStrategy { - typedef MovingAvg::Type AvgType; - enum Keep { keep_never = 0, keep_restart = 1, keep_block = 2, keep_always = 3 }; - RestartSchedule() : ScheduleStrategy() {} - //! Creates dynamic sequence. - static RestartSchedule dynamic(uint32 base, float k, uint32 lim, AvgType fast, Keep keep,AvgType slow, uint32 slowW); - - bool isDynamic() const { return type == 3u; } - // only valid if isDynamic() is true. - float k() const { return grow; } - uint32 lbdLim() const { return len; } - uint32 adjustLim() const { return lbdLim() != UINT32_MAX ? 16000 : UINT32_MAX; } - AvgType fastAvg() const; - AvgType slowAvg() const; - uint32 slowWin() const; - Keep keepAvg() const; + using AvgType = MovingAvg::Type; + enum Keep : uint32_t { keep_never = 0, keep_restart = 1, keep_block = 2, keep_always = 3 }; + //! Creates dynamic sequence. + static RestartSchedule dynamic(uint32_t base, float k, uint32_t lim, AvgType fast, Keep keep, AvgType slow, + uint32_t slowW); + + [[nodiscard]] bool isDynamic() const { return type == 3u; } + // only valid if isDynamic() is true. + [[nodiscard]] float k() const { return grow; } + [[nodiscard]] uint32_t lbdLim() const { return len; } + [[nodiscard]] uint32_t adjustLim() const { return lbdLim() != UINT32_MAX ? 16000 : UINT32_MAX; } + [[nodiscard]] AvgType fastAvg() const; + [[nodiscard]] AvgType slowAvg() const; + [[nodiscard]] uint32_t slowWin() const; + [[nodiscard]] Keep keepAvg() const; }; //! Aggregates restart-parameters to configure restarts during search. @@ -298,31 +306,31 @@ struct RestartSchedule : ScheduleStrategy { * \see ScheduleStrategy */ struct RestartParams { - enum SeqUpdate { seq_continue = 0, seq_repeat = 1, seq_disable = 2 }; - typedef RestartSchedule Schedule; - RestartParams(); - uint32 prepare(bool withLookback); - void disable(); - bool disabled() const { return base() == 0; } - bool local() const { return cntLocal != 0; } - SeqUpdate update() const { return static_cast(upRestart); } - uint32 base() const { return rsSched.base; } - Schedule rsSched; - struct Block { - float scale() const { return static_cast(fscale) / 100.0f; } - uint32 window : 23; /**< Size of moving assignment average for blocking restarts (0: disable). */ - uint32 fscale : 9; /**< Scaling factor for blocking restarts. */ - CLASP_ALIGN_BITFIELD(uint32) - uint32 first : 29; /**< Disable blocking restarts for first conflicts. */ - uint32 avg : 3; /**< Use avg strategy (see MovingAvg::Type) */ - } block; /**< Blocking restarts options. */ - uint32 counterRestart:16; /**< Apply counter implication bump every counterRestart restarts (0: disable). */ - uint32 counterBump :16; /**< Bump factor for counter implication restarts. */ - CLASP_ALIGN_BITFIELD(uint32) - uint32 shuffle :14; /**< Shuffle program after shuffle restarts (0: disable). */ - uint32 shuffleNext :14; /**< Re-Shuffle program every shuffleNext restarts (0: disable). */ - uint32 upRestart : 2; /**< How to update restart sequence after a model was found (one of SeqUpdate). */ - uint32 cntLocal : 1; /**< Count conflicts globally or relative to current branch? */ + enum SeqUpdate { seq_continue = 0, seq_repeat = 1, seq_disable = 2 }; + using Schedule = RestartSchedule; + RestartParams(); + uint32_t prepare(bool withLookback); + void disable(); + [[nodiscard]] bool disabled() const { return base() == 0; } + [[nodiscard]] bool local() const { return cntLocal != 0; } + [[nodiscard]] SeqUpdate update() const { return static_cast(upRestart); } + [[nodiscard]] uint32_t base() const { return rsSched.base; } + Schedule rsSched; + struct Block { + [[nodiscard]] double scale() const { return static_cast(fscale) / 100.0; } + uint32_t window : 23; /**< Size of moving assignment average for blocking restarts (0: disable). */ + uint32_t fscale : 9; /**< Scaling factor for blocking restarts. */ + CLASP_ALIGN_BITFIELD(uint32_t) + uint32_t first : 29; /**< Disable blocking restarts for first conflicts. */ + uint32_t avg : 3; /**< Use avg strategy (see MovingAvg::Type) */ + } block; /**< Blocking restarts options. */ + uint32_t counterRestart : 16; /**< Apply counter implication bump every counterRestart restarts (0: disable). */ + uint32_t counterBump : 16; /**< Bump factor for counter implication restarts. */ + CLASP_ALIGN_BITFIELD(uint32_t) + uint32_t shuffle : 14; /**< Shuffle program after shuffle restarts (0: disable). */ + uint32_t shuffleNext : 14; /**< Re-Shuffle program every shuffleNext restarts (0: disable). */ + uint32_t upRestart : 2; /**< How to update restart sequence after a model was found (one of SeqUpdate). */ + uint32_t cntLocal : 1; /**< Count conflicts globally or relative to current branch? */ }; //! Type for implementing Glucose-style dynamic restarts. @@ -332,62 +340,67 @@ struct RestartParams { * dynamically adjusting the margin ratio K. */ struct DynamicLimit { - typedef RestartSchedule::Keep Keep; - enum Type { lbd_limit, level_limit }; - //! Creates new limit with moving average of the given window size. - DynamicLimit(float k, uint32 window, MovingAvg::Type fast, Keep keep, MovingAvg::Type slow, uint32 slowWin = 0, uint32 adjustLimit = 16000); - - //! Resets adjust strategy and optionally the moving (fast) average. - void resetAdjust(float k, Type type, uint32 lim, bool resetAvg = false); - //! Resets current run - depending on the Keep strategy this also clears the moving average. - void block(); - //! Resets moving and global average. - void reset(); - //! Adds an observation and updates the moving average. Typically called on conflict. - void update(uint32 conflictLevel, uint32 lbd); - //! Notifies this object about a restart. - /*! - * The function checks whether to adjust the active margin ratio and/or - * whether to switch from LBD based to conflict level based restarts. - * - * \param maxLBD Threshold for switching between lbd and conflict level queue. - * \param k Lower bound for margin ratio. - */ - uint32 restart(uint32 maxLBD, float k); - //! Returns the number of updates since last restart. - uint32 runLen() const { return num_; } - //! Returns whether it is time to restart. - bool reached() const { return runLen() >= avg_.win() && (movingAverage() * adjust.rk) > globalAverage(); } - struct { - //! Returns the average restart length, i.e. number of conflicts between restarts. - double avgRestart() const { return ratio(samples, restarts); } - uint32 limit; //!< Number of conflicts before an update is forced. - uint32 restarts;//!< Number of restarts since last update. - uint32 samples; //!< Number of samples since last update. - float rk; //!< LBD/CFL dynamic limit factor (typically < 1.0). - Type type; //!< Dynamic limit based on lbd or conflict level. - } adjust; //!< Data for dynamically adjusting margin ratio (rk). - - double globalAverage() const { return global_.avg(adjust.type); } - double movingAverage() const { return avg_.get(); } + using Keep = RestartSchedule::Keep; + enum Type { lbd_limit, level_limit }; + //! Creates new limit with moving average of the given window size. + DynamicLimit(float k, uint32_t window, MovingAvg::Type fast, Keep keep, MovingAvg::Type slow, uint32_t slowWin = 0, + uint32_t adjustLimit = 16000); + DynamicLimit(const DynamicLimit&) = delete; + DynamicLimit& operator=(const DynamicLimit&) = delete; + + //! Resets adjust strategy and optionally the moving (fast) average. + void resetAdjust(float k, Type type, uint32_t lim, bool resetAvg = false); + //! Resets current run - depending on the Keep strategy this also clears the moving average. + void block(); + //! Resets moving and global average. + void reset(); + //! Adds an observation and updates the moving average. Typically called on conflict. + void update(uint32_t conflictLevel, uint32_t lbd); + //! Notifies this object about a restart. + /*! + * The function checks whether to adjust the active margin ratio and/or + * whether to switch from LBD based to conflict level based restarts. + * + * \param maxLbd Threshold for switching between lbd and conflict level queue. + * \param k Lower bound for margin ratio. + */ + uint32_t restart(uint32_t maxLbd, float k); + //! Returns the number of updates since last restart. + [[nodiscard]] uint32_t runLen() const { return num_; } + //! Returns whether it is time to restart. + [[nodiscard]] bool reached() const { + return runLen() >= avg_.win() && (movingAverage() * adjust.rk) > globalAverage(); + } + struct { + //! Returns the average restart length, i.e. number of conflicts between restarts. + [[nodiscard]] double avgRestart() const { return ratio(samples, restarts); } + + uint32_t limit; //!< Number of conflicts before an update is forced. + uint32_t restarts; //!< Number of restarts since last update. + uint32_t samples; //!< Number of samples since last update. + float rk; //!< LBD/CFL dynamic limit factor (typically < 1.0). + Type type; //!< Dynamic limit based on lbd or conflict level. + } adjust{}; //!< Data for dynamically adjusting margin ratio (rk). + + [[nodiscard]] double globalAverage() const { return global_.avg(adjust.type); } + [[nodiscard]] double movingAverage() const { return avg_.get(); } + private: - DynamicLimit(const DynamicLimit&); - DynamicLimit& operator=(const DynamicLimit&); - void resetRun(Keep k); - struct Global { - explicit Global(MovingAvg::Type type, uint32 size = 0); - //! Returns the global lbd or conflict level average. - double avg(Type t) const { return (t == lbd_limit ? lbd : cfl).get(); } - void reset() { - lbd.clear(); - cfl.clear(); - } - MovingAvg lbd; //!< Moving average of lbds - MovingAvg cfl; //!< Moving average of conflict levels - } global_; //!< Global lbd/conflict level data. - MovingAvg avg_; //!< (Fast) moving average. - uint32 num_; //!< Number of samples in this run. - Keep keep_; //!< Strategy for keeping fast moving average. + void resetRun(Keep k); + struct Global { + explicit Global(MovingAvg::Type type, uint32_t size = 0); + //! Returns the global lbd or conflict level average. + [[nodiscard]] double avg(Type t) const { return (t == lbd_limit ? lbd : cfl).get(); } + void reset() { + lbd.clear(); + cfl.clear(); + } + MovingAvg lbd; //!< Moving average of lbds + MovingAvg cfl; //!< Moving average of conflict levels + } global_; //!< Global lbd/conflict level data. + MovingAvg avg_; //!< (Fast) moving average. + uint32_t num_; //!< Number of samples in this run. + Keep keep_; //!< Strategy for keeping fast moving average. }; //! Type for implementing Glucose-style blocking of restarts. @@ -396,18 +409,18 @@ struct DynamicLimit { * \see A. Biere, A. Froehlich "Evaluating CDCL Restart Schemes" */ struct BlockLimit { - explicit BlockLimit(uint32 windowSize, double R = 1.4, MovingAvg::Type t = MovingAvg::avg_ema); - bool push(uint32 nAssign) { - avg.push(nAssign); - return ++n >= next; - } - //! Returns the exponential moving average scaled by r. - double scaled() const { return avg.get() * r; } - MovingAvg avg; //!< Moving average. - uint64 next; //!< Enable once n >= next. - uint64 n; //!< Number of data points seen so far. - uint32 inc; //!< Block restart for next inc conflicts. - float r; //!< Scale factor for moving average. + explicit BlockLimit(uint32_t windowSize, double rf = 1.4, MovingAvg::Type t = MovingAvg::Type::avg_ema); + bool push(uint32_t nAssign) { + avg.push(nAssign); + return ++n >= next; + } + //! Returns the exponential moving average scaled by r. + [[nodiscard]] double scaled() const { return avg.get() * r; } + MovingAvg avg; //!< Moving average. + uint64_t next; //!< Enable once n >= next. + uint64_t n; //!< Number of data points seen so far. + uint32_t inc; //!< Block restart for next inc conflicts. + float r; //!< Scale factor for moving average. }; //! Reduce strategy used during solving. @@ -416,51 +429,58 @@ struct BlockLimit { * for measuring "activity" of learnt constraints. */ struct ReduceStrategy { - //! Reduction algorithm to use during solving. - enum Algorithm { - reduce_linear = 0, //!< Linear algorithm from clasp-1.3.x. - reduce_stable = 1, //!< Sort constraints by score but keep order in learnt db. - reduce_sort = 2, //!< Sort learnt db by score and remove fraction with lowest score. - reduce_heap = 3 //!< Similar to reduce_sort but only partially sorts learnt db. - }; - //! Score to measure "activity" of learnt constraints. - enum Score { - score_act = 0, //!< Activity only: how often constraint is used during conflict analysis. - score_lbd = 1, //!< Use literal block distance as activity. - score_both = 2 //!< Use activity and lbd together. - }; - //! Strategy for estimating size of problem. - enum EstimateSize { - est_dynamic = 0, //!< Dynamically decide whether to use number of variables or constraints. - est_con_complexity = 1, //!< Measure size in terms of constraint complexities. - est_num_constraints = 2, //!< Measure size in terms of number constraints. - est_num_vars = 3 //!< Measure size in terms of number variable. - }; - static uint32 scoreAct(const ConstraintScore& sc) { return sc.activity(); } - static uint32 scoreLbd(const ConstraintScore& sc) { return uint32(LBD_MAX+1)-sc.lbd(); } - static uint32 scoreBoth(const ConstraintScore& sc) { return (sc.activity()+1) * scoreLbd(sc); } - static int compare(Score sc, const ConstraintScore& lhs, const ConstraintScore& rhs) { - int fs = 0; - if (sc == score_act) { fs = ((int)scoreAct(lhs)) - ((int)scoreAct(rhs)); } - else if (sc == score_lbd) { fs = ((int)scoreLbd(lhs)) - ((int)scoreLbd(rhs)); } - return fs != 0 ? fs : ((int)scoreBoth(lhs)) - ((int)scoreBoth(rhs)); - } - static uint32 asScore(Score sc, const Clasp::ConstraintScore& act) { - if (sc == score_act) { return scoreAct(act); } - if (sc == score_lbd) { return scoreLbd(act); } - /* sc == score_both*/{ return scoreBoth(act);} - } - ReduceStrategy() : protect(0), glue(0), fReduce(75), fRestart(0), score(0), algo(0), estimate(0), noGlue(0) { - static_assert(sizeof(ReduceStrategy) == sizeof(uint32), "invalid bitset"); - } - uint32 protect : 7; /*!< Protect nogoods whose lbd was reduced and is now <= freeze. */ - uint32 glue : 4; /*!< Don't remove nogoods with lbd <= glue. */ - uint32 fReduce : 7; /*!< Fraction of nogoods to remove in percent. */ - uint32 fRestart: 7; /*!< Fraction of nogoods to remove on restart. */ - uint32 score : 2; /*!< One of Score. */ - uint32 algo : 2; /*!< One of Algorithm. */ - uint32 estimate: 2; /*!< How to estimate problem size in init. */ - uint32 noGlue : 1; /*!< Do not count glue clauses in limit. */ + //! Reduction algorithm to use during solving. + enum Algorithm { + reduce_linear = 0, //!< Linear algorithm from clasp-1.3.x. + reduce_stable = 1, //!< Sort constraints by score but keep order in learnt db. + reduce_sort = 2, //!< Sort learnt db by score and remove fraction with the lowest score. + reduce_heap = 3 //!< Similar to reduce_sort but only partially sorts learnt db. + }; + //! Score to measure "activity" of learnt constraints. + enum Score { + score_act = 0, //!< Activity only: how often constraint is used during conflict analysis. + score_lbd = 1, //!< Use literal block distance as activity. + score_both = 2 //!< Use activity and lbd together. + }; + //! Strategy for estimating size of problem. + enum EstimateSize { + est_dynamic = 0, //!< Dynamically decide whether to use number of variables or constraints. + est_con_complexity = 1, //!< Measure size in terms of constraint complexities. + est_num_constraints = 2, //!< Measure size in terms of number constraints. + est_num_vars = 3 //!< Measure size in terms of number variable. + }; + static uint32_t scoreAct(const ConstraintScore& sc) { return sc.activity(); } + static uint32_t scoreLbd(const ConstraintScore& sc) { return (lbd_max + 1) - sc.lbd(); } + static uint32_t scoreBoth(const ConstraintScore& sc) { return (sc.activity() + 1) * scoreLbd(sc); } + static int compare(Score sc, const ConstraintScore& lhs, const ConstraintScore& rhs) { + int fs = 0; + if (sc == score_act) { + fs = static_cast(scoreAct(lhs)) - static_cast(scoreAct(rhs)); + } + else if (sc == score_lbd) { + fs = static_cast(scoreLbd(lhs)) - static_cast(scoreLbd(rhs)); + } + return fs != 0 ? fs : static_cast(scoreBoth(lhs)) - static_cast(scoreBoth(rhs)); + } + static uint32_t asScore(Score sc, const ConstraintScore& act) { + if (sc == score_act) { + return scoreAct(act); + } + if (sc == score_lbd) { + return scoreLbd(act); + } + /* sc == score_both*/ { return scoreBoth(act); } + } + constexpr ReduceStrategy() = default; + + uint32_t protect : 7 = 0; /*!< Protect nogoods whose lbd was reduced and is now <= freeze. */ + uint32_t glue : 4 = 0; /*!< Don't remove nogoods with lbd <= glue. */ + uint32_t fReduce : 7 = 75; /*!< Fraction of nogoods to remove in percent. */ + uint32_t fRestart : 7 = 0; /*!< Fraction of nogoods to remove on restart. */ + uint32_t score : 2 = 0; /*!< One of Score. */ + uint32_t algo : 2 = 0; /*!< One of Algorithm. */ + uint32_t estimate : 2 = 0; /*!< How to estimate problem size in init. */ + uint32_t noGlue : 1 = 0; /*!< Do not count glue clauses in limit. */ }; //! Aggregates parameters for the nogood deletion heuristic used during search. @@ -474,30 +494,28 @@ struct ReduceStrategy { * . */ struct ReduceParams { - ReduceParams() : cflSched(ScheduleStrategy::none()), growSched(ScheduleStrategy::def()) - , fInit(1.0f/3.0f) - , fMax(3.0f) - , fGrow(1.1f) - , initRange(10, UINT32_MAX) - , maxRange(UINT32_MAX) - , memMax(0) {} - void disable(); - uint32 prepare(bool withLookback); - Range32 sizeInit(const SharedContext& ctx) const; - uint32 cflInit(const SharedContext& ctx) const; - uint32 getBase(const SharedContext& ctx) const; - float fReduce() const { return static_cast(strategy.fReduce) / 100.0f; } - float fRestart() const { return static_cast(strategy.fRestart)/ 100.0f; } - static uint32 getLimit(uint32 base, double f, const Range& r); - ScheduleStrategy cflSched; /**< Conflict-based deletion schedule. */ - ScheduleStrategy growSched; /**< Growth-based deletion schedule. */ - ReduceStrategy strategy; /**< Strategy to apply during nogood deletion. */ - float fInit; /**< Initial limit. X = P*fInit clamped to initRange.*/ - float fMax; /**< Maximal limit. X = P*fMax clamped to maxRange. */ - float fGrow; /**< Growth factor for db. */ - Range32 initRange; /**< Allowed range for initial limit. */ - uint32 maxRange; /**< Allowed range for maximal limit: [initRange.lo,maxRange]*/ - uint32 memMax; /**< Memory limit in MB (0 = no limit). */ + ReduceParams() + : cflSched(ScheduleStrategy::none()) + , growSched(ScheduleStrategy::def()) + , fInit(1.0f / 3.0f) + , initRange(10, UINT32_MAX) {} + void disable(); + uint32_t prepare(bool withLookback); + [[nodiscard]] Range32 sizeInit(const SharedContext& ctx) const; + [[nodiscard]] uint32_t cflInit(const SharedContext& ctx) const; + [[nodiscard]] uint32_t getBase(const SharedContext& ctx) const; + [[nodiscard]] float fReduce() const { return static_cast(strategy.fReduce) / 100.0f; } + [[nodiscard]] float fRestart() const { return static_cast(strategy.fRestart) / 100.0f; } + static uint32_t getLimit(uint32_t base, double f, const Range32& r); + ScheduleStrategy cflSched; /**< Conflict-based deletion schedule. */ + ScheduleStrategy growSched; /**< Growth-based deletion schedule. */ + ReduceStrategy strategy; /**< Strategy to apply during nogood deletion. */ + float fInit; /**< Initial limit. X = P*fInit clamped to initRange.*/ + float fMax{3.0f}; /**< Maximal limit. X = P*fMax clamped to maxRange. */ + float fGrow{1.1f}; /**< Growth factor for db. */ + Range32 initRange; /**< Allowed range for initial limit. */ + uint32_t maxRange{UINT32_MAX}; /**< Allowed range for maximal limit: [initRange.lo,maxRange]*/ + uint32_t memMax{0}; /**< Memory limit in MB (0 = no limit). */ }; //! Parameter-Object for grouping solve-related options. @@ -505,29 +523,28 @@ struct ReduceParams { * \ingroup enumerator */ struct SolveParams { - //! Creates a default-initialized object. - /*! - * The following parameters are used: - * - restart : quadratic: 100*1.5^k / no restarts after first solution - * - deletion : initial size: vars()/3, grow factor: 1.1, max factor: 3.0, do not reduce on restart - * - randomization: disabled - * - randomProp : 0.0 (disabled) - * . - */ - SolveParams(); - uint32 prepare(bool withLookback); - bool randomize(Solver& s) const; - RestartParams restart; - ReduceParams reduce; - uint32 randRuns:16; /*!< Number of initial randomized-runs. */ - uint32 randConf:16; /*!< Number of conflicts comprising one randomized-run. */ - float randProb; /*!< Use random heuristic with given probability ([0,1]) */ - struct FwdCheck { /*!< Options for (partial checks in) DLP-solving; */ - uint32 highStep : 24; /*!< Init/inc high level when reached. */ - uint32 highPct : 7; /*!< Check on low + (high - low) * highPct/100 */ - uint32 signDef : 2; /*!< Default sign heuristic for atoms in disjunctions. */ - FwdCheck() { std::memset(this, 0, sizeof(*this)); } - } fwdCheck; + //! Creates a default-initialized object. + /*! + * The following parameters are used: + * - restart : quadratic: 100*1.5^k / no restarts after first solution + * - deletion : initial size: vars()/3, grow factor: 1.1, max factor: 3.0, do not reduce on restart + * - randomization: disabled + * - randomProp : 0.0 (disabled) + * . + */ + SolveParams(); + uint32_t prepare(bool withLookback); + bool randomize(Solver& s) const; + RestartParams restart; + ReduceParams reduce; + uint32_t randRuns : 16; /*!< Number of initial randomized-runs. */ + uint32_t randConf : 16; /*!< Number of conflicts comprising one randomized-run. */ + float randProb; /*!< Use random heuristic with given probability ([0,1]) */ + struct FwdCheck { /*!< Options for (partial checks in) DLP-solving; */ + uint32_t highStep : 24 = 0; /*!< Init/inc high level when reached. */ + uint32_t highPct : 7 = 0; /*!< Check on low + (high - low) * highPct/100 */ + uint32_t signDef : 2 = 0; /*!< Default sign heuristic for atoms in disjunctions. */ + } fwdCheck; }; class SharedContext; @@ -535,187 +552,200 @@ class SatPreprocessor; //! Parameters for (optional) Sat-preprocessing. struct SatPreParams { - enum Algo { - sat_pre_no = 0, /**< Disable sat-preprocessing. */ - sat_pre_ve = 1, /**< Run variable elimination. */ - sat_pre_ve_bce = 2, /**< Run variable- and limited blocked clause elimination. */ - sat_pre_full = 3, /**< Run variable- and full blocked clause elimination. */ - }; - SatPreParams() : type(0u), limIters(0u), limTime(0u), limFrozen(0u), limClause(4000u), limOcc(0u) {} - uint32 type : 2; /**< One of algo. */ - uint32 limIters : 11; /**< Max. number of iterations. (0=no limit)*/ - uint32 limTime : 12; /**< Max. runtime in sec, checked after each iteration. (0=no limit)*/ - uint32 limFrozen: 7; /**< Run only if percent of frozen vars < maxFrozen. (0=no limit)*/ - uint32 limClause: 16; /**< Run only if \#clauses \< (limClause*1000) (0=no limit)*/ - uint32 limOcc : 16; /**< Skip v, if \#occ(v) \>= limOcc && \#occ(~v) \>= limOcc.(0=no limit) */ - bool clauseLimit(uint32 nc) const { return limClause && nc > (limClause*1000u); } - bool occLimit(uint32 pos, uint32 neg) const { return limOcc && pos > (limOcc-1u) && neg > (limOcc-1u); } - uint32 bce() const { return type != sat_pre_no ? type - 1 : 0; } - void disableBce() { type = std::min(type, uint32(sat_pre_ve));} - static SatPreprocessor* create(const SatPreParams&); + enum Algo { + sat_pre_no = 0, /**< Disable sat-preprocessing. */ + sat_pre_ve = 1, /**< Run variable elimination. */ + sat_pre_ve_bce = 2, /**< Run variable- and limited blocked clause elimination. */ + sat_pre_full = 3, /**< Run variable- and full blocked clause elimination. */ + }; + static SatPreprocessor* create(const SatPreParams&); + [[nodiscard]] constexpr bool clauseLimit(uint32_t nc) const { return limClause && nc > (limClause * 1000u); } + [[nodiscard]] constexpr bool occLimit(uint32_t pos, uint32_t neg) const { + return limOcc && pos > (limOcc - 1u) && neg > (limOcc - 1u); + } + [[nodiscard]] constexpr uint32_t bce() const { return type != sat_pre_no ? type - 1 : 0; } + constexpr void disableBce() { type = std::min(type, static_cast(sat_pre_ve)); } + + uint32_t type : 2 = 0u; /**< One of algo. */ + uint32_t limIters : 11 = 0u; /**< Max. number of iterations. (0=no limit)*/ + uint32_t limTime : 12 = 0u; /**< Max. runtime in sec, checked after each iteration. (0=no limit)*/ + uint32_t limFrozen : 7 = 0u; /**< Run only if percent of frozen vars < maxFrozen. (0=no limit)*/ + uint32_t limClause : 16 = 4000u; /**< Run only if \#clauses \< (limClause*1000) (0=no limit)*/ + uint32_t limOcc : 16 = 0u; /**< Skip v, if \#occ(v) \>= limOcc && \#occ(~v) \>= limOcc.(0=no limit) */ }; //! Parameters for a SharedContext object. struct ContextParams { - //! How to handle short learnt clauses. - enum ShortMode { - short_implicit = 0, /*!< Share short learnt clauses via short implication graph. */ - short_explicit = 1, /*!< Do not use short implication graph. */ - }; - //! How to handle physical sharing of (explicit) constraints. - enum ShareMode { - share_no = 0, /*!< Do not physically share constraints (use copies instead). */ - share_problem = 1, /*!< Share problem constraints but copy learnt constraints. */ - share_learnt = 2, /*!< Copy problem constraints but share learnt constraints. */ - share_all = 3, /*!< Share all constraints. */ - share_auto = 4, /*!< Use share_no or share_all depending on number of solvers. */ - }; - ContextParams() : shareMode(share_auto), stats(0), shortMode(short_implicit), seed(1), hasConfig(0), cliConfig(0) {} - SatPreParams satPre; /*!< Preprocessing options. */ - uint8 shareMode : 3; /*!< Physical sharing mode (one of ShareMode). */ - uint8 stats : 2; /*!< See SharedContext::enableStats(). */ - uint8 shortMode : 1; /*!< One of ShortMode. */ - uint8 seed : 1; /*!< Apply new seed when adding solvers. */ - uint8 hasConfig : 1; /*!< Reserved for command-line interface. */ - uint8 cliConfig; /*!< Reserved for command-line interface. */ + //! How to handle short learnt clauses. + enum ShortMode { + short_implicit = 0, /*!< Share short learnt clauses via short implication graph. */ + short_explicit = 1, /*!< Do not use short implication graph. */ + }; + //! How to simplify short (learnt) clauses. + enum ShortSimpMode { + simp_no = 0, /*!< No additional simplifications. */ + simp_learnt = 1, /*!< Drop duplicate learnt short clauses. */ + }; + //! How to handle physical sharing of (explicit) constraints. + enum ShareMode { + share_no = 0, /*!< Do not physically share constraints (use copies instead). */ + share_problem = 1, /*!< Share problem constraints but copy learnt constraints. */ + share_learnt = 2, /*!< Copy problem constraints but share learnt constraints. */ + share_all = 3, /*!< Share all constraints. */ + share_auto = 4, /*!< Use share_no or share_all depending on number of solvers. */ + }; + SatPreParams satPre; /*!< Preprocessing options. */ + uint8_t shareMode : 3 = share_auto; /*!< Physical sharing mode (one of ShareMode). */ + uint8_t shortMode : 1 = short_implicit; /*!< One of ShortMode. */ + uint8_t shortSimp : 2 = 0; /*!< One of ShortSimpMode. */ + uint8_t seed : 1 = 1; /*!< Apply new seed when adding solvers. */ + uint8_t hasConfig : 1 = 0; /*!< Reserved for command-line interface. */ + uint8_t cliConfig = 0; /*!< Reserved for command-line interface. */ + uint8_t stats = 0; /*!< See SharedContext::enableStats(). */ }; //! Interface for configuring a SharedContext object and its associated solvers. class Configuration { public: - typedef SolverParams SolverOpts; - typedef SolveParams SearchOpts; - typedef ContextParams CtxOpts; - virtual ~Configuration(); - //! Prepares this configuration for the usage in the given context. - virtual void prepare(SharedContext&) = 0; - //! Returns the options for the shared context. - virtual const CtxOpts& context() const = 0; - //! Returns the number of solver options in this config. - virtual uint32 numSolver() const = 0; - //! Returns the number of search options in this config. - virtual uint32 numSearch() const = 0; - //! Returns the solver options for the i'th solver to be attached to the SharedContext. - virtual const SolverOpts& solver(uint32 i) const = 0; - //! Returns the search options for the i'th solver of the SharedContext. - virtual const SearchOpts& search(uint32 i) const = 0; - //! Returns the heuristic to be used in the i'th solver. - /*! - * The function is called in Solver::startInit(). - * \note The returned object is owned by the caller. - */ - virtual DecisionHeuristic* heuristic(uint32 i) const = 0; - //! Adds post propagators to the given solver. - virtual bool addPost(Solver& s) const = 0; - //! Returns the configuration with the given name or 0 if no such config exists. - /*! - * The default implementation returns this - * if n is empty or one of "." or "/". - * Otherwise, 0 is returned. - */ - virtual Configuration* config(const char* n); + using SolverOpts = SolverParams; + using SearchOpts = SolveParams; + using CtxOpts = ContextParams; + virtual ~Configuration(); + //! Prepares this configuration for the usage in the given context. + virtual void prepare(SharedContext&) = 0; + //! Returns the options for the shared context. + [[nodiscard]] virtual const CtxOpts& context() const = 0; + //! Returns the number of solver options in this config. + [[nodiscard]] virtual uint32_t numSolver() const = 0; + //! Returns the number of search options in this config. + [[nodiscard]] virtual uint32_t numSearch() const = 0; + //! Returns the solver options for the i-th solver to be attached to the SharedContext. + [[nodiscard]] virtual const SolverOpts& solver(uint32_t i) const = 0; + //! Returns the search options for the i-th solver of the SharedContext. + [[nodiscard]] virtual const SearchOpts& search(uint32_t i) const = 0; + //! Returns the heuristic to be used in the i-th solver. + /*! + * The function is called in Solver::startInit(). + * \note The returned object is owned by the caller. + */ + [[nodiscard]] virtual DecisionHeuristic* heuristic(uint32_t i) const = 0; + //! Adds post propagators to the given solver. + virtual bool addPost(Solver& s) const = 0; + //! Returns the configuration with the given name or 0 if no such config exists. + /*! + * The default implementation returns this + * if n is empty or one of "." or "/". + * Otherwise, 0 is returned. + */ + virtual Configuration* config(const char* n); }; //! Base class for user-provided configurations. class UserConfiguration : public Configuration { public: - //! Adds a lookahead post propagator to the given solver if requested. - /*! - * The function adds a lookahead post propagator if indicated by - * the solver's SolverParams. - */ - virtual bool addPost(Solver& s) const; - //! Returns the (modifiable) solver options for the i'th solver. - virtual SolverOpts& addSolver(uint32 i) = 0; - //! Returns the (modifiable) search options for the i'th solver. - virtual SearchOpts& addSearch(uint32 i) = 0; + //! Adds a lookahead post propagator to the given solver if requested. + /*! + * The function adds a lookahead post propagator if indicated by + * the solver's SolverParams. + */ + bool addPost(Solver& s) const override; + //! Returns the (modifiable) solver options for the i-th solver. + virtual SolverOpts& addSolver(uint32_t i) = 0; + //! Returns the (modifiable) search options for the i-th solver. + virtual SearchOpts& addSearch(uint32_t i) = 0; }; -//! Simple factory for decision heuristics. -struct Heuristic_t { - enum Type { Default = 0, Berkmin = 1, Vsids = 2, Vmtf = 3, Domain = 4, Unit = 5, None = 6, User = 7 }; - static inline bool isLookback(uint32 type) { return type >= (uint32)Berkmin && type < (uint32)Unit; } - //! Default callback for creating decision heuristics. - static DecisionHeuristic* create(Type t, const HeuParams& p); +//! Supported decision heuristics. +enum class HeuristicType : uint32_t { + def = 0, + berkmin = 1, + vsids = 2, + vmtf = 3, + domain = 4, + unit = 5, + none = 6, + user = 7 }; +POTASSCO_SET_DEFAULT_ENUM_MAX(HeuristicType::user); +POTASSCO_ENABLE_CMP_OPS(HeuristicType); -struct ProjectMode_t { - enum Mode { Implicit = 0u, Output = 1u, Explicit = 2u }; -}; -typedef ProjectMode_t::Mode ProjectMode; +constexpr bool isLookbackHeuristic(HeuristicType type) { + return type >= HeuristicType::berkmin && type < HeuristicType::unit; +} +constexpr bool isLookbackHeuristic(uint32_t type) { return isLookbackHeuristic(static_cast(type)); } +//! Default factory for decision heuristics. +DecisionHeuristic* createHeuristic(HeuristicType type, const HeuParams& p); + +enum class ProjectMode { implicit = 0u, output = 1u, project = 2u }; //! Basic configuration for one or more SAT solvers. -class BasicSatConfig : public UserConfiguration, public ContextParams { +class BasicSatConfig + : public UserConfiguration + , public ContextParams { public: - struct HeuristicCreator { - virtual ~HeuristicCreator(); - virtual DecisionHeuristic* create(Heuristic_t::Type t, const HeuParams& p) = 0; - }; - - BasicSatConfig(); - void prepare(SharedContext&); - const CtxOpts& context() const { return *this; } - uint32 numSolver() const { return sizeVec(solver_); } - uint32 numSearch() const { return sizeVec(search_); } - const SolverOpts& solver(uint32 i) const { return solver_[i % solver_.size() ]; } - const SearchOpts& search(uint32 i) const { return search_[i % search_.size() ]; } - DecisionHeuristic* heuristic(uint32 i) const; - SolverOpts& addSolver(uint32 i); - SearchOpts& addSearch(uint32 i); - - virtual void reset(); - virtual void resize(uint32 numSolver, uint32 numSearch); - void setHeuristicCreator(HeuristicCreator* hc, Ownership_t::Type = Ownership_t::Acquire); + using HeuristicCreator = std::function; + + BasicSatConfig(); + void prepare(SharedContext&) override; + [[nodiscard]] const CtxOpts& context() const override { return *this; } + [[nodiscard]] uint32_t numSolver() const override { return size32(solver_); } + [[nodiscard]] uint32_t numSearch() const override { return size32(search_); } + [[nodiscard]] const SolverOpts& solver(uint32_t i) const override { return solver_[i % solver_.size()]; } + [[nodiscard]] const SearchOpts& search(uint32_t i) const override { return search_[i % search_.size()]; } + [[nodiscard]] DecisionHeuristic* heuristic(uint32_t i) const override; + SolverOpts& addSolver(uint32_t i) override; + SearchOpts& addSearch(uint32_t i) override; + + virtual void reset(); + virtual void resize(uint32_t numSolver, uint32_t numSearch); + void setHeuristicCreator(HeuristicCreator hc); + private: - typedef PodVector::type SolverVec; - typedef PodVector::type SearchVec; - typedef SingleOwnerPtr HeuFactory; - SolverVec solver_; - SearchVec search_; - HeuFactory heu_; + using SolverVec = PodVector_t; + using SearchVec = PodVector_t; + SolverVec solver_; + SearchVec search_; + HeuristicCreator heu_; }; /////////////////////////////////////////////////////////////////////////////// // SearchLimits /////////////////////////////////////////////////////////////////////////////// //! Parameter-Object for managing search limits. struct SearchLimits { - typedef DynamicLimit* LimitPtr; - typedef BlockLimit* BlockPtr; - SearchLimits(); - uint64 used; - struct { - uint64 conflicts; //!< Soft limit on number of conflicts for restart. - LimitPtr dynamic; //!< Use dynamic restarts based on lbd or conflict level. - BlockPtr block; //!< Optional strategy to increase restart limit. - bool local; //!< Apply conflict limit against active branch. - } restart; //!< Restart limits. - uint64 conflicts; //!< Soft limit on number of conflicts. - uint64 memory; //!< Soft memory limit for learnt lemmas (in bytes). - uint32 learnts; //!< Limit on number of learnt lemmas. + using LimitPtr = DynamicLimit*; + using BlockPtr = BlockLimit*; + uint64_t used = 0; + struct { + uint64_t conflicts = UINT64_MAX; //!< Soft limit on number of conflicts for restart. + LimitPtr dynamic = nullptr; //!< Use dynamic restarts based on lbd or conflict level. + BlockPtr block = nullptr; //!< Optional strategy to increase restart limit. + bool local = false; //!< Apply conflict limit against active branch. + } restart; //!< Restart limits. + uint64_t conflicts = UINT64_MAX; //!< Soft limit on number of conflicts. + uint64_t memory = UINT64_MAX; //!< Soft memory limit for learnt lemmas (in bytes). + uint32_t learnts = UINT32_MAX; //!< Limit on number of learnt lemmas. }; //! Base class for solving related events. -template -struct SolveEvent : Event_t { - SolveEvent(const Solver& s, Event::Verbosity verb) : Event_t(Event::subsystem_solve, verb), solver(&s) {} - const Solver* solver; +struct SolveEvent : Event { + template + SolveEvent(DerivedT* self, const Solver& s, Verbosity v) : Event(self, subsystem_solve, v) + , solver(&s) {} + const Solver* solver; }; struct Model; //! Base class for handling results of a solve operation. class ModelHandler { public: - virtual ~ModelHandler(); - virtual bool onModel(const Solver&, const Model&) = 0; - virtual bool onUnsat(const Solver&, const Model&); + virtual ~ModelHandler(); + virtual bool onModel(const Solver&, const Model&) = 0; + virtual bool onUnsat(const Solver&, const Model&); }; //! Type for storing the lower bound of a minimize statement. struct LowerBound { - LowerBound() : level(0), bound(CLASP_WEIGHT_SUM_MIN) {} - void reset() { *this = LowerBound(); } - bool active() const { return bound != CLASP_WEIGHT_SUM_MIN; } - uint32 level; - wsum_t bound; + [[nodiscard]] constexpr bool active() const { return bound != weight_sum_min; } + uint32_t level = 0; + Wsum_t bound = weight_sum_min; }; -} -#endif +} // namespace Clasp diff --git a/clasp/solver_types.h b/clasp/solver_types.h index 010aca5..c310ce0 100644 --- a/clasp/solver_types.h +++ b/clasp/solver_types.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,17 +21,13 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SOLVER_TYPES_H_INCLUDED -#define CLASP_SOLVER_TYPES_H_INCLUDED -#ifdef _MSC_VER #pragma once -#endif -#include #include +#include #include #include -#include + #include /*! * \file @@ -54,11 +50,11 @@ class StatsMap; #endif #define CLASP_STAT_DEFINE(m, k, a, accu) m #define NO_ARG -#define CLASP_DECLARE_ISTATS(T) \ - void accu(const T& o);\ - static uint32 size();\ - static const char* key(uint32 i);\ - StatisticObject at(const char* key) const +#define CLASP_DECLARE_ISTATS(T) \ + void accu(const T& o); \ + static uint32_t size(); \ + static const char* key(uint32_t i); \ + StatisticObject at(const char* key) const //! A struct for holding core statistics used by a solver. /*! @@ -66,171 +62,252 @@ class StatsMap; * can be used by heuristics. */ struct CoreStats { -#define CLASP_CORE_STATS(STAT, LHS, RHS) \ - STAT(uint64 choices; DOXY(number of choices) , "choices" , VALUE(choices) , LHS.choices += RHS.choices )\ - STAT(uint64 conflicts; DOXY(number of conflicts) , "conflicts" , VALUE(conflicts) , LHS.conflicts += RHS.conflicts )\ - STAT(uint64 analyzed; DOXY(number of conflicts analyzed), "conflicts_analyzed", VALUE(analyzed) , LHS.analyzed += RHS.analyzed )\ - STAT(uint64 restarts; DOXY(number of restarts) , "restarts" , VALUE(restarts) , LHS.restarts += RHS.restarts )\ - STAT(uint64 lastRestart;DOXY(length of last restart) , "restarts_last" , VALUE(lastRestart), LHS.lastRestart = std::max(LHS.lastRestart, RHS.lastRestart)) \ - STAT(uint64 blRestarts; DOXY(number of blocked restarts), "restarts_blocked", VALUE(blRestarts), LHS.blRestarts = std::max(LHS.blRestarts, RHS.blRestarts)) - - CoreStats() { reset(); } - void reset(); - uint64 backtracks() const { return conflicts-analyzed; } - uint64 backjumps() const { return analyzed; } - double avgRestart() const { return ratio(analyzed, restarts); } - CLASP_DECLARE_ISTATS(CoreStats); - CLASP_CORE_STATS(CLASP_STAT_DEFINE, NO_ARG, NO_ARG) +#define CLASP_CORE_STATS(STAT, LHS, RHS) \ + STAT(uint64_t choices{}; DOXY(number of choices), "choices", VALUE(choices), (LHS).choices += (RHS).choices) \ + STAT(uint64_t conflicts{}; \ + DOXY(number of conflicts), "conflicts", VALUE(conflicts), (LHS).conflicts += (RHS).conflicts) \ + STAT(uint64_t analyzed{}; \ + DOXY(number of conflicts analyzed), "conflicts_analyzed", VALUE(analyzed), (LHS).analyzed += (RHS).analyzed) \ + STAT(uint64_t restarts{}; DOXY(number of restarts), "restarts", VALUE(restarts), (LHS).restarts += (RHS).restarts) \ + STAT(uint64_t lastRestart{}; DOXY(length of last restart), "restarts_last", VALUE(lastRestart), \ + (LHS).lastRestart = std::max((LHS).lastRestart, (RHS).lastRestart)) \ + STAT(uint64_t blRestarts{}; DOXY(number of blocked restarts), "restarts_blocked", VALUE(blRestarts), \ + (LHS).blRestarts = std::max((LHS).blRestarts, (RHS).blRestarts)) + + constexpr CoreStats() = default; + [[nodiscard]] constexpr uint64_t backtracks() const { return conflicts - analyzed; } + [[nodiscard]] constexpr uint64_t backjumps() const { return analyzed; } + [[nodiscard]] constexpr double avgRestart() const { return ratio(analyzed, restarts); } + CLASP_DECLARE_ISTATS(CoreStats); + CLASP_CORE_STATS(CLASP_STAT_DEFINE, NO_ARG, NO_ARG) }; //! A struct for holding (optional) jump statistics. struct JumpStats { -#define CLASP_JUMP_STATS(STAT, LHS, RHS) \ - STAT(uint64 jumps; DOXY(number of backjumps) , "jumps" , VALUE(jumps) , LHS.jumps += RHS.jumps) \ - STAT(uint64 bounded; DOXY(backjumps bounded by root level) , "jumps_bounded" , VALUE(bounded) , LHS.bounded += RHS.bounded) \ - STAT(uint64 jumpSum; DOXY(levels removed by jumps) , "levels" , VALUE(jumpSum) , LHS.jumpSum += RHS.jumpSum) \ - STAT(uint64 boundSum; DOXY(levels kept because of root level) , "levels_bounded" , VALUE(boundSum) , LHS.boundSum += RHS.boundSum) \ - STAT(uint32 maxJump; DOXY(longest backjump) , "max" , VALUE(maxJump) , MAX_MEM(LHS.maxJump, RHS.maxJump)) \ - STAT(uint32 maxJumpEx;DOXY(longest unbounded backjump) , "max_executed" , VALUE(maxJumpEx), MAX_MEM(LHS.maxJumpEx,RHS.maxJumpEx)) \ - STAT(uint32 maxBound; DOXY(max levels kept because of root level), "max_bounded" , VALUE(maxBound) , MAX_MEM(LHS.maxBound, RHS.maxBound)) - - JumpStats() { reset(); } - void reset(); - void update(uint32 dl, uint32 uipLevel, uint32 bLevel) { - ++jumps; - jumpSum += dl - uipLevel; - maxJump = std::max(maxJump, dl - uipLevel); - if (uipLevel < bLevel) { - ++bounded; - boundSum += bLevel - uipLevel; - maxJumpEx = std::max(maxJumpEx, dl - bLevel); - maxBound = std::max(maxBound, bLevel - uipLevel); - } - else { maxJumpEx = maxJump; } - } - uint64 jumped() const { return jumpSum - boundSum; } - double jumpedRatio()const { return ratio(jumped(), jumpSum); } - double avgBound() const { return ratio(boundSum, bounded); } - double avgJump() const { return ratio(jumpSum, jumps); } - double avgJumpEx() const { return ratio(jumped(), jumps); } - CLASP_DECLARE_ISTATS(JumpStats); - CLASP_JUMP_STATS(CLASP_STAT_DEFINE, NO_ARG, NO_ARG) +#define CLASP_JUMP_STATS(STAT, LHS, RHS) \ + STAT(uint64_t jumps{}; DOXY(number of backjumps), "jumps", VALUE(jumps), (LHS).jumps += (RHS).jumps) \ + STAT(uint64_t bounded{}; \ + DOXY(backjumps bounded by root level), "jumps_bounded", VALUE(bounded), (LHS).bounded += (RHS).bounded) \ + STAT(uint64_t jumpSum{}; DOXY(levels removed by jumps), "levels", VALUE(jumpSum), (LHS).jumpSum += (RHS).jumpSum) \ + STAT(uint64_t boundSum{}; \ + DOXY(levels kept because of root level), "levels_bounded", VALUE(boundSum), (LHS).boundSum += (RHS).boundSum) \ + STAT(uint32_t maxJump{}; DOXY(longest backjump), "max", VALUE(maxJump), MAX_MEM((LHS).maxJump, (RHS).maxJump)) \ + STAT(uint32_t maxJumpEx{}; DOXY(longest unbounded backjump), "max_executed", VALUE(maxJumpEx), \ + MAX_MEM((LHS).maxJumpEx, (RHS).maxJumpEx)) \ + STAT(uint32_t maxBound{}; DOXY(max levels kept because of root level), "max_bounded", VALUE(maxBound), \ + MAX_MEM((LHS).maxBound, (RHS).maxBound)) + + constexpr JumpStats() = default; + void update(uint32_t dl, uint32_t uipLevel, uint32_t bLevel) { + ++jumps; + jumpSum += dl - uipLevel; + maxJump = std::max(maxJump, dl - uipLevel); + if (uipLevel < bLevel) { + ++bounded; + boundSum += bLevel - uipLevel; + maxJumpEx = std::max(maxJumpEx, dl - bLevel); + maxBound = std::max(maxBound, bLevel - uipLevel); + } + else { + maxJumpEx = maxJump; + } + } + [[nodiscard]] constexpr uint64_t jumped() const { return jumpSum - boundSum; } + [[nodiscard]] constexpr double jumpedRatio() const { return ratio(jumped(), jumpSum); } + [[nodiscard]] constexpr double avgBound() const { return ratio(boundSum, bounded); } + [[nodiscard]] constexpr double avgJump() const { return ratio(jumpSum, jumps); } + [[nodiscard]] constexpr double avgJumpEx() const { return ratio(jumped(), jumps); } + CLASP_DECLARE_ISTATS(JumpStats); + CLASP_JUMP_STATS(CLASP_STAT_DEFINE, NO_ARG, NO_ARG) }; //! A struct for holding (optional) extended statistics. struct ExtendedStats { - typedef ConstraintType type_t; - //! An array for storing a value[t-1] for each learnt Constraint_t::Type t. - typedef uint64 Array[Constraint_t::Type__max]; -#define CLASP_EXTENDED_STATS(STAT, LHS, RHS) \ - STAT(uint64 domChoices; DOXY(number of domain choices) , "domain_choices" , VALUE(domChoices) , LHS.domChoices += RHS.domChoices) \ - STAT(uint64 models; DOXY(number of models) , "models" , VALUE(models) , LHS.models += RHS.models) \ - STAT(uint64 modelLits; DOXY(decision levels in models) , "models_level" , VALUE(modelLits) , LHS.modelLits += RHS.modelLits) \ - STAT(uint64 hccTests; DOXY(number of stability tests) , "hcc_tests" , VALUE(hccTests) , LHS.hccTests += RHS.hccTests) \ - STAT(uint64 hccPartial; DOXY(number of partial tests) , "hcc_partial" , VALUE(hccPartial) , LHS.hccPartial += RHS.hccPartial) \ - STAT(uint64 deleted; DOXY(lemmas deleted ) , "lemmas_deleted" , VALUE(deleted) , LHS.deleted += RHS.deleted) \ - STAT(uint64 distributed;DOXY(lemmas distributed ) , "distributed" , VALUE(distributed), LHS.distributed+= RHS.distributed)\ - STAT(uint64 sumDistLbd; DOXY(lbds of distributed lemmas) , "distributed_sum_lbd" , VALUE(sumDistLbd) , LHS.sumDistLbd += RHS.sumDistLbd) \ - STAT(uint64 integrated; DOXY(lemmas integrated ) , "integrated" , VALUE(integrated) , LHS.integrated += RHS.integrated) \ - STAT(Array learnts; DOXY(lemmas of each learnt type) , "lemmas" , MEM_FUN(lemmas) , NO_ARG) \ - STAT(Array lits; DOXY(lits of each learnt type) , "lits_learnt" , MEM_FUN(learntLits), NO_ARG) \ - STAT(uint32 binary; DOXY(number of binary lemmas) , "lemmas_binary" , VALUE(binary) , LHS.binary += RHS.binary) \ - STAT(uint32 ternary; DOXY(number of ternary lemmas) , "lemmas_ternary" , VALUE(ternary) , LHS.ternary += RHS.ternary) \ - STAT(double cpuTime; DOXY(cpu time used ) , "cpu_time" , VALUE(cpuTime) , LHS.cpuTime += RHS.cpuTime) \ - STAT(uint64 intImps; DOXY(implications on integrating), "integrated_imps" , VALUE(intImps) , LHS.intImps+= RHS.intImps) \ - STAT(uint64 intJumps; DOXY(backjumps on integrating) , "integrated_jumps" , VALUE(intJumps), LHS.intJumps+=RHS.intJumps) \ - STAT(uint64 gpLits; DOXY(lits in received gps) , "guiding_paths_lits" , VALUE(gpLits) , LHS.gpLits += RHS.gpLits) \ - STAT(uint32 gps; DOXY(guiding paths received) , "guiding_paths" , VALUE(gps) , LHS.gps += RHS.gps) \ - STAT(uint32 splits; DOXY(split requests handled) , "splits" , VALUE(splits) , LHS.splits += RHS.splits) \ - STAT(NO_ARG , "lemmas_conflict", VALUE(learnts[0]) , LHS.learnts[0] += RHS.learnts[0]) \ - STAT(NO_ARG , "lemmas_loop" , VALUE(learnts[1]) , LHS.learnts[1] += RHS.learnts[1]) \ - STAT(NO_ARG , "lemmas_other" , VALUE(learnts[2]) , LHS.learnts[2] += RHS.learnts[2]) \ - STAT(NO_ARG , "lits_conflict" , VALUE(lits[0]) , LHS.lits[0] += RHS.lits[0]) \ - STAT(NO_ARG , "lits_loop" , VALUE(lits[1]) , LHS.lits[1] += RHS.lits[1]) \ - STAT(NO_ARG , "lits_other" , VALUE(lits[2]) , LHS.lits[2] += RHS.lits[2]) \ - STAT(JumpStats jumps;DOXY(backjump statistics) , "jumps" , MAP(jumps) , LHS.jumps.accu(RHS.jumps)) - - ExtendedStats() { reset(); } - void reset(); - void addLearnt(uint32 size, type_t t) { - if (t == Constraint_t::Static) return; - learnts[t-1]+= 1; - lits[t-1] += size; - binary += (size == 2); - ternary += (size == 3); - } - //! Total number of lemmas learnt. - uint64 lemmas() const { return std::accumulate(learnts, learnts+Constraint_t::Type__max, uint64(0)); } - //! Total number of literals in all learnt lemmas. - uint64 learntLits() const { return std::accumulate(lits, lits+Constraint_t::Type__max, uint64(0)); } - //! Number of lemmas of learnt type t. - uint64 lemmas(type_t t)const { return learnts[t-1]; } - //! Average length of lemmas of learnt type t. - double avgLen(type_t t)const { return ratio(lits[t-1], lemmas(t)); } - //! Average decision level on which models were found. - double avgModel() const { return ratio(modelLits, models); } - //! Ratio of lemmas that were distributed to other threads. - double distRatio() const { return ratio(distributed, learnts[0] + learnts[1]); } - //! Average lbd of lemmas that were distributed to other threads. - double avgDistLbd() const { return ratio(sumDistLbd, distributed); } - double avgIntJump() const { return ratio(intJumps, intImps); } - //! Average length (i.e. number of literals) of guiding paths. - double avgGp() const { return ratio(gpLits, gps); } - //! Ratio of lemmas integrated. - double intRatio() const { return ratio(integrated, distributed); } - CLASP_DECLARE_ISTATS(ExtendedStats); - CLASP_EXTENDED_STATS(CLASP_STAT_DEFINE,NO_ARG,NO_ARG) + //! An array for storing a value[t-1] for each learnt ConstraintType t. + using Array = uint64_t[Potassco::enum_max()]; + static constexpr uint32_t index(ConstraintType t) { return +t - 1; } +#define CLASP_EXTENDED_STATS(STAT, LHS, RHS) \ + STAT(uint64_t domChoices{}; \ + DOXY(number of domain choices), "domain_choices", VALUE(domChoices), (LHS).domChoices += (RHS).domChoices) \ + STAT(uint64_t models{}; DOXY(number of models), "models", VALUE(models), (LHS).models += (RHS).models) \ + STAT(uint64_t modelLits{}; \ + DOXY(decision levels in models), "models_level", VALUE(modelLits), (LHS).modelLits += (RHS).modelLits) \ + STAT(uint64_t hccTests{}; \ + DOXY(number of stability tests), "hcc_tests", VALUE(hccTests), (LHS).hccTests += (RHS).hccTests) \ + STAT(uint64_t hccPartial{}; \ + DOXY(number of partial tests), "hcc_partial", VALUE(hccPartial), (LHS).hccPartial += (RHS).hccPartial) \ + STAT(uint64_t deleted{}; DOXY(lemmas deleted), "lemmas_deleted", VALUE(deleted), (LHS).deleted += (RHS).deleted) \ + STAT(uint64_t distributed{}; \ + DOXY(lemmas distributed), "distributed", VALUE(distributed), (LHS).distributed += (RHS).distributed) \ + STAT(uint64_t sumDistLbd{}; DOXY(lbds of distributed lemmas), "distributed_sum_lbd", VALUE(sumDistLbd), \ + (LHS).sumDistLbd += (RHS).sumDistLbd) \ + STAT(uint64_t integrated{}; \ + DOXY(lemmas integrated), "integrated", VALUE(integrated), (LHS).integrated += (RHS).integrated) \ + STAT(Array learnts{}; DOXY(lemmas of each learnt type), "lemmas", MEM_FUN(lemmas), NO_ARG) \ + STAT(Array lits{}; DOXY(lits of each learnt type), "lits_learnt", MEM_FUN(learntLits), NO_ARG) \ + STAT(uint32_t binary{}; \ + DOXY(number of binary lemmas), "lemmas_binary", VALUE(binary), (LHS).binary += (RHS).binary) \ + STAT(uint32_t ternary{}; \ + DOXY(number of ternary lemmas), "lemmas_ternary", VALUE(ternary), (LHS).ternary += (RHS).ternary) \ + STAT(double cpuTime{}; DOXY(cpu time used), "cpu_time", VALUE(cpuTime), (LHS).cpuTime += (RHS).cpuTime) \ + STAT(uint64_t intImps{}; \ + DOXY(implications on integrating), "integrated_imps", VALUE(intImps), (LHS).intImps += (RHS).intImps) \ + STAT(uint64_t intJumps{}; \ + DOXY(backjumps on integrating), "integrated_jumps", VALUE(intJumps), (LHS).intJumps += (RHS).intJumps) \ + STAT(uint64_t gpLits{}; \ + DOXY(lits in received gps), "guiding_paths_lits", VALUE(gpLits), (LHS).gpLits += (RHS).gpLits) \ + STAT(uint32_t gps{}; DOXY(guiding paths received), "guiding_paths", VALUE(gps), (LHS).gps += (RHS).gps) \ + STAT(uint32_t splits{}; DOXY(split requests handled), "splits", VALUE(splits), (LHS).splits += (RHS).splits) \ + STAT(NO_ARG, "lemmas_conflict", VALUE(learnts[0]), (LHS).learnts[0] += (RHS).learnts[0]) \ + STAT(NO_ARG, "lemmas_loop", VALUE(learnts[1]), (LHS).learnts[1] += (RHS).learnts[1]) \ + STAT(NO_ARG, "lemmas_other", VALUE(learnts[2]), (LHS).learnts[2] += (RHS).learnts[2]) \ + STAT(NO_ARG, "lits_conflict", VALUE(lits[0]), (LHS).lits[0] += (RHS).lits[0]) \ + STAT(NO_ARG, "lits_loop", VALUE(lits[1]), (LHS).lits[1] += (RHS).lits[1]) \ + STAT(NO_ARG, "lits_other", VALUE(lits[2]), (LHS).lits[2] += (RHS).lits[2]) \ + STAT(JumpStats jumps; DOXY(backjump statistics), "jumps", MAP(jumps), (LHS).jumps.accu((RHS).jumps)) + + constexpr ExtendedStats() = default; + constexpr void addLearnt(uint32_t size, ConstraintType t) { + if (t == ConstraintType::static_) { + return; + } + auto idx = index(t); + learnts[idx] += 1; + lits[idx] += size; + binary += (size == 2); + ternary += (size == 3); + } + //! Total number of lemmas learnt. + [[nodiscard]] constexpr uint64_t lemmas() const { + return std::accumulate(std::begin(learnts), std::end(learnts), static_cast(0)); + } + //! Total number of literals in all learnt lemmas. + [[nodiscard]] constexpr uint64_t learntLits() const { + return std::accumulate(std::begin(lits), std::end(lits), static_cast(0)); + } + //! Number of lemmas of learnt type t. + [[nodiscard]] constexpr uint64_t lemmas(ConstraintType t) const { return learnts[index(t)]; } + //! Average length of lemmas of learnt type t. + [[nodiscard]] constexpr double avgLen(ConstraintType t) const { return ratio(lits[index(t)], lemmas(t)); } + //! Average decision level on which models were found. + [[nodiscard]] constexpr double avgModel() const { return ratio(modelLits, models); } + //! Ratio of lemmas that were distributed to other threads. + [[nodiscard]] constexpr double distRatio() const { return ratio(distributed, learnts[0] + learnts[1]); } + //! Average lbd of lemmas that were distributed to other threads. + [[nodiscard]] constexpr double avgDistLbd() const { return ratio(sumDistLbd, distributed); } + [[nodiscard]] constexpr double avgIntJump() const { return ratio(intJumps, intImps); } + //! Average length (i.e. number of literals) of guiding paths. + [[nodiscard]] constexpr double avgGp() const { return ratio(gpLits, gps); } + //! Ratio of lemmas integrated. + [[nodiscard]] constexpr double intRatio() const { return ratio(integrated, distributed); } + CLASP_DECLARE_ISTATS(ExtendedStats); + CLASP_EXTENDED_STATS(CLASP_STAT_DEFINE, NO_ARG, NO_ARG) }; //! A struct for aggregating statistics maintained in a solver object. -struct SolverStats : public CoreStats { - SolverStats(); - SolverStats(const SolverStats& o); - ~SolverStats(); - bool enableExtended(); - bool enable(const SolverStats& o) { return !o.extra || enableExtended(); } - void reset(); - void accu(const SolverStats& o); - void accu(const SolverStats& o, bool enableRhs); - void swapStats(SolverStats& o); - void flush() const; - uint32 size() const; - const char* key(uint32 i) const; - StatisticObject at(const char* key) const; - void addTo(const char* key, StatsMap& solving, StatsMap* accu) const; - inline void addLearnt(uint32 size, ConstraintType t); - inline void addConflict(uint32 dl, uint32 uipLevel, uint32 bLevel, uint32 lbd); - inline void addDeleted(uint32 num); - inline void addDistributed(uint32 lbd, ConstraintType t); - inline void addTest(bool partial); - inline void addModel(uint32 decisionLevel); - inline void addCpuTime(double t); - inline void addSplit(uint32 num = 1); - inline void addDomChoice(uint32 num = 1); - inline void addIntegratedAsserting(uint32 receivedDL, uint32 jumpDL); - inline void addIntegrated(uint32 num = 1); - inline void removeIntegrated(uint32 num = 1); - inline void addPath(const LitVec::size_type& sz); - ExtendedStats* extra; /**< Optional extended statistics. */ - SolverStats* multi; /**< Not owned: set to accu stats in multishot solving. */ -private: SolverStats& operator=(const SolverStats&); +struct SolverStats : CoreStats { + SolverStats() = default; + SolverStats(const SolverStats& o); + SolverStats& operator=(const SolverStats&) = delete; + ~SolverStats(); + bool enableExtended(); + bool enable(const SolverStats& o) { return not o.extra || enableExtended(); } + void reset(); + void accu(const SolverStats& o); + void accu(const SolverStats& o, bool enableRhs); + void swapStats(SolverStats& o); + void flush() const; + [[nodiscard]] uint32_t size() const; + [[nodiscard]] const char* key(uint32_t i) const; + StatisticObject at(const char* key) const; + void addTo(const char* key, StatsMap& solving, StatsMap* accu) const; + inline void addLearnt(uint32_t size, ConstraintType t); + inline void addConflict(uint32_t dl, uint32_t uipLevel, uint32_t bLevel, uint32_t lbd); + inline void addDeleted(uint32_t num); + inline void addDistributed(uint32_t lbd, ConstraintType t); + inline void addTest(bool partial); + inline void addModel(uint32_t dl); + inline void addCpuTime(double t); + inline void addSplit(uint32_t n = 1); + inline void addDomChoice(uint32_t n = 1); + inline void addIntegratedAsserting(uint32_t startLevel, uint32_t jumpLevel); + inline void addIntegrated(uint32_t n = 1); + inline void removeIntegrated(uint32_t n = 1); + inline void addPath(LitView::size_type sz); + ExtendedStats* extra = nullptr; /**< Optional extended statistics. */ + SolverStats* multi = nullptr; /**< Not owned: set to accu stats in multishot solving. */ }; -inline void SolverStats::addLearnt(uint32 size, ConstraintType t) { if (extra) { extra->addLearnt(size, t); } } -inline void SolverStats::addDeleted(uint32 num) { if (extra) { extra->deleted += num; } } -inline void SolverStats::addDistributed(uint32 lbd, ConstraintType){ if (extra) { ++extra->distributed; extra->sumDistLbd += lbd; } } -inline void SolverStats::addIntegrated(uint32 n) { if (extra) { extra->integrated += n;} } -inline void SolverStats::removeIntegrated(uint32 n) { if (extra) { extra->integrated -= n;} } -inline void SolverStats::addCpuTime(double t) { if (extra) { extra->cpuTime += t; } } -inline void SolverStats::addSplit(uint32 num) { if (extra) { extra->splits += num; } } -inline void SolverStats::addPath(const LitVec::size_type& sz) { if (extra) { ++extra->gps; extra->gpLits += sz; } } -inline void SolverStats::addTest(bool partial) { if (extra) { ++extra->hccTests; extra->hccPartial += (uint32)partial; } } -inline void SolverStats::addModel(uint32 DL) { if (extra) { ++extra->models; extra->modelLits += DL; } } -inline void SolverStats::addDomChoice(uint32 n) { if (extra) { extra->domChoices += n; } } -inline void SolverStats::addIntegratedAsserting(uint32 rDL, uint32 jDL) { - if (extra) { ++extra->intImps; extra->intJumps += (rDL - jDL); } +// NOLINTBEGIN(readability-make-member-function-const) +inline void SolverStats::addLearnt(uint32_t size, ConstraintType t) { + if (extra) { + extra->addLearnt(size, t); + } +} +inline void SolverStats::addDeleted(uint32_t num) { + if (extra) { + extra->deleted += num; + } +} +inline void SolverStats::addDistributed(uint32_t lbd, ConstraintType) { + if (extra) { + ++extra->distributed; + extra->sumDistLbd += lbd; + } +} +inline void SolverStats::addIntegrated(uint32_t n) { + if (extra) { + extra->integrated += n; + } +} +inline void SolverStats::removeIntegrated(uint32_t n) { + if (extra) { + extra->integrated -= n; + } +} +inline void SolverStats::addCpuTime(double t) { + if (extra) { + extra->cpuTime += t; + } +} +inline void SolverStats::addSplit(uint32_t n) { + if (extra) { + extra->splits += n; + } +} +inline void SolverStats::addPath(LitView::size_type sz) { + if (extra) { + ++extra->gps; + extra->gpLits += sz; + } +} +inline void SolverStats::addTest(bool partial) { + if (extra) { + ++extra->hccTests; + extra->hccPartial += static_cast(partial); + } +} +inline void SolverStats::addModel(uint32_t dl) { + if (extra) { + ++extra->models; + extra->modelLits += dl; + } +} +inline void SolverStats::addDomChoice(uint32_t n) { + if (extra) { + extra->domChoices += n; + } +} +inline void SolverStats::addIntegratedAsserting(uint32_t startLevel, uint32_t jumpLevel) { + if (extra) { + ++extra->intImps; + extra->intJumps += (startLevel - jumpLevel); + } } -inline void SolverStats::addConflict(uint32 dl, uint32 uipLevel, uint32 bLevel, uint32 lbd) { - ++analyzed; - if (extra) { extra->jumps.update(dl, uipLevel, bLevel); } +// NOLINTEND(readability-make-member-function-const) +inline void SolverStats::addConflict(uint32_t dl, uint32_t uipLevel, uint32_t bLevel, uint32_t) { + ++analyzed; + if (extra) { + extra->jumps.update(dl, uipLevel, bLevel); + } } #undef CLASP_STAT_DEFINE #undef NO_ARG @@ -241,15 +318,21 @@ inline void SolverStats::addConflict(uint32 dl, uint32 uipLevel, uint32 bLevel, /////////////////////////////////////////////////////////////////////////////// //! Primitive representation of a clause. struct ClauseRep { - typedef ConstraintInfo Info; - static ClauseRep create(Literal* cl, uint32 sz, const Info& i = Info()) { return ClauseRep(cl, sz, false, i);} - static ClauseRep prepared(Literal* cl, uint32 sz, const Info& i = Info()){ return ClauseRep(cl, sz, true, i); } - ClauseRep(Literal* cl = 0, uint32 sz = 0, bool p = false, const Info& i = Info()) : info(i), size(sz), prep(uint32(p)), lits(cl) {} - Info info; /*!< Additional clause info. */ - uint32 size:31; /*!< Size of array of literals. */ - uint32 prep: 1; /*!< Whether lits is already prepared. */ - Literal* lits; /*!< Pointer to array of literals (not owned!). */ - bool isImp() const { return size > 1 && size < 4; } + using Info = ConstraintInfo; + static constexpr ClauseRep create(std::span lits, const Info& i = Info()) { + return {i, size32(lits), false, lits.data()}; + } + static constexpr ClauseRep prepared(std::span lits, const Info& i = Info()) { + return {i, size32(lits), true, lits.data()}; + } + + [[nodiscard]] constexpr bool isImp() const { return size > 1 && size < 4; } + [[nodiscard]] constexpr LitView literals() const { return {lits, size}; } + + Info info; /*!< Additional clause info. */ + uint32_t size : 31 = 0; /*!< Size of array of literals. */ + uint32_t prep : 1 = 0; /*!< Whether lits is already prepared. */ + Literal* lits = nullptr; /*!< Pointer to array of literals (not owned!). */ }; //! (Abstract) base class for clause types. @@ -262,154 +345,160 @@ struct ClauseRep { */ class ClauseHead : public Constraint { public: - enum { HEAD_LITS = 3, MAX_SHORT_LEN = 5 }; - explicit ClauseHead(const InfoType& init); - // base interface - //! Propagates the head and calls updateWatch() if necessary. - PropResult propagate(Solver& s, Literal, uint32& data); - //! Type of clause. - Type type() const { return info_.type(); } - //! Returns the activity of this clause. - ScoreType activity() const { return info_.score(); } - //! True if this clause currently is the antecedent of an assignment. - bool locked(const Solver& s) const; - //! Halves the activity of this clause. - void decreaseActivity() { info_.score().reduce(); } - void resetActivity() { info_.score().reset(); } - //! Downcast from LearntConstraint. - ClauseHead* clause() { return this; } - - // clause interface - typedef std::pair BoolPair; - //! Adds watches for first two literals in head to solver. - void attach(Solver& s); - void resetScore(ScoreType sc); - //! Returns true if head is satisfied w.r.t current assignment in s. - bool satisfied(const Solver& s) const; - //! Conditional clause? - bool tagged() const { return info_.tagged(); } - //! Contains aux vars? - bool aux() const { return info_.aux(); } - bool learnt() const { return info_.learnt(); } - uint32 lbd() const { return info_.lbd(); } - //! Removes watches from s. - virtual void detach(Solver& s); - //! Returns the size of this clause. - virtual uint32 size() const = 0; - //! Returns the literals of this clause in out. - virtual void toLits(LitVec& out) const = 0; - //! Returns true if this clause is a valid "reverse antecedent" for p. - virtual bool isReverseReason(const Solver& s, Literal p, uint32 maxL, uint32 maxN) = 0; - //! Removes p from clause if possible. - /*! - * \return - * The first component of the returned pair specifies whether or not - * p was removed from the clause. - * The second component of the returned pair specifies whether - * the clause should be kept (false) or removed (true). - */ - virtual BoolPair strengthen(Solver& s, Literal p, bool allowToShort = true) = 0; + static constexpr auto head_lits = 3u; + static constexpr auto max_short_len = 5u; + + explicit ClauseHead(const InfoType& init); + // base interface + //! Propagates the head and calls updateWatch() if necessary. + PropResult propagate(Solver& s, Literal, uint32_t& data) override; + //! Type of clause. + [[nodiscard]] Type type() const override { return info_.type(); } + //! Returns the activity of this clause. + [[nodiscard]] ScoreType activity() const override { return info_.score(); } + //! True if this clause currently is the antecedent of an assignment. + [[nodiscard]] bool locked(const Solver& s) const override; + //! Halves the activity of this clause. + void decreaseActivity() override { info_.score().reduce(); } + void resetActivity() override { info_.score().reset(); } + //! Downcast from LearntConstraint. + ClauseHead* clause() override { return this; } + + // clause interface + //! Adds watches for first two literals in head to solver. + void attach(Solver& s); + void resetScore(ScoreType sc); + //! Returns true if head is satisfied w.r.t current assignment in s. + [[nodiscard]] bool satisfied(const Solver& s) const; + //! Conditional clause? + [[nodiscard]] bool tagged() const { return info_.tagged(); } + //! Contains aux vars? + [[nodiscard]] bool aux() const { return info_.aux(); } + [[nodiscard]] bool learnt() const { return info_.learnt(); } + [[nodiscard]] uint32_t lbd() const { return info_.lbd(); } + //! Removes watches from s. + virtual void detach(Solver& s); + //! Returns the size of this clause. + [[nodiscard]] virtual uint32_t size() const = 0; + //! Returns the literals of this clause in out. + virtual void toLits(LitVec& out) const = 0; + //! Returns true if this clause is a valid "reverse antecedent" for p. + virtual bool isReverseReason(const Solver& s, Literal p, uint32_t maxL, uint32_t maxN) = 0; + struct StrengthenResult { + bool litRemoved = false; + bool removeClause = false; + }; + //! Removes p from clause if possible. + /*! + * \return A StrengthenResult object @c r, with: + * - @c r.litRemoved if p was removed from the clause. + * - @c r.removeClause if the clause should be removed. + */ + virtual StrengthenResult strengthen(Solver& s, Literal p, bool allowToShort = true) = 0; + protected: - struct Local { - void init(uint32 sz); - bool isSmall() const { return (mem[0] & 1u) == 0u; } - bool contracted() const { return (mem[0] & 3u) == 3u; } - bool strengthened()const { return (mem[0] & 5u) == 5u; } - uint32 size() const { return mem[0] >> 3; } - void setSize(uint32 size) { mem[0] = (size << 3) | (mem[0] & 7u); } - void markContracted() { mem[0] |= 2u; } - void markStrengthened() { mem[0] |= 4u; } - void clearContracted() { mem[0] &= ~2u; } - void clearIdx() { mem[1] = 0; } - uint32 mem[2]; - }; - bool toImplication(Solver& s); - void clearTagged() { info_.setTagged(false); } - void setLbd(uint32 x) { info_.setLbd(x); } - //! Shall replace the watched literal at position pos with a non-false literal. - /*! - * \pre pos in [0,1] - * \pre s.isFalse(head_[pos]) && s.isFalse(head_[2]) - * \pre head_[pos^1] is the other watched literal - */ - virtual bool updateWatch(Solver& s, uint32 pos) = 0; - union { - Local local_; - SharedLiterals* shared_; - }; - InfoType info_; - Literal head_[HEAD_LITS]; // two watched literals and one cache literal + struct Local { + void init(uint32_t sz); + [[nodiscard]] bool isSmall() const { return (mem[0] & 1u) == 0u; } + [[nodiscard]] bool contracted() const { return (mem[0] & 3u) == 3u; } + [[nodiscard]] bool strengthened() const { return (mem[0] & 5u) == 5u; } + [[nodiscard]] uint32_t size() const { return mem[0] >> 3; } + void setSize(uint32_t size) { mem[0] = (size << 3) | (mem[0] & 7u); } + void markContracted() { mem[0] |= 2u; } + void markStrengthened() { mem[0] |= 4u; } + void clearContracted() { mem[0] &= ~2u; } + void clearIdx() { mem[1] = 0; } + uint32_t mem[2]; + }; + bool toImplication(Solver& s); + void clearTagged() { info_.setTagged(false); } + void setLbd(uint32_t x) { info_.setLbd(x); } + //! Shall replace the watched literal at position pos with a non-false literal. + /*! + * \pre pos in [0,1] + * \pre s.isFalse(head_[pos]) && s.isFalse(head_[2]) + * \pre head_[pos^1] is the other watched literal + */ + virtual bool updateWatch(Solver& s, uint32_t pos) = 0; + union { + Local local_; + SharedLiterals* shared_; + }; + InfoType info_; + Literal head_[head_lits]; // two watched literals and one cache literal }; //! Allocator for small (at most 32-byte) clauses. class SmallClauseAlloc { public: - SmallClauseAlloc(); - ~SmallClauseAlloc(); - void* allocate() { - if(freeList_ == 0) { - allocBlock(); - } - Chunk* r = freeList_; - freeList_ = r->next; - return r; - } - void free(void* mem) { - Chunk* b = reinterpret_cast(mem); - b->next = freeList_; - freeList_= b; - } + SmallClauseAlloc(); + ~SmallClauseAlloc(); + SmallClauseAlloc(SmallClauseAlloc&&) = delete; + + void* allocate() { + if (freeList_ == nullptr) { + allocBlock(); + } + Chunk* r = freeList_; + freeList_ = r->next; + return r; + } + void free(void* mem) { + auto* b = static_cast(mem); + b->next = freeList_; + freeList_ = b; + } + private: - SmallClauseAlloc(const SmallClauseAlloc&); - SmallClauseAlloc& operator=(const SmallClauseAlloc&); - struct Chunk { - Chunk* next; // enforce ptr alignment - unsigned char mem[32 - sizeof(Chunk*)]; - }; - struct Block { - enum { num_chunks = 1023 }; - Block* next; - unsigned char pad[32-sizeof(Block*)]; - Chunk chunk[num_chunks]; - }; - void allocBlock(); - Block* blocks_; - Chunk* freeList_; + struct Chunk { + Chunk* next; // enforce ptr alignment + unsigned char mem[32 - sizeof(Chunk*)]; + }; + struct Block { + static constexpr auto num_chunks = 1023u; + + Block* next; + unsigned char pad[32 - sizeof(Block*)]; + Chunk chunk[num_chunks]; + }; + void allocBlock(); + Block* blocks_; + Chunk* freeList_; }; /////////////////////////////////////////////////////////////////////////////// // Watches /////////////////////////////////////////////////////////////////////////////// //! Represents a clause watch in a Solver. struct ClauseWatch { - //! Clause watch: clause head - explicit ClauseWatch(ClauseHead* a_head) : head(a_head) { } - ClauseHead* head; - struct EqHead { - explicit EqHead(ClauseHead* h) : head(h) {} - bool operator()(const ClauseWatch& w) const { return head == w.head; } - ClauseHead* head; - }; + //! Clause watch: clause head + explicit ClauseWatch(ClauseHead* a_head) : head(a_head) {} + ClauseHead* head; + struct EqHead { + constexpr explicit EqHead(ClauseHead* h) : head(h) {} + constexpr bool operator()(const ClauseWatch& w) const { return head == w.head; } + ClauseHead* head; + }; }; //! Represents a generic watch in a Solver. struct GenericWatch { - //! A constraint and some associated data. - explicit GenericWatch(Constraint* a_con, uint32 a_data = 0) : con(a_con), data(a_data) {} - //! Calls propagate on the stored constraint and passes the stored data to that constraint. - Constraint::PropResult propagate(Solver& s, Literal p) { return con->propagate(s, p, data); } - - Constraint* con; /**< The constraint watching a certain literal. */ - uint32 data; /**< Additional data associated with this watch - passed to constraint on update. */ - - struct EqConstraint { - explicit EqConstraint(Constraint* c) : con(c) {} - bool operator()(const GenericWatch& w) const { return con == w.con; } - Constraint* con; - }; + //! A constraint and some associated data. + explicit GenericWatch(Constraint* a_con, uint32_t a_data = 0) : con(a_con), data(a_data) {} + //! Calls propagate on the stored constraint and passes the stored data to that constraint. + Constraint::PropResult propagate(Solver& s, Literal p) { return con->propagate(s, p, data); } + + Constraint* con; /**< The constraint watching a certain literal. */ + uint32_t data; /**< Additional data associated with this watch - passed to constraint on update. */ + + struct EqConstraint { + constexpr explicit EqConstraint(Constraint* c) : con(c) {} + constexpr bool operator()(const GenericWatch& w) const { return con == w.con; } + Constraint* con; + }; }; //! Watch list type. -typedef bk_lib::left_right_sequence WatchList; -inline void releaseVec(WatchList& w) { w.clear(true); } +using WatchList = bk_lib::left_right_sequence; +inline void releaseVec(WatchList& w) { w.reset(); } /////////////////////////////////////////////////////////////////////////////// // Assignment @@ -420,40 +509,50 @@ inline void releaseVec(WatchList& w) { w.clear(true); } /*! * \note On 32-bit systems additional data is stored in the high-word of antecedents. */ -struct ReasonStore32 : PodVector::type { - uint32 data(uint32 v) const { return decode((*this)[v]);} - void setData(uint32 v, uint32 data) { encode((*this)[v], data); } - static void encode(Antecedent& a, uint32 data) { - a.asUint() = (uint64(data)<<32) | static_cast(a.asUint()); - } - static uint32 decode(const Antecedent& a) { - return static_cast(a.asUint()>>32); - } - struct value_type { - value_type(const Antecedent& a, uint32 d) : ante_(a) { - if (d != UINT32_MAX) { encode(ante_, d); assert(data() == d && ante_.type() == Antecedent::Generic); } - } - const Antecedent& ante() const { return ante_; } - uint32 data() const { return ante_.type() == Antecedent::Generic ? decode(ante_) : UINT32_MAX; } - Antecedent ante_; - }; +struct ReasonStore32 : PodVector_t { + [[nodiscard]] uint32_t data(uint32_t v) const { return decode((*this)[v]); } + void setData(uint32_t v, uint32_t data) { encode((*this)[v], data); } + static void encode(Antecedent& a, uint32_t data) { + a.asUint() = (static_cast(data) << 32) | static_cast(a.asUint()); + } + static uint32_t decode(const Antecedent& a) { return static_cast(a.asUint() >> 32); } + struct value_type { // NOLINT + value_type(const Antecedent& ante, uint32_t d) : a(ante) { + if (d != UINT32_MAX) { + encode(a, d); + assert(data() == d && a.type() == Antecedent::generic); + } + } + [[nodiscard]] const Antecedent& ante() const { return a; } + [[nodiscard]] uint32_t data() const { return a.type() == Antecedent::generic ? decode(a) : UINT32_MAX; } + Antecedent a; + }; }; //! Type for storing reasons for variable assignments together with additional data. /* * \note On 64-bit systems additional data is stored in a separate container. */ -struct ReasonStore64 : PodVector::type { - uint32 dataSize() const { return (uint32)data_.size(); } - void dataResize(uint32 nv) { if (nv > dataSize()) data_.resize(nv, UINT32_MAX); } - uint32 data(uint32 v) const { return v < dataSize() ? data_[v] : UINT32_MAX; } - void setData(uint32 v, uint32 data) { dataResize(v+1); data_[v] = data; } - VarVec data_; - struct value_type : std::pair { - value_type(const Antecedent& a, uint32 d) : std::pair(a, d) {} - const Antecedent& ante() const { return first; } - uint32 data() const { return second; } - }; +struct ReasonStore64 : PodVector_t { + [[nodiscard]] uint32_t dataSize() const { return size32(dv); } + void dataResize(uint32_t nv) { + if (nv > dataSize()) { + dv.resize(nv, UINT32_MAX); + } + } + [[nodiscard]] uint32_t data(uint32_t v) const { return v < dataSize() ? dv[v] : UINT32_MAX; } + void setData(uint32_t v, uint32_t data) { + dataResize(v + 1); + dv[v] = data; + } + VarVec dv; + struct value_type { // NOLINT + constexpr value_type(const Antecedent& ante, uint32_t data) : a(ante), d(data) {} + [[nodiscard]] const Antecedent& ante() const { return a; } + [[nodiscard]] uint32_t data() const { return d; } + Antecedent a; + uint32_t d; + }; }; //! A set of configurable values for a variable. @@ -466,16 +565,24 @@ struct ReasonStore64 : PodVector::type { * user > saved > preferred > current sign score of heuristic > default value */ struct ValueSet { - ValueSet() : rep(0) {} - enum Value { user_value = 0x03u, saved_value = 0x0Cu, pref_value = 0x30u, def_value = 0xC0u }; - bool sign() const { return (right_most_bit(rep) & 0xAAu) != 0; } - bool empty() const { return rep == 0; } - bool has(Value v) const { return (rep & v) != 0; } - bool has(uint32 f)const { return (rep & f) != 0; } - ValueRep get(Value v) const { return static_cast((rep & v) / right_most_bit(static_cast(v))); } - void set(Value which, ValueRep to) { rep &= ~which; rep |= (to * right_most_bit(static_cast(which))); } - void save(ValueRep x) { rep &= ~saved_value; rep |= (x << 2); } - uint8 rep; + enum Value : uint32_t { user_value = 0x03u, saved_value = 0x0Cu, pref_value = 0x30u, def_value = 0xC0u }; + constexpr ValueSet() = default; + [[nodiscard]] constexpr bool sign() const { return Potassco::test_any(Potassco::right_most_bit(rep), 0xAAu); } + [[nodiscard]] constexpr bool empty() const { return rep == 0; } + [[nodiscard]] constexpr bool has(Value v) const { return Potassco::test_any(rep, v); } + [[nodiscard]] constexpr bool has(uint32_t f) const { return Potassco::test_any(rep, f); } + [[nodiscard]] constexpr Val_t get(Value v) const { + return static_cast((rep & v) / Potassco::right_most_bit(v)); + } + constexpr void set(Value which, Val_t to) { + Potassco::store_clear_mask(rep, which); + Potassco::store_set_mask(rep, to * Potassco::right_most_bit(which)); + } + constexpr void save(Val_t x) { + Potassco::store_clear_mask(rep, saved_value); + Potassco::store_set_mask(rep, x << 2); + } + uint8_t rep{0}; }; //! Stores assignment related information. @@ -489,196 +596,222 @@ struct ValueSet { * Furthermore, the class stores the sequences of assignments as a set of * true literals in its trail-member. */ -class Assignment { +class Assignment { public: - typedef PodVector::type AssignVec; - typedef PodVector::type PrefVec; - typedef bk_lib::detail::if_then_else< - sizeof(Constraint*)==sizeof(uint64) - , ReasonStore64 - , ReasonStore32>::type ReasonVec; - typedef ReasonVec::value_type ReasonWithData; - Assignment() : front(0), elims_(0), units_(0) { } - LitVec trail; // assignment sequence - uint32 front; // and "propagation queue" - bool qEmpty() const { return front == static_cast(trail.size()); } - uint32 qSize() const { return static_cast(trail.size() - front); } - Literal qPop() { return trail[front++]; } - void qReset() { front = static_cast(trail.size()); } - - //! Number of variables in the three-valued assignment. - uint32 numVars() const { return (uint32)assign_.size(); } - //! Number of assigned variables. - uint32 assigned() const { return (uint32)trail.size(); } - //! Number of free variables. - uint32 free() const { return numVars() - (assigned()+elims_); } - //! Returns the largest possible decision level. - uint32 maxLevel() const { return (1u<<28)-2; } - //! Returns v's value in the three-valued assignment. - ValueRep value(Var v) const { return ValueRep(assign_[v] & 3u); } - //! Returns the decision level on which v was assigned if value(v) != value_free. - uint32 level(Var v) const { return assign_[v] >> 4u; } - //! Returns true if v was not eliminated from the assignment. - bool valid(Var v) const { return (assign_[v] & elim_mask) != elim_mask; } - //! Returns the set of preferred values of v. - const ValueSet pref(Var v) const { return v < pref_.size() ? pref_[v] : ValueSet(); } - //! Returns the reason for v being assigned if value(v) != value_free. - const Antecedent& reason(Var v)const { return reason_[v]; } - //! Returns the reason data associated with v. - uint32 data(Var v) const { return reason_.data(v); } - - void reserve(uint32 n) { - assign_.reserve(n); - reason_.reserve(n); - } - //! Resize to nv variables. - void resize(uint32 nv) { - assign_.resize(nv); - reason_.resize(nv); - } - //! Adds a var to assignment - initially the new var is unassigned. - Var addVar() { - assign_.push_back(0); - reason_.push_back(0); - return numVars()-1; - } - //! Allocates space for storing preferred values for all variables. - void requestPrefs() { - if (pref_.size() != assign_.size()) { pref_.resize(assign_.size()); } - } - //! Eliminates v from the assignment. - void eliminate(Var v) { - assert(value(v) == value_free && "Can not eliminate assigned var!\n"); - if (valid(v)) { assign_[v] = elim_mask|value_true; ++elims_; } - } - //! Assigns p.var() on level lev to the value that makes p true and stores x as reason for the assignment. - /*! - * \return true if the assignment is consistent. False, otherwise. - * \post If true is returned, p is in trail. Otherwise, ~p is. - */ - bool assign(Literal p, uint32 lev, const Antecedent& x) { - const Var v = p.var(); - const ValueRep val = value(v); - if (val == value_free) { - assert(valid(v)); - assign_[v] = (lev<<4) + trueValue(p); - reason_[v] = x; - trail.push_back(p); - return true; - } - return val == trueValue(p); - } - bool assign(Literal p, uint32 lev, Constraint* c, uint32 data) { - const Var v = p.var(); - const ValueRep val = value(v); - if (val == value_free) { - assert(valid(v)); - assign_[v] = (lev<<4) + trueValue(p); - reason_[v] = c; - reason_.setData(v, data); - trail.push_back(p); - return true; - } - return val == trueValue(p); - } - //! Undos all assignments in the range trail[first, last). - /*! - * \param first First assignment to be undone. - * \param save If true, previous assignment of a var is saved before it is undone. - */ - void undoTrail(LitVec::size_type first, bool save) { - if (!save) { popUntil<&Assignment::clear>(trail[first]); } - else { requestPrefs(); popUntil<&Assignment::saveAndClear>(trail[first]); } - qReset(); - } - //! Undos the last assignment. - void undoLast() { clear(trail.back().var()); trail.pop_back(); } - //! Returns the last assignment as a true literal. - Literal last() const { return trail.back(); } - Literal&last() { return trail.back(); } - /*! - * \name Implementation functions - * Low-level implementation functions. Use with care and only if you - * know what you are doing! - */ - //@{ - uint32 units() const { return units_; } - bool seen(Var v, uint8 m) const { return (assign_[v] & (m<<2)) != 0; } - void setSeen(Var v, uint8 m) { assign_[v] |= (m<<2); } - void clearSeen(Var v) { assign_[v] &= ~uint32(12); } - void clearValue(Var v) { assign_[v] &= ~uint32(3); } - void setValue(Var v, ValueRep val) { - assert(value(v) == val || value(v) == value_free); - assign_[v] |= val; - } - void setReason(Var v, const Antecedent& a) { reason_[v] = a; } - void setData(Var v, uint32 data) { reason_.setData(v, data); } - void setPref(Var v, ValueSet::Value which, ValueRep to) { pref_[v].set(which, to); } - void copyAssignment(Assignment& o) const { o.assign_ = assign_; } - bool markUnits() { while (units_ != front) { setSeen(trail[units_++].var(), 3u); } return true; } - void setUnits(uint32 ts) { units_ = ts; } - void resetPrefs() { pref_.assign(pref_.size(), ValueSet()); } - void clear(Var v) { assign_[v] = 0; } - void saveAndClear(Var v) { pref_[v].save(value(v)); clear(v); } - //@} + using AssignVec = PodVector_t; + using PrefVec = PodVector_t; + using ReasonVec = std::conditional_t; + using ReasonWithData = ReasonVec::value_type; + Assignment() = default; + Assignment(const Assignment&) = delete; + Assignment& operator=(const Assignment&) = delete; + + LitVec trail; // assignment sequence + uint32_t front{0}; // and "propagation queue" + [[nodiscard]] bool qEmpty() const { return front == size32(trail); } + [[nodiscard]] uint32_t qSize() const { return size32(trail) - front; } + Literal qPop() { return trail[front++]; } + void qReset() { front = size32(trail); } + + //! Number of variables in the three-valued assignment. + [[nodiscard]] uint32_t numVars() const { return size32(assign_); } + //! Number of assigned variables. + [[nodiscard]] uint32_t assigned() const { return size32(trail); } + //! Number of free variables. + [[nodiscard]] uint32_t free() const { return numVars() - (assigned() + elims_); } + //! Returns the largest possible decision level. + [[nodiscard]] uint32_t maxLevel() const { return (1u << 28) - 2; } + //! Returns v's value in the three-valued assignment. + [[nodiscard]] Val_t value(Var_t v) const { return static_cast(assign_[v] & value_mask); } + //! Returns the decision level on which v was assigned if value(v) != value_free. + [[nodiscard]] uint32_t level(Var_t v) const { return assign_[v] >> level_shift; } + //! Returns true if v was not eliminated from the assignment. + [[nodiscard]] bool valid(Var_t v) const { return not Potassco::test_mask(assign_[v], elim_mask); } + //! Returns the set of preferred values of v. + [[nodiscard]] ValueSet pref(Var_t v) const { return v < pref_.size() ? pref_[v] : ValueSet(); } + //! Returns the reason for v being assigned if value(v) != value_free. + [[nodiscard]] const Antecedent& reason(Var_t v) const { return reason_[v]; } + //! Returns the reason data associated with v. + [[nodiscard]] uint32_t data(Var_t v) const { return reason_.data(v); } + + void reserve(uint32_t n) { + assign_.reserve(n); + reason_.reserve(n); + } + //! Resize to nv variables. + void resize(uint32_t nv) { + assign_.resize(nv); + reason_.resize(nv); + } + //! Adds a var to assignment - initially the new var is unassigned. + Var_t addVar() { + assign_.push_back(0); + reason_.push_back(nullptr); + return numVars() - 1; + } + //! Allocates space for storing preferred values for all variables. + void requestPrefs() { + if (pref_.size() != assign_.size()) { + pref_.resize(assign_.size()); + } + } + //! Eliminates v from the assignment. + void eliminate(Var_t v) { + assert(value(v) == value_free && "Can not eliminate assigned var!\n"); + if (valid(v)) { + assign_[v] = elim_mask | value_true; + ++elims_; + } + } + //! Assigns p.var() on level lev to the value that makes p true and stores x as reason for the assignment. + /*! + * \return true if the assignment is consistent. False, otherwise. + * \post If true is returned, p is in trail. Otherwise, ~p is. + */ + template + bool assign(Literal p, uint32_t lev, const Antecedent& x, DataT... data) { + static_assert(sizeof...(DataT) <= 1); + const auto v = p.var(); + const auto val = value(v); + if (val == value_free) { + assert(valid(v)); + assign_[v] = (lev << level_shift) + trueValue(p); + reason_[v] = x; + if constexpr (sizeof...(DataT) > 0) { + reason_.setData(v, data...); + } + trail.push_back(p); + return true; + } + return val == trueValue(p); + } + //! Undos all assignments in the range trail[first, last). + /*! + * \param first First assignment to be undone. + * \param save If true, previous assignment of a var is saved before it is undone. + */ + void undoTrail(LitVec::size_type first, bool save) { + if (auto stop = trail[first]; not save) { + popUntil(stop); + } + else { + requestPrefs(); + popUntil(stop); + } + qReset(); + } + //! Undos the last assignment. + void undoLast() { + clear(trail.back().var()); + trail.pop_back(); + } + //! Returns the last assignment as a true literal. + [[nodiscard]] Literal last() const { return trail.back(); } + Literal& last() { return trail.back(); } + /*! + * \name Implementation functions + * Low-level implementation functions. Use with care and only if you + * know what you are doing! + */ + //@{ + [[nodiscard]] uint32_t units() const { return units_; } + [[nodiscard]] bool seen(Var_t v) const { return Potassco::test_any(assign_[v], seen_mask_v); } + [[nodiscard]] bool seen(Literal p) const { return Potassco::test_any(assign_[p.var()], seen_mask(p)); } + void values(ValueVec& out) const { + out.clear(); + out.reserve(assign_.size()); + for (auto x : assign_) { out.push_back(static_cast(x & value_mask)); } + } + void setSeen(Var_t v) { Potassco::store_set_mask(assign_[v], seen_mask_v); } + void setSeen(Literal p) { Potassco::store_set_mask(assign_[p.var()], seen_mask(p)); } + void clearSeen(Var_t v) { Potassco::store_clear_mask(assign_[v], seen_mask_v); } + void clearValue(Var_t v) { Potassco::store_clear_mask(assign_[v], value_mask); } + void setValue(Var_t v, Val_t val) { + assert(value(v) == val || value(v) == value_free); + assign_[v] |= val; + } + void setReason(Var_t v, const Antecedent& a) { reason_[v] = a; } + void setData(Var_t v, uint32_t data) { reason_.setData(v, data); } + void setPref(Var_t v, ValueSet::Value which, Val_t to) { pref_[v].set(which, to); } + bool markUnits() { + while (units_ != front) { setSeen(trail[units_++].var()); } + return true; + } + void setUnits(uint32_t ts) { units_ = ts; } + void resetPrefs() { pref_.assign(pref_.size(), ValueSet()); } + void clear(Var_t v) { assign_[v] = 0; } + //@} private: - static const uint32 elim_mask = uint32(0xFFFFFFF0u); - Assignment(const Assignment&); - Assignment& operator=(const Assignment&); - template - void popUntil(Literal stop) { - Literal p; - do { - p = trail.back(); trail.pop_back(); - (this->*op)(p.var()); - } while (p != stop); - } - AssignVec assign_; // for each var: three-valued assignment - ReasonVec reason_; // for each var: reason for being assigned (+ optional data) - PrefVec pref_; // for each var: set of preferred values - uint32 elims_; // number of variables that were eliminated from the assignment - uint32 units_; // number of marked top-level assignments + static constexpr uint32_t elim_mask = 0xFFFFFFF0u; + static constexpr uint32_t seen_mask_v = 0b1100u; + static constexpr uint32_t value_mask = 0b0011u; + static constexpr uint32_t level_shift = 4u; + static constexpr uint32_t seen_mask(Literal p) { return static_cast(trueValue(p)) << 2u; } + + template + void popUntil(Literal stop) { + Literal p; + do { + p = trail.back(); + trail.pop_back(); + auto v = p.var(); + if constexpr (SaveVal) { + pref_[v].save(value(v)); + } + clear(v); + } while (p != stop); + } + AssignVec assign_; // for each var: three-valued assignment (28-bit decision level, 2-bit seen, 2-bit value) + ReasonVec reason_; // for each var: reason for being assigned (+ optional data) + PrefVec pref_; // for each var: set of preferred values + uint32_t elims_{0}; // number of variables that were eliminated from the assignment + uint32_t units_{0}; // number of marked top-level assignments }; //! Stores information about a literal that is implied on an earlier level than the current decision level. struct ImpliedLiteral { - typedef Assignment::ReasonWithData AnteInfo; - ImpliedLiteral(Literal a_lit, uint32 a_level, const Antecedent& a_ante, uint32 a_data = UINT32_MAX) - : lit(a_lit) - , level(a_level) - , ante(a_ante, a_data) { - } - Literal lit; /**< The implied literal */ - uint32 level; /**< The earliest decision level on which lit is implied */ - AnteInfo ante; /**< The reason why lit is implied on decision-level level */ + using AnteInfo = Assignment::ReasonWithData; + ImpliedLiteral(Literal a_lit, uint32_t a_level, const Antecedent& a_ante, uint32_t a_data = UINT32_MAX) + : lit(a_lit) + , level(a_level) + , ante(a_ante, a_data) {} + Literal lit; /**< The implied literal */ + uint32_t level; /**< The earliest decision level on which lit is implied */ + AnteInfo ante; /**< The reason why lit is implied on decision-level level */ }; //! A type for storing ImpliedLiteral objects. struct ImpliedList { - typedef PodVector::type VecType; - typedef VecType::const_iterator iterator; - ImpliedList() : level(0), front(0) {} - //! Searches for an entry

in list. Returns 0 if none is found. - ImpliedLiteral* find(Literal p) { - for (VecType::size_type i = 0, end = lits.size(); i != end; ++i) { - if (lits[i].lit == p) { return &lits[i]; } - } - return 0; - } - //! Adds a new object to the list. - void add(uint32 dl, const ImpliedLiteral& n) { - if (dl > level) { level = dl; } - lits.push_back(n); - } - //! Returns true if list contains entries that must be reassigned on current dl. - bool active(uint32 dl) const { return dl < level && front != lits.size(); } - //! Reassigns all literals that are still implied. - bool assign(Solver& s); - iterator begin() const { return lits.begin(); } - iterator end() const { return lits.end(); } - VecType lits; // current set of (out-of-order) implied literals - uint32 level; // highest dl on which lits must be reassigned - uint32 front; // current starting position in lits + using VecType = PodVector_t; + using iterator = VecType::const_iterator; // NOLINT + ImpliedList() = default; + //! Searches for an entry

in list. Returns nullptr if none is found. + ImpliedLiteral* find(Literal p) { + for (auto& x : lits) { + if (x.lit == p) { + return &x; + } + } + return nullptr; + } + //! Adds a new object to the list. + void add(uint32_t dl, const ImpliedLiteral& n) { + if (dl > level) { + level = dl; + } + lits.push_back(n); + } + //! Returns true if list contains entries that must be reassigned on current dl. + [[nodiscard]] bool active(uint32_t dl) const { return dl < level && front != lits.size(); } + //! Reassigns all literals that are still implied. + bool assign(Solver& s); + [[nodiscard]] iterator begin() const { return lits.begin(); } + [[nodiscard]] iterator end() const { return lits.end(); } + VecType lits; // current set of (out-of-order) implied literals + uint32_t level{0}; // highest dl on which lits must be reassigned + uint32_t front{0}; // current starting position in lits }; + +using SolverSet = Potassco::Bitset; + //@} -} -#endif +} // namespace Clasp diff --git a/clasp/statistics.h b/clasp/statistics.h index 970c2ce..8758a40 100644 --- a/clasp/statistics.h +++ b/clasp/statistics.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2016-2017 Benjamin Kaufmann +// Copyright (c) 2016-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,225 +23,246 @@ // //! \file //! \brief Types and functions for accessing statistics. -#ifndef CLASP_STATISTICS_H_INCLUDED -#define CLASP_STATISTICS_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include -#include + #include +#include #include namespace Clasp { -template -double _getValue(const T* v) { return static_cast(*v); } - //! Discriminated union representing either a single statistic value or a composite. class StatisticObject { public: - typedef Potassco::Statistics_t Type; - struct Hasher { std::size_t operator()(const StatisticObject& o) const { return o.hash(); } }; - //! Creates an empty (invalid) object. - StatisticObject(); - //! Creates a Value object - static_cast(*obj) shall be valid. - template - static StatisticObject value(const T* obj) { - return value(obj); - } - //! Creates a mapped Value object: f(obj) -> double - template - static StatisticObject value(const T* obj) { - return StatisticObject(obj, registerValue()); - } - //! Creates a Map object. - /*! - * The following expression shall be valid: - * obj->size(): shall return the number of keys in obj - * obj->key(i): shall return the i'th key of this object (i >= 0). - * obj->at(const char* k): shall return the StatisticObject under the given key. - * If k is invalid, shall either throw an exception or return an empty object. - */ - template - static StatisticObject map(const T* obj) { - return StatisticObject(obj, registerMap()); - } - //! Creates an Array object. - /*! - * The following expression shall be valid: - * obj->size(): shall return the size of the array. - * obj->at(i): shall return the StatisticObject under the given key i >= 0. - * If i is invalid, shall either throw an exception or return an empty object. - */ - template - static StatisticObject array(const T* obj) { - return StatisticObject(obj, registerArray()); - } - //! Returns the type of this object. - Type type() const; - //! Returns whether this object is empty. - bool empty() const; - //! Returns the number of children of this object or 0 if this is not a composite object. - uint32 size() const; + using Type = Potassco::StatisticsType; + //! Creates a Type::value object with value 0.0. + StatisticObject(); + //! Creates a Type::value object - static_cast(*obj) shall be valid. + template + static StatisticObject value(const T* obj) { + return value(obj); + } + //! Creates a mapped Type::value object: f(obj) -> double + template + static StatisticObject value(const T* obj) { + return StatisticObject(obj, registerValue()); + } + //! Creates a Type::map object. + /*! + * The following expression shall be valid: + * - obj->size(): shall return the number of keys in obj + * - obj->key(i): shall return the i-th key of this object (i >= 0). + * - obj->at(const char* k): shall return the StatisticObject under the given key. + * If k is invalid, shall either throw an exception or return an empty object. + */ + template + static StatisticObject map(const T* obj) { + return StatisticObject(obj, registerMap()); + } + //! Creates a Type::array object. + /*! + * The following expression shall be valid: + * - obj->size(): shall return the size of the array. + * - obj->at(i): shall return the StatisticObject under the given index i >= 0. + * If the index is invalid, shall either throw an exception or return an empty object. + */ + template + static StatisticObject array(const T* obj) { + return StatisticObject(obj, registerArray()); + } + //! Returns the type of this object. + [[nodiscard]] Type type() const; + //! Returns the number of children of this object or 0 if this is not a composite object. + [[nodiscard]] uint32_t size() const; + + /*! + * \name Map + * \pre type() == Type::map + */ + //@{ + //! Returns the i-th key of this map. + /*! + * \pre i < size() + */ + [[nodiscard]] const char* key(uint32_t i) const; + //! Returns the object under the given key. + /*! + * \pre k in key([0;size())) + */ + StatisticObject at(const char* k) const; + //@} - /*! - * \name Map - * \pre type() == Map - */ - //@{ - //! Returns the i'th key of this map. - /*! - * \pre i < size() - */ - const char* key(uint32 i) const; - //! Returns the object under the given key. - /*! - * \pre k in key([0..size())) - */ - StatisticObject at(const char* k) const; - //@} + //! Returns the object at the given index. + /*! + * \pre type() == Type::array + * \pre i < size() + */ + StatisticObject operator[](uint32_t i) const; - //! Returns the object at the given index. - /*! - * \pre Type() == Array - * \pre i < size() - */ - StatisticObject operator[](uint32 i) const; + //! Returns the value of this object. + /*! + * \pre type() == Type::value + */ + [[nodiscard]] double value() const; - //! Returns the value of this object. - /*! - * \pre type() == Value - */ - double value() const; + [[nodiscard]] std::size_t hash() const; + [[nodiscard]] uint64_t toRep() const; + [[nodiscard]] const void* self() const; + [[nodiscard]] std::size_t typeId() const; + static StatisticObject fromRep(uint64_t); + + bool operator==(const StatisticObject&) const = default; + auto operator<=>(const StatisticObject&) const = default; - bool operator==(const StatisticObject& rhs) const { - return this->handle_ == rhs.handle_; - } - bool operator<(const StatisticObject& rhs) const { - return this->handle_ < rhs.handle_; - } - std::size_t hash() const; - uint64 toRep() const; - const void* self() const; - std::size_t typeId()const; - static StatisticObject fromRep(uint64); private: - struct I { - typedef const void* ObjPtr; - explicit I(Type t) : type(t) {} - Type type; - }; - struct V : I { - V(double(*v)(ObjPtr)) : I(Potassco::Statistics_t::Value), value(v) {} - double(*value)(ObjPtr); - }; - struct A : I { - A(uint32(*sz)(ObjPtr), StatisticObject(*a)(ObjPtr, uint32)) : I(Potassco::Statistics_t::Array), size(sz), at(a) {} - uint32(*size)(ObjPtr); - StatisticObject(*at)(ObjPtr, uint32); - }; - struct M : I { - M(uint32(*sz)(ObjPtr), StatisticObject(*a)(ObjPtr, const char*), const char* (*k)(ObjPtr, uint32)) : I(Potassco::Statistics_t::Map), size(sz), at(a), key(k) {} - uint32(*size)(ObjPtr); - StatisticObject(*at)(ObjPtr, const char*); - const char* (*key)(ObjPtr, uint32); - }; - static uint32 registerType(const I* vtab) { - types_s.push_back(vtab); - return static_cast(types_s.size() - 1); - } - template - static uint32 registerValue(); - template - static uint32 registerMap(); - template - static uint32 registerArray(); - StatisticObject(const void* obj, uint32 type); + template + static double toDouble(const T* v) { + return static_cast(*v); + } + struct I { + using ObjPtr = const void*; + explicit I(Type t) : type(t) {} + template + const T* as() const { + return static_cast(this); + } + Type type; + }; + struct V : I { + explicit V(double (*v)(ObjPtr)) : I(Type::value), value(v) {} + double (*value)(ObjPtr); + }; + struct A : I { + A(uint32_t (*sz)(ObjPtr), StatisticObject (*a)(ObjPtr, uint32_t)) : I(Type::array), size(sz), at(a) {} + uint32_t (*size)(ObjPtr); + StatisticObject (*at)(ObjPtr, uint32_t); + }; + struct M : I { + M(uint32_t (*sz)(ObjPtr), StatisticObject (*a)(ObjPtr, const char*), const char* (*k)(ObjPtr, uint32_t)) + : I(Type::map) + , size(sz) + , at(a) + , key(k) {} + uint32_t (*size)(ObjPtr); + StatisticObject (*at)(ObjPtr, const char*); + const char* (*key)(ObjPtr, uint32_t); + }; + static uint32_t registerType(const I* vtab) { + s_types_.push_back(vtab); + return size32(s_types_) - 1; + } + template + static uint32_t registerValue(); + template + static uint32_t registerMap(); + template + static uint32_t registerArray(); + StatisticObject(const void* obj, uint32_t type); + explicit StatisticObject(uint64_t h); - typedef PodVector::type RegVec; - const I* tid() const; - static I empty_s; - static RegVec types_s; - uint64 handle_; + using RegVec = PodVector_t; + [[nodiscard]] const I* tid() const; + static V s_empty_; + static RegVec s_types_; + uint64_t handle_; }; -template -uint32 StatisticObject::registerArray() { - static const struct Array_T : A { - Array_T() : A(&Array_T::size, &Array_T::at) {} - static uint32 size(ObjPtr obj) { return toU32(static_cast(obj)->size()); } - static StatisticObject at(ObjPtr obj, uint32 i) { return static_cast(obj)->at(i); } - } vtab_s; - static const uint32 id = registerType(&vtab_s); - return id; +template +uint32_t StatisticObject::registerArray() { + static const struct Array_t : A { + Array_t() : A(&Array_t::size, &Array_t::at) {} + static uint32_t size(ObjPtr obj) { return toU32(static_cast(obj)->size()); } + static StatisticObject at(ObjPtr obj, uint32_t i) { return static_cast(obj)->at(i); } + } vtab_s; + static const uint32_t id = registerType(&vtab_s); + return id; } -template -uint32 StatisticObject::registerMap() { - static const struct Map_T : M { - Map_T() : M(&Map_T::size, &Map_T::at, &Map_T::key) {} - static inline const T* cast(ObjPtr obj) { return static_cast(obj); } - static uint32 size(ObjPtr obj) { return cast(obj)->size(); } - static StatisticObject at(ObjPtr obj, const char* k) { return cast(obj)->at(k); } - static const char* key(ObjPtr obj, uint32 i) { return cast(obj)->key(i); } - } vtab_s; - static const uint32 id = registerType(&vtab_s); - return id; +template +uint32_t StatisticObject::registerMap() { + static const struct Map_t : M { + Map_t() : M(&Map_t::size, &Map_t::at, &Map_t::key) {} + static const T* cast(ObjPtr obj) { return static_cast(obj); } + static uint32_t size(ObjPtr obj) { return cast(obj)->size(); } + static StatisticObject at(ObjPtr obj, const char* k) { return cast(obj)->at(k); } + static const char* key(ObjPtr obj, uint32_t i) { return cast(obj)->key(i); } + } vtab_s; + static const uint32_t id = registerType(&vtab_s); + return id; } -template -uint32 StatisticObject::registerValue() { - static const struct Value_T : V { - Value_T() : V(&Value_T::value) {} - static double value(ObjPtr obj) { return f(static_cast(obj)); } - } vtab_s; - static const uint32 id = StatisticObject::registerType(&vtab_s); - return id; +template +uint32_t StatisticObject::registerValue() { + static const struct Value_t : V { + Value_t() : V(&Value_t::value) {} + static double value(ObjPtr obj) { return Fun(static_cast(obj)); } + } vtab_s; + static const uint32_t id = StatisticObject::registerType(&vtab_s); + return id; } //! A type that maps string keys to statistic objects. class StatsMap { public: - // StatisticObject - uint32 size() const { return sizeVec(keys_); } - const char* key(uint32 i) const { return keys_.at(i).first; } - StatisticObject at(const char* k) const; - // Own interface - const StatisticObject* find(const char* k) const; - bool add(const char* k, const StatisticObject&); - void push(const char* k, const StatisticObject&); - StatisticObject toStats() const { return StatisticObject::map(this); } + // StatisticObject + [[nodiscard]] uint32_t size() const { return size32(keys_); } + [[nodiscard]] const char* key(uint32_t i) const { return keys_.at(i).first; } + StatisticObject at(const char* k) const; + // Own interface + const StatisticObject* find(const char* k) const; + bool add(const char* k, const StatisticObject&); + void push(const char* k, const StatisticObject&); + [[nodiscard]] StatisticObject toStats() const { return StatisticObject::map(this); } + private: - typedef PodVector >::type MapType; - MapType keys_; + using MapType = PodVector_t>; + MapType keys_; }; //! An array of statistic objects. -template -class StatsVec : private PodVector::type { +template +class StatsVec : private PodVector_t { public: - StatsVec() : own_(true) {} - ~StatsVec() { - if (own_) { for (iterator it = this->begin(), end = this->end(); it != end; ++it) { delete *it; } } - } - typedef typename PodVector::type base_type; - typedef typename base_type::const_iterator const_iterator; - typedef typename base_type::iterator iterator; - using base_type::size; - using base_type::operator[]; - using base_type::begin; - using base_type::end; - void growTo(uint32 newSize) { if (newSize > size()) this->resize(newSize); } - void reset() { for (iterator it = this->begin(), end = this->end(); it != end; ++it) { (*it)->reset(); } } - StatisticObject at(uint32 i) const { return get_(this->base_type::at(i), bk_lib::detail::int2type()); } - StatisticObject toStats() const { return StatisticObject::array(this); } - void acquire() { own_ = true; } - void release() { own_ = false; } + StatsVec() = default; + ~StatsVec() { + if (own_) { + for (auto* s : *this) { delete s; } + } + } + StatsVec(const StatsVec&) = delete; + StatsVec& operator=(const StatsVec&) = delete; + + using base_type = PodVector_t; // NOLINT + using const_iterator = typename base_type::const_iterator; // NOLINT + using iterator = typename base_type::iterator; // NOLINT + using base_type::size; + using base_type::operator[]; + using base_type::begin; + using base_type::empty; + using base_type::end; + void growTo(uint32_t newSize) { + if (newSize > size()) { + this->resize(newSize); + } + } + void reset() { + for (T* s : *this) { s->reset(); } + } + [[nodiscard]] StatisticObject at(uint32_t i) const { + const T* ptr = this->base_type::at(i); + if constexpr (ElemType == Potassco::StatisticsType::map) { + return StatisticObject::map(ptr); + } + else if constexpr (ElemType == Potassco::StatisticsType::array) { + return StatisticObject::array(ptr); + } + else { + static_assert(ElemType == Potassco::StatisticsType::value, "invalid element type"); + return StatisticObject::value(ptr); + } + } + [[nodiscard]] StatisticObject toStats() const { return StatisticObject::array(this); } + void acquire() { own_ = true; } + void release() { own_ = false; } + private: - static StatisticObject get_(const T* ptr, bk_lib::detail::int2type) { return StatisticObject::map(ptr); } - static StatisticObject get_(const T* ptr, bk_lib::detail::int2type) { return StatisticObject::array(ptr); } - static StatisticObject get_(const T* ptr, bk_lib::detail::int2type) { return StatisticObject::value(ptr); } - StatsVec(const StatsVec&); - StatsVec& operator=(const StatsVec&); - bool own_; + bool own_{true}; }; //! A class for traversing, querying, and adding statistics. @@ -250,45 +271,42 @@ class StatsVec : private PodVector::type { */ class ClaspStatistics : public Potassco::AbstractStatistics { public: - typedef Potassco::Statistics_t Type; - ClaspStatistics(); - ClaspStatistics(StatisticObject root); - ~ClaspStatistics(); + ClaspStatistics(); + explicit ClaspStatistics(StatisticObject root); + ~ClaspStatistics() override; + ClaspStatistics(ClaspStatistics&&) = delete; + + StatsMap* makeRoot(); - StatsMap* makeRoot(); + // Base interface + [[nodiscard]] Key_t root() const override; + [[nodiscard]] Type type(Key_t key) const override; + [[nodiscard]] size_t size(Key_t key) const override; + [[nodiscard]] bool writable(Key_t key) const override; + [[nodiscard]] Key_t at(Key_t arrK, size_t index) const override; + Key_t push(Key_t arr, Type type) override; + [[nodiscard]] const char* key(Key_t mapK, size_t i) const override; + Key_t get(Key_t mapK, const char* key) const override; + bool find(Key_t mapK, const char* element, Key_t* outKey) const override; + Key_t add(Key_t mapK, const char* name, Type type) override; + [[nodiscard]] double value(Key_t key) const override; + void set(Key_t key, double value) override; - // Base interface - virtual Key_t root() const; - virtual Type type(Key_t key) const; - virtual size_t size(Key_t key) const; - virtual bool writable(Key_t key) const; - virtual Key_t at(Key_t arrK, size_t index) const; - virtual Key_t push(Key_t arr, Type type); - virtual const char* key(Key_t mapK, size_t i) const; - virtual Key_t get(Key_t mapK, const char* key) const; - virtual bool find(Key_t mapK, const char* element, Key_t* outKey) const; - virtual Key_t add(Key_t mapK, const char* name, Type type); - virtual double value(Key_t key) const; - virtual void set(Key_t key, double value); + Key_t changeRoot(Key_t newRoot); + bool removeStat(const StatisticObject&, bool recurse); + bool removeStat(Key_t k, bool recurse); - Key_t changeRoot(Key_t newRoot); - bool removeStat(const StatisticObject&, bool recurse); - bool removeStat(Key_t k, bool recurse); + // Remove unreachable stats + void update(); + [[nodiscard]] StatisticObject getObject(Key_t k) const; - // Remove unreachable stats - void update(); - StatisticObject findObject(Key_t root, const char* path, Key_t* track = 0) const; - StatisticObject getObject(Key_t k) const; private: - ClaspStatistics(const ClaspStatistics&); - ClaspStatistics& operator=(const ClaspStatistics&); - struct Impl; - Impl* impl_; + StatisticObject findObject(Key_t root, const char* path, Key_t* track = nullptr) const; + struct Impl; + std::unique_ptr impl_; }; struct SolverStats; -struct JumpStats; -struct ExtendedStats; struct ProblemStats; //! Interface for visiting statistics. @@ -297,22 +315,21 @@ struct ProblemStats; */ class StatsVisitor { public: - enum Operation { Enter, Leave } ; - virtual ~StatsVisitor(); - // compound - virtual bool visitGenerator(Operation op); // default: return true - virtual bool visitThreads(Operation op); // default: return true - virtual bool visitTester(Operation op); // default: return true - virtual bool visitHccs(Operation op); // default: return true + enum Operation { enter, leave }; + virtual ~StatsVisitor(); + // compound + virtual bool visitGenerator(Operation op); // default: return true + virtual bool visitThreads(Operation op); // default: return true + virtual bool visitTester(Operation op); // default: return true + virtual bool visitHccs(Operation op); // default: return true - // leafs - virtual void visitThread(uint32, const SolverStats& stats); - virtual void visitHcc(uint32, const ProblemStats& p, const SolverStats& s); - virtual void visitLogicProgramStats(const Asp::LpStats& stats) = 0; - virtual void visitProblemStats(const ProblemStats& stats) = 0; - virtual void visitSolverStats(const SolverStats& stats) = 0; - virtual void visitExternalStats(const StatisticObject& stats) = 0; + // leafs + virtual void visitThread(uint32_t, const SolverStats& stats); + virtual void visitHcc(uint32_t, const ProblemStats& p, const SolverStats& s); + virtual void visitLogicProgramStats(const Asp::LpStats& stats) = 0; + virtual void visitProblemStats(const ProblemStats& stats) = 0; + virtual void visitSolverStats(const SolverStats& stats) = 0; + virtual void visitExternalStats(const StatisticObject& stats) = 0; }; -} -#endif +} // namespace Clasp diff --git a/clasp/unfounded_check.h b/clasp/unfounded_check.h index ea7a9cd..7f55afc 100644 --- a/clasp/unfounded_check.h +++ b/clasp/unfounded_check.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2010-2017 Benjamin Kaufmann +// Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,17 +21,12 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_UNFOUNDED_CHECK_H_INCLUDED -#define CLASP_UNFOUNDED_CHECK_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif -#include -#include -#include + #include +#include +#include +#include namespace Clasp { class LoopFormula; @@ -41,270 +36,215 @@ class LoopFormula; * Searches for unfounded atoms by checking the positive dependency graph (PDG) * * Basic Idea: - * - For each (non-false) atom a, let source(a) be a body B in body(a) that provides an external support for a - * - If no such B exists, a must be false - * - If source(a) becomes false and a is not false: + * - For each (non-false) atom 'a', let source(a) be a body B in body(a) that provides an external support for 'a' + * - If no such B exists, 'a' must be false + * - If source(a) becomes false and 'a' is not false: * - Let Q = {}; - * - add a to Q + * - add 'a' to Q * - For each B' s.th B' is not external to Q * - add { a' | source(a') = B } to Q - * - Try to find new sources for all atoms a in Q + * - Try to find new sources for all atoms 'a' in Q */ class DefaultUnfoundedCheck : public PostPropagator { public: - typedef Asp::PrgDepGraph DependencyGraph; - typedef DependencyGraph::NodeId NodeId; - typedef DependencyGraph::BodyNode BodyNode; - typedef DependencyGraph::AtomNode AtomNode; - typedef const DependencyGraph* ConstGraphPtr; - typedef DependencyGraph* GraphPtr; - //! Defines the supported reasons for explaining assignments. - enum ReasonStrategy { - common_reason = LoopReason_t::Explicit, /*!< one reason for each unfounded set but one clause for each atom */ - only_reason = LoopReason_t::Implicit, /*!< store only the reason but don't learn a nogood */ - distinct_reason, /*!< distinct reason and clause for each unfounded atom */ - shared_reason, /*!< one shared loop formula for each unfounded set */ - no_reason, /*!< do no compute reasons for unfounded sets (only valid if learning is disabled!) */ - }; + using DependencyGraph = Asp::PrgDepGraph; + using NodeId = DependencyGraph::NodeId; + using BodyNode = DependencyGraph::BodyNode; + using AtomNode = DependencyGraph::AtomNode; + using ConstGraphPtr = const DependencyGraph*; + using GraphPtr = DependencyGraph*; + //! Defines the supported reasons for explaining assignments. + enum ReasonStrategy { + common_reason = 0, /*!< one reason for each unfounded set but one clause for each atom */ + only_reason = 1, /*!< store only the reason but don't learn a nogood */ + distinct_reason, /*!< distinct reason and clause for each unfounded atom */ + shared_reason, /*!< one shared loop formula for each unfounded set */ + no_reason, /*!< do no compute reasons for unfounded sets (only valid if learning is disabled!) */ + }; - explicit DefaultUnfoundedCheck(DependencyGraph& graph, ReasonStrategy st = common_reason); - ~DefaultUnfoundedCheck(); + explicit DefaultUnfoundedCheck(DependencyGraph& graph, ReasonStrategy st = common_reason); + ~DefaultUnfoundedCheck() override; + DefaultUnfoundedCheck(DefaultUnfoundedCheck&&) = delete; - ReasonStrategy reasonStrategy() const { return strategy_; } - void setReasonStrategy(ReasonStrategy rs); + [[nodiscard]] ReasonStrategy reasonStrategy() const { return strategy_; } + void setReasonStrategy(ReasonStrategy rs); - ConstGraphPtr graph() const { return graph_; } - uint32 nodes() const { return static_cast(atoms_.size() + bodies_.size()); } + [[nodiscard]] ConstGraphPtr graph() const { return graph_; } + [[nodiscard]] uint32_t nodes() const { return size32(atoms_) + size32(bodies_); } + + // base interface + [[nodiscard]] uint32_t priority() const override { return priority_reserved_ufs; } + bool init(Solver&) override; + void reset() override; + bool propagateFixpoint(Solver& s, PostPropagator* ctx) override; + bool isModel(Solver& s) override; + bool valid(Solver& s) override; + bool simplify(Solver& s, bool) override; + void destroy(Solver* s, bool detach) override; - // base interface - uint32 priority() const { return uint32(priority_reserved_ufs); } - bool init(Solver&); - void reset(); - bool propagateFixpoint(Solver& s, PostPropagator* ctx); - bool isModel(Solver& s); - bool valid(Solver& s); - bool simplify(Solver& s, bool); - void destroy(Solver* s, bool detach); private: - DefaultUnfoundedCheck(const DefaultUnfoundedCheck&); - DefaultUnfoundedCheck& operator=(const DefaultUnfoundedCheck&); - enum UfsType { - ufs_none, - ufs_poly, - ufs_non_poly - }; - enum WatchType { - watch_source_false = 0, - watch_head_false = 1, - watch_head_true = 2, - watch_subgoal_false= 3, - }; - // data for each body - struct BodyData { - BodyData() : watches(0), picked(0) {} - uint32 watches : 31; // how many atoms watch this body as source? - uint32 picked : 1; // flag used in computeReason() - uint32 lower_or_ext; // unsourced preds or index of extended body - }; - struct BodyPtr { - BodyPtr(const BodyNode* n, uint32 i) : node(n), id(i) {} - const BodyNode* node; - uint32 id; - }; - // data for extended bodies - struct ExtData { - ExtData(weight_t bound, uint32 preds) : lower(bound), slack(-bound) { - for (uint32 i = 0; i != flagSize(preds); ++i) { flags[i] = 0; } - } - bool addToWs(uint32 idx, weight_t w) { - const uint32 fIdx = (idx / 32); - const uint32 m = (1u << (idx & 31)); - assert((flags[fIdx] & m) == 0); - flags[fIdx] |= m; - return (lower -= w) <= 0; - } - bool inWs(uint32 idx) const { - const uint32 fIdx = (idx / 32); - const uint32 m = (1u << (idx & 31)); - return (flags[fIdx] & m) != 0; - } - void removeFromWs(uint32 idx, weight_t w) { - if (inWs(idx)) { - lower += w; - flags[(idx / 32)] &= ~(uint32(1) << (idx & 31)); - } - } - static uint32 flagSize(uint32 preds) { return (preds+31)/32; } - weight_t lower; - weight_t slack; -POTASSCO_WARNING_BEGIN_RELAXED - uint32 flags[0]; -POTASSCO_WARNING_END_RELAXED - }; - // data for each atom - struct AtomData { - AtomData() : source(nill_source), todo(0), ufs(0), validS(0) {} - // returns the body that is currently watched as possible source - NodeId watch() const { return source; } - // returns true if atom has currently a source, i.e. a body that can still define it - bool hasSource() const { return validS; } - // mark source as invalid but keep the watch - void markSourceInvalid() { validS = 0; } - // restore validity of source - void resurrectSource() { validS = 1; } - // sets b as source for this atom - void setSource(NodeId b) { - source = b; - validS = 1; - } - static const uint32 nill_source = (uint32(1) << 29)-1; - uint32 source : 29; // id of body currently watched as source - uint32 todo : 1; // in todo-queue? - uint32 ufs : 1; // in ufs-queue? - uint32 validS : 1; // is source valid? - }; - // Watch-structure used to update extended bodies affected by literal assignments - struct ExtWatch { - NodeId bodyId; - uint32 data; - }; - // Minimality checker for disjunctive logic programs. - struct MinimalityCheck { - typedef SolveParams::FwdCheck FwdCheck; - explicit MinimalityCheck(const FwdCheck& fwd); - bool partialCheck(uint32 level); - void schedNext(uint32 level, bool ok); - FwdCheck fwd; - uint32 high; - uint32 low; - uint32 next; - uint32 scc; - }; - // ------------------------------------------------------------------------------------------- - // constraint interface - PropResult propagate(Solver&, Literal, uint32& data) { - uint32 index = data >> 2; - uint32 type = (data & 3u); - if (type != watch_source_false || bodies_[index].watches) { - invalidQ_.push_back(data); - } - return PropResult(true, true); - } - void reason(Solver& s, Literal, LitVec&); - // ------------------------------------------------------------------------------------------- - // initialization - BodyPtr getBody(NodeId bId) const { return BodyPtr(&graph_->getBody(bId), bId); } - void initBody(const BodyPtr& n); - void initExtBody(const BodyPtr& n); - void initSuccessors(const BodyPtr& n, weight_t lower); - void addWatch(Literal, uint32 data, WatchType type); - void addExtWatch(Literal p, const BodyPtr& n, uint32 data); - struct InitExtWatches { - void operator()(Literal p, uint32 idx, bool ext) const { - extra->slack += B->node->pred_weight(idx, ext); - self->addExtWatch(~p, *B, (idx<<1)+uint32(ext)); - if (ext && !self->solver_->isFalse(p)) { - extra->addToWs(idx, B->node->pred_weight(idx, true)); - } - } - DefaultUnfoundedCheck* self; - const BodyPtr* B; - ExtData* extra; - }; - struct RemExtWatches { - void operator()(Literal p, uint32, bool) const { s->removeWatch(~p, self); } - Constraint* self; - Solver* s; - }; - // ------------------------------------------------------------------------------------------- - // propagating source pointers - void propagateSource(); - struct AddSource { // an atom in a body has a new source, check if body is now a valid source - explicit AddSource(DefaultUnfoundedCheck* u) : self(u) {} - // normal body - void operator()(NodeId bId) const { - BodyPtr n(self->getBody(bId)); - if (--self->bodies_[bId].lower_or_ext == 0 && !self->solver_->isFalse(n.node->lit)) { self->forwardSource(n); } - } - // extended body - void operator()(NodeId bId, uint32 idx) const; - DefaultUnfoundedCheck* self; - }; - struct RemoveSource {// an atom in a body has lost its source, check if body is no longer a valid source - explicit RemoveSource(DefaultUnfoundedCheck* u, bool add = false) : self(u), addTodo(add) {} - // normal body - void operator()(NodeId bId) const { - if (++self->bodies_[bId].lower_or_ext == 1 && self->bodies_[bId].watches != 0) { - self->forwardUnsource(self->getBody(bId), addTodo); - } - } - // extended body - void operator()(NodeId bId, uint32 idx) const; - DefaultUnfoundedCheck* self; - bool addTodo; - }; - void setSource(NodeId atom, const BodyPtr& b); - void removeSource(NodeId bodyId); - void forwardSource(const BodyPtr& n); - void forwardUnsource(const BodyPtr& n, bool add); - void updateSource(AtomData& atom, const BodyPtr& n); - // ------------------------------------------------------------------------------------------- - // finding & propagating unfounded sets - void updateAssignment(Solver& s); - bool findSource(NodeId atom); - bool isValidSource(const BodyPtr&); - void addUnsourced(const BodyPtr&); - bool falsifyUfs(UfsType t); - bool assertAtom(Literal a, UfsType t); - void computeReason(UfsType t); - void addIfReason(const BodyPtr&, uint32 uScc); - bool isExternal(const BodyPtr&, weight_t& slack) const; - void addDeltaReason(const BodyPtr& body, uint32 uScc); - void addReasonLit(Literal); - void createLoopFormula(); - struct AddReasonLit { - void operator()(Literal p, NodeId id, bool ext) const { - if (self->solver_->isFalse(p) && slack >= 0) { - slack -= node->pred_weight(id, ext); - self->addReasonLit(p); - } - } - DefaultUnfoundedCheck* self; - const BodyNode* node; - mutable weight_t slack; - }; - UfsType findUfs(Solver& s, bool checkNonHcf); - UfsType findNonHcfUfs(Solver& s); - // ------------------------------------------------------------------------------------------- - bool pushTodo(NodeId at) { return (atoms_[at].todo == 0 && (todo_.push(at), atoms_[at].todo = 1) != 0); } - bool pushUfs(NodeId at) { return (atoms_[at].ufs == 0 && (ufs_.push(at), atoms_[at].ufs = 1) != 0); } - void resetTodo() { while (!todo_.empty()){ atoms_[todo_.pop_ret()].todo = 0; } todo_.clear(); } - void resetUfs() { while (!ufs_.empty()) { atoms_[ufs_.pop_ret()].ufs = 0; } ufs_.clear(); } - // ------------------------------------------------------------------------------------------- - typedef PodVector::type AtomVec; - typedef PodVector::type BodyVec; - typedef PodVector::type ExtVec; - typedef PodVector::type WatchVec; - typedef PodQueue IdQueue; - typedef SingleOwnerPtr MiniPtr; - // ------------------------------------------------------------------------------------------- - Solver* solver_; // my solver - GraphPtr graph_; // PBADG - MiniPtr mini_; // minimality checker (only for DLPs) - AtomVec atoms_; // data for each atom - BodyVec bodies_; // data for each body - IdQueue todo_; // ids of atoms that recently lost their source - IdQueue ufs_; // ids of atoms that are unfounded wrt the current assignment (limited to one scc) - VarVec invalidQ_; // ids of invalid elements to be processed - VarVec sourceQ_; // source-pointer propagation queue - ExtVec extended_; // data for each extended body - WatchVec watches_; // watches for handling choice-, cardinality- and weight rules - VarVec pickedExt_; // extended bodies visited during reason computation - LitVec loopAtoms_; // only used if strategy_ == shared_reason - LitVec activeClause_;// activeClause_[0] is the current unfounded atom - LitVec* reasons_; // only used if strategy_ == only_reason. reasons_[v] reason why v is unfounded - ConstraintInfo info_; // info on active clause - ReasonStrategy strategy_; // what kind of reasons to compute? + enum UfsType { ufs_none, ufs_poly, ufs_non_poly }; + enum WatchType : uint32_t { + watch_source_false = 0, + watch_head_false = 1, + watch_head_true = 2, + watch_subgoal_false = 3, + }; + // data for each body + struct BodyData { + uint32_t watches : 31 {0}; // how many atoms watch this body as source? + uint32_t picked : 1 {0}; // flag used in computeReason() + uint32_t lowerOrExt{0}; // unsourced preds or index of extended body + }; + struct BodyPtr { + constexpr BodyPtr(const BodyNode* n, uint32_t i) : node(n), id(i) {} + const BodyNode* node; + uint32_t id; + }; + // data for extended bodies + struct ExtData { + using SetType = Potassco::Bitset; + [[nodiscard]] static constexpr uint32_t word(uint32_t idx) { return idx / 32; } + [[nodiscard]] static constexpr uint32_t pos(uint32_t idx) { return idx & 31; } + [[nodiscard]] constexpr bool inWs(uint32_t idx) const { return flags[word(idx)].contains(pos(idx)); } + + constexpr bool addToWs(uint32_t idx, Weight_t w) { + auto r = flags[word(idx)].add(pos(idx)); + POTASSCO_DEBUG_ASSERT(r); + static_cast(r); + return (lower -= w) <= 0; + } + constexpr void removeFromWs(uint32_t idx, Weight_t w) { + if (flags[word(idx)].remove(pos(idx))) { + lower += w; + } + } + Weight_t lower{}; + Weight_t slack{}; + POTASSCO_WARNING_BEGIN_RELAXED + SetType flags[0]; + POTASSCO_WARNING_END_RELAXED + }; + // data for each atom + struct AtomData { + static constexpr uint32_t nil_source = (static_cast(1) << 29) - 1; + // returns the body that is currently watched as possible source + [[nodiscard]] constexpr NodeId watch() const { return source; } + // returns true if atom has currently a source, i.e. a body that can still define it + [[nodiscard]] constexpr bool hasSource() const { return validS; } + // mark source as invalid but keep the watch + constexpr void markSourceInvalid() { validS = 0; } + // restore validity of source + constexpr void resurrectSource() { validS = 1; } + // sets b as source for this atom + constexpr void setSource(NodeId b) { + source = b; + validS = 1; + } + + uint32_t source : 29 {nil_source}; // id of body currently watched as source + uint32_t todo : 1 {0}; // in todo-queue? + uint32_t ufs : 1 {0}; // in ufs-queue? + uint32_t validS : 1 {0}; // is source valid? + }; + // Watch-structure used to update extended bodies affected by literal assignments + struct ExtWatch { + NodeId bodyId; + uint32_t data; + }; + // Minimality checker for disjunctive logic programs. + struct MinimalityCheck { + using FwdCheck = SolveParams::FwdCheck; + explicit MinimalityCheck(const FwdCheck& fwd); + bool partialCheck(uint32_t level); + void schedNext(uint32_t level, bool ok); + FwdCheck fwd; + uint32_t high; + uint32_t low; + uint32_t next; + uint32_t scc; + }; + // ------------------------------------------------------------------------------------------- + // constraint interface + PropResult propagate(Solver&, Literal, uint32_t& data) override { + uint32_t index = data >> 2; + uint32_t type = (data & 3u); + if (type != watch_source_false || bodies_[index].watches) { + invalid_.push_back(data); + } + return PropResult(true, true); + } + void reason(Solver& s, Literal, LitVec&) override; + // ------------------------------------------------------------------------------------------- + // initialization + [[nodiscard]] BodyPtr getBody(NodeId bId) const { return {&graph_->getBody(bId), bId}; } + void initBody(const BodyPtr& n); + void initExtBody(const BodyPtr& n); + void initSuccessors(const BodyPtr& n, Weight_t lower); + void addWatch(Literal, uint32_t data, WatchType type); + void addExtWatch(Literal p, const BodyPtr& n, uint32_t data); + // ------------------------------------------------------------------------------------------- + // propagating source pointers + void propagateSource(); + void addSource(const AtomNode& atom); + void removeSource(const AtomNode& atom, bool addTodo); + void setSource(NodeId atom, const BodyPtr& b); + void removeSource(NodeId bodyId); + void forwardSource(const BodyPtr& n); + void forwardUnsource(const BodyPtr& n, bool add); + void updateSource(AtomData& atom, const BodyPtr& n); + // ------------------------------------------------------------------------------------------- + // finding & propagating unfounded sets + void updateAssignment(const Solver& s); + bool findSource(NodeId atom); + bool isValidSource(const BodyPtr&); + void addUnsourced(const BodyPtr&); + bool falsifyUfs(UfsType t); + bool assertAtom(Literal a, UfsType t); + void computeReason(UfsType t); + void addIfReason(const BodyPtr&, uint32_t uScc); + bool isExternal(const BodyPtr&, Weight_t& slack) const; + void addDeltaReason(const BodyPtr& body, uint32_t uScc); + void addReasonLit(Literal); + void createLoopFormula(); + UfsType findUfs(Solver& s, bool checkNonHcf); + UfsType findNonHcfUfs(Solver& s); + // ------------------------------------------------------------------------------------------- + bool pushTodo(NodeId at) { return atoms_[at].todo == 0 && (todo_.push(at), atoms_[at].todo = 1, true); } + bool pushUfs(NodeId at) { return atoms_[at].ufs == 0 && (ufs_.push(at), atoms_[at].ufs = 1, true); } + void resetTodo() { + while (not todo_.empty()) { atoms_[todo_.pop_ret()].todo = 0; } + todo_.clear(); + } + void resetUfs() { + while (not ufs_.empty()) { atoms_[ufs_.pop_ret()].ufs = 0; } + ufs_.clear(); + } + // ------------------------------------------------------------------------------------------- + using AtomVec = PodVector_t; + using BodyVec = PodVector_t; + using ExtVec = PodVector_t; + using WatchVec = PodVector_t; + using IdQueue = PodQueue; + using MiniPtr = std::unique_ptr; + using ReasonPtr = std::unique_ptr; + // ------------------------------------------------------------------------------------------- + Solver* solver_; // my solver + GraphPtr graph_; // PBADG + MiniPtr mini_; // minimality checker (only for DLPs) + AtomVec atoms_; // data for each atom + BodyVec bodies_; // data for each body + IdQueue todo_; // ids of atoms that recently lost their source + IdQueue ufs_; // ids of atoms that are unfounded wrt the current assignment (limited to one scc) + IdQueue sourceQ_; // source-pointer propagation queue + VarVec invalid_; // ids of invalid elements to be processed + ExtVec extended_; // data for each extended body + WatchVec watches_; // watches for handling choice-, cardinality- and weight rules + VarVec pickedExt_; // extended bodies visited during reason computation + LitVec loopAtoms_; // only used if strategy_ == shared_reason + LitVec activeClause_; // activeClause_[0] is the current unfounded atom + ReasonPtr reasons_; // only used if strategy_ == only_reason. reasons_[v] reason why v is unfounded + ConstraintInfo info_; // info on active clause + ReasonStrategy strategy_; // what kind of reasons to compute? }; -} -#endif +} // namespace Clasp diff --git a/clasp/util/hash.h b/clasp/util/hash.h deleted file mode 100644 index 0d4e17d..0000000 --- a/clasp/util/hash.h +++ /dev/null @@ -1,48 +0,0 @@ -// -// Copyright (c) 2016-2017 Benjamin Kaufmann -// -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// -#ifndef CLASP_HASH_H_INCLUDED -#define CLASP_HASH_H_INCLUDED -#include -#include -namespace Clasp { -//! Hasher for strings. -/*! - * \see http://research.microsoft.com/en-us/people/palarson/ - */ -struct StrHash { - std::size_t operator()(const char* str) const { - std::size_t h = 0; - for (const char* s = str; *s; ++s) { - h = h * 101 + static_cast(*s); - } - return h; - } -}; -//! Comparison function for C-strings to be used with hash map/set. -struct StrEq { - bool operator()(const char* lhs, const char* rhs) const { return std::strcmp(lhs, rhs) == 0; } -}; - -} -#endif diff --git a/clasp/util/indexed_priority_queue.h b/clasp/util/indexed_priority_queue.h index 0e1c82a..60206d5 100644 --- a/clasp/util/indexed_priority_queue.h +++ b/clasp/util/indexed_priority_queue.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,200 +21,154 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef BK_LIB_INDEXED_PRIORITY_QUEUE_H_INCLUDED -#define BK_LIB_INDEXED_PRIORITY_QUEUE_H_INCLUDED - -#ifdef _MSC_VER -#pragma warning (disable : 4267) -#pragma warning (disable : 4244) #pragma once -#endif -#include #include "pod_vector.h" -namespace bk_lib { namespace detail { - -typedef std::size_t key_type; -const key_type noKey = static_cast(-1); -inline key_type heap_root() { return 0; } -inline key_type heap_left(std::size_t i) { return (i<<1)+1; } -inline key_type heap_right(std::size_t i) { return (i+1)<<1; } -inline key_type heap_parent(std::size_t i) { return (i-1)>>1; } -} +namespace bk_lib { // NOLINT // Note: Uses a Max-Heap! -template < - class Cmp // sort-predicate - if Cmp(k1, k2) == true, n1 has higher priority than n2 -> -class indexed_priority_queue { +template +class indexed_priority_queue { // NOLINT public: - typedef detail::key_type key_type; - typedef pod_vector index_container_type; - typedef std::size_t size_type; - typedef Cmp compare_type; - - explicit indexed_priority_queue( const compare_type& c = compare_type() ); - indexed_priority_queue(const indexed_priority_queue& other); - - indexed_priority_queue& operator=(const indexed_priority_queue& other) { - indices_ = other.indices_; - heap_ = other.heap_; - compare_ = other.compare_; - return *this; - } - - const compare_type& key_compare() const { - return compare_; - } - - bool empty() const { - return heap_.empty(); - } - void reserve(size_type n) { - indices_.reserve(n); - } - - void push(key_type k) { - assert( !is_in_queue(k) ); - if ((key_type)indices_.size() <= k) { - if (indices_.capacity() <= k) { indices_.reserve(((k+1)*3)>>1); } - indices_.resize(k+1, detail::noKey); - } - indices_[k] = (key_type)heap_.size(); - heap_.push_back(k); - siftup(indices_[k]); - } - - void pop() { - assert(!empty()); - key_type x = heap_[0]; - heap_[0] = heap_.back(); - indices_[heap_[0]] = 0; - indices_[x] = detail::noKey; - heap_.pop_back(); - if (heap_.size() > 1) {siftdown(0);} - } - - void clear() { - heap_.clear(); - indices_.clear(); - } - - template - void swapMem(indexed_priority_queue& o) { - clear(); - o.clear(); - heap_.swap(o.heap_); - indices_.swap(o.indices_); - } - size_type size( ) const { - return heap_.size(); - } - - key_type top() const { - assert(!empty()); - return heap_[0]; - } - - void update(key_type k) { - if (!is_in_queue(k)) { - push(k); - } - else { - siftup(indices_[k]); - siftdown(indices_[k]); - } - } - // call if priority of k has increased - void increase(key_type k) { - assert(is_in_queue(k)); - siftup(indices_[k]); - } - // call if priority of k has decreased - void decrease(key_type k) { - assert(is_in_queue(k)); - siftdown(indices_[k]); - } - - bool is_in_queue(key_type k) const { - assert(valid_key(k)); - return k < (key_type)indices_.size() && indices_[k] != detail::noKey; - } - - void remove(key_type k) { - if (is_in_queue(k)) { - key_type kInHeap = indices_[k]; - heap_[kInHeap] = heap_.back(); - indices_[heap_.back()] = kInHeap; - heap_.pop_back(); - indices_[k] = detail::noKey; - if (heap_.size() > 1 && kInHeap != (key_type)heap_.size()) { - siftup(kInHeap); - siftdown(kInHeap); - } - } - } + using key_type = T; + using heap_type = pod_vector; + using idx_type = typename heap_type::size_type; + using index_container_type = pod_vector; + using size_type = idx_type; + using compare_type = Cmp; + static_assert(sizeof(T) <= sizeof(idx_type)); + + explicit indexed_priority_queue(const compare_type& c = {}) noexcept : indices_(), heap_(), compare_(c) {} + + const compare_type& key_compare() const { return compare_; } + + [[nodiscard]] bool empty() const { return heap_.empty(); } + void reserve(size_type n) { indices_.reserve(n); } + + void push(key_type k) { + assert(not is_in_queue(k)); + if (k >= max_pos(indices_)) { + if (k >= indices_.capacity()) { + indices_.reserve(((k + 1) * 3) >> 1); + } + indices_.resize(k + 1, no_pos); + } + indices_[k] = max_pos(heap_); + heap_.push_back(k); + siftup(indices_[k]); + } + + void pop() { + assert(not empty()); + key_type x = heap_[0]; + heap_[0] = heap_.back(); + indices_[heap_[0]] = 0; + indices_[x] = no_pos; + heap_.pop_back(); + if (heap_.size() > 1) { + siftdown(0); + } + } + + void clear() { + heap_.clear(); + indices_.clear(); + } + + [[nodiscard]] size_type size() const { return heap_.size(); } + + [[nodiscard]] key_type top() const { + assert(not empty()); + return heap_[0]; + } + + void update(key_type k) { + if (not is_in_queue(k)) { + push(k); + } + else { + siftup(indices_[k]); + siftdown(indices_[k]); + } + } + // call if priority of k has increased + void increase(key_type k) { + assert(is_in_queue(k)); + siftup(indices_[k]); + } + // call if priority of k has decreased + void decrease(key_type k) { + assert(is_in_queue(k)); + siftdown(indices_[k]); + } + + [[nodiscard]] bool is_in_queue(key_type k) const { return k < max_pos(indices_) && indices_[k] != no_pos; } + + void remove(key_type k) { + if (is_in_queue(k)) { + idx_type kInHeap = indices_[k]; + heap_[kInHeap] = heap_.back(); + indices_[heap_.back()] = kInHeap; + heap_.pop_back(); + indices_[k] = no_pos; + if (heap_.size() > 1 && kInHeap != max_pos(heap_)) { + siftup(kInHeap); + siftdown(kInHeap); + } + } + } + private: - template - friend class indexed_priority_queue; - bool valid_key(key_type k) const { - return k != detail::noKey; - } - index_container_type indices_; - index_container_type heap_; - compare_type compare_; - void siftup(key_type n) { - using namespace detail; - key_type x = heap_[n]; - key_type p = heap_parent(n); - while (n != 0 && compare_(x, heap_[p])){ - heap_[n] = heap_[p]; - indices_[heap_[n]] = n; - n = p; - p = heap_parent(n); - } - heap_[n] = x; - indices_[x] = n; - } - - void siftdown(key_type n) { - using namespace detail; - key_type x = heap_[n]; - while (heap_left(n) < (key_type)heap_.size()){ - key_type child = smaller_child(n); - if (!compare_(heap_[child], x)) { - break; - } - heap_[n] = heap_[child]; - indices_[heap_[n]] = n; - n = child; - } - heap_[n] = x; - indices_[x] = n; - } - - key_type smaller_child(size_type n) const { - using namespace detail; - return heap_right(n) < (key_type)heap_.size() && compare_(heap_[heap_right(n)], heap_[heap_left(n)]) - ? heap_right(n) - : heap_left(n); - } + static constexpr idx_type no_pos = static_cast(-1); + template + static constexpr idx_type max_pos(const C& c) { + return static_cast(c.size()); + } + static constexpr idx_type heap_parent(idx_type i) { return (i - 1) >> 1; } + static constexpr idx_type heap_left(idx_type i) { return (i << 1) + 1; } + static constexpr idx_type heap_right(idx_type i) { return (i + 1) << 1; } + + void siftup(idx_type n) { + using namespace detail; + key_type x = heap_[n]; + idx_type p = heap_parent(n); + while (n != 0 && compare_(x, heap_[p])) { + heap_[n] = heap_[p]; + indices_[heap_[n]] = n; + n = p; + p = heap_parent(n); + } + heap_[n] = x; + indices_[x] = n; + } + + void siftdown(idx_type n) { + using namespace detail; + key_type x = heap_[n]; + while (heap_left(n) < max_pos(heap_)) { + idx_type child = smaller_child(n); + if (not compare_(heap_[child], x)) { + break; + } + heap_[n] = heap_[child]; + indices_[heap_[n]] = n; + n = child; + } + heap_[n] = x; + indices_[x] = n; + } + + [[nodiscard]] idx_type smaller_child(idx_type n) const { + using namespace detail; + return heap_right(n) < max_pos(heap_) && compare_(heap_[heap_right(n)], heap_[heap_left(n)]) ? heap_right(n) + : heap_left(n); + } + index_container_type indices_; + heap_type heap_; + compare_type compare_; }; -template -indexed_priority_queue::indexed_priority_queue( const compare_type& c ) - : indices_() - , heap_() - , compare_(c) { -} - -template -indexed_priority_queue::indexed_priority_queue(const indexed_priority_queue& other) - : indices_(other.indices_) - , heap_(other.heap_) - , compare_(other.compare_) { -} - -} -#endif +} // namespace bk_lib diff --git a/clasp/util/left_right_sequence.h b/clasp/util/left_right_sequence.h index 81e8c94..83be08a 100644 --- a/clasp/util/left_right_sequence.h +++ b/clasp/util/left_right_sequence.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,225 +21,255 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef BK_LIB_LEFT_RIGHT_SEQUENCE_INCLUDED -#define BK_LIB_LEFT_RIGHT_SEQUENCE_INCLUDED +#pragma once #ifdef _MSC_VER -#pragma warning( push ) -#pragma warning (disable : 4200) +#pragma warning(push) +#pragma warning(disable : 4200) #endif #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wclass-memaccess" #endif -#include "type_manip.h" -#include #include #include -namespace bk_lib { namespace detail { +#include +#include + +namespace bk_lib { // NOLINT +namespace detail { // NOLINT // base class for left_right_sequence // see below -template -class left_right_rep { +template +class left_right_rep { // NOLINT public: - typedef L left_type; - typedef R right_type; - typedef unsigned int size_type; - typedef L* left_iterator; - typedef const L* const_left_iterator; - typedef std::reverse_iterator right_iterator; - typedef std::reverse_iterator const_right_iterator; + using left_type = L; + using right_type = R; + using size_type = unsigned int; + using left_iterator = L*; + using const_left_iterator = const L*; + using right_iterator = std::reverse_iterator; + using const_right_iterator = std::reverse_iterator; + using left_view_type = std::span; + using right_view_type = std::ranges::reverse_view>; + using max_type = std::conditional_t= sizeof(right_type), left_type, right_type>; + using align_type = std::conditional_t= alignof(right_type), left_type, right_type>; + static_assert(sizeof(left_type) >= alignof(left_type) && sizeof(right_type) >= alignof(right_type)); + static constexpr auto block_size = static_cast( + ((sizeof(max_type) + (sizeof(align_type) - 1)) / sizeof(align_type)) * sizeof(align_type)); + + left_right_rep() = default; + [[nodiscard]] bool empty() const { return left_ == 0 && right_ == cap_; } + [[nodiscard]] size_type size() const { return left_size() + right_size(); } - typedef typename bk_lib::detail::align_of::type left_align_type; - typedef typename bk_lib::detail::align_of::type right_align_type; -///@cond - typedef typename bk_lib::detail::if_then_else< - sizeof(left_type) >= sizeof(right_type), - left_type, - right_type>::type max_type; - typedef typename bk_lib::detail::if_then_else< - sizeof(left_align_type) >= sizeof(right_align_type), - left_align_type, - right_align_type>::type align_type; -///@endcond - left_right_rep() : buf_(0), cap_(0), free_(0), left_(0), right_(0) {} + [[nodiscard]] size_type left_size() const { return left_ / sizeof(left_type); } + [[nodiscard]] size_type left_capacity() const { return (cap_ / sizeof(left_type)); } + const_left_iterator left_begin() const { return ptr(begin()); } + const_left_iterator left_end() const { return ptr(buf_ + left_); } + left_iterator left_begin() { return ptr(begin()); } + left_iterator left_end() { return ptr(buf_ + left_); } + [[nodiscard]] left_view_type left_view() const { return {left_begin(), left_size()}; } + const left_type& left(size_type i) const { return *(left_begin() + i); } + left_type& left(size_type i) { return *(left_begin() + i); } - bool empty() const { return left_ == 0 && right_ == cap_; } - size_type left_size() const { return left_/sizeof(left_type); } - size_type right_size() const { return (cap_-right_)/sizeof(right_type); } - size_type size() const { return left_size() + right_size(); } - size_type left_capacity() const { return (cap_ / sizeof(left_type)); } - size_type right_capacity()const { return (cap_ / sizeof(right_type)); } - const_left_iterator left_begin() const { return const_left_iterator(reinterpret_cast(begin())); } - const_left_iterator left_end() const { return const_left_iterator(reinterpret_cast(buf_+left_)); } - left_iterator left_begin() { return left_iterator(reinterpret_cast(begin())); } - left_iterator left_end() { return left_iterator(reinterpret_cast(buf_+left_)); } - const_right_iterator right_begin()const { return const_right_iterator(reinterpret_cast(end())); } - const_right_iterator right_end() const { return const_right_iterator(reinterpret_cast(buf_+right_)); } - right_iterator right_begin() { return right_iterator(reinterpret_cast(end())); } - right_iterator right_end() { return right_iterator(reinterpret_cast(buf_+right_)); } - const left_type& left(size_type i)const { return *(left_begin()+i); } - left_type& left(size_type i) { return *(left_begin()+i); } + [[nodiscard]] size_type right_size() const { return (cap_ - right_) / sizeof(right_type); } + [[nodiscard]] size_type right_capacity() const { return (cap_ / sizeof(right_type)); } + const_right_iterator right_begin() const { return const_right_iterator(ptr(end())); } + const_right_iterator right_end() const { return const_right_iterator(ptr(buf_ + right_)); } + right_iterator right_begin() { return right_iterator(ptr(end())); } + right_iterator right_end() { return right_iterator(ptr(buf_ + right_)); } + [[nodiscard]] right_view_type right_view() const { + return std::views::reverse(std::span(ptr(buf_ + right_), right_size())); + } - void clear(bool releaseMem = false) { - if (releaseMem) { - release(); - buf_ = 0; - cap_ = 0; - free_ = 0; - } - left_ = 0; - right_= cap_; - } - void push_left(const left_type& x) { - if ((left_ + sizeof(left_type)) > right_) { - realloc(); - } - new (left())left_type(x); - left_ += sizeof(left_type); - } + void push_left(const left_type& x) { + if ((left_ + sizeof(left_type)) > right_) { + realloc(); + } + new (buf_ + left_) left_type(x); + left_ += sizeof(left_type); + } - void push_right(const right_type& x) { - if ( (left_ + sizeof(right_type)) > right_ ) { - realloc(); - } - right_ -= sizeof(right_type); - new (right()) right_type(x); - } - void pop_left() { - assert(left_size() != 0); - left_ -= sizeof(left_type); - } - void pop_right() { - assert(right_size() != 0); - right_ += sizeof(right_type); - } + void push_right(const right_type& x) { + if ((left_ + sizeof(right_type)) > right_) { + realloc(); + } + right_ -= sizeof(right_type); + new (right()) right_type(x); + } + void pop_left() { + assert(left_size() != 0); + left_ -= sizeof(left_type); + } + void pop_right() { + assert(right_size() != 0); + right_ += sizeof(right_type); + } + + void erase_left(left_iterator it) { + if (it != left_end()) { + left_iterator x = it++; + std::memmove(x, it, static_cast(left_end() - it) * sizeof(left_type)); + left_ -= sizeof(left_type); + } + } + void erase_left_unordered(left_iterator it) { + if (auto ep = left_end(); it != ep) { + *it = *--ep; + pop_left(); + } + } + void erase_right(right_iterator it) { + if (it != right_end()) { + right_type* r = (++it).base(); + auto* b = ptr(right()); + assert(r >= b); + std::memmove(b + 1, b, static_cast(r - b) * sizeof(right_type)); + right_ += sizeof(right_type); + } + } + void erase_right_unordered(right_iterator it) { + if (it != right_end()) { + *it = *ptr(right()); + right_ += sizeof(right_type); + } + } + void shrink_left(left_iterator it) { left_ = static_cast(it - left_begin()) * sizeof(left_type); } + void shrink_right(right_iterator it) { + auto* x = ptr(it.base()); + right_ = static_cast(x - begin()); + } - void erase_left(left_iterator it) { - if (it != left_end()) { - left_iterator x = it++; - std::memmove(x, it, (left_end()-it)*sizeof(left_type)); - left_ -= sizeof(left_type); - } - } - void erase_left_unordered(left_iterator it) { - if (it != left_end()) { - left_ -= sizeof(left_type); - *it = *reinterpret_cast(left()); - } - } - void erase_right(right_iterator it) { - if (it != right_end()) { - right_type* r = (++it).base(); - right_type* b = reinterpret_cast(right()); - assert(r >= b); - std::memmove(b+1, b, (r-b)*sizeof(right_type)); - right_ += sizeof(right_type); - } - } - void erase_right_unordered(right_iterator it) { - if (it != right_end()) { - *it = *reinterpret_cast(right()); - right_+= sizeof(right_type); - } - } - void shrink_left(left_iterator it) { - left_ = static_cast((it - left_begin())*sizeof(left_type)); - } - void shrink_right(right_iterator it) { - buf_type* x = reinterpret_cast(it.base()); - right_ = static_cast(x - begin()); - } - enum { block_size = ((sizeof(max_type)+(sizeof(align_type)-1)) / sizeof(align_type)) * sizeof(align_type) }; protected: - left_right_rep(const left_right_rep&) { } - left_right_rep& operator=(const left_right_rep&) { return *this; } - typedef unsigned char buf_type; - buf_type* begin() { return buf_; } - const buf_type* begin()const { return buf_; } - buf_type* end() { return buf_+cap_; } - const buf_type* end() const { return buf_+cap_; } - buf_type* left() { return buf_+left_; } - buf_type* right() { return buf_+right_; } - size_type capacity()const { return cap_ / block_size; } - size_type raw_size()const { return left_ + (cap_-right_); } - void release() { if (free_ != 0) { ::operator delete(buf_); } } - void realloc(); - buf_type* buf_; - size_type cap_ : 31; - size_type free_: 1; - size_type left_; - size_type right_; + template + static X* ptr(B* p) { + return reinterpret_cast(p); + } + + using buf_type = unsigned char; + void init_buffer(buf_type* b, size_type c, bool own) { + buf_ = b; + cap_ = c; + free_ = own; + left_ = 0; + right_ = cap_; + } + ~left_right_rep() { release(); } + + void assign(const left_right_rep& other) { + if (auto os = other.raw_size(); os > cap_) { + auto c = ((os + (block_size - 1)) / block_size) * block_size; + auto* b = static_cast(::operator new(c * sizeof(buf_type))); + release(); + init_buffer(b, c, true); + } + left_ = other.left_; + right_ = this->cap_ - (other.right_size() * sizeof(right_type)); + if (other.buf_) { + std::memcpy(begin(), other.begin(), other.left_size() * sizeof(left_type)); + std::memcpy(right(), const_cast(other).right(), other.right_size() * sizeof(right_type)); + } + } + void move(left_right_rep&& other) { + init_buffer(other.buf_, other.cap_, other.free_); + left_ = other.left_; + right_ = other.right_; + } + + buf_type* begin() { return buf_; } + [[nodiscard]] const buf_type* begin() const { return buf_; } + buf_type* end() { return buf_ + cap_; } + [[nodiscard]] const buf_type* end() const { return buf_ + cap_; } + buf_type* right() { return buf_ + right_; } + [[nodiscard]] size_type capacity() const { return cap_ / block_size; } + [[nodiscard]] size_type raw_size() const { return left_ + (cap_ - right_); } + void release() { + if (free_ != 0) { + ::operator delete(buf_); + free_ = 0; + } + } + void realloc(); + buf_type* buf_; + size_type cap_ : 31; + size_type free_ : 1; + size_type left_; + size_type right_; }; -template +template void left_right_rep::realloc() { - size_type new_cap = ((capacity()*3)>>1) * block_size; - size_type min_cap = 4 * block_size; - if (new_cap < min_cap) new_cap = min_cap; - buf_type* temp = (buf_type*)::operator new(new_cap*sizeof(buf_type)); - size_type r = cap_ - right_; - if (!empty()) { - // copy left - std::memcpy(temp, begin(), left_size()*sizeof(L)); - // copy right - std::memcpy(temp+(new_cap-r), right(), right_size() * sizeof(R)); - } - // swap - release(); - buf_ = temp; - cap_ = new_cap; - free_ = 1; - right_ = new_cap - r; + size_type new_cap = ((capacity() * 3) >> 1) * block_size; + size_type min_cap = 4 * block_size; + if (new_cap < min_cap) { + new_cap = min_cap; + } + auto* temp = static_cast(::operator new(new_cap * sizeof(buf_type))); + size_type r = cap_ - right_; + if (this->buf_) { + // copy left + std::memcpy(temp, begin(), left_size() * sizeof(L)); + // copy right + std::memcpy(temp + (new_cap - r), right(), right_size() * sizeof(R)); + } + // swap + release(); + buf_ = temp; + cap_ = new_cap; + free_ = 1; + right_ = new_cap - r; } +template +struct left_right_buffer; // NOLINT + // always store sequence in heap-allocated buffer -template -struct no_inline_buffer : public left_right_rep { - typedef typename left_right_rep::buf_type buf_type; - enum { inline_raw_cap = 0 }; - buf_type* extra() { return 0; } +template +struct left_right_buffer : public left_right_rep { + static constexpr unsigned inline_raw_cap = 0; + void init() { this->init_buffer(nullptr, 0u, false); } }; // store small sequences directly inside of object -template -struct with_inline_buffer : public left_right_rep { - typedef typename left_right_rep::buf_type buf_type; - typedef typename left_right_rep::align_type align_type; - enum { inline_raw_cap = cap }; - buf_type* extra() { return rep_.mem; } - union X { - align_type align; - buf_type mem[inline_raw_cap]; - } rep_; -}; +template +struct left_right_buffer : public left_right_rep { // NOLINT + static constexpr unsigned inline_raw_cap = Cap; + using buf_type = typename left_right_rep::buf_type; + using align_type = typename left_right_rep::align_type; -// select proper base class for left_right_sequence based -// on parameter i -template -struct select_base { -private: - typedef unsigned char buf_type; - typedef left_right_rep base_type; - typedef typename base_type::size_type size_type; - typedef typename base_type::align_type align_type; - typedef no_inline_buffer no_extra_type; - typedef with_inline_buffer with_extra_type; + void init() { this->init_buffer(mem, Cap, false); } - enum { padding = sizeof(with_extra_type) - (sizeof(no_extra_type)+sizeof(align_type)) }; - enum { size_with_pad = sizeof(no_extra_type) + padding }; - enum { store_extra = (i > size_with_pad) && (i - size_with_pad) >= base_type::block_size }; - enum { inline_raw_cap = store_extra ? ((i - size_with_pad)/base_type::block_size)*base_type::block_size : 0 }; -public: - typedef typename if_then_else< - store_extra!=0, - with_inline_buffer, - no_inline_buffer >::type type; + void try_shrink() { + if (this->raw_size() <= Cap && this->buf_ != mem) { + left_right_buffer t; + t.move(static_cast&&>(*this)); + this->init(); + this->assign(t); + } + } + + alignas(align_type) buf_type mem[Cap]; }; +// compute inline capacity for left_right_sequence based on parameter i +template +static consteval unsigned compute_raw_cap() { + constexpr auto bs = left_right_rep::block_size; + constexpr auto ms = sizeof(left_right_buffer); + if constexpr (ms > I) { + return 0; + } + else { + constexpr auto c = (((I - ms) / bs) + 1) * bs; + static_assert(sizeof(left_right_buffer) <= I || c >= bs * 2); + return sizeof(left_right_buffer) <= I ? c : c - bs; + } +} +template +using left_right_buffer_t = left_right_buffer()>; -} // bk_lib::detail +} // namespace detail //! Stores two sequences in one contiguous memory block /*! @@ -249,121 +279,74 @@ struct select_base { * * \param L value type of left sequence * \param R value type of right sequence - * \param i max size on stack + * \param I max size on stack * \pre L and R can be copied with memcpy (i.e. have trivial copy constructor and trivial destructor) */ -template -class left_right_sequence : public bk_lib::detail::select_base::type { +template +class left_right_sequence : public bk_lib::detail::left_right_buffer_t { // NOLINT public: - typedef typename bk_lib::detail::select_base::type base_type; - typedef typename base_type::left_type left_type; - typedef typename base_type::right_type right_type; - typedef typename base_type::size_type size_type; - typedef typename base_type::align_type align_type; - typedef typename base_type::max_type max_type; - typedef typename base_type::buf_type buf_type; + using base_type = bk_lib::detail::left_right_buffer_t; + using left_type = typename base_type::left_type; + using right_type = typename base_type::right_type; + using size_type = typename base_type::size_type; + using align_type = typename base_type::align_type; + using max_type = typename base_type::max_type; + using buf_type = typename base_type::buf_type; + using left_iterator = typename base_type::left_iterator; + using const_left_iterator = typename base_type::const_left_iterator; + using right_iterator = typename base_type::right_iterator; + using const_right_iterator = typename base_type::const_right_iterator; + + left_right_sequence() { base_type::init(); } + left_right_sequence(const left_right_sequence& other) { + base_type::init(); + base_type::assign(other); + } + left_right_sequence(left_right_sequence&& other) noexcept { this->move_or_copy(std::move(other)); } + left_right_sequence& operator=(const left_right_sequence& other) { + if (this != &other) { + base_type::assign(other); + } + return *this; + } + left_right_sequence& operator=(left_right_sequence&& other) noexcept { + if (this != &other) { + base_type::release(); + this->move_or_copy(std::move(other)); + } + return *this; + } + ~left_right_sequence() = default; - typedef typename base_type::left_iterator left_iterator; - typedef typename base_type::const_left_iterator const_left_iterator; - typedef typename base_type::right_iterator right_iterator; - typedef typename base_type::const_right_iterator const_right_iterator; + void clear() { + this->left_ = 0; + this->right_ = this->cap_; + } - left_right_sequence() { - this->buf_ = this->extra(); - this->cap_ = base_type::inline_raw_cap; - this->free_ = 0; - this->left_ = 0; - this->right_= base_type::inline_raw_cap; - } - left_right_sequence(const left_right_sequence& other); - ~left_right_sequence() { this->release(); } - left_right_sequence& operator=(const left_right_sequence&); + void reset() { + base_type::release(); + base_type::init(); + } - void try_shrink() { - if (this->raw_size() <= base_type::inline_raw_cap && this->buf_ != this->extra()) { - buf_type* e = this->extra(); - size_type c = base_type::inline_raw_cap; - size_type r = c - (this->right_size()*sizeof(right_type)); - if (!this->empty()) { - std::memcpy(e, this->begin(), this->left_size() * sizeof(left_type)); - std::memcpy(e+r, this->right(), this->right_size()* sizeof(right_type)); - } - this->release(); - this->buf_ = e; - this->cap_ = c; - this->free_ = 0; - this->right_= r; - } - } - void move(left_right_sequence& other) { - this->clear(true); - if (other.raw_size() <= base_type::inline_raw_cap) { - copy(other); - other.clear(true); - } - else { - this->buf_ = other.buf_; - this->cap_ = other.cap_; - this->free_ = other.free_; - this->left_ = other.left_; - this->right_ = other.right_; - other.buf_ = other.extra(); - other.cap_ = base_type::inline_raw_cap; - other.free_ = 0; - other.left_ = 0; - other.right_= base_type::inline_raw_cap; - } - } private: - void copy(const left_right_sequence&); + void move_or_copy(left_right_sequence&& other) noexcept { + if (other.raw_size() > base_type::inline_raw_cap) { + base_type::move(std::move(other)); + } + else { + this->init(); + this->assign(other); + other.release(); + } + other.init(); + } }; -template -void left_right_sequence::copy(const left_right_sequence& other) { - size_type os = other.raw_size(); - if ( os <= base_type::inline_raw_cap ) { - this->buf_ = this->extra(); - this->cap_ = base_type::inline_raw_cap; - this->free_= 0; - } - else { - os = ((os + (base_type::block_size-1)) / base_type::block_size) * base_type::block_size; - this->buf_ = (buf_type*)::operator new(os*sizeof(buf_type)); - this->cap_ = os; - this->free_= 1; - } - this->left_ = other.left_; - this->right_= this->cap_ - (other.right_size()*sizeof(right_type)); - if (!other.empty()) { - std::memcpy(this->begin(), other.begin(), other.left_size()*sizeof(left_type)); - std::memcpy(this->right(), const_cast(other).right(), other.right_size()*sizeof(right_type)); - } -} - -template -left_right_sequence::left_right_sequence(const left_right_sequence& other) : base_type(other) { - copy(other); -} - -template -left_right_sequence& left_right_sequence::operator=(const left_right_sequence& other) { - if (this != &other) { - this->release(); - copy(other); - } - return *this; -} - - } // namespace bk_lib - #ifdef _MSC_VER #pragma warning(pop) #endif #if defined(__GNUC__) && __GNUC__ >= 8 #pragma GCC diagnostic pop #endif - -#endif - diff --git a/clasp/util/misc_types.h b/clasp/util/misc_types.h index b232b4d..968a958 100644 --- a/clasp/util/misc_types.h +++ b/clasp/util/misc_types.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,15 +21,17 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_UTIL_MISC_TYPES_H_INCLUDED -#define CLASP_UTIL_MISC_TYPES_H_INCLUDED +#pragma once #include -#include // std::pair -#include // std::unary_function, std::binary_function -#include +#include + #include +#include +#if defined(__cpp_lib_saturation_arithmetic) && __cpp_lib_saturation_arithmetic == 202311L +#include +#endif + #if defined(__GNUC__) || defined(__clang__) // Disable deprecation warnings from std::{unary,binary}_function #pragma GCC system_header @@ -47,466 +49,433 @@ namespace Clasp { */ //@{ -template -inline T bit_mask(unsigned n) { return static_cast(1) << n; } -//! Returns whether bit n is set in x. -template -inline bool test_bit(T x, unsigned n) { return (x & bit_mask(n)) != 0; } -template -inline T clear_bit(T x, unsigned n) { return x & ~bit_mask(n); } -template -inline T set_bit(T x, unsigned n) { return x | bit_mask(n); } -template -inline T toggle_bit(T x, unsigned n) { return x ^ bit_mask(n); } -template -inline T& store_clear_bit(T& x, unsigned n) { return (x &= ~bit_mask(n)); } -template -inline T& store_set_bit(T& x, unsigned n) { return (x |= bit_mask(n)); } -template -inline T& store_toggle_bit(T& x, unsigned n) { return (x ^= bit_mask(n)); } -template -inline T right_most_bit(T x) { return x & (-x); } - -inline uint32 log2(uint32 x) { - uint32 ln = 0; - if (x & 0xFFFF0000u) { x >>= 16; ln |= 16; } - if (x & 0xFF00u ) { x >>= 8; ln |= 8; } - if (x & 0xF0u ) { x >>= 4; ln |= 4; } - if (x & 0xCu ) { x >>= 2; ln |= 2; } - if (x & 0x2u ) {/*x>>=1*/; ln |= 1; } - return ln; -} - //! Computes n choose k. -inline uint64 choose(unsigned n, unsigned k) { - if (k == 0) return 1; - if (k > n) return 0; - if (2 * k > n) { return choose(n, n-k);} - uint64 res = n; - for (unsigned i = 2 ; i <= k; ++i) { - res *= (n + 1 - i); - res /= i; - } - return res; +constexpr uint64_t choose(unsigned n, unsigned k) { + if (k > n) { + return 0; + } + if (auto sym = n - k; k > sym) { + k = sym; + } + uint64_t res = k > 0 ? n : 1; + for (unsigned i = 2; i <= k; ++i) { + res *= (n + 1 - i); + res /= i; + } + return res; } -inline double ratio(uint64 x, uint64 y) { return y ? static_cast(x) / static_cast(y) : 0; } -inline double percent(uint64 x, uint64 y) { return ratio(x, y) * 100.0; } +constexpr double ratio(uint64_t x, uint64_t y) { return y ? static_cast(x) / static_cast(y) : 0; } +constexpr double ratio(uint64_t x, uint64_t y, double z) { return y ? ratio(x, y) : z; } +constexpr double percent(uint64_t x, uint64_t y) { return ratio(x, y) * 100.0; } //! A very simple but fast Pseudo-random number generator. /*! * \note This class is a replacement for the standard rand-function. It is provided * in order to get reproducible random numbers among different compilers. */ -class RNG { +class Rng { public: - explicit RNG(uint32 seed = 1) : seed_(seed) {} - - //! Sets the starting point for random-number generation. - /*! - * The function sets the starting point for generating a series of pseudorandom integers. - * To reinitialize the generator, use 1 as the seed argument. Any other value for seed - * sets the generator to a random starting point. Calling rand() before any call to srand() - * generates the same sequence as calling srand() with seed passed as 1. - */ - void srand(uint32 seed) { seed_ = seed; } - - //! Generates a pseudorandom number - /*! - * The rand function returns a pseudorandom integer in the range 0 to 32767 - * Use the srand function to seed the pseudorandom-number generator before calling rand. - */ - uint32 rand() { - return( ((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff ); - } - - //! random floating point number in the range [0, 1.0) - double drand() { - return this->rand()/static_cast(0x8000u); - } - - //! random number in the range [0, max) - unsigned irand(unsigned max) { - return static_cast(drand() * max); - } - - uint32 seed() const { return seed_; } - - uint32 operator()(unsigned max) { return irand(max); } - uint32 operator()() { return rand(); } + constexpr explicit Rng(uint32_t seed = 1) : seed_(seed) {} + + //! Sets the starting point for random-number generation. + /*! + * The function sets the starting point for generating a series of pseudorandom integers. + * To reinitialize the generator, use 1 as the seed argument. Any other value for seed + * sets the generator to a random starting point. Calling rand() before any call to srand() + * generates the same sequence as calling srand() with seed passed as 1. + */ + constexpr void srand(uint32_t seed) { seed_ = seed; } + + //! Generates a pseudorandom number + /*! + * The rand function returns a pseudorandom integer in the range 0 to 32767 + * Use the srand function to seed the pseudorandom-number generator before calling rand. + */ + constexpr uint32_t rand() { return (((seed_ = seed_ * 214013L + 2531011L) >> 16) & 0x7fff); } + + //! random floating point number in the range [0, 1.0) + constexpr double drand() { return this->rand() / static_cast(0x8000u); } + + //! random number in the range [0, max) + constexpr unsigned irand(unsigned max) { return static_cast(drand() * max); } + + [[nodiscard]] constexpr uint32_t seed() const { return seed_; } + + constexpr uint32_t operator()(unsigned max) { return irand(max); } + constexpr uint32_t operator()() { return rand(); } + + // Replacement for deprecated std::random_shuffle(first, last, *this). + template + constexpr void shuffle(RandIt first, RandIt last) { + for (auto it = first + (first != last); it != last; ++it) { + if (auto n = first + (*this)(static_cast(it - first) + 1); it != n) { + std::iter_swap(it, n); + } + } + } + private: - uint32 seed_; + uint32_t seed_; }; class MovingAvg { public: - enum Type { avg_sma = 0, avg_ema = 1, avg_ema_log = 2, avg_ema_smooth = 3, avg_ema_log_smooth = 4 }; - - MovingAvg(uint32 window, Type type) - : avg_(0.0), extra_(), pos_(0), win_(window), full_(window == 0), ema_(type != avg_sma), smooth_(0) { - assert(window > 0 || type == avg_sma); - if (ema_) { - smooth_ = (type >= avg_ema_smooth); - extra_.alpha = (type & 1u) != 0 ? 2.0 / (window + 1) : 1.0 / (1u << log2(window)); - } - else if (window) { - extra_.sma = new uint32[window]; - } - else { - extra_.num = 0; - } - } - - ~MovingAvg() { - if (!ema_ && win_) - delete [] extra_.sma; - } - - //! Updates the given exponential moving average with the given sample. - /*! - * Computes ema = current + ((sample - current)*alpha); - */ - static inline double ema(double current, double sample, double alpha) { - return current + (alpha * (sample - current)); - } - - //! Updates the given cumulative moving average with the given sample. - static inline double cma(double current, double sample, uint64 numSeen) { - return (sample + (current * static_cast(numSeen))) / static_cast(numSeen + 1); - } - - //! Updates the given simple moving average with the given sample. - static inline double sma(double current, uint32 sample, uint32* buffer, uint32 cap, uint32 pos, bool full) { - assert(pos < cap); - double oldS = static_cast(buffer[pos]); - double newS = static_cast(sample); - buffer[pos] = sample; - return full ? current + ((newS - oldS) / cap) : cma(current, newS, pos); - } - - static double smoothAlpha(double alpha, uint32 pos) { - return pos < 32 ? std::max(alpha, 1.0 / static_cast(uint32(1) << pos)) : alpha; - } - - bool push(uint32 val) { - if (!win_) { avg_ = cma(avg_, val, extra_.num++); } - else if (!ema_) { avg_ = sma(avg_, val, extra_.sma, win_, pos_, valid()); } - else if (valid()) { avg_ = ema(avg_, val, extra_.alpha); } - else { avg_ = smooth_ ? ema(avg_, val, smoothAlpha(extra_.alpha, pos_)) : cma(avg_, val, pos_); } - if (++pos_ == win_) { pos_ = 0; full_ = 1; } - return valid(); - } - - void clear() { - avg_ = 0.0; - pos_ = 0; - !win_ ? extra_.num = 0 : full_ = 0; - } - - double get() const { return avg_; } - bool valid() const { return full_ != 0; } - uint32 win() const { return win_; } + enum Type { avg_sma = 0, avg_ema = 1, avg_ema_log = 2, avg_ema_smooth = 3, avg_ema_log_smooth = 4 }; + + MovingAvg(uint32_t window, Type type) : win_(window), full_(window == 0), ema_(type != avg_sma), smooth_(0) { + assert(window > 0 || type == avg_sma); + if (ema_) { + smooth_ = (type >= avg_ema_smooth); + extra_.alpha = (type & 1u) != 0 ? 2.0 / (window + 1) : 1.0 / (1u << Potassco::log2(window)); + } + else if (window) { + extra_.sma = new uint32_t[window]; + } + else { + extra_.num = 0; + } + } + MovingAvg(const MovingAvg&) = delete; + MovingAvg& operator=(const MovingAvg&) = delete; + + ~MovingAvg() { + if (not ema_ && win_) { + delete[] extra_.sma; + } + } + + //! Updates the given exponential moving average with the given sample. + /*! + * Computes ema = current + ((sample - current)*alpha); + */ + static constexpr double ema(double current, double sample, double alpha) { + return current + (alpha * (sample - current)); + } + + //! Updates the given cumulative moving average with the given sample. + static constexpr double cma(double current, double sample, uint64_t numSeen) { + return (sample + (current * static_cast(numSeen))) / static_cast(numSeen + 1); + } + + //! Updates the given simple moving average with the given sample. + static constexpr double sma(double current, uint32_t sample, uint32_t* buffer, uint32_t cap, uint32_t pos, + bool full) { + assert(pos < cap); + auto oldS = static_cast(buffer[pos]); + auto newS = static_cast(sample); + buffer[pos] = sample; + return full ? current + ((newS - oldS) / cap) : cma(current, newS, pos); + } + + static constexpr double smoothAlpha(double alpha, uint32_t pos) { + return pos < 32 ? std::max(alpha, 1.0 / static_cast(1u << pos)) : alpha; + } + + bool push(uint32_t val) { + if (not win_) { + avg_ = cma(avg_, val, extra_.num++); + } + else if (not ema_) { + avg_ = sma(avg_, val, extra_.sma, win_, pos_, valid()); + } + else if (valid()) { + avg_ = ema(avg_, val, extra_.alpha); + } + else { + avg_ = smooth_ ? ema(avg_, val, smoothAlpha(extra_.alpha, pos_)) : cma(avg_, val, pos_); + } + if (++pos_ == win_) { + pos_ = 0; + full_ = 1; + } + return valid(); + } + + void clear() { + avg_ = 0.0; + pos_ = 0; + not win_ ? extra_.num = 0 : full_ = 0; + } + + [[nodiscard]] double get() const { return avg_; } + [[nodiscard]] bool valid() const { return full_ != 0; } + [[nodiscard]] uint32_t win() const { return win_; } private: - MovingAvg(const MovingAvg&); - MovingAvg& operator=(const MovingAvg&); - union Extra { - uint32* sma; // Buffer for SMA - double alpha; // Smoothing factor for EMA - uint64 num; // Number of data points for CMA - }; - double avg_; // Current average - Extra extra_; // Additional data for active average type - uint32 pos_; // Number of data points % window size - uint32 win_ : 29; // Window size (for SMA/EMA) - uint32 full_ : 1; // Enough data points seen? - uint32 ema_ : 1; // Exponential Moving Average? - uint32 smooth_ : 1; // Use smoothing or cumulative moving average for first (ema) data points? + union Extra { + uint32_t* sma; // Buffer for SMA + double alpha; // Smoothing factor for EMA + uint64_t num; // Number of data points for CMA + }; + double avg_{0.0}; // Current average + Extra extra_{}; // Additional data for active average type + uint32_t pos_{0}; // Number of data points % window size + uint32_t win_ : 29; // Window size (for SMA/EMA) + uint32_t full_ : 1; // Enough data points seen? + uint32_t ema_ : 1; // Exponential Moving Average? + uint32_t smooth_ : 1; // Use smoothing or cumulative moving average for first (ema) data points? }; -//! An unary operator function that calls p->destroy(). +//! Unary operator function that calls p->destroy(). struct DestroyObject { - template void operator()(T* p) const { if (p) p->destroy(); } + template + void operator()(T* p) const { + if (p) { + p->destroy(); + } + } }; -//! An unary operator function that calls delete p. +//! Unary operator function that calls delete p. struct DeleteObject { - template void operator()(T* p) const { delete p; } + template + void operator()(T* p) const { + std::default_delete()(p); + } }; -//! An unary operator function that calls p->release(). +//! Unary operator function that calls p->release(). struct ReleaseObject { - template void operator()(T* p) const { if (p) p->release(); } -}; -//! An unary operator function that returns whether its argument is 0. -struct IsNull { - template bool operator()(const T& p) const { return p == 0; } -}; - -//! A predicate that checks whether a std::pair contains a certain value. -template -struct PairContains { - PairContains(const T& p) : p_(p) {} - bool operator()(const std::pair& s) const { - return s.first == p_ || s.second == p_; - } - T p_; -}; - -//! Removes from the container c the first occurrence of a value v for which p(v) returns true. -/*! - * \pre C is a container that provides back() and pop_back() - * \note Removal is implemented by replacing the element to be removed with - * the back()-element followed by a call to pop_back(). - */ -template -void remove_first_if(C& cont, const P& p) { - for (typename C::iterator it = cont.begin(), end = cont.end(); it != end; ++it) { - if (p(*it)) { - *it = cont.back(); - cont.pop_back(); - return; - } - } -} - -//! An unary operator function that simply returns its argument. -template -struct identity : std::unary_function{ - T& operator()(T& x) const { return x; } - const T& operator()(const T& x) const { return x; } -}; - - -//! An unary operator function that returns the first value of a std::pair. -template -struct select1st : std::unary_function { - typename P::first_type& operator()(P& x) const { - return x.first; - } - const typename P::first_type& operator()(const P& x) const { - return x.first; - } + template + void operator()(T* p) const { + if (p) { + p->release(); + } + } }; -//! An unary operator function that returns the second value of a std::pair. -template -struct select2nd : std::unary_function { - typename P::second_type& operator()(P& x) const { - return x.second; - } - const typename P::second_type& operator()(const P& x) const { - return x.second; - } -}; +template +class TaggedPtr { +public: + static_assert(N > 0 && N < (sizeof(void*) * CHAR_BIT), "invalid number of tag bits"); + static constexpr auto c_mask = Potassco::bit_max(N); + + constexpr TaggedPtr() noexcept = default; + constexpr explicit TaggedPtr(T* ptr) noexcept : ptr_(reinterpret_cast(ptr)) { + static_assert(alignof(T) > c_mask, "too many tag bits"); + } + + template + requires(static_cast(I) < N) + [[nodiscard]] constexpr bool test() const noexcept { + return Potassco::test_bit(ptr_, I); + } + [[nodiscard]] constexpr bool any() const noexcept { return Potassco::test_any(ptr_, c_mask); } + template + requires(static_cast(I) < N) + constexpr void set() noexcept { + Potassco::store_set_bit(ptr_, I); + } + template + requires(static_cast(I) < N) + constexpr void clear() noexcept { + Potassco::store_clear_bit(ptr_, I); + } + + constexpr explicit operator bool() const noexcept { return get() != nullptr; } + + constexpr T& operator*() const noexcept { return *get(); } + constexpr T* operator->() const noexcept { return get(); } + constexpr T* get() const noexcept { return reinterpret_cast(Potassco::clear_mask(ptr_, c_mask)); } + constexpr void swap(TaggedPtr& o) noexcept { std::swap(ptr_, o.ptr_); } -//! An unary operator function that returns Op1(Op2(x)). -template -struct compose_1 : public std::unary_function< - typename OP2::argument_type, - typename OP1::result_type> { - compose_1(const OP1& op1, const OP2& op2) - : op1_(op1) - , op2_(op2) {} - - typename OP1::result_type operator()(const typename OP2::argument_type& x) const { - return op1_(op2_(x)); - } -protected: - OP1 op1_; - OP2 op2_; +private: + uintptr_t ptr_{}; }; -/*! - * A template helper function used to construct objects of type compose_1, - * where the component types are based on the data types passed as parameters. - */ -template -inline compose_1 compose1(const OP1& op1, const OP2& op2) { - return compose_1(op1, op2); +template +constexpr T clamp(T val, std::type_identity_t lo, std::type_identity_t hi) { + return std::clamp(val, lo, hi); +} +#if defined(__cpp_lib_saturation_arithmetic) && __cpp_lib_saturation_arithmetic != 202311L +using std::saturate_cast; +#else +template +constexpr Res saturate_cast(U x) noexcept { + if (std::in_range(x)) { + return static_cast(x); + } + if (Res lo = std::numeric_limits::min(); x < static_cast(lo)) { + return lo; + } + return std::numeric_limits::max(); } +#endif -//! An unary operator function that returns OP1(OP2(x), OP3(x)). -template -struct compose_2_1 : public std::unary_function< - typename OP2::argument_type, - typename OP1::result_type> { - compose_2_1(const OP1& op1, const OP2& op2, const OP3& op3) - : op1_(op1) - , op2_(op2) - , op3_(op3) {} - - typename OP1::result_type operator()(const typename OP2::argument_type& x) const { - return op1_(op2_(x), op3_(x)); - } -protected: - OP1 op1_; - OP2 op2_; - OP3 op3_; +//! A (numerical) range represented by a low and a high value. +struct Range32 { + constexpr Range32(uint32_t x, uint32_t y) : lo(x), hi(y) { + if (x > y) { + hi = x; + lo = y; + } + } + [[nodiscard]] constexpr uint32_t clamp(uint32_t val) const { return std::clamp(val, lo, hi); } + + uint32_t lo; + uint32_t hi; }; -/*! - * A template helper function used to construct objects of type compose_2_1, - * where the component types are based on the data types passed as parameters. - */ -template -inline compose_2_1 compose2(const OP1& op1, const OP2& op2, const OP3& op3) { - return compose_2_1(op1, op2, op3); +//! Generator functions for creating (lazy) half-open integer ranges. +//@{ +//! Returns a (lazy) half-open range of integers [begin, end). +template +constexpr auto irange(T begin, std::type_identity_t end) { + return std::views::iota(begin, end); } +//! Behaves like irange(0u, size). +constexpr auto irange(unsigned size) { return irange(0u, size); } +//! Behaves like irange(r.size()). +template +requires requires(const T& x) { + { x.size() } -> std::unsigned_integral; +} +constexpr auto irange(const T& r) { + return irange(0u, size32(r)); +} +//! Behaves like irange(N). +template +constexpr auto irange(const T (&)[N]) { + return irange(0u, N); +} +//@} +//@} - -//! A binary operator function that returns OP1(OP2(x), OP3(y)). -template -struct compose_2_2 : public std::binary_function< - typename OP2::argument_type, - typename OP3::argument_type, - typename OP1::result_type> { - compose_2_2(const OP1& op1 = OP1(), const OP2& op2 = OP2(), const OP3& op3 = OP3()) - : op1_(op1) - , op2_(op2) - , op3_(op3) {} - - typename OP1::result_type operator()(const typename OP2::argument_type& x, const typename OP3::argument_type& y) const { - return op1_(op2_(x), op3_(y)); - } -protected: - OP1 op1_; - OP2 op2_; - OP3 op3_; +//! Base class for library events. +struct Event { + template + struct Id { + static const uint32_t id_s; + }; + + //! Set of known event sources. + enum Subsystem { subsystem_facade = 0, subsystem_load = 1, subsystem_prepare = 2, subsystem_solve = 3 }; + //! Possible verbosity levels. + enum Verbosity { verbosity_quiet = 0, verbosity_low = 1, verbosity_high = 2, verbosity_max = 3 }; + template + Event(SelfType*, Subsystem sys, Verbosity verbosity) + : system(sys) + , verb(verbosity) + , op(0) + , id(eventId()) { + static_assert(std::is_base_of_v); + } + static uint32_t nextId(); + template + static uint32_t eventId() { + return Id::id_s; + } + + uint32_t system : 2; //!< One of Event::Subsystem - subsystem that produced the event. + uint32_t verb : 2; //!< One of Event::Verbosity - the verbosity level of this event. + uint32_t op : 8; //!< Operation that triggered the event. + uint32_t id : 16; //!< Type id of event. }; +template +const uint32_t Event::Id::id_s = Event::nextId(); -/*! - * A template helper function used to construct objects of type compose_2_2, - * where the component types are based on the data types passed as parameters. - */ -template -inline compose_2_2 compose22(const OP1& op1, const OP2& op2, const OP3& op3) { - return compose_2_2(op1, op2, op3); -} - -//! TODO: replace with std::is_sorted once we switch to C++11 -template -bool isSorted(ForwardIterator first, ForwardIterator last, Compare comp) { - if (first != last) { - for (ForwardIterator n = first; ++n != last; ++first) { - if (comp(*n, *first)) return false; - } - } - return true; +template +const ToType* event_cast(const Event& ev) { + return ev.id == Event::eventId() ? static_cast(&ev) : nullptr; } -//! Possible ownership operations. -struct Ownership_t { - enum Type { Retain = 0, Acquire = 1 }; +template +struct Overload : Ts... { + using Ts::operator()...; }; -//! A smart pointer that optionally owns its pointee. -template -class SingleOwnerPtr { + +//! Unsigned type with N-1 bits payload and 1-bit spin-lock or pointer type with LSB used as spin-lock bit. +template +class LockedValue { public: - SingleOwnerPtr() : ptr_(0) {} - explicit SingleOwnerPtr(T* ptr, Ownership_t::Type t = Ownership_t::Acquire) - : ptr_(uintp(ptr) | uintp(t == Ownership_t::Acquire)) { - } - ~SingleOwnerPtr() { *this = 0; } - bool is_owner() const { return test_bit(ptr_, 0); } - T* get() const { return (T*)clear_bit(ptr_, 0); } - T& operator*() const { return *get(); } - T* operator->() const { return get(); } - SingleOwnerPtr& operator=(T* ptr) { reset(ptr); return *this; } - void swap(SingleOwnerPtr& o) { std::swap(ptr_, o.ptr_); } - T* release() { store_clear_bit(ptr_, 0); return get(); } - T* acquire() { store_set_bit(ptr_, 0); return get(); } - void reset(T* x) { - if (x != get() && is_owner()) { D deleter; deleter(release()); } - ptr_ = set_bit(uintp(x),0); - } + static_assert(std::unsigned_integral || std::is_pointer_v, "must be unsigned or pointer"); + + explicit LockedValue(T val = {}) : val_(fromVal(val, 0u)) {} + + [[nodiscard]] T value() const noexcept { return toVal(val_.load(mt::memory_order_acquire)); } + + T lock() noexcept { + T ret; + while (not try_lock(&ret)) { wait(val_.ref(), ret); } + return ret; + } + bool try_lock(T* out = nullptr) noexcept { + T ignore; + return try_lock(val_.ref(), out ? *out : ignore); + } + void store_unlock(T value) noexcept { + val_.store(fromVal(value, 0u), mt::memory_order_release); + notify(val_.ref()); + } + void unlock() noexcept { store_unlock({}); } + private: - SingleOwnerPtr(const SingleOwnerPtr&); - SingleOwnerPtr& operator=(const SingleOwnerPtr&); - uintp ptr_; + using StoreType = std::conditional_t, uintptr_t, T>; + static T toVal(StoreType in) { + if constexpr (std::is_pointer_v) { + return reinterpret_cast(Potassco::clear_mask(in, 1u)); + } + else { + return in >> 1u; + } + } + static StoreType fromVal(T in, StoreType locked) { + if constexpr (std::is_pointer_v) { + assert(not Potassco::test_bit(reinterpret_cast(in), 0u)); + return reinterpret_cast(in) | locked; + } + else { + assert(in < std::numeric_limits::max() / 2); + return (in << 1u) | locked; + } + } + + template + static bool try_lock(A& address, T& out) { + auto x = address.fetch_or(1u, mt::memory_order_acquire); + out = toVal(x); + return not Potassco::test_bit(x, 0u); + } + template + static void wait(A& address, T v) { + if constexpr (requires { address.wait(fromVal(v, 1u), mt::memory_order_relaxed); }) { + address.wait(fromVal(v, 1u), mt::memory_order_relaxed); + } + } + template + static void notify(A& address) { + if constexpr (requires { address.notify_one(); }) { + address.notify_one(); + } + } + mt::ThreadSafe val_; }; -template -class FlaggedPtr { + +class RefCount { public: - FlaggedPtr() : ptr_(0) {} - explicit FlaggedPtr(T* ptr, bool sf = false) : ptr_(uintp(ptr)) { if (sf) flag(); } - bool flagged() const { return test_bit(ptr_, 0); } - T* get() const { return (T*)clear_bit(ptr_, 0); } - T& operator*() const { return *get(); } - T* operator->() const { return get(); } - void swap(FlaggedPtr& o) { std::swap(ptr_, o.ptr_); } - void flag() { store_set_bit(ptr_, 0); } - void unflag() { store_clear_bit(ptr_, 0); } -private: - uintp ptr_; -}; -template -inline FlaggedPtr make_flagged(T* ptr, bool setFlag) { return FlaggedPtr(ptr, setFlag); } + explicit RefCount(uint32_t init = 1) : rc_{init} {} + [[nodiscard]] uint32_t count() const noexcept { return rc_.load(mt::memory_order_acquire); } + operator uint32_t() const noexcept { return count(); } + void reset(uint32_t n) { rc_.store(n, mt::memory_order_relaxed); } + void add(uint32_t n = 1) { rc_.add(n, mt::memory_order_relaxed); } + bool release(uint32_t n = 1) { return release_fetch(n) == 0; } + uint32_t release_fetch(uint32_t n = 1) { return rc_.sub(n); } -//! A (numerical) range represented by a low and a high value. -template -struct Range { - Range(T x, T y) : lo(x), hi(y) { if (x > y) { hi = x; lo = y; } } - T clamp(T val) const { - if (val < lo) return lo; - if (val > hi) return hi; - return val; - } - T lo; - T hi; -}; -template -inline bool operator==(const Range& lhs, const Range& rhs) { - return lhs.lo == rhs.lo && lhs.hi == rhs.hi; -} -//! An iterator type for iterating over a range of numerical values. -template -struct num_iterator : std::iterator { - explicit num_iterator(const T& val) : val_(val) {} - typedef typename std::iterator::value_type value_type; - typedef typename std::iterator::difference_type difference_type; - bool operator==(const num_iterator& rhs) const { return val_ == rhs.val_; } - bool operator!=(const num_iterator& rhs) const { return val_ != rhs.val_; } - bool operator<=(const num_iterator& rhs) const { return val_ <= rhs.val_; } - bool operator< (const num_iterator& rhs) const { return val_ < rhs.val_; } - bool operator>=(const num_iterator& rhs) const { return val_ >= rhs.val_; } - bool operator> (const num_iterator& rhs) const { return val_ > rhs.val_; } - value_type operator*() const { return val_; } - T const* operator->() const { return &val_; } - num_iterator& operator++() { ++val_; return *this; } - num_iterator operator++(int) { num_iterator t(*this); ++*this; return t; } - num_iterator& operator--() { --val_; return *this; } - num_iterator operator--(int) { num_iterator t(*this); --*this; return t; } - num_iterator& operator+=(difference_type n) { val_ += n; return *this; } - num_iterator& operator-=(difference_type n) { val_ -= n; return *this; } - num_iterator operator+(difference_type n) const { return num_iterator(static_cast(val_ + n)); } - num_iterator operator-(difference_type n) const { return num_iterator(static_cast(val_ - n)); } - value_type operator[](difference_type n)const { return val_ + n; } - friend num_iterator operator+(difference_type n, num_iterator it) { return num_iterator(it.val_ + n); } private: - T val_; + mt::ThreadSafe rc_; }; -//@} -//! Base class for library events. -struct Event { - //! Set of known event sources. - enum Subsystem { subsystem_facade = 0, subsystem_load = 1, subsystem_prepare = 2, subsystem_solve = 3 }; - //! Possible verbosity levels. - enum Verbosity { verbosity_quiet = 0, verbosity_low = 1, verbosity_high = 2, verbosity_max = 3 }; - explicit Event(Subsystem sys, uint32 evId, Verbosity verbosity) : system(sys), verb(verbosity), op(0), id(evId) {} - uint32 system : 2; //!< One of Event::Subsystem - subsystem that produced the event. - uint32 verb : 2; //!< One of Event::Verbosity - the verbosity level of this event. - uint32 op : 8; //!< Operation that triggered the event. - uint32 id : 16;//!< Type id of event. - static uint32 nextId(); -}; -//! CRTP-base class for events of type T that registers an id for type T. -template -struct Event_t : Event { - Event_t(Subsystem sys, Verbosity verb) : Event(sys, id_s, verb) {} - static const uint32 id_s; -}; -template const uint32 Event_t::id_s = Event::nextId(); +class SigAtomic { +public: + SigAtomic() = default; + [[nodiscard]] int value() const noexcept { return sig_.load(mt::memory_order_acquire); } + operator int() const noexcept { return value(); } + bool set_if_unset(int sig) { + int unset = 0; + return sig_.compare_exchange_strong(unset, sig); + } + int exchange(int sig) { return sig_.exchange(sig, mt::memory_order_acquire); } -template const ToType* event_cast(const EvType& ev) { return ev.id == ToType::id_s ? static_cast(&ev) : 0; } +private: + mt::ThreadSafe sig_{0}; +}; -} -#endif +} // namespace Clasp diff --git a/clasp/util/multi_queue.h b/clasp/util/multi_queue.h index bd352ba..fdefb59 100644 --- a/clasp/util/multi_queue.h +++ b/clasp/util/multi_queue.h @@ -1,7 +1,7 @@ // // Copyright (c) 2010-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,239 +21,234 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_MULTI_QUEUE_H_INCLUDED -#define CLASP_MULTI_QUEUE_H_INCLUDED +#pragma once #include -namespace Clasp { namespace mt { namespace Detail { +#include -struct RawNode; -typedef Clasp::Atomic_t::type SafeNodePtr; -struct RawNode { - SafeNodePtr next; -}; -// Lock-free stack that is NOT ABA-safe by itself -struct RawStack { - RawStack() { top = static_cast(0); } - RawNode* tryPop() { - RawNode* n = 0, *next = 0; - do { - if ((n = top) == 0) { return 0; } - // NOTE: - // it is the caller's job to guarantee that n is safe - // and n->next is ABA-safe at this point. - next = n->next; - } while (compare_and_swap(top, n, next) != n); - return n; - } - void push(RawNode* n) { - RawNode* assumedTop; - do { - assumedTop = top; - n->next = assumedTop; - } while (compare_and_swap(top, assumedTop, n) != assumedTop); - } - SafeNodePtr top; -}; -struct DefaultDeleter { - template - void operator()(T& obj) const { - (void)obj; - obj.~T(); - } +namespace Clasp::mt { // NOLINT + +//! Non-owning lock-free stack that is NOT ABA-safe by itself. +class LockFreeStack { +public: + struct Node { + using AtomicPtr = ThreadSafe; + AtomicPtr next; + }; + + LockFreeStack() = default; + ~LockFreeStack() = default; + LockFreeStack(LockFreeStack&&) = delete; + + void push(Node* n) { + auto* assumedTop = top_.load(memory_order_acquire); + do { + n->next.store(assumedTop, memory_order_relaxed); // not shared + } while (not top_.compare_exchange_weak(assumedTop, n)); + } + + Node* tryPop() { + Node *next, *n = top_.load(memory_order_acquire); + do { + if (n == nullptr) { + return nullptr; + } + // NOTE: + // it is the caller's job to guarantee that n is safe + // and n->next is ABA-safe at this point. + next = n->next.load(memory_order_relaxed); + } while (not top_.compare_exchange_weak(n, next)); + return n; + } + + Node* release() { return top_.exchange(nullptr); } + +private: + Node::AtomicPtr top_{nullptr}; }; -} -//! A (base) class for distributing items between n different threads. +//! A class for distributing items between a fixed number of threads. /*! - * Logically, the class maintains n queues, one for each - * involved thread. Threads must register themselves by - * calling addThread(). The returned handle has then - * to be used for publishing and consuming items. + * Logically, the class maintains n queues, one for each involved thread. + * Threads must register themselves by calling addThread(). + * The returned handle can then be used for consuming items. */ -template +template class MultiQueue { -protected: - typedef Detail::RawNode RawNode; - typedef Clasp::Atomic_t::type SafeInt; - struct Node : Detail::RawNode { - explicit Node(uint32 rc, const T& d) : data(d) { next = 0; refs = rc; } - SafeInt refs; - T data; - }; public: - typedef Detail::RawNode* ThreadId; - //! creates a new object for at most m threads - explicit MultiQueue(uint32 m, const Deleter& d = Deleter()) : maxQ_(m), deleter_(d) { - head_.next = 0; - tail_ = &head_; - } - uint32 maxThreads() const { return maxQ_; } - void reserve(uint32 c) { - struct NodeHead : RawNode { SafeInt refs; }; - for (uint32 i = 0; i != c; ++i) { - free_.push(new (::operator new(sizeof(Node))) NodeHead()); - } - } - //! destroys the object and all unconsumed items - ~MultiQueue() { - for (Detail::RawNode* x = head_.next; x ; ) { - Node* n = toNode(x); - x = x->next; - deleter_(n->data); - ::operator delete(n); - } - for (Detail::RawNode* x; (x = free_.tryPop()) != 0; ) { - ::operator delete(toNode(x)); - } - } - //! adds a new thread to the object - /*! - * \note Shall be called at most m times - * \return A handle identifying the new thread - */ - ThreadId addThread() { - return &head_; - } - bool hasItems(ThreadId& cId) const { return cId != tail_; } + using ThreadId = LockFreeStack::Node*; - //! tries to consume an item - /*! - * \pre cId was initially obtained via a call to addThread() - * \note tryConsume() is thread-safe w.r.t different ThreadIds - */ - bool tryConsume(ThreadId& cId, T& out) { - if (cId != tail_) { - RawNode* n = cId; - cId = cId->next; - assert(cId != 0 && "MultiQueue is corrupted!"); - release(n); - out = toNode(cId)->data; - return true; - } - return false; - } - //! pops an item from the queue associated with the given thread - /*! - * \pre hasItems(cId) == true - */ - void pop(ThreadId& cId) { - assert(hasItems(cId) && "Cannot pop from empty queue!"); - RawNode* n = cId; - cId = cId->next; - release(n); - } -protected: - //! publishes a new item - /*! - * \note the function is *not* thread-safe, i.e. - * it must not be called concurrently - */ - void unsafePublish(const T& in, const ThreadId&) { unsafePublish(in); } - void unsafePublish(const T& in) { publishRelaxed(allocate(maxQ_, in)); } + //! Creates a new object for at most m threads. + explicit MultiQueue(uint32_t m) : maxQ_(m) {} + //! Destroys the object and all unconsumed items. + ~MultiQueue() { + destroy(head_.next.load(), true); + destroy(free_.release(), false); + } + MultiQueue(MultiQueue&&) = delete; - //! concurrency-safe version of unsafePublish - void publish(const T& in, const ThreadId&) { - RawNode* newNode = allocate(maxQ_, in); - RawNode* assumedTail, *assumedNext; - do { - assumedTail = tail_; - assumedNext = assumedTail->next; - if (assumedTail != tail_) { - // tail has changed - try again - continue; - } - if (assumedNext != 0) { - // someone has added a new node but has not yet - // moved the tail - assist him and start over - compare_and_swap(tail_, assumedTail, assumedNext); - continue; - } - } while (compare_and_swap(assumedTail->next, static_cast(0), newNode) != 0); - // Now that we managed to link a new node to what we think is the current tail - // we try to update the tail. If the tail is still what we think it is, - // it is moved - otherwise some other thread already did that for us. - compare_and_swap(tail_, assumedTail, newNode); - } + //! Pre-allocate a given number of internal queue nodes. + void reserve(uint32_t n) { + for (uint32_t i = 0; i != n; ++i) { free_.push(new SharedNode()); } + } + + //! Returns the maximal number of threads that can access this queue. + [[nodiscard]] uint32_t maxThreads() const { return maxQ_; } + + //! Registers a thread with the queue. + /*! + * \note Shall be called at most @c maxThreads() times. + * \return A handle identifying the new thread. + */ + ThreadId addThread() { return &head_; } + + //! Returns whether the given thread has unconsumed items in the queue. + [[nodiscard]] bool hasItems(const ThreadId& cId) const { return cId != tail_.load(); } + + //! Tries to consume an item from the queue associated with the given thread. + /*! + * \pre cId was initially obtained via a call to addThread() + * \note tryConsume() is thread-safe w.r.t different ThreadIds + * \note The returned pointer is only valid until the next call to this function from the given thread. + */ + const T* tryConsume(ThreadId& cId) { + if (cId != tail_.load()) { + auto* n = cId; + cId = cId->next.load(); + assert(cId != nullptr && "MultiQueue is corrupted!"); + release(n); + return toNode(cId)->value(); + } + return nullptr; + } + + //! Adds a new item to all queues. + void publish(T&& in) { publishSafe(allocate(std::forward(in))); } + + //! Adds new item to all queues. + /*! + * \note the function is *not* thread-safe, i.e. it must not be called concurrently. + */ + void unsafePublish(T&& in) { publishUnsafe(allocate(std::forward(in))); } - //! Non-atomically adds n to the global queue - void publishRelaxed(Node* n) { - static_cast(tail_)->next = n; - tail_ = n; - } - uint32 maxQ() const { return maxQ_; } - Node* allocate(uint32 maxR, const T& in) { - // If the queue is used correctly, the raw stack is ABA-safe at this point. - // The ref-counting in the queue makes sure that a node cannot be added back - // to the stack while another thread is still in tryPop() - that thread had - // not yet the chance to decrease the node's ref count. - if (Node* n = toNode(free_.tryPop())) { - n->next = 0; - n->refs = maxR; - new (&n->data)T(in); - return n; - } - return new (::operator new(sizeof(Node))) Node(maxR, in); - } private: - MultiQueue(const MultiQueue&); - MultiQueue& operator=(const MultiQueue&); - Node*toNode(Detail::RawNode* x) const { return static_cast(x); } - void release(Detail::RawNode* n) { - if (n != &head_ && --toNode(n)->refs == 0) { - head_.next = static_cast(n->next); - deleter_(toNode(n)->data); - free_.push(n); - } - } - RawNode head_; - Detail::SafeNodePtr tail_; - Detail::RawStack free_; - const uint32 maxQ_; - Deleter deleter_; + using TailPtr = LockFreeStack::Node::AtomicPtr; + using BaseNode = LockFreeStack::Node; + struct SharedNode : BaseNode { + RefCount refs; + alignas(T) char buffer[sizeof(T)]; + const T* value() const { return reinterpret_cast(buffer); } + }; + static constexpr SharedNode* toNode(BaseNode* x) { return static_cast(x); } + static void destroyValue(SharedNode* x) { std::destroy_at(reinterpret_cast(x->buffer)); } + + void destroy(BaseNode* head, bool destruct) { + for (auto* x = head; x;) { + auto* n = toNode(x); + x = x->next.load(memory_order_relaxed); + if (destruct) { + destroyValue(n); + } + delete n; + } + } + SharedNode* allocate(T&& in) { + // If the queue is used correctly, the raw stack is ABA-safe at this point. + // The ref-counting in the queue ensures that a node cannot be added back + // to the stack while another thread is still in tryConsume() - that thread had + // not yet the chance to decrease the node's ref count. + auto* n = toNode(free_.tryPop()); + if (not n) { + n = new SharedNode(); + } + n->next.store(nullptr, memory_order_relaxed); + n->refs.reset(maxQ_); + std::construct_at(std::launder(reinterpret_cast(n->buffer)), std::forward(in)); + return n; + } + void release(BaseNode* n) { + if (n != &head_ && toNode(n)->refs.release()) { + head_.next.store(n->next.load()); + destroyValue(toNode(n)); + free_.push(n); + } + } + void publishSafe(BaseNode* newNode) { + assert(newNode->next.load() == nullptr); + for (;;) { + auto* assumedTail = tail_.load(); + auto* assumedNext = assumedTail->next.load(); + if (assumedTail != tail_.load()) { + // tail has changed - try again + continue; + } + if (assumedNext != nullptr) { + // someone has added a new node but has not yet + // moved the tail - assist him and start over + tail_.compare_exchange_weak(assumedTail, assumedNext); + } + else if (assumedTail->next.compare_exchange_weak(assumedNext, newNode)) { + // Now that we managed to link a new node to what we think is the current tail + // we try to update the tail. If the tail is still what we think it is, + // it is moved - otherwise some other thread already did that for us. + tail_.compare_exchange_strong(assumedTail, newNode); + break; + } + } + } + void publishUnsafe(BaseNode* newNode) { + tail_.load()->next.store(newNode); + tail_.store(newNode); + } + + BaseNode head_{}; + TailPtr tail_{&head_}; + LockFreeStack free_{}; + uint32_t maxQ_; }; //! Unbounded non-intrusive lock-free multi-producer single consumer queue. /*! * Based on Dmitriy Vyukov's MPSC queue: - * http://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue + * https://www.1024cores.net/home/lock-free-algorithms/queues/non-intrusive-mpsc-node-based-queue */ -class MPSCPtrQueue { +template +class MpScPtrQueue { public: - typedef Detail::RawNode RawNode; - struct Node : RawNode { void* data; }; - typedef Clasp::Atomic_t::type SafeNodePtr; - Node* toNode(RawNode* n) const { return static_cast(n); } - MPSCPtrQueue() {} - void init(Node* sent) { - sent->next = 0; - sent->data = 0; - head_ = sent; - tail_ = sent; - } - bool empty() const { return !static_cast(tail_->next); } - void push(Node* n) { - n->next = 0; - Node* p = head_.exchange(n); - p->next = n; - } - Node* pop() { - Node* t = tail_; - Node* n = toNode(t->next); - if (!n) { return 0; } - tail_ = n; - t->data = n->data; - n->data = 0; - return t; - } + using BaseNode = LockFreeStack::Node; + struct Node : BaseNode { + void* data{}; + }; + using SafeNodePtr = ThreadSafe; + static Node* toNode(BaseNode* n) { return static_cast(n); } + explicit MpScPtrQueue(Node& sent) : head_(&sent), tail_(&sent) { + sent.next.store(nullptr, std::memory_order_relaxed); + sent.data = nullptr; + } + MpScPtrQueue(const MpScPtrQueue&) = delete; + MpScPtrQueue& operator=(const MpScPtrQueue&) = delete; + + [[nodiscard]] bool empty() const { return tail_->next == nullptr; } + void push(Node* n) { + n->next.store(nullptr, std::memory_order_relaxed); + Node* p = head_.exchange(n); + p->next.store(n, std::memory_order_release); + } + Node* pop() { + Node* t = tail_; + if (Node* n = toNode(t->next.load(std::memory_order_acquire)); n) { + tail_ = n; + t->data = n->data; + n->data = nullptr; + return t; + } + return nullptr; + } + private: - MPSCPtrQueue(const MPSCPtrQueue&); - MPSCPtrQueue& operator=(const MPSCPtrQueue&); - SafeNodePtr head_; // producers - char pad_[64 - sizeof(Node*)]; - Node* tail_; // consumer + SafeNodePtr head_{}; // producers + alignas(AlignSize) Node* tail_{}; // consumer }; -} } // end namespace Clasp::mt -#endif +} // end namespace Clasp::mt diff --git a/clasp/util/pod_vector.h b/clasp/util/pod_vector.h index 36e0868..785ac51 100644 --- a/clasp/util/pod_vector.h +++ b/clasp/util/pod_vector.h @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,561 +21,606 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef BK_LIB_POD_VECTOR_H_INCLUDED -#define BK_LIB_POD_VECTOR_H_INCLUDED -#include "type_manip.h" -#include -#include -#include +#pragma once + #include #include +#include +#include +#include +#include #include +#include +#include #if defined(__GNUC__) #pragma GCC system_header #endif -namespace bk_lib { namespace detail { - template - void fill(T* first, T* last, const T& x) { - assert(first <= last); - switch ((last - first) & 7u) - { - case 0: - while (first != last) - { - new(first++) T(x); - case 7: new(first++) T(x); - case 6: new(first++) T(x); - case 5: new(first++) T(x); - case 4: new(first++) T(x); - case 3: new(first++) T(x); - case 2: new(first++) T(x); - case 1: new(first++) T(x); - assert(first <= last); - } - } - } - template - void copy(Iter first, Iter last, std::size_t s, T* out) { - switch (s & 7u) - { - case 0: - while (first != last) - { - new(out++) T(*first++); - case 7: new(out++) T(*first++); - case 6: new(out++) T(*first++); - case 5: new(out++) T(*first++); - case 4: new(out++) T(*first++); - case 3: new(out++) T(*first++); - case 2: new(out++) T(*first++); - case 1: new(out++) T(*first++); - } - } - } - template - struct Fill { - Fill(const T& val) : val_(val) {} - void operator()(T* first, std::size_t n) const { detail::fill(first, first + n, val_); } - const T& val_; - private: Fill& operator=(const Fill&); - }; - template - struct Copy { - Copy(Iter first, Iter last) : first_(first), last_(last) {} - template - void operator()(T* out, std::size_t n) const { detail::copy(first_, last_, n, out); } - Iter first_; - Iter last_; - }; - template - struct Memcpy { - Memcpy(const T* first) : first_(first) {} - void operator()(T* out, std::size_t n) const { - if (out && n) { - std::memcpy(out, first_, n*sizeof(T)); - } - } - const T* first_; - }; - typedef char yes_type; - typedef char (&no_type)[2]; - template - struct IterType { - static yes_type isPtr(const volatile void*); - static no_type isPtr(...); - static yes_type isLong(long long); - static no_type isLong(...); - static T& makeT(); - enum { ptr = sizeof(isPtr(makeT())) == sizeof(yes_type) }; - enum { num = sizeof(isLong(makeT())) == sizeof(yes_type) }; - enum { value = ptr ? 1 : num ? 2 : 0 }; - }; - -} // end namespace bk_lib::detail +namespace bk_lib { +namespace detail { +template +void fill(T* first, T* last, const T& x) { + assert(first <= last); + switch ((last - first) & 7u) { + default: + while (first != last) { + new (first++) T(x); + case 7: new (first++) T(x); [[fallthrough]]; + case 6: new (first++) T(x); [[fallthrough]]; + case 5: new (first++) T(x); [[fallthrough]]; + case 4: new (first++) T(x); [[fallthrough]]; + case 3: new (first++) T(x); [[fallthrough]]; + case 2: new (first++) T(x); [[fallthrough]]; + case 1: new (first++) T(x); assert(first <= last); + } + } +} +template +void copy(Iter first, Iter last, std::size_t s, T* out) { + switch (s & 7u) { + default: + while (first != last) { + new (out++) T(*first++); + case 7: new (out++) T(*first++); [[fallthrough]]; + case 6: new (out++) T(*first++); [[fallthrough]]; + case 5: new (out++) T(*first++); [[fallthrough]]; + case 4: new (out++) T(*first++); [[fallthrough]]; + case 3: new (out++) T(*first++); [[fallthrough]]; + case 2: new (out++) T(*first++); [[fallthrough]]; + case 1: new (out++) T(*first++); [[fallthrough]]; + } + } +} + +template +struct Copy { + Copy(Iter b, Iter e) : first(b), last(e) {} + template + void operator()(T* out, std::size_t n) const { + detail::copy(first, last, n, out); + } + Iter first; + Iter last; +}; +template +struct Memcpy { + Memcpy(const T* b) : first(b) {} + void operator()(T* out, std::size_t n) const { not out || std::memcpy(out, first, n * sizeof(T)); } + const T* first; +}; + +} // namespace detail //! A std::vector-replacement for POD-Types. /*! * \pre T is a POD-Type - * \see http://www.comeaucomputing.com/techtalk/#pod for a description of POD-Types. + * \see https://en.cppreference.com/w/cpp/named_req/PODType for a description of POD-Types. * \note Does not call any destructors and uses std::memcpy to copy/move elements * \note On LP64-machines size and capacity are represented as unsigned integers (instead of e.g. std::size_t) */ -template > +template > class pod_vector { public: - // types: - typedef pod_vector this_type;//not standard - typedef Allocator allocator_type; - typedef typename Allocator::reference reference; - typedef typename Allocator::const_reference const_reference; - typedef typename Allocator::pointer iterator; - typedef typename Allocator::const_pointer const_iterator; - typedef typename Allocator::pointer pointer; - typedef typename Allocator::const_pointer const_pointer; - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; - typedef T value_type; - typedef typename detail::if_then_else< - sizeof(typename Allocator::size_type)<=sizeof(unsigned int), - typename Allocator::size_type, - unsigned int>::type size_type; - typedef typename detail::if_then_else< - sizeof(typename Allocator::difference_type)<=sizeof(int), - typename Allocator::difference_type, - int>::type difference_type; - // ctors - //! constructs an empty pod_vector. - /*! - * \post size() == capacity() == 0 - */ - pod_vector() : ebo_(0, allocator_type()) { } - - //! constructs an empty pod_vector that uses a copy of a for memory allocations. - /*! - * \post size() == capacity() == 0 - */ - explicit pod_vector(const allocator_type& a) : ebo_(0, a) { } - - //! constructs a pod_vector containing n copies of value. - /*! - * \post size() == n - */ - explicit pod_vector(size_type n, const T& value = T(), const allocator_type& a = allocator_type()) - : ebo_(n, a) { - detail::fill(ebo_.buf, ebo_.buf + n, value); - ebo_.size = n; - } - - //! constructs a pod_vector equal to the range [first, last). - /*! - * \post size() = distance between first and last. - */ - template - pod_vector(Iter first, Iter last, const allocator_type& a = allocator_type(), typename detail::disable_if::num>::type* = 0) - : ebo_(0, a) { - insert_range(end(), first, last, typename std::iterator_traits::iterator_category()); - } - - //! creates a copy of other - /*! - * \post size() == other.size() && capacity() == other.size() - */ - pod_vector(const pod_vector& other) : ebo_(other.size(), other.get_allocator()) { - if (const_pointer buf = other.begin()) { - std::memcpy(ebo_.buf, buf, other.size()*sizeof(T)); - } - ebo_.size = other.size(); - } - - pod_vector& operator=(const pod_vector& other) { - if (this != &other) { - assign(other.begin(), other.end()); - } - return *this; - } - - //! frees all memory allocated by this pod_vector. - /*! - * \note Won't call any destructors, because PODs don't have those. - */ - ~pod_vector() { } - - /** @name inspectors - * inspector-functions - */ - //@{ - - //! returns the number of elements currently stored in this pod_vector. - size_type size() const { return ebo_.size; } - //! size of the largest possible pod_vector - size_type max_size() const { - typename allocator_type::size_type x = get_allocator().max_size(); - std::size_t y = size_type(-1)/sizeof(T); - return static_cast(std::min(std::size_t(x), y)); - } - //! returns the total number of elements this pod_vector can hold without requiring reallocation. - size_type capacity() const { return ebo_.cap; } - //! returns size() == 0 - bool empty() const { return ebo_.size == 0; } - - const_iterator begin() const { return ebo_.buf; } - const_iterator end() const { return ebo_.buf+ebo_.size;} - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - - iterator begin() { return ebo_.buf; } - iterator end() { return ebo_.buf+ebo_.size; } - reverse_iterator rbegin() { return reverse_iterator(end()); } - reverse_iterator rend() { return reverse_iterator(begin()); } - - //! returns a copy of the allocator used by this pod_vector - allocator_type get_allocator() const { return ebo_; } - - //@} - /** @name elemacc - * element access - */ - //@{ - - //! returns a reference to the element at position n - /*! - * \pre n < size() - */ - reference operator[](size_type n) { - assert(n < size()); - return ebo_.buf[n]; - } - - //! returns a reference-to-const to the element at position n - /*! - * \pre n < size() - */ - const_reference operator[](size_type n) const { - assert(n < size()); - return ebo_.buf[n]; - } - - //! same as operator[] but throws std::out_of_range if pre-condition is not met. - const_reference at(size_type n) const { - if (n < size()) return ebo_.buf[n]; - throw std::out_of_range("pod_vector::at"); - } - //! same as operator[] but throws std::out_of_range if pre-condition is not met. - reference at(size_type n) { - if (n < size()) return ebo_.buf[n]; - throw std::out_of_range("pod_vector::at"); - } - - //! equivalent to *begin() - reference front() { assert(!empty()); return *ebo_.buf; } - //! equivalent to *begin() - const_reference front() const { assert(!empty()); return *ebo_.buf; } - - //! equivalent to *--end() - reference back() { assert(!empty()); return ebo_.buf[ebo_.size-1]; } - - //! equivalent to *--end() - const_reference back() const { assert(!empty()); return ebo_.buf[ebo_.size-1]; } - - //@} - /** @name mutators - * mutator functions - */ - //@{ - - //! erases all elements in the range [begin(), end) - /*! - * \post size() == 0 - */ - void clear() { ebo_.size = 0; } - - void assign(size_type n, const T& val) { - clear(); - insert(end(), n, val); - } - - template - void assign(Iter first, Iter last) { - clear(); - insert(end(), first, last); - } - - //! erases the element pointed to by pos. - /*! - * \pre pos != end() && !empty() - * \return an iterator pointing to the element following pos (before that element was erased) - * of end() if no such element exists. - * - * \note invalidates all iterators and references referring to elements after pos. - */ - iterator erase(iterator pos) { - assert(!empty() && pos != end()); - erase(pos, pos + 1); - return pos; - } - - //! erases the elements in the range [first, last) - /*! - * \pre [first, last) must be a valid range. - */ - iterator erase(iterator first, iterator last) { - if (end() - last > 0) { - std::memmove(first, last, (end() - last) * sizeof(T)); - } - ebo_.size -= static_cast(last - first); - return first; - } - - //! adjusts the size of this pod_vector to ns. - /*! - * resize is equivalent to: - * if ns > size insert(end(), ns - size(), val) - * if ns < size erase(begin() + ns, end()) - * - * \post size() == ns - */ - void resize(size_type ns, const T& val = T()) { - if (ns > size()) { - ns <= capacity() ? detail::fill(end(), end()+(ns-size()), val) : append_realloc(ns-size(), val); - } - ebo_.size = ns; - } - - //! reallocates storage if necessary but never changes the size() of this pod_vector. - /*! - * \note if n is <= capacity() reserve is a noop. Otherwise, a reallocation takes place - * and capacity() >= n after reserve returned. - * \note reallocation invalidates all references, pointers and iterators referring to - * elements in this pod_vector. - * - * \note when reallocation occurs elements are copied from the old storage using memcpy. - */ - void reserve(size_type n) { - if (n > capacity()) { - T* temp = ebo_.allocate(n); - if (ebo_.buf) { - std::memcpy(temp, ebo_.buf, size()*sizeof(T)); - } - ebo_.release(); - ebo_.buf = temp; - ebo_.cap = n; - } - } - - void swap(pod_vector& other) { - std::swap(ebo_.buf, other.ebo_.buf); - std::swap(ebo_.size, other.ebo_.size); - std::swap(ebo_.cap, other.ebo_.cap); - } - - //! equivalent to insert(end(), x); - void push_back(const T& x) { - if (size() < capacity()) { - new ((ebo_.buf+ebo_.size++)) T(x); - } - else { - append_realloc(1, x); - } - } - - //! equivalent to erase(--end()); - /*! - * \pre !empty() - */ - void pop_back() { - assert(!empty()); - --ebo_.size; - } - - //! inserts a copy of val before pos. - /*! - * \pre pos is a valid iterator. - * \return an iterator pointing to the copy of val that was inserted. - * \note if size() + 1 > capacity() reallocation occurs. Otherwise iterators and - * references referring to elements before pos remain valid. - * - */ - iterator insert(iterator pos, const T& val) { - return insert(pos, (size_type)1, val); - } - - //! inserts n copies of val before pos. - /*! - * \pre pos is a valid iterator. - */ - iterator insert(iterator pos, size_type n, const T& val) { - size_type off = static_cast(pos-begin()); - insert_impl(pos, n, detail::Fill(val)); - return ebo_.buf + off; - } - - //! inserts copies of elements in the range [first, last) before pos. - /*! - * \pre first and last are not iterators into this pod_vector. - * \pre pos is a valid iterator. - * \note if first and last are pointers, memcpy is used to insert the elements - * in the range [first, last) into this container. - * - */ - template - void insert(iterator pos, Iter first, Iter last, typename detail::disable_if::num>::type* = 0) { - insert_range(pos, first, last, typename std::iterator_traits::iterator_category()); - } - - - /** @name nonstd - * Non-standard interface - */ - //@{ - - //! adjusts the size of this pod_vector to ns. - /*! - * In contrast to pod_vector::resize this function does not - * initializes new elements in case ns > size(). - * This reflects the behaviour of built-in arrays of pod-types. - * \note - * Any access to an uninitialized element is illegal unless it is accessed - * in order to assign a new value. - */ - void resize_no_init(size_type ns) { - reserve(ns); - ebo_.size = ns; - } - //@} + // NOLINTBEGIN + // types: + using this_type = pod_vector; // not standard + using alloc_traits = std::allocator_traits; + using allocator_type = Allocator; + using reference = T&; + using const_reference = const T&; + using iterator = typename alloc_traits::pointer; + using const_iterator = typename alloc_traits::const_pointer; + using pointer = typename alloc_traits::pointer; + using const_pointer = typename alloc_traits::const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using value_type = T; + using size_type = std::conditional_t; + using difference_type = std::conditional_t; + // NOLINTEND + + // ctors + //! constructs an empty pod_vector. + /*! + * \post size() == capacity() == 0 + */ + pod_vector() : ebo_(0, allocator_type()) {} + + //! constructs an empty pod_vector that uses a copy of a for memory allocations. + /*! + * \post size() == capacity() == 0 + */ + explicit pod_vector(const allocator_type& a) : ebo_(0, a) {} + + //! constructs a pod_vector containing n copies of value. + /*! + * \post size() == n + */ + explicit pod_vector(size_type n, const T& value = T(), const allocator_type& a = allocator_type()) : ebo_(n, a) { + detail::fill(ebo_.buf, ebo_.buf + n, value); + ebo_.size = n; + } + + //! constructs a pod_vector equal to the range [first, last). + /*! + * \post size() = distance between first and last. + */ + template + pod_vector(Iter first, Iter last, const allocator_type& a = allocator_type()) : ebo_(0, a) { + insert_range(end(), first, last); + } + + //! construct a pod_vector from an initializer list. + pod_vector(std::initializer_list l, const allocator_type& a = allocator_type()) + : pod_vector(l.begin(), l.end(), a) {} + + //! creates a copy of other + /*! + * \post size() == other.size() && capacity() == other.size() + */ + pod_vector(const pod_vector& other) : ebo_(other.size(), other.get_allocator()) { + if (auto* buf = other.begin()) { + std::memcpy(ebo_.buf, buf, other.size() * sizeof(T)); + } + ebo_.size = other.size(); + } + + pod_vector(pod_vector&& other) noexcept : ebo_(std::move(other.ebo_)) {} + + pod_vector& operator=(const pod_vector& other) { + if (this != &other) { + assign(other.begin(), other.end()); + } + return *this; + } + + pod_vector& operator=(pod_vector&& other) noexcept { + pod_vector(std::move(other)).swap(*this); + return *this; + } + + pod_vector& operator=(std::initializer_list l) { + assign(l.begin(), l.end()); + return *this; + } + + //! frees all memory allocated by this pod_vector. + /*! + * \note Won't call any destructors, because PODs don't have those. + */ + ~pod_vector() = default; + + /** @name inspectors + * inspector-functions + */ + //@{ + + //! returns the number of elements currently stored in this pod_vector. + size_type size() const { return ebo_.size; } + //! size of the largest possible pod_vector + size_type max_size() const { + typename allocator_type::size_type x = get_allocator().max_size(); + std::size_t y = size_type(-1) / sizeof(T); + return static_cast(std::min(static_cast(x), y)); + } + //! returns the total number of elements this pod_vector can hold without requiring reallocation. + size_type capacity() const { return ebo_.cap; } + //! returns size() == 0 + [[nodiscard]] bool empty() const { return ebo_.size == 0; } + + const_pointer data() const { return ebo_.buf; } + pointer data() { return ebo_.buf; } + + const_iterator begin() const { return ebo_.buf; } + const_iterator end() const { return ebo_.buf + ebo_.size; } + const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } + const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } + + iterator begin() { return ebo_.buf; } + iterator end() { return ebo_.buf + ebo_.size; } + reverse_iterator rbegin() { return reverse_iterator(end()); } + reverse_iterator rend() { return reverse_iterator(begin()); } + + //! returns a copy of the allocator used by this pod_vector + allocator_type get_allocator() const { return ebo_; } + + //@} + /** @name elemacc + * element access + */ + //@{ + + //! returns a reference to the element at position n + /*! + * \pre n < size() + */ + reference operator[](size_type n) { + assert(n < size()); + return ebo_.buf[n]; + } + + //! returns a reference-to-const to the element at position n + /*! + * \pre n < size() + */ + const_reference operator[](size_type n) const { + assert(n < size()); + return ebo_.buf[n]; + } + + //! same as operator[] but throws std::out_of_range if pre-condition is not met. + const_reference at(size_type n) const { + if (n < size()) { + return ebo_.buf[n]; + } + throw std::out_of_range("pod_vector::at"); + } + //! same as operator[] but throws std::out_of_range if pre-condition is not met. + reference at(size_type n) { + if (n < size()) { + return ebo_.buf[n]; + } + throw std::out_of_range("pod_vector::at"); + } + + //! equivalent to *begin() + reference front() { + assert(not empty()); + return *ebo_.buf; + } + //! equivalent to *begin() + const_reference front() const { + assert(not empty()); + return *ebo_.buf; + } + + //! equivalent to *--end() + reference back() { + assert(not empty()); + return ebo_.buf[ebo_.size - 1]; + } + + //! equivalent to *--end() + const_reference back() const { + assert(not empty()); + return ebo_.buf[ebo_.size - 1]; + } + + //@} + /** @name mutators + * mutator functions + */ + //@{ + + //! erases all elements in the range [begin(), end) + /*! + * \post size() == 0 + */ + void clear() { ebo_.size = 0; } + + void assign(size_type n, const T& val) { + clear(); + insert(end(), n, val); + } + + template + void assign(Iter first, Iter last) { + clear(); + insert(end(), first, last); + } + + void assign(std::initializer_list l) { assign(l.begin(), l.end()); } + + //! erases the element pointed to by pos. + /*! + * \pre pos != end() && !empty() + * \return an iterator pointing to the element following pos (before that element was erased) + * of end() if no such element exists. + * + * \note invalidates all iterators and references referring to elements after pos. + */ + iterator erase(iterator pos) { + assert(not empty() && pos != end()); + erase(pos, pos + 1); + return pos; + } + + //! erases the elements in the range [first, last) + /*! + * \pre [first, last) must be a valid range. + */ + iterator erase(iterator first, iterator last) { + if (end() - last > 0) { + std::memmove(first, last, (end() - last) * sizeof(T)); + } + ebo_.size -= static_cast(last - first); + return first; + } + + //! adjusts the size of this pod_vector to ns. + /*! + * resize is equivalent to: + * if ns > size insert(end(), ns - size(), val) + * if ns < size erase(begin() + ns, end()) + * + * \post size() == ns + */ + void resize(size_type ns, const T& val = T()) { + if (ns > size()) { + ns <= capacity() ? detail::fill(end(), end() + (ns - size()), val) : append_realloc(ns - size(), val); + } + ebo_.size = ns; + } + + //! reallocates storage if necessary but never changes the size() of this pod_vector. + /*! + * \note if n is <= capacity() reserve is a noop. Otherwise, a reallocation takes place + * and capacity() >= n after reserve returned. + * \note reallocation invalidates all references, pointers and iterators referring to + * elements in this pod_vector. + * + * \note when reallocation occurs elements are copied from the old storage using memcpy. + */ + void reserve(size_type n) { + if (n > capacity()) { + T* temp = ebo_.allocate(n); + not ebo_.buf || std::memcpy(temp, ebo_.buf, size() * sizeof(T)); + ebo_.release(); + ebo_.buf = temp; + ebo_.cap = n; + } + } + + void swap(pod_vector& other) noexcept { + std::swap(ebo_.buf, other.ebo_.buf); + std::swap(ebo_.size, other.ebo_.size); + std::swap(ebo_.cap, other.ebo_.cap); + } + + //! equivalent to insert(end(), x); + void push_back(const T& x) { + if (size() < capacity()) { + new ((ebo_.buf + ebo_.size++)) T(x); + } + else { + append_realloc(1, x); + } + } + + //! equivalent to erase(--end()); + /*! + * \pre !empty() + */ + void pop_back() { + assert(not empty()); + --ebo_.size; + } + + //! inserts a copy of val before pos. + /*! + * \pre pos is a valid iterator. + * \return an iterator pointing to the copy of val that was inserted. + * \note if size() + 1 > capacity() reallocation occurs. Otherwise, iterators and + * references referring to elements before pos remain valid. + * + */ + iterator insert(iterator pos, const T& val) { return insert(pos, static_cast(1), val); } + + //! inserts n copies of val before pos. + /*! + * \pre pos is a valid iterator. + */ + iterator insert(iterator pos, size_type n, const T& val) { + auto off = static_cast(pos - begin()); + insert_impl(pos, n, [&val](T* first, std::size_t num) { detail::fill(first, first + num, val); }); + return ebo_.buf + off; + } + + //! inserts copies of elements in the range [first, last) before pos. + /*! + * \pre first and last are not iterators into this pod_vector. + * \pre pos is a valid iterator. + * \note if first and last are pointers, memcpy is used to insert the elements + * in the range [first, last) into this container. + * + */ + template + void insert(iterator pos, Iter first, Iter last) { + insert_range(pos, first, last); + } + + iterator insert(const_iterator pos, std::initializer_list l) { return insert(pos, l.begin(), l.end()); } + + /** @name nonstd + * Non-standard interface + */ + //@{ + + //! adjusts the size of this pod_vector to ns. + /*! + * In contrast to pod_vector::resize this function does not + * initialize new elements in case ns > size(). + * This reflects the behaviour of built-in arrays of pod-types. + * \note + * Any access to an uninitialized element is illegal unless it is accessed + * in order to assign a new value. + */ + void resize_no_init(size_type ns) { + reserve(ns); + ebo_.size = ns; + } + //@} private: - size_type grow_size(size_type n) { - size_type new_cap = size() + n; - assert(new_cap > size() && "pod_vector: max size exceeded!"); - assert(new_cap > capacity()); - if (new_cap < 4) new_cap = 1 << (new_cap+1); - size_type x = (capacity()*3)>>1; - if (new_cap < x) new_cap = x; - return new_cap; - } - void append_realloc(size_type n, const T& x) { - size_type new_cap = grow_size(n); - pointer temp = ebo_.allocate(new_cap); - if (ebo_.buf) { - std::memcpy(temp, ebo_.buf, size()*sizeof(T)); - } - detail::fill(temp+size(), temp+size()+n, x); - ebo_.release(); - ebo_.buf = temp; - ebo_.cap = new_cap; - ebo_.size+= n; - } - void move_right(iterator pos, size_type n) { - assert( (pos || n == 0) && (ebo_.eos() - pos) >= (int)n); - if (pos) { - std::memmove(pos + n, pos, (end() - pos) * sizeof(T)); - } - } - template - void insert_range(iterator pos, It first, It last, std::random_access_iterator_tag, - typename detail::disable_if::value == 0 && detail::same_type::value == 0>::type* = 0) { - assert( (first < begin() || first >= end()) && "pod_vec::insert(): Precondition violated!"); - typename allocator_type::difference_type diff = std::distance(first, last); - assert(diff == 0 || (static_cast(size()+diff) > size() && "pod_vector: max size exceeded!")); - insert_impl(pos, static_cast(diff), detail::Memcpy(first)); - } - template - void insert_range(iterator pos, It first, It last, std::forward_iterator_tag) { - typename allocator_type::difference_type diff = std::distance(first, last); - assert(diff == 0 || (static_cast(size()+diff) > size() && "pod_vector: max size exceeded!")); - insert_impl(pos, static_cast(diff), detail::Copy(first, last)); - } - template - void insert_range(iterator pos, Iter first, Iter last, std::input_iterator_tag) { - pod_vector temp; - while (first != last) temp.push_back(*first++); - insert(pos, temp.begin(), temp.end()); - } - - // NOTE: template parameter ST should always equal size_type - // and is only needed to workaround an internal compiler error - // in gcc 3.4.3 - template - void insert_impl(iterator pos, ST n, const P& pred) { - assert(n == 0 || (size()+n) > size() ); - if (size()+n <= capacity()) { - move_right(pos, n); - pred(pos, n); - ebo_.size += n; - } - else { - size_type new_cap = grow_size(n); - pointer temp = ebo_.allocate(new_cap); - size_type prefix = static_cast(pos-begin()); - // copy prefix - if (const_pointer buf = begin()) { - std::memcpy(temp, buf, prefix*sizeof(T)); - } - // insert new stuff - pred(temp+prefix, n); - // copy suffix - if (pos) { - std::memcpy(temp+prefix+n, pos, (end()-pos)*sizeof(T)); - } - ebo_.release(); - ebo_.buf = temp; - ebo_.size+= n; - ebo_.cap = new_cap; - } - } - struct ebo : public Allocator { // empty-base-optimization - typedef typename this_type::size_type size_type; - typedef typename this_type::allocator_type A; - pointer buf; // pointer to array - size_type size; // current size (used elements) - size_type cap; // max size before regrow - ebo(size_type n, const Allocator& a) : Allocator(a), buf(0), size(0), cap(n) { - if (n > 0) { buf = A::allocate(n); } - } - ~ebo() { release(); } - void release() { if (buf) A::deallocate(buf, cap); } - T* eos() const { return buf + cap; } - } ebo_; + size_type grow_size(size_type n) { + size_type new_cap = size() + n; + assert(new_cap > size() && "pod_vector: max size exceeded!"); + assert(new_cap > capacity()); + if (new_cap < 4) { + new_cap = 1 << (new_cap + 1); + } + size_type x = (capacity() * 3) >> 1; + if (new_cap < x) { + new_cap = x; + } + return new_cap; + } + void append_realloc(size_type n, const T& x) { + size_type new_cap = grow_size(n); + pointer temp = ebo_.allocate(new_cap); + not ebo_.buf || std::memcpy(temp, ebo_.buf, size() * sizeof(T)); + detail::fill(temp + size(), temp + size() + n, x); + ebo_.release(); + ebo_.buf = temp; + ebo_.cap = new_cap; + ebo_.size += n; + } + void move_right(iterator pos, size_type n) { + assert((pos || n == 0) && (ebo_.eos() - pos) >= static_cast(n)); + not pos || std::memmove(pos + n, pos, (end() - pos) * sizeof(T)); + } + + template + void insert_range(iterator pos, It first, It last) { + typename allocator_type::difference_type diff = std::distance(first, last); + assert(diff == 0 || (static_cast(size() + diff) > size() && "pod_vector: max size exceeded!")); + if (diff == 0) { + return; + } + if constexpr (std::is_same_v || std::is_same_v) { + assert((first < begin() || first >= end()) && "pod_vec::insert(): Precondition violated!"); + insert_impl(pos, static_cast(diff), detail::Memcpy(first)); + } + else if constexpr (std::is_constructible_v) { + insert_impl(pos, static_cast(diff), detail::Memcpy(&*first)); + } + else { + insert_impl(pos, static_cast(diff), detail::Copy(first, last)); + } + } + + template + void insert_range(iterator pos, It first, It last) { + typename allocator_type::difference_type diff = std::distance(first, last); + assert(diff == 0 || (static_cast(size() + diff) > size() && "pod_vector: max size exceeded!")); + insert_impl(pos, static_cast(diff), detail::Copy(first, last)); + } + + template + void insert_range(iterator pos, It first, It last) { + pod_vector temp; + while (first != last) { temp.push_back(*first++); } + insert(pos, temp.begin(), temp.end()); + } + + // NOTE: template parameter ST should always equal size_type + // and is only needed to work around an internal compiler error + // in gcc 3.4.3 + template + void insert_impl(iterator pos, ST n, const P& pred) { + assert(n == 0 || (size() + n) > size()); + if (size() + n <= capacity()) { + move_right(pos, n); + pred(pos, n); + ebo_.size += n; + } + else { + size_type new_cap = grow_size(n); + pointer temp = ebo_.allocate(new_cap); + auto prefix = static_cast(pos - begin()); + if (pos) { + // copy prefix + std::memcpy(temp, begin(), prefix * sizeof(T)); + // insert new stuff + pred(temp + prefix, n); + // copy suffix + std::memcpy(temp + prefix + n, pos, (end() - pos) * sizeof(T)); + } + else { + assert(not begin() && not prefix); + // insert new stuff + pred(temp, n); + } + ebo_.release(); + ebo_.buf = temp; + ebo_.size += n; + ebo_.cap = new_cap; + } + } + struct ebo : public Allocator { // empty-base-optimization + using size_type = typename this_type::size_type; + using A = typename this_type::allocator_type; + pointer buf; // pointer to array + size_type size; // current size (used elements) + size_type cap; // max size before regrow + ebo(size_type n, const Allocator& a) : Allocator(a), buf(0), size(0), cap(n) { + if (n > 0) { + buf = A::allocate(n); + } + } + ebo(ebo&& other) noexcept + : Allocator(static_cast(other)) + , buf(std::exchange(other.buf, nullptr)) + , size(std::exchange(other.size, 0)) + , cap(std::exchange(other.cap, 0)) {} + ~ebo() { release(); } + void release() { + if (buf) { + A::deallocate(buf, cap); + } + } + T* eos() const { return buf + cap; } + } ebo_; }; -template +template inline bool operator==(const pod_vector& lhs, const pod_vector& rhs) { - return lhs.size() == rhs.size() - && std::equal(lhs.begin(), lhs.end(), rhs.begin()); + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); } -template +template inline bool operator!=(const pod_vector& lhs, const pod_vector& rhs) { - return ! (lhs == rhs); + return !(lhs == rhs); } -template +template inline bool operator<(const pod_vector& lhs, const pod_vector& rhs) { - return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } -template +template inline bool operator>(const pod_vector& lhs, const pod_vector& rhs) { - return rhs < lhs; + return rhs < lhs; } -template +template inline bool operator<=(const pod_vector& lhs, const pod_vector& rhs) { - return !(rhs < lhs); + return !(rhs < lhs); } -template +template inline bool operator>=(const pod_vector& lhs, const pod_vector& rhs) { - return !(lhs < rhs); + return !(lhs < rhs); } -template -inline void swap(pod_vector& lhs, pod_vector& rhs) { - lhs.swap(rhs); +template +inline void swap(pod_vector& lhs, pod_vector& rhs) noexcept { + lhs.swap(rhs); } +template +constexpr typename pod_vector::size_type erase_if(pod_vector& c, Pred pred) { + auto sz = c.size(); + c.erase(std::remove_if(c.begin(), c.end(), pred), c.end()); + return sz - c.size(); +} +template +constexpr typename pod_vector::size_type erase(pod_vector& c, const T& v) { + auto sz = c.size(); + c.erase(std::remove(c.begin(), c.end(), v), c.end()); + return sz - c.size(); } -#endif - +} // namespace bk_lib diff --git a/clasp/util/timer.h b/clasp/util/timer.h index 1ed76cf..e86708f 100644 --- a/clasp/util/timer.h +++ b/clasp/util/timer.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,13 +21,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // - -#ifndef CLASP_TIMER_H_INCLUDED -#define CLASP_TIMER_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif + /*! * \file * \brief Defines various types for getting absolute times. @@ -36,20 +31,20 @@ namespace Clasp { //! A type for getting the current process time. struct ProcessTime { - static double getTime(); + static double getTime(); }; //! A type for getting the current thread time. struct ThreadTime { - static double getTime(); + static double getTime(); }; //! A tpe for getting the current wall-clock time. struct RealTime { - static double getTime(); + static double getTime(); }; -inline double diffTime(double tEnd, double tStart) { - double diff = tEnd - tStart; - return diff >= 0 ? diff : 0.0; +constexpr double diffTime(double tEnd, double tStart) { + double diff = tEnd - tStart; + return diff >= 0 ? diff : 0.0; } //! A class for measuring elapsed time. @@ -60,23 +55,27 @@ inline double diffTime(double tEnd, double tStart) { template class Timer { public: - Timer() : start_(0), split_(0), total_(0) {} + Timer() : start_(0), split_(0), total_(0) {} + + void start() { start_ = TimeType::getTime(); } + void stop() { split(TimeType::getTime()); } + void reset() { *this = Timer(); } + //! Same as stop(), start(); + void lap() { + double t; + split(t = TimeType::getTime()); + start_ = t; + } + //! Returns the elapsed time (in seconds) for last start-stop cycle. + [[nodiscard]] double elapsed() const { return split_; } + //! Returns the total elapsed time for all start-stop cycles. + [[nodiscard]] double total() const { return total_; } - void start() { start_ = TimeType::getTime(); } - void stop() { split(TimeType::getTime()); } - void reset() { *this = Timer(); } - //! Same as stop(), start(); - void lap() { double t; split(t = TimeType::getTime()); start_ = t; } - //! Returns the elapsed time (in seconds) for last start-stop cycle. - double elapsed() const { return split_; } - //! Returns the total elapsed time for all start-stop cycles. - double total() const { return total_; } private: - void split(double t) { total_ += (split_ = diffTime(t, start_)); } - double start_; - double split_; - double total_; + void split(double t) { total_ += (split_ = diffTime(t, start_)); } + double start_; + double split_; + double total_; }; -} -#endif +} // namespace Clasp diff --git a/clasp/util/type_manip.h b/clasp/util/type_manip.h deleted file mode 100644 index dc28636..0000000 --- a/clasp/util/type_manip.h +++ /dev/null @@ -1,141 +0,0 @@ -// -// Copyright (c) 2010-2017 Benjamin Kaufmann -// -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to -// deal in the Software without restriction, including without limitation the -// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or -// sell copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -// IN THE SOFTWARE. -// -#ifndef BK_LIB_TYPE_MANIP_H_INCLUDED -#define BK_LIB_TYPE_MANIP_H_INCLUDED -namespace bk_lib { namespace detail { -#if (_MSC_VER >= 1300) -#define ALIGNOF(PARAM) (__alignof(PARAM)) -#elif defined(__GNUC__) -#define ALIGNOF(PARAM) (__alignof__(PARAM)) -#else -template -struct align_helper { char x; T y; }; -#define ALIGNOF(T) (sizeof(align_helper)-sizeof(T)) -#endif - - -// if b then if_type else else_type -template -struct if_then_else; - -template -struct if_then_else { typedef if_type type; }; -template -struct if_then_else { typedef else_type type; }; - -// 1 if T == U, else 0 -template struct same_type { enum { value = 0 }; }; -template struct same_type { enum { value = 1 }; }; - -template struct disable_if { typedef bool type; }; -template <> struct disable_if { }; - -// not in list - marks end of type list -struct nil_type {}; - -// list of types - terminated by nil_type -template -struct type_list { - typedef head head_type; - typedef tail tail_type; -}; - -// generates a type lits with up to 18 elements -template < - typename T1 = nil_type, typename T2 = nil_type, typename T3 = nil_type, - typename T4 = nil_type, typename T5 = nil_type, typename T6 = nil_type, - typename T7 = nil_type, typename T8 = nil_type, typename T9 = nil_type, - typename T10 = nil_type, typename T11 = nil_type, typename T12 = nil_type, - typename T13 = nil_type, typename T14 = nil_type, typename T15 = nil_type, - typename T16 = nil_type, typename T17 = nil_type, typename T18 = nil_type -> -struct generate_type_list { - typedef typename generate_type_list::type tail_type; - typedef type_list type; -}; - -template <> -struct generate_type_list<> { typedef nil_type type; }; - -// maps an integer constant to a type -template -struct int2type { enum { value = i }; }; -typedef int2type<0> false_type; -typedef int2type<1> true_type; - -// declared but not defined -struct unknown_type; - -// finds the element in the type list TList that -// has the same alignment as X or X if no such element exists -template -struct max_align; - -// IF ALIGNOF(X) == ALIGNOF(H) then H -// ELSE max_align -template -struct max_align_aux; - -// Base case: ALIGNOF(X) == ALIGNOF(H) -template -struct max_align_aux { - typedef H type; -}; - -// Recursive case -template -struct max_align_aux { - typedef typename max_align::type type; -}; - -template -struct max_align { - typedef X type; -}; - -template -struct max_align > { -private: - enum { x_align = ALIGNOF(X) }; - enum { h_align = ALIGNOF(H) }; -public: - typedef typename max_align_aux(x_align) == static_cast(h_align), X, H, T>::type type; - enum { value = sizeof(type) }; -}; - -// computes alignment size (::value) and type (::type) of T -template -struct align_of { - typedef generate_type_list::type align_list; - typedef typename max_align::type type; - enum { value = max_align::value }; -}; - -#undef ALIGNOF - -}} -#endif - diff --git a/clasp/weight_constraint.h b/clasp/weight_constraint.h index 1cb0f37..a00bba8 100644 --- a/clasp/weight_constraint.h +++ b/clasp/weight_constraint.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,12 +21,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // -#ifndef CLASP_SMODELS_CONSTRAINTS_H_INCLUDED -#define CLASP_SMODELS_CONSTRAINTS_H_INCLUDED - -#ifdef _MSC_VER #pragma once -#endif #include @@ -34,29 +29,30 @@ namespace Clasp { //! Primitive representation of weight constraint literals in normal form. struct WeightLitsRep { - //! Transforms the given literals to the normal form expected by WeightConstraint. - /*! - * The function simplifies lits and bound by removing assigned and - * merging duplicate/complementary literals. Furthermore, negative weights and - * their literals are inverted, bound is updated accordingly, and literals - * are sorted by decreasing weight. - */ - static WeightLitsRep create(Solver& s, WeightLitVec& lits, weight_t bound); - //! Propagates the constraint W == *this. - /*! - * If *this is always satisfied (bound <= 0) or unsatisfied (bound > reach), - * the function forward propagates W. Otherwise, if W is not free, it assigns - * (and removes) literals from *this that must hold. - */ - bool propagate(Solver& s, Literal W); - bool sat() const { return bound <= 0; } - bool unsat() const { return reach < bound; } - bool open() const { return bound > 0 && bound <= reach;} - bool hasWeights() const { return size && lits[0].second > 1; } - WeightLiteral* lits; /*!< Literals sorted by decreasing weight. */ - uint32 size; /*!< Number of literals in lits. */ - weight_t bound; /*!< Rhs of linear constraint. */ - weight_t reach; /*!< Sum of weights of lits. */ + //! Transforms the given literals to the normal form expected by WeightConstraint. + /*! + * The function simplifies lits and bound by removing assigned and + * merging duplicate/complementary literals. Furthermore, negative weights and + * their literals are inverted, bound is updated accordingly, and literals + * are sorted by decreasing weight. + */ + static WeightLitsRep create(Solver& s, WeightLitVec& lits, Weight_t bound); + //! Propagates the constraint con == *this. + /*! + * If *this is always satisfied (bound <= 0) or unsatisfied (bound > reach), + * the function forward propagates con. Otherwise, if con is not free, it assigns + * (and removes) literals from *this that must hold. + */ + bool propagate(Solver& s, Literal con); + [[nodiscard]] bool sat() const { return bound <= 0; } + [[nodiscard]] bool unsat() const { return reach < bound; } + [[nodiscard]] bool open() const { return bound > 0 && bound <= reach; } + [[nodiscard]] bool hasWeights() const { return size && lits[0].weight > 1; } + [[nodiscard]] auto literals() const -> WeightLitView { return std::span{lits, size}; } + WeightLiteral* lits; /*!< Literals sorted by decreasing weight. */ + uint32_t size; /*!< Number of literals in lits. */ + Weight_t bound; /*!< Rhs of linear constraint. */ + Weight_t reach; /*!< Sum of weights of lits. */ }; //! Class implementing smodels-like cardinality- and weight constraints. @@ -69,165 +65,174 @@ struct WeightLitsRep { * the body of a basic weight rule. In this case W is the literal associated with the body. * A cardinality constraint is handled like a weight constraint where all weights are equal to 1. * - * Given a WeightConstraint with bound \p B and set of literals \p L, - * - let \p sumTrue be the sum of the weights of all literals \p l in \p L that are currently true, - * - \p sumReach be the sum of the weights of all literals \p l in \p L that are currently not false, and - * - \p U be the set of literals \p l in \p L that are currently unassigned - * . - * the class implements the following four inference rules: - * - \b FTB: If \p sumTrue >= \p B: assign \p W to true. - * - \b BFB: If \p W is false: set false all literals \p l in \p U for which \p sumTrue + weight(\p l) >= \p B. - * - \b FFB: If \p sumReach < \p B: assign \p W to false. - * - \b BTB: If \p W is true: set true all literals \p l in \p U for which \p sumReach - weight(\p l) < \p B. + * Given a WeightConstraint with bound @c B and set of literals @c L, + * - let @c sumTrue be the sum of the weights of all literals @c l in @c L that are currently true, + * - @c sumReach be the sum of the weights of all literals @c l in @c L that are currently not false, and + * - @c U be the set of literals @c l in @c L that are currently unassigned * . + * the class implements the following four inference rules: + * - @c FTB: If @c sumTrue >= @c B: assign @c W to true. + * - @c BFB: If @c W is false: set false all literals @c l in @c U for which @c sumTrue + @c weight(l) >= @c B. + * - @c FFB: If @c sumReach < @c B: assign @c W to false. + * - @c BTB: If @c W is true: set true all literals @c l in @c U for which @c sumReach - @c weight(l) < @c B. + * */ class WeightConstraint : public Constraint { public: - //! Flags controlling weight constraint creation. - enum CreationFlags { - create_explicit = 1u, //!< Force creation of explicit constraint even if size/bound is small. - create_no_add = 3u, //!< Do not add constraint to solver db. - create_sat = 4u, //!< Force creation even if constraint is always satisfied. - create_no_freeze = 8u, //!< Do not freeze variables in constraint. - create_no_share =16u, //!< Do not allow sharing of literals between threads. - create_eq_bound =32u, //!< Create equality instead of greater-or-equal constraint. - create_only_btb =64u, //!< Only create FFB_BTB constraint. - create_only_bfb =128u,//!< Only create FTB_BFB constraint. - }; - //! Type used to communicate result of create(). - class CPair { - public: - CPair() { con[0] = con[1] = 0; } - bool ok() const { return con[0] != (WeightConstraint*)0x1 && con[1] != (WeightConstraint*)0x1; } - WeightConstraint* first() const { return con[0]; } - WeightConstraint* second()const { return con[1]; } - private: - friend class WeightConstraint; - WeightConstraint* con[2]; - }; - //! Creates a new weight constraint from the given weight literals. - /*! - * If the right hand side of the weight constraint is initially true/false (FTB/FFB), - * W is assigned appropriately but no constraint is created. Otherwise, - * the new weight constraint is added to s unless creationFlags contains create_no_add. - * - * \param s Solver in which the new constraint is to be used. - * \param W The literal that is associated with the constraint. - * \param lits The literals of the weight constraint. - * \param bound The lower bound of the weight constraint. - * \param creationFlags Set of CreationFlags to apply. - * \note Cardinality constraint are represented as weight constraints with all weights equal to 1. - * \note If creationFlags contains create_eq_bound, a constraint W == (lits == bound) is created that - * is represented by up to two weight constraints. - */ - static CPair create(Solver& s, Literal W, WeightLitVec& lits, weight_t bound, uint32 creationFlags = 0); + //! Flags controlling weight constraint creation. + enum CreateFlag : uint32_t { + create_explicit = 1u, //!< Force creation of explicit constraint even if size/bound is small. + create_no_add = 2u, //!< Do not add constraint to solver db (implies create_explicit). + create_sat = 4u, //!< Force creation even if constraint is always satisfied. + create_no_freeze = 8u, //!< Do not freeze variables in constraint. + create_no_share = 16u, //!< Do not allow sharing of literals between threads. + create_eq_bound = 32u, //!< Create equality instead of greater-or-equal constraint. + create_only_btb = 64u, //!< Only create ffb_btb constraint. + create_only_bfb = 128u, //!< Only create ftb_bfb constraint. + }; + POTASSCO_ENABLE_BIT_OPS(CreateFlag, friend); + //! Type used to communicate result of create(). + class CPair { + public: + constexpr CPair() = default; + [[nodiscard]] bool ok() const { + return con_[0] != reinterpret_cast(0x1) && + con_[1] != reinterpret_cast(0x1); + } + [[nodiscard]] WeightConstraint* first() const { return con_[0]; } + [[nodiscard]] WeightConstraint* second() const { return con_[1]; } + + private: + friend class WeightConstraint; + WeightConstraint* con_[2] = {nullptr, nullptr}; + }; + //! Creates a new weight constraint from the given weight literals. + /*! + * If the right hand side of the weight constraint is initially true/false (FTB/FFB), + * W is assigned appropriately but no constraint is created. Otherwise, + * the new weight constraint is added to s unless creationFlags contains create_no_add. + * + * \param s Solver in which the new constraint is to be used. + * \param con The literal that is associated with the constraint. + * \param lits The literals of the weight constraint. + * \param bound The lower bound of the weight constraint. + * \param creationFlags Set of creation flags to apply. + * \note Cardinality constraint are represented as weight constraints with all weights equal to 1. + * \note If creationFlags contains @c create_eq_bound, a constraint W == (lits == bound) is created that is + * represented by up to two weight constraints. + */ + static CPair create(Solver& s, Literal con, WeightLitVec& lits, Weight_t bound, CreateFlag creationFlags = {}); + + //! Low level creation function. + /*! + * \note flag @c create_eq_bound is ignored by this function, that is, this function always creates + * a single >= constraint. + */ + static CPair create(Solver& s, Literal con, WeightLitsRep& rep, CreateFlag flags); + // constraint interface + Constraint* cloneAttach(Solver&) override; + bool simplify(Solver& s, bool = false) override; + void destroy(Solver*, bool) override; + PropResult propagate(Solver& s, Literal p, uint32_t& data) override; + void reason(Solver&, Literal p, LitVec& lits) override; + bool minimize(Solver& s, Literal p, CCMinRecursive* r) override; + void undoLevel(Solver& s) override; + [[nodiscard]] uint32_t estimateComplexity(const Solver& s) const override; + /*! + * Logically, we distinguish two constraints: + * - ffb_btb for handling forward false body and backward true body and + * - ftb_bfb for handling forward true body and backward false body. + * . + * Physically, we store the literals in one array: ~W=1, l0=w0,...,ln-1=wn-1. + */ + enum ActiveConstraint : uint32_t { + ffb_btb = 0, //!< (@c SumW - @c B)+1 [~W=1, l0=w0,..., ln-1=wn-1] + ftb_bfb = 1, //!< @c B [ W=1,~l0=w0,...,~ln-1=wn-1] + }; + /*! + * Returns the i-th literal of constraint c, i.e. + * - li, iff c == ffb_btb + * - ~li, iff c == ftb_bfb. + */ + [[nodiscard]] Literal lit(uint32_t i, ActiveConstraint c) const { return Literal::fromId(lits_->lit(i).id() ^ c); } + //! Returns the weight of the i-th literal or 1 if constraint is a cardinality constraint. + [[nodiscard]] Weight_t weight(uint32_t i) const { return lits_->weight(i); } + //! Returns the number of literals in this constraint (including W). + [[nodiscard]] uint32_t size() const { return lits_->size(); } + //! Returns false if constraint is a cardinality constraint. + [[nodiscard]] bool isWeight() const { return lits_->weights(); } + // Returns the index of next literal to look at during backward propagation. + [[nodiscard]] uint32_t getBpIndex() const { return not isWeight() ? 1 : undo_[0].data >> 1; } - //! Low level creation function. - /*! - * \note flag create_eq_bound is ignored by this function, that is, this function always creates - * a single >= constraint. - */ - static CPair create(Solver& s, Literal W, WeightLitsRep& rep, uint32 flags); - // constraint interface - Constraint* cloneAttach(Solver&); - bool simplify(Solver& s, bool = false); - void destroy(Solver*, bool); - PropResult propagate(Solver& s, Literal p, uint32& data); - void reason(Solver&, Literal p, LitVec& lits); - bool minimize(Solver& s, Literal p, CCMinRecursive* r); - void undoLevel(Solver& s); - uint32 estimateComplexity(const Solver& s) const; - /*! - * Logically, we distinguish two constraints: - * - FFB_BTB for handling forward false body and backward true body and - * - FTB_BFB for handling forward true body and backward false body. - * . - * Physically, we store the literals in one array: ~W=1, l0=w0,...,ln-1=wn-1. - */ - enum ActiveConstraint { - FFB_BTB = 0, //!< (\p SumW - \p B)+1 [~W=1, l0=w0,..., ln-1=wn-1] - FTB_BFB = 1, //!< \p B [ W=1,~l0=w0,...,~ln-1=wn-1] - }; - /*! - * Returns the i'th literal of constraint c, i.e. - * - li, iff c == FFB_BTB - * - ~li, iff c == FTB_BFB. - */ - Literal lit(uint32 i, ActiveConstraint c) const { return Literal::fromId( lits_->lit(i).id() ^ c ); } - //! Returns the weight of the i'th literal or 1 if constraint is a cardinality constraint. - weight_t weight(uint32 i) const { return lits_->weight(i); } - //! Returns the number of literals in this constraint (including W). - uint32 size() const { return lits_->size(); } - //! Returns false if constraint is a cardinality constraint. - bool isWeight() const { return lits_->weights(); } - // Returns the index of next literal to look at during backward propagation. - uint32 getBpIndex() const { return !isWeight() ? 1 : undo_[0].data>>1; } private: - static WeightConstraint* doCreate(Solver& s, Literal W, WeightLitsRep& rep, uint32 flags); - bool integrateRoot(Solver& s); - struct WL { - WL(uint32 s, bool shared, bool w); - bool shareable() const { return rc != 0; } - bool unique() const { return rc == 0 || refCount() == 1; } - bool weights() const { return w != 0; } - uint32 size() const { return sz; } - Literal lit(uint32 i) const { return lits[(i<(lits[(i << 1) + 1].rep()); + } + [[nodiscard]] uint32_t refCount() const; + WL* clone(); + void release(); + uint8_t* address(); + uint32_t sz : 30; // number of literals + uint32_t rc : 1; // ref counted? + uint32_t w : 1; // has weights? + POTASSCO_WARNING_BEGIN_RELAXED + Literal lits[0]; // Literals of constraint: ~B [Bw], l1 [w1], ..., ln-1 [Wn-1] + POTASSCO_WARNING_END_RELAXED + }; + WeightConstraint(Solver& s, SharedContext* ctx, Literal con, const WeightLitsRep&, WL* out, uint32_t act = 3u); + WeightConstraint(Solver& s, const WeightConstraint& other); - static const uint32 NOT_ACTIVE = 3u; + static constexpr uint32_t not_active = 3u; - // Represents a literal on the undo stack. - // idx() returns the index of the literal. - // constraint() returns the constraint that added the literal to the undo stack. - // Note: Only 31-bits are used for undo info. - // The remaining bit is used as a flag for marking processed literals. - struct UndoInfo { - explicit UndoInfo(uint32 d = 0) : data(d) {} - uint32 idx() const { return data >> 2; } - ActiveConstraint constraint() const { return static_cast((data&2) != 0); } - uint32 data; - }; - // Is literal idx contained as reason lit in the undo stack? - bool litSeen(uint32 idx) const { return (undo_[idx].data & 1) != 0; } - // Mark/unmark literal idx. - void toggleLitSeen(uint32 idx) { undo_[idx].data ^= 1; } - // Add watch for idx'th literal of c to the solver. - void addWatch(Solver& s, uint32 idx, ActiveConstraint c); - // Updates bound_[c] and adds an undo watch to the solver if necessary. - // Then adds the literal at position idx to the reason set (and the undo stack). - void updateConstraint(Solver& s, uint32 level, uint32 idx, ActiveConstraint c); - // Returns the starting index of the undo stack. - uint32 undoStart() const { return isWeight(); } - UndoInfo undoTop() const { assert(up_ != undoStart()); return undo_[up_-1]; } - // Returns the decision level of the last assigned literal - // or 0 if no literal was assigned yet. - uint32 highestUndoLevel(Solver&) const; - void setBpIndex(uint32 n); - WL* lits_; // literals of constraint - uint32 up_ : 27; // undo position; [undoStart(), up_) is the undo stack - uint32 ownsLit_: 1; // owns lits_? - uint32 active_ : 2; // which of the two sub-constraints is currently unit? - uint32 watched_: 2; // which constraint is watched (3 both, 2 ignore, FTB_BFB, FFB_BTB) - weight_t bound_[2]; // FFB_BTB: (sumW-bound)+1 / FTB_BFB: bound -POTASSCO_WARNING_BEGIN_RELAXED - UndoInfo undo_[0]; // undo stack + seen flag for each literal -POTASSCO_WARNING_END_RELAXED -}; -} + // Represents a literal on the undo stack. + // idx() returns the index of the literal. + // constraint() returns the constraint that added the literal to the undo stack. + // Note: Only 31-bits are used for undo info. + // The remaining bit is used as a flag for marking processed literals. + struct UndoInfo { + explicit UndoInfo(uint32_t d = 0) : data(d) {} + [[nodiscard]] uint32_t idx() const { return data >> 2; } + [[nodiscard]] ActiveConstraint constraint() const { return static_cast((data & 2) != 0); } + uint32_t data; + }; + // Is literal idx contained as reason lit in the undo stack? + [[nodiscard]] bool litSeen(uint32_t idx) const { return (undo_[idx].data & 1) != 0; } + // Mark/unmark literal idx. + void toggleLitSeen(uint32_t idx) { undo_[idx].data ^= 1; } + // Add watch for idx-th literal of c to the solver. + void addWatch(Solver& s, uint32_t idx, ActiveConstraint c); + // Updates bound_[c] and adds an undo watch to the solver if necessary. + // Then adds the literal at position idx to the reason set (and the undo stack). + void updateConstraint(Solver& s, uint32_t level, uint32_t idx, ActiveConstraint c); + // Returns the starting index of the undo stack. + [[nodiscard]] uint32_t undoStart() const { return isWeight(); } + [[nodiscard]] UndoInfo undoTop() const { + assert(up_ != undoStart()); + return undo_[up_ - 1]; + } + // Returns the decision level of the last assigned literal + // or 0 if no literal was assigned yet. + [[nodiscard]] uint32_t highestUndoLevel(const Solver&) const; + void setBpIndex(uint32_t n); -#endif + WL* lits_; // literals of constraint + uint32_t up_ : 27; // undo position; [undoStart(), up_) is the undo stack + uint32_t ownsLit_ : 1; // owns lits_? + uint32_t active_ : 2; // which of the two sub-constraints is currently unit? + uint32_t watched_ : 2; // which constraint is watched (3 both, 2 ignore, ftb_bfb, ffb_btb) + Weight_t bound_[2]{}; // ffb_btb: (sumW-bound)+1 / ftb_bfb: bound + POTASSCO_WARNING_BEGIN_RELAXED + UndoInfo undo_[0]; // undo stack + seen flag for each literal + POTASSCO_WARNING_END_RELAXED +}; +} // namespace Clasp diff --git a/doc/api/clasp.txt b/doc/api/clasp.txt index 1462762..aba1261 100644 --- a/doc/api/clasp.txt +++ b/doc/api/clasp.txt @@ -19,7 +19,7 @@ constraint solving including: - ASP/SAT/PB modulo acyclicity . -For more information please visit the clasp homepage: http://www.cs.uni-potsdam.de/clasp +For more information please visit the clasp homepage: https://potassco.org/clasp @defgroup facade Facade @defgroup cli Cli diff --git a/doc/output.md b/doc/output.md index 998a33a..8bec1ca 100644 --- a/doc/output.md +++ b/doc/output.md @@ -66,7 +66,7 @@ programs, `--stats` accepts a second parameter controlling the output of solving ### Solving Statistics: ``` -Choices : 66072 +Choices : 66072 Conflicts : 52827 (Analyzed: 52826) Restarts : 172 (Average: 307.13 Last: 239 Blocked: 0) Model-Level : 33.5 @@ -74,9 +74,9 @@ Problems : 1 (Average Length: 0.00 Splits: 0) Lemmas : 53391 (Deleted: 33339) Binary : 1488 (Ratio: 2.79%) Ternary : 2007 (Ratio: 3.76%) - Conflict : 52826 (Average Length: 22.9 Ratio: 98.94%) - Loop : 565 (Average Length: 10.7 Ratio: 1.06%) - Other : 0 (Average Length: 0.0 Ratio: 0.00%) + Conflict : 52826 (Average Length: 22.9 Ratio: 98.94%) + Loop : 565 (Average Length: 10.7 Ratio: 1.06%) + Other : 0 (Average Length: 0.0 Ratio: 0.00%) Backjumps : 52826 (Average: 1.24 Max: 16 Sum: 65426) Executed : 52826 (Average: 1.24 Max: 16 Sum: 65426 Ratio: 100.00%) Bounded : 0 (Average: 0.00 Max: 0 Sum: 0 Ratio: 0.00%) @@ -128,7 +128,7 @@ Program statistics provide information about the input logic program, problem tr ``` Rules : 11649 (Original: 10317) - Choice : 742 + Choice : 742 Atoms : 5742 (Original: 5664 Auxiliary: 78) Disjunctions : 6 (Original: 8) Bodies : 9539 (Original: 8287) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f5010c9..63e1bc4 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,10 +1,10 @@ set(files - example.h - example1.cpp - example2.cpp - example3.cpp - example4.cpp - main.cpp) + example.h + example1.cpp + example2.cpp + example3.cpp + example4.cpp + main.cpp) add_executable(clasp_examples ${files}) target_link_libraries(clasp_examples libclasp) set_target_properties(clasp_examples PROPERTIES FOLDER exe) diff --git a/examples/example.h b/examples/example.h index ced1bcc..e8c92cd 100644 --- a/examples/example.h +++ b/examples/example.h @@ -1,7 +1,7 @@ // -// Copyright (c) 2014-2017 Benjamin Kaufmann +// Copyright (c) 2014-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -21,13 +21,16 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. // +#pragma once #include #include namespace Clasp { - struct Model; - class OutputTable; - namespace Asp { class LogicProgram; } +struct Model; +class OutputTable; +namespace Asp { +class LogicProgram; } +} // namespace Clasp void printModel(const Clasp::OutputTable& out, const Clasp::Model& model); void addSimpleProgram(Clasp::Asp::LogicProgram& prg); diff --git a/examples/example1.cpp b/examples/example1.cpp index 6ebf958..bbdb015 100644 --- a/examples/example1.cpp +++ b/examples/example1.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2009-2017 Benjamin Kaufmann +// Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -24,103 +24,99 @@ #include "example.h" // Add the libclasp directory to the list of // include directories of your build system. -#include // for defining logic programs -#include // unfounded set checkers -#include // for enumerating answer sets -#include // for enumerating answer sets - +#include // for defining logic programs +#include // for enumerating answer sets +#include // for enumerating answer sets +#include // unfounded set checkers // Compute the stable models of the program // a :- not b. // b :- not a. void example1(bool basicSolve) { - // LogicProgram provides the interface for - // defining logic programs. - // It also preprocesses the program and converts it - // to the internal solver format. - // See logic_program.h for details. - Clasp::Asp::LogicProgram lp; - Potassco::RuleBuilder rb; + // LogicProgram provides the interface for + // defining logic programs. + // It also preprocesses the program and converts it + // to the internal solver format. + // See logic_program.h for details. + Clasp::Asp::LogicProgram lp; + Potassco::RuleBuilder rb; - // Among other things, SharedContext maintains a Solver object - // which hosts the data and functions for CDNL answer set solving. - // See shared_context.h for details. - Clasp::SharedContext ctx; + // Among other things, SharedContext maintains a Solver object + // which hosts the data and functions for CDNL answer set solving. + // See shared_context.h for details. + Clasp::SharedContext ctx; - // startProgram must be called once before we can add atoms/rules - lp.startProgram(ctx); + // startProgram must be called once before we can add atoms/rules + lp.startProgram(ctx); - // Define the rules of the program. - Potassco::Atom_t a = lp.newAtom(); - Potassco::Atom_t b = lp.newAtom(); - lp.addRule(rb.start().addHead(a).addGoal(Potassco::neg(b))); - lp.addRule(rb.start().addHead(b).addGoal(Potassco::neg(a))); + // Define the rules of the program. + Potassco::Atom_t a = lp.newAtom(); + Potassco::Atom_t b = lp.newAtom(); + lp.addRule(rb.start().addHead(a).addGoal(Potassco::neg(b))); + lp.addRule(rb.start().addHead(b).addGoal(Potassco::neg(a))); - // Populate output table. - // The output table defines what is printed for a literal - // that is part of an answer set. - lp.addOutput("a", a); - lp.addOutput("b", b); - // It is not limited to atoms. For example, the following - // statement results in the output "~b" whenever b is not - // in a stable model. - lp.addOutput("~b", Potassco::neg(b)); - // And we always want to have "eureka"... - lp.addOutput("eureka", Potassco::toSpan()); + // Populate output table. + // The output table defines what is printed for a literal that is part of an answer set. + lp.addOutput("a", a); + lp.addOutput("b", b); + // It is not limited to atoms. For example, the following + // statement results in the output "~b" whenever b is not + // in a stable model. + lp.addOutput("~b", Potassco::neg(b)); + // And we always want to have "eureka"... + lp.addOutput("eureka", {}); - // Once all rules are defined, call endProgram() to load the (simplified) - // program into the context object. - lp.endProgram(); + // Once all rules are defined, call endProgram() to load the (simplified) + // program into the context object. + lp.endProgram(); - // Since we want to compute more than one - // answer set, we need an enumerator. - // See enumerator.h for details - Clasp::ModelEnumerator enumerator; - enumerator.init(ctx); + // Since we want to compute more than one answer set, we need an enumerator. + // See enumerator.h for details. + Clasp::ModelEnumerator enumerator; + enumerator.init(ctx); - // We are done with problem setup. - // Prepare for solving. - ctx.endInit(); + // We are done with problem setup. + // Prepare for solving. + ctx.endInit(); - if (basicSolve) { - std::cout << "With Clasp::BasicSolve" << std::endl; - // BasicSolve implements a basic search for a model. - // It handles the various strategies like restarts, deletion, etc. - Clasp::BasicSolve solve(*ctx.master()); - // Prepare the solver for enumeration. - enumerator.start(solve.solver()); - while (solve.solve() == Clasp::value_true) { - // Make the enumerator aware of the new model and - // let it compute a new constraint and/or backtracking level. - if (enumerator.commitModel(solve.solver())) { printModel(ctx.output, enumerator.lastModel()); } - // Integrate the model into the search and thereby prepare - // the solver for the search for the next model. - enumerator.update(solve.solver()); - } - std::cout << "No more models!" << std::endl; - } - else { - std::cout << "With Clasp::SequentialSolve" << std::endl; - // SequentialSolve combines a BasicSolve object, - // which implements search for a model and handles - // various strategies like restarts, deletion, etc., - // with an enumerator to provide more complex reasoning, - // like enumeration or optimization. - Clasp::SequentialSolve solve; - solve.setEnumerator(enumerator); - // Start the solve algorithm and prepare solver for enumeration. - solve.start(ctx); - // Extract and print models one by one. - while (solve.next()) { - printModel(ctx.output, solve.model()); - } - if (!solve.more()) { - std::cout << "No more models!" << std::endl; - } - } + if (basicSolve) { + std::cout << "With Clasp::BasicSolve" << std::endl; + // BasicSolve implements a basic search for a model. + // It handles the various strategies like restarts, deletion, etc. + Clasp::BasicSolve solve(*ctx.master()); + // Prepare the solver for enumeration. + enumerator.start(solve.solver()); + while (solve.solve() == Clasp::value_true) { + // Make the enumerator aware of the new model and + // let it compute a new constraint and/or backtracking level. + if (enumerator.commitModel(solve.solver())) { + printModel(ctx.output, enumerator.lastModel()); + } + // Integrate the model into the search and thereby prepare + // the solver for the search for the next model. + enumerator.update(solve.solver()); + } + std::cout << "No more models!" << std::endl; + } + else { + std::cout << "With Clasp::SequentialSolve" << std::endl; + // SequentialSolve combines a BasicSolve object, + // which implements search for a model and handles + // various strategies like restarts, deletion, etc., + // with an enumerator to provide more complex reasoning, + // like enumeration or optimization. + Clasp::SequentialSolve solve; + // Start the solve algorithm and prepare solver for enumeration. + solve.start(enumerator, ctx); + // Extract and print models one by one. + while (solve.next()) { printModel(ctx.output, solve.model()); } + if (not solve.more()) { + std::cout << "No more models!" << std::endl; + } + } } void example1() { - example1(true); - example1(false); + example1(true); + example1(false); } diff --git a/examples/example2.cpp b/examples/example2.cpp index b99c301..8690894 100644 --- a/examples/example2.cpp +++ b/examples/example2.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2009-2017 Benjamin Kaufmann +// Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -24,67 +24,64 @@ // Add the libclasp directory to the list of // include directories of your build system. +#include "example.h" #include #include -#include "example.h" -// This example uses the ClaspFacade to compute -// the stable models of the program +// This example uses the ClaspFacade to compute the stable models of the program: // a :- not b. // b :- not a. // // The ClaspFacade is a convenient wrapper for the services of the clasp library. // See clasp_facade.h for details. - -// In order to get models from the ClaspFacade, we must provide a suitable -// event handler. +// In order to get models from the ClaspFacade, we must provide a suitable event handler. class ModelPrinter : public Clasp::EventHandler { public: - ModelPrinter() {} - bool onModel(const Clasp::Solver& s, const Clasp::Model& m) { - printModel(s.outputTable(), m); - return true; - } + ModelPrinter() = default; + bool onModel(const Clasp::Solver& s, const Clasp::Model& m) override { + printModel(s.outputTable(), m); + return true; + } }; void addSimpleProgram(Clasp::Asp::LogicProgram& prg) { - Potassco::Atom_t a = prg.newAtom(); - Potassco::Atom_t b = prg.newAtom(); - Potassco::RuleBuilder rb; - prg.addRule(rb.start().addHead(a).addGoal(Potassco::neg(b))); - prg.addRule(rb.start().addHead(b).addGoal(Potassco::neg(a))); - prg.addOutput("a", a); - prg.addOutput("b", b); + Potassco::Atom_t a = prg.newAtom(); + Potassco::Atom_t b = prg.newAtom(); + Potassco::RuleBuilder rb; + prg.addRule(rb.start().addHead(a).addGoal(Potassco::neg(b))); + prg.addRule(rb.start().addHead(b).addGoal(Potassco::neg(a))); + prg.addOutput("a", a); + prg.addOutput("b", b); } void example2() { - // Aggregates configuration options. - // Using config, you can control many parts of the search, e.g. - // - the amount and kind of preprocessing - // - the enumerator to use and the number of models to compute - // - the heuristic used for decision making - // - the restart strategy - // - ... - Clasp::ClaspConfig config; - // We want to compute all models but - // otherwise we use the default configuration. - config.solve.numModels = 0; + // Aggregates configuration options. + // Using config, you can control many parts of the search, e.g. + // - the amount and kind of preprocessing + // - the enumerator to use and the number of models to compute + // - the heuristic used for decisions + // - the restart strategy + // - ... + Clasp::ClaspConfig config; + // We want to compute all models, but + // otherwise we use the default configuration. + config.solve.numModels = 0; - // The "interface" to the clasp library. - Clasp::ClaspFacade libclasp; + // The "interface" to the clasp library. + Clasp::ClaspFacade libclasp; - // LogicProgram provides the interface for defining logic programs. - // The returned object is already setup and ready to use. - // See logic_program.h for details. - Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); - addSimpleProgram(asp); - // We are done with problem setup. - // Prepare the problem for solving. - libclasp.prepare(); + // LogicProgram provides the interface for defining logic programs. + // The returned object is already setup and ready to use. + // See logic_program.h for details. + Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); + addSimpleProgram(asp); + // We are done with problem setup. + // Prepare the problem for solving. + libclasp.prepare(); - // Start the actual solving process. - ModelPrinter printer; - libclasp.solve(&printer); - std::cout << "No more models!" << std::endl; + // Start the actual solving process. + ModelPrinter printer; + libclasp.solve(&printer); + std::cout << "No more models!" << std::endl; } diff --git a/examples/example3.cpp b/examples/example3.cpp index c549dcf..e0350ae 100644 --- a/examples/example3.cpp +++ b/examples/example3.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2014-2017 Benjamin Kaufmann +// Copyright (c) 2014-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -23,47 +23,45 @@ // // Add the libclasp directory to the list of // include directories of your build system. +#include "example.h" #include #include -#include "example.h" -// This example uses the ClaspFacade to compute -// the stable models of the program -// a :- not b. -// b :- not a. +// This example uses the ClaspFacade to compute the stable models of the program: +// a :- not b. +// b :- not a. // -// It is similar to example2() but uses generator based solving. -void example3(Clasp::SolveMode_t mode) { - // See example2() - Clasp::ClaspConfig config; - config.solve.numModels = 0; +// It is similar to example2() but uses generator based solving. +void example3(Clasp::SolveMode mode) { + // See example2() + Clasp::ClaspConfig config; + config.solve.numModels = 0; - // The "interface" to the clasp library. - Clasp::ClaspFacade libclasp; + // The "interface" to the clasp library. + Clasp::ClaspFacade libclasp; - // See example2() - Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); - addSimpleProgram(asp); + // See example2() + Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); + addSimpleProgram(asp); - // We are done with problem setup. - // Prepare the problem for solving. - libclasp.prepare(); + // We are done with problem setup. + // Prepare the problem for solving. + libclasp.prepare(); - // Start the solving process. - std::cout << "With Clasp::" << (((mode & Clasp::SolveMode_t::Async) != 0) ? "AsyncYield" : "Yield") << "\n"; - Clasp::ClaspFacade::SolveHandle it = libclasp.solve(mode|Clasp::SolveMode_t::Yield); - // Get models one by one until iterator is exhausted. - while (it.model()) { - printModel(libclasp.ctx.output, *it.model()); - // Resume search for next model. - it.resume(); - } - std::cout << "No more models!" << std::endl; + // Start the solving process. + std::cout << "With Clasp::" << (Potassco::test(mode, Clasp::SolveMode::async) ? "AsyncYield" : "Yield") << "\n"; + Clasp::ClaspFacade::SolveHandle it = libclasp.solve(mode | Clasp::SolveMode::yield); + // Get models one by one until iterator is exhausted. + while (it.model()) { + printModel(libclasp.ctx.output, *it.model()); + // Resume search for next model. + it.resume(); + } + std::cout << "No more models!" << std::endl; } void example3() { - example3(Clasp::SolveMode_t::Default); + example3(Clasp::SolveMode::def); #if CLASP_HAS_THREADS - example3(Clasp::SolveMode_t::Async); + example3(Clasp::SolveMode::async); #endif } - diff --git a/examples/example4.cpp b/examples/example4.cpp index e96b7c3..96faf02 100644 --- a/examples/example4.cpp +++ b/examples/example4.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2009-2017 Benjamin Kaufmann +// Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -24,39 +24,37 @@ // Add the libclasp directory to the list of // include directories of your build system. +#include "example.h" #include #include -#include "example.h" // This example demonstrates how user code can influence model enumeration. static void excludeModel(const Clasp::Solver& s, const Clasp::Model& m) { - Clasp::LitVec clause; - for (uint32_t i = 1; i <= s.decisionLevel(); ++i) { - clause.push_back(~s.decision(i)); - } - m.ctx->commitClause(clause); + Clasp::LitVec clause; + for (uint32_t i = 1; i <= s.decisionLevel(); ++i) { clause.push_back(~s.decision(i)); } + m.ctx->commitClause(clause); } void example4() { - Clasp::ClaspConfig config; - config.solve.enumMode = Clasp::EnumOptions::enum_user; - config.solve.numModels = 0; - - // The "interface" to the clasp library. - Clasp::ClaspFacade libclasp; - - Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); - addSimpleProgram(asp); - - libclasp.prepare(); - - // Start the actual solving process. - for (Clasp::ClaspFacade::SolveHandle h = libclasp.solve(Clasp::SolveMode_t::Yield); h.next(); ) { - // print the model - printModel(libclasp.ctx.output, *h.model()); - // exclude this model - excludeModel(*libclasp.ctx.solver(h.model()->sId), *h.model()); - } - std::cout << "No more models!" << std::endl; + Clasp::ClaspConfig config; + config.solve.enumMode = Clasp::EnumOptions::enum_user; + config.solve.numModels = 0; + + // The "interface" to the clasp library. + Clasp::ClaspFacade libclasp; + + Clasp::Asp::LogicProgram& asp = libclasp.startAsp(config); + addSimpleProgram(asp); + + libclasp.prepare(); + + // Start the actual solving process. + for (Clasp::ClaspFacade::SolveHandle h = libclasp.solve(Clasp::SolveMode::yield); h.next();) { + // print the model + printModel(libclasp.ctx.output, *h.model()); + // exclude this model + excludeModel(*libclasp.ctx.solver(h.model()->sId), *h.model()); + } + std::cout << "No more models!" << std::endl; } diff --git a/examples/main.cpp b/examples/main.cpp index 0a69a5f..29fd01f 100644 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2009-2017 Benjamin Kaufmann +// Copyright (c) 2009-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -25,29 +25,34 @@ #include #include void printModel(const Clasp::OutputTable& out, const Clasp::Model& model) { - std::cout << "Model " << model.num << ": \n"; - // Always print facts. - for (Clasp::OutputTable::fact_iterator it = out.fact_begin(), end = out.fact_end(); it != end; ++it) { - std::cout << *it << " "; - } - // Print elements that are true wrt the current model. - for (Clasp::OutputTable::pred_iterator it = out.pred_begin(), end = out.pred_end(); it != end; ++it) { - if (model.isTrue(it->cond)) { - std::cout << it->name << " "; - } - } - // Print additional output variables. - for (Clasp::OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) { - std::cout << (model.isTrue(Clasp::posLit(*it)) ? int(*it) : -int(*it)) << " "; - } - std::cout << std::endl; + std::cout << "Model " << model.num << ": \n"; + // Always print facts. + for (const auto& fact : out.fact_range()) { std::cout << fact.view() << " "; } + // Print elements that are true wrt the current model. + for (const auto& p : out.pred_range()) { + if (model.isTrue(p.cond)) { + std::cout << p.name.view() << " "; + } + } + // Print additional output variables. + for (auto v : out.vars_range()) { + std::cout << (model.isTrue(Clasp::posLit(v)) ? static_cast(v) : -static_cast(v)) << " "; + } + std::cout << std::endl; } -#define RUN(x) try { std::cout << "*** Running " << static_cast(#x) << " ***" << std::endl; x(); } catch (const std::exception& e) { std::cout << " *** ERROR: " << e.what() << std::endl; } +#define RUN(x) \ + try { \ + std::cout << "*** Running " << static_cast(#x) << " ***" << std::endl; \ + x(); \ + } \ + catch (const std::exception& e) { \ + std::cout << " *** ERROR: " << e.what() << std::endl; \ + } int main() { - RUN(example1); - RUN(example2); - RUN(example3); - RUN(example4); + RUN(example1); + RUN(example2); + RUN(example3); + RUN(example4); } diff --git a/libpotassco b/libpotassco index e8107a4..8925c90 160000 --- a/libpotassco +++ b/libpotassco @@ -1 +1 @@ -Subproject commit e8107a42556854095ac51615137660cd6e64b8e2 +Subproject commit 8925c909a5932011b2af80aee6c073da38844848 diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e529763..d798f79 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,164 +2,155 @@ set(header_path ${CLASP_SOURCE_DIR}/clasp) # generate config.h set(CLASP_HAS_THREADS ${CLASP_BUILD_WITH_THREADS}) -if (CLASP_BUILD_WITH_THREADS) -set(CLASP_DEFINE_ATOMIC "namespace mt { using std::atomic; }") -set(CLASP_ATOMIC_TYPE "std::atomic") -else() -set(CLASP_DEFINE_ATOMIC " -template -struct Plain { - explicit Plain(T v = T()) : val_(v) {} - T operator=(T nv) { return (val_ = nv); } - operator T () const { return val_; } - operator T&() { return val_; } - T exchange(T nVal) { T oVal = val_; val_ = nVal; return oVal; } - T fetch_or(T xVal) { return exchange(val_ | xVal); } - T fetch_and(T xVal) { return exchange(val_ & xVal); } - bool compare_exchange_strong(T& oVal, T nVal) { - if (val_ == oVal) { val_ = nVal; return true; } - else { oVal = val_; return false; } - } - T val_; -};") -set(CLASP_ATOMIC_TYPE "Clasp::Plain") -endif() -if (NOT DEFINED CLASP_USE_STD_VECTOR) - if (MSVC) - set(CLASP_USE_STD_VECTOR "_DEBUG") - else() - set(CLASP_USE_STD_VECTOR 0) - endif() +if(NOT DEFINED CLASP_USE_STD_VECTOR) + if(MSVC) + set(CLASP_USE_STD_VECTOR "_DEBUG") + else() + set(CLASP_USE_STD_VECTOR 0) + endif() endif() -if (CLASP_VERSION_TWEAK) - set(CLASP_VERSION_STRING "${CLASP_VERSION_MAJOR}.${CLASP_VERSION_MINOR}.${CLASP_VERSION_PATCH}-dev(${CLASP_VERSION_TWEAK})") +if(CLASP_VERSION_TWEAK) + set(CLASP_VERSION_STRING "${CLASP_VERSION_MAJOR}.${CLASP_VERSION_MINOR}.${CLASP_VERSION_PATCH}-dev(${CLASP_VERSION_TWEAK})") else() - set(CLASP_VERSION_STRING "${CLASP_VERSION}") + set(CLASP_VERSION_STRING "${CLASP_VERSION}") +endif() +set(CLASP_CACHE_LINE_SIZE 64) +if(CMAKE_HOST_APPLE) + message(STATUS "checking cache line size...") + execute_process( + COMMAND sysctl -q hw.cachelinesize + OUTPUT_VARIABLE _cl_out + RESULT_VARIABLE _cl_result + ) + if(NOT _cl_result EQUAL 0 OR NOT _cl_out MATCHES "hw.cachelinesize[:= ]+([0-9]+)") + message(STATUS "checking cache line size failed - using default") + else() + message(STATUS "got cache line size: ${CMAKE_MATCH_1}") + set(CLASP_CACHE_LINE_SIZE ${CMAKE_MATCH_1}) + endif() endif() +message(STATUS "setting cache line size to ${CLASP_CACHE_LINE_SIZE}") configure_file(${header_path}/config.h.in ${CLASP_BINARY_DIR}/clasp/config.h @ONLY) set(header - ${CLASP_BINARY_DIR}/clasp/config.h - ${header_path}/heuristics.h - ${header_path}/statistics.h - ${header_path}/claspfwd.h - ${header_path}/logic_program.h - ${header_path}/pod_vector.h - ${header_path}/weight_constraint.h - ${header_path}/asp_preprocessor.h - ${header_path}/enumerator.h - ${header_path}/clingo.h - ${header_path}/lookahead.h - ${header_path}/shared_context.h - ${header_path}/solver_strategies.h - ${header_path}/solve_algorithms.h - ${header_path}/literal.h - ${header_path}/satelite.h - ${header_path}/program_builder.h - ${header_path}/dependency_graph.h - ${header_path}/clause.h - ${header_path}/cb_enumerator.h - ${header_path}/minimize_constraint.h - ${header_path}/unfounded_check.h - ${header_path}/solver_types.h - ${header_path}/solver.h - ${header_path}/parser.h - ${header_path}/clasp_facade.h - ${header_path}/model_enumerators.h - ${header_path}/logic_program_types.h - ${header_path}/constraint.h) + ${CLASP_BINARY_DIR}/clasp/config.h + ${header_path}/heuristics.h + ${header_path}/statistics.h + ${header_path}/claspfwd.h + ${header_path}/logic_program.h + ${header_path}/pod_vector.h + ${header_path}/weight_constraint.h + ${header_path}/asp_preprocessor.h + ${header_path}/enumerator.h + ${header_path}/clingo.h + ${header_path}/lookahead.h + ${header_path}/shared_context.h + ${header_path}/solver_strategies.h + ${header_path}/solve_algorithms.h + ${header_path}/literal.h + ${header_path}/satelite.h + ${header_path}/program_builder.h + ${header_path}/dependency_graph.h + ${header_path}/clause.h + ${header_path}/cb_enumerator.h + ${header_path}/minimize_constraint.h + ${header_path}/unfounded_check.h + ${header_path}/solver_types.h + ${header_path}/solver.h + ${header_path}/parser.h + ${header_path}/clasp_facade.h + ${header_path}/model_enumerators.h + ${header_path}/logic_program_types.h + ${header_path}/constraint.h) set(ide_header "Header Files") source_group("${ide_header}" FILES ${header}) set(header_util - ${header_path}/util/misc_types.h - ${header_path}/util/multi_queue.h - ${header_path}/util/left_right_sequence.h - ${header_path}/util/pod_vector.h - ${header_path}/util/hash.h - ${header_path}/util/indexed_priority_queue.h - ${header_path}/util/type_manip.h - ${header_path}/util/timer.h) + ${header_path}/util/misc_types.h + ${header_path}/util/multi_queue.h + ${header_path}/util/left_right_sequence.h + ${header_path}/util/pod_vector.h + ${header_path}/util/indexed_priority_queue.h + ${header_path}/util/timer.h) source_group("${ide_header}\\util" FILES ${header_util}) set(header_cli - ${header_path}/cli/clasp_app.h - ${header_path}/cli/clasp_cli_configs.inl - ${header_path}/cli/clasp_cli_options.inl - ${header_path}/cli/clasp_output.h - ${header_path}/cli/clasp_options.h) + ${header_path}/cli/clasp_app.h + ${header_path}/cli/clasp_cli_configs.inl + ${header_path}/cli/clasp_cli_options.inl + ${header_path}/cli/clasp_output.h + ${header_path}/cli/clasp_options.h) source_group("${ide_header}\\cli" FILES ${header_cli}) set(src - asp_preprocessor.cpp - cb_enumerator.cpp - clasp_app.cpp - clasp_facade.cpp - clasp_options.cpp - clasp_output.cpp - clause.cpp - clingo.cpp - constraint.cpp - dependency_graph.cpp - enumerator.cpp - heuristics.cpp - logic_program.cpp - logic_program_types.cpp - lookahead.cpp - minimize_constraint.cpp - model_enumerators.cpp - parser.cpp - program_builder.cpp - satelite.cpp - shared_context.cpp - solve_algorithms.cpp - solver.cpp - solver_strategies.cpp - solver_types.cpp - statistics.cpp - timer.cpp - unfounded_check.cpp - weight_constraint.cpp) -if (CLASP_BUILD_WITH_THREADS) -LIST(APPEND src - parallel_solve.cpp) -set(header_mt - ${header_path}/mt/thread.h - ${header_path}/mt/mutex.h - ${header_path}/mt/parallel_solve.h) -source_group("${ide_header}\\mt" FILES ${header_mt}) + asp_preprocessor.cpp + cb_enumerator.cpp + clasp_app.cpp + clasp_facade.cpp + clasp_options.cpp + clasp_output.cpp + clause.cpp + clingo.cpp + constraint.cpp + dependency_graph.cpp + enumerator.cpp + heuristics.cpp + logic_program.cpp + logic_program_types.cpp + lookahead.cpp + minimize_constraint.cpp + model_enumerators.cpp + parser.cpp + program_builder.cpp + satelite.cpp + shared_context.cpp + solve_algorithms.cpp + solver.cpp + solver_strategies.cpp + solver_types.cpp + statistics.cpp + timer.cpp + unfounded_check.cpp + weight_constraint.cpp) +if(CLASP_BUILD_WITH_THREADS) + LIST(APPEND src + parallel_solve.cpp) + set(header_mt + ${header_path}/mt/thread.h + ${header_path}/mt/parallel_solve.h) + source_group("${ide_header}\\mt" FILES ${header_mt}) endif() add_library(libclasp ${header} ${header_util} ${header_cli} ${header_mt} ${src}) -if (CLASP_BUILD_WITH_THREADS) - target_link_libraries(libclasp PUBLIC Threads::Threads) - target_compile_options(libclasp PRIVATE - $<$,$,$>: - -Wno-deprecated-declarations>) +set_property(TARGET libclasp PROPERTY CXX_STANDARD 20) +if(CLASP_BUILD_WITH_THREADS) + target_link_libraries(libclasp PUBLIC Threads::Threads) + target_compile_options(libclasp PRIVATE + $<$,$,$>: + -Wno-deprecated-declarations>) endif() if(MSVC) - target_compile_definitions(libclasp PRIVATE _SCL_SECURE_NO_WARNINGS) - target_compile_options(libclasp PRIVATE "$<$:/W4>") - set(VC_RELEASE_OPTIONS /Oi /Oy /GL /Gy /GS-) - target_compile_definitions(libclasp PUBLIC "$<$:_SECURE_SCL=0>") - target_compile_options(libclasp PUBLIC "$<$:${VC_RELEASE_OPTIONS}>") + target_compile_definitions(libclasp PRIVATE _SCL_SECURE_NO_WARNINGS) + set(VC_RELEASE_OPTIONS /Oi /Oy /GL /Gy /GS-) + target_compile_definitions(libclasp PUBLIC "$<$:_SECURE_SCL=0>") + target_compile_options(libclasp PUBLIC "$<$:${VC_RELEASE_OPTIONS}>") endif() target_include_directories(libclasp PUBLIC - $ - $ - $) -target_link_libraries(libclasp PUBLIC libpotassco) + $ + $ + $) +target_link_libraries(libclasp PUBLIC libpotassco PRIVATE amc::amc potassco_default_warnings) set_target_properties(libclasp PROPERTIES VERSION ${PROJECT_VERSION}) set_target_properties(libclasp PROPERTIES - OUTPUT_NAME clasp - FOLDER lib) + OUTPUT_NAME clasp + FOLDER lib) # installation -if (CLASP_INSTALL_LIB) - install(TARGETS libclasp EXPORT ClaspTargets DESTINATION "${CMAKE_INSTALL_LIBDIR}/${clasp_library_dest}") - install(FILES ${header} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp") - install(FILES ${header_util} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/util") - install(FILES ${header_cli} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/cli") - if (header_mt) - install(FILES ${header_mt} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/mt") - endif() +if(CLASP_INSTALL_LIB) + install(TARGETS libclasp EXPORT ClaspTargets DESTINATION "${CMAKE_INSTALL_LIBDIR}/${clasp_library_dest}") + install(FILES ${header} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp") + install(FILES ${header_util} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/util") + install(FILES ${header_cli} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/cli") + if(header_mt) + install(FILES ${header_mt} DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/${clasp_include_dest}/clasp/mt") + endif() endif() diff --git a/src/asp_preprocessor.cpp b/src/asp_preprocessor.cpp index e78bbb7..08ec7de 100644 --- a/src/asp_preprocessor.cpp +++ b/src/asp_preprocessor.cpp @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -22,9 +22,11 @@ // IN THE SOFTWARE. // #include + #include #include -namespace Clasp { namespace Asp { + +namespace Clasp::Asp { ///////////////////////////////////////////////////////////////////////////////////////// // simple preprocessing // @@ -32,51 +34,46 @@ namespace Clasp { namespace Asp { // Then assign variables to non-trivial supported bodies and atoms. ///////////////////////////////////////////////////////////////////////////////////////// bool Preprocessor::preprocessSimple() { - if (!prg_->propagate(true)) { return false; } - uint32 startVar = prg_->ctx()->numVars() + 1; - // start with initially supported bodies - VarVec& supported = prg_->getSupportedBodies(true); - VarVec unitBodies; - for (VarVec::size_type i = 0; i != supported.size(); ++i) { - // set up body - PrgBody* b = prg_->getBody(supported[i]); - if (!b->simplify(*prg_, false)) { return false; } - if (b->var() < startVar) { - if (b->size() != 1) { b->assignVar(*prg_); } - else { unitBodies.push_back(supported[i]); } - } - // add all heads of b to the "upper"-closure and - // remove any false/removed atoms from head - if (!addHeadsToUpper(b) || !b->simplifyHeads(*prg_, true)) { - return false; - } - } - for (VarVec::const_iterator it = unitBodies.begin(), end = unitBodies.end(); it != end; ++it) { - prg_->getBody(*it)->assignVar(*prg_); - } - return prg_->propagate(); + if (not prg_->propagate(true)) { + return false; + } + auto startVar = prg_->ctx()->numVars() + 1; + // start with initially supported bodies + VarVec unitBodies; + const auto& supported = prg_->getSupportedBodies(true); + // NOTE: adding heads might result in new supported bodies + for (auto qFront = static_cast(0); qFront < supported.size();) { + auto id = supported[qFront++]; + PrgBody* b = prg_->getBody(id); + if (not b->simplify(*prg_, false)) { + return false; + } + if (b->var() < startVar) { + if (b->size() != 1) { + b->assignVar(*prg_); + } + else { + unitBodies.push_back(id); + } + } + // add all heads of b to the "upper"-closure and remove any false/removed atoms from head + if (not addHeadsToUpper(b) || not b->simplifyHeads(*prg_, true)) { + return false; + } + } + for (auto id : unitBodies) { prg_->getBody(id)->assignVar(*prg_); } + return prg_->propagate(); } -bool Preprocessor::addHeadToUpper(PrgHead* head, PrgEdge support) { - assert(head->relevant() && !head->inUpper()); - head->simplifySupports(*prg_, false); - head->assignVar(*prg_, support, eq()); - head->clearSupports(); - head->setInUpper(true); - if (head->isAtom()) { - return propagateAtomVar(static_cast(head), support); - } - // add all unseen atoms of disjunction to upper - PrgDisj* d = static_cast(head); - support = PrgEdge::newEdge(*d, PrgEdge::Choice); - bool ok = true; - for (PrgDisj::atom_iterator it = d->begin(), end = d->end(); it != end && ok; ++it) { - PrgAtom* at = prg_->getAtom(*it); - if (!at->relevant()) { continue; } - if (!at->inUpper()) { ok = addHeadToUpper(at, support); } - at->addSupport(support); - } - return ok; +bool Preprocessor::addToUpper(PrgHead* head, PrgEdge support) { + assert(head->relevant() && not head->inUpper()); + if (not head->simplifySupports(*prg_, false)) { + return false; + } + head->assignVar(*prg_, support, eq()); + head->clearSupports(); + head->setInUpper(true); + return true; } ///////////////////////////////////////////////////////////////////////////////////////// // equivalence preprocessing @@ -84,157 +81,181 @@ bool Preprocessor::addHeadToUpper(PrgHead* head, PrgEdge support) { // Computes max consequences and minimizes the number of necessary variables // by computing equivalence-classes. ///////////////////////////////////////////////////////////////////////////////////////// -bool Preprocessor::preprocessEq(uint32 maxIters) { - uint32 startVar = prg_->ctx()->numVars(); - ValueRep res = value_true; - pass_ = 0; - maxPass_ = maxIters; - HeadRange atoms = HeadRange(prg_->atom_begin() + prg_->startAtom(), prg_->atom_end()); - bodyInfo_.resize( prg_->numBodies() + 1 ); - do { - if (++pass_ > 1) { - for (HeadIter it = prg_->atom_begin(), end = atoms.second; it != end; ) { - while (it != atoms.first){ (*it)->setInUpper(false); ++it; } - while (it != end) { (*it)->clearLiteral(false); (*it)->setInUpper(false); ++it; } - } - for (HeadIter it = prg_->disj_begin(), end = prg_->disj_end(); it != end; ++it) { - (*it)->clearLiteral(false); - (*it)->setInUpper(false); - } - prg_->ctx()->popVars(prg_->ctx()->numVars() - startVar); - litToNode_.clear(); - } - VarVec& supported = prg_->getSupportedBodies(true); - if (!classifyProgram(supported)) { return false; } - res = simplifyClassifiedProgram(atoms, pass_ != maxPass_, supported); - } while (res == value_free && pass_ != maxPass_); - return res != value_false; +bool Preprocessor::preprocessEq(uint32_t maxIters) { + pass_ = 0; + maxPass_ = maxIters; + bodyInfo_.resize(prg_->numBodies() + 1); + for (auto startVar = prg_->ctx()->numVars();;) { + ++pass_; + if (not classifyProgram()) { + return false; + } + if (auto r = simplifyClassifiedProgram(pass_ != maxPass_); r != value_free || pass_ == maxPass_) { + return r != value_false; + } + for (PrgHead* h : prg_->oldAtoms()) { h->setInUpper(false); } + for (const auto& range : {node_cast(prg_->disjunctions()), node_cast(prg_->stepAtoms())}) { + for (PrgHead* h : range) { + h->setInUpper(false); + h->clearLiteral(false); + } + } + prg_->ctx()->popVars(prg_->ctx()->numVars() - startVar); + litToNode_.clear(); + } } - -// Computes necessary equivalence-classes starting from the supported bodies -// of a program. -bool Preprocessor::classifyProgram(const VarVec& supported) { - Var bodyId; PrgBody* body; - VarVec::size_type index = 0; - follow_.clear(); - if (!prg_->propagate(true)) { return false; } - for (VarVec::size_type i = 0;;) { - while ( (bodyId = nextBodyId(index)) != varMax ) { - body = addBodyVar(bodyId); - if (prg_->hasConflict()) { return false; } - if (!addHeadsToUpper(body)) { return false; } - } - follow_.clear(); - index = 0; - // select next unclassified supported body - for (; i < supported.size(); ++i) { - bodyId = supported[i]; - body = prg_->getBody(bodyId); - if (bodyInfo_[bodyId].bSeen == 0 && body->relevant()) { - follow_.push_back(bodyId); - break; - } - else if (!body->relevant() && body->hasVar()) { - body->clearLiteral(false); - } - } - if (follow_.empty()) break; - } - return !prg_->hasConflict(); +uint32_t Preprocessor::popFollow(uint32_t& idx) { + assert(idx < size32(follow_)); + if (dfs_) { + auto id = follow_.back(); + follow_.pop_back(); + return id; + } + return follow_[idx++]; +} +// Computes necessary equivalence-classes starting from the supported bodies of a program. +bool Preprocessor::classifyProgram() { + if (not prg_->propagate(true)) { + return false; + } + VarVec& supported = prg_->getSupportedBodies(true); + follow_.clear(); + // start from next unclassified supported body + for (uint32_t root = 0u; root < size32(supported); ++root) { // NOTE: supported might change! + auto bodyId = supported[root]; + auto* body = prg_->getBody(bodyId); + if (bodyInfo_[bodyId].bSeen == 0 && body->relevant()) { + for (uint32_t front = 0;;) { // classify body and all bodies following from it + body = addBodyVar(bodyId); + if (prg_->hasConflict() || not addHeadsToUpper(body)) { + return false; + } + if (front == size32(follow_)) { + follow_.clear(); + break; + } + bodyId = popFollow(front); + } + } + else if (not body->relevant() && body->hasVar()) { + body->clearLiteral(false); + } + } + assert(follow_.empty()); + return not prg_->hasConflict(); } -ValueRep Preprocessor::simplifyClassifiedProgram(const HeadRange& atoms, bool more, VarVec& supported) { - ValueRep res = value_true, simp; - if (!prg_->propagate()) { return value_false; } - supported.clear(); - // simplify supports - for (uint32 i = 0; i != prg_->numBodies(); ++i) { - PrgBody* b = prg_->getBody(i); - if (bodyInfo_[i].bSeen == 0 || !b->relevant()) { - // !bodyInfo_[i].bSeen: body is unsupported - // !b->relevant() : body is eq to other body or was derived to false - // In either case, body is no longer relevant and can be ignored. - b->clearLiteral(true); - b->markRemoved(); - } - else if ( (simp = simplifyBody(b, more, supported)) != value_true ) { - if (simp == value_false) { return simp; } - res = value_free; - } - } - if (!prg_->propagate()) { return value_false; } - PrgEdge noSup = PrgEdge::noEdge(); - for (LogicProgram::VarIter it = prg_->unfreeze_begin(), end = prg_->unfreeze_end(); it != end; ++it) { - PrgAtom* a = prg_->getAtom(*it); - ValueRep v = a->value(); - if (!a->simplifySupports(*prg_, true)){ return value_false; } - else if (!a->inUpper() && v != value_false){ - if (!prg_->assignValue(a, value_false, noSup)){ return value_false; } - if (more && a->hasDep(PrgAtom::dep_all)) { res = value_free; } - } - } - if (!prg_->propagate()) { return value_false; } - bool strong = more && res == value_true; - HeadRange heads[2] = { HeadRange(prg_->disj_begin(), prg_->disj_end()), atoms }; - for (const HeadRange* range = heads, *endR = heads+2; range != endR; ++range) { - for (HeadIter it = range->first, end = range->second; it != end; ++it) { - PrgHead* head = *it; - if ((simp = simplifyHead(head, strong)) != value_true) { - if (simp == value_false){ return simp; } - else if (strong) { strong = false; res = value_free; } - } - } - } - if (!prg_->propagate()) { res = value_false; } - return res; +Val_t Preprocessor::simplifyClassifiedProgram(bool more) { + if (not prg_->propagate()) { + return value_false; + } + VarVec& supported = prg_->getSupportedBodies(false); + supported.clear(); + // simplify supports + auto res = value_true; + for (uint32_t id = 0; auto* b : prg_->bodies()) { + if (bodyInfo_[id].bSeen == 0 || not b->relevant()) { + // not bodyInfo_[i].bSeen: body is unsupported + // not b->relevant() : body is eq to other body or was derived to false + // In either case, body is no longer relevant and can be ignored. + b->clearLiteral(true); + b->markRemoved(); + } + else if (auto simp = simplifyBody(b, more, supported); simp != value_true) { + if (simp == value_false) { + return simp; + } + res = value_free; + } + ++id; + } + if (not prg_->propagate()) { + return value_false; + } + PrgEdge noSup = PrgEdge::noEdge(); + for (auto u : prg_->unfreeze()) { + PrgAtom* a = prg_->getAtom(u); + auto v = a->value(); + if (not a->simplifySupports(*prg_, true)) { + return value_false; + } + if (not a->inUpper() && v != value_false) { + if (not prg_->assignValue(a, value_false, noSup)) { + return value_false; + } + if (more && a->hasDep(PrgAtom::dep_all)) { + res = value_free; + } + } + } + if (not prg_->propagate()) { + return value_false; + } + bool strong = more && res == value_true; + for (const auto& range : {node_cast(prg_->disjunctions()), node_cast(prg_->stepAtoms())}) { + for (PrgHead* head : range) { + if (auto simp = simplifyHead(head, strong); simp != value_true) { + if (simp == value_false) { + return simp; + } + if (strong) { + strong = false; + res = value_free; + } + } + } + } + if (not prg_->propagate()) { + res = value_false; + } + return res; } // associates a variable with the body if necessary -PrgBody* Preprocessor::addBodyVar(Var bodyId) { - // make sure we don't add an irrelevant body - PrgBody* body = prg_->getBody(bodyId); - assert((body->isSupported() && !body->eq()) || body->hasVar()); - body->clearLiteral(false); // clear var in case we are iterating - bodyInfo_[bodyId].bSeen = 1; // mark as seen, so we don't classify the body again - bool known = bodyInfo_[bodyId].known == body->size(); - uint32 eqId; - if (!body->simplifyBody(*prg_, known, &eqId) || !body->simplifyHeads(*prg_, false)) { - prg_->setConflict(); - return body; - } - if (superfluous(body)) { - body->markRemoved(); - return body; - } - if (eqId == bodyId) { - // The body is unique - body->assignVar(*prg_); - PrgAtom* aEq = body->size() == 1 ? prg_->getAtom(body->goal(0).var()) : 0; - if (!known) { body->markDirty(); } - else if (aEq && aEq->var() == body->var()){ - // Body is equivalent to an atom or its negation - // Check if the atom is itself equivalent to a body. - // If so, the body is equivalent to the atom's body. - PrgBody* r = 0; // possible eq-body - uint32 rId = varMax; - if (body->goal(0).sign()) { - Var dualAtom = getRootAtom(body->literal()); - aEq = dualAtom != varMax ? prg_->getAtom(dualAtom) : 0; - } - if (aEq && aEq->supports() && aEq->supps_begin()->isBody()) { - rId = aEq->supps_begin()->node(); - r = prg_->getBody(rId); - if (r && r->var() == aEq->var()) { - mergeEqBodies(body, rId, false); - } - } - } - } - else { - // body is eq to eq body - mergeEqBodies(body, eqId, true); - } - return body; +PrgBody* Preprocessor::addBodyVar(uint32_t bodyId) { + // make sure we don't add an irrelevant body + PrgBody* body = prg_->getBody(bodyId); + assert((body->isSupported() && not body->eq()) || body->hasVar()); + body->clearLiteral(false); // clear var in case we are iterating + bodyInfo_[bodyId].bSeen = 1; // mark as seen, so we don't classify the body again + bool known = bodyInfo_[bodyId].known == body->size(); + uint32_t eqId; + if (not body->simplifyBody(*prg_, known, &eqId) || not body->simplifyHeads(*prg_, false)) { + prg_->setConflict(); + return body; + } + if (superfluous(body)) { + body->markRemoved(); + return body; + } + if (eqId == bodyId) { + // The body is unique + body->assignVar(*prg_); + PrgAtom* aEq = body->size() == 1 ? prg_->getAtom(body->goal(0).var()) : nullptr; + if (not known) { + body->markDirty(); + } + else if (aEq && aEq->var() == body->var()) { + // Body is equivalent to an atom or its negation + // Check if the atom is itself equivalent to a body. + // If so, the body is equivalent to the atom's body. + if (body->goal(0).sign()) { + auto dualAtom = getRootAtom(body->literal()); + aEq = dualAtom != var_max ? prg_->getAtom(dualAtom) : nullptr; + } + if (aEq && aEq->support().isBody()) { + auto rId = aEq->support().node(); + if (PrgBody* r = prg_->getBody(rId); r && r->var() == aEq->var()) { + mergeEqBodies(body, rId, false); + } + } + } + } + else { + // body is eq to eq body + mergeEqBodies(body, eqId, true); + } + return body; } // Adds all heads of body to the upper closure if not yet present and @@ -242,44 +263,68 @@ PrgBody* Preprocessor::addBodyVar(Var bodyId) { // The body b is the supported body that provides a support for the heads. // RETURN: true if no conflict // POST : the addition of atoms to the closure was propagated -bool Preprocessor::addHeadsToUpper(PrgBody* body) { - PrgHead* head; - PrgEdge support; - bool ok = !prg_->hasConflict(); - int dirty= 0; - for (PrgBody::head_iterator it = body->heads_begin(), end = body->heads_end(); it != end && ok; ++it) { - head = prg_->getHead(*it); - support= PrgEdge::newEdge(*body, it->type()); - if (head->relevant() && head->value() != value_false) { - if (body->value() == value_true && head->isAtom()) { - // Since b is true, it is always a valid support for head, head can never become unfounded. - // So ignore it during SCC check and unfounded set computation. - head->setIgnoreScc(true); - if (support.isNormal() && head->isAtom()) { - ok = propagateAtomValue(static_cast(head), value_true, support); - } - } - if (!head->inUpper()) { - // first time we see this head - assign var... - ok = addHeadToUpper(head, support); - } - else if (head->supports() && head->supps_begin()->isNormal()) { - PrgEdge source = *head->supps_begin(); - assert(source.isBody()); - if (prg_->getBody(source.node())->var() == body->var()) { - // Check if we really need a new variable for head. - head->markDirty(); - } - } - head->addSupport(support, PrgHead::no_simplify); - } - dirty += (head->eq() || head->value() == value_false); - } - if (dirty) { - // remove eq atoms from head - prg_->getBody(body->id())->markHeadsDirty(); - } - return ok; +bool Preprocessor::addHeadsToUpper(const PrgBody* body) { + bool ok = not prg_->hasConflict(); + int dirty = 0; + for (auto h : body->heads()) { + if (not ok) { + break; + } + auto* head = prg_->getHead(h); + auto support = PrgEdge::newEdge(*body, h.type()); + if (head->relevant() && head->value() != value_false) { + if (body->value() == value_true && head->isAtom()) { + // Since b is true, it is always a valid support for head, head can never become unfounded. + // So ignore it during SCC check and unfounded set computation. + head->setIgnoreScc(true); + if (support.isNormal() && head->isAtom()) { + ok = propagateAtomValue(node_cast(head), value_true, support); + } + } + if (not head->inUpper()) { + // first time we see this head - assign var... + ok = head->isAtom() ? addAtomToUpper(node_cast(head), support) + : addDisjToUpper(node_cast(head), support); + } + else if (head->support().isNormal()) { + PrgEdge source = head->support(); + assert(source.isBody()); + if (prg_->getBody(source.node())->var() == body->var()) { + // Check if we really need a new variable for head. + head->markDirty(); + } + } + head->addSupport(support, PrgHead::no_simplify); + } + dirty += (head->eq() || head->value() == value_false); + } + if (dirty) { + // remove eq atoms from head + prg_->getBody(body->id())->markHeadsDirty(); + } + return ok; +} + +bool Preprocessor::addAtomToUpper(PrgAtom* atom, PrgEdge support) { + return addToUpper(atom, support) && propagateAtomVar(atom, support); +} + +bool Preprocessor::addDisjToUpper(PrgDisj* disj, PrgEdge support) { + bool ok = addToUpper(disj, support); + if (ok) { + // add unseen atoms of disjunction to upper + support = PrgEdge::newEdge(*disj, PrgEdge::choice); + for (auto a : disj->atoms()) { + if (PrgAtom* at = prg_->getAtom(a); at->relevant()) { + ok = at->inUpper() || addAtomToUpper(at, support); + at->addSupport(support); + if (not ok) { + break; + } + } + } + } + return ok; } // Propagates that `a` was added to the "upper"-closure. @@ -290,127 +335,136 @@ bool Preprocessor::addHeadsToUpper(PrgBody* body) { // In case that a == a', we also mark all bodies containing a // for head simplification in order to detect rules like: a' :- a,B. and a' :- B,not a. bool Preprocessor::propagateAtomVar(PrgAtom* a, PrgEdge source) { - const Var aId = a->id(); - PrgAtom* comp = 0; - ValueRep value = a->value(); - bool fullEq = eq(); - bool removeAtom = value == value_true || value == value_false; - bool removeNeg = removeAtom || value == value_weak_true; - Literal aLit = a->literal(); - if (fullEq) { - if (getRootAtom(aLit) == varMax) { - setRootAtom(aLit, aId); - } - else if (prg_->mergeEqAtoms(a, getRootAtom(aLit))) { - assert(source.isBody()); - removeAtom = true; - removeNeg = true; - value = a->value(); - PrgBody* B = prg_->getBody(source.node()); - a->setEqGoal(posLit(a->id())); - // set positive eq goal - replace if a == {not a'}, replace a with not a' in bodies - if (getRootAtom(~aLit) != varMax && B->literal() == aLit && B->size() == 1 && B->goal(0).sign()) { - a->setEqGoal(negLit(getRootAtom(~aLit))); - } - a->clearLiteral(true); // equivalent atoms don't need vars - } - else { return false; } - } - if (getRootAtom(~aLit) != varMax) { - PrgAtom* negA = prg_->getAtom(getRootAtom(~aLit)); - assert(aLit == ~negA->literal()); - // propagate any truth-value to complementary eq-class - ValueRep cv = value_free; - uint32 mark = 0; - if (value != value_free && (cv = (value_false | (value^value_true))) != negA->value()) { - mark = 1; - if (!propagateAtomValue(negA, cv, PrgEdge::noEdge())) { - return false; - } - } - if ( !removeAtom ) { - for (PrgAtom::dep_iterator it = (comp=negA)->deps_begin(); it != comp->deps_end(); ++it) { - bodyInfo_[it->var()].mBody = 1; - if (mark) { prg_->getBody(it->var())->markDirty(); } - } - } - } - for (PrgAtom::dep_iterator it = a->deps_begin(), end = a->deps_end(); it != end; ++it) { - Var bodyId = it->var(); - PrgBody* bn = prg_->getBody(bodyId); - if (bn->relevant()) { - bool wasSup = bn->isSupported(); - bool isSup = wasSup || (value != value_false && !it->sign() && bn->propagateSupported(aId)); - bool seen = false; - bool dirty = removeAtom || (removeNeg && it->sign()); - if (fullEq) { - seen = bodyInfo_[bodyId].bSeen != 0; - dirty |= bodyInfo_[bodyId].mBody == 1; - if (++bodyInfo_[bodyId].known == bn->size() && !seen && isSup) { - follow_.push_back( bodyId ); - seen = true; - } - } - if (!seen && isSup && !wasSup) { - prg_->getSupportedBodies(false).push_back(bodyId); - } - if (dirty) { - bn->markDirty(); - if (a->eq()) { - bn->markHeadsDirty(); - } - } - } - } - if (removeAtom) { a->clearDeps(PrgAtom::dep_all); } - else if (removeNeg) { a->clearDeps(PrgAtom::dep_neg); } - if (comp) { - for (PrgAtom::dep_iterator it = comp->deps_begin(), end = comp->deps_end(); it != end; ++it) { - bodyInfo_[it->var()].mBody = 0; - } - } - return true; + const auto aId = a->id(); + PrgAtom* comp = nullptr; + auto value = a->value(); + bool fullEq = eq(); + bool removeAtom = value == value_true || value == value_false; + bool removeNeg = removeAtom || value == value_weak_true; + Literal aLit = a->literal(); + if (fullEq) { + if (getRootAtom(aLit) == var_max) { + setRootAtom(aLit, aId); + } + else if (prg_->mergeEqAtoms(a, getRootAtom(aLit))) { + assert(source.isBody()); + removeAtom = true; + removeNeg = true; + value = a->value(); + PrgBody* bn = prg_->getBody(source.node()); + a->setEqGoal(posLit(a->id())); + // set positive eq goal - replace if a == {not a'}, replace a with not a' in bodies + if (getRootAtom(~aLit) != var_max && bn->literal() == aLit && bn->size() == 1 && bn->goal(0).sign()) { + a->setEqGoal(negLit(getRootAtom(~aLit))); + } + a->clearLiteral(true); // equivalent atoms don't need vars + } + else { + return false; + } + } + if (getRootAtom(~aLit) != var_max) { + PrgAtom* negA = prg_->getAtom(getRootAtom(~aLit)); + assert(aLit == ~negA->literal()); + // propagate any truth-value to complementary eq-class + auto cv = value_free; + uint32_t mark = 0; + if (value != value_free && (cv = (value_false | (value ^ value_true))) != negA->value()) { + mark = 1; + if (not propagateAtomValue(negA, cv, PrgEdge::noEdge())) { + return false; + } + } + if (not removeAtom) { + for (auto dep : (comp = negA)->deps()) { + bodyInfo_[dep.var()].mBody = 1; + if (mark) { + prg_->getBody(dep.var())->markDirty(); + } + } + } + } + for (auto dep : a->deps()) { + auto bodyId = dep.var(); + if (PrgBody* bn = prg_->getBody(bodyId); bn->relevant()) { + bool wasSup = bn->isSupported(); + bool isSup = wasSup || (value != value_false && not dep.sign() && bn->propagateSupported(aId)); + bool seen = false; + bool dirty = removeAtom || (removeNeg && dep.sign()); + if (fullEq) { + seen = bodyInfo_[bodyId].bSeen != 0; + dirty |= bodyInfo_[bodyId].mBody == 1; + if (++bodyInfo_[bodyId].known == bn->size() && not seen && isSup) { + follow_.push_back(bodyId); + seen = true; + } + } + if (not seen && isSup && not wasSup) { + prg_->getSupportedBodies(false).push_back(bodyId); + } + if (dirty) { + bn->markDirty(); + if (a->eq()) { + bn->markHeadsDirty(); + } + } + } + } + if (removeAtom) { + a->clearDeps(PrgAtom::dep_all); + } + else if (removeNeg) { + a->clearDeps(PrgAtom::dep_neg); + } + if (comp) { + for (auto dep : comp->deps()) { bodyInfo_[dep.var()].mBody = 0; } + } + return true; } // Propagates the assignment of val to atom. -bool Preprocessor::propagateAtomValue(PrgAtom* atom, ValueRep val, PrgEdge sup) { - // No backpropagation possible because supports are not yet fully established. - return prg_->assignValue(atom, val, sup) && prg_->propagate(false); +bool Preprocessor::propagateAtomValue(PrgAtom* atom, Val_t val, PrgEdge sup) { + // No backpropagation possible because supports are not yet fully established. + return prg_->assignValue(atom, val, sup) && prg_->propagate(false); } -bool Preprocessor::mergeEqBodies(PrgBody* body, Var rootId, bool equalLits) { - PrgBody* root = prg_->mergeEqBodies(body, rootId, equalLits, false); - if (root && root != body && bodyInfo_[root->id()].bSeen == 0) { - // If root is not yet classified, we can ignore body. - // The heads of body are added to the "upper"-closure - // once root is eventually classified. - body->clearHeads(); - body->markRemoved(); - } - return root != 0; +bool Preprocessor::mergeEqBodies(PrgBody* body, uint32_t rootId, bool equalLits) { + PrgBody* root = prg_->mergeEqBodies(body, rootId, equalLits, false); + if (root && root != body && bodyInfo_[root->id()].bSeen == 0) { + // If root is not yet classified, we can ignore body. + // The heads of body are added to the "upper"-closure + // once root is eventually classified. + body->clearHeads(); + body->markRemoved(); + } + return root != nullptr; } -bool Preprocessor::hasRootLiteral(PrgBody* body) const { - return body->size() >= 1 - && getRootAtom(body->literal()) == varMax - && getRootAtom(~body->literal())== varMax; +bool Preprocessor::hasRootLiteral(const PrgBody* body) const { + return body->size() >= 1 && getRootAtom(body->literal()) == var_max && getRootAtom(~body->literal()) == var_max; } // Pre: body is simplified! -bool Preprocessor::superfluous(PrgBody* body) const { - if (!body->relevant()) { return true; } - if (!body->inRule()) { - if (body->value() == value_free) { return true; } - if (body->bound() <= 0) { return true; } - if (body->size() == 1) { - // unit constraint - ValueRep exp = body->value() ^ (int)body->goal(0).sign(); - ValueRep got = prg_->getAtom(body->goal(0).var())->value(); - assert(got != value_free || !prg_->options().backprop); - return got != value_free && (got&value_true) == (exp&value_true); - } - } - return false; +bool Preprocessor::superfluous(const PrgBody* body) const { + if (not body->relevant()) { + return true; + } + if (not body->inRule()) { + if (body->value() == value_free) { + return true; + } + if (body->bound() <= 0) { + return true; + } + if (body->size() == 1) { + // unit constraint + auto exp = static_cast(body->value() ^ static_cast(body->goal(0).sign())); + auto got = prg_->getAtom(body->goal(0).var())->value(); + assert(got != value_free || not prg_->options().backprop); + return got != value_free && (got & value_true) == (exp & value_true); + } + } + return false; } // Simplify the classified body with the given id. @@ -418,127 +472,128 @@ bool Preprocessor::superfluous(PrgBody* body) const { // value_false : conflict // value_true : ok // value_weak_true: ok but program should be reclassified -ValueRep Preprocessor::simplifyBody(PrgBody* b, bool reclass, VarVec& supported) { - assert(b->relevant() && bodyInfo_[b->id()].bSeen == 1); - bodyInfo_[b->id()].bSeen = 0; - bodyInfo_[b->id()].known = 0; - bool hadHeads = b->hasHeads(); - bool hasRoot = hasRootLiteral(b); - uint32 eqId = b->id(); - if (!b->simplify(*prg_, true, &eqId)) { - return value_false; - } - ValueRep ret = value_true; - if (reclass) { - if (hadHeads && b->value() == value_false) { - assert(b->hasHeads() == false); - // New false body. If it was derived to false, we can ignore the body. - // Otherwise, we have a new integrity constraint. - if (!b->relevant()) { - b->clearLiteral(true); - } - } - else if (b->var() != 0 && superfluous(b)) { - // Body is no longer needed. All heads are either superfluous or equivalent - // to other atoms. - // Reclassify only if var is not used - if (getRootAtom(b->literal()) == varMax) { ret = value_weak_true; } - b->clearLiteral(true); - b->markRemoved(); - } - else if (b->value() == value_true && b->var() != 0) { - // New fact body - for (PrgBody::head_iterator it = b->heads_begin(), end = b->heads_end(); it != end; ++it) { - if (it->isNormal() && prg_->getHead(*it)->var() != 0) { - ret = value_weak_true; - break; - } - } - b->markDirty(); - } - } - if (b->relevant() && eqId != b->id() && (reclass || prg_->getBody(eqId)->var() == b->var())) { - // Body is now eq to some other body - reclassify if body var is not needed - Var bVar = b->var(); - prg_->mergeEqBodies(b, eqId, true, true); - if (hasRoot && bVar != b->var()) { - ret = value_weak_true; - } - } - if (b->relevant() && b->resetSupported()) { - supported.push_back(b->id()); - } - return ret; +Val_t Preprocessor::simplifyBody(PrgBody* b, bool reclass, VarVec& supported) { + assert(b->relevant() && bodyInfo_[b->id()].bSeen == 1); + bodyInfo_[b->id()].bSeen = 0; + bodyInfo_[b->id()].known = 0; + auto hadHeads = b->hasHeads(); + auto hasRoot = hasRootLiteral(b); + auto eqId = b->id(); + if (not b->simplify(*prg_, true, &eqId)) { + return value_false; + } + auto ret = value_true; + if (reclass) { + if (hadHeads && b->value() == value_false) { + assert(b->hasHeads() == false); + // New false body. If it was derived to false, we can ignore the body. + // Otherwise, we have a new integrity constraint. + if (not b->relevant()) { + b->clearLiteral(true); + } + } + else if (b->var() != 0 && superfluous(b)) { + // Body is no longer needed. All heads are either superfluous or equivalent + // to other atoms. + // Reclassify only if var is not used + if (getRootAtom(b->literal()) == var_max) { + ret = value_weak_true; + } + b->clearLiteral(true); + b->markRemoved(); + } + else if (b->value() == value_true && b->var() != 0) { + // New fact body + for (auto h : b->heads()) { + if (h.isNormal() && prg_->getHead(h)->var() != 0) { + ret = value_weak_true; + break; + } + } + b->markDirty(); + } + } + if (b->relevant() && eqId != b->id() && (reclass || prg_->getBody(eqId)->var() == b->var())) { + // Body is now eq to some other body - reclassify if body var is not needed + auto bVar = b->var(); + prg_->mergeEqBodies(b, eqId, true, true); + if (hasRoot && bVar != b->var()) { + ret = value_weak_true; + } + } + if (b->relevant() && b->resetSupported()) { + supported.push_back(b->id()); + } + return ret; } // Simplify the classified head h. // Update list of bodies defining this head and check -// if atom or disjunction has a distinct var, although it is eq to some body. +// if atom or disjunction has a distinct var, although it is eq to some rule body. // Return: // value_false : conflict // value_true : ok // value_weak_true: ok but atom should be reclassified -ValueRep Preprocessor::simplifyHead(PrgHead* h, bool more) { - if (!h->hasVar() || !h->relevant()) { - // unsupported or eq - h->clearLiteral(false); - h->markRemoved(); - h->clearSupports(); - h->setInUpper(false); - return value_true; - } - assert(h->inUpper()); - ValueRep v = h->value(); - ValueRep ret = value_true; - PrgEdge support = h->supports() ? *h->supps_begin() : PrgEdge::noEdge(); - uint32 numSuppLits= 0; - if (!h->simplifySupports(*prg_, true, &numSuppLits)) { - return value_false; - } - if (v != h->value() && (h->value() == value_false || (h->value() == value_true && h->var() != 0))) { - ret = value_weak_true; - } - if (more) { - if (numSuppLits == 0 && h->hasVar()) { - // unsupported head does not need a variable - ret = value_weak_true; - } - else if (h->supports() > 0 && h->supps_begin()->rep != support.rep) { - // support for head has changed - ret = value_weak_true; - } - else if ((support.isNormal() && h->supports() == 1) || (h->supports() > 1 && numSuppLits == 1 && h->isAtom())) { - assert(support.isBody()); - PrgBody* supBody = prg_->getBody(support.node()); - if (supBody->literal() != h->literal()) { - if (h->supports() > 1) { - // atom is equivalent to one of its bodies - EdgeVec temp(h->supps_begin(), h->supps_end()); - h->clearSupports(); - support = temp[0]; - for (EdgeIterator it = temp.begin(), end = temp.end(); it != end; ++it) { - assert(!it->isDisj()); - PrgBody* B = prg_->getBody(it->node()); - if (it->isNormal() && B->size() == 1 && B->goal(0).sign()) { - support = *it; - } - B->removeHead(h, it->type()); - } - supBody = prg_->getBody(support.node()); - supBody->addHead(h, support.type()); - if (!supBody->simplifyHeads(*prg_, true)) { - return value_false; - } - } - ret = value_weak_true; - if (h->value() == value_weak_true || h->value() == value_true) { - supBody->assignValue(h->value()); - supBody->propagateValue(*prg_, true); - } - } - } - } - return ret; +Val_t Preprocessor::simplifyHead(PrgHead* h, bool reclassify) { + if (not h->hasVar() || not h->relevant()) { + // unsupported or eq + h->clearLiteral(false); + h->markRemoved(); + h->clearSupports(); + h->setInUpper(false); + return value_true; + } + assert(h->inUpper()); + auto v = h->value(); + PrgEdge support = h->support(); + uint32_t numSuppLits = 0; + if (not h->simplifySupports(*prg_, true, &numSuppLits)) { + return value_false; + } + if (reclassify) { + if (numSuppLits == 0 && h->hasVar()) { + // unsupported head does not need a variable + return value_weak_true; + } + if (h->support() != support) { + // support for head has changed + return value_weak_true; + } + if (auto numSupps = h->numSupports(); + (support.isNormal() && numSupps == 1) || (numSupps > 1 && numSuppLits == 1 && h->isAtom())) { + assert(support.isBody()); + if (PrgBody* supBody = prg_->getBody(support.node()); supBody->literal() != h->literal()) { + if (numSupps > 1) { + // atom is equivalent to one of its bodies + EdgeVec temp; + h->clearSupports(temp); + support = temp[0]; + for (auto s : temp) { + assert(not s.isDisj()); + PrgBody* bn = prg_->getBody(s.node()); + if (s.isNormal() && bn->size() == 1 && bn->goal(0).sign()) { + support = s; + } + bn->removeHead(h, s.type()); + } + supBody = prg_->getBody(support.node()); + supBody->addHead(h, support.type()); + if (not supBody->simplifyHeads(*prg_, true)) { + return value_false; + } + } + if (h->value() == value_weak_true || h->value() == value_true) { + supBody->assignValue(h->value()); + supBody->propagateValue(*prg_, true); + } + return value_weak_true; + } + } + } + if (v != h->value() && (h->value() == value_false || (h->value() == value_true && h->var() != 0))) { + return value_weak_true; + } + return value_true; } -} } +} // namespace Clasp::Asp diff --git a/src/cb_enumerator.cpp b/src/cb_enumerator.cpp index eb9e72c..37696f8 100644 --- a/src/cb_enumerator.cpp +++ b/src/cb_enumerator.cpp @@ -1,7 +1,7 @@ // // Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -22,329 +22,359 @@ // IN THE SOFTWARE. // #include -#include #include -#if CLASP_HAS_THREADS -#include -#define ACQUIRE_LOCK(m) while ( (m).exchange(1) != 0 ) Clasp::mt::this_thread::yield() -#define RELEASE_LOCK(m) (m) = 0 -#else -#define ACQUIRE_LOCK(m) -#define RELEASE_LOCK(m) -#endif +#include namespace Clasp { ///////////////////////////////////////////////////////////////////////////////////////// // CBConsequences::SharedConstraint ///////////////////////////////////////////////////////////////////////////////////////// class CBConsequences::SharedConstraint { public: - SharedConstraint() : current(0) { mutex = 0; } - ~SharedConstraint() { - if (current) { current->release(); } - } - SharedLiterals* fetch_if_neq(SharedLiterals* last) const { - ACQUIRE_LOCK(mutex); - SharedLiterals* ret = last != current ? current->share() : 0; - RELEASE_LOCK(mutex); - return ret; - } - void release(SharedLiterals* newLits) { - ACQUIRE_LOCK(mutex); - SharedLiterals* old = current; - current = newLits; - RELEASE_LOCK(mutex); - if (old) { old->release(); } - } - SharedLiterals* current; - typedef Clasp::Atomic_t::type MutexType; - mutable MutexType mutex; + SharedConstraint() = default; + ~SharedConstraint() { release(nullptr); } + SharedLiterals* fetch_if_neq(const SharedLiterals* last) { + auto* val = current.lock(); + auto* ret = val && val != last ? val->share() : nullptr; + current.store_unlock(val); + return ret; + } + void release(SharedLiterals* newLits) { + auto* prev = current.lock(); + current.store_unlock(newLits); + if (prev) { + prev->release(); + } + } + LockedValue current; }; -#undef ACQUIRE_LOCK -#undef RELEASE_LOCK ///////////////////////////////////////////////////////////////////////////////////////// // CBConsequences::CBFinder ///////////////////////////////////////////////////////////////////////////////////////// class CBConsequences::CBFinder : public EnumerationConstraint { public: - typedef CBConsequences::SharedConstraint SharedCon; - typedef Solver::ConstraintDB ConstraintDB; - typedef SharedLiterals SharedLits; - explicit CBFinder(SharedCon* sh) : EnumerationConstraint(), shared(sh), last(0) {} - ConPtr clone() { return new CBFinder(shared); } - void doCommitModel(Enumerator& ctx, Solver& s) { static_cast(ctx).addCurrent(s, current, s.model, rootLevel()); } - void destroy(Solver* s, bool detach); - bool doUpdate(Solver& s); - void pushLocked(Solver& s, ClauseHead* h); - LitVec current; - SharedCon* shared; - SharedLits* last; - ConstraintDB locked; + using SharedCon = SharedConstraint; + using ConstraintDB = Solver::ConstraintDB; + using SharedLits = SharedLiterals; + explicit CBFinder(SharedCon* sh) : shared(sh) {} + ConPtr clone() override { return new CBFinder(shared); } + void doCommitModel(Enumerator& ctx, Solver& s) override { + static_cast(ctx).addCurrent(s, current, lastM, rootLevel()); + } + bool doExtractModel(Solver&, ValueVec& out, bool sat) override { + if (sat && not lastM.empty()) { + out = std::move(lastM); + return true; + } + return false; + } + void destroy(Solver* s, bool detach) override; + bool doUpdate(Solver& s) override; + void pushLocked(Solver& s, ClauseHead* c); + LitVec current; + SharedCon* shared; + SharedLits* last{nullptr}; + ConstraintDB locked; + ValueVec lastM; }; ///////////////////////////////////////////////////////////////////////////////////////// // CBConsequences::QueryFinder ///////////////////////////////////////////////////////////////////////////////////////// -class CBConsequences::QueryFinder : public EnumerationConstraint{ +class CBConsequences::QueryFinder : public EnumerationConstraint { public: - class State { - public: - State(uint32 nVars) : refs_(1), size_(nVars) { - value_ = new ValueType[nVars]; - for (uint32 i = 0; i != nVars; ++i) { value_[i] = 0; } - } - State* share() { ++refs_; return this; } - void release() { if (--refs_ == 0) delete this; } - uint32 size() const { return size_; } - bool open(Literal p) const { return (value_[p.var()] & Model::estMask(p)) != 0; } - void setModel(ValueVec& m) { m.assign(value_, value_ + size_); } - void push(Literal p) { value_[p.var()] = Model::estMask(p)|trueValue(p);} - void pop(Literal p) { value_[p.var()] = 0; } - void fix(Literal p) { value_[p.var()] = trueValue(p); } - private: - ~State() { delete [] value_; } - typedef Clasp::Atomic_t::type ValueType; - typedef Clasp::Atomic_t::type SizeType; - typedef ValueType* ValueVec; - ValueVec value_; - uint32 size_; - SizeType refs_; - }; - explicit QueryFinder(const LitVec& c, uint32 nVars) : EnumerationConstraint(), open_(c), state_(new State(nVars)), query_(lit_false()) { - state_->push(query_); // start with true as initial query - } - explicit QueryFinder(const LitVec& c, State* st) : EnumerationConstraint(), open_(c), state_(st), query_(lit_false()) { - } - ~QueryFinder() { state_->release(); } - ConPtr clone() { return new QueryFinder(open_, state_->share()); } - bool doUpdate(Solver& s); - void doCommitModel(Enumerator&, Solver&); - void doCommitUnsat(Enumerator&, Solver&); - void initUpper(Solver& s); - void updateUpper(Solver& s, uint32 root); - void updateOpen(Solver& s); - bool selectOpen(Solver& s, Literal& q); - void reason(Solver& s, Literal p, LitVec& out) { - for (uint32 i = 1, end = s.level(p.var()); i <= end; ++i) { - Literal q = s.decision(i); - if (q != p) { out.push_back(q); } - } - } - bool popQuery(Solver& s) { - if (s.isFalse(query_) && query_ != lit_false()) { - uint32 diff = s.rootLevel() - s.level(query_.var()); - return s.popRootLevel(diff + 1); - } - return s.popRootLevel(0); - } - LitVec open_; - State* state_; - Literal query_; + class State { + public: + explicit State(uint32_t nVars) : value_(std::make_unique(nVars)), size_(nVars), refs_(1) {} + State* share() { + refs_.add(); + return this; + } + void release() { + if (refs_.release()) { + delete this; + } + } + [[nodiscard]] uint32_t size() const { return size_; } + [[nodiscard]] bool open(Literal p) const { + return Potassco::test_any(value_[p.var()].load(), Model::estMask(p)); + } + void setModel(ValueVec& m) { m.assign(value_.get(), value_.get() + size_); } + void push(Literal p) { value_[p.var()].store(Model::estMask(p) | trueValue(p)); } + void pop(Literal p) { value_[p.var()].store(value_free); } + void fix(Literal p) { value_[p.var()].store(trueValue(p)); } + + private: + using ValueType = mt::ThreadSafe; + using ValueVec = std::unique_ptr; + ValueVec value_; + uint32_t size_; + RefCount refs_; + }; + explicit QueryFinder(LitVec c, uint32_t nVars) : open(std::move(c)), state(new State(nVars)), query(lit_false) { + state->push(query); // start with true as initial query + } + explicit QueryFinder(LitVec c, State* st) : open(std::move(c)), state(st), query(lit_false) {} + ~QueryFinder() override { state->release(); } + ConPtr clone() override { return new QueryFinder(open, state->share()); } + bool doUpdate(Solver& s) override; + void doCommitModel(Enumerator&, Solver&) override; + void doCommitUnsat(Enumerator&, Solver&) override; + bool doExtractModel(Solver& s, ValueVec& out, bool sat) override; + void initUpper(const Solver& s); + void updateUpper(const Solver& s, uint32_t root); + void updateOpen(const Solver& s); + bool addQuery(Solver& s); + void reason(Solver& s, Literal p, LitVec& out) override { + for (uint32_t i = 1, end = s.level(p.var()); i <= end; ++i) { + if (Literal q = s.decision(i); q != p) { + out.push_back(q); + } + } + } + bool popQuery(Solver& s) { // NOLINT(readability-make-member-function-const) + if (s.isFalse(query) && query != lit_false) { + auto diff = s.rootLevel() - s.level(query.var()); + return s.popRootLevel(diff + 1); + } + return s.popRootLevel(0); + } + LitVec open; + State* state; + Literal query; + bool model = false; }; // Init overestimate to current model stored in s. -void CBConsequences::QueryFinder::initUpper(Solver& s) { - LitVec::iterator j = open_.begin(); - for (LitVec::iterator it = j, end = open_.end(); it != end; ++it) { - if (s.isTrue(*it)) { - if (s.level(it->var())) { state_->push(*j++ = *it); } - else { state_->fix(*it); } - } - } - open_.erase(j, open_.end()); +void CBConsequences::QueryFinder::initUpper(const Solver& s) { + auto j = open.begin(); + for (auto x : open) { + if (s.isTrue(x)) { + if (s.level(x.var())) { + state->push(*j++ = x); + } + else { + state->fix(x); + } + } + } + open.erase(j, open.end()); } // Reduce the overestimate by computing c = c \cap M, where M is the current model stored in s. -void CBConsequences::QueryFinder::updateUpper(Solver& s, uint32 root) { - LitVec::iterator j = open_.begin(); - for (LitVec::iterator it = j, end = open_.end(); it != end; ++it) { - if (state_->open(*it)) { - if (!s.isTrue(*it)) { state_->pop(*it); } - else if (s.level(it->var()) < root) { state_->fix(*it); } - else { *j++ = *it; } - } - } - open_.erase(j, open_.end()); +void CBConsequences::QueryFinder::updateUpper(const Solver& s, uint32_t root) { + auto j = open.begin(); + for (auto x : open) { + if (state->open(x)) { + if (not s.isTrue(x)) { + state->pop(x); + } + else if (s.level(x.var()) < root) { + state->fix(x); + } + else { + *j++ = x; + } + } + } + open.erase(j, open.end()); } // Removes no longer open literals from estimate. -void CBConsequences::QueryFinder::updateOpen(Solver& s) { - assert(s.decisionLevel() == s.rootLevel()); - for (LitVec::size_type i = 0, end = open_.size();;) { - for (; i != end && s.value(open_[i].var()) == value_free && state_->open(open_[i]); ++i) { ; } - if (i == end) { break; } - Literal q = open_[i]; - if (s.isTrue(q)) { state_->fix(q); } - else if (state_->open(q)) { state_->pop(q); } - open_[i] = open_.back(); - open_.pop_back(); - --end; - } +void CBConsequences::QueryFinder::updateOpen(const Solver& s) { + assert(s.decisionLevel() == s.rootLevel()); + for (auto i = 0u, end = size32(open);;) { + for (; i != end && s.value(open[i].var()) == value_free && state->open(open[i]); ++i) { ; } + if (i == end) { + break; + } + if (auto q = open[i]; s.isTrue(q)) { + state->fix(q); + } + else if (state->open(q)) { + state->pop(q); + } + open[i] = open.back(); + open.pop_back(); + --end; + } } -bool CBConsequences::QueryFinder::selectOpen(Solver& s, Literal& q) { - updateOpen(s); - if (open_.empty()) { - return false; - } - q = s.heuristic()->selectRange(s, &open_[0], &open_[0] + open_.size()); - return true; +bool CBConsequences::QueryFinder::addQuery(Solver& s) { + updateOpen(s); + if (not open.empty()) { + query = s.heuristic()->selectRange(s, open); + return s.pushRoot(~query); + } + return s.force(query = lit_false); } // solve(~query) produced a model - query is not a cautious consequence, update overestimate void CBConsequences::QueryFinder::doCommitModel(Enumerator&, Solver& s) { - assert(s.isFalse(query_)); - if (isSentinel(query_)) { - state_->fix(~query_); - initUpper(s); - } - else { - state_->pop(query_); - updateUpper(s, s.level(query_.var())); - } - state_->setModel(s.model); + assert(s.isFalse(query)); + if (isSentinel(query)) { + state->fix(~query); + initUpper(s); + } + else { + state->pop(query); + updateUpper(s, s.level(query.var())); + } + model = true; +} +bool CBConsequences::QueryFinder::doExtractModel(Solver&, ValueVec& out, bool) { + if (std::exchange(model, false)) { + state->setModel(out); + return true; + } + return false; } // solve(~query) failed - query is a cautious consequence void CBConsequences::QueryFinder::doCommitUnsat(Enumerator&, Solver& s) { - assert(s.isFalse(query_)); - bool commit = !isSentinel(query_) && !disjointPath() && s.hasConflict() && !s.hasStopConflict(); - if (popQuery(s) && commit && state_->open(query_)) { - assert(s.decisionLevel() == s.rootLevel()); - state_->fix(query_); - updateOpen(s); - state_->setModel(s.model); - } + assert(s.isFalse(query)); + bool commit = not isSentinel(query) && not disjointPath() && s.hasConflict() && not s.hasStopConflict(); + if (popQuery(s) && commit && state->open(query)) { + assert(s.decisionLevel() == s.rootLevel()); + state->fix(query); + updateOpen(s); + model = true; + } } bool CBConsequences::QueryFinder::doUpdate(Solver& s) { - bool newQ = !state_->open(query_); - if (newQ || s.value(query_.var()) == value_free) { // query was SAT/UNSAT or solved by other thread - if (!popQuery(s)) { return false; } - assert(s.decisionLevel() == s.rootLevel()); - return newQ && !selectOpen(s, query_) ? s.force(query_ = lit_false(), this) : s.pushRoot(~query_); - } - return true; + model = false; + if (not state->open(query) || s.value(query.var()) == value_free) { + // query was SAT/UNSAT or solved by other thread + return popQuery(s) && addQuery(s); + } + return true; } ///////////////////////////////////////////////////////////////////////////////////////// // CBConsequences ///////////////////////////////////////////////////////////////////////////////////////// -CBConsequences::CBConsequences(Type type, Algo algo) - : Enumerator() - , shared_(0) - , type_(type) - , algo_(algo) { - if (type_ != Cautious) { algo_ = Default; } +CBConsequences::CBConsequences(Type type, Algo algo) : shared_(nullptr), type_(type), algo_(algo) { + if (type_ != cautious) { + algo_ = def; + } } Enumerator* EnumOptions::createConsEnumerator(const EnumOptions& opts) { - return new CBConsequences(opts.enumMode == enum_brave ? CBConsequences::Brave : CBConsequences::Cautious, opts.enumMode != enum_query ? CBConsequences::Default : CBConsequences::Query); -} -CBConsequences::~CBConsequences() { - delete shared_; + return new CBConsequences(opts.enumMode == enum_brave ? CBConsequences::brave : CBConsequences::cautious, + opts.enumMode != enum_query ? CBConsequences::def : CBConsequences::query); } +CBConsequences::~CBConsequences() = default; bool CBConsequences::supportsSplitting(const SharedContext& problem) const { - return algo_ == Default && Enumerator::supportsSplitting(problem); -} -int CBConsequences::unsatType() const { - return algo_ == Default ? Enumerator::unsatType() : Enumerator::unsat_sync; + return algo_ == def && Enumerator::supportsSplitting(problem); } +int CBConsequences::unsatType() const { return algo_ == def ? Enumerator::unsatType() : Enumerator::unsat_sync; } EnumerationConstraint* CBConsequences::doInit(SharedContext& ctx, SharedMinimizeData* m, int) { - cons_.clear(); - const OutputTable& out = ctx.output; - if (out.projectMode() == ProjectMode_t::Output) { - if (out.numFacts()) { - addLit(ctx, lit_true()); - } - for (OutputTable::pred_iterator it = out.pred_begin(), end = out.pred_end(); it != end; ++it) { - addLit(ctx, it->cond); - } - for (OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) { - addLit(ctx, posLit(*it)); - } - } - else { - for (OutputTable::lit_iterator it = out.proj_begin(), end = out.proj_end(); it != end; ++it) { - addLit(ctx, *it); - } - } - if (m && m->optimize() && algo_ == Query) { - ctx.warn("Query algorithm does not support optimization!"); - algo_ = Default; - } - // init M to either cons or {} depending on whether we compute cautious or brave cons. - const uint32 fMask = (type_ == Cautious && algo_ != Query); - const uint32 vMask = (type_ == Cautious) ? 3u : 0u; - for (LitVec::iterator it = cons_.begin(), end = cons_.end(); it != end; ++it) { - it->rep() |= fMask; - ctx.unmark(it->var()); - if (!ctx.varInfo(it->var()).nant()) { - ctx.master()->setPref(it->var(), ValueSet::def_value, static_cast(trueValue(*it) ^ vMask)); - } - } - delete shared_; shared_ = 0; - setIgnoreSymmetric(true); - if (type_ != Cautious || algo_ != Query) { - shared_ = ctx.concurrency() > 1 ? new SharedConstraint() : 0; - return new CBFinder(shared_); - } - return new QueryFinder(cons_, ctx.numVars() + 1); + cons_.clear(); + const OutputTable& out = ctx.output; + if (out.projectMode() == ProjectMode::output) { + if (out.numFacts()) { + addLit(ctx, lit_true); + } + for (const auto& pred : out.pred_range()) { addLit(ctx, pred.cond); } + for (auto v : out.vars_range()) { addLit(ctx, posLit(v)); } + } + else { + for (auto lit : out.proj_range()) { addLit(ctx, lit); } + } + if (m && m->optimize() && algo_ == query) { + ctx.warn("Query algorithm does not support optimization!"); + algo_ = def; + } + // init M to either cons or {} depending on whether we compute cautious or brave cons. + const uint32_t fMask = (type_ == cautious && algo_ != query); + const uint32_t vMask = (type_ == cautious) ? 3u : 0u; + for (auto& lit : cons_) { + lit.rep() |= fMask; + ctx.unmark(lit.var()); + if (not ctx.varInfo(lit.var()).nant()) { + ctx.master()->setPref(lit.var(), ValueSet::def_value, static_cast(trueValue(lit) ^ vMask)); + } + } + shared_.reset(); + setIgnoreSymmetric(true); + if (type_ != cautious || algo_ != query) { + shared_ = ctx.concurrency() > 1 ? std::make_unique() : nullptr; + return new CBFinder(shared_.get()); + } + return new QueryFinder(cons_, ctx.numVars() + 1); } void CBConsequences::addLit(SharedContext& ctx, Literal p) { - if (!ctx.marked(p) && !ctx.eliminated(p.var())) { - cons_.push_back(p); - ctx.setFrozen(p.var(), true); - ctx.mark(p); - } + if (not ctx.marked(p) && not ctx.eliminated(p.var())) { + cons_.push_back(p); + ctx.setFrozen(p.var(), true); + ctx.mark(p); + } } -void CBConsequences::addCurrent(Solver& s, LitVec& con, ValueVec& m, uint32 root) { - con.assign(1, ~s.sharedContext()->stepLiteral()); - // reset state of variables - m.assign(m.size(), 0); - // let M be all lits p with p.watch() == true - for (LitVec::iterator it = cons_.begin(), end = cons_.end(); it != end; ++it) { - Literal& p = *it; - uint32 dl = s.level(p.var()); - uint32 ost = dl > root ? Model::estMask(p) : 0; - if (type_ == Brave) { - // brave: extend M with true literals and force a literal not in M to true - if (p.flagged() || s.isTrue(p)) { p.flag(); ost = 0; } - else if (dl) { con.push_back(p); } - } - else if (type_ == Cautious) { - // cautious: intersect M with true literals and force a literal in M to false - if (!p.flagged() || s.isFalse(p)) { p.unflag(); ost = 0; } - else if (dl) { con.push_back(~p); } - } - // set output state - if (p.flagged()) { ost |= trueValue(p); } - m[p.var()] |= ost; - } - if (shared_) { - shared_->release(SharedLiterals::newShareable(con, Constraint_t::Other, 1)); - } +void CBConsequences::addCurrent(const Solver& s, LitVec& con, ValueVec& m, uint32_t root) { + con.assign(1, ~s.sharedContext()->stepLiteral()); + // reset state of variables + m.assign(s.numVars() + 1, value_free); + // let M be all lits p with p.watch() == true + for (auto& p : cons_) { + auto dl = s.level(p.var()); + auto ost = dl > root ? Model::estMask(p) : 0u; + if (type_ == brave) { + // brave: extend M with true literals and force a literal not in M to true + if (p.flagged() || s.isTrue(p)) { + p.flag(); + ost = 0u; + } + else if (dl) { + con.push_back(p); + } + } + else if (type_ == cautious) { + // cautious: intersect M with true literals and force a literal in M to false + if (not p.flagged() || s.isFalse(p)) { + p.unflag(); + ost = 0u; + } + else if (dl) { + con.push_back(~p); + } + } + // set output state + if (p.flagged()) { + ost |= trueValue(p); + } + m[p.var()] |= ost; + } + if (shared_) { + shared_->release(SharedLiterals::newShareable(con, ConstraintType::other, 1)); + } } ///////////////////////////////////////////////////////////////////////////////////////// // CBConsequences::CBFinder implementation ///////////////////////////////////////////////////////////////////////////////////////// void CBConsequences::CBFinder::destroy(Solver* s, bool detach) { - Clasp::destroyDB(locked, s, detach); - if (last) { - last->release(); - } - EnumerationConstraint::destroy(s, detach); + Clasp::destroyDB(locked, s, detach); + if (last) { + last->release(); + } + EnumerationConstraint::destroy(s, detach); } void CBConsequences::CBFinder::pushLocked(Solver& s, ClauseHead* c) { - for (ClauseHead* h; !locked.empty() && !(h = static_cast(locked.back()))->locked(s);) { - h->destroy(&s, true); - locked.pop_back(); - } - locked.push_back(c); + for (ClauseHead* h; not locked.empty() && not(h = static_cast(locked.back()))->locked(s);) { + h->destroy(&s, true); + locked.pop_back(); + } + locked.push_back(c); } bool CBConsequences::CBFinder::doUpdate(Solver& s) { - ClauseCreator::Result ret; - uint32 flags = ClauseCreator::clause_explicit|ClauseCreator::clause_no_add; - if (!shared) { - ret = !current.empty() ? ClauseCreator::create(s, current, flags, ConstraintInfo(Constraint_t::Other)) : ClauseCreator::Result(); - } - else if (SharedLiterals* x = shared->fetch_if_neq(last)) { - if (last) { last->release(); } - last = x; - ret = ClauseCreator::integrate(s, x, flags | ClauseCreator::clause_no_release); - } - if (ret.local) { pushLocked(s, ret.local); } - current.clear(); - return ret.ok(); -} + ClauseCreator::Result ret; + auto flags = ClauseCreator::clause_explicit | ClauseCreator::clause_no_add; + lastM.clear(); + if (not shared) { + ret = not current.empty() ? ClauseCreator::create(s, current, flags, ConstraintInfo(ConstraintType::other)) + : ClauseCreator::Result(); + } + else if (SharedLiterals* x = shared->fetch_if_neq(last)) { + if (last) { + last->release(); + } + last = x; + ret = ClauseCreator::integrate(s, x, flags | ClauseCreator::clause_no_release); + } + if (ret.local) { + pushLocked(s, ret.local); + } + current.clear(); + return ret.ok(); } +} // namespace Clasp diff --git a/src/clasp_app.cpp b/src/clasp_app.cpp index 3b4052b..d97cfc4 100644 --- a/src/clasp_app.cpp +++ b/src/clasp_app.cpp @@ -1,7 +1,7 @@ // -// Copyright (c) 2006-2017 Benjamin Kaufmann +// Copyright (c) 2006-present Benjamin Kaufmann // -// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/ +// This file is part of Clasp. See https://potassco.org/clasp/ // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to @@ -22,688 +22,810 @@ // IN THE SOFTWARE. // #include -#include + +#include #include #include -#include +#include +#include + #include -#include -#include -#include -#include -#include -#include -#ifdef _MSC_VER -#pragma warning (disable : 4996) -#endif -#if defined(__GLIBC__) || defined(__GNU_LIBRARY__) -#include -#if defined(_FPU_EXTENDED) && defined(_FPU_SINGLE) && defined(_FPU_DOUBLE) && defined(_FPU_GETCW) && defined(_FPU_SETCW) -#define CLASP_HAS_FPU_CONTROL -inline unsigned fpuReset(unsigned m) { _FPU_SETCW(m); return m; } -inline unsigned fpuInit() { unsigned r; _FPU_GETCW(r); fpuReset((r & ~_FPU_EXTENDED & ~_FPU_SINGLE) | _FPU_DOUBLE); return r; } -#endif -#elif defined (_MSC_VER) && !defined(_WIN64) -#include -#define CLASP_HAS_FPU_CONTROL -inline unsigned fpuReset(unsigned m) { _controlfp(m, _MCW_PC); return m; } -inline unsigned fpuInit() { unsigned r = _controlfp(0, 0); fpuReset(_PC_53); return r; } -#pragma fenv_access (on) +#include +#include + +POTASSCO_WARNING_BEGIN_RELAXED +#include +POTASSCO_WARNING_END_RELAXED + +POTASSCO_WARNING_IGNORE_MSVC(4996) + +#if __has_include() +#include #endif -#if !defined(CLASP_HAS_FPU_CONTROL) -inline unsigned fpuReset(unsigned) { return 0u; } -inline unsigned fpuInit() { return 0u; } + +#if __has_include() +#include #endif -inline bool setFpuMode() { return (sizeof(void*)*CHAR_BIT) < size_t(64u); } + +#include +#include +#include +#include + namespace Clasp { ///////////////////////////////////////////////////////////////////////////////////////// // Some helpers ///////////////////////////////////////////////////////////////////////////////////////// -static double shutdownTime_g; -static const std::string stdinStr = "stdin"; -static const std::string stdoutStr = "stdout"; -inline bool isStdIn(const std::string& in) { return in == "-" || in == stdinStr; } -inline bool isStdOut(const std::string& out) { return out == "-" || out == stdoutStr; } +#define WRITE_STDERR(TYPE, MSG, ...) \ + do { \ + char buffer[256]; \ + auto len = formatMessage(buffer, Potassco::Application::TYPE, (MSG) POTASSCO_OPTARGS(__VA_ARGS__)); \ + fwrite(buffer, sizeof(char), len, stderr); \ + fflush(stderr); \ + } while (0) +static double g_shutdownTime; +static const std::string stdin_str = "stdin"; +static const std::string stdout_str = "stdout"; +inline bool isStdIn(const std::string& in) { return in == "-" || in == stdin_str; } +inline bool isStdOut(const std::string& out) { return out == "-" || out == stdout_str; } ///////////////////////////////////////////////////////////////////////////////////////// // ClaspAppOptions ///////////////////////////////////////////////////////////////////////////////////////// namespace Cli { -ClaspAppOptions::ClaspAppOptions() : outf(0), compute(0), ifs(' '), hideAux(false), onlyPre(0), printPort(false) { - quiet[0] = quiet[1] = quiet[2] = static_cast(UCHAR_MAX); -} void ClaspAppOptions::initOptions(Potassco::ProgramOptions::OptionContext& root) { - using namespace Potassco::ProgramOptions; - OptionGroup basic("Basic Options"); - basic.addOptions() - ("print-portfolio,@1", flag(printPort), "Print default portfolio and exit") - ("quiet,q" , notify(this, &ClaspAppOptions::mappedOpts)->implicit("2,2,2")->arg(""), - "Configure printing of models, costs, and calls\n" - " %A: [,][,]\n" - " : print {0=all|1=last|2=no} models\n" - " : print {0=all|1=last|2=no} optimize values []\n" - " : print {0=all|1=last|2=no} call steps [2]") - ("pre", notify(this, &ClaspAppOptions::mappedOpts)->arg("")->implicit("aspif"), "Print simplified program and exit\n" - " %A: Set output format to {aspif|smodels} (implicit: %I)") - ("outf,@1", storeTo(outf)->arg(""), "Use {0=default|1=competition|2=JSON|3=no} output") - ("out-atomf,@2" , storeTo(outAtom), "Set atom format string (

?%%0?)")
-		("out-ifs,@2"   , notify(this, &ClaspAppOptions::mappedOpts), "Set internal field separator")
-		("out-hide-aux,@1" , flag(hideAux), "Hide auxiliary atoms in answers")
-		("lemma-in,@1"     , storeTo(lemmaIn)->arg(""), "Read additional lemmas from %A")
-		("lemma-out,@1"    , storeTo(lemmaLog)->arg(""), "Log learnt lemmas to %A")
-		("lemma-out-lbd,@2", storeTo(lemma.lbdMax)->arg(""), "Only log lemmas with lbd <= %A")
-		("lemma-out-max,@2", storeTo(lemma.logMax)->arg(""), "Stop logging after %A lemmas")
-		("lemma-out-dom,@2", notify(this, &ClaspAppOptions::mappedOpts), "Log lemmas over  variables")
-		("lemma-out-txt,@2", flag(lemma.logText), "Log lemmas as ground integrity constraints")
-		("hcc-out,@2", storeTo(hccOut)->arg(""), "Write non-hcf programs to %A.#scc")
-		("file,f,@3" , storeTo(input)->composing(), "Input files")
-		("compute,@2", storeTo(compute)->arg(""), "Force given literal to true")
-	;
-	root.add(basic);
-}
-bool ClaspAppOptions::mappedOpts(ClaspAppOptions* this_, const std::string& name, const std::string& value) {
-	if (name == "quiet") {
-		const char* err = 0;
-		uint32      q[3]= {uint32(UCHAR_MAX),uint32(UCHAR_MAX),uint32(UCHAR_MAX)};
-		int      parsed = Potassco::xconvert(value.c_str(), q, &err);
-		for (int i = 0; i != parsed; ++i) { this_->quiet[i] = static_cast(q[i]); }
-		return parsed && *err == 0;
-	}
-	else if (name == "out-ifs") {
-		if (value.empty() || value.size() > 2) { return false;}
-		if (value.size() == 1) { this_->ifs = value[0]; return true; }
-		if (value[1] == 't')   { this_->ifs = '\t'; return true; }
-		if (value[1] == 'n')   { this_->ifs = '\n'; return true; }
-		if (value[1] == 'v')   { this_->ifs = '\v'; return true; }
-		if (value[1] == '\\')  { this_->ifs = '\\'; return true; }
-	}
-	else if (name == "lemma-out-dom") {
-		return (this_->lemma.domOut = (strcasecmp(value.c_str(), "output") == 0)) == true || strcasecmp(value.c_str(), "input") == 0;
-	}
-	else if (name == "pre") {
-		if      (strcasecmp(value.c_str(), "aspif")   == 0) { this_->onlyPre = (int8)AspParser::format_aspif; return true; }
-		else if (strcasecmp(value.c_str(), "smodels") == 0) { this_->onlyPre = (int8)AspParser::format_smodels; return true; }
-	}
-	return false;
+    using namespace Potassco::ProgramOptions;
+    OptionGroup basic("Basic Options");
+    auto        applyOpt = [this](const std::string& name, const std::string& value) { return apply(name, value); };
+    basic.addOptions()                                                                                        //
+        ("print-portfolio,@1", flag(printPort), "Print default portfolio and exit")                           //
+        ("quiet,q", parse(applyOpt)->implicit("2,2,2")->arg(""),                                      //
+         "Configure printing of models, costs, and calls\n"                                                   //
+         "      %A: [,][,]\n"                                                                //
+         "         : print {0=all|1=last|2=no} models\n"                                                 //
+         "        : print {0=all|1=last|2=no} optimize values []\n"                                //
+         "        : print {0=all|1=last|2=no} call steps      [2]")                                     //
+        ("pre", parse(applyOpt)->arg("")->implicit("aspif"),                                             //
+         "Print simplified program and exit\n"                                                                //
+         "      %A: Set output format to {aspif|smodels} (implicit: %I)")                                     //
+        ("outf,@1", storeTo(outf)->arg(""), "Use {0=default|1=competition|2=JSON|3=no} output")            //
+        ("out-atomf,@2", storeTo(outAtom), "Set atom format string (
?%%0?)")                       //
+        ("out-ifs,@2", parse(applyOpt),                                                                       //
+         "Set internal field separator")("out-hide-aux,@1", flag(hideAux), "Hide auxiliary atoms in answers") //
+        ("lemma-in,@1", storeTo(lemmaIn)->arg(""),                                                      //
+         "Read additional lemmas from %A")                                                                    //
+        ("lemma-out,@1", storeTo(lemmaLog)->arg(""), "Log learnt lemmas to %A")                         //
+        ("lemma-out-lbd,@2", storeTo(lemma.lbdMax)->arg(""), "Only log lemmas with lbd <= %A")             //
+        ("lemma-out-max,@2", storeTo(lemma.logMax)->arg(""), "Stop logging after %A lemmas")               //
+        ("lemma-out-dom,@2", parse(applyOpt),                                                                 //
+         "Log lemmas over  variables")                                                    //
+        ("lemma-out-txt,@2", flag(lemma.logText), "Log lemmas as ground integrity constraints")               //
+        ("hcc-out,@2", storeTo(hccOut)->arg(""), "Write non-hcf programs to %A.#scc")                   //
+        ("file,f,@3", storeTo(input)->composing(), "Input files")                                             //
+        ("compute,@2", storeTo(compute)->arg(""), "Force given literal to true");                        //
+    root.add(basic);
+}
+bool ClaspAppOptions::apply(const std::string& name, const std::string& value) {
+    using Potassco::extract;
+    using Potassco::Parse::eqIgnoreCase;
+    if (name == "quiet") {
+        namespace Parse = Potassco::Parse;
+        std::string_view in(value);
+        uint32_t         q[3]    = {};
+        auto             parsed  = 0u;
+        auto             bracket = Parse::matchOpt(in, '[');
+        while (Parse::ok(extract(in, q[parsed])) && ++parsed < std::size(q) && Parse::matchOpt(in, ',')) {}
+        if (parsed && (not bracket || Parse::matchOpt(in, ']')) && in.empty()) {
+            for (auto i : irange(parsed)) { quiet[i] = static_cast(q[i]); }
+            return true;
+        }
+    }
+    else if (name == "lemma-out-dom") {
+        return (lemma.domOut = eqIgnoreCase(value.c_str(), "output")) == true || eqIgnoreCase(value.c_str(), "input");
+    }
+    else if (name == "pre") {
+        if (eqIgnoreCase(value.c_str(), "aspif")) {
+            onlyPre = static_cast(AspParser::format_aspif);
+            return true;
+        }
+        if (eqIgnoreCase(value.c_str(), "smodels")) {
+            onlyPre = static_cast(AspParser::format_smodels);
+            return true;
+        }
+    }
+    else if (name == "out-ifs" && not value.empty() && value.size() == 1 + (value[0] == '\\')) {
+        if (auto x = value.size() == 1 ? value[0] : [](char c) {
+            switch (c) {
+                case 't' : return '\t';
+                case 'n' : return '\n';
+                case 'v' : return '\v';
+                case '\\': return '\\';
+                default  : return static_cast(0);
+            }
+        }(value[1]); x != 0) {
+            ifs = x;
+            return true;
+        }
+    }
+    return false;
 }
 bool ClaspAppOptions::validateOptions(const Potassco::ProgramOptions::ParsedOptions&) {
-	if (quiet[1] == static_cast(UCHAR_MAX)) { quiet[1] = quiet[0]; }
-	return true;
+    if (quiet[1] == static_cast(UCHAR_MAX)) {
+        quiet[1] = quiet[0];
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspAppBase
 /////////////////////////////////////////////////////////////////////////////////////////
-ClaspAppBase::ClaspAppBase() { }
-ClaspAppBase::~ClaspAppBase(){ }
+struct ClaspAppBase::LemmaReader {
+    using Program = Potassco::AbstractProgram;
+    LemmaReader(const std::string& fn, std::unique_ptr p) : input(*p), prg(std::move(p)) {
+        if (not isStdIn(fn)) {
+            file.open(fn.c_str());
+        }
+        std::istream& str = file.is_open() ? file : std::cin;
+        POTASSCO_CHECK(input.accept(str), std::errc::operation_not_supported, "'lemma-in': invalid input file!");
+    }
+    void parse() { input.parse(); }
+
+    Potassco::AspifInput     input;
+    std::unique_ptr prg;
+    std::ifstream            file;
+};
+
+ClaspAppBase::ClaspAppBase()  = default;
+ClaspAppBase::~ClaspAppBase() = default;
 const int* ClaspAppBase::getSignals() const {
-	static const int signals[] = {
-		SIGINT, SIGTERM
-#if !defined (_WIN32)
-		, SIGUSR1, SIGUSR2, SIGQUIT, SIGHUP, SIGXCPU, SIGXFSZ
+    static const int signals[] = {
+        SIGINT,
+        SIGTERM
+#if !defined(_WIN32)
+        ,
+        SIGUSR1,
+        SIGUSR2,
+        SIGQUIT,
+        SIGHUP,
+        SIGXCPU,
+        SIGXFSZ
 #endif
-		, 0};
-		return signals;
+        ,
+        0,
+    };
+    return signals;
 }
-bool ClaspAppBase::parsePositional(const std::string& t, std::string& out) {
-	int num;
-	if (Potassco::string_cast(t, num)) { out = "number"; }
-	else                               { out = "file";   }
-	return true;
+const char* ClaspAppBase::getPositional(const std::string& value) const {
+    if (int num; Potassco::stringTo(value, num) == std::errc{}) {
+        return "number";
+    }
+    return "file";
 }
+
 void ClaspAppBase::initOptions(Potassco::ProgramOptions::OptionContext& root) {
-	claspConfig_.addOptions(root);
-	claspAppOpts_.initOptions(root);
-	root.find("verbose")->get()->value()->defaultsTo("1");
-}
-
-void ClaspAppBase::validateOptions(const Potassco::ProgramOptions::OptionContext&, const Potassco::ProgramOptions::ParsedOptions& parsed, const Potassco::ProgramOptions::ParsedValues& values) {
-	if (claspAppOpts_.printPort) {
-		printTemplate();
-		exit(E_UNKNOWN);
-	}
-	setExitCode(E_NO_RUN);
-	ProblemType pt = getProblemType();
-	POTASSCO_REQUIRE(claspAppOpts_.validateOptions(parsed) && claspConfig_.finalize(parsed, pt, true), "command-line error!");
-	ClaspAppOptions& app = claspAppOpts_;
-	POTASSCO_REQUIRE(app.lemmaLog.empty() || isStdOut(app.lemmaLog) || (std::find(app.input.begin(), app.input.end(), app.lemmaLog) == app.input.end() && app.lemmaIn != app.lemmaLog),
-		"'lemma-out': cowardly refusing to overwrite input file!");
-	POTASSCO_REQUIRE(app.lemmaIn.empty() || isStdIn(app.lemmaIn) || std::ifstream(app.lemmaIn.c_str()).is_open(),
-		"'lemma-in': could not open file!");
-	for (std::size_t i = 1; i < app.input.size(); ++i) {
-		POTASSCO_EXPECT(isStdIn(app.input[i]) || std::ifstream(app.input[i].c_str()).is_open(),
-			"'%s': could not open input file!", app.input[i].c_str());
-	}
-	POTASSCO_REQUIRE(!app.onlyPre || pt == Problem_t::Asp, "Option '--pre' only supported for ASP!");
-	setExitCode(0);
-	storeCommandArgs(values);
+    claspConfig_.addOptions(root);
+    claspAppOpts_.initOptions(root);
+    root.find("verbose")->get()->value()->defaultsTo("1");
+}
+
+void ClaspAppBase::validateOptions(const Potassco::ProgramOptions::OptionContext&,
+                                   const Potassco::ProgramOptions::ParsedOptions& parsed,
+                                   const Potassco::ProgramOptions::ParsedValues&  values) {
+    if (claspAppOpts_.printPort) {
+        printTemplate();
+        exit(exit_unknown);
+    }
+    setExitCode(exit_no_run);
+    auto pt = getProblemType();
+    POTASSCO_CHECK(claspAppOpts_.validateOptions(parsed) && claspConfig_.finalize(parsed, pt, true),
+                   std::errc::invalid_argument, "command-line error!");
+    ClaspAppOptions& app = claspAppOpts_;
+    POTASSCO_CHECK(app.lemmaLog.empty() || isStdOut(app.lemmaLog) ||
+                       (not Clasp::contains(app.input, app.lemmaLog) && app.lemmaIn != app.lemmaLog),
+                   std::errc::file_exists, "'lemma-out': cowardly refusing to overwrite input file!");
+    POTASSCO_CHECK(app.lemmaIn.empty() || isStdIn(app.lemmaIn) || std::ifstream(app.lemmaIn.c_str()).is_open(),
+                   std::errc::no_such_file_or_directory, "'lemma-in': could not open file!");
+    for (std::size_t i = 1; i < app.input.size(); ++i) {
+        POTASSCO_CHECK(isStdIn(app.input[i]) || std::ifstream(app.input[i].c_str()).is_open(),
+                       std::errc::no_such_file_or_directory, "'%s': could not open input file!", app.input[i].c_str());
+    }
+    POTASSCO_CHECK(not app.onlyPre || pt == ProblemType::asp, std::errc::operation_not_supported,
+                   "Option '--pre' only supported for ASP!");
+    setExitCode(0);
+    storeCommandArgs(values);
 }
 void ClaspAppBase::setup() {
-	ProblemType pt = getProblemType();
-	clasp_         = new ClaspFacade();
-	if (setFpuMode()) { fpuMode_ = fpuInit(); }
-	if (!claspAppOpts_.onlyPre) {
-		out_ = createOutput(pt);
-		Event::Verbosity verb	= (Event::Verbosity)std::min(verbose(), (uint32)Event::verbosity_max);
-		if (out_.get() && out_->verbosity() < (uint32)verb) { verb = (Event::Verbosity)out_->verbosity(); }
-		if (!claspAppOpts_.lemmaLog.empty()) {
-			logger_ = new LemmaLogger(claspAppOpts_.lemmaLog.c_str(), claspAppOpts_.lemma);
-		}
-		EventHandler::setVerbosity(Event::subsystem_facade , verb);
-		EventHandler::setVerbosity(Event::subsystem_load   , verb);
-		EventHandler::setVerbosity(Event::subsystem_prepare, verb);
-		EventHandler::setVerbosity(Event::subsystem_solve  , verb);
-		clasp_->ctx.setEventHandler(this, logger_.get() == 0 ? SharedContext::report_default : SharedContext::report_conflict);
-	}
+    auto pt  = getProblemType();
+    clasp_   = std::make_unique();
+    fpuMode_ = Potassco::initFpuPrecision();
+    if (fpuMode_ == UINT32_MAX) {
+        WRITE_STDERR(message_warning, "could not set fpu mode: results can be non-deterministic!\n");
+    }
+    if (not claspAppOpts_.onlyPre) {
+        out_.reset(createOutput(pt));
+        auto verb = static_cast(std::min(getVerbose(), static_cast(Event::verbosity_max)));
+        if (out_.get() && out_->verbosity() < static_cast(verb)) {
+            verb = static_cast(out_->verbosity());
+        }
+        if (not claspAppOpts_.lemmaLog.empty()) {
+            logger_ = std::make_unique(claspAppOpts_.lemmaLog.c_str(), claspAppOpts_.lemma);
+        }
+        setVerbosity(Event::subsystem_facade, verb);
+        setVerbosity(Event::subsystem_load, verb);
+        setVerbosity(Event::subsystem_prepare, verb);
+        setVerbosity(Event::subsystem_solve, verb);
+        clasp_->ctx.setEventHandler(this, logger_.get() == nullptr ? SharedContext::report_default
+                                                                   : SharedContext::report_conflict);
+    }
 }
 
 void ClaspAppBase::shutdown() {
-	if (!clasp_.get()) { return; }
-	if (logger_.get()) { logger_->close(); }
-	lemmaIn_ = 0;
-	const ClaspFacade::Summary& result = clasp_->shutdown();
-	if (shutdownTime_g) {
-		shutdownTime_g += RealTime::getTime();
-		info(POTASSCO_FORMAT("Shutdown completed in %.3f seconds", shutdownTime_g));
-	}
-	if (out_.get())  { out_->shutdown(result); }
-	setExitCode(getExitCode() | exitCode(result));
-	if (setFpuMode()){ fpuReset(fpuMode_); }
+    if (not clasp_.get()) {
+        return;
+    }
+    if (logger_.get()) {
+        logger_->close();
+    }
+    lemmaIn_                           = nullptr;
+    const ClaspFacade::Summary& result = clasp_->shutdown();
+    if (g_shutdownTime != 0.0) {
+        g_shutdownTime += RealTime::getTime();
+        WRITE_STDERR(message_info, "Shutdown completed in %.3f seconds\n", g_shutdownTime);
+    }
+    if (out_.get()) {
+        out_->shutdown(result);
+    }
+    setExitCode(getExitCode() | exitCode(result));
+    if (auto mode = std::exchange(fpuMode_, 0u); mode != UINT32_MAX) {
+        Potassco::restoreFpuPrecision(mode);
+    }
 }
 
 void ClaspAppBase::run() {
-	if (out_.get()) {
-		Potassco::Span in = !claspAppOpts_.input.empty() ? Potassco::toSpan(claspAppOpts_.input) : Potassco::toSpan(&stdinStr, 1);
-		out_->run(getName(), getVersion(), Potassco::begin(in), Potassco::end(in));
-	}
-	try        { run(*clasp_); }
-	catch(...) {
-		try { blockSignals(); setExitCode(E_ERROR); throw; }
-		catch (const std::bad_alloc&  ) { setExitCode(E_MEMORY); error("std::bad_alloc"); }
-		catch (const std::exception& e) { error(e.what()); }
-		catch (...)                     { ; }
-	}
+    if (out_.get()) {
+        auto in = not claspAppOpts_.input.empty() ? std::span(claspAppOpts_.input) : std::span(&stdin_str, 1);
+        out_->run(getName(), getVersion(), in.data(), in.data() + in.size());
+    }
+    run(*clasp_);
+}
+
+static void writeSigMessage(std::string_view message) { // async signal safe
+    for (auto fd = fileno(stderr); not message.empty();) {
+        if (auto x = write(fd, message.data(), size32(message)); x >= 0) {
+            message.remove_prefix(static_cast(x));
+        }
+        else if (errno != EINTR) {
+            break;
+        }
+    }
 }
 
 bool ClaspAppBase::onSignal(int sig) {
-	if (!clasp_.get() || !clasp_->interrupt(sig)) {
-		info("INTERRUPTED by signal!");
-		setExitCode(E_INTERRUPT);
-		shutdown();
-		exit(getExitCode());
-	}
-	else {
-		// multiple threads are active - shutdown was initiated
-		shutdownTime_g = -RealTime::getTime();
-		info("Sending shutdown signal...");
-	}
-	return false; // ignore all future signals
+    char message[80];
+    if (not clasp_.get() || not clasp_->interrupt(sig)) {
+        auto len = formatMessage(message, message_info, "INTERRUPTED by signal!\n");
+        writeSigMessage({message, len});
+        setExitCode(exit_interrupt);
+        shutdown();
+        exit(getExitCode());
+    }
+    else {
+        // multiple threads are active - shutdown was initiated
+        g_shutdownTime = -RealTime::getTime();
+        auto len       = formatMessage(message, message_info, "Sending shutdown signal...\n");
+        writeSigMessage({message, len});
+    }
+    return false; // ignore all future signals
 }
 
 void ClaspAppBase::onEvent(const Event& ev) {
-	const LogEvent* log = event_cast(ev);
-	if (log && log->isWarning()) {
-		warn(log->msg);
-		return;
-	}
-	else if (const NewConflictEvent* cfl = event_cast(ev)) {
-		if (logger_.get()) { logger_->add(*cfl->solver, *cfl->learnt, cfl->info); }
-		return;
-	}
-	if (out_.get()) {
-		blockSignals();
-		out_->onEvent(ev);
-		unblockSignals(true);
-	}
+    if (const auto* log = event_cast(ev); log && log->isWarning()) {
+        WRITE_STDERR(message_warning, "%s\n", log->msg);
+    }
+    else if (const auto* cfl = event_cast(ev)) {
+        if (logger_.get()) {
+            logger_->add(*cfl->solver, cfl->learnt, cfl->info);
+        }
+    }
+    else if (out_.get()) {
+        blockSignals();
+        out_->onEvent(ev);
+        unblockSignals(true);
+    }
 }
 
 bool ClaspAppBase::onModel(const Solver& s, const Model& m) {
-	bool ret = true;
-	if (out_.get() && !out_->quiet()) {
-		blockSignals();
-		ret = out_->onModel(s, m);
-		unblockSignals(true);
-	}
-	return ret;
+    bool ret = true;
+    if (out_.get() && not out_->quiet()) {
+        blockSignals();
+        ret = out_->onModel(s, m);
+        unblockSignals(true);
+    }
+    return ret;
 }
 bool ClaspAppBase::onUnsat(const Solver& s, const Model& m) {
-	bool ret = true;
-	if (out_.get() && !out_->quiet()) {
-		blockSignals();
-		ret = out_->onUnsat(s, m);
-		unblockSignals(true);
-	}
-	return ret;
-}
-
-int ClaspAppBase::exitCode(const RunSummary& run) const {
-	int ec = 0;
-	if (run.sat())               { ec |= E_SAT;       }
-	if (run.complete())          { ec |= E_EXHAUST;   }
-	if (run.result.interrupted()){ ec |= E_INTERRUPT; }
-	return ec;
-}
-
-void ClaspAppBase::printTemplate() const {
-	printf(
-		"# clasp %s configuration file\n"
-		"# A configuration file contains a (possibly empty) list of configurations.\n"
-		"# Each of which must have the following format:\n"
-		"#   [()]: \n"
-		"# where\n"
-		"#  is an alphanumeric identifier optionally enclosed in brackets,\n"
-		"#  is the name of one of clasp's default configs and optional, and\n"
-		"#   is a command-line string of clasp options in long-format, e.g.\n"
-		"# ('--heuristic=vsids --restarts=L,100').\n"
-		"#\n"
-		"# SEE: clasp --help=3\n"
-		"#\n"
-		"# NOTE: The options '--configuration' and '--tester' must not occur in a\n"
-		"#       configuration file. All other global options are ignored unless\n"
-		"#       explicitly given in the very first configuration after the colon.\n"
-		"#       In particular, global options from base configurations are ignored.\n"
-		"#\n"
-		"# NOTE: Options given on the command-line are added to all configurations in a\n"
-		"#       configuration file. If an option is given both on the command-line and\n"
-		"#       in a configuration file, the one from the command-line is preferred.\n"
-		"#\n"
-		"# NOTE: If, after adding command-line options, a configuration\n"
-		"#       contains mutually exclusive options an error is raised.\n"
-		"#\n"
-		"# EXAMPLE: To create a new config based on clasp's inbuilt tweety configuration\n"
-		"#          with global options but a different heuristic one could write:\n"
-		"#\n"
-		"#            'Config1(tweety): --eq=3 --trans-ext=dynamic --heuristic=domain'\n"
-		"#\n"
-		"#          'Config1' is the purely descriptive name of the configuration and could\n"
-		"#          also be written as '[Config1]'. The following '(tweety)' indicates that\n"
-		"#          our configuration should be based on clasp's tweety configuration. Finally,\n"
-		"#          since global options from base configurations are ignored, we explicitly add\n"
-		"#          tweety's global options '--eq=3 --trans-ext=dynamic' after the colon.\n"
-		"#\n", CLASP_VERSION);
-	for (ConfigIter it = ClaspCliConfig::getConfig(Clasp::Cli::config_many); it.valid(); it.next()) {
-		printf("%s: %s\n", it.name(), it.args());
-	}
-}
-void ClaspAppBase::printVersion() {
-	Potassco::Application::printVersion();
-	printLibClaspVersion();
-	printLicense();
-}
-void ClaspAppBase::printLicense() const {
-	printf("License: The MIT License \n");
-}
-void ClaspAppBase::printLibClaspVersion() const {
-	printf("libclasp version %s (libpotassco version %s)\n", CLASP_VERSION, LIB_POTASSCO_VERSION);
-	printf("Configuration: WITH_THREADS=%d\n", CLASP_HAS_THREADS);
-	printf("%s\n", CLASP_LEGAL);
-	fflush(stdout);
-}
-
-void ClaspAppBase::printHelp(const Potassco::ProgramOptions::OptionContext& root) {
-	Potassco::Application::printHelp(root);
-	if (root.getActiveDescLevel() >= Potassco::ProgramOptions::desc_level_e1) {
-		printf("[asp] %s\n", ClaspCliConfig::getDefaults(Problem_t::Asp));
-		printf("[cnf] %s\n", ClaspCliConfig::getDefaults(Problem_t::Sat));
-		printf("[opb] %s\n", ClaspCliConfig::getDefaults(Problem_t::Pb));
-	}
-	if (root.getActiveDescLevel() >= Potassco::ProgramOptions::desc_level_e2) {
-		printf("\nDefault configurations:\n");
-		printDefaultConfigs();
-	}
-	else {
-		const char* ht3 = "\nType ";
-		if (root.getActiveDescLevel() == Potassco::ProgramOptions::desc_level_default) {
-			printf("\nType '%s --help=2' for more options and defaults\n", getName());
-			ht3 = "and ";
-		}
-		printf("%s '%s --help=3' for all options and configurations.\n", ht3, getName());
-	}
-	fflush(stdout);
-}
-void ClaspAppBase::printConfig(ConfigKey k) const {
-	uint32 minW = 2, maxW = 80;
-	ConfigIter it = ClaspCliConfig::getConfig(k);
-	printf("%s:\n%*c", it.name(), minW-1, ' ');
-	const char* opts = it.args();
-	for (std::size_t size = std::strlen(opts), n = maxW - minW; n < size;) {
-		while (n && opts[n] != ' ') { --n; }
-		if (!n) { break; }
-		printf("%.*s\n%*c", static_cast(n), opts, static_cast(minW - 1), ' ');
-		size -= n + 1;
-		opts += n + 1;
-		n = (maxW - minW);
-	}
-	printf("%s\n", opts);
-}
-void ClaspAppBase::printDefaultConfigs() const {
-	for (int i = Clasp::Cli::config_default+1; i != Clasp::Cli::config_default_max_value; ++i) {
-		printConfig(static_cast(i));
-	}
+    bool ret = true;
+    if (out_.get() && not out_->quiet()) {
+        blockSignals();
+        ret = out_->onUnsat(s, m);
+        unblockSignals(true);
+    }
+    return ret;
+}
+
+int ClaspAppBase::exitCode(const RunSummary& run) {
+    int ec = 0;
+    if (run.sat()) {
+        ec |= exit_sat;
+    }
+    if (run.complete()) {
+        ec |= exit_exhaust;
+    }
+    if (run.result.interrupted()) {
+        ec |= exit_interrupt;
+    }
+    return ec;
+}
+
+void ClaspAppBase::printTemplate() {
+    printf("# clasp %s configuration file\n"
+           "# A configuration file contains a (possibly empty) list of configurations.\n"
+           "# Each of which must have the following format:\n"
+           "#   [()]: \n"
+           "# where\n"
+           "#  is an alphanumeric identifier optionally enclosed in brackets,\n"
+           "#  is the name of one of clasp's default configs and optional, and\n"
+           "#   is a command-line string of clasp options in long-format, e.g.\n"
+           "# ('--heuristic=vsids --restarts=L,100').\n"
+           "#\n"
+           "# SEE: clasp --help=3\n"
+           "#\n"
+           "# NOTE: The options '--configuration' and '--tester' must not occur in a\n"
+           "#       configuration file. All other global options are ignored unless\n"
+           "#       explicitly given in the very first configuration after the colon.\n"
+           "#       In particular, global options from base configurations are ignored.\n"
+           "#\n"
+           "# NOTE: Options given on the command-line are added to all configurations in a\n"
+           "#       configuration file. If an option is given both on the command-line and\n"
+           "#       in a configuration file, the one from the command-line is preferred.\n"
+           "#\n"
+           "# NOTE: If, after adding command-line options, a configuration\n"
+           "#       contains mutually exclusive options an error is raised.\n"
+           "#\n"
+           "# EXAMPLE: To create a new config based on clasp's inbuilt tweety configuration\n"
+           "#          with global options but a different heuristic one could write:\n"
+           "#\n"
+           "#            'Config1(tweety): --eq=3 --trans-ext=dynamic --heuristic=domain'\n"
+           "#\n"
+           "#          'Config1' is the purely descriptive name of the configuration and could\n"
+           "#          also be written as '[Config1]'. The following '(tweety)' indicates that\n"
+           "#          our configuration should be based on clasp's tweety configuration. Finally,\n"
+           "#          since global options from base configurations are ignored, we explicitly add\n"
+           "#          tweety's global options '--eq=3 --trans-ext=dynamic' after the colon.\n"
+           "#\n",
+           CLASP_VERSION);
+    for (auto it = ClaspCliConfig::getConfig(Clasp::Cli::config_many); it.valid(); it.next()) {
+        printf("%s: %s\n", it.name(), it.args());
+    }
+}
+
+void ClaspAppBase::onVersion(const std::string& version) {
+    printf("%s\n", version.c_str());
+    printLibClaspVersion();
+    printLicense();
+}
+void ClaspAppBase::printLicense() { printf("License: The MIT License \n"); }
+void ClaspAppBase::printLibClaspVersion() {
+    printf("libclasp version %s (libpotassco version %s)\n", CLASP_VERSION, LIB_POTASSCO_VERSION);
+    printf("Configuration: WITH_THREADS=%d\n", CLASP_HAS_THREADS);
+    printf("%s\n", CLASP_LEGAL);
+}
+
+void ClaspAppBase::onHelp(const std::string& help, Potassco::ProgramOptions::DescriptionLevel level) {
+    printf("%s\n", help.c_str());
+    if (level >= Potassco::ProgramOptions::desc_level_e1) {
+        printf("[asp] %s\n", ClaspCliConfig::getDefaults(ProblemType::asp));
+        printf("[cnf] %s\n", ClaspCliConfig::getDefaults(ProblemType::sat));
+        printf("[opb] %s\n", ClaspCliConfig::getDefaults(ProblemType::pb));
+    }
+    if (level >= Potassco::ProgramOptions::desc_level_e2) {
+        printf("\nDefault configurations:\n");
+        printDefaultConfigs();
+    }
+    else {
+        const char* ht3 = "\nType ";
+        if (level == Potassco::ProgramOptions::desc_level_default) {
+            printf("\nType '%s --help=2' for more options and defaults\n", getName());
+            ht3 = "and ";
+        }
+        printf("%s '%s --help=3' for all options and configurations.\n", ht3, getName());
+    }
+}
+void ClaspAppBase::flush() {
+    fflush(stdout);
+    fflush(stderr);
+}
+
+void ClaspAppBase::printConfig(ConfigKey k) {
+    uint32_t   minW = 2, maxW = 80;
+    ConfigIter it = ClaspCliConfig::getConfig(k);
+    printf("%s:\n%*c", it.name(), minW - 1, ' ');
+    const char* opts = it.args();
+    for (std::size_t size = std::strlen(opts), n = maxW - minW; n < size;) {
+        while (n && opts[n] != ' ') { --n; }
+        if (not n) {
+            break;
+        }
+        printf("%.*s\n%*c", static_cast(n), opts, static_cast(minW - 1), ' ');
+        size -= n + 1;
+        opts += n + 1;
+        n     = (maxW - minW);
+    }
+    printf("%s\n", opts);
+}
+void ClaspAppBase::printDefaultConfigs() {
+    for (int i = config_default + 1; i != config_default_max_value; ++i) { printConfig(static_cast(i)); }
 }
 void ClaspAppBase::writeNonHcfs(const PrgDepGraph& graph) const {
-	Potassco::StringBuilder buf;
-	for (PrgDepGraph::NonHcfIter it = graph.nonHcfBegin(), end = graph.nonHcfEnd(); it != end; ++it) {
-		buf.appendFormat(".%u", (*it)->id());
-		WriteCnf cnf(claspAppOpts_.hccOut + buf.c_str());
-		const SharedContext& ctx = (*it)->ctx();
-		cnf.writeHeader(ctx.numVars(), ctx.numConstraints());
-		cnf.write(ctx.numVars(), ctx.shortImplications());
-		Solver::DBRef db = ctx.master()->constraints();
-		for (uint32 i = 0; i != db.size(); ++i) {
-			if (ClauseHead* x = db[i]->clause()) { cnf.write(x); }
-		}
-		for (uint32 i = 0; i != ctx.master()->trail().size(); ++i) {
-			cnf.write(ctx.master()->trail()[i]);
-		}
-		cnf.close();
-		buf.clear();
-	}
+    for (auto* component : graph.nonHcfs()) {
+        WriteCnf             cnf(claspAppOpts_.hccOut + '.' + std::to_string(component->id()));
+        const SharedContext& ctx = component->ctx();
+        cnf.writeHeader(ctx.numVars(), ctx.numConstraints());
+        cnf.write(ctx.numVars(), ctx.shortImplications());
+        Solver::DBRef db = ctx.master()->constraints();
+        for (auto* c : db) {
+            if (ClauseHead* x = c->clause()) {
+                cnf.write(x);
+            }
+        }
+        for (auto lit : ctx.master()->trailView()) { cnf.write(lit); }
+        cnf.close();
+    }
 }
 std::istream& ClaspAppBase::getStream(bool reopen) const {
-	static std::ifstream file;
-	static bool isOpen = false;
-	if (!isOpen || reopen) {
-		file.close();
-		isOpen = true;
-		if (!claspAppOpts_.input.empty() && !isStdIn(claspAppOpts_.input[0])) {
-			file.open(claspAppOpts_.input[0].c_str());
-			POTASSCO_EXPECT(file.is_open(), "Can not read from '%s'!", claspAppOpts_.input[0].c_str());
-		}
-	}
-	return file.is_open() ? file : std::cin;
+    static std::ifstream file;
+    static bool          isOpen = false;
+    if (not isOpen || reopen) {
+        file.close();
+        isOpen = true;
+        if (not claspAppOpts_.input.empty() && not isStdIn(claspAppOpts_.input[0])) {
+            file.open(claspAppOpts_.input[0].c_str());
+            POTASSCO_CHECK(file.is_open(), std::errc::no_such_file_or_directory, "Can not read from '%s'!",
+                           claspAppOpts_.input[0].c_str());
+        }
+    }
+    return file.is_open() ? file : std::cin;
 }
 
 // Creates output object suitable for given input format
 Output* ClaspAppBase::createOutput(ProblemType f) {
-	SingleOwnerPtr out;
-	if (claspAppOpts_.outf == ClaspAppOptions::out_none) {
-		return 0;
-	}
-	if (claspAppOpts_.outf != ClaspAppOptions::out_json || claspAppOpts_.onlyPre) {
-		TextOptions options;
-		options.format = TextOutput::format_asp;
-		if      (f == Problem_t::Sat){ options.format = TextOutput::format_sat09; }
-		else if (f == Problem_t::Pb) { options.format = TextOutput::format_pb09;  }
-		else if (f == Problem_t::Asp && claspAppOpts_.outf == ClaspAppOptions::out_comp) {
-			options.format = TextOutput::format_aspcomp;
-		}
-		options.verbosity = verbose();
-		options.catAtom = claspAppOpts_.outAtom.c_str();
-		options.ifs = claspAppOpts_.ifs;
-		out.reset(createTextOutput(options));
-		TextOutput* textOut = dynamic_cast(out.get());
-		if (claspConfig_.parse.isEnabled(ParserOptions::parse_maxsat) && f == Problem_t::Sat && textOut) {
-			textOut->result[TextOutput::res_sat] = "UNKNOWN";
-		}
-	}
-	else {
-		out.reset(createJsonOutput(verbose()));
-	}
-
-	if (out.get()) {
-		if (claspAppOpts_.quiet[0] != static_cast(UCHAR_MAX)) {
-			out->setModelQuiet((Output::PrintLevel)std::min(uint8(Output::print_no), claspAppOpts_.quiet[0]));
-		}
-		if (claspAppOpts_.quiet[1] != static_cast(UCHAR_MAX)) {
-			out->setOptQuiet((Output::PrintLevel)std::min(uint8(Output::print_no), claspAppOpts_.quiet[1]));
-		}
-		if (claspAppOpts_.quiet[2] != static_cast(UCHAR_MAX)) {
-			out->setCallQuiet((Output::PrintLevel)std::min(uint8(Output::print_no), claspAppOpts_.quiet[2]));
-		}
-	}
-	if (claspAppOpts_.hideAux && clasp_.get()) {
-		clasp_->ctx.output.setFilter('_');
-	}
-	return out.release();
+    std::unique_ptr out;
+    if (claspAppOpts_.outf == ClaspAppOptions::out_none) {
+        return nullptr;
+    }
+    if (claspAppOpts_.outf != ClaspAppOptions::out_json || claspAppOpts_.onlyPre) {
+        out.reset(createTextOutput({.format =
+                                        [](ProblemType t, bool comp) {
+                                            switch (t) {
+                                                case ProblemType::sat: return TextOutput::format_sat09;
+                                                case ProblemType::pb : return TextOutput::format_pb09;
+                                                default:
+                                                    return not comp ? TextOutput::format_asp
+                                                                    : TextOutput::format_aspcomp;
+                                            }
+                                        }(f, claspAppOpts_.outf == ClaspAppOptions::out_comp),
+                                    .verbosity = getVerbose(),
+                                    .catAtom   = claspAppOpts_.outAtom.c_str(),
+                                    .ifs       = claspAppOpts_.ifs}));
+
+        if (auto* textOut = dynamic_cast(out.get());
+            textOut && claspConfig_.parse.isEnabled(ParserOptions::parse_maxsat) && f == ProblemType::sat) {
+            textOut->result[TextOutput::res_sat] = "UNKNOWN";
+        }
+    }
+    else {
+        out.reset(createJsonOutput(getVerbose()));
+    }
+
+    if (out) {
+        auto quiet = static_cast(Output::print_no);
+        if (auto q0 = claspAppOpts_.quiet[0]; q0 != ClaspAppOptions::q_def) {
+            out->setModelQuiet(static_cast(std::min(quiet, q0)));
+        }
+        if (auto q1 = claspAppOpts_.quiet[1]; q1 != ClaspAppOptions::q_def) {
+            out->setOptQuiet(static_cast(std::min(quiet, q1)));
+        }
+        if (auto q2 = claspAppOpts_.quiet[2]; q2 != ClaspAppOptions::q_def) {
+            out->setCallQuiet(static_cast(std::min(quiet, q2)));
+        }
+    }
+    if (claspAppOpts_.hideAux && clasp_.get()) {
+        clasp_->ctx.output.setFilter('_');
+    }
+    return out.release();
 }
 
 Output* ClaspAppBase::createTextOutput(const TextOptions& options) {
-	return new TextOutput(options.verbosity, options.format, options.catAtom, options.ifs);
+    return new TextOutput(options.verbosity, options.format, options.catAtom, options.ifs);
 }
 
-Output* ClaspAppBase::createJsonOutput(unsigned verbosity) {
-	return new JsonOutput(verbosity);
-}
+Output* ClaspAppBase::createJsonOutput(unsigned verbosity) { return new JsonOutput(verbosity); }
 
-void ClaspAppBase::storeCommandArgs(const Potassco::ProgramOptions::ParsedValues&) {
-	/* We don't need the values */
-}
+void ClaspAppBase::storeCommandArgs(const Potassco::ProgramOptions::ParsedValues&) { /* We don't need the values */ }
 void ClaspAppBase::handleStartOptions(ClaspFacade& clasp) {
-	if (!clasp.incremental()) {
-		claspConfig_.releaseOptions();
-	}
-	if (claspAppOpts_.compute && clasp.program()->type() == Problem_t::Asp) {
-		Potassco::Lit_t lit = Potassco::neg(claspAppOpts_.compute);
-		static_cast(clasp.program())->addRule(Potassco::Head_t::Disjunctive, Potassco::toSpan(), Potassco::toSpan(&lit, 1));
-	}
-	if (!claspAppOpts_.lemmaIn.empty()) {
-		class LemmaIn : public Potassco::AspifInput {
-		public:
-			typedef Potassco::AbstractProgram PrgAdapter;
-			LemmaIn(const std::string& fn, PrgAdapter* prg) : Potassco::AspifInput(*prg), prg_(prg) {
-				if (!isStdIn(fn)) { file_.open(fn.c_str()); }
-				POTASSCO_REQUIRE(accept(getStream()), "'lemma-in': invalid input file!");
-			}
-			~LemmaIn() { delete prg_; }
-		private:
-			std::istream& getStream() { return file_.is_open() ? file_ : std::cin; }
-			PrgAdapter*   prg_;
-			std::ifstream file_;
-		};
-		SingleOwnerPtr prgTemp;
-		if (clasp.program()->type() == Problem_t::Asp) { prgTemp = new Asp::LogicProgramAdapter(*static_cast(clasp.program())); }
-		else { prgTemp = new BasicProgramAdapter(*clasp.program()); }
-		lemmaIn_ = new LemmaIn(claspAppOpts_.lemmaIn, prgTemp.release());
-	}
-}
-bool ClaspAppBase::handlePostGroundOptions(ProgramBuilder& prg) {
-	if (!claspAppOpts_.onlyPre) {
-		if (lemmaIn_.get()) { lemmaIn_->parse(); }
-		if (logger_.get())  { logger_->startStep(prg, clasp_->incremental()); }
-		return true;
-	}
-	prg.endProgram();
-	if (prg.type() == Problem_t::Asp) {
-		Asp::LogicProgram& asp = static_cast(prg);
-		AspParser::Format outf = static_cast(claspAppOpts_.onlyPre);
-		if (outf == AspParser::format_smodels && !asp.supportsSmodels()) {
-			std::ofstream null;
-			try { AspParser::write(asp, null, outf); }
-			catch (const std::logic_error& e) {
-				error("Option '--pre': unsupported input format!");
-				info(std::string(e.what()).append(" in 'smodels' format").c_str());
-				info("Try '--pre=aspif' to print in 'aspif' format");
-				setExitCode(E_ERROR);
-				return false;
-			}
-		}
-		AspParser::write(asp, std::cout, outf);
-	}
-	else {
-		error("Option '--pre': unsupported input format!");
-		setExitCode(E_ERROR);
-	}
-	return false;
+    if (not clasp.incremental()) {
+        claspConfig_.releaseOptions();
+    }
+    if (auto* p = clasp.asp(); p && claspAppOpts_.compute) {
+        auto lit = Potassco::neg(claspAppOpts_.compute);
+        p->addRule(Potassco::HeadType::disjunctive, {}, {&lit, 1});
+    }
+    if (not claspAppOpts_.lemmaIn.empty()) {
+        std::unique_ptr prgTemp;
+        if (auto* p = clasp.asp()) {
+            prgTemp = std::make_unique(*p);
+        }
+        else {
+            prgTemp = std::make_unique(*clasp.program());
+        }
+        lemmaIn_ = std::make_unique(claspAppOpts_.lemmaIn, std::move(prgTemp));
+    }
+}
+bool ClaspAppBase::handlePostGroundOptions(ClaspFacade& clasp) {
+    if (not claspAppOpts_.onlyPre) {
+        if (lemmaIn_) {
+            lemmaIn_->parse();
+        }
+        if (logger_.get() && clasp.program()) {
+            logger_->startStep(clasp.ctx, clasp.program()->endProgram() ? clasp.asp() : nullptr, clasp.incremental());
+        }
+        return clasp.ok();
+    }
+    if (auto* asp = clasp.asp()) {
+        asp->endProgram();
+        auto        outf = static_cast(claspAppOpts_.onlyPre);
+        const char* err;
+        if (outf == AspParser::format_smodels && not asp->supportsSmodels(&err)) {
+            fail(exit_error, "Option '--pre': unsupported input format!",
+                 std::string(err).append(" directive not supported!\nTry '--pre=aspif' to print in 'aspif' format"));
+        }
+        AspParser::write(*asp, std::cout, outf);
+    }
+    else {
+        fail(exit_error, "Option '--pre': unsupported input format!");
+    }
+    return false;
 }
 bool ClaspAppBase::handlePreSolveOptions(ClaspFacade& clasp) {
-	if (!claspAppOpts_.hccOut.empty() && clasp.ctx.sccGraph.get()){ writeNonHcfs(*clasp.ctx.sccGraph); }
-	return true;
+    if (not claspAppOpts_.hccOut.empty() && clasp.ctx.sccGraph.get()) {
+        writeNonHcfs(*clasp.ctx.sccGraph);
+    }
+    return true;
 }
 void ClaspAppBase::run(ClaspFacade& clasp) {
-	clasp.start(claspConfig_, getStream());
-	handleStartOptions(clasp);
-	while (clasp.read()) {
-		if (handlePostGroundOptions(*clasp.program())) {
-			clasp.prepare();
-			if (handlePreSolveOptions(clasp)) { clasp.solve(); }
-		}
-	}
+    clasp.start(claspConfig_, getStream());
+    handleStartOptions(clasp);
+    while (clasp.read()) {
+        if (handlePostGroundOptions(clasp)) {
+            clasp.prepare();
+            if (handlePreSolveOptions(clasp)) {
+                clasp.solve();
+            }
+        }
+    }
+}
+bool ClaspAppBase::onUnhandledException(const char* msg) {
+    setExitCode(std::strstr(msg, std::bad_alloc().what()) ? exit_memory : exit_error);
+    fprintf(stderr, "%s\n", msg);
+    return false;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspApp
 /////////////////////////////////////////////////////////////////////////////////////////
-ClaspApp::ClaspApp() {}
+ClaspApp::ClaspApp() = default;
 
-ProblemType ClaspApp::getProblemType() {
-	return ClaspFacade::detectProblemType(getStream());
-}
+ProblemType ClaspApp::getProblemType() { return ClaspFacade::detectProblemType(getStream()); }
 
-void ClaspApp::run(ClaspFacade& clasp) {
-	ClaspAppBase::run(clasp);
-}
+void ClaspApp::run(ClaspFacade& clasp) { ClaspAppBase::run(clasp); }
 
-void ClaspApp::printHelp(const Potassco::ProgramOptions::OptionContext& root) {
-	ClaspAppBase::printHelp(root);
-	printf("\nclasp is part of Potassco: %s\n", "http://potassco.org/clasp");
-	printf("Get help/report bugs via : %s\n"  , "http://potassco.org/support\n");
-	fflush(stdout);
+void ClaspApp::onHelp(const std::string& help, Potassco::ProgramOptions::DescriptionLevel level) {
+    ClaspAppBase::onHelp(help, level);
+    printf("\nclasp is part of Potassco: %s\n", "https://potassco.org/clasp");
+    printf("Get help/report bugs via : %s\n", "https://potassco.org/support\n");
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // LemmaLogger
 /////////////////////////////////////////////////////////////////////////////////////////
 LemmaLogger::LemmaLogger(const std::string& to, const Options& o)
-	: str_(isStdOut(to) ? stdout : fopen(to.c_str(), "w"))
-	, inputType_(Problem_t::Asp)
-	, options_(o)
-	, step_(0) {
-	POTASSCO_EXPECT(str_, "Could not open lemma log file '%s'!", to.c_str());
+    : str_(isStdOut(to) ? stdout : fopen(to.c_str(), "w"))
+    , asp_(false)
+    , options_(o)
+    , step_(0) {
+    POTASSCO_CHECK(str_, std::errc::no_such_file_or_directory, "Could not open lemma log file '%s'!", to.c_str());
 }
 LemmaLogger::~LemmaLogger() { close(); }
-void LemmaLogger::startStep(ProgramBuilder& prg, bool inc) {
-	logged_ = 0;
-	++step_;
-	if (!options_.logText) {
-		if (step_ == 1) { fprintf(str_, "asp 1 0 0%s\n", inc ? " incremental" : ""); }
-		else            { fprintf(str_, "0\n"); }
-	}
-	if ((inputType_ = static_cast(prg.type())) == Problem_t::Asp && prg.endProgram()) {
-		// create solver variable to potassco literal mapping
-		Asp::LogicProgram& asp = static_cast(prg);
-		for (Asp::Atom_t a = asp.startAtom(); a != asp.startAuxAtom(); ++a) {
-			Literal sLit = asp.getLiteral(a);
-			if (sLit.var() >= solver2asp_.size()) {
-				solver2asp_.resize(sLit.var() + 1, 0);
-			}
-			Potassco::Lit_t& p = solver2asp_[sLit.var()];
-			if (!p || (!sLit.sign() && p < 0)) {
-				p = !sLit.sign() ? Potassco::lit(a) : Potassco::neg(a);
-			}
-		}
-	}
-	solver2NameIdx_.clear();
-	if (options_.logText && prg.endProgram()) {
-		const SharedContext& ctx = *prg.ctx();
-		for (OutputTable::pred_iterator beg = ctx.output.pred_begin(), it = beg, end = ctx.output.pred_end(); it != end; ++it) {
-			Var v = it->cond.var();
-			if (ctx.varInfo(v).output()) {
-				if (solver2NameIdx_.size() <= v) { solver2NameIdx_.resize(v + 1, UINT32_MAX); }
-				solver2NameIdx_[v] = static_cast(it - beg);
-			}
-		}
-	}
-}
-void LemmaLogger::add(const Solver& s, const LitVec& cc, const ConstraintInfo& info) {
-	LitVec temp;
-	const LitVec* out = &cc;
-	uint32 lbd = info.lbd();
-	if (lbd > options_.lbdMax || logged_ >= options_.logMax) { return; }
-	if (info.aux() || options_.domOut || std::find_if(cc.begin(), cc.end(), std::not1(std::bind1st(std::mem_fun(&Solver::inputVar), &s))) != cc.end()) {
-		uint8 vf = options_.domOut ? VarInfo::Input|VarInfo::Output : VarInfo::Input;
-		if (!s.resolveToFlagged(cc, vf, temp, lbd) || lbd > options_.lbdMax) { return; }
-		out = &temp;
-	}
-	char buffer[1024];
-	Potassco::StringBuilder str(buffer, sizeof(buffer), Potassco::StringBuilder::Dynamic);
-	if (options_.logText) { formatText(*out, s.sharedContext()->output, lbd, str); }
-	else                  { formatAspif(*out, lbd, str); }
-	fwrite(str.c_str(), sizeof(char), str.size(), str_);
-	++logged_;
-}
-void LemmaLogger::formatAspif(const LitVec& cc, uint32, Potassco::StringBuilder& out) const {
-	out.appendFormat("1 0 0 0 %u", (uint32)cc.size());
-	for (LitVec::const_iterator it = cc.begin(), end = cc.end(); it != end; ++it) {
-		Literal sLit = ~*it; // clause -> constraint
-		Potassco::Lit_t a = toInt(sLit);
-		if (inputType_ == Problem_t::Asp) {
-			a = sLit.var() < solver2asp_.size() ? solver2asp_[sLit.var()] : 0;
-			if (!a) { return; }
-			if (sLit.sign() != (a < 0)) { a = -a; }
-		}
-		out.appendFormat(" %d", a);
-	}
-	out.append("\n");
-}
-void LemmaLogger::formatText(const LitVec& cc, const OutputTable& tab, uint32 lbd, Potassco::StringBuilder& out) const {
-	out.append(":-");
-	const char* sep = " ";
-	for (LitVec::const_iterator it = cc.begin(), end = cc.end(); it != end; ++it) {
-		Literal sLit = ~*it; // clause -> constraint
-		uint32 idx = sLit.var() < solver2NameIdx_.size() ? solver2NameIdx_[sLit.var()] : UINT32_MAX;
-		if (idx != UINT32_MAX) {
-			const OutputTable::PredType& p = *(tab.pred_begin() + idx);
-			assert(sLit.var() == p.cond.var());
-			out.appendFormat("%s%s%s", sep, sLit.sign() != p.cond.sign() ? "not " : "", p.name.c_str());
-		}
-		else {
-			if (inputType_ == Problem_t::Asp) {
-				Potassco::Lit_t a = sLit.var() < solver2asp_.size() ? solver2asp_[sLit.var()] : 0;
-				if (!a) { return; }
-				if (sLit.sign() != (a < 0)) { a = -a; }
-				sLit = Literal(Potassco::atom(a), a < 0);
-			}
-			out.appendFormat("%s%s__atom(%u)", sep, sLit.sign() ? "not " : "", sLit.var());
-		}
-		sep = ", ";
-	}
-	out.appendFormat(".  %%lbd = %u\n", lbd);
+void LemmaLogger::startStep(const SharedContext& ctx, Asp::LogicProgram* asp, bool inc) {
+    logged_.store(0);
+    ++step_;
+    if (not options_.logText) {
+        if (step_ == 1) {
+            fprintf(str_, "asp 1 0 0%s\n", inc ? " incremental" : "");
+        }
+        else {
+            fprintf(str_, "0\n");
+        }
+    }
+    asp_ = asp != nullptr;
+    if (asp) {
+        // create solver variable to potassco literal mapping
+        for (auto a : irange(asp->startAtom(), asp->startAuxAtom())) {
+            Literal sLit = asp->getLiteral(a);
+            if (sLit.var() >= solver2Asp_.size()) {
+                solver2Asp_.resize(sLit.var() + 1, 0);
+            }
+            Potassco::Lit_t& p = solver2Asp_[sLit.var()];
+            if (not p || (not sLit.sign() && p < 0)) {
+                p = not sLit.sign() ? Potassco::lit(a) : Potassco::neg(a);
+            }
+        }
+    }
+    solver2NameIdx_.clear();
+    if (options_.logText) {
+        unsigned idx = 0;
+        for (const auto& pred : ctx.output.pred_range()) {
+            auto v = pred.cond.var();
+            if (ctx.varInfo(v).output()) {
+                if (solver2NameIdx_.size() <= v) {
+                    solver2NameIdx_.resize(v + 1, UINT32_MAX);
+                }
+                solver2NameIdx_[v] = idx;
+            }
+            ++idx;
+        }
+    }
+}
+void LemmaLogger::add(const Solver& s, LitView cc, const ConstraintInfo& info) {
+    LitVec temp;
+    auto   lbd = info.lbd();
+    if (lbd > options_.lbdMax || logged_ >= options_.logMax) {
+        return;
+    }
+    if (info.aux() || options_.domOut || not std::ranges::all_of(cc, [&s](Literal p) { return s.inputVar(p); })) {
+        uint8_t vf = options_.domOut ? VarInfo::flag_input | VarInfo::flag_output : VarInfo::flag_input;
+        if (not s.resolveToFlagged(cc, vf, temp, lbd) || lbd > options_.lbdMax) {
+            return;
+        }
+        cc = temp;
+    }
+    struct B {
+        B& append(const std::string_view& data) {
+            str.insert(str.end(), data.begin(), data.end());
+            return *this;
+        }
+        void push_back(char c) { str.push_back(c); }
+
+        amc::SmallVector str;
+    } buf;
+    bool log;
+    if (options_.logText) {
+        log = formatText(cc, s.sharedContext()->output, lbd, buf);
+    }
+    else {
+        log = formatAspif(cc, lbd, buf);
+    }
+    if (log) {
+        buf.str.push_back('\n');
+        fwrite(buf.str.data(), sizeof(char), buf.str.size(), str_);
+        logged_.add(1);
+    }
+}
+template 
+bool LemmaLogger::formatAspif(LitView cc, uint32_t, S& out) const {
+    using namespace std::literals;
+    out.append("1 0 0 0 "sv);
+    Potassco::toChars(out, cc.size());
+    for (auto lit : cc) {
+        Literal         sLit = ~lit; // clause -> constraint
+        Potassco::Lit_t a    = toInt(sLit);
+        if (asp_) {
+            a = sLit.var() < solver2Asp_.size() ? solver2Asp_[sLit.var()] : 0;
+            if (not a) {
+                return false;
+            }
+            if (sLit.sign() != (a < 0)) {
+                a = -a;
+            }
+        }
+        out.push_back(' ');
+        Potassco::toChars(out, a);
+    }
+    return true;
+}
+template 
+bool LemmaLogger::formatText(LitView cc, const OutputTable& tab, uint32_t lbd, S& out) const {
+    using namespace std::literals;
+    out.append(":-"sv);
+    const char* sep   = " ";
+    auto        preds = tab.pred_range();
+    for (auto lit : cc) {
+        Literal  sLit = ~lit; // clause -> constraint
+        uint32_t idx  = sLit.var() < solver2NameIdx_.size() ? solver2NameIdx_[sLit.var()] : UINT32_MAX;
+        if (idx != UINT32_MAX) {
+            const OutputTable::PredType& p = preds[idx];
+            assert(sLit.var() == p.cond.var());
+            out.append(sep).append(sLit.sign() != p.cond.sign() ? "not "sv : ""sv).append(p.name.view());
+        }
+        else {
+            if (asp_) {
+                Potassco::Lit_t a = sLit.var() < solver2Asp_.size() ? solver2Asp_[sLit.var()] : 0;
+                if (not a) {
+                    return false;
+                }
+                if (sLit.sign() != (a < 0)) {
+                    a = -a;
+                }
+                sLit = Literal(Potassco::atom(a), a < 0);
+            }
+            out.append(sep).append(sLit.sign() ? "not "sv : ""sv).append("__atom("sv);
+            Potassco::toChars(out, sLit.var());
+            out.push_back(')');
+        }
+        sep = ", ";
+    }
+    out.append(".  %lbd = "sv);
+    Potassco::toChars(out, lbd);
+    return true;
 }
 void LemmaLogger::close() {
-	if (!str_) { return; }
-	if (!options_.logText) { fprintf(str_, "0\n"); }
-	fflush(str_);
-	if (str_ != stdout) { fclose(str_); }
-	str_ = 0;
-	solver2asp_.clear();
+    if (not str_) {
+        return;
+    }
+    if (not options_.logText) {
+        fprintf(str_, "0\n");
+    }
+    fflush(str_);
+    if (str_ != stdout) {
+        fclose(str_);
+    }
+    str_ = nullptr;
+    solver2Asp_.clear();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // WriteCnf
 /////////////////////////////////////////////////////////////////////////////////////////
 WriteCnf::WriteCnf(const std::string& outFile) : str_(fopen(outFile.c_str(), "w")) {
-	POTASSCO_EXPECT(str_, "Could not open cnf file '%s'!", outFile.c_str());
+    POTASSCO_CHECK(str_, std::errc::no_such_file_or_directory, "Could not open cnf file '%s'!", outFile.c_str());
 }
 WriteCnf::~WriteCnf() { close(); }
-void WriteCnf::writeHeader(uint32 numVars, uint32 numCons) {
-	fprintf(str_, "p cnf %u %u\n", numVars, numCons);
-}
-void WriteCnf::write(ClauseHead* h) {
-	lits_.clear();
-	h->toLits(lits_);
-	for (LitVec::const_iterator it = lits_.begin(), end = lits_.end(); it != end; ++it) {
-		fprintf(str_, "%d ", toInt(*it));
-	}
-	fprintf(str_, "%d\n", 0);
-}
-void WriteCnf::write(Var maxVar, const ShortImplicationsGraph& g) {
-	for (Var v = 1; v <= maxVar; ++v) {
-		g.forEach(posLit(v), *this);
-		g.forEach(negLit(v), *this);
-	}
-}
-void WriteCnf::write(Literal u) {
-	fprintf(str_, "%d 0\n", toInt(u));
-}
+void WriteCnf::writeHeader(uint32_t numVars, uint32_t numCons) { fprintf(str_, "p cnf %u %u\n", numVars, numCons); }
+void WriteCnf::write(const ClauseHead* h) {
+    lits_.clear();
+    h->toLits(lits_);
+    for (auto lit : lits_) { fprintf(str_, "%d ", toInt(lit)); }
+    fprintf(str_, "%d\n", 0);
+}
+void WriteCnf::write(Var_t maxVar, const ShortImplicationsGraph& g) {
+    auto op = [this](Literal p, Literal q, Literal r = lit_false) {
+        return r == lit_false ? unary(p, q) : binary(p, q, r);
+    };
+    for (auto v : irange(1u, maxVar + 1)) {
+        g.forEach(posLit(v), op);
+        g.forEach(negLit(v), op);
+    }
+}
+void WriteCnf::write(Literal u) { fprintf(str_, "%d 0\n", toInt(u)); }
+
 bool WriteCnf::unary(Literal p, Literal x) const {
-	return p.rep() >= x.rep() || fprintf(str_, "%d %d 0\n", toInt(~p), toInt(x)) > 0;
+    return p.rep() >= x.rep() || fprintf(str_, "%d %d 0\n", toInt(~p), toInt(x)) > 0;
 }
 bool WriteCnf::binary(Literal p, Literal x, Literal y) const {
-	return p.rep() >= x.rep() || p.rep() >= y.rep() || fprintf(str_, "%d %d %d 0\n", toInt(~p), toInt(x), toInt(y)) > 0;
+    return p.rep() >= x.rep() || p.rep() >= y.rep() || fprintf(str_, "%d %d %d 0\n", toInt(~p), toInt(x), toInt(y)) > 0;
 }
 void WriteCnf::close() {
-	if (str_) {
-		fflush(str_);
-		fclose(str_);
-		str_ = 0;
-	}
+    if (str_) {
+        fflush(str_);
+        fclose(str_);
+        str_ = nullptr;
+    }
 }
 
-}} // end of namespace Clasp::Cli
-
+} // namespace Cli
+} // namespace Clasp
diff --git a/src/clasp_facade.cpp b/src/clasp_facade.cpp
index b66813a..230eefb 100644
--- a/src/clasp_facade.cpp
+++ b/src/clasp_facade.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,21 +22,17 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
+#include 
 #include 
-#include 
 #include 
-#include 
+#include 
 #include 
-#include 
-#include 
-#include 
-#include 
+
 #include 
-#include 
+#include 
 #if CLASP_HAS_THREADS
-#include 
 #include 
 #endif
 namespace Clasp {
@@ -44,1076 +40,1217 @@ namespace Clasp {
 // ClaspConfig
 /////////////////////////////////////////////////////////////////////////////////////////
 struct ClaspConfig::Impl {
-	struct ConfiguratorProxy {
-		enum { OnceBit = 62, AcquireBit = 61 };
-		ConfiguratorProxy(Configurator* c, Ownership_t::Type own, bool once) : cfg(reinterpret_cast(c)), set(0) {
-			if (once) { store_set_bit(cfg, OnceBit); }
-			if (own == Ownership_t::Acquire) { store_set_bit(cfg, AcquireBit); }
-			assert(ptr() == c);
-		}
-		bool applyConfig(Solver& s) {
-			POTASSCO_ASSERT(s.id() < 64, "invalid solver id!");
-			if (test_bit(set, s.id()))  { return true;  }
-			if (test_bit(cfg, OnceBit)) { store_set_bit(set, s.id()); }
-			return this->ptr()->applyConfig(s);
-		}
-		void prepare(SharedContext& ctx) {
-			if (ctx.concurrency() < 64) {
-				set &= (bit_mask(ctx.concurrency()) - 1);
-			}
-			ptr()->prepare(ctx);
-		}
-		void unfreeze(SharedContext& ctx) {
-			ptr()->unfreeze(ctx);
-		}
-		void destroy() { if (test_bit(cfg, AcquireBit)) { delete ptr(); } }
-		Configurator* ptr() const {
-			static const uint64 ptrMask = ~(bit_mask(OnceBit) | bit_mask(AcquireBit));
-			return reinterpret_cast(static_cast(cfg & ptrMask));
-		}
-		uint64 cfg;
-		uint64 set;
-	};
-	typedef PodVector::type PPVec;
-	Impl()  { acycSet = 0; }
-	~Impl() { reset(); }
-	void reset();
-	void prepare(SharedContext& ctx);
-	bool addPost(Solver& s, const SolverParams& opts);
-	void add(Configurator* c, Ownership_t::Type t, bool once) { pp.push_back(ConfiguratorProxy(c, t, once)); }
-	void unfreeze(SharedContext& ctx);
-	PPVec   pp;
-	uint64  acycSet;
+    struct ConfiguratorProxy {
+        static constexpr uint32_t once_bit = 0u;
+        ConfiguratorProxy(Configurator& c, bool once) : cfg(&c) {
+            POTASSCO_ASSERT(cfg.get() == &c && not cfg.any(), "invalid configurator pointer");
+            if (once) {
+                cfg.set();
+            }
+        }
+        bool applyConfig(Solver& s) {
+            POTASSCO_ASSERT(s.id() < 64, "invalid solver id!");
+            if (set.contains(s.id())) {
+                return true;
+            }
+            if (cfg.test()) {
+                set.add(s.id());
+            }
+            return cfg->applyConfig(s);
+        }
+        void prepare(SharedContext& ctx) {
+            set.removeMax(ctx.concurrency());
+            cfg->prepare(ctx);
+        }
+        // NOLINTBEGIN(readability-make-member-function-const)
+        void unfreeze(SharedContext& ctx) { cfg->unfreeze(ctx); }
+        // NOLINTEND(readability-make-member-function-const)
+        using Ptr = TaggedPtr;
+        Ptr       cfg;
+        SolverSet set;
+    };
+    using ProxyVec = PodVector_t;
+    Impl()         = default;
+    ~Impl() { reset(); }
+    void      reset();
+    void      prepare(SharedContext& ctx);
+    bool      addPost(Solver& s, const SolverParams& opts);
+    void      add(Configurator& c, bool once) { pp.push_back(ConfiguratorProxy(c, once)); }
+    void      unfreeze(SharedContext& ctx);
+    ProxyVec  pp;
+    SolverSet acycSet;
 #if CLASP_HAS_THREADS
-	Clasp::mt::mutex mutex;
+    mt::mutex mutex;
 #endif
 };
-void ClaspConfig::Impl::reset() {
-	for (; !pp.empty(); pp.pop_back()) { pp.back().destroy(); }
-}
+void ClaspConfig::Impl::reset() { pp.clear(); }
 
 void ClaspConfig::Impl::prepare(SharedContext& ctx) {
-	if (ctx.concurrency() < 64) {
-		acycSet &= (bit_mask(ctx.concurrency()) - 1);
-	}
-	for (PPVec::iterator it = pp.begin(), end = pp.end(); it != end; ++it) {
-		it->prepare(ctx);
-	}
+    acycSet.removeMax(ctx.concurrency());
+    for (auto& p : pp) { p.prepare(ctx); }
 }
 bool ClaspConfig::Impl::addPost(Solver& s, const SolverParams& opts) {
 #if CLASP_HAS_THREADS
-#define LOCKED() for (Clasp::mt::unique_lock lock(mutex); lock.owns_lock(); lock.unlock())
+#define LOCKED(...)                                                                                                    \
+    [&]() {                                                                                                            \
+        mt::unique_lock lock(mutex);                                                                                   \
+        return __VA_ARGS__;                                                                                            \
+    }()
 #else
-#define LOCKED()
+#define LOCKED(...) __VA_ARGS__
 #endif
-	POTASSCO_ASSERT(s.sharedContext() != 0, "Solver not attached!");
-	if (s.sharedContext()->sccGraph.get()) {
-		if (DefaultUnfoundedCheck* ufs = static_cast(s.getPost(PostPropagator::priority_reserved_ufs))) {
-			ufs->setReasonStrategy(static_cast(opts.loopRep));
-		}
-		else if (!s.addPost(new DefaultUnfoundedCheck(*s.sharedContext()->sccGraph, static_cast(opts.loopRep)))) {
-			return false;
-		}
-	}
-	if (s.sharedContext()->extGraph.get()) {
-		bool addAcyc = false;
-		// protect access to acycSet
-		LOCKED() { addAcyc = !test_bit(acycSet, s.id()) && store_set_bit(acycSet, s.id()); }
-		if (addAcyc && !s.addPost(new AcyclicityCheck(s.sharedContext()->extGraph.get()))) {
-			return false;
-		}
-	}
-	for (PPVec::iterator it = pp.begin(), end = pp.end(); it != end; ++it) {
-		// protect call to user code
-		LOCKED() { if (!it->applyConfig(s)) { return false; } }
-	}
-	return true;
+    POTASSCO_ASSERT(s.sharedContext() != nullptr, "Solver not attached!");
+    if (s.sharedContext()->sccGraph.get()) {
+        if (auto* ufs = static_cast(s.getPost(PostPropagator::priority_reserved_ufs))) {
+            ufs->setReasonStrategy(static_cast(opts.loopRep));
+        }
+        else if (not s.addPost(new DefaultUnfoundedCheck(
+                     *s.sharedContext()->sccGraph, static_cast(opts.loopRep)))) {
+            return false;
+        }
+    }
+    if (s.sharedContext()->extGraph.get()) {
+        // protect access to acycSet
+        bool addAcyc = LOCKED(acycSet.add(s.id()));
+        if (addAcyc && not s.addPost(new AcyclicityCheck(s.sharedContext()->extGraph.get()))) {
+            return false;
+        }
+    }
+    for (auto& p : pp) {
+        if (not LOCKED(p.applyConfig(s))) { // protect call to user code
+            return false;
+        }
+    }
+    return true;
 #undef LOCKED
 }
 void ClaspConfig::Impl::unfreeze(SharedContext& ctx) {
-	for (PPVec::iterator it = pp.begin(), end = pp.end(); it != end; ++it) {
-		it->unfreeze(ctx);
-	}
+    for (auto& p : pp) { p.unfreeze(ctx); }
 }
 
-ClaspConfig::ClaspConfig() : tester_(0), impl_(new Impl()) {}
-ClaspConfig::~ClaspConfig() {
-	delete impl_;
-	delete tester_;
-}
+ClaspConfig::ClaspConfig() : tester_(nullptr), impl_(std::make_unique()) {}
+ClaspConfig::~ClaspConfig() = default;
 
 void ClaspConfig::reset() {
-	if (tester_) { tester_->reset(); }
-	impl_->reset();
-	BasicSatConfig::reset();
-	solve = SolveOptions();
-	asp   = AspOptions();
+    if (tester_) {
+        tester_->reset();
+    }
+    impl_->reset();
+    BasicSatConfig::reset();
+    solve = SolveOptions();
+    asp   = AspOptions();
 }
 
 BasicSatConfig* ClaspConfig::addTesterConfig() {
-	if (!tester_) { tester_ = new BasicSatConfig(); }
-	return tester_;
+    if (not tester_) {
+        tester_ = std::make_unique();
+    }
+    return tester_.get();
 }
 
 void ClaspConfig::prepare(SharedContext& ctx) {
-	BasicSatConfig::prepare(ctx);
-	uint32 numS = solve.numSolver();
-	if (numS > solve.supportedSolvers()) {
-		ctx.warn("Too many solvers.");
-		numS = solve.supportedSolvers();
-	}
-	if (numS > solve.recommendedSolvers()) {
-		ctx.warn(POTASSCO_FORMAT("Oversubscription: #Threads=%u exceeds logical CPUs=%u.", numS, solve.recommendedSolvers()));
-	}
-	for (uint32 i = 0; i != numS; ++i) {
-		if (solver(i).heuId == Heuristic_t::Domain) {
-			parse.enableHeuristic();
-			break;
-		}
-	}
-	solve.setSolvers(numS);
-	if (std::abs(static_cast(solve.numModels)) != 1 || !solve.models()) {
-		ctx.setPreserveModels(true);
-	}
-	ctx.setConcurrency(solve.numSolver(), SharedContext::resize_resize);
-	impl_->prepare(ctx);
+    BasicSatConfig::prepare(ctx);
+    uint32_t numS = solve.numSolver();
+    if (numS > SolveOptions::supportedSolvers()) {
+        ctx.warn("Too many solvers.");
+        numS = SolveOptions::supportedSolvers();
+    }
+    if (numS > SolveOptions::recommendedSolvers()) {
+        ctx.warnFmt("Oversubscription: #Threads=%u exceeds logical CPUs=%u.", numS, SolveOptions::recommendedSolvers());
+    }
+    for (auto i : irange(numS)) {
+        if (solver(i).heuId == HeuristicType::domain) {
+            parse.enableHeuristic();
+            break;
+        }
+    }
+    solve.setSolvers(numS);
+    if (std::abs(static_cast(solve.numModels)) != 1 || not solve.models()) {
+        ctx.setPreserveModels(true);
+    }
+    ctx.setConcurrency(solve.numSolver(), SharedContext::resize_resize);
+    impl_->prepare(ctx);
 }
 
 Configuration* ClaspConfig::config(const char* n) {
-	return (n && std::strcmp(n, "tester") == 0) ? testerConfig() : BasicSatConfig::config(n);
-}
-
-void ClaspConfig::addConfigurator(Configurator* c, Ownership_t::Type t, bool once) {
-	impl_->add(c, t, once);
+    return (n && std::strcmp(n, "tester") == 0) ? testerConfig() : BasicSatConfig::config(n);
 }
 
-bool ClaspConfig::addPost(Solver& s) const {
-	return impl_->addPost(s, solver(s.id())) && BasicSatConfig::addPost(s);
-}
-
-void ClaspConfig::unfreeze(SharedContext& ctx) {
-	impl_->unfreeze(ctx);
-}
-
-ClaspConfig::Configurator::~Configurator() {}
+void ClaspConfig::addConfigurator(Configurator& c, bool once) { impl_->add(c, once); }
+bool ClaspConfig::addPost(Solver& s) const { return impl_->addPost(s, solver(s.id())) && BasicSatConfig::addPost(s); }
+void ClaspConfig::unfreeze(SharedContext& ctx) { impl_->unfreeze(ctx); }
+ClaspConfig::Configurator::~Configurator() = default;
 void ClaspConfig::Configurator::prepare(SharedContext&) {}
-void ClaspConfig::Configurator::unfreeze(SharedContext&){}
+void ClaspConfig::Configurator::unfreeze(SharedContext&) {}
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspFacade::SolveStrategy
 /////////////////////////////////////////////////////////////////////////////////////////
 struct ClaspFacade::SolveStrategy {
-public:
-	enum { SIGCANCEL = 9, SIGERROR = 128};
-	enum State { state_run = 1u, state_model = 2u, state_done = 4u };
-	enum Event { event_attach, event_model, event_resume, event_detach };
-	virtual ~SolveStrategy() {}
-	static SolveStrategy* create(SolveMode_t m, ClaspFacade& f, SolveAlgorithm& algo);
-	void start(EventHandler* h, const LitVec& a);
-	bool running()  const { return (state_ & uint32(state_done-1)) != 0u; }
-	bool error()    const { return signal_ == SIGERROR; }
-	bool ready()    const { return state_ != uint32(state_run); }
-	int  signal()   const { return static_cast(signal_); }
-	bool interrupt(int sig) {
-		bool stopped = running() && compare_and_swap(signal_, uint32(0), uint32(sig)) == 0u && algo_->interrupt();
-		if (sig == SIGCANCEL) { wait(-1.0); }
-		return stopped;
-	}
-	bool wait(double s) { return doWait(s); }
-	void resume() { doNotify(event_resume); }
-	bool setModel(const Solver& s, const Model& m) {
-		result_.flags |= SolveResult::SAT;
-		bool ok = !handler_ || handler_->onModel(s, m);
-		ok      = s.sharedContext()->report(s, m) && ok;
-		if ((mode_ & SolveMode_t::Yield) != 0) { doNotify(event_model); }
-		return ok && !signal();
-	}
-	Result result() {
-		wait(-1.0);
-		POTASSCO_EXPECT(!error(), error_.c_str());
-		return result_;
-	}
-	const Model* model() {
-		return state_ == state_model || (result().sat() && state_ == state_model)
-			? &algo_->model()
-			: 0;
-	}
-	const LitVec* unsatCore() {
-		return result().unsat()
-			? algo_->unsatCore()
-			: 0;
-	}
-	bool next() {
-		return running() && (state_ != state_model || (resume(), true)) && model() != 0;
-	}
-	void release() {
-		if      (--nrefs_ == 1) { interrupt(SIGCANCEL); }
-		else if (nrefs_   == 0) { delete this; }
-	}
-	SolveStrategy* share() { ++nrefs_; return this; }
+    static constexpr int sig_cancel = 9;
+    static constexpr int sig_error  = 128;
+    enum State : uint32_t { state_run = 1u, state_model = 2u, state_done = 4u };
+    enum Event { event_attach, event_model, event_resume, event_detach };
+    virtual ~SolveStrategy() = default;
+    static SolveStrategy* create(SolveMode m, ClaspFacade& f, SolveAlgorithm& algo);
+    void                  start(EventHandler* h, LitView a);
+    [[nodiscard]] bool    running() const noexcept { return (state_ & (state_done - 1u)) != 0u; }
+    [[nodiscard]] bool    error() const noexcept { return signal_ == sig_error; }
+    [[nodiscard]] bool    ready() const noexcept { return state_ != state_run; }
+    [[nodiscard]] int     signal() const noexcept { return signal_; }
+    bool                  interrupt(int sig) {
+        bool stopped = running() && signal_.set_if_unset(sig) && algo_->interrupt();
+        if (sig == sig_cancel) {
+            wait(-1.0);
+        }
+        return stopped;
+    }
+    bool wait(double s) { return doWait(s); }
+    void resume() { doNotify(event_resume); }
+    bool setModel(const Solver& s, const Model& m) {
+        result_.flags |= SolveResult::res_sat;
+        bool ok        = not handler_ || handler_->onModel(s, m);
+        ok             = s.sharedContext()->report(s, m) && ok;
+        if (Potassco::test(mode_, SolveMode::yield)) {
+            doNotify(event_model);
+        }
+        return ok && not signal();
+    }
+    Result result() {
+        wait(-1.0);
+        POTASSCO_CHECK(not error(), std::errc::operation_canceled, "%s", error_.c_str());
+        return result_;
+    }
+    const Model* model() {
+        return state_ == state_model || (result().sat() && state_ == state_model) ? &algo_->model() : nullptr;
+    }
+    LitView unsatCore() { return result().unsat() ? algo_->unsatCore() : LitView{}; }
+    bool    next() { return running() && (state_ != state_model || (resume(), true)) && model() != nullptr; }
+    void    release() {
+        if (auto n = nrefs_.release_fetch(); n == 1u) {
+            interrupt(sig_cancel);
+        }
+        else if (n == 0) {
+            delete this;
+        }
+    }
+    SolveStrategy* share() {
+        nrefs_.add();
+        return this;
+    }
+
 protected:
-	SolveStrategy(SolveMode_t m, ClaspFacade& f, SolveAlgorithm* algo);
-	ClaspFacade*    facade_;
-	SolveAlgorithm* algo_;
-	void startAlgo(SolveMode_t m);
-	void continueAlgo() {
-		bool detach = true;
-		try {
-			detach = (signal() && running()) || (state_ == state_run && !algo_->next());
-			if (detach) { detach = false; detachAlgo(algo_->more(), 0); }
-		}
-		catch (...) {
-			if (detach) { detachAlgo(algo_->more(), 1); }
-			else        { throw; }
-		}
-	}
-	void detachAlgo(bool more, int nException, int state = 0);
+    SolveStrategy(SolveMode m, ClaspFacade& f, SolveAlgorithm* algo);
+    ClaspFacade*    facade_;
+    SolveAlgorithm* algo_;
+    void            startAlgo(SolveMode m);
+    void            continueAlgo();
+
 private:
-	struct Async;
-	virtual void doStart() { startAlgo(mode_);  }
-	virtual bool doWait(double maxTime) {
-		POTASSCO_REQUIRE(maxTime < 0.0, "Timed wait not supported!");
-		if (mode_ == SolveMode_t::Yield) { continueAlgo(); }
-		return true;
-	}
-	virtual void doNotify(Event event) {
-		switch (event) {
-			case event_attach: state_ = state_run;   break;
-			case event_model : state_ = state_model; break;
-			case event_resume: compare_and_swap(state_, uint32(state_model), uint32(state_run)); break;
-			case event_detach: state_ = state_done; break;
-		};
-	}
-	typedef Clasp::Atomic_t::type SafeIntType;
-	std::string   error_;
-	EventHandler* handler_;
-	SafeIntType   nrefs_;   // Facade + #Handle objects
-	SafeIntType   state_;
-	SafeIntType   signal_;
-	Result        result_;
-	SolveMode_t   mode_;
-	uint32        aTop_;
+    void detachAlgo(bool more);
+    struct Detacher {
+        explicit Detacher(SolveStrategy* s) : self(s) {}
+        ~Detacher() noexcept(false) { run(); }
+        void run() {
+            if (auto x = std::exchange(self, nullptr); x) {
+                x->detachAlgo(more < 0 ? x->algo_->more() : more > 0);
+            }
+        }
+        SolveStrategy* self = nullptr;
+        int            more = -1;
+    };
+    struct Async;
+    virtual void doStart() { startAlgo(mode_); }
+    virtual bool doWait(double maxTime) {
+        POTASSCO_CHECK_PRE(maxTime < 0.0, "Timed wait not supported!");
+        if (mode_ == SolveMode::yield) {
+            continueAlgo();
+        }
+        return true;
+    }
+    virtual void doNotify(Event event) {
+        switch (event) {
+            case event_attach: state_.store(state_run); break;
+            case event_model : state_.store(state_model); break;
+            case event_resume: handleResume(); break;
+            case event_detach: state_.store(state_done); break;
+        }
+    }
+    bool handleResume() {
+        uint32_t cmp = state_model;
+        return state_.compare_exchange_strong(cmp, state_run);
+    }
+    using SafeIntType = mt::ThreadSafe;
+    std::string   error_;
+    EventHandler* handler_{nullptr};
+    SigAtomic     signal_;
+    RefCount      nrefs_{1}; // Facade + #Handle objects
+    SafeIntType   state_;
+    Result        result_{};
+    SolveMode     mode_{};
+    uint32_t      aTop_{};
 };
-ClaspFacade::SolveStrategy::SolveStrategy(SolveMode_t m, ClaspFacade& f, SolveAlgorithm* algo)
-	: facade_(&f)
-	, algo_(algo)
-	, handler_(0)
-	, mode_(m) {
-	nrefs_ = 1;
-	state_ = signal_ = 0;
-}
-void ClaspFacade::SolveStrategy::start(EventHandler* h, const LitVec& a) {
-	ClaspFacade& f = *facade_;
-	aTop_ = (uint32)f.assume_.size();
-	f.assume_.insert(f.assume_.end(), a.begin(), a.end());
-	if (!isSentinel(f.ctx.stepLiteral())) {
-		f.assume_.push_back(f.ctx.stepLiteral());
-	}
-	handler_ = h;
-	std::memset(&result_, 0, sizeof(SolveResult));
-	// We forward models to the SharedContext ourselves.
-	algo_->setReportModels(false);
-	doStart();
-	assert(running() || ready());
-}
-void ClaspFacade::SolveStrategy::startAlgo(SolveMode_t m) {
-	bool more = true, detach = true;
-	doNotify(event_attach);
-	try {
-		facade_->interrupt(0); // handle pending interrupts
-		if (!signal_ && !facade_->ctx.master()->hasConflict()) {
-			facade_->step_.solveTime = facade_->step_.unsatTime = RealTime::getTime();
-			if ((m & SolveMode_t::Yield) == 0) {
-				more = algo_->solve(facade_->ctx, facade_->assume_, facade_);
-			}
-			else {
-				algo_->start(facade_->ctx, facade_->assume_, facade_);
-				detach = false;
-			}
-		}
-		else {
-			facade_->ctx.report(Clasp::Event::subsystem_solve);
-			more = facade_->ctx.ok();
-		}
-		if (detach) { detach = false; detachAlgo(more, 0); }
-	}
-	catch (...) {
-		if (detach) { detachAlgo(more, 1); }
-		else        { throw; }
-	}
-}
-void ClaspFacade::SolveStrategy::detachAlgo(bool more, int nException, int state) {
-#define PROTECT(ec, X) if ((ec)) try { X; } catch (...) {} else X
-	try {
-		if (nException == 1) { throw; }
-		switch (state) {
-			case 0: ++state; PROTECT(nException, algo_->stop());  // FALLTHROUGH
-			case 1: ++state; PROTECT(nException, facade_->stopStep(signal_, !more));  // FALLTHROUGH
-			case 2: ++state; if (handler_) { PROTECT(nException, handler_->onEvent(StepReady(facade_->summary()))); }   // FALLTHROUGH
-			case 3: state = -1;
-				result_ = facade_->result();
-				facade_->assume_.resize(aTop_);
-				doNotify(event_detach);
-			default:	break;
-		}
-	}
-	catch (...) {
-		error_ = "Operation failed: ";
-		if (!signal_)    { signal_ = SIGERROR; }
-		if (state != -1) { detachAlgo(more, 2, state); }
-		if ((mode_ & SolveMode_t::Async) == 0) {
-			error_ += "exception thrown";
-			throw;
-		}
-		try { throw; }
-		catch (const std::exception& e) { error_ = e.what(); }
-		catch (...)                     { error_ = "unknown error"; }
-	}
+ClaspFacade::SolveStrategy::SolveStrategy(SolveMode m, ClaspFacade& f, SolveAlgorithm* algo)
+    : facade_(&f)
+    , algo_(algo)
+    , mode_(m) {}
+
+void ClaspFacade::SolveStrategy::start(EventHandler* h, LitView a) {
+    ClaspFacade& f = *facade_;
+    aTop_          = size32(f.assume_);
+    f.assume_.insert(f.assume_.end(), a.begin(), a.end());
+    if (not isSentinel(f.ctx.stepLiteral())) {
+        f.assume_.push_back(f.ctx.stepLiteral());
+    }
+    handler_ = h;
+    std::memset(&result_, 0, sizeof(SolveResult));
+    // We forward models to the SharedContext ourselves.
+    algo_->setReportModels(false);
+    doStart();
+    assert(running() || ready());
+}
+void ClaspFacade::SolveStrategy::startAlgo(SolveMode m) {
+    doNotify(event_attach);
+    Detacher detacher(this);
+    try {
+        facade_->interrupt(0); // handle pending interrupts
+        if (not signal_ && not facade_->ctx.master()->hasConflict()) {
+            auto* en = facade_->enumerator();
+            POTASSCO_CHECK_PRE(en, "enumerator expected!");
+            facade_->step_.solveTime = facade_->step_.unsatTime = RealTime::getTime();
+            if (not Potassco::test(m, SolveMode::yield)) {
+                detacher.more = algo_->solve(*en, facade_->ctx, facade_->assume_, facade_);
+            }
+            else {
+                algo_->start(*en, facade_->ctx, facade_->assume_, facade_);
+                detacher.self = nullptr;
+            }
+        }
+        else {
+            facade_->ctx.report(Clasp::Event::subsystem_solve);
+            detacher.more = facade_->ctx.ok();
+        }
+    }
+    catch (...) {
+        detacher.run();
+    }
+}
+void ClaspFacade::SolveStrategy::continueAlgo() {
+    Detacher detacher(this);
+    try {
+        if (auto detach = (signal() && running()) || (state_ == state_run && not algo_->next()); not detach) {
+            detacher.self = nullptr; // release
+        }
+    }
+    catch (...) {
+        detacher.run();
+    }
+}
+void ClaspFacade::SolveStrategy::detachAlgo(bool more) {
+    auto error = std::current_exception();
+    for (unsigned state = 0; state != UINT32_MAX;) {
+        try {
+            switch (state) {
+                case 0:
+                    ++state;
+                    algo_->stop();
+                    [[fallthrough]];
+                case 1:
+                    ++state;
+                    facade_->stopStep(signal_, not more);
+                    [[fallthrough]];
+                case 2:
+                    ++state;
+                    if (handler_) {
+                        handler_->onEvent(StepReady(facade_->summary()));
+                    }
+                    [[fallthrough]];
+                case 3:
+                    ++state;
+                    result_ = facade_->result();
+                    facade_->assume_.resize(aTop_);
+                    doNotify(event_detach);
+                    [[fallthrough]];
+                default: state = UINT32_MAX; break;
+            }
+        }
+        catch (...) {
+            if (not error) {
+                error = std::current_exception();
+            }
+        }
+    }
+    if (error) {
+        signal_.set_if_unset(sig_error);
+        if (not Potassco::test(mode_, SolveMode::async)) {
+            error_ = "Operation failed: exception thrown";
+            std::rethrow_exception(error);
+        }
+        try {
+            std::rethrow_exception(error);
+        }
+        catch (const std::exception& e) {
+            error_ = e.what();
+        }
+        catch (...) {
+            error_ = "unknown error";
+        }
+    }
 }
+
 #if CLASP_HAS_THREADS
-struct ClaspFacade::SolveStrategy::Async : public ClaspFacade::SolveStrategy {
-	enum { state_async = (state_done << 1), state_next = state_model | state_async, state_join = state_done | state_async };
-	Async(SolveMode_t m, ClaspFacade& f, SolveAlgorithm* algo) : SolveStrategy(m, f, algo) {}
-	virtual void doStart() {
-		algo_->enableInterrupts();
-		Clasp::mt::thread(std::mem_fun(&SolveStrategy::startAlgo), this, SolveMode_t::Async).swap(task_);
-		for (mt::unique_lock lock(mqMutex_); state_ == 0u;) {
-			mqCond_.wait(lock);
-		}
-	}
-	virtual bool doWait(double t) {
-		for (mt::unique_lock lock(mqMutex_);;) {
-			if (signal() && running()) { // propagate signal to async thread and force wait
-				mqCond_.notify_all();
-				mqCond_.wait(lock);
-			}
-			else if (ready()) { break; }
-			else if (t < 0.0) { mqCond_.wait(lock); }
-			else if (t > 0.0) { mqCond_.wait_for(lock, t); t = 0.0; }
-			else              { return false; }
-		}
-		assert(ready());
-		// acknowledge current model or join if first to see done
-		if (compare_and_swap(state_, uint32(state_next), uint32(state_model)) == state_done
-			&& compare_and_swap(state_, uint32(state_done), uint32(state_join)) == state_done) {
-			task_.join();
-		}
-		return true;
-	}
-	virtual void doNotify(Event event) {
-		mt::unique_lock lock(mqMutex_);
-		switch (event) {
-			case event_attach: state_ = state_run;  break;
-			case event_model : state_ = state_next; break;
-			case event_resume: if (state_ == state_model) { state_ = state_run; break; } else { return; }
-			case event_detach: state_ = state_done;  break;
-		};
-		lock.unlock(); // synchronize-with other threads but no need to notify under lock
-		mqCond_.notify_all();
-		if (event == event_model) {
-			for (lock.lock(); state_ != state_run && !signal();) {
-				mqCond_.wait(lock);
-			}
-		}
-	}
-	typedef Clasp::mt::condition_variable ConditionVar;
-	Clasp::mt::thread task_;   // async solving thread
-	Clasp::mt::mutex  mqMutex_;// protects mqCond
-	ConditionVar      mqCond_; // for iterating over models one by one
+struct ClaspFacade::SolveStrategy::Async : SolveStrategy {
+    enum {
+        state_async = (state_done << 1),
+        state_next  = state_model | state_async,
+        state_join  = state_done | state_async
+    };
+    Async(SolveMode m, ClaspFacade& f, SolveAlgorithm* algo) : SolveStrategy(m, f, algo) {}
+    void doStart() override {
+        algo_->enableInterrupts();
+        task = Clasp::mt::thread([this]() { startAlgo(SolveMode::async); });
+        for (mt::unique_lock lock(mqMutex); state_ == 0u;) { mqCond.wait(lock); }
+    }
+    bool doWait(double t) override {
+        for (mt::unique_lock lock(mqMutex);;) {
+            if (signal() && running()) { // propagate signal to async thread and force wait
+                mqCond.notify_all();
+                mqCond.wait(lock);
+            }
+            else if (ready()) {
+                break;
+            }
+            else if (t < 0.0) {
+                mqCond.wait(lock);
+            }
+            else if (t > 0.0) {
+                mqCond.wait_for(lock, mt::toMillis(t));
+                t = 0.0;
+            }
+            else {
+                return false;
+            }
+        }
+        assert(ready());
+        // acknowledge current model or join if first to see done
+        if (uint32_t prev = state_next; not state_.compare_exchange_strong(prev, state_model) && prev == state_done &&
+                                        state_.compare_exchange_strong(prev, state_join)) {
+            task.join();
+        }
+        return true;
+    }
+    void doNotify(Event event) override {
+        mt::unique_lock lock(mqMutex);
+        switch (event) {
+            case event_attach: state_.store(state_run); break;
+            case event_model : state_.store(state_next); break;
+            case event_resume:
+                if (handleResume()) {
+                    break;
+                }
+                return;
+            case event_detach: state_.store(state_done); break;
+        }
+        lock.unlock(); // synchronize-with other threads but no need to notify under lock
+        mqCond.notify_all();
+        if (event == event_model) {
+            for (lock.lock(); state_ != state_run && not signal();) { mqCond.wait(lock); }
+        }
+    }
+    using ConditionVar = Clasp::mt::condition_variable;
+    Clasp::mt::thread task;    // async solving thread
+    Clasp::mt::mutex  mqMutex; // protects mqCond
+    ConditionVar      mqCond;  // for iterating over models one by one
 };
 #endif
-ClaspFacade::SolveStrategy* ClaspFacade::SolveStrategy::create(SolveMode_t m, ClaspFacade& f, SolveAlgorithm& algo) {
-	if ((m & SolveMode_t::Async) == 0) { return new SolveStrategy(m, f, &algo); }
+ClaspFacade::SolveStrategy* ClaspFacade::SolveStrategy::create(SolveMode m, ClaspFacade& f, SolveAlgorithm& algo) {
+    if (not Potassco::test(m, SolveMode::async)) {
+        return new SolveStrategy(m, f, &algo);
+    }
 #if CLASP_HAS_THREADS
-	return new SolveStrategy::Async(m, f, &algo);
+    return new SolveStrategy::Async(m, f, &algo);
 #else
-	POTASSCO_REQUIRE(CLASP_HAS_THREADS, "Solve mode not supported!");
+    POTASSCO_CHECK_PRE(CLASP_HAS_THREADS, "Solve mode not supported!");
 #endif
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspFacade::SolveData
 /////////////////////////////////////////////////////////////////////////////////////////
 struct ClaspFacade::SolveData {
-	struct BoundArray {
-		enum Type { Lower, Costs };
-		BoundArray(SolveData* d, Type t) : data(d), type(t) {}
-		~BoundArray() { while (!refs.empty()) { delete refs.back(); refs.pop_back(); } }
-		struct LevelRef {
-			LevelRef(const BoundArray* a, uint32 l) : arr(a), at(l) {}
-			static double value(const LevelRef* ref) { return ref->arr->_at(ref->at); }
-			const BoundArray* arr;
-			uint32            at;
-		};
-		typedef typename PodVector::type ElemVec;
-		uint32 size() const { return data->numBounds(); }
-		StatisticObject at(uint32 i) const {
-			POTASSCO_REQUIRE(i < size(), "invalid key");
-			while (i >= refs.size()) { refs.push_back(new LevelRef(this, sizeVec(refs))); }
-			return StatisticObject::value(refs[i]);
-		}
-		double _at(uint32_t idx) const {
-			POTASSCO_REQUIRE(idx < size(), "expired key");
-			const wsum_t bound = data->_bound(type, idx);
-			return bound != SharedMinimizeData::maxBound()
-				? static_cast(bound)
-				: std::numeric_limits::infinity();
-		}
-		const SolveData* data;
-		mutable ElemVec  refs;
-		Type             type;
-	};
-	typedef SingleOwnerPtr AlgoPtr;
-	typedef SingleOwnerPtr     EnumPtr;
-	typedef Clasp::Atomic_t::type     SafeIntType;
-	typedef const SharedMinimizeData*      MinPtr;
+    struct BoundArray {
+        enum Type { lower, costs };
+        BoundArray(const SolveData* d, Type t) : data(d), type(t) {}
+        ~BoundArray() {
+            while (not refs.empty()) {
+                delete refs.back();
+                refs.pop_back();
+            }
+        }
+        struct LevelRef {
+            LevelRef(const BoundArray* a, uint32_t l) : arr(a), at(l) {}
+            static double     value(const LevelRef* ref) { return ref->arr->bound(ref->at); }
+            const BoundArray* arr;
+            uint32_t          at;
+        };
+        using ElemVec = PodVector_t;
+        uint32_t        size() const { return data->numBounds(); }
+        StatisticObject at(uint32_t i) const {
+            POTASSCO_CHECK_PRE(i < size(), "invalid key");
+            while (i >= refs.size()) { refs.push_back(new LevelRef(this, size32(refs))); }
+            return StatisticObject::value(refs[i]);
+        }
+        double bound(uint32_t idx) const {
+            POTASSCO_CHECK_PRE(idx < size(), "expired key");
+            const Wsum_t bound = data->bound(type, idx);
+            return bound != SharedMinimizeData::maxBound() ? static_cast(bound)
+                                                           : std::numeric_limits::infinity();
+        }
+        const SolveData* data;
+        mutable ElemVec  refs;
+        Type             type;
+    };
+    using AlgoPtr = std::unique_ptr;
+    using EnumPtr = std::unique_ptr;
+    using MinPtr  = const SharedMinimizeData*;
 
-	SolveData() : en(0), algo(0), active(0), costs(this, BoundArray::Costs), lower(this, BoundArray::Lower), keepPrg(false), prepared(false), solved(false), interruptible(false) { qSig = 0; }
-	~SolveData() { reset(); }
-	void init(SolveAlgorithm* algo, Enumerator* en);
-	void reset();
-	void prepareEnum(SharedContext& ctx, EnumMode mode, const EnumOptions& options);
-	bool interrupt(int sig) {
-		if (solving()) { return active->interrupt(sig); }
-		if (!qSig && sig != SolveStrategy::SIGCANCEL) { qSig = sig; }
-		return false;
-	}
-	bool onModel(const Solver& s, const Model& m) {
-		return !active || active->setModel(s, m);
-	}
-	bool         solving()   const { return active && active->running(); }
-	const Model* lastModel() const { return en.get() ? &en->lastModel() : 0; }
-	const LitVec*unsatCore() const { return active ? active->unsatCore() : 0; }
-	MinPtr       minimizer() const { return en.get() ? en->minimizer() : 0; }
-	Enumerator*  enumerator()const { return en.get(); }
-	int          modelType() const { return en.get() ? en->modelType() : 0; }
-	int          signal()    const { return solving() ? active->signal() : static_cast(qSig); }
-	uint32       numBounds() const { return minimizer() ? minimizer()->numRules() : 0; }
+    SolveData() = default;
+    ~SolveData() { reset(); }
+    void init(AlgoPtr a, EnumPtr e);
+    void reset();
+    void prepareEnum(SharedContext& actx, EnumMode mode, const EnumOptions& options);
+    bool interrupt(int sig) {
+        if (solving()) {
+            return active->interrupt(sig);
+        }
+        if (sig != SolveStrategy::sig_cancel) {
+            qSig.set_if_unset(sig);
+        }
+        return false;
+    }
+    bool         onModel(const Solver& s, const Model& m) const { return not active || active->setModel(s, m); }
+    bool         solving() const { return active && active->running(); }
+    const Model* lastModel() const { return en.get() ? &en->lastModel() : nullptr; }
+    LitView      unsatCore() const { return active ? active->unsatCore() : LitView{}; }
+    MinPtr       minimizer() const { return en.get() ? en->minimizer() : nullptr; }
+    Enumerator*  enumerator() const { return en.get(); }
+    int          modelType() const { return en.get() ? en->modelType() : 0; }
+    int          signal() const { return solving() ? active->signal() : static_cast(qSig); }
+    uint32_t     numBounds() const { return minimizer() ? minimizer()->numRules() : 0; }
 
-	wsum_t _bound(BoundArray::Type type, uint32 idx) const {
-		const Model* m = lastModel();
-		if (m && m->costs && (m->opt || type == BoundArray::Costs)) {
-			return m->costs->at(idx);
-		}
-		const wsum_t b = type == BoundArray::Costs ? minimizer()->sum(idx) : minimizer()->lower(idx);
-		return b + (b != SharedMinimizeData::maxBound() ? minimizer()->adjust(idx) : 0);
-	}
+    Wsum_t bound(BoundArray::Type type, uint32_t idx) const {
+        if (const Model* m = lastModel(); m && m->hasCosts() && (m->opt || type == BoundArray::costs)) {
+            POTASSCO_CHECK(idx < m->costs.size(), ERANGE);
+            return m->costs[idx];
+        }
+        const Wsum_t b = type == BoundArray::costs ? minimizer()->sum(idx) : minimizer()->lower(idx);
+        return b + (b != SharedMinimizeData::maxBound() ? minimizer()->adjust(idx) : 0);
+    }
 
-	EnumPtr        en;
-	AlgoPtr        algo;
-	SolveStrategy* active;
-	BoundArray     costs;
-	BoundArray     lower;
-	SafeIntType    qSig;
-	bool           keepPrg;
-	bool           prepared;
-	bool           solved;
-	bool           interruptible;
+    EnumPtr        en;
+    AlgoPtr        algo;
+    SolveStrategy* active = nullptr;
+    BoundArray     costs{this, BoundArray::costs};
+    BoundArray     lower{this, BoundArray::lower};
+    SigAtomic      qSig;
+    bool           keepPrg       = false;
+    bool           prepared      = false;
+    bool           solved        = false;
+    bool           interruptible = false;
 };
-void ClaspFacade::SolveData::init(SolveAlgorithm* a, Enumerator* e) {
-	en = e;
-	algo = a;
-	algo->setEnumerator(*en);
-	if (interruptible) {
-		this->algo->enableInterrupts();
-	}
+void ClaspFacade::SolveData::init(AlgoPtr a, EnumPtr e) {
+    en   = std::move(e);
+    algo = std::move(a);
+    if (interruptible) {
+        algo->enableInterrupts();
+    }
 }
 void ClaspFacade::SolveData::reset() {
-	if (active)     { active->interrupt(SolveStrategy::SIGCANCEL); active->release(); active = 0; }
-	if (algo.get()) { algo->resetSolve(); }
-	if (en.get())   { en->reset(); }
-	prepared = solved = false;
+    if (active) {
+        active->interrupt(SolveStrategy::sig_cancel);
+        active->release();
+        active = nullptr;
+    }
+    if (algo.get()) {
+        algo->resetSolve();
+    }
+    if (en.get()) {
+        en->reset();
+    }
+    prepared = solved = false;
 }
-void ClaspFacade::SolveData::prepareEnum(SharedContext& ctx, EnumMode mode, const EnumOptions& options) {
-	POTASSCO_REQUIRE(!active, "Solve operation still active");
-	if (ctx.ok() && !ctx.frozen() && !prepared) {
-		if (mode == enum_volatile && ctx.solveMode() == SharedContext::solve_multi) {
-			ctx.requestStepVar();
-		}
-		ctx.output.setProjectMode(options.proMode);
-		int64 numM = options.numModels;
-		int lim = en->init(ctx, options.optMode, (int)Range(-1, INT_MAX).clamp(numM));
-		if (lim == 0 || options.numModels < 0) {
-			numM = lim;
-		}
-		algo->setEnumLimit(numM ? static_cast(numM) : UINT64_MAX);
-		algo->setOptLimit(options.optStop);
-		prepared = true;
-	}
+
+void ClaspFacade::SolveData::prepareEnum(SharedContext& actx, EnumMode mode, const EnumOptions& options) {
+    POTASSCO_CHECK_PRE(not active, "Solve operation still active");
+    if (actx.ok() && not actx.frozen() && not prepared) {
+        if (mode == enum_volatile && actx.solveMode() == SharedContext::solve_multi) {
+            actx.requestStepVar();
+        }
+        actx.output.setProjectMode(options.proMode);
+        auto numM = options.numModels;
+        int  lim  = en->init(actx, options.optMode, static_cast(Clasp::clamp(numM, -1, INT_MAX)));
+        if (lim == 0 || numM < 0) {
+            numM = lim;
+        }
+        algo->setEnumLimit(numM ? static_cast(numM) : UINT64_MAX);
+        algo->setOptLimit(options.optStop);
+        prepared = true;
+    }
 }
 ClaspFacade::SolveHandle::SolveHandle(SolveStrategy* s) : strat_(s->share()) {}
 ClaspFacade::SolveHandle::~SolveHandle() { strat_->release(); }
 ClaspFacade::SolveHandle::SolveHandle(const SolveHandle& o) : strat_(o.strat_->share()) {}
-int  ClaspFacade::SolveHandle::interrupted()        const { return strat_->signal(); }
-bool ClaspFacade::SolveHandle::error()              const { return ready() && strat_->error(); }
-bool ClaspFacade::SolveHandle::ready()              const { return strat_->ready(); }
-bool ClaspFacade::SolveHandle::running()            const { return strat_->running(); }
-void ClaspFacade::SolveHandle::cancel()             const { strat_->interrupt(SolveStrategy::SIGCANCEL); }
-void ClaspFacade::SolveHandle::wait()               const { strat_->wait(-1.0); }
-bool ClaspFacade::SolveHandle::waitFor(double s)    const { return strat_->wait(s); }
-void ClaspFacade::SolveHandle::resume()             const { strat_->resume(); }
-SolveResult ClaspFacade::SolveHandle::get()         const { return strat_->result(); }
-const Model*  ClaspFacade::SolveHandle::model()     const { return strat_->model(); }
-const LitVec* ClaspFacade::SolveHandle::unsatCore() const { return strat_->unsatCore(); }
-bool ClaspFacade::SolveHandle::next()               const { return strat_->next(); }
+int  ClaspFacade::SolveHandle::interrupted() const { return strat_->signal(); }
+bool ClaspFacade::SolveHandle::error() const { return ready() && strat_->error(); }
+bool ClaspFacade::SolveHandle::ready() const { return strat_->ready(); }
+bool ClaspFacade::SolveHandle::running() const { return strat_->running(); }
+void ClaspFacade::SolveHandle::cancel() const { strat_->interrupt(SolveStrategy::sig_cancel); }
+void ClaspFacade::SolveHandle::wait() const { strat_->wait(-1.0); }
+bool ClaspFacade::SolveHandle::waitFor(double s) const { return strat_->wait(s); }
+void ClaspFacade::SolveHandle::resume() const { strat_->resume(); }
+auto ClaspFacade::SolveHandle::get() const -> SolveResult { return strat_->result(); }
+auto ClaspFacade::SolveHandle::model() const -> const Model* { return strat_->model(); }
+auto ClaspFacade::SolveHandle::unsatCore() const -> LitView { return strat_->unsatCore(); }
+bool ClaspFacade::SolveHandle::next() const { return strat_->next(); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspFacade::Statistics
 /////////////////////////////////////////////////////////////////////////////////////////
 namespace {
-struct KV { const char* key; StatisticObject(*get)(const ClaspFacade::Summary*); };
-template 
-StatisticObject _getT(const ClaspFacade::Summary* x) { return StatisticObject::value(&static_cast((x->*time))); }
-template 
-StatisticObject _getM(const ClaspFacade::Summary* x) { return StatisticObject::value(&static_cast((x->*model))); }
-static const KV sumKeys_s[] = {
-	{"total"     , _getT<&ClaspFacade::Summary::totalTime>},
-	{"cpu"       , _getT<&ClaspFacade::Summary::cpuTime>},
-	{"solve"     , _getT<&ClaspFacade::Summary::solveTime>},
-	{"unsat"     , _getT<&ClaspFacade::Summary::unsatTime>},
-	{"sat"       , _getT<&ClaspFacade::Summary::satTime>},
-	{"enumerated", _getM<&ClaspFacade::Summary::numEnum>},
-	{"optimal"   , _getM<&ClaspFacade::Summary::numOptimal>},
-};
 struct SummaryStats {
-	SummaryStats() : stats_(0), range_(0,0) {}
-	void bind(const ClaspFacade::Summary& x, Range32 r) {
-		stats_ = &x;
-		range_ = r;
-	}
-	uint32          size()        const { return range_.hi - range_.lo; }
-	const char*     key(uint32 i) const { POTASSCO_CHECK(i < size(), ERANGE); return sumKeys_s[i + range_.lo].key; }
-	StatisticObject at(const char* key) const {
-		for (const KV* x = sumKeys_s + range_.lo, *end = sumKeys_s + range_.hi; x != end; ++x) {
-			if (std::strcmp(x->key, key) == 0) { return x->get(stats_); }
-		}
-		POTASSCO_CHECK(false, ERANGE);
-	}
-	StatisticObject toStats() const { return StatisticObject::map(this); }
-	const ClaspFacade::Summary* stats_;
-	Range32 range_;
+    struct Stat {
+        const char* key;
+        StatisticObject (*get)(const ClaspFacade::Summary*);
+    };
+    template 
+    static StatisticObject getT(const ClaspFacade::Summary* x) {
+        return StatisticObject::value(&static_cast((x->*Time)));
+    }
+    template 
+    static StatisticObject getM(const ClaspFacade::Summary* x) {
+        return StatisticObject::value(&static_cast((x->*Model)));
+    }
+    using StatRange = SpanView;
+
+    static constexpr Stat sum_keys[] = {
+        {"total", getT<&ClaspFacade::Summary::totalTime>},    {"cpu", getT<&ClaspFacade::Summary::cpuTime>},
+        {"solve", getT<&ClaspFacade::Summary::solveTime>},    {"unsat", getT<&ClaspFacade::Summary::unsatTime>},
+        {"sat", getT<&ClaspFacade::Summary::satTime>},        {"enumerated", getM<&ClaspFacade::Summary::numEnum>},
+        {"optimal", getM<&ClaspFacade::Summary::numOptimal>},
+    };
+
+    SummaryStats() = default;
+    void bind(const ClaspFacade::Summary& x, StatRange r) {
+        stats = &x;
+        range = r;
+    }
+    [[nodiscard]] uint32_t    size() const { return size32(range); }
+    [[nodiscard]] const char* key(uint32_t i) const {
+        POTASSCO_CHECK(i < size(), ERANGE);
+        return range[i].key;
+    }
+    StatisticObject at(const char* key) const {
+        for (const auto& x : range) {
+            if (std::strcmp(x.key, key) == 0) {
+                return x.get(stats);
+            }
+        }
+        POTASSCO_CHECK(false, ERANGE);
+    }
+    [[nodiscard]] StatisticObject toStats() const { return StatisticObject::map(this); }
+    const ClaspFacade::Summary*   stats{nullptr};
+    StatRange                     range;
 };
 
-double _getConcurrency(const SharedContext* ctx) { return ctx->concurrency(); }
-double _getWinner(const SharedContext* ctx) { return ctx->winner(); }
-double _getResult(const SolveResult* r) { return static_cast(r->operator Clasp::SolveResult::Base()); }
-double _getSignal(const SolveResult* r) { return static_cast(r->signal); }
-double _getExhausted(const SolveResult* r) { return static_cast(r->exhausted()); }
-}
+} // namespace
+constexpr auto c_get_concurrency = [](const SharedContext* ctx) -> double { return ctx->concurrency(); };
+constexpr auto c_get_winner      = [](const SharedContext* ctx) -> double { return ctx->winner(); };
+constexpr auto c_get_result      = [](const SolveResult* r) -> double {
+    return static_cast(r->operator SolveResult::Res());
+};
+constexpr auto c_get_signal    = [](const SolveResult* r) -> double { return static_cast(r->signal); };
+constexpr auto c_get_exhausted = [](const SolveResult* r) -> double { return static_cast(r->exhausted()); };
 
 struct ClaspFacade::Statistics {
-	Statistics(ClaspFacade& f) : self_(&f), tester_(0), level_(0), clingo_(0) {}
-	~Statistics() { delete clingo_; delete solvers_.multi; }
-	void start(uint32 level);
-	void initLevel(uint32 level);
-	void end();
-	void addTo(StatsMap& solving, StatsMap* accu) const;
-	void accept(StatsVisitor& out, bool final) const;
-	bool incremental() const { return self_->incremental(); }
-	typedef StatsVec        SolverVec;
-	typedef SingleOwnerPtr LpStatsPtr;
-	typedef PrgDepGraph::NonHcfStats     TesterStats;
-	ClaspFacade* self_;
-	LpStatsPtr   lp_;      // level 0 and asp
-	SolverStats  solvers_; // level 0
-	SolverVec    solver_;  // level > 1
-	SolverVec    accu_;    // level > 1 and incremental
-	TesterStats* tester_;  // level > 0 and nonhcfs
-	uint32       level_;   // active stats level
-	// For clingo stats interface
-	class ClingoView : public ClaspStatistics {
-	public:
-		explicit ClingoView(const ClaspFacade& f);
-		void update(const Statistics& s);
-		Key_t user(bool final) const;
-	private:
-		struct StepStats {
-			SummaryStats times;
-			SummaryStats models;
-			void bind(const ClaspFacade::Summary& x) {
-				times.bind(x, Range32(0, 5));
-				models.bind(x, Range32(5, 7));
-			}
-			void addTo(StatsMap& summary) {
-				summary.add("times", times.toStats());
-				summary.add("models", models.toStats());
-			}
-		};
-		StatsMap*   keys_;
-		StatsMap    problem_;
-		StatsMap    solving_;
-		struct Summary : StatsMap { StepStats step; } summary_;
-		struct Accu    : StatsMap { StepStats step; StatsMap solving_; };
-		typedef SingleOwnerPtr AccuPtr;
-		AccuPtr accu_;
-	}* clingo_; // new clingo stats interface
-	ClingoView* getClingo();
+    Statistics(ClaspFacade& f) : self_(&f) {}
+    ~Statistics() { delete solvers_.multi; }
+    void               start(uint32_t level);
+    void               initLevel(uint32_t level);
+    void               enableAsp() { lp_ = std::make_unique(); }
+    void               end();
+    void               addTo(StatsMap& solving, StatsMap* accu) const;
+    void               accept(StatsVisitor& out, bool final) const;
+    [[nodiscard]] bool incremental() const { return self_->incremental(); }
+
+    // For clingo stats interface
+    class ClingoView : public ClaspStatistics {
+    public:
+        explicit ClingoView(const ClaspFacade& f);
+        void                update(const Statistics& s);
+        [[nodiscard]] Key_t user(bool final) const;
+
+    private:
+        struct StepStats {
+            SummaryStats times;
+            SummaryStats models;
+            void         bind(const Summary& x) {
+                auto r = SummaryStats::StatRange(SummaryStats::sum_keys);
+                times.bind(x, r.subspan(0, 5));
+                models.bind(x, r.subspan(5));
+            }
+            void addTo(StatsMap& summary) const {
+                summary.add("times", times.toStats());
+                summary.add("models", models.toStats());
+            }
+        };
+        StatsMap* keys_;
+        StatsMap  problem_;
+        StatsMap  solving_;
+        struct Summary : StatsMap {
+            StepStats step;
+        } summary_;
+        struct Accu : StatsMap {
+            StepStats step;
+            StatsMap  solving;
+        };
+        std::unique_ptr accu_;
+    };
+    ClingoView*                 getClingo();
+    [[nodiscard]] Asp::LpStats* lp() const { return lp_.get(); }
+
+private:
+    using SolverVec   = StatsVec;
+    using LpStatsPtr  = std::unique_ptr;
+    using TesterStats = PrgDepGraph::NonHcfStats;
+    std::unique_ptr clingo_; // new clingo stats interface
+    ClaspFacade*                self_;
+    LpStatsPtr                  lp_;              // level 0 and asp
+    SolverStats                 solvers_;         // level 0
+    SolverVec                   solver_;          // level > 1
+    SolverVec                   accu_;            // level > 1 and incremental
+    TesterStats*                tester_{nullptr}; // level > 0 and nonhcfs
+    uint32_t                    level_{0};        // active stats level
 };
-void ClaspFacade::Statistics::initLevel(uint32 level) {
-	if (level_ < level) {
-		if (incremental() && !solvers_.multi) { solvers_.multi = new SolverStats(); }
-		level_ = level;
-	}
-	if (self_->ctx.sccGraph.get() && self_->ctx.sccGraph->numNonHcfs() && !tester_) {
-		tester_ = self_->ctx.sccGraph->nonHcfStats();
-	}
+void ClaspFacade::Statistics::initLevel(uint32_t level) {
+    if (level_ < level) {
+        if (incremental() && not solvers_.multi) {
+            solvers_.multi = new SolverStats();
+        }
+        level_ = level;
+    }
+    if (self_->ctx.sccGraph.get() && self_->ctx.sccGraph->numNonHcfs() && not tester_) {
+        tester_ = self_->ctx.sccGraph->nonHcfStats();
+    }
 }
 
-void ClaspFacade::Statistics::start(uint32 level) {
-	// cleanup previous state
-	solvers_.reset();
-	solver_.reset();
-	if (tester_) { tester_->startStep(self_->config()->testerConfig() ? self_->config()->testerConfig()->context().stats : 0); }
-	// init next step
-	initLevel(level);
-	if (lp_.get() && self_->step_.lpStep()) {
-		lp_->accu(*self_->step_.lpStep());
-	}
-	if (level > 1 && solver_.size() < self_->ctx.concurrency()) {
-		uint32 sz = sizeVec(solver_);
-		solver_.growTo(self_->ctx.concurrency());
-		for (const bool inc = incremental() && (accu_.growTo(sizeVec(solver_)), true); sz != sizeVec(solver_); ++sz) {
-			if (!inc) { solver_[sz] = &self_->ctx.solverStats(sz); }
-			else      { (solver_[sz] = new SolverStats())->multi = (accu_[sz] = new SolverStats()); }
-		}
-		if (!incremental()) { solver_.release(); }
-	}
+void ClaspFacade::Statistics::start(uint32_t level) {
+    // cleanup previous state
+    solvers_.reset();
+    solver_.reset();
+    if (tester_) {
+        tester_->startStep(self_->config()->testerConfig() ? self_->config()->testerConfig()->context().stats : 0);
+    }
+    // init next step
+    initLevel(level);
+    if (lp_.get() && self_->step_.lpStep()) {
+        lp_->accu(*self_->step_.lpStep());
+    }
+    if (level > 1 && solver_.size() < self_->ctx.concurrency()) {
+        auto newIdx = irange(size32(solver_), self_->ctx.concurrency());
+        solver_.growTo(self_->ctx.concurrency());
+        if (incremental()) {
+            accu_.growTo(size32(solver_));
+            for (auto i : newIdx) {
+                solver_[i]        = new SolverStats();
+                accu_[i]          = new SolverStats();
+                solver_[i]->multi = accu_[i];
+            }
+        }
+        else {
+            solver_.release();
+            for (auto i : newIdx) { solver_[i] = &self_->ctx.solverStats(i); }
+        }
+    }
 }
 void ClaspFacade::Statistics::end() {
-	self_->ctx.accuStats(solvers_); // compute solvers = sum(solver[1], ... , solver[n])
-	solvers_.flush();
-	for (uint32 i = incremental() ? 0 : sizeVec(solver_), end = sizeVec(solver_); i != end && self_->ctx.hasSolver(i); ++i) {
-		solver_[i]->accu(self_->ctx.solverStats(i), true);
-		solver_[i]->flush();
-	}
-	if (tester_) { tester_->endStep(); }
-	if (clingo_) { clingo_->update(*this); }
+    self_->ctx.accuStats(solvers_); // compute solvers = sum(solver[1], ... , solver[n])
+    solvers_.flush();
+    for (uint32_t i = incremental() ? 0 : size32(solver_), end = size32(solver_); i != end && self_->ctx.hasSolver(i);
+         ++i) {
+        solver_[i]->accu(self_->ctx.solverStats(i), true);
+        solver_[i]->flush();
+    }
+    if (tester_) {
+        tester_->endStep();
+    }
+    if (clingo_) {
+        clingo_->update(*this);
+    }
 }
 void ClaspFacade::Statistics::addTo(StatsMap& solving, StatsMap* accu) const {
-	solvers_.addTo("solvers", solving, accu);
-	if (solver_.size())       { solving.add("solver", solver_.toStats()); }
-	if (accu && accu_.size()) { accu->add("solver", accu_.toStats()); }
+    solvers_.addTo("solvers", solving, accu);
+    if (not solver_.empty()) {
+        solving.add("solver", solver_.toStats());
+    }
+    if (accu && not accu_.empty()) {
+        accu->add("solver", accu_.toStats());
+    }
 }
 void ClaspFacade::Statistics::accept(StatsVisitor& out, bool final) const {
-	final = final && solvers_.multi;
-	if (out.visitGenerator(StatsVisitor::Enter)) {
-		out.visitSolverStats(final ? *solvers_.multi : solvers_);
-		if (lp_.get()) { out.visitLogicProgramStats(*lp_); }
-		out.visitProblemStats(self_->ctx.stats());
-		const SolverVec& solver = final ? accu_ : solver_;
-		const uint32 nThreads = final ? (uint32)accu_.size() : self_->ctx.concurrency();
-		const uint32 nSolver  = (uint32)solver.size();
-		if (const AbstractStatistics::Key_t userKey = clingo_ ? clingo_->user(final) : 0) {
-			out.visitExternalStats(clingo_->getObject(userKey));
-		}
-		if (nThreads > 1 && nSolver > 1 && out.visitThreads(StatsVisitor::Enter)) {
-			for (uint32 i = 0, end = std::min(nSolver, nThreads); i != end; ++i) {
-				out.visitThread(i, *solver[i]);
-			}
-			out.visitThreads(StatsVisitor::Leave);
-		}
-		out.visitGenerator(StatsVisitor::Leave);
-	}
-	if (tester_ && out.visitTester(StatsVisitor::Enter)) {
-		tester_->accept(out, final);
-		out.visitTester(StatsVisitor::Leave);
-	}
+    final = final && solvers_.multi;
+    if (out.visitGenerator(StatsVisitor::enter)) {
+        out.visitSolverStats(final ? *solvers_.multi : solvers_);
+        if (lp_.get()) {
+            out.visitLogicProgramStats(*lp_);
+        }
+        out.visitProblemStats(self_->ctx.stats());
+        const SolverVec& solver   = final ? accu_ : solver_;
+        const uint32_t   nThreads = final ? size32(accu_) : self_->ctx.concurrency();
+        const auto       nSolver  = size32(solver);
+        if (const AbstractStatistics::Key_t userKey = clingo_ ? clingo_->user(final) : 0) {
+            out.visitExternalStats(clingo_->getObject(userKey));
+        }
+        if (nThreads > 1 && nSolver > 1 && out.visitThreads(StatsVisitor::enter)) {
+            for (auto i : irange(std::min(nSolver, nThreads))) { out.visitThread(i, *solver[i]); }
+            out.visitThreads(StatsVisitor::leave);
+        }
+        out.visitGenerator(StatsVisitor::leave);
+    }
+    if (tester_ && out.visitTester(StatsVisitor::enter)) {
+        tester_->accept(out, final);
+        out.visitTester(StatsVisitor::leave);
+    }
 }
 ClaspFacade::Statistics::ClingoView* ClaspFacade::Statistics::getClingo() {
-	if (!clingo_) {
-		clingo_ = new ClingoView(*this->self_);
-		clingo_->update(*this);
-	}
-	return clingo_;
+    if (not clingo_) {
+        clingo_ = std::make_unique(*this->self_);
+        clingo_->update(*this);
+    }
+    return clingo_.get();
 }
 ClaspFacade::Statistics::ClingoView::ClingoView(const ClaspFacade& f) {
-	keys_ = makeRoot();
-	summary_.add("call"       , StatisticObject::value(&f.step_.step));
-	summary_.add("result"     , StatisticObject::value(&f.step_.result));
-	summary_.add("signal"     , StatisticObject::value(&f.step_.result));
-	summary_.add("exhausted"  , StatisticObject::value(&f.step_.result));
-	summary_.add("costs"      , StatisticObject::array(&f.solve_->costs));
-	summary_.add("lower"      , StatisticObject::array(&f.solve_->lower));
-	summary_.add("concurrency", StatisticObject::value(&f.ctx));
-	summary_.add("winner"     , StatisticObject::value(&f.ctx));
-	summary_.step.bind(f.step_);
-	summary_.step.addTo(summary_);
-	if (f.step_.lpStats()) {
-		problem_.add("lp", StatisticObject::map(f.step_.lpStats()));
-		if (f.incremental()) { problem_.add("lpStep", StatisticObject::map(f.step_.lpStep())); }
-	}
-	problem_.add("generator", StatisticObject::map(&f.ctx.stats()));
-	keys_->add("problem", problem_.toStats());
-	keys_->add("solving", solving_.toStats());
-	keys_->add("summary", summary_.toStats());
+    keys_ = makeRoot();
+    summary_.add("call", StatisticObject::value(&f.step_.step));
+    summary_.add("result", StatisticObject::value(&f.step_.result));
+    summary_.add("signal", StatisticObject::value(&f.step_.result));
+    summary_.add("exhausted", StatisticObject::value(&f.step_.result));
+    summary_.add("costs", StatisticObject::array(&f.solve_->costs));
+    summary_.add("lower", StatisticObject::array(&f.solve_->lower));
+    summary_.add("concurrency", StatisticObject::value(&f.ctx));
+    summary_.add("winner", StatisticObject::value(&f.ctx));
+    summary_.step.bind(f.step_);
+    summary_.step.addTo(summary_);
+    if (f.step_.lpStats()) {
+        problem_.add("lp", StatisticObject::map(f.step_.lpStats()));
+        if (f.incremental()) {
+            problem_.add("lpStep", StatisticObject::map(f.step_.lpStep()));
+        }
+    }
+    problem_.add("generator", StatisticObject::map(&f.ctx.stats()));
+    keys_->add("problem", problem_.toStats());
+    keys_->add("solving", solving_.toStats());
+    keys_->add("summary", summary_.toStats());
 
-	if (f.incremental()) {
-		accu_ = new Accu();
-		accu_->step.bind(*f.accu_.get());
-	}
+    if (f.incremental()) {
+        accu_ = std::make_unique();
+        accu_->step.bind(*f.accu_.get());
+    }
 }
 Potassco::AbstractStatistics::Key_t ClaspFacade::Statistics::ClingoView::user(bool final) const {
-	Key_t key = 0;
-	find(root(), final ? "user_accu" : "user_step", &key);
-	return key;
+    Key_t key = 0;
+    find(root(), final ? "user_accu" : "user_step", &key);
+    return key;
 }
 void ClaspFacade::Statistics::ClingoView::update(const ClaspFacade::Statistics& stats) {
-	if (stats.level_ > 0 && accu_.get() && keys_->add("accu", accu_->toStats())) {
-		accu_->step.addTo(*accu_);
-		accu_->add("solving", accu_->solving_.toStats());
-	}
-	stats.addTo(solving_, stats.level_ > 0 && accu_.get() ? &accu_->solving_ : 0);
-	if (stats.tester_) {
-		stats.tester_->addTo(problem_, solving_, stats.level_ > 0 && accu_.get() ? &accu_->solving_ : 0);
-	}
+    if (stats.level_ > 0 && accu_.get() && keys_->add("accu", accu_->toStats())) {
+        accu_->step.addTo(*accu_);
+        accu_->add("solving", accu_->solving.toStats());
+    }
+    stats.addTo(solving_, stats.level_ > 0 && accu_.get() ? &accu_->solving : nullptr);
+    if (stats.tester_) {
+        stats.tester_->addTo(problem_, solving_, stats.level_ > 0 && accu_.get() ? &accu_->solving : nullptr);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspFacade
 /////////////////////////////////////////////////////////////////////////////////////////
-ClaspFacade::ClaspFacade() : config_(0) { step_.init(*this); }
-ClaspFacade::~ClaspFacade() {}
-bool ClaspFacade::prepared() const {
-	return solve_.get() && solve_->prepared;
-}
-bool ClaspFacade::solving() const {
-	return solve_.get() && solve_->solving();
-}
-bool ClaspFacade::solved() const {
-	return solve_.get() && solve_->solved;
-}
-bool ClaspFacade::interrupted() const {
-	return result().interrupted();
-}
-bool ClaspFacade::incremental() const {
-	return accu_.get() != 0;
-}
-ProblemType ClaspFacade::detectProblemType(std::istream& str) {
-	return Clasp::detectProblemType(str);
-}
-const ClaspFacade::Summary&  ClaspFacade::summary(bool accu) const {
-	return accu && accu_.get() ? *accu_ : step_;
-}
+ClaspFacade::ClaspFacade() { step_.init(*this); }
+ClaspFacade::~ClaspFacade() {
+    if (solve_) {
+        solve_->reset(); // cancel any active solve operation before resetting our solve pointer
+        solve_.reset();
+    }
+}
+bool ClaspFacade::prepared() const { return solve_.get() && solve_->prepared; }
+bool ClaspFacade::solving() const { return solve_.get() && solve_->solving() && not solve_->solved; }
+bool ClaspFacade::solved() const { return solve_.get() && solve_->solved; }
+bool ClaspFacade::interrupted() const { return result().interrupted(); }
+bool ClaspFacade::incremental() const { return accu_.get() != nullptr; }
+auto ClaspFacade::detectProblemType(std::istream& str) -> ProblemType { return Clasp::detectProblemType(str); }
+auto ClaspFacade::summary(bool accu) const -> const Summary& { return accu && accu_.get() ? *accu_ : step_; }
 
 void ClaspFacade::discardProblem() {
-	config_  = 0;
-	builder_ = 0;
-	stats_   = 0;
-	solve_   = 0;
-	accu_    = 0;
-	step_.init(*this);
-	if (ctx.frozen() || ctx.numVars()) { ctx.reset(); }
+    config_  = nullptr;
+    builder_ = nullptr;
+    stats_   = nullptr;
+    solve_   = nullptr;
+    accu_    = nullptr;
+    step_.init(*this);
+    if (ctx.frozen() || ctx.numVars()) {
+        ctx.reset();
+    }
 }
 void ClaspFacade::init(ClaspConfig& config, bool discard) {
-	if (discard) { discardProblem(); }
-	ctx.setConfiguration(0, Ownership_t::Retain); // force reload of configuration once done
-	config_ = &config;
-	if (config_->solve.enumMode == EnumOptions::enum_dom_record && config_->solver(0).heuId != Heuristic_t::Domain) {
-		ctx.warn("Reasoning mode requires domain heuristic and is ignored.");
-		config_->solve.enumMode = EnumOptions::enum_auto;
-	}
-	SolveData::EnumPtr e(config.solve.createEnumerator(config.solve));
-	if (e.get() == 0) { e = EnumOptions::nullEnumerator(); }
-	if (config.solve.numSolver() > 1 && !e->supportsParallel()) {
-		ctx.warn("Selected reasoning mode implies #Threads=1.");
-		config.solve.setSolvers(1);
-	}
-	ctx.setConfiguration(&config, Ownership_t::Retain); // prepare and apply config
-	if (program() && type_ == Problem_t::Asp) {
-		Asp::LogicProgram* p = static_cast(program());
-		p->setOptions(config.asp);
-		p->setNonHcfConfiguration(config.testerConfig());
-	}
-	if (!solve_.get()) { solve_ = new SolveData(); }
-	SolveData::AlgoPtr a(config.solve.createSolveObject());
-	solve_->init(a.release(), e.release());
-	if (discard) { startStep(0); }
+    if (discard) {
+        discardProblem();
+    }
+    ctx.setConfiguration(nullptr); // force reload of configuration once done
+    config_ = &config;
+    if (config_->solve.enumMode == EnumOptions::enum_dom_record && config_->solver(0).heuId != HeuristicType::domain) {
+        ctx.warn("Reasoning mode requires domain heuristic and is ignored.");
+        config_->solve.enumMode = EnumOptions::enum_auto;
+    }
+    SolveData::EnumPtr e(config.solve.createEnumerator(config.solve));
+    if (e.get() == nullptr) {
+        e.reset(EnumOptions::nullEnumerator());
+    }
+    if (config.solve.numSolver() > 1 && not e->supportsParallel()) {
+        ctx.warn("Selected reasoning mode implies #Threads=1.");
+        config.solve.setSolvers(1);
+    }
+    ctx.setConfiguration(&config); // prepare and apply config
+    if (auto* p = asp()) {
+        p->setOptions(config.asp);
+        p->setNonHcfConfiguration(config.testerConfig());
+    }
+    if (not solve_.get()) {
+        solve_ = std::make_unique();
+    }
+    SolveData::AlgoPtr a(config.solve.createSolveObject());
+    solve_->init(std::move(a), std::move(e));
+    if (discard) {
+        startStep(0);
+    }
 }
 
 void ClaspFacade::initBuilder(ProgramBuilder* in) {
-	builder_ = in;
-	assume_.clear();
-	builder_->startProgram(ctx);
+    builder_.reset(in);
+    assume_.clear();
+    builder_->startProgram(ctx);
 }
 ProgramBuilder& ClaspFacade::start(ClaspConfig& config, ProblemType t) {
-	if      (t == Problem_t::Sat) { return startSat(config); }
-	else if (t == Problem_t::Pb)  { return startPB(config);  }
-	else if (t == Problem_t::Asp) { return startAsp(config); }
-	else                          { POTASSCO_CHECK(false, EDOM, "Unknown problem type!"); }
+    if (t == ProblemType::sat) {
+        return startSat(config);
+    }
+    if (t == ProblemType::pb) {
+        return startPB(config);
+    }
+    POTASSCO_CHECK(t == ProblemType::asp, EDOM, "Unknown problem type (%u)!", static_cast(t));
+    return startAsp(config);
 }
 
 ProgramBuilder& ClaspFacade::start(ClaspConfig& config, std::istream& str) {
-	ProgramParser& p = start(config, detectProblemType(str)).parser();
-	POTASSCO_REQUIRE(p.accept(str, config_->parse), "Auto detection failed!");
-	if (p.incremental()) { enableProgramUpdates(); }
-	return *program();
+    ProgramParser& p = start(config, detectProblemType(str)).parser();
+    POTASSCO_CHECK_PRE(p.accept(str, config_->parse), "Auto detection failed!");
+    if (p.incremental()) {
+        enableProgramUpdates();
+    }
+    return *program();
 }
 
 SatBuilder& ClaspFacade::startSat(ClaspConfig& config) {
-	init(config, true);
-	initBuilder(new SatBuilder());
-	type_ = Problem_t::Sat;
-	return static_cast(*builder_.get());
+    init(config, true);
+    initBuilder(new SatBuilder());
+    type_ = ProblemType::sat;
+    return static_cast(*builder_.get());
 }
 
 PBBuilder& ClaspFacade::startPB(ClaspConfig& config) {
-	init(config, true);
-	initBuilder(new PBBuilder());
-	type_ = Problem_t::Sat;
-	return static_cast(*builder_.get());
+    init(config, true);
+    initBuilder(new PBBuilder());
+    type_ = ProblemType::sat;
+    return static_cast(*builder_.get());
 }
 
 Asp::LogicProgram& ClaspFacade::startAsp(ClaspConfig& config, bool enableUpdates) {
-	init(config, true);
-	Asp::LogicProgram* p = new Asp::LogicProgram();
-	initBuilder(p);
-	p->setOptions(config.asp);
-	p->setNonHcfConfiguration(config.testerConfig());
-	type_ = Problem_t::Asp;
-	stats_->lp_ = new Asp::LpStats();
-	if (enableUpdates) { enableProgramUpdates(); }
-	return *p;
+    init(config, true);
+    auto* p = new Asp::LogicProgram();
+    initBuilder(p);
+    p->setOptions(config.asp);
+    p->setNonHcfConfiguration(config.testerConfig());
+    type_ = ProblemType::asp;
+    stats_->enableAsp();
+    if (enableUpdates) {
+        enableProgramUpdates();
+    }
+    return *p;
+}
+Asp::LogicProgram* ClaspFacade::asp() const {
+    return builder_ != nullptr && type_ == ProblemType::asp ? static_cast(builder_.get()) : nullptr;
 }
 
 bool ClaspFacade::enableProgramUpdates() {
-	POTASSCO_REQUIRE(program(), "Program was already released!");
-	POTASSCO_REQUIRE(!solving() && !program()->frozen());
-	if (!accu_.get()) {
-		keepProgram();
-		builder_->updateProgram();
-		ctx.setSolveMode(SharedContext::solve_multi);
-		enableSolveInterrupts();
-		accu_ = new Summary();
-		accu_->init(*this);
-		accu_->step = UINT32_MAX;
-	}
-	return isAsp(); // currently only ASP supports program updates
+    POTASSCO_CHECK_PRE(program(), "Program was already released!");
+    POTASSCO_CHECK_PRE(not solving() && not program()->frozen());
+    if (not accu_) {
+        keepProgram();
+        builder_->updateProgram();
+        ctx.setSolveMode(SharedContext::solve_multi);
+        enableSolveInterrupts();
+        accu_ = std::make_unique();
+        accu_->init(*this);
+        accu_->step = UINT32_MAX;
+    }
+    return asp() != nullptr; // currently only ASP supports program updates
 }
 void ClaspFacade::enableSolveInterrupts() {
-	POTASSCO_REQUIRE(!solving(), "Solving is already active!");
-	POTASSCO_ASSERT(solve_.get(), "Active program required!");
-	if (!solve_->interruptible) {
-		solve_->interruptible = true;
-		solve_->algo->enableInterrupts();
-	}
+    POTASSCO_CHECK_PRE(not solving(), "Solving is already active!");
+    POTASSCO_ASSERT(solve_.get(), "Active program required!");
+    if (not solve_->interruptible) {
+        solve_->interruptible = true;
+        solve_->algo->enableInterrupts();
+    }
 }
 
-void Clasp::ClaspFacade::keepProgram() {
-	POTASSCO_REQUIRE(program(), "Program was already released!");
-	POTASSCO_ASSERT(solve_.get(), "Active program required!");
-	solve_->keepPrg = true;
-	if (isAsp()) {
-		static_cast(builder_.get())->enableOutputState();
-	}
+void ClaspFacade::keepProgram() {
+    POTASSCO_CHECK_PRE(program(), "Program was already released!");
+    POTASSCO_ASSERT(solve_.get(), "Active program required!");
+    solve_->keepPrg = true;
+    if (auto* p = asp()) {
+        p->enableOutputState();
+    }
 }
 
-void ClaspFacade::startStep(uint32 n) {
-	step_.init(*this);
-	step_.totalTime = RealTime::getTime();
-	step_.cpuTime   = ProcessTime::getTime();
-	step_.step      = n;
-	solve_->solved  = false;
-	if (!stats_.get()) { stats_ = new Statistics(*this); }
-	ctx.report(StepStart(*this));
+void ClaspFacade::startStep(uint32_t n) {
+    step_.init(*this);
+    step_.totalTime = RealTime::getTime();
+    step_.cpuTime   = ProcessTime::getTime();
+    step_.step      = n;
+    solve_->solved  = false;
+    lower_.clear();
+    if (not stats_.get()) {
+        stats_ = std::make_unique(*this);
+    }
+    ctx.report(StepStart(*this));
 }
 
 ClaspFacade::Result ClaspFacade::stopStep(int signal, bool complete) {
-	if (!solved()) {
-		double t = RealTime::getTime();
-		solve_->solved  = true;
-		step_.totalTime = diffTime(t, step_.totalTime);
-		step_.cpuTime   = diffTime(ProcessTime::getTime(), step_.cpuTime);
-		if (step_.solveTime) {
-			step_.solveTime = diffTime(t, step_.solveTime);
-			step_.unsatTime = complete ? diffTime(t, step_.unsatTime) : 0;
-		}
-		Result res = {uint8(0), uint8(signal)};
-		if (complete) { res.flags = uint8(step_.numEnum ? Result::SAT : Result::UNSAT) | Result::EXT_EXHAUST; }
-		else          { res.flags = uint8(step_.numEnum ? Result::SAT : Result::UNKNOWN); }
-		if (signal)   { res.flags|= uint8(Result::EXT_INTERRUPT); }
-		step_.result = res;
-		if (res.sat() && step_.model()->opt && !step_.numOptimal) {
-			step_.numOptimal = 1;
-		}
-		updateStats();
-		ctx.report(StepReady(step_));
-		ctx.report(Event::subsystem_facade);
-	}
-	return result();
+    if (not solved()) {
+        double t        = RealTime::getTime();
+        solve_->solved  = true;
+        step_.totalTime = diffTime(t, step_.totalTime);
+        step_.cpuTime   = diffTime(ProcessTime::getTime(), step_.cpuTime);
+        if (step_.solveTime != 0.0) {
+            step_.solveTime = diffTime(t, step_.solveTime);
+            step_.unsatTime = complete ? diffTime(t, step_.unsatTime) : 0;
+        }
+        Result res = {static_cast(0), static_cast(signal)};
+        if (complete) {
+            res.flags = static_cast(step_.numEnum ? Result::res_sat : Result::res_unsat) | Result::ext_exhaust;
+        }
+        else {
+            res.flags = static_cast(step_.numEnum ? Result::res_sat : Result::res_unknown);
+        }
+        if (signal) {
+            res.flags |= static_cast(Result::ext_interrupt);
+        }
+        lower_.clear();
+        if (const auto* min = enumerator()->minimizer(); min && min->lower(0) != 0) {
+            lower_.reserve(min->numRules());
+            for (auto i : irange(min->numRules())) { lower_.push_back(min->lower(i) + min->adjust(i)); }
+        }
+        step_.result = res;
+        if (res.sat() && step_.model()->opt && not step_.numOptimal) {
+            step_.numOptimal = 1;
+        }
+        updateStats();
+        ctx.report(StepReady(step_));
+        ctx.report(Event::subsystem_facade);
+    }
+    return result();
 }
 
 void ClaspFacade::updateStats() {
-	if (stats_.get()) {
-		stats_->end();
-	}
-	if (accu_.get() && accu_->step != step_.step) {
-		accu_->totalTime  += step_.totalTime;
-		accu_->cpuTime    += step_.cpuTime;
-		accu_->solveTime  += step_.solveTime;
-		accu_->unsatTime  += step_.unsatTime;
-		accu_->satTime    += step_.satTime;
-		accu_->numEnum    += step_.numEnum;
-		accu_->numOptimal += step_.numOptimal;
-		// no aggregation
-		accu_->step   = step_.step;
-		accu_->result = step_.result;
-	}
+    if (stats_.get()) {
+        stats_->end();
+    }
+    if (accu_.get() && accu_->step != step_.step) {
+        accu_->totalTime  += step_.totalTime;
+        accu_->cpuTime    += step_.cpuTime;
+        accu_->solveTime  += step_.solveTime;
+        accu_->unsatTime  += step_.unsatTime;
+        accu_->satTime    += step_.satTime;
+        accu_->numEnum    += step_.numEnum;
+        accu_->numOptimal += step_.numOptimal;
+        // no aggregation
+        accu_->step   = step_.step;
+        accu_->result = step_.result;
+    }
 }
 
 bool ClaspFacade::interrupt(int signal) {
-	return solve_.get() && (signal || (signal = solve_->qSig.exchange(0)) != 0) && solve_->interrupt(signal);
+    return solve_.get() && (signal || (signal = solve_->qSig.exchange(0)) != 0) && solve_->interrupt(signal);
 }
 
 const ClaspFacade::Summary& ClaspFacade::shutdown() {
-	if (solve_.get()) {
-		solve_->interrupt(SolveStrategy::SIGCANCEL);
-		stopStep(solve_->signal(), !ok());
-	}
-	return summary(true);
+    if (solve_.get()) {
+        solve_->interrupt(SolveStrategy::sig_cancel);
+        stopStep(solve_->signal(), not ok());
+    }
+    return summary(true);
 }
 
 bool ClaspFacade::read() {
-	POTASSCO_REQUIRE(solve_.get());
-	if (!program() || interrupted()) { return false; }
-	ProgramParser& p = program()->parser();
-	if (!p.isOpen() || (solved() && !update().ok())) { return false; }
-	POTASSCO_REQUIRE(p.parse(), "Invalid input stream!");
-	if (!p.more()) { p.reset(); }
-	return true;
+    POTASSCO_CHECK_PRE(solve_.get());
+    if (not program() || interrupted()) {
+        return false;
+    }
+    ProgramParser& p = program()->parser();
+    if (not p.isOpen() || (solved() && not update().ok())) {
+        return false;
+    }
+    POTASSCO_CHECK(p.parse(), std::errc::not_supported, "Invalid input stream!");
+    if (not p.more()) {
+        p.reset();
+    }
+    return true;
 }
 
 void ClaspFacade::prepare(EnumMode enumMode) {
-	POTASSCO_REQUIRE(solve_.get() && !solving());
-	POTASSCO_REQUIRE(!solved() || ctx.solveMode() == SharedContext::solve_multi);
-	EnumOptions& en = config_->solve;
-	if (solved()) {
-		doUpdate(0, false, SIG_DFL);
-		solve_->prepareEnum(ctx, enumMode, en);
-		ctx.endInit();
-	}
-	if (prepared()) { return; }
-	SharedMinimizeData* m = 0;
-	ProgramBuilder*   prg = program();
-	if (prg && prg->endProgram()) {
-		assume_.clear();
-		prg->getAssumptions(assume_);
-		prg->getWeakBounds(en.optBound);
-	}
-	stats_->start(uint32(config_->context().stats));
-	if (ctx.ok() && en.optMode != MinimizeMode_t::ignore && (m = ctx.minimize()) != 0) {
-		if (!m->setMode(en.optMode, en.optBound)) {
-			assume_.push_back(lit_false());
-		}
-		if (en.optMode == MinimizeMode_t::enumerate && en.optBound.empty()) {
-			ctx.warn("opt-mode=enum: No bound given, optimize statement ignored.");
-		}
-	}
-	if (incremental() || config_->solver(0).heuId == Heuristic_t::Domain) {
-		ctx.setPreserveHeuristic(true);
-	}
-	POTASSCO_REQUIRE(!ctx.ok() || !ctx.frozen());
-	solve_->prepareEnum(ctx, enumMode, en);
-	if      (!solve_->keepPrg) { builder_ = 0; }
-	else if (isAsp())          { static_cast(builder_.get())->dispose(false); }
-	if (!builder_.get() && !ctx.heuristic.empty()) {
-		bool keepDom = false;
-		for (uint32 i = 0; i != config_->solve.numSolver() && !keepDom; ++i) {
-			keepDom = config_->solver(i).heuId == Heuristic_t::Domain;
-		}
-		if (!keepDom) { ctx.heuristic.reset(); }
-	}
-	if (ctx.ok()) { ctx.endInit(); }
+    POTASSCO_CHECK_PRE(solve_.get() && not solving());
+    POTASSCO_CHECK_PRE(not solved() || ctx.solveMode() == SharedContext::solve_multi);
+    EnumOptions& en = config_->solve;
+    if (solved()) {
+        doUpdate(nullptr, false, SIG_DFL);
+        solve_->prepareEnum(ctx, enumMode, en);
+        ctx.endInit();
+    }
+    if (prepared()) {
+        return;
+    }
+    if (ProgramBuilder* prg = program(); prg && prg->endProgram()) {
+        assume_.clear();
+        prg->getAssumptions(assume_);
+        prg->getWeakBounds(en.optBound);
+    }
+    stats_->start(config_->context().stats);
+    if (ctx.ok() && en.optMode != MinimizeMode::ignore && ctx.hasMinimize()) {
+        if (not ctx.minimize()->setMode(en.optMode, en.optBound)) {
+            assume_.push_back(lit_false);
+        }
+        if (en.optMode == MinimizeMode::enumerate && en.optBound.empty()) {
+            ctx.warn("opt-mode=enum: No bound given, optimize statement ignored.");
+        }
+    }
+    if (incremental() || config_->solver(0).heuId == HeuristicType::domain) {
+        ctx.setPreserveHeuristic(true);
+    }
+    POTASSCO_CHECK_PRE(not ctx.ok() || not ctx.frozen());
+    solve_->prepareEnum(ctx, enumMode, en);
+    if (not solve_->keepPrg) {
+        builder_ = nullptr;
+    }
+    else if (auto* p = asp(); p) {
+        p->dispose(false);
+    }
+    if (not builder_.get() && not ctx.heuristic.empty() &&
+        std::ranges::none_of(irange(config_->solve.numSolver()),
+                             [&](uint32_t sId) { return config_->solver(sId).heuId == HeuristicType::domain; })) {
+        ctx.heuristic.reset();
+    }
+    if (ctx.ok()) {
+        ctx.endInit();
+    }
 }
 
-ClaspFacade::SolveHandle ClaspFacade::solve(SolveMode_t p, const LitVec& a, EventHandler* eh) {
-	prepare();
-	solve_->active = SolveStrategy::create(p, *this, *solve_->algo.get());
-	solve_->active->start(eh, a);
-	return SolveHandle(solve_->active);
+ClaspFacade::SolveHandle ClaspFacade::solve(SolveMode p, LitView a, EventHandler* eh) {
+    prepare();
+    solve_->active = SolveStrategy::create(p, *this, *solve_->algo.get());
+    solve_->active->start(eh, a);
+    return SolveHandle(solve_->active);
 }
-ClaspFacade::Result ClaspFacade::solve(const LitVec& a, EventHandler* handler) {
-	return solve(SolveMode_t::Default, a, handler).get();
+ClaspFacade::Result ClaspFacade::solve(LitView a, EventHandler* handler) {
+    return solve(SolveMode::def, a, handler).get();
 }
 
 ProgramBuilder& ClaspFacade::update(bool updateConfig, void (*sigAct)(int)) {
-	POTASSCO_REQUIRE(config_ && program() && !solving(), "Program updates not supported!");
-	POTASSCO_REQUIRE(!program()->frozen() || incremental(), "Program updates not supported!");
-	doUpdate(program(), updateConfig, sigAct);
-	return *program();
-}
-ProgramBuilder& ClaspFacade::update(bool updateConfig) {
-	return update(updateConfig, SIG_DFL);
+    POTASSCO_CHECK_PRE(config_ && program() && not solving(), "Program updates not supported!");
+    POTASSCO_CHECK_PRE(not program()->frozen() || incremental(), "Program updates not supported!");
+    doUpdate(program(), updateConfig, sigAct);
+    return *program();
 }
 
-void ClaspFacade::doUpdate(ProgramBuilder* p, bool updateConfig, void(*sigAct)(int)) {
-	if (updateConfig) {
-		init(*config_, false);
-	}
-	if (solved()) {
-		startStep(step() + 1);
-	}
-	if (p && p->frozen()) {
-		p->updateProgram();
-	}
-	if (ctx.frozen()) {
-		ctx.unfreeze();
-	}
-	solve_->reset();
-	config_->unfreeze(ctx);
-	int sig = sigAct == SIG_DFL ? 0 : solve_->qSig.exchange(0);
-	if (sig && sigAct != SIG_IGN) { sigAct(sig); }
+void ClaspFacade::doUpdate(ProgramBuilder* p, bool updateConfig, void (*sigAct)(int)) {
+    if (updateConfig) {
+        init(*config_, false);
+    }
+    if (solved()) {
+        startStep(static_cast(step()) + 1u);
+    }
+    if (p && p->frozen()) {
+        p->updateProgram();
+    }
+    if (ctx.frozen()) {
+        ctx.unfreeze();
+    }
+    solve_->reset();
+    config_->unfreeze(ctx);
+    int sig = sigAct == SIG_DFL ? 0 : solve_->qSig.exchange(0);
+    if (sig && sigAct != SIG_IGN) {
+        sigAct(sig);
+    }
 }
 
 bool ClaspFacade::onModel(const Solver& s, const Model& m) {
-	step_.unsatTime = RealTime::getTime();
-	if (++step_.numEnum == 1) { step_.satTime = diffTime(step_.unsatTime, step_.solveTime); }
-	if (m.opt) { ++step_.numOptimal; }
-	return solve_->onModel(s, m);
-}
-Enumerator* ClaspFacade::enumerator() const { return solve_.get() ? solve_->enumerator() : 0; }
+    step_.unsatTime = RealTime::getTime();
+    if (++step_.numEnum == 1) {
+        step_.satTime = diffTime(step_.unsatTime, step_.solveTime);
+    }
+    if (m.opt) {
+        ++step_.numOptimal;
+    }
+    return solve_->onModel(s, m);
+}
+Enumerator*                   ClaspFacade::enumerator() const { return solve_.get() ? solve_->enumerator() : nullptr; }
 Potassco::AbstractStatistics* ClaspFacade::getStats() const {
-	POTASSCO_REQUIRE(stats_.get() && !solving(), "statistics not (yet) available");
-	return stats_->getClingo();
+    POTASSCO_CHECK_PRE(stats_.get() && not solving(), "statistics not (yet) available");
+    return stats_->getClingo();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspFacade::Summary
 /////////////////////////////////////////////////////////////////////////////////////////
-void ClaspFacade::Summary::init(ClaspFacade& f)  { std::memset(this, 0, sizeof(Summary)); facade = &f;}
-const Model* ClaspFacade::Summary::model() const { return facade->solve_.get() ? facade->solve_->lastModel() : 0; }
-const SumVec* ClaspFacade::Summary::costs()const { return model() ? model()->costs : 0; }
-uint64  ClaspFacade::Summary::optimal()    const { return facade->step_.numOptimal; }
-bool ClaspFacade::Summary::optimize()      const {
-	if (const Enumerator* e = facade->enumerator()){
-		return e->optimize() || e->lastModel().opt;
-	}
-	return false;
-}
-const LitVec* ClaspFacade::Summary::unsatCore() const { return facade->solve_.get() ? facade->solve_->unsatCore() : 0; }
+void ClaspFacade::Summary::init(ClaspFacade& f) {
+    std::memset(this, 0, sizeof(Summary));
+    facade = &f;
+}
+const Model* ClaspFacade::Summary::model() const {
+    return facade->solve_.get() ? facade->solve_->lastModel() : nullptr;
+}
+auto ClaspFacade::Summary::costs() const -> SumView { return model() ? model()->costs : SumView{}; }
+auto ClaspFacade::Summary::optimal() const -> uint64_t { return facade->step_.numOptimal; }
+bool ClaspFacade::Summary::optimize() const {
+    if (const Enumerator* e = facade->enumerator()) {
+        return e->optimize() || e->lastModel().opt;
+    }
+    return false;
+}
+LitView ClaspFacade::Summary::unsatCore() const { return facade->solve_ ? facade->solve_->unsatCore() : LitView{}; }
 const Asp::LpStats* ClaspFacade::Summary::lpStep() const {
-	return facade->isAsp() ? &static_cast(facade->program())->stats : 0;
+    auto* p = facade->asp();
+    return p ? &p->stats : nullptr;
 }
 const Asp::LpStats* ClaspFacade::Summary::lpStats() const {
-	return facade->stats_.get() ? facade->stats_->lp_.get() : lpStep();
+    return facade->stats_.get() ? facade->stats_->lp() : lpStep();
 }
 const char* ClaspFacade::Summary::consequences() const {
-	const Model* m = model();
-	return m && m->consequences() ? modelType(*m) : 0;
-}
-
-bool ClaspFacade::Summary::hasLower() const {
-	const SharedMinimizeData* m = optimize() ? facade->enumerator()->minimizer() : 0;
-	return m && m->lower(0) != 0;
-}
-SumVec ClaspFacade::Summary::lower() const {
-	if (hasLower()) {
-		const SharedMinimizeData* m = facade->enumerator()->minimizer();
-		SumVec ret(m->numRules());
-		for (uint32 i = 0; i != m->numRules(); ++i) {
-			ret[i] = m->lower(i) + m->adjust(i);
-		}
-		return ret;
-	}
-	return SumVec();
-}
-void ClaspFacade::Summary::accept(StatsVisitor& out) const {
-	if (facade->solved()) { facade->stats_->accept(out, this == facade->accu_.get()); }
+    const auto* m = model();
+    return m && m->consequences() ? modelType(*m) : nullptr;
 }
 
+bool    ClaspFacade::Summary::hasCosts() const { return model() && model()->hasCosts(); }
+bool    ClaspFacade::Summary::hasLower() const { return not facade->lower_.empty(); }
+SumView ClaspFacade::Summary::lower() const { return facade->lower_; }
+void    ClaspFacade::Summary::accept(StatsVisitor& out) const {
+    if (facade->solved()) {
+        facade->stats_->accept(out, this == facade->accu_.get());
+    }
 }
 
+} // namespace Clasp
diff --git a/src/clasp_options.cpp b/src/clasp_options.cpp
index 309304c..5f03845 100644
--- a/src/clasp_options.cpp
+++ b/src/clasp_options.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,208 +22,229 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
+#include 
 #include 
+
 #include 
 #include 
-#include 
+
+#include 
+
+#include 
 #include 
+#include 
 #include 
-#include 
-#ifdef _MSC_VER
-#pragma warning (disable : 4996)
-#endif
-#if (defined(__cplusplus) && __cplusplus > 199711) || (defined(_MSC_VER) && _MSC_VER >= 1900)
-#define CLASP_NOEXCEPT_X(X) noexcept(X)
-#else
-#define CLASP_NOEXCEPT_X(X)
-#endif
+
 /////////////////////////////////////////////////////////////////////////////////////////
 // Helper MACROS
 /////////////////////////////////////////////////////////////////////////////////////////
-#define SET(x, v)           ( ((x)=(v)) == (v) )
-#define SET_LEQ(x, v, m)    ( ((v)<=(m)) && SET((x), (v)) )
-#define SET_GEQ(x, v, m)    ( ((v)>=(m)) && SET((x), (v)) )
-#define SET_OR_FILL(x, v)   ( SET((x),(v)) || ((x) = 0, (x) = ~(x),true) )
-#define SET_OR_ZERO(x,v)    ( SET((x),(v)) || SET((x),uint32(0)) )
-#define SET_R(x, v, lo, hi) ( ((lo)<=(v)) && ((v)<=(hi)) && SET((x), (v)) )
+#define SET(x, v)           (((x) = (v)) == (v))
+#define SET_LEQ(x, v, m)    (((v) <= (m)) && SET((x), (v)))
+#define SET_GEQ(x, v, m)    (((v) >= (m)) && SET((x), (v)))
+#define SET_OR_FILL(x, v)   (SET((x), (v)) || ((x) = 0, (x) = ~(x), true))
+#define SET_OR_ZERO(x, v)   (SET((x), (v)) || SET((x), uint32_t(0)))
+#define SET_R(x, v, lo, hi) (((lo) <= (v)) && ((v) <= (hi)) && SET((x), (v)))
 #define ITE(c, a, b)        (!!(c) ? (a) : (b))
 /////////////////////////////////////////////////////////////////////////////////////////
 // Primitive types/functions for string <-> T conversions
 /////////////////////////////////////////////////////////////////////////////////////////
-namespace bk_lib {
-template 
-static int xconvert(const char* x, pod_vector& out, const char** errPos, int sep) {
-	if (sep == 0) { sep = Potassco::def_sep; }
-	typename pod_vector::size_type sz = out.size();
-	std::size_t t = Potassco::convert_seq(x, out.max_size() - sz, std::back_inserter(out), static_cast(sep), errPos);
-	if (!t) { out.resize(sz); }
-	return static_cast(t);
-}
-template 
-static std::string& xconvert(std::string& out, const pod_vector& x) { return Potassco::xconvert(out, x.begin(), x.end()); }
-}
 namespace Potassco {
-struct KV { const char* key; int value; };
-static const struct OffType {} off = {};
-static int xconvert(const char* x, const OffType&, const char** errPos, int) {
-	bool temp = true;
-	const char* n = x;
-	if (xconvert(n, temp, &n, 0) && !temp) { x = n; }
-	if (errPos) { *errPos = x; }
-	return int(temp == false);
-}
-static std::string& xconvert(std::string& out, const OffType&) { return out.append("no"); }
-
-static const KV* findValue(const Span& map, const char* key, const char** next, const char* sep = ",") {
-	std::size_t kLen = std::strcspn(key, sep);
-	const KV* needle = 0;
-	for (const KV* it = Potassco::begin(map), *end = Potassco::end(map); it != end; ++it) {
-		if (strncasecmp(key, it->key, kLen) == 0 && !it->key[kLen]) {
-			needle = it;
-			key   += kLen;
-			break;
-		}
-	}
-	if (next) { *next = key; }
-	return needle;
-}
-static const char* findKey(const Span& map, int x) {
-	for (const KV* it = Potassco::begin(map), *end = Potassco::end(map); it != end; ++it) {
-		if (it->value == x) { return it->key; }
-	}
-	return "";
-}
-
-struct ArgString {
-	ArgString(const char* x) : in(x), skip(0) { }
-	~ArgString() CLASP_NOEXCEPT_X(false) { POTASSCO_ASSERT(!ok() || !*in || off(), "Unused argument!"); }
-	bool ok()       const { return in != 0; }
-	bool off()      const { return ok() && stringTo(in, Potassco::off); }
-	bool empty()    const { return ok() && !*in; }
-	operator void*()const { return (void*)in; }
-	char peek()     const { return ok() ? in[(*in == skip)] : 0; }
-	template 
-	ArgString& get(T& x)  {
-		if (ok()) {
-			const char* next = in + (*in == skip);
-			in = xconvert(next, x, &next, 0) != 0 ? next : 0;
-			skip = ',';
-		}
-		return *this;
-	}
-	const char* in;
-	char  skip;
-	template 
-	struct Opt_t {
-		Opt_t(T& x) : obj(&x) {}
-		T* obj;
-	};
+namespace {
+struct KeyVal {
+    const char* key;
+    int         value;
 };
-template 
-inline ArgString::Opt_t opt(T& x) { return ArgString::Opt_t(x); }
-template 
-inline ArgString& operator>>(ArgString& arg, T& x) { return arg.get(x); }
-template 
-inline ArgString& operator>>(ArgString& arg, const ArgString::Opt_t& x) { return !arg.empty() ? arg.get(*x.obj) : arg; }
-
+struct OffType {
+    friend std::string&           toChars(std::string& out, const OffType&) { return out.append("no"); }
+    friend std::from_chars_result fromChars(std::string_view in, const OffType&) {
+        bool temp = true;
+        if (auto r = fromChars(in, temp); r.ec == std::errc{} && not temp) {
+            return r;
+        }
+        return {std::data(in), std::errc::invalid_argument};
+    }
+};
+constexpr OffType off = {};
 struct StringRef {
-	StringRef(std::string& o) : out(&o) {}
-	std::string* out;
+    explicit StringRef(std::string& o) : out(&o) {}
+    template 
+    friend StringRef& operator<<(StringRef& str, const T& val) {
+        if (not str.out->empty()) {
+            str.out->append(1, ',');
+        }
+        toChars(*str.out, val);
+        return str;
+    }
+    std::string* out;
 };
-template 
-inline StringRef& operator<<(StringRef& str, const T& val) {
-	if (!str.out->empty()) { str.out->append(1, ','); }
-	xconvert(*str.out, val);
-	return str;
-}
-
-template 
+template 
 struct Set {
-	Set(unsigned v = 0) : val(v) {}
-	unsigned value() const { return val; }
-	unsigned val;
+    explicit Set(unsigned v = 0) : val(v) {}
+    [[nodiscard]] unsigned value() const { return val; }
+    unsigned               val;
+    friend std::string&    toChars(std::string& out, const Set& x) {
+        if (unsigned bitset = x.val; bitset) {
+            for (const auto& kv : enumMap(static_cast(nullptr))) {
+                if (auto ev = static_cast(kv.value); bitset == ev || (ev && (ev & bitset) == ev)) {
+                    out.append(kv.key);
+                    bitset -= ev;
+                    if (bitset == 0u) {
+                        return out;
+                    }
+                    out.append(1, ',');
+                }
+            }
+            return toChars(out, static_cast(bitset));
+        }
+        return toChars(out, off);
+    }
+    // |
+    friend std::from_chars_result fromChars(std::string_view in, Set& out) {
+        unsigned n;
+        EnumT    v;
+        auto     orig = in;
+        if (auto r = Potassco::extract(in, n); Parse::ok(r)) {
+            unsigned sum = 0;
+            for (const auto& [_, value] : enumMap(static_cast(nullptr))) {
+                sum |= static_cast(value);
+                if (n == static_cast(value) || (n && Potassco::test_mask(n, sum))) {
+                    out.val = n;
+                    return Parse::success(in, 0);
+                }
+            }
+            return Parse::error(orig);
+        }
+        else if (r = extract(in, v); Parse::ok(r)) {
+            do {
+                out.val |= static_cast(v);
+            } while (Parse::matchOpt(in, ',') && Parse::ok(r = extract(in, v)));
+            return Parse::success(in, 0);
+        }
+        else {
+            return Parse::error(in, r);
+        }
+    }
 };
-// |
-template 
-static int xconvert(const char* x, Set& out, const char** errPos, int e) {
-	const char* it = x, *next;
-	unsigned n, len = 0u; ET v;
-	if (xconvert(it, n, &next, e)) {
-		const Potassco::Span em = enumMap(static_cast(0));
-		for (size_t i = 0, sum = 0; i != em.size && !len; ++i) {
-			sum |= static_cast(em[i].value);
-			len += (n == static_cast(em[i].value)) || (n && (n & sum) == n);
-		}
-	}
-	else {
-		for (next = "", n = 0u; xconvert(it + int(*next == ','), v, &next, e); it = next, ++len) {
-			n |= static_cast(v);
-		}
-	}
-	if (len)    { out.val = n; it = next; }
-	if (errPos) { *errPos = it; }
-	return static_cast(len);
-}
-template 
-static std::string& xconvert(std::string& out, const Set& x) {
-	const Potassco::Span em = enumMap(static_cast(0));
-	if (unsigned bitset = x.val) {
-		for (const KV* k = Potassco::begin(em), *kEnd = Potassco::end(em); k != kEnd; ++k) {
-			unsigned ev = static_cast(k->value);
-			if (bitset == ev || (ev && (ev & bitset) == ev)) {
-				out.append(k->key);
-				if ((bitset -= ev) == 0u) { return out; }
-				out.append(1, ',');
-			}
-		}
-		return xconvert(out, static_cast(bitset));
-	}
-	return xconvert(out, off);
-}
 
+struct ArgString {
+    explicit ArgString(const char* x) : in(x) {}
+    ~ArgString() noexcept(false) {
+        POTASSCO_CHECK(not ok() || in.empty() || off(), std::errc::invalid_argument,
+                       "unexpected extra data in argument");
+    }
+    [[nodiscard]] bool ok() const { return in.data() != nullptr; }
+    [[nodiscard]] bool off() const { return ok() && Parse::ok(stringTo(in, Potassco::off)); }
+    [[nodiscard]] bool empty() const { return ok() && in.empty(); }
+    operator const void*() const { return in.data(); } // NOLINT
+    [[nodiscard]] char peek() const {
+        auto r = in.substr(in.starts_with(skip));
+        return not r.empty() ? r.front() : static_cast(0);
+    }
+    template 
+    ArgString& get(T& x) {
+        if (ok()) {
+            if (auto r = fromChars(in.substr(in.starts_with(skip)), x); Parse::ok(r)) {
+                in.remove_prefix(static_cast(r.ptr - in.data()));
+            }
+            else {
+                in = {nullptr, 0};
+            }
+            skip = ',';
+        }
+        return *this;
+    }
+    template 
+    friend ArgString& operator>>(ArgString& arg, T& x) {
+        return arg.get(x);
+    }
+    std::string_view in;
+    char             skip{0};
+    template 
+    struct Opt {
+        explicit Opt(T& x) : obj(&x) {}
+        T*                obj;
+        friend ArgString& operator>>(ArgString& arg, const Opt& x) { return not arg.empty() ? arg.get(*x.obj) : arg; }
+    };
+};
+} // namespace
+template 
+static constexpr ArgString::Opt opt(T& x) {
+    return ArgString::Opt(x);
+}
+using namespace std::literals;
+static const KeyVal* findValue(Clasp::SpanView map, std::string_view in, std::size_t* len,
+                               std::string_view sep = ","sv) {
+    auto          key    = in.substr(0, in.find_first_of(sep));
+    const KeyVal* needle = nullptr;
+    std::size_t   pop    = 0;
+    for (const auto& kv : map) {
+        if (Parse::eqIgnoreCase(key.data(), kv.key, key.length()) && not kv.key[key.length()]) {
+            needle = &kv;
+            pop    = key.length();
+            break;
+        }
+    }
+    if (len) {
+        *len = pop;
+    }
+    return needle;
+}
+static const char* findKey(Clasp::SpanView map, int x) {
+    for (const auto& [key, value] : map) {
+        if (value == x) {
+            return key;
+        }
+    }
+    return "";
 }
+
+} // namespace Potassco
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Errors
 /////////////////////////////////////////////////////////////////////////////////////////
-typedef Potassco::ProgramOptions::ContextError OptionError;
-typedef Potassco::ProgramOptions::ValueError   ValueError;
+using OptionError = Potassco::ProgramOptions::ContextError;
+using ValueError  = Potassco::ProgramOptions::ValueError;
 POTASSCO_ATTR_NORETURN void failOption(OptionError::Type type, const std::string& ctx, const std::string& opt,
                                        const std::string& desc = "") {
-	using namespace Potassco::ProgramOptions;
-	switch (type) {
-		case OptionError::unknown_option: throw UnknownOption(ctx, opt);
-		case OptionError::ambiguous_option: throw AmbiguousOption(ctx, opt, desc);
-		default: throw ContextError(ctx, type, opt, desc);
-	}
+    using namespace Potassco::ProgramOptions;
+    switch (type) {
+        case OptionError::unknown_option  : throw UnknownOption(ctx, opt);
+        case OptionError::ambiguous_option: throw AmbiguousOption(ctx, opt, desc);
+        default                           : throw ContextError(ctx, type, opt, desc);
+    }
 }
 
 POTASSCO_ATTR_NORETURN void failValue(ValueError::Type type, const std::string& ctx, const std::string& opt,
                                       const std::string& value) {
-	using namespace Potassco::ProgramOptions;
-	throw ValueError(ctx, type, opt, value);
+    using namespace Potassco::ProgramOptions;
+    throw ValueError(ctx, type, opt, value);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Enum mappings for clasp types
 /////////////////////////////////////////////////////////////////////////////////////////
-#define MAP(x, y) {static_cast(x), static_cast(y)}
-#define DEFINE_ENUM_MAPPING(X, ...) \
-static Potassco::Span enumMap(const X*) {\
-	static const Potassco::KV map[] = {__VA_ARGS__};\
-	return Potassco::toSpan(map, sizeof(map)/sizeof(map[0]));\
-}\
-static int xconvert(const char* x, X& out, const char** errPos, int) {\
-	if (const Potassco::KV* it = Potassco::findValue(enumMap(&out), x, errPos)) { \
-		out = static_cast(it->value); \
-		return 1;\
-	}\
-	return 0;\
-}\
-static std::string& xconvert(std::string& out, X x) { \
-	return out.append(Potassco::findKey(enumMap(&x), static_cast(x))); \
-}
+#define MAP(x, y)                                                                                                      \
+    { static_cast(x), static_cast(y) }
+#define DEFINE_ENUM_MAPPING(X, ...)                                                                                    \
+    static Clasp::SpanView enumMap(const X*) {                                                       \
+        static const Potassco::KeyVal map[] = {__VA_ARGS__};                                                           \
+        return {map, sizeof(map) / sizeof(map[0])};                                                                    \
+    }                                                                                                                  \
+    std::from_chars_result fromChars(std::string_view in, X& out) {                                                    \
+        auto len = std::size_t(0);                                                                                     \
+        auto ec  = std::errc::invalid_argument;                                                                        \
+        if (const auto* it = Potassco::findValue(enumMap(&out), in, &len)) {                                           \
+            out = static_cast(it->value);                                                                           \
+            ec  = std::errc{};                                                                                         \
+        }                                                                                                              \
+        return {in.data() + len, ec};                                                                                  \
+    }                                                                                                                  \
+    static std::string& toChars(std::string& out, X x) {                                                               \
+        return out.append(Potassco::findKey(enumMap(&x), static_cast(x)));                                        \
+    }
 #define OPTION(k, e, a, d, ...) a
 #define CLASP_ALL_GROUPS
 #define ARG_EXT(a, X) X
@@ -231,985 +252,1221 @@ static std::string& xconvert(std::string& out, X x) { \
 #define NO_ARG
 #include 
 namespace Cli {
-DEFINE_ENUM_MAPPING(ConfigKey, \
-  MAP("auto",   config_default), MAP("frumpy", config_frumpy), MAP("jumpy",  config_jumpy), \
-  MAP("tweety", config_tweety) , MAP("handy" , config_handy) ,\
-  MAP("crafty", config_crafty) , MAP("trendy", config_trendy), MAP("many", config_many))
+DEFINE_ENUM_MAPPING(ConfigKey, MAP("auto", config_default), MAP("frumpy", config_frumpy), MAP("jumpy", config_jumpy),
+                    MAP("tweety", config_tweety), MAP("handy", config_handy), MAP("crafty", config_crafty),
+                    MAP("trendy", config_trendy), MAP("many", config_many))
 }
 #undef MAP
 #undef DEFINE_ENUM_MAPPING
 /////////////////////////////////////////////////////////////////////////////////////////
 // Conversion functions for complex clasp types
 /////////////////////////////////////////////////////////////////////////////////////////
-static int convertRet(int res, const char* in, const char** next) {
-	if (next) *next = in;
-	return res;
+using Potassco::Parse::ok;
+static std::string& toChars(std::string& out, const SatPreParams& p) {
+    if (not p.type) {
+        return toChars(out, Potassco::off);
+    }
+    Potassco::toChars(out, p.type);
+    if (auto n = p.limIters) {
+        Potassco::toChars(out.append(",iter="), n);
+    }
+    if (auto n = p.limOcc) {
+        Potassco::toChars(out.append(",occ="), n);
+    }
+    if (auto n = p.limTime) {
+        Potassco::toChars(out.append(",time="), n);
+    }
+    if (auto n = p.limFrozen) {
+        Potassco::toChars(out.append(",frozen="), n);
+    }
+    if (auto n = p.limClause) {
+        Potassco::toChars(out.append(",size="), n);
+    }
+    return out;
+}
+static std::from_chars_result fromChars(std::string_view in, SatPreParams& out) {
+    if (auto r = fromChars(in, Potassco::off); ok(r)) {
+        out = SatPreParams();
+        return r;
+    }
+    uint32_t n;
+    if (auto r = Potassco::extract(in, n); not ok(r) || not SET(out.type, n)) {
+        return Potassco::Parse::error(in, not ok(r) ? r : std::errc::result_out_of_range);
+    }
+    Potassco::KeyVal kv[5] = {{"iter", 0}, {"occ", 0}, {"time", 0}, {"frozen", 0}, {"size", 4000}};
+    for (uint32_t id = 0; Potassco::Parse::matchOpt(in, ','); ++id) {
+        std::size_t len;
+        if (const auto* val = Potassco::findValue(kv, in, &len, ":="); val != nullptr) {
+            id = static_cast(val - kv);
+            in.remove_prefix(len);
+            Potassco::Parse::matchOpt(in, '=') || Potassco::Parse::matchOpt(in, ':');
+        }
+        if (id > 4 || not ok(Potassco::extract(in, kv[id].value))) {
+            break;
+        }
+    }
+    SET_OR_ZERO(out.limIters, unsigned(kv[0].value));
+    SET_OR_ZERO(out.limOcc, unsigned(kv[1].value));
+    SET_OR_ZERO(out.limTime, unsigned(kv[2].value));
+    SET_OR_ZERO(out.limFrozen, unsigned(kv[3].value));
+    SET_OR_ZERO(out.limClause, unsigned(kv[4].value));
+    return Potassco::Parse::success(in, 0);
 }
 
-static int xconvert(const char* x, ScheduleStrategy& out, const char** errPos, int e) {
-	using Potassco::xconvert;
-	const char* next = std::strchr(x ? x : "", ',');
-	uint32      base = 0;
-	if (!next || !xconvert(next+1, base, &next, e) || base == 0) { return convertRet(0, x, errPos); }
-	if (strncasecmp(x, "f,", 2) == 0 || strncasecmp(x, "fixed,", 6) == 0) {
-		out = ScheduleStrategy::fixed(base);
-	}
-	else if (strncasecmp(x, "l,", 2) == 0 || strncasecmp(x, "luby,", 5) == 0) {
-		uint32 lim = 0;
-		if (*next == ',' && !xconvert(next+1, lim, &next, e)) { return convertRet(0, next, errPos); }
-		out = ScheduleStrategy::luby(base, lim);
-	}
-	else if (strncmp(x, "+,", 2) == 0 || strncasecmp(x, "add,", 4) == 0) {
-		std::pair arg(0, 0);
-		if (*next != ',' || !xconvert(next+1, arg, &next, e)) { return convertRet(0, next, errPos); }
-		out = ScheduleStrategy::arith(base, arg.first, arg.second);
-	}
-	else if (strncmp(x, "x,", 2) == 0 || strncmp(x, "*,", 2) == 0) {
-		std::pair arg(0, 0);
-		if (*next != ',' || !xconvert(next+1, arg, &next, e) || arg.first < 1.0) { return convertRet(0, next, errPos); }
-		out = ScheduleStrategy::geom(base, arg.first, arg.second);
-	}
-	else {
-		return convertRet(0, x, errPos);
-	}
-	return convertRet(1, next, errPos);
-}
-static std::string& xconvert(std::string& out, const ScheduleStrategy& sched) {
-	using Potassco::xconvert;
-	if (sched.defaulted()){ return xconvert(out, ScheduleStrategy()); }
-	if (sched.disabled()) { return out.append("0"); }
-	std::size_t t = out.size();
-	out.append("f,");
-	xconvert(out, sched.base);
-	switch (sched.type) {
-		case ScheduleStrategy::Geometric:
-			out[t] = 'x';
-			return xconvert(out.append(1, ','), std::make_pair((double)sched.grow, sched.len));
-		case ScheduleStrategy::Arithmetic:
-			if (sched.grow) { out[t] = '+'; return xconvert(out.append(1, ','), std::make_pair((uint32)sched.grow, sched.len)); }
-			else            { out[t] = 'f'; return out; }
-		case ScheduleStrategy::Luby:
-			out[t] = 'l';
-			if (sched.len) { return xconvert(out.append(1, ','), sched.len); }
-			else           { return out; }
-		default: POTASSCO_ASSERT(false, "xconvert(ScheduleStrategy): unknown type");
-	}
-}
-static int xconvert(const char* x, RestartSchedule& out, const char** errPos, int e) {
-	if (!x || (*x != 'd' && *x != 'D'))
-		return xconvert(x, static_cast(out), errPos, e);
-	using Potassco::xconvert;
-	std::pair req(0, 0);
-	uint32             lim  = 0, sWin = 0;
-	MovingAvg::Type    fast = MovingAvg::avg_sma;
-	MovingAvg::Type    slow = MovingAvg::avg_sma;
-	DynamicLimit::Keep keep = RestartSchedule::keep_never;
-	const char*        next = 0;
-	if (x[1] != ',' || !xconvert(x + 2, req, &x, e) || req.first == 0 || req.second <= 0.0)
-		return convertRet(0, x, errPos);
-	if (*x == ',' && !xconvert(x + 1, lim, &x, e))
-		return convertRet(0, x, errPos);
-	if (*x == ',' && !xconvert(x + 1, fast, &x, e))
-		return convertRet(0, x, errPos);
-	if (*x == ',' && fast != MovingAvg::avg_sma && xconvert(x + 1, keep, &next, e))
-		x = next;
-	if (*x == ',' && !xconvert(x + 1, slow, &x, e))
-		return convertRet(0, x, errPos);
-	if (*x == ',' && slow != MovingAvg::avg_sma && !xconvert(x + 1, sWin, &x, e))
-		return convertRet(0, x, errPos);
-	out = RestartSchedule::dynamic(req.first, static_cast(req.second), lim, fast, keep, slow, sWin);
-	return convertRet(1, x, errPos);
-}
-static std::string& xconvert(std::string& out, const RestartSchedule& in) {
-	if (in.disabled() || !in.isDynamic())
-		return xconvert(out, static_cast(in));
-	using Potassco::xconvert;
-	xconvert(out.append("d,"), std::make_pair(in.base, in.grow));
-	uint32 lbdLim = in.lbdLim();
-	MovingAvg::Type fast = in.fastAvg();
-	MovingAvg::Type slow = in.slowAvg();
-	if (lbdLim || fast != MovingAvg::avg_sma || slow != MovingAvg::avg_sma)
-		xconvert(out.append(1, ','), lbdLim);
-	if (fast != MovingAvg::avg_sma || slow != MovingAvg::avg_sma)
-		xconvert(out.append(1, ','), fast);
-	if (fast != MovingAvg::avg_sma && in.keepAvg())
-		xconvert(out.append(1, ','), static_cast(in.keepAvg()));
-	if (slow != MovingAvg::avg_sma) {
-		xconvert(out.append(1, ','), slow);
-		if (in.slowWin()) xconvert(out.append(1, ','), in.slowWin());
-	}
-	return out;
+static std::string& toChars(std::string& out, const OptParams& p) {
+    toChars(out, static_cast(p.type));
+    if (p.type == OptParams::type_usc) {
+        toChars(out.append(1, ','), static_cast(p.algo));
+        if (p.algo == OptParams::usc_k) {
+            Potassco::toChars(out.append(1, ','), p.kLim);
+        }
+        if (p.opts) {
+            toChars(out.append(1, ','), Potassco::Set(p.opts));
+        }
+    }
+    else {
+        toChars(out.append(1, ','), static_cast(p.algo));
+    }
+    return out;
 }
 
-static bool setOptLegacy(OptParams& out, uint32 n) {
-	if (n >= 20) { return false; }
-	out.type = n < 4  ? OptParams::type_bb : OptParams::type_usc;
-	out.algo = n < 4  ? n : 0;
-	out.opts = 0u;
-	out.kLim = 0u;
-	if (n > 3 && (n -= 4u) != 0u) {
-		if (test_bit(n, 0)) { out.opts |= OptParams::usc_disjoint; }
-		if (test_bit(n, 1)) { out.opts |= OptParams::usc_succinct; }
-		if (test_bit(n, 2)) { out.algo = OptParams::usc_pmr; }
-		if (test_bit(n, 3)) { out.opts |= OptParams::usc_stratify; }
-	}
-	return true;
-}
-static int xconvert(const char* x, OptParams& out, const char** err, int e) {
-	using Potassco::xconvert;
-	using Potassco::toString;
-	const char* it = x, *next;
-	unsigned n = 0u, len = 0u;
-	OptParams::Type t;
-	// clasp-3.0: 
-	if (xconvert(it, n, &next, e) && setOptLegacy(out, n)) {
-		it = next; ++len;
-	}
-	else if (xconvert(it, t, &next, e)) {
-		setOptLegacy(out, uint32(t)*4);
-		it = next; ++len;
-		if (*it == ',') {
-			union { OptParams::BBAlgo bb; OptParams::UscAlgo usc; } algo;
-			if (xconvert(it+1, n, &next, e) && setOptLegacy(out, n + (uint32(t)*4))) { // clasp-3.2: (bb|usc),
-				it = next; ++len;
-			}
-			else if (t == OptParams::type_bb && xconvert(it+1, algo.bb, &next, e)) {
-				out.algo = algo.bb;
-				it = next; ++len;
-			}
-			else if (t == OptParams::type_usc) {
-				Potassco::Set opts(0);
-				if (xconvert(it+1, algo.usc, &next, e)) {
-					out.algo = algo.usc;
-					it = next; ++len;
-					if (*it == ',' && algo.usc == OptParams::usc_k && xconvert(it + 1, n, &next)) {
-						SET_OR_FILL(out.kLim, n);
-						it = next; ++len;
-					}
-				}
-				if (*it == ',' && (xconvert(it + 1, Potassco::off, &next, e) || xconvert(it + 1, opts, &next, e))) {
-					out.opts = opts.value();
-					it = next; ++len;
-				}
-			}
-		}
-	}
-	if (err) { *err = it; }
-	return static_cast(len);
-}
-static std::string& xconvert(std::string& out, const OptParams& p) {
-	xconvert(out, static_cast(p.type));
-	if (p.type == OptParams::type_usc) {
-		xconvert(out.append(1, ','), static_cast(p.algo));
-		if (p.algo == OptParams::usc_k ) { Potassco::xconvert(out.append(1, ','), p.kLim); }
-		if (p.opts) { Potassco::xconvert(out.append(1, ','), Potassco::Set(p.opts)); }
-	}
-	else {
-		xconvert(out.append(1, ','), static_cast(p.algo));
-	}
-	return out;
-}
-static int xconvert(const char* x, SatPreParams& out, const char** err, int e) {
-	using Potassco::xconvert;
-	if (xconvert(x, Potassco::off, err, e)) {
-		out = SatPreParams();
-		return 1;
-	}
-	uint32 n, len = 0;
-	const char *next;
-	if (xconvert(x, n, &next, e) && SET(out.type, n)) {
-		x = next; ++len;
-		Potassco::KV kv[5] = {{"iter", 0}, {"occ", 0}, {"time", 0}, {"frozen", 0}, {"size", 4000}};
-		Potassco::Span map = Potassco::toSpan(kv, 5);
-		for (uint32 id = 0; *x == ','; ++id, ++len) {
-			const char* it = x;
-			if (const Potassco::KV* val = Potassco::findValue(map, it + 1, &next, ":=")) {
-				id = static_cast(val - kv);
-				it = next;
-			}
-			if (id > 4 || !xconvert(it + 1, kv[id].value, &next, e)) { break; }
-			x = next;
-		}
-		SET_OR_ZERO(out.limIters,  unsigned(kv[0].value));
-		SET_OR_ZERO(out.limOcc,    unsigned(kv[1].value));
-		SET_OR_ZERO(out.limTime,   unsigned(kv[2].value));
-		SET_OR_ZERO(out.limFrozen, unsigned(kv[3].value));
-		SET_OR_ZERO(out.limClause, unsigned(kv[4].value));
-	}
-	if (err) { *err = x; }
-	return static_cast(len);
-}
-static std::string& xconvert(std::string& out, const SatPreParams& p) {
-	if (p.type) {
-		Potassco::xconvert(out, p.type);
-		if (uint32 n = p.limIters)  { Potassco::xconvert(out.append(",iter="), n);   }
-		if (uint32 n = p.limOcc)    { Potassco::xconvert(out.append(",occ="), n);    }
-		if (uint32 n = p.limTime)   { Potassco::xconvert(out.append(",time="), n);   }
-		if (uint32 n = p.limFrozen) { Potassco::xconvert(out.append(",frozen="), n); }
-		if (uint32 n = p.limClause) { Potassco::xconvert(out.append(",size="), n); }
-		return out;
-	}
-	else {
-		return xconvert(out, Potassco::off);
-	}
-}
-namespace Asp { using Clasp::xconvert; }
-namespace mt  { using Clasp::xconvert; }
+static bool setOptLegacy(OptParams& out, uint32_t n) {
+    if (n >= 20) {
+        return false;
+    }
+    out.type = n < 4 ? OptParams::type_bb : OptParams::type_usc;
+    out.algo = n < 4 ? n : 0;
+    out.opts = 0u;
+    out.kLim = 0u;
+    if (n > 4) {
+        n -= 4;
+        if (Potassco::test_bit(n, 0)) {
+            out.opts |= OptParams::usc_disjoint;
+        }
+        if (Potassco::test_bit(n, 1)) {
+            out.opts |= OptParams::usc_succinct;
+        }
+        if (Potassco::test_bit(n, 2)) {
+            out.algo = OptParams::usc_pmr;
+        }
+        if (Potassco::test_bit(n, 3)) {
+            out.opts |= OptParams::usc_stratify;
+        }
+    }
+    return true;
+}
+static std::from_chars_result fromChars(std::string_view in, OptParams& out) {
+    unsigned        n;
+    OptParams::Type t;
+    if (auto r = Potassco::extract(in, n); ok(r)) { // clasp-3.0: 
+        return setOptLegacy(out, n) ? Potassco::Parse::success(in, 0)
+                                    : Potassco::Parse::error(in, std::errc::result_out_of_range);
+    }
+    if (auto r = Potassco::extract(in, t); not ok(r)) { // {bb|usc}[,]
+        return Potassco::Parse::error(in);
+    }
+    setOptLegacy(out, static_cast(t) * 4);
+    if (Potassco::Parse::matchOpt(in, ',')) {
+        if (auto r = Potassco::extract(in, n); ok(r)) { // clasp-3.2: (bb|usc),
+            return setOptLegacy(out, n + (static_cast(t) * 4))
+                       ? Potassco::Parse::success(in, 0)
+                       : Potassco::Parse::error(in, std::errc::result_out_of_range);
+        }
+        if (OptParams::BBAlgo bb; t == OptParams::type_bb && ok(Potassco::extract(in, bb))) {
+            out.algo = bb;
+        }
+        else if (t == OptParams::type_usc) {
+            auto usc  = OptParams::usc_oll;
+            auto more = true;
+            if (ok(Potassco::extract(in, usc))) {
+                auto next = in;
+                if (usc == OptParams::usc_k && Potassco::Parse::matchOpt(next, ',') && ok(Potassco::extract(next, n))) {
+                    SET_OR_FILL(out.kLim, n);
+                    in = next;
+                }
+                more = Potassco::Parse::matchOpt(in, ',');
+            }
+            Potassco::Set opts(0);
+            out.algo = usc;
+            if (more && (ok(Potassco::extract(in, Potassco::off)) || ok(Potassco::extract(in, opts)))) {
+                out.opts = opts.value();
+            }
+        }
+    }
+    return Potassco::Parse::success(in, 0);
+}
+
+static std::string& toChars(std::string& out, const ScheduleStrategy& sched) {
+    using Potassco::toChars;
+    if (sched.defaulted()) {
+        return toChars(out, ScheduleStrategy());
+    }
+    if (sched.disabled()) {
+        return out.append("0");
+    }
+    auto t = out.size();
+    out.append("f,");
+    toChars(out, sched.base);
+    switch (sched.type) {
+        case ScheduleStrategy::sched_geom:
+            out[t] = 'x';
+            return toChars(out.append(1, ','), std::make_pair(static_cast(sched.grow), sched.len));
+        case ScheduleStrategy::sched_arith:
+            if (sched.grow != 0.0f) {
+                out[t] = '+';
+                return toChars(out.append(1, ','), std::make_pair(static_cast(sched.grow), sched.len));
+            }
+            out[t] = 'f';
+            return out;
+        case ScheduleStrategy::sched_luby:
+            out[t] = 'l';
+            if (sched.len) {
+                return toChars(out.append(1, ','), sched.len);
+            }
+            return out;
+        default: POTASSCO_ASSERT_NOT_REACHED("toChars(ScheduleStrategy): unknown type");
+    }
+}
+static std::string& toChars(std::string& out, const RestartSchedule& in) {
+    if (in.disabled() || not in.isDynamic()) {
+        return toChars(out, static_cast(in));
+    }
+    using Potassco::toChars;
+    toChars(out.append("d,"), std::make_pair(in.base, in.grow));
+    auto lbdLim = in.lbdLim();
+    auto fast   = in.fastAvg();
+    auto slow   = in.slowAvg();
+    if (lbdLim || fast != MovingAvg::avg_sma || slow != MovingAvg::avg_sma) {
+        toChars(out.append(1, ','), lbdLim);
+    }
+    if (fast != MovingAvg::avg_sma || slow != MovingAvg::avg_sma) {
+        toChars(out.append(1, ','), fast);
+    }
+    if (fast != MovingAvg::avg_sma && in.keepAvg()) {
+        toChars(out.append(1, ','), static_cast(in.keepAvg()));
+    }
+    if (slow != MovingAvg::avg_sma) {
+        toChars(out.append(1, ','), slow);
+        if (in.slowWin()) {
+            toChars(out.append(1, ','), in.slowWin());
+        }
+    }
+    return out;
+}
+
+// ,[,][,]
+static std::from_chars_result fromChars(std::string_view in, ScheduleStrategy& out) {
+    constexpr Potassco::KeyVal types[] = {{"f", 'f'}, {"fixed", 'f'}, {"l", 'l'}, {"luby", 'l'},
+                                          {"x", 'x'}, {"*", 'x'},     {"+", '+'}, {"add", '+'}};
+
+    std::size_t len  = 0;
+    const auto* type = Potassco::findValue(types, in, &len);
+    uint32_t    base = 0;
+    using namespace Potassco::Parse;
+    if (not type || not matchOpt(in = in.substr(len), ',') || not ok(Potassco::extract(in, base)) || base == 0) {
+        return error(in);
+    }
+    std::errc ec = {};
+    switch (static_cast(type->value)) {
+        default: POTASSCO_ASSERT_NOT_REACHED("unexpected schedule strategy");
+        case 'f': // Fixed
+            out = ScheduleStrategy::fixed(base);
+            break;
+        case 'l': // Luby
+            if (uint32_t lim = 0; not matchOpt(in, ',') || ok(ec = Potassco::extract(in, lim))) {
+                out = ScheduleStrategy::luby(base, lim);
+            }
+            break;
+        case 'x': // Geometric
+            ec = std::errc::invalid_argument;
+            if (std::pair arg(0, 0); matchOpt(in, ',') && ok(ec = Potassco::extract(in, arg))) {
+                out = ScheduleStrategy::geom(base, arg.first, arg.second);
+            }
+            break;
+        case '+': // Arithmetic
+            ec = std::errc::invalid_argument;
+            if (std::pair arg(0, 0); matchOpt(in, ',') && ok(ec = Potassco::extract(in, arg))) {
+                out = ScheduleStrategy::arith(base, arg.first, arg.second);
+            }
+            break;
+    }
+    return ok(ec) ? success(in, 0) : error(in, ec);
+}
+
+static std::from_chars_result fromChars(std::string_view in, RestartSchedule& out) {
+    if (not in.starts_with("d,") && not in.starts_with("D,")) {
+        return fromChars(in, static_cast(out));
+    }
+    using namespace Potassco::Parse;
+    in.remove_prefix(2);
+    // ,[,]
+    std::pair req(0, 0);
+    auto                        next = in;
+    if (not ok(Potassco::extract(next, req)) || req.first == 0 || req.second <= 0.0) {
+        return error(in);
+    }
+    uint32_t lim = 0, sWin = 0;
+    auto     fast = MovingAvg::Type::avg_sma;
+    auto     slow = MovingAvg::Type::avg_sma;
+    auto     keep = RestartSchedule::keep_never;
+    in            = next;
+    if (matchOpt(in, ',') && not ok(Potassco::extract(in, lim))) {
+        return error(in);
+    }
+    if (matchOpt(in, ',') && not ok(Potassco::extract(in, fast))) {
+        return error(in);
+    }
+    next = in;
+    if (matchOpt(next, ',') && fast != MovingAvg::Type::avg_sma && ok(Potassco::extract(next, keep))) {
+        in = next;
+    }
+    if (matchOpt(in, ',') && not ok(Potassco::extract(in, slow))) {
+        return error(in);
+    }
+    if (matchOpt(in, ',') && slow != MovingAvg::Type::avg_sma && not ok(Potassco::extract(in, sWin))) {
+        return error(in);
+    }
+    out = RestartSchedule::dynamic(req.first, static_cast(req.second), lim, fast, keep, slow, sWin);
+    return success(in, 0);
+}
+namespace Asp {
+using Clasp::fromChars;
+using Clasp::toChars;
+} // namespace Asp
+namespace mt {
+using Clasp::fromChars;
+using Clasp::toChars;
+} // namespace mt
 namespace Cli {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Option -> Key mapping
 /////////////////////////////////////////////////////////////////////////////////////////
 namespace {
 enum OptionKey {
-	detail_before_options = -1,
-	meta_config = 0,
-#define CLASP_CONTEXT_OPTIONS  GRP(option_category_nodes_end,   option_category_context_begin),
-#define CLASP_GLOBAL_OPTIONS   GRP(option_category_context_end, option_category_global_begin),
-#define CLASP_SOLVER_OPTIONS   GRP(option_category_global_end,  option_category_solver_begin),
-#define CLASP_SEARCH_OPTIONS   GRP(option_category_solver_end,  option_category_search_begin),
-#define CLASP_ASP_OPTIONS      GRP(option_category_search_end,  option_category_asp_begin),
-#define CLASP_SOLVE_OPTIONS    GRP(option_category_asp_end,     option_category_solve_begin),
-#define OPTION(k,e,...) opt_##k,
-#define GROUP_BEGIN(X) X
-#define GRP(X, Y) X, Y = X, detail_before_##Y = X - 1
+    meta_config = 0,
+#define CLASP_CONTEXT_OPTIONS GRP(option_category_nodes_end, option_category_context_begin),
+#define CLASP_GLOBAL_OPTIONS  GRP(option_category_context_end, option_category_global_begin),
+#define CLASP_SOLVER_OPTIONS  GRP(option_category_global_end, option_category_solver_begin),
+#define CLASP_SEARCH_OPTIONS  GRP(option_category_solver_end, option_category_search_begin),
+#define CLASP_ASP_OPTIONS     GRP(option_category_search_end, option_category_asp_begin),
+#define CLASP_SOLVE_OPTIONS   GRP(option_category_asp_end, option_category_solve_begin),
+#define OPTION(k, e, ...)     opt_##k,
+#define GROUP_BEGIN(X)        X
+#define GRP(X, Y)             X, Y = X, detail_before_##Y = X - 1 // NOLINT(bugprone-macro-parentheses)
 #include 
+
 #undef GRP
-	option_category_solve_end,
-	detail_num_options = option_category_solve_end,
-	meta_tester = detail_num_options
+    option_category_solve_end,
+    detail_num_options = option_category_solve_end,
+    meta_tester        = detail_num_options
 };
 #if CLASP_HAS_THREADS
-#define MANY_DESC  "        many  : Use default portfolio to configure solver(s)\n"
-#define MANY_ARG   "|many"
+#define MANY_DESC "        many  : Use default portfolio to configure solver(s)\n"
+#define MANY_ARG  "|many"
 #else
 #define MANY_DESC
-#define MANY_ARG   ""
+#define MANY_ARG ""
 #endif
-#define KEY_INIT_DESC(desc) \
-desc "      : {auto|frumpy|jumpy|tweety|handy|crafty|trendy" MANY_ARG "|}\n" \
-"        auto  : Select configuration based on problem type\n"                          \
-"        frumpy: Use conservative defaults\n"                                           \
-"        jumpy : Use aggressive defaults\n"                                             \
-"        tweety: Use defaults geared towards asp problems\n"                            \
-"        handy : Use defaults geared towards large problems\n"                          \
-"        crafty: Use defaults geared towards crafted problems\n"                        \
-"        trendy: Use defaults geared towards industrial problems\n"                     \
-         MANY_DESC                                                                      \
-"        : Use configuration file to configure solver(s)"
+#define KEY_INIT_DESC(desc)                                                                                            \
+    desc "      : {auto|frumpy|jumpy|tweety|handy|crafty|trendy" MANY_ARG "|}\n"                            \
+         "        auto  : Select configuration based on problem type\n"                                                \
+         "        frumpy: Use conservative defaults\n"                                                                 \
+         "        jumpy : Use aggressive defaults\n"                                                                   \
+         "        tweety: Use defaults geared towards asp problems\n"                                                  \
+         "        handy : Use defaults geared towards large problems\n"                                                \
+         "        crafty: Use defaults geared towards crafted problems\n"                                              \
+         "        trendy: Use defaults geared towards industrial problems\n" MANY_DESC                                 \
+         "        : Use configuration file to configure solver(s)"
 struct NodeKey {
-	const char* name;
-	const char* desc;
-	int16       skBeg;
-	uint16      skSize;
+    const char* name;
+    const char* desc;
+    int16_t     skBeg;
+    uint16_t    skSize;
 };
-enum { key_leaf = 0, key_solver = -1, key_asp = -2, key_solve = -3, key_tester = -4, key_root = -5 };
+enum { id_root = -5, id_tester = -4, id_solve = -3, id_asp = -2, id_solver = -1, id_leaf = 0 };
 struct Name2Id {
-	const char *name;
-	int key;
-	bool operator<(const Name2Id &rhs) const { return *this < rhs.name; }
-	bool operator<(const char *rhs) const { return std::strcmp(name, rhs) < 0; }
+    const char* name;
+    int         key;
+    bool        operator<(const Name2Id& rhs) const { return *this < rhs.name; }
+    bool        operator<(const char* rhs) const { return std::strcmp(name, rhs) < 0; }
 };
-Name2Id index_g[detail_num_options + 1] = {
-	{"configuration", meta_config},
-#define OPTION(k, e, ...) { #k, opt_##k },
+Name2Id g_index[detail_num_options + 1] = {{"configuration", meta_config},
+#define OPTION(k, e, ...) {#k, opt_##k},
 #define CLASP_ALL_GROUPS
 #include 
-	{"tester", meta_tester}
-};
-bool init_index_g = (std::sort(index_g, index_g + detail_num_options + 1), true);
-}
+
+                                           {"tester", meta_tester}};
+[[maybe_unused]] bool g_init_index = (std::sort(g_index, g_index + detail_num_options + 1), true);
+} // namespace
 /// \cond
 // Valid option keys.
-static inline bool  isOption(int k)         { return k >= option_category_nodes_end && k < detail_num_options; }
-static inline bool  isGlobalOption(int k)   { return k >= option_category_global_begin && k < option_category_global_end; }
-static inline bool  isTesterOption(int k)   { return k >= option_category_nodes_end && k < option_category_search_end && !isGlobalOption(k); }
-static inline bool  isSolverOption(int k)   { return k >= option_category_solver_begin && k < option_category_search_end; }
-static inline int16 decodeKey(uint32 key)   { return static_cast(static_cast(key)); }
-static inline uint8 decodeMode(uint32 key)  { return static_cast( (key >> 24) ); }
-static inline uint8 decodeSolver(uint32 key){ return static_cast( (key >> 16) ); }
-static inline bool  isValidId(int16 id)     { return id >= key_root && id < detail_num_options; }
-static inline bool  isLeafId(int16 id)      { return id >= key_leaf && id < detail_num_options; }
-static inline uint32 makeKeyHandle(int16 kId, uint32 mode, uint32 sId) {
-	assert(sId <= 255 && mode <= 255);
-	return (mode << 24) | (sId << 16) | static_cast(kId);
-}
-static const uint8 mode_solver = 1u;
-static const uint8 mode_tester = 2u;
-static const uint8 mode_relaxed= 4u;
-static const uint8 mode_meta   = 8u;
-static inline bool isTester(uint8 mode) { return (mode & mode_tester) != 0; }
-static inline bool isSolver(uint8 mode) { return (mode & mode_solver) != 0; }
-static inline BasicSatConfig* active(ClaspConfig* config, uint8 mode) {
-	return !isTester(mode) ? config : config->testerConfig();
-}
-static inline const BasicSatConfig* active(const ClaspConfig* config, uint8 mode) {
-	return active(const_cast(config), mode);
-}
-static int16 findOption(const char* needle, bool prefix) {
-	const Name2Id* end = index_g + detail_num_options + 1;
-	const Name2Id* it  = std::lower_bound(const_cast(index_g), end, needle);
-	int ret            = -1;
-	if (it != end) {
-		std::size_t len = std::strlen(needle);
-		if (std::strncmp(it->name, needle, len) == 0 && (!it->name[len] || prefix)) {
-			const Name2Id* next = it + 1;
-			ret = !it->name[len] || next == end || std::strncmp(next->name, needle, len) != 0 ? it->key : -2;
-		}
-	}
-	return static_cast(ret);
-}
-static NodeKey makeNode(const char* name, const char* desc, int16 skBeg = 0, int16 skEnd = 0) {
-	NodeKey n = {name, desc, skBeg, static_cast(skEnd - skBeg) };
-	return n;
-}
-static NodeKey getNode(int16 id) {
-	assert(isValidId(id));
-	switch(id) {
-		case key_root  : return makeNode("", "Options", key_tester, option_category_global_end);
-		case key_tester: return makeNode("tester", "Tester Options", key_solver, option_category_context_end);
-		case key_solve : return makeNode("solve", "Solve Options", option_category_solve_begin, option_category_solve_end);
-		case key_asp   : return makeNode("asp", "Asp Options", option_category_asp_begin, option_category_asp_end);
-		case key_solver: return makeNode("solver", "Solver Options", option_category_solver_begin, option_category_search_end);
-		case key_leaf  : return makeNode("configuration", KEY_INIT_DESC("Initializes this configuration\n"));
-		#define OPTION(k, e, a, d, x, v) case opt_##k: return makeNode(#k, d);
-		#define CLASP_ALL_GROUPS
-		#include 
-		default        : return makeNode("", "");
-	}
-}
-const ClaspCliConfig::KeyType ClaspCliConfig::KEY_INVALID = static_cast(-1);
-const ClaspCliConfig::KeyType ClaspCliConfig::KEY_ROOT    = makeKeyHandle(key_root, 0, 0);
-const ClaspCliConfig::KeyType ClaspCliConfig::KEY_SOLVER  = makeKeyHandle(key_solver, 0, 0);
-const ClaspCliConfig::KeyType ClaspCliConfig::KEY_TESTER  = makeKeyHandle(key_tester, mode_tester, 0);
+static constexpr bool isOption(int k) { return k >= option_category_nodes_end && k < detail_num_options; }
+static constexpr bool isGlobalOption(int k) {
+    return k >= option_category_global_begin && k < option_category_global_end;
+}
+static constexpr bool isTesterOption(int k) {
+    return k >= option_category_nodes_end && k < option_category_search_end && not isGlobalOption(k);
+}
+static constexpr bool isSolverOption(int k) {
+    return k >= option_category_solver_begin && k < option_category_search_end;
+}
+static constexpr int16_t  decodeKey(uint32_t key) { return static_cast(static_cast(key)); }
+static constexpr uint8_t  decodeMode(uint32_t key) { return static_cast((key >> 24)); }
+static constexpr uint8_t  decodeSolver(uint32_t key) { return static_cast((key >> 16)); }
+static constexpr bool     isValidId(int16_t id) { return id >= id_root && id < detail_num_options; }
+static constexpr bool     isLeafId(int16_t id) { return id >= id_leaf && id < detail_num_options; }
+static constexpr uint32_t makeKeyHandle(int16_t kId, uint32_t mode, uint32_t sId) {
+    assert(sId <= 255 && mode <= 255);
+    return (mode << 24) | (sId << 16) | static_cast(kId);
+}
+static constexpr uint8_t         mode_solver  = 1u;
+static constexpr uint8_t         mode_tester  = 2u;
+static constexpr uint8_t         mode_relaxed = 4u;
+static constexpr uint8_t         mode_meta    = 8u;
+static constexpr bool            isTester(uint8_t mode) { return (mode & mode_tester) != 0; }
+static constexpr bool            isSolver(uint8_t mode) { return (mode & mode_solver) != 0; }
+static constexpr BasicSatConfig* active(ClaspConfig* config, uint8_t mode) {
+    return not isTester(mode) ? config : config->testerConfig();
+}
+static constexpr const BasicSatConfig* active(const ClaspConfig* config, uint8_t mode) {
+    return active(const_cast(config), mode);
+}
+static constexpr int16_t findOption(const char* needle, bool prefix) {
+    const Name2Id* end = g_index + detail_num_options + 1;
+    const Name2Id* it  = std::lower_bound(const_cast(g_index), end, needle);
+    int            ret = -1;
+    if (it != end) {
+        std::size_t len = std::strlen(needle);
+        if (std::strncmp(it->name, needle, len) == 0 && (not it->name[len] || prefix)) {
+            const Name2Id* next = it + 1;
+            ret = not it->name[len] || next == end || std::strncmp(next->name, needle, len) != 0 ? it->key : -2;
+        }
+    }
+    return static_cast(ret);
+}
+static constexpr NodeKey makeNode(const char* name, const char* desc, int16_t skBeg = 0, int16_t skEnd = 0) {
+    NodeKey n = {name, desc, skBeg, static_cast(skEnd - skBeg)};
+    return n;
+}
+static NodeKey getNode(int16_t id) {
+    assert(isValidId(id));
+    switch (id) {
+        case id_root  : return makeNode("", "Options", id_tester, option_category_global_end);
+        case id_tester: return makeNode("tester", "Tester Options", id_solver, option_category_context_end);
+        case id_solve:
+            return makeNode("solve", "Solve Options", option_category_solve_begin, option_category_solve_end);
+        case id_asp: return makeNode("asp", "Asp Options", option_category_asp_begin, option_category_asp_end);
+        case id_solver:
+            return makeNode("solver", "Solver Options", option_category_solver_begin, option_category_search_end);
+        case id_leaf: return makeNode("configuration", KEY_INIT_DESC("Initializes this configuration\n"));
+#define OPTION(k, e, a, d, x, v)                                                                                       \
+    case opt_##k: return makeNode(#k, d);
+#define CLASP_ALL_GROUPS
+#include 
+
+        default: return makeNode("", "");
+    }
+}
+constinit const ClaspCliConfig::KeyType ClaspCliConfig::key_invalid = static_cast(-1);
+constinit const ClaspCliConfig::KeyType ClaspCliConfig::key_root    = makeKeyHandle(id_root, 0, 0);
+constinit const ClaspCliConfig::KeyType ClaspCliConfig::key_solver  = makeKeyHandle(id_solver, 0, 0);
+constinit const ClaspCliConfig::KeyType ClaspCliConfig::key_tester  = makeKeyHandle(id_tester, mode_tester, 0);
 /// \endcond
 /////////////////////////////////////////////////////////////////////////////////////////
 // Interface to ProgramOptions
 /////////////////////////////////////////////////////////////////////////////////////////
 // Converts option key to command-line option name.
 static void keyToCliName(std::string& out, const char* n, const char* ext) {
-	out.clear();
-	for (const char* x; (x = std::strchr(n, '_')) != 0; n = ++x) {
-		out.append(n, x-n);
-		out.append(1, '-');
-	}
-	out.append(n).append(ext);
+    out.clear();
+    for (const char* x; (x = std::strchr(n, '_')) != nullptr; n = ++x) {
+        out.append(n, static_cast(x - n));
+        out.append(1, '-');
+    }
+    out.append(n).append(ext);
 }
 // Converts command-line option name to option key.
 static void cliNameToKey(std::string& out, const char* n) {
-	out.clear();
-	for (const char* x; (x = std::strchr(n, '-')) != 0; n = ++x) {
-		out.append(n, x-n);
-		out.append(1, '_');
-	}
-	out.append(n);
+    out.clear();
+    for (const char* x; (x = std::strchr(n, '-')) != nullptr; n = ++x) {
+        out.append(n, static_cast(x - n));
+        out.append(1, '_');
+    }
+    out.append(n);
 }
 // Type for storing one command-line option.
 // Adapter for parsing a command string.
-struct ClaspCliConfig::ParseContext : public Potassco::ProgramOptions::ParseContext{
-	typedef Potassco::ProgramOptions::SharedOptPtr OptPtr;
-	ParseContext(ClaspCliConfig& x, const char* c, const ParsedOpts* ex, uint8 m, uint32 s, ParsedOpts* o)
-		: self(&x), prev(x.parseCtx_), config(c), exclude(ex), out(o), sId(s), mode(m) {
-		seen[0] = seen[1] = 0;
-		x.parseCtx_ = this;
-	}
-	~ParseContext() { self->parseCtx_ = this->prev; }
-	OptPtr getOption(const char* name, FindType ft);
-	OptPtr getOption(int, const char* key) { failOption(OptionError::unknown_option, config, key); }
-	void   addValue(const OptPtr& key, const std::string& value);
-	uint64            seen[2];
-	std::string       temp;
-	ClaspCliConfig*   self;
-	ParseContext*     prev;
-	const char*       config;
-	const ParsedOpts* exclude;
-	ParsedOpts*       out;
-	uint32            sId;
-	uint8             mode;
+struct ClaspCliConfig::ParseContext : Potassco::ProgramOptions::ParseContext {
+    using OptPtr = Potassco::ProgramOptions::SharedOptPtr;
+    ParseContext(ClaspCliConfig& x, const char* c, const ParsedOpts* ex, uint8_t m, uint32_t s, ParsedOpts* o)
+        : self(&x)
+        , prev(x.parseCtx_)
+        , config(c)
+        , exclude(ex)
+        , out(o)
+        , sId(s)
+        , mode(m) {
+        x.parseCtx_ = this;
+    }
+    ~ParseContext() override { self->parseCtx_ = this->prev; }
+    OptPtr            getOption(const char* name, FindType ft) override;
+    OptPtr            getOption(int, const char* key) override { failOption(OptionError::unknown_option, config, key); }
+    void              addValue(const OptPtr& key, const std::string& value) override;
+    uint64_t          seen[2] = {0, 0};
+    std::string       temp;
+    ClaspCliConfig*   self;
+    ParseContext*     prev;
+    const char*       config;
+    const ParsedOpts* exclude;
+    ParsedOpts*       out;
+    uint32_t          sId;
+    uint8_t           mode;
 };
 class ClaspCliConfig::ProgOption : public Potassco::ProgramOptions::Value {
 public:
-	ProgOption(ClaspCliConfig& c, int o) : Potassco::ProgramOptions::Value(0), config_(&c), option_(o) {}
-	bool doParse(const std::string& opt, const std::string& value) {
-		uint8 mode = config_->parseCtx_ ? config_->parseCtx_->mode : 0;
-		uint32 sId = config_->parseCtx_ ? config_->parseCtx_->sId  : 0;
-		int ret = isOption(option_) ? config_->setOption(option_, mode, sId, value.c_str()) : config_->setAppOpt(option_, mode, value.c_str());
-		if (ret == -1) { failOption(OptionError::unknown_option, !isTester(mode) ? "" : "", opt); }
-		return ret > 0;
-	}
-	int option() const { return option_; }
+    ProgOption(ClaspCliConfig& c, int o) : config_(&c), option_(o) {}
+    bool doParse(const std::string& opt, const std::string& value) override {
+        uint8_t  mode = config_->parseCtx_ ? config_->parseCtx_->mode : 0;
+        uint32_t sId  = config_->parseCtx_ ? config_->parseCtx_->sId : 0;
+        int      ret  = isOption(option_) ? config_->setOption(option_, mode, sId, value.c_str())
+                                          : config_->setAppOpt(option_, mode, value.c_str());
+        if (ret == -1) {
+            failOption(OptionError::unknown_option, not isTester(mode) ? "" : "", opt);
+        }
+        return ret > 0;
+    }
+    [[nodiscard]] int option() const { return option_; }
+
 private:
-	ClaspCliConfig* config_;
-	int             option_;
+    ClaspCliConfig* config_;
+    int             option_;
 };
 void ClaspCliConfig::ParseContext::addValue(const OptPtr& key, const std::string& value) {
-	using namespace Potassco::ProgramOptions;
-	if (exclude->count(key->name()) == 0) {
-		ProgOption* v = static_cast(key->value());
-		Value::State s= v->state();
-		int        id = v->option();
-		uint64&    xs = seen[id/64];
-		uint64      m = static_cast(1u) << (id & 63);
-		if ((xs & m) != 0 && !v->isComposing()){ failValue(ValueError::multiple_occurrences, config, key->name(), value); }
-		if (!v->parse(key->name(), value, s))  { failValue(ValueError::invalid_value, config, key->name(), value); }
-		if (out) { out->add(key->name()); }
-		xs |= m;
-	}
+    using namespace Potassco::ProgramOptions;
+    if (not exclude->contains(key->name())) {
+        auto*     v  = static_cast(key->value());
+        auto      s  = v->state();
+        int       id = v->option();
+        uint64_t& xs = seen[id / 64];
+        uint64_t  m  = static_cast(1u) << (id & 63);
+        if ((xs & m) != 0 && not v->isComposing()) {
+            failValue(ValueError::multiple_occurrences, config, key->name(), value);
+        }
+        if (not v->parse(key->name(), value, s)) {
+            failValue(ValueError::invalid_value, config, key->name(), value);
+        }
+        if (out) {
+            out->add(key->name());
+        }
+        xs |= m;
+    }
 }
 Potassco::ProgramOptions::SharedOptPtr ClaspCliConfig::ParseContext::getOption(const char* cmdName, FindType ft) {
-	Options::option_iterator end   = self->opts_->end(), it = end;
-	OptionError::Type        error = OptionError::unknown_option;
-	bool                     meta  = (mode & mode_meta) != 0;
-	if (ft == OptionContext::find_alias) {
-		char a = cmdName[*cmdName == '-'];
-		for (it = self->opts_->begin(); it != end && it->get()->alias() != a; ++it) { ; }
-	}
-	else {
-		const char* name = cmdName;
-		if (std::strchr(cmdName, '-') != 0) { cliNameToKey(temp, cmdName); name = temp.c_str(); }
-		int16 opt = findOption(name, (ft & OptionContext::find_prefix) != 0);
-		if      (opt >= 0)  { it = self->opts_->begin() + opt; }
-		else if (opt == -2) { error = OptionError::ambiguous_option; }
-		assert(it == end || static_cast(it->get()->value())->option() == opt);
-	}
-	if (it != end && (meta || isOption(static_cast(it->get()->value())->option()))) {
-		return *it;
-	}
-	failOption(error, config, cmdName);
+    auto              end = self->opts_->end(), it = end;
+    OptionError::Type error = OptionError::unknown_option;
+    bool              meta  = (mode & mode_meta) != 0;
+    if (ft == OptionContext::find_alias) {
+        char a = cmdName[*cmdName == '-'];
+        for (it = self->opts_->begin(); it != end && it->get()->alias() != a; ++it) { ; }
+    }
+    else {
+        const char* name = cmdName;
+        if (std::strchr(cmdName, '-') != nullptr) {
+            cliNameToKey(temp, cmdName);
+            name = temp.c_str();
+        }
+        int16_t opt = findOption(name, (ft & OptionContext::find_prefix) != 0);
+        if (opt >= 0) {
+            it = self->opts_->begin() + opt;
+        }
+        else if (opt == -2) {
+            error = OptionError::ambiguous_option;
+        }
+        assert(it == end || static_cast(it->get()->value())->option() == opt);
+    }
+    if (it != end && (meta || isOption(static_cast(it->get()->value())->option()))) {
+        return *it;
+    }
+    failOption(error, config, cmdName);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Default Configs
 /////////////////////////////////////////////////////////////////////////////////////////
-static inline const char* skipWs(const char* x) {
-	while (*x == ' ' || *x == '\t') { ++x; }
-	return x;
+static constexpr const char* skipWs(const char* x) {
+    while (*x == ' ' || *x == '\t') { ++x; }
+    return x;
 }
-static inline const char* getIdent(const char* x, std::string& to) {
-	for (x = skipWs(x); std::strchr(" \t:()[]", *x) == 0; ++x) { to += *x; }
-	return x;
+static const char* getIdent(const char* x, std::string& to) {
+    for (x = skipWs(x); std::strchr(" \t:()[]", *x) == nullptr; ++x) { to += *x; }
+    return x;
 }
-static inline bool matchSep(const char*& x, char c) {
-	if (*(x = skipWs(x)) == c) { ++x; return true; }
-	return false;
+static constexpr bool matchSep(const char*& x, char c) {
+    if (x = skipWs(x); *x == c) {
+        ++x;
+        return true;
+    }
+    return false;
 }
 static bool appendConfig(std::string& to, const std::string& line) {
-	const char* x = skipWs(line.c_str());
-	const bool  p = matchSep(x, '[');
-	to.append("/[", 2);
-	// match name in optional square brackets
-	bool ok = matchSep(x = getIdent(x, to), ']') == p;
-	to.append("]\0/", 3);
-	// match optional base in parentheses followed by start of option list
-	if (ok && (!matchSep(x, '(') || matchSep((x = getIdent(x, to)), ')')) && matchSep(x, ':')) {
-		to.append("\0/", 2);
-		to.append(skipWs(x));
-		to.erase(to.find_last_not_of(" \t") + 1);
-		to.append(1, '\0');
-		return true;
-	}
-	return false;
+    const char* x = skipWs(line.c_str());
+    const bool  p = matchSep(x, '[');
+    to.append("/[", 2);
+    // match name in optional square brackets
+    bool ok = matchSep(x = getIdent(x, to), ']') == p;
+    to.append("]\0/", 3);
+    // match optional base in parentheses followed by start of option list
+    if (ok && (not matchSep(x, '(') || matchSep((x = getIdent(x, to)), ')')) && matchSep(x, ':')) {
+        to.append("\0/", 2);
+        to.append(skipWs(x));
+        to.erase(to.find_last_not_of(" \t") + 1);
+        to.append(1, '\0');
+        return true;
+    }
+    return false;
 }
 ConfigIter ClaspCliConfig::getConfig(ConfigKey k) {
-	#define MAKE_CONFIG(n, o1, o2) "/[" n "]\0/\0/" o1 " " o2 "\0"
-	switch(k) {
-		#define CONFIG(id, n,c,s,p) case config_##n: return ConfigIter(MAKE_CONFIG(#n, s, c));
-		#define CLASP_CLI_DEFAULT_CONFIGS
-		#define CLASP_CLI_AUX_CONFIGS
-		#include 
-		case config_many:
-		#define CONFIG(id,n,c,s,p) MAKE_CONFIG("solver." POTASSCO_STRING(id), c, p)
-		#define CLASP_CLI_DEFAULT_CONFIGS
-		#define CLASP_CLI_AUX_CONFIGS
-			return ConfigIter(
-				#include 
-			);
-		default: POTASSCO_REQUIRE(k == config_default, "Invalid config key '%d'", (int)k); return ConfigIter("/default\0/\0/\0");
-	}
-	#undef MAKE_CONFIG
-}
-ConfigIter ClaspCliConfig::getConfig(uint8 key, std::string& tempMem) const {
-	POTASSCO_REQUIRE(key <= (config_max_value + 1), "Invalid key!");
-	if (key < config_max_value) { return getConfig(static_cast(key)); }
-	const char* name = config_[key - config_max_value].c_str();
-	std::ifstream file(name);
-	POTASSCO_EXPECT(file, "Could not open config file '%s'", name);
-	uint32 lineNum = 0;
-	tempMem.clear();
-	for (std::string line, cont; std::getline(file, line); ) {
-		++lineNum;
-		line.erase(0, line.find_first_not_of(" \t"));
-		if (line.empty() || line[0] == '#') { continue; }
-		if (*line.rbegin() == '\\')         { *line.rbegin() = ' '; cont += line; continue; }
-		if (!cont.empty()) { cont += line; cont.swap(line); cont.clear(); }
-		POTASSCO_EXPECT(appendConfig(tempMem, line), "'%s@%u': Invalid configuration", name, lineNum);
-	}
-	tempMem.append(1, '\0');
-	return ConfigIter(tempMem.data());
+#define MAKE_CONFIG(n, o1, o2) "/[" n "]\0/\0/" o1 " " o2 "\0"
+    switch (k) {
+#define CONFIG(id, n, c, s, p)                                                                                         \
+    case config_##n: return ConfigIter(MAKE_CONFIG(#n, s, c));
+#define CLASP_CLI_DEFAULT_CONFIGS
+#define CLASP_CLI_AUX_CONFIGS
+#include 
+
+        case config_many:
+#define CONFIG(id, n, c, s, p) MAKE_CONFIG("solver." POTASSCO_STRING(id), c, p)
+#define CLASP_CLI_DEFAULT_CONFIGS
+#define CLASP_CLI_AUX_CONFIGS
+            return {
+#include 
+
+            };
+        default:
+            POTASSCO_CHECK_PRE(k == config_default, "Invalid config key '%d'", (int) k);
+            return {"/default\0/\0/\0"};
+    }
+#undef MAKE_CONFIG
+}
+ConfigIter ClaspCliConfig::getConfig(uint8_t key, std::string& tempMem) const {
+    POTASSCO_CHECK_PRE(key <= (config_max_value + 1), "Invalid key!");
+    if (key < config_max_value) {
+        return getConfig(static_cast(key));
+    }
+    const char*   name = config_[key - config_max_value].c_str();
+    std::ifstream file(name);
+    POTASSCO_CHECK(file, std::errc::no_such_file_or_directory, "Could not open config file '%s'", name);
+    uint32_t lineNum = 0;
+    tempMem.clear();
+    for (std::string line, cont; std::getline(file, line);) {
+        ++lineNum;
+        line.erase(0, line.find_first_not_of(" \t"));
+        if (line.empty() || line[0] == '#') {
+            continue;
+        }
+        if (*line.rbegin() == '\\') {
+            *line.rbegin()  = ' ';
+            cont           += line;
+            continue;
+        }
+        if (not cont.empty()) {
+            cont += line;
+            cont.swap(line);
+            cont.clear();
+        }
+        POTASSCO_CHECK(appendConfig(tempMem, line), std::errc::not_supported, "'%s@%u': Invalid configuration", name,
+                       lineNum);
+    }
+    tempMem.append(1, '\0');
+    return {tempMem.data()};
 }
 int ClaspCliConfig::getConfigKey(const char* k) {
-	ConfigKey ret;
-	return Potassco::string_cast(k, ret) ? ret : -1;
+    ConfigKey ret;
+    return ok(Potassco::stringTo(k, ret)) ? ret : -1;
 }
 const char* ClaspCliConfig::getDefaults(ProblemType t) {
-	if (t == Problem_t::Asp){ return "--configuration=tweety"; }
-	else                    { return "--configuration=trendy"; }
+    return t == ProblemType::asp ? "--configuration=tweety" : "--configuration=trendy";
 }
 ConfigIter::ConfigIter(const char* x) : base_(x) {}
 const char* ConfigIter::name() const { return base_ + 1; }
 const char* ConfigIter::base() const { return base_ + std::strlen(base_) + 2; }
-const char* ConfigIter::args() const { const char* x = base(); return x + std::strlen(x) + 2; }
-bool        ConfigIter::valid()const { return *base_ != 0; }
-bool        ConfigIter::next()       {
-	base_ = args();
-	base_+= std::strlen(base_) + 1;
-	return valid();
+const char* ConfigIter::args() const {
+    const char* x = base();
+    return x + std::strlen(x) + 2;
+}
+bool ConfigIter::valid() const { return *base_ != 0; }
+bool ConfigIter::next() {
+    base_  = args();
+    base_ += std::strlen(base_) + 1;
+    return valid();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspCliConfig
 /////////////////////////////////////////////////////////////////////////////////////////
-ClaspCliConfig::ClaspCliConfig() : parseCtx_(0), validate_(false) {
-	static_assert(
-		(option_category_context_begin< option_category_solver_begin) &&
-		(option_category_solver_begin < option_category_search_begin) &&
-		(option_category_search_begin < option_category_asp_begin)    &&
-		(option_category_asp_begin    < option_category_solve_begin)  &&
-		(option_category_solve_begin  < option_category_solve_end), "unexpected option order");
-}
-ClaspCliConfig::~ClaspCliConfig() {}
+ClaspCliConfig::ClaspCliConfig() : parseCtx_(nullptr), validate_(false) {
+    static_assert((option_category_context_begin < option_category_solver_begin) &&
+                      (option_category_solver_begin < option_category_search_begin) &&
+                      (option_category_search_begin < option_category_asp_begin) &&
+                      (option_category_asp_begin < option_category_solve_begin) &&
+                      (option_category_solve_begin < option_category_solve_end),
+                  "unexpected option order");
+}
+ClaspCliConfig::~ClaspCliConfig() = default;
 void ClaspCliConfig::reset() {
-	config_[0] = config_[1] = "";
-	validate_  = false;
-	ClaspConfig::reset();
+    config_[0] = config_[1] = "";
+    validate_               = false;
+    ClaspConfig::reset();
 }
 void ClaspCliConfig::prepare(SharedContext& ctx) {
-	if (testerConfig()) {
-		// Force init
-		ClaspCliConfig::config("tester");
-	}
-	if (validate_) {
-		ClaspCliConfig::validate();
-	}
-	ClaspConfig::prepare(ctx);
+    if (testerConfig()) {
+        // Force init
+        ClaspCliConfig::config("tester");
+    }
+    if (validate_) {
+        ClaspCliConfig::validate();
+    }
+    ClaspConfig::prepare(ctx);
 }
 Configuration* ClaspCliConfig::config(const char* n) {
-	if (n && std::strcmp(n, "tester") == 0) {
-		if (!testerConfig()) {
-			setAppOpt(meta_tester, 0, "");
-		}
-		return testerConfig();
-	}
-	return ClaspConfig::config(n);
+    if (n && std::strcmp(n, "tester") == 0) {
+        if (not testerConfig()) {
+            setAppOpt(meta_tester, 0, "");
+        }
+        return testerConfig();
+    }
+    return ClaspConfig::config(n);
 }
 
-ClaspCliConfig::ProgOption* ClaspCliConfig::createOption(int o) {  return new ProgOption(*this, o); }
+ClaspCliConfig::ProgOption* ClaspCliConfig::createOption(int o) { return new ProgOption(*this, o); }
 
 void ClaspCliConfig::createOptions() {
-	if (opts_.get()) { return; }
-	opts_ = new Options();
-	using namespace Potassco::ProgramOptions;
-	opts_->addOptions()("configuration", createOption(meta_config)->defaultsTo("auto")->state(Value::value_defaulted), KEY_INIT_DESC("Set default configuration [%D]\n"));
-	std::string cmdName;
+    if (opts_.get()) {
+        return;
+    }
+    opts_ = std::make_unique();
+    using namespace Potassco::ProgramOptions;
+    opts_->addOptions()("configuration", createOption(meta_config)->defaultsTo("auto")->state(Value::value_defaulted),
+                        KEY_INIT_DESC("Set default configuration [%D]\n"));
+    std::string cmdName;
 #define CLASP_ALL_GROUPS
-#define OPTION(k, e, a, d, ...) keyToCliName(cmdName, #k, e); opts_->addOptions()(cmdName.c_str(),static_cast( createOption(opt_##k)a ), d);
-#define ARG(a) ->a
+#define OPTION(k, e, a, d, ...)                                                                                        \
+    keyToCliName(cmdName, #k, e);                                                                                      \
+    opts_->addOptions()(cmdName.c_str(), static_cast(createOption(opt_##k) a), d);
+#define ARG(a)        ->a
 #define ARG_EXT(a, X) ARG(a)
 #define NO_ARG
 #include 
-	opts_->addOptions()("tester", createOption(meta_tester)->arg(""), "Pass (quoted) string of %A to tester");
+
+    opts_->addOptions()("tester", createOption(meta_tester)->arg(""), "Pass (quoted) string of %A to tester");
 }
 void ClaspCliConfig::addOptions(OptionContext& root) {
-	createOptions();
-	using namespace Potassco::ProgramOptions;
-	OptionGroup configOpts("Clasp.Config Options");
-	OptionGroup ctxOpts("Clasp.Context Options", Potassco::ProgramOptions::desc_level_e1);
-	OptionGroup solving("Clasp.Solving Options");
-	OptionGroup aspOpts("Clasp.ASP Options", Potassco::ProgramOptions::desc_level_e1);
-	OptionGroup search("Clasp.Search Options", Potassco::ProgramOptions::desc_level_e1);
-	OptionGroup lookback("Clasp.Lookback Options", Potassco::ProgramOptions::desc_level_e1);
-	configOpts.addOption(*opts_->begin());
-	configOpts.addOption(*(opts_->end()-1));
-	for (Options::option_iterator it = opts_->begin() + 1, end = opts_->end() - 1; it != end; ++it) {
-		int oId = static_cast(it->get()->value())->option();
-		if      (isGlobalOption(oId))               { configOpts.addOption(*it);}
-		else if (oId < option_category_context_end) { ctxOpts.addOption(*it); }
-		else if (oId < opt_no_lookback)             { search.addOption(*it); }
-		else if (oId < option_category_solver_end)  { lookback.addOption(*it); }
-		else if (oId < opt_restarts)                { search.addOption(*it); }
-		else if (oId < option_category_search_end)  { lookback.addOption(*it); }
-		else if (oId < option_category_asp_end)     { aspOpts.addOption(*it); }
-		else                                        { solving.addOption(*it); }
-	}
-	root.add(configOpts).add(ctxOpts).add(aspOpts).add(solving).add(search).add(lookback);
-	root.addAlias("number", root.find("models")); // remove on next version
-	root.addAlias("opt-sat", root.find("parse-maxsat")); // remove on next version
+    createOptions();
+    using namespace Potassco::ProgramOptions;
+    OptionGroup configOpts("Clasp.Config Options");
+    OptionGroup ctxOpts("Clasp.Context Options", Potassco::ProgramOptions::desc_level_e1);
+    OptionGroup solving("Clasp.Solving Options");
+    OptionGroup aspOpts("Clasp.ASP Options", Potassco::ProgramOptions::desc_level_e1);
+    OptionGroup search("Clasp.Search Options", Potassco::ProgramOptions::desc_level_e1);
+    OptionGroup lookback("Clasp.Lookback Options", Potassco::ProgramOptions::desc_level_e1);
+    configOpts.addOption(*opts_->begin());
+    configOpts.addOption(*(opts_->end() - 1));
+    for (const auto& o : std::ranges::subrange(opts_->begin() + 1, opts_->end() - 1)) {
+        if (int oId = static_cast(o->value())->option(); isGlobalOption(oId)) {
+            configOpts.addOption(o);
+        }
+        else if (oId < option_category_context_end) {
+            ctxOpts.addOption(o);
+        }
+        else if (oId < option_category_search_end) {
+            if (oId < opt_no_lookback || (oId >= option_category_solver_end && oId < opt_restarts)) {
+                search.addOption(o);
+            }
+            else {
+                lookback.addOption(o);
+            }
+        }
+        else if (oId < option_category_asp_end) {
+            aspOpts.addOption(o);
+        }
+        else {
+            solving.addOption(o);
+        }
+    }
+
+    root.add(configOpts).add(ctxOpts).add(aspOpts).add(solving).add(search).add(lookback);
+    root.addAlias("number", root.find("models"));        // remove on next version
+    root.addAlias("opt-sat", root.find("parse-maxsat")); // remove on next version
 }
 bool ClaspCliConfig::assignDefaults(const Potassco::ProgramOptions::ParsedOptions& exclude) {
-	for (Options::option_iterator it = opts_->begin(), end = opts_->end(); it != end; ++it) {
-		const Potassco::ProgramOptions::Option& o = **it;
-		POTASSCO_REQUIRE(exclude.count(o.name()) != 0 || o.assignDefault(), "Option '%s': invalid default value '%s'\n", o.name().c_str(), o.value()->defaultsTo());
-	}
-	return true;
-}
-void ClaspCliConfig::releaseOptions() {
-	opts_ = 0;
-}
+    for (const auto& it : *opts_) {
+        const Potassco::ProgramOptions::Option& o = *it;
+        POTASSCO_CHECK_PRE(exclude.contains(o.name()) || o.assignDefault(), "Option '%s': invalid default value '%s'\n",
+                           o.name().c_str(), o.value()->defaultsTo());
+    }
+    return true;
+}
+void        ClaspCliConfig::releaseOptions() { opts_ = nullptr; }
 static bool matchPath(const char*& path, const char* what) {
-	std::size_t wLen = std::strlen(what);
-	if (strncmp(path, what, wLen) != 0 || (path[wLen] && path[wLen++] != '.')) {
-		return false;
-	}
-	path += wLen;
-	return true;
-}
-ClaspCliConfig::KeyType ClaspCliConfig::getKey(KeyType k, const char* path) const {
-	int16 id = decodeKey(k);
-	if (!isValidId(id) || !path || !*path || (*path == '.' && !*++path)) {
-		return k;
-	}
-	if (isLeafId(id)){ return KEY_INVALID; }
-	NodeKey nk = getNode(id);
-	for (int16 sk = nk.skBeg; sk < 0; ++sk) {
-		if (matchPath(path, getNode(sk).name)) {
-			KeyType ret = makeKeyHandle(sk, (sk == key_tester ? mode_tester : 0) | decodeMode(k), 0);
-			if (!*path) { return ret; }
-			return getKey(ret, path);
-		}
-	}
-	uint8 mode = decodeMode(k);
-	if (id == key_solver) {
-		uint32 solverId;
-		if (!isSolver(mode) && *path != '.' && Potassco::xconvert(path, solverId, &path, 0) == 1) {
-			return getKey(makeKeyHandle(id, mode | mode_solver, std::min(solverId, (uint32)uint8(-1))), path);
-		}
-		mode |= mode_solver;
-	}
-	int16 opt = findOption(path, false);
-	// remaining name must be a valid option in our subkey range
-	if (opt < 0 || opt < nk.skBeg || opt >= static_cast(nk.skBeg + nk.skSize)) {
-		return KEY_INVALID;
-	}
-	return makeKeyHandle(opt, mode, decodeSolver(k));
+    std::size_t wLen = std::strlen(what);
+    if (strncmp(path, what, wLen) != 0 || (path[wLen] && path[wLen++] != '.')) {
+        return false;
+    }
+    path += wLen;
+    return true;
+}
+// NOLINTNEXTLINE(misc-no-recursion)
+ClaspCliConfig::KeyType ClaspCliConfig::getKey(KeyType key, const char* name) const {
+    int16_t id = decodeKey(key);
+    if (not isValidId(id) || not name || !*name || (*name == '.' && !*++name)) {
+        return key;
+    }
+    if (isLeafId(id)) {
+        return key_invalid;
+    }
+    NodeKey nk = getNode(id);
+    for (int16_t sk = nk.skBeg; sk < 0; ++sk) {
+        if (matchPath(name, getNode(sk).name)) {
+            KeyType ret = makeKeyHandle(sk, (sk == id_tester ? mode_tester : 0) | decodeMode(key), 0);
+            if (!*name) {
+                return ret;
+            }
+            return getKey(ret, name);
+        }
+    }
+    uint8_t mode = decodeMode(key);
+    if (id == id_solver) {
+        if (not isSolver(mode) && std::isdigit(static_cast(*name))) {
+            uint32_t solverId;
+            if (auto ret = Potassco::fromChars(name, solverId); ret.ec == std::errc{}) {
+                return getKey(
+                    makeKeyHandle(id, mode | mode_solver, std::min(solverId, static_cast(UINT8_MAX))),
+                    ret.ptr);
+            }
+        }
+        mode |= mode_solver;
+    }
+    int16_t opt = findOption(name, false);
+    // remaining name must be a valid option in our subkey range
+    if (opt < 0 || opt < nk.skBeg || opt >= static_cast(nk.skBeg + nk.skSize)) {
+        return key_invalid;
+    }
+    return makeKeyHandle(opt, mode, decodeSolver(key));
 }
 
 ClaspCliConfig::KeyType ClaspCliConfig::getArrKey(KeyType k, unsigned i) const {
-	int16 id = decodeKey(k);
-	if (id != key_solver || isSolver(decodeMode(k)) || i >= solve.supportedSolvers()) { return KEY_INVALID; }
-	return makeKeyHandle(id, decodeMode(k) | mode_solver, i);
+    int16_t id = decodeKey(k);
+    if (id != id_solver || isSolver(decodeMode(k)) || i >= solve.supportedSolvers()) {
+        return key_invalid;
+    }
+    return makeKeyHandle(id, decodeMode(k) | mode_solver, i);
 }
 int ClaspCliConfig::getKeyInfo(KeyType k, int* nSubkeys, int* arrLen, const char** help, int* nValues) const {
-	int16 id = decodeKey(k);
-	int ret  = 0;
-	if (!isValidId(id)){ return -1; }
-	if (isLeafId(id)){
-		if (nSubkeys && ++ret) { *nSubkeys = 0;  }
-		if (arrLen && ++ret)   { *arrLen   = -1; }
-		if (nValues && ++ret)  { *nValues  = static_cast( !isTester(decodeMode(k)) || testerConfig() != 0 ); }
-		if (help && ++ret)     { *help = getNode(id).desc; }
-		return ret;
-	}
-	NodeKey x = getNode(id);
-	if (nSubkeys && ++ret) { *nSubkeys = x.skSize; }
-	if (nValues && ++ret)  { *nValues = -1; }
-	if (help && ++ret)     { *help = x.desc; }
-	if (arrLen && ++ret)   {
-		*arrLen = -1;
-		if (id == key_solver && !isSolver(decodeMode(k))) {
-			const UserConfig* c = active(this, decodeMode(k));
-			*arrLen = c ? (int)c->numSolver() : 0;
-		}
-	}
-	return ret;
+    int16_t id  = decodeKey(k);
+    int     ret = 0;
+    if (not isValidId(id)) {
+        return -1;
+    }
+    if (isLeafId(id)) {
+        if (nSubkeys && ++ret) {
+            *nSubkeys = 0;
+        }
+        if (arrLen && ++ret) {
+            *arrLen = -1;
+        }
+        if (nValues && ++ret) {
+            *nValues = static_cast(not isTester(decodeMode(k)) || testerConfig() != nullptr);
+        }
+        if (help && ++ret) {
+            *help = getNode(id).desc;
+        }
+        return ret;
+    }
+    NodeKey x = getNode(id);
+    if (nSubkeys && ++ret) {
+        *nSubkeys = x.skSize;
+    }
+    if (nValues && ++ret) {
+        *nValues = -1;
+    }
+    if (help && ++ret) {
+        *help = x.desc;
+    }
+    if (arrLen && ++ret) {
+        *arrLen = -1;
+        if (id == id_solver && not isSolver(decodeMode(k))) {
+            const UserConfig* c = active(this, decodeMode(k));
+            *arrLen             = c ? static_cast(c->numSolver()) : 0;
+        }
+    }
+    return ret;
 }
 bool ClaspCliConfig::isLeafKey(KeyType k) { return isLeafId(decodeKey(k)); }
-const char* ClaspCliConfig::getSubkey(KeyType k, uint32 i) const {
-	int16 id = decodeKey(k);
-	if (!isValidId(id) || isLeafId(id)) { return 0; }
-	NodeKey nk = getNode(id);
-	if (i >= nk.skSize) { return 0; }
-	return getNode(static_cast(nk.skBeg + i)).name;
+// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
+const char* ClaspCliConfig::getSubkey(KeyType k, uint32_t i) const {
+    int16_t id = decodeKey(k);
+    if (not isValidId(id) || isLeafId(id)) {
+        return nullptr;
+    }
+    auto nk = getNode(id);
+    if (i >= nk.skSize) {
+        return nullptr;
+    }
+    return getNode(static_cast(static_cast(i) + nk.skBeg)).name;
 }
 int ClaspCliConfig::getValue(KeyType key, std::string& out) const {
-	try {
-		const UserConfig* base = active(this, decodeMode(key));
-		int16 o = decodeKey(key);
-		int   r = isLeafId(o) && base ? 1 : -1;
-		if (r > 0 && isOption(o)) {
-			POTASSCO_ASSERT(base == this || isTesterOption(o));
-			uint32 sId = decodeSolver(key);
-			const SolverParams* solver = &base->solver(sId);
-			const SolveParams*  search = &base->search(sId);
-			// helper macros used in get
-			using Potassco::toString; using Potassco::off; using Potassco::Set;
-			#define GET_FUN(x)     Potassco::StringRef x(out); if (!x.out);else
-			#define GET(...)       out = toString( __VA_ARGS__ )
-			#define GET_IF(c, ...) out = ITE((c), toString(__VA_ARGS__), toString(off))
-			switch(static_cast(o)) {
-				default: POTASSCO_ASSERT(false, "invalid option");
-				#define OPTION(k, e, a, h, _, GET) case opt_##k: { GET ; } break;
-				#define CLASP_ALL_GROUPS
-				#include 
-			}
-			#undef GET_FUN
-			#undef GET
-			#undef GET_IF
-		}
-		else if (r > 0 && o == meta_config) {
-			if (base->cliConfig < config_max_value) { xconvert(out, static_cast(base->cliConfig)); }
-			else                                    { out.append(config_[base == testerConfig()]); }
-		}
-		return r > 0 ? static_cast(out.length()) : r;
-	}
-	catch (...) { return -2; }
+    try {
+        const UserConfig* base = active(this, decodeMode(key));
+        int16_t           o    = decodeKey(key);
+        int               r    = isLeafId(o) && base ? 1 : -1;
+        if (r > 0 && isOption(o)) {
+            POTASSCO_ASSERT(base == this || isTesterOption(o));
+            uint32_t            sId    = decodeSolver(key);
+            const SolverParams* solver = &base->solver(sId);
+            const SolveParams*  search = &base->search(sId);
+            // helper macros used in get
+            using Potassco::off;
+            using Potassco::Set;
+            using Potassco::toString;
+#define GET_FUN(x)                                                                                                     \
+    if (Potassco::StringRef x(out); false)                                                                             \
+        ;                                                                                                              \
+    else
+#define GET(...)       out = toString(__VA_ARGS__)
+#define GET_IF(c, ...) out = ITE((c), toString(__VA_ARGS__), toString(off))
+            switch (static_cast(o)) {
+                default: POTASSCO_ASSERT(false, "invalid option");
+#define OPTION(k, e, a, h, _, GET)                                                                                     \
+    case opt_##k: {                                                                                                    \
+        GET;                                                                                                           \
+    } break;
+#define CLASP_ALL_GROUPS
+#include 
+            }
+#undef GET_FUN
+#undef GET
+#undef GET_IF
+        }
+        else if (r > 0 && o == meta_config) {
+            if (base->cliConfig < config_max_value) {
+                toChars(out, static_cast(base->cliConfig));
+            }
+            else {
+                out.append(config_[base == testerConfig()]);
+            }
+        }
+        return r > 0 ? static_cast(out.length()) : r;
+    }
+    catch (...) {
+        return -2;
+    }
 }
 int ClaspCliConfig::getValue(KeyType key, char* buffer, std::size_t bufSize) const {
-	std::string temp;
-	int ret = getValue(key, temp);
-	if (ret <= 0) { return ret; }
-	if (buffer && bufSize) {
-		std::size_t n = temp.length() >= bufSize ? bufSize - 1 : temp.length();
-		std::memcpy(buffer, temp.c_str(), n * sizeof(char));
-		buffer[n] = 0;
-	}
-	return static_cast(temp.length());
+    std::string temp;
+    int         ret = getValue(key, temp);
+    if (ret <= 0) {
+        return ret;
+    }
+    if (buffer && bufSize) {
+        std::size_t n = temp.length() >= bufSize ? bufSize - 1 : temp.length();
+        std::memcpy(buffer, temp.c_str(), n * sizeof(char));
+        buffer[n] = 0;
+    }
+    return static_cast(temp.length());
 }
 std::string ClaspCliConfig::getValue(const char* path) const {
-	std::string temp;
-	POTASSCO_REQUIRE(getValue(getKey(KEY_ROOT, path), temp) >= 0, "Invalid key: '%s'", path);
-	return temp;
+    std::string temp;
+    POTASSCO_CHECK_PRE(getValue(getKey(key_root, path), temp) >= 0, "Invalid key: '%s'", path);
+    return temp;
 }
 bool ClaspCliConfig::hasValue(const char* path) const {
-	int nVals;
-	return getKeyInfo(getKey(KEY_ROOT, path), 0, 0, 0, &nVals) == 1 && nVals > 0;
+    int nVals;
+    return getKeyInfo(getKey(key_root, path), nullptr, nullptr, nullptr, &nVals) == 1 && nVals > 0;
 }
 
 int ClaspCliConfig::setValue(KeyType key, const char* value) {
-	int16 id = decodeKey(key);
-	if (!isLeafId(id)) { return -1; }
-	try {
-		uint8 mode = decodeMode(key);
-		validate_  = true;
-		if (isTester(mode)) {
-			addTesterConfig();
-		}
-		if (isOption(id)) {
-			return setOption(id, mode, decodeSolver(key), value);
-		}
-		else {
-			int sz = setAppOpt(id, mode, value);
-			if (sz <= 0) { return 0; }
-			std::string m;
-			UserConfig* act = active(this, mode);
-			ConfigIter  it  = getConfig(act->cliConfig, m);
-			act->hasConfig  = 0;
-			mode |= mode_relaxed;
-			act->resize(1, 1);
-			for (uint32 sId = 0; it.valid(); it.next()) {
-				if (!setConfig(it, mode, sId, ParsedOpts(), 0)){ return 0; }
-				if (++sId == static_cast(sz))          { break; }
-				mode |= mode_solver;
-			}
-			if (sz < 65 && static_cast(sz) > act->numSolver()) {
-				for (uint32 sId = act->numSolver(), mod = sId, end = static_cast(sz); sId != end; ++sId) {
-					SolverParams& solver = act->addSolver(sId);
-					SolveParams&  search = act->addSearch(sId);
-					(solver = act->solver(sId % mod)).setId(sId);
-					search = act->search(sId % mod);
-				}
-			}
-			act->hasConfig = 1;
-			return 1;
-		}
-	}
-	catch (...) {
-		return -2;
-	}
+    int16_t id = decodeKey(key);
+    if (not isLeafId(id)) {
+        return -1;
+    }
+    try {
+        uint8_t mode = decodeMode(key);
+        validate_    = true;
+        if (isTester(mode)) {
+            addTesterConfig();
+        }
+        if (isOption(id)) {
+            return setOption(id, mode, decodeSolver(key), value);
+        }
+        int sz = setAppOpt(id, mode, value);
+        if (sz <= 0) {
+            return 0;
+        }
+        std::string m;
+        UserConfig* act  = active(this, mode);
+        ConfigIter  it   = getConfig(act->cliConfig, m);
+        act->hasConfig   = 0;
+        mode            |= mode_relaxed;
+        act->resize(1, 1);
+        for (uint32_t sId = 0; it.valid(); it.next()) {
+            if (not setConfig(it, mode, sId, ParsedOpts(), nullptr)) {
+                return 0;
+            }
+            if (++sId == static_cast(sz)) {
+                break;
+            }
+            mode |= mode_solver;
+        }
+        if (sz < 65 && static_cast(sz) > act->numSolver()) {
+            for (uint32_t sId = act->numSolver(), mod = sId, end = static_cast(sz); sId != end; ++sId) {
+                SolverParams& solver = act->addSolver(sId);
+                SolveParams&  search = act->addSearch(sId);
+                (solver = act->solver(sId % mod)).setId(sId);
+                search = act->search(sId % mod);
+            }
+        }
+        act->hasConfig = 1;
+        return 1;
+    }
+    catch (...) {
+        return -2;
+    }
 }
 
 bool ClaspCliConfig::setValue(const char* path, const char* value) {
-	int ret = setValue(getKey(KEY_ROOT, path), value);
-	POTASSCO_REQUIRE(ret >= 0, (ret == -1 ? "Invalid or incomplete key: '%s'" : "Value error in key: '%s'"), path);
-	return ret != 0;
-}
-int ClaspCliConfig::setOption(int option, uint8 mode_, uint32 sId, const char* _val_) {
-	if (!_val_ || (isTester(mode_) && !isTesterOption(option)) || (isSolver(mode_) && !isSolverOption(option))) {
-		return mode_ & mode_relaxed ? 1 : -1;
-	}
-	BasicSatConfig* base = active(this, mode_);
-	SolverParams* solver = isSolverOption(option) ? &base->addSolver(sId) : 0;
-	SolveParams*  search = isSolverOption(option) ? &base->addSearch(sId) : 0;
-	// action & helper macros used in set
-	using Potassco::stringTo; using Potassco::opt; using Potassco::Set;
-	int ret = 1;
-	try {
-		#define FUN(x)           for (Potassco::ArgString x = _val_;;)
-		#define STORE(obj)       { return stringTo((_val_), obj); }
-		#define STORE_LEQ(x, y)  { unsigned _n; return stringTo(_val_, _n) && SET_LEQ(x, _n, y); }
-		#define STORE_FLAG(x)    { bool _b; return stringTo(_val_, _b) && SET(x, static_cast(_b)); }
-		#define STORE_OR_FILL(x) { unsigned _n; return stringTo(_val_, _n) && SET_OR_FILL(x, _n); }
-		#define STORE_U(E, x)    { E _e; return stringTo((_val_), _e) && SET(x, static_cast(_e));}
-		switch (static_cast(option)) {
-			default: POTASSCO_ASSERT(false, "invalid option");
-			#define OPTION(k, e, a, d, SET, ...) case opt_##k: { SET ; } break;
-			#define CLASP_ALL_GROUPS
-			#include 
-		}
-		#undef FUN
-		#undef STORE
-		#undef STORE_LEQ
-		#undef STORE_FLAG
-		#undef STORE_OR_FILL
-		#undef STORE_U
-	}
-	catch (...) {
-		ret = 0;
-	}
-	return ret;
+    int ret = setValue(getKey(key_root, path), value);
+    POTASSCO_CHECK_PRE(ret >= 0, (ret == -1 ? "Invalid or incomplete key: '%s'" : "Value error in key: '%s'"), path);
+    return ret != 0;
 }
 
-int ClaspCliConfig::setAppOpt(int o, uint8 mode, const char* val) {
-	if (o == meta_config) {
-		std::pair defC(config_default, INT_MAX);
-		if (Potassco::stringTo(val, defC)) { active(this, mode)->cliConfig = (uint8)defC.first; }
-		else {
-			POTASSCO_EXPECT(std::ifstream(val).is_open(), "Could not open config file '%s'", val);
-			config_[isTester(mode)] = val;
-			active(this, mode)->cliConfig = config_max_value + isTester(mode);
-		}
-		return Range(0, INT_MAX).clamp(defC.second);
-	}
-	else if (o == meta_tester && !isTester(mode)) {
-		addTesterConfig();
-		ParsedOpts ex;
-		bool ret = setConfig("", val, mode_tester | mode_meta, 0, ParsedOpts(), &ex);
-		return ret && finalizeAppConfig(mode_tester, finalizeParsed(mode_tester, ex, ex), Problem_t::Asp, true);
-	}
-	return -1; // invalid option
-}
-bool ClaspCliConfig::setAppDefaults(ConfigKey config, uint8 mode, const ParsedOpts& seen, ProblemType t) {
-	std::string mem;
-	if (t != Problem_t::Asp && !seen.count(getOptionName(opt_sat_prepro, mem))) {
-		POTASSCO_REQUIRE(setOption(opt_sat_prepro, mode, 0, "2,iter=20,occ=25,time=120"));
-	}
-	if (!isTester(mode) && config == config_many && t == Problem_t::Asp) {
-		POTASSCO_REQUIRE(seen.count(getOptionName(opt_eq, mem)) || setOption(opt_eq, mode, 0, "3"));
-		POTASSCO_REQUIRE(seen.count(getOptionName(opt_trans_ext, mem)) || setOption(opt_trans_ext, mode, 0, "dynamic"));
-	}
-	if (config != config_nolearn && active(this, mode)->solver(0).search == SolverParams::no_learning) {
-		POTASSCO_REQUIRE(setConfig(getConfig(config_nolearn), mode | mode_relaxed, 0, seen, 0));
-	}
-	return true;
+int ClaspCliConfig::setOption(int option, uint8_t setMode, uint32_t sId, const char* _val_) {
+    if (not _val_ || (isTester(setMode) && not isTesterOption(option)) ||
+        (isSolver(setMode) && not isSolverOption(option))) {
+        return setMode & mode_relaxed ? 1 : -1;
+    }
+    BasicSatConfig* base   = active(this, setMode);
+    SolverParams*   solver = isSolverOption(option) ? &base->addSolver(sId) : nullptr;
+    SolveParams*    search = isSolverOption(option) ? &base->addSearch(sId) : nullptr;
+    // action & helper macros used in set
+    using Potassco::opt;
+    using Potassco::Set;
+    using Potassco::stringTo;
+    int ret = 1;
+    try {
+        unsigned _n;
+        bool     _b;
+#define FUN(x)           for (Potassco::ArgString x{_val_};;)
+#define STORE(obj)       return ok(stringTo((_val_), obj));
+#define STORE_LEQ(x, y)  return ok(stringTo(_val_, _n)) && SET_LEQ(x, _n, y);
+#define STORE_FLAG(x)    return ok(stringTo(_val_, _b)) && SET(x, static_cast(_b));
+#define STORE_OR_FILL(x) return ok(stringTo(_val_, _n)) && SET_OR_FILL(x, _n);
+#define STORE_U(E, x)                                                                                                  \
+    {                                                                                                                  \
+        E _e;                                                                                                          \
+        return ok(stringTo((_val_), _e)) && SET(x, static_cast(_e));                                         \
+    }
+
+        switch (static_cast(option)) {
+            default: POTASSCO_ASSERT(false, "invalid option");
+#define OPTION(k, e, a, d, SET, ...)                                                                                   \
+    case opt_##k: {                                                                                                    \
+        SET;                                                                                                           \
+    } break;
+#define CLASP_ALL_GROUPS
+#include 
+        }
+#undef FUN
+#undef STORE
+#undef STORE_LEQ
+#undef STORE_FLAG
+#undef STORE_OR_FILL
+#undef STORE_U
+    }
+    catch (...) {
+        ret = 0;
+    }
+    return ret;
 }
 
-bool ClaspCliConfig::setConfig(const char* name, const char* args, uint8 mode, uint32 sId, const ParsedOpts& exclude, ParsedOpts* out) {
-	createOptions();
-	ParseContext ctx(*this, name, &exclude, mode, sId, out);
-	Potassco::ProgramOptions::parseCommandString(args, ctx, Potassco::ProgramOptions::command_line_allow_flag_value);
-	return true;
-}
-bool ClaspCliConfig::setConfig(const ConfigIter& config, uint8 mode, uint32 sId, const ParsedOpts& exclude, ParsedOpts* out) {
-	if (*config.base()) {
-		ConfigKey baseK = config_default;
-		POTASSCO_REQUIRE(Potassco::stringTo(config.base(), baseK), "%s: '%s': Invalid base config!", config.name(), config.base());
-		ConfigIter base = getConfig(baseK);
-		if (!setConfig(base.name(), base.args(), mode | mode_solver, sId, exclude, out)) {
-			return false;
-		}
-	}
-	return setConfig(config.name(), config.args(), mode, sId, exclude, out);
+int ClaspCliConfig::setAppOpt(int o, uint8_t mode, const char* val) {
+    if (o == meta_config) {
+        std::pair defC(config_default, INT_MAX);
+        if (ok(Potassco::stringTo(val, defC))) {
+            active(this, mode)->cliConfig = static_cast(defC.first);
+        }
+        else {
+            POTASSCO_CHECK(std::ifstream(val).is_open(), std::errc::no_such_file_or_directory,
+                           "Could not open config file '%s'", val);
+            config_[isTester(mode)]       = val;
+            active(this, mode)->cliConfig = config_max_value + isTester(mode);
+        }
+        return Clasp::saturate_cast(defC.second);
+    }
+    if (o == meta_tester && not isTester(mode)) {
+        addTesterConfig();
+        ParsedOpts ex;
+        bool       ret = setConfig("", val, mode_tester | mode_meta, 0, ParsedOpts(), &ex);
+        return ret && finalizeAppConfig(mode_tester, finalizeParsed(mode_tester, ex, ex), ProblemType::asp, true);
+    }
+    return -1; // invalid option
+}
+bool ClaspCliConfig::setAppDefaults(ConfigKey config, uint8_t mode, const ParsedOpts& seen, ProblemType t) {
+    std::string mem;
+    if (t != ProblemType::asp && not seen.contains(getOptionName(opt_sat_prepro, mem))) {
+        POTASSCO_CHECK_PRE(setOption(opt_sat_prepro, mode, 0, "2,iter=20,occ=25,time=120"));
+    }
+    if (not isTester(mode) && config == config_many && t == ProblemType::asp) {
+        POTASSCO_CHECK_PRE(seen.contains(getOptionName(opt_eq, mem)) || setOption(opt_eq, mode, 0, "3"));
+        POTASSCO_CHECK_PRE(seen.contains(getOptionName(opt_trans_ext, mem)) ||
+                           setOption(opt_trans_ext, mode, 0, "dynamic"));
+    }
+    if (config != config_nolearn && active(this, mode)->solver(0).search == SolverParams::no_learning) {
+        POTASSCO_CHECK_PRE(setConfig(getConfig(config_nolearn), mode | mode_relaxed, 0, seen, nullptr));
+    }
+    return true;
+}
+
+bool ClaspCliConfig::setConfig(const char* name, const char* args, uint8_t mode, uint32_t sId,
+                               const ParsedOpts& exclude, ParsedOpts* out) {
+    createOptions();
+    ParseContext ctx(*this, name, &exclude, mode, sId, out);
+    parseCommandString(args, ctx, Potassco::ProgramOptions::command_line_allow_flag_value);
+    return true;
+}
+bool ClaspCliConfig::setConfig(const ConfigIter& config, uint8_t mode, uint32_t sId, const ParsedOpts& exclude,
+                               ParsedOpts* out) {
+    if (*config.base()) {
+        ConfigKey baseK = config_default;
+        POTASSCO_CHECK_PRE(ok(Potassco::stringTo(config.base(), baseK)), "%s: '%s': Invalid base config!",
+                           config.name(), config.base());
+        if (ConfigIter base = getConfig(baseK);
+            not setConfig(base.name(), base.args(), mode | mode_solver, sId, exclude, out)) {
+            return false;
+        }
+    }
+    return setConfig(config.name(), config.args(), mode, sId, exclude, out);
 }
 bool ClaspCliConfig::validate() {
-	UserConfiguration* arr[3] = { this, testerConfig(), 0 };
-	UserConfiguration** c     = arr;
-	const char* ctx = *c == this ? "config":"tester";
-	const char* err = 0;
-	do {
-		for (uint32 i = 0; i != (*c)->numSolver(); ++i) {
-			POTASSCO_REQUIRE((err = Clasp::Cli::validate((*c)->solver(i), (*c)->search(i))) == 0, "<%s>.%u: %s", ctx, i, err);
-		}
-	} while (*++c);
-	validate_ = false;
-	return true;
+    UserConfiguration*  arr[3] = {this, testerConfig(), nullptr};
+    UserConfiguration** c      = arr;
+    const char*         ctx    = *c == this ? "config" : "tester";
+    const char*         err    = nullptr;
+    do {
+        for (uint32_t i : irange((*c)->numSolver())) {
+            POTASSCO_CHECK_PRE((err = Clasp::Cli::validate((*c)->solver(i), (*c)->search(i))) == nullptr, "<%s>.%u: %s",
+                               ctx, i, err);
+        }
+    } while (*++c);
+    validate_ = false;
+    return true;
 }
 
 bool ClaspCliConfig::finalize(const ParsedOpts& x, ProblemType t, bool defs) {
-	ParsedOpts temp;
-	return finalizeAppConfig(0, finalizeParsed(0, x, temp), t, defs)
-		&& finalizeAppConfig(mode_tester, ParsedOpts(), Problem_t::Asp, true);
+    ParsedOpts temp;
+    return finalizeAppConfig(0, finalizeParsed(0, x, temp), t, defs) &&
+           finalizeAppConfig(mode_tester, ParsedOpts(), ProblemType::asp, true);
 }
 
-void ClaspCliConfig::addDisabled(ParsedOpts& parsed) {
-	finalizeParsed(0, parsed, parsed);
-}
+void ClaspCliConfig::addDisabled(ParsedOpts& parsed) { finalizeParsed(0, parsed, parsed); }
 
 const std::string& ClaspCliConfig::getOptionName(int o, std::string& mem) const {
-	POTASSCO_ASSERT(isOption(o));
-	if (opts_.get()) {
-		return (opts_->begin() + o)->get()->name();
-	}
-	keyToCliName(mem, getNode(static_cast(o)).name, "");
-	return mem;
+    POTASSCO_ASSERT(isOption(o));
+    if (opts_.get()) {
+        return (opts_->begin() + o)->get()->name();
+    }
+    keyToCliName(mem, getNode(static_cast(o)).name, "");
+    return mem;
 }
 
-const ClaspCliConfig::ParsedOpts& ClaspCliConfig::finalizeParsed(uint8 mode, const ParsedOpts& parsed, ParsedOpts& exclude) const {
-	const ParsedOpts* ret = &parsed;
-	std::string mem;
-	if (active(this, mode)->search(0).reduce.fReduce() == 0 && parsed.count(getOptionName(opt_deletion, mem)) != 0) {
-		if (ret != &exclude) { exclude = parsed; }
-		exclude.add(getOptionName(opt_del_cfl, mem));
-		exclude.add(getOptionName(opt_del_max, mem));
-		exclude.add(getOptionName(opt_del_grow, mem));
-		ret = &exclude;
-	}
-	return *ret;
+const ClaspCliConfig::ParsedOpts& ClaspCliConfig::finalizeParsed(uint8_t mode, const ParsedOpts& parsed,
+                                                                 ParsedOpts& exclude) const {
+    const ParsedOpts* ret = &parsed;
+    std::string       mem;
+    if (active(this, mode)->search(0).reduce.fReduce() == 0 && parsed.contains(getOptionName(opt_deletion, mem))) {
+        if (ret != &exclude) {
+            exclude = parsed;
+        }
+        exclude.add(getOptionName(opt_del_cfl, mem));
+        exclude.add(getOptionName(opt_del_max, mem));
+        exclude.add(getOptionName(opt_del_grow, mem));
+        ret = &exclude;
+    }
+    return *ret;
 }
 
-bool ClaspCliConfig::finalizeAppConfig(uint8 mode, const ParsedOpts& parsed, ProblemType t, bool defs) {
-	UserConfig* config = active(this, mode);
-	if (!config || config->hasConfig) { return true; }
-	SolverParams defSolver = config->solver(0);
-	SolveParams  defSearch = config->search(0);
-	uint8 c = config->cliConfig;
-	if (c == config_many && solve.numSolver() == 1) { c = config_default; }
-	if (c == config_default) {
-		if      (defSolver.search == SolverParams::no_learning)       { c = config_nolearn; }
-		else if (isTester(mode))                                      { c = config_tester_default; }
-		else if (solve.numSolver() == 1 || !solve.defaultPortfolio()) { c = t == Problem_t::Asp ? (uint8)config_asp_default : (uint8)config_sat_default; }
-		else                                                          { c = config_many; }
-	}
-	if (defs && !setAppDefaults(static_cast(c), mode, parsed, t)) { return false; }
-	std::string m;
-	ConfigIter conf = getConfig(c, m);
-	mode           |= mode_relaxed;
-	const char* ctx = isTester(mode) ? "tester" : "config", *err = 0;
-	for (uint32 i = 0; i != solve.numSolver() && conf.valid(); ++i) {
-		SolverParams& solver = (config->addSolver(i) = defSolver).setId(i);
-		SolveParams&  search = (config->addSearch(i) = defSearch);
-		if (!setConfig(conf, mode, i, parsed, 0)) {
-			return false;
-		}
-		POTASSCO_REQUIRE((err = Clasp::Cli::validate(solver, search)) == 0, "<%s>.%s : %s", ctx, conf.name(), err);
-		conf.next();
-		mode |= mode_solver;
-	}
-	config->hasConfig = 1;
-	return true;
+bool ClaspCliConfig::finalizeAppConfig(uint8_t mode, const ParsedOpts& parsed, ProblemType t, bool defs) {
+    UserConfig* config = active(this, mode);
+    if (not config || config->hasConfig) {
+        return true;
+    }
+    auto    defSolver = config->solver(0);
+    auto    defSearch = config->search(0);
+    uint8_t c         = config->cliConfig;
+    if (c == config_many && solve.numSolver() == 1) {
+        c = config_default;
+    }
+    if (c == config_default) {
+        if (defSolver.search == SolverParams::no_learning) {
+            c = config_nolearn;
+        }
+        else if (isTester(mode)) {
+            c = config_tester_default;
+        }
+        else if (solve.numSolver() == 1 || not solve.defaultPortfolio()) {
+            c = static_cast(t == ProblemType::asp ? config_asp_default : config_sat_default);
+        }
+        else {
+            c = config_many;
+        }
+    }
+    if (defs && not setAppDefaults(static_cast(c), mode, parsed, t)) {
+        return false;
+    }
+    std::string m;
+    ConfigIter  conf  = getConfig(c, m);
+    mode             |= mode_relaxed;
+    const char *ctx = isTester(mode) ? "tester" : "config", *err = nullptr;
+    for (uint32_t i = 0; i != solve.numSolver() && conf.valid(); ++i) {
+        SolverParams& solver = (config->addSolver(i) = defSolver).setId(i);
+        SolveParams&  search = (config->addSearch(i) = defSearch);
+        if (not setConfig(conf, mode, i, parsed, nullptr)) {
+            return false;
+        }
+        POTASSCO_CHECK_PRE((err = Clasp::Cli::validate(solver, search)) == nullptr, "<%s>.%s : %s", ctx, conf.name(),
+                           err);
+        conf.next();
+        mode |= mode_solver;
+    }
+    config->hasConfig = 1;
+    return true;
 }
 
 bool ClaspCliConfig::setAppConfig(const std::string& args, ProblemType t) {
-	Potassco::ProgramOptions::ParsedOptions exclude;
-	reset();
-	return setConfig("setConfig", args.c_str(), mode_meta, 0, exclude, &exclude) && assignDefaults(exclude) && finalize(exclude, t, true);
+    Potassco::ProgramOptions::ParsedOptions exclude;
+    reset();
+    return setConfig("setConfig", args.c_str(), mode_meta, 0, exclude, &exclude) && assignDefaults(exclude) &&
+           finalize(exclude, t, true);
 }
 
 const char* validate(const SolverParams& solver, const SolveParams& search) {
-	const ReduceParams& reduce = search.reduce;
-	if (solver.search == SolverParams::no_learning) {
-		if (Heuristic_t::isLookback(solver.heuId)) return "Heuristic requires lookback strategy!";
-		if (!search.restart.disabled()) return "'no-lookback': restart options disabled!";
-		if (!reduce.cflSched.disabled() || (!reduce.growSched.disabled() && !reduce.growSched.defaulted()) || search.reduce.fReduce() != 0) return "'no-lookback': deletion options disabled!";
-	}
-	bool hasSched = !reduce.cflSched.disabled() || !reduce.growSched.disabled() || reduce.maxRange != UINT32_MAX;
-	if (hasSched  && reduce.fReduce() == 0.0f && !reduce.growSched.defaulted()) return "'no-deletion': deletion strategies disabled!";
-	if (!hasSched && reduce.fReduce() != 0.0f && !reduce.growSched.defaulted()) return "'deletion': deletion strategy required!";
-	return 0;
-}
-}}
+    const ReduceParams& reduce = search.reduce;
+    if (solver.search == SolverParams::no_learning) {
+        if (isLookbackHeuristic(solver.heuId)) {
+            return "Heuristic requires lookback strategy!";
+        }
+        if (not search.restart.disabled()) {
+            return "'no-lookback': restart options disabled!";
+        }
+        if (not reduce.cflSched.disabled() || (not reduce.growSched.disabled() && not reduce.growSched.defaulted()) ||
+            search.reduce.fReduce() != 0) {
+            return "'no-lookback': deletion options disabled!";
+        }
+    }
+    bool hasSched = not reduce.cflSched.disabled() || not reduce.growSched.disabled() || reduce.maxRange != UINT32_MAX;
+    if (hasSched && reduce.fReduce() == 0.0f && not reduce.growSched.defaulted()) {
+        return "'no-deletion': deletion strategies disabled!";
+    }
+    if (not hasSched && reduce.fReduce() != 0.0f && not reduce.growSched.defaulted()) {
+        return "'deletion': deletion strategy required!";
+    }
+    return nullptr;
+}
+} // namespace Cli
+} // namespace Clasp
diff --git a/src/clasp_output.cpp b/src/clasp_output.cpp
index aa02391..4a81610 100644
--- a/src/clasp_output.cpp
+++ b/src/clasp_output.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2009-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,1247 +22,1384 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
+#include 
 #include 
-#include 
-#include 
-#if !defined(_WIN32)
-#include 
-#elif !defined(SIGALRM)
-#define SIGALRM 14
-#endif
-#ifdef _MSC_VER
-#include 
-#define CLASP_ISNAN(x) (_isnan(x) != 0)
-#pragma warning (disable : 4996)
-#elif defined(__cplusplus) && __cplusplus >= 201103L
+
 #include 
-#define CLASP_ISNAN(x) std::isnan(x)
-#else
-#include 
-#define CLASP_ISNAN(x) isnan(x)
-#endif
-inline bool isNan(double d) { return CLASP_ISNAN(d); }
-#undef CLASP_ISNAN
-#if defined(_MSC_VER) || defined(__MINGW32__)
-static inline void flockfile(FILE* file) {
-	_lock_file(file);
-}
+#include 
+#include 
 
-static inline void funlockfile(FILE* file) {
-	_unlock_file(file);
-}
+#if defined(_MSC_VER) || defined(__MINGW32__)
+static inline void flockfile(FILE* file) { _lock_file(file); }
+static inline void funlockfile(FILE* file) { _unlock_file(file); }
 #endif
+namespace {
 struct FileLock {
-	FileLock(FILE* f) {
-		flockfile(file = f);
-	}
-	~FileLock() {
-		fflush(file);
-		funlockfile(file);
-	}
-	FILE* file;
+    explicit FileLock(FILE* f) : file(f) { flockfile(f); }
+    ~FileLock() {
+        fflush(file);
+        funlockfile(file);
+    }
+    FILE* file;
 };
-namespace Clasp { namespace Cli {
+} // namespace
+namespace Clasp::Cli {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Event formatting
 /////////////////////////////////////////////////////////////////////////////////////////
-template <>
-void formatEvent(const Clasp::BasicSolveEvent& ev, Potassco::StringBuilder& str) {
-	const Solver& s = *ev.solver;
-	str.appendFormat("%2u:%c|%7u/%-7u|%8u/%-8u|%10" PRIu64"/%-6.3f|%8" PRId64"/%-10" PRId64"|"
-		, s.id()
-		, static_cast(ev.op)
-		, s.numFreeVars()
-		, (s.decisionLevel() > 0 ? s.levelStart(1) : s.numAssignedVars())
-		, s.numConstraints()
-		, s.numLearntConstraints()
-		, s.stats.conflicts
-		, ratio(s.stats.conflicts, s.stats.choices)
-		, ev.cLimit <= (UINT32_MAX) ? (int64)ev.cLimit:-1
-		, ev.lLimit != (UINT32_MAX) ? (int64)ev.lLimit:-1
-	);
-}
-template <>
-void formatEvent(const Clasp::SolveTestEvent& ev, Potassco::StringBuilder& str) {
-	str.appendFormat("%2u:%c| %c HCC: %-6u |%8u/%-8u|%10" PRIu64"/%-6.3f| Time: %10.3fs |", ev.solver->id()
-		, "FP"[ev.partial]
-		, "?NY"[Range(-1, 1).clamp(ev.result) + 1]
-		, ev.hcc
-		, ev.solver->numConstraints()
-		, ev.solver->numLearntConstraints()
-		, ev.conflicts()
-		, ratio(ev.conflicts(), ev.choices())
-		, ev.time
-	);
+static int formatEvent(const BasicSolveEvent& ev, std::span& str) {
+    const Solver& s = *ev.solver;
+    return snprintf(
+        str.data(), str.size(), "%2u:%c|%7u/%-7u|%8u/%-8u|%10" PRIu64 "/%-6.3f|%8" PRId64 "/%-10" PRId64 "|", s.id(),
+        static_cast(ev.op), s.numFreeVars(), s.decisionLevel() > 0 ? s.levelStart(1) : s.numAssignedVars(),
+        s.numConstraints(), s.numLearntConstraints(), s.stats.conflicts, ratio(s.stats.conflicts, s.stats.choices),
+        ev.cLimit <= UINT32_MAX ? static_cast(ev.cLimit) : -1,
+        ev.lLimit != UINT32_MAX ? static_cast(ev.lLimit) : -1);
+}
+
+static int formatEvent(const SolveTestEvent& ev, std::span& str) {
+    return snprintf(str.data(), str.size(), "%2u:%c| %c HCC: %-6u |%8u/%-8u|%10" PRIu64 "/%-6.3f| Time: %10.3fs |",
+                    ev.solver->id(), "FP"[ev.partial], "?NY"[Clasp::clamp(ev.result, -1, 1) + 1], ev.hcc,
+                    ev.solver->numConstraints(), ev.solver->numLearntConstraints(), ev.conflicts(),
+                    ratio(ev.conflicts(), ev.choices()), ev.time);
 }
 #if CLASP_HAS_THREADS
-template <>
-void formatEvent(const Clasp::mt::MessageEvent& ev, Potassco::StringBuilder& str) {
-	typedef Clasp::mt::MessageEvent EV;
-	if (ev.op != EV::completed) { str.appendFormat("%2u:X| %-15s %-53s |", ev.solver->id(), ev.msg, ev.op == EV::sent ? "sent" : "received"); }
-	else                        { str.appendFormat("%2u:X| %-15s %-35s in %13.3fs |", ev.solver->id(), ev.msg, "completed", ev.time); }
+static int formatEvent(const mt::MessageEvent& ev, std::span& str) {
+    using EventType = mt::MessageEvent;
+    if (ev.op != EventType::completed) {
+        return snprintf(str.data(), str.size(), "%2u:X| %-30.30s %-38s |", ev.solver->id(), ev.msg,
+                        ev.op == EventType::sent ? "sent" : "received");
+    }
+    return snprintf(str.data(), str.size(), "%2u:X| %-30.30s %-20s in %13.3fs |", ev.solver->id(), ev.msg, "completed",
+                    ev.time);
 }
 #endif
 /////////////////////////////////////////////////////////////////////////////////////////
 // Output
 /////////////////////////////////////////////////////////////////////////////////////////
-Output::Output(uint32 verb) : time_(-1.0), model_(-1.0), summary_(0), verbose_(0), last_(false) {
-	std::memset(quiet_, 0, sizeof(quiet_));
-	setCallQuiet(print_no);
-	setVerbosity(verb);
-}
-Output::~Output() {}
-void Output::setVerbosity(uint32 verb) {
-	Event::Verbosity x = (Event::Verbosity)std::min(verbose_ = verb, (uint32)Event::verbosity_max);
-	EventHandler::setVerbosity(Event::subsystem_facade , x);
-	EventHandler::setVerbosity(Event::subsystem_load   , x);
-	EventHandler::setVerbosity(Event::subsystem_prepare, x);
-	EventHandler::setVerbosity(Event::subsystem_solve  , x);
-}
-void Output::setModelQuiet(PrintLevel model){ quiet_[0] = static_cast(model); }
-void Output::setOptQuiet(PrintLevel opt)    { quiet_[1] = static_cast(opt);   }
-void Output::setCallQuiet(PrintLevel call)  { quiet_[2] = static_cast(call);  }
-
+Output::Output(uint32_t verb) : time_(-1.0), model_(-1.0) {
+    setCallQuiet(print_no);
+    setVerbosity(verb);
+}
+Output::~Output() = default;
+void Output::setVerbosity(uint32_t verb) {
+    auto x = static_cast(std::min(verbose_ = verb, static_cast(Event::verbosity_max)));
+    EventHandler::setVerbosity(Event::subsystem_facade, x);
+    EventHandler::setVerbosity(Event::subsystem_load, x);
+    EventHandler::setVerbosity(Event::subsystem_prepare, x);
+    EventHandler::setVerbosity(Event::subsystem_solve, x);
+}
+void Output::setModelQuiet(PrintLevel model) { quiet_[0] = static_cast(model); }
+void Output::setOptQuiet(PrintLevel opt) { quiet_[1] = static_cast(opt); }
+void Output::setCallQuiet(PrintLevel call) { quiet_[2] = static_cast(call); }
 void Output::onEvent(const Event& ev) {
-	typedef ClaspFacade::StepStart StepStart;
-	typedef ClaspFacade::StepReady StepReady;
-	if (const StepStart* start = event_cast(ev)) {
-		if (time_ == -1.0) { time_ = RealTime::getTime(); }
-		startStep(*start->facade);
-	}
-	else if (const StepReady* ready = event_cast(ev)) {
-		stopStep(*ready->summary);
-	}
+    using StepStart = ClaspFacade::StepStart;
+    using StepReady = ClaspFacade::StepReady;
+    if (time_ == -1.0) {
+        time_ = RealTime::getTime();
+    }
+    if (const auto* start = event_cast(ev)) {
+        startStep(*start->facade);
+    }
+    else if (const auto* ready = event_cast(ev)) {
+        stopStep(*ready->summary);
+    }
 }
 bool Output::onModel(const Solver& s, const Model& m) {
-	PrintLevel type = (m.opt == 1 && !m.consequences()) || m.def ? print_best : print_all;
-	bool hasMeta = m.consequences() || m.costs;
-	model_ = elapsedTime();
-	if (modelQ() <= type || (hasMeta && optQ() <= type)) {
-		printModel(s.outputTable(), m, type);
-	}
-	last_ = type != print_best && (modelQ() == print_best || (optQ() == print_best && hasMeta));
-	return true;
+    PrintLevel type    = (m.opt == 1 && not m.consequences()) || m.def ? print_best : print_all;
+    bool       hasMeta = m.consequences() || m.hasCosts();
+    model_             = elapsedTime();
+    if (modelQ() <= type || (hasMeta && optQ() <= type)) {
+        printModel(s.outputTable(), m, type);
+    }
+    last_ = type != print_best && (modelQ() == print_best || (optQ() == print_best && hasMeta));
+    return true;
 }
 bool Output::onUnsat(const Solver& s, const Model& m) {
-	if (m.ctx) {
-		const LowerBound* lower = m.ctx->optimize() && s.lower.active() ? &s.lower : 0;
-		const Model*  prevModel = m.num ? &m : 0;
-		if (modelQ() == print_all || optQ() == print_all) {
-			printUnsat(s.outputTable(), lower, prevModel);
-		}
-	}
-	return true;
-}
-void Output::startStep(const ClaspFacade&) { summary_ = 0; last_ = false; }
-void Output::stopStep(const ClaspFacade::Summary& s){
-	if (s.model() && last_) {
-		Model m = *s.model();
-		m.up = 0; // ignore update state and always print as model
-		printModel(s.ctx().output, m, print_best);
-	}
-	else if (modelQ() == print_all && s.model() && s.model()->up && !s.model()->def) {
-		printModel(s.ctx().output, *s.model(), print_all);
-	}
-	if (callQ() == print_all) {
-		printSummary(s, false);
-		if (stats(s)) { printStatistics(s, false); }
-	}
-	else if (callQ() == print_best) {
-		summary_ = &s;
-	}
+    if (m.ctx) {
+        auto        lowerBound = m.ctx->lowerBound();
+        const auto* prevModel  = m.num ? &m : nullptr;
+        if (modelQ() == print_all || optQ() == print_all) {
+            printUnsat(s.outputTable(), lowerBound.active() ? &lowerBound : nullptr, prevModel);
+        }
+    }
+    return true;
+}
+void Output::startStep(const ClaspFacade&) {
+    summary_ = nullptr;
+    last_    = false;
+}
+void Output::stopStep(const ClaspFacade::Summary& s) {
+    if (s.model() && last_) {
+        Model m = *s.model();
+        m.up    = 0; // ignore update state and always print as model
+        printModel(s.ctx().output, m, print_best);
+    }
+    else if (modelQ() == print_all && s.model() && s.model()->up && not s.model()->def) {
+        printModel(s.ctx().output, *s.model(), print_all);
+    }
+    if (callQ() == print_all) {
+        printSummary(s, false);
+        if (stats(s)) {
+            printStatistics(s, false);
+        }
+    }
+    else if (callQ() == print_best) {
+        summary_ = &s;
+    }
 }
 void Output::shutdown(const ClaspFacade::Summary& summary) {
-	if (summary_) {
-		printSummary(*summary_, false);
-		if (stats(summary)) { printStatistics(*summary_, false); }
-	}
-	printSummary(summary, true);
-	if (stats(summary)) { printStatistics(summary, true); }
-	shutdown();
-	time_ = -1.0;
+    if (summary_) {
+        printSummary(*summary_, false);
+        if (stats(summary)) {
+            printStatistics(*summary_, false);
+        }
+    }
+    printSummary(summary, true);
+    if (stats(summary)) {
+        printStatistics(summary, true);
+    }
+    shutdown();
+    time_ = -1.0;
 }
 // Returns the number of consequences and estimates in m.
 // For a model m with m.consequences and a return value ret:
 //   - ret.first  is the number of definite consequences
 //   - ret.second is the number of remaining possible consequences
-Output::UPair Output::numCons(const OutputTable& out, const Model& m) const {
-	uint32 low = 0, up = 0;
-	if (out.projectMode() == ProjectMode_t::Output) {
-		low += out.numFacts();
-		for (OutputTable::pred_iterator it = out.pred_begin(); it != out.pred_end(); ++it) {
-			low += m.isDef(it->cond);
-			up  += m.isEst(it->cond);
-		}
-		for (OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) {
-			Literal p = posLit(*it);
-			low += m.isDef(p);
-			up  += m.isEst(p);
-		}
-	}
-	else {
-		for (OutputTable::lit_iterator it = out.proj_begin(), end = out.proj_end(); it != end; ++it) {
-			low += m.isDef(*it);
-			up  += m.isEst(*it);
-		}
-	}
-	return UPair(low, m.def ? 0 : up);
+Output::UPair Output::numCons(const OutputTable& out, const Model& m) {
+    uint32_t low = 0, up = 0;
+    if (out.projectMode() == ProjectMode::output) {
+        low += out.numFacts();
+        for (const auto& pred : out.pred_range()) {
+            low += m.isDef(pred.cond);
+            up  += m.isEst(pred.cond);
+        }
+        for (auto v : out.vars_range()) {
+            Literal p  = posLit(v);
+            low       += m.isDef(p);
+            up        += m.isEst(p);
+        }
+    }
+    else {
+        for (auto lit : out.proj_range()) {
+            low += m.isDef(lit);
+            up  += m.isEst(lit);
+        }
+    }
+    return {low, m.def ? 0 : up};
 }
 
 // Prints shown symbols in model.
-// The functions prints
+// The function prints:
 // - true literals in definite answer, followed by
 // - true literals in current estimate if m.consequences()
-void Output::printWitness(const OutputTable& out, const Model& m, uintp data) {
-	for (OutputTable::fact_iterator it = out.fact_begin(); it != out.fact_end(); ++it) {
-		data = doPrint(OutPair(*it, lit_true()), data);
-	}
-	for (const char* x = out.theory ? out.theory->first(m) : 0; x; x = out.theory->next()) {
-		data = doPrint(OutPair(x, lit_true()), data);
-	}
-	const bool onlyD = m.type != Model::Cautious || m.def;
-	for (bool D = true;; D = !D) {
-		for (OutputTable::pred_iterator it = out.pred_begin(); it != out.pred_end(); ++it) {
-			if (m.isTrue(it->cond) && (onlyD || m.isDef(it->cond) == D)) {
-				data = doPrint(OutPair(it->name, lit_true()), data);
-			}
-		}
-		if (out.vars_begin() != out.vars_end()) {
-			const bool showNeg = !m.consequences();
-			if (out.projectMode() == ProjectMode_t::Output || !out.filter("_")) {
-				for (OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) {
-					Literal p = posLit(*it);
-					if ((showNeg || m.isTrue(p)) && (onlyD || m.isDef(p) == D)) {
-						data = doPrint(OutPair(static_cast(0), m.isTrue(p) ? p : ~p), data);
-					}
-				}
-			}
-			else {
-				for (OutputTable::lit_iterator it = out.proj_begin(), end = out.proj_end(); it != end; ++it) {
-					if ((showNeg || m.isTrue(*it)) && (onlyD || m.isDef(*it) == D)) {
-						data = doPrint(OutPair(static_cast(0), m.isTrue(*it) ? *it : ~*it), data);
-					}
-				}
-			}
-		}
-		if (D == onlyD) { return; }
-	}
-}
-void Output::printUnsat(const OutputTable&, const LowerBound*, const Model*) {}
-uintp Output::doPrint(const OutPair&, UPtr x) { return x; }
-bool Output::stats(const ClaspFacade::Summary& summary) const {
-	return summary.facade->config()->context().stats != 0;
-}
-double Output::elapsedTime() const { return time_ != -1.0 ? RealTime::getTime() - time_ : -1.0; }
-double Output::modelTime() const { return model_; }
+void Output::printWitness(const OutputTable& out, const Model& m, uintptr_t data) {
+    for (const auto& n : out.fact_range()) { data = doPrint(OutPair(n.c_str(), lit_true), data); }
+    for (const char* x = out.theory ? out.theory->first(m) : nullptr; x; x = out.theory->next()) {
+        data = doPrint(OutPair(x, lit_true), data);
+    }
+    const bool onlyD = m.type != Model::cautious || m.def;
+    for (bool def = true;; def = not def) {
+        for (const auto& pred : out.pred_range()) {
+            if (m.isTrue(pred.cond) && (onlyD || m.isDef(pred.cond) == def)) {
+                data = doPrint(OutPair(pred.name.c_str(), lit_true), data);
+            }
+        }
+        if (not out.vars_range().empty()) {
+            const bool showNeg = not m.consequences();
+            if (out.projectMode() == ProjectMode::output || not out.filter("_")) {
+                for (auto v : out.vars_range()) {
+                    Literal p = posLit(v);
+                    if ((showNeg || m.isTrue(p)) && (onlyD || m.isDef(p) == def)) {
+                        data = doPrint(OutPair(static_cast(nullptr), m.isTrue(p) ? p : ~p), data);
+                    }
+                }
+            }
+            else {
+                for (auto lit : out.proj_range()) {
+                    if ((showNeg || m.isTrue(lit)) && (onlyD || m.isDef(lit) == def)) {
+                        data = doPrint(OutPair(static_cast(nullptr), m.isTrue(lit) ? lit : ~lit), data);
+                    }
+                }
+            }
+        }
+        if (def == onlyD) {
+            return;
+        }
+    }
+}
+void      Output::printUnsat(const OutputTable&, const LowerBound*, const Model*) {}
+uintptr_t Output::doPrint(const OutPair&, UPtr data) { return data; }
+bool      Output::stats(const ClaspFacade::Summary& summary) { return summary.facade->config()->context().stats != 0; }
+double    Output::elapsedTime() const { return time_ != -1.0 ? RealTime::getTime() - time_ : -1.0; }
+double    Output::modelTime() const { return model_; }
+using StatsType = Potassco::StatisticsType;
 /////////////////////////////////////////////////////////////////////////////////////////
 // JsonOutput
 /////////////////////////////////////////////////////////////////////////////////////////
-JsonOutput::JsonOutput(uint32 v) : Output(std::min(v, uint32(1))), open_("") {
-	objStack_.reserve(10);
-}
+JsonOutput::JsonOutput(uint32_t v) : Output(std::min(v, 1u)), open_("") { objStack_.reserve(10); }
 JsonOutput::~JsonOutput() { JsonOutput::shutdown(); }
 void JsonOutput::run(const char* solver, const char* version, const std::string* iBeg, const std::string* iEnd) {
-	if (indent() == 0) {
-		open_ = "";
-		pushObject();
-	}
-	printKeyValue("Solver", std::string(solver).append(" version ").append(version).c_str());
-	pushObject("Input", type_array, true);
-	for (const char* sep = ""; iBeg != iEnd; ++iBeg, sep = ",") {
-		printString(iBeg->c_str(), sep);
-	}
-	popObject();
-	pushObject("Call", type_array);
+    if (indent() == 0) {
+        open_ = "";
+        pushObject();
+    }
+    printKeyValue("Solver", std::string(solver).append(" version ").append(version).c_str());
+    pushObject("Input", type_array, true);
+    for (const char* sep = ""; iBeg != iEnd; ++iBeg, sep = ",") { printString(iBeg->c_str(), sep); }
+    popObject();
+    pushObject("Call", type_array);
 }
 void JsonOutput::shutdown(const ClaspFacade::Summary& summary) {
-	while (!objStack_.empty() && *objStack_.rbegin() == '[') {
-		popObject();
-	}
-	Output::shutdown(summary);
+    while (not objStack_.empty() && *objStack_.rbegin() == '[') { popObject(); }
+    Output::shutdown(summary);
 }
 
 void JsonOutput::shutdown() {
-	if (!objStack_.empty()) {
-		do { popObject(); } while (!objStack_.empty());
-		printf("\n");
-	}
-	fflush(stdout);
+    if (not objStack_.empty()) {
+        do { popObject(); } while (not objStack_.empty());
+        printf("\n");
+    }
+    fflush(stdout);
 }
 void JsonOutput::startStep(const ClaspFacade& f) {
-	Output::startStep(f);
-	pushObject(0, type_object);
-	printTime("Start", elapsedTime());
+    Output::startStep(f);
+    pushObject(nullptr, type_object);
+    printTime("Start", elapsedTime());
 }
 void JsonOutput::stopStep(const ClaspFacade::Summary& s) {
-	assert(!objStack_.empty());
-	Output::stopStep(s);
-	if (hasWitnesses()) { popObject(); }
-	printTime("Stop", elapsedTime());
-	popObject();
+    assert(not objStack_.empty());
+    Output::stopStep(s);
+    if (hasWitnesses()) {
+        popObject();
+    }
+    printTime("Stop", elapsedTime());
+    popObject();
 }
 
 bool JsonOutput::visitThreads(Operation op) {
-	if      (op == Enter) { pushObject("Thread", type_array); }
-	else if (op == Leave) { popObject(); }
-	return true;
+    if (op == enter) {
+        pushObject("Thread", type_array);
+    }
+    else if (op == leave) {
+        popObject();
+    }
+    return true;
 }
 bool JsonOutput::visitTester(Operation op) {
-	if      (op == Enter) { pushObject("Tester"); }
-	else if (op == Leave) { popObject(); }
-	return true;
+    if (op == enter) {
+        pushObject("Tester");
+    }
+    else if (op == leave) {
+        popObject();
+    }
+    return true;
 }
 bool JsonOutput::visitHccs(Operation op) {
-	if      (op == Enter) { pushObject("HCC", type_array); }
-	else if (op == Leave) { popObject(); }
-	return true;
-}
-void JsonOutput::visitThread(uint32, const SolverStats& stats) {
-	pushObject(0, type_object);
-	JsonOutput::visitSolverStats(stats);
-	popObject();
-}
-void JsonOutput::visitHcc(uint32, const ProblemStats& p, const SolverStats& s) {
-	pushObject(0, type_object);
-	JsonOutput::visitProblemStats(p);
-	JsonOutput::visitSolverStats(s);
-	popObject();
+    if (op == enter) {
+        pushObject("HCC", type_array);
+    }
+    else if (op == leave) {
+        popObject();
+    }
+    return true;
+}
+void JsonOutput::visitThread(uint32_t, const SolverStats& stats) {
+    pushObject(nullptr, type_object);
+    JsonOutput::visitSolverStats(stats);
+    popObject();
+}
+void JsonOutput::visitHcc(uint32_t, const ProblemStats& p, const SolverStats& s) {
+    pushObject(nullptr, type_object);
+    JsonOutput::visitProblemStats(p);
+    JsonOutput::visitSolverStats(s);
+    popObject();
 }
 
 void JsonOutput::visitSolverStats(const SolverStats& stats) {
-	printCoreStats(stats);
-	if (stats.extra) {
-		printExtStats(*stats.extra, objStack_.size() == 2);
-		printJumpStats(stats.extra->jumps);
-	}
+    printCoreStats(stats);
+    if (stats.extra) {
+        printExtStats(*stats.extra, objStack_.size() == 2);
+        printJumpStats(stats.extra->jumps);
+    }
 }
 
-void JsonOutput::printChildren(const StatisticObject& s) {
-	for (uint32 i = 0; i != s.size(); ++i) {
-		const char* key = s.type() == Potassco::Statistics_t::Map ? s.key(i) : 0;
-		StatisticObject child = key ? s.at(key) : s[i];
-		if (child.type() == Potassco::Statistics_t::Value) {
-			printKeyValue(key, child);
-		}
-		else if (child.size()) {
-			pushObject(key, child.type() == Potassco::Statistics_t::Map ? type_object : type_array);
-			printChildren(child);
-			popObject();
-		}
-	}
+void JsonOutput::printChildren(const StatisticObject& s) { // NOLINT(misc-no-recursion)
+    for (auto i : irange(s)) {
+        const char*     key   = s.type() == StatsType::map ? s.key(i) : nullptr;
+        StatisticObject child = key ? s.at(key) : s[i];
+        if (child.type() == StatsType::value) {
+            printKeyValue(key, child);
+        }
+        else {
+            pushObject(key, child.type() == StatsType::map ? type_object : type_array);
+            printChildren(child);
+            popObject();
+        }
+    }
 }
 
 void JsonOutput::visitExternalStats(const StatisticObject& stats) {
-	POTASSCO_ASSERT(stats.type() == Potassco::Statistics_t::Map, "Non map statistic!");
-	printChildren(stats);
+    POTASSCO_ASSERT(stats.type() == StatsType::map, "Non map statistic!");
+    printChildren(stats);
 }
 
 void JsonOutput::printCoreStats(const CoreStats& st) {
-	pushObject("Core");
-	printKeyValue("Choices"    , st.choices);
-	printKeyValue("Conflicts"  , st.conflicts);
-	printKeyValue("Backtracks" , st.backtracks());
-	printKeyValue("Backjumps"  , st.backjumps());
-	printKeyValue("Restarts"   , st.restarts);
-	printKeyValue("RestartAvg" , st.avgRestart());
-	printKeyValue("RestartLast", st.lastRestart);
-	popObject(); // Core
+    pushObject("Core");
+    printKeyValue("Choices", st.choices);
+    printKeyValue("Conflicts", st.conflicts);
+    printKeyValue("Backtracks", st.backtracks());
+    printKeyValue("Backjumps", st.backjumps());
+    printKeyValue("Restarts", st.restarts);
+    printKeyValue("RestartAvg", st.avgRestart());
+    printKeyValue("RestartLast", st.lastRestart);
+    popObject(); // Core
 }
 
 void JsonOutput::printExtStats(const ExtendedStats& stx, bool generator) {
-	pushObject("More");
-	printKeyValue("CPU", stx.cpuTime);
-	printKeyValue("Models", stx.models);
-	if (stx.domChoices) {
-		printKeyValue("DomChoices", stx.domChoices);
-	}
-	if (stx.hccTests) {
-		pushObject("StabTests");
-		printKeyValue("Sum", stx.hccTests);
-		printKeyValue("Full", stx.hccTests - stx.hccPartial);
-		printKeyValue("Partial", stx.hccPartial);
-		popObject();
-	}
-	if (stx.models) {
-		printKeyValue("AvgModel", stx.avgModel());
-	}
-	printKeyValue("Splits", stx.splits);
-	printKeyValue("Problems", stx.gps);
-	printKeyValue("AvgGPLength", stx.avgGp());
-	pushObject("Lemma");
-	printKeyValue("Sum", stx.lemmas());
-	printKeyValue("Deleted" , stx.deleted);
-	pushObject("Type", type_array);
-	const char* names[] = {"Short", "Conflict", "Loop", "Other"};
-	for (int i = Constraint_t::Static; i <= Constraint_t::Other; ++i) {
-		pushObject();
-		printKeyValue("Type", names[i]);
-		if (i == 0) {
-			printKeyValue("Sum", stx.binary+stx.ternary);
-			printKeyValue("Ratio", percent(stx.binary+stx.ternary, stx.lemmas()));
-			printKeyValue("Binary", stx.binary);
-			printKeyValue("Ternary", stx.ternary);
-		}
-		else {
-			printKeyValue("Sum", stx.lemmas(ConstraintType(i)));
-			printKeyValue("AvgLen", stx.avgLen(ConstraintType(i)));
-		}
-		popObject();
-	}
-	popObject();
-	popObject(); // Lemma
-	if (stx.distributed || stx.integrated) {
-		pushObject("Distribution");
-		printKeyValue("Distributed", stx.distributed);
-		printKeyValue("Ratio", stx.distRatio());
-		printKeyValue("AvgLbd", stx.avgDistLbd());
-		popObject();
-		pushObject("Integration");
-		printKeyValue("Integrated", stx.integrated);
-		printKeyValue("Units", stx.intImps);
-		printKeyValue("AvgJump", stx.avgIntJump());
-		if (generator) { printKeyValue("Ratio", stx.intRatio()); }
-		popObject();
-	}
-	popObject(); // More
+    pushObject("More");
+    printKeyValue("CPU", stx.cpuTime);
+    printKeyValue("Models", stx.models);
+    if (stx.domChoices) {
+        printKeyValue("DomChoices", stx.domChoices);
+    }
+    if (stx.hccTests) {
+        pushObject("StabTests");
+        printKeyValue("Sum", stx.hccTests);
+        printKeyValue("Full", stx.hccTests - stx.hccPartial);
+        printKeyValue("Partial", stx.hccPartial);
+        popObject();
+    }
+    if (stx.models) {
+        printKeyValue("AvgModel", stx.avgModel());
+    }
+    printKeyValue("Splits", stx.splits);
+    printKeyValue("Problems", stx.gps);
+    printKeyValue("AvgGPLength", stx.avgGp());
+    pushObject("Lemma");
+    printKeyValue("Sum", stx.lemmas());
+    printKeyValue("Deleted", stx.deleted);
+    pushObject("Type", type_array);
+    const char* names[] = {"Short", "Conflict", "Loop", "Other"};
+    for (auto i : irange(names)) {
+        pushObject();
+        printKeyValue("Type", names[i]);
+        if (i == ConstraintType::static_) {
+            printKeyValue("Sum", stx.binary + stx.ternary);
+            printKeyValue("Ratio", percent(stx.binary + stx.ternary, stx.lemmas()));
+            printKeyValue("Binary", stx.binary);
+            printKeyValue("Ternary", stx.ternary);
+        }
+        else {
+            printKeyValue("Sum", stx.lemmas(static_cast(i)));
+            printKeyValue("AvgLen", stx.avgLen(static_cast(i)));
+        }
+        popObject();
+    }
+    popObject();
+    popObject(); // Lemma
+    if (stx.distributed || stx.integrated) {
+        pushObject("Distribution");
+        printKeyValue("Distributed", stx.distributed);
+        printKeyValue("Ratio", stx.distRatio());
+        printKeyValue("AvgLbd", stx.avgDistLbd());
+        popObject();
+        pushObject("Integration");
+        printKeyValue("Integrated", stx.integrated);
+        printKeyValue("Units", stx.intImps);
+        printKeyValue("AvgJump", stx.avgIntJump());
+        if (generator) {
+            printKeyValue("Ratio", stx.intRatio());
+        }
+        popObject();
+    }
+    popObject(); // More
 }
 void JsonOutput::printJumpStats(const JumpStats& st) {
-	pushObject("Jumps");
-	printKeyValue("Sum", st.jumps);
-	printKeyValue("Max", st.maxJump);
-	printKeyValue("MaxExec", st.maxJumpEx);
-	printKeyValue("Avg", st.avgJump());
-	printKeyValue("AvgExec", st.avgJumpEx());
-	printKeyValue("Levels", st.jumpSum);
-	printKeyValue("LevelsExec", st.jumped());
-	pushObject("Bounded");
-	printKeyValue("Sum", st.bounded);
-	printKeyValue("Max", st.maxBound);
-	printKeyValue("Avg", st.avgBound());
-	printKeyValue("Levels", st.boundSum);
-	popObject();
-	popObject();
+    pushObject("Jumps");
+    printKeyValue("Sum", st.jumps);
+    printKeyValue("Max", st.maxJump);
+    printKeyValue("MaxExec", st.maxJumpEx);
+    printKeyValue("Avg", st.avgJump());
+    printKeyValue("AvgExec", st.avgJumpEx());
+    printKeyValue("Levels", st.jumpSum);
+    printKeyValue("LevelsExec", st.jumped());
+    pushObject("Bounded");
+    printKeyValue("Sum", st.bounded);
+    printKeyValue("Max", st.maxBound);
+    printKeyValue("Avg", st.avgBound());
+    printKeyValue("Levels", st.boundSum);
+    popObject();
+    popObject();
 }
 void JsonOutput::visitLogicProgramStats(const Asp::LpStats& lp) {
-	using namespace Asp;
-	pushObject("LP");
-	pushObject("Rules");
-	printKeyValue("Original", lp.rules[0].sum());
-	printKeyValue("Final",    lp.rules[1].sum());
-	for (uint32 i = 0; i != RuleStats::numKeys(); ++i) {
-		if (i != RuleStats::Normal && lp.rules[0][i]) {
-			pushObject(RuleStats::toStr(i));
-			printKeyValue("Original", lp.rules[0][i]);
-			printKeyValue("Final",    lp.rules[1][i]);
-			popObject();
-		}
-	}
-	popObject(); // Rules
-	printKeyValue("Atoms", lp.atoms);
-	if (lp.auxAtoms) { printKeyValue("AuxAtoms", lp.auxAtoms); }
-	if (lp.disjunctions[0]) {
-		pushObject("Disjunctions");
-		printKeyValue("Original", lp.disjunctions[0]);
-		printKeyValue("Final", lp.disjunctions[1]);
-		popObject();
-	}
-	pushObject("Bodies");
-	printKeyValue("Original", lp.bodies[0].sum());
-	printKeyValue("Final"   , lp.bodies[1].sum());
-	for (uint32 i = 1; i != BodyStats::numKeys(); ++i) {
-		if (lp.bodies[0][i]) {
-			pushObject(BodyStats::toStr(i));
-			printKeyValue("Original", lp.bodies[0][i]);
-			printKeyValue("Final",    lp.bodies[1][i]);
-			popObject();
-		}
-	}
-	popObject();
-	if      (lp.sccs == 0)              { printKeyValue("Tight", "yes"); }
-	else if (lp.sccs == PrgNode::noScc) { printKeyValue("Tight", "N/A"); }
-	else                                {
-		printKeyValue("Tight", "no");
-		printKeyValue("SCCs", lp.sccs);
-		printKeyValue("NonHcfs", lp.nonHcfs);
-		printKeyValue("UfsNodes", lp.ufsNodes);
-		printKeyValue("NonHcfGammas", lp.gammas);
-	}
-	pushObject("Equivalences");
-	printKeyValue("Sum", lp.eqs());
-	printKeyValue("Atom", lp.eqs(Var_t::Atom));
-	printKeyValue("Body", lp.eqs(Var_t::Body));
-	printKeyValue("Other", lp.eqs(Var_t::Hybrid));
-	popObject();
-	popObject(); // LP
+    using namespace Asp;
+    pushObject("LP");
+    pushObject("Rules");
+    printKeyValue("Original", lp.rules[0].sum());
+    printKeyValue("Final", lp.rules[1].sum());
+    for (auto i : irange(RuleStats::numKeys())) {
+        if (i != RuleStats::normal && lp.rules[0][i]) {
+            pushObject(RuleStats::toStr(i));
+            printKeyValue("Original", lp.rules[0][i]);
+            printKeyValue("Final", lp.rules[1][i]);
+            popObject();
+        }
+    }
+    popObject(); // Rules
+    printKeyValue("Atoms", lp.atoms);
+    if (lp.auxAtoms) {
+        printKeyValue("AuxAtoms", lp.auxAtoms);
+    }
+    if (lp.disjunctions[0]) {
+        pushObject("Disjunctions");
+        printKeyValue("Original", lp.disjunctions[0]);
+        printKeyValue("Final", lp.disjunctions[1]);
+        popObject();
+    }
+    pushObject("Bodies");
+    printKeyValue("Original", lp.bodies[0].sum());
+    printKeyValue("Final", lp.bodies[1].sum());
+    for (uint32_t i : irange(1u, BodyStats::numKeys())) {
+        if (lp.bodies[0][i]) {
+            pushObject(BodyStats::toStr(i));
+            printKeyValue("Original", lp.bodies[0][i]);
+            printKeyValue("Final", lp.bodies[1][i]);
+            popObject();
+        }
+    }
+    popObject();
+    if (lp.sccs == 0) {
+        printKeyValue("Tight", "yes");
+    }
+    else if (lp.sccs == PrgNode::no_scc) {
+        printKeyValue("Tight", "N/A");
+    }
+    else {
+        printKeyValue("Tight", "no");
+        printKeyValue("SCCs", lp.sccs);
+        printKeyValue("NonHcfs", lp.nonHcfs);
+        printKeyValue("UfsNodes", lp.ufsNodes);
+        printKeyValue("NonHcfGammas", lp.gammas);
+    }
+    pushObject("Equivalences");
+    printKeyValue("Sum", lp.eqs());
+    printKeyValue("Atom", lp.eqs(VarType::atom));
+    printKeyValue("Body", lp.eqs(VarType::body));
+    printKeyValue("Other", lp.eqs(VarType::hybrid));
+    popObject();
+    popObject(); // LP
 }
 void JsonOutput::visitProblemStats(const ProblemStats& p) {
-	pushObject("Problem");
-	printKeyValue("Variables", p.vars.num);
-	printKeyValue("Eliminated", p.vars.eliminated);
-	printKeyValue("Frozen", p.vars.frozen);
-	pushObject("Constraints");
-	uint32 sum = p.numConstraints();
-	printKeyValue("Sum", sum);
-	printKeyValue("Binary", p.constraints.binary);
-	printKeyValue("Ternary", p.constraints.ternary);
-	popObject(); // Constraints
-	printKeyValue("AcycEdges", p.acycEdges);
-	popObject(); // PS
+    pushObject("Problem");
+    printKeyValue("Variables", p.vars.num);
+    printKeyValue("Eliminated", p.vars.eliminated);
+    printKeyValue("Frozen", p.vars.frozen);
+    pushObject("Constraints");
+    uint32_t sum = p.numConstraints();
+    printKeyValue("Sum", sum);
+    printKeyValue("Binary", p.constraints.binary);
+    printKeyValue("Ternary", p.constraints.ternary);
+    popObject(); // Constraints
+    printKeyValue("AcycEdges", p.acycEdges);
+    popObject(); // PS
 }
 
 void JsonOutput::printKey(const char* k) {
-	if (k) { printf("%s%-*.*s\"%s\": ", open_, indent(), indent(), " ", k); }
-	else   { printf("%s%-*.*s", open_, indent(), indent(), " "); }
+    if (k) {
+        printf("%s%-*.*s\"%s\": ", open_, indent(), indent(), " ", k);
+    }
+    else {
+        printf("%s%-*.*s", open_, indent(), indent(), " ");
+    }
 }
 
 void JsonOutput::printString(const char* v, const char* sep) {
-	assert(v);
-	const uint32 BUF_SIZE = 1024;
-	char buf[BUF_SIZE];
-	const char* special = "\b\f\n\r\t\"\\";
-	const char* replace = "bfnrt\"\\";
-	buf[0] = '"';
-	for (uint32 n = 1; (buf[n] = *v) != 0; ++v) {
-		if (const char* esc = strchr(special, buf[n])) {
-			buf[n]   = '\\';
-			buf[++n] = replace[esc - special];
-		}
-		if (++n > BUF_SIZE - 2) { buf[n] = 0; printf("%s%s", sep, buf); n = 0; sep = ""; }
-	}
-	printf("%s%s\"", sep, buf);
+    static constexpr auto     special  = "\b\f\n\r\t\"\\";
+    static constexpr auto     replace  = "bfnrt\"\\";
+    static constexpr uint32_t buf_size = 1024;
+    assert(v);
+    char buf[buf_size];
+    buf[0] = '"';
+    for (uint32_t n = 1; (buf[n] = *v) != 0; ++v) {
+        if (const char* esc = strchr(special, buf[n])) {
+            buf[n]   = '\\';
+            buf[++n] = replace[esc - special];
+        }
+        if (++n > buf_size - 2) {
+            buf[n] = 0;
+            printf("%s%s", sep, buf);
+            n   = 0;
+            sep = "";
+        }
+    }
+    printf("%s%s\"", sep, buf);
 }
 
 void JsonOutput::printKeyValue(const char* k, const char* v) {
-	printf("%s%-*s\"%s\": ", open_, indent(), " ", k);
-	printString(v,"");
-	open_ = ",\n";
+    printf("%s%-*s\"%s\": ", open_, indent(), " ", k);
+    printString(v, "");
+    open_ = ",\n";
 }
-void JsonOutput::printKeyValue(const char* k, uint64 v) {
-	printf("%s%-*s\"%s\": %" PRIu64, open_, indent(), " ", k, v);
-	open_ = ",\n";
+void JsonOutput::printKeyValue(const char* k, uint64_t v) {
+    printf("%s%-*s\"%s\": %" PRIu64, open_, indent(), " ", k, v);
+    open_ = ",\n";
 }
-void JsonOutput::printKeyValue(const char* k, uint32 v) { return printKeyValue(k, uint64(v)); }
+void JsonOutput::printKeyValue(const char* k, uint32_t v) { return printKeyValue(k, static_cast(v)); }
 void JsonOutput::printKeyValue(const char* k, double v) {
-	if (!isNan(v)) { printf("%s%-*s\"%s\": %.3f", open_, indent(), " ", k, v); }
-	else           { printf("%s%-*s\"%s\": %s", open_, indent(), " ", k, "null"); }
-	open_ = ",\n";
+    if (not std::isnan(v)) {
+        printf("%s%-*s\"%s\": %.3f", open_, indent(), " ", k, v);
+    }
+    else {
+        printf("%s%-*s\"%s\": %s", open_, indent(), " ", k, "null");
+    }
+    open_ = ",\n";
 }
 void JsonOutput::printKeyValue(const char* k, const StatisticObject& o) {
-	double v = o.value();
-	printKey(k);
-	if (!isNan(v)) { printf("%g", v); }
-	else           { printf("%s", "null"); }
-	open_ = ",\n";
+    double v = o.value();
+    printKey(k);
+    if (not std::isnan(v)) {
+        printf("%g", v);
+    }
+    else {
+        printf("%s", "null");
+    }
+    open_ = ",\n";
 }
 
 void JsonOutput::pushObject(const char* k, ObjType t, bool startIndent) {
-	printKey(k);
-	char o = t == type_object ? '{' : '[';
-	objStack_ += o;
-	printf("%c\n", o);
-	open_ = "";
-	if (startIndent) { printf("%-*s", indent(), " "); }
+    printKey(k);
+    char o     = t == type_object ? '{' : '[';
+    objStack_ += o;
+    printf("%c\n", o);
+    open_ = "";
+    if (startIndent) {
+        printf("%-*s", indent(), " ");
+    }
 }
 char JsonOutput::popObject() {
-	assert(!objStack_.empty());
-	char o = *objStack_.rbegin();
-	objStack_.erase(objStack_.size()-1);
-	printf("\n%-*.*s%c", indent(), indent(), " ", o == '{' ? '}' : ']');
-	open_ = ",\n";
-	return o;
+    assert(not objStack_.empty());
+    char o = objStack_.back();
+    objStack_.pop_back();
+    printf("\n%-*.*s%c", indent(), indent(), " ", o == '{' ? '}' : ']');
+    open_ = ",\n";
+    return o;
 }
 void JsonOutput::startWitness(double time) {
-	if (!hasWitnesses()) {
-		pushObject("Witnesses", type_array);
-	}
-	pushObject();
-	printTime("Time", time);
+    if (not hasWitnesses()) {
+        pushObject("Witnesses", type_array);
+    }
+    pushObject();
+    printTime("Time", time);
 }
 void JsonOutput::endWitness() {
-	popObject();
-	fflush(stdout);
-}
-uintp JsonOutput::doPrint(const OutPair& out, uintp data) {
-	const char* sep = reinterpret_cast(data);
-	if (out.first){ printString(out.first, sep); }
-	else          { printf("%s%d", sep, out.second.sign() ? -static_cast(out.second.var()) : static_cast(out.second.var())); }
-	return reinterpret_cast(", ");
+    popObject();
+    fflush(stdout);
+}
+uintptr_t JsonOutput::doPrint(const OutPair& out, uintptr_t data) {
+    const char* sep = reinterpret_cast(data);
+    if (out.first) {
+        printString(out.first, sep);
+    }
+    else {
+        printf("%s%d", sep,
+               out.second.sign() ? -static_cast(out.second.var()) : static_cast(out.second.var()));
+    }
+    return reinterpret_cast(", ");
 }
 void JsonOutput::printModel(const OutputTable& out, const Model& m, PrintLevel x) {
-	bool hasModel = false;
-	if (modelQ() <= x) {
-		hasModel = (startWitness(modelTime()), true);
-		pushObject("Value", type_array, true);
-		printWitness(out, m, reinterpret_cast(""));
-		popObject();
-	}
-	if (optQ() <= x && (m.consequences() || m.costs)) {
-		if (!hasModel)        { hasModel = (startWitness(modelTime()), true); }
-		if (m.consequences()) { printCons(numCons(out, m)); }
-		if (m.costs)          { printCosts(*m.costs); }
-	}
-	if (hasModel) { endWitness(); }
-}
-void JsonOutput::printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel) {
-	if (lower && optQ() == print_all) {
-		startWitness(elapsedTime());
-		Potassco::Span first = Potassco::toSpan();
-		if (prevModel && prevModel->costs && prevModel->costs->size() > lower->level) {
-			first = Potassco::toSpan(&prevModel->costs->at(0), lower->level);
-		}
-		printSum("Lower", first, &lower->bound);
-		endWitness();
-	}
-}
-void JsonOutput::printSum(const char* name, Potassco::Span sum, const wsum_t* last) {
-	pushObject(name, type_array, true);
-	const char* sep = "";
-	for (Potassco::Span::iterator it = Potassco::begin(sum), end = Potassco::end(sum); it != end; ++it) {
-		printf("%s%" PRId64, sep, *it);
-		sep = ", ";
-	}
-	if (last) {
-		printf("%s%" PRId64, sep, *last);
-	}
-	popObject();
-}
-void JsonOutput::printCosts(const SumVec& opt, const char* name) {
-	printSum(name, Potassco::toSpan(opt));
-}
+    auto onEnter = objStack_.size();
+    if (modelQ() <= x) {
+        startWitness(modelTime());
+        pushObject("Value", type_array, true);
+        printWitness(out, m, reinterpret_cast(""));
+        popObject();
+    }
+    if (optQ() <= x && (m.consequences() || m.hasCosts())) {
+        if (objStack_.size() == onEnter) {
+            startWitness(modelTime());
+        }
+        if (m.consequences()) {
+            printCons(numCons(out, m));
+        }
+        if (m.hasCosts()) {
+            printCosts(m.costs);
+        }
+    }
+    if (objStack_.size() != onEnter) {
+        endWitness();
+    }
+}
+void JsonOutput::printUnsat(const OutputTable&, const LowerBound* lower, const Model* prevModel) {
+    if (lower && optQ() == print_all) {
+        startWitness(elapsedTime());
+        auto first = SumView();
+        if (prevModel && prevModel->hasCosts() && prevModel->costs.size() > lower->level) {
+            first = prevModel->costs.subspan(0, lower->level);
+        }
+        printSum("Lower", first, &lower->bound);
+        endWitness();
+    }
+}
+void JsonOutput::printSum(const char* name, SumView sum, const Wsum_t* last) {
+    pushObject(name, type_array, true);
+    const char* sep = "";
+    for (Wsum_t s : sum) {
+        printf("%s%" PRId64, sep, s);
+        sep = ", ";
+    }
+    if (last) {
+        printf("%s%" PRId64, sep, *last);
+    }
+    popObject();
+}
+void JsonOutput::printCosts(SumView costs, const char* name) { printSum(name, costs); }
 void JsonOutput::printCons(const UPair& cons) {
-	pushObject("Consequences");
-	printKeyValue("True", cons.first);
-	printKeyValue("Open", cons.second);
-	popObject();
+    pushObject("Consequences");
+    printKeyValue("True", cons.first);
+    printKeyValue("Open", cons.second);
+    popObject();
 }
 
 void JsonOutput::printSummary(const ClaspFacade::Summary& run, bool final) {
-	if (hasWitnesses()) { popObject(); }
-	const char* res = "UNKNOWN";
-	if      (run.unsat()) { res = "UNSATISFIABLE"; }
-	else if (run.sat())   { res = !run.optimum() ? "SATISFIABLE" : "OPTIMUM FOUND"; }
-	printKeyValue("Result", res);
-	if (verbosity()) {
-		if (run.result.interrupted()){ printKeyValue(run.result.signal != SIGALRM ? "INTERRUPTED" : "TIME LIMIT", uint32(1));  }
-		pushObject("Models");
-		printKeyValue("Number", run.numEnum);
-		printKeyValue("More"  , run.complete() ? "no" : "yes");
-		if (run.sat()) {
-			if (run.consequences()){
-				printKeyValue(run.consequences(), run.complete() ? "yes":"unknown");
-				printCons(numCons(run.ctx().output, *run.model()));
-			}
-			if (run.optimize())    {
-				printKeyValue("Optimum", run.optimum()?"yes":"unknown");
-				printKeyValue("Optimal", run.optimal());
-				printCosts(*run.costs());
-			}
-		}
-		popObject();
-		if (run.hasLower() && !run.optimum()) {
-			pushObject("Bounds");
-			printCosts(run.lower(), "Lower");
-			printCosts(run.costs() ? *run.costs() : SumVec(), "Upper");
-			popObject();
-		}
-		if (final) { printKeyValue("Calls", run.step + 1); }
-		pushObject("Time");
-		printKeyValue("Total", run.totalTime);
-		printKeyValue("Solve", run.solveTime);
-		printKeyValue("Model", run.satTime);
-		printKeyValue("Unsat", run.unsatTime);
-		printKeyValue("CPU"  , run.cpuTime);
-		popObject(); // Time
-		if (run.ctx().concurrency() > 1) {
-			printKeyValue("Threads", run.ctx().concurrency());
-			printKeyValue("Winner",  run.ctx().winner());
-		}
-	}
+    if (hasWitnesses()) {
+        popObject();
+    }
+    const char* res = "UNKNOWN";
+    if (run.unsat()) {
+        res = "UNSATISFIABLE";
+    }
+    else if (run.sat()) {
+        res = not run.optimum() ? "SATISFIABLE" : "OPTIMUM FOUND";
+    }
+    printKeyValue("Result", res);
+    if (verbosity()) {
+        if (run.result.interrupted()) {
+            printKeyValue(run.result.signal != SIGALRM ? "INTERRUPTED" : "TIME LIMIT", 1u);
+        }
+        pushObject("Models");
+        printKeyValue("Number", run.numEnum);
+        printKeyValue("More", run.complete() ? "no" : "yes");
+        if (run.sat()) {
+            if (run.consequences()) {
+                printKeyValue(run.consequences(), run.complete() ? "yes" : "unknown");
+                printCons(numCons(run.ctx().output, *run.model()));
+            }
+            if (run.optimize()) {
+                printKeyValue("Optimum", run.optimum() ? "yes" : "unknown");
+                printKeyValue("Optimal", run.optimal());
+                printCosts(run.costs());
+            }
+        }
+        popObject();
+        if (run.hasLower() && not run.optimum()) {
+            pushObject("Bounds");
+            printCosts(run.lower(), "Lower");
+            printCosts(run.costs(), "Upper");
+            popObject();
+        }
+        if (final) {
+            printKeyValue("Calls", run.step + 1);
+        }
+        pushObject("Time");
+        printKeyValue("Total", run.totalTime);
+        printKeyValue("Solve", run.solveTime);
+        printKeyValue("Model", run.satTime);
+        printKeyValue("Unsat", run.unsatTime);
+        printKeyValue("CPU", run.cpuTime);
+        popObject(); // Time
+        if (run.ctx().concurrency() > 1) {
+            printKeyValue("Threads", run.ctx().concurrency());
+            printKeyValue("Winner", run.ctx().winner());
+        }
+    }
 }
 void JsonOutput::printStatistics(const ClaspFacade::Summary& summary, bool) {
-	if (hasWitnesses()) { popObject(); }
-	pushObject("Stats", type_object);
-	summary.accept(*this);
-	popObject();
+    if (hasWitnesses()) {
+        popObject();
+    }
+    pushObject("Stats", type_object);
+    summary.accept(*this);
+    popObject();
 }
 void JsonOutput::printTime(const char* name, double t) {
-	if (t >= 0.0) { printKeyValue(name, t); }
+    if (t >= 0.0) {
+        printKeyValue(name, t);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // TextOutput
 /////////////////////////////////////////////////////////////////////////////////////////
-#define printKeyValue(k, fmt, value) printf("%s%-*s: " fmt, format[cat_comment], width_, (k), (value))
-#define printLN(cat, fmt, ...)       printf("%s" fmt "\n", format[cat], __VA_ARGS__)
-#define printBR(cat)                 printf("%s\n", format[cat])
-#define printKey(k)                  printf("%s%-*s: ", format[cat_comment], width_, (k))
-const char* const rowSep   = "------------------------------------------------------------------------------------------|";
-const char* const finalSep = "====================================== Accumulation ======================================|";
-const char* const satPre   = "Sat-Prepro";
+#define PRINT_KEY_VALUE(k, fmt, value)   printf("%s%-*s: " fmt, format[cat_comment], width_, (k), (value))
+#define PRINT_S_KEY_VALUE(k, fmt, value) printf("  %s%-*s: " fmt, format[cat_comment], width_ - 2, (k), (value))
+#define PRINT_LN(cat, fmt, ...)          printf("%s" fmt "\n", format[cat], __VA_ARGS__)
+#define PRINT_BR(cat)                    printf("%s\n", format[cat])
+#define PRINT_KEY(k)                     printf("%s%-*s: ", format[cat_comment], width_, (k))
+constexpr auto row_sep = "------------------------------------------------------------------------------------------|";
+constexpr auto acc_sep = "====================================== Accumulation ======================================|";
+constexpr auto sat_pre = "Sat-Prepro";
 
-static inline std::string prettify(const std::string& str) {
-	if (str.size() < 40) return str;
-	std::string t("...");
-	t.append(str.end()-38, str.end());
-	return t;
-}
-TextOutput::TextOutput(uint32 verbosity, Format fmt, const char* catAtom, char ifs) : Output(verbosity), stTime_(0.0), state_(0) {
-	result[res_unknown]    = "UNKNOWN";
-	result[res_sat]        = "SATISFIABLE";
-	result[res_unsat]      = "UNSATISFIABLE";
-	result[res_opt]        = "OPTIMUM FOUND";
-	format[cat_comment]    = "";
-	format[cat_value]      = "";
-	format[cat_objective]  = "Optimization: ";
-	format[cat_result]     = "";
-	format[cat_value_term] = "";
-	format[cat_atom_name]  = "%s";
-	format[cat_atom_var]   = "-%d";
-	if (fmt == format_aspcomp) {
-		format[cat_comment]   = "% ";
-		format[cat_value]     = "ANSWER\n";
-		format[cat_objective] = "COST ";
-		format[cat_atom_name] = "%s.";
-		result[res_sat]       = "";
-		result[res_unsat]     = "INCONSISTENT";
-		result[res_opt]       = "OPTIMUM";
-		setModelQuiet(print_best);
-		setOptQuiet(print_best);
-	}
-	else if (fmt == format_sat09 || fmt == format_pb09) {
-		format[cat_comment]   = "c ";
-		format[cat_value]     = "v ";
-		format[cat_objective] = "o ";
-		format[cat_result]    = "s ";
-		format[cat_value_term]= "0";
-		if (fmt == format_pb09) {
-			format[cat_value_term]= "";
-			format[cat_atom_var]  = "-x%d";
-			setModelQuiet(print_best);
-		}
-	}
-	if (catAtom && *catAtom) {
-		char f = 0;
-		for (const char* x = catAtom; *x; ++x) {
-			POTASSCO_REQUIRE(*x != '\n', "cat_atom: Invalid format string - new line not allowed");
-			if (*x == '%')  {
-				POTASSCO_REQUIRE(*++x, "cat_atom: Invalid format string - missing format specifier");
-				if (*x != '%') {
-					POTASSCO_REQUIRE(f == 0, "cat_atom: Invalid format string - too many arguments");
-					POTASSCO_REQUIRE(std::strchr("sd0", *x) != 0, "cat_atom: Invalid format string - invalid format specifier");
-					f = *x;
-				}
-			}
-		}
-		if (f == '0') {
-			std::size_t len = std::strlen(catAtom);
-			fmt_.reserve((len * 2) + 2);
-			fmt_.append(catAtom).append(1, '\0').append(1, '-').append(catAtom);
-			std::string::size_type p = fmt_.find("%0") + 1;
-			fmt_[p] = 's';
-			fmt_[len + 2 + p] = 'd';
-			format[cat_atom_name] = fmt_.c_str();
-			format[cat_atom_var]  = fmt_.c_str() + len + 1;
-		}
-		else {
-			format[f == 's' ? cat_atom_name : cat_atom_var] = catAtom;
-		}
-	}
-	POTASSCO_REQUIRE(*format[cat_atom_var] == '-' , "cat_atom: Invalid format string - must start with '-'");
-	ifs_[0] = ifs;
-	ifs_[1] = 0;
-	width_  = 13+(int)strlen(format[cat_comment]);
-	progress_.clear();
-}
-TextOutput::~TextOutput() {}
+static std::string prettify(const std::string& str) {
+    if (str.size() < 40) {
+        return str;
+    }
+    std::string t("...");
+    t.append(str.end() - 38, str.end());
+    return t;
+}
+TextOutput::TextOutput(uint32_t verbosity, Format fmt, const char* catAtom, char ifs) : Output(verbosity) {
+    result[res_unknown]    = "UNKNOWN";
+    result[res_sat]        = "SATISFIABLE";
+    result[res_unsat]      = "UNSATISFIABLE";
+    result[res_opt]        = "OPTIMUM FOUND";
+    format[cat_comment]    = "";
+    format[cat_value]      = "";
+    format[cat_objective]  = "Optimization: ";
+    format[cat_result]     = "";
+    format[cat_value_term] = "";
+    format[cat_atom_name]  = "%s";
+    format[cat_atom_var]   = "-%d";
+    if (fmt == format_aspcomp) {
+        format[cat_comment]   = "% ";
+        format[cat_value]     = "ANSWER\n";
+        format[cat_objective] = "COST ";
+        format[cat_atom_name] = "%s.";
+        result[res_sat]       = "";
+        result[res_unsat]     = "INCONSISTENT";
+        result[res_opt]       = "OPTIMUM";
+        setModelQuiet(print_best);
+        setOptQuiet(print_best);
+    }
+    else if (fmt == format_sat09 || fmt == format_pb09) {
+        format[cat_comment]    = "c ";
+        format[cat_value]      = "v ";
+        format[cat_objective]  = "o ";
+        format[cat_result]     = "s ";
+        format[cat_value_term] = "0";
+        if (fmt == format_pb09) {
+            format[cat_value_term] = "";
+            format[cat_atom_var]   = "-x%d";
+            setModelQuiet(print_best);
+        }
+    }
+    if (catAtom && *catAtom) {
+        char f = 0;
+        for (const char* x = catAtom; *x; ++x) {
+            POTASSCO_CHECK_PRE(*x != '\n', "cat_atom: Invalid format string - new line not allowed");
+            if (*x == '%') {
+                POTASSCO_CHECK_PRE(*++x, "cat_atom: Invalid format string - missing format specifier");
+                if (*x != '%') {
+                    POTASSCO_CHECK_PRE(f == 0, "cat_atom: Invalid format string - too many arguments");
+                    POTASSCO_CHECK_PRE(std::strchr("sd0", *x) != nullptr,
+                                       "cat_atom: Invalid format string - invalid format specifier");
+                    f = *x;
+                }
+            }
+        }
+        if (f == '0') {
+            auto len = std::strlen(catAtom);
+            fmt_.reserve((len * 2) + 2);
+            fmt_.append(catAtom).append(1, '\0').append(1, '-').append(catAtom);
+            auto p                = fmt_.find("%0") + 1;
+            fmt_[p]               = 's';
+            fmt_[len + 2 + p]     = 'd';
+            format[cat_atom_name] = fmt_.c_str();
+            format[cat_atom_var]  = fmt_.c_str() + len + 1;
+        }
+        else {
+            format[f == 's' ? cat_atom_name : cat_atom_var] = catAtom;
+        }
+    }
+    POTASSCO_CHECK_PRE(*format[cat_atom_var] == '-', "cat_atom: Invalid format string - must start with '-'");
+    ifs_[0] = ifs;
+    ifs_[1] = 0;
+    width_  = 13 + static_cast(strlen(format[cat_comment]));
+    progress_.clear();
+}
+TextOutput::~TextOutput() = default;
 
-void TextOutput::comment(uint32 v, const char* fmt, ...) const {
-	if (verbosity() >= v) {
-		printf("%s", format[cat_comment]);
-		va_list args;
-		va_start(args, fmt);
-		vfprintf(stdout, fmt, args);
-		va_end(args);
-		fflush(stdout);
-	}
+void TextOutput::comment(uint32_t v, const char* fmt, ...) const {
+    if (verbosity() >= v) {
+        printf("%s", format[cat_comment]);
+        POTASSCO_WARNING_PUSH()
+        POTASSCO_WARNING_IGNORE_CLANG("-Wformat-nonliteral")
+        va_list args;
+        va_start(args, fmt);
+        vfprintf(stdout, fmt, args);
+        va_end(args);
+        POTASSCO_WARNING_POP()
+        fflush(stdout);
+    }
 }
 
-void TextOutput::run(const char* solver, const char* version, const std::string* begInput, const std::string* endInput) {
-	if (!version) version = "";
-	if (solver) comment(1, "%s version %s\n", solver, version);
-	if (begInput != endInput) {
-		comment(1, "Reading from %s%s\n", prettify(*begInput).c_str(), (endInput - begInput) > 1 ? " ..." : "");
-	}
+void TextOutput::run(const char* solver, const char* version, const std::string* begInput,
+                     const std::string* endInput) {
+    if (solver) {
+        comment(1, "%s version %s\n", solver, version ? version : "");
+    }
+    if (begInput != endInput) {
+        comment(1, "Reading from %s%s\n", prettify(*begInput).c_str(), (endInput - begInput) > 1 ? " ..." : "");
+    }
 }
 void TextOutput::shutdown() {}
 void TextOutput::printSummary(const ClaspFacade::Summary& run, bool final) {
-	if (final && callQ() != print_no){
-		comment(1, "%s\n", finalSep);
-	}
-	const char* res = result[res_unknown];
-	if      (run.unsat()) { res = result[res_unsat]; }
-	else if (run.sat())   { res = !run.optimum() ? result[res_sat] : result[res_opt]; }
-	if (*res) { printLN(cat_result, "%s", res); }
-	if (verbosity() || stats(run)) {
-		printBR(cat_comment);
-		if (run.result.interrupted()){ printKeyValue((run.result.signal != SIGALRM ? "INTERRUPTED" : "TIME LIMIT"), "%u\n", uint32(1));  }
-		const char* const moreStr = run.complete() ? "" :  "+";
-		printKey("Models");
-		printf("%" PRIu64 "%s\n", run.numEnum, moreStr);
-		if (run.sat()) {
-			if (run.consequences()) { printLN(cat_comment, "  %-*s: %s", width_-2, run.consequences(), (run.complete()?"yes":"unknown")); }
-			if (run.costs())        { printKeyValue("  Optimum", "%s\n", run.optimum()?"yes":"unknown"); }
-			if (run.optimize())     {
-				if (run.optimal() > 1){ printKeyValue("  Optimal", "%" PRIu64"\n", run.optimal()); }
-				printKey("Optimization");
-				printCostsImpl(*run.costs(), ' ');
-				printf("\n");
-			}
-			if (run.consequences()) {
-				printKey("Consequences");
-				printf("%u%s\n", numCons(run.ctx().output, *run.model()).first, moreStr);
-			}
-		}
-		if (run.hasLower() && !run.optimum()) {
-			printKey("Bounds");
-			printBounds(run.lower(), run.costs() ? *run.costs() : SumVec());
-			printf("\n");
-		}
-		if (final) { printKeyValue("Calls", "%u\n", run.step + 1); }
-		printKey("Time");
-		printf("%.3fs (Solving: %.2fs 1st Model: %.2fs Unsat: %.2fs)\n"
-			, run.totalTime
-			, run.solveTime
-			, run.satTime
-			, run.unsatTime);
-		printKeyValue("CPU Time", "%.3fs\n", run.cpuTime);
-		if (run.ctx().concurrency() > 1) {
-			printKeyValue("Threads", "%-8u", run.ctx().concurrency());
-			printf(" (Winner: %u)\n", run.ctx().winner());
-		}
-	}
+    if (final && callQ() != print_no) {
+        comment(1, "%s\n", acc_sep);
+    }
+    const char* res = result[res_unknown];
+    if (run.unsat()) {
+        res = result[res_unsat];
+    }
+    else if (run.sat()) {
+        res = not run.optimum() ? result[res_sat] : result[res_opt];
+    }
+    if (*res) {
+        PRINT_LN(cat_result, "%s", res);
+    }
+    if (verbosity() || stats(run)) {
+        PRINT_BR(cat_comment);
+        if (run.result.interrupted()) {
+            PRINT_KEY_VALUE((run.result.signal != SIGALRM ? "INTERRUPTED" : "TIME LIMIT"), "%u\n", 1u);
+        }
+        const char* const moreStr = run.complete() ? "" : "+";
+        PRINT_KEY("Models");
+        printf("%" PRIu64 "%s\n", run.numEnum, moreStr);
+        if (run.sat()) {
+            if (run.consequences()) {
+                PRINT_LN(cat_comment, "  %-*s: %s", width_ - 2, run.consequences(),
+                         (run.complete() ? "yes" : "unknown"));
+            }
+            if (run.hasCosts()) {
+                PRINT_KEY_VALUE("  Optimum", "%s\n", run.optimum() ? "yes" : "unknown");
+            }
+            if (run.optimize()) {
+                if (run.optimal() > 1) {
+                    PRINT_KEY_VALUE("  Optimal", "%" PRIu64 "\n", run.optimal());
+                }
+                PRINT_KEY("Optimization");
+                printCostsImpl(run.costs(), ' ');
+                printf("\n");
+            }
+            if (run.consequences()) {
+                PRINT_KEY("Consequences");
+                printf("%u%s\n", numCons(run.ctx().output, *run.model()).first, moreStr);
+            }
+        }
+        if (run.hasLower() && not run.optimum()) {
+            PRINT_KEY("Bounds");
+            printBounds(run.lower(), run.costs());
+            printf("\n");
+        }
+        if (final) {
+            PRINT_KEY_VALUE("Calls", "%u\n", run.step + 1);
+        }
+        PRINT_KEY("Time");
+        printf("%.3fs (Solving: %.2fs 1st Model: %.2fs Unsat: %.2fs)\n", run.totalTime, run.solveTime, run.satTime,
+               run.unsatTime);
+        PRINT_KEY_VALUE("CPU Time", "%.3fs\n", run.cpuTime);
+        if (run.ctx().concurrency() > 1) {
+            PRINT_KEY_VALUE("Threads", "%-8u", run.ctx().concurrency());
+            printf(" (Winner: %u)\n", run.ctx().winner());
+        }
+    }
 }
 void TextOutput::printStatistics(const ClaspFacade::Summary& run, bool) {
-	printBR(cat_comment);
-	accu_ = true;
-	run.accept(*this);
+    PRINT_BR(cat_comment);
+    accu_ = true;
+    run.accept(*this);
 }
 void TextOutput::startStep(const ClaspFacade& f) {
-	Output::startStep(f);
-	setState(Event::subsystem_facade, 0, 0);
-	if (callQ() != print_no) {
-		comment(1, "%s\n", rowSep);
-		comment(2, "%-13s: %d\n", "Call", f.step()+1);
-	}
+    Output::startStep(f);
+    setState(Event::subsystem_facade, 0, nullptr);
+    if (callQ() != print_no) {
+        comment(1, "%s\n", row_sep);
+        comment(2, "%-13s: %d\n", "Call", f.step() + 1);
+    }
 }
 void TextOutput::stopStep(const ClaspFacade::Summary& s) {
-	setState(Event::subsystem_facade, 0, 0);
-	comment(2 - (callQ() != print_no), "%s\n", rowSep);
-	Output::stopStep(s);
+    setState(Event::subsystem_facade, 0, nullptr);
+    comment(2 - (callQ() != print_no), "%s\n", row_sep);
+    Output::stopStep(s);
 }
 void TextOutput::onEvent(const Event& ev) {
-	typedef SatElite::Progress SatPre;
-	if (ev.verb <= verbosity() && ev.system != Event::subsystem_facade) {
-		if (ev.system == state_) {
-			if      (ev.system == Event::subsystem_solve)       { printSolveProgress(ev); }
-			else if (const SatPre* sat = event_cast(ev)){
-				if      (sat->op != SatElite::Progress::event_algorithm) { comment(2, "%-13s: %c: %8u/%-8u\r", satPre, (char)sat->op, sat->cur, sat->max); }
-				else if (sat->cur!= sat->max)                            { setState(ev.system, 2, satPre); }
-				else {
-					SatPreprocessor* p = sat->self;
-					double tEnd = RealTime::getTime();
-					comment(2, "%-13s: %.3fs (ClRemoved: %u ClAdded: %u LitsStr: %u)\n", satPre, tEnd - stTime_, p->stats.clRemoved, p->stats.clAdded, p->stats.litsRemoved);
-					state_ = Event::subsystem_facade;
-				}
-			}
-		}
-		else if (const LogEvent* log = event_cast(ev)) {
-			setState(ev.system, ev.verb, log->msg);
-		}
-	}
-	Output::onEvent(ev);
-}
-void TextOutput::setState(uint32 state, uint32 verb, const char* m) {
-	double ts = RealTime::getTime();
-	if (verb <= verbosity()) {
-		if (state_ == Event::subsystem_load || state_ == Event::subsystem_prepare) {
-			printf("%.3fs\n", ts - stTime_);
-		}
-		if      (state == Event::subsystem_load)   { comment(2, "%-13s: ", m ? m : "Reading"); }
-		else if (state == Event::subsystem_prepare){ comment(2, "%-13s:%s", m ? m : "Preprocessing", m == satPre ? "\r" : " "); }
-		else if (state == Event::subsystem_solve)  { comment(1, "Solving...\n"); }
-	}
-	progress_.clear();
-	stTime_ = ts;
-	state_  = state;
+    using SatPre = SatElite::Progress;
+    if (ev.verb <= verbosity() && ev.system != Event::subsystem_facade) {
+        if (ev.system == state_) {
+            if (ev.system == Event::subsystem_solve) {
+                printSolveProgress(ev);
+            }
+            else if (const auto* sat = event_cast(ev)) {
+                if (sat->op != SatElite::Progress::event_algorithm) {
+                    comment(2, "%-13s: %c: %8u/%-8u\r", sat_pre, static_cast(sat->op), sat->cur, sat->max);
+                }
+                else if (sat->cur != sat->max) {
+                    setState(ev.system, 2, sat_pre);
+                }
+                else {
+                    auto*  p    = sat->self;
+                    double tEnd = RealTime::getTime();
+                    comment(2, "%-13s: %.3fs (ClRemoved: %u ClAdded: %u LitsStr: %u)\n", sat_pre, tEnd - stTime_,
+                            p->stats.clRemoved, p->stats.clAdded, p->stats.litsRemoved);
+                    state_ = Event::subsystem_facade;
+                }
+            }
+        }
+        else if (const auto* log = event_cast(ev)) {
+            setState(ev.system, ev.verb, log->msg);
+        }
+    }
+    Output::onEvent(ev);
+}
+
+void TextOutput::setState(uint32_t state, uint32_t verb, const char* m) {
+    double ts = RealTime::getTime();
+    if (verb <= verbosity()) {
+        if (state_ == Event::subsystem_load || state_ == Event::subsystem_prepare) {
+            printf("%.3fs\n", ts - stTime_);
+        }
+        if (state == Event::subsystem_load) {
+            comment(2, "%-13s: ", m ? m : "Reading");
+        }
+        else if (state == Event::subsystem_prepare) {
+            comment(2, "%-13s:%s", m ? m : "Preprocessing", m == sat_pre ? "\r" : " ");
+        }
+        else if (state == Event::subsystem_solve) {
+            comment(1, "Solving...\n");
+        }
+    }
+    progress_.clear();
+    stTime_ = ts;
+    state_  = state;
 }
+
 void TextOutput::printSolveProgress(const Event& ev) {
-	if (ev.id == SolveTestEvent::id_s  && (verbosity() & 4) == 0) { return; }
-	if (ev.id == BasicSolveEvent::id_s && (verbosity() & 1) == 0) { return; }
-	char lEnd = '\n';
-	char line[128];
-	int eventId = static_cast(ev.id);
-	Potassco::StringBuilder str(line, sizeof(line));
-	if      (const BasicSolveEvent* be = event_cast(ev)) { Clasp::Cli::formatEvent(*be, str); }
-	else if (const SolveTestEvent*  te = event_cast(ev) ) { Clasp::Cli::formatEvent(*te, str); lEnd= te->result == -1 ? '\r' : '\n'; }
+    char      lEnd = '\n';
+    char      line[128];
+    int       eventId = static_cast(ev.id);
+    std::span buffer(line, sizeof(line));
+    int       written = -1;
+    if (const auto* be = event_cast(ev)) {
+        if ((verbosity() & 1) == 0) {
+            return;
+        }
+        written = formatEvent(*be, buffer);
+    }
+    else if (const auto* te = event_cast(ev)) {
+        if ((verbosity() & 4) == 0) {
+            return;
+        }
+        written = formatEvent(*te, buffer);
+        lEnd    = te->result == -1 ? '\r' : '\n';
+    }
 #if CLASP_HAS_THREADS
-	else if (const mt::MessageEvent*me = event_cast(ev)){
-		Clasp::Cli::formatEvent(*me, str);
-		eventId = LogEvent::id_s;
-	}
+    else if (const auto* me = event_cast(ev)) {
+        written = formatEvent(*me, buffer);
+        eventId = static_cast(Event::eventId());
+    }
 #endif
-	else if (const LogEvent* log = event_cast(ev))              {
-		char timeBuffer[30];
-		Potassco::StringBuilder time(timeBuffer, sizeof(timeBuffer));
-		time.appendFormat("[Solving+%.3fs]", RealTime::getTime() - stTime_);
-		str.appendFormat("%2u:L| %-30s %-38s |", log->solver->id(), time.c_str(), log->msg);
-	}
-	else                                                                  { return; }
-	str.appendFormat(" %10.3fs |", elapsedTime());
-	FileLock lock(stdout);
-	if (progress_.lines <= 0 || eventId != progress_.last) {
-		if (progress_.lines <= 0) {
-			const char* prefix = format[cat_comment];
-			if ((this->verbosity() & 1) != 0 || ev.id == SolveTestEvent::id_s) {
-				printf("%s%s\n"
-					"%sID:T       Vars           Constraints         State            Limits            Time     |\n"
-					"%s       #free/#fixed   #problem/#learnt  #conflicts/ratio #conflict/#learnt                |\n"
-					"%s%s\n", prefix, rowSep, prefix, prefix, prefix, rowSep);
-			}
-			else {
-				printf("%s%s\n"
-					"%sID:T       Info                     Info                      Info               Time     |\n"
-					"%s%s\n", prefix, rowSep, prefix, prefix, rowSep);
-			}
-			progress_.lines = 20;
-		}
-		else if (progress_.last != -1) {
-			printLN(cat_comment, "%s", rowSep);
-		}
-		progress_.last = eventId;
-	}
-	progress_.lines -= static_cast(lEnd == '\n');
-	printf("%s%s%c", format[cat_comment], line, lEnd);
-}
-
-static inline bool endsWith(const char* str, char c) {
-	return *str && str[strlen(str) - 1] == c;
+    else if (const auto* log = event_cast(ev)) {
+        char tb[30];
+        tb[0] = 0;
+        snprintf(tb, std::size(tb), "[Solving+%.3fs]", RealTime::getTime() - stTime_);
+        written = snprintf(buffer.data(), buffer.size(), "%2u:L| %-30s %-38.38s |", log->solver->id(), tb, log->msg);
+    }
+    if (written < 0 || static_cast(written) >= buffer.size()) {
+        return;
+    }
+    buffer = buffer.subspan(static_cast(written));
+    snprintf(buffer.data(), buffer.size(), " %10.3fs |", elapsedTime());
+    FileLock lock(stdout);
+    if (progress_.lines <= 0 || eventId != progress_.last) {
+        if (progress_.lines <= 0) {
+            const char* prefix = format[cat_comment];
+            if ((this->verbosity() & 1) != 0 || ev.id == Event::eventId()) {
+                printf("%s%s\n"
+                       "%sID:T       Vars           Constraints         State            Limits            Time     |\n"
+                       "%s       #free/#fixed   #problem/#learnt  #conflicts/ratio #conflict/#learnt                |\n"
+                       "%s%s\n",
+                       prefix, row_sep, prefix, prefix, prefix, row_sep);
+            }
+            else {
+                printf("%s%s\n"
+                       "%sID:T       Info                     Info                      Info               Time     |\n"
+                       "%s%s\n",
+                       prefix, row_sep, prefix, prefix, row_sep);
+            }
+            progress_.lines = 20;
+        }
+        else if (progress_.last != -1) {
+            PRINT_LN(cat_comment, "%s", row_sep);
+        }
+        progress_.last = eventId;
+    }
+    progress_.lines -= static_cast(lEnd == '\n');
+    printf("%s%s%c", format[cat_comment], line, lEnd);
 }
 
 const char* TextOutput::getIfsSuffix(char ifs, CategoryKey c) const {
-	return ifs != '\n' || endsWith(format[c], '\n') ? "" : format[c];
+    return ifs != '\n' || std::string_view(format[c]).ends_with('\n') ? "" : format[c];
 }
-const char* TextOutput::getIfsSuffix(CategoryKey c) const {  return getIfsSuffix(ifs_[0], c);  }
+const char* TextOutput::getIfsSuffix(CategoryKey c) const { return getIfsSuffix(ifs_[0], c); }
 const char* TextOutput::fieldSeparator() const { return ifs_; }
-bool TextOutput::clearProgress(int nLines) {
-	if (progress_.last != -1) {
-		if (progress_.last != INT_MAX ) { progress_.last = INT_MAX; comment(2, "%s\n", rowSep); }
-		progress_.lines -= nLines;
-		return true;
-	}
-	return false;
-}
-int TextOutput::printSep(CategoryKey k) const { return printf("%s%s", fieldSeparator(), getIfsSuffix(k)); }
-uintp TextOutput::doPrint(const OutPair& s, UPtr data) {
-	const uint32 MSB = 31u;
-	uint32& accu    = reinterpret_cast(data)->first;
-	uint32& maxLine = reinterpret_cast(data)->second;
-	if (accu == 0 && *getIfsSuffix(cat_value)) store_set_bit(accu, MSB);
-	const char* suf = test_bit(accu, MSB) ? format[cat_value] : "";
-	store_clear_bit(accu, MSB);
-	if      (accu < maxLine) { accu += printf("%c%s", *fieldSeparator(), suf); }
-	else if (!maxLine)       { maxLine = s.first || *fieldSeparator() != ' ' ? UINT32_MAX : 70; }
-	else                     { printf("%c%s", '\n', getIfsSuffix('\n', cat_value)); accu = 0; }
-	if (s.first){ accu += printf(format[cat_atom_name], s.first); }
-	else        { accu += printf(format[cat_atom_var] + !s.second.sign(), static_cast(s.second.var())); }
-	if (*suf) store_set_bit(accu, MSB);
-	return data;
+bool        TextOutput::clearProgress(int nLines) {
+    if (progress_.last != -1) {
+        if (progress_.last != INT_MAX) {
+            progress_.last = INT_MAX;
+            comment(2, "%s\n", row_sep);
+        }
+        progress_.lines -= nLines;
+        return true;
+    }
+    return false;
+}
+int       TextOutput::printSep(CategoryKey k) const { return printf("%s%s", fieldSeparator(), getIfsSuffix(k)); }
+uintptr_t TextOutput::doPrint(const OutPair& s, UPtr data) {
+    constexpr uint32_t msb     = 31u;
+    uint32_t&          accu    = reinterpret_cast(data)->first;
+    uint32_t&          maxLine = reinterpret_cast(data)->second;
+    if (accu == 0 && *getIfsSuffix(cat_value)) {
+        Potassco::store_set_bit(accu, msb);
+    }
+    const char* suf = Potassco::test_bit(accu, msb) ? format[cat_value] : "";
+    Potassco::store_clear_bit(accu, msb);
+    if (accu < maxLine) {
+        accu += static_cast(printf("%c%s", *fieldSeparator(), suf));
+    }
+    else if (not maxLine) {
+        maxLine = s.first || *fieldSeparator() != ' ' ? UINT32_MAX : 70;
+    }
+    else {
+        printf("%c%s", '\n', getIfsSuffix('\n', cat_value));
+        accu = 0;
+    }
+    POTASSCO_WARNING_PUSH()
+    POTASSCO_WARNING_IGNORE_GNU("-Wformat-nonliteral") // format not a string literal
+    if (s.first) {
+        accu += static_cast(printf(format[cat_atom_name], s.first));
+    }
+    else {
+        accu +=
+            static_cast(printf(format[cat_atom_var] + not s.second.sign(), static_cast(s.second.var())));
+    }
+    POTASSCO_WARNING_POP()
+    if (*suf) {
+        Potassco::store_set_bit(accu, msb);
+    }
+    return data;
 }
 void TextOutput::printValues(const OutputTable& out, const Model& m) {
-	printf("%s", format[cat_value]);
-	UPair data;
-	printWitness(out, m, reinterpret_cast(&data));
-	if (*format[cat_value_term]) {
-		printf("%c%s%s", *fieldSeparator(), getIfsSuffix(cat_value), format[cat_value_term]);
-	}
-	printf("\n");
+    printf("%s", format[cat_value]);
+    UPair data;
+    printWitness(out, m, reinterpret_cast(&data));
+    if (*format[cat_value_term]) {
+        printf("%c%s%s", *fieldSeparator(), getIfsSuffix(cat_value), format[cat_value_term]);
+    }
+    printf("\n");
 }
 void TextOutput::printMeta(const OutputTable& out, const Model& m) {
-	if (m.consequences()) {
-		UPair cons = numCons(out, m);
-		printLN(cat_comment, "Consequences: [%u;%u]", cons.first, cons.first + cons.second);
-	}
-	if (m.costs) {
-		printf("%s", format[cat_objective]);
-		printCosts(*m.costs);
-		printf("\n");
-	}
-}
-void TextOutput::printModelValues(const OutputTable& out, const Model& m) {
-	printValues(out, m);
+    if (m.consequences()) {
+        UPair cons = numCons(out, m);
+        PRINT_LN(cat_comment, "Consequences: [%u;%u]", cons.first, cons.first + cons.second);
+    }
+    if (m.hasCosts()) {
+        printf("%s", format[cat_objective]);
+        printCosts(m.costs);
+        printf("\n");
+    }
 }
+
+void TextOutput::printModelValues(const OutputTable& out, const Model& m) { printValues(out, m); }
+
 void TextOutput::printModel(const OutputTable& out, const Model& m, PrintLevel x) {
-	FileLock lock(stdout);
-	bool printValues = modelQ() <= x;
-	bool printOpt    = optQ() <= x;
-	if (printValues || printOpt) {
-		const char* type = !m.up ? "Answer" : "Update";
-		clearProgress(3);
-		comment(1, "%s: %" PRIu64" (Time: %.3fs)\n", type, m.num, modelTime());
-		if (printValues) { printModelValues(out, m); }
-		if (printOpt)    { printMeta(out, m); }
-	}
+    FileLock lock(stdout);
+    bool     printValues = modelQ() <= x;
+    bool     printOpt    = optQ() <= x;
+    if (printValues || printOpt) {
+        const char* type = not m.up ? "Answer" : "Update";
+        clearProgress(3);
+        comment(1, "%s: %" PRIu64 " (Time: %.3fs)\n", type, m.num, modelTime());
+        if (printValues) {
+            printModelValues(out, m);
+        }
+        if (printOpt) {
+            printMeta(out, m);
+        }
+    }
 }
 void TextOutput::printUnsat(const OutputTable& out, const LowerBound* lower, const Model* prevModel) {
-	FileLock lock(stdout);
-	if (lower && optQ() == print_all) {
-		const SumVec* costs = prevModel ? prevModel->costs : 0;
-		double ts = elapsedTime();
-		clearProgress(1);
-		comment(0, "%-12s: ", "Progression");
-		if (costs && costs->size() > lower->level) {
-			for (uint32 i = 0; i != lower->level; ++i) {
-				printf("%" PRId64 " ", (*costs)[i]);
-			}
-			wsum_t ub = (*costs)[lower->level];
-			int w = 1; for (wsum_t x = ub; x > 9; ++w) { x /= 10; }
-			double err = double(ub - lower->bound)/double(lower->bound);
-			if (err < 0) { err = -err; }
-			printf("[%*" PRId64 ";%" PRId64 "] (Error: %g ", w, lower->bound, ub, err);
-		}
-		else {
-			printf("[%6" PRId64 ";inf] (", lower->bound);
-		}
-		printf("Time: %.3fs)\n", ts);
-	}
-	if (prevModel && prevModel->up && optQ() == print_all) {
-		printMeta(out, *prevModel);
-	}
+    FileLock lock(stdout);
+    if (lower && optQ() == print_all) {
+        auto   costs = prevModel ? prevModel->costs : SumView{};
+        double ts    = elapsedTime();
+        clearProgress(1);
+        comment(0, "%-12s: ", "Progression");
+        if (costs.size() > lower->level) {
+            for (auto i : irange(lower->level)) { printf("%" PRId64 " ", costs[i]); }
+            Wsum_t ub = costs[lower->level];
+            int    w  = 1;
+            for (Wsum_t x = ub; x > 9; ++w) { x /= 10; }
+            double err = static_cast(ub - lower->bound) / static_cast(lower->bound);
+            if (err < 0) {
+                err = -err;
+            }
+            printf("[%*" PRId64 ";%" PRId64 "] (Error: %g ", w, lower->bound, ub, err);
+        }
+        else {
+            printf("[%6" PRId64 ";inf] (", lower->bound);
+        }
+        printf("Time: %.3fs)\n", ts);
+    }
+    if (prevModel && prevModel->up && optQ() == print_all) {
+        printMeta(out, *prevModel);
+    }
 }
 
-void TextOutput::printBounds(const SumVec& lower, const SumVec& upper) const {
-	const char* sep = "";
-	for (uint32 i = 0, uMax = upper.size(), lMax = lower.size(), end = std::max(uMax, lMax); i != end; ++i) {
-		if (i >= uMax) {
-			printf("%s[%" PRId64";*]", sep, lower[i]);
-		}
-		else if (i >= lMax || lower[i] == upper[i]) {
-			printf("%s%" PRId64, sep, upper[i]);
-		}
-		else {
-			printf("%s[%" PRId64";%" PRId64"]", sep, lower[i], upper[i]);
-		}
-		sep = " ";
-	}
+void TextOutput::printBounds(SumView lower, SumView upper) const {
+    const char* sep = "";
+    for (auto uMax = size32(upper), lMax = size32(lower); auto i : irange(std::max(uMax, lMax))) {
+        if (i >= uMax) {
+            printf("%s[%" PRId64 ";*]", sep, lower[i]);
+        }
+        else if (i >= lMax || lower[i] == upper[i]) {
+            printf("%s%" PRId64, sep, upper[i]);
+        }
+        else {
+            printf("%s[%" PRId64 ";%" PRId64 "]", sep, lower[i], upper[i]);
+        }
+        sep = " ";
+    }
 }
 
-void TextOutput::printCosts(const SumVec& costs) const {
-	printCostsImpl(costs, *fieldSeparator(), getIfsSuffix(cat_objective));
+void TextOutput::printCosts(SumView costs) const {
+    printCostsImpl(costs, *fieldSeparator(), getIfsSuffix(cat_objective));
 }
 
-void TextOutput::printCostsImpl(const SumVec& costs, char ifs, const char* ifsSuffix) const {
-	if (!costs.empty()) {
-		printf("%" PRId64, costs[0]);
-		for (uint32 i = 1, end = (uint32)costs.size(); i != end; ++i) {
-			printf("%c%s%" PRId64, ifs, ifsSuffix, costs[i]);
-		}
-	}
+void TextOutput::printCostsImpl(SumView costs, char ifs, const char* ifsSuffix) const {
+    if (not costs.empty()) {
+        printf("%" PRId64, costs[0]);
+        for (auto w : costs.subspan(1)) { printf("%c%s%" PRId64, ifs, ifsSuffix, w); }
+    }
 }
 bool TextOutput::startSection(const char* n) const {
-	printLN(cat_comment, "============ %s Stats ============", n);
-	printBR(cat_comment);
-	return true;
+    PRINT_LN(cat_comment, "============ %s Stats ============", n);
+    PRINT_BR(cat_comment);
+    return true;
 }
-void TextOutput::startObject(const char* n, uint32 i) const {
-	printLN(cat_comment, "[%s %u]", n, i);
-	printBR(cat_comment);
+void TextOutput::startObject(const char* n, uint32_t i) const {
+    PRINT_LN(cat_comment, "[%s %u]", n, i);
+    PRINT_BR(cat_comment);
 }
 bool TextOutput::visitThreads(Operation op) {
-	accu_ = false;
-	return op != Enter || startSection("Thread");
+    accu_ = false;
+    return op != enter || startSection("Thread");
 }
 bool TextOutput::visitTester(Operation op) {
-	accu_ = false;
-	return op != Enter || startSection("Tester");
+    accu_ = false;
+    return op != enter || startSection("Tester");
 }
-void TextOutput::visitThread(uint32 i, const SolverStats& stats) {
-	startObject("Thread", i);
-	TextOutput::visitSolverStats(stats);
+void TextOutput::visitThread(uint32_t i, const SolverStats& stats) {
+    startObject("Thread", i);
+    TextOutput::visitSolverStats(stats);
 }
-void TextOutput::visitHcc(uint32 i, const ProblemStats& p, const SolverStats& s) {
-	startObject("HCC", i);
-	TextOutput::visitProblemStats(p);
-	TextOutput::visitSolverStats(s);
+void TextOutput::visitHcc(uint32_t i, const ProblemStats& p, const SolverStats& s) {
+    startObject("HCC", i);
+    TextOutput::visitProblemStats(p);
+    TextOutput::visitSolverStats(s);
 }
 void TextOutput::visitLogicProgramStats(const Asp::LpStats& lp) {
-	using namespace Asp;
-	uint32 rFinal = lp.rules[1].sum(), rOriginal = lp.rules[0].sum();
-	printKeyValue("Rules", "%-8u", rFinal);
-	if (rFinal != rOriginal) {
-		printf(" (Original: %u)", rOriginal);
-	}
-	printf("\n");
-	Potassco::StringBuilder out;
-	for (uint32 i = 0; i != RuleStats::numKeys(); ++i) {
-		if (i == RuleStats::Normal) { continue; }
-		if (uint32 r = lp.rules[0][i]) {
-			printKeyValue(out.append("  ").append(RuleStats::toStr(i)).c_str(), "%-8u", lp.rules[1][i]);
-			if (r != lp.rules[1][i]) { printf(" (Original: %u)", r); }
-			printf("\n");
-			out.clear();
-		}
-	}
-	printKeyValue("Atoms", "%-8u", lp.atoms);
-	if (lp.auxAtoms) {
-		printf(" (Original: %u Auxiliary: %u)", lp.atoms-lp.auxAtoms, lp.auxAtoms);
-	}
-	printf("\n");
-	if (lp.disjunctions[0]) {
-		printKeyValue("Disjunctions", "%-8u", lp.disjunctions[1]);
-		printf(" (Original: %u)\n", lp.disjunctions[0]);
-	}
-	uint32 bFinal = lp.bodies[1].sum(), bOriginal = lp.bodies[0].sum();
-	printKeyValue("Bodies", "%-8u", bFinal);
-	if (bFinal != bOriginal) {
-		printf(" (Original: %u)", bOriginal);
-	}
-	printf("\n");
-	for (uint32 i = 1; i != BodyStats::numKeys(); ++i) {
-		if (uint32 b = lp.bodies[0][i]) {
-			printKeyValue(out.append("  ").append(BodyStats::toStr(i)).c_str(), "%-8u", lp.bodies[1][i]);
-			if (b != lp.bodies[1][i]) { printf(" (Original: %u)", b); }
-			printf("\n");
-			out.clear();
-		}
-	}
-	if (lp.eqs() > 0) {
-		printKeyValue("Equivalences", "%-8u", lp.eqs());
-		printf(" (Atom=Atom: %u Body=Body: %u Other: %u)\n"
-			, lp.eqs(Var_t::Atom)
-			, lp.eqs(Var_t::Body)
-			, lp.eqs(Var_t::Hybrid));
-	}
-	printKey("Tight");
-	if      (lp.sccs == 0)              { printf("Yes"); }
-	else if (lp.sccs != PrgNode::noScc) { printf("%-8s (SCCs: %u Non-Hcfs: %u Nodes: %u Gammas: %u)", "No", lp.sccs, lp.nonHcfs, lp.ufsNodes, lp.gammas); }
-	else                                { printf("N/A"); }
-	printf("\n");
+    using namespace Asp;
+    uint32_t rFinal = lp.rules[1].sum(), rOriginal = lp.rules[0].sum();
+    PRINT_KEY_VALUE("Rules", "%-8u", rFinal);
+    if (rFinal != rOriginal) {
+        printf(" (Original: %u)", rOriginal);
+    }
+    printf("\n");
+    for (auto i : irange(RuleStats::numKeys())) {
+        if (i == RuleStats::normal) {
+            continue;
+        }
+        if (uint32_t r = lp.rules[0][i]) {
+            PRINT_S_KEY_VALUE(RuleStats::toStr(i), "%-8u", lp.rules[1][i]);
+            if (r != lp.rules[1][i]) {
+                printf(" (Original: %u)", r);
+            }
+            printf("\n");
+        }
+    }
+    PRINT_KEY_VALUE("Atoms", "%-8u", lp.atoms);
+    if (lp.auxAtoms) {
+        printf(" (Original: %u Auxiliary: %u)", lp.atoms - lp.auxAtoms, lp.auxAtoms);
+    }
+    printf("\n");
+    if (lp.disjunctions[0]) {
+        PRINT_KEY_VALUE("Disjunctions", "%-8u", lp.disjunctions[1]);
+        printf(" (Original: %u)\n", lp.disjunctions[0]);
+    }
+    uint32_t bFinal = lp.bodies[1].sum(), bOriginal = lp.bodies[0].sum();
+    PRINT_KEY_VALUE("Bodies", "%-8u", bFinal);
+    if (bFinal != bOriginal) {
+        printf(" (Original: %u)", bOriginal);
+    }
+    printf("\n");
+    for (auto i : irange(1u, BodyStats::numKeys())) {
+        if (uint32_t b = lp.bodies[0][i]) {
+            PRINT_S_KEY_VALUE(BodyStats::toStr(i), "%-8u", lp.bodies[1][i]);
+            if (b != lp.bodies[1][i]) {
+                printf(" (Original: %u)", b);
+            }
+            printf("\n");
+        }
+    }
+    if (lp.eqs() > 0) {
+        PRINT_KEY_VALUE("Equivalences", "%-8u", lp.eqs());
+        printf(" (Atom=Atom: %u Body=Body: %u Other: %u)\n", lp.eqs(VarType::atom), lp.eqs(VarType::body),
+               lp.eqs(VarType::hybrid));
+    }
+    PRINT_KEY("Tight");
+    if (lp.sccs == 0) {
+        printf("Yes");
+    }
+    else if (lp.sccs != PrgNode::no_scc) {
+        printf("%-8s (SCCs: %u Non-Hcfs: %u Nodes: %u Gammas: %u)", "No", lp.sccs, lp.nonHcfs, lp.ufsNodes, lp.gammas);
+    }
+    else {
+        printf("N/A");
+    }
+    printf("\n");
 }
 void TextOutput::visitProblemStats(const ProblemStats& ps) {
-	uint32 sum = ps.numConstraints();
-	printKeyValue("Variables", "%-8u", ps.vars.num);
-	printf(" (Eliminated: %4u Frozen: %4u)\n", ps.vars.eliminated, ps.vars.frozen);
-	printKeyValue("Constraints", "%-8u", sum);
-	printf(" (Binary: %5.1f%% Ternary: %5.1f%% Other: %5.1f%%)\n"
-		, percent(ps.constraints.binary, sum)
-		, percent(ps.constraints.ternary, sum)
-		, percent(ps.constraints.other, sum));
-	if (ps.acycEdges) {
-		printKeyValue("Acyc-Edges", "%-8u\n", ps.acycEdges);
-	}
-	printBR(cat_comment);
+    uint32_t sum = ps.numConstraints();
+    PRINT_KEY_VALUE("Variables", "%-8u", ps.vars.num);
+    printf(" (Eliminated: %4u Frozen: %4u)\n", ps.vars.eliminated, ps.vars.frozen);
+    PRINT_KEY_VALUE("Constraints", "%-8u", sum);
+    printf(" (Binary: %5.1f%% Ternary: %5.1f%% Other: %5.1f%%)\n", percent(ps.constraints.binary, sum),
+           percent(ps.constraints.ternary, sum), percent(ps.constraints.other, sum));
+    if (ps.acycEdges) {
+        PRINT_KEY_VALUE("Acyc-Edges", "%-8u\n", ps.acycEdges);
+    }
+    PRINT_BR(cat_comment);
 }
 void TextOutput::visitSolverStats(const SolverStats& st) {
-	printStats(st);
-	printBR(cat_comment);
+    printStats(st);
+    PRINT_BR(cat_comment);
 }
 
-int TextOutput::printChildKey(unsigned level, const char* key, uint32 idx, const char* pre) const {
-	const unsigned indent = level * 2;
-	int len = 0;
-	printf("%s%-*.*s", format[cat_comment], indent, indent, " ");
-	if      (key) { len = printf("%s", key); }
-	else if (pre) { len = printf("[%s %u]", pre, idx); }
-	else          { len = printf("[%u]", idx); }
-	return (width_ - (int)indent) - len;
+int TextOutput::printChildKey(unsigned level, const char* key, uint32_t idx, const char* prefix) const {
+    const unsigned indent = level * 2;
+    int            len;
+    printf("%s%-*.*s", format[cat_comment], indent, indent, " ");
+    if (key) {
+        len = printf("%s", key);
+    }
+    else if (prefix) {
+        len = printf("[%s %u]", prefix, idx);
+    }
+    else {
+        len = printf("[%u]", idx);
+    }
+    return (width_ - static_cast(indent)) - len;
 }
 
+// NOLINTNEXTLINE(misc-no-recursion)
 void TextOutput::printChildren(const StatisticObject& s, unsigned level, char const* prefix) {
-	const bool map = s.type() == Potassco::Statistics_t::Map;
-	for (uint32 i = 0; i != s.size(); ++i) {
-		const char* key       = map ? s.key(i)  : 0;
-		StatisticObject child = map ? s.at(key) : s[i];
-		if (child.type() == Potassco::Statistics_t::Value) {
-			int align = printChildKey(level, key, i, prefix);
-			printf("%-*s: %g\n", std::max(0, align), "", child.value());
-		}
-		else if (child.type() == Potassco::Statistics_t::Array && key) {
-			printChildren(child, level, key);
-		}
-		else if (child.size()) {
-			printChildKey(level, key, i, prefix);
-			printf("\n");
-			printChildren(child, level + 1);
-		}
-	}
+    const bool map = s.type() == StatsType::map;
+    for (auto i : irange(s)) {
+        const char*     key   = map ? s.key(i) : nullptr;
+        StatisticObject child = map ? s.at(key) : s[i];
+        if (child.type() == StatsType::value) {
+            int align = printChildKey(level, key, i, prefix);
+            printf("%-*s: %g\n", std::max(0, align), "", child.value());
+        }
+        else if (child.type() == StatsType::array && key) {
+            printChildren(child, level, key);
+        }
+        else {
+            std::ignore = printChildKey(level, key, i, prefix);
+            printf("\n");
+            printChildren(child, level + 1);
+        }
+    }
 }
 
 void TextOutput::visitExternalStats(const StatisticObject& stats) {
-	POTASSCO_ASSERT(stats.type() == Potassco::Statistics_t::Map, "Non map statistic!");
-	printChildren(stats);
+    POTASSCO_ASSERT(stats.type() == StatsType::map, "Non map statistic!");
+    printChildren(stats);
 }
 
 void TextOutput::printStats(const SolverStats& st) const {
-	if (!accu_ && st.extra) {
-		printKeyValue("CPU Time", "%.3fs\n", st.extra->cpuTime);
-		printKeyValue("Models", "%" PRIu64"\n", st.extra->models);
-	}
-	printKeyValue("Choices", "%-8" PRIu64, st.choices);
-	if (st.extra && st.extra->domChoices) { printf(" (Domain: %" PRIu64")", st.extra->domChoices); }
-	printf("\n");
-	printKeyValue("Conflicts", "%-8" PRIu64"", st.conflicts);
-	printf(" (Analyzed: %" PRIu64")\n", st.backjumps());
-	printKeyValue("Restarts", "%-8" PRIu64"", st.restarts);
-	if (st.restarts) {
-		printf(" (Average: %.2f Last: %" PRIu64" Blocked: %" PRIu64")", st.avgRestart(), st.lastRestart, st.blRestarts);
-	}
-	printf("\n");
-	if (!st.extra) return;
-	const ExtendedStats& stx = *st.extra;
-	if (stx.hccTests) {
-		printKeyValue("Stab. Tests", "%-8" PRIu64, stx.hccTests);
-		printf(" (Full: %" PRIu64" Partial: %" PRIu64")\n", stx.hccTests - stx.hccPartial, stx.hccPartial);
-	}
-	if (stx.models) {
-		printKeyValue("Model-Level", "%-8.1f\n", stx.avgModel());
-	}
-	printKeyValue("Problems", "%-8" PRIu64, (uint64)stx.gps);
-	printf(" (Average Length: %.2f Splits: %" PRIu64")\n", stx.avgGp(), (uint64)stx.splits);
-	uint64 sum = stx.lemmas();
-	printKeyValue("Lemmas", "%-8" PRIu64, sum);
-	printf(" (Deleted: %" PRIu64")\n", stx.deleted);
-	printKeyValue("  Binary", "%-8" PRIu64, uint64(stx.binary));
-	printf(" (Ratio: %6.2f%%)\n", percent(stx.binary, sum));
-	printKeyValue("  Ternary", "%-8" PRIu64, uint64(stx.ternary));
-	printf(" (Ratio: %6.2f%%)\n", percent(stx.ternary, sum));
-	const char* names[] = {"  Conflict", "  Loop", "  Other"};
-	for (int i = 0; i != sizeof(names)/sizeof(names[0]); ++i) {
-		ConstraintType type = static_cast(i+1);
-		printKeyValue(names[i], "%-8" PRIu64, stx.lemmas(type));
-		printf(" (Average Length: %6.1f Ratio: %6.2f%%) \n", stx.avgLen(type), percent(stx.lemmas(type), sum));
-	}
-	if (stx.distributed || stx.integrated) {
-		printKeyValue("  Distributed", "%-8" PRIu64, stx.distributed);
-		printf(" (Ratio: %6.2f%% Average LBD: %.2f) \n", stx.distRatio()*100.0, stx.avgDistLbd());
-		printKeyValue("  Integrated", "%-8" PRIu64, stx.integrated);
-		if (accu_) { printf(" (Ratio: %6.2f%% ", stx.intRatio()*100.0); }
-		else { printf(" ("); }
-		printf("Unit: %" PRIu64" Average Jumps: %.2f)\n", stx.intImps, stx.avgIntJump());
-	}
-	printJumps(stx.jumps);
+    if (not accu_ && st.extra) {
+        PRINT_KEY_VALUE("CPU Time", "%.3fs\n", st.extra->cpuTime);
+        PRINT_KEY_VALUE("Models", "%" PRIu64 "\n", st.extra->models);
+    }
+    PRINT_KEY_VALUE("Choices", "%-8" PRIu64, st.choices);
+    if (st.extra && st.extra->domChoices) {
+        printf(" (Domain: %" PRIu64 ")", st.extra->domChoices);
+    }
+    printf("\n");
+    PRINT_KEY_VALUE("Conflicts", "%-8" PRIu64 "", st.conflicts);
+    printf(" (Analyzed: %" PRIu64 ")\n", st.backjumps());
+    PRINT_KEY_VALUE("Restarts", "%-8" PRIu64 "", st.restarts);
+    if (st.restarts) {
+        printf(" (Average: %.2f Last: %" PRIu64 " Blocked: %" PRIu64 ")", st.avgRestart(), st.lastRestart,
+               st.blRestarts);
+    }
+    printf("\n");
+    if (not st.extra) {
+        return;
+    }
+    const ExtendedStats& stx = *st.extra;
+    if (stx.hccTests) {
+        PRINT_KEY_VALUE("Stab. Tests", "%-8" PRIu64, stx.hccTests);
+        printf(" (Full: %" PRIu64 " Partial: %" PRIu64 ")\n", stx.hccTests - stx.hccPartial, stx.hccPartial);
+    }
+    if (stx.models) {
+        PRINT_KEY_VALUE("Model-Level", "%-8.1f\n", stx.avgModel());
+    }
+    PRINT_KEY_VALUE("Problems", "%-8" PRIu64, static_cast(stx.gps));
+    printf(" (Average Length: %.2f Splits: %" PRIu64 ")\n", stx.avgGp(), static_cast(stx.splits));
+    uint64_t sum = stx.lemmas();
+    PRINT_KEY_VALUE("Lemmas", "%-8" PRIu64, sum);
+    printf(" (Deleted: %" PRIu64 ")\n", stx.deleted);
+    PRINT_KEY_VALUE("  Binary", "%-8" PRIu64, static_cast(stx.binary));
+    printf(" (Ratio: %6.2f%%)\n", percent(stx.binary, sum));
+    PRINT_KEY_VALUE("  Ternary", "%-8" PRIu64, static_cast(stx.ternary));
+    printf(" (Ratio: %6.2f%%)\n", percent(stx.ternary, sum));
+    const char* names[] = {"  Conflict", "  Loop", "  Other"};
+    for (auto i : irange(names)) {
+        auto type = static_cast(i + 1);
+        PRINT_KEY_VALUE(names[i], "%-8" PRIu64, stx.lemmas(type));
+        printf(" (Average Length: %6.1f Ratio: %6.2f%%) \n", stx.avgLen(type), percent(stx.lemmas(type), sum));
+    }
+    if (stx.distributed || stx.integrated) {
+        PRINT_KEY_VALUE("  Distributed", "%-8" PRIu64, stx.distributed);
+        printf(" (Ratio: %6.2f%% Average LBD: %.2f) \n", stx.distRatio() * 100.0, stx.avgDistLbd());
+        PRINT_KEY_VALUE("  Integrated", "%-8" PRIu64, stx.integrated);
+        if (accu_) {
+            printf(" (Ratio: %6.2f%% ", stx.intRatio() * 100.0);
+        }
+        else {
+            printf(" (");
+        }
+        printf("Unit: %" PRIu64 " Average Jumps: %.2f)\n", stx.intImps, stx.avgIntJump());
+    }
+    printJumps(stx.jumps);
 }
 void TextOutput::printJumps(const JumpStats& st) const {
-	printKeyValue("Backjumps", "%-8" PRIu64, st.jumps);
-	printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64")\n", st.avgJump(), st.maxJump, st.jumpSum);
-	printKeyValue("  Executed", "%-8" PRIu64, st.jumps-st.bounded);
-	printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64" Ratio: %6.2f%%)\n", st.avgJumpEx(), st.maxJumpEx, st.jumped(), st.jumpedRatio()*100.0);
-	printKeyValue("  Bounded", "%-8" PRIu64, st.bounded);
-	printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64" Ratio: %6.2f%%)\n", st.avgBound(), st.maxBound, st.boundSum, 100.0 - (st.jumpedRatio()*100.0));
+    PRINT_KEY_VALUE("Backjumps", "%-8" PRIu64, st.jumps);
+    printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64 ")\n", st.avgJump(), st.maxJump, st.jumpSum);
+    PRINT_KEY_VALUE("  Executed", "%-8" PRIu64, st.jumps - st.bounded);
+    printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64 " Ratio: %6.2f%%)\n", st.avgJumpEx(), st.maxJumpEx, st.jumped(),
+           st.jumpedRatio() * 100.0);
+    PRINT_KEY_VALUE("  Bounded", "%-8" PRIu64, st.bounded);
+    printf(" (Average: %5.2f Max: %3u Sum: %6" PRIu64 " Ratio: %6.2f%%)\n", st.avgBound(), st.maxBound, st.boundSum,
+           100.0 - (st.jumpedRatio() * 100.0));
 }
 
-#undef printKeyValue
-#undef printKey
-#undef printLN
-#undef printBR
-}}
+#undef PRINT_KEY_VALUE
+#undef PRINT_S_KEY_VALUE
+#undef PRINT_KEY
+#undef PRINT_LN
+#undef PRINT_BR
+} // namespace Clasp::Cli
diff --git a/src/clause.cpp b/src/clause.cpp
index ee9659f..3259375 100644
--- a/src/clause.cpp
+++ b/src/clause.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,1067 +22,1229 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
 #include 
-#include 
 
-namespace Clasp { namespace Detail {
-struct GreaterLevel {
-	GreaterLevel(const Solver& s) : solver_(s) {}
-	bool operator()(const Literal& p1, const Literal& p2) const {
-		assert(solver_.value(p1.var()) != value_free && solver_.value(p2.var()) != value_free);
-		return solver_.level(p1.var()) > solver_.level(p2.var());
-	}
-private:
-	GreaterLevel& operator=(const GreaterLevel&);
-	const Solver& solver_;
-};
+#include 
 
-struct Sink {
-	explicit Sink(SharedLiterals* c) : clause(c) {}
-	~Sink() { if (clause) clause->release(); }
-	SharedLiterals* clause;
-};
+#include 
 
-void* alloc(uint32 size) {
-	POTASSCO_PRAGMA_TODO("replace with CACHE_LINE_ALIGNED alloc")
-	return ::operator new(size);
-}
-void free(void* mem) {
-	::operator delete(mem);
+namespace Clasp {
+namespace Detail {
+
+static void* alloc(std::size_t size) {
+    POTASSCO_PRAGMA_TODO("replace with CACHE_LINE_ALIGNED alloc")
+    return ::operator new(size);
 }
+static void free(void* mem) { ::operator delete(mem); }
+using SharedLitsPtr = std::unique_ptr;
 
 } // namespace Detail
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // SharedLiterals
 /////////////////////////////////////////////////////////////////////////////////////////
-SharedLiterals* SharedLiterals::newShareable(const Literal* lits, uint32 size, ConstraintType t, uint32 numRefs) {
-	void* m = Detail::alloc(sizeof(SharedLiterals)+(size*sizeof(Literal)));
-	return new (m) SharedLiterals(lits, size, t, numRefs);
+SharedLiterals* SharedLiterals::newShareable(LitView lits, ConstraintType t, uint32_t numRefs) {
+    void* m = Detail::alloc(sizeof(SharedLiterals) + (lits.size() * sizeof(Literal)));
+    return new (m) SharedLiterals(lits, t, numRefs);
 }
 
-SharedLiterals::SharedLiterals(const Literal* a_lits, uint32 size, ConstraintType t, uint32 refs)
-	: size_type_( (size << 2) + t ) {
-	refCount_ = std::max(uint32(1),refs);
-	if (a_lits) {
-		std::memcpy(lits_, a_lits, size*sizeof(Literal));
-	}
+SharedLiterals::SharedLiterals(LitView lits, ConstraintType t, uint32_t refs)
+    : refCount_(std::max(1u, refs))
+    , size_type_((size32(lits) << 2) + +t) {
+    std::memcpy(lits_, lits.data(), lits.size() * sizeof(Literal));
 }
 
-uint32 SharedLiterals::simplify(Solver& s) {
-	bool   removeFalse = unique();
-	uint32   newSize   = 0;
-	Literal* r         = lits_;
-	Literal* e         = lits_+size();
-	ValueRep v;
-	for (Literal* c = r; r != e; ++r) {
-		if ( (v = s.value(r->var())) == value_free ) {
-			if (c != r) *c = *r;
-			++c; ++newSize;
-		}
-		else if (v == trueValue(*r)) {
-			newSize = 0; break;
-		}
-		else if (!removeFalse) ++c;
-	}
-	if (removeFalse && newSize != size()) {
-		size_type_ = (newSize << 2) | (size_type_ & uint32(3));
-	}
-	return newSize;
+uint32_t SharedLiterals::simplify(Solver& s) {
+    auto falseInc = 1u - unique();
+    auto newSize  = 0u;
+    for (Literal* c = lits_; auto lit : literals()) {
+        if (auto v = s.value(lit.var()); v == value_free) {
+            if (*c != lit) {
+                *c = lit;
+            }
+            ++c;
+            ++newSize;
+        }
+        else if (v == trueValue(lit)) {
+            newSize = 0;
+            break;
+        }
+        else {
+            c += falseInc;
+        }
+    }
+    if (falseInc == 0 && newSize != size()) {
+        size_type_ = (newSize << 2) | (size_type_ & 3u);
+    }
+    return newSize;
 }
 
-void SharedLiterals::release(uint32 n) {
-	if ((refCount_ -= n) == 0) {
-		this->~SharedLiterals();
-		Detail::free(this);
-	}
+void SharedLiterals::release(int n) {
+    if (n > 0 && refCount_.release(static_cast(n))) {
+        this->~SharedLiterals();
+        Detail::free(this);
+    }
 }
 SharedLiterals* SharedLiterals::share() {
-	++refCount_;
-	return this;
+    refCount_.add();
+    return this;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClauseCreator
 /////////////////////////////////////////////////////////////////////////////////////////
-ClauseCreator::ClauseCreator(Solver* s)
-	: solver_(s)
-	, flags_(0){
-}
+ClauseCreator::ClauseCreator(Solver* s) : solver_(s), flags_{} {}
 
 ClauseCreator& ClauseCreator::start(ConstraintType t) {
-	assert(solver_ && (solver_->decisionLevel() == 0 || t != Constraint_t::Static));
-	literals_.clear();
-	extra_ = ConstraintInfo(t);
-	return *this;
+    assert(solver_ && (solver_->decisionLevel() == 0 || t != ConstraintType::static_));
+    literals_.clear();
+    extra_ = ConstraintInfo(t);
+    return *this;
 }
 
-uint32 ClauseCreator::watchOrder(const Solver& s, Literal p) {
-	ValueRep value_p = s.value(p.var());
-	// DL+1,  if isFree(p)
-	// DL(p), if isFalse(p)
-	// ~DL(p),if isTrue(p)
-	uint32   abstr_p = value_p == value_free ? s.decisionLevel()+1 : s.level(p.var()) ^ -(value_p==trueValue(p));
-	assert(abstr_p > 0 || (s.isFalse(p) && s.level(p.var()) == 0));
-	return abstr_p;
+uint32_t ClauseCreator::watchOrder(const Solver& s, Literal p) {
+    auto value_p = s.value(p.var());
+    if (value_p == value_free) { // DL+1,  if isFree(p)
+        return s.decisionLevel() + 1;
+    }
+    // DL(p), if isFalse(p)
+    // ~DL(p),if isTrue(p)
+    return s.level(p.var()) ^ static_cast(0 - (value_p == trueValue(p)));
+}
+ClauseRep ClauseCreator::prepare(Solver& s, LitView in, const ConstraintInfo& e, CreateFlag flags,
+                                 std::span out) {
+    assert(not out.empty() || in.empty());
+    ClauseRep ret     = ClauseRep::prepared({out.data(), 0u}, e);
+    uint32_t  abst_w1 = 0, abst_w2 = 0;
+    bool      simplify = Potassco::test(flags, clause_force_simplify) && out.size() >= in.size();
+    Literal   tag      = ~s.tagLiteral();
+    Var_t     vMax     = s.numProblemVars() > s.numVars() && not in.empty() ? std::ranges::max_element(in)->var() : 0;
+    s.acquireProblemVar(vMax);
+    for (uint32_t j = 0, max_out = size32(out) - 1; auto p : in) {
+        auto abst_p = watchOrder(s, p);
+        if ((abst_p + 1) > 1 && (not simplify || not s.seen(p.var()))) {
+            out[j] = p;
+            if (p == tag) {
+                ret.info.setTagged(true);
+            }
+            if (p.var() > vMax) {
+                vMax = p.var();
+            }
+            if (simplify) {
+                s.markSeen(p);
+            }
+            if (abst_p > abst_w1) {
+                std::swap(abst_p, abst_w1);
+                std::swap(out[0], out[j]);
+            }
+            if (abst_p > abst_w2) {
+                std::swap(abst_p, abst_w2);
+                std::swap(out[1], out[j]);
+            }
+            if (j != max_out) {
+                ++j;
+            }
+            ++ret.size;
+        }
+        else if (abst_p == UINT32_MAX || (simplify && abst_p && s.seen(~p))) {
+            abst_w1 = UINT32_MAX;
+            break;
+        }
+    }
+    if (simplify) {
+        assert(ret.size <= size32(out));
+        for (auto x : irange(ret.size)) { s.clearSeen(out[x].var()); }
+    }
+    if (abst_w1 == UINT32_MAX || (abst_w2 && out[0].var() == out[1].var())) {
+        out[0]   = abst_w1 == UINT32_MAX || out[0] == ~out[1] ? lit_true : out[0];
+        ret.size = 1;
+    }
+    ret.info.setAux(s.auxVar(vMax));
+    return ret;
 }
 
-ClauseRep ClauseCreator::prepare(Solver& s, const Literal* in, uint32 inSize, const ConstraintInfo& e, uint32 flags, Literal* out, uint32 outMax) {
-	assert(out && outMax > 2);
-	ClauseRep ret  = ClauseRep::prepared(out, 0, e);
-	uint32 abst_w1 = 0, abst_w2 = 0;
-	bool simplify  = ((flags & clause_force_simplify) != 0) && inSize > 2 && outMax >= inSize;
-	Literal tag    = ~s.tagLiteral();
-	Var     vMax   = s.numProblemVars() > s.numVars() && inSize ? std::max_element(in, in + inSize)->var() : 0;
-	s.acquireProblemVar(vMax);
-	for (uint32 i = 0, j = 0, MAX_OUT = outMax - 1; i != inSize; ++i) {
-		Literal p     = in[i];
-		uint32 abst_p = watchOrder(s, p);
-		if ((abst_p + 1) > 1 && (!simplify || !s.seen(p.var()))) {
-			out[j] = p;
-			if (p == tag)         { ret.info.setTagged(true); }
-			if (p.var() > vMax)   { vMax = p.var();}
-			if (simplify)         { s.markSeen(p); }
-			if (abst_p > abst_w1) { std::swap(abst_p, abst_w1); std::swap(out[0], out[j]); }
-			if (abst_p > abst_w2) { std::swap(abst_p, abst_w2); std::swap(out[1], out[j]); }
-			if (j != MAX_OUT)     { ++j;  }
-			++ret.size;
-		}
-		else if (abst_p == UINT32_MAX || (simplify && abst_p && s.seen(~p))) {
-			abst_w1 = UINT32_MAX;
-			break;
-		}
-	}
-	if (simplify) {
-		for (uint32 x = 0, end = ret.size; x != end; ++x) { s.clearSeen(out[x].var()); }
-	}
-	if (abst_w1 == UINT32_MAX || (abst_w2 && out[0].var() == out[1].var())) {
-		out[0]   = abst_w1 == UINT32_MAX || out[0] == ~out[1] ? lit_true() : out[0];
-		ret.size = 1;
-	}
-	ret.info.setAux(s.auxVar(vMax));
-	return ret;
-}
-
-
-ClauseRep ClauseCreator::prepare(Solver& s, LitVec& lits, uint32 flags, const ConstraintInfo& info) {
-	if (lits.empty()) { lits.push_back(lit_false()); }
-	if ((flags & clause_no_prepare) == 0 || (flags & clause_force_simplify) != 0) {
-		ClauseRep x = prepare(s, &lits[0], (uint32)lits.size(), info, flags, &lits[0]);
-		shrinkVecTo(lits, x.size);
-		return x;
-	}
-	return ClauseRep::prepared(&lits[0], (uint32)lits.size(), info);
+ClauseRep ClauseCreator::prepare(Solver& s, LitVec& lits, CreateFlag flags, const ConstraintInfo& info) {
+    if (lits.empty()) {
+        lits.push_back(lit_false);
+    }
+    if (not Potassco::test(flags, clause_no_prepare) || Potassco::test(flags, clause_force_simplify)) {
+        ClauseRep x = prepare(s, lits, info, flags, lits);
+        shrinkVecTo(lits, x.size);
+        return x;
+    }
+    return ClauseRep::prepared(lits, info);
 }
 
 ClauseRep ClauseCreator::prepare(bool forceSimplify) {
-	return prepare(*solver_, literals_, forceSimplify ? clause_force_simplify : 0, extra_);
+    return prepare(*solver_, literals_, forceSimplify ? clause_force_simplify : CreateFlag{}, extra_);
 }
 
-ClauseCreator::Status ClauseCreator::status(const Solver& s, const Literal* clause_begin, const Literal* clause_end) {
-	if (clause_end <= clause_begin) { return status_empty; }
-	Literal temp[3];
-	ClauseRep x = prepare(const_cast(s), clause_begin, uint32(clause_end - clause_begin), ConstraintInfo(), 0, temp, 3);
-	return status(s, x);
+ClauseCreator::Status ClauseCreator::status(const Solver& s, LitView lits) {
+    if (lits.empty()) {
+        return status_empty;
+    }
+    Literal temp[3];
+    auto    x = prepare(const_cast(s), lits, ConstraintInfo(), {}, temp);
+    return statusPrepared(s, x);
 }
 
 ClauseCreator::Status ClauseCreator::status(const Solver& s, const ClauseRep& c) {
-	if (!c.prep)
-		return status(s, c.lits, c.lits + c.size);
+    return c.prep ? statusPrepared(s, c) : status(s, c.literals());
+}
 
-	uint32 dl = s.decisionLevel();
-	uint32 fw = c.size     ? watchOrder(s, c.lits[0]) : 0;
-	if (fw == UINT32_MAX) { return status_subsumed; }
-	uint32 sw = c.size > 1 ? watchOrder(s, c.lits[1]) : 0;
-	uint32 st = status_open;
-	if      (fw > varMax)   { st|= status_sat; fw = ~fw; }
-	else if (fw <= dl)      { st|= (fw ? status_unsat : status_empty); }
-	if (sw <= dl && fw > sw){ st|= status_unit;  }
-	return static_cast(st);
+ClauseCreator::Status ClauseCreator::statusPrepared(const Solver& s, const ClauseRep& c) {
+    uint32_t dl = s.decisionLevel();
+    uint32_t fw = c.size ? watchOrder(s, c.lits[0]) : 0;
+    if (fw == UINT32_MAX) {
+        return status_subsumed;
+    }
+    uint32_t sw = c.size > 1 ? watchOrder(s, c.lits[1]) : 0;
+    uint32_t st = status_open;
+    if (fw > var_max) {
+        st |= status_sat;
+        fw  = ~fw;
+    }
+    else if (fw <= dl) {
+        st |= (fw ? status_unsat : status_empty);
+    }
+    if (sw <= dl && fw > sw) {
+        st |= status_unit;
+    }
+    return static_cast(st);
 }
 
-bool ClauseCreator::ignoreClause(const Solver& s, const ClauseRep& c, Status st, uint32 flags) {
-	uint32 x = (st & (status_sat|status_unsat));
-	if (x == status_open)  { return false; }
-	if (x == status_unsat) { return st != status_empty && (flags & clause_not_conflict) != 0; }
-	return st == status_subsumed || (st == status_sat && ( (flags & clause_not_sat) != 0 || ((flags & clause_not_root_sat) != 0 && s.level(c.lits[0].var()) <= s.rootLevel())));
+bool ClauseCreator::ignoreClause(const Solver& s, const ClauseRep& c, Status st, CreateFlag flags) {
+    auto x = (st & (status_sat | status_unsat));
+    if (x == status_open) {
+        return false;
+    }
+    if (x == status_unsat) {
+        return st != status_empty && Potassco::test(flags, clause_not_conflict);
+    }
+    assert(x == status_sat);
+    return st == status_subsumed ||
+           (st == status_sat && (Potassco::test(flags, clause_not_sat) || (Potassco::test(flags, clause_not_root_sat) &&
+                                                                           s.level(c.lits[0].var()) <= s.rootLevel())));
 }
 
-ClauseCreator::Result ClauseCreator::end(uint32 flags) {
-	assert(solver_);
-	flags |= flags_;
-	return ClauseCreator::create_prepared(*solver_, prepare(*solver_, literals_, flags, extra_), flags);
+ClauseCreator::Result ClauseCreator::end(CreateFlag flags) {
+    assert(solver_);
+    flags |= flags_;
+    return createPrepared(*solver_, prepare(*solver_, literals_, flags, extra_), flags);
 }
 
-ClauseHead* ClauseCreator::newProblemClause(Solver& s, const ClauseRep& clause, uint32 flags) {
-	ClauseHead* ret;
-	Solver::WatchInitMode wMode = s.watchInitMode();
-	if      (flags&clause_watch_first){ wMode = SolverStrategies::watch_first;}
-	else if (flags&clause_watch_rand) { wMode = SolverStrategies::watch_rand; }
-	else if (flags&clause_watch_least){ wMode = SolverStrategies::watch_least;}
-	if (clause.size > 2 && wMode != SolverStrategies::watch_first) {
-		uint32 fw = 0, sw = 1;
-		if (wMode == SolverStrategies::watch_rand) {
-			fw = s.rng.irand(clause.size);
-			do { sw = s.rng.irand(clause.size); } while (sw == fw);
-		}
-		else if (wMode == SolverStrategies::watch_least) {
-			uint32 cw1 = s.numWatches(~clause.lits[0]);
-			uint32 cw2 = s.numWatches(~clause.lits[1]);
-			if (cw1 > cw2) { std::swap(fw, sw); std::swap(cw1, cw2); }
-			for (uint32 i = 2; i != clause.size && cw2; ++i) {
-				uint32 p   = i;
-				uint32 cwp = s.numWatches(~clause.lits[i]);
-				if (cwp < cw1) { std::swap(cwp, cw1); std::swap(fw, p); }
-				if (cwp < cw2) { std::swap(cwp, cw2); std::swap(sw, p); }
-			}
-		}
-		std::swap(clause.lits[0], clause.lits[fw]);
-		std::swap(clause.lits[1], clause.lits[sw]);
-	}
-	if (clause.size <= Clause::MAX_SHORT_LEN || !s.sharedContext()->physicalShareProblem()) {
-		ret = Clause::newClause(s, clause);
-	}
-	else {
-		ret = Clause::newShared(s, SharedLiterals::newShareable(clause.lits, clause.size, clause.info.type(), 1), clause.info, clause.lits, false);
-	}
-	if ( (flags & clause_no_add) == 0 ) {
-		assert(!clause.info.aux());
-		s.add(ret);
-	}
-	return ret;
+ClauseHead* ClauseCreator::newProblemClause(Solver& s, const ClauseRep& clause, CreateFlag flags) {
+    ClauseHead* ret;
+    auto        wMode = s.watchInitMode();
+    if (Potassco::test(flags, clause_watch_first)) {
+        wMode = SolverStrategies::watch_first;
+    }
+    else if (Potassco::test(flags, clause_watch_rand)) {
+        wMode = SolverStrategies::watch_rand;
+    }
+    else if (Potassco::test(flags, clause_watch_least)) {
+        wMode = SolverStrategies::watch_least;
+    }
+    if (clause.size > 2 && wMode != SolverStrategies::watch_first) {
+        uint32_t fw = 0, sw = 1;
+        if (wMode == SolverStrategies::watch_rand) {
+            fw = s.rng.irand(clause.size);
+            do { sw = s.rng.irand(clause.size); } while (sw == fw);
+        }
+        else if (wMode == SolverStrategies::watch_least) {
+            uint32_t cw1 = s.numWatches(~clause.lits[0]);
+            uint32_t cw2 = s.numWatches(~clause.lits[1]);
+            if (cw1 > cw2) {
+                std::swap(fw, sw);
+                std::swap(cw1, cw2);
+            }
+            for (uint32_t i = 2; i != clause.size && cw2; ++i) {
+                uint32_t p   = i;
+                uint32_t cwp = s.numWatches(~clause.lits[i]);
+                if (cwp < cw1) {
+                    std::swap(cwp, cw1);
+                    std::swap(fw, p);
+                }
+                if (cwp < cw2) {
+                    std::swap(cwp, cw2);
+                    std::swap(sw, p);
+                }
+            }
+        }
+        std::swap(clause.lits[0], clause.lits[fw]);
+        std::swap(clause.lits[1], clause.lits[sw]);
+    }
+    if (clause.size <= Clause::max_short_len || not s.sharedContext()->physicalShareProblem()) {
+        ret = Clause::newClause(s, clause);
+    }
+    else {
+        ret = Clause::newShared(s, SharedLiterals::newShareable(clause.literals(), clause.info.type(), 1), clause.info,
+                                clause.lits, false);
+    }
+    if (not Potassco::test(flags, clause_no_add)) {
+        assert(not clause.info.aux());
+        s.add(ret);
+    }
+    return ret;
 }
 
-ClauseHead* ClauseCreator::newLearntClause(Solver& s, const ClauseRep& clause, uint32 flags) {
-	ClauseHead* ret;
-	Detail::Sink sharedPtr(0);
-	sharedPtr.clause = s.distribute(clause.lits, clause.size, clause.info);
-	if (clause.size <= Clause::MAX_SHORT_LEN || sharedPtr.clause == 0) {
-		if (!s.isFalse(clause.lits[1]) || clause.size < s.compressLimit()) {
-			ret = Clause::newClause(s, clause);
-		}
-		else {
-			ret = Clause::newContractedClause(s, clause, 2, true);
-		}
-	}
-	else {
-		ret              = Clause::newShared(s, sharedPtr.clause, clause.info, clause.lits, false);
-		sharedPtr.clause = 0;
-	}
-	if ( (flags & clause_no_add) == 0 ) {
-		s.addLearnt(ret, clause.size, clause.info.type());
-	}
-	return ret;
+ClauseHead* ClauseCreator::newLearntClause(Solver& s, const ClauseRep& clause, CreateFlag flags) {
+    ClauseHead* ret;
+    auto        shared = Detail::SharedLitsPtr(s.distribute(clause.literals(), clause.info));
+    if (clause.size <= Clause::max_short_len || not shared) {
+        if (not s.isFalse(clause.lits[1]) || clause.size < s.compressLimit()) {
+            ret = Clause::newClause(s, clause);
+        }
+        else {
+            ret = Clause::newContractedClause(s, clause, 2, true);
+        }
+    }
+    else {
+        ret = Clause::newShared(s, shared.release(), clause.info, clause.lits, false);
+    }
+    if (not Potassco::test(flags, clause_no_add)) {
+        s.addLearnt(ret, clause.size, clause.info.type());
+    }
+    return ret;
 }
 
-ClauseHead* ClauseCreator::newUnshared(Solver& s, SharedLiterals* clause, const Literal* w, const ConstraintInfo& e) {
-	LitVec temp; temp.reserve(clause->size());
-	temp.assign(w, w+2);
-	for (const Literal* x = clause->begin(), *end = clause->end(); x != end; ++x) {
-		if (watchOrder(s, *x) > 0 && *x != temp[0] && *x != temp[1]) {
-			temp.push_back(*x);
-		}
-	}
-	return Clause::newClause(s, ClauseRep::prepared(&temp[0], (uint32)temp.size(), e));
+ClauseHead* ClauseCreator::newUnshared(Solver& s, const SharedLiterals* clause, const Literal* w,
+                                       const ConstraintInfo& e) {
+    LitVec temp;
+    temp.reserve(clause->size());
+    temp.assign(w, w + 2);
+    for (auto x : *clause) {
+        if (watchOrder(s, x) > 0 && x != temp[0] && x != temp[1]) {
+            temp.push_back(x);
+        }
+    }
+    return Clause::newClause(s, ClauseRep::prepared(temp, e));
 }
 
-ClauseCreator::Result ClauseCreator::create_prepared(Solver& s, const ClauseRep& clause, uint32 flags) {
-	assert(s.decisionLevel() == 0 || (clause.info.learnt() && clause.prep));
-	Status x = status(s, clause);
-	if (ignoreClause(s, clause, x, flags)){
-		return Result(0, x);
-	}
-	if (clause.size > 1) {
-		Result ret(0, x);
-		if (!clause.info.learnt() && s.satPrepro() && !s.sharedContext()->frozen()) {
-			return Result(0, s.satPrepro()->addClause(clause.lits, clause.size) ? x : status_unsat);
-		}
-		if ((flags & clause_no_heuristic) == 0) { s.heuristic()->newConstraint(s, clause.lits, clause.size, clause.info.type()); }
-		if (clause.size > 3 || (flags&clause_explicit) != 0 || !s.allowImplicit(clause)) {
-			ret.local = clause.info.learnt() ? newLearntClause(s, clause, flags) : newProblemClause(s, clause, flags);
-		}
-		else {
-			// add implicit short rep
-			s.add(clause);
-		}
-		if ((x & (status_unit|status_unsat)) != 0) {
-			Antecedent ante(ret.local);
-			if (!ret.local){ ante = clause.size == 3 ? Antecedent(~clause.lits[1], ~clause.lits[2]) : Antecedent(~clause.lits[1]); }
-			ret.status = s.force(clause.lits[0], s.level(clause.lits[1].var()), ante) ? status_unit : status_unsat;
-		}
-		return ret;
-	}
-	s.add(clause);
-	return Result(0, !s.hasConflict() ? status_unit : status_unsat);
+ClauseCreator::Result ClauseCreator::createPrepared(Solver& s, const ClauseRep& clause, CreateFlag flags) {
+    assert(s.decisionLevel() == 0 || (clause.info.learnt() && clause.prep));
+    Status x = status(s, clause);
+    if (ignoreClause(s, clause, x, flags)) {
+        return Result(nullptr, x);
+    }
+    if (clause.size > 1) {
+        Result ret(nullptr, x);
+        if (not clause.info.learnt() && s.satPrepro() && not s.sharedContext()->frozen()) {
+            return Result(nullptr, s.satPrepro()->addClause(clause.literals()) ? x : status_unsat);
+        }
+        if (not Potassco::test(flags, clause_no_heuristic)) {
+            s.heuristic()->newConstraint(s, clause.literals(), clause.info.type());
+        }
+        if (clause.size > 3 || Potassco::test(flags, clause_explicit) || not s.allowImplicit(clause)) {
+            ret.local = clause.info.learnt() ? newLearntClause(s, clause, flags) : newProblemClause(s, clause, flags);
+        }
+        else {
+            // add implicit short rep
+            s.add(clause);
+        }
+        if ((x & (status_unit | status_unsat)) != 0) {
+            Antecedent ante(ret.local);
+            if (not ret.local) {
+                ante = clause.size == 3 ? Antecedent(~clause.lits[1], ~clause.lits[2]) : Antecedent(~clause.lits[1]);
+            }
+            ret.status = s.force(clause.lits[0], s.level(clause.lits[1].var()), ante) ? status_unit : status_unsat;
+        }
+        return ret;
+    }
+    s.add(clause);
+    return Result(nullptr, not s.hasConflict() ? status_unit : status_unsat);
 }
 
-ClauseCreator::Result ClauseCreator::create(Solver& s, LitVec& lits, uint32 flags, const ConstraintInfo& extra) {
-	return create_prepared(s, prepare(s, lits, flags, extra), flags);
+ClauseCreator::Result ClauseCreator::create(Solver& s, LitVec& lits, CreateFlag flags, const ConstraintInfo& extra) {
+    return createPrepared(s, prepare(s, lits, flags, extra), flags);
 }
 
-ClauseCreator::Result ClauseCreator::create(Solver& s, const ClauseRep& rep, uint32 flags) {
-	return create_prepared(s, (rep.prep == 0 && (flags & clause_no_prepare) == 0
-		? prepare(s, rep.lits, rep.size, rep.info, flags, rep.lits)
-		: ClauseRep::prepared(rep.lits, rep.size, rep.info)), flags);
+ClauseCreator::Result ClauseCreator::create(Solver& s, const ClauseRep& rep, CreateFlag flags) {
+    return createPrepared(s,
+                          rep.prep == 0 && not Potassco::test(flags, clause_no_prepare)
+                              ? prepare(s, rep.literals(), rep.info, flags, {rep.lits, rep.size})
+                              : ClauseRep::prepared({rep.lits, rep.size}, rep.info),
+                          flags);
 }
 
-ClauseCreator::Result ClauseCreator::integrate(Solver& s, SharedLiterals* clause, uint32 modeFlags, ConstraintType t) {
-	assert(!s.hasConflict() && "ClauseCreator::integrate() - precondition violated!");
-	Detail::Sink shared( (modeFlags & clause_no_release) == 0 ? clause : 0);
-	// determine state of clause
-	Literal temp[Clause::MAX_SHORT_LEN]; temp[0] = temp[1] = lit_false();
-	ClauseRep x    = prepare(s, clause->begin(), clause->size(), ConstraintInfo(t), 0, temp, Clause::MAX_SHORT_LEN);
-	uint32 impSize = (modeFlags & clause_explicit) != 0 || !s.allowImplicit(x) ? 1 : 3;
-	Status xs      = status(s, x);
-	if (ignoreClause(s, x, xs, modeFlags)) {
-		return Result(0, xs);
-	}
-	Result result(0, xs);
-	if ((modeFlags & clause_no_heuristic) == 0) { s.heuristic()->newConstraint(s, clause->begin(), clause->size(), t); }
-	if (x.size > Clause::MAX_SHORT_LEN && s.sharedContext()->physicalShare(t)) {
-		result.local  = Clause::newShared(s, clause, x.info, temp, shared.clause == 0);
-		shared.clause = 0;
-	}
-	else if (x.size > impSize) {
-		result.local  = x.size <= Clause::MAX_SHORT_LEN ? Clause::newClause(s, x) : newUnshared(s, clause, temp, x.info);
-	}
-	else {
-		// unary clause or implicitly shared via binary/ternary implication graph;
-		// only check for implication/conflict but do not create
-		// a local representation for the clause
-		s.stats.addLearnt(x.size, x.info.type());
-		modeFlags |= clause_no_add;
-	}
-	if ((modeFlags & clause_no_add) == 0) { s.addLearnt(result.local, x.size, x.info.type()); }
-	if ((xs & (status_unit|status_unsat)) != 0) {
-		Antecedent ante = result.local ? Antecedent(result.local) : Antecedent(~temp[1], ~temp[2]);
-		uint32 impLevel = s.level(temp[1].var());
-		result.status   = s.force(temp[0], impLevel, ante) ? status_unit : status_unsat;
-		if (result.local && (modeFlags & clause_int_lbd) != 0) {
-			uint32 lbd = s.countLevels(clause->begin(), clause->end());
-			result.local->resetScore(makeScore(x.info.activity(), lbd));
-		}
-	}
-	return result;
-}
-ClauseCreator::Result ClauseCreator::integrate(Solver& s, SharedLiterals* clause, uint32 modeFlags) {
-	return integrate(s, clause, modeFlags, clause->type());
+ClauseCreator::Result ClauseCreator::integrate(Solver& s, SharedLiterals* clause, CreateFlag modeFlags,
+                                               ConstraintType t) {
+    assert(not s.hasConflict() && "ClauseCreator::integrate() - precondition violated!");
+    auto shared = Detail::SharedLitsPtr(not Potassco::test(modeFlags, clause_no_release) ? clause : nullptr);
+    // determine state of clause
+    Literal temp[Clause::max_short_len];
+    temp[0] = temp[1] = lit_false;
+    ClauseRep x       = prepare(s, clause->literals(), ConstraintInfo(t), {}, temp);
+    uint32_t  impSize = Potassco::test(modeFlags, clause_explicit) || not s.allowImplicit(x) ? 1 : 3;
+    Status    xs      = status(s, x);
+    if (ignoreClause(s, x, xs, modeFlags)) {
+        return Result(nullptr, xs);
+    }
+    Result result(nullptr, xs);
+    if (not Potassco::test(modeFlags, clause_no_heuristic)) {
+        s.heuristic()->newConstraint(s, {clause->begin(), clause->size()}, t);
+    }
+    if (x.size > Clause::max_short_len && s.sharedContext()->physicalShare(t)) {
+        result.local = Clause::newShared(s, clause, x.info, temp, shared.release() == nullptr);
+    }
+    else if (x.size > impSize) {
+        result.local = x.size <= Clause::max_short_len ? Clause::newClause(s, x) : newUnshared(s, clause, temp, x.info);
+    }
+    else {
+        // unary clause or implicitly shared via binary/ternary implication graph;
+        // only check for implication/conflict but do not create
+        // a local representation for the clause
+        s.stats.addLearnt(x.size, x.info.type());
+        modeFlags |= clause_no_add;
+    }
+    if (not Potassco::test(modeFlags, clause_no_add)) {
+        s.addLearnt(result.local, x.size, x.info.type());
+    }
+    if (unitOrUnsat(xs)) {
+        Antecedent ante     = result.local ? Antecedent(result.local) : Antecedent(~temp[1], ~temp[2]);
+        uint32_t   impLevel = s.level(temp[1].var());
+        result.status       = s.force(temp[0], impLevel, ante) ? status_unit : status_unsat;
+        if (result.local && Potassco::test(modeFlags, clause_int_lbd)) {
+            uint32_t lbd = s.countLevels(clause->literals());
+            result.local->resetScore(ConstraintScore(x.info.activity(), lbd));
+        }
+    }
+    return result;
+}
+ClauseCreator::Result ClauseCreator::integrate(Solver& s, SharedLiterals* clause, CreateFlag modeFlags) {
+    return integrate(s, clause, modeFlags, clause->type());
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Clause
 /////////////////////////////////////////////////////////////////////////////////////////
-void* Clause::alloc(Solver& s, uint32 lits, bool learnt) {
-	if (lits <= Clause::MAX_SHORT_LEN) {
-		if (learnt) { s.addLearntBytes(32); }
-		return s.allocSmall();
-	}
-	uint32 extra = std::max((uint32)ClauseHead::HEAD_LITS, lits) - ClauseHead::HEAD_LITS;
-	uint32 bytes = sizeof(Clause) + (extra)*sizeof(Literal);
-	if (learnt) { s.addLearntBytes(bytes); }
-	return Detail::alloc(bytes);
+void* Clause::alloc(Solver& s, uint32_t lits, bool learnt) {
+    if (lits <= max_short_len) {
+        if (learnt) {
+            s.addLearntBytes(32);
+        }
+        return s.allocSmall();
+    }
+    uint32_t extra = std::max(head_lits, lits) - head_lits;
+    uint32_t bytes = sizeof(Clause) + (extra) * sizeof(Literal);
+    if (learnt) {
+        s.addLearntBytes(bytes);
+    }
+    return Detail::alloc(bytes);
 }
 
 ClauseHead* Clause::newClause(void* mem, Solver& s, const ClauseRep& rep) {
-	assert(rep.size >= 2 && mem);
-	return new (mem) Clause(s, rep);
+    assert(rep.size >= 2 && mem);
+    return new (mem) Clause(s, rep);
 }
 
-ClauseHead* Clause::newShared(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits, bool addRef) {
-	return mt::SharedLitsClause::newClause(s, shared_lits, e, lits, addRef);
+ClauseHead* Clause::newShared(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits,
+                              bool addRef) {
+    return mt::SharedLitsClause::newClause(s, shared_lits, e, lits, addRef);
 }
 
-ClauseHead* Clause::newContractedClause(Solver& s, const ClauseRep& rep, uint32 tailStart, bool extend) {
-	assert(rep.size >= 2);
-	if (extend) { std::stable_sort(rep.lits+tailStart, rep.lits+rep.size, Detail::GreaterLevel(s)); }
-	return new (alloc(s, rep.size, rep.info.learnt())) Clause(s, rep, tailStart, extend);
-}
-void ClauseHead::Local::init(uint32 sz) {
-	std::memset(mem, 0, sizeof(mem));
-	if (sz > ClauseHead::MAX_SHORT_LEN) { mem[0] = (sz << 3) + 1; }
-	assert(isSmall() == (sz <= ClauseHead::MAX_SHORT_LEN));
-}
-Clause::Clause(Solver& s, const ClauseRep& rep, uint32 tail, bool extend)
-	: ClauseHead(rep.info) {
-	assert(tail >= rep.size || s.isFalse(rep.lits[tail]));
-	local_.init(rep.size);
-	if (!isSmall()) {
-		// copy literals
-		std::memcpy(head_, rep.lits, rep.size*sizeof(Literal));
-		tail = std::max(tail, (uint32)ClauseHead::HEAD_LITS);
-		if (tail < rep.size) {       // contracted clause
-			head_[rep.size-1].flag();  // mark last literal of clause
-			Literal t = head_[tail];
-			if (s.level(t.var()) > 0) {
-				local_.markContracted();
-				if (extend) {
-					s.addUndoWatch(s.level(t.var()), this);
-				}
-			}
-			local_.setSize(tail);
-		}
-	}
-	else {
-		std::memcpy(head_, rep.lits, std::min(rep.size, (uint32)ClauseHead::HEAD_LITS)*sizeof(Literal));
-		small()[0] = rep.size > ClauseHead::HEAD_LITS   ? rep.lits[ClauseHead::HEAD_LITS]   : lit_false();
-		small()[1] = rep.size > ClauseHead::HEAD_LITS+1 ? rep.lits[ClauseHead::HEAD_LITS+1] : lit_false();
-		assert(isSmall() && Clause::size() == rep.size);
-	}
-	attach(s);
+ClauseHead* Clause::newContractedClause(Solver& s, const ClauseRep& rep, uint32_t tailStart, bool extend) {
+    assert(rep.size >= 2);
+    if (extend) {
+        std::stable_sort(rep.lits + tailStart, rep.lits + rep.size, [&s](const Literal& p1, const Literal& p2) {
+            assert(s.value(p1.var()) != value_free && s.value(p2.var()) != value_free);
+            return s.level(p1.var()) > s.level(p2.var());
+        });
+    }
+    return new (alloc(s, rep.size, rep.info.learnt())) Clause(s, rep, tailStart, extend);
+}
+void ClauseHead::Local::init(uint32_t sz) {
+    std::memset(mem, 0, sizeof(mem));
+    if (sz > max_short_len) {
+        mem[0] = (sz << 3) + 1;
+    }
+    assert(isSmall() == (sz <= ClauseHead::max_short_len));
+}
+Clause::Clause(Solver& s, const ClauseRep& rep, uint32_t tail, bool extend) : ClauseHead(rep.info) {
+    assert(tail >= rep.size || s.isFalse(rep.lits[tail]));
+    local_.init(rep.size);
+    if (not isSmall()) {
+        // copy literals
+        auto* lits = static_cast(std::memcpy(head_, rep.lits, rep.size * sizeof(Literal)));
+        tail       = std::max(tail, head_lits);
+        if (tail < rep.size) {         // contracted clause
+            lits[rep.size - 1].flag(); // mark last literal of clause
+            if (Literal t = lits[tail]; s.level(t.var()) > 0) {
+                local_.markContracted();
+                if (extend) {
+                    s.addUndoWatch(s.level(t.var()), this);
+                }
+            }
+            local_.setSize(tail);
+        }
+    }
+    else {
+        std::memcpy(head_, rep.lits, std::min(rep.size, head_lits) * sizeof(Literal));
+        small()[0] = rep.size > head_lits ? rep.lits[head_lits] : lit_false;
+        small()[1] = rep.size > head_lits + 1 ? rep.lits[head_lits + 1] : lit_false;
+        assert(isSmall() && Clause::size() == rep.size);
+    }
+    attach(s);
 }
 
 Clause::Clause(Solver& s, const Clause& other) : ClauseHead(other.info_) {
-	uint32 oSize = other.size();
-	local_.init(oSize);
-	if      (!isSmall())      { std::memcpy(head_, other.head_, oSize*sizeof(Literal)); }
-	else if (other.isSmall()) { std::memcpy(&local_, &other.local_, (ClauseHead::MAX_SHORT_LEN+1)*sizeof(Literal)); }
-	else { // this is small, other is not
-		std::memcpy(head_, other.head_, ClauseHead::HEAD_LITS*sizeof(Literal));
-		std::memcpy(&local_, other.head_+ClauseHead::HEAD_LITS, 2*sizeof(Literal));
-	}
-	attach(s);
+    uint32_t oSize = other.size();
+    local_.init(oSize);
+    if (not isSmall()) {
+        std::memcpy(head_, other.head_, oSize * sizeof(Literal));
+    }
+    else if (other.isSmall()) {
+        std::memcpy(&local_, &other.local_, (max_short_len + 1) * sizeof(Literal));
+    }
+    else { // this is small, other is not
+        std::memcpy(head_, other.head_, head_lits * sizeof(Literal));
+        std::memcpy(&local_, other.head_ + head_lits, 2 * sizeof(Literal));
+    }
+    attach(s);
 }
 
-Constraint* Clause::cloneAttach(Solver& other) {
-	assert(!learnt());
-	return new (alloc(other, Clause::size(), false)) Clause(other, *this);
-}
-Literal* Clause::small()          { return static_cast(static_cast(&local_)); }
-bool Clause::contracted()   const { return local_.contracted(); }
-bool Clause::isSmall()      const { return local_.isSmall(); }
-bool Clause::strengthened() const { return local_.strengthened(); }
-void Clause::destroy(Solver* s, bool detachFirst) {
-	if (s) {
-		if (detachFirst) { Clause::detach(*s); }
-		if (learnt())    { s->freeLearntBytes(computeAllocSize()); }
-	}
-	void* mem   = static_cast(this);
-	bool  small = isSmall();
-	this->~Clause();
-	if (!small) { Detail::free(mem); }
-	else if (s) { s->freeSmall(mem); }
+ClauseHead* Clause::cloneAttach(Solver& other) {
+    assert(not learnt());
+    return new (alloc(other, Clause::size(), false)) Clause(other, *this);
+}
+Literal* Clause::small() { return reinterpret_cast(local_.mem); }
+bool     Clause::contracted() const { return local_.contracted(); }
+bool     Clause::isSmall() const { return local_.isSmall(); }
+bool     Clause::strengthened() const { return local_.strengthened(); }
+void     Clause::destroy(Solver* s, bool detachFirst) {
+    if (s) {
+        if (detachFirst) {
+            Clause::detach(*s);
+        }
+        if (learnt()) {
+            s->freeLearntBytes(computeAllocSize());
+        }
+    }
+    void* mem   = static_cast(this);
+    bool  small = isSmall();
+    this->~Clause();
+    if (not small) {
+        Detail::free(mem);
+    }
+    else if (s) {
+        s->freeSmall(mem);
+    }
 }
 
 void Clause::detach(Solver& s) {
-	if (contracted()) {
-		Literal* eoc = end();
-		if (s.isFalse(*eoc) && s.level(eoc->var()) != 0) {
-			s.removeUndoWatch(s.level(eoc->var()), this);
-		}
-	}
-	ClauseHead::detach(s);
+    if (contracted()) {
+        if (Literal* eoc = end(); s.isFalse(*eoc) && s.level(eoc->var()) != 0) {
+            s.removeUndoWatch(s.level(eoc->var()), this);
+        }
+    }
+    ClauseHead::detach(s);
 }
 
-uint32 Clause::computeAllocSize() const {
-	if (isSmall()) { return 32; }
-	uint32 rt = sizeof(Clause) - (ClauseHead::HEAD_LITS*sizeof(Literal));
-	uint32 sz = local_.size();
-	uint32 nw = contracted() + strengthened();
-	if (nw != 0u) {
-		const Literal* eoc = head_ + sz;
-		do { nw -= eoc++->flagged(); } while (nw);
-		sz = static_cast(eoc - head_);
-	}
-	return rt + (sz*sizeof(Literal));
+uint32_t Clause::computeAllocSize() const {
+    if (isSmall()) {
+        return 32;
+    }
+    uint32_t rt = sizeof(Clause) - (head_lits * sizeof(Literal));
+    uint32_t sz = local_.size();
+    if (auto nw = contracted() + strengthened(); nw != 0u) {
+        const Literal* eoc = head_ + sz;
+        do { nw -= eoc++->flagged(); } while (nw);
+        sz = static_cast(eoc - head_);
+    }
+    return rt + (sz * sizeof(Literal));
 }
 
-bool Clause::updateWatch(Solver& s, uint32 pos) {
-	Literal* it;
-	if (!isSmall()) {
-		for (Literal* begin = head_ + ClauseHead::HEAD_LITS, *end = this->end(), *first = begin + local_.mem[1];;) {
-			for (it = first; it < end; ++it) {
-				if (!s.isFalse(*it)) {
-					std::swap(*it, head_[pos]);
-					local_.mem[1] = static_cast(++it - begin);
-					return true;
-				}
-			}
-			if (first == begin) { break; }
-			end = first;
-			first = begin;
-		}
-	}
-	else if (!s.isFalse(*(it = this->small())) || !s.isFalse(*++it)) {
+bool Clause::updateWatch(Solver& s, uint32_t pos) {
+    Literal* it;
+    if (not isSmall()) {
+        for (Literal *begin = head_ + head_lits, *end = this->end(), *first = begin + local_.mem[1];;) {
+            for (it = first; it < end; ++it) {
+                if (not s.isFalse(*it)) {
+                    std::swap(*it, head_[pos]);
+                    local_.mem[1] = static_cast(++it - begin);
+                    return true;
+                }
+            }
+            if (first == begin) {
+                break;
+            }
+            end   = first;
+            first = begin;
+        }
+    }
+    else if (it = this->small(); not s.isFalse(*it) || not s.isFalse(*++it)) {
 #if defined(__GNUC__) && __GNUC__ == 7 && __GNUC_MINOR__ < 2
-		// Add compiler barrier to prevent gcc Bug 81365:
-		// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81365
-		asm volatile("" ::: "memory");
+        // Add compiler barrier to prevent gcc Bug 81365:
+        // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81365
+        asm volatile("" ::: "memory");
 #endif
-		std::swap(head_[pos], *it);
-		return true;
-	}
-	return false;
-}
-Clause::LitRange Clause::tail() {
-	if (!isSmall()) { return LitRange(head_+ClauseHead::HEAD_LITS, end()); }
-	Literal* tBeg = small(), *tEnd = tBeg;
-	if (*tEnd != lit_false()) ++tEnd;
-	if (*tEnd != lit_false()) ++tEnd;
-	return LitRange(tBeg, tEnd);
+        std::swap(head_[pos], *it);
+        return true;
+    }
+    return false;
+}
+Clause::Tail Clause::tail() {
+    if (not isSmall()) {
+        return {head_ + head_lits, end()};
+    }
+    Literal *tBeg = small(), *tEnd = tBeg;
+    tEnd += *tEnd != lit_false;
+    tEnd += *tEnd != lit_false;
+    return {tBeg, tEnd};
 }
 void Clause::reason(Solver& s, Literal p, LitVec& out) {
-	out.push_back(~head_[p == head_[0]]);
-	if (!isSentinel(head_[2])) {
-		out.push_back(~head_[2]);
-		LitRange t = tail();
-		for (const Literal* r = t.first; r != t.second; ++r) {
-			out.push_back(~*r);
-		}
-		if (contracted()) {
-			const Literal* r = t.second;
-			do { out.push_back(~*r); } while (!r++->flagged());
-		}
-	}
-	if (learnt()) {
-		s.updateOnReason(info_.score(), p, out);
-	}
+    out.push_back(~head_[p == head_[0]]);
+    if (not isSentinel(head_[2])) {
+        out.push_back(~head_[2]);
+        auto t = tail();
+        for (auto r : t) { out.push_back(~r); }
+        if (contracted()) {
+            const Literal* r = t.end();
+            do { out.push_back(~*r); } while (not r++->flagged());
+        }
+    }
+    if (learnt()) {
+        s.updateOnReason(info_.score(), p, out);
+    }
 }
 
 bool Clause::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	s.updateOnMinimize(info_.score());
-	uint32 other = p == head_[0];
-	if (!s.ccMinimize(~head_[other], rec) || !s.ccMinimize(~head_[2], rec)) { return false; }
-	LitRange t = tail();
-	for (const Literal* r = t.first; r != t.second; ++r) {
-		if (!s.ccMinimize(~*r, rec)) { return false; }
-	}
-	if (contracted()) {
-		do {
-			if (!s.ccMinimize(~*t.second, rec)) { return false; }
-		} while (!t.second++->flagged());
-	}
-	return true;
+    s.updateOnMinimize(info_.score());
+    uint32_t other = p == head_[0];
+    if (not s.ccMinimize(~head_[other], rec) || not s.ccMinimize(~head_[2], rec)) {
+        return false;
+    }
+    auto t = tail();
+    for (auto r : t) {
+        if (not s.ccMinimize(~r, rec)) {
+            return false;
+        }
+    }
+    if (contracted()) {
+        const Literal* r = t.end();
+        do {
+            if (not s.ccMinimize(~*r, rec)) {
+                return false;
+            }
+        } while (not r++->flagged());
+    }
+    return true;
 }
 
-bool Clause::isReverseReason(const Solver& s, Literal p, uint32 maxL, uint32 maxN) {
-	uint32 other   = p == head_[0];
-	if (!isRevLit(s, head_[other], maxL) || !isRevLit(s, head_[2], maxL)) { return false; }
-	uint32 notSeen = !s.seen(head_[other].var()) + !s.seen(head_[2].var());
-	LitRange t     = tail();
-	for (const Literal* r = t.first; r != t.second && notSeen <= maxN; ++r) {
-		if (!isRevLit(s, *r, maxL)) { return false; }
-		notSeen += !s.seen(r->var());
-	}
-	if (contracted()) {
-		const Literal* r = t.second;
-		do { notSeen += !s.seen(r->var()); } while (notSeen <= maxN && !r++->flagged());
-	}
-	return notSeen <= maxN;
+bool Clause::isReverseReason(const Solver& s, Literal p, uint32_t maxL, uint32_t maxN) {
+    uint32_t other = p == head_[0];
+    if (not isRevLit(s, head_[other], maxL) || not isRevLit(s, head_[2], maxL)) {
+        return false;
+    }
+    if (uint32_t notSeen = not s.seen(head_[other].var()) + not s.seen(head_[2].var()); notSeen <= maxN) {
+        auto t = tail();
+        for (auto r : t) {
+            if (not isRevLit(s, r, maxL) || (not s.seen(r.var()) && ++notSeen > maxN)) {
+                return false;
+            }
+        }
+        if (contracted()) {
+            const Literal* r = t.end();
+            do { notSeen += not s.seen(r->var()); } while (notSeen <= maxN && not r++->flagged());
+        }
+        return notSeen <= maxN;
+    }
+    return false;
 }
 
 bool Clause::simplify(Solver& s, bool reinit) {
-	assert(s.decisionLevel() == 0 && s.queueSize() == 0);
-	if (ClauseHead::satisfied(s)) {
-		detach(s);
-		return true;
-	}
-	LitRange t = tail();
-	Literal* it= t.first - !isSmall(), *j;
-	// skip free literals
-	while (it != t.second && s.value(it->var()) == value_free) { ++it; }
-	// copy remaining free literals
-	for (j = it; it != t.second; ++it) {
-		if      (s.value(it->var()) == value_free) { *j++ = *it; }
-		else if (s.isTrue(*it)) { Clause::detach(s); return true;}
-	}
-	// replace any false lits with sentinels
-	for (Literal* r = j; r != t.second; ++r) { *r = lit_false(); }
-	if (!isSmall()) {
-		uint32 size = std::max((uint32)ClauseHead::HEAD_LITS, static_cast(j-head_));
-		local_.setSize(size);
-		local_.clearIdx();
-		if (j != t.second && learnt() && !strengthened()) {
-			// mark last literal so that we can recompute alloc size later
-			t.second[-1].flag();
-			local_.markStrengthened();
-		}
-		if (reinit && size > 3) {
-			detach(s);
-			std::random_shuffle(head_, j, s.rng);
-			attach(s);
-		}
-	}
-	else if (s.isFalse(head_[2])) {
-		head_[2]   = t.first[0];
-		t.first[0] = t.first[1];
-		t.first[1] = lit_false();
-		--j;
-	}
-	return j <= t.first && ClauseHead::toImplication(s);
+    assert(s.decisionLevel() == 0 && s.queueSize() == 0);
+    if (satisfied(s)) {
+        detach(s);
+        return true;
+    }
+    auto     t  = tail();
+    Literal *it = t.begin() - not isSmall(), *j, *eot = t.end();
+    // skip free literals
+    while (it != eot && s.value(it->var()) == value_free) { ++it; }
+    // copy remaining free literals
+    for (j = it; it != eot; ++it) {
+        if (s.value(it->var()) == value_free) {
+            *j++ = *it;
+        }
+        else if (s.isTrue(*it)) {
+            Clause::detach(s);
+            return true;
+        }
+    }
+    // replace any false lits with sentinels
+    for (Literal* r = j; r != eot; ++r) { *r = lit_false; }
+    if (not isSmall()) {
+        uint32_t size = std::max(head_lits, static_cast(j - head_));
+        local_.setSize(size);
+        local_.clearIdx();
+        if (j != eot && learnt() && not strengthened()) {
+            // mark last literal so that we can recompute alloc size later
+            eot[-1].flag();
+            local_.markStrengthened();
+        }
+        if (reinit && size > 3) {
+            detach(s);
+            s.rng.shuffle(head_, j);
+            attach(s);
+        }
+    }
+    else if (s.isFalse(head_[2])) {
+        head_[2] = t.b[0];
+        t.b[0]   = t.b[1];
+        t.b[1]   = lit_false;
+        --j;
+    }
+    return j <= t.begin() && toImplication(s);
 }
 
-uint32 Clause::isOpen(const Solver& s, const TypeSet& x, LitVec& freeLits) {
-	if (!x.inSet(ClauseHead::type()) || ClauseHead::satisfied(s)) {
-		return 0;
-	}
-	assert(s.queueSize() == 0 && "Watches might be false!");
-	freeLits.push_back(head_[0]);
-	freeLits.push_back(head_[1]);
-	if (!s.isFalse(head_[2])) freeLits.push_back(head_[2]);
-	ValueRep v;
-	LitRange t = tail();
-	for (Literal* r = t.first; r != t.second; ++r) {
-		if ( (v = s.value(r->var())) == value_free) {
-			freeLits.push_back(*r);
-		}
-		else if (v == trueValue(*r)) {
-			std::swap(head_[2], *r);
-			return 0;
-		}
-	}
-	return ClauseHead::type();
+uint32_t Clause::isOpen(const Solver& s, const TypeSet& x, LitVec& freeLits) {
+    if (not x.contains(ClauseHead::type()) || satisfied(s)) {
+        return 0;
+    }
+    assert(s.queueSize() == 0 && "Watches might be false!");
+    freeLits.push_back(head_[0]);
+    freeLits.push_back(head_[1]);
+    if (not s.isFalse(head_[2])) {
+        freeLits.push_back(head_[2]);
+    }
+    for (Literal& r : tail()) {
+        if (auto v = s.value(r.var()); v == value_free) {
+            freeLits.push_back(r);
+        }
+        else if (v == trueValue(r)) {
+            std::swap(head_[2], r);
+            return 0;
+        }
+    }
+    return +ClauseHead::type();
 }
 
 void Clause::undoLevel(Solver& s) {
-	assert(!isSmall());
-	uint32   t = local_.size();
-	uint32  ul = s.jumpLevel();
-	Literal* r = head_+t;
-	while (!r->flagged() && (s.value(r->var()) == value_free || s.level(r->var()) > ul)) {
-		++t;
-		++r;
-	}
-	if (r->flagged() || s.level(r->var()) == 0) {
-		r->unflag();
-		t += !isSentinel(*r);
-		local_.clearContracted();
-	}
-	else {
-		s.addUndoWatch(s.level(r->var()), this);
-	}
-	local_.setSize(t);
+    assert(not isSmall());
+    uint32_t t  = local_.size();
+    uint32_t ul = s.jumpLevel();
+    Literal* r  = head_ + t;
+    while (not r->flagged() && (s.value(r->var()) == value_free || s.level(r->var()) > ul)) {
+        ++t;
+        ++r;
+    }
+    if (r->flagged() || s.level(r->var()) == 0) {
+        r->unflag();
+        t += not isSentinel(*r);
+        local_.clearContracted();
+    }
+    else {
+        s.addUndoWatch(s.level(r->var()), this);
+    }
+    local_.setSize(t);
 }
 
 void Clause::toLits(LitVec& out) const {
-	out.insert(out.end(), head_, (head_+ClauseHead::HEAD_LITS)-isSentinel(head_[2]));
-	LitRange t = const_cast(*this).tail();
-	if (contracted()) { while (!t.second++->flagged()) {;} }
-	out.insert(out.end(), t.first, t.second);
+    out.insert(out.end(), head_, (head_ + head_lits) - isSentinel(head_[2]));
+    auto t = const_cast(*this).tail();
+    if (contracted()) {
+        while (not t.e++->flagged()) { ; }
+    }
+    out.insert(out.end(), t.begin(), t.end());
 }
 
-ClauseHead::BoolPair Clause::strengthen(Solver& s, Literal p, bool toShort) {
-	LitRange t  = tail();
-	Literal* eoh= head_+ClauseHead::HEAD_LITS;
-	Literal* eot= t.second;
-	Literal* it = std::find(head_, eoh, p);
-	BoolPair ret(false, false);
-	if (it != eoh) {
-		if (it != head_+2) { // update watch
-			*it = head_[2];
-			s.removeWatch(~p, this);
-			Literal* best = it, *n;
-			for (n = t.first; n != eot && s.isFalse(*best); ++n) {
-				if (!s.isFalse(*n) || s.level(n->var()) > s.level(best->var())) {
-					best = n;
-				}
-			}
-			std::swap(*it, *best);
-			s.addWatch(~*it, ClauseWatch(this));
-			it = head_+2;
-		}
-		// replace cache literal with literal from tail
-		if ((*it  = *t.first) != lit_false()) {
-			eot     = removeFromTail(s, t.first, eot);
-		}
-		ret.first = true;
-	}
-	else if ((it = std::find(t.first, eot, p)) != eot) {
-		eot       = removeFromTail(s, it, eot);
-		ret.first = true;
-	}
-	else if (contracted()) {
-		for (; *it != p && !it->flagged(); ++it) { ; }
-		ret.first = *it == p;
-		eot       = *it == p ? removeFromTail(s, it, eot) : it + 1;
-	}
-	if (ret.first && ~p == s.tagLiteral()) {
-		clearTagged();
-	}
-	ret.second = toShort && eot == t.first && toImplication(s);
-	return ret;
+ClauseHead::StrengthenResult Clause::strengthen(Solver& s, Literal p, bool toShort) {
+    auto  t      = tail();
+    auto* eoh    = head_ + head_lits;
+    auto* eot    = t.end();
+    auto* it     = std::find(head_, eoh, p);
+    auto  litRem = false;
+    if (it != eoh) {
+        if (it != head_ + 2) { // update watch
+            *it = head_[2];
+            s.removeWatch(~p, this);
+            Literal* best = it;
+            for (Literal* n = t.begin(); n != eot && s.isFalse(*best); ++n) {
+                if (not s.isFalse(*n) || s.level(n->var()) > s.level(best->var())) {
+                    best = n;
+                }
+            }
+            std::swap(*it, *best);
+            s.addWatch(~*it, ClauseWatch(this));
+            it = head_ + 2;
+        }
+        // replace cache literal with literal from tail
+        if (*it = *t.begin(); *it != lit_false) {
+            eot = removeFromTail(s, t.begin(), eot);
+        }
+        litRem = true;
+    }
+    else if (it = std::find(t.begin(), eot, p); it != eot) {
+        eot    = removeFromTail(s, it, eot);
+        litRem = true;
+    }
+    else if (contracted()) {
+        for (; *it != p && not it->flagged(); ++it) { ; }
+        litRem = (*it == p);
+        eot    = *it == p ? removeFromTail(s, it, eot) : it + 1;
+    }
+    if (litRem && ~p == s.tagLiteral()) {
+        clearTagged();
+    }
+    return {.litRemoved = litRem, .removeClause = toShort && eot == t.begin() && toImplication(s)};
 }
 
 Literal* Clause::removeFromTail(Solver& s, Literal* it, Literal* end) {
-	assert(it != end || contracted());
-	if (!contracted()) {
-		*it  = *--end;
-		*end = lit_false();
-		if (!isSmall()) {
-			local_.setSize(local_.size()-1);
-			local_.clearIdx();
-		}
-	}
-	else {
-		uint32 uLev  = s.level(end->var());
-		Literal* j   = it;
-		while ( !j->flagged() ) { *j++ = *++it; }
-		*j           = lit_false();
-		uint32 nLev  = s.level(end->var());
-		if (uLev != nLev && s.removeUndoWatch(uLev, this) && nLev != 0) {
-			s.addUndoWatch(nLev, this);
-		}
-		if (j != end) { (j-1)->flag(); }
-		else          { local_.clearContracted(); }
-		end = j;
-	}
-	if (learnt() && !isSmall() && !strengthened()) {
-		end->flag();
-		local_.markStrengthened();
-	}
-	return end;
-}
-uint32 Clause::size() const {
-	LitRange t = const_cast(*this).tail();
-	return !isSentinel(head_[2])
-		? 3u + static_cast(t.second - t.first)
-		: 2u;
+    assert(it != end || contracted());
+    if (not contracted()) {
+        *it  = *--end;
+        *end = lit_false;
+        if (not isSmall()) {
+            local_.setSize(local_.size() - 1);
+            local_.clearIdx();
+        }
+    }
+    else {
+        uint32_t uLev = s.level(end->var());
+        Literal* j    = it;
+        while (not j->flagged()) { *j++ = *++it; }
+        *j            = lit_false;
+        uint32_t nLev = s.level(end->var());
+        if (uLev != nLev && s.removeUndoWatch(uLev, this) && nLev != 0) {
+            s.addUndoWatch(nLev, this);
+        }
+        if (j != end) {
+            (j - 1)->flag();
+        }
+        else {
+            local_.clearContracted();
+        }
+        end = j;
+    }
+    if (learnt() && not isSmall() && not strengthened()) {
+        end->flag();
+        local_.markStrengthened();
+    }
+    return end;
+}
+uint32_t Clause::size() const {
+    auto t = const_cast(*this).tail();
+    return not isSentinel(head_[2]) ? 3u + t.size() : 2u;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // mt::SharedLitsClause
 /////////////////////////////////////////////////////////////////////////////////////////
 namespace mt {
-ClauseHead* SharedLitsClause::newClause(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits, bool addRef) {
-	return new (s.allocSmall()) SharedLitsClause(s, shared_lits, lits, e, addRef);
+ClauseHead* SharedLitsClause::newClause(Solver& s, SharedLiterals* shared_lits, const InfoType& e, const Literal* lits,
+                                        bool addRef) {
+    return new (s.allocSmall()) SharedLitsClause(s, shared_lits, lits, e, addRef);
 }
 
-SharedLitsClause::SharedLitsClause(Solver& s, SharedLiterals* shared_lits, const Literal* w, const InfoType& e, bool addRef)
-	: ClauseHead(e) {
-	static_assert(sizeof(SharedLitsClause) <= 32, "Unsupported Padding");
-	shared_ = addRef ? shared_lits->share() : shared_lits;
-	std::memcpy(head_, w, std::min((uint32)ClauseHead::HEAD_LITS, shared_lits->size())*sizeof(Literal));
-	attach(s);
-	if (learnt()) { s.addLearntBytes(32); }
+SharedLitsClause::SharedLitsClause(Solver& s, SharedLiterals* shared_lits, const Literal* w, const InfoType& e,
+                                   bool addRef)
+    : ClauseHead(e) {
+    static_assert(sizeof(SharedLitsClause) <= 32, "Unsupported Padding");
+    shared_ = addRef ? shared_lits->share() : shared_lits;
+    std::memcpy(head_, w, std::min(head_lits, shared_lits->size()) * sizeof(Literal));
+    attach(s);
+    if (learnt()) {
+        s.addLearntBytes(32);
+    }
 }
 
-Constraint* SharedLitsClause::cloneAttach(Solver& other) {
-	return SharedLitsClause::newClause(other, shared_, InfoType(this->type()), head_);
+ClauseHead* SharedLitsClause::cloneAttach(Solver& other) {
+    return newClause(other, shared_, InfoType(this->type()), head_);
 }
 
-bool SharedLitsClause::updateWatch(Solver& s, uint32 pos) {
-	Literal  other = head_[1^pos];
-	for (const Literal* r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
-		// at this point we know that head_[2] is false, so we only need to check
-		// that we do not watch the other watched literal twice!
-		if (!s.isFalse(*r) && *r != other) {
-			head_[pos] = *r; // replace watch
-			// try to replace cache literal
-			switch( std::min(static_cast(8), static_cast(end-r)) ) {
-				case 8: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 7: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 6: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 5: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 4: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 3: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				case 2: if (!s.isFalse(*++r) && *r != other) { head_[2] = *r; return true; } // FALLTHROUGH
-				default: return true;
-			}
-		}
-	}
-	return false;
+bool SharedLitsClause::updateWatch(Solver& s, uint32_t pos) {
+#define REPLACE_CACHE_OR()                                                                                             \
+    if (not s.isFalse(*++r) && *r != other) {                                                                          \
+        head_[2] = *r;                                                                                                 \
+        return true;                                                                                                   \
+    }
+    Literal other = head_[1 ^ pos];
+    for (const Literal *r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
+        // at this point we know that head_[2] is false, so we only need to check
+        // that we do not watch the other watched literal twice!
+        if (not s.isFalse(*r) && *r != other) {
+            head_[pos] = *r; // replace watch
+            // try to replace cache literal
+            // NOLINTBEGIN(bugprone-branch-clone)
+            switch (std::min(static_cast(8), static_cast(end - r))) {
+                case 8 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 7 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 6 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 5 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 4 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 3 : REPLACE_CACHE_OR() [[fallthrough]];
+                case 2 : REPLACE_CACHE_OR() [[fallthrough]];
+                default: return true;
+            }
+            // NOLINTEND(bugprone-branch-clone)
+        }
+    }
+#undef REPLACE_CACHE_OR
+    return false;
 }
 
 void SharedLitsClause::reason(Solver& s, Literal p, LitVec& out) {
-	for (const Literal* r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
-		assert(s.isFalse(*r) || *r == p);
-		if (*r != p) { out.push_back(~*r); }
-	}
-	if (learnt()) {
-		s.updateOnReason(info_.score(), p, out);
-	}
+    for (auto r : *shared_) {
+        assert(s.isFalse(r) || r == p);
+        if (r != p) {
+            out.push_back(~r);
+        }
+    }
+    if (learnt()) {
+        s.updateOnReason(info_.score(), p, out);
+    }
 }
 
 bool SharedLitsClause::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	s.updateOnMinimize(info_.score());
-	for (const Literal* r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
-		if (*r != p && !s.ccMinimize(~*r, rec)) { return false; }
-	}
-	return true;
+    s.updateOnMinimize(info_.score());
+    return std::ranges::all_of(*shared_, [&](Literal r) { return r == p || s.ccMinimize(~r, rec); });
 }
 
-bool SharedLitsClause::isReverseReason(const Solver& s, Literal p, uint32 maxL, uint32 maxN) {
-	uint32 notSeen = 0;
-	for (const Literal* r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
-		if (*r == p) continue;
-		if (!isRevLit(s, *r, maxL)) return false;
-		if (!s.seen(r->var()) && ++notSeen > maxN) return false;
-	}
-	return true;
+bool SharedLitsClause::isReverseReason(const Solver& s, Literal p, uint32_t maxL, uint32_t maxN) {
+    uint32_t notSeen = 0;
+    for (auto r : *shared_) {
+        if (r == p) {
+            continue;
+        }
+        if (not isRevLit(s, r, maxL)) {
+            return false;
+        }
+        if (not s.seen(r.var()) && ++notSeen > maxN) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool SharedLitsClause::simplify(Solver& s, bool reinit) {
-	if (ClauseHead::satisfied(s)) {
-		detach(s);
-		return true;
-	}
-	uint32 optSize = shared_->simplify(s);
-	if (optSize == 0) {
-		detach(s);
-		return true;
-	}
-	else if (optSize <= Clause::MAX_SHORT_LEN) {
-		Literal lits[Clause::MAX_SHORT_LEN];
-		Literal* j = lits;
-		for (const Literal* r = shared_->begin(), *e = shared_->end(); r != e; ++r) {
-			if (!s.isFalse(*r)) *j++ = *r;
-		}
-		// safe extra data
-		InfoType myInfo = info_;
-		// detach & destroy but do not release memory
-		detach(s);
-		SharedLitsClause::destroy(0, false);
-		// construct short clause in "this"
-		ClauseHead* h = Clause::newClause(this, s, ClauseRep::prepared(lits, static_cast(j-lits), myInfo));
-		return h->simplify(s, reinit);
-	}
-	else if (s.isFalse(head_[2])) {
-		// try to replace cache lit with non-false literal
-		for (const Literal* r = shared_->begin(), *e = shared_->end(); r != e; ++r) {
-			if (!s.isFalse(*r) && std::find(head_, head_+2, *r) == head_+2) {
-				head_[2] = *r;
-				break;
-			}
-		}
-	}
-	return false;
+    if (satisfied(s)) {
+        detach(s);
+        return true;
+    }
+    if (uint32_t optSize = shared_->simplify(s); optSize == 0) {
+        detach(s);
+        return true;
+    }
+    else if (optSize <= max_short_len) {
+        Literal  lits[max_short_len];
+        Literal* j = lits;
+        for (auto r : *shared_) {
+            if (not s.isFalse(r)) {
+                *j++ = r;
+            }
+        }
+        // safe extra data
+        InfoType myInfo = info_;
+        // detach & destroy but do not release memory
+        detach(s);
+        SharedLitsClause::destroy(nullptr, false);
+        // construct short clause in "this"
+        void*       mem = std::launder(this);
+        ClauseHead* h = Clause::newClause(mem, s, ClauseRep::prepared({lits, static_cast(j - lits)}, myInfo));
+        return h->simplify(s, reinit);
+    }
+    else if (s.isFalse(head_[2])) {
+        // try to replace cache lit with non-false literal
+        for (auto r : *shared_) {
+            if (not s.isFalse(r) && std::find(head_, head_ + 2, r) == head_ + 2) {
+                head_[2] = r;
+                break;
+            }
+        }
+    }
+    return false;
 }
 
 void SharedLitsClause::destroy(Solver* s, bool detachFirst) {
-	if (s) {
-		if (detachFirst) { ClauseHead::detach(*s); }
-		if (learnt())    { s->freeLearntBytes(32); }
-	}
-	shared_->release();
-	void* mem = this;
-	this->~SharedLitsClause();
-	if (s) { s->freeSmall(mem); }
+    if (s) {
+        if (detachFirst) {
+            ClauseHead::detach(*s);
+        }
+        if (learnt()) {
+            s->freeLearntBytes(32);
+        }
+    }
+    shared_->release();
+    void* mem = this;
+    this->~SharedLitsClause();
+    if (s) {
+        s->freeSmall(mem);
+    }
 }
 
-uint32 SharedLitsClause::isOpen(const Solver& s, const TypeSet& x, LitVec& freeLits) {
-	if (!x.inSet(ClauseHead::type()) || ClauseHead::satisfied(s)) {
-		return 0;
-	}
-	Literal* head = head_;
-	ValueRep v;
-	for (const Literal* r = shared_->begin(), *end = shared_->end(); r != end; ++r) {
-		if ( (v = s.value(r->var())) == value_free ) {
-			freeLits.push_back(*r);
-		}
-		else if (v == trueValue(*r)) {
-			head[2] = *r; // remember as cache literal
-			return 0;
-		}
-	}
-	return ClauseHead::type();
+uint32_t SharedLitsClause::isOpen(const Solver& s, const TypeSet& x, LitVec& freeLits) {
+    if (not x.contains(ClauseHead::type()) || satisfied(s)) {
+        return 0;
+    }
+    Literal* head = head_;
+    for (auto r : *shared_) {
+        if (auto v = s.value(r.var()); v == value_free) {
+            freeLits.push_back(r);
+        }
+        else if (v == trueValue(r)) {
+            head[2] = r; // remember as cache literal
+            return 0;
+        }
+    }
+    return +ClauseHead::type();
 }
 
-void SharedLitsClause::toLits(LitVec& out) const {
-	out.insert(out.end(), shared_->begin(), shared_->end());
-}
+void SharedLitsClause::toLits(LitVec& out) const { out.insert(out.end(), shared_->begin(), shared_->end()); }
 
-ClauseHead::BoolPair SharedLitsClause::strengthen(Solver&, Literal, bool) {
-	return BoolPair(false, false);
-}
+ClauseHead::StrengthenResult SharedLitsClause::strengthen(Solver&, Literal, bool) { return {}; }
 
-uint32 SharedLitsClause::size() const { return shared_->size(); }
+uint32_t SharedLitsClause::size() const { return shared_->size(); }
 } // end namespace mt
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // LoopFormula
 /////////////////////////////////////////////////////////////////////////////////////////
-LoopFormula* LoopFormula::newLoopFormula(Solver& s, const ClauseRep& c1, const Literal* atoms, uint32 nAtoms, bool heu) {
-	uint32 bytes = sizeof(LoopFormula) + (c1.size + nAtoms + 2) * sizeof(Literal);
-	void* mem = Detail::alloc(bytes);
-	s.addLearntBytes(bytes);
-	return new (mem)LoopFormula(s, c1, atoms, nAtoms, heu);
-}
-LoopFormula::LoopFormula(Solver& s, const ClauseRep& c1, const Literal* atoms, uint32 nAtoms, bool heu) {
-	act_ = c1.info.score();
-	lits_[0] = lit_true(); // Starting sentinel
-	std::memcpy(lits_ + 1, c1.lits, c1.size * sizeof(Literal));
-	lits_[end_ = c1.size + 1] = lit_true(); // Ending sentinel
-	s.addWatch(~lits_[2], this, (2 << 1) + 1);
-	lits_[2].flag();
-	size_  = c1.size + nAtoms + 2;
-	str_   = 0;
-	xPos_  = 1;
-	other_ = 1;
-	for (uint32 i = 0, x = end_ + 1; i != nAtoms; ++i, ++x) {
-		act_.bumpActivity();
-		s.addWatch(~(lits_[x] = atoms[i]), this, (1 << 1) + 1);
-		if (heu) {
-			lits_[1] = atoms[i];
-			s.heuristic()->newConstraint(s, lits_ + 1, c1.size, Constraint_t::Loop);
-		}
-	}
-	(lits_[1] = c1.lits[0]).flag();
+LoopFormula* LoopFormula::newLoopFormula(Solver& s, const ClauseRep& c1, LitView atoms, bool heu) {
+    uint32_t bytes = sizeof(LoopFormula) + (c1.size + size32(atoms) + 2) * sizeof(Literal);
+    void*    mem   = Detail::alloc(bytes);
+    s.addLearntBytes(bytes);
+    return new (mem) LoopFormula(s, c1, atoms, heu);
+}
+LoopFormula::LoopFormula(Solver& s, const ClauseRep& c1, LitView atoms, bool heu) {
+    act_     = c1.info.score();
+    lits_[0] = lit_true; // Starting sentinel
+    std::memcpy(lits_ + 1, c1.lits, c1.size * sizeof(Literal));
+    lits_[end_ = c1.size + 1] = lit_true; // Ending sentinel
+    s.addWatch(~lits_[2], this, (2 << 1) + 1);
+    lits_[2].flag();
+    size_  = c1.size + size32(atoms) + 2;
+    str_   = 0;
+    xPos_  = 1;
+    other_ = 1;
+    for (uint32_t x = end_ + 1; auto a : atoms) {
+        act_.bumpActivity();
+        s.addWatch(~(lits_[x++] = a), this, (1 << 1) + 1);
+        if (heu) {
+            lits_[1] = a;
+            s.heuristic()->newConstraint(s, {lits_ + 1, c1.size}, ConstraintType::loop);
+        }
+    }
+    (lits_[1] = c1.lits[0]).flag();
 }
 void LoopFormula::destroy(Solver* s, bool detach) {
-	if (s) {
-		if (detach) { this->detach(*s); }
-		if (str_)   { while (lits_[size_++].rep() != 3u) { ; } }
-		s->freeLearntBytes(sizeof(LoopFormula) + (size_ * sizeof(Literal)));
-	}
-	void* mem = static_cast(this);
-	this->~LoopFormula();
-	Detail::free(mem);
+    if (s) {
+        if (detach) {
+            this->detach(*s);
+        }
+        if (str_) {
+            while (lits_[size_++].rep() != 3u) { ; }
+        }
+        s->freeLearntBytes(sizeof(LoopFormula) + (size_ * sizeof(Literal)));
+    }
+    void* mem = static_cast(this);
+    this->~LoopFormula();
+    Detail::free(mem);
 }
 void LoopFormula::detach(Solver& s) {
-	for (Literal* it = begin() + xPos_; !isSentinel(*it); ++it) {
-		if (it->flagged()) { s.removeWatch(~*it, this); it->unflag(); }
-	}
-	for (Literal* it = xBegin(), *end = xEnd(); it != end; ++it) {
-		s.removeWatch(~*it, this);
-	}
+    for (Literal* it = begin() + xPos_; not isSentinel(*it); ++it) {
+        if (it->flagged()) {
+            s.removeWatch(~*it, this);
+            it->unflag();
+        }
+    }
+    for (auto lit : xSpan()) { s.removeWatch(~lit, this); }
 }
 bool LoopFormula::otherIsSat(const Solver& s) {
-	if (other_ != xPos_)          { return s.isTrue(lits_[other_]); }
-	if (!s.isTrue(lits_[other_])) { return false; }
-	for (Literal* it = xBegin(), *end = xEnd(); it != end; ++it) {
-		if (!s.isTrue(*it)) {
-			if (lits_[xPos_].flagged()){ (lits_[xPos_] = *it).flag(); }
-			else                       { lits_[xPos_] = *it; }
-			return false;
-		}
-	}
-	return true;
-}
-Constraint::PropResult LoopFormula::propagate(Solver& s, Literal p, uint32& data) {
-	if (otherIsSat(s)) { // already satisfied?
-		return PropResult(true, true);
-	}
-	uint32 idx = data >> 1;
-	Literal* w = lits_ + idx;
-	bool  head = idx == xPos_;
-	if (head) { // p is one of the atoms - move to active part
-		p = ~p;
-		if (*w != p && s.isFalse(*w)) { return PropResult(true, true); }
-		if (!w->flagged())            { *w = p; return PropResult(true, true); }
-		(*w = p).flag();
-	}
-	for (int bounds = 0, dir = ((data & 1) << 1) - 1;;) {
-		// search non-false literal - sentinels guarantee termination
-		for (w += dir; s.isFalse(*w); w += dir) {;}
-		if (!isSentinel(*w)) {
-			uint32 nIdx = static_cast(w - lits_);
-			// other watched literal?
-			if (w->flagged()) { other_ = nIdx; continue; }
-			// replace watch
-			lits_[idx].unflag();
-			w->flag();
-			// add new watch only w is not one of the atoms
-			// and keep previous watch if p is one of the atoms
-			if (nIdx != xPos_) { s.addWatch(~*w, this, (nIdx << 1) + (dir==1)); }
-			return PropResult(true, head);
-		}
-		else if (++bounds == 1) {
-			w     = lits_ + idx; // Halfway through, restart search, but
-			dir  *= -1;          // this time walk in the opposite direction.
-			data ^= 1;           // Save new direction of watch
-		}
-		else { // clause is unit
-			bool ok = s.force(lits_[other_], this);
-			if (other_ == xPos_ && ok) { // all lits in inactive part are implied
-				for (Literal* it = xBegin(), *end = xEnd(); it != end && (ok = s.force(*it, this)) == true; ++it) { ; }
-			}
-			return PropResult(ok, true);
-		}
-	}
+    if (other_ != xPos_) {
+        return s.isTrue(lits_[other_]);
+    }
+    if (not s.isTrue(lits_[other_])) {
+        return false;
+    }
+    for (auto lit : xSpan()) {
+        if (not s.isTrue(lit)) {
+            if (lits_[xPos_].flagged()) {
+                (lits_[xPos_] = lit).flag();
+            }
+            else {
+                lits_[xPos_] = lit;
+            }
+            return false;
+        }
+    }
+    return true;
+}
+Constraint::PropResult LoopFormula::propagate(Solver& s, Literal p, uint32_t& data) {
+    if (otherIsSat(s)) { // already satisfied?
+        return PropResult(true, true);
+    }
+    uint32_t idx  = data >> 1;
+    Literal* w    = lits_ + idx;
+    bool     head = idx == xPos_;
+    if (head) { // p is one of the atoms - move to active part
+        p = ~p;
+        if (*w != p && s.isFalse(*w)) {
+            return PropResult(true, true);
+        }
+        if (not w->flagged()) {
+            *w = p;
+            return PropResult(true, true);
+        }
+        (*w = p).flag();
+    }
+    for (int bounds = 0, dir = static_cast((data & 1) << 1) - 1;;) {
+        // search non-false literal - sentinels guarantee termination
+        for (w += dir; s.isFalse(*w); w += dir) { ; }
+        if (not isSentinel(*w)) {
+            auto nIdx = static_cast(w - lits_);
+            // other watched literal?
+            if (w->flagged()) {
+                other_ = nIdx;
+                continue;
+            }
+            // replace watch
+            lits_[idx].unflag();
+            w->flag();
+            // add new watch only w is not one of the atoms
+            // and keep previous watch if p is one of the atoms
+            if (nIdx != xPos_) {
+                s.addWatch(~*w, this, (nIdx << 1) + (dir == 1));
+            }
+            return PropResult(true, head);
+        }
+        else if (++bounds == 1) {
+            w     = lits_ + idx; // Halfway through, restart search, but
+            dir  *= -1;          // this time walk in the opposite direction.
+            data ^= 1;           // Save new direction of watch
+        }
+        else { // clause is unit
+            bool ok = s.force(lits_[other_], this);
+            if (other_ == xPos_ && ok) { // all lits in inactive part are implied
+                for (auto lit : xSpan()) {
+                    if (ok = s.force(lit, this); not ok) {
+                        break;
+                    }
+                }
+            }
+            return PropResult(ok, true);
+        }
+    }
 }
 void LoopFormula::reason(Solver& s, Literal p, LitVec& lits) {
-	// p = body: all literals in active clause
-	// p = atom: only bodies
-	for (Literal* it = begin() + (other_ == xPos_); !isSentinel(*it); ++it) {
-		if (*it != p) { lits.push_back(~*it); }
-	}
-	s.updateOnReason(act_, p, lits);
+    // p = body: all literals in active clause
+    // p = atom: only bodies
+    for (Literal* it = begin() + (other_ == xPos_); not isSentinel(*it); ++it) {
+        if (*it != p) {
+            lits.push_back(~*it);
+        }
+    }
+    s.updateOnReason(act_, p, lits);
 }
 bool LoopFormula::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	s.updateOnMinimize(act_);
-	for (Literal* it = begin() + (other_ == xPos_); !isSentinel(*it); ++it) {
-		if (*it != p && !s.ccMinimize(~*it, rec)) { return false; }
-	}
-	return true;
-}
-uint32 LoopFormula::size() const {
-	return size_ - (2 + xPos_);
-}
-bool LoopFormula::locked(const Solver& s) const {
-	if (other_ != xPos_ || !s.isTrue(lits_[other_])) {
-		return s.isTrue(lits_[other_]) && s.reason(lits_[other_]) == this;
-	}
-	LoopFormula& self = const_cast(*this);
-	for (const Literal* it = self.xBegin(), *end = self.xEnd(); it != end; ++it) {
-		if (s.isTrue(*it) && s.reason(*it) == this) { return true; }
-	}
-	return false;
-}
-uint32 LoopFormula::isOpen(const Solver& s, const TypeSet& xs, LitVec& freeLits) {
-	if (!xs.inSet(Constraint_t::Loop) || otherIsSat(s)) {
-		return 0;
-	}
-	for (Literal* it = begin() + xPos_; !isSentinel(*it); ++it) {
-		if      (s.value(it->var()) == value_free) { freeLits.push_back(*it); }
-		else if (s.isTrue(*it))                    { other_ = static_cast(it-lits_); return 0; }
-	}
-	for (Literal* it = xBegin(), *end = xEnd(); it != end; ++it) {
-		if (s.value(it->var()) == value_free) { freeLits.push_back(*it); }
-	}
-	return Constraint_t::Loop;
+    s.updateOnMinimize(act_);
+    for (Literal* it = begin() + (other_ == xPos_); not isSentinel(*it); ++it) {
+        if (*it != p && not s.ccMinimize(~*it, rec)) {
+            return false;
+        }
+    }
+    return true;
+}
+uint32_t LoopFormula::size() const { return size_ - (2u + xPos_); }
+bool     LoopFormula::locked(const Solver& s) const {
+    if (other_ != xPos_ || not s.isTrue(lits_[other_])) {
+        return s.isTrue(lits_[other_]) && s.reason(lits_[other_]) == this;
+    }
+    auto& self = const_cast(*this);
+    return std::ranges::any_of(self.xSpan(), [&](Literal lit) { return s.isTrue(lit) && s.reason(lit) == this; });
+}
+uint32_t LoopFormula::isOpen(const Solver& s, const TypeSet& xs, LitVec& freeLits) {
+    if (not xs.contains(ConstraintType::loop) || otherIsSat(s)) {
+        return 0;
+    }
+    for (Literal* it = begin() + xPos_; not isSentinel(*it); ++it) {
+        if (s.value(it->var()) == value_free) {
+            freeLits.push_back(*it);
+        }
+        else if (s.isTrue(*it)) {
+            other_ = static_cast(it - lits_);
+            return 0;
+        }
+    }
+    for (auto lit : xSpan()) {
+        if (s.value(lit.var()) == value_free) {
+            freeLits.push_back(lit);
+        }
+    }
+    return +ConstraintType::loop;
 }
 bool LoopFormula::simplify(Solver& s, bool) {
-	if (otherIsSat(s) || (other_ != xPos_ && (other_ = xPos_) != 0 && otherIsSat(s))) {
-		detach(s);
-		return true;
-	}
-	Literal* it = begin(), *j, *end = xEnd();
-	while (s.value(it->var()) == value_free) { ++it; }
-	if (!isSentinel(*(j=it))) {
-		// simplify active clause
-		if (*it == lits_[xPos_]){ xPos_ = 0; }
-		for (GenericWatch* w; !isSentinel(*it); ++it) {
-			if (s.value(it->var()) == value_free) {
-				if (it->flagged() && (w = s.getWatch(~*it, this)) != 0) {
-					w->data = (static_cast(j - lits_) << 1) + (w->data&1);
-				}
-				*j++ = *it;
-			}
-			else if (s.isTrue(*it)) { detach(s); return true; }
-			else                    { assert(!it->flagged() && "Constraint not propagated!"); }
-		}
-		*j   = lit_true();
-		end_ = static_cast(j - lits_);
-	}
-	// simplify extra part
-	for (++it, ++j; it != end; ++it) {
-		if (s.value(it->var()) == value_free && xPos_) { *j++ = *it; }
-		else { s.removeWatch(~*it, this); }
-	}
-	bool isClause = static_cast(j - xBegin()) == 1;
-	if (isClause) { --j; }
-	if (j != end) { // size changed?
-		if (!str_)   { (end-1)->rep() = 3u; str_ = 1u; }
-		if (isClause){
-			assert(xPos_ && *j == lits_[xPos_]);
-			if (!lits_[xPos_].flagged()) { s.removeWatch(~*j, this); }
-			xPos_ = 0;
-		}
-		size_ = static_cast((end = j) - lits_);
-	}
-	assert(!isClause || xPos_ == 0);
-	other_ = xPos_ + 1;
-	ClauseRep act = ClauseRep::create(begin(), end_ - 1, Constraint_t::Loop);
-	if (act.isImp() && s.allowImplicit(act)) {
-		detach(s);
-		ClauseCreator::Result res;
-		for (it = xBegin(); it != end && res.ok() && !res.local; ++it) {
-			lits_[xPos_] = *it;
-			res = ClauseCreator::create(s, act, ClauseCreator::clause_no_add);
-			POTASSCO_ASSERT(lits_[xPos_] == *it, "LOOP MUST NOT CONTAIN ASSIGNED VARS!");
-		}
-		if (!xPos_) { res = ClauseCreator::create(s, act, ClauseCreator::clause_no_add); }
-		POTASSCO_ASSERT(res.ok() && !res.local, "LOOP MUST NOT CONTAIN AUX VARS!");
-		return true;
-	}
-	return false;
+    if (otherIsSat(s) || (other_ != xPos_ && (other_ = xPos_) != 0 && otherIsSat(s))) {
+        detach(s);
+        return true;
+    }
+    Literal *it = begin(), *j, *end = xEnd();
+    while (s.value(it->var()) == value_free) { ++it; }
+    if (j = it; not isSentinel(*j)) {
+        // simplify active clause
+        if (*it == lits_[xPos_]) {
+            xPos_ = 0;
+        }
+        for (GenericWatch* w; not isSentinel(*it); ++it) {
+            if (s.value(it->var()) == value_free) {
+                if (it->flagged() && (w = s.getWatch(~*it, this)) != nullptr) {
+                    w->data = (static_cast(j - lits_) << 1) + (w->data & 1);
+                }
+                *j++ = *it;
+            }
+            else if (s.isTrue(*it)) {
+                detach(s);
+                return true;
+            }
+            else {
+                assert(not it->flagged() && "Constraint not propagated!");
+            }
+        }
+        *j   = lit_true;
+        end_ = static_cast(j - lits_);
+    }
+    // simplify extra part
+    for (++it, ++j; it != end; ++it) {
+        if (s.value(it->var()) == value_free && xPos_) {
+            *j++ = *it;
+        }
+        else {
+            s.removeWatch(~*it, this);
+        }
+    }
+    bool isClause = static_cast(j - xBegin()) == 1;
+    if (isClause) {
+        --j;
+    }
+    if (j != end) { // size changed?
+        if (not str_) {
+            (end - 1)->rep() = 3u;
+            str_             = 1u;
+        }
+        if (isClause) {
+            assert(xPos_ && *j == lits_[xPos_]);
+            if (not lits_[xPos_].flagged()) {
+                s.removeWatch(~*j, this);
+            }
+            xPos_ = 0;
+        }
+        size_ = static_cast((end = j) - lits_);
+    }
+    assert(not isClause || xPos_ == 0);
+    other_        = xPos_ + 1;
+    ClauseRep act = ClauseRep::create({begin(), end_ - 1}, ConstraintType::loop);
+    POTASSCO_ASSERT(act.size > 1);
+    if (s.allowImplicit(act)) {
+        detach(s);
+        act.prep = 1;
+        for (auto lit : xPos_ ? xSpan() : std::span{begin(), 1u}) {
+            POTASSCO_ASSERT(s.value(lit.var()) == value_free);
+            lits_[xPos_] = lit;
+            auto res     = ClauseCreator::create(s, act, ClauseCreator::clause_no_add);
+            POTASSCO_ASSERT(res.ok() && not res.local, "LOOP MUST NOT CONTAIN AUX VARS!");
+        }
+        return true;
+    }
+    return false;
 }
 
-}
+} // namespace Clasp
diff --git a/src/clingo.cpp b/src/clingo.cpp
index 9b42f06..36039c6 100644
--- a/src/clingo.cpp
+++ b/src/clingo.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2015-2017 Benjamin Kaufmann
+// Copyright (c) 2015-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,548 +22,605 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
+#include 
+
+#include 
+
 #include 
-#include POTASSCO_EXT_INCLUDE(unordered_map)
-namespace Clasp { namespace {
-	template 
-	struct Scoped {
-		Scoped(L* lock, O* obj, const OP& op = OP()) : lock_(lock), obj_(obj), op_(op) { if (lock) (lock->*enter)(); }
-		~Scoped() { if (lock_) (lock_->*exit)(); }
-		O* operator->() const { op_(); return obj_; }
-		L* lock_;
-		O* obj_;
-		OP op_;
-	};
-	struct Nop { void operator()() const {} };
-	struct Inc { Inc(LitVec::size_type& e) : epoch_(&e) {} void operator()() const { ++*epoch_; } LitVec::size_type* epoch_; };
-	typedef Scoped ScopedLock;
-	typedef Scoped ScopedUnlock;
-}
-ClingoPropagatorLock::~ClingoPropagatorLock() {}
+#include 
+namespace Clasp {
+namespace {
+template 
+struct Scoped {
+    Scoped(L* lk, O* ptr, const Op& o = Op()) : lock(lk), obj(ptr), op(o) {
+        if (lock) {
+            (lock->*Enter)();
+        }
+    }
+    ~Scoped() {
+        if (lock) {
+            (lock->*Exit)();
+        }
+    }
+    O* operator->() const {
+        op();
+        return obj;
+    }
+    L* lock;
+    O* obj;
+    Op op;
+};
+struct Nop {
+    constexpr void operator()() const {}
+};
+struct Inc {
+    explicit Inc(uint32_t& e) : epoch(&e) {}
+    constexpr void operator()() const { ++*epoch; }
+    uint32_t*      epoch;
+};
+using ScopedLock = Scoped;
+using ScopedUnlock =
+    Scoped;
+} // namespace
+ClingoPropagatorLock::~ClingoPropagatorLock() = default;
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClingoAssignment
 /////////////////////////////////////////////////////////////////////////////////////////
-static const uint32_t trailOffset = 1u; // Offset for handling true literal.
+static constexpr uint32_t trail_offset = 1u; // Offset for handling true literal.
 
-ClingoAssignment::ClingoAssignment(const Solver& s)
-	: solver_(&s) {}
+ClingoAssignment::ClingoAssignment(const Solver& s) : solver_(&s) {}
 
-ClingoAssignment::Value_t ClingoAssignment::value(Lit_t lit)  const {
-	POTASSCO_REQUIRE(ClingoAssignment::hasLit(lit), "Invalid literal");
-	const uint32_t var = decodeVar(lit);
-	switch (solver_->validVar(var) ? solver_->value(var) : value_free) {
-		default: return Value_t::Free;
-		case value_true:  return lit >= 0 ? Value_t::True  : Value_t::False;
-		case value_false: return lit >= 0 ? Value_t::False : Value_t::True;
-	}
+ClingoAssignment::Value_t ClingoAssignment::value(Lit_t lit) const {
+    POTASSCO_CHECK_PRE(ClingoAssignment::hasLit(lit), "Invalid literal");
+    const uint32_t var = decodeVar(lit);
+    switch (solver_->validVar(var) ? solver_->value(var) : value_free) {
+        default         : return Value_t::free;
+        case value_true : return lit >= 0 ? Value_t::true_ : Value_t::false_;
+        case value_false: return lit >= 0 ? Value_t::false_ : Value_t::true_;
+    }
 }
-uint32_t ClingoAssignment::level(Lit_t lit)  const {
-	return ClingoAssignment::value(lit) != Potassco::Value_t::Free
-		? solver_->level(decodeVar(lit))
-		: UINT32_MAX;
+uint32_t ClingoAssignment::level(Lit_t lit) const {
+    return ClingoAssignment::value(lit) != Value_t::free ? solver_->level(decodeVar(lit)) : UINT32_MAX;
 }
 ClingoAssignment::Lit_t ClingoAssignment::decision(uint32_t dl) const {
-	POTASSCO_REQUIRE(dl <= solver_->decisionLevel(), "Invalid decision level");
-	return encodeLit(dl ? solver_->decision(dl) : lit_true());
+    POTASSCO_CHECK_PRE(dl <= solver_->decisionLevel(), "Invalid decision level");
+    return encodeLit(dl ? solver_->decision(dl) : lit_true);
 }
 ClingoAssignment::Lit_t ClingoAssignment::trailAt(uint32_t pos) const {
-	POTASSCO_REQUIRE(pos < trailSize(), "Invalid trail position");
-	return pos != 0 ? encodeLit(solver_->trail()[pos - trailOffset]) : encodeLit(lit_true());
+    POTASSCO_CHECK_PRE(pos < trailSize(), "Invalid trail position");
+    return encodeLit(pos != 0 ? solver_->trailLit(pos - trail_offset) : lit_true);
 }
 uint32_t ClingoAssignment::trailBegin(uint32_t dl) const {
-	POTASSCO_REQUIRE(dl <= solver_->decisionLevel(), "Invalid decision level");
-	return dl != 0 ? solver_->levelStart(dl) + trailOffset : 0;
-}
-uint32_t ClingoAssignment::size()            const { return std::max(solver_->numVars(), solver_->numProblemVars()) + trailOffset; }
-uint32_t ClingoAssignment::unassigned()      const { return size() - trailSize(); }
-bool     ClingoAssignment::hasConflict()     const { return solver_->hasConflict(); }
-uint32_t ClingoAssignment::level()           const { return solver_->decisionLevel(); }
-uint32_t ClingoAssignment::rootLevel()       const { return solver_->rootLevel(); }
+    POTASSCO_CHECK_PRE(dl <= solver_->decisionLevel(), "Invalid decision level");
+    return dl != 0 ? solver_->levelStart(dl) + trail_offset : 0;
+}
+uint32_t ClingoAssignment::size() const {
+    return std::max(solver_->numVars(), solver_->numProblemVars()) + trail_offset;
+}
+uint32_t ClingoAssignment::unassigned() const { return size() - trailSize(); }
+bool     ClingoAssignment::hasConflict() const { return solver_->hasConflict(); }
+uint32_t ClingoAssignment::level() const { return solver_->decisionLevel(); }
+uint32_t ClingoAssignment::rootLevel() const { return solver_->rootLevel(); }
 bool     ClingoAssignment::hasLit(Lit_t lit) const { return decodeVar(lit) < size(); }
-bool     ClingoAssignment::isTotal()         const { return unassigned() == 0u; }
-uint32_t ClingoAssignment::trailSize()       const { return static_cast(solver_->trail().size() + trailOffset); }
+bool     ClingoAssignment::isTotal() const { return unassigned() == 0u; }
+uint32_t ClingoAssignment::trailSize() const { return solver_->numAssignedVars() + trail_offset; }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClingoPropagator::Control
 /////////////////////////////////////////////////////////////////////////////////////////
 class ClingoPropagator::Control : public Potassco::AbstractSolver {
 public:
-	Control(ClingoPropagator& ctx, Solver& s, uint32 st = 0u) : ctx_(&ctx), assignment_(s), state_(st | state_ctrl) {}
-	const Potassco::AbstractAssignment& assignment() const { return assignment_; }
-
-	// Potassco::AbstractSolver
-	virtual Potassco::Id_t id() const { return solver().id(); }
-	virtual bool addClause(const Potassco::LitSpan& clause, Potassco::Clause_t prop);
-	virtual bool propagate();
-	virtual Lit  addVariable();
-	virtual bool hasWatch(Lit lit) const;
-	virtual void addWatch(Lit lit);
-	virtual void removeWatch(Lit lit);
+    Control(ClingoPropagator& ctx, const Solver& s, State st = {})
+        : ctx_(&ctx)
+        , assignment_(s)
+        , state_{st | state_ctrl} {}
+    [[nodiscard]] const Potassco::AbstractAssignment& assignment() const override { return assignment_; }
+
+    // Potassco::AbstractSolver
+    [[nodiscard]] Potassco::Id_t id() const override { return solver().id(); }
+    bool                         addClause(Potassco::LitSpan clause, Potassco::ClauseType prop) override;
+    bool                         propagate() override;
+    Lit_t                        addVariable() override;
+    [[nodiscard]] bool           hasWatch(Lit_t lit) const override;
+    void                         addWatch(Lit_t lit) override;
+    void                         removeWatch(Lit_t lit) override;
+
 private:
-	typedef ClingoPropagator::State State;
-	ClingoPropagatorLock* lock()   const { return (state_ & state_init) == 0 ? ctx_->call_->lock() : 0; }
-	Solver&               solver() const { return const_cast(assignment_.solver()); }
-	ClingoPropagator* ctx_;
-	ClingoAssignment  assignment_;
-	uint32            state_;
+    using State = ClingoPropagator::State;
+    [[nodiscard]] ClingoPropagatorLock* lock() const {
+        return not Potassco::test(state_, state_init) ? ctx_->call_->lock() : nullptr;
+    }
+    [[nodiscard]] Solver& solver() const { return const_cast(assignment_.solver()); }
+    ClingoPropagator*     ctx_;
+    ClingoAssignment      assignment_;
+    State                 state_;
 };
-bool ClingoPropagator::Control::addClause(const Potassco::LitSpan& clause, Potassco::Clause_t prop) {
-	POTASSCO_REQUIRE(!assignment_.hasConflict(), "Invalid addClause() on conflicting assignment");
-	ScopedUnlock pp(lock(), ctx_);
-	pp->toClause(solver(), clause, prop);
-	return pp->addClause(solver(), state_);
+bool ClingoPropagator::Control::addClause(Potassco::LitSpan clause, Potassco::ClauseType prop) {
+    POTASSCO_CHECK_PRE(not assignment_.hasConflict(), "Invalid addClause() on conflicting assignment");
+    ScopedUnlock pp(lock(), ctx_);
+    pp->toClause(solver(), clause, prop);
+    return pp->addClause(solver(), state_);
 }
 bool ClingoPropagator::Control::propagate() {
-	ScopedUnlock unlocked(lock(), ctx_);
-	if (solver().hasConflict())    { return false; }
-	if (solver().queueSize() == 0) { return true;  }
-
-	ClingoPropagator::size_t epoch = ctx_->epoch_;
-	ctx_->registerUndoCheck(solver());
-	ctx_->propL_ = solver().decisionLevel();
-	const bool result = (state_ & state_prop) != 0u && solver().propagateUntil(unlocked.obj_) && epoch == ctx_->epoch_;
-	ctx_->propL_ = UINT32_MAX;
-	return result;
+    ScopedUnlock unlocked(lock(), ctx_);
+    if (solver().hasConflict()) {
+        return false;
+    }
+    if (solver().queueSize() == 0) {
+        return true;
+    }
+
+    auto epoch = ctx_->epoch_;
+    ctx_->registerUndoCheck(solver());
+    ctx_->propL_ = solver().decisionLevel();
+    const bool result =
+        Potassco::test(state_, state_prop) && solver().propagateUntil(unlocked.obj) && epoch == ctx_->epoch_;
+    ctx_->propL_ = UINT32_MAX;
+    return result;
 }
 Potassco::Lit_t ClingoPropagator::Control::addVariable() {
-	POTASSCO_REQUIRE(!assignment_.hasConflict(), "Invalid addVariable() on conflicting assignment");
-	ScopedUnlock unlocked(lock(), ctx_);
-	return encodeLit(posLit(solver().pushAuxVar()));
+    POTASSCO_CHECK_PRE(not assignment_.hasConflict(), "Invalid addVariable() on conflicting assignment");
+    ScopedUnlock unlocked(lock(), ctx_);
+    return encodeLit(posLit(solver().pushAuxVar()));
 }
 bool ClingoPropagator::Control::hasWatch(Lit_t lit) const {
-	ScopedUnlock unlocked(lock(), ctx_);
-	return assignment_.hasLit(lit) && solver().hasWatch(decodeLit(lit), ctx_);
+    ScopedUnlock unlocked(lock(), ctx_);
+    return assignment_.hasLit(lit) && solver().hasWatch(decodeLit(lit), ctx_);
 }
 void ClingoPropagator::Control::addWatch(Lit_t lit) {
-	ScopedUnlock unlocked(lock(), ctx_);
-	POTASSCO_REQUIRE(assignment_.hasLit(lit), "Invalid literal");
-	Literal p = decodeLit(lit);
-	Solver& s = solver();
-	if (!s.hasWatch(p, ctx_)) {
-		POTASSCO_REQUIRE(!s.sharedContext()->validVar(p.var()) || !s.sharedContext()->eliminated(p.var()), "Watched literal not frozen");
-		s.addWatch(p, ctx_);
-		if ((state_ & state_init) != 0u && s.isTrue(p)) {
-			// are we too late?
-			bool inQ = std::find(s.trail().begin() + s.assignment().front, s.trail().end(), p) != s.trail().end();
-			if (!inQ && !ctx_->inTrail(p)) {
-				uint32 ignore = 0;
-				ctx_->propagate(s, p, ignore);
-			}
-		}
-	}
+    ScopedUnlock unlocked(lock(), ctx_);
+    POTASSCO_CHECK_PRE(assignment_.hasLit(lit), "Invalid literal");
+    Literal p = decodeLit(lit);
+    Solver& s = solver();
+    if (not s.hasWatch(p, ctx_)) {
+        POTASSCO_CHECK_PRE(not s.sharedContext()->validVar(p.var()) || not s.sharedContext()->eliminated(p.var()),
+                           "Watched literal not frozen");
+        s.addWatch(p, ctx_);
+        if (Potassco::test(state_, state_init) && s.isTrue(p)) {
+            // are we too late?
+            if (not contains(s.trailView(s.assignment().front), p) && not ctx_->inTrail(p)) {
+                uint32_t ignore = 0;
+                ctx_->propagate(s, p, ignore);
+            }
+        }
+    }
 }
 void ClingoPropagator::Control::removeWatch(Lit_t lit) {
-	ScopedUnlock unlocked(lock(), ctx_);
-	if (assignment_.hasLit(lit)) {
-		solver().removeWatch(decodeLit(lit), ctx_);
-	}
+    ScopedUnlock unlocked(lock(), ctx_);
+    if (assignment_.hasLit(lit)) {
+        solver().removeWatch(decodeLit(lit), ctx_);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClingoPropagator
 /////////////////////////////////////////////////////////////////////////////////////////
-static const uint32 CHECK_BIT = 31;
+static constexpr uint32_t check_bit = 31;
 // flags for clauses from propagator
-static const uint32 ccFlags_s[2] = {
-	/* 0: learnt */ ClauseCreator::clause_not_sat | Clasp::ClauseCreator::clause_int_lbd,
-	/* 1: static */ ClauseCreator::clause_no_add  | ClauseCreator::clause_explicit
-};
-ClingoPropagator::ClingoPropagator(Propagator* p)
-	: call_(p)
-	, prop_(0), epoch_(0), level_(0), propL_(UINT32_MAX), front_(-1), init_(0) {
+static constexpr ClauseCreator::CreateFlag cc_flags[2] = {
+    /* 0: learnt */ ClauseCreator::clause_not_sat | ClauseCreator::clause_int_lbd,
+    /* 1: static */ ClauseCreator::clause_no_add | ClauseCreator::clause_explicit};
+static constexpr bool isVolatile(Potassco::ClauseType clause) {
+    return Potassco::test(clause, Potassco::ClauseType::transient);
+}
+static constexpr bool isStatic(Potassco::ClauseType clause) {
+    return Potassco::test(clause, Potassco::ClauseType::locked);
 }
-uint32 ClingoPropagator::priority() const { return static_cast(priority_class_general); }
+ClingoPropagator::ClingoPropagator(Propagator* p) : call_(p) {}
+uint32_t ClingoPropagator::priority() const { return static_cast(priority_class_general); }
 
 void ClingoPropagator::destroy(Solver* s, bool detach) {
-	if (s && detach) {
-		for (Var v = 1; v <= s->numVars(); ++v) {
-			s->removeWatch(posLit(v), this);
-			s->removeWatch(negLit(v), this);
-		}
-	}
-	destroyDB(db_, s, detach);
-	PostPropagator::destroy(s, detach);
+    if (s && detach) {
+        for (auto v : s->vars()) {
+            s->removeWatch(posLit(v), this);
+            s->removeWatch(negLit(v), this);
+        }
+    }
+    destroyDB(db_, s, detach);
+    PostPropagator::destroy(s, detach);
 }
 
 bool ClingoPropagator::init(Solver& s) {
-	POTASSCO_REQUIRE(s.decisionLevel() == 0 && prop_ <= trail_.size(), "Invalid init");
-	Control ctrl(*this, s, state_init);
-	s.acquireProblemVars();
-	if (s.isMaster() && !s.sharedContext()->frozen())
-		call_->prepare(const_cast(*s.sharedContext()));
-	init_  = call_->init(init_, ctrl);
-	front_ = (call_->checkMode() & ClingoPropagatorCheck_t::Fixpoint) ? -1 : INT32_MAX;
-	return true;
-}
-
-bool ClingoPropagator::inTrail(Literal p) const {
-	return std::find(trail_.begin(), trail_.end(), encodeLit(p)) != trail_.end();
-}
-
-void ClingoPropagator::registerUndo(Solver& s, uint32 data) {
-	uint32 dl = s.decisionLevel();
-	if (dl != level_) {
-		POTASSCO_REQUIRE(dl > level_, "Stack property violated");
-		// first time we see this level
-		s.addUndoWatch(level_ = dl, this);
-		undo_.push_back(data);
-	}
-	else if (!undo_.empty() && data < undo_.back()) {
-		POTASSCO_ASSERT(test_bit(undo_.back(), CHECK_BIT));
-		// first time a watched literal is processed on this level
-		undo_.back() = data;
-	}
+    POTASSCO_CHECK_PRE(s.decisionLevel() == 0 && prop_ <= size32(trail_), "Invalid init");
+    Control ctrl(*this, s, state_init);
+    s.acquireProblemVars();
+    if (s.isMaster() && not s.sharedContext()->frozen()) {
+        call_->prepare(const_cast(*s.sharedContext()));
+    }
+    init_  = call_->init(init_, ctrl);
+    front_ = Potassco::test(call_->checkMode(), ClingoPropagatorCheckType::fixpoint) ? -1 : INT32_MAX;
+    return true;
+}
+
+bool ClingoPropagator::inTrail(Literal p) const { return contains(trail_, encodeLit(p)); }
+
+void ClingoPropagator::registerUndo(Solver& s, uint32_t data) {
+    if (uint32_t dl = s.decisionLevel(); dl != level_) {
+        POTASSCO_CHECK_PRE(dl > level_, "Stack property violated");
+        // first time we see this level
+        s.addUndoWatch(level_ = dl, this);
+        undo_.push_back(data);
+    }
+    else if (not undo_.empty() && data < undo_.back()) {
+        POTASSCO_ASSERT(Potassco::test_bit(undo_.back(), check_bit));
+        // first time a watched literal is processed on this level
+        undo_.back() = data;
+    }
 }
 
 void ClingoPropagator::registerUndoCheck(Solver& s) {
-	if (uint32 dl = s.decisionLevel()) {
-		registerUndo(s, set_bit(s.decision(dl).var(), CHECK_BIT));
-	}
+    if (uint32_t dl = s.decisionLevel()) {
+        registerUndo(s, Potassco::set_bit(s.decision(dl).var(), check_bit));
+    }
 }
 
-Constraint::PropResult ClingoPropagator::propagate(Solver& s, Literal p, uint32&) {
-	registerUndo(s, static_cast(trail_.size()));
-	trail_.push_back(encodeLit(p));
-	return PropResult(true, true);
+Constraint::PropResult ClingoPropagator::propagate(Solver& s, Literal p, uint32_t&) {
+    registerUndo(s, size32(trail_));
+    trail_.push_back(encodeLit(p));
+    return PropResult(true, true);
 }
 
 void ClingoPropagator::undoLevel(Solver& s) {
-	POTASSCO_REQUIRE(s.decisionLevel() == level_, "Invalid undo");
-	uint32 beg = undo_.back();
-	undo_.pop_back();
-
-	if (test_bit(beg, CHECK_BIT) && call_->undoMode() == ClingoPropagatorUndo_t::Always) {
-		assert(beg >= prop_);
-		Potassco::LitSpan change = Potassco::toSpan();
-		ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->undo(Control(*this, s), change);
-	}
-
-	if (prop_ > beg) {
-		Potassco::LitSpan change = Potassco::toSpan(&trail_[0] + beg, prop_ - beg);
-		ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->undo(Control(*this, s), change);
-		prop_ = beg;
-	}
-	else if (level_ == propL_) {
-		propL_ = UINT32_MAX;
-		++epoch_;
-	}
-
-	if (front_ != INT32_MAX)
-		front_ = -1;
-
-	if (!test_bit(beg, CHECK_BIT))
-		trail_.resize(beg);
-
-	if (!undo_.empty()) {
-		uint32 prev = undo_.back();
-		if (test_bit(prev, CHECK_BIT)) {
-			prev = clear_bit(prev, CHECK_BIT);
-		}
-		else {
-			POTASSCO_ASSERT(prev < trail_.size());
-			prev = decodeLit(trail_[prev]).var();
-		}
-		level_ = s.level(prev);
-	}
-	else {
-		level_ = 0;
-	}
-}
-
-bool ClingoPropagator::propagateFixpoint(Clasp::Solver& s, Clasp::PostPropagator*) {
-	POTASSCO_REQUIRE(prop_ <= trail_.size(), "Invalid propagate");
-	if (!s.sharedContext()->frozen())
-		return true;
-
-	for (Control ctrl(*this, s, state_prop); prop_ != trail_.size() || front_ < (int32)s.numAssignedVars();) {
-		if (prop_ != trail_.size()) {
-			// create copy because trail might change during call to user propagation
-			temp_.assign(trail_.begin() + prop_, trail_.end());
-			POTASSCO_REQUIRE(s.level(decodeLit(temp_[0]).var()) == s.decisionLevel(), "Propagate must be called on each level");
-			prop_ = static_cast(trail_.size());
-			ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->propagate(ctrl, Potassco::toSpan(temp_));
-		}
-		else {
-			registerUndoCheck(s);
-			front_ = (int32)s.numAssignedVars();
-			ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->check(ctrl);
-		}
-		if (!addClause(s, state_prop) || (s.queueSize() && !s.propagateUntil(this))) {
-			return false;
-		}
-	}
-	return true;
-}
-
-void ClingoPropagator::toClause(Solver& s, const Potassco::LitSpan& clause, Potassco::Clause_t prop) {
-	POTASSCO_REQUIRE(todo_.empty(), "Assignment not propagated");
-	Literal max;
-	LitVec& mem = todo_.mem;
-	for (const Potassco::Lit_t* it = Potassco::begin(clause); it != Potassco::end(clause); ++it) {
-		Literal p = decodeLit(*it);
-		if (max < p) { max = p; }
-		mem.push_back(p);
-	}
-	if (aux_ < max) { aux_ = max; }
-	if ((Potassco::Clause_t::isVolatile(prop) || s.auxVar(max.var())) && !isSentinel(s.sharedContext()->stepLiteral())) {
-		mem.push_back(~s.sharedContext()->stepLiteral());
-		POTASSCO_REQUIRE(s.value(mem.back().var()) != value_free || s.decisionLevel() == 0, "Step literal must be assigned on level 1");
-	}
-	todo_.clause = ClauseCreator::prepare(s, mem, Clasp::ClauseCreator::clause_force_simplify, Constraint_t::Other);
-	todo_.flags  = ccFlags_s[int(Potassco::Clause_t::isStatic(prop))];
-	if (mem.empty()) {
-		mem.push_back(lit_false());
-	}
-}
-bool ClingoPropagator::addClause(Solver& s, uint32 st) {
-	if (s.hasConflict()) {
-		POTASSCO_REQUIRE(todo_.empty(), "Assignment not propagated");
-		return false;
-	}
-	if (todo_.empty())   { return true; }
-	const ClauseRep& clause = todo_.clause;
-	Literal w0 = clause.size > 0 ? clause.lits[0] : lit_false();
-	Literal w1 = clause.size > 1 ? clause.lits[1] : lit_false();
-	uint32  cs = ClauseCreator::status(s, clause);
-	bool local = (todo_.flags & ClauseCreator::clause_no_add) != 0;
-	if (cs & (ClauseCreator::status_unit|ClauseCreator::status_unsat)) {
-		uint32 dl = (cs & ClauseCreator::status_unsat) && !local ? s.level(w0.var()) : s.level(w1.var());
-		if (dl < s.decisionLevel() && s.isUndoLevel()) {
-			if ((st & state_ctrl) != 0u) { return false; }
-			if ((st & state_prop) != 0u) { ClingoPropagator::reset(); cancelPropagation(); }
-			s.undoUntil(dl);
-		}
-	}
-	if (!s.isFalse(w0) || local || s.force(w0, this)) {
-		ClauseCreator::Result res = ClauseCreator::create(s, clause, todo_.flags);
-		if (res.local && local) { db_.push_back(res.local); }
-	}
-	todo_.clear();
-	return !s.hasConflict();
+    POTASSCO_CHECK_PRE(s.decisionLevel() == level_, "Invalid undo");
+    uint32_t beg = undo_.back();
+    undo_.pop_back();
+
+    if (Potassco::test_bit(beg, check_bit) && call_->undoMode() == ClingoPropagatorUndoType::always) {
+        assert(beg >= prop_);
+        Potassco::LitSpan change;
+        ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->undo(Control(*this, s), change);
+    }
+
+    if (prop_ > beg) {
+        Potassco::LitSpan change{trail_.data() + beg, prop_ - beg};
+        ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->undo(Control(*this, s), change);
+        prop_ = beg;
+    }
+    else if (level_ == propL_) {
+        propL_ = UINT32_MAX;
+        ++epoch_;
+    }
+
+    if (front_ != INT32_MAX) {
+        front_ = -1;
+    }
+
+    if (not Potassco::test_bit(beg, check_bit)) {
+        trail_.resize(beg);
+    }
+
+    if (not undo_.empty()) {
+        uint32_t prev = undo_.back();
+        if (Potassco::test_bit(prev, check_bit)) {
+            prev = Potassco::clear_bit(prev, check_bit);
+        }
+        else {
+            POTASSCO_ASSERT(prev < size32(trail_));
+            prev = decodeLit(trail_[prev]).var();
+        }
+        level_ = s.level(prev);
+    }
+    else {
+        level_ = 0;
+    }
+}
+
+bool ClingoPropagator::propagateFixpoint(Solver& s, PostPropagator*) {
+    POTASSCO_CHECK_PRE(prop_ <= size32(trail_), "Invalid propagate");
+    if (not s.sharedContext()->frozen()) {
+        return true;
+    }
+    for (Control ctrl(*this, s, state_prop); prop_ != size32(trail_) || std::cmp_less(front_, s.numAssignedVars());) {
+        if (prop_ != size32(trail_)) {
+            // create copy because trail might change during call to user propagation
+            temp_.assign(trail_.begin() + static_cast(prop_), trail_.end());
+            POTASSCO_CHECK_PRE(s.level(decodeLit(temp_[0]).var()) == s.decisionLevel(),
+                               "Propagate must be called on each level");
+            prop_ = size32(trail_);
+            ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->propagate(ctrl, temp_);
+        }
+        else {
+            registerUndoCheck(s);
+            front_ = static_cast(s.numAssignedVars());
+            ScopedLock(call_->lock(), call_->propagator(), Inc(epoch_))->check(ctrl);
+        }
+        if (not addClause(s, state_prop) || (s.queueSize() && not s.propagateUntil(this))) {
+            return false;
+        }
+    }
+    return true;
+}
+
+void ClingoPropagator::toClause(Solver& s, const Potassco::LitSpan& clause, Potassco::ClauseType prop) {
+    POTASSCO_CHECK_PRE(todo_.empty(), "Assignment not propagated");
+    Literal max;
+    LitVec& mem = todo_.mem;
+    for (auto lit : clause) {
+        Literal p = decodeLit(lit);
+        if (max < p) {
+            max = p;
+        }
+        mem.push_back(p);
+    }
+    if (aux_ < max) {
+        aux_ = max;
+    }
+    if ((isVolatile(prop) || s.auxVar(max.var())) && not isSentinel(s.sharedContext()->stepLiteral())) {
+        mem.push_back(~s.sharedContext()->stepLiteral());
+        POTASSCO_CHECK_PRE(s.value(mem.back().var()) != value_free || s.decisionLevel() == 0,
+                           "Step literal must be assigned on level 1");
+    }
+    todo_.clause = ClauseCreator::prepare(s, mem, Clasp::ClauseCreator::clause_force_simplify, ConstraintType::other);
+    todo_.flags  = cc_flags[static_cast(isStatic(prop))];
+    if (mem.empty()) {
+        mem.push_back(lit_false);
+    }
+}
+bool ClingoPropagator::addClause(Solver& s, State st) {
+    if (s.hasConflict()) {
+        POTASSCO_CHECK_PRE(todo_.empty(), "Assignment not propagated");
+        return false;
+    }
+    if (todo_.empty()) {
+        return true;
+    }
+    const ClauseRep& clause = todo_.clause;
+    Literal          w0     = clause.size > 0 ? clause.lits[0] : lit_false;
+    Literal          w1     = clause.size > 1 ? clause.lits[1] : lit_false;
+    auto             flags  = ClauseCreator::CreateFlag{todo_.flags};
+    bool             local  = Potassco::test(flags, ClauseCreator::clause_no_add);
+    if (auto cs = ClauseCreator::status(s, clause); unitOrUnsat(cs)) {
+        auto dl = Potassco::test(cs, ClauseCreator::status_unsat) && not local ? s.level(w0.var()) : s.level(w1.var());
+        if (dl < s.decisionLevel() && s.isUndoLevel()) {
+            if (Potassco::test(st, state_ctrl)) {
+                return false;
+            }
+            if (Potassco::test(st, state_prop)) {
+                ClingoPropagator::reset();
+                cancelPropagation();
+            }
+            s.undoUntil(dl);
+        }
+    }
+    if (not s.isFalse(w0) || local || s.force(w0, this)) {
+        if (auto res = ClauseCreator::create(s, clause, flags); res.local && local) {
+            db_.push_back(res.local);
+        }
+    }
+    todo_.clear();
+    return not s.hasConflict();
 }
 
 void ClingoPropagator::reason(Solver&, Literal p, LitVec& r) {
-	if (!todo_.empty() && todo_.mem[0] == p) {
-		for (LitVec::const_iterator it = todo_.mem.begin() + 1, end = todo_.mem.end(); it != end; ++it) {
-			r.push_back(~*it);
-		}
-	}
+    if (not todo_.empty() && todo_.mem[0] == p) {
+        std::ranges::transform(todo_.mem.begin() + 1, todo_.mem.end(), std::back_inserter(r), &Literal::operator~);
+    }
 }
 
 bool ClingoPropagator::simplify(Solver& s, bool) {
-	if (!s.validVar(aux_.var())) {
-		ClauseDB::size_type i, j, end = db_.size();
-		LitVec cc;
-		Var last = s.numVars();
-		aux_ = lit_true();
-		for (i = j = 0; i != end; ++i) {
-			db_[j++] = db_[i];
-			ClauseHead* c = db_[i]->clause();
-			if (c && c->aux()) {
-				cc.clear();
-				c->toLits(cc);
-				Literal x = *std::max_element(cc.begin(), cc.end());
-				if (x.var() > last) {
-					c->destroy(&s, true);
-					--j;
-				}
-				else if (aux_ < x) { aux_ = x; }
-			}
-		}
-		db_.erase(db_.begin()+j, db_.end());
-	}
-	simplifyDB(s, db_, false);
-	return false;
+    if (not s.validVar(aux_.var())) {
+        LitVec cc;
+        aux_ = lit_true;
+        erase_if(db_, [&](Constraint* con) {
+            if (ClauseHead* clause = con->clause(); clause && clause->aux()) {
+                cc.clear();
+                clause->toLits(cc);
+                if (Literal x = *std::ranges::max_element(cc); not s.validVar(x.var())) {
+                    clause->destroy(&s, true);
+                    return true;
+                }
+                else if (aux_ < x) {
+                    aux_ = x;
+                }
+            }
+            return false;
+        });
+    }
+    simplifyDB(s, db_, false);
+    return false;
 }
 
 bool ClingoPropagator::isModel(Solver& s) {
-	POTASSCO_REQUIRE(prop_ == trail_.size(), "Assignment not propagated");
-	if (call_->checkMode() & ClingoPropagatorCheck_t::Total) {
-		front_ = -1;
-		s.propagateFrom(this);
-		front_ = (call_->checkMode() & ClingoPropagatorCheck_t::Fixpoint) ? front_ : INT32_MAX;
-		return !s.hasConflict() && s.numFreeVars() == 0;
-	}
-	return true;
+    POTASSCO_CHECK_PRE(prop_ == size32(trail_), "Assignment not propagated");
+    if (Potassco::test(call_->checkMode(), ClingoPropagatorCheckType::total)) {
+        front_ = -1;
+        s.propagateFrom(this);
+        front_ = Potassco::test(call_->checkMode(), ClingoPropagatorCheckType::fixpoint) ? front_ : INT32_MAX;
+        return not s.hasConflict() && s.numFreeVars() == 0;
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClingoPropagatorInit
 /////////////////////////////////////////////////////////////////////////////////////////
-ClingoPropagatorInit::Change::Change(Lit_t p, Action a)
-	: lit(p), sId(-1), action(static_cast(a)) {}
-ClingoPropagatorInit::Change::Change(Lit_t p, Action a, uint32 sId)
-	: lit(p), sId(static_cast(sId)), action(static_cast(a)) {}
-bool ClingoPropagatorInit::Change::operator<(const Change& rhs) const {
-	int cmp = std::abs(lit) - std::abs(rhs.lit);
-	return cmp != 0 ? cmp < 0 : lit < rhs.lit;
-}
-uint64 ClingoPropagatorInit::Change::solverMask() const {
-	return static_cast(sId) > 63 ? UINT64_MAX : bit_mask(static_cast(sId));
+struct ClingoPropagatorInit::Change::Less {
+    bool operator()(const Change& lhs, const Change& rhs) const {
+        auto x = std::abs(lhs.lit) <=> std::abs(rhs.lit);
+        return x != std::strong_ordering::equal ? x == std::strong_ordering::less : lhs.lit < rhs.lit;
+    }
+};
+ClingoPropagatorInit::Change::Change(Lit_t p, Action a) : lit(p), sId(-1), action(static_cast(a)) {}
+ClingoPropagatorInit::Change::Change(Lit_t p, Action a, uint32_t s)
+    : lit(p)
+    , sId(static_cast(s))
+    , action(static_cast(a)) {}
+uint64_t ClingoPropagatorInit::Change::solverMask() const {
+    return static_cast(sId) > 63 ? UINT64_MAX : Potassco::nth_bit(static_cast(sId));
 }
 void ClingoPropagatorInit::Change::apply(Potassco::AbstractSolver& s) const {
-	switch (action) {
-		case AddWatch:    s.addWatch(lit);    break;
-		case RemoveWatch: s.removeWatch(lit); break;
-		default: break;
-	}
-}
-
-struct ClingoPropagatorInit::History : POTASSCO_EXT_NS::unordered_map{
-	void add(const Change& change) {
-		if (change.action == AddWatch) {
-			std::pair x = insert(value_type(change.lit, 0));
-			x.first->second |= change.solverMask();
-		}
-		else if (change.action == RemoveWatch) {
-			iterator it = find(change.lit);
-			if (it != end() && (it->second &= ~change.solverMask()) == 0) {
-				erase(it);
-			}
-		}
-	}
+    switch (action) {
+        case add_watch   : s.addWatch(lit); break;
+        case remove_watch: s.removeWatch(lit); break;
+        default          : break;
+    }
+}
+
+struct ClingoPropagatorInit::History : std::unordered_map {
+    void add(const Change& change) {
+        if (auto sm = change.solverMask(); change.action == add_watch) {
+            auto [it, _] = emplace(change.lit, 0);
+            Potassco::store_set_mask(it->second, sm);
+        }
+        else if (change.action == remove_watch) {
+            if (auto it = find(change.lit); it != end() && Potassco::store_clear_mask(it->second, sm) == 0) {
+                erase(it);
+            }
+        }
+    }
 };
 
 ClingoPropagatorInit::ClingoPropagatorInit(Potassco::AbstractPropagator& cb, ClingoPropagatorLock* lock, CheckType m)
-	: prop_(&cb), lock_(lock), history_(0), step_(1), check_(m), undo_(ClingoPropagatorUndo_t::Default) {
-}
-ClingoPropagatorInit::~ClingoPropagatorInit()         { delete history_; }
-bool ClingoPropagatorInit::applyConfig(Solver& s)     { return s.addPost(new ClingoPropagator(this)); }
-void ClingoPropagatorInit::prepare(SharedContext& ctx){
-	std::stable_sort(changes_.begin(), changes_.end());
-	for (ChangeList::const_iterator it = changes_.begin(), end = changes_.end(); it != end;) {
-		Lit_t lit = it->lit;
-		uint64_t addWatch = 0;
-		bool     freeze   = false;
-		do {
-			switch (it->action) {
-				case AddWatch:    addWatch |=  it->solverMask(); break;
-				case RemoveWatch: addWatch &= ~it->solverMask(); break;
-				case FreezeLit:   freeze = true; break;
-				default: break;
-			}
-		} while (++it != end && it->lit == lit);
-		if (freeze || addWatch)
-			ctx.setFrozen(decodeVar(lit), true);
-	}
-}
-void ClingoPropagatorInit::unfreeze(SharedContext&)   {
-	if (history_) {
-		for (ChangeList::const_iterator it = changes_.begin(), end = changes_.end(); it != end; ++it) {
-			history_->add(*it);
-		}
-	}
-	ChangeList().swap(changes_);
-	++step_;
-}
-
-void ClingoPropagatorInit::freezeLit(Literal lit) {
-	changes_.push_back(Change(encodeLit(lit), FreezeLit, 64));
-}
+    : prop_(&cb)
+    , lock_(lock)
+    , history_(nullptr)
+    , step_(1)
+    , check_(m)
+    , undo_(ClingoPropagatorUndoType::def) {}
+ClingoPropagatorInit::~ClingoPropagatorInit() { delete history_; }
+bool ClingoPropagatorInit::applyConfig(Solver& s) { return s.addPost(new ClingoPropagator(this)); }
+void ClingoPropagatorInit::prepare(SharedContext& ctx) {
+    std::ranges::stable_sort(changes_, Change::Less{});
+    for (auto it = changes_.begin(), end = changes_.end(); it != end;) {
+        Lit_t    lit      = it->lit;
+        uint64_t addWatch = 0;
+        bool     freeze   = false;
+        do {
+            switch (it->action) {
+                case add_watch   : Potassco::store_set_mask(addWatch, it->solverMask()); break;
+                case remove_watch: Potassco::store_clear_mask(addWatch, it->solverMask()); break;
+                case freeze_lit  : freeze = true; break;
+                default          : break;
+            }
+        } while (++it != end && it->lit == lit);
+        if (freeze || addWatch) {
+            ctx.setFrozen(decodeVar(lit), true);
+        }
+    }
+}
+void ClingoPropagatorInit::unfreeze(SharedContext&) {
+    if (history_) {
+        for (const auto& change : changes_) { history_->add(change); }
+    }
+    discardVec(changes_);
+    ++step_;
+}
+
+void ClingoPropagatorInit::freezeLit(Literal lit) { changes_.push_back(Change(encodeLit(lit), freeze_lit, 64)); }
 
 Potassco::Lit_t ClingoPropagatorInit::addWatch(Literal lit) {
-	changes_.push_back(Change(encodeLit(lit), AddWatch));
-	return changes_.back().lit;
-}
-
-Potassco::Lit_t ClingoPropagatorInit::addWatch(uint32 sId, Literal lit) {
-	POTASSCO_REQUIRE(sId < 64, "Invalid solver id");
-	changes_.push_back(Change(encodeLit(lit), AddWatch, sId));
-	return changes_.back().lit;
-}
-
-void ClingoPropagatorInit::removeWatch(Literal lit) {
-	changes_.push_back(Change(encodeLit(lit), RemoveWatch));
-}
-
-void ClingoPropagatorInit::removeWatch(uint32 sId, Literal lit) {
-	POTASSCO_REQUIRE(sId < 64, "Invalid solver id");
-	changes_.push_back(Change(encodeLit(lit), RemoveWatch, sId));
-}
-
-uint32 ClingoPropagatorInit::init(uint32 lastStep, Potassco::AbstractSolver& s) {
-	POTASSCO_REQUIRE(s.id() < 64, "Invalid solver id");
-	int16 sId = static_cast(s.id());
-	if (history_ && (step_ - lastStep) > 1) {
-		for (History::const_iterator it = history_->begin(), end = history_->end(); it != end; ++it) {
-			if (test_bit(it->second, sId)) { Change(it->first, AddWatch, sId).apply(s); }
-		}
-	}
-	ChangeList changesForSolver;
-	bool isSorted = true;
-	for (ChangeList::const_iterator it = changes_.begin(), end = changes_.end(); it != end; ++it) {
-		if (it->sId < 0 || it->sId == sId) {
-			isSorted = isSorted && (changesForSolver.empty() || !(*it < changesForSolver.back()));
-			changesForSolver.push_back(*it);
-		}
-	}
-	if (!isSorted)
-		std::stable_sort(changesForSolver.begin(), changesForSolver.end());
-	for (ChangeList::const_iterator it = changesForSolver.begin(), end = changesForSolver.end(); it != end; ++it) {
-		Lit_t lit = it->lit;
-		// skip all but the last change for a given literal
-		while ((it + 1) != end && (it + 1)->lit == lit) { ++it; }
-		it->apply(s);
-	}
-	return step_;
-}
-
-void ClingoPropagatorInit::enableClingoPropagatorCheck(CheckType checkMode) {
-	check_ = checkMode;
-}
-
-void ClingoPropagatorInit::enableClingoPropagatorUndo(UndoType undoMode) {
-	undo_ = undoMode;
-}
+    changes_.push_back(Change(encodeLit(lit), add_watch));
+    return changes_.back().lit;
+}
+
+Potassco::Lit_t ClingoPropagatorInit::addWatch(uint32_t sId, Literal lit) {
+    POTASSCO_CHECK_PRE(sId < 64, "Invalid solver id");
+    changes_.push_back(Change(encodeLit(lit), add_watch, sId));
+    return changes_.back().lit;
+}
+
+void ClingoPropagatorInit::removeWatch(Literal lit) { changes_.push_back(Change(encodeLit(lit), remove_watch)); }
+
+void ClingoPropagatorInit::removeWatch(uint32_t sId, Literal lit) {
+    POTASSCO_CHECK_PRE(sId < 64, "Invalid solver id");
+    changes_.push_back(Change(encodeLit(lit), remove_watch, sId));
+}
+
+uint32_t ClingoPropagatorInit::init(uint32_t lastStep, Potassco::AbstractSolver& s) {
+    POTASSCO_CHECK_PRE(s.id() < 64, "Invalid solver id");
+    auto sId = s.id();
+    if (history_ && (step_ - lastStep) > 1) {
+        for (const auto& [lit, mask] : *history_) {
+            if (Potassco::test_bit(mask, sId)) {
+                Change(lit, add_watch, sId).apply(s);
+            }
+        }
+    }
+    ChangeList changesForSolver;
+    bool       isSorted = true;
+    auto       lessLit  = Change::Less{};
+    for (const auto& change : changes_) {
+        if (change.sId < 0 || std::cmp_equal(change.sId, sId)) {
+            isSorted = isSorted && (changesForSolver.empty() || not lessLit(change, changesForSolver.back()));
+            changesForSolver.push_back(change);
+        }
+    }
+    if (not isSorted) {
+        std::ranges::stable_sort(changesForSolver, lessLit);
+    }
+    for (auto it = changesForSolver.begin(), end = changesForSolver.end(); it != end; ++it) {
+        auto lit = it->lit;
+        // skip all but the last change for a given literal
+        while ((it + 1) != end && (it + 1)->lit == lit) { ++it; }
+        it->apply(s);
+    }
+    return step_;
+}
+
+void ClingoPropagatorInit::enableClingoPropagatorCheck(CheckType checkMode) { check_ = checkMode; }
+
+void ClingoPropagatorInit::enableClingoPropagatorUndo(UndoType undoMode) { undo_ = undoMode; }
 
 void ClingoPropagatorInit::enableHistory(bool b) {
-	if (!b)             { delete history_; history_ = 0; }
-	else if (!history_) { history_ = new History(); }
+    if (not b) {
+        delete history_;
+        history_ = nullptr;
+    }
+    else if (not history_) {
+        history_ = new History();
+    }
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClingoHeuristic
 /////////////////////////////////////////////////////////////////////////////////////////
-ClingoHeuristic::ClingoHeuristic(Potassco::AbstractHeuristic& clingoHeuristic, DecisionHeuristic* claspHeuristic, ClingoPropagatorLock* lock)
-	: clingo_(&clingoHeuristic)
-	, clasp_(claspHeuristic)
-	, lock_(lock) {}
+ClingoHeuristic::ClingoHeuristic(Potassco::AbstractHeuristic& clingoHeuristic, DecisionHeuristic* claspHeuristic,
+                                 ClingoPropagatorLock* lock)
+    : clingo_(&clingoHeuristic)
+    , clasp_(claspHeuristic)
+    , lock_(lock) {}
 
 Literal ClingoHeuristic::doSelect(Solver& s) {
-	typedef Scoped ScopedLock;
-	Literal fallback = clasp_->doSelect(s);
-	if (s.hasConflict())
-		return fallback;
-
-	ClingoAssignment assignment(s);
-	Potassco::Lit_t lit = ScopedLock(lock_, clingo_)->decide(s.id(), assignment, encodeLit(fallback));
-	Literal decision = lit != 0 ? decodeLit(lit) : fallback;
-	return s.validVar(decision.var()) && !s.isFalse(decision) ? decision : fallback;
+    using LockT = Scoped;
+
+    auto decision = clasp_->doSelect(s);
+    if (not s.hasConflict()) {
+        ClingoAssignment assignment(s);
+        auto             lit = LockT(lock_, clingo_)->decide(s.id(), assignment, encodeLit(decision));
+        if (Literal user; lit != 0 && s.validVar((user = decodeLit(lit)).var()) && not s.isFalse(user)) {
+            decision = user;
+        }
+    }
+    return decision;
+}
+
+void ClingoHeuristic::startInit(const Solver& s) { clasp_->startInit(s); }
+void ClingoHeuristic::endInit(Solver& s) { clasp_->endInit(s); }
+void ClingoHeuristic::detach(Solver& s) {
+    if (clasp_) {
+        clasp_->detach(s);
+    }
 }
-
-void ClingoHeuristic::startInit(const Solver& s)    { clasp_->startInit(s); }
-void ClingoHeuristic::endInit(Solver& s)            { clasp_->endInit(s); }
-void ClingoHeuristic::detach(Solver& s)             { if (clasp_.is_owner()) { clasp_->detach(s); } }
 void ClingoHeuristic::setConfig(const HeuParams& p) { clasp_->setConfig(p); }
-void ClingoHeuristic::newConstraint(const Solver& s, const Literal* p, LitVec::size_type sz, ConstraintType t) {
-	clasp_->newConstraint(s, p, sz, t);
+void ClingoHeuristic::newConstraint(const Solver& s, LitView lits, ConstraintType t) {
+    clasp_->newConstraint(s, lits, t);
 }
 
-void ClingoHeuristic::updateVar(const Solver& s, Var v, uint32 n)                   { clasp_->updateVar(s, v, n); }
-void ClingoHeuristic::simplify(const Solver& s, LitVec::size_type st)               { clasp_->simplify(s, st); }
-void ClingoHeuristic::undoUntil(const Solver& s, LitVec::size_type st)              { clasp_->undoUntil(s, st); }
-void ClingoHeuristic::updateReason(const Solver& s, const LitVec& x, Literal r)     { clasp_->updateReason(s, x, r); }
-bool ClingoHeuristic::bump(const Solver& s, const WeightLitVec& w, double d)        { return clasp_->bump(s, w, d); }
-Literal ClingoHeuristic::selectRange(Solver& s, const Literal* f, const Literal* l) { return clasp_->selectRange(s, f, l); }
-
+void    ClingoHeuristic::updateVar(const Solver& s, Var_t v, uint32_t n) { clasp_->updateVar(s, v, n); }
+void    ClingoHeuristic::simplify(const Solver& s, LitView sp) { clasp_->simplify(s, sp); }
+void    ClingoHeuristic::undo(const Solver& s, LitView undo) { clasp_->undo(s, undo); }
+void    ClingoHeuristic::updateReason(const Solver& s, LitView x, Literal r) { clasp_->updateReason(s, x, r); }
+bool    ClingoHeuristic::bump(const Solver& s, WeightLitView w, double d) { return clasp_->bump(s, w, d); }
+Literal ClingoHeuristic::selectRange(Solver& s, LitView range) { return clasp_->selectRange(s, range); }
 
-DecisionHeuristic* ClingoHeuristic::fallback() const { return clasp_.get();  }
+DecisionHeuristic* ClingoHeuristic::fallback() const { return clasp_.get(); }
 
-ClingoHeuristic::Factory::Factory(Potassco::AbstractHeuristic& clingoHeuristic, ClingoPropagatorLock* lock)
-	: clingo_(&clingoHeuristic)
-	, lock_(lock) {}
-
-DecisionHeuristic* ClingoHeuristic::Factory::create(Heuristic_t::Type t, const HeuParams& p) {
-	return new ClingoHeuristic(*clingo_, Heuristic_t::create(t, p), lock_);
+BasicSatConfig::HeuristicCreator ClingoHeuristic::creator(Potassco::AbstractHeuristic& clingoHeuristic,
+                                                          ClingoPropagatorLock*        lock) {
+    return [heu = &clingoHeuristic, lock](HeuristicType t, const HeuParams& p) {
+        return new ClingoHeuristic(*heu, createHeuristic(t, p), lock);
+    };
 }
 
-}
+} // namespace Clasp
diff --git a/src/constraint.cpp b/src/constraint.cpp
index c50d1c7..7212249 100644
--- a/src/constraint.cpp
+++ b/src/constraint.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -23,79 +23,109 @@
 //
 
 #include 
+
+#include 
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Constraint
 /////////////////////////////////////////////////////////////////////////////////////////
-Constraint::Constraint()                  {}
-Constraint::~Constraint()                 {}
-void Constraint::destroy(Solver*, bool)   { delete this; }
-ConstraintType Constraint::type() const   { return Constraint_t::Static; }
-bool Constraint::simplify(Solver&, bool)  { return false; }
-void Constraint::undoLevel(Solver&)       {}
-uint32 Constraint::estimateComplexity(const Solver&) const { return 1;  }
-bool Constraint::valid(Solver&)           { return true; }
-ClauseHead* Constraint::clause()          { return 0; }
-void Constraint::decreaseActivity()       {}
-void Constraint::resetActivity()          {}
-ConstraintScore Constraint::activity() const { return makeScore(); }
-bool Constraint::locked(const Solver&) const { return true; }
-uint32 Constraint::isOpen(const Solver&, const TypeSet&, LitVec&) { return 0; }
+Constraint::Constraint()  = default;
+Constraint::~Constraint() = default;
+void        Constraint::destroy(Solver*, bool) { delete this; }
+auto        Constraint::type() const -> ConstraintType { return ConstraintType::static_; }
+bool        Constraint::simplify(Solver&, bool) { return false; }
+void        Constraint::undoLevel(Solver&) {}
+uint32_t    Constraint::estimateComplexity(const Solver&) const { return 1; }
+bool        Constraint::valid(Solver&) { return true; }
+ClauseHead* Constraint::clause() { return nullptr; }
+void        Constraint::decreaseActivity() {}
+void        Constraint::resetActivity() {}
+auto        Constraint::activity() const -> ConstraintScore { return ConstraintScore{}; }
+bool        Constraint::locked(const Solver&) const { return true; }
+uint32_t    Constraint::isOpen(const Solver&, const TypeSet&, LitVec&) { return 0; }
 /////////////////////////////////////////////////////////////////////////////////////////
 // PostPropagator
 /////////////////////////////////////////////////////////////////////////////////////////
-PostPropagator::PostPropagator() : next(0)           {}
-PostPropagator::~PostPropagator()                    {}
-bool PostPropagator::init(Solver&)                   { return true; }
-void PostPropagator::reset()                         {}
-bool PostPropagator::isModel(Solver& s)              { return valid(s); }
+PostPropagator::PostPropagator() : next(nullptr) {}
+PostPropagator::~PostPropagator() = default;
+bool PostPropagator::init(Solver&) { return true; }
+void PostPropagator::reset() {}
+bool PostPropagator::isModel(Solver& s) { return valid(s); }
 void PostPropagator::reason(Solver&, Literal, LitVec&) {}
-Constraint::PropResult PostPropagator::propagate(Solver&, Literal, uint32&) {
-	return PropResult(true, false);
-}
-void PostPropagator::cancelPropagation() {
-	for (PostPropagator* n = this->next; n; n = n->next) { n->reset(); }
+auto PostPropagator::propagate(Solver&, Literal, uint32_t&) -> PropResult { return PropResult(true, false); }
+void PostPropagator::cancelPropagation() { // NOLINT(readability-make-member-function-const)
+    for (PostPropagator* n = this->next; n; n = n->next) { n->reset(); }
 }
-MessageHandler::MessageHandler() {}
+MessageHandler::MessageHandler() = default;
 /////////////////////////////////////////////////////////////////////////////////////////
 // PropagatorList
 /////////////////////////////////////////////////////////////////////////////////////////
-PropagatorList::PropagatorList() : head_(0) {}
+PropagatorList::PropagatorList() : head_(nullptr) {}
 PropagatorList::~PropagatorList() { clear(); }
 void PropagatorList::clear() {
-	for (PostPropagator* r = head_; r;) {
-		PostPropagator* t = r;
-		r = r->next;
-		t->destroy();
-	}
-	head_ = 0;
+    for (auto* r = head_; r;) {
+        auto* t = r;
+        r       = r->next;
+        t->destroy();
+    }
+    head_ = nullptr;
 }
 void PropagatorList::add(PostPropagator* p) {
-	POTASSCO_REQUIRE(p && p->next == 0, "Invalid post propagator");
-	uint32 prio = p->priority();
-	for (PostPropagator** r = head(), *x;; r = &x->next) {
-		if ((x = *r) == 0 || prio < (uint32)x->priority()) {
-			p->next = x;
-			*r      = p;
-			break;
-		}
-	}
+    POTASSCO_CHECK_PRE(p && p->next == nullptr, "Invalid post propagator");
+    uint32_t prio = p->priority();
+    for (PostPropagator **r = head(), *x;; r = &x->next) {
+        if (x = *r; x == nullptr || prio < x->priority()) {
+            p->next = x;
+            *r      = p;
+            break;
+        }
+    }
 }
 void PropagatorList::remove(PostPropagator* p) {
-	POTASSCO_REQUIRE(p, "Invalid post propagator");
-	for (PostPropagator** r = head(), *x; *r; r = &x->next) {
-		if ((x = *r) == p) {
-			*r      = x->next;
-			p->next = 0;
-			break;
-		}
-	}
+    POTASSCO_CHECK_PRE(p, "Invalid post propagator");
+    for (PostPropagator **r = head(), *x; *r; r = &x->next) {
+        if (x = *r; x == p) {
+            *r      = x->next;
+            p->next = nullptr;
+            break;
+        }
+    }
+}
+PostPropagator* PropagatorList::find(uint32_t prio) const {
+    for (auto* x = head_; x; x = x->next) {
+        if (auto xp = x->priority(); xp >= prio) {
+            return xp == prio ? x : nullptr;
+        }
+    }
+    return nullptr;
+}
+
+bool PropagatorList::init(Solver& s) {
+    for (PostPropagator **r = head(), *pp = nullptr; (pp = *r) != nullptr; r = pp == *r ? &pp->next : r) {
+        if (not pp->init(s)) {
+            return false;
+        }
+    }
+    return true;
 }
-PostPropagator* PropagatorList::find(uint32 prio) const {
-	for (PostPropagator* x = head_; x; x = x->next) {
-		uint32 xp = x->priority();
-		if (xp >= prio) { return xp == prio ? x : 0; }
-	}
-	return 0;
+
+bool PropagatorList::simplify(Solver& s, bool reinit) {
+    for (PostPropagator **r = head(), *pp = nullptr; (pp = *r) != nullptr; r = pp == *r ? &pp->next : r) {
+        if (pp->simplify(s, reinit)) {
+            *r = pp->next;
+            pp->destroy(&s, false);
+        }
+    }
+    return false;
 }
+
+bool PropagatorList::isModel(Solver& s) {
+    for (PostPropagator **r = head(), *pp = nullptr; (pp = *r) != nullptr; r = pp == *r ? &pp->next : r) {
+        if (not pp->isModel(s)) {
+            return false;
+        }
+    }
+    return true;
 }
+
+} // namespace Clasp
diff --git a/src/dependency_graph.cpp b/src/dependency_graph.cpp
index e50470c..0066755 100644
--- a/src/dependency_graph.cpp
+++ b/src/dependency_graph.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2010-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,57 +22,55 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
 #include 
+#include 
 #include 
+
+#include 
+#include 
+
+#include 
+
 namespace Clasp {
-SolveTestEvent::SolveTestEvent(const Solver& s, uint32 a_hcc, bool part)
-	: SolveEvent(s, Event::verbosity_max)
-	, result(-1), hcc(a_hcc), partial(part) {
-	confDelta   = s.stats.conflicts;
-	choiceDelta = s.stats.choices;
-	time        = 0.0;
-}
-uint64 SolveTestEvent::choices() const {
-	return solver->stats.choices - choiceDelta;
-}
-uint64 SolveTestEvent::conflicts() const {
-	return solver->stats.conflicts - confDelta;
+SolveTestEvent::SolveTestEvent(const Solver& s, uint32_t a_hcc, bool part)
+    : SolveEvent(this, s, verbosity_max)
+    , result(-1)
+    , hcc(a_hcc)
+    , partial(part) {
+    confDelta   = s.stats.conflicts;
+    choiceDelta = s.stats.choices;
+    time        = 0.0;
 }
+uint64_t SolveTestEvent::choices() const { return solver->stats.choices - choiceDelta; }
+uint64_t SolveTestEvent::conflicts() const { return solver->stats.conflicts - confDelta; }
 namespace Asp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgDepGraph
 /////////////////////////////////////////////////////////////////////////////////////////
 PrgDepGraph::PrgDepGraph(NonHcfMapType m) {
-	// add sentinel atom needed for disjunctions
-	createAtom(lit_false(), PrgNode::noScc);
-	VarVec adj;	adj.push_back(idMax);
-	initAtom(sentinel_atom, 0, adj, 0);
-	seenComponents_ = 0;
-	mapType_        = (uint32)m;
-	stats_ = 0;
+    // add sentinel atom needed for disjunctions
+    createAtom(lit_false, PrgNode::no_scc);
+    uint32_t empty[1] = {id_max};
+    initAtom(sentinel_atom, 0, empty, 0);
+    seenComponents_ = 0;
+    mapType_        = static_cast(m);
+    stats_          = nullptr;
 }
 
 PrgDepGraph::~PrgDepGraph() {
-	for (AtomVec::size_type i = 0; i != atoms_.size(); ++i) {
-		delete [] atoms_[i].adj_;
-	}
-	for (AtomVec::size_type i = 0; i != bodies_.size(); ++i) {
-		delete [] bodies_[i].adj_;
-	}
-	delete stats_;
-	while (!components_.empty()) {
-		delete components_.back();
-		components_.pop_back();
-	}
+    for (auto& atom : atoms_) { delete[] atom.adj; }
+    for (auto& body : bodies_) { delete[] body.adj; }
+    while (not components_.empty()) {
+        delete components_.back();
+        components_.pop_back();
+    }
 }
-bool PrgDepGraph::relevantPrgAtom(const Solver& s, PrgAtom* a) const {
-	return !a->ignoreScc() && a->inUpper() && a->scc() != PrgNode::noScc && !s.isFalse(a->literal());
-}
-bool PrgDepGraph::relevantPrgBody(const Solver& s, PrgBody* b) const {
-	return !s.isFalse(b->literal());
+static bool relevantPrgAtom(const Solver& s, const PrgAtom* a) {
+    return not a->ignoreScc() && a->inUpper() && a->scc() != PrgNode::no_scc && not s.isFalse(a->literal());
 }
+static bool relevantPrgBody(const Solver& s, const PrgBody* b) { return not s.isFalse(b->literal()); }
 
 // Creates a positive-body-atom-dependency graph (PBADG)
 // The PBADG contains a node for each atom A of a non-trivial SCC and
@@ -80,335 +78,380 @@ bool PrgDepGraph::relevantPrgBody(const Solver& s, PrgBody* b) const {
 // B in body(A).
 // Pre : b->seen() = 1 for all new and relevant bodies b
 // Post: b->seen() = 0 for all bodies that were added to the PBADG
-void PrgDepGraph::addSccs(LogicProgram& prg, const AtomList& sccAtoms, const NonHcfSet& nonHcfs) {
-	// Pass 1: Create graph atom nodes and estimate number of bodies
-	atoms_.reserve(atoms_.size() + sccAtoms.size());
-	AtomList::size_type numBodies = 0;
-	SharedContext& ctx = *prg.ctx();
-	for (AtomList::size_type i = 0, end = sccAtoms.size(); i != end; ++i) {
-		PrgAtom* a = sccAtoms[i];
-		if (relevantPrgAtom(*ctx.master(), a)) {
-			// add graph atom node and store link between program node and graph node for later lookup
-			a->resetId(createAtom(a->literal(), a->scc()), true);
-			// atom is defined by more than just a bunch of clauses
-			ctx.setFrozen(a->var(), true);
-			numBodies += a->supports();
-		}
-	}
-	// Pass 2: Init atom nodes and create body nodes
-	VarVec adj, ext;
-	bodies_.reserve(bodies_.size() + numBodies/2);
-	PrgBody* prgBody; PrgDisj* prgDis;
-	for (AtomList::size_type i = 0, end = sccAtoms.size(); i != end; ++i) {
-		PrgAtom*   a  = sccAtoms[i];
-		if (relevantPrgAtom(*ctx.master(), a)) {
-			uint32 prop = 0;
-			for (PrgAtom::sup_iterator it = a->supps_begin(), endIt = a->supps_end(); it != endIt; ++it) {
-				assert(it->isBody() || it->isDisj());
-				NodeId bId= PrgNode::noNode;
-				if (it->isBody() && !it->isGamma()) {
-					prgBody = prg.getBody(it->node());
-					bId     = relevantPrgBody(*ctx.master(), prgBody) ? addBody(prg, prgBody) : PrgNode::noNode;
-				}
-				else if (it->isDisj()) {
-					prgDis  = prg.getDisj(it->node());
-					bId     = addDisj(prg, prgDis);
-					prop   |= AtomNode::property_in_disj;
-				}
-				if (bId != PrgNode::noNode) {
-					if (!bodies_[bId].seen()) {
-						bodies_[bId].seen(true);
-						adj.push_back(bId);
-					}
-					if (it->isChoice()) {
-						// mark atom as in choice
-						prop |= AtomNode::property_in_choice;
-					}
-				}
-			}
-			uint32 nPred= (uint32)adj.size();
-			for (PrgAtom::dep_iterator it = a->deps_begin(), endIt = a->deps_end(); it != endIt; ++it) {
-				if (!it->sign()) {
-					prgBody = prg.getBody(it->var());
-					if (relevantPrgBody(*ctx.master(), prgBody) && prgBody->scc(prg) == a->scc()) {
-						NodeId bodyId = addBody(prg, prgBody);
-						if (!bodies_[bodyId].extended()) {
-							adj.push_back(bodyId);
-						}
-						else {
-							ext.push_back(bodyId);
-							ext.push_back(bodies_[bodyId].get_pred_idx(a->id()));
-							assert(bodies_[bodyId].get_pred(ext.back()) == a->id());
-							prop |= AtomNode::property_in_ext;
-						}
-					}
-				}
-			}
-			if (!ext.empty()) {
-				adj.push_back(idMax);
-				adj.insert(adj.end(), ext.begin(), ext.end());
-			}
-			adj.push_back(idMax);
-			initAtom(a->id(), prop, adj, nPred);
-			adj.clear(); ext.clear();
-		}
-	}
-	if (nonHcfs.size() != 0 && stats_ == 0 && nonHcfs.config && nonHcfs.config->context().stats) {
-		stats_ = enableNonHcfStats(nonHcfs.config->context().stats, prg.isIncremental());
-	}
-	// "update" existing non-hcf components
-	for (NonHcfIter it = nonHcfBegin(), end = nonHcfEnd(); it != end; ++it) {
-		(*it)->update(ctx);
-	}
-	// add new non-hcf components
-	uint32 hcc = seenComponents_;
-	for (NonHcfSet::const_iterator it = nonHcfs.begin() + seenComponents_, end = nonHcfs.end(); it != end; ++it, ++hcc) {
-		addNonHcf(hcc, ctx, nonHcfs.config, *it);
-	}
-	seenComponents_ = nonHcfs.size();
+void PrgDepGraph::addSccs(const LogicProgram& prg, const AtomList& sccAtoms, const NonHcfSet& nonHcfs) {
+    // Pass 1: Create graph atom nodes and estimate number of bodies
+    atoms_.reserve(atoms_.size() + sccAtoms.size());
+    auto           numBodies = 0u;
+    SharedContext& ctx       = *prg.ctx();
+    for (auto* atom : sccAtoms) {
+        if (relevantPrgAtom(*ctx.master(), atom)) {
+            // add graph atom node and store link between program node and graph node for later lookup
+            atom->resetId(createAtom(atom->literal(), atom->scc()), true);
+            // atom is defined by more than just a bunch of clauses
+            ctx.setFrozen(atom->var(), true);
+            numBodies += atom->numSupports();
+        }
+    }
+    // Pass 2: Init atom nodes and create body nodes
+    VarVec adj, ext;
+    bodies_.reserve(bodies_.size() + numBodies / 2);
+    PrgBody* prgBody;
+    for (auto* atom : sccAtoms) {
+        if (relevantPrgAtom(*ctx.master(), atom)) {
+            uint32_t prop = 0;
+            for (auto s : atom->supports()) {
+                assert(s.isBody() || s.isDisj());
+                NodeId bId = PrgNode::no_node;
+                if (s.isBody() && not s.isGamma()) {
+                    prgBody = prg.getBody(s.node());
+                    bId     = relevantPrgBody(*ctx.master(), prgBody) ? addBody(prg, prgBody) : PrgNode::no_node;
+                }
+                else if (s.isDisj()) {
+                    PrgDisj* prgDis  = prg.getDisj(s.node());
+                    bId              = addDisj(prg, prgDis);
+                    prop            |= AtomNode::property_in_disj;
+                }
+                if (bId != PrgNode::no_node) {
+                    if (not bodies_[bId].seen()) {
+                        bodies_[bId].seen(true);
+                        adj.push_back(bId);
+                    }
+                    if (s.isChoice()) {
+                        // mark atom as in choice
+                        prop |= AtomNode::property_in_choice;
+                    }
+                }
+            }
+            auto nPred = size32(adj);
+            for (auto dep : atom->deps()) {
+                if (not dep.sign()) {
+                    prgBody = prg.getBody(dep.var());
+                    if (relevantPrgBody(*ctx.master(), prgBody) && prgBody->scc(prg) == atom->scc()) {
+                        if (NodeId bodyId = addBody(prg, prgBody); not bodies_[bodyId].extended()) {
+                            adj.push_back(bodyId);
+                        }
+                        else {
+                            ext.push_back(bodyId);
+                            ext.push_back(bodies_[bodyId].findPred(atom->id()));
+                            prop |= AtomNode::property_in_ext;
+                        }
+                    }
+                }
+            }
+            if (not ext.empty()) {
+                adj.push_back(id_max);
+                adj.insert(adj.end(), ext.begin(), ext.end());
+            }
+            adj.push_back(id_max);
+            initAtom(atom->id(), prop, adj, nPred);
+            adj.clear();
+            ext.clear();
+        }
+    }
+    if (not nonHcfs.empty() && stats_ == nullptr && nonHcfs.config && nonHcfs.config->context().stats) {
+        enableNonHcfStats(nonHcfs.config->context().stats, prg.isIncremental());
+    }
+    // "update" existing non-hcf components
+    for (auto* hcc : components_) { hcc->update(ctx); }
+    // add new non-hcf components
+    for (uint32_t hcc = seenComponents_; auto x : nonHcfs.view(seenComponents_)) {
+        addNonHcf(hcc++, ctx, nonHcfs.config, x);
+    }
+    seenComponents_ = size32(nonHcfs);
 }
 
-uint32 PrgDepGraph::createAtom(Literal lit, uint32 aScc) {
-	NodeId id    = (uint32)atoms_.size();
-	atoms_.push_back(AtomNode());
-	AtomNode& ua = atoms_.back();
-	ua.lit       = lit;
-	ua.scc       = aScc;
-	return id;
+uint32_t PrgDepGraph::createAtom(Literal lit, uint32_t aScc) {
+    auto id = size32(atoms_);
+    atoms_.push_back(AtomNode());
+    AtomNode& ua = atoms_.back();
+    ua.lit       = lit;
+    ua.scc       = aScc;
+    return id;
 }
 
-void PrgDepGraph::initAtom(uint32 id, uint32 prop, const VarVec& adj, uint32 numPreds) {
-	AtomNode& ua = atoms_[id];
-	ua.setProperties(prop);
-	ua.adj_      = new NodeId[adj.size()];
-	ua.sep_      = ua.adj_ + numPreds;
-	NodeId* sExt = ua.adj_;
-	NodeId* sSame= sExt + numPreds;
-	uint32  aScc = ua.scc;
-	for (VarVec::const_iterator it = adj.begin(), end = adj.begin()+numPreds; it != end; ++it) {
-		BodyNode& bn = bodies_[*it];
-		if (bn.scc != aScc) { *sExt++ = *it; }
-		else                { *--sSame= *it; }
-		bn.seen(false);
-	}
-	std::reverse(sSame, ua.adj_ + numPreds);
-	std::copy(adj.begin()+numPreds, adj.end(), ua.sep_);
+void PrgDepGraph::initAtom(uint32_t id, uint32_t prop, VarView adj, uint32_t numPreds) {
+    AtomNode& ua = atoms_[id];
+    ua.setProperties(prop);
+    ua.adj         = new NodeId[adj.size()];
+    ua.sep         = ua.adj + numPreds;
+    NodeId*  sExt  = ua.adj;
+    NodeId*  sSame = sExt + numPreds;
+    uint32_t aScc  = ua.scc;
+    for (auto bId : adj.subspan(0, numPreds)) {
+        BodyNode& bn = bodies_[bId];
+        if (bn.scc != aScc) {
+            *sExt++ = bId;
+        }
+        else {
+            *--sSame = bId;
+        }
+        bn.seen(false);
+    }
+    std::reverse(sSame, ua.adj + numPreds);
+    std::ranges::copy(adj.subspan(numPreds), ua.sep);
 }
 
-uint32 PrgDepGraph::createBody(PrgBody* b, uint32 bScc) {
-	NodeId id = (uint32)bodies_.size();
-	bodies_.push_back(BodyNode(b, bScc));
-	return id;
+uint32_t PrgDepGraph::createBody(const PrgBody* b, uint32_t bScc) {
+    auto id = size32(bodies_);
+    bodies_.push_back(BodyNode(b, bScc));
+    return id;
 }
 
 // Creates and initializes a body node for the given body b.
-uint32 PrgDepGraph::addBody(const LogicProgram& prg, PrgBody* b) {
-	if (b->seen()) {     // first time we see this body -
-		VarVec preds, atHeads;
-		uint32 bScc  = b->scc(prg);
-		NodeId bId   = createBody(b, bScc);
-		addPreds(prg, b, bScc, preds);
-		addHeads(prg, b, atHeads);
-		initBody(bId, preds, atHeads);
-		b->resetId(bId, false);
-		prg.ctx()->setFrozen(b->var(), true);
-	}
-	return b->id();
+uint32_t PrgDepGraph::addBody(const LogicProgram& prg, PrgBody* b) {
+    if (b->seen()) { // first time we see this body -
+        VarVec   preds, atHeads;
+        uint32_t bScc = b->scc(prg);
+        NodeId   bId  = createBody(b, bScc);
+        addPreds(prg, b, bScc, preds);
+        addHeads(prg, b, atHeads);
+        initBody(bId, preds, atHeads);
+        b->resetId(bId, false);
+        prg.ctx()->setFrozen(b->var(), true);
+    }
+    return b->id();
 }
 
-// Adds all relevant predecessors of b to preds.
+// Adds all relevant predecessors of 'b' to preds.
 // The returned list looks like this:
-// [[B], a1, [w1], ..., aj, [wj], idMax, l1, [w1], ..., lk, [wk], idMax], where
-// B is the bound of b (only for card/weight rules),
-// ai is a positive predecessor from bScc,
-// wi is the weight of ai (only for weight rules), and
-// li is a literal of a subgoal from some other scc (only for cardinality/weight rules)
-void PrgDepGraph::addPreds(const LogicProgram& prg, PrgBody* b, uint32 bScc, VarVec& preds) const {
-	if (bScc == PrgNode::noScc) { preds.clear(); return; }
-	const bool weights = b->type() == Body_t::Sum;
-	for (uint32 i = 0; i != b->size() && !b->goal(i).sign(); ++i) {
-		PrgAtom* pred = prg.getAtom(b->goal(i).var());
-		if (relevantPrgAtom(*prg.ctx()->master(), pred) && pred->scc() == bScc) {
-			preds.push_back( pred->id() );
-			if (weights) { preds.push_back(b->weight(i)); }
-		}
-	}
-	if (b->type() != Body_t::Normal) {
-		preds.insert(preds.begin(), b->bound());
-		preds.push_back(idMax);
-		for (uint32 n = 0; n != b->size(); ++n) {
-			PrgAtom* pred = prg.getAtom(b->goal(n).var());
-			bool     ext  = b->goal(n).sign() || pred->scc() != bScc;
-			Literal lit   = b->goal(n).sign() ? ~pred->literal() : pred->literal();
-			if (ext && !prg.ctx()->master()->isFalse(lit)) {
-				preds.push_back(lit.rep());
-				if (weights) { preds.push_back(b->weight(n)); }
-			}
-		}
-	}
-	preds.push_back(idMax);
+// [[B], a1, [w1], ..., aj, [wj], id_max, l1, [w1], ..., lk, [wk], id_max], where
+// 'B' is the bound of b (only for card/weight rules),
+// 'ai' is a positive predecessor from bScc,
+// 'wi' is the weight of 'ai' (only for weight rules), and
+// 'li' is a literal of a subgoal from some other scc (only for cardinality/weight rules)
+void PrgDepGraph::addPreds(const LogicProgram& prg, const PrgBody* b, uint32_t bScc, VarVec& preds) {
+    if (bScc == PrgNode::no_scc) {
+        preds.clear();
+        return;
+    }
+    const bool weights = b->type() == BodyType::sum;
+    for (uint32_t n = 0; auto g : b->goals()) {
+        if (g.sign()) {
+            break;
+        }
+        auto* pred = prg.getAtom(g.var());
+        if (relevantPrgAtom(*prg.ctx()->master(), pred) && pred->scc() == bScc) {
+            preds.push_back(pred->id());
+            if (weights) {
+                preds.push_back(static_cast(b->weight(n)));
+            }
+        }
+        ++n;
+    }
+    if (b->type() != BodyType::normal) {
+        preds.insert(preds.begin(), static_cast(b->bound()));
+        preds.push_back(id_max);
+        for (uint32_t n = 0; auto g : b->goals()) {
+            PrgAtom* pred = prg.getAtom(g.var());
+            bool     ext  = g.sign() || pred->scc() != bScc;
+            Literal  lit  = g.sign() ? ~pred->literal() : pred->literal();
+            if (ext && not prg.ctx()->master()->isFalse(lit)) {
+                preds.push_back(lit.rep());
+                if (weights) {
+                    preds.push_back(static_cast(b->weight(n)));
+                }
+            }
+            ++n;
+        }
+    }
+    preds.push_back(id_max);
 }
 
 // Splits the heads of b into atoms and disjunctions.
 // Disjunctions are flattened to sentinel-enclosed atom-lists.
-uint32 PrgDepGraph::addHeads(const LogicProgram& prg, PrgBody* b, VarVec& heads) const {
-	for (PrgBody::head_iterator it = b->heads_begin(), end = b->heads_end(); it != end; ++it) {
-		if (it->isAtom() && !it->isGamma()) {
-			PrgAtom* a = prg.getAtom(it->node());
-			if (relevantPrgAtom(*prg.ctx()->master(), a)) {
-				heads.push_back(a->id());
-			}
-		}
-		else if (it->isDisj()) {
-			assert(prg.getDisj(it->node())->inUpper() && prg.getDisj(it->node())->supports() == 1);
-			PrgDisj* d = prg.getDisj(it->node());
-			// flatten disjunction and enclose in sentinels
-			heads.push_back(sentinel_atom);
-			getAtoms(prg, d, heads);
-			heads.push_back(sentinel_atom);
-		}
-	}
-	return sizeVec(heads);
+uint32_t PrgDepGraph::addHeads(const LogicProgram& prg, const PrgBody* b, VarVec& heads) {
+    for (auto e : b->heads()) {
+        if (e.isAtom() && not e.isGamma()) {
+            PrgAtom* a = prg.getAtom(e.node());
+            if (relevantPrgAtom(*prg.ctx()->master(), a)) {
+                heads.push_back(a->id());
+            }
+        }
+        else if (e.isDisj()) {
+            assert(prg.getDisj(e.node())->inUpper() && prg.getDisj(e.node())->numSupports() == 1);
+            PrgDisj* d = prg.getDisj(e.node());
+            // flatten disjunction and enclose in sentinels
+            heads.push_back(sentinel_atom);
+            getAtoms(prg, d, heads);
+            heads.push_back(sentinel_atom);
+        }
+    }
+    return size32(heads);
 }
 
 // Adds the atoms from the given disjunction to atoms and returns the disjunction's scc.
-uint32 PrgDepGraph::getAtoms(const LogicProgram& prg, PrgDisj* d, VarVec& atoms) const {
-	uint32 scc = PrgNode::noScc;
-	for (PrgDisj::atom_iterator it = d->begin(), end = d->end(); it != end; ++it) {
-		PrgAtom* a = prg.getAtom(*it);
-		if (relevantPrgAtom(*prg.ctx()->master(), a)) {
-			assert(scc == PrgNode::noScc || scc == a->scc());
-			atoms.push_back(a->id());
-			scc = a->scc();
-		}
-	}
-	return scc;
+uint32_t PrgDepGraph::getAtoms(const LogicProgram& prg, const PrgDisj* d, VarVec& atoms) {
+    uint32_t scc = PrgNode::no_scc;
+    for (auto id : d->atoms()) {
+        auto* a = prg.getAtom(id);
+        if (relevantPrgAtom(*prg.ctx()->master(), a)) {
+            assert(scc == PrgNode::no_scc || scc == a->scc());
+            atoms.push_back(a->id());
+            scc = a->scc();
+        }
+    }
+    return scc;
 }
 
 // Initializes preds and succs lists of the body node with the given id.
-void PrgDepGraph::initBody(uint32 id, const VarVec& preds, const VarVec& atHeads) {
-	BodyNode* bn = &bodies_[id];
-	uint32 nSuccs= sizeVec(atHeads);
-	bn->adj_     = new NodeId[nSuccs + preds.size()];
-	bn->sep_     = bn->adj_ + nSuccs;
-	NodeId* sSame= bn->adj_;
-	NodeId* sExt = sSame + nSuccs;
-	uint32  bScc = bn->scc;
-	uint32  hScc = PrgNode::noScc;
-	uint32  disj = 0;
-	for (VarVec::const_iterator it = atHeads.begin(), end = atHeads.end(); it != end;) {
-		if (*it) {
-			hScc = getAtom(*it).scc;
-			if (hScc == bScc) { *sSame++ = *it++; }
-			else              { *--sExt  = *it++; }
-		}
-		else {
-			hScc = getAtom(it[1]).scc; ++disj;
-			if (hScc == bScc) { *sSame++ = *it++; while ( (*sSame++ = *it++) ) { ; } }
-			else              { *--sExt  = *it++; while ( (*--sExt  = *it++) ) { ; } }
-		}
-	}
-	std::copy(preds.begin(), preds.end(), bn->sep_);
-	bn->sep_ += bn->extended();
-	if (disj) { bodies_[id].data |= BodyNode::flag_has_delta; }
+void PrgDepGraph::initBody(uint32_t id, VarView preds, VarView atHeads) {
+    BodyNode* bn     = &bodies_[id];
+    uint32_t  nSuccs = size32(atHeads);
+    bn->adj          = new NodeId[nSuccs + preds.size()];
+    bn->sep          = bn->adj + nSuccs;
+    NodeId*  sSame   = bn->adj;
+    NodeId*  sExt    = sSame + nSuccs;
+    uint32_t bScc    = bn->scc;
+    uint32_t disj    = 0;
+    for (auto it = atHeads.begin(), end = atHeads.end(); it != end;) {
+        if (*it) {
+            auto hScc = getAtom(*it).scc;
+            if (hScc == bScc) {
+                *sSame++ = *it++;
+            }
+            else {
+                *--sExt = *it++;
+            }
+        }
+        else {
+            auto hScc = getAtom(it[1]).scc;
+            ++disj;
+            if (hScc == bScc) {
+                *sSame++ = *it++;
+                while ((*sSame++ = *it++)) { ; }
+            }
+            else {
+                *--sExt = *it++;
+                while ((*--sExt = *it++)) { ; }
+            }
+        }
+    }
+    std::ranges::copy(preds, bn->sep);
+    bn->sep += bn->extended();
+    if (disj) {
+        bodies_[id].data |= BodyNode::flag_has_delta;
+    }
 }
 
-uint32 PrgDepGraph::addDisj(const LogicProgram& prg, PrgDisj* d) {
-	assert(d->inUpper() && d->supports() == 1);
-	if (d->seen()) { // first time we see this disjunction
-		PrgBody* prgBody = prg.getBody(d->supps_begin()->node());
-		uint32   bId     = PrgNode::noNode;
-		if (relevantPrgBody(*prg.ctx()->master(), prgBody)) {
-			bId = addBody(prg, prgBody);
-		}
-		d->resetId(bId, false);
-	}
-	return d->id();
+uint32_t PrgDepGraph::addDisj(const LogicProgram& prg, PrgDisj* d) {
+    assert(d->inUpper() && d->numSupports() == 1);
+    if (d->seen()) { // first time we see this disjunction
+        PrgBody* prgBody = prg.getBody(d->support().node());
+        uint32_t bId     = PrgNode::no_node;
+        if (relevantPrgBody(*prg.ctx()->master(), prgBody)) {
+            bId = addBody(prg, prgBody);
+        }
+        d->resetId(bId, false);
+    }
+    return d->id();
 }
 
-void PrgDepGraph::addNonHcf(uint32 id, SharedContext& ctx, Configuration* config, uint32 scc) {
-	VarVec sccAtoms, sccBodies;
-	// get all atoms from scc
-	for (uint32 i = 0; i != numAtoms(); ++i) {
-		if (getAtom(i).scc == scc) {
-			sccAtoms.push_back(i);
-			atoms_[i].set(AtomNode::property_in_non_hcf);
-		}
-	}
-	// get all bodies defining an atom in scc
-	const Solver& generator = *ctx.master(); (void)generator;
-	for (uint32 i = 0; i != sccAtoms.size(); ++i) {
-		const AtomNode& a = getAtom(sccAtoms[i]);
-		for (const NodeId* bodyIt = a.bodies_begin(), *bodyEnd = a.bodies_end(); bodyIt != bodyEnd; ++bodyIt) {
-			BodyNode& B = bodies_[*bodyIt];
-			if (!B.seen()) {
-				assert(generator.value(B.lit.var()) != value_free || !generator.seen(B.lit));
-				sccBodies.push_back(*bodyIt);
-				B.seen(true);
-			}
-		}
-	}
-	for (uint32 i = 0; i != sccBodies.size(); ++i) { bodies_[sccBodies[i]].seen(false); }
-	components_.push_back( new NonHcfComponent(id, *this, ctx, config, scc, sccAtoms, sccBodies) );
-	if (stats_) { stats_->addHcc(*components_.back()); }
+void PrgDepGraph::addNonHcf(uint32_t id, SharedContext& ctx, Configuration* config, uint32_t scc) {
+    VarVec sccAtoms, sccBodies;
+    // get all atoms from scc
+    for (auto i : irange(numAtoms())) {
+        if (getAtom(i).scc == scc) {
+            sccAtoms.push_back(i);
+            atoms_[i].set(AtomNode::property_in_non_hcf);
+        }
+    }
+    // get all bodies defining an atom in scc
+    const Solver& generator = *ctx.master();
+    for (auto atomId : sccAtoms) {
+        const AtomNode& a = getAtom(atomId);
+        for (auto bId : a.bodies()) {
+            BodyNode& body = bodies_[bId];
+            if (not body.seen()) {
+                POTASSCO_ASSERT(generator.value(body.lit.var()) != value_free || not generator.seen(body.lit));
+                sccBodies.push_back(bId);
+                body.seen(true);
+            }
+        }
+    }
+    for (auto bodyId : sccBodies) { bodies_[bodyId].seen(false); }
+    components_.push_back(new NonHcfComponent(id, *this, ctx, config, scc, sccAtoms, sccBodies));
+    if (stats_) {
+        stats_->addHcc(*components_.back());
+    }
 }
 void PrgDepGraph::simplify(const Solver& s) {
-	const bool shared        = s.sharedContext()->isShared();
-	ComponentVec::iterator j = components_.begin();
-	for (ComponentVec::iterator it = components_.begin(), end = components_.end(); it != end; ++it) {
-		bool ok = (*it)->simplify(s);
-		if (!shared) {
-			if (ok) { *j++ = *it; }
-			else    {
-				if (stats_) { stats_->removeHcc(**it); }
-				delete *it;
-			}
-		}
-	}
-	if (!shared) { components_.erase(j, components_.end()); }
+    const bool shared = s.sharedContext()->isShared();
+    auto       j      = components_.begin();
+    for (auto& component : components_) {
+        bool ok = component->simplify(s);
+        if (not shared) {
+            if (ok) {
+                *j++ = component;
+            }
+            else {
+                if (stats_) {
+                    stats_->removeHcc(*component);
+                }
+                delete component;
+            }
+        }
+    }
+    if (not shared) {
+        components_.erase(j, components_.end());
+    }
 }
-PrgDepGraph::NonHcfStats* PrgDepGraph::enableNonHcfStats(uint32 level, bool inc) {
-	if (!stats_) { stats_ = new NonHcfStats(*this, level, inc); }
-	return stats_;
+PrgDepGraph::NonHcfStats* PrgDepGraph::enableNonHcfStats(uint32_t level, bool inc) {
+    if (not stats_) {
+        stats_ = std::make_unique(*this, level, inc);
+    }
+    return stats_.get();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgDepGraph::NonHcfComponent::ComponentMap
 /////////////////////////////////////////////////////////////////////////////////////////
 class PrgDepGraph::NonHcfComponent::ComponentMap {
 public:
-	ComponentMap() { static_assert(sizeof(Mapping) == sizeof(uint64), "Invalid padding!"); }
-	struct Mapping {
-		explicit Mapping(NodeId id) : node(id), var(0), ext(0) { }
-		uint32 node;     // node id in dep-graph of generator program P
-		uint32 var : 30; // var in tester solver
-		uint32 ext :  2; // additional data
-		// Atom
-		bool  disj() const { return ext != 0u; }
-		bool hasTp() const { return ext == 2u; }
-		Literal up() const { return posLit(var); }
-		Literal hp() const { assert(disj()); return posLit(var + 1); }
-		Literal tp() const { assert(disj()); return posLit((var + 2)*uint32(hasTp())); }
-		// Body
-		Literal fb() const { return Literal(var, (ext & 1u) != 0u); }
-		bool    eq() const { return ext != 0u; }
-		bool operator<(const Mapping& other) const { return node < other.node; }
-	};
-	typedef  PrgDepGraph                 SccGraph;
-	typedef  PodVector::type    NodeMap;
-	typedef  NodeMap::iterator           MapIt;
-	typedef  NodeMap::const_iterator     MapIt_c;
-	typedef  std::pair MapRange;
-	void     addVars(Solver& generator, const SccGraph& dep, const VarVec& atoms, const VarVec& bodies, SharedContext& out);
-	void     addAtomConstraints(SharedContext& out);
-	void     addBodyConstraints(const Solver& generator, const SccGraph& dep, uint32 scc, SharedContext& out);
-	void     mapGeneratorAssignment(const Solver& generator, const SccGraph& dep, LitVec& out) const;
-	void     mapTesterModel(const Solver& tester, VarVec& out) const;
-	bool     simplify(const Solver& generator, const SccGraph& dep, Solver& tester);
-	MapRange atoms() const { return MapRange(mapping.begin(), mapping.begin() + numAtoms); }
-	MapRange bodies()const { return MapRange(mapping.begin() + numAtoms, mapping.end()); }
-	MapIt_c  findAtom(NodeId nodeId) const { return std::lower_bound(mapping.begin(), mapping.begin()+numAtoms, Mapping(nodeId)); }
-	NodeMap  mapping; // maps nodes of P to literals in C;
-	uint32   numAtoms;// number of atoms
+    ComponentMap() = default;
+    struct Mapping {
+        explicit Mapping(NodeId id) : node(id) {
+            static_assert(sizeof(Mapping) == sizeof(uint64_t), "Invalid padding!");
+        }
+        uint32_t node;         // node id in dep-graph of generator program P
+        uint32_t var : 30 {0}; // var in tester solver
+        uint32_t ext : 2 {0};  // additional data
+        // Atom
+        [[nodiscard]] bool    disj() const { return ext != 0u; }
+        [[nodiscard]] bool    hasTp() const { return ext == 2u; }
+        [[nodiscard]] Literal up() const { return posLit(var); }
+        [[nodiscard]] Literal hp() const {
+            assert(disj());
+            return posLit(var + 1);
+        }
+        [[nodiscard]] Literal tp() const {
+            assert(disj());
+            return posLit((var + 2) * static_cast(hasTp()));
+        }
+        // Body
+        [[nodiscard]] Literal fb() const { return {var, (ext & 1u) != 0u}; }
+        [[nodiscard]] bool    eq() const { return ext != 0u; }
+
+        bool operator==(const Mapping& other) const { return node == other.node; }
+        auto operator<=>(const Mapping& other) const { return node <=> other.node; }
+    };
+    using SccGraph = PrgDepGraph;
+    using NodeMap  = PodVector_t;
+    using MapIt    = NodeMap::iterator;
+    using MapIt_c  = NodeMap::const_iterator;
+    using MapSpan  = SpanView;
+
+    void addVars(Solver& generator, const SccGraph& dep, VarView atoms, VarView bodies, SharedContext& comp);
+    void addAtomConstraints(SharedContext& comp);
+    void addBodyConstraints(const Solver& generator, const SccGraph& dep, uint32_t scc, SharedContext& comp);
+    void mapGeneratorAssignment(const Solver& s, const SccGraph& dep, LitVec& assume) const;
+    void mapTesterModel(const Solver& s, VarVec& out) const;
+    bool simplify(const Solver& generator, const SccGraph& dep, Solver& tester);
+    [[nodiscard]] MapSpan atoms() const { return {mapping.begin(), mapping.begin() + numAtoms}; }
+    [[nodiscard]] MapSpan bodies() const { return {mapping.begin() + numAtoms, mapping.end()}; }
+    [[nodiscard]] MapIt_c findAtom(NodeId nodeId) const {
+        return std::lower_bound(mapping.begin(), mapping.begin() + numAtoms, Mapping(nodeId));
+    }
+    NodeMap  mapping;     // maps nodes of P to literals in C;
+    uint32_t numAtoms{0}; // number of atoms
 };
 // Adds necessary variables for all atoms and bodies to the component program.
 // Input-Vars: (set via assumptions)
@@ -418,650 +461,721 @@ class PrgDepGraph::NonHcfComponent::ComponentMap {
 //  hp: for each atom p in a proper disjunctive head, hp is true iff tp and ~up
 // Output: (unfounded sets)
 //  up: for each atom p, up is true iff a is unfounded w.r.t the assignment of P.
-void PrgDepGraph::NonHcfComponent::ComponentMap::addVars(Solver& generator, const SccGraph& dep, const VarVec& atoms, const VarVec& bodies, SharedContext& comp) {
-	assert(generator.decisionLevel() == 0);
-	mapping.reserve(atoms.size() + bodies.size());
-	const PrgDepGraph::NonHcfMapType mt = dep.nonHcfMapType();
-	for (VarVec::const_iterator it = atoms.begin(), end = atoms.end(); it != end; ++it) {
-		const AtomNode& at = dep.getAtom(*it);
-		Literal gen        = at.lit;
-		if (generator.isFalse(gen)) { continue; }
-		Mapping map(*it);
-		// up [ hp [tp] ]
-		map.var = comp.addVar(Var_t::Atom); // up
-		map.ext = (mt == PrgDepGraph::map_old || at.inDisjunctive());
-		comp.setFrozen(map.var, true);
-		if (map.ext) {
-			comp.addVar(Var_t::Atom); // hp
-			if (!generator.isTrue(gen)) { // tp
-				comp.setFrozen(comp.addVar(Var_t::Atom), true);
-				++map.ext;
-			}
-		}
-		mapping.push_back(map);
-	}
-	numAtoms = (uint32)mapping.size();
-	std::stable_sort(mapping.begin(), mapping.end());
-	// add necessary vars for bodies
-	for (VarVec::const_iterator it = bodies.begin(), end = bodies.end(); it != end; ++it) {
-		Literal gen = dep.getBody(*it).lit;
-		if (generator.isFalse(gen))  { continue; }
-		Mapping map(*it);
-		if (!generator.seen(gen) && !generator.isTrue(gen)) {
-			map.var = comp.addVar(Var_t::Atom);
-			comp.setFrozen(map.var, true);
-			generator.markSeen(gen);
-		}
-		else if (generator.isTrue(gen)) {
-			map.ext = 1u;
-		}
-		else {
-			map.ext = 2u;
-			for (MapRange r = this->bodies(); r.first != r.second;) {
-				--r.second;
-				if (dep.getBody(r.second->node).lit == gen) {
-					map.var = r.second->var;
-					break;
-				}
-			}
-		}
-		assert(map.var <= comp.numVars() && (map.var || map.ext == 1u));
-		mapping.push_back(map);
-	}
-	for (MapRange r = this->bodies(); r.first != r.second; ++r.first) {
-		if (!r.first->eq()) {
-			Var v = dep.getBody(r.first->node).lit.var();
-			generator.clearSeen(v);
-		}
-	}
+void PrgDepGraph::NonHcfComponent::ComponentMap::addVars(Solver& generator, const SccGraph& dep, VarView atoms,
+                                                         VarView bodies, SharedContext& comp) {
+    assert(generator.decisionLevel() == 0);
+    mapping.reserve(atoms.size() + bodies.size());
+    const auto mt = dep.nonHcfMapType();
+    for (auto atomId : atoms) {
+        const AtomNode& at  = dep.getAtom(atomId);
+        Literal         gen = at.lit;
+        if (generator.isFalse(gen)) {
+            continue;
+        }
+        Mapping map(atomId);
+        // up [ hp [tp] ]
+        map.var = comp.addVar(VarType::atom); // up
+        map.ext = (mt == PrgDepGraph::map_old || at.inDisjunctive());
+        comp.setFrozen(map.var, true);
+        if (map.ext) {
+            comp.addVar(VarType::atom);      // hp
+            if (not generator.isTrue(gen)) { // tp
+                comp.setFrozen(comp.addVar(VarType::atom), true);
+                ++map.ext;
+            }
+        }
+        mapping.push_back(map);
+    }
+    numAtoms = size32(mapping);
+    std::ranges::sort(mapping);
+    // add necessary vars for bodies
+    for (auto bodyId : bodies) {
+        Literal gen = dep.getBody(bodyId).lit;
+        if (generator.isFalse(gen)) {
+            continue;
+        }
+        Mapping map(bodyId);
+        if (not generator.seen(gen) && not generator.isTrue(gen)) {
+            map.var = comp.addVar(VarType::atom);
+            comp.setFrozen(map.var, true);
+            generator.markSeen(gen);
+        }
+        else if (generator.isTrue(gen)) {
+            map.ext = 1u;
+        }
+        else {
+            map.ext = 2u;
+            auto bs = this->bodies();
+            if (auto it = std::find_if(bs.rbegin(), bs.rend(),
+                                       [&](const Mapping& m) { return dep.getBody(m.node).lit == gen; });
+                it != bs.rend()) {
+                map.var = it->var;
+            }
+        }
+        assert(map.var <= comp.numVars() && (map.var || map.ext == 1u));
+        mapping.push_back(map);
+    }
+    for (const auto& m : this->bodies()) {
+        if (not m.eq()) {
+            auto v = dep.getBody(m.node).lit.var();
+            generator.clearSeen(v);
+        }
+    }
 }
 
 // Adds constraints stemming from the given atoms to the component program.
 // 1. [up(a0) v ... v up(an-1)], where
 //   - ai is an atom in P from the given atom set, and
 //   - up(ai) is the corresponding output-atom in the component program C.
-// 2. For each atom ai in atom set occurring in a proper disjunction, [hp(ai) <=> tp(ai), ~up(ai)], where
+// 2. For each atom 'ai' in atom set occurring in a proper disjunction, [hp(ai) <=> tp(ai), ~up(ai)], where
 //   tp(ai), hp(ai), up(ai) are the input, aux, and output atoms in C.
 void PrgDepGraph::NonHcfComponent::ComponentMap::addAtomConstraints(SharedContext& comp) {
-	ClauseCreator cc1(comp.master()), cc2(comp.master());
-	cc1.addDefaultFlags(ClauseCreator::clause_force_simplify);
-	cc1.start();
-	for (MapRange r = atoms(); r.first != r.second; ++r.first) {
-		const Mapping& m = *r.first;
-		cc1.add(m.up());
-		if (m.disj()) {
-			cc2.start().add(~m.tp()).add(m.up()).add(m.hp()).end(); // [~tp v up v hp]
-			cc2.start().add(~m.hp()).add(m.tp()).end();  // [~hp v tp]
-			cc2.start().add(~m.hp()).add(~m.up()).end(); // [~hp v ~up]
-		}
-	}
-	cc1.end();
+    ClauseCreator cc1(comp.master()), cc2(comp.master());
+    cc1.addDefaultFlags(ClauseCreator::clause_force_simplify);
+    cc1.start();
+    for (const auto& m : atoms()) {
+        cc1.add(m.up());
+        if (m.disj()) {
+            cc2.start().add(~m.tp()).add(m.up()).add(m.hp()).end(); // [~tp v up v hp]
+            cc2.start().add(~m.hp()).add(m.tp()).end();             // [~hp v tp]
+            cc2.start().add(~m.hp()).add(~m.up()).end();            // [~hp v ~up]
+        }
+    }
+    cc1.end();
 }
 
 // Adds constraints stemming from the given bodies to the component program.
-// For each atom ai and rule a0 | ai | ...| an :- B, s.th. B in bodies
+// For each atom 'ai' and rule a0 | ai | ...| an :- B, s.th. B in bodies
 //  [~up(ai) v fb(B) V hp(aj), j != i V up(p), p in B+ ^ C], where
-// hp(ai), up(ai) are the aux and output atoms of ai in C.
-void PrgDepGraph::NonHcfComponent::ComponentMap::addBodyConstraints(const Solver& generator, const SccGraph& dep, uint32 scc, SharedContext& comp) {
-	ClauseCreator cc(comp.master());
-	cc.addDefaultFlags(ClauseCreator::clause_force_simplify);
-	ClauseCreator dc(comp.master());
-	MapIt j = mapping.begin() + numAtoms;
-	for (MapRange r = bodies(); r.first != r.second; ++r.first) {
-		const BodyNode& B = dep.getBody(r.first->node);
-		if (generator.isFalse(B.lit)) { continue; }
-		POTASSCO_REQUIRE(!B.extended(), "Extended bodies not supported - use '--trans-ext=weight'");
-		for (const NodeId* hIt = B.heads_begin(), *hEnd = B.heads_end(); hIt != hEnd; ++hIt) {
-			uint32 hScc = *hIt ? dep.getAtom(*hIt).scc : dep.getAtom(hIt[1]).scc;
-			if (hScc != scc) {
-				// the head is not relevant to this non-hcf - skip it
-				if (!*hIt) { do { ++hIt; } while (*hIt); }
-				continue;
-			}
-			// [fb(B) v ~up(a) V hp(o) for all o != a in B.disHead V up(b) for each b in B+ ^ C]
-			cc.start().add(r.first->fb());
-			if (B.scc == scc) { // add subgoals from same scc
-				for (const NodeId* aIt = B.preds(); *aIt != idMax; ++aIt) {
-					MapIt_c atMapped = findAtom(*aIt);
-					cc.add(atMapped->up());
-				}
-			}
-			if (*hIt) { // normal head
-				MapIt_c atMapped = findAtom(*hIt);
-				assert(atMapped != atoms().second);
-				cc.add(~atMapped->up());
-				cc.end();
-			}
-			else { // disjunctive head
-				const NodeId* dHead = ++hIt;
-				for (; *hIt; ++hIt) {
-					dc.start();
-					dc = cc;
-					MapIt_c atMapped = findAtom(*hIt);
-					dc.add(~atMapped->up());
-					for (const NodeId* other = dHead; *other; ++other) {
-						if (*other != *hIt) {
-							assert(dep.getAtom(*other).scc == scc);
-							atMapped = findAtom(*other);
-							dc.add(atMapped->hp());
-						}
-					}
-					dc.end();
-				}
-			}
-		}
-		if (!r.first->eq()) { *j++ = *r.first; }
-	}
-	mapping.erase(j, mapping.end());
+// hp(ai), up(ai) are the aux and output atoms of 'ai' in C.
+void PrgDepGraph::NonHcfComponent::ComponentMap::addBodyConstraints(const Solver& generator, const SccGraph& dep,
+                                                                    uint32_t scc, SharedContext& comp) {
+    ClauseCreator cc(comp.master());
+    cc.addDefaultFlags(ClauseCreator::clause_force_simplify);
+    ClauseCreator dc(comp.master());
+    MapIt         j = mapping.begin() + numAtoms;
+    for (const auto& m : bodies()) {
+        const BodyNode& body = dep.getBody(m.node);
+        if (generator.isFalse(body.lit)) {
+            continue;
+        }
+        POTASSCO_CHECK_PRE(not body.extended(), "Extended bodies not supported - use '--trans-ext=weight'");
+        auto headSpan = body.heads();
+        for (auto hIt = headSpan.begin(), hEnd = headSpan.end(); hIt != hEnd; ++hIt) {
+            uint32_t hScc = *hIt ? dep.getAtom(*hIt).scc : dep.getAtom(hIt[1]).scc;
+            if (hScc != scc) {
+                // the head is not relevant to this non-hcf - skip it
+                if (!*hIt) {
+                    do { ++hIt; } while (*hIt);
+                }
+                continue;
+            }
+            // [fb(B) v ~up(a) V hp(o) for all o != a in B.disHead V up(b) for each b in B+ ^ C]
+            cc.start().add(m.fb());
+            if (body.scc == scc) { // add subgoals from same scc
+                for (const auto& x : body.predecessors(true)) {
+                    MapIt_c atMapped = findAtom(x.id());
+                    cc.add(atMapped->up());
+                }
+            }
+            if (*hIt) { // normal head
+                MapIt_c atMapped = findAtom(*hIt);
+                assert(atMapped != mapping.begin() + numAtoms);
+                cc.add(~atMapped->up());
+                cc.end();
+            }
+            else { // disjunctive head
+                for (auto dHead = ++hIt; *hIt; ++hIt) {
+                    dc.start();
+                    dc               = cc;
+                    MapIt_c atMapped = findAtom(*hIt);
+                    dc.add(~atMapped->up());
+                    for (auto other = dHead; *other; ++other) {
+                        if (*other != *hIt) {
+                            assert(dep.getAtom(*other).scc == scc);
+                            atMapped = findAtom(*other);
+                            dc.add(atMapped->hp());
+                        }
+                    }
+                    dc.end();
+                }
+            }
+        }
+        if (not m.eq()) {
+            *j++ = m;
+        }
+    }
+    mapping.erase(j, mapping.end());
 }
 
 // Maps the generator assignment given in s to a list of tester assumptions.
-void PrgDepGraph::NonHcfComponent::ComponentMap::mapGeneratorAssignment(const Solver& s, const SccGraph& dep, LitVec& assume) const {
-	Literal  gen;
-	assume.clear(); assume.reserve(mapping.size());
-	for (MapRange r = atoms(); r.first != r.second; ++r.first) {
-		const Mapping& at = *r.first;
-		gen = dep.getAtom(at.node).lit;
-		if (at.hasTp()) {
-			assume.push_back(at.tp() ^ (!s.isTrue(gen)));
-		}
-		if (s.isFalse(gen)) { assume.push_back(~at.up()); }
-	}
-	for (MapRange r = bodies(); r.first != r.second; ++r.first) {
-		gen = dep.getBody(r.first->node).lit;
-		assume.push_back(r.first->fb() ^ (!s.isFalse(gen)));
-	}
+void PrgDepGraph::NonHcfComponent::ComponentMap::mapGeneratorAssignment(const Solver& s, const SccGraph& dep,
+                                                                        LitVec& assume) const {
+    Literal gen;
+    assume.clear();
+    assume.reserve(mapping.size());
+    for (const auto& at : atoms()) {
+        gen = dep.getAtom(at.node).lit;
+        if (at.hasTp()) {
+            assume.push_back(at.tp() ^ (not s.isTrue(gen)));
+        }
+        if (s.isFalse(gen)) {
+            assume.push_back(~at.up());
+        }
+    }
+    for (const auto& m : bodies()) {
+        gen = dep.getBody(m.node).lit;
+        assume.push_back(m.fb() ^ (not s.isFalse(gen)));
+    }
 }
 // Maps the tester model given in s back to a list of unfounded atoms in the generator.
 void PrgDepGraph::NonHcfComponent::ComponentMap::mapTesterModel(const Solver& s, VarVec& out) const {
-	assert(s.numFreeVars() == 0);
-	out.clear();
-	for (MapRange r = atoms(); r.first != r.second; ++r.first) {
-		if (s.isTrue(r.first->up())) {
-			out.push_back(r.first->node);
-		}
-	}
+    assert(s.numFreeVars() == 0);
+    out.clear();
+    for (const auto& m : atoms()) {
+        if (s.isTrue(m.up())) {
+            out.push_back(m.node);
+        }
+    }
 }
-bool PrgDepGraph::NonHcfComponent::ComponentMap::simplify(const Solver& generator, const SccGraph& dep, Solver& tester) {
-	if (!tester.popRootLevel(UINT32_MAX)) { return false; }
-	if (tester.sharedContext()->isShared() && (tester.sharedContext()->allowImplicit(Constraint_t::Conflict) || tester.sharedContext()->distributor.get())) {
-		// Simplification not safe: top-level assignments of threads are
-		// not necessarily synchronised at this point and clauses simplified
-		// with top-level assignment of this thread might not (yet) be valid
-		// wrt possible assumptions in other threads.
-		return true;
-	}
-	const bool rem = !tester.sharedContext()->isShared();
-	MapIt j        = rem ? mapping.begin() : mapping.end();
-	for (MapIt_c it = mapping.begin(), aEnd = it + numAtoms, end = mapping.end(); it != end; ++it) {
-		const Mapping& m = *it;
-		const bool  atom = it < aEnd;
-		Literal        g = atom ? dep.getAtom(m.node).lit : dep.getBody(m.node).lit;
-		if (generator.topValue(g.var()) == value_free) {
-			if (rem) { *j++ = m; }
-			continue;
-		}
-		bool isFalse = generator.isFalse(g);
-		bool ok      = atom || tester.force(isFalse ? m.fb() : ~m.fb());
-		if (atom) {
-			if (!isFalse){ ok = !m.hasTp() || tester.force(m.tp());  if (rem) { *j++ = m; } }
-			else         { ok = tester.force(~m.up()) && (!m.hasTp() || tester.force(~m.tp())); numAtoms -= (ok && rem); }
-		}
-		if (!ok) {
-			if (rem) { j = std::copy(it, end, j); }
-			break;
-		}
-	}
-	mapping.erase(j, mapping.end());
-	return tester.simplify();
+bool PrgDepGraph::NonHcfComponent::ComponentMap::simplify(const Solver& generator, const SccGraph& dep,
+                                                          Solver& tester) {
+    if (not tester.popRootLevel(UINT32_MAX)) {
+        return false;
+    }
+    if (tester.sharedContext()->isShared() && (tester.sharedContext()->allowImplicit(ConstraintType::conflict) ||
+                                               tester.sharedContext()->distributor.get())) {
+        // Simplification not safe: top-level assignments of threads are
+        // not necessarily synchronised at this point and clauses simplified
+        // with top-level assignment of this thread might not (yet) be valid
+        // wrt possible assumptions in other threads.
+        return true;
+    }
+    const bool rem = not tester.sharedContext()->isShared();
+    MapIt      j   = rem ? mapping.begin() : mapping.end();
+    for (MapIt_c it = mapping.begin(), aEnd = it + numAtoms, end = mapping.end(); it != end; ++it) {
+        const Mapping& m    = *it;
+        const bool     atom = it < aEnd;
+        Literal        g    = atom ? dep.getAtom(m.node).lit : dep.getBody(m.node).lit;
+        if (generator.topValue(g.var()) == value_free) {
+            if (rem) {
+                *j++ = m;
+            }
+            continue;
+        }
+        bool isFalse = generator.isFalse(g);
+        bool ok      = atom || tester.force(isFalse ? m.fb() : ~m.fb());
+        if (atom) {
+            if (not isFalse) {
+                ok = not m.hasTp() || tester.force(m.tp());
+                if (rem) {
+                    *j++ = m;
+                }
+            }
+            else {
+                ok        = tester.force(~m.up()) && (not m.hasTp() || tester.force(~m.tp()));
+                numAtoms -= (ok && rem);
+            }
+        }
+        if (not ok) {
+            if (rem) {
+                j = std::copy(it, end, j);
+            }
+            break;
+        }
+    }
+    mapping.erase(j, mapping.end());
+    return tester.simplify();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgDepGraph::NonHcfComponent
 /////////////////////////////////////////////////////////////////////////////////////////
-PrgDepGraph::NonHcfComponent::NonHcfComponent(uint32 id, const PrgDepGraph& dep, SharedContext& genCtx, Configuration* c, uint32 scc, const VarVec& atoms, const VarVec& bodies)
-	: dep_(&dep)
-	, prg_(new SharedContext())
-	, comp_(new ComponentMap())
-	, id_(id)
-	, scc_(scc) {
-	Solver& generator = *genCtx.master();
-	prg_->setConcurrency(genCtx.concurrency(), SharedContext::resize_reserve);
-	prg_->setConfiguration(c, Ownership_t::Retain);
-	comp_->addVars(generator, dep, atoms, bodies, *prg_);
-	prg_->startAddConstraints();
-	comp_->addAtomConstraints(*prg_);
-	comp_->addBodyConstraints(generator, dep, scc, *prg_);
-	prg_->endInit(true);
+PrgDepGraph::NonHcfComponent::NonHcfComponent(uint32_t id, const PrgDepGraph& dep, SharedContext& genCtx,
+                                              Configuration* c, uint32_t scc, VarView atoms, VarView bodies)
+    : dep_(&dep)
+    , prg_(std::make_unique())
+    , comp_(std::make_unique())
+    , id_(id)
+    , scc_(scc) {
+    Solver& generator = *genCtx.master();
+    prg_->setConcurrency(genCtx.concurrency(), SharedContext::resize_reserve);
+    prg_->setConfiguration(c);
+    comp_->addVars(generator, dep, atoms, bodies, *prg_);
+    prg_->startAddConstraints();
+    comp_->addAtomConstraints(*prg_);
+    comp_->addBodyConstraints(generator, dep, scc, *prg_);
+    prg_->endInit(true);
 }
 
-PrgDepGraph::NonHcfComponent::~NonHcfComponent() {
-	delete prg_;
-	delete comp_;
-}
+PrgDepGraph::NonHcfComponent::~NonHcfComponent() = default;
 
 void PrgDepGraph::NonHcfComponent::update(const SharedContext& generator) {
-	for (uint32 i = 0; generator.hasSolver(i); ++i) {
-		if (!prg_->hasSolver(i)) { prg_->attach(prg_->pushSolver());   }
-		else                     { prg_->initStats(*prg_->solver(i)); }
-	}
+    for (uint32_t i = 0; generator.hasSolver(i); ++i) {
+        if (not prg_->hasSolver(i)) {
+            prg_->attach(prg_->pushSolver());
+        }
+        else {
+            prg_->initStats(*prg_->solver(i));
+        }
+    }
 }
 
 void PrgDepGraph::NonHcfComponent::assumptionsFromAssignment(const Solver& s, LitVec& assume) const {
-	comp_->mapGeneratorAssignment(s, *dep_, assume);
+    comp_->mapGeneratorAssignment(s, *dep_, assume);
 }
 
-bool PrgDepGraph::NonHcfComponent::test(const Solver& generator, const LitVec& assume, VarVec& unfoundedOut) const {
-	assert(generator.id() < prg_->concurrency() && "Invalid id!");
-	// Forwards to message handler of generator so that messages are
-	// handled during long-running tests.
-	struct Tester : MessageHandler {
-		Tester(Solver& s, MessageHandler* gen) : solver(&s), generator(gen) { if (gen) { s.addPost(this); } }
-		~Tester() { if (generator) { solver->removePost(this); } }
-		bool handleMessages()                            { return generator->handleMessages(); }
-		bool propagateFixpoint(Solver&, PostPropagator*) { return Tester::handleMessages() || !terminate(); }
-		bool terminate()                                 { solver->setStopConflict(); return true; }
-		int test(const LitVec& assume) {
-			return int(BasicSolve(*solver).satisfiable(assume, solver->stats.choices == 0) == false);
-		}
-		Solver*         solver;
-		MessageHandler* generator;
-	} tester(*prg_->solver(generator.id()), static_cast(generator.getPost(PostPropagator::priority_reserved_msg)));
-	SolveTestEvent ev(*tester.solver, id_, generator.numFreeVars() != 0);
-	tester.solver->stats.addTest(ev.partial);
-	generator.sharedContext()->report(ev);
-	ev.time = ThreadTime::getTime();
-	if ((ev.result = tester.test(assume)) == 0) {
-		tester.solver->stats.addModel(tester.solver->decisionLevel());
-		comp_->mapTesterModel(*tester.solver, unfoundedOut);
-	}
-	ev.time = ThreadTime::getTime() - ev.time;
-	tester.solver->stats.addCpuTime(ev.time);
-	generator.sharedContext()->report(ev);
-	return ev.result != 0;
+bool PrgDepGraph::NonHcfComponent::test(const Solver& generator, LitView assume, VarVec& unfoundedOut) const {
+    assert(generator.id() < prg_->concurrency() && "Invalid id!");
+    // Forwards to message handler of generator so that messages are
+    // handled during long-running tests.
+    struct Tester : MessageHandler {
+        Tester(Solver& s, MessageHandler* gen) : solver(&s), generator(gen) {
+            if (gen) {
+                s.addPost(this);
+            }
+        }
+        ~Tester() override {
+            if (generator) {
+                solver->removePost(this);
+            }
+        }
+        bool handleMessages() override { return generator->handleMessages(); }
+        bool propagateFixpoint(Solver&, PostPropagator*) override {
+            if (not Tester::handleMessages()) {
+                solver->setStopConflict(); // terminate
+                return false;
+            }
+            return true;
+        }
+        [[nodiscard]] int test(LitView assume) const {
+            bool init = solver->stats.choices == 0;
+            return static_cast(not BasicSolve(*solver).satisfiable(assume, init));
+        }
+        Solver*         solver;
+        MessageHandler* generator;
+    } tester(*prg_->solver(generator.id()),
+             static_cast(generator.getPost(PostPropagator::priority_reserved_msg)));
+    SolveTestEvent ev(*tester.solver, id_, generator.numFreeVars() != 0);
+    tester.solver->stats.addTest(ev.partial);
+    generator.sharedContext()->report(ev);
+    ev.time = ThreadTime::getTime();
+    if (ev.result = tester.test(assume); ev.result == 0) {
+        tester.solver->stats.addModel(tester.solver->decisionLevel());
+        comp_->mapTesterModel(*tester.solver, unfoundedOut);
+    }
+    ev.time = ThreadTime::getTime() - ev.time;
+    tester.solver->stats.addCpuTime(ev.time);
+    generator.sharedContext()->report(ev);
+    return ev.result != 0;
 }
 bool PrgDepGraph::NonHcfComponent::simplify(const Solver& s) const {
-	return comp_->simplify(s, *dep_, *prg_->solver(s.id()));
+    return comp_->simplify(s, *dep_, *prg_->solver(s.id()));
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgDepGraph::NonHcfStats
 /////////////////////////////////////////////////////////////////////////////////////////
 struct PrgDepGraph::NonHcfStats::Data {
-	typedef StatsVec ProblemVec;
-	typedef StatsVec  SolverVec;
-	struct ComponentStats {
-		ProblemVec problem;
-		SolverVec  solvers;
-		SolverVec  accu;
-	};
-	Data(uint32 level, bool inc) : components(level > 1 ? new ComponentStats : 0) {
-		if (inc) { solvers.multi = new SolverStats(); }
-	}
-	~Data() { delete components; delete solvers.multi; }
-	void addHcc(const NonHcfComponent& c) {
-		assert(components);
-		ProblemVec&   hcc = components->problem;
-		SolverVec& solver = components->solvers;
-		SolverVec*   accu = solvers.multi ? &components->accu : 0;
-		uint32 id = c.id();
-		if (id >= hcc.size()) {
-			hcc.growTo(id + 1);
-			solver.growTo(id + 1);
-			if (accu) { accu->growTo(id + 1); }
-		}
-		if (!hcc[id]) {
-			hcc[id]    = new ProblemStats(c.ctx().stats());
-			solver[id] = new SolverStats();
-			if (accu) { (*accu)[id] = new SolverStats(); solver[id]->multi = (*accu)[id]; }
-		}
-	}
-	void updateHcc(const NonHcfComponent& c) {
-		c.ctx().accuStats(solvers);
-		if (components && c.id() < components->solvers.size()) {
-			POTASSCO_REQUIRE(components->solvers[c.id()], "component not added to stats!");
-			c.ctx().accuStats(*components->solvers[c.id()]);
-			components->solvers[c.id()]->flush();
-		}
-	}
-	ProblemStats    hccs;
-	SolverStats     solvers;
-	ComponentStats* components;
+    using ProblemVec = StatsVec;
+    using SolverVec  = StatsVec;
+    struct ComponentStats {
+        ProblemVec problem;
+        SolverVec  solvers;
+        SolverVec  accu;
+    };
+    Data(uint32_t level, bool inc) : components(level > 1 ? std::make_unique() : nullptr) {
+        if (inc) {
+            accus         = std::make_unique();
+            solvers.multi = accus.get();
+        }
+    }
+    void addHcc(const NonHcfComponent& c) {
+        assert(components);
+        ProblemVec& hcc    = components->problem;
+        SolverVec&  solver = components->solvers;
+        SolverVec*  accu   = solvers.multi ? &components->accu : nullptr;
+        uint32_t    id     = c.id();
+        if (id >= hcc.size()) {
+            hcc.growTo(id + 1);
+            solver.growTo(id + 1);
+            if (accu) {
+                accu->growTo(id + 1);
+            }
+        }
+        if (not hcc[id]) {
+            hcc[id]    = new ProblemStats(c.ctx().stats());
+            solver[id] = new SolverStats();
+            if (accu) {
+                (*accu)[id]       = new SolverStats();
+                solver[id]->multi = (*accu)[id];
+            }
+        }
+    }
+    void updateHcc(const NonHcfComponent& c) {
+        c.ctx().accuStats(solvers);
+        if (components && c.id() < components->solvers.size()) {
+            POTASSCO_CHECK_PRE(components->solvers[c.id()], "component not added to stats!");
+            c.ctx().accuStats(*components->solvers[c.id()]);
+            components->solvers[c.id()]->flush();
+        }
+    }
+    ProblemStats                    hccs;
+    SolverStats                     solvers;
+    std::unique_ptr components;
+    std::unique_ptr    accus;
 };
-PrgDepGraph::NonHcfStats::NonHcfStats(PrgDepGraph& g, uint32 l, bool inc) : graph_(&g), data_(new Data(l, inc)) {
-	for (NonHcfIter it = g.nonHcfBegin(), end = g.nonHcfEnd(); it != end; ++it) {
-		addHcc(**it);
-	}
+PrgDepGraph::NonHcfStats::NonHcfStats(PrgDepGraph& g, uint32_t l, bool inc)
+    : graph_(&g)
+    , data_(std::make_unique(l, inc)) {
+    for (auto* hcc : g.nonHcfs()) { addHcc(*hcc); }
 }
-PrgDepGraph::NonHcfStats::~NonHcfStats() { delete data_; }
+PrgDepGraph::NonHcfStats::~NonHcfStats() = default;
 void PrgDepGraph::NonHcfStats::accept(StatsVisitor& out, bool final) const {
-	if (!data_->solvers.multi) { final = false; }
-	out.visitProblemStats(data_->hccs);
-	out.visitSolverStats(final ? *data_->solvers.multi : data_->solvers);
-	if (data_->components && out.visitHccs(StatsVisitor::Enter)) {
-		const Data::SolverVec& solver = final ? data_->components->accu : data_->components->solvers;
-		const Data::ProblemVec&   hcc = data_->components->problem;
-		for (uint32 i = 0, end = sizeVec(hcc); i != end; ++i) {
-			out.visitHcc(i, *hcc[i], *solver[i]);
-		}
-		out.visitHccs(StatsVisitor::Leave);
-	}
+    if (not data_->solvers.multi) {
+        final = false;
+    }
+    out.visitProblemStats(data_->hccs);
+    out.visitSolverStats(final ? *data_->solvers.multi : data_->solvers);
+    if (data_->components && out.visitHccs(StatsVisitor::enter)) {
+        const Data::SolverVec&  solver = final ? data_->components->accu : data_->components->solvers;
+        const Data::ProblemVec& hcc    = data_->components->problem;
+        for (auto i : irange(hcc)) { out.visitHcc(i, *hcc[i], *solver[i]); }
+        out.visitHccs(StatsVisitor::leave);
+    }
 }
-void PrgDepGraph::NonHcfStats::startStep(uint32 statsLevel) {
-	data_->solvers.reset();
-	if (data_->components) { data_->components->solvers.reset(); }
-	if (statsLevel > 1 && !data_->components) {
-		data_->components = new Data::ComponentStats();
-		for (NonHcfIter it = graph_->nonHcfBegin(), end = graph_->nonHcfEnd(); it != end; ++it) {
-			data_->addHcc(**it);
-		}
-	}
+void PrgDepGraph::NonHcfStats::startStep(uint32_t statsLevel) {
+    data_->solvers.reset();
+    if (data_->components) {
+        data_->components->solvers.reset();
+    }
+    if (statsLevel > 1 && not data_->components) {
+        data_->components = std::make_unique();
+        for (auto* hcc : graph_->nonHcfs()) { data_->addHcc(*hcc); }
+    }
 }
 void PrgDepGraph::NonHcfStats::endStep() {
-	for (NonHcfIter it = graph_->nonHcfBegin(), end = graph_->nonHcfEnd(); it != end; ++it) {
-		data_->updateHcc(**it);
-	}
-	data_->solvers.flush();
+    for (auto* hcc : graph_->nonHcfs()) { data_->updateHcc(*hcc); }
+    data_->solvers.flush();
 }
 void PrgDepGraph::NonHcfStats::addHcc(const NonHcfComponent& c) {
-	data_->hccs.accu(c.ctx().stats());
-	if (data_->components) { data_->addHcc(c); }
-}
-void PrgDepGraph::NonHcfStats::removeHcc(const NonHcfComponent& c) {
-	data_->updateHcc(c);
+    data_->hccs.accu(c.ctx().stats());
+    if (data_->components) {
+        data_->addHcc(c);
+    }
 }
+void PrgDepGraph::NonHcfStats::removeHcc(const NonHcfComponent& c) { data_->updateHcc(c); }
 void PrgDepGraph::NonHcfStats::addTo(StatsMap& problem, StatsMap& solving, StatsMap* accu) const {
-	data_->solvers.addTo("hccs", solving, accu);
-	problem.add("hccs", StatisticObject::map(&data_->hccs));
-	if (data_->components) {
-		problem.add("hcc", data_->components->problem.toStats());
-		solving.add("hcc", data_->components->solvers.toStats());
-		if (accu) { accu->add("hcc", data_->components->accu.toStats()); }
-	}
+    data_->solvers.addTo("hccs", solving, accu);
+    problem.add("hccs", StatisticObject::map(&data_->hccs));
+    if (data_->components) {
+        problem.add("hcc", data_->components->problem.toStats());
+        solving.add("hcc", data_->components->solvers.toStats());
+        if (accu) {
+            accu->add("hcc", data_->components->accu.toStats());
+        }
+    }
 }
 } // namespace Asp
 /////////////////////////////////////////////////////////////////////////////////////////
 // class ExtDepGraph
 /////////////////////////////////////////////////////////////////////////////////////////
-ExtDepGraph::ExtDepGraph(uint32) : maxNode_(0), comEdge_(0), genCnt_(0) {}
-ExtDepGraph::~ExtDepGraph(){}
-void ExtDepGraph::addEdge(Literal lit, uint32 startNode, uint32 endNode) {
-	POTASSCO_REQUIRE(!frozen(), "ExtDepGraph::update() not called!");
-	fwdArcs_.push_back(Arc::create(lit, startNode, endNode));
-	maxNode_ = std::max(std::max(startNode, endNode)+uint32(1), maxNode_);
-	if (comEdge_ && std::min(startNode, endNode) < nodes_.size()) {
-		invArcs_.clear();
-		comEdge_ = 0;
-		++genCnt_;
-	}
-}
-bool ExtDepGraph::frozen() const {
-	return !fwdArcs_.empty() && fwdArcs_.back().tail() == UINT32_MAX;
+ExtDepGraph::ExtDepGraph(uint32_t) : maxNode_(0), comEdge_(0), genCnt_(0) {}
+ExtDepGraph::~ExtDepGraph() = default;
+void ExtDepGraph::addEdge(Literal lit, uint32_t startNode, uint32_t endNode) {
+    POTASSCO_CHECK_PRE(not frozen(), "ExtDepGraph::update() not called!");
+    fwdArcs_.push_back(Arc::create(lit, startNode, endNode));
+    maxNode_ = std::max(std::max(startNode, endNode) + 1u, maxNode_);
+    if (comEdge_ && std::min(startNode, endNode) < nodes_.size()) {
+        invArcs_.clear();
+        comEdge_ = 0;
+        ++genCnt_;
+    }
 }
+bool ExtDepGraph::frozen() const { return not fwdArcs_.empty() && fwdArcs_.back().tail() == UINT32_MAX; }
 void ExtDepGraph::update() {
-	if (frozen()) {
-		fwdArcs_.pop_back();
-	}
+    if (frozen()) {
+        fwdArcs_.pop_back();
+    }
 }
-uint32 ExtDepGraph::finalize(SharedContext& ctx) {
-	if (frozen()) {
-		return comEdge_;
-	}
-	// sort by end node
-	std::sort(fwdArcs_.begin() + comEdge_, fwdArcs_.end(), CmpArc<1>());
-	invArcs_.reserve(fwdArcs_.size());
-	Node sent = { UINT32_MAX, UINT32_MAX };
-	nodes_.resize(maxNode_, sent);
-	for (ArcVec::const_iterator it = fwdArcs_.begin() + comEdge_, end = fwdArcs_.end(); it != end;) {
-		uint32 node = it->head();
-		POTASSCO_REQUIRE(!comEdge_ || nodes_[node].invOff == UINT32_MAX, "ExtDepGraph: invalid incremental update!");
-		Inv inv;
-		nodes_[node].invOff = (uint32)invArcs_.size();
-		do {
-			inv.lit  = it->lit;
-			inv.rep  = static_cast(it->tail() << 1) | 1u;
-			invArcs_.push_back(inv);
-			ctx.setFrozen(it->lit.var(), true);
-		} while (++it != end && it->head() == node);
-		invArcs_.back().rep ^= 1u;
-	}
-	// sort by start node
-	std::sort(fwdArcs_.begin() + comEdge_, fwdArcs_.end(), CmpArc<0>());
-	for (ArcVec::const_iterator it = fwdArcs_.begin() + comEdge_, end = fwdArcs_.end(); it != end;) {
-		uint32 node = it->tail();
-		POTASSCO_REQUIRE(!comEdge_ || nodes_[node].fwdOff == UINT32_MAX, "ExtDepGraph: invalid incremental update!");
-		nodes_[node].fwdOff = static_cast(it - fwdArcs_.begin());
-		it          = std::lower_bound(it, end, node + 1, CmpArc<0>());
-	}
-	comEdge_ = (uint32)fwdArcs_.size();
-	fwdArcs_.push_back(Arc::create(lit_false(), UINT32_MAX, UINT32_MAX));
-	return comEdge_;
+uint32_t ExtDepGraph::finalize(SharedContext& ctx) {
+    if (frozen()) {
+        return comEdge_;
+    }
+    // sort by end node
+    std::sort(fwdArcs_.begin() + comEdge_, fwdArcs_.end(), CmpArc<1>());
+    invArcs_.reserve(fwdArcs_.size());
+    Node sent = {UINT32_MAX, UINT32_MAX};
+    nodes_.resize(maxNode_, sent);
+    for (auto it = fwdArcs_.begin() + comEdge_, end = fwdArcs_.end(); it != end;) {
+        uint32_t node = it->head();
+        POTASSCO_CHECK_PRE(not comEdge_ || nodes_[node].invOff == UINT32_MAX,
+                           "ExtDepGraph: invalid incremental update!");
+        Inv inv;
+        nodes_[node].invOff = size32(invArcs_);
+        do {
+            inv.lit = it->lit;
+            inv.rep = static_cast(it->tail() << 1) | 1u;
+            invArcs_.push_back(inv);
+            ctx.setFrozen(it->lit.var(), true);
+        } while (++it != end && it->head() == node);
+        invArcs_.back().rep ^= 1u;
+    }
+    // sort by start node
+    std::sort(fwdArcs_.begin() + comEdge_, fwdArcs_.end(), CmpArc<0>());
+    for (auto it = fwdArcs_.begin() + comEdge_, end = fwdArcs_.end(); it != end;) {
+        uint32_t node = it->tail();
+        POTASSCO_CHECK_PRE(not comEdge_ || nodes_[node].fwdOff == UINT32_MAX,
+                           "ExtDepGraph: invalid incremental update!");
+        nodes_[node].fwdOff = static_cast(it - fwdArcs_.begin());
+        it                  = std::lower_bound(it, end, node + 1, CmpArc<0>());
+    }
+    comEdge_ = size32(fwdArcs_);
+    fwdArcs_.push_back(Arc::create(lit_false, UINT32_MAX, UINT32_MAX));
+    return comEdge_;
 }
-uint64 ExtDepGraph::attach(Solver& s, Constraint& p, uint64 genId) {
-	uint32 count = static_cast(genId >> 32);
-	uint32 edges = static_cast(genId);
-	uint32 update= count == genCnt_ ? 0 : edges;
-	GenericWatch* w;
-	for (uint32 i = (count == genCnt_ ? edges : 0), eId, end = comEdge_; i < end; ++i) {
-		const Arc& a = fwdArcs_[i];
-		if (a.head() != a.tail()) {
-			if (s.topValue(a.lit.var()) == value_free) {
-				if (!update || (w = s.getWatch(a.lit, &p)) == 0) {
-					s.addWatch(a.lit, &p, i);
-				}
-				else {
-					w->data = i;
-					--update;
-				}
-			}
-			else if (s.isTrue(a.lit)) {
-				p.propagate(s, a.lit, (eId = i));
-			}
-		}
-		else if (!s.force(~a.lit)) {
-			break;
-		}
-	}
-	return (static_cast(genCnt_) << 32) | comEdge_;
+uint64_t ExtDepGraph::attach(Solver& s, Constraint& p, uint64_t genId) {
+    auto count = static_cast(genId >> 32);
+    auto edges = static_cast(genId);
+    POTASSCO_ASSERT(edges <= comEdge_);
+    uint32_t update = count == genCnt_ ? 0u : edges;
+    for (auto i : irange(count == genCnt_ ? edges : 0u, comEdge_)) {
+        const Arc& a = fwdArcs_[i];
+        if (a.head() != a.tail()) {
+            if (s.topValue(a.lit.var()) == value_free) {
+                if (GenericWatch* w = update ? s.getWatch(a.lit, &p) : nullptr; not w) {
+                    s.addWatch(a.lit, &p, i);
+                }
+                else {
+                    w->data = i;
+                    --update;
+                }
+            }
+            else if (s.isTrue(a.lit)) {
+                p.propagate(s, a.lit, i);
+            }
+        }
+        else if (not s.force(~a.lit)) {
+            break;
+        }
+    }
+    return (static_cast(genCnt_) << 32) | comEdge_;
 }
 void ExtDepGraph::detach(Solver* s, Constraint& p) {
-	if (s) {
-		for (ArcVec::size_type i = fwdArcs_.size(); i--; ) {
-			s->removeWatch(fwdArcs_[i].lit, &p);
-		}
-	}
+    if (s) {
+        for (auto i = fwdArcs_.size(); i--;) { s->removeWatch(fwdArcs_[i].lit, &p); }
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class AcyclicityCheck
 /////////////////////////////////////////////////////////////////////////////////////////
 struct AcyclicityCheck::ReasonStore {
-	typedef PodVector::type NogoodMap;
-	NogoodMap db;
-	void getReason(Literal p, LitVec& out) {
-		if (const LitVec* r = db[p.var()]) {
-			out.insert(out.end(), r->begin(), r->end());
-		}
-	}
-	void setReason(Literal p, LitVec::const_iterator first, LitVec::const_iterator end) {
-		Var v = p.var();
-		if (v >= db.size()) { db.resize(v+1, 0); }
-		if (db[v] == 0)     { db[v] = new LitVec(first, end); }
-		else                { db[v]->assign(first, end); }
-	}
-	~ReasonStore() {
-		std::for_each(db.begin(), db.end(), DeleteObject());
-	}
+    using NogoodMap = PodVector_t;
+    NogoodMap db;
+    void      getReason(Literal p, LitVec& out) {
+        if (const LitVec* r = db[p.var()]) {
+            out.insert(out.end(), r->begin(), r->end());
+        }
+    }
+    void setReason(Literal p, LitView reason) {
+        auto v = p.var();
+        if (v >= db.size()) {
+            db.resize(v + 1, nullptr);
+        }
+        if (db[v] == nullptr) {
+            db[v] = new LitVec;
+        }
+        db[v]->assign(reason.begin(), reason.end());
+    }
+    ~ReasonStore() { std::ranges::for_each(db, DeleteObject()); }
 };
-AcyclicityCheck::AcyclicityCheck(DependencyGraph* graph) : graph_(graph), solver_(0), nogoods_(0), strat_(bit_mask(config_bit)), tagCnt_(0), genId_(0)  {
-}
-AcyclicityCheck::~AcyclicityCheck() {
-	delete nogoods_;
-}
+AcyclicityCheck::AcyclicityCheck(DependencyGraph* graph)
+    : graph_(graph)
+    , solver_(nullptr)
+    , nogoods_(nullptr)
+    , strat_(Potassco::nth_bit(config_bit))
+    , tagCnt_(0)
+    , genId_(0) {}
+AcyclicityCheck::~AcyclicityCheck() = default;
 
-void AcyclicityCheck::setStrategy(Strategy p) {
-	strat_ = p;
-}
+void AcyclicityCheck::setStrategy(Strategy p) { strat_ = p; }
 void AcyclicityCheck::setStrategy(const SolverParams& p) {
-	if (p.acycFwd) { setStrategy(prop_fwd);  }
-	else           { setStrategy(p.loopRep == LoopReason_t::Implicit ? prop_full_imp : prop_full); }
-	store_set_bit(strat_, config_bit);
+    if (p.acycFwd) {
+        setStrategy(prop_fwd);
+    }
+    else {
+        setStrategy(p.loopRep ? prop_full_imp : prop_full);
+    }
+    Potassco::store_set_bit(strat_, config_bit);
 }
 
 bool AcyclicityCheck::init(Solver& s) {
-	if (!graph_) { graph_ = s.sharedContext()->extGraph.get(); }
-	if (!graph_) { return true; }
-	if (test_bit(strat_, config_bit)) {
-		setStrategy(s.sharedContext()->configuration()->solver(s.id()));
-	}
-	tags_.assign(graph_->nodes(), tagCnt_ = 0);
-	parent_.resize(graph_->nodes());
-	todo_.clear();
-	solver_ = &s;
-	genId_  = graph_->attach(s, *this, genId_);
-	return true;
+    if (not graph_) {
+        graph_ = s.sharedContext()->extGraph.get();
+    }
+    if (not graph_) {
+        return true;
+    }
+    if (Potassco::test_bit(strat_, config_bit)) {
+        setStrategy(s.sharedContext()->configuration()->solver(s.id()));
+    }
+    tags_.assign(graph_->nodes(), tagCnt_ = 0);
+    parent_.resize(graph_->nodes());
+    todo_.clear();
+    solver_ = &s;
+    genId_  = graph_->attach(s, *this, genId_);
+    return true;
 }
 
-uint32 AcyclicityCheck::startSearch() {
-	if (++tagCnt_ != 0) { return tagCnt_; }
-	const uint32 last = tagCnt_ - 1;
-	for (Var v = 0; v != tags_.size(); ++v) {
-		tags_[v] = tags_[v] == last;
-	}
-	return tagCnt_ = 2;
+uint32_t AcyclicityCheck::startSearch() {
+    if (++tagCnt_ != 0) {
+        return tagCnt_;
+    }
+    const uint32_t last = tagCnt_ - 1;
+    for (auto& tag : tags_) { tag = (tag == last); }
+    return tagCnt_ = 2;
 }
-void AcyclicityCheck::setReason(Literal p, LitVec::const_iterator first, LitVec::const_iterator end) {
-	if (!nogoods_) { nogoods_ = new ReasonStore(); }
-	nogoods_->setReason(p, first, end);
+void AcyclicityCheck::setReason(Literal p, LitView reason) {
+    if (not nogoods_) {
+        nogoods_ = std::make_unique();
+    }
+    nogoods_->setReason(p, reason);
 }
 void AcyclicityCheck::addClauseLit(Solver& s, Literal p) {
-	assert(s.isFalse(p));
-	uint32 dl = s.level(p.var());
-	if (dl && !s.seen(p)) {
-		s.markSeen(p);
-		s.markLevel(dl);
-		reason_.push_back(p);
-	}
+    assert(s.isFalse(p));
+    uint32_t dl = s.level(p.var());
+    if (dl && not s.seen(p)) {
+        s.markSeen(p);
+        s.markLevel(dl);
+        reason_.push_back(p);
+    }
 }
 
 void AcyclicityCheck::reset() {
-	todo_.clear();
-	reason_.clear();
+    todo_.clear();
+    reason_.clear();
 }
 
 bool AcyclicityCheck::valid(Solver& s) {
-	if (todo_.empty()) { return true; }
-	return AcyclicityCheck::propagateFixpoint(s, 0);
-}
-bool AcyclicityCheck::isModel(Solver& s) {
-	return AcyclicityCheck::valid(s);
+    if (todo_.empty()) {
+        return true;
+    }
+    return AcyclicityCheck::propagateFixpoint(s, nullptr);
 }
+bool AcyclicityCheck::isModel(Solver& s) { return AcyclicityCheck::valid(s); }
 
 void AcyclicityCheck::destroy(Solver* s, bool detach) {
-	if (s && detach) {
-		s->removePost(this);
-	}
-	if (graph_) {
-		graph_->detach(detach ? s : 0, *this);
-	}
-	PostPropagator::destroy(s, detach);
+    if (s && detach) {
+        s->removePost(this);
+    }
+    if (graph_) {
+        graph_->detach(detach ? s : nullptr, *this);
+    }
+    PostPropagator::destroy(s, detach);
 }
 void AcyclicityCheck::reason(Solver&, Literal p, LitVec& out) {
-	if (!reason_.empty() && reason_[0] == p) {
-		out.insert(out.end(), reason_.begin()+1, reason_.end());
-	}
-	else if (nogoods_) {
-		nogoods_->getReason(p, out);
-	}
+    if (not reason_.empty() && reason_[0] == p) {
+        out.insert(out.end(), reason_.begin() + 1, reason_.end());
+    }
+    else if (nogoods_) {
+        nogoods_->getReason(p, out);
+    }
 }
 
 bool AcyclicityCheck::propagateFixpoint(Solver& s, PostPropagator*) {
-	for (Arc x; !todo_.empty();) {
-		x = todo_.pop_ret();
-		if (!dfsForward(s, x) || (strategy() != prop_fwd && !dfsBackward(s, x))) {
-			return false;
-		}
-	}
-	todo_.clear();
-	return true;
+    for (Arc x; not todo_.empty();) {
+        x = todo_.pop_ret();
+        if (not dfsForward(s, x) || (strategy() != prop_fwd && not dfsBackward(s, x))) {
+            return false;
+        }
+    }
+    todo_.clear();
+    return true;
 }
 bool AcyclicityCheck::dfsForward(Solver& s, const Arc& root) {
-	const uint32 tag = startSearch();
-	nStack_.clear();
-	pushVisit(root.head(), tag);
-	for (Var node, nodeNext; !nStack_.empty();) {
-		node = nStack_.back();
-		nStack_.pop_back();
-		for (const Arc* a = graph_->fwdBegin(node); a; a = graph_->fwdNext(a)) {
-			if (s.isTrue(a->lit)) {
-				nodeNext = a->head();
-				if (nodeNext == root.tail()) {
-					setParent(nodeNext, Parent::create(a->lit, node));
-					reason_.assign(1, ~root.lit);
-					for (Var n0 = nodeNext; n0 != root.head();) {
-						Parent parent = parent_[n0];
-						assert(s.isTrue(parent.lit));
-						reason_.push_back(parent.lit);
-						n0 = parent.node;
-					}
-					return s.force(~root.lit, this);
-				}
-				else if (!visited(nodeNext, tag)) {
-					setParent(nodeNext, Parent::create(a->lit, node));
-					pushVisit(nodeNext, tag);
-				}
-			}
-		}
-	}
-	return true;
+    const uint32_t tag = startSearch();
+    nStack_.clear();
+    pushVisit(root.head(), tag);
+    while (not nStack_.empty()) {
+        auto node = nStack_.back();
+        nStack_.pop_back();
+        for (const Arc* a = graph_->fwdBegin(node); a; a = a->next()) {
+            if (s.isTrue(a->lit)) {
+                auto nodeNext = a->head();
+                if (nodeNext == root.tail()) {
+                    setParent(nodeNext, {.lit = a->lit, .node = node});
+                    reason_.assign(1, ~root.lit);
+                    for (auto n0 = nodeNext; n0 != root.head();) {
+                        const auto& [plit, pnode] = parent_[n0];
+                        assert(s.isTrue(plit));
+                        reason_.push_back(plit);
+                        n0 = pnode;
+                    }
+                    return s.force(~root.lit, this);
+                }
+                if (not visited(nodeNext, tag)) {
+                    setParent(nodeNext, {.lit = a->lit, .node = node});
+                    pushVisit(nodeNext, tag);
+                }
+            }
+        }
+    }
+    return true;
 }
 bool AcyclicityCheck::dfsBackward(Solver& s, const Arc& root) {
-	const uint32 tag = startSearch();
-	const uint32 fwd = tag - 1;
-	nStack_.clear();
-	pushVisit(root.tail(), tag);
-	for (Var node, nodeNext; !nStack_.empty(); ) {
-		node = nStack_.back();
-		nStack_.pop_back();
-		for (const Inv* a = graph_->invBegin(node); a; a = graph_->invNext(a)) {
-			ValueRep val = s.value(a->lit.var());
-			if (val == falseValue(a->lit) || visited(nodeNext = a->tail(), tag)) { continue; }
-			if (visited(nodeNext, fwd)) { // a->lit would complete a cycle - force to false
-				assert(val == value_free || s.level(a->lit.var()) == s.decisionLevel());
-				reason_.assign(1, ~a->lit);
-				addClauseLit(s, ~root.lit);
-				for (Var n = nodeNext; n != root.head(); ) {
-					Parent parent = parent_[n];
-					assert(s.isTrue(parent.lit) && visited(parent.node, fwd));
-					addClauseLit(s, ~parent.lit);
-					n = parent.node;
-				}
-				for (Var n = node; n != root.tail(); ) {
-					Parent parent = parent_[n];
-					assert(s.isTrue(parent.lit)&& visited(parent.node, tag));
-					addClauseLit(s, ~parent.lit);
-					n = parent.node;
-				}
-				if (val == value_free && strategy() == prop_full) {
-					ConstraintInfo info(Constraint_t::Loop);
-					s.finalizeConflictClause(reason_, info, 0);
-					ClauseCreator::create(s, reason_, ClauseCreator::clause_no_prepare, info);
-				}
-				else {
-					for (uint32 i = 1; i != reason_.size(); ++i) {
-						s.clearSeen(reason_[i].var());
-						reason_[i] = ~reason_[i];
-					}
-					if (!s.force(~a->lit, this)) { return false; }
-					setReason(~a->lit, reason_.begin()+1, reason_.end());
-				}
-				assert(s.isFalse(a->lit));
-				if (!s.propagateUntil(this)) { return false; }
-			}
-			else if (val != value_free) { // follow true edge backward
-				setParent(nodeNext, Parent::create(a->lit, node));
-				pushVisit(nodeNext, tag);
-			}
-		}
-	}
-	return true;
+    const uint32_t tag = startSearch();
+    const uint32_t fwd = tag - 1;
+    nStack_.clear();
+    pushVisit(root.tail(), tag);
+    while (not nStack_.empty()) {
+        auto node = nStack_.back();
+        nStack_.pop_back();
+        for (const Inv* a = graph_->invBegin(node); a; a = a->next()) {
+            auto val      = s.value(a->lit.var());
+            auto nodeNext = a->tail();
+            if (val == falseValue(a->lit) || visited(nodeNext, tag)) {
+                continue;
+            }
+            if (visited(nodeNext, fwd)) { // a->lit would complete a cycle - force to false
+                assert(val == value_free || s.level(a->lit.var()) == s.decisionLevel());
+                reason_.assign(1, ~a->lit);
+                addClauseLit(s, ~root.lit);
+                for (auto n = nodeNext; n != root.head();) {
+                    const auto& [plit, pnode] = parent_[n];
+                    assert(s.isTrue(plit) && visited(pnode, fwd));
+                    addClauseLit(s, ~plit);
+                    n = pnode;
+                }
+                for (auto n = node; n != root.tail();) {
+                    const auto& [plit, pnode] = parent_[n];
+                    assert(s.isTrue(plit) && visited(pnode, tag));
+                    addClauseLit(s, ~plit);
+                    n = pnode;
+                }
+                if (val == value_free && strategy() == prop_full) {
+                    ConstraintInfo info(ConstraintType::loop);
+                    s.finalizeConflictClause(reason_, info, 0);
+                    ClauseCreator::create(s, reason_, ClauseCreator::clause_no_prepare, info);
+                }
+                else {
+                    for (auto& p : drop(reason_, 1u)) {
+                        s.clearSeen(p.var());
+                        p = ~p;
+                    }
+                    if (not s.force(~a->lit, this)) {
+                        return false;
+                    }
+                    setReason(~a->lit, drop(reason_, 1u));
+                }
+                assert(s.isFalse(a->lit));
+                if (not s.propagateUntil(this)) {
+                    return false;
+                }
+            }
+            else if (val != value_free) { // follow true edge backward
+                setParent(nodeNext, {.lit = a->lit, .node = node});
+                pushVisit(nodeNext, tag);
+            }
+        }
+    }
+    return true;
 }
-}
-
+} // namespace Clasp
diff --git a/src/enumerator.cpp b/src/enumerator.cpp
index 72a6113..12c32b8 100644
--- a/src/enumerator.cpp
+++ b/src/enumerator.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,318 +22,385 @@
 // IN THE SOFTWARE.
 //
 #include 
+
+#include 
 #include 
 #include 
-#include 
+
+#include 
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
-// Enumerator - Shared Queue / Thread Queue
+// Enumerator - Shared Queue
 /////////////////////////////////////////////////////////////////////////////////////////
-class Enumerator::SharedQueue : public mt::MultiQueue {
+class Enumerator::SharedQueue {
 public:
-	typedef mt::MultiQueue BaseType;
-	explicit SharedQueue(uint32 m) : BaseType(m, releaseLits) { reserve(m + 1); }
-	bool pushRelaxed(SharedLiterals* clause)  { unsafePublish(clause); return true; }
-	static void releaseLits(SharedLiterals* x){ x->release(); }
-};
-class Enumerator::ThreadQueue {
-public:
-	explicit ThreadQueue(SharedQueue& q) : queue_(&q) { tail_ = q.addThread(); }
-	bool         pop(SharedLiterals*& out){ return queue_->tryConsume(tail_, out); }
-	ThreadQueue* clone()                  { return new ThreadQueue(*queue_); }
-private:
-	Enumerator::SharedQueue*          queue_;
-	Enumerator::SharedQueue::ThreadId tail_;
+    using SharedLitsPtr = std::unique_ptr;
+    using QueueType     = mt::MultiQueue;
+    using IdType        = QueueType::ThreadId;
+
+    explicit SharedQueue(uint32_t m) : queue(m) { queue.reserve(m + 1); }
+    IdType addSolver() { return queue.addThread(); }
+    bool   pushRelaxed(SharedLiterals* clause) {
+        queue.unsafePublish(SharedLitsPtr(clause));
+        return true;
+    }
+    SharedLiterals* pop(IdType& qId) {
+        auto* x = queue.tryConsume(qId);
+        return x ? (*x).get() : nullptr;
+    }
+    QueueType queue;
 };
 /////////////////////////////////////////////////////////////////////////////////////////
 // EnumerationConstraint
 /////////////////////////////////////////////////////////////////////////////////////////
-EnumerationConstraint::EnumerationConstraint() : mini_(0), root_(0), state_(0), upMode_(0), heuristic_(0), disjoint_(false)  {
-	setDisjoint(false);
-}
-EnumerationConstraint::~EnumerationConstraint() { }
+struct EnumerationConstraint::QueueReader {
+    explicit QueueReader(QueuePtr q) : queue(q), id(q->addSolver()) {}
+    SharedLiterals*                 pop() { return queue->pop(id); }
+    QueuePtr                        queue;
+    Enumerator::SharedQueue::IdType id;
+};
+EnumerationConstraint::EnumerationConstraint()  = default;
+EnumerationConstraint::~EnumerationConstraint() = default;
 void EnumerationConstraint::init(Solver& s, SharedMinimizeData* m, QueuePtr p) {
-	mini_ = 0;
-	queue_ = p;
-	upMode_ = value_false;
-	heuristic_ = 0;
-	if (m) {
-		OptParams opt = s.sharedContext()->configuration()->solver(s.id()).opt;
-		mini_ = m->attach(s, opt);
-		if (optimize()) {
-			if   (opt.type != OptParams::type_bb) { upMode_ |= value_true; }
-			else { heuristic_ |= 1; }
-		}
-		if (opt.hasOption(OptParams::heu_sign)) {
-			for (const WeightLiteral* it = m->lits; !isSentinel(it->first); ++it) {
-				s.setPref(it->first.var(), ValueSet::pref_value, falseValue(it->first));
-			}
-		}
-		if (opt.hasOption(OptParams::heu_model)) { heuristic_ |= 2; }
-	}
+    mini_      = nullptr;
+    queue_     = p ? std::make_unique(p) : nullptr;
+    heuristic_ = 0;
+    if (m) {
+        OptParams opt = s.sharedContext()->configuration()->solver(s.id()).opt;
+        mini_         = m->attach(s, opt);
+        if (optimize()) {
+            if (opt.type != OptParams::type_bb) {
+                s.strategies().resetOnModel = 1;
+            }
+            else {
+                heuristic_ |= 1;
+            }
+        }
+        if (opt.hasOption(OptParams::heu_sign)) {
+            for (const auto& wl : *m) { s.setPref(wl.lit.var(), ValueSet::pref_value, falseValue(wl.lit)); }
+        }
+        if (opt.hasOption(OptParams::heu_model)) {
+            heuristic_ |= 2;
+        }
+    }
 }
-bool EnumerationConstraint::valid(Solver& s)         { return !optimize() || mini_->valid(s); }
-void EnumerationConstraint::add(Constraint* c)       { if (c) { nogoods_.push_back(c); } }
-bool EnumerationConstraint::integrateBound(Solver& s){ return !mini_ || mini_->integrate(s); }
-bool EnumerationConstraint::optimize() const         { return mini_ && mini_->shared()->optimize(); }
-void EnumerationConstraint::setDisjoint(bool x)      { disjoint_ = x; }
+bool EnumerationConstraint::valid(Solver& s) { return not optimize() || mini_->valid(s); }
+void EnumerationConstraint::add(Constraint* c) {
+    if (c) {
+        nogoods_.push_back(c);
+    }
+}
+bool        EnumerationConstraint::integrateBound(Solver& s) { return not mini_ || mini_->integrate(s); }
+bool        EnumerationConstraint::optimize() const { return mini_ && mini_->shared()->optimize(); }
+void        EnumerationConstraint::setDisjoint(bool x) { disjoint_ = x; }
 Constraint* EnumerationConstraint::cloneAttach(Solver& s) {
-	EnumerationConstraint* c = clone();
-	POTASSCO_REQUIRE(c != 0, "Cloning not supported by Enumerator");
-	c->init(s, mini_ ? const_cast(mini_->shared()) : 0, queue_.get() ? queue_->clone() : 0);
-	return c;
+    EnumerationConstraint* c = clone();
+    POTASSCO_CHECK_PRE(c != nullptr, "Cloning not supported by Enumerator");
+    auto sharedQ = queue_ ? queue_->queue : nullptr;
+    c->init(s, mini_ ? const_cast(mini_->shared()) : nullptr, sharedQ);
+    return c;
 }
 void EnumerationConstraint::end(Solver& s) {
-	if (mini_) { mini_->relax(s, disjointPath()); }
-	state_ = 0;
-	setDisjoint(false);
-	next_.clear();
-	if (s.rootLevel() > root_) { s.popRootLevel(s.rootLevel() - root_); }
+    if (mini_) {
+        mini_->relax(s, disjointPath());
+    }
+    state_ = value_free;
+    setDisjoint(false);
+    next_.clear();
+    if (s.rootLevel() > root_) {
+        s.popRootLevel(s.rootLevel() - root_);
+    }
 }
-bool EnumerationConstraint::start(Solver& s, const LitVec& path, bool disjoint) {
-	state_ = 0;
-	root_  = s.rootLevel();
-	setDisjoint(disjoint);
-	if (s.pushRoot(path, true)) {
-		integrateBound(s);
-		integrateNogoods(s);
-		return true;
-	}
-	return false;
+bool EnumerationConstraint::start(Solver& s, LitView path, bool disjoint) {
+    state_ = value_free;
+    root_  = s.rootLevel();
+    setDisjoint(disjoint);
+    if (s.pushRoot(path, true)) {
+        integrateBound(s);
+        integrateNogoods(s);
+        return true;
+    }
+    return false;
 }
 bool EnumerationConstraint::update(Solver& s) {
-	ValueRep st = state();
-	if (st == value_true) {
-		if (s.restartOnModel()) { s.undoUntil(0); }
-		if (optimize())         { s.strengthenConditional(); }
-	}
-	else if (st == value_false && !s.pushRoot(next_)) {
-		if (!s.hasConflict()) { s.setStopConflict(); }
-		return false;
-	}
-	state_ = 0;
-	next_.clear();
-	do {
-		if (!s.hasConflict() && doUpdate(s) && integrateBound(s) && integrateNogoods(s)) {
-			if (st == value_true) { modelHeuristic(s); }
-			return true;
-		}
-	} while (st != value_free && s.hasConflict() && s.resolveConflict());
-	return false;
+    auto st = state();
+    if (st == value_true) {
+        if (s.restartOnModel()) {
+            s.undoUntil(0);
+        }
+        if (optimize()) {
+            s.strengthenConditional();
+        }
+    }
+    else if (st == value_false && not s.pushRoot(next_)) {
+        if (not s.hasConflict()) {
+            s.setStopConflict();
+        }
+        return false;
+    }
+    state_ = value_free;
+    next_.clear();
+    do {
+        if (not s.hasConflict() && doUpdate(s) && integrateBound(s) && integrateNogoods(s)) {
+            if (st == value_true) {
+                modelHeuristic(s);
+            }
+            return true;
+        }
+    } while (st != value_free && s.hasConflict() && s.resolveConflict());
+    return false;
 }
 bool EnumerationConstraint::integrateNogoods(Solver& s) {
-	if (!queue_.get() || s.hasConflict()) { return !s.hasConflict(); }
-	const uint32 f = ClauseCreator::clause_no_add | ClauseCreator::clause_no_release | ClauseCreator::clause_explicit;
-	for (SharedLiterals* clause; queue_->pop(clause); ) {
-		ClauseCreator::Result res = ClauseCreator::integrate(s, clause, f);
-		if (res.local) { add(res.local);}
-		if (!res.ok()) { return false;  }
-	}
-	return true;
+    if (not queue_ || s.hasConflict()) {
+        return not s.hasConflict();
+    }
+    constexpr auto f = ClauseCreator::clause_no_add | ClauseCreator::clause_no_release | ClauseCreator::clause_explicit;
+    while (SharedLiterals* clause = queue_->pop()) {
+        ClauseCreator::Result res = ClauseCreator::integrate(s, clause, f);
+        if (res.local) {
+            add(res.local);
+        }
+        if (not res.ok()) {
+            return false;
+        }
+    }
+    return true;
 }
 void EnumerationConstraint::destroy(Solver* s, bool x) {
-	if (mini_) { mini_->destroy(s, x); mini_ = 0; }
-	queue_ = 0;
-	Clasp::destroyDB(nogoods_, s, x);
-	Constraint::destroy(s, x);
+    if (mini_) {
+        mini_->destroy(s, x);
+        mini_ = nullptr;
+    }
+    queue_ = nullptr;
+    destroyDB(nogoods_, s, x);
+    Constraint::destroy(s, x);
 }
 bool EnumerationConstraint::simplify(Solver& s, bool reinit) {
-	if (mini_) { mini_->simplify(s, reinit); }
-	simplifyDB(s, nogoods_, reinit);
-	return false;
+    if (mini_) {
+        mini_->simplify(s, reinit);
+    }
+    simplifyDB(s, nogoods_, reinit);
+    return false;
 }
 
 bool EnumerationConstraint::commitModel(Enumerator& ctx, Solver& s) {
-	if (mini_ && !mini_->handleModel(s)){ return false; }
-	if (!ctx.tentative()) { doCommitModel(ctx, s); }
-	state_ |= value_true;
-	return true;
+    if (mini_ && not mini_->handleModel(s)) {
+        return false;
+    }
+    if (not ctx.tentative()) {
+        doCommitModel(ctx, s);
+    }
+    POTASSCO_ASSERT(state_ != value_false);
+    state_ = value_true;
+    return true;
+}
+bool EnumerationConstraint::extractModel(Solver& s, ValueVec& out) {
+    return doExtractModel(s, out, state() == value_true);
 }
+bool EnumerationConstraint::doExtractModel(Solver&, ValueVec&, bool) { return false; }
 bool EnumerationConstraint::commitUnsat(Enumerator& ctx, Solver& s) {
-	next_.clear();
-	s.model.clear();
-	state_ |= value_false;
-	if (mini_) {
-		mini_->handleUnsat(s, !disjointPath(), next_);
-	}
-	if (!ctx.tentative()) {
-		doCommitUnsat(ctx, s);
-	}
-	return !s.hasConflict() || s.decisionLevel() != s.rootLevel();
+    next_.clear();
+    POTASSCO_ASSERT(state_ != value_true);
+    state_ = value_false;
+    if (mini_) {
+        mini_->handleUnsat(s, not disjointPath(), next_);
+    }
+    if (not ctx.tentative()) {
+        doCommitUnsat(ctx, s);
+    }
+    return not s.hasConflict() || s.decisionLevel() != s.rootLevel();
 }
 void EnumerationConstraint::modelHeuristic(Solver& s) {
-	const bool full = heuristic_ > 1;
-	const bool heuristic = full || (heuristic_ == 1 && s.queueSize() == 0 && s.decisionLevel() == s.rootLevel());
-	if (optimize() && heuristic && s.propagate()) {
-		for (const WeightLiteral* w = mini_->shared()->lits; !isSentinel(w->first); ++w) {
-			if (s.value(w->first.var()) == value_free) {
-				s.assume(~w->first);
-				if (!full || !s.propagate()) { break; }
-			}
-		}
-	}
+    const bool full      = heuristic_ > 1;
+    const bool heuristic = full || (heuristic_ == 1 && s.queueSize() == 0 && s.decisionLevel() == s.rootLevel());
+    if (optimize() && heuristic && s.propagate()) {
+        for (const auto& [lit, _] : *mini_->shared()) {
+            if (s.value(lit.var()) == value_free) {
+                s.assume(~lit);
+                if (not full || not s.propagate()) {
+                    break;
+                }
+            }
+        }
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Enumerator
 /////////////////////////////////////////////////////////////////////////////////////////
-void Model::reset() { std::memset(this, 0, sizeof(Model)); }
-Enumerator::Enumerator() : mini_(0), queue_(0) { model_.reset(); }
-Enumerator::~Enumerator()                            { delete queue_; }
-void Enumerator::setDisjoint(Solver& s, bool b)const { constraintRef(s).setDisjoint(b); }
-void Enumerator::setIgnoreSymmetric(bool b)          { model_.sym = static_cast(b == false); }
-void Enumerator::end(Solver& s)                const { constraintRef(s).end(s); }
-void Enumerator::doReset()                           {}
-void Enumerator::reset() {
-	if (mini_) { mini_ = 0; }
-	if (queue_){ delete queue_; queue_ = 0; }
-	model_.reset();
-	model_.ctx  = this;
-	model_.sym  = 1;
-	model_.type = uint32(modelType());
-	model_.sId  = 0;
-	doReset();
+static auto enumCon(const Solver& s) -> EnumerationConstraint& {
+    auto* c = static_cast(s.enumerationConstraint()); // NOLINT(*-pro-type-static-cast-downcast)
+    POTASSCO_CHECK_PRE(c, "Solver not attached");
+    return *c;
 }
-int  Enumerator::init(SharedContext& ctx, OptMode oMode, int limit)  {
-	ctx.master()->setEnumerationConstraint(0);
-	reset();
-	if (oMode != MinimizeMode_t::ignore){ mini_ = ctx.minimize(); }
-	limit      = limit >= 0 ? limit : 1 - int(exhaustive());
-	if (limit != 1) { ctx.setPreserveModels(true); }
-	queue_     = new SharedQueue(ctx.concurrency());
-	ConPtr c   = doInit(ctx, mini_, limit);
-	bool cons  = model_.consequences();
-	if      (tentative())        { model_.type = Model::Sat; }
-	else if (cons && optimize()) { ctx.warn("Optimization: Consequences may depend on enumeration order."); }
-	c->init(*ctx.master(), mini_, new ThreadQueue(*queue_));
-	ctx.master()->setEnumerationConstraint(c);
-	return limit;
-}
-Enumerator::ConRef Enumerator::constraintRef(const Solver& s) const {
-	POTASSCO_ASSERT(s.enumerationConstraint(), "Solver not attached");
-	return static_cast(*s.enumerationConstraint());
+Enumerator::Enumerator()  = default;
+Enumerator::~Enumerator() = default;
+void Enumerator::setDisjoint(Solver& s, bool b) const { enumCon(s).setDisjoint(b); }
+void Enumerator::setIgnoreSymmetric(bool b) { model_.sym = static_cast(b == false); }
+void Enumerator::clearUpdate() { model_.up = 0; }
+void Enumerator::end(Solver& s) const { enumCon(s).end(s); }
+void Enumerator::doReset() {}
+void Enumerator::reset() {
+    mini_ = nullptr;
+    queue_.reset();
+    model_      = {};
+    model_.ctx  = this;
+    model_.sym  = 1;
+    model_.type = static_cast(modelType());
+    model_.sId  = 0;
+    doReset();
 }
-Enumerator::ConPtr Enumerator::constraint(const Solver& s) const {
-	return static_cast(s.enumerationConstraint());
+int Enumerator::init(SharedContext& ctx, OptMode opt, int limit) {
+    ctx.master()->setEnumerationConstraint(nullptr);
+    reset();
+    if (opt != MinimizeMode::ignore) {
+        mini_ = ctx.minimize();
+    }
+    limit = limit >= 0 ? limit : 1 - static_cast(exhaustive());
+    if (limit != 1) {
+        ctx.setPreserveModels(true);
+    }
+    queue_  = std::make_unique(ctx.concurrency());
+    auto* c = doInit(ctx, mini_, limit);
+    if (tentative()) {
+        model_.type = Model::sat;
+    }
+    else if (model_.consequences() && optimize()) {
+        ctx.warn("Optimization: Consequences may depend on enumeration order.");
+    }
+    c->init(*ctx.master(), mini_, queue_.get());
+    ctx.master()->setEnumerationConstraint(c);
+    return limit;
 }
-bool Enumerator::start(Solver& s, const LitVec& path, bool disjointPath) const {
-	return constraintRef(s).start(s, path, disjointPath);
+LowerBound Enumerator::lowerBound() const { return optimize() ? mini_->lowerBound() : LowerBound{}; }
+bool       Enumerator::start(Solver& s, LitView path, bool disjointPath) const {
+    return enumCon(s).start(s, path, disjointPath);
 }
-ValueRep Enumerator::commit(Solver& s) {
-	if      (s.hasConflict() && s.decisionLevel() == s.rootLevel())         { return commitUnsat(s) ? value_free : value_false; }
-	else if (s.numFreeVars() == 0 && s.queueSize() == 0 && !s.hasConflict()){ return commitModel(s) ? value_true : value_free;  }
-	return value_free;
+Val_t Enumerator::commit(Solver& s) {
+    if (s.hasConflict() && s.decisionLevel() == s.rootLevel()) {
+        return commitUnsat(s) ? value_free : value_false;
+    }
+    if (s.numFreeVars() == 0 && s.queueSize() == 0 && not s.hasConflict()) {
+        return commitModel(s) ? value_true : value_free;
+    }
+    return value_free;
 }
 bool Enumerator::commitModel(Solver& s) {
-	assert(s.numFreeVars() == 0 && !s.hasConflict() && s.queueSize() == 0);
-	if (constraintRef(s).commitModel(*this, s)) {
-		s.stats.addModel(s.decisionLevel());
-		if (model_.fin) {
-			model_.num  = 0;
-			model_.type = uint32(modelType());
-			model_.fin  = 0;
-		}
-		++model_.num;
-		setModel(s, false);
-		model_.sId    = s.id();
-		model_.values = &values_;
-		model_.costs  = 0;
-		sym_.clear();
-		if (minimizer()) {
-			costs_.resize(minimizer()->numRules());
-			std::transform(minimizer()->adjust(), minimizer()->adjust()+costs_.size(), minimizer()->sum(), costs_.begin(), std::plus());
-			model_.costs = &costs_;
-		}
-		if (model_.sym && !optimize()) {
-			sym_ = s.symmetric();
-		}
-		return true;
-	}
-	return false;
+    assert(s.numFreeVars() == 0 && not s.hasConflict() && s.queueSize() == 0);
+    if (enumCon(s).commitModel(*this, s)) {
+        s.stats.addModel(s.decisionLevel());
+        if (model_.fin) {
+            model_.num  = 0;
+            model_.type = static_cast(modelType());
+            model_.fin  = 0;
+        }
+        if (model_.type == Model::sat) {
+            s.values(values_);
+        }
+        else {
+            enumCon(s).extractModel(s, values_);
+        }
+        ++model_.num;
+        model_.sId    = s.id();
+        model_.values = values_;
+        model_.costs  = {};
+        sym_.clear();
+        if (minimizer()) {
+            costs_.resize(minimizer()->numRules());
+            std::transform(minimizer()->adjust(), minimizer()->adjust() + costs_.size(), minimizer()->sum(),
+                           costs_.begin(), std::plus{});
+            model_.costs = costs_;
+        }
+        if (model_.sym && not optimize() && s.satPrepro()) {
+            s.satPrepro()->extendModel(values_, sym_);
+        }
+        return true;
+    }
+    return false;
 }
+bool Enumerator::hasSymmetric(const Solver& s) const { return not sym_.empty() && s.satPrepro(); }
 bool Enumerator::commitSymmetric(Solver& s) {
-	if (!sym_.empty() && s.satPrepro()) {
-		s.satPrepro()->extendModel(values_, sym_);
-		s.stats.addModel(s.decisionLevel());
-		++model_.num;
-		return true;
-	}
-	return false;
+    if (hasSymmetric(s)) {
+        s.satPrepro()->extendModel(values_, sym_);
+        s.stats.addModel(s.decisionLevel());
+        ++model_.num;
+        return true;
+    }
+    return false;
 }
 bool Enumerator::commitUnsat(Solver& s) {
-	bool ok = constraintRef(s).commitUnsat(*this, s);
-	if (ok && !s.model.empty() && unsatType() == unsat_sync) {
-		setModel(s, true);
-	}
-	sym_.clear();
-	return ok;
+    auto& c  = enumCon(s);
+    bool  ok = c.commitUnsat(*this, s);
+    if (ok && not model_.values.empty() && model_.type != Model::sat && c.extractModel(s, values_)) {
+        model_.up = 1;
+    }
+    sym_.clear();
+    return ok;
 }
-bool Enumerator::commitClause(const LitVec& clause)  const {
-	return queue_ && queue_->pushRelaxed(SharedLiterals::newShareable(clause, Constraint_t::Other));
+bool Enumerator::commitClause(LitView clause) const {
+    return queue_ && queue_->pushRelaxed(SharedLiterals::newShareable(clause, ConstraintType::other));
 }
 bool Enumerator::commitComplete() {
-	if (enumerated()) {
-		model_.fin  = 1;
-		if (tentative()) {
-			mini_->markOptimal();
-			model_.opt = 1;
-			return false;
-		}
-		model_.opt |= uint32(optimize());
-		model_.def |= uint32(model_.consequences());
-	}
-	return true;
-}
-bool Enumerator::update(Solver& s) const {
-	return constraintRef(s).update(s);
+    if (enumerated()) {
+        model_.fin = 1;
+        if (tentative()) {
+            mini_->markOptimal();
+            model_.opt = 1;
+            return false;
+        }
+        model_.opt |= static_cast(optimize());
+        model_.def |= static_cast(model_.consequences());
+    }
+    return true;
 }
+bool Enumerator::update(Solver& s) const { return enumCon(s).update(s); }
 bool Enumerator::supportsSplitting(const SharedContext& ctx) const {
-	if (!optimize()) { return true; }
-	const Configuration* config = ctx.configuration();
-	bool ok = true;
-	for (uint32 i = 0; i != ctx.concurrency() && ok; ++i) {
-		if      (ctx.hasSolver(i) && constraint(*ctx.solver(i))){ ok = constraint(*ctx.solver(i))->minimizer()->supportsSplitting(); }
-		else if (config && i < config->numSolver())             { ok = config->solver(i).opt.supportsSplitting(); }
-	}
-	return ok;
-}
-int Enumerator::unsatType() const {
-	return !optimize() ? unsat_stop : unsat_cont;
-}
-void Enumerator::setModel(Solver& s, bool up) {
-	model_.up = up;
-	values_.swap(s.model);
-	s.model.clear();
+    if (not optimize()) {
+        return true;
+    }
+    const auto* config = ctx.configuration();
+    return std::ranges::all_of(irange(ctx.concurrency()), [&](auto idx) {
+        if (const Solver* s = ctx.hasSolver(idx) ? ctx.solver(idx) : nullptr; s && s->enumerationConstraint()) {
+            return enumCon(*s).minimizer()->supportsSplitting();
+        }
+        return not config || idx >= config->numSolver() || config->solver(idx).opt.supportsSplitting();
+    });
 }
+int Enumerator::unsatType() const { return not optimize() ? unsat_stop : unsat_cont; }
 /////////////////////////////////////////////////////////////////////////////////////////
 // EnumOptions
 /////////////////////////////////////////////////////////////////////////////////////////
 Enumerator* EnumOptions::createEnumerator(const EnumOptions& opts) {
-	if      (opts.models())      { return createModelEnumerator(opts);}
-	else if (opts.consequences()){ return createConsEnumerator(opts); }
-	else                         { return nullEnumerator(); }
+    if (opts.models()) {
+        return createModelEnumerator(opts);
+    }
+    if (opts.consequences()) {
+        return createConsEnumerator(opts);
+    }
+    return nullEnumerator();
 }
 Enumerator* EnumOptions::nullEnumerator() {
-	struct NullEnum : Enumerator {
-		ConPtr doInit(SharedContext&, SharedMinimizeData*, int) {
-			struct Constraint : public EnumerationConstraint {
-				Constraint() : EnumerationConstraint() {}
-				ConPtr      clone()          { return new Constraint(); }
-				bool        doUpdate(Solver&){ return true; }
-			};
-			return new Constraint();
-		}
-	};
-	return new NullEnum;
+    struct NullEnum : Enumerator {
+        ConPtr doInit(SharedContext&, SharedMinimizeData*, int) override {
+            struct NullCon : EnumerationConstraint {
+                NullCon() = default;
+                ConPtr clone() override { return new NullCon(); }
+                bool   doUpdate(Solver&) override { return true; }
+            };
+            return new NullCon();
+        }
+    };
+    return new NullEnum;
 }
 
 const char* modelType(const Model& m) {
-	switch (m.type) {
-		case Model::Sat     : return "Model";
-		case Model::Brave   : return "Brave";
-		case Model::Cautious: return "Cautious";
-		case Model::User    : return "User";
-		default: return 0;
-	}
+    switch (m.type) {
+        case Model::sat     : return "Model";
+        case Model::brave   : return "Brave";
+        case Model::cautious: return "Cautious";
+        case Model::user    : return "User";
+        default             : return nullptr;
+    }
 }
 
-}
+} // namespace Clasp
diff --git a/src/heuristics.cpp b/src/heuristics.cpp
index b04223f..7819260 100644
--- a/src/heuristics.cpp
+++ b/src/heuristics.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,952 +22,1044 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
+
 #include 
+#include 
+
 #include 
-#include 
+#include 
 #include 
+#include 
 #include 
 #include 
-#include 
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Lookback selection strategies
 /////////////////////////////////////////////////////////////////////////////////////////
-uint32 momsScore(const Solver& s, Var v) {
-	uint32 sc;
-	if (s.sharedContext()->numBinary()) {
-		uint32 s1 = s.estimateBCP(posLit(v), 0) - 1;
-		uint32 s2 = s.estimateBCP(negLit(v), 0) - 1;
-		sc = ((s1 * s2)<<10) + (s1 + s2);
-	}
-	else {
-		// problem does not contain binary constraints - fall back to counting watches
-		uint32 s1 = s.numWatches(posLit(v));
-		uint32 s2 = s.numWatches(negLit(v));
-		sc = ((s1 * s2)<<10) + (s1 + s2);
-	}
-	return sc;
-}
-static void addOther(TypeSet& t, uint32 other) {
-	if (other != HeuParams::other_no)  { t.addSet(Constraint_t::Loop); }
-	if (other == HeuParams::other_all) { t.addSet(Constraint_t::Other); }
+uint32_t momsScore(const Solver& s, Var_t v) {
+    uint32_t sc[2];
+    if (s.sharedContext()->numBinary()) {
+        sc[0] = s.estimateBCP(posLit(v), 0) - 1;
+        sc[1] = s.estimateBCP(negLit(v), 0) - 1;
+    }
+    else {
+        // problem does not contain binary constraints - fall back to counting watches
+        sc[0] = s.numWatches(posLit(v));
+        sc[1] = s.numWatches(negLit(v));
+    }
+    return ((sc[0] * sc[1]) << 10) + (sc[0] + sc[1]);
+}
+static void addOther(TypeSet& t, uint32_t other) {
+    if (other != HeuParams::other_no) {
+        t.add(ConstraintType::loop);
+    }
+    if (other == HeuParams::other_all) {
+        t.add(ConstraintType::other);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Berkmin selection strategy
 /////////////////////////////////////////////////////////////////////////////////////////
-#define BERK_NUM_CANDIDATES 5
-#define BERK_CACHE_GROW 2.0
-#define BERK_MAX_MOMS_VARS 9999
-#define BERK_MAX_MOMS_DECS 50
-#define BERK_MAX_DECAY 65534
+static constexpr auto berk_num_candidates = 5;
+static constexpr auto berk_cache_grow     = 2.0;
+static constexpr auto berk_max_moms_vars  = 9999;
+static constexpr auto berk_max_moms_decs  = 50;
+static constexpr auto berk_max_decay      = 65534;
 
 ClaspBerkmin::ClaspBerkmin(const HeuParams& params)
-	: topConflict_(UINT32_MAX)
-	, topOther_(UINT32_MAX)
-	, front_(1)
-	, cacheSize_(5)
-	, numVsids_(0) {
-	ClaspBerkmin::setConfig(params);
+    : topConflict_(UINT32_MAX)
+    , topOther_(UINT32_MAX)
+    , front_(1)
+    , cacheFront_()
+    , cacheSize_(5)
+    , numVsids_(0)
+    , maxBerkmin_(0) {
+    ClaspBerkmin::setConfig(params);
 }
 
 void ClaspBerkmin::setConfig(const HeuParams& params) {
-	maxBerkmin_     = params.param == 0 ? UINT32_MAX : params.param;
-	order_.nant     = params.nant != 0;
-	order_.huang    = params.huang != 0;
-	order_.resScore = params.score == HeuParams::score_auto ? static_cast(HeuParams::score_multi_set) : params.score;
-	addOther(types_ = TypeSet(), params.other);
-	if (params.moms) { types_.addSet(Constraint_t::Static); }
-}
-
-Var ClaspBerkmin::getMostActiveFreeVar(const Solver& s) {
-	++numVsids_;
-	// first: check for a cache hit
-	for (Pos end = cache_.end(); cacheFront_ != end; ++cacheFront_) {
-		if (s.value(*cacheFront_) == value_free) {
-			return *cacheFront_;
-		}
-	}
-	// Second: cache miss - refill cache with most active vars
-	if (!cache_.empty() && cacheSize_ < s.numFreeVars()/10) {
-		cacheSize_ = static_cast( (cacheSize_*BERK_CACHE_GROW) + .5 );
-	}
-	cache_.clear();
-	Order::Compare  comp(&order_);
-	// Pre: At least one unassigned var!
-	for (; s.value( front_ ) != value_free; ++front_) {;}
-	Var v = front_;
-	LitVec::size_type cs = std::min(cacheSize_, s.numFreeVars());
-	for (;;) { // add first cs free variables to cache
-		cache_.push_back(v);
-		std::push_heap(cache_.begin(), cache_.end(), comp);
-		if (cache_.size() == cs) break;
-		while ( s.value(++v) != value_free ) {;} // skip over assigned vars
-	}
-	for (v = (cs == cacheSize_ ? v+1 : s.numVars()+1); v <= s.numVars(); ++v) {
-		// replace vars with low activity
-		if (s.value(v) == value_free && comp(v, cache_[0])) {
-			std::pop_heap(cache_.begin(), cache_.end(), comp);
-			cache_.back() = v;
-			std::push_heap(cache_.begin(), cache_.end(), comp);
-		}
-	}
-	std::sort_heap(cache_.begin(), cache_.end(), comp);
-	return *(cacheFront_ = cache_.begin());
-}
-
-Var ClaspBerkmin::getTopMoms(const Solver& s) {
-	// Pre: At least one unassigned var!
-	for (; s.value( front_ ) != value_free; ++front_) {;}
-	Var var   = front_;
-	uint32 ms = momsScore(s, var);
-	uint32 ls = 0;
-	for (Var v = var+1; v <= s.numProblemVars(); ++v) {
-		if (s.value(v) == value_free && (ls = momsScore(s, v)) > ms) {
-			var = v;
-			ms  = ls;
-		}
-	}
-	if (++numVsids_ >= BERK_MAX_MOMS_DECS || ms < 2) {
-		// Scores are not relevant or too many moms-based decisions
-		// - disable MOMS
-		hasActivities(true);
-	}
-	return var;
+    maxBerkmin_  = params.param == 0 ? UINT32_MAX : params.param;
+    order_.nant  = params.nant != 0;
+    order_.huang = params.huang != 0;
+    order_.resScore =
+        params.score == HeuParams::score_auto ? static_cast(HeuParams::score_multi_set) : params.score;
+    addOther(types_ = TypeSet(), params.other);
+    if (params.moms) {
+        types_.add(ConstraintType::static_);
+    }
+}
+
+Var_t ClaspBerkmin::getMostActiveFreeVar(const Solver& s) {
+    ++numVsids_;
+    // first: check for a cache hit
+    for (Pos end = cache_.end(); cacheFront_ != end; ++cacheFront_) {
+        if (s.value(*cacheFront_) == value_free) {
+            return *cacheFront_;
+        }
+    }
+    // Second: cache miss - refill cache with most active vars
+    if (not cache_.empty() && cacheSize_ < s.numFreeVars() / 10) {
+        cacheSize_ = static_cast((cacheSize_ * berk_cache_grow) + .5); // NOLINT(*-incorrect-roundings)
+    }
+    cache_.clear();
+    Order::Compare comp(&order_);
+    // Pre: At least one unassigned var!
+    for (; s.value(front_) != value_free; ++front_) { ; }
+    auto v = front_;
+    assert(cacheSize_);
+    for (auto cs = std::min(cacheSize_, s.numFreeVars());;) { // add first cs free variables to cache
+        cache_.push_back(v);
+        std::ranges::push_heap(cache_, comp);
+        if (--cs == 0) {
+            for (auto n : s.vars(v + 1)) {
+                // replace vars with low activity
+                if (s.value(n) == value_free && comp(n, cache_[0])) {
+                    std::ranges::pop_heap(cache_, comp);
+                    cache_.back() = n;
+                    std::ranges::push_heap(cache_, comp);
+                }
+            }
+            std::ranges::sort_heap(cache_, comp);
+            return *(cacheFront_ = cache_.begin());
+        }
+        while (s.value(++v) != value_free) { ; } // skip over assigned vars
+    }
+}
+
+Var_t ClaspBerkmin::getTopMoms(const Solver& s) {
+    // Pre: At least one unassigned var!
+    for (; s.value(front_) != value_free; ++front_) { ; }
+    Var_t    var = front_;
+    uint32_t ms  = momsScore(s, var);
+    for (auto v = var + 1; v <= s.numProblemVars(); ++v) {
+        if (uint32_t ls = s.value(v) == value_free ? momsScore(s, v) : 0u; ls > ms) {
+            var = v;
+            ms  = ls;
+        }
+    }
+    if (++numVsids_ >= berk_max_moms_decs || ms < 2) {
+        // Scores are not relevant or too many moms-based decisions
+        // - disable MOMS
+        hasActivities(true);
+    }
+    return var;
 }
 
 void ClaspBerkmin::startInit(const Solver& s) {
-	if (order_.score.empty()) {
-		rng_.srand(s.rng.seed());
-	}
-	order_.score.resize(s.numVars()+1);
-	initHuang(order_.huang);
+    if (order_.score.empty()) {
+        rng_.srand(s.rng.seed());
+    }
+    order_.score.resize(s.numVars() + 1);
+    initHuang(order_.huang);
 
-	cache_.clear();
-	cacheSize_ = 5;
-	cacheFront_ = cache_.end();
+    cache_.clear();
+    cacheSize_  = 5;
+    cacheFront_ = cache_.end();
 
-	freeLits_.clear();
-	freeOtherLits_.clear();
-	topConflict_ = topOther_ = (uint32)-1;
+    freeLits_.clear();
+    freeOtherLits_.clear();
+    topConflict_ = topOther_ = static_cast(-1);
 
-	front_    = 1;
-	numVsids_ = 0;
+    front_    = 1;
+    numVsids_ = 0;
 }
 
 void ClaspBerkmin::endInit(Solver& s) {
-	if (initHuang()) {
-		const bool clearScore = types_.inSet(Constraint_t::Static);
-		// initialize preferred values of vars
-		cache_.clear();
-		for (Var v = 1; v <= s.numVars(); ++v) {
-			order_.decayedScore(v);
-			if (order_.occ(v) != 0 && s.pref(v).get(ValueSet::saved_value) == value_free) {
-				s.setPref(v, ValueSet::saved_value, order_.occ(v) > 0 ? value_true : value_false);
-			}
-			if   (clearScore) order_.score[v] = HScore(order_.decay);
-			else cache_.push_back(v);
-		}
-		initHuang(false);
-	}
-	if (!types_.inSet(Constraint_t::Static) || s.numFreeVars() > BERK_MAX_MOMS_VARS) {
-		hasActivities(true);
-	}
-	std::stable_sort(cache_.begin(), cache_.end(), Order::Compare(&order_));
-	cacheFront_ = cache_.begin();
-}
-
-void ClaspBerkmin::updateVar(const Solver& s, Var v, uint32 n) {
-	if (s.validVar(v)) { growVecTo(order_.score, v+n); }
-	front_ = 1;
-	cache_.clear();
-	cacheFront_ = cache_.end();
-}
-
-void ClaspBerkmin::newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t) {
-	if (t == Constraint_t::Conflict) { hasActivities(true); }
-	if ((t == Constraint_t::Conflict && order_.resScore == HeuParams::score_min)
-	 || (t == Constraint_t::Static   && order_.huang)) {
-		for (const Literal* x = first, *end = first+size; x != end; ++x) {
-			order_.inc(*x, s.varInfo(x->var()).nant());
-		}
-	}
-	if (t != Constraint_t::Static && !order_.huang) {
-		for (const Literal* x = first, *end = first+size; x != end; ++x) { order_.incOcc(*x); }
-	}
-}
-
-void ClaspBerkmin::updateReason(const Solver& s, const LitVec& lits, Literal r) {
-	if (order_.resScore > HeuParams::score_min) {
-		const bool ms = order_.resScore == HeuParams::score_multi_set;
-		for (LitVec::size_type i = 0, e = lits.size(); i != e; ++i) {
-			if (ms || !s.seen(lits[i])) { order_.inc(~lits[i], s.varInfo(lits[i].var()).nant()); }
-		}
-	}
-	if ((order_.resScore & 1u) != 0 && !isSentinel(r)) {
-		order_.inc(r, s.varInfo(r.var()).nant());
-	}
-}
-
-bool ClaspBerkmin::bump(const Solver& s, const WeightLitVec& lits, double adj) {
-	for (WeightLitVec::const_iterator it = lits.begin(), end = lits.end(); it != end; ++it) {
-		if (!order_.nant || s.varInfo(it->first.var()).nant()) {
-			uint32 xf = order_.decayedScore(it->first.var()) + static_cast(it->second*adj);
-			order_.score[it->first.var()].act = (uint16)std::min(xf, UINT32_MAX>>16);
-		}
-	}
-	return true;
-}
-
-void ClaspBerkmin::undoUntil(const Solver&, LitVec::size_type) {
-	topConflict_ = topOther_ = static_cast(-1);
-	front_ = 1;
-	cache_.clear();
-	cacheFront_ = cache_.end();
-	if (cacheSize_ > 5 && numVsids_ > 0 && (numVsids_*3) < cacheSize_) {
-		cacheSize_ = static_cast(cacheSize_/BERK_CACHE_GROW);
-	}
-	numVsids_ = 0;
-}
-
-bool ClaspBerkmin::hasTopUnsat(Solver& s) {
-	topConflict_  = std::min(s.numLearntConstraints(), topConflict_);
-	topOther_     = std::min(s.numLearntConstraints(), topOther_);
-	assert(topConflict_ <= topOther_);
-	freeOtherLits_.clear();
-	freeLits_.clear();
-	TypeSet ts = types_;
-	if (ts.m > 1) {
-		while (topOther_ > topConflict_) {
-			if (s.getLearnt(topOther_-1).isOpen(s, ts, freeLits_) != 0) {
-				freeLits_.swap(freeOtherLits_);
-				ts.m = 0;
-				break;
-			}
-			--topOther_;
-			freeLits_.clear();
-		}
-	}
-	ts.addSet(Constraint_t::Conflict);
-	uint32 stopAt = topConflict_ < maxBerkmin_ ? 0 : topConflict_ - maxBerkmin_;
-	while (topConflict_ != stopAt) {
-		uint32 x = s.getLearnt(topConflict_-1).isOpen(s, ts, freeLits_);
-		if (x != 0) {
-			if (x == Constraint_t::Conflict) { break; }
-			topOther_  = topConflict_;
-			freeLits_.swap(freeOtherLits_);
-			ts.m = 0;
-			ts.addSet(Constraint_t::Conflict);
-		}
-		--topConflict_;
-		freeLits_.clear();
-	}
-	if (freeOtherLits_.empty())  topOther_ = topConflict_;
-	if (freeLits_.empty())      freeOtherLits_.swap(freeLits_);
-	return !freeLits_.empty();
+    if (initHuang()) {
+        const bool clearScore = types_.contains(ConstraintType::static_);
+        // initialize preferred values of vars
+        cache_.clear();
+        for (auto v : s.vars()) {
+            order_.decayedScore(v);
+            if (order_.occ(v) != 0 && s.pref(v).get(ValueSet::saved_value) == value_free) {
+                s.setPref(v, ValueSet::saved_value, order_.occ(v) > 0 ? value_true : value_false);
+            }
+            if (clearScore) {
+                order_.score[v] = HScore(order_.decay);
+            }
+            else {
+                cache_.push_back(v);
+            }
+        }
+        initHuang(false);
+    }
+    if (not types_.contains(ConstraintType::static_) || s.numFreeVars() > berk_max_moms_vars) {
+        hasActivities(true);
+    }
+    std::ranges::stable_sort(cache_, Order::Compare(&order_));
+    cacheFront_ = cache_.begin();
+}
+
+void ClaspBerkmin::updateVar(const Solver& s, Var_t v, uint32_t n) {
+    if (s.validVar(v)) {
+        growVecTo(order_.score, v + n);
+    }
+    front_ = 1;
+    cache_.clear();
+    cacheFront_ = cache_.end();
+}
+
+void ClaspBerkmin::newConstraint(const Solver& s, LitView lits, ConstraintType t) {
+    if (t == ConstraintType::conflict) {
+        hasActivities(true);
+    }
+    if ((t == ConstraintType::conflict && order_.resScore == HeuParams::score_min) ||
+        (t == ConstraintType::static_ && order_.huang)) {
+        for (const auto& lit : lits) { order_.inc(lit, s.varInfo(lit.var()).nant()); }
+    }
+    if (t != ConstraintType::static_ && not order_.huang) {
+        for (const auto& lit : lits) { order_.incOcc(lit); }
+    }
+}
+
+void ClaspBerkmin::updateReason(const Solver& s, LitView lits, Literal r) {
+    if (order_.resScore > HeuParams::score_min) {
+        const bool ms = order_.resScore == HeuParams::score_multi_set;
+        for (auto lit : lits) {
+            if (ms || not s.seen(lit)) {
+                order_.inc(~lit, s.varInfo(lit.var()).nant());
+            }
+        }
+    }
+    if ((order_.resScore & 1u) != 0 && not isSentinel(r)) {
+        order_.inc(r, s.varInfo(r.var()).nant());
+    }
+}
+
+bool ClaspBerkmin::bump(const Solver& s, WeightLitView lits, double adj) {
+    for (const auto& lit : lits) {
+        if (not order_.nant || s.varInfo(lit.lit.var()).nant()) {
+            auto     sc = lit.weight * adj;
+            uint32_t xf = order_.decayedScore(lit.lit.var()) + (sc > 0 ? static_cast(sc) : 0u);
+            order_.score[lit.lit.var()].act = static_cast(std::min(xf, UINT32_MAX >> 16));
+        }
+    }
+    return true;
+}
+
+void ClaspBerkmin::undo(const Solver&, LitView) {
+    topConflict_ = topOther_ = static_cast(-1);
+    front_                   = 1;
+    cache_.clear();
+    cacheFront_ = cache_.end();
+    if (cacheSize_ > 5 && numVsids_ > 0 && (numVsids_ * 3) < cacheSize_) {
+        cacheSize_ = static_cast(cacheSize_ / berk_cache_grow);
+    }
+    numVsids_ = 0;
+}
+
+bool ClaspBerkmin::hasTopUnsat(const Solver& s) {
+    static constexpr auto conflict_set = TypeSet{ConstraintType::conflict};
+
+    topConflict_ = std::min(s.numLearntConstraints(), topConflict_);
+    topOther_    = std::min(s.numLearntConstraints(), topOther_);
+    assert(topConflict_ <= topOther_);
+    freeOtherLits_.clear();
+    freeLits_.clear();
+    TypeSet ts = types_;
+    if (ts > conflict_set) {
+        while (topOther_ > topConflict_) {
+            if (s.getLearnt(topOther_ - 1).isOpen(s, ts, freeLits_) != 0) {
+                freeLits_.swap(freeOtherLits_);
+                ts.clear();
+                break;
+            }
+            --topOther_;
+            freeLits_.clear();
+        }
+    }
+    ts.add(ConstraintType::conflict);
+    uint32_t stopAt = topConflict_ < maxBerkmin_ ? 0 : topConflict_ - maxBerkmin_;
+    while (topConflict_ != stopAt) {
+        if (uint32_t x = s.getLearnt(topConflict_ - 1).isOpen(s, ts, freeLits_); x != 0) {
+            if (x == ConstraintType::conflict) {
+                break;
+            }
+            topOther_ = topConflict_;
+            freeLits_.swap(freeOtherLits_);
+            ts = conflict_set;
+        }
+        --topConflict_;
+        freeLits_.clear();
+    }
+    if (freeOtherLits_.empty()) {
+        topOther_ = topConflict_;
+    }
+    if (freeLits_.empty()) {
+        freeOtherLits_.swap(freeLits_);
+    }
+    return not freeLits_.empty();
 }
 
 Literal ClaspBerkmin::doSelect(Solver& s) {
-	const uint32 decayMask = order_.huang ? 127 : 511;
-	if ( ((s.stats.choices + 1)&decayMask) == 0 ) {
-		if ((order_.decay += (1+!order_.huang)) == BERK_MAX_DECAY) {
-			order_.resetDecay();
-		}
-	}
-	if (hasTopUnsat(s)) {        // Berkmin based decision
-		assert(!freeLits_.empty());
-		Literal x = selectRange(s, &freeLits_[0], &freeLits_[0]+freeLits_.size());
-		return selectLiteral(s, x.var(), false);
-	}
-	else if (hasActivities()) {  // Vsids based decision
-		return selectLiteral(s, getMostActiveFreeVar(s), true);
-	}
-	else {                       // Moms based decision
-		return selectLiteral(s, getTopMoms(s), true);
-	}
-}
-
-Literal ClaspBerkmin::selectRange(Solver& s, const Literal* first, const Literal* last) {
-	Literal candidates[BERK_NUM_CANDIDATES];
-	candidates[0] = *first;
-	uint32 c = 1;
-	uint32  ms  = static_cast(-1);
-	uint32  ls  = 0;
-	for (++first; first != last; ++first) {
-		Var v = first->var();
-		assert(s.value(v) == value_free);
-		int cmp = order_.compare(v, candidates[0].var());
-		if (cmp > 0) {
-			candidates[0] = *first;
-			c = 1;
-			ms  = static_cast(-1);
-		}
-		else if (cmp == 0) {
-			if (ms == static_cast(-1)) ms = momsScore(s, candidates[0].var());
-			if ( (ls = momsScore(s,v)) > ms) {
-				candidates[0] = *first;
-				c = 1;
-				ms  = ls;
-			}
-			else if (ls == ms && c != BERK_NUM_CANDIDATES) {
-				candidates[c++] = *first;
-			}
-		}
-	}
-	return c == 1 ? candidates[0] : candidates[rng_.irand(c)];
-}
-
-Literal ClaspBerkmin::selectLiteral(Solver& s, Var v, bool vsids) const {
-	ValueSet pref = s.pref(v);
-	int signScore = order_.occ(v);
-	if (order_.huang && std::abs(signScore) > 32 && !pref.has(ValueSet::user_value)) {
-		return Literal(v, signScore < 0);
-	}
-	// compute expensive sign score only if necessary
-	if (vsids && !pref.has(ValueSet::user_value | ValueSet::pref_value | ValueSet::saved_value)) {
-		int32 w0 = (int32)s.estimateBCP(posLit(v), 5);
-		int32 w1 = (int32)s.estimateBCP(negLit(v), 5);
-		if (w1 != 1 || w0 != w1) { signScore = w0 - w1; }
-	}
-	return DecisionHeuristic::selectLiteral(s, v, signScore);
+    const uint32_t decayMask = order_.huang ? 127 : 511;
+    if (not Potassco::test_any(s.stats.choices + 1, decayMask)) {
+        if (auto dec = order_.decay += (1 + not order_.huang); dec == berk_max_decay) {
+            order_.resetDecay();
+        }
+    }
+    if (hasTopUnsat(s)) { // Berkmin based decision
+        assert(not freeLits_.empty());
+        Literal x = selectRange(s, freeLits_);
+        return selectLiteral(s, x.var(), false);
+    }
+    if (hasActivities()) { // Vsids based decision
+        return selectLiteral(s, getMostActiveFreeVar(s), true);
+    }
+    // Moms based decision
+    return selectLiteral(s, getTopMoms(s), true);
+}
+
+Literal ClaspBerkmin::selectRange(Solver& s, LitView range) {
+    Literal candidates[berk_num_candidates];
+    candidates[0] = range[0];
+    uint32_t c    = 1;
+    auto     ms   = static_cast(-1);
+    for (auto lit : drop(range, 1u)) {
+        auto v = lit.var();
+        assert(s.value(v) == value_free);
+        if (int cmp = order_.compare(v, candidates[0].var()); cmp > 0) {
+            candidates[0] = lit;
+            c             = 1;
+            ms            = static_cast(-1);
+        }
+        else if (cmp == 0) {
+            if (ms == static_cast(-1)) {
+                ms = momsScore(s, candidates[0].var());
+            }
+            if (uint32_t ls = momsScore(s, v); ls > ms) {
+                candidates[0] = lit;
+                c             = 1;
+                ms            = ls;
+            }
+            else if (ls == ms && c != berk_num_candidates) {
+                candidates[c++] = lit;
+            }
+        }
+    }
+    return c == 1 ? candidates[0] : candidates[rng_.irand(c)];
+}
+
+Literal ClaspBerkmin::selectLiteral(const Solver& s, Var_t v, bool vsids) const {
+    ValueSet pref      = s.pref(v);
+    int      signScore = order_.occ(v);
+    if (order_.huang && std::abs(signScore) > 32 && not pref.has(ValueSet::user_value)) {
+        return {v, signScore < 0};
+    }
+    // compute expensive sign score only if necessary
+    if (vsids && not pref.has(ValueSet::user_value | ValueSet::pref_value | ValueSet::saved_value)) {
+        auto w0 = static_cast(s.estimateBCP(posLit(v), 5));
+        auto w1 = static_cast(s.estimateBCP(negLit(v), 5));
+        if (w1 != 1 || w0 != w1) {
+            signScore = w0 - w1;
+        }
+    }
+    return DecisionHeuristic::selectLiteral(s, v, signScore);
 }
 
 void ClaspBerkmin::Order::resetDecay() {
-	for (Scores::size_type i = 1, end = score.size(); i < end; ++i) {
-		decayedScore(i);
-		score[i].dec = 0;
-	}
-	decay = 0;
+    for (auto i : irange(1u, size32(score))) {
+        decayedScore(i);
+        score[i].dec = 0;
+    }
+    decay = 0;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspVmtf selection strategy
 /////////////////////////////////////////////////////////////////////////////////////////
-ClaspVmtf::ClaspVmtf(const HeuParams& params) : decay_(0), nList_(0) {
-	ClaspVmtf::setConfig(params);
-}
+ClaspVmtf::ClaspVmtf(const HeuParams& params) { ClaspVmtf::setConfig(params); }
 
 void ClaspVmtf::setConfig(const HeuParams& params) {
-	nMove_  = params.param ? std::max(params.param, uint32(2)) : 8u;
-	scType_ = params.score != HeuParams::score_auto ? params.score : static_cast(HeuParams::score_min);
-	nant_   = params.nant != 0;
-	addOther(types_ = TypeSet(), params.other != HeuParams::other_auto ? params.other : static_cast(HeuParams::other_no));
-	if (params.moms) { types_.addSet(Constraint_t::Static); }
-	if (scType_ == HeuParams::score_min) { types_.addSet(Constraint_t::Conflict); }
-}
-
-void ClaspVmtf::startInit(const Solver& s) {
-	score_.resize(s.numVars()+1, VarInfo());
-}
-
-void ClaspVmtf::addToList(Var v) {
-	assert(v && v < score_.size() && !score_[v].inList());
-	VarInfo& link    = score_[v];
-	Var   tl         = score_[0].prev_;
-	link.next_       = 0;
-	link.prev_       = tl;
-	score_[tl].next_ = v;
-	score_[0].prev_  = v;
-	++nList_;
-}
-
-void ClaspVmtf::removeFromList(Var v) {
-	assert(v && v < score_.size() && score_[v].inList());
-	VarInfo& link    = score_[v];
-	score_[link.next_].prev_ = link.prev_;
-	score_[link.prev_].next_ = link.next_;
-	link.prev_ = link.next_ = 0;
-	--nList_;
-}
-
-void ClaspVmtf::moveToFront(Var v) {
-	if (score_[0].next_ == v)
-		return;
-	removeFromList(v);
-	Var ph = score_[0].next_;
-	score_[v].next_  = ph;
-	score_[ph].prev_ = v;
-	score_[0].next_  = v;
-	score_[v].prev_  = 0;
-	++nList_;
+    nMove_  = params.param ? std::max(params.param, 2u) : 8u;
+    scType_ = params.score != HeuParams::score_auto ? params.score : static_cast(HeuParams::score_min);
+    nant_   = params.nant != 0;
+    addOther(types_ = TypeSet(),
+             params.other != HeuParams::other_auto ? params.other : static_cast(HeuParams::other_no));
+    if (params.moms) {
+        types_.add(ConstraintType::static_);
+    }
+    if (scType_ == HeuParams::score_min) {
+        types_.add(ConstraintType::conflict);
+    }
+}
+
+void ClaspVmtf::startInit(const Solver& s) { score_.resize(s.numVars() + 1, VarInfo()); }
+
+void ClaspVmtf::addToList(Var_t v) {
+    assert(v && v < score_.size() && not score_[v].inList());
+    VarInfo& link   = score_[v];
+    Var_t    tl     = score_[0].prev;
+    link.next       = 0;
+    link.prev       = tl;
+    score_[tl].next = v;
+    score_[0].prev  = v;
+    ++nList_;
+}
+
+void ClaspVmtf::removeFromList(Var_t v) {
+    assert(v && v < score_.size() && score_[v].inList());
+    VarInfo& link          = score_[v];
+    score_[link.next].prev = link.prev;
+    score_[link.prev].next = link.next;
+    link.prev = link.next = 0;
+    --nList_;
+}
+
+void ClaspVmtf::moveToFront(Var_t v) {
+    if (score_[0].next == v) {
+        return;
+    }
+    removeFromList(v);
+    Var_t ph        = score_[0].next;
+    score_[v].next  = ph;
+    score_[ph].prev = v;
+    score_[0].next  = v;
+    score_[v].prev  = 0;
+    ++nList_;
 }
 
 void ClaspVmtf::endInit(Solver& s) {
-	bool moms = types_.inSet(Constraint_t::Static);
-	if (!moms) {
-		// - add all new vars
-		for (Var v = 1; v <= s.numVars(); ++v) {
-			if (s.value(v) == value_free) {
-				score_[v].activity(decay_);
-				if (!score_[v].inList()) {
-					addToList(v);
-				}
-			}
-		}
-	}
-	else {
-		// - set activity of all vars not in list to moms
-		// - append new vars in moms-activity order
-		const uint32 momsStamp = decay_ + 1;
-		const uint32 assumeNew = (s.numVars() + 1) - nList_;
-		VarVec vars;
-		vars.reserve(assumeNew);
-		for (Var v = 1; v <= s.numVars(); ++v) {
-			if (s.value(v) == value_free) {
-				score_[v].activity(decay_);
-				if (!score_[v].inList()) {
-					score_[v].activity_ = momsScore(s, v);
-					score_[v].decay_    = momsStamp;
-					vars.push_back(v);
-				}
-			}
-		}
-		std::stable_sort(vars.begin(), vars.end(), LessLevel(s, score_));
-		for (VarVec::iterator it = vars.begin(); it != vars.end(); ++it) {
-			addToList(*it);
-			if (score_[*it].decay_ == momsStamp) {
-				score_[*it].activity_ = 0;
-				score_[*it].decay_    = decay_;
-			}
-		}
-	}
-	front_ = getFront();
-}
-
-void ClaspVmtf::updateVar(const Solver& s, Var v, uint32 n) {
-	if (s.validVar(v)) {
-		growVecTo(score_, v+n, VarInfo());
-		for (uint32 end = v+n; v != end; ++v) {
-			if (!score_[v].inList()) { addToList(v); }
-			else                     { front_ = getFront(); }
-		}
-	}
-	else if (v < score_.size()) {
-		if ((v + n) > score_.size()) { n = score_.size() - v; }
-		for (uint32 x = v + n; x-- != v; ) {
-			if (score_[x].inList()) {
-				removeFromList(x);
-			}
-		}
-	}
-}
-
-void ClaspVmtf::simplify(const Solver& s, LitVec::size_type i) {
-	for (; i < s.numAssignedVars(); ++i) {
-		if (score_[s.trail()[i].var()].inList()) {
-			removeFromList(s.trail()[i].var());
-		}
-	}
-	front_ = getFront();
-}
-
-void ClaspVmtf::newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t) {
-	if (t != Constraint_t::Static) {
-		LessLevel comp(s, score_);
-		const bool upAct = types_.inSet(t);
-		const VarVec::size_type mtf = t == Constraint_t::Conflict ? nMove_ : (nMove_*uint32(upAct))/2;
-		for (LitVec::size_type i = 0; i < size; ++i, ++first) {
-			Var v = first->var();
-			score_[v].occ_ += 1 - (((int)first->sign())<<1);
-			if (upAct) { ++score_[v].activity(decay_); }
-			if (mtf && (!nant_ || s.varInfo(v).nant())){
-				if (mtf_.size() < mtf) {
-					mtf_.push_back(v);
-					std::push_heap(mtf_.begin(), mtf_.end(), comp);
-				}
-				else if (comp(v, mtf_[0])) {
-					assert(s.level(v) <= s.level(mtf_[0]));
-					std::pop_heap(mtf_.begin(), mtf_.end(), comp);
-					mtf_.back() = v;
-					std::push_heap(mtf_.begin(), mtf_.end(), comp);
-				}
-			}
-		}
-		for (VarVec::size_type i = 0; i != mtf_.size(); ++i) {
-			Var v = mtf_[i];
-			if (score_[v].inList()) {
-				moveToFront(v);
-			}
-		}
-		mtf_.clear();
-		front_ = getFront();
-	}
-}
-
-void ClaspVmtf::undoUntil(const Solver&, LitVec::size_type) {
-	front_ = getFront();
-}
-
-void ClaspVmtf::updateReason(const Solver& s, const LitVec& lits, Literal r) {
-	if (scType_ > HeuParams::score_min) {
-		const bool  ms = scType_ == HeuParams::score_multi_set;
-		const uint32 D = decay_;
-		for (LitVec::size_type i = 0, e = lits.size(); i != e; ++i) {
-			if (ms || !s.seen(lits[i])) { ++score_[lits[i].var()].activity(D); }
-		}
-	}
-	if (scType_ & 1u) { ++score_[r.var()].activity(decay_); }
-}
-
-bool ClaspVmtf::bump(const Solver&, const WeightLitVec& lits, double adj) {
-	for (WeightLitVec::const_iterator it = lits.begin(), end = lits.end(); it != end; ++it) {
-		score_[it->first.var()].activity(decay_) += static_cast(it->second*adj);
-	}
-	return true;
+    bool moms     = types_.contains(ConstraintType::static_);
+    auto varRange = s.vars();
+    if (not moms) {
+        // - add all new vars
+        for (auto v : varRange) {
+            if (s.value(v) == value_free) {
+                score_[v].activity(decay_);
+                if (not score_[v].inList()) {
+                    addToList(v);
+                }
+            }
+        }
+    }
+    else {
+        // - set activity of all vars not in list to moms
+        // - append new vars in moms-activity order
+        const uint32_t momsStamp = decay_ + 1;
+        const uint32_t assumeNew = (s.numVars() + 1) - nList_;
+        VarVec         vars;
+        vars.reserve(assumeNew);
+        for (auto v : varRange) {
+            if (s.value(v) == value_free) {
+                score_[v].activity(decay_);
+                if (not score_[v].inList()) {
+                    score_[v].act   = momsScore(s, v);
+                    score_[v].decay = momsStamp;
+                    vars.push_back(v);
+                }
+            }
+        }
+        std::ranges::stable_sort(vars, LessLevel(s, score_));
+        for (auto var : vars) {
+            addToList(var);
+            if (score_[var].decay == momsStamp) {
+                score_[var].act   = 0;
+                score_[var].decay = decay_;
+            }
+        }
+    }
+    front_ = getFront();
+}
+
+void ClaspVmtf::updateVar(const Solver& s, Var_t v, uint32_t n) {
+    if (s.validVar(v)) {
+        growVecTo(score_, v + n, VarInfo());
+        for (auto end = v + n; v != end; ++v) {
+            if (not score_[v].inList()) {
+                addToList(v);
+            }
+            else {
+                front_ = getFront();
+            }
+        }
+    }
+    else if (auto sz = size32(score_); v < sz) {
+        if ((v + n) > sz) {
+            n = sz - v;
+        }
+        for (uint32_t x = v + n; x-- != v;) {
+            if (score_[x].inList()) {
+                removeFromList(x);
+            }
+        }
+    }
+}
+
+void ClaspVmtf::simplify(const Solver&, LitView facts) {
+    for (auto lit : facts) {
+        if (score_[lit.var()].inList()) {
+            removeFromList(lit.var());
+        }
+    }
+    front_ = getFront();
+}
+
+void ClaspVmtf::newConstraint(const Solver& s, LitView lits, ConstraintType t) {
+    if (t != ConstraintType::static_) {
+        LessLevel  comp(s, score_);
+        const bool upAct = types_.contains(t);
+        const auto mtf   = t == ConstraintType::conflict ? nMove_ : (nMove_ * static_cast(upAct)) / 2;
+        for (const auto& lit : lits) {
+            auto v         = lit.var();
+            score_[v].occ += 1 - (static_cast(lit.sign()) << 1);
+            if (upAct) {
+                ++score_[v].activity(decay_);
+            }
+            if (mtf && (not nant_ || s.varInfo(v).nant())) {
+                if (size32(mtf_) < mtf) {
+                    mtf_.push_back(v);
+                    std::ranges::push_heap(mtf_, comp);
+                }
+                else if (comp(v, mtf_[0])) {
+                    assert(s.level(v) <= s.level(mtf_[0]));
+                    std::ranges::pop_heap(mtf_, comp);
+                    mtf_.back() = v;
+                    std::ranges::push_heap(mtf_, comp);
+                }
+            }
+        }
+        for (auto v : mtf_) {
+            if (score_[v].inList()) {
+                moveToFront(v);
+            }
+        }
+        mtf_.clear();
+        front_ = getFront();
+    }
+}
+
+void ClaspVmtf::undo(const Solver&, LitView) { front_ = getFront(); }
+
+void ClaspVmtf::updateReason(const Solver& s, LitView lits, Literal r) {
+    if (scType_ > HeuParams::score_min) {
+        const bool     ms  = scType_ == HeuParams::score_multi_set;
+        const uint32_t dec = decay_;
+        for (auto lit : lits) {
+            if (ms || not s.seen(lit)) {
+                ++score_[lit.var()].activity(dec);
+            }
+        }
+    }
+    if (scType_ & 1u) {
+        ++score_[r.var()].activity(decay_);
+    }
+}
+
+bool ClaspVmtf::bump(const Solver&, WeightLitView lits, double adj) {
+    for (const auto& [lit, w] : lits) { score_[lit.var()].activity(decay_) += static_cast(w * adj); }
+    return true;
 }
 
 Literal ClaspVmtf::doSelect(Solver& s) {
-	decay_ += ((s.stats.choices + 1) & 511) == 0;
-	for (; s.value(front_) != value_free; front_ = getNext(front_)) {;}
-	Literal c;
-	if (s.numFreeVars() > 1) {
-		Var v2 = front_;
-		uint32 distance = 0;
-		do {
-			v2 = getNext(v2);
-			++distance;
-		} while (s.value(v2) != value_free);
-		c = (score_[front_].activity(decay_) + (distance<<1)+ 3) > score_[v2].activity(decay_)
-		    ? selectLiteral(s, front_, score_[front_].occ_)
-		    : selectLiteral(s, v2, score_[v2].occ_);
-	}
-	else {
-		c = selectLiteral(s, front_, score_[front_].occ_);
-	}
-	return c;
-}
-
-Literal ClaspVmtf::selectRange(Solver&, const Literal* first, const Literal* last) {
-	Literal best = *first;
-	for (++first; first != last; ++first) {
-		if (score_[first->var()].activity(decay_) > score_[best.var()].activity(decay_)) {
-			best = *first;
-		}
-	}
-	return best;
+    decay_ += ((s.stats.choices + 1) & 511) == 0;
+    for (; s.value(front_) != value_free; front_ = getNext(front_)) { ; }
+    Literal c;
+    if (s.numFreeVars() > 1) {
+        auto     v2       = front_;
+        uint32_t distance = 0;
+        do {
+            v2 = getNext(v2);
+            ++distance;
+        } while (s.value(v2) != value_free);
+        c = (score_[front_].activity(decay_) + (distance << 1) + 3) > score_[v2].activity(decay_)
+                ? selectLiteral(s, front_, score_[front_].occ)
+                : selectLiteral(s, v2, score_[v2].occ);
+    }
+    else {
+        c = selectLiteral(s, front_, score_[front_].occ);
+    }
+    return c;
 }
 
+Literal ClaspVmtf::selectRange(Solver&, LitView range) {
+    return *std::ranges::max_element(range, [this](Literal best, Literal current) {
+        return score_[current.var()].activity(decay_) > score_[best.var()].activity(decay_);
+    });
+}
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspVsids selection strategy
 /////////////////////////////////////////////////////////////////////////////////////////
-static bool isDom(const VsidsScore&) { return false; }
-static bool isDom(const DomScore& s) { return s.isDom(); }
-static double initDecay(uint32 p) {
-	double m = static_cast(p ? p : 0.95);
-	while (m > 1.0) { m /= 10.0; }
-	return m;
-}
-
-template 
-ClaspVsids_t::ClaspVsids_t(const HeuParams& params)
-	: vars_(CmpScore(score_))
-	, inc_(1.0)
-	, acids_(false) {
-	ClaspVsids_t::setConfig(params);
-}
-
-template 
-void ClaspVsids_t::setConfig(const HeuParams& params) {
-	addOther(types_ = TypeSet(), params.other != HeuParams::other_auto ? params.other : static_cast(HeuParams::other_no));
-	scType_ = params.score != HeuParams::score_auto ? params.score : static_cast(HeuParams::score_min);
-	decay_  = Decay(params.decay.init ? initDecay(params.decay.init) : 0.0, initDecay(params.param), params.decay.bump, params.decay.freq);
-	acids_  = params.acids != 0;
-	nant_   = params.nant != 0;
-	if (params.moms) { types_.addSet(Constraint_t::Static); }
-	if (scType_ == HeuParams::score_min) { types_.addSet(Constraint_t::Conflict); }
-}
-
-template 
-void ClaspVsids_t::startInit(const Solver& s) {
-	score_.resize(s.numVars()+1);
-	occ_.resize(s.numVars()+1);
-	vars_.reserve(s.numVars() + 1);
-}
-
-template 
-void ClaspVsids_t::endInit(Solver& s) {
-	vars_.clear();
-	initScores(s, types_.inSet(Constraint_t::Static));
-	double mx = 0;
-	unsigned warn = 0;
-	for (Var v = 1; v <= s.numVars(); ++v) {
-		if (s.value(v) != value_free) {
-			if (s.sharedContext()->eliminated(v) && isDom(score_[v])) { ++warn; }
-			continue;
-		}
-		mx = std::max(mx, score_[v].get());
-		if (!vars_.is_in_queue(v)) { vars_.push(v); }
-	}
-	if (acids_ && mx > inc_) {
-		inc_ = std::ceil(mx);
-	}
-	if (warn && s.isMaster()) {
-		s.sharedContext()->warn("heuristic modifications on eliminated variables - results may be unexpected");
-	}
-}
-template 
-void ClaspVsids_t::updateVarActivity(const Solver& s, Var v, double f) {
-	if (!nant_ || s.varInfo(v).nant()) {
-		double o = score_[v].get(), n;
-		f = ScoreType::applyFactor(score_, v, f);
-		if      (!acids_)  { n = o + (f * inc_); }
-		else if (f == 1.0) { n = (o + inc_) / 2.0; }
-		else if (f != 0.0) { n = std::max( (o + inc_ + f) / 2.0, f + o ); }
-		else               { return; }
-		score_[v].set(n);
-		if (n > 1e100) { normalize(); }
-		if (vars_.is_in_queue(v)) {
-			if (n >= o) { vars_.increase(v); }
-			else        { vars_.decrease(v); }
-		}
-	}
-}
-template 
-void ClaspVsids_t::updateVar(const Solver& s, Var v, uint32 n) {
-	if (s.validVar(v)) {
-		growVecTo(score_, v+n);
-		growVecTo(occ_, v+n);
-		for (uint32 end = v+n; v != end; ++v) { vars_.update(v); }
-	}
-	else {
-		for (uint32 end = v+n; v != end; ++v) { vars_.remove(v); }
-	}
-}
-template 
-void ClaspVsids_t::initScores(Solver& s, bool moms) {
-	if (!moms) { return; }
-	double maxS = 0.0, ms;
-	for (Var v = 1; v <= s.numVars(); ++v) {
-		if (s.value(v) == value_free && score_[v].get() == 0.0 && (ms = (double)momsScore(s, v)) != 0.0) {
-			maxS = std::max(maxS, ms);
-			score_[v].set(-ms);
-		}
-	}
-	for (Var v = 1; v <= s.numVars(); ++v) {
-		double d = score_[v].get();
-		if (d < 0) {
-			d *= -1.0;
-			d /= maxS;
-			score_[v].set(d);
-		}
-	}
-}
-template 
-void ClaspVsids_t::simplify(const Solver& s, LitVec::size_type i) {
-	for (; i < s.numAssignedVars(); ++i) {
-		vars_.remove(s.trail()[i].var());
-	}
-}
-
-template 
-void ClaspVsids_t::normalize() {
-	const double min  = std::numeric_limits::min();
-	const double minD = min * 1e100;
-	inc_             *= 1e-100;
-	for (LitVec::size_type i = 0; i != score_.size(); ++i) {
-		double d = score_[i].get();
-		if (d > 0) {
-			// keep relative ordering but
-			// actively avoid denormals
-			d += minD;
-			d *= 1e-100;
-		}
-		score_[i].set(d);
-	}
-}
-template 
-void ClaspVsids_t::newConstraint(const Solver& s, const Literal* first, LitVec::size_type size, ConstraintType t) {
-	if (t != Constraint_t::Static) {
-		const bool upAct = types_.inSet(t);
-		for (LitVec::size_type i = 0; i < size; ++i, ++first) {
-			incOcc(*first);
-			if (upAct) {
-				updateVarActivity(s, first->var());
-			}
-		}
-		if (t == Constraint_t::Conflict) {
-			if (decay_.next && --decay_.next == 0 && decay_.lo < decay_.hi) {
-				decay_.lo  += decay_.bump / 100.0;
-				decay_.next = decay_.freq;
-				decay_.df   = 1.0 / decay_.lo;
-			}
-			if (!acids_) { inc_ *= decay_.df; }
-			else         { inc_ += 1.0; }
-		}
-	}
-}
-template 
-void ClaspVsids_t::updateReason(const Solver& s, const LitVec& lits, Literal r) {
-	if (scType_ > HeuParams::score_min) {
-		const bool ms = scType_ == HeuParams::score_multi_set;
-		for (LitVec::size_type i = 0, end = lits.size(); i != end; ++i) {
-			if (ms || !s.seen(lits[i])) { updateVarActivity(s, lits[i].var()); }
-		}
-	}
-	if ((scType_ & 1u) != 0 && r.var() != 0) { updateVarActivity(s, r.var()); }
-}
-template 
-bool ClaspVsids_t::bump(const Solver& s, const WeightLitVec& lits, double adj) {
-	double mf = 1.0, f;
-	for (WeightLitVec::const_iterator it = lits.begin(), end = lits.end(); it != end; ++it) {
-		updateVarActivity(s, it->first.var(), (f = it->second*adj));
-		if (acids_ && f > mf) { mf = f; }
-	}
-	if (acids_ && mf > 1.0) {
-		inc_ = std::ceil(mf + inc_);
-	}
-	return true;
-}
-template 
-void ClaspVsids_t::undoUntil(const Solver& s , LitVec::size_type st) {
-	const LitVec& a = s.trail();
-	for (; st < a.size(); ++st) {
-		if (!vars_.is_in_queue(a[st].var())) {
-			vars_.push(a[st].var());
-		}
-	}
-}
-template 
-Literal ClaspVsids_t::doSelect(Solver& s) {
-	while ( s.value(vars_.top()) != value_free ) {
-		vars_.pop();
-	}
-	return selectLiteral(s, vars_.top(), occ(vars_.top()));
-}
-template 
-Literal ClaspVsids_t::selectRange(Solver&, const Literal* first, const Literal* last) {
-	Literal best = *first;
-	for (++first; first != last; ++first) {
-		if (vars_.key_compare()(first->var(), best.var())) {
-			best = *first;
-		}
-	}
-	return best;
-}
-template class ClaspVsids_t;
+static bool   isDom(const VsidsScore&) { return false; }
+static bool   isDom(const DomScore& s) { return s.isDom(); }
+static double initDecay(uint32_t p) {
+    double m = p ? static_cast(p) : 0.95;
+    while (m > 1.0) { m /= 10.0; }
+    return m;
+}
+
+template 
+ClaspVsidsBase::ClaspVsidsBase(const HeuParams& params) : vars_(CmpScore(score_)) {
+    ClaspVsidsBase::setConfig(params);
+}
+
+template 
+void ClaspVsidsBase::setConfig(const HeuParams& params) {
+    addOther(types_ = TypeSet(),
+             params.other != HeuParams::other_auto ? params.other : static_cast(HeuParams::other_no));
+    scType_ = params.score != HeuParams::score_auto ? params.score : static_cast(HeuParams::score_min);
+    dyn_    = {};
+    auto d  = initDecay(params.param);
+    if (params.decay.init && params.decay.init != params.param && params.decay.freq) {
+        auto b    = initDecay(params.decay.init);
+        dyn_.curr = std::min(d, b);
+        dyn_.stop = std::max(d, b);
+        dyn_.bump = params.decay.bump;
+        dyn_.freq = dyn_.next = params.decay.freq;
+        d                     = dyn_.curr;
+    }
+    decay_ = 1.0 / d;
+    acids_ = params.acids != 0;
+    nant_  = params.nant != 0;
+    if (params.moms) {
+        types_.add(ConstraintType::static_);
+    }
+    if (scType_ == HeuParams::score_min) {
+        types_.add(ConstraintType::conflict);
+    }
+}
+
+template 
+void ClaspVsidsBase::startInit(const Solver& s) {
+    score_.resize(s.numVars() + 1);
+    occ_.resize(s.numVars() + 1);
+    vars_.reserve(s.numVars() + 1);
+}
+
+template 
+void ClaspVsidsBase::endInit(Solver& s) {
+    vars_.clear();
+    initScores(s, types_.contains(ConstraintType::static_));
+    double   mx   = 0;
+    unsigned warn = 0;
+    for (auto v : s.vars()) {
+        if (s.value(v) != value_free) {
+            if (s.sharedContext()->eliminated(v) && isDom(score_[v])) {
+                ++warn;
+            }
+            continue;
+        }
+        mx = std::max(mx, score_[v].get());
+        if (not vars_.is_in_queue(v)) {
+            vars_.push(v);
+        }
+    }
+    if (acids_ && mx > inc_) {
+        inc_ = std::ceil(mx);
+    }
+    if (warn && s.isMaster()) {
+        s.sharedContext()->warn("heuristic modifications on eliminated variables - results may be unexpected");
+    }
+}
+template 
+void ClaspVsidsBase::updateVarActivity(const Solver& s, Var_t v, double f) {
+    if (not nant_ || s.varInfo(v).nant()) {
+        double o = score_[v].get(), n;
+        f        = ScoreType::applyFactor(score_, v, f);
+        if (not acids_) {
+            n = o + (f * inc_);
+        }
+        else if (f == 1.0) {
+            n = (o + inc_) / 2.0;
+        }
+        else if (f != 0.0) {
+            n = std::max((o + inc_ + f) / 2.0, f + o);
+        }
+        else {
+            return;
+        }
+        score_[v].set(n);
+        if (n > 1e100) {
+            normalize();
+        }
+        if (vars_.is_in_queue(v)) {
+            if (n >= o) {
+                vars_.increase(v);
+            }
+            else {
+                vars_.decrease(v);
+            }
+        }
+    }
+}
+template 
+void ClaspVsidsBase::updateVar(const Solver& s, Var_t v, uint32_t n) {
+    if (s.validVar(v)) {
+        growVecTo(score_, v + n);
+        growVecTo(occ_, v + n);
+        for (uint32_t end = v + n; v != end; ++v) { vars_.update(v); }
+    }
+    else {
+        for (uint32_t end = v + n; v != end; ++v) { vars_.remove(v); }
+    }
+}
+template 
+void ClaspVsidsBase::initScores(Solver& s, bool moms) {
+    if (not moms) {
+        return;
+    }
+    double maxS     = 0.0;
+    auto   varRange = s.vars();
+    for (auto v : varRange) {
+        if (s.value(v) != value_free || score_[v].get() != 0.0) {
+            continue;
+        }
+        if (auto ms = static_cast(momsScore(s, v)); ms != 0.0) {
+            maxS = std::max(maxS, ms);
+            score_[v].set(-ms);
+        }
+    }
+    for (auto v : varRange) {
+        if (double d = score_[v].get(); d < 0) {
+            d *= -1.0;
+            d /= maxS;
+            score_[v].set(d);
+        }
+    }
+}
+template 
+void ClaspVsidsBase::simplify(const Solver&, LitView facts) {
+    for (auto lit : facts) { vars_.remove(lit.var()); }
+}
+
+template 
+void ClaspVsidsBase::normalize() {
+    constexpr double min   = std::numeric_limits::min();
+    constexpr double minD  = min * 1e100;
+    inc_                  *= 1e-100;
+    for (ScoreType& sc : score_) {
+        double d = sc.get();
+        if (d > 0) {
+            // keep relative ordering but
+            // actively avoid denormals
+            d += minD;
+            d *= 1e-100;
+        }
+        sc.set(d);
+    }
+}
+template 
+void ClaspVsidsBase::newConstraint(const Solver& s, LitView lits, ConstraintType t) {
+    if (t != ConstraintType::static_) {
+        const bool upAct = types_.contains(t);
+        for (const auto& lit : lits) {
+            incOcc(lit);
+            if (upAct) {
+                updateVarActivity(s, lit.var());
+            }
+        }
+        if (t == ConstraintType::conflict) {
+            if (dyn_.next && --dyn_.next == 0) {
+                dyn_.curr += dyn_.bump / 100.0;
+                decay_     = 1.0 / dyn_.curr;
+                if (dyn_.curr < dyn_.stop) {
+                    dyn_.next = dyn_.freq;
+                }
+            }
+            if (not acids_) {
+                inc_ *= decay_;
+            }
+            else {
+                inc_ += 1.0;
+            }
+        }
+    }
+}
+template 
+void ClaspVsidsBase::updateReason(const Solver& s, LitView lits, Literal r) {
+    if (scType_ > HeuParams::score_min) {
+        const bool ms = scType_ == HeuParams::score_multi_set;
+        for (auto lit : lits) {
+            if (ms || not s.seen(lit)) {
+                updateVarActivity(s, lit.var());
+            }
+        }
+    }
+    if ((scType_ & 1u) != 0 && r.var() != 0) {
+        updateVarActivity(s, r.var());
+    }
+}
+template 
+bool ClaspVsidsBase::bump(const Solver& s, WeightLitView lits, double adj) {
+    double mf = 1.0, f;
+    for (const auto& [lit, weight] : lits) {
+        updateVarActivity(s, lit.var(), (f = weight * adj));
+        if (acids_ && f > mf) {
+            mf = f;
+        }
+    }
+    if (acids_ && mf > 1.0) {
+        inc_ = std::ceil(mf + inc_);
+    }
+    return true;
+}
+template 
+void ClaspVsidsBase::undo(const Solver&, LitView undo) {
+    for (auto lit : undo) {
+        if (not vars_.is_in_queue(lit.var())) {
+            vars_.push(lit.var());
+        }
+    }
+}
+template 
+Literal ClaspVsidsBase::doSelect(Solver& s) {
+    while (s.value(vars_.top()) != value_free) { vars_.pop(); }
+    return selectLiteral(s, vars_.top(), occ(vars_.top()));
+}
+template 
+Literal ClaspVsidsBase::selectRange(Solver&, LitView range) {
+    return *std::ranges::max_element(
+        range, [&cmp = vars_.key_compare()](Literal best, Literal current) { return cmp(current.var(), best.var()); });
+}
+template class ClaspVsidsBase;
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // DomainHeuristic selection strategy
 /////////////////////////////////////////////////////////////////////////////////////////
-DomainHeuristic::DomainHeuristic(const HeuParams& params) : ClaspVsids_t(params), domSeen_(0), defMax_(0), defMod_(0), defPref_(0) {
-	frames_.push_back(Frame(0,DomAction::UNDO_NIL));
-	setDefaultMod(static_cast(params.domMod), params.domPref);
+DomainHeuristic::DomainHeuristic(const HeuParams& params)
+    : ClaspVsidsBase(params)
+    , domSeen_(0)
+    , defMax_(0)
+    , defMod_(0)
+    , defPref_(0) {
+    frames_.push_back(Frame(0, DomAction::undo_nil));
+    setDefaultMod(static_cast(params.domMod), params.domPref);
 }
-DomainHeuristic::~DomainHeuristic() {}
+DomainHeuristic::~DomainHeuristic() = default;
 
 void DomainHeuristic::setConfig(const HeuParams& params) {
-	ClaspVsids_t::setConfig(params);
-	setDefaultMod(static_cast(params.domMod), params.domPref);
+    ClaspVsidsBase::setConfig(params);
+    setDefaultMod(static_cast(params.domMod), params.domPref);
 }
-void DomainHeuristic::setDefaultMod(HeuParams::DomMod mod, uint32 prefSet) {
-	defMod_ = (uint16)mod;
-	defPref_= prefSet;
+void DomainHeuristic::setDefaultMod(HeuParams::DomMod mod, uint32_t prefSet) {
+    defMod_  = static_cast(mod);
+    defPref_ = prefSet;
 }
 void DomainHeuristic::detach(Solver& s) {
-	if (!actions_.empty()) {
-		for (DomainTable::iterator it = s.sharedContext()->heuristic.begin(), end = s.sharedContext()->heuristic.end(); it != end; ++it) {
-			if (it->hasCondition()) {
-				s.removeWatch(it->cond(), this);
-			}
-		}
-	}
-	while (frames_.back().dl != 0) {
-		s.removeUndoWatch(frames_.back().dl, this);
-		frames_.pop_back();
-	}
-	Var end = std::min(static_cast(score_.size()), static_cast(s.numVars() + 1));
-	for (Var v = 0; v != end; ++v) {
-		if (score_[v].sign) { s.setPref(v, ValueSet::user_value, value_free); }
-	}
-	actions_.clear();
-	prios_.clear();
-	domSeen_ = 0;
-	defMax_  = 0;
+    if (not actions_.empty()) {
+        for (const auto& dom : s.sharedContext()->heuristic) {
+            if (dom.hasCondition()) {
+                s.removeWatch(dom.cond(), this);
+            }
+        }
+    }
+    while (frames_.back().dl != 0) {
+        s.removeUndoWatch(frames_.back().dl, this);
+        frames_.pop_back();
+    }
+    for (auto v : irange(std::min(size32(score_), s.numVars() + 1))) {
+        if (score_[v].sign) {
+            s.setPref(v, ValueSet::user_value, value_free);
+        }
+    }
+    actions_.clear();
+    prios_.clear();
+    domSeen_ = 0;
+    defMax_  = 0;
 }
 void DomainHeuristic::startInit(const Solver& s) {
-	BaseType::startInit(s);
-	domSeen_ = std::min(domSeen_, s.sharedContext()->heuristic.size());
+    BaseType::startInit(s);
+    domSeen_ = std::min(domSeen_, s.sharedContext()->heuristic.size());
 }
 void DomainHeuristic::initScores(Solver& s, bool moms) {
-	const DomainTable& domTab = s.sharedContext()->heuristic;
-	BaseType::initScores(s, moms);
-	uint32 nKey = (uint32)prios_.size();
-	if (defMax_) {
-		defMax_ = std::min(defMax_, s.numVars()) + 1;
-		for (Var v = 1; v != defMax_; ++v) {
-			if (score_[v].domP >= nKey) {
-				bool sign = score_[v].sign;
-				score_[v] = DomScore(score_[v].value);
-				if (sign) { s.setPref(v, ValueSet::user_value, value_free); }
-			}
-		}
-		defMax_ = 0;
-	}
-	if (domSeen_ < domTab.size()) {
-		// apply new domain modifications
-		VarScoreVec saved;
-		Literal lastW = lit_true();
-		uint32 dKey = nKey;
-		for (DomainTable::iterator it = domTab.begin() + domSeen_, end = domTab.end(); it != end; ++it) {
-			if (s.topValue(it->var()) != value_free || s.isFalse(it->cond())) {
-				continue;
-			}
-			if (score_[it->var()].domP >= nKey){
-				score_[it->var()].setDom(nKey++);
-				prios_.push_back(DomPrio());
-				prios_.back().clear();
-			}
-			uint32 k = addDomAction(*it, s, saved, lastW);
-			if (k > dKey) { dKey = k; }
-		}
-		for (VarScoreVec::value_type x; !saved.empty(); saved.pop_back()) {
-			x = saved.back();
-			score_[x.first].value += x.second;
-			score_[x.first].init   = 0;
-		}
-		if (!actions_.empty()) {
-			actions_.back().next = 0;
-		}
-		if ((nKey - dKey) > dKey && s.sharedContext()->solveMode() == SharedContext::solve_once) {
-			PrioVec(prios_.begin(), prios_.begin()+dKey).swap(prios_);
-		}
-		domSeen_ = domTab.size();
-	}
-	// apply default modification
-	if (defMod_) {
-		struct DefAction : public DomainTable::DefaultAction {
-			DomainHeuristic* self; Solver* solver; uint32 key;
-			DefAction(DomainHeuristic& h, Solver& s, uint32 k) : self(&h), solver(&s), key(k) { }
-			virtual void atom(Literal p, HeuParams::DomPref s, uint32 b) {
-				self->addDefAction(*solver, p, b ? b : 1, key + log2(s));
-			}
-		} act(*this, s, nKey + 1);
-		DomainTable::applyDefault(*s.sharedContext(), act, defPref_);
-	}
-}
-
-uint32 DomainHeuristic::addDomAction(const DomMod& e, Solver& s, VarScoreVec& initOut, Literal& lastW) {
-	if (e.comp()) {
-		DomMod level(e.var(), DomModType::Level, e.bias(), e.prio(), e.cond());
-		DomMod sign(e.var(), DomModType::Sign, e.type() == DomModType::True ? 1 : -1, e.prio(), e.cond());
-		uint32 k1 = addDomAction(level, s, initOut, lastW);
-		uint32 k2 = addDomAction(sign, s, initOut, lastW);
-		return std::max(k1, k2);
-	}
-	bool isStatic = !e.hasCondition() || s.topValue(e.cond().var()) == trueValue(e.cond());
-	uint16& sPrio = prio(e.var(), e.type());
-	DomScore& es  = score_[e.var()];
-	if (e.prio() < sPrio || (!isStatic && e.type() == DomModType::Init)) {
-		return 0;
-	}
-	if (e.type() == DomModType::Init && es.init == 0) {
-		initOut.push_back(std::make_pair(e.var(), es.value));
-		es.init = 1;
-	}
-	DomAction a = { e.var(), (uint32)e.type(), DomAction::UNDO_NIL, uint32(0), e.bias(), e.prio() };
-	if (a.mod == DomModType::Sign && a.bias != 0) {
-		a.bias = a.bias > 0 ? value_true : value_false;
-	}
-	POTASSCO_ASSERT(e.type() == a.mod, "Invalid dom modifier!");
-	if (isStatic) {
-		applyAction(s, a, sPrio);
-		es.sign |= static_cast(e.type() == DomModType::Sign);
-		return 0;
-	}
-	if (e.cond() != lastW) {
-		s.addWatch(lastW = e.cond(), this, (uint32)actions_.size());
-	}
-	else {
-		actions_.back().next = 1;
-	}
-	actions_.push_back(a);
-	return es.domP + 1;
-}
-
-void DomainHeuristic::addDefAction(Solver& s, Literal x, int16 lev, uint32 domKey) {
-	if (s.value(x.var()) != value_free || score_[x.var()].domP < domKey) { return; }
-	const bool signMod = defMod_ <  HeuParams::mod_init && ((defMod_ & HeuParams::mod_init) != 0);
-	const bool valMod  = defMod_ >= HeuParams::mod_init || ((defMod_ & HeuParams::mod_level)!= 0);
-	DomScore& xs       = score_[x.var()];
-	if (xs.domP > domKey && lev && valMod) {
-		if      (defMod_ < HeuParams::mod_init)    { xs.level  += lev; }
-		else if (defMod_ == HeuParams::mod_init)   { xs.value  += (lev*100); }
-		else if (defMod_ == HeuParams::mod_factor) { xs.factor += 1 + (lev > 3) + (lev > 15); }
-	}
-	if (signMod) {
-		ValueRep oPref = s.pref(x.var()).get(ValueSet::user_value);
-		ValueRep nPref = (defMod_ & HeuParams::mod_spos) != 0 ? trueValue(x) : falseValue(x);
-		if (oPref == value_free || (xs.sign == 1 && domKey != xs.domP)) {
-			s.setPref(x.var(), ValueSet::user_value, nPref);
-			xs.sign = 1;
-		}
-		else if (xs.sign == 1 && oPref != nPref) {
-			s.setPref(x.var(), ValueSet::user_value, value_free);
-			xs.sign = 0;
-		}
-	}
-	if (x.var() > defMax_) {
-		defMax_ = x.var();
-	}
-	xs.setDom(domKey);
+    const DomainTable& domTab = s.sharedContext()->heuristic;
+    BaseType::initScores(s, moms);
+    auto nKey = size32(prios_);
+    if (defMax_) {
+        defMax_ = std::min(defMax_, s.numVars()) + 1;
+        for (auto v : irange(1u, defMax_)) {
+            if (score_[v].domP >= nKey) {
+                bool sign = score_[v].sign;
+                score_[v] = DomScore(score_[v].value);
+                if (sign) {
+                    s.setPref(v, ValueSet::user_value, value_free);
+                }
+            }
+        }
+        defMax_ = 0;
+    }
+    if (domSeen_ < domTab.size()) {
+        // apply new domain modifications
+        VarScoreVec saved;
+        Literal     lastW = lit_true;
+        uint32_t    dKey  = nKey;
+        for (const auto& dom : domTab.dropView(domSeen_)) {
+            if (s.topValue(dom.var()) != value_free || s.isFalse(dom.cond())) {
+                continue;
+            }
+            if (score_[dom.var()].domP >= nKey) {
+                score_[dom.var()].setDom(nKey++);
+                prios_.push_back(DomPrio());
+                prios_.back().clear();
+            }
+            if (uint32_t k = addDomAction(dom, s, saved, lastW); k > dKey) {
+                dKey = k;
+            }
+        }
+        while (not saved.empty()) {
+            const auto& [var, score]  = saved.back();
+            score_[var].value        += score;
+            score_[var].init          = 0;
+            saved.pop_back();
+        }
+        if (not actions_.empty()) {
+            actions_.back().next = 0;
+        }
+        if ((nKey - dKey) > dKey && s.sharedContext()->solveMode() == SharedContext::solve_once) {
+            PrioVec(prios_.begin(), prios_.begin() + dKey).swap(prios_);
+        }
+        domSeen_ = domTab.size();
+    }
+    // apply default modification
+    if (defMod_) {
+        unsigned key = nKey + 1;
+        DomainTable::applyDefault(
+            *s.sharedContext(),
+            [&](Literal p, HeuParams::DomPref pref, uint32_t b) {
+                assert(std::in_range(b));
+                addDefAction(s, p, static_cast(b ? b : 1), key + Potassco::log2(static_cast(pref)));
+            },
+            defPref_);
+    }
+}
+
+uint32_t DomainHeuristic::addDomAction(const DomMod& e, Solver& s, VarScoreVec& initOut, Literal& lastW) {
+    bool isStatic = not e.hasCondition() || s.topValue(e.cond().var()) == trueValue(e.cond());
+    if (not isStatic && e.type() == DomModType::init) {
+        return 0;
+    }
+    POTASSCO_ASSERT(e.comp() || +e.type() <= 3u, "Invalid dom modifier!");
+    DomAction a = {e.var(), not e.comp() ? +e.type() : +DomModType::level, DomAction::undo_nil, 0u, e.bias(), e.prio()};
+    DomScore& es = score_[a.var];
+    for (uint32_t res = 0;;) {
+        if (auto& sPrio = prio(a.var, a.mod); a.prio >= sPrio) {
+            if (a.mod == DomModType::init && es.init == 0) {
+                initOut.push_back({a.var, es.value});
+                es.init = 1;
+            }
+            else if (a.mod == DomModType::sign && a.bias != 0) {
+                a.bias = a.bias > 0 ? value_true : value_false;
+            }
+            if (isStatic) {
+                applyAction(s, a, sPrio);
+                es.sign |= static_cast(a.mod == DomModType::sign);
+            }
+            else {
+                if (e.cond() != lastW) {
+                    s.addWatch(lastW = e.cond(), this, size32(actions_));
+                }
+                else {
+                    actions_.back().next = 1;
+                }
+                actions_.push_back(a);
+                res = es.domP + 1u;
+            }
+        }
+        if (not e.comp() || a.mod == DomModType::sign) {
+            return res;
+        }
+        POTASSCO_ASSERT(e.comp(), "Invalid dom modifier!");
+        a.mod  = +DomModType::sign;
+        a.bias = e.type() == DomModType::true_ ? 1 : -1;
+        a.prio = e.prio();
+    }
+}
+
+void DomainHeuristic::addDefAction(Solver& s, Literal x, int16_t lev, uint32_t domKey) {
+    if (s.value(x.var()) != value_free || score_[x.var()].domP < domKey) {
+        return;
+    }
+    const bool signMod = defMod_ < HeuParams::mod_init && ((defMod_ & HeuParams::mod_init) != 0);
+    const bool valMod  = defMod_ >= HeuParams::mod_init || ((defMod_ & HeuParams::mod_level) != 0);
+    DomScore&  xs      = score_[x.var()];
+    if (xs.domP > domKey && lev && valMod) {
+        if (defMod_ < HeuParams::mod_init) {
+            auto nl  = xs.level + lev;
+            xs.level = Clasp::saturate_cast(nl);
+        }
+        else if (defMod_ == HeuParams::mod_init) {
+            xs.value += (lev * 100);
+        }
+        else if (defMod_ == HeuParams::mod_factor) {
+            auto nf   = xs.factor + 1 + (lev > 3) + (lev > 15);
+            xs.factor = Clasp::saturate_cast(nf);
+        }
+    }
+    if (signMod) {
+        auto oPref = s.pref(x.var()).get(ValueSet::user_value);
+        auto nPref = (defMod_ & HeuParams::mod_spos) != 0 ? trueValue(x) : falseValue(x);
+        if (oPref == value_free || (xs.sign == 1 && domKey != xs.domP)) {
+            s.setPref(x.var(), ValueSet::user_value, nPref);
+            xs.sign = 1;
+        }
+        else if (xs.sign == 1 && oPref != nPref) {
+            s.setPref(x.var(), ValueSet::user_value, value_free);
+            xs.sign = 0;
+        }
+    }
+    if (x.var() > defMax_) {
+        defMax_ = x.var();
+    }
+    xs.setDom(domKey);
 }
 
 Literal DomainHeuristic::doSelect(Solver& s) {
-	Literal x = BaseType::doSelect(s);
-	s.stats.addDomChoice(isDom(score_[x.var()]));
-	return x;
-}
-
-Constraint::PropResult DomainHeuristic::propagate(Solver& s, Literal, uint32& aId) {
-	uint32 n = aId;
-	uint32 D = s.decisionLevel();
-	do {
-		DomAction& a = actions_[n];
-		uint16& prio = this->prio(a.var, a.mod);
-		if (s.value(a.var) == value_free && a.prio >= prio) {
-			applyAction(s, a, prio);
-			if (D != frames_.back().dl) {
-				s.addUndoWatch(D, this);
-				frames_.push_back(Frame(D, DomAction::UNDO_NIL));
-			}
-			pushUndo(frames_.back().head, n);
-		}
-	} while (actions_[n++].next);
-	return PropResult(true, true);
-}
-
-void DomainHeuristic::applyAction(Solver& s, DomAction& a, uint16& gPrio) {
-	std::swap(gPrio, a.prio);
-	switch (a.mod) {
-		default: assert(false); break;
-		case DomModType::Init:
-			score_[a.var].value = a.bias;
-			break;
-		case DomModType::Factor: std::swap(score_[a.var].factor, a.bias); break;
-		case DomModType::Level:
-			std::swap(score_[a.var].level, a.bias);
-			if (vars_.is_in_queue(a.var)) { vars_.update(a.var); }
-			break;
-		case DomModType::Sign:
-			int16 old = s.pref(a.var).get(ValueSet::user_value);
-			s.setPref(a.var, ValueSet::user_value, ValueRep(a.bias));
-			a.bias    = old;
-			break;
-	}
-}
-void DomainHeuristic::pushUndo(uint32& head, uint32 actionId) {
-	actions_[actionId].undo = head;
-	head = actionId;
+    Literal x = BaseType::doSelect(s);
+    s.stats.addDomChoice(isDom(score_[x.var()]));
+    return x;
 }
 
-void DomainHeuristic::undoLevel(Solver& s) {
-	while (frames_.back().dl >= s.decisionLevel()) {
-		for (uint32 n = frames_.back().head; n != DomAction::UNDO_NIL;) {
-			DomAction& a = actions_[n];
-			n            = a.undo;
-			applyAction(s, a, prio(a.var, a.mod));
-		}
-		frames_.pop_back();
-	}
+Constraint::PropResult DomainHeuristic::propagate(Solver& s, Literal, uint32_t& aId) {
+    uint32_t n  = aId;
+    uint32_t dl = s.decisionLevel();
+    do {
+        DomAction& a    = actions_[n];
+        uint16_t&  prio = this->prio(a.var, a.mod);
+        if (s.value(a.var) == value_free && a.prio >= prio) {
+            applyAction(s, a, prio);
+            if (dl != frames_.back().dl) {
+                s.addUndoWatch(dl, this);
+                frames_.push_back(Frame(dl, DomAction::undo_nil));
+            }
+            pushUndo(frames_.back().head, n);
+        }
+    } while (actions_[n++].next);
+    return PropResult(true, true);
 }
-template class ClaspVsids_t;
+
+void DomainHeuristic::applyAction(Solver& s, DomAction& a, uint16_t& gPrio) {
+    std::swap(gPrio, a.prio);
+    switch (static_cast(a.mod)) {
+        default                : POTASSCO_ASSERT_NOT_REACHED("unexpected domain modification");
+        case DomModType::init  : score_[a.var].value = a.bias; break;
+        case DomModType::factor: std::swap(score_[a.var].factor, a.bias); break;
+        case DomModType::level:
+            std::swap(score_[a.var].level, a.bias);
+            if (vars_.is_in_queue(a.var)) {
+                vars_.update(a.var);
+            }
+            break;
+        case DomModType::sign:
+            auto old = s.pref(a.var).get(ValueSet::user_value);
+            s.setPref(a.var, ValueSet::user_value, static_cast(a.bias));
+            a.bias = old;
+            break;
+    }
+}
+void DomainHeuristic::pushUndo(uint32_t& head, uint32_t actionId) {
+    actions_[actionId].undo = head;
+    head                    = actionId;
+}
+
+void DomainHeuristic::undoLevel(Solver& s) {
+    while (frames_.back().dl >= s.decisionLevel()) {
+        for (uint32_t n = frames_.back().head; n != DomAction::undo_nil;) {
+            DomAction& a = actions_[n];
+            n            = a.undo;
+            applyAction(s, a, prio(a.var, a.mod));
+        }
+        frames_.pop_back();
+    }
 }
+} // namespace Clasp
diff --git a/src/logic_program.cpp b/src/logic_program.cpp
index 1bc0e24..35be04f 100644
--- a/src/logic_program.cpp
+++ b/src/logic_program.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2013-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,1798 +22,1907 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
-#include 
-#include 
-#include 
+
 #include 
 #include 
 #include 
+#include 
 #include 
+#include 
+#include 
+#include 
+
 #include 
-#include 
-#include POTASSCO_EXT_INCLUDE(unordered_map)
-#include POTASSCO_EXT_INCLUDE(unordered_set)
-#include 
+
 #include 
-#include 
-namespace Clasp { namespace Asp {
+#include 
+#include 
+
+namespace Clasp::Asp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Statistics
 /////////////////////////////////////////////////////////////////////////////////////////
 #define RK(x) RuleStats::x
-const char* RuleStats::toStr(int k) {
-	POTASSCO_ASSERT(k >= 0 && uint32(k) <= numKeys(), "Invalid key");
-	switch (k) {
-		case Normal   : return "Normal";
-		case Choice   : return "Choice";
-		case Minimize : return "Minimize";
-		case Acyc     : return "Acyc";
-		case Heuristic: return "Heuristic";
-		default:        return "None";
-	}
-}
-uint32 RuleStats::sum() const {
-	return std::accumulate(key, key + numKeys(), uint32(0));
-}
-const char* BodyStats::toStr(int t) {
-	POTASSCO_ASSERT(t >= 0 && uint32(t) < numKeys(), "Invalid body type!");
-	switch (t) {
-		default           : return "Normal";
-		case Body_t::Count: return "Count";
-		case Body_t::Sum  : return "Sum";
-	}
-}
-uint32 BodyStats::sum() const {
-	return std::accumulate(key, key + numKeys(), uint32(0));
-}
+const char* RuleStats::toStr(unsigned k) {
+    POTASSCO_ASSERT(k <= numKeys(), "Invalid key");
+    switch (k) {
+        case normal   : return "Normal";
+        case choice   : return "Choice";
+        case minimize : return "Minimize";
+        case acyc     : return "Acyc";
+        case heuristic: return "Heuristic";
+        default       : return "None";
+    }
+}
+uint32_t    RuleStats::sum() const { return std::accumulate(key, key + numKeys(), 0u); }
+const char* BodyStats::toStr(unsigned t) {
+    POTASSCO_ASSERT(t < numKeys(), "Invalid body type!");
+    switch (t) {
+        default                            : return "Normal";
+        case to_underlying(BodyType::count): return "Count";
+        case to_underlying(BodyType::sum)  : return "Sum";
+    }
+}
+uint32_t BodyStats::sum() const { return std::accumulate(key, key + numKeys(), 0u); }
 
 namespace {
-template 
-double sumBodies(const LpStats* self) { return self->bodies[i].sum(); }
-template 
-double sumRules(const LpStats* self) { return self->rules[i].sum(); }
-double sumEqs(const LpStats* self)   { return self->eqs(); }
-}
-#define LP_STATS( APPLY )                                        \
-	APPLY("atoms"               , VALUE(atoms))                  \
-	APPLY("atoms_aux"           , VALUE(auxAtoms))               \
-	APPLY("disjunctions"        , VALUE(disjunctions[0]))        \
-	APPLY("disjunctions_non_hcf", VALUE(disjunctions[1]))        \
-	APPLY("bodies"              , FUNC(sumBodies<0>))            \
-	APPLY("bodies_tr"           , FUNC(sumBodies<1>))            \
-	APPLY("sum_bodies"          , VALUE(bodies[0][Body_t::Sum])) \
-	APPLY("sum_bodies_tr"       , VALUE(bodies[1][Body_t::Sum])) \
-	APPLY("count_bodies"        , VALUE(bodies[0][Body_t::Count]))\
-	APPLY("count_bodies_tr"     , VALUE(bodies[1][Body_t::Count]))\
-	APPLY("sccs"                , VALUE(sccs))                   \
-	APPLY("sccs_non_hcf"        , VALUE(nonHcfs))                \
-	APPLY("gammas"              , VALUE(gammas))                 \
-	APPLY("ufs_nodes"           , VALUE(ufsNodes))               \
-	APPLY("rules"               , FUNC(sumRules<0>))             \
-	APPLY("rules_normal"        , VALUE(rules[0][RK(Normal)]))   \
-	APPLY("rules_choice"        , VALUE(rules[0][RK(Choice)]))   \
-	APPLY("rules_minimize"      , VALUE(rules[0][RK(Minimize)])) \
-	APPLY("rules_acyc"          , VALUE(rules[0][RK(Acyc)]))     \
-	APPLY("rules_heuristic"     , VALUE(rules[0][RK(Heuristic)]))\
-	APPLY("rules_tr"            , FUNC(sumRules<1>))             \
-	APPLY("rules_tr_normal"     , VALUE(rules[1][RK(Normal)]))   \
-	APPLY("rules_tr_choice"     , VALUE(rules[1][RK(Choice)]))   \
-	APPLY("rules_tr_minimize"   , VALUE(rules[1][RK(Minimize)])) \
-	APPLY("rules_tr_acyc"       , VALUE(rules[1][RK(Acyc)]))     \
-	APPLY("rules_tr_heuristic"  , VALUE(rules[1][RK(Heuristic)]))\
-	APPLY("eqs"                 , FUNC(sumEqs))                  \
-	APPLY("eqs_atom"            , VALUE(eqs_[Var_t::Atom-1]))    \
-	APPLY("eqs_body"            , VALUE(eqs_[Var_t::Body-1]))    \
-	APPLY("eqs_other"           , VALUE(eqs_[Var_t::Hybrid-1]))
-
-void LpStats::reset() {
-	std::memset(this, 0, sizeof(LpStats));
-}
+template 
+double sumBodies(const LpStats* self) {
+    return self->bodies[I].sum();
+}
+template 
+double sumRules(const LpStats* self) {
+    return self->rules[I].sum();
+}
+double sumEqs(const LpStats* self) { return self->eqs(); }
+} // namespace
+#define LP_STATS(APPLY)                                                                                                \
+    APPLY("atoms", VALUE(atoms))                                                                                       \
+    APPLY("atoms_aux", VALUE(auxAtoms))                                                                                \
+    APPLY("disjunctions", VALUE(disjunctions[0]))                                                                      \
+    APPLY("disjunctions_non_hcf", VALUE(disjunctions[1]))                                                              \
+    APPLY("bodies", FUNC(sumBodies<0>))                                                                                \
+    APPLY("bodies_tr", FUNC(sumBodies<1>))                                                                             \
+    APPLY("sum_bodies", VALUE(bodies[0][to_underlying(BodyType::sum)]))                                                \
+    APPLY("sum_bodies_tr", VALUE(bodies[1][to_underlying(BodyType::sum)]))                                             \
+    APPLY("count_bodies", VALUE(bodies[0][to_underlying(BodyType::count)]))                                            \
+    APPLY("count_bodies_tr", VALUE(bodies[1][to_underlying(BodyType::count)]))                                         \
+    APPLY("sccs", VALUE(sccs))                                                                                         \
+    APPLY("sccs_non_hcf", VALUE(nonHcfs))                                                                              \
+    APPLY("gammas", VALUE(gammas))                                                                                     \
+    APPLY("ufs_nodes", VALUE(ufsNodes))                                                                                \
+    APPLY("rules", FUNC(sumRules<0>))                                                                                  \
+    APPLY("rules_normal", VALUE(rules[0][RK(normal)]))                                                                 \
+    APPLY("rules_choice", VALUE(rules[0][RK(choice)]))                                                                 \
+    APPLY("rules_minimize", VALUE(rules[0][RK(minimize)]))                                                             \
+    APPLY("rules_acyc", VALUE(rules[0][RK(acyc)]))                                                                     \
+    APPLY("rules_heuristic", VALUE(rules[0][RK(heuristic)]))                                                           \
+    APPLY("rules_tr", FUNC(sumRules<1>))                                                                               \
+    APPLY("rules_tr_normal", VALUE(rules[1][RK(normal)]))                                                              \
+    APPLY("rules_tr_choice", VALUE(rules[1][RK(choice)]))                                                              \
+    APPLY("rules_tr_minimize", VALUE(rules[1][RK(minimize)]))                                                          \
+    APPLY("rules_tr_acyc", VALUE(rules[1][RK(acyc)]))                                                                  \
+    APPLY("rules_tr_heuristic", VALUE(rules[1][RK(heuristic)]))                                                        \
+    APPLY("eqs", FUNC(sumEqs))                                                                                         \
+    APPLY("eqs_atom", VALUE(eqs_[+VarType::atom - 1]))                                                                 \
+    APPLY("eqs_body", VALUE(eqs_[+VarType::body - 1]))                                                                 \
+    APPLY("eqs_other", VALUE(eqs_[+VarType::hybrid - 1]))
 
 void LpStats::accu(const LpStats& o) {
-	atoms    += o.atoms;
-	auxAtoms += o.auxAtoms;
-	ufsNodes += o.ufsNodes;
-	if (sccs == PrgNode::noScc || o.sccs == PrgNode::noScc) {
-		sccs    = o.sccs;
-		nonHcfs = o.nonHcfs;
-		gammas  = o.gammas;
-	}
-	else {
-		sccs   += o.sccs;
-		nonHcfs+= o.nonHcfs;
-		gammas += o.gammas;
-	}
-	for (int i = 0; i != 2; ++i) {
-		disjunctions[i] += o.disjunctions[i];
-		for (uint32 k = 0; k != BodyStats::numKeys(); ++k) {
-			bodies[i][k] += o.bodies[i][k];
-		}
-		for (uint32 k = 0; k != RuleStats::numKeys(); ++k) {
-			rules[i][k] += o.rules[i][k];
-		}
-	}
-	for (int i = 0; i != sizeof(eqs_)/sizeof(eqs_[0]); ++i) {
-		eqs_[i] += o.eqs_[i];
-	}
+    atoms    += o.atoms;
+    auxAtoms += o.auxAtoms;
+    ufsNodes += o.ufsNodes;
+    if (sccs == PrgNode::no_scc || o.sccs == PrgNode::no_scc) {
+        sccs    = o.sccs;
+        nonHcfs = o.nonHcfs;
+        gammas  = o.gammas;
+    }
+    else {
+        sccs    += o.sccs;
+        nonHcfs += o.nonHcfs;
+        gammas  += o.gammas;
+    }
+    for (auto i : irange(disjunctions)) {
+        disjunctions[i] += o.disjunctions[i];
+        for (auto k : irange(BodyStats::numKeys())) { bodies[i][k] += o.bodies[i][k]; }
+        for (auto k : irange(RuleStats::numKeys())) { rules[i][k] += o.rules[i][k]; }
+    }
+    std::ranges::transform(eqs_, o.eqs_, eqs_, std::plus{});
 }
 
-static const char* lpStats_s[] = {
+static constexpr const char* lp_stats_s[] = {
 #define KEY(x, y) x,
-	LP_STATS(KEY)
+    LP_STATS(KEY)
 #undef KEY
-	"lp"
-};
-uint32 LpStats::size() {
-	return (sizeof(lpStats_s)/sizeof(const char*))-1;
-}
-const char* LpStats::key(uint32 i) {
-	POTASSCO_CHECK(i < size(), ERANGE);
-	return lpStats_s[i];
+        "lp"};
+uint32_t    LpStats::size() { return (sizeof(lp_stats_s) / sizeof(const char*)) - 1; }
+const char* LpStats::key(uint32_t i) {
+    POTASSCO_CHECK(i < size(), ERANGE);
+    return lp_stats_s[i];
 }
 StatisticObject LpStats::at(const char* k) const {
-#define MAP_IF(x, A) if (std::strcmp(k, x) == 0)  return A;
+#define MAP_IF(x, A)                                                                                                   \
+    if (std::strcmp(k, x) == 0)                                                                                        \
+        return A;
 #define VALUE(X) StatisticObject::value(&(X))
-#define FUNC(F) StatisticObject::value(this)
-	LP_STATS(MAP_IF)
+#define FUNC(F)  StatisticObject::value(this)
+    LP_STATS(MAP_IF)
 #undef VALUE
 #undef FUNC
 #undef MAP_IF
-	POTASSCO_CHECK(false, ERANGE);
+    POTASSCO_CHECK(false, ERANGE);
 }
 #undef LP_STATS
 /////////////////////////////////////////////////////////////////////////////////////////
 // class LogicProgram
 /////////////////////////////////////////////////////////////////////////////////////////
-static PrgAtom trueAtom_g(0, false);
-static const uint32 falseId = PrgNode::noNode;
-static const uint32 bodyId  = PrgNode::noNode + 1;
-static bool init_trueAtom_g = (trueAtom_g.setEq(0), true);
-inline bool isAtom(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) < bodyId; }
-inline bool isBody(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) >= bodyId; }
-inline Id_t nodeId(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) - (isAtom(uid) ? 0 : bodyId); }
-inline bool signId(Id_t uid) { return Potassco::lit(uid) < 0; }
+constexpr uint32_t false_id = PrgNode::no_node;
+constexpr uint32_t body_id  = PrgNode::no_node + 1;
+constexpr bool     isAtom(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) < body_id; }
+constexpr bool     isBody(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) >= body_id; }
+constexpr Id_t     nodeId(Id_t uid) { return Potassco::atom(Potassco::lit(uid)) - (isAtom(uid) ? 0 : body_id); }
+constexpr bool     signId(Id_t uid) { return Potassco::lit(uid) < 0; }
 
-namespace {
-typedef std::pair AtomVal;
-inline uint32 encodeExternal(Atom_t a, Potassco::Value_t value) {
-	return (a << 2) | static_cast(value);
-}
-inline AtomVal decodeExternal(uint32 x) {
-	return AtomVal(x >> 2, static_cast(x & 3u));
-}
-struct LessBodySize {
-	LessBodySize(const BodyList& bl) : bodies_(&bl) {}
-	bool operator()(Id_t b1, Id_t b2 ) const {
-		return (*bodies_)[b1]->size() < (*bodies_)[b2]->size()
-			|| ((*bodies_)[b1]->size() == (*bodies_)[b2]->size() && (*bodies_)[b1]->type() < (*bodies_)[b2]->type());
-	}
-private:
-	const BodyList* bodies_;
-};
-// Adds nogoods representing this node to the solver.
-template 
-bool toConstraint(NT* node, const LogicProgram& prg, ClauseCreator& c) {
-	if (node->value() != value_free && !prg.ctx()->addUnary(node->trueLit())) {
-    	return false;
-	}
-	return !node->relevant() || node->addConstraints(prg, c);
+using AtomVal = std::pair;
+constexpr uint32_t encodeExternal(Atom_t a, Potassco::TruthValue value) {
+    return (a << 2) | static_cast(value);
 }
+constexpr AtomVal decodeExternal(uint32_t x) { return {x >> 2, static_cast(x & 3u)}; }
+
+// Adds nogoods representing this node to the solver.
+template 
+static bool toConstraint(NodeType* node, const LogicProgram& prg, ClauseCreator& c) {
+    if (node->value() != value_free && not prg.ctx()->addUnary(node->trueLit())) {
+        return false;
+    }
+    return not node->relevant() || node->addConstraints(prg, c);
 }
 
-typedef POTASSCO_EXT_NS::unordered_set IdSet;
-typedef POTASSCO_EXT_NS::unordered_multimap IndexMap;
-typedef IndexMap::iterator              IndexIter;
-typedef std::pair IndexRange;
+using IdSet    = std::unordered_set;
+using IndexMap = std::unordered_multimap;
 struct LogicProgram::Aux {
-	AtomList  scc;          // atoms that are strongly connected
-	DomRules  dom;          // list of domain heuristic directives
-	AcycRules acyc;         // list of user-defined edges for acyclicity check
-	VarVec    project;      // atoms in projection directives
-	VarVec    external;     // atoms in external directives
-	IdSet     skippedHeads; // heads of rules that have been removed during parsing
+    AtomList  scc;          // atoms that are strongly connected
+    DomRules  dom;          // list of domain heuristic directives
+    AcycRules acyc;         // list of user-defined edges for acyclicity check
+    VarVec    project;      // atoms in projection directives
+    VarVec    external;     // atoms in external directives
+    IdSet     skippedHeads; // heads of rules that have been removed during parsing
 };
 
 struct LogicProgram::IndexData {
-	IndexData() : distTrue(false), outState(false) {}
-	IndexMap body;  // hash -> body id
-	IndexMap disj;  // hash -> disjunction id
-	IndexMap domEq; // maps eq atoms modified by dom heuristic to aux vars
-	VarVec   outSet;// atoms with non-trivial out state (shown and/or projected)
-	bool     distTrue;
-	bool     outState;
+    IndexMap body;   // hash -> body id
+    IndexMap disj;   // hash -> disjunction id
+    IndexMap domEq;  // maps eq atoms modified by dom heuristic to aux vars
+    VarVec   outSet; // atoms with non-trivial out state (shown and/or projected)
+    PrgAtom* eqTrue{nullptr};
+    bool     distTrue{false};
+    bool     outState{false};
 };
 
-LogicProgram::LogicProgram() : theory_(0), input_(1, UINT32_MAX), auxData_(0), incData_(0) {
-	POTASSCO_ASSERT(init_trueAtom_g, "invalid static init");
-	index_ = new IndexData();
-}
-LogicProgram::~LogicProgram() { dispose(true); delete index_; }
+LogicProgram::LogicProgram()
+    : index_(std::make_unique())
+    , theory_(nullptr)
+    , input_(1, UINT32_MAX)
+    , statsId_(0)
+    , auxData_(nullptr)
+    , incData_(nullptr) {}
+LogicProgram::~LogicProgram() { dispose(true); }
 LogicProgram::Incremental::Incremental() : startScc(0) {}
 void LogicProgram::dispose(bool force) {
-	// remove rules
-	std::for_each(bodies_.begin(), bodies_.end(), DestroyObject());
-	std::for_each(disjunctions_.begin(), disjunctions_.end(), DestroyObject());
-	std::for_each(extended_.begin(), extended_.end(), DeleteObject());
-	std::for_each(minimize_.begin(), minimize_.end(), DeleteObject());
-	PodVector::destruct(show_);
-	delete auxData_; auxData_ = 0;
-	MinList().swap(minimize_);
-	RuleList().swap(extended_);
-	BodyList().swap(bodies_);
-	DisjList().swap(disjunctions_);
-	index_->body.clear();
-	index_->disj.clear();
-	VarVec().swap(initialSupp_);
-	if (theory_) {
-		theory_->reset();
-	}
-	if (force) {
-		deleteAtoms(0);
-		AtomList().swap(atoms_);
-		AtomState().swap(atomState_);
-		LpLitVec().swap(assume_);
-		delete theory_;
-		delete incData_;
-		VarVec().swap(propQ_);
-		stats.reset();
-		incData_ = 0;
-		theory_  = 0;
-		input_   = AtomRange(1, UINT32_MAX);
-		statsId_ = 0;
-		*index_  = IndexData();
-	}
-	rule_.clear();
-}
-void LogicProgram::deleteAtoms(uint32 start) {
-	for (AtomList::const_iterator it = atoms_.begin() + start, end = atoms_.end(); it != end; ++it) {
-		if (*it != &trueAtom_g) { delete *it; }
-	}
+    // remove rules
+    std::ranges::for_each(bodies_, DestroyObject());
+    std::ranges::for_each(disjunctions_, DestroyObject());
+    std::ranges::for_each(extended_, DeleteObject());
+    std::ranges::for_each(minimize_, DeleteObject());
+    PodVector::destruct(show_);
+    auxData_.reset();
+    discardVec(minimize_);
+    discardVec(extended_);
+    discardVec(bodies_);
+    discardVec(disjunctions_);
+    index_->body.clear();
+    index_->disj.clear();
+    discardVec(initialSupp_);
+    if (theory_) {
+        theory_->reset();
+    }
+    if (force) {
+        deleteAtoms(0);
+        discardVec(atoms_);
+        discardVec(atomState_);
+        discardVec(assume_);
+        theory_.reset();
+        incData_.reset();
+        discardVec(propQ_);
+        stats    = {};
+        input_   = AtomRange(1, UINT32_MAX);
+        statsId_ = 0;
+        *index_  = IndexData();
+    }
+    rule_.clear();
+}
+void LogicProgram::deleteAtoms(uint32_t start) {
+    for (PrgAtom* atom : atoms(start)) {
+        if (atom != index_->eqTrue) {
+            delete atom;
+        }
+    }
+    if (start == 0 && index_ && index_->eqTrue) {
+        delete std::exchange(index_->eqTrue, nullptr);
+    }
 }
 bool LogicProgram::doStartProgram() {
-	dispose(true);
-	// atom 0 is always true
-	PrgAtom* trueAt = new PrgAtom(0, false);
-	atoms_.push_back(trueAt);
-	trueAt->assignValue(value_true);
-	trueAt->setInUpper(true);
-	trueAt->setLiteral(lit_true());
-	atomState_.set(0, AtomState::fact_flag);
-	auxData_ = new Aux();
-	return true;
+    dispose(true);
+    // atom 0 is always true
+    auto* trueAt = new PrgAtom(0, false);
+    atoms_.push_back(trueAt);
+    trueAt->assignValue(value_true);
+    trueAt->setInUpper(true);
+    trueAt->setLiteral(lit_true);
+    atomState_.set(0, AtomState::fact_flag);
+    auxData_ = std::make_unique();
+    return true;
 }
 void LogicProgram::setOptions(const AspOptions& opts) {
-	opts_ = opts;
-	if (opts.suppMod) { opts_.noSCC = 1; }
-	if (opts.suppMod && ctx() && ctx()->sccGraph.get()) {
-		ctx()->warn("'supp-models' ignored for non-tight programs.");
-		opts_.suppMod = 0;
-		opts_.noSCC   = 0;
-	}
-}
-void LogicProgram::enableDistinctTrue() {
-	index_->distTrue = true;
-}
-void LogicProgram::enableOutputState() {
-	index_->outState = true;
-}
-ProgramParser* LogicProgram::doCreateParser() {
-	return new AspParser(*this);
-}
+    opts_ = opts;
+    if (opts.suppMod) {
+        opts_.noSCC = 1;
+    }
+    if (opts.suppMod && ctx() && ctx()->sccGraph.get()) {
+        ctx()->warn("'supp-models' ignored for non-tight programs.");
+        opts_.suppMod = 0;
+        opts_.noSCC   = 0;
+    }
+}
+void LogicProgram::enableDistinctTrue() { index_->distTrue = true; }
+void LogicProgram::enableOutputState() { index_->outState = true; }
+auto LogicProgram::doCreateParser() -> ProgramParser* { return new AspParser(*this); }
 bool LogicProgram::doUpdateProgram() {
-	if (!incData_) { incData_ = new Incremental(); }
-	if (!frozen()) { return true; }
-	// delete bodies/disjunctions...
-	dispose(false);
-	setFrozen(false);
-	auxData_ = new Aux();
-	assume_.clear();
-	if (theory_) { theory_->update(); }
-	incData_->unfreeze.clear();
-	input_.hi = std::min(input_.hi, endAtom());
-	// reset prop queue and add supported atoms from previous steps
-	// {ai | ai in P}.
-	PrgBody* support = input_.hi > 1 ? getTrueBody() : 0;
-	propQ_.clear();
-	for (Atom_t i = 1, end = startAuxAtom(); i != end; ++i) {
-		if (getRootId(i) >= end) {
-			// atom is equivalent to some aux atom - make i the new root
-			uint32 r = getRootId(i);
-			std::swap(atoms_[i], atoms_[r]);
-			atoms_[r]->setEq(i);
-		}
-		// remove dangling references
-		PrgAtom* a = atoms_[i];
-		a->clearSupports();
-		a->clearDeps(PrgAtom::dep_all);
-		a->setIgnoreScc(false);
-		if (a->relevant() || a->frozen()) {
-			ValueRep v = a->value();
-			a->setValue(value_free);
-			a->resetId(i, !a->frozen());
-			if (ctx()->master()->value(a->var()) != value_free && !a->frozen()) {
-				v = ctx()->master()->isTrue(a->literal()) ? value_true : value_false;
-			}
-			if (v != value_free) { assignValue(a, v, PrgEdge::noEdge()); }
-			if (!a->frozen() && a->value() != value_false) {
-				a->setIgnoreScc(true);
-				support->addHead(a, PrgEdge::GammaChoice);
-			}
-		}
-		else if (a->removed() || (!a->eq() && a->value() == value_false)) {
-			a->resetId(i, true);
-			a->setValue(value_false);
-			atomState_.set(i, AtomState::false_flag);
-		}
-	}
-	// delete any introduced aux atoms
-	// this is safe because aux atoms are never part of the input program
-	// it is necessary in order to free their ids, i.e. the id of an aux atom
-	// from step I might be needed for a program atom in step I+1
-	deleteAtoms(startAuxAtom());
-	atoms_.erase(atoms_.begin()+startAuxAtom(), atoms_.end());
-	uint32 nAtoms = (uint32)atoms_.size();
-	atomState_.resize(nAtoms);
-	input_ = AtomRange(nAtoms, UINT32_MAX);
-	stats.reset();
-	statsId_ = 0;
-	return true;
+    if (not incData_) {
+        incData_ = std::make_unique();
+    }
+    if (not frozen()) {
+        return true;
+    }
+    // delete bodies/disjunctions...
+    dispose(false);
+    setFrozen(false);
+    auxData_ = std::make_unique();
+    assume_.clear();
+    if (theory_) {
+        theory_->update();
+    }
+    incData_->unfreeze.clear();
+    input_.hi = std::min(input_.hi, endAtom());
+    // reset prop queue and add supported atoms from previous steps
+    // {'ai' | 'ai' in P}.
+    PrgBody* support = input_.hi > 1 ? getTrueBody() : nullptr;
+    propQ_.clear();
+    for (auto end = startAuxAtom(); Atom_t i : irange(1u, end)) {
+        if (getRootId(i) >= end) {
+            // atom is equivalent to some aux atom - make i the new root
+            uint32_t r = getRootId(i);
+            std::swap(atoms_[i], atoms_[r]);
+            atoms_[r]->setEq(i);
+        }
+        // remove dangling references
+        PrgAtom* a = atoms_[i];
+        a->clearSupports();
+        a->clearDeps(PrgAtom::dep_all);
+        a->setIgnoreScc(false);
+        if (a->relevant() || a->frozen()) {
+            auto v = a->value();
+            a->setValue(value_free);
+            a->resetId(i, not a->frozen());
+            if (ctx()->master()->value(a->var()) != value_free && not a->frozen()) {
+                v = ctx()->master()->isTrue(a->literal()) ? value_true : value_false;
+            }
+            if (v != value_free) {
+                assignValue(a, v, PrgEdge::noEdge());
+            }
+            if (not a->frozen() && a->value() != value_false) {
+                a->setIgnoreScc(true);
+                support->addHead(a, PrgEdge::gamma_choice);
+            }
+        }
+        else if (a->removed() || (not a->eq() && a->value() == value_false)) {
+            a->resetId(i, true);
+            a->setValue(value_false);
+            atomState_.set(i, AtomState::false_flag);
+        }
+    }
+    // delete any introduced aux atoms
+    // this is safe because aux atoms are never part of the input program
+    // it is necessary in order to free their ids, i.e. the id of an aux atom
+    // from step I might be needed for a program atom in step I+1
+    deleteAtoms(startAuxAtom());
+    shrinkVecTo(atoms_, startAuxAtom());
+    auto nAtoms = size32(atoms_);
+    atomState_.resize(nAtoms);
+    input_   = AtomRange(nAtoms, UINT32_MAX);
+    stats    = {};
+    statsId_ = 0;
+    return true;
 }
 bool LogicProgram::doEndProgram() {
-	if (!frozen() && ctx()->ok()) {
-		prepareProgram(!opts_.noSCC);
-		addConstraints();
-		addDomRules();
-		addAcycConstraint();
-	}
-	return ctx()->ok();
+    if (not frozen() && ctx()->ok()) {
+        prepareProgram(not opts_.noSCC);
+        addConstraints();
+        addDomRules();
+        addAcycConstraint();
+    }
+    return ctx()->ok();
 }
 
 bool LogicProgram::clone(SharedContext& oCtx) {
-	assert(frozen());
-	if (&oCtx == ctx()) {
-		return true;
-	}
-	for (Var v = oCtx.numVars() + 1; ctx()->validVar(v); ++v) {
-		oCtx.addVar(Var_t::Atom, ctx()->varInfo(v).rep);
-	}
-	SharedContext* t = ctx();
-	setCtx(&oCtx);
-	bool ok = addConstraints();
-	if (ok) {
-		oCtx.output    = ctx()->output;
-		oCtx.heuristic = t->heuristic;
-	}
-	setCtx(t);
-	return ok;
+    assert(frozen());
+    if (&oCtx == ctx()) {
+        return true;
+    }
+    for (Var_t v = oCtx.numVars() + 1; ctx()->validVar(v); ++v) { oCtx.addVar(VarType::atom, ctx()->varInfo(v).rep); }
+    SharedContext* t = ctx();
+    setCtx(&oCtx);
+    bool ok = addConstraints();
+    if (ok) {
+        oCtx.output    = ctx()->output;
+        oCtx.heuristic = t->heuristic;
+    }
+    setCtx(t);
+    return ok;
 }
 
 void LogicProgram::addMinimize() {
-	POTASSCO_ASSERT(frozen());
-	for (MinList::iterator it = minimize_.begin(), end = minimize_.end(); it != end; ++it) {
-		const LpWLitVec& lits = (*it)->lits;
-		const weight_t   prio = (*it)->prio;
-		for (LpWLitVec::const_iterator xIt = lits.begin(), xEnd = lits.end(); xIt != xEnd; ++xIt) {
-			addMinLit(prio, WeightLiteral(getLiteral(Potassco::id(xIt->lit)), xIt->weight));
-		}
-		// Make sure minimize constraint is not empty
-		if (lits.empty()) addMinLit(prio, WeightLiteral(lit_false(), 1));
-	}
+    POTASSCO_ASSERT(frozen());
+    for (const auto* min : minimize_) {
+        auto prio = min->bound();
+        auto lits = min->sumLits();
+        for (const auto& [lit, weight] : lits) { addMinLit(prio, WeightLiteral{getLiteral(Asp::id(lit)), weight}); }
+        // Make sure minimize constraint is not empty
+        if (lits.empty()) {
+            addMinLit(prio, WeightLiteral{lit_false, 1});
+        }
+    }
 }
 static void outRule(Potassco::AbstractProgram& out, const Rule& r) {
-	if (r.normal()) { out.rule(r.ht, r.head, r.cond); }
-	else            { out.rule(r.ht, r.head, r.agg.bound, r.agg.lits); }
+    if (r.normal()) {
+        out.rule(r.ht, r.head, r.cond);
+    }
+    else {
+        out.rule(r.ht, r.head, r.agg.bound, r.agg.lits);
+    }
 }
 
 void LogicProgram::accept(Potassco::AbstractProgram& out) {
-	if (!ok()) {
-		out.rule(Head_t::Disjunctive, Potassco::toSpan(), Potassco::toSpan());
-		return;
-	}
-	// visit external directives
-	for (VarVec::const_iterator it = auxData_->external.begin(), end = auxData_->external.end(); it != end; ++it) {
-		AtomVal x = decodeExternal(*it);
-		out.external(x.first, x.second);
-	}
-	// visit eq- and assigned atoms
-	for (Atom_t i = startAtom(); i < atoms_.size(); ++i) {
-		if (atoms_[i]->eq()) {
-			Potassco::AtomSpan head = Potassco::toSpan(&i, 1);
-			Potassco::Lit_t    body = Potassco::lit(getRootId(i));
-			if (isFact(Potassco::atom(body))) {
-				out.rule(Head_t::Disjunctive, head, Potassco::toSpan());
-			}
-			else if (inProgram(Potassco::atom(body))) {
-				out.rule(Head_t::Disjunctive, head, Potassco::toSpan(&body, 1));
-			}
-		}
-		else if (!atomState_.isFact(i) && atoms_[i]->value() != value_free) {
-			Potassco::AtomSpan head = Potassco::toSpan();
-			Potassco::Lit_t    body = Potassco::neg(i);
-			if (atoms_[i]->value() != value_false) {
-				out.rule(Head_t::Disjunctive, head, Potassco::toSpan(&body, 1));
-			}
-			else if (inProgram(i)) {
-				body = Potassco::neg(body);
-				out.rule(Head_t::Disjunctive, head, Potassco::toSpan(&body, 1));
-			}
-		}
-	}
-	// visit program rules
-	const bool simp = frozen();
-	using Potassco::Lit_t;
-	VarVec choice;
-	for (BodyList::iterator bIt = bodies_.begin(); bIt != bodies_.end(); ++bIt) {
-		rule_.clear();
-		choice.clear();
-		Atom_t head;
-		Lit_t  auxB;
-		PrgBody* b = *bIt;
-		if (b->relevant() && (b->inRule() || b->value() == value_false) && b->toData(*this, rule_)) {
-			if (b->value() == value_false) {
-				outRule(out, rule_.rule());
-				continue;
-			}
-			uint32 numDis = 0;
-			Rule r = rule_.rule();
-			for (PrgBody::head_iterator hIt = b->heads_begin(); hIt != b->heads_end(); ++hIt) {
-				if (hIt->isGamma() || (simp && !getHead(*hIt)->hasVar())) { continue; }
-				if (hIt->isAtom() && hIt->node() && inProgram(hIt->node())) {
-					if      (hIt->isNormal()) { r.head = Potassco::toSpan(&(head = hIt->node()), 1); outRule(out, r); }
-					else if (hIt->isChoice()) { choice.push_back(hIt->node()); }
-					if (simp && getRootAtom(hIt->node())->var() == b->var() && !r.normal()) {
-						// replace complex body with head atom
-						auxB = Potassco::lit(hIt->node());
-						if (getRootAtom(hIt->node())->literal() != b->literal()) { auxB *= -1; }
-						r.bt   = Body_t::Normal;
-						r.cond = Potassco::toSpan(&auxB, 1);
-					}
-				}
-				else if (hIt->isDisj()) { ++numDis; }
-			}
-			if (!choice.empty()) {
-				r.head = Potassco::toSpan(choice);
-				r.ht   = Head_t::Choice;
-				outRule(out, r);
-			}
-			for (PrgBody::head_iterator hIt = b->heads_begin(); hIt != b->heads_end() && numDis; ++hIt) {
-				if (hIt->isDisj()) {
-					PrgDisj* d = getDisj(hIt->node());
-					r.head = Potassco::toSpan(d->begin(), d->size());
-					r.ht   = Head_t::Disjunctive;
-					outRule(out, r);
-					--numDis;
-				}
-			}
-		}
-	}
-	LpWLitVec wlits;
-	for (MinList::iterator it = minimize_.begin(), end = minimize_.end(); it != end; ++it) {
-		Potassco::WeightLitSpan ws = Potassco::toSpan((*it)->lits);
-		for (const Potassco::WeightLit_t* x = Potassco::begin(ws), *xEnd = Potassco::end(ws); x != xEnd; ++x) {
-			if (x->weight == 0 || !inProgram(Potassco::atom(*x))) { // simplify literals
-				wlits.assign(Potassco::begin(ws), x);
-				for (; x != xEnd; ++x) {
-					if (x->weight == 0)
-						continue;
-					Atom_t atom = getRootId(Potassco::atom(*x));
-					if (x->weight < 0 || x->lit < 0 || inProgram(atom)) {
-						wlits.push_back(*x);
-					}
-				}
-				ws = Potassco::toSpan(wlits);
-				break;
-			}
-		}
-		out.minimize((*it)->prio, ws);
-	}
-	Potassco::LitVec lits;
-	// visit output directives
-	for (ShowVec::const_iterator it = show_.begin(); it != show_.end(); ++it) {
-		if (extractCondition(it->first, lits)) {
-			out.output(Potassco::toSpan(it->second.c_str(), std::strlen(it->second.c_str())), Potassco::toSpan(lits));
-		}
-	}
-	// visit projection directives
-	if (!auxData_->project.empty()) {
-		out.project(auxData_->project.back() ? Potassco::toSpan(auxData_->project) : Potassco::toSpan());
-	}
-	// visit assumptions
-	if (!assume_.empty()) {
-		out.assume(Potassco::toSpan(assume_));
-	}
-	// visit heuristics
-	if (!auxData_->dom.empty()) {
-		for (DomRules::const_iterator it = auxData_->dom.begin(), end = auxData_->dom.end(); it != end; ++it) {
-			if (extractCondition(it->cond, lits)) {
-				out.heuristic(it->atom, static_cast(it->type), it->bias, it->prio, Potassco::toSpan(lits));
-			}
-		}
-	}
-	// visit acyc edges
-	if (!auxData_->acyc.empty()) {
-		for (AcycRules::const_iterator it = auxData_->acyc.begin(), end = auxData_->acyc.end(); it != end; ++it) {
-			if (extractCondition(it->cond, lits)) {
-				out.acycEdge(it->node[0], it->node[1], Potassco::toSpan(lits));
-			}
-		}
-	}
-	if (theory_ && theory_->numAtoms()) {
-		struct This : public Potassco::TheoryData::Visitor {
-			This(const Asp::LogicProgram& p, Potassco::AbstractProgram& o, Potassco::LitVec& c) : self(&p), out(&o), cond(&c) {}
-			virtual void visit(const Potassco::TheoryData& data, Potassco::Id_t termId, const Potassco::TheoryTerm& t) {
-				if (!addSeen(termId, 1)) { return; }
-				data.accept(t, *this, Potassco::TheoryData::visit_current);
-				Potassco::print(*out, termId, t);
-			}
-			virtual void visit(const Potassco::TheoryData& data, Potassco::Id_t elemId, const Potassco::TheoryElement& e) {
-				if (!addSeen(elemId, 2)) { return; }
-				data.accept(e, *this, Potassco::TheoryData::visit_current);
-				cond->clear();
-				if (e.condition()) { self->extractCondition(e.condition(), *cond); }
-				out->theoryElement(elemId, e.terms(), Potassco::toSpan(*cond));
-			}
-			virtual void visit(const Potassco::TheoryData& data, const Potassco::TheoryAtom& a) {
-				data.accept(a, *this, Potassco::TheoryData::visit_current);
-				Potassco::print(*out, a);
-				const Atom_t id = a.atom();
-				if (self->validAtom(id) && self->atomState_.isSet(id, AtomState::false_flag) && !self->inProgram(id)) {
-					Potassco::Lit_t x = Potassco::lit(id);
-					out->rule(Head_t::Disjunctive, Potassco::toSpan(), Potassco::toSpan(&x, 1));
-				}
-			}
-			bool addSeen(Potassco::Id_t id, unsigned char n) {
-				if (id >= seen.size()) { seen.resize(id + 1, 0); }
-				unsigned char old = seen[id];
-				return (seen[id] |= n) != old;
-			}
-			typedef PodVector::type IdSet;
-			const Asp::LogicProgram*   self;
-			Potassco::AbstractProgram* out;
-			Potassco::LitVec*          cond;
-			IdSet                      seen;
-		} self(*this, out, lits);
-		theory_->accept(self, Potassco::TheoryData::visit_current);
-	}
+    if (not ok()) {
+        out.rule(HeadType::disjunctive, {}, {});
+        return;
+    }
+    // visit external directives
+    for (auto e : auxData_->external) {
+        auto [atom, value] = decodeExternal(e);
+        out.external(atom, value);
+    }
+    // visit eq- and assigned atoms
+    for (auto i : irange(startAtom(), size32(atoms_))) {
+        if (auto* atom = atoms_[i]; atom->eq()) {
+            auto head = Potassco::toSpan(i);
+            if (auto body = Potassco::lit(getRootId(i)); isFact(Potassco::atom(body))) {
+                out.rule(HeadType::disjunctive, head, {});
+            }
+            else if (inProgram(Potassco::atom(body))) {
+                out.rule(HeadType::disjunctive, head, Potassco::toSpan(body));
+            }
+        }
+        else if (not atomState_.isFact(i) && atom->value() != value_free) {
+            auto body = Potassco::neg(i);
+            if (atoms_[i]->value() != value_false) {
+                out.rule(HeadType::disjunctive, {}, Potassco::toSpan(body));
+            }
+            else if (inProgram(i)) {
+                body = Potassco::neg(body);
+                out.rule(HeadType::disjunctive, {}, Potassco::toSpan(body));
+            }
+        }
+    }
+    // visit program rules
+    const bool simp = frozen();
+    using Potassco::Lit_t;
+    VarVec choice;
+    for (auto* b : bodies_) {
+        rule_.clear();
+        choice.clear();
+        if (b->relevant() && (b->inRule() || b->value() == value_false) && b->toData(*this, rule_)) {
+            if (b->value() == value_false) {
+                outRule(out, rule_.rule());
+                continue;
+            }
+            uint32_t numDis = 0;
+            Atom_t   head;
+            Lit_t    auxB;
+            Rule     r = rule_.rule();
+            for (auto h : b->heads()) {
+                if (h.isGamma() || (simp && not getHead(h)->hasVar())) {
+                    continue;
+                }
+                if (h.isAtom() && h.node() && inProgram(h.node())) {
+                    if (h.isNormal()) {
+                        head   = h.node();
+                        r.head = Potassco::toSpan(head);
+                        outRule(out, r);
+                    }
+                    else if (h.isChoice()) {
+                        choice.push_back(h.node());
+                    }
+                    if (simp && getRootAtom(h.node())->var() == b->var() && not r.normal()) {
+                        // replace complex body with head atom
+                        auxB = Potassco::lit(h.node());
+                        if (getRootAtom(h.node())->literal() != b->literal()) {
+                            auxB *= -1;
+                        }
+                        r.bt   = BodyType::normal;
+                        r.cond = Potassco::toSpan(auxB);
+                    }
+                }
+                else if (h.isDisj()) {
+                    ++numDis;
+                }
+            }
+            if (not choice.empty()) {
+                r.head = choice;
+                r.ht   = HeadType::choice;
+                outRule(out, r);
+            }
+            if (numDis) {
+                for (auto h : b->heads()) {
+                    if (h.isDisj()) {
+                        PrgDisj* d = getDisj(h.node());
+                        r.head     = d->atoms();
+                        r.ht       = HeadType::disjunctive;
+                        outRule(out, r);
+                        if (--numDis == 0) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    LpWLitVec wlits;
+    auto      pred = [&](Potassco::WeightLit x) {
+        return x.weight != 0 && (x.weight < 0 || x.lit < 0 || inProgram(getRootId(Potassco::atom(x))));
+    };
+    for (const auto* min : minimize_) {
+        WeightLitSpan ws = min->sumLits();
+        if (auto it = std::ranges::find_if_not(ws, pred); it != ws.end()) {
+            // simplify literals
+            wlits.assign(ws.begin(), it);
+            for (++it; it != ws.end(); ++it) {
+                if (pred(*it)) {
+                    wlits.push_back(*it);
+                }
+            }
+            ws = wlits;
+        }
+        out.minimize(min->bound(), ws);
+    }
+    Potassco::LitVec lits;
+    // visit output directives
+    for (const auto& [id, name] : show_) {
+        if (extractCondition(id, lits)) {
+            out.output(name.c_str(), lits);
+        }
+    }
+    // visit projection directives
+    if (not auxData_->project.empty()) {
+        out.project(auxData_->project.back() ? Potassco::AtomSpan(auxData_->project) : Potassco::AtomSpan{});
+    }
+    // visit assumptions
+    if (not assume_.empty()) {
+        out.assume(assume_);
+    }
+    // visit heuristics
+    for (const auto& dom : auxData_->dom) {
+        if (extractCondition(dom.cond, lits)) {
+            out.heuristic(dom.atom, static_cast(dom.type), dom.bias, dom.prio, lits);
+        }
+    }
+    // visit acyc edges
+    for (const auto& a : auxData_->acyc) {
+        if (extractCondition(a.cond, lits)) {
+            out.acycEdge(static_cast(a.node[0]), static_cast(a.node[1]), lits);
+        }
+    }
+
+    if (theory_ && theory_->numAtoms()) {
+        struct This final : TheoryData::Visitor {
+            This(const LogicProgram& p, Potassco::AbstractProgram& o, Potassco::LitVec& c)
+                : self(&p)
+                , out(&o)
+                , cond(&c) {}
+            void visit(const TheoryData& data, Id_t termId, const Potassco::TheoryTerm& t) override {
+                if (not addTermSeen(termId)) {
+                    return;
+                }
+                data.accept(t, *this, TheoryData::visit_current);
+                Potassco::print(*out, termId, t);
+            }
+            void visit(const TheoryData& data, Id_t elemId, const Potassco::TheoryElement& e) override {
+                if (not addElemSeen(elemId)) {
+                    return;
+                }
+                data.accept(e, *this, TheoryData::visit_current);
+                cond->clear();
+                if (e.condition()) {
+                    self->extractCondition(e.condition(), *cond);
+                }
+                out->theoryElement(elemId, e.terms(), *cond);
+            }
+            void visit(const TheoryData& data, const Potassco::TheoryAtom& a) override {
+                data.accept(a, *this, TheoryData::visit_current);
+                Potassco::print(*out, a);
+                const Atom_t id = a.atom();
+                if (self->validAtom(id) && self->atomState_.isSet(id, AtomState::false_flag) &&
+                    not self->inProgram(id)) {
+                    Lit_t x = Potassco::lit(id);
+                    out->rule(HeadType::disjunctive, {}, Potassco::toSpan(x));
+                }
+            }
+            bool addTermSeen(Id_t id) { return seen.add(id * 2); }
+            bool addElemSeen(Id_t id) { return seen.add((id * 2) + 1); }
+
+            const LogicProgram*        self;
+            Potassco::AbstractProgram* out;
+            Potassco::LitVec*          cond;
+            Potassco::DynamicBitset    seen;
+        } self(*this, out, lits);
+        theory_->accept(self, TheoryData::visit_current);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Program mutating functions
 /////////////////////////////////////////////////////////////////////////////////////////
-#define check_not_frozen() POTASSCO_REQUIRE(!frozen(), "Can't update frozen program!")
-static inline const char* getAtomName(const LogicProgram& prg, Atom_t a) {
-	const char* ret = prg.findName(a);
-	return ret && *ret ? ret : "_";
+#define CHECK_NOT_FROZEN() POTASSCO_CHECK_PRE(not frozen(), "Can't update frozen program!")
+static const char* getAtomName(const LogicProgram& prg, Atom_t a) {
+    const char* ret = prg.findName(a);
+    return ret && *ret ? ret : "_";
 }
-#define check_modular(x, atomId) POTASSCO_REQUIRE(x, "redefinition of atom <'%s',%u>", getAtomName(*this, (atomId)), (atomId))
+#define CHECK_MODULAR(x, atomId)                                                                                       \
+    POTASSCO_CHECK_PRE(x, "redefinition of atom <'%s',%u>", getAtomName(*this, (atomId)), (atomId))
 
 Atom_t LogicProgram::newAtom() {
-	check_not_frozen();
-	Atom_t id = static_cast(atoms_.size());
-	atoms_.push_back( new PrgAtom(id) );
-	return id;
-}
-Id_t LogicProgram::newCondition(const Potassco::LitSpan& cond) {
-	check_not_frozen();
-	SRule meta;
-	if (simplifyNormal(Head_t::Disjunctive, Potassco::toSpan(), cond, rule_, meta)) {
-		Rule r = rule_.rule();
-		if (r.cond.size == 0) { return 0; }
-		if (r.cond.size == 1) { return Potassco::id(r.cond[0]); }
-		PrgBody* b = getBodyFor(r, meta);
-		b->markFrozen();
-		return static_cast(Clasp::Asp::bodyId | b->id());
-	}
-	return static_cast(Clasp::Asp::falseId);
-}
-LogicProgram& LogicProgram::addOutput(const ConstString& str, const Potassco::LitSpan& cond) {
-	if (cond.size == 1) {
-		POTASSCO_REQUIRE(Potassco::atom(cond[0]) < bodyId, "Atom out of bounds");
-		return addOutput(str, Potassco::id(cond[0]));
-	}
-	if (!ctx()->output.filter(str)) {
-		show_.push_back(ShowPair(newCondition(cond), str));
-	}
-	return *this;
-}
-LogicProgram& LogicProgram::addOutput(const ConstString& str, Id_t id) {
-	if (!ctx()->output.filter(str) && id != falseId) {
-		if (Potassco::atom(id) < bodyId) {
-			resize(Potassco::atom(id));
-		}
-		show_.push_back(ShowPair(id, str));
-	}
-	return *this;
+    CHECK_NOT_FROZEN();
+    auto id = size32(atoms_);
+    atoms_.push_back(new PrgAtom(id));
+    return id;
+}
+Id_t LogicProgram::newCondition(Potassco::LitSpan cond) {
+    CHECK_NOT_FROZEN();
+    SRule meta;
+    if (simplifyNormal(HeadType::disjunctive, {}, cond, rule_, meta)) {
+        Rule r = rule_.rule();
+        if (r.cond.empty()) {
+            return 0;
+        }
+        if (r.cond.size() == 1) {
+            return Asp::id(r.cond[0]);
+        }
+        PrgBody* b = getBodyFor(r, meta);
+        b->markFrozen();
+        return static_cast(Clasp::Asp::body_id | b->id());
+    }
+    return static_cast(Clasp::Asp::false_id);
+}
+LogicProgram& LogicProgram::addOutput(const Potassco::ConstString& str, Potassco::LitSpan cond) {
+    if (cond.size() == 1) {
+        POTASSCO_CHECK_PRE(Potassco::atom(cond[0]) < body_id, "Atom out of bounds");
+        return addOutput(str, Asp::id(cond[0]));
+    }
+    if (not ctx()->output.filter(str)) {
+        show_.push_back(ShowPair(newCondition(cond), str));
+    }
+    return *this;
+}
+LogicProgram& LogicProgram::addOutput(const Potassco::ConstString& str, Id_t id) {
+    if (not ctx()->output.filter(str) && id != false_id) {
+        if (Potassco::atom(id) < body_id) {
+            resize(Potassco::atom(id));
+        }
+        show_.push_back(ShowPair(id, str));
+    }
+    return *this;
 }
 
-LogicProgram& LogicProgram::addProject(const Potassco::AtomSpan& atoms) {
-	check_not_frozen();
-	VarVec& pro = auxData_->project;
-	if (!Potassco::empty(atoms)) {
-		if (!pro.empty() && pro.back() == 0) { pro.pop_back(); }
-		pro.insert(pro.end(), Potassco::begin(atoms), Potassco::end(atoms));
-	}
-	else if (pro.empty()) {
-		pro.push_back(0);
-	}
-	return *this;
+LogicProgram& LogicProgram::addProject(Potassco::AtomSpan atoms) {
+    CHECK_NOT_FROZEN();
+    VarVec& pro = auxData_->project;
+    if (not atoms.empty()) {
+        if (not pro.empty() && pro.back() == 0) {
+            pro.pop_back();
+        }
+        pro.insert(pro.end(), atoms.begin(), atoms.end());
+    }
+    else if (pro.empty()) {
+        pro.push_back(0);
+    }
+    return *this;
 }
-
 LogicProgram& LogicProgram::removeProject() {
-	bool cleanup = !auxData_->project.empty() || ctx()->output.hasProject();
-	auxData_->project.clear();
-	ctx()->output.clearProject();
-	if (cleanup) {
-		for (VarVec::iterator it = index_->outSet.begin(), end = index_->outSet.end(); it != end; ++it) {
-			*it &= ~static_cast(out_projected);
-		}
-	}
-	return *this;
+    bool cleanup = not auxData_->project.empty() || ctx()->output.hasProject();
+    auxData_->project.clear();
+    ctx()->output.clearProject();
+    if (cleanup) {
+        for (auto& x : index_->outSet) { Potassco::store_clear_mask(x, out_projected); }
+    }
+    return *this;
 }
 
 TheoryData& LogicProgram::theoryData() {
-	if (!theory_) { theory_ = new TheoryData(); }
-	return *theory_;
+    if (not theory_) {
+        theory_ = std::make_unique();
+    }
+    return *theory_;
 }
 
-void LogicProgram::pushFrozen(PrgAtom* atom, ValueRep value) {
-	if (!atom->frozen()) { frozen_.push_back(atom->id()); }
-	atom->markFrozen(value);
+void LogicProgram::pushFrozen(PrgAtom* atom, Val_t v) {
+    if (not atom->frozen()) {
+        frozen_.push_back(atom->id());
+    }
+    atom->markFrozen(v);
 }
 
-LogicProgram& LogicProgram::addExternal(Atom_t atomId, Potassco::Value_t value) {
-	check_not_frozen();
-	PrgAtom* a = resize(atomId);
-	if (a->supports() == 0 && (isNew(a->id()) || a->frozen())) {
-		ValueRep fv = static_cast(value);
-		if (value == Potassco::Value_t::Release) {
-			// add dummy edge - will be removed once we update the set of frozen atoms
-			a->addSupport(PrgEdge::noEdge());
-			fv = value_free;
-		}
-		pushFrozen(a, fv);
-		auxData_->external.push_back(encodeExternal(a->id(), value));
-	}
-	return *this;
+LogicProgram& LogicProgram::addExternal(Atom_t atomId, Potassco::TruthValue value) {
+    CHECK_NOT_FROZEN();
+    if (PrgAtom* a = resize(atomId); a->numSupports() == 0 && (isNew(a->id()) || a->frozen())) {
+        if (value == Potassco::TruthValue::release) {
+            // add dummy edge - will be removed once we update the set of frozen atoms
+            a->addSupport(PrgEdge::noEdge());
+            value = Potassco::TruthValue::free;
+        }
+        pushFrozen(a, static_cast(value));
+        auxData_->external.push_back(encodeExternal(a->id(), value));
+    }
+    return *this;
 }
 
-LogicProgram& LogicProgram::freeze(Atom_t atomId, ValueRep value) {
-	POTASSCO_ASSERT(value < value_weak_true);
-	return addExternal(atomId, static_cast(value));
+LogicProgram& LogicProgram::freeze(Atom_t atomId, Val_t value) {
+    POTASSCO_ASSERT(value < value_weak_true);
+    return addExternal(atomId, static_cast(value));
 }
 
-LogicProgram& LogicProgram::unfreeze(Atom_t atomId) {
-	return addExternal(atomId, Potassco::Value_t::Release);
-}
-void LogicProgram::setMaxInputAtom(uint32 n) {
-	check_not_frozen();
-	resize(n++);
-	POTASSCO_REQUIRE(n >= startAtom(), "invalid input range");
-	input_.hi = n;
-}
-Atom_t LogicProgram::startAuxAtom() const {
-	return validAtom(input_.hi) ? input_.hi : (uint32)atoms_.size();
-}
-bool LogicProgram::supportsSmodels() const {
-	if (incData_ || theory_)        { return false; }
-	if (!auxData_->dom.empty())     { return false; }
-	if (!auxData_->acyc.empty())    { return false; }
-	if (!assume_.empty())           { return false; }
-	if (!auxData_->project.empty()) { return false; }
-	for (ShowVec::const_iterator it = show_.begin(), end = show_.end(); it != end; ++it) {
-		Potassco::Lit_t lit = Potassco::lit(it->first);
-		if (lit <= 0 || static_cast(lit) >= bodyId) { return false; }
-	}
-	return true;
+LogicProgram& LogicProgram::unfreeze(Atom_t atomId) { return addExternal(atomId, Potassco::TruthValue::release); }
+void          LogicProgram::setMaxInputAtom(uint32_t n) {
+    CHECK_NOT_FROZEN();
+    resize(n++);
+    POTASSCO_CHECK_PRE(n >= startAtom(), "invalid input range");
+    input_.hi = n;
+}
+Atom_t LogicProgram::startAuxAtom() const { return validAtom(input_.hi) ? input_.hi : size32(atoms_); }
+bool   LogicProgram::supportsSmodels(const char** errorOut) const {
+    const char*  ignore;
+    const char*& eOut = errorOut ? *errorOut : ignore;
+    if (incData_ || theory_) {
+        eOut = incData_ ? "incremental" : "theory";
+        return false;
+    }
+    if (not auxData_->dom.empty()) {
+        eOut = "heuristic";
+        return false;
+    }
+    if (not auxData_->acyc.empty()) {
+        eOut = "edge";
+        return false;
+    }
+    if (not assume_.empty()) {
+        eOut = "assumption";
+        return false;
+    }
+    if (not auxData_->project.empty()) {
+        eOut = "projection";
+        return false;
+    }
+    if (not std::ranges::all_of(show_, [](const ShowPair& s) {
+            auto lit = Potassco::lit(s.first);
+            return lit > 0 && static_cast(lit) < body_id;
+        })) {
+        eOut = "general output";
+        return false;
+    }
+    return true;
 }
 
 bool LogicProgram::isExternal(Atom_t aId) const {
-	if (!aId || !validAtom(aId)) { return false; }
-	PrgAtom* a = getRootAtom(aId);
-	return a->frozen() && (a->supports() == 0 || frozen());
+    if (not aId || not validAtom(aId)) {
+        return false;
+    }
+    PrgAtom* a = getRootAtom(aId);
+    return a->frozen() && (a->numSupports() == 0 || frozen());
 }
 bool LogicProgram::isFact(Atom_t aId) const {
-	return validAtom(aId) && (atomState_.isFact(aId) || atomState_.isFact(getRootId(aId)));
+    return validAtom(aId) && (atomState_.isFact(aId) || atomState_.isFact(getRootId(aId)));
 }
 bool LogicProgram::isDefined(Atom_t aId) const {
-	if (!validAtom(aId) || getAtom(aId)->removed()) {
-		return false;
-	}
-	PrgAtom* a = getAtom(aId);
-	return isFact(aId) || (a->relevant() && a->supports() && !isExternal(aId));
+    if (not validAtom(aId) || getAtom(aId)->removed()) {
+        return false;
+    }
+    PrgAtom* a = getAtom(aId);
+    return isFact(aId) || (a->relevant() && a->numSupports() && not isExternal(aId));
 }
 bool LogicProgram::inProgram(Atom_t id) const {
-	if (PrgAtom* a = (validAtom(id) ? getAtom(id) : 0)) {
-		return a->relevant() && (a->supports() || a->frozen() || !isNew(id));
-	}
-	return false;
+    if (PrgAtom* a = (validAtom(id) ? getAtom(id) : nullptr)) {
+        return a->relevant() && (a->numSupports() || a->frozen() || not isNew(id));
+    }
+    return false;
 }
-LogicProgram& LogicProgram::addAssumption(const Potassco::LitSpan& lits) {
-	assume_.insert(assume_.end(), Potassco::begin(lits), Potassco::end(lits));
-	return *this;
+LogicProgram& LogicProgram::addAssumption(Potassco::LitSpan lits) {
+    assume_.insert(assume_.end(), lits.begin(), lits.end());
+    return *this;
 }
 
-LogicProgram& LogicProgram::addAcycEdge(uint32 n1, uint32 n2, Id_t condId) {
-	if (condId != falseId) {
-		AcycArc arc = { condId, {n1, n2} };
-		auxData_->acyc.push_back(arc);
-	}
-	upStat(RK(Acyc), 1);
-	return *this;
+LogicProgram& LogicProgram::addAcycEdge(uint32_t n1, uint32_t n2, Id_t condId) {
+    if (condId != false_id) {
+        AcycArc arc = {condId, {n1, n2}};
+        auxData_->acyc.push_back(arc);
+    }
+    upStat(RK(acyc), 1);
+    return *this;
 }
 
 LogicProgram& LogicProgram::addDomHeuristic(Atom_t atom, DomModType t, int bias, unsigned prio) {
-	return addDomHeuristic(atom, t, bias, prio, Potassco::toSpan());
+    return addDomHeuristic(atom, t, bias, prio, {});
 }
 LogicProgram& LogicProgram::addDomHeuristic(Atom_t atom, DomModType type, int bias, unsigned prio, Id_t cond) {
-	static_assert(sizeof(DomRule) == sizeof(uint32[3]), "Invalid DomRule size");
-	if (cond != falseId) {
-		auxData_->dom.push_back(DomRule());
-		DomRule& x = auxData_->dom.back();
-		x.atom = atom;
-		x.type = type;
-		x.cond = cond;
-		x.bias = (int16)Range(INT16_MIN, INT16_MAX).clamp(bias);
-		x.prio = (uint16)Range(unsigned(0), unsigned(~uint16(0)) ).clamp(prio);
-	}
-	upStat(RK(Heuristic), 1);
-	return *this;
+    static_assert(sizeof(DomRule) == sizeof(uint32_t[3]), "Invalid DomRule size");
+    if (cond != false_id) {
+        auxData_->dom.push_back(DomRule());
+        DomRule& x = auxData_->dom.back();
+        x.atom     = atom;
+        x.type     = +type;
+        x.cond     = cond;
+        x.bias     = Clasp::saturate_cast(bias);
+        x.prio     = Clasp::saturate_cast(prio);
+    }
+    upStat(RK(heuristic), 1);
+    return *this;
 }
 LogicProgram& LogicProgram::addRule(const Rule& rule) {
-	check_not_frozen();
-	SRule meta;
-	if (simplifyRule(rule, rule_, meta)) {
-		Rule sRule = rule_.rule();
-		upStat(sRule.ht);
-		if (handleNatively(sRule)) { // and can be handled natively
-			addRule(sRule, meta);
-		}
-		else {
-			upStat(sRule.bt);
-			if (Potassco::size(sRule.head) <= 1 && transformNoAux(sRule)) {
-				// rule transformation does not require aux atoms - do it now
-				int oId  = statsId_;
-				statsId_ = 1;
-				RuleTransform tm(*this);
-				upStat(sRule.bt, -1);
-				upStat(rule.ht, -1);
-				tm.transform(sRule, RuleTransform::strategy_no_aux);
-				statsId_ = oId;
-			}
-			else {
-				// make sure we have all head atoms
-				for (Potassco::AtomSpan::iterator it = Potassco::begin(sRule.head), end = Potassco::end(sRule.head); it != end; ++it) {
-					resize(*it);
-				}
-				extended_.push_back(new RuleBuilder(rule_));
-			}
-		}
-	}
-	if (statsId_ == 0) {
-		// Assume all (new) heads are initially in "upper" closure.
-		for (Potassco::AtomSpan::iterator it = Potassco::begin(rule.head), end = Potassco::end(rule.head); it != end; ++it) {
-			if (!isNew(*it)) continue;
-			if (validAtom(*it))
-				getAtom(*it)->setInUpper(true);
-			else
-				auxData_->skippedHeads.insert(*it);
-		}
-	}
-	rule_.clear();
-	return *this;
+    CHECK_NOT_FROZEN();
+    if (SRule meta; simplifyRule(rule, rule_, meta)) {
+        Rule sRule = rule_.rule();
+        upStat(sRule.ht);
+        if (handleNatively(sRule)) { // and can be handled natively
+            addRule(sRule, meta);
+        }
+        else {
+            upStat(sRule.bt);
+            if (sRule.head.size() <= 1 && transformNoAux(sRule)) {
+                // rule transformation does not require aux atoms - do it now
+                int oId  = statsId_;
+                statsId_ = 1;
+                RuleTransform tm(*this);
+                upStat(sRule.bt, -1);
+                upStat(rule.ht, -1);
+                tm.transform(sRule, RuleTransform::strategy_no_aux);
+                statsId_ = oId;
+            }
+            else {
+                // make sure we have all head atoms
+                for (auto head : sRule.head) { resize(head); }
+                extended_.push_back(new RuleBuilder(rule_));
+            }
+        }
+    }
+    if (statsId_ == 0) {
+        // Assume all (new) heads are initially in "upper" closure.
+        for (auto h : rule.head) {
+            if (not isNew(h)) {
+                continue;
+            }
+            if (validAtom(h)) {
+                getAtom(h)->setInUpper(true);
+            }
+            else {
+                auxData_->skippedHeads.insert(h);
+            }
+        }
+    }
+    rule_.clear();
+    return *this;
 }
 
-LogicProgram& LogicProgram::addRule(Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body) {
-	return addRule(Rule::normal(ht, head, body));
-}
-LogicProgram& LogicProgram::addRule(Head_t ht, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& lits) {
-	return addRule(Rule::sum(ht, head, bound, lits));
-}
 LogicProgram& LogicProgram::addRule(Potassco::RuleBuilder& rb) {
-	LogicProgramAdapter prg(*this);
-	rb.end(&prg);
-	return *this;
-}
-LogicProgram& LogicProgram::addMinimize(weight_t prio, const Potassco::WeightLitSpan& lits) {
-	SingleOwnerPtr n(new Min());
-	n->prio = prio;
-	MinList::iterator it = std::lower_bound(minimize_.begin(), minimize_.end(), n.get(), CmpMin());
-	if (it == minimize_.end() || (*it)->prio != prio) {
-		n->lits.assign(Potassco::begin(lits), Potassco::end(lits));
-		minimize_.insert(it, n.get());
-		n.release();
-		upStat(RuleStats::Minimize);
-	}
-	else {
-		(*it)->lits.insert((*it)->lits.end(), Potassco::begin(lits), Potassco::end(lits));
-	}
-	// Touch all atoms in minimize -> these are input atoms even if they won't occur in a head.
-	for (Potassco::WeightLitSpan::iterator wIt = Potassco::begin(lits), end = Potassco::end(lits); wIt != end; ++wIt) {
-		resize(Potassco::atom(*wIt));
-	}
-	return *this;
+    LogicProgramAdapter prg(*this);
+    rb.end(&prg);
+    return *this;
+}
+LogicProgram& LogicProgram::addMinimize(Weight_t prio, WeightLitSpan lits) {
+    auto it = std::ranges::lower_bound(minimize_, prio, std::less{}, [](const auto* rb) { return rb->bound(); });
+    if (it == minimize_.end() || (*it)->bound() != prio) {
+        upStat(RuleStats::minimize);
+        it = minimize_.insert(it, new RuleBuilder());
+        (*it)->startMinimize(prio);
+    }
+    for (const auto& lit : lits) {
+        (*it)->addGoal(lit);
+        // Touch all atoms in minimize -> these are input atoms even if they won't occur in a head.
+        resize(Potassco::atom(lit));
+    }
+    return *this;
 }
 LogicProgram& LogicProgram::removeMinimize() {
-	std::for_each(minimize_.begin(), minimize_.end(), DeleteObject());
-	MinList().swap(minimize_);
-	ctx()->removeMinimize();
-	return *this;
+    std::ranges::for_each(minimize_, DeleteObject());
+    discardVec(minimize_);
+    ctx()->removeMinimize();
+    return *this;
 }
-#undef check_not_frozen
+#undef CHECK_NOT_FROZEN
 /////////////////////////////////////////////////////////////////////////////////////////
 // Query functions
 /////////////////////////////////////////////////////////////////////////////////////////
-bool LogicProgram::isFact(PrgAtom* a) const {
-	uint32 eqId = getRootId(a->id());
-	if (atomState_.isFact(eqId)) {
-		return true;
-	}
-	if (a->value() == value_true) {
-		for (PrgAtom::sup_iterator it = a->supps_begin(), end = a->supps_end(); it != end; ++it) {
-			if (it->isBody() && it->isNormal() && getBody(it->node())->bound() == 0) {
-				return true;
-			}
-		}
-	}
-	return false;
-}
-Literal LogicProgram::getLiteral(Id_t id, MapLit_t m) const {
-	Literal out = lit_false();
-	Potassco::Id_t nId = nodeId(id);
-	if (isAtom(id) && validAtom(nId)) {
-		out = getRootAtom(nId)->literal();
-		if (m == MapLit_t::Refined) {
-			IndexMap::const_iterator dom;
-			if ((dom = index_->domEq.find(nId)) != index_->domEq.end()) {
-				out = posLit(dom->second);
-			}
-			else if (isSentinel(out) && incData_ && !incData_->steps.empty()) {
-				Var v = isNew(id)
-					? incData_->steps.back().second
-					: std::lower_bound(incData_->steps.begin(), incData_->steps.end(), Incremental::StepTrue(nId, 0))->second;
-				out = Literal(v, out.sign());
-			}
-		}
-	}
-	else if (isBody(id)) {
-		POTASSCO_ASSERT(validBody(nId), "Invalid condition");
-		out = getBody(getEqBody(nId))->literal();
-	}
-	return out ^ signId(id);
+bool LogicProgram::isFact(const PrgAtom* a) const {
+    uint32_t eqId = getRootId(a->id());
+    if (atomState_.isFact(eqId)) {
+        return true;
+    }
+    if (a->value() == value_true) {
+        for (auto b : a->supports()) {
+            if (b.isBody() && b.isNormal() && getBody(b.node())->bound() == 0) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+Literal LogicProgram::getLiteral(Id_t id, MapLit m) const {
+    Literal out = lit_false;
+    if (Id_t nId = nodeId(id); isAtom(id) && validAtom(nId)) {
+        out = getRootAtom(nId)->literal();
+        if (m == MapLit::refined) {
+            if (auto dom = index_->domEq.find(nId); dom != index_->domEq.end()) {
+                out = posLit(dom->second);
+            }
+            else if (isSentinel(out) && incData_ && not incData_->steps.empty()) {
+                auto v = isNew(id) ? incData_->steps.back().second
+                                   : std::ranges::lower_bound(incData_->steps, Incremental::StepTrue(nId, 0))->second;
+                out    = Literal(v, out.sign());
+            }
+        }
+    }
+    else if (isBody(id)) {
+        POTASSCO_CHECK_PRE(validBody(nId), "Invalid condition");
+        out = getBody(getEqBody(nId))->literal();
+    }
+    return out ^ signId(id);
 }
 
-LogicProgram::OutputState LogicProgram::getOutputState(Atom_t atom, MapLit_t mode) const {
-	uint32 res = out_none;
-	while (validAtom(atom)) {
-		Var key = atom << 2u;
-		VarVec::const_iterator it = std::lower_bound(index_->outSet.begin(), index_->outSet.end(), key);
-		if (it != index_->outSet.end() && (*it & ~3u) == key) {
-			res |= static_cast(*it & 3u);
-		}
-		Atom_t next = mode == MapLit_t::Raw ? atom : getRootId(atom);
-		if (next == atom) {
-			break;
-		}
-		atom = next;
-		mode =  MapLit_t::Raw;
-	}
-	return static_cast(res);
+LogicProgram::OutputState LogicProgram::getOutputState(Atom_t atom, MapLit mode) const {
+    uint32_t res = out_none;
+    while (validAtom(atom)) {
+        Var_t key = atom << 2u;
+        auto  it  = std::ranges::lower_bound(index_->outSet, key);
+        if (it != index_->outSet.end() && (*it & ~3u) == key) {
+            res |= static_cast(*it & 3u);
+        }
+        Atom_t next = mode == MapLit::raw ? atom : getRootId(atom);
+        if (next == atom) {
+            break;
+        }
+        atom = next;
+        mode = MapLit::raw;
+    }
+    return static_cast(res);
 }
 
 void LogicProgram::doGetAssumptions(LitVec& out) const {
-	for (VarVec::const_iterator it = frozen_.begin(), end = frozen_.end(); it != end; ++it) {
-		Literal lit = getRootAtom(*it)->assumption();
-		if (lit != lit_true()) { out.push_back( lit ); }
-	}
-	for (Potassco::LitVec::const_iterator it = assume_.begin(), end = assume_.end(); it != end; ++it) {
-		out.push_back(getLiteral(Potassco::id(*it)));
-	}
-}
-bool LogicProgram::extractCore(const LitVec& solverCore, Potassco::LitVec& prgLits) const 	{
-	uint32 marked = 0;
-	prgLits.clear();
-	for (LitVec::const_iterator it = solverCore.begin(); it != solverCore.end(); ++it) {
-		if (!ctx()->validVar(it->var())) { break; }
-		ctx()->mark(*it);
-		++marked;
-	}
-	if (marked == solverCore.size()) {
-		for (VarVec::const_iterator it = frozen_.begin(), end = frozen_.end(); it != end && marked; ++it) {
-			PrgAtom* atom = getRootAtom(*it);
-			Literal lit = atom->assumption();
-			if (lit == lit_true() || !ctx()->marked(lit)) continue;
-			prgLits.push_back(atom->literal() == lit ? Potassco::lit(*it) : Potassco::neg(*it));
-			ctx()->unmark(lit);
-			--marked;
-		}
-		for (Potassco::LitVec::const_iterator it = assume_.begin(), end = assume_.end(); it != end && marked; ++it) {
-			Literal lit = getLiteral(Potassco::id(*it));
-			if (!ctx()->marked(lit)) continue;
-			prgLits.push_back(*it);
-			ctx()->unmark(lit);
-			--marked;
-		}
-	}
-	for (LitVec::const_iterator it = solverCore.begin(); it != solverCore.end(); ++it) {
-		if (ctx()->validVar(it->var()))
-			ctx()->unmark(it->var());
-	}
-	return prgLits.size() == solverCore.size();
+    for (auto v : frozen_) {
+        if (Literal lit = getRootAtom(v)->assumption(); lit != lit_true) {
+            out.push_back(lit);
+        }
+    }
+    for (auto v : assume_) { out.push_back(getLiteral(Asp::id(v))); }
+}
+bool LogicProgram::translateCore(LitView solverCore, Potassco::LitVec& prgLits) const {
+    uint32_t marked = 0;
+    prgLits.clear();
+    for (auto lit : solverCore) {
+        if (not ctx()->validVar(lit.var())) {
+            break;
+        }
+        ctx()->mark(lit);
+        ++marked;
+    }
+    if (marked == solverCore.size()) {
+        for (auto it = frozen_.begin(), end = frozen_.end(); it != end && marked; ++it) {
+            PrgAtom* atom = getRootAtom(*it);
+            Literal  lit  = atom->assumption();
+            if (lit == lit_true || not ctx()->marked(lit)) {
+                continue;
+            }
+            prgLits.push_back(atom->literal() == lit ? Potassco::lit(*it) : Potassco::neg(*it));
+            ctx()->unmark(lit);
+            --marked;
+        }
+        for (auto it = assume_.begin(), end = assume_.end(); it != end && marked; ++it) {
+            Literal lit = getLiteral(Asp::id(*it));
+            if (not ctx()->marked(lit)) {
+                continue;
+            }
+            prgLits.push_back(*it);
+            ctx()->unmark(lit);
+            --marked;
+        }
+    }
+    for (auto lit : solverCore) {
+        if (ctx()->validVar(lit.var())) {
+            ctx()->unmark(lit.var());
+        }
+    }
+    return prgLits.size() == solverCore.size();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Program definition - private
 /////////////////////////////////////////////////////////////////////////////////////////
 void LogicProgram::addRule(const Rule& r, const SRule& meta) {
-	if (Potassco::size(r.head) <= 1 && r.ht == Head_t::Disjunctive) {
-		if      (Potassco::empty(r.head))        { addIntegrity(r, meta); return; }
-		else if (r.normal() && r.cond.size == 0) { addFact(r.head); return; }
-	}
-	PrgBody* b = getBodyFor(r, meta);
-	// only a non-false body can define atoms
-	if (b->value() != value_false) {
-		bool const disjunctive = Potassco::size(r.head) > 1 && r.ht == Head_t::Disjunctive;
-		const EdgeType t = r.ht == Head_t::Disjunctive ? PrgEdge::Normal : PrgEdge::Choice;
-		uint32 headHash = 0;
-		bool ignoreScc  = opts_.noSCC || b->size() == 0;
-		for (Potassco::AtomSpan::iterator it = Potassco::begin(r.head), end = Potassco::end(r.head); it != end; ++it) {
-			PrgAtom* a = resize(*it);
-			check_modular(isNew(*it) || a->frozen() || a->value() == value_false, *it);
-			if (!disjunctive) {
-				// Note: b->heads may now contain duplicates. They are removed in PrgBody::simplifyHeads.
-				b->addHead(a, t);
-				if (ignoreScc) { a->setIgnoreScc(ignoreScc); }
-			}
-			else {
-				headHash += hashLit(posLit(*it));
-				atomState_.addToHead(*it);
-			}
-		}
-		if (disjunctive) {
-			PrgDisj* d = getDisjFor(r.head, headHash);
-			b->addHead(d, t);
-		}
-	}
-}
-void LogicProgram::addFact(const Potassco::AtomSpan& head) {
-	PrgBody* tb = 0;
-	for (Potassco::AtomSpan::iterator it = Potassco::begin(head), end = Potassco::end(head); it != end; ++it) {
-		PrgAtom* a = resize(*it);
-		check_modular(isNew(*it) || a->frozen() || a->value() == value_false, *it);
-		if (*it != a->id() || atomState_.isFact(*it)) { continue; }
-		a->setIgnoreScc(true);
-		atomState_.set(*it, AtomState::fact_flag);
-		if (!a->hasDep(PrgAtom::dep_all) && !a->frozen()) {
-			if (!a->assignValue(value_true) || !a->propagateValue(*this, false)) {
-				setConflict();
-			}
-			for (PrgAtom::sup_iterator bIt = a->supps_begin(), bEnd = a->supps_end(); bIt != bEnd; ++bIt) {
-				if      (bIt->isBody()) { getBody(bIt->node())->markHeadsDirty(); }
-				else if (bIt->isDisj()) { getDisj(bIt->node())->markDirty(); }
-			}
-			atoms_[*it] = &trueAtom_g;
-			delete a;
-		}
-		else {
-			if (!tb) tb = getTrueBody();
-			tb->addHead(a, PrgEdge::Normal);
-			assignValue(a, value_true, PrgEdge::newEdge(*tb, PrgEdge::Normal));
-		}
-	}
+    if (r.head.size() <= 1 && r.ht == HeadType::disjunctive) {
+        if (r.head.empty()) {
+            addIntegrity(r, meta);
+            return;
+        }
+        if (r.normal() && r.cond.empty()) {
+            addFact(r.head[0]);
+            return;
+        }
+    }
+    // only a non-false body can define atoms
+    if (PrgBody* b = getBodyFor(r, meta); b->value() != value_false) {
+        bool const     disjunctive = r.head.size() > 1 && r.ht == HeadType::disjunctive;
+        const EdgeType t           = r.ht == HeadType::disjunctive ? PrgEdge::normal : PrgEdge::choice;
+        uint32_t       headHash    = 0;
+        bool           ignoreScc   = opts_.noSCC || b->size() == 0;
+        for (auto h : r.head) {
+            PrgAtom* a = resize(h);
+            CHECK_MODULAR(isNew(h) || a->frozen() || a->value() == value_false, h);
+            if (not disjunctive) {
+                // Note: b->heads may now contain duplicates. They are removed in PrgBody::simplifyHeads.
+                b->addHead(a, t);
+                if (ignoreScc) {
+                    a->setIgnoreScc(ignoreScc);
+                }
+            }
+            else {
+                headHash += hashLit(posLit(h));
+                atomState_.addToHead(h);
+            }
+        }
+        if (disjunctive) {
+            PrgDisj* d = getDisjFor(r.head, headHash);
+            b->addHead(d, t);
+        }
+    }
+}
+void LogicProgram::addFact(Atom_t atomId) {
+    PrgAtom* a = resize(atomId);
+    CHECK_MODULAR(isNew(atomId) || a->frozen() || a->value() == value_false, atomId);
+    if (atomId != a->id() || atomState_.isFact(atomId)) {
+        return;
+    }
+    a->setIgnoreScc(true);
+    atomState_.set(atomId, AtomState::fact_flag);
+    if (a->frozen() || not a->deps().empty()) {
+        PrgBody* tb = getTrueBody();
+        tb->addHead(a, PrgEdge::normal);
+        assignValue(a, value_true, PrgEdge::newEdge(*tb, PrgEdge::normal));
+        return;
+    }
+    // Simplify and remove atom from program
+    if (not a->assignValue(value_true)) {
+        setConflict();
+    }
+    EdgeVec supps;
+    a->clearSupports(supps);
+    for (auto s : supps) {
+        if (s.isBody()) {
+            getBody(s.node())->markHeadsDirty();
+        }
+        else if (s.isDisj()) { // Disjunction is true
+            getDisj(s.node())->detach(*this);
+        }
+    }
+    if (index_->eqTrue == nullptr) {
+        a->setInUpper(false);
+        a->clearLiteral(true);
+        a->setEq(0);
+        index_->eqTrue = std::exchange(a, nullptr);
+    }
+    atoms_[atomId] = index_->eqTrue;
+    delete a;
 }
 void LogicProgram::addIntegrity(const Rule& r, const SRule& meta) {
-	if (r.sum() || r.cond.size != 1 || meta.bid != varMax) {
-		PrgBody* B = getBodyFor(r, meta);
-		if (!B->assignValue(value_false) || !B->propagateValue(*this, true)) {
-			setConflict();
-		}
-	}
-	else {
-		PrgAtom* a = resize(Potassco::atom(r.cond[0]));
-		ValueRep v = r.cond[0] > 0 ? value_false : value_weak_true;
-		assignValue(a, v, PrgEdge::noEdge());
-	}
-}
-bool LogicProgram::assignValue(PrgAtom* a, ValueRep v, PrgEdge reason) {
-	if (a->eq()) { a = getRootAtom(a->id()); }
-	ValueRep old = a->value();
-	if (old == value_weak_true && v != value_weak_true) old = value_free;
-	if (!a->assignValue(v)) { setConflict(); return false; }
-	if (old == value_free)  { propQ_.push_back(a->id());  }
-	if (v == value_false) {
-		atomState_.set(a->id(), AtomState::false_flag);
-	}
-	else if (v == value_true && reason.isBody() && reason.isNormal() && getBody(reason.node())->bound() == 0) {
-		atomState_.set(a->id(), AtomState::fact_flag);
-	}
-	return true;
-}
-bool LogicProgram::assignValue(PrgHead* h, ValueRep v, PrgEdge reason) {
-	return !h->isAtom() || assignValue(static_cast(h), v, reason);
+    if (r.sum() || r.cond.size() != 1 || meta.bid != var_max) {
+        PrgBody* body = getBodyFor(r, meta);
+        if (not body->assignValue(value_false) || not body->propagateValue(*this, true)) {
+            setConflict();
+        }
+    }
+    else {
+        PrgAtom* a = resize(Potassco::atom(r.cond[0]));
+        auto     v = r.cond[0] > 0 ? value_false : value_weak_true;
+        assignValue(a, v, PrgEdge::noEdge());
+    }
+}
+bool LogicProgram::assignValue(PrgAtom* a, Val_t v, PrgEdge reason) {
+    if (a->eq()) {
+        a = getRootAtom(a->id());
+    }
+    auto old = a->value();
+    if (old == value_weak_true && v != value_weak_true) {
+        old = value_free;
+    }
+    if (not a->assignValue(v)) {
+        setConflict();
+        return false;
+    }
+    if (old == value_free) {
+        propQ_.push_back(a->id());
+    }
+    if (v == value_false) {
+        atomState_.set(a->id(), AtomState::false_flag);
+    }
+    else if (v == value_true && reason.isBody() && reason.isNormal() && getBody(reason.node())->bound() == 0) {
+        atomState_.set(a->id(), AtomState::fact_flag);
+    }
+    return true;
+}
+bool LogicProgram::assignValue(PrgHead* h, Val_t v, PrgEdge reason) {
+    return not h->isAtom() || assignValue(node_cast(h), v, reason);
 }
 
 bool LogicProgram::handleNatively(const Rule& r) const {
-	ExtendedRuleMode m = opts_.erMode;
-	if (m == mode_native || (r.normal() && r.ht == Head_t::Disjunctive)) {
-		return true;
-	}
-	else if (m == mode_transform_integ || m == mode_transform_scc || m == mode_transform_nhcf) {
-		return true;
-	}
-	else if (m == mode_transform) {
-		return false;
-	}
-	else if (m == mode_transform_dynamic) {
-		return r.normal() || transformNoAux(r) == false;
-	}
-	else if (m == mode_transform_choice) {
-		return r.ht != Head_t::Choice;
-	}
-	else if (m == mode_transform_card)   {
-		return r.bt != Body_t::Count;
-	}
-	else if (m == mode_transform_weight) {
-		return r.normal();
-	}
-	assert(false && "unhandled extended rule mode");
-	return true;
+    auto mode = opts_.erMode;
+    if (mode == mode_native || (r.normal() && r.ht == HeadType::disjunctive)) {
+        return true;
+    }
+    switch (mode) {
+        case mode_transform        : return false;
+        case mode_transform_choice : return r.ht != HeadType::choice;
+        case mode_transform_card   : return r.bt != BodyType::count;
+        case mode_transform_weight : return r.normal();
+        case mode_transform_dynamic: return r.normal() || transformNoAux(r) == false;
+        default:
+            POTASSCO_ASSERT(mode == mode_transform_integ || mode == mode_transform_scc || mode == mode_transform_nhcf,
+                            "unhandled extended rule mode");
+            return true;
+    }
 }
 
-bool LogicProgram::transformNoAux(const Rule& r) const {
-	return r.ht == Head_t::Disjunctive && r.sum() && (r.agg.bound == 1 || (Potassco::size(r.agg.lits) <= 6 && choose(toU32(Potassco::size(r.agg.lits)), r.agg.bound) <= 15));
+bool LogicProgram::transformNoAux(const Rule& r) {
+    return r.ht == HeadType::disjunctive && r.sum() &&
+           (r.agg.bound == 1 ||
+            (r.agg.lits.size() <= 6 && choose(size32(r.agg.lits), static_cast(r.agg.bound)) <= 15));
 }
 
 void LogicProgram::transformExtended() {
-	uint32 a = numAtoms();
-	RuleTransform tm(*this);
-	for (RuleList::size_type i = 0; i != extended_.size(); ++i) {
-		Rule r = extended_[i]->rule();
-		upStat(r.ht, -1);
-		upStat(r.bt, -1);
-		if (r.normal() || (r.ht == Head_t::Disjunctive && Potassco::size(r.head) < 2)) {
-			tm.transform(r);
-		}
-		else {
-			using Potassco::Lit_t;
-			Atom_t aux = newAtom();
-			Lit_t auxB = Potassco::lit(aux);
-			Rule rAux1 = r; // aux :- body
-			rAux1.ht   = Head_t::Disjunctive;
-			rAux1.head = Potassco::toSpan(&aux, 1);
-			Rule rAux2 = Rule::normal(r.ht, r.head, Potassco::toSpan(&auxB, 1));  // head :- auxB
-			if (handleNatively(rAux1)) { addRule(rAux1); }
-			else {
-				RuleTransform::Strategy st = transformNoAux(rAux1) ? RuleTransform::strategy_no_aux : RuleTransform::strategy_default;
-				tm.transform(rAux1, st);
-			}
-			if (handleNatively(rAux2)) { addRule(rAux2); }
-			else                       { tm.transform(rAux2); }
-		}
-		delete extended_[i];
-	}
-	extended_.clear();
-	incTrAux(numAtoms() - a);
+    uint32_t      a = numAtoms();
+    RuleTransform tm(*this);
+    for (const auto* rb : extended_) {
+        Rule r = rb->rule();
+        upStat(r.ht, -1);
+        upStat(r.bt, -1);
+        if (r.normal() || (r.ht == HeadType::disjunctive && r.head.size() < 2)) {
+            tm.transform(r);
+        }
+        else {
+            using Potassco::Lit_t;
+            Atom_t aux   = newAtom();
+            Lit_t  auxB  = Potassco::lit(aux);
+            Rule   rAux1 = r; // aux :- body
+            rAux1.ht     = HeadType::disjunctive;
+            rAux1.head   = Potassco::toSpan(aux);
+            Rule rAux2   = Rule::normal(r.ht, r.head, Potassco::toSpan(auxB)); // head :- auxB
+            if (handleNatively(rAux1)) {
+                addRule(rAux1);
+            }
+            else {
+                auto st = transformNoAux(rAux1) ? RuleTransform::strategy_no_aux : RuleTransform::strategy_default;
+                tm.transform(rAux1, st);
+            }
+            if (handleNatively(rAux2)) {
+                addRule(rAux2);
+            }
+            else {
+                tm.transform(rAux2);
+            }
+        }
+        delete rb;
+    }
+    extended_.clear();
+    incTrAux(numAtoms() - a);
 }
 
-void LogicProgram::transformIntegrity(uint32 nAtoms, uint32 maxAux) {
-	if (stats.bodies[1][Body_t::Count] == 0) { return; }
-	// find all constraint rules that are integrity constraints
-	BodyList integrity;
-	for (uint32 i = 0, end = static_cast(bodies_.size()); i != end; ++i) {
-		PrgBody* b = bodies_[i];
-		if (b->relevant() && b->type() == Body_t::Count && b->value() == value_false) {
-			integrity.push_back(b);
-		}
-	}
-	if (!integrity.empty() && (integrity.size() == 1 || (nAtoms/double(bodies_.size()) > 0.5 && integrity.size() / double(bodies_.size()) < 0.01))) {
-		uint32 aux = static_cast(atoms_.size());
-		RuleTransform tr(*this);
-		RuleBuilder temp;
-		// transform integrity constraints
-		for (BodyList::size_type i = 0; i != integrity.size(); ++i) {
-			PrgBody* b = integrity[i];
-			uint32 est = b->bound()*( b->sumW()-b->bound() );
-			if (est > maxAux) {
-				// reached limit on aux atoms - stop transformation
-				break;
-			}
-			if (b->toData(*this, temp) && temp.bodyType() != Body_t::Normal) {
-				maxAux -= est;
-				// transform rule
-				setFrozen(false);
-				upStat(Head_t::Disjunctive, -1);
-				upStat(Body_t::Count, -1);
-				tr.transform(Rule::sum(Head_t::Disjunctive, Potassco::toSpan(), temp.sum()));
-				setFrozen(true);
-				// propagate integrity condition to new rules
-				propagate(true);
-				b->markRemoved();
-			}
-			temp.clear();
-		}
-		// create vars for new atoms/bodies
-		for (uint32 i = aux; i != atoms_.size(); ++i) {
-			PrgAtom* a = atoms_[i];
-			for (PrgAtom::sup_iterator it = a->supps_begin(); it != a->supps_end(); ++it) {
-				PrgBody* nb = bodies_[it->node()];
-				assert(nb->value() != value_false);
-				nb->assignVar(*this);
-			}
-			a->assignVar(*this, a->supports() ? *a->supps_begin() : PrgEdge::noEdge());
-		}
-		incTrAux(static_cast(atoms_.size()) - aux);
-	}
+void LogicProgram::transformIntegrity(uint32_t nAtoms, uint32_t maxAux) {
+    if (stats.bodies[1][to_underlying(BodyType::count)] == 0) {
+        return;
+    }
+    // find all constraint rules that are integrity constraints
+    BodyList integrity;
+    for (auto* b : bodies_) {
+        if (b->relevant() && b->type() == BodyType::count && b->value() == value_false) {
+            integrity.push_back(b);
+        }
+    }
+    if (not integrity.empty() && (integrity.size() == 1 || (ratio(nAtoms, size32(bodies_)) > 0.5 &&
+                                                            ratio(integrity.size(), size32(bodies_)) < 0.01))) {
+        auto          aux = size32(atoms_);
+        RuleTransform tr(*this);
+        RuleBuilder   temp;
+        // transform integrity constraints
+        for (auto* b : integrity) {
+            auto est = static_cast(b->bound() * (b->sumW() - b->bound()));
+            if (est > maxAux) {
+                // reached limit on aux atoms - stop transformation
+                break;
+            }
+            if (b->toData(*this, temp) && temp.bodyType() != BodyType::normal) {
+                maxAux -= est;
+                // transform rule
+                setFrozen(false);
+                upStat(HeadType::disjunctive, -1);
+                upStat(BodyType::count, -1);
+                tr.transform(Rule::sum(HeadType::disjunctive, {}, temp.sum()));
+                setFrozen(true);
+                // propagate integrity condition to new rules
+                propagate(true);
+                b->markRemoved();
+            }
+            temp.clear();
+        }
+        // create vars for new atoms/bodies
+        for (auto* a : atoms(aux)) {
+            for (auto s : a->supports()) {
+                PrgBody* nb = bodies_[s.node()];
+                assert(nb->value() != value_false);
+                nb->assignVar(*this);
+            }
+            a->assignVar(*this, a->support());
+        }
+        incTrAux(size32(atoms_) - aux);
+    }
 }
 
 void LogicProgram::prepareExternals() {
-	if (auxData_->external.empty()) { return; }
-	VarVec& external = auxData_->external;
-	VarVec::iterator j = external.begin();
-	for (VarVec::const_iterator it = j, end = external.end(); it != end; ++it) {
-		Atom_t id = getRootId(decodeExternal(*it).first);
-		const PrgAtom* atom = getAtom(id);
-		if (!atomState_.inHead(id) && (atom->supports() == 0 || *atom->supps_begin() == PrgEdge::noEdge())) {
-			Potassco::Value_t value = atom->supports() == 0 ? static_cast(atom->freezeValue()) : Potassco::Value_t::Release;
-			atomState_.addToHead(id);
-			*j++ = encodeExternal(id, value);
-		}
-	}
-	external.erase(j, external.end());
-	for (VarVec::const_iterator it = external.begin(), end = external.end(); it != end; ++it) {
-		atomState_.clearRule(decodeExternal(*it).first);
-	}
+    if (auxData_->external.empty()) {
+        return;
+    }
+    VarVec& external = auxData_->external;
+    auto    j        = external.begin();
+    for (auto ext : external) {
+        Atom_t         id   = getRootId(decodeExternal(ext).first);
+        const PrgAtom* atom = getAtom(id);
+        if (not atomState_.inHead(id) && not atom->support()) {
+            auto value = atom->numSupports() == 0 ? static_cast(atom->freezeValue())
+                                                  : Potassco::TruthValue::release;
+            atomState_.addToHead(id);
+            *j++ = encodeExternal(id, value);
+        }
+    }
+    external.erase(j, external.end());
+    atomState_.clearRule(external, [](unsigned ext) { return decodeExternal(ext).first; });
 }
 void LogicProgram::updateFrozenAtoms() {
-	if (frozen_.empty()) { return; }
-	PrgBody* support   = 0;
-	VarVec::iterator j = frozen_.begin();
-	for (VarVec::const_iterator it = j, end = frozen_.end(); it != end; ++it) {
-		Id_t id = getRootId(*it);
- 		PrgAtom* a = getAtom(id);
-		assert(a->frozen());
-		a->resetId(id, false);
-		if (a->supports() == 0) {
-			assert(a->relevant());
-			POTASSCO_REQUIRE(id < startAuxAtom(), "frozen atom shall be an input atom");
-			if (!support) { support = getTrueBody(); }
-			a->setIgnoreScc(true);
-			support->addHead(a, PrgEdge::GammaChoice);
-			*j++ = id; // still frozen
-		}
-		else {
-			a->clearFrozen();
-			if (*a->supps_begin() == PrgEdge::noEdge()) {
-				// remove dummy edge added in unfreeze()
-				a->removeSupport(PrgEdge::noEdge());
-			}
-			if (!isNew(id) && incData_) {
-				// add to unfreeze so that we can later perform completion
-				incData_->unfreeze.push_back(id);
-			}
-		}
-	}
-	frozen_.erase(j, frozen_.end());
+    if (frozen_.empty()) {
+        return;
+    }
+    PrgBody* support = nullptr;
+    auto     j       = frozen_.begin();
+    for (auto f : frozen_) {
+        Id_t     id = getRootId(f);
+        PrgAtom* a  = getAtom(id);
+        assert(a->frozen());
+        a->resetId(id, false);
+        if (a->numSupports() == 0) {
+            assert(a->relevant());
+            POTASSCO_CHECK_PRE(id < startAuxAtom(), "frozen atom shall be an input atom");
+            if (not support) {
+                support = getTrueBody();
+            }
+            a->setIgnoreScc(true);
+            support->addHead(a, PrgEdge::gamma_choice);
+            *j++ = id; // still frozen
+        }
+        else {
+            a->clearFrozen();
+            if (not a->support()) {
+                // remove dummy edge added in unfreeze()
+                a->removeSupport(PrgEdge::noEdge());
+            }
+            if (not isNew(id) && incData_) {
+                // add to unfreeze so that we can later perform completion
+                incData_->unfreeze.push_back(id);
+            }
+        }
+    }
+    frozen_.erase(j, frozen_.end());
 }
 
 void LogicProgram::prepareProgram(bool checkSccs) {
-	assert(!frozen());
-	prepareExternals();
-	// Given that freezeTheory() might introduce otherwise
-	// unused atoms, it must be called before we fix the
-	// number of input atoms. It must also be called before resetting
-	// the initial "upper" closure so that we can correctly classify
-	// theory atoms.
-	freezeTheory();
-	// Prepare for preprocessing by resetting our "upper" closure.
-	for (uint32 i = startAtom(); i != endAtom(); ++i) {
-		getAtom(i)->setInUpper(false);
-	}
-	uint32 nAtoms = (input_.hi = std::min(input_.hi, endAtom()));
-	stats.auxAtoms += endAtom() - nAtoms;
-	for (uint32 i = 0; i != RuleStats::numKeys(); ++i) {
-		stats.rules[1][i] += stats.rules[0][i];
-	}
-	for (uint32 i = 0; i != BodyStats::numKeys(); ++i) {
-		stats.bodies[1][i] += stats.bodies[0][i];
-	}
-	statsId_ = 1;
-	transformExtended();
-	updateFrozenAtoms();
-	PrgAtom* suppAtom = 0;
-	if (opts_.suppMod) {
-		VarVec h;
-		suppAtom  = getAtom(newAtom());
-		h.assign(1, suppAtom->id());
-		addRule(Head_t::Choice, Potassco::toSpan(h), Potassco::toSpan());
-		Potassco::Lit_t body = Potassco::lit(suppAtom->id());
-		h.clear();
-		for (Atom_t v = startAtom(), end = suppAtom->id(); v != end; ++v) {
-			if (atoms_[v]->supports() != 0) { h.push_back(v); }
-		}
-		addRule(Head_t::Choice, Potassco::toSpan(h), Potassco::toSpan(&body, 1));
-	}
-	setFrozen(true);
-	Preprocessor p;
-	if (hasConflict() || !propagate(true) || !p.preprocess(*this, opts_.iters != 0 ? Preprocessor::full_eq : Preprocessor::no_eq, opts_.iters, opts_.dfOrder != 0)) {
-		setConflict();
-		return;
-	}
-	if (suppAtom && (!assignValue(suppAtom, value_false, PrgEdge::noEdge()) || !propagate(true))) {
-		setConflict();
-		return;
-	}
-	if (opts_.erMode == mode_transform_integ || opts_.erMode == mode_transform_dynamic) {
-		nAtoms -= startAtom();
-		transformIntegrity(nAtoms, std::min(uint32(15000), nAtoms*2));
-	}
-	addMinimize();
-	uint32 sccs = 0;
-	if (checkSccs) {
-		uint32 startScc = incData_ ? incData_->startScc : 0;
-		SccChecker c(*this, auxData_->scc, startScc);
-		sccs       = c.sccs();
-		stats.sccs = (sccs-startScc);
-		if (incData_) { incData_->startScc = c.sccs(); }
-		if (!disjunctions_.empty() || (opts_.erMode == mode_transform_scc && sccs)) {
-			// reset node ids changed by scc checking
-			for (uint32 i = 0; i != bodies_.size(); ++i) {
-				if (getBody(i)->relevant()) { getBody(i)->resetId(i, true); }
-			}
-			for (uint32 i = 0; i != atoms_.size(); ++i) {
-				if (getAtom(i)->relevant()) { getAtom(i)->resetId(i, true); }
-			}
-		}
-	}
-	else { stats.sccs = PrgNode::noScc; }
-	finalizeDisjunctions(p, sccs);
-	prepareComponents();
-	prepareOutputTable();
-	freezeAssumptions();
-	if (incData_ && index_->distTrue) {
-		for (Var a = startAtom(), end = startAuxAtom(); a != end; ++a) {
-			if (isSentinel(getRootAtom(a)->literal())) {
-				Incremental::StepTrue t(end - 1, 0);
-				if (!incData_->steps.empty()) { t.second = ctx()->addVar(Var_t::Atom, 0); }
-				incData_->steps.push_back(t);
-				break;
-			}
-		}
-	}
-	if (theory_) {
-		TFilter f = { this };
-		theory_->filter(f);
-	}
-	stats.atoms = static_cast(atoms_.size()) - startAtom();
-	index_->body.clear();
-	index_->disj.clear();
+    assert(not frozen());
+    prepareExternals();
+    // Given that freezeTheory() might introduce otherwise
+    // unused atoms, it must be called before we fix the
+    // number of input atoms. It must also be called before resetting
+    // the initial "upper" closure so that we can correctly classify
+    // theory atoms.
+    freezeTheory();
+    // Prepare for preprocessing by resetting our "upper" closure.
+    for (auto* atom : stepAtoms()) { atom->setInUpper(false); }
+    uint32_t nAtoms  = (input_.hi = std::min(input_.hi, endAtom()));
+    stats.auxAtoms  += endAtom() - nAtoms;
+    for (auto i : irange(RuleStats::numKeys())) { stats.rules[1][i] += stats.rules[0][i]; }
+    for (auto i : irange(BodyStats::numKeys())) { stats.bodies[1][i] += stats.bodies[0][i]; }
+    statsId_ = 1;
+    transformExtended();
+    updateFrozenAtoms();
+    PrgAtom* suppAtom = nullptr;
+    if (opts_.suppMod) {
+        VarVec h;
+        suppAtom = getAtom(newAtom());
+        h.assign(1, suppAtom->id());
+        addRule(HeadType::choice, h, {});
+        auto body = Potassco::lit(suppAtom->id());
+        h.clear();
+        for (Atom_t v : irange(startAtom(), suppAtom->id())) {
+            if (atoms_[v]->numSupports()) {
+                h.push_back(v);
+            }
+        }
+        addRule(HeadType::choice, h, Potassco::toSpan(body));
+    }
+    setFrozen(true);
+    Preprocessor p;
+    if (hasConflict() || not propagate(true) ||
+        not p.preprocess(*this, opts_.iters != 0 ? Preprocessor::full_eq : Preprocessor::no_eq, opts_.iters,
+                         opts_.dfOrder != 0)) {
+        setConflict();
+        return;
+    }
+    if (suppAtom && (not assignValue(suppAtom, value_false, PrgEdge::noEdge()) || not propagate(true))) {
+        setConflict();
+        return;
+    }
+    if (opts_.erMode == mode_transform_integ || opts_.erMode == mode_transform_dynamic) {
+        nAtoms -= startAtom();
+        transformIntegrity(nAtoms, std::min(15000u, nAtoms * 2));
+    }
+    addMinimize();
+    uint32_t sccs = 0;
+    if (checkSccs) {
+        uint32_t   startScc = incData_ ? incData_->startScc : 0;
+        SccChecker c(*this, auxData_->scc, startScc);
+        sccs       = c.sccs();
+        stats.sccs = (sccs - startScc);
+        if (incData_) {
+            incData_->startScc = c.sccs();
+        }
+        if (not disjunctions_.empty() || (opts_.erMode == mode_transform_scc && sccs)) {
+            // reset node ids changed by scc checking
+            for (auto i : irange(bodies_)) {
+                if (auto* b = getBody(i); b->relevant()) {
+                    b->resetId(i, true);
+                }
+            }
+            for (auto i : irange(atoms_)) {
+                if (auto* a = getAtom(i); a->relevant()) {
+                    a->resetId(i, true);
+                }
+            }
+        }
+    }
+    else {
+        stats.sccs = PrgNode::no_scc;
+    }
+    finalizeDisjunctions(p, sccs);
+    prepareComponents();
+    prepareOutputTable();
+    freezeAssumptions();
+    if (incData_ && index_->distTrue) {
+        for (auto end = startAuxAtom(); auto a : irange(startAtom(), end)) {
+            if (isSentinel(getRootAtom(a)->literal())) {
+                Incremental::StepTrue t(end - 1, 0);
+                if (not incData_->steps.empty()) {
+                    t.second = ctx()->addVar(VarType::atom, 0);
+                }
+                incData_->steps.push_back(t);
+                break;
+            }
+        }
+    }
+    if (theory_) {
+        theory_->filter([this](const Potassco::TheoryAtom& a) {
+            Atom_t id = a.atom();
+            if (getLiteral(id) != lit_false && getRootAtom(id)->value() != value_false) {
+                ctx()->setFrozen(getLiteral(id).var(), true);
+                return false;
+            }
+            PrgAtom* at = getRootAtom(id);
+            return not at->frozen();
+        });
+    }
+    stats.atoms = size32(atoms_) - startAtom();
+    index_->body.clear();
+    index_->disj.clear();
 }
 void LogicProgram::freezeTheory() {
-	if (theory_) {
-		const IdSet& skippedHeads = auxData_->skippedHeads;
-		for (TheoryData::atom_iterator it = theory_->currBegin(), end = theory_->end(); it != end; ++it) {
-			const Potassco::TheoryAtom& a = **it;
-			if (isFact(a.atom()) || !isNew(a.atom())) { continue; }
-			PrgAtom* atom = resize(a.atom());
-			bool inUpper  = atom->inUpper() || skippedHeads.count(a.atom()) != 0;
-			if (!atom->frozen() && atom->supports() == 0 && atom->relevant() && !inUpper) {
-				pushFrozen(atom, value_free);
-			}
-		}
-	}
-}
-bool LogicProgram::TFilter::operator()(const Potassco::TheoryAtom& a) const {
-	Atom_t id = a.atom();
-	if (self->getLiteral(id) != lit_false() && self->getRootAtom(id)->value() != value_false) {
-		self->ctx()->setFrozen(self->getLiteral(id).var(), true);
-		return false;
-	}
-	PrgAtom* at = self->getRootAtom(id);
-	return !at->frozen();
-}
-struct LogicProgram::DlpTr : public RuleTransform::ProgramAdapter {
-	DlpTr(LogicProgram* x, EdgeType et) : self(x), type(et), scc(PrgNode::noScc) {}
-	virtual Atom_t newAtom() {
-		Atom_t x   = self->newAtom();
-		PrgAtom* a = self->getAtom(x);
-		a->setScc(scc);
-		a->setSeen(true);
-		atoms.push_back(x);
-		if (scc != PrgNode::noScc) { self->auxData_->scc.push_back(a); }
-		return x;
-	}
-	virtual void addRule(const Rule& r) {
-		SRule meta;
-		if (!self->simplifyRule(r, rule, meta)) { return; }
-		bool gamma = type == PrgEdge::Gamma;
-		Rule rs = rule.rule();
-		PrgAtom* a = self->getAtom(rs.head[0]);
-		PrgBody* B = self->assignBodyFor(rs, meta, type, gamma);
-		if (B->value() != value_false && !B->hasHead(a, PrgEdge::Normal)) {
-			B->addHead(a, type);
-			self->stats.gammas += uint32(gamma);
-		}
-	}
-	void assignAuxAtoms() {
-		self->incTrAux(sizeVec(atoms));
-		while (!atoms.empty()) {
-			PrgAtom* ax = self->getAtom(atoms.back());
-			atoms.pop_back();
-			if (ax->supports()) {
-				ax->setInUpper(true);
-				ax->assignVar(*self, *ax->supps_begin());
-			}
-			else { self->assignValue(ax, value_false, PrgEdge::noEdge()); }
-		}
-	}
-	LogicProgram* self;
-	EdgeType      type;
-	uint32        scc;
-	VarVec        atoms;
-	RuleBuilder   rule;
+    if (theory_) {
+        const IdSet& skippedHeads = auxData_->skippedHeads;
+        for (const auto* a : theory_->currAtoms()) {
+            if (isFact(a->atom()) || not isNew(a->atom())) {
+                continue;
+            }
+            PrgAtom* atom    = resize(a->atom());
+            bool     inUpper = atom->inUpper() || skippedHeads.contains(a->atom());
+            if (not atom->frozen() && atom->numSupports() == 0 && atom->relevant() && not inUpper) {
+                pushFrozen(atom, value_free);
+            }
+        }
+    }
+}
+
+POTASSCO_WARNING_PUSH()
+POTASSCO_WARNING_IGNORE_GNU("-Wnon-virtual-dtor") // Base class dtor is protected and therefore non-virtual is safe.
+struct LogicProgram::DlpTr final : RuleTransform::ProgramAdapter {
+    DlpTr(LogicProgram* x, EdgeType et) : self(x), type(et), scc(PrgNode::no_scc) {}
+    Atom_t newAtom() override {
+        Atom_t   x = self->newAtom();
+        PrgAtom* a = self->getAtom(x);
+        a->setScc(scc);
+        a->setSeen(true);
+        atoms.push_back(x);
+        if (scc != PrgNode::no_scc) {
+            self->auxData_->scc.push_back(a);
+        }
+        return x;
+    }
+    void addRule(const Rule& r) override {
+        SRule meta;
+        if (not self->simplifyRule(r, rule, meta)) {
+            return;
+        }
+        bool     gamma = type == PrgEdge::gamma;
+        Rule     rs    = rule.rule();
+        PrgAtom* a     = self->getAtom(rs.head[0]);
+        PrgBody* body  = self->assignBodyFor(rs, meta, type, gamma);
+        if (body->value() != value_false && not body->hasHead(a, PrgEdge::normal)) {
+            body->addHead(a, type);
+            self->stats.gammas += static_cast(gamma);
+        }
+    }
+    void assignAuxAtoms() {
+        self->incTrAux(size32(atoms));
+        while (not atoms.empty()) {
+            PrgAtom* ax = self->getAtom(atoms.back());
+            atoms.pop_back();
+            if (auto s = ax->support(); s) {
+                ax->setInUpper(true);
+                ax->assignVar(*self, s);
+            }
+            else {
+                assert(ax->numSupports() == 0);
+                self->assignValue(ax, value_false, s);
+            }
+        }
+    }
+    LogicProgram* self;
+    EdgeType      type;
+    uint32_t      scc;
+    VarVec        atoms;
+    RuleBuilder   rule;
 };
+POTASSCO_WARNING_POP()
 
 // replace disjunctions with gamma (shifted) and delta (component-shifted) rules
-void LogicProgram::finalizeDisjunctions(Preprocessor& p, uint32 numSccs) {
-	if (disjunctions_.empty()) { return; }
-	VarVec head; BodyList supports;
-	index_->disj.clear();
-	SccMap sccMap;
-	sccMap.resize(numSccs, 0);
-	enum SccFlag { seen_scc = 1u, is_scc_non_hcf = 128u };
-	// replace disjunctions with shifted rules and non-hcf-disjunctions
-	DisjList disj; disj.swap(disjunctions_);
-	setFrozen(false);
-	uint32 shifted = 0;
-	stats.nonHcfs  = uint32(nonHcfs_.size());
-	Literal bot    = lit_false();
-	Potassco::LitVec rb;
-	VarVec rh;
-	DlpTr tr(this, PrgEdge::Gamma);
+void LogicProgram::finalizeDisjunctions(Preprocessor& p, uint32_t numSccs) {
+    if (disjunctions_.empty()) {
+        return;
+    }
+    VarVec   head;
+    BodyList supports;
+    index_->disj.clear();
+    SccMap sccMap;
+    sccMap.resize(numSccs, 0);
+    enum SccFlag : uint32_t { seen_scc = 1u, is_scc_non_hcf = 128u };
+    // replace disjunctions with shifted rules and non-hcf-disjunctions
+    DisjList disj;
+    disj.swap(disjunctions_);
+    setFrozen(false);
+    uint32_t shifted     = 0;
+    stats.nonHcfs        = size32(nonHcfs_);
+    Literal          bot = lit_false;
+    Potassco::LitVec rb;
+    VarVec           rh;
+    DlpTr            tr(this, PrgEdge::gamma);
 
-	// detach disjunctions
-	for (uint32 id = 0, maxId = sizeVec(disj); id != maxId; ++id) {
-		PrgDisj* d = disj[id];
-		d->resetId(id, true);    // id changed during scc checking
-		d->detach(*this, false); // remove from atoms and bodies but keep state
-	}
+    // detach disjunctions
+    for (uint32_t id : irange(disj)) {
+        PrgDisj* d = disj[id];
+        d->resetId(id, true);    // id changed during scc checking
+        d->detach(*this, false); // remove from atoms and bodies but keep state
+    }
 
-	// replace disjunctions with shifted rules or new component-shifted disjunction
-	for (uint32 id = 0, maxId = sizeVec(disj); id != maxId; ++id) {
-		PrgDisj* d = disj[id];
-		Literal dx = d->inUpper() ? d->literal() : bot;
-		head.clear(); supports.clear();
-		for (PrgDisj::atom_iterator it = d->begin(), end = d->end(); it != end; ++it) {
-			uint32  aId = *it;
-			PrgAtom* at = getAtom(aId);
-			if (dx == bot) { continue; }
-			if (at->eq())  {
-				at = getAtom(aId = getRootId(aId));
-			}
-			if (isFact(at)){
-				dx = bot;
-				continue;
-			}
-			if (at->inUpper()) {
-				head.push_back(aId);
-				if (at->scc() != PrgNode::noScc){ sccMap[at->scc()] = seen_scc; }
-			}
-		}
-		EdgeVec temp;
-		d->clearSupports(temp);
-		for (EdgeVec::iterator it = temp.begin(), end = temp.end(); it != end; ++it) {
-			PrgBody* b = getBody(it->node());
-			if (b->relevant() && b->value() != value_false) { supports.push_back(b); }
-		}
-		d->destroy();
-		// create shortcut for supports to avoid duplications during shifting
-		Literal supportLit = dx != bot ? getEqAtomLit(dx, supports, p, sccMap) : dx;
-		// create shifted rules and split disjunctions into non-hcf components
-		RuleTransform shifter(tr);
-		for (VarVec::iterator hIt = head.begin(), hEnd = head.end(); hIt != hEnd; ++hIt) {
-			uint32 scc = getAtom(*hIt)->scc();
-			if (scc == PrgNode::noScc || (sccMap[scc] & seen_scc) != 0) {
-				if (scc != PrgNode::noScc) { sccMap[scc] &= ~seen_scc; }
-				else                       { scc = UINT32_MAX; }
-				rh.assign(1, *hIt);
-				rb.clear();
-				if (supportLit.var() != 0) { rb.push_back(toInt(supportLit)); }
-				else if (supportLit.sign()){ continue; }
-				for (VarVec::iterator oIt = head.begin(); oIt != hEnd; ++oIt) {
-					if (oIt != hIt) {
-						if (getAtom(*oIt)->scc() == scc) { rh.push_back(*oIt); }
-						else                             { rb.push_back(Potassco::neg(*oIt)); }
-					}
-				}
-				SRule meta;
-				if (!simplifyRule(Rule::normal(Head_t::Disjunctive, Potassco::toSpan(rh), Potassco::toSpan(rb)), rule_, meta)) {
-					continue;
-				}
-				Rule sr = rule_.rule();
-				PrgBody* B = assignBodyFor(sr, meta, PrgEdge::Normal, true);
-				if (B->value() != value_false && Potassco::size(sr.head) == 1) {
-					++shifted;
-					B->addHead(getAtom(sr.head[0]), PrgEdge::Normal);
-				}
-				else if (B->value() != value_false && Potassco::size(sr.head) > 1) {
-					PrgDisj* x = getDisjFor(sr.head, 0);
-					B->addHead(x, PrgEdge::Normal);
-					x->assignVar(*this, *x->supps_begin());
-					x->setInUpper(true);
-					x->setSeen(true);
-					if ((sccMap[scc] & is_scc_non_hcf) == 0) {
-						sccMap[scc] |= is_scc_non_hcf;
-						nonHcfs_.add(scc);
-					}
-					if (!options().noGamma) {
-						if (Potassco::size(sr.cond) >= 4) {
-							// make body eq to a new aux atom
-							tr.scc = B->scc(*this) == scc ? scc : PrgNode::noScc;
-							Atom_t eqAtom = tr.newAtom();
-							B->addHead(getAtom(eqAtom), PrgEdge::Normal);
-							rb.assign(1, Potassco::lit(eqAtom));
-							sr.cond = Potassco::toSpan(rb);
-							tr.assignAuxAtoms();
-							tr.scc = PrgNode::noScc;
-						}
-						shifter.transform(sr, RuleTransform::strategy_no_aux);
-					}
-					else {
-						// only add support edge
-						for (PrgDisj::atom_iterator a = x->begin(), end = x->end(); a != end; ++a) {
-							B->addHead(getAtom(*a), PrgEdge::GammaChoice);
-						}
-					}
-				}
-			}
-		}
-	}
-	assert(tr.atoms.empty());
-	if (!disjunctions_.empty() && nonHcfs_.config == 0) {
-		nonHcfs_.config = ctx()->configuration()->config("tester");
-	}
-	upStat(RK(Normal), shifted);
-	stats.nonHcfs = uint32(nonHcfs_.size()) - stats.nonHcfs;
-	rh.clear();
-	setFrozen(true);
+    // replace disjunctions with shifted rules or new component-shifted disjunction
+    for (auto* d : disj) {
+        Literal dx = d->inUpper() ? d->literal() : bot;
+        head.clear();
+        supports.clear();
+        for (auto aId : d->atoms()) {
+            PrgAtom* at = getAtom(aId);
+            if (dx == bot) {
+                continue;
+            }
+            if (at->eq()) {
+                at = getAtom(aId = getRootId(aId));
+            }
+            if (isFact(at)) {
+                dx = bot;
+                continue;
+            }
+            if (at->inUpper()) {
+                head.push_back(aId);
+                if (at->scc() != PrgNode::no_scc) {
+                    sccMap[at->scc()] = seen_scc;
+                }
+            }
+        }
+        EdgeVec temp;
+        d->clearSupports(temp);
+        for (auto edge : temp) {
+            PrgBody* b = getBody(edge.node());
+            if (b->relevant() && b->value() != value_false) {
+                supports.push_back(b);
+            }
+        }
+        d->destroy();
+        // create shortcut for supports to avoid duplications during shifting
+        Literal supportLit = dx != bot ? getEqAtomLit(dx, supports, p, sccMap) : dx;
+        // create shifted rules and split disjunctions into non-hcf components
+        RuleTransform shifter(tr);
+        for (auto h : head) {
+            uint32_t scc = getAtom(h)->scc();
+            if (scc == PrgNode::no_scc || (sccMap[scc] & seen_scc) != 0) {
+                if (scc != PrgNode::no_scc) {
+                    sccMap[scc] &= ~seen_scc;
+                }
+                else {
+                    scc = UINT32_MAX;
+                }
+                rh.assign(1, h);
+                rb.clear();
+                if (supportLit.var() != 0) {
+                    rb.push_back(toInt(supportLit));
+                }
+                else if (supportLit.sign()) {
+                    continue;
+                }
+                for (auto o : head) {
+                    if (o != h) {
+                        if (getAtom(o)->scc() == scc) {
+                            rh.push_back(o);
+                        }
+                        else {
+                            rb.push_back(Potassco::neg(o));
+                        }
+                    }
+                }
+                SRule meta;
+                if (not simplifyRule(Rule::normal(HeadType::disjunctive, rh, rb), rule_, meta)) {
+                    continue;
+                }
+                Rule     sr   = rule_.rule();
+                PrgBody* body = assignBodyFor(sr, meta, PrgEdge::normal, true);
+                if (body->value() != value_false && sr.head.size() == 1) {
+                    ++shifted;
+                    body->addHead(getAtom(sr.head[0]), PrgEdge::normal);
+                }
+                else if (body->value() != value_false && sr.head.size() > 1) {
+                    PrgDisj* x = getDisjFor(sr.head, 0);
+                    body->addHead(x, PrgEdge::normal);
+                    x->assignVar(*this, x->support());
+                    x->setInUpper(true);
+                    x->setSeen(true);
+                    if ((sccMap[scc] & is_scc_non_hcf) == 0) {
+                        sccMap[scc] |= is_scc_non_hcf;
+                        nonHcfs_.add(scc);
+                    }
+                    if (not options().noGamma) {
+                        if (sr.cond.size() >= 4) {
+                            // make body eq to a new aux atom
+                            tr.scc        = body->scc(*this) == scc ? scc : PrgNode::no_scc;
+                            Atom_t eqAtom = tr.newAtom();
+                            body->addHead(getAtom(eqAtom), PrgEdge::normal);
+                            rb.assign(1, Potassco::lit(eqAtom));
+                            sr.cond = rb;
+                            tr.assignAuxAtoms();
+                            tr.scc = PrgNode::no_scc;
+                        }
+                        shifter.transform(sr, RuleTransform::strategy_no_aux);
+                    }
+                    else {
+                        // only add support edge
+                        for (auto a : x->atoms()) { body->addHead(getAtom(a), PrgEdge::gamma_choice); }
+                    }
+                }
+            }
+        }
+    }
+    assert(tr.atoms.empty());
+    if (not disjunctions_.empty() && nonHcfs_.config == nullptr) {
+        nonHcfs_.config = ctx()->configuration()->config("tester");
+    }
+    upStat(RK(normal), static_cast(shifted));
+    stats.nonHcfs = size32(nonHcfs_) - stats.nonHcfs;
+    rh.clear();
+    setFrozen(true);
 }
 // optionally transform extended rules in sccs
 void LogicProgram::prepareComponents() {
-	int trRec = opts_.erMode == mode_transform_scc;
-	// HACK: force transformation of extended rules in non-hcf components
-	// REMOVE this once minimality check supports aggregates
-	if (!disjunctions_.empty() && trRec != 1) {
-		trRec = 2;
-	}
-	if (trRec != 0) {
-		DlpTr tr(this, PrgEdge::Normal);
-		RuleTransform trans(tr);
-		RuleBuilder temp;
-		setFrozen(false);
-		EdgeVec heads;
-		// find recursive aggregates
-		for (uint32 bIdx = 0, bEnd = numBodies(); bIdx != bEnd; ++bIdx) {
-			PrgBody* B = bodies_[bIdx];
-			if (B->type() == Body_t::Normal || !B->hasVar() || B->value() == value_false) { continue; } // not aggregate or not relevant
-			tr.scc = B->scc(*this);
-			if (tr.scc == PrgNode::noScc || (trRec == 2 && !nonHcfs_.find(tr.scc))) { continue; } // not recursive
-			// transform all rules a :- B, where scc(a) == scc(B):
-			heads.clear();
-			for (PrgBody::head_iterator hIt = B->heads_begin(), hEnd = B->heads_end(); hIt != hEnd; ++hIt) {
-				assert(hIt->isAtom());
-				if (getAtom(hIt->node())->scc() == tr.scc) { heads.push_back(*hIt); }
-			}
-			if (heads.empty()) { continue; }
-			using Potassco::Lit_t;
-			Head_t ht = !isChoice(heads[0].type()) ? Head_t::Disjunctive : Head_t::Choice;
-			Atom_t  h = heads[0].node();
-			Lit_t aux = 0;
-			if (heads.size() > 1) { // more than one head, make body eq to some new aux atom
-				ht  = Head_t::Disjunctive;
-				h   = tr.newAtom();
-				aux = Potassco::lit(h);
-			}
-			temp.clear();
-			if (!B->toData(*this, temp) || temp.bodyType() == Body_t::Normal) {
-				B->simplify(*this, true, 0);
-				continue;
-			}
-			trans.transform(Rule::sum(ht, Potassco::toSpan(&h, 1), temp.sum()));
-			for (EdgeVec::const_iterator hIt = heads.begin(); hIt != heads.end(); ++hIt) {
-				B->removeHead(getAtom(hIt->node()), hIt->type());
-				if (h != hIt->node()) {
-					ht = !isChoice(hIt->type()) ? Head_t::Disjunctive : Head_t::Choice;
-					h  = hIt->node();
-					tr.type = ht == Head_t::Disjunctive ? PrgEdge::Normal : PrgEdge::Choice;
-					tr.addRule(Rule::normal(ht, Potassco::toSpan(&h, 1), Potassco::toSpan(&aux, 1)));
-				}
-			}
-		}
-		tr.assignAuxAtoms();
-		setFrozen(true);
-	}
+    int trRec = opts_.erMode == mode_transform_scc;
+    // HACK: force transformation of extended rules in non-hcf components
+    // REMOVE this once minimality check supports aggregates
+    if (not disjunctions_.empty() && trRec != 1) {
+        trRec = 2;
+    }
+    if (trRec != 0) {
+        DlpTr         tr(this, PrgEdge::normal);
+        RuleTransform trans(tr);
+        RuleBuilder   temp;
+        setFrozen(false);
+        EdgeVec heads;
+        // find recursive aggregates
+        for (auto bId : irange(numBodies())) { // NOTE: set of bodies might change
+            PrgBody* body = getBody(bId);
+            if (body->type() == BodyType::normal || not body->hasVar() || body->value() == value_false) {
+                continue;
+            } // not aggregate or not relevant
+            tr.scc = body->scc(*this);
+            if (tr.scc == PrgNode::no_scc || (trRec == 2 && not nonHcfs_.find(tr.scc))) {
+                continue;
+            } // not recursive
+            // transform all rules a :- B, where scc(a) == scc(B):
+            heads.clear();
+            for (auto h : body->heads()) {
+                assert(h.isAtom());
+                if (getAtom(h.node())->scc() == tr.scc) {
+                    heads.push_back(h);
+                }
+            }
+            if (heads.empty()) {
+                continue;
+            }
+            temp.clear();
+            if (not body->toData(*this, temp) || temp.bodyType() == BodyType::normal) {
+                body->simplify(*this, true, nullptr);
+                continue;
+            }
+            HeadType ht = not isChoice(heads[0].type()) ? HeadType::disjunctive : HeadType::choice;
+            Atom_t   h  = heads[0].node();
+            if (heads.size() > 1) { // more than one head, make body eq to some new aux atom
+                ht = HeadType::disjunctive;
+                h  = tr.newAtom();
+            }
+            trans.transform(Rule::sum(ht, Potassco::toSpan(h), temp.sum()));
+            temp.clearBody().addGoal(Potassco::lit(h));
+            for (auto head : heads) {
+                body->removeHead(getAtom(head.node()), head.type());
+                if (h != head.node()) {
+                    ht      = not isChoice(head.type()) ? HeadType::disjunctive : HeadType::choice;
+                    h       = head.node();
+                    tr.type = ht == HeadType::disjunctive ? PrgEdge::normal : PrgEdge::choice;
+                    tr.addRule(Rule::normal(ht, Potassco::toSpan(h), temp.body()));
+                }
+            }
+        }
+        tr.assignAuxAtoms();
+        setFrozen(true);
+    }
 }
 
 void LogicProgram::mergeOutput(VarVec::iterator& hint, Atom_t atom, OutputState state) {
-	if (!index_->outState) {
-		return; // not enabled
-	}
-	Var key = atom << 2u;
-	if (hint == index_->outSet.end() || key < (*hint & ~3u)) {
-		hint = index_->outSet.begin();
-	}
-	hint = std::lower_bound(hint, index_->outSet.end(), key);
-	if (hint == index_->outSet.end() || (*hint & ~3u) != key) {
-		hint = index_->outSet.insert(hint, key | state);
-	}
-	else {
-		*hint |= state;
-	}
+    if (not index_->outState) {
+        return; // not enabled
+    }
+    Var_t key = atom << 2u;
+    if (hint == index_->outSet.end() || key < (*hint & ~3u)) {
+        hint = index_->outSet.begin();
+    }
+    hint = std::lower_bound(hint, index_->outSet.end(), key);
+    if (hint == index_->outSet.end() || (*hint & ~3u) != key) {
+        hint = index_->outSet.insert(hint, key | state);
+    }
+    else {
+        *hint |= state;
+    }
 }
 void LogicProgram::addOutputState(Atom_t atom, OutputState state) {
-	VarVec::iterator outPos = index_->outSet.end();
-	mergeOutput(outPos, atom, state);
+    auto outPos = index_->outSet.end();
+    mergeOutput(outPos, atom, state);
 }
 
 void LogicProgram::prepareOutputTable() {
-	OutputTable& out = ctx()->output;
-	VarVec::iterator outPos = index_->outSet.end();
-	// add new output predicates in program order to output table
-	std::stable_sort(show_.begin(), show_.end(), compose22(std::less(), select1st(), select1st()));
-	for (ShowVec::iterator it = show_.begin(), end = show_.end(); it != end; ++it) {
-		Literal lit = getLiteral(it->first);
-		bool isAtom = it->first < startAuxAtom();
-		if      (!isSentinel(lit))  { out.add(it->second, lit, it->first); }
-		else if (lit == lit_true()) { out.add(it->second); }
-		if (isAtom) {
-			ctx()->setOutput(lit.var(), true);
-			mergeOutput(outPos, it->first, out_shown);
-		}
-	}
-	if (!auxData_->project.empty()) {
-		std::sort(auxData_->project.begin(), auxData_->project.end());
-		for (VarVec::const_iterator it = auxData_->project.begin(), end = auxData_->project.end(); it != end; ++it) {
-			out.addProject(getLiteral(*it));
-			mergeOutput(outPos, *it, out_projected);
-		}
-	}
+    OutputTable& out    = ctx()->output;
+    auto         outPos = index_->outSet.end();
+    // add new output predicates in program order to output table
+    std::ranges::stable_sort(show_.begin(), show_.end(), std::less{}, [](const ShowPair& p) { return p.first; });
+    for (const auto& [id, name] : show_) {
+        Literal lit    = getLiteral(id);
+        bool    isAtom = id < startAuxAtom();
+        if (not isSentinel(lit)) {
+            out.add(name, lit, id);
+        }
+        else if (lit == lit_true) {
+            out.add(name);
+        }
+        if (isAtom) {
+            ctx()->setOutput(lit.var(), true);
+            mergeOutput(outPos, id, out_shown);
+        }
+    }
+    std::ranges::sort(auxData_->project);
+    for (auto p : auxData_->project) {
+        out.addProject(getLiteral(p));
+        mergeOutput(outPos, p, out_projected);
+    }
 }
 
 // Make assumptions/externals exempt from sat-preprocessing
 void LogicProgram::freezeAssumptions() {
-	for (VarVec::const_iterator it = frozen_.begin(), end = frozen_.end(); it != end; ++it) {
-		ctx()->setFrozen(getRootAtom(*it)->var(), true);
-	}
-	for (Potassco::LitVec::const_iterator it = assume_.begin(), end = assume_.end(); it != end; ++it) {
-		ctx()->setFrozen(getLiteral(Potassco::id(*it)).var(), true);
-	}
+    for (auto a : frozen_) { ctx()->setFrozen(getRootAtom(a)->var(), true); }
+    for (auto l : assume_) { ctx()->setFrozen(getLiteral(Asp::id(l)).var(), true); }
 }
 
 // add (completion) nogoods
 bool LogicProgram::addConstraints() {
-	ClauseCreator gc(ctx()->master());
-	if (options().iters == 0) {
-		gc.addDefaultFlags(ClauseCreator::clause_force_simplify);
-	}
-	ctx()->startAddConstraints();
-	// handle initial conflict, if any
-	if (!ctx()->ok() || !ctx()->addUnary(getTrueAtom()->trueLit())) {
-		return false;
-	}
-	if (incData_ && !incData_->steps.empty() && !ctx()->addUnary(posLit(incData_->steps.back().second))) {
-		return false;
-	}
-	if (options().noGamma && !disjunctions_.empty()) {
-		// add "rule" nogoods for disjunctions
-		for (DisjList::const_iterator it = disjunctions_.begin(); it != disjunctions_.end(); ++it) {
-			gc.start().add(~(*it)->literal());
-			for (PrgDisj::atom_iterator a = (*it)->begin(); a != (*it)->end(); ++a) {
-				gc.add(getAtom(*a)->literal());
-			}
-			if (!gc.end()) { return false; }
-		}
-	}
-	// add bodies from this step
-	for (BodyList::const_iterator it = bodies_.begin(); it != bodies_.end(); ++it) {
-		if (!toConstraint((*it), *this, gc)) { return false; }
-	}
-	// add atoms thawed in this step
-	for (VarIter it = unfreeze_begin(), end = unfreeze_end(); it != end; ++it) {
-		if (!toConstraint(getAtom(*it), *this, gc)) { return false; }
-	}
-	// add atoms from this step
-	const bool freezeAll = incData_ != 0;
-	const uint32 hiAtom  = startAuxAtom();
-	uint32 id = startAtom();
-	for (AtomList::const_iterator it = atoms_.begin()+startAtom(), end = atoms_.end(); it != end; ++it, ++id) {
-		if (!toConstraint(*it, *this, gc)) { return false; }
-		if (id < hiAtom && (*it)->hasVar()){
-			if (freezeAll) { ctx()->setFrozen((*it)->var(), true); }
-			ctx()->setInput((*it)->var(), true);
-		}
-	}
-	if (!auxData_->scc.empty()) {
-		if (ctx()->sccGraph.get() == 0) {
-			ctx()->sccGraph = new PrgDepGraph(static_cast(opts_.oldMap == 0));
-		}
-		uint32 oldNodes = ctx()->sccGraph->nodes();
-		ctx()->sccGraph->addSccs(*this, auxData_->scc, nonHcfs_);
-		stats.ufsNodes  = ctx()->sccGraph->nodes()-oldNodes;
-	}
-	return true;
+    ClauseCreator gc(ctx()->master());
+    if (options().iters == 0) {
+        gc.addDefaultFlags(ClauseCreator::clause_force_simplify);
+    }
+    ctx()->startAddConstraints();
+    // handle initial conflict, if any
+    if (not ctx()->ok() || not ctx()->addUnary(getTrueAtom()->trueLit())) {
+        return false;
+    }
+    if (incData_ && not incData_->steps.empty() && not ctx()->addUnary(posLit(incData_->steps.back().second))) {
+        return false;
+    }
+    if (options().noGamma && not disjunctions_.empty()) {
+        // add "rule" nogoods for disjunctions
+        for (const auto* disjunction : disjunctions_) {
+            gc.start().add(~disjunction->literal());
+            for (auto a : disjunction->atoms()) { gc.add(getAtom(a)->literal()); }
+            if (not gc.end()) {
+                return false;
+            }
+        }
+    }
+    // add bodies from this step
+    for (auto* body : bodies_) {
+        if (not toConstraint(body, *this, gc)) {
+            return false;
+        }
+    }
+    // add atoms thawed in this step
+    for (auto u : unfreeze()) {
+        if (not toConstraint(getAtom(u), *this, gc)) {
+            return false;
+        }
+    }
+    // add atoms from this step
+    const bool     freezeAll = incData_ != nullptr;
+    const uint32_t hiAtom    = startAuxAtom();
+    for (uint32_t id = startAtom(); auto* a : atoms(id)) {
+        if (not toConstraint(a, *this, gc)) {
+            return false;
+        }
+        if (id < hiAtom && a->hasVar()) {
+            if (freezeAll) {
+                ctx()->setFrozen(a->var(), true);
+            }
+            ctx()->setInput(a->var(), true);
+        }
+        ++id;
+    }
+    if (not auxData_->scc.empty()) {
+        if (not ctx()->sccGraph) {
+            ctx()->sccGraph = std::make_unique(static_cast(opts_.oldMap == 0));
+        }
+        uint32_t oldNodes = ctx()->sccGraph->nodes();
+        ctx()->sccGraph->addSccs(*this, auxData_->scc, nonHcfs_);
+        stats.ufsNodes = ctx()->sccGraph->nodes() - oldNodes;
+    }
+    return true;
 }
 void LogicProgram::addDomRules() {
-	if (auxData_->dom.empty()) { return; }
-	VarVec domVec;
-	EqVec eqVec;
-	DomRules&  doms = auxData_->dom;
-	Solver const& s = *ctx()->master();
-	// mark any previous domain atoms so that we can decide
-	// whether existing variables can be used for the atoms in doms
-	if (incData_) {
-		domVec.swap(incData_->doms);
-		for (VarVec::const_iterator it = domVec.begin(); it != domVec.end(); ++it) {
-			if (s.value(*it) == value_free) { ctx()->mark(posLit(*it)); }
-		}
-	}
-	DomRules::iterator j;
-	IndexMap::const_iterator eq;
-	DomRule r;
-	for (DomRules::iterator it = (j = doms.begin()), end = doms.end(); it != end; ++it) {
-		Literal cond = getLiteral(it->cond);
-		Literal slit = getLiteral(it->atom);
-		Var     svar = slit.var();
-		if (s.isFalse(cond) || s.value(svar) != value_free) { continue; }
-		if (s.isTrue(cond)) { it->cond = 0; cond = lit_true(); }
-		// check if atom is the root for its var
-		if (!atomState_.isSet(it->atom, AtomState::dom_flag)) {
-			if (!ctx()->marked(posLit(svar))) {
-				// var(it->atom) is not yet used - make it->atom its root
-				ctx()->mark(posLit(svar));
-				atomState_.set(it->atom, AtomState::dom_flag);
-				domVec.push_back(svar);
-			}
-			else if ((eq = index_->domEq.find(it->atom)) != index_->domEq.end()) {
-				// var(it->atom) is used but we already created a new var for it->atom
-				slit = posLit(svar = eq->second);
-			}
-			else {
-				// var(it->atom) is used - introduce new aux var and make it eq to lit(atom)
-				Eq n = { ctx()->addVar(Var_t::Atom, VarInfo::Nant), slit };
-				eqVec.push_back(n);
-				svar = n.var;
-				slit = posLit(svar);
-				index_->domEq.insert(IndexMap::value_type(static_cast(it->atom), svar));
-			}
-		}
-		*j++ = (r = *it);
-		if (slit.sign()) {
-			if      (r.type == DomModType::Sign)  { r.bias = r.bias != 0 ? -r.bias : 0; }
-			else if (r.type == DomModType::True)  { r.type = DomModType::False; }
-			else if (r.type == DomModType::False) { r.type = DomModType::True; }
-		}
-		ctx()->heuristic.add(svar, static_cast(r.type), r.bias, r.prio, cond);
-	}
-	if (j != doms.end()) {
-		upStat(RK(Heuristic), -static_cast(doms.end() - j));
-		doms.erase(j, doms.end());
-	}
-	// cleanup var flags
-	for (VarVec::const_iterator it = domVec.begin(); it != domVec.end(); ++it) {
-		ctx()->unmark(*it);
-	}
-	if (incData_) {
-		incData_->doms.swap(domVec);
-	}
-	if (!eqVec.empty()) {
-		ctx()->startAddConstraints();
-		for (EqVec::const_iterator it = eqVec.begin(), end = eqVec.end(); it != end; ++it) {
-			// it->var == it->lit
-			ctx()->addBinary(~it->lit, posLit(it->var));
-			ctx()->addBinary( it->lit, negLit(it->var));
-		}
-	}
+    if (auxData_->dom.empty()) {
+        return;
+    }
+    VarVec        domVec;
+    EqVec         eqVec;
+    DomRules&     doms = auxData_->dom;
+    Solver const& s    = *ctx()->master();
+    // mark any previous domain atoms so that we can decide
+    // whether existing variables can be used for the atoms in doms
+    if (incData_) {
+        domVec.swap(incData_->doms);
+        for (auto v : domVec) {
+            if (s.value(v) == value_free) {
+                ctx()->mark(posLit(v));
+            }
+        }
+    }
+    DomRule r{};
+    auto    j = doms.begin();
+    for (auto& dr : doms) {
+        Literal cond = getLiteral(dr.cond);
+        Literal slit = getLiteral(dr.atom);
+        auto    svar = slit.var();
+        if (s.isFalse(cond) || s.value(svar) != value_free) {
+            continue;
+        }
+        if (s.isTrue(cond)) {
+            dr.cond = 0;
+            cond    = lit_true;
+        }
+        // check if atom is the root for its var
+        if (not atomState_.isSet(dr.atom, AtomState::dom_flag)) {
+            if (not ctx()->marked(posLit(svar))) {
+                // var(it->atom) is not yet used - make it->atom its root
+                ctx()->mark(posLit(svar));
+                atomState_.set(dr.atom, AtomState::dom_flag);
+                domVec.push_back(svar);
+            }
+            else if (auto eq = index_->domEq.find(dr.atom); eq != index_->domEq.end()) {
+                // var(it->atom) is used but we already created a new var for it->atom
+                slit = posLit(svar = eq->second);
+            }
+            else {
+                // var(it->atom) is used - introduce new aux var and make it eq to lit(atom)
+                Eq n = {ctx()->addVar(VarType::atom, VarInfo::flag_nant), slit};
+                eqVec.push_back(n);
+                svar = n.var;
+                slit = posLit(svar);
+                index_->domEq.emplace(static_cast(dr.atom), svar);
+            }
+        }
+        *j++ = (r = dr);
+        if (slit.sign()) {
+            if (auto mod = static_cast(r.type); mod == DomModType::sign) {
+                r.bias = static_cast(r.bias != 0 ? -r.bias : 0);
+            }
+            else if (mod == DomModType::true_) {
+                r.type = +DomModType::false_;
+            }
+            else if (mod == DomModType::false_) {
+                r.type = +DomModType::true_;
+            }
+        }
+        ctx()->heuristic.add(svar, static_cast(r.type), r.bias, r.prio, cond);
+    }
+    if (j != doms.end()) {
+        upStat(RK(heuristic), -static_cast(doms.end() - j));
+        doms.erase(j, doms.end());
+    }
+    // cleanup var flags
+    for (auto v : domVec) { ctx()->unmark(v); }
+    if (incData_) {
+        incData_->doms.swap(domVec);
+    }
+    if (not eqVec.empty()) {
+        ctx()->startAddConstraints();
+        for (const auto& [var, lit] : eqVec) {
+            // var == lit
+            ctx()->addBinary(~lit, posLit(var));
+            ctx()->addBinary(lit, negLit(var));
+        }
+    }
 }
 
 void LogicProgram::addAcycConstraint() {
-	AcycRules& acyc = auxData_->acyc;
-	if (acyc.empty()) { return; }
-	SharedContext& ctx = *this->ctx();
-	ExtDepGraph* graph = ctx.extGraph.get();
-	const Solver&    s = *ctx.master();
-	if (graph) { graph->update(); }
-	else       { ctx.extGraph = (graph = new ExtDepGraph()); }
-	for (AcycRules::const_iterator it = acyc.begin(), end = acyc.end(); it != end; ++it) {
-		Literal lit = getLiteral(it->cond);
-		if (!s.isFalse(lit)) {
-			graph->addEdge(lit, it->node[0], it->node[1]);
-		}
-		else {
-			upStat(RK(Acyc), -1);
-		}
-	}
-	if (graph->finalize(ctx) == 0) { ctx.extGraph = 0; }
-}
-#undef check_modular
+    AcycRules& acyc = auxData_->acyc;
+    if (acyc.empty()) {
+        return;
+    }
+    SharedContext& ctx = *this->ctx();
+    const Solver&  s   = *ctx.master();
+    if (ctx.extGraph) {
+        ctx.extGraph->update();
+    }
+    else {
+        ctx.extGraph = std::make_unique();
+    }
+    auto* graph = ctx.extGraph.get();
+    for (auto x : acyc) {
+        if (auto lit = getLiteral(x.cond); not s.isFalse(lit)) {
+            graph->addEdge(lit, x.node[0], x.node[1]);
+        }
+        else {
+            upStat(RK(acyc), -1);
+        }
+    }
+    if (graph->finalize(ctx) == 0) {
+        ctx.extGraph = nullptr;
+    }
+}
+#undef CHECK_MODULAR
 /////////////////////////////////////////////////////////////////////////////////////////
 // misc/helper functions
 /////////////////////////////////////////////////////////////////////////////////////////
 PrgAtom* LogicProgram::resize(Atom_t atomId) {
-	while (atoms_.size() <= AtomList::size_type(atomId)) {
-		newAtom();
-	}
-	return getRootAtom(atomId);
+    POTASSCO_CHECK(atomId < body_id, EOVERFLOW, "Atom out of bounds");
+    while (size32(atoms_) <= atomId) { newAtom(); }
+    return getRootAtom(atomId);
 }
 
 bool LogicProgram::propagate(bool backprop) {
-	assert(frozen());
-	bool oldB = opts_.backprop != 0;
-	opts_.backprop = backprop;
-	for (VarVec::size_type i = 0; i != propQ_.size(); ++i) {
-		PrgAtom* a = getAtom(propQ_[i]);
-		if (!a->relevant()) { continue; }
-		if (!a->propagateValue(*this, backprop)) {
-			setConflict();
-			return false;
-		}
-		if (a->hasVar() && a->id() < startAtom() && !ctx()->addUnary(a->trueLit())) {
-			setConflict();
-			return false;
-		}
-	}
-	opts_.backprop = oldB;
-	propQ_.clear();
-	return true;
-}
-ValueRep LogicProgram::litVal(const PrgAtom* a, bool pos) const {
-	if (a->value() != value_free || !a->relevant()) {
-		bool vSign = a->value() == value_false || !a->relevant();
-		if  (vSign == pos) { return value_false; }
-		return a->value() != value_weak_true ? value_true : value_free;
-	}
-	return value_free;
+    assert(frozen());
+    bool oldB      = opts_.backprop != 0;
+    opts_.backprop = backprop;
+    for (auto qFront = static_cast(0); qFront < propQ_.size();) {
+        PrgAtom* a = getAtom(propQ_[qFront++]);
+        if (not a->relevant()) {
+            continue;
+        }
+        if (not a->propagateValue(*this, backprop)) {
+            setConflict();
+            return false;
+        }
+        if (a->hasVar() && a->id() < startAtom() && not ctx()->addUnary(a->trueLit())) {
+            setConflict();
+            return false;
+        }
+    }
+    opts_.backprop = oldB;
+    propQ_.clear();
+    return true;
+}
+Val_t LogicProgram::litVal(const PrgAtom* a, bool pos) {
+    if (a->value() != value_free || not a->relevant()) {
+        if (bool vSign = a->value() == value_false || not a->relevant(); vSign == pos) {
+            return value_false;
+        }
+        return a->value() != value_weak_true ? value_true : value_free;
+    }
+    return value_free;
 }
 
 // Simplifies the given normal rule H :- l1, ..., ln
 //  - removes true and duplicate literals from body: {T,a,b,a} -> {a, b}.
-//  - checks for contradictions and false literals in body: {a, not a} -> F
+//  - checks for contradictions and false literals in body: {'a', not 'a'} -> F
 //  - checks for satisfied head and removes false atoms from head
 // POST: if true out contains the simplified normal rule.
-bool LogicProgram::simplifyNormal(Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body, RuleBuilder& out, SRule& meta) {
-	out.clear();
-	out.startBody();
-	meta = SRule();
-	bool ok = true;
-	for (Potassco::LitSpan::iterator it = Potassco::begin(body), end = Potassco::end(body); it != end; ++it) {
-		POTASSCO_CHECK(Potassco::atom(*it) < bodyId, EOVERFLOW, "Atom out of bounds");
-		PrgAtom* a = resize(Potassco::atom(*it));
-		Literal  p = Literal(a->id(), *it < 0);// replace any eq atoms
-		ValueRep v = litVal(a, !p.sign());
-		if (v == value_false || atomState_.inBody(~p)) {
-			ok = false;
-			break;
-		}
-		else if (v != value_true  && !atomState_.inBody(p)) {
-			atomState_.addToBody(p);
-			out.addGoal(toInt(p));
-			meta.pos  += !p.sign();
-			meta.hash += hashLit(p);
-		}
-	}
-	uint32_t bs = toU32(size(out.body()));
-	meta.bid = ok ? findBody(meta.hash, Body_t::Normal, bs) : varMax;
-	ok = ok && pushHead(ht, head, 0, out);
-	for (const Potassco::Lit_t* it = out.lits_begin(); bs--;) {
-		atomState_.clearRule(Potassco::atom(*it++));
-	}
-	return ok;
+bool LogicProgram::simplifyNormal(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body, RuleBuilder& out,
+                                  SRule& meta) {
+    out.clear();
+    out.startBody();
+    meta    = SRule();
+    bool ok = true;
+    for (auto lit : body) {
+        auto* a = resize(Potassco::atom(lit));
+        auto  p = Literal(a->id(), lit < 0); // replace any eq atoms
+        auto  v = litVal(a, not p.sign());
+        if (v == value_false || atomState_.inBody(~p)) {
+            ok = false;
+            break;
+        }
+        if (v != value_true && not atomState_.inBody(p)) {
+            atomState_.addToBody(p);
+            out.addGoal(toInt(p));
+            meta.pos  += not p.sign();
+            meta.hash += hashLit(p);
+        }
+    }
+    meta.bid = ok ? findBody(meta.hash, size32(out.body())) : var_max;
+    ok       = ok && pushHead(ht, head, 0, out);
+    atomState_.clearRule(out.body());
+    return ok;
 }
 
-struct IsLit {
-	IsLit(Potassco::Lit_t x) : lhs(x) {}
-	template 
-	bool operator()(const P& rhs) const { return lhs == Potassco::lit(rhs); }
-	Potassco::Lit_t lhs;
-};
-
 // Simplifies the given sum rule: H :- lb { l1 = w1 ... ln = wn }.
 //  - removes assigned literals and updates lb accordingly
 //  - removes literals li with weight wi = 0
@@ -1823,488 +1932,527 @@ struct IsLit {
 //  - replaces sum with count if all weights are equal
 //  - replaces sum with normal body if all literals must be true for the sum to be satisfied
 // POST: if true out contains the simplified rule.
-bool LogicProgram::simplifySum(Head_t ht, const Potassco::AtomSpan& head, const Potassco::Sum_t& body, RuleBuilder& out, SRule& meta) {
-	meta = SRule();
-	weight_t bound = body.bound, maxW = 1, minW = CLASP_WEIGHT_T_MAX, sumW = 0, dirty = 0;
-	out.clear();
-	out.startSum(bound);
-	for (Potassco::WeightLitSpan::iterator it = Potassco::begin(body.lits), end = Potassco::end(body.lits); it != end && bound > 0; ++it) {
-		POTASSCO_CHECK(it->weight >= 0, EDOM, "Non-negative weight expected!");
-		POTASSCO_CHECK(Potassco::atom(*it) < bodyId, EOVERFLOW, "Atom out of bounds");
-		if (it->weight == 0) continue; // skip irrelevant lits
-		PrgAtom* a = resize(Potassco::atom(*it));
-		Literal  p = Literal(a->id(), Potassco::lit(*it) < 0);// replace any eq atoms
-		ValueRep v = litVal(a, !p.sign());
-		weight_t w = Potassco::weight(*it);
-		if (v == value_true) { bound -= w; }
-		else if (v != value_false) {
-			POTASSCO_CHECK((CLASP_WEIGHT_T_MAX-sumW)>= w, EOVERFLOW, "Integer overflow!");
-			sumW += w;
-			if (!atomState_.inBody(p)) {
-				atomState_.addToBody(p);
-				out.addGoal(toInt(p), w);
-				meta.pos += !p.sign();
-				meta.hash += hashLit(p);
-			}
-			else { // Merge duplicate lits
-				Potassco::WeightLit_t* pos = std::find_if(out.wlits_begin(), out.wlits_end(), IsLit(toInt(p)));
-				POTASSCO_ASSERT(pos != out.wlits_end());
-				w = (pos->weight += w);
-				++dirty;
-			}
-			if (w > maxW) { maxW = w; }
-			if (w < minW) { minW = w; }
-			dirty += static_cast(atomState_.inBody(~p));
-		}
-	}
-	weight_t sumR = sumW;
-	if (bound > 0 && (dirty || maxW > bound)) {
-		sumR = 0, minW = CLASP_WEIGHT_T_MAX;
-		for (Potassco::WeightLit_t* it = out.wlits_begin(), *end = out.wlits_end(); it != end; ++it) {
-			Literal  p = toLit(it->lit);
-			weight_t w = it->weight;
-			if (w > bound) { sumW -= (w - bound); it->weight = (maxW = w = bound); }
-			if (w < minW) { minW = w; }
-			sumR += w;
-			if (p.sign() && atomState_.inBody(~p)) {
-				// body contains p and ~p: we can achieve at most max(weight(p), weight(~p))
-				sumR -= std::min(w, std::find_if(out.wlits_begin(), end, IsLit(Potassco::neg(it->lit)))->weight);
-			}
-		}
-	}
-	out.setBound(bound);
-	if (bound <= 0 || sumR < bound) {
-		for (const Potassco::WeightLit_t* it = out.wlits_begin(), *end = out.wlits_end(); it != end; ++it) { atomState_.clearRule(Potassco::atom(*it)); }
-		return bound <= 0 && simplifyNormal(ht, head, Potassco::toSpan(), out, meta);
-	}
-	else if ((sumW - minW) < bound) {
-		out.weaken(Body_t::Normal);
-		meta.bid = findBody(meta.hash, Body_t::Normal, toU32(size(out.body())));
-		bool ok = pushHead(ht, head, 0, out);
-		for (const Potassco::Lit_t* it = out.lits_begin(), *end = out.lits_end(); it != end; ++it) {
-			atomState_.clearRule(Potassco::atom(*it));
-		}
-		return ok;
-	}
-	else if (minW == maxW) {
-		out.weaken(Body_t::Count, maxW != 1);
-		bound = out.bound();
-	}
-	meta.bid = findBody(meta.hash, out.bodyType(), (uint32_t)std::distance(out.wlits_begin(), out.wlits_end()), out.bound(), out.wlits_begin());
-	bool ok  = pushHead(ht, head, sumW - out.bound(), out);
-	for (const Potassco::WeightLit_t* it = out.wlits_begin(), *end = out.wlits_end(); it != end; ++it) {
-		atomState_.clearRule(Potassco::atom(*it));
-	}
-	return ok;
+bool LogicProgram::simplifySum(HeadType ht, Potassco::AtomSpan head, const Potassco::Sum& body, RuleBuilder& out,
+                               SRule& meta) {
+    meta           = SRule();
+    Weight_t bound = body.bound, maxW = 1, minW = weight_max, sumW = 0, dirty = 0;
+    out.clear();
+    out.startSum(bound);
+    for (const auto& [lit, weight] : body.lits) {
+        if (bound <= 0) {
+            break;
+        }
+        if (weight <= 0) {
+            POTASSCO_CHECK(weight == 0, EDOM, "Non-negative weight expected!");
+            continue; // skip irrelevant lits
+        }
+        auto* a = resize(Potassco::atom(lit));
+        auto  p = Literal(a->id(), lit < 0); // replace any eq atoms
+        if (auto v = litVal(a, not p.sign()); v == value_true) {
+            bound -= weight;
+        }
+        else if (v != value_false) {
+            POTASSCO_CHECK((weight_max - sumW) >= weight, EOVERFLOW, "Integer overflow!");
+            sumW   += weight;
+            auto w  = weight;
+            if (not atomState_.inBody(p)) {
+                atomState_.addToBody(p);
+                out.addGoal(toInt(p), weight);
+                meta.pos  += not p.sign();
+                meta.hash += hashLit(p);
+            }
+            else { // Merge duplicate lits
+                auto* pos = out.findSumLit(toInt(p));
+                POTASSCO_ASSERT(pos);
+                w = (pos->weight += weight);
+                ++dirty; // minW might have changed
+            }
+            if (w > maxW) {
+                maxW = w;
+            }
+            if (w < minW) {
+                minW = w;
+            }
+            dirty += static_cast(atomState_.inBody(~p));
+        }
+    }
+    Weight_t sumR = sumW;
+    if (bound > 0 && (dirty || maxW > bound)) {
+        sumR = 0, minW = weight_max;
+        for (auto& [lit, weight] : out.sumLits()) {
+            if (weight > bound) {
+                sumW   -= (weight - bound);
+                weight  = (maxW = bound);
+            }
+            if (weight < minW) {
+                minW = weight;
+            }
+            if (not atomState_.inBody(~toLit(lit))) {
+                sumR += weight;
+            }
+            else if (lit > 0) { // body contains lit and ~lit: we can achieve at most max(weight(lit), weight(~lit))
+                auto cpw  = out.findSumLit(Potassco::neg(lit))->weight;
+                sumR     += std::max(weight, std::min(cpw, bound));
+            }
+        }
+    }
+    out.setBound(bound);
+    if (bound <= 0 || sumR < bound) {
+        atomState_.clearRule(out.sumLits());
+        return bound <= 0 && simplifyNormal(ht, head, {}, out, meta);
+    }
+    if ((sumW - minW) < bound) {
+        out.weaken(BodyType::normal);
+        meta.bid = findBody(meta.hash, size32(out.body()));
+        bool ok  = pushHead(ht, head, 0, out);
+        atomState_.clearRule(out.body());
+        return ok;
+    }
+    if (minW == maxW) {
+        out.weaken(BodyType::count, maxW != 1);
+        bound = out.bound();
+    }
+    meta.bid = findBody(meta.hash, out.bodyType(), out.bound(), out.sumLits());
+    bool ok  = pushHead(ht, head, sumW - out.bound(), out);
+    atomState_.clearRule(out.sumLits());
+    return ok;
 }
 
 // Pushes the given rule head to the body given in out.
 // Pre: Body literals are marked and lits is != 0 if body is a sum.
-bool LogicProgram::pushHead(Head_t ht, const Potassco::AtomSpan& head, weight_t slack, RuleBuilder& out) {
-	const uint8 ignoreMask = AtomState::false_flag|AtomState::head_flag;
-	uint32 hs = 0;
-	bool sat = false, sum = out.bodyType() == Body_t::Sum;
-	out.start(ht);
-	for (Potassco::AtomSpan::iterator it = Potassco::begin(head), end = Potassco::end(head); it != end; ++it) {
-		if (!atomState_.isSet(*it, AtomState::simp_mask)) {
-			out.addHead(*it);
-			atomState_.addToHead(*it);
-			++hs;
-		}
-		else if (!atomState_.isSet(*it, ignoreMask)) { // h occurs in B+ and/or B- or is true
-			weight_t wp = weight_t(atomState_.inBody(posLit(*it))), wn = weight_t(atomState_.inBody(negLit(*it)));
-			if (wp && sum) { wp = std::find_if(out.wlits_begin(), out.wlits_end(), IsLit(Potassco::lit(*it)))->weight; }
-			if (wn && sum) { wn = std::find_if(out.wlits_begin(), out.wlits_end(), IsLit(Potassco::neg(*it)))->weight; }
-			if (atomState_.isFact(*it) || wp > slack) { sat = true; }
-			else if (wn <= slack) {
-				out.addHead(*it);
-				atomState_.addToHead(*it);
-				++hs;
-			}
-		}
-	}
-	for (const Atom_t* it = out.head_begin(), *end = it + hs; it != end; ++it) {
-		atomState_.clearRule(*it);
-	}
-	return !sat || (ht == Head_t::Choice && hs);
+bool LogicProgram::pushHead(HeadType ht, Potassco::AtomSpan head, Weight_t slack, RuleBuilder& out) {
+    constexpr uint8_t ignoreMask = AtomState::false_flag | AtomState::head_flag;
+    bool              sat = false, sum = out.bodyType() == BodyType::sum;
+    out.start(ht);
+    for (auto h : head) {
+        if (not atomState_.isSet(h, AtomState::simp_mask)) {
+            out.addHead(h);
+            atomState_.addToHead(h);
+        }
+        else if (not atomState_.isSet(h, ignoreMask)) { // h occurs in B+ and/or B- or is true
+            auto wp = static_cast(atomState_.inBody(posLit(h))),
+                 wn = static_cast(atomState_.inBody(negLit(h)));
+            if (wp && sum) {
+                wp = out.findSumLit(Potassco::lit(h))->weight;
+            }
+            if (wn && sum) {
+                wn = out.findSumLit(Potassco::neg(h))->weight;
+            }
+            if (atomState_.isFact(h) || wp > slack) {
+                sat = true;
+            }
+            else if (wn <= slack) {
+                out.addHead(h);
+                atomState_.addToHead(h);
+            }
+        }
+    }
+    atomState_.clearRule(out.head());
+    return not sat || (ht == HeadType::choice && not out.head().empty());
 }
 
 bool LogicProgram::simplifyRule(const Rule& r, Potassco::RuleBuilder& out, SRule& meta) {
-	return r.normal()
-		? simplifyNormal(r.ht, r.head, r.cond, out, meta)
-		: simplifySum(r.ht, r.head, r.agg, out, meta);
+    return r.normal() ? simplifyNormal(r.ht, r.head, r.cond, out, meta) : simplifySum(r.ht, r.head, r.agg, out, meta);
 }
 // create new atom aux representing supports, i.e.
 // aux == S1 v ... v Sn
 Literal LogicProgram::getEqAtomLit(Literal lit, const BodyList& supports, Preprocessor& p, const SccMap& sccMap) {
-	if (supports.empty() || lit == lit_false()) {
-		return lit_false();
-	}
-	else if (supports.size() == 1 && supports[0]->size() < 2 && supports.back()->literal() == lit) {
-		return supports[0]->size() == 0 ? lit_true() : supports[0]->goal(0);
-	}
-	else if (p.getRootAtom(lit) != varMax && opts_.noSCC) {
-		// Use existing root atom only if scc checking is disabled.
-		// Otherwise, we would have to recheck SCCs from that atom again because
-		// adding a new edge could create a new or change an existing SCC.
-		return posLit(p.getRootAtom(lit));
-	}
-	incTrAux(1);
-	Atom_t auxV  = newAtom();
-	PrgAtom* aux = getAtom(auxV);
-	uint32 scc   = PrgNode::noScc;
-	aux->setLiteral(lit);
-	aux->setSeen(true);
-	if (p.getRootAtom(lit) == varMax)
-		p.setRootAtom(aux->literal(), auxV);
-	for (BodyList::const_iterator sIt = supports.begin(); sIt != supports.end(); ++sIt) {
-		PrgBody* b = *sIt;
-		if (b->relevant() && b->value() != value_false) {
-			for (uint32 g = 0; scc == PrgNode::noScc && g != b->size() && !b->goal(g).sign(); ++g) {
-				uint32 aScc = getAtom(b->goal(g).var())->scc();
-				if (aScc != PrgNode::noScc && (sccMap[aScc] & 1u)) { scc = aScc; }
-			}
-			b->addHead(aux, PrgEdge::Normal);
-			if (b->value() != value_free && !assignValue(aux, b->value(), PrgEdge::newEdge(*b, PrgEdge::Normal))) {
-				break;
-			}
-			aux->setInUpper(true);
-		}
-	}
-	if (!aux->inUpper()) {
-		aux->setValue(value_false);
-		return lit_false();
-	}
-	else if (scc != PrgNode::noScc) {
-		aux->setScc(scc);
-		auxData_->scc.push_back(aux);
-	}
-	return posLit(auxV);
+    if (supports.empty() || lit == lit_false) {
+        return lit_false;
+    }
+    if (supports.size() == 1 && supports[0]->size() < 2 && supports.back()->literal() == lit) {
+        return supports[0]->size() == 0 ? lit_true : supports[0]->goal(0);
+    }
+    if (p.getRootAtom(lit) != var_max && opts_.noSCC) {
+        // Use existing root atom only if scc checking is disabled.
+        // Otherwise, we would have to recheck SCCs from that atom again because
+        // adding a new edge could create a new or change an existing SCC.
+        return posLit(p.getRootAtom(lit));
+    }
+    incTrAux(1);
+    Atom_t   auxV = newAtom();
+    PrgAtom* aux  = getAtom(auxV);
+    aux->setLiteral(lit);
+    aux->setSeen(true);
+    if (p.getRootAtom(lit) == var_max) {
+        p.setRootAtom(aux->literal(), auxV);
+    }
+    uint32_t scc = PrgNode::no_scc;
+    for (auto* b : supports) {
+        if (b->relevant() && b->value() != value_false) {
+            for (uint32_t g = 0; scc == PrgNode::no_scc && g != b->size() && not b->goal(g).sign(); ++g) {
+                uint32_t aScc = getAtom(b->goal(g).var())->scc();
+                if (aScc != PrgNode::no_scc && (sccMap[aScc] & 1u)) {
+                    scc = aScc;
+                }
+            }
+            b->addHead(aux, PrgEdge::normal);
+            if (b->value() != value_free && not assignValue(aux, b->value(), PrgEdge::newEdge(*b, PrgEdge::normal))) {
+                break;
+            }
+            aux->setInUpper(true);
+        }
+    }
+    if (not aux->inUpper()) {
+        aux->setValue(value_false);
+        return lit_false;
+    }
+    if (scc != PrgNode::no_scc) {
+        aux->setScc(scc);
+        auxData_->scc.push_back(aux);
+    }
+    return posLit(auxV);
 }
 
 PrgBody* LogicProgram::getBodyFor(const Rule& r, const SRule& meta, bool addDeps) {
-	if (meta.bid < bodies_.size()) {
-		return getBody(meta.bid);
-	}
-	// no corresponding body exists, create a new object
-	PrgBody* b = PrgBody::create(*this, numBodies(), r, meta.pos, addDeps);
-	index_->body.insert(IndexMap::value_type(meta.hash, b->id()));
-	bodies_.push_back(b);
-	if (b->isSupported()) {
-		initialSupp_.push_back(b->id());
-	}
-	upStat(r.bt);
-	return b;
+    if (meta.bid < size32(bodies_)) {
+        return getBody(meta.bid);
+    }
+    // no corresponding body exists, create a new object
+    PrgBody* b = PrgBody::create(*this, numBodies(), r, meta.pos, addDeps);
+    index_->body.emplace(meta.hash, b->id());
+    bodies_.push_back(b);
+    if (b->isSupported()) {
+        initialSupp_.push_back(b->id());
+    }
+    upStat(r.bt);
+    return b;
 }
 PrgBody* LogicProgram::getTrueBody() {
-	uint32 id = findBody(0, Body_t::Normal, 0);
-	if (id < bodies_.size()) {
-		return getBody(id);
-	}
-	return getBodyFor(Rule::normal(Head_t::Choice, Potassco::toSpan(), Potassco::toSpan()), SRule());
+    if (uint32_t id = findBody(0, 0); validBody(id)) {
+        return getBody(id);
+    }
+    return getBodyFor(Rule::normal(HeadType::choice, {}, {}), SRule());
 }
 PrgBody* LogicProgram::assignBodyFor(const Rule& r, const SRule& meta, EdgeType depEdge, bool simpStrong) {
-	PrgBody* b = getBodyFor(r, meta, depEdge != PrgEdge::Gamma);
-	if (!b->hasVar() && !b->seen()) {
-		uint32 eqId;
-		b->markDirty();
-		b->simplify(*this, simpStrong, &eqId);
-		if (eqId != b->id()) {
-			assert(b->id() == bodies_.size()-1);
-			removeBody(b, meta.hash);
-			bodies_.pop_back();
-			if (depEdge != PrgEdge::Gamma) {
-				for (uint32 i = 0; i != b->size(); ++i) {
-					getAtom(b->goal(i).var())->removeDep(b->id(), !b->goal(i).sign());
-				}
-			}
-			b->destroy();
-			b = bodies_[eqId];
-		}
-	}
-	b->setSeen(true);
-	b->assignVar(*this);
-	return b;
+    PrgBody* b = getBodyFor(r, meta, depEdge != PrgEdge::gamma);
+    if (not b->hasVar() && not b->seen()) {
+        uint32_t eqId;
+        b->markDirty();
+        b->simplify(*this, simpStrong, &eqId);
+        if (eqId != b->id()) {
+            assert(b->id() == size32(bodies_) - 1);
+            removeBody(b, meta.hash);
+            bodies_.pop_back();
+            if (depEdge != PrgEdge::gamma) {
+                for (auto g : b->goals()) { getAtom(g.var())->removeDep(b->id(), not g.sign()); }
+            }
+            b->destroy();
+            b = bodies_[eqId];
+        }
+    }
+    b->setSeen(true);
+    b->assignVar(*this);
+    return b;
 }
 
-bool LogicProgram::equalLits(const PrgBody& b, const WeightLitSpan& lits) const {
-	WeightLitSpan::iterator lBeg = Potassco::begin(lits), lEnd = Potassco::end(lits);
-	for (uint32 i = 0, end = b.size(); i != end; ++i) {
-		Potassco::WeightLit_t wl = { toInt(b.goal(i)), b.weight(i) };
-		if (!std::binary_search(lBeg, lEnd, wl)) { return false; }
-	}
-	return true;
+bool LogicProgram::equalLits(const PrgBody& b, WeightLitSpan lits) {
+    auto last = lits.begin(), e = lits.end();
+    for (auto i : irange(b.size())) {
+        Potassco::WeightLit wl = {toInt(b.goal(i)), b.weight(i)};
+        if (auto x = wl <=> *last; x == std::strong_ordering::equal) {
+            continue;
+        }
+        else if (x == std::strong_ordering::less) {
+            last = std::lower_bound(lits.begin(), e = last, wl);
+        }
+        else {
+            last = std::lower_bound(last + 1, e = lits.end(), wl);
+        }
+        if (last == e || *last != wl) {
+            return false;
+        }
+    }
+    return true;
 }
 
 // Pre: all literals in body are marked.
-uint32 LogicProgram::findBody(uint32 hash, Body_t type, uint32 size, weight_t bound, Potassco::WeightLit_t* sum) {
-	IndexRange bodies = index_->body.equal_range(hash);
-	bool sorted = false;
-	if (type == Body_t::Normal) { bound = static_cast(size); }
-	for (IndexIter it = bodies.first; it != bodies.second; ++it) {
-		const PrgBody& b = *getBody(it->second);
-		if (!checkBody(b, type, size, bound) || !atomState_.inBody(b.goals_begin(), b.goals_end())) {
-			continue;
-		}
-		else if (!b.hasWeights()) {
-			return b.id();
-		}
-		else if (sum) {
-			if (!sorted) {
-				std::sort(sum, sum + size);
-				sorted = true;
-			}
-			if (equalLits(b, Potassco::toSpan(sum, size))) { return b.id(); }
-		}
-	}
-	return varMax;
+uint32_t LogicProgram::findBody(uint32_t hash, BodyType type, uint32_t size, Weight_t bound,
+                                Potassco::WeightLit* sum) const {
+    assert(type != BodyType::normal || bound == static_cast(size));
+    bool sorted = false;
+    for (auto [it, end] = index_->body.equal_range(hash); it != end; ++it) {
+        const PrgBody& b = *getBody(it->second);
+        if (not checkBody(b, type, size, bound) || not atomState_.inBody(b.goals())) {
+            continue;
+        }
+        if (not b.hasWeights()) {
+            return b.id();
+        }
+        if (sum) {
+            if (not sorted) {
+                std::sort(sum, sum + size);
+                sorted = true;
+            }
+            if (equalLits(b, {sum, size})) {
+                return b.id();
+            }
+        }
+    }
+    return var_max;
 }
 
-uint32 LogicProgram::findEqBody(const PrgBody* b, uint32 hash) {
-	IndexRange bodies = index_->body.equal_range(hash);
-	if (bodies.first == bodies.second)  { return varMax;  }
-	uint32 eqId = varMax, n = 0, r = 0;
-	for (IndexIter it = bodies.first; it != bodies.second && eqId == varMax; ++it) {
-		const PrgBody& rhs = *getBody(it->second);
-		if (!checkBody(rhs, b->type(), b->size(), b->bound())) { continue; }
-		else if (b->size() == 0)  { eqId = rhs.id(); }
-		else if (b->size() == 1)  { eqId = b->goal(0) == rhs.goal(0) && b->weight(0) == rhs.weight(0) ? rhs.id() : varMax; }
-		else {
-			if (++n == 1) { std::for_each(b->goals_begin(), b->goals_end(), std::bind1st(std::mem_fun(&AtomState::addToBody), &atomState_)); }
-			if      (!atomState_.inBody(rhs.goals_begin(), rhs.goals_end())) { continue; }
-			else if (!b->hasWeights()) { eqId = rhs.id(); }
-			else {
-				if (n == 1 || r == 0) {
-					rule_.clear();
-					if (!b->toData(*this, rule_) || rule_.bodyType() != Body_t::Sum) {
-						rule_.clear();
-						continue;
-					}
-					r = 1;
-					std::sort(rule_.wlits_begin(), rule_.wlits_end());
-				}
-				if (equalLits(rhs, rule_.sum().lits)) { eqId = rhs.id(); }
-			}
-		}
-	}
-	if (n) {
-		rule_.clear();
-		std::for_each(b->goals_begin(), b->goals_end(), std::bind1st(std::mem_fun(&AtomState::clearBody), &atomState_));
-	}
-	return eqId;
+uint32_t LogicProgram::findEqBody(const PrgBody* b, uint32_t hash) {
+    uint32_t eqId = var_max, n = 0, r = 0;
+    for (auto [it, end] = index_->body.equal_range(hash); it != end && eqId == var_max; ++it) {
+        const PrgBody& rhs = *getBody(it->second);
+        if (not checkBody(rhs, b->type(), b->size(), b->bound())) {
+            continue;
+        }
+        if (b->size() == 0) {
+            eqId = rhs.id();
+        }
+        else if (b->size() == 1) {
+            eqId = b->goal(0) == rhs.goal(0) && b->weight(0) == rhs.weight(0) ? rhs.id() : var_max;
+        }
+        else {
+            if (++n == 1) {
+                atomState_.addToBody(b->goals());
+            }
+            if (not atomState_.inBody(rhs.goals())) {
+                continue;
+            }
+            if (not b->hasWeights()) {
+                eqId = rhs.id();
+            }
+            else {
+                if (n == 1 || r == 0) {
+                    rule_.clear();
+                    if (not b->toData(*this, rule_) || rule_.bodyType() != BodyType::sum) {
+                        rule_.clear();
+                        continue;
+                    }
+                    r = 1;
+                    std::ranges::sort(rule_.sumLits());
+                }
+                if (equalLits(rhs, rule_.sumLits())) {
+                    eqId = rhs.id();
+                }
+            }
+        }
+    }
+    if (n) {
+        rule_.clear();
+        atomState_.clearBody(b->goals());
+    }
+    return eqId;
 }
 
-PrgDisj* LogicProgram::getDisjFor(const Potassco::AtomSpan& head, uint32 headHash) {
-	PrgDisj* d = 0;
-	if (headHash) {
-		IndexRange eqRange = index_->disj.equal_range(headHash);
-		for (; eqRange.first != eqRange.second; ++eqRange.first) {
-			PrgDisj& o = *disjunctions_[eqRange.first->second];
-			if (o.relevant() && o.size() == Potassco::size(head) && atomState_.allMarked(o.begin(), o.end(), AtomState::head_flag)) {
-				assert(o.id() == eqRange.first->second);
-				d = &o;
-				break;
-			}
-		}
-		for (Potassco::AtomSpan::iterator it = Potassco::begin(head), end = Potassco::end(head); it != end; ++it) {
-			atomState_.clearRule(*it);
-		}
-	}
-	if (!d) {
-		// no corresponding disjunction exists, create a new object
-		// and link it to all atoms
-		++stats.disjunctions[statsId_];
-		d = PrgDisj::create((uint32)disjunctions_.size(), head);
-		disjunctions_.push_back(d);
-		PrgEdge edge = PrgEdge::newEdge(*d, PrgEdge::Choice);
-		for (Potassco::AtomSpan::iterator it = Potassco::begin(head), end = Potassco::end(head); it != end; ++it) {
-			getAtom(*it)->addSupport(edge);
-		}
-		if (headHash) {
-			index_->disj.insert(IndexMap::value_type(headHash, d->id()));
-		}
-	}
-	return d;
+PrgDisj* LogicProgram::getDisjFor(Potassco::AtomSpan head, uint32_t headHash) {
+    PrgDisj* d = nullptr;
+    if (headHash) {
+        for (auto [it, end] = index_->disj.equal_range(headHash); it != end; ++it) {
+            PrgDisj& o = *disjunctions_[it->second];
+            if (o.relevant() && o.size() == head.size() && atomState_.allMarked(o.atoms(), AtomState::head_flag)) {
+                assert(o.id() == it->second);
+                d = &o;
+                break;
+            }
+        }
+        atomState_.clearRule(head);
+    }
+    if (not d) {
+        // no corresponding disjunction exists, create a new object
+        // and link it to all atoms
+        ++stats.disjunctions[statsId_];
+        d = PrgDisj::create(size32(disjunctions_), head);
+        disjunctions_.push_back(d);
+        PrgEdge edge = PrgEdge::newEdge(*d, PrgEdge::choice);
+        for (auto h : head) { getAtom(h)->addSupport(edge); }
+        if (headHash) {
+            index_->disj.emplace(headHash, d->id());
+        }
+    }
+    return d;
 }
 
 // body has changed - update index
-uint32 LogicProgram::update(PrgBody* body, uint32 oldHash, uint32 newHash) {
-	uint32 id   = removeBody(body, oldHash);
-	if (body->relevant()) {
-		uint32 eqId = findEqBody(body, newHash);
-		if (eqId == varMax) {
-			// No equivalent body found.
-			// Add new entry to index
-			index_->body.insert(IndexMap::value_type(newHash, id));
-		}
-		return eqId;
-	}
-	return varMax;
+uint32_t LogicProgram::update(PrgBody* body, uint32_t oldHash, uint32_t newHash) {
+    uint32_t id = removeBody(body, oldHash);
+    if (body->relevant()) {
+        uint32_t eqId = findEqBody(body, newHash);
+        if (eqId == var_max) {
+            // No equivalent body found.
+            // Add new entry to index
+            index_->body.emplace(newHash, id);
+        }
+        return eqId;
+    }
+    return var_max;
 }
 
 // body b has changed - remove old entry from body node index
-uint32 LogicProgram::removeBody(PrgBody* b, uint32 hash) {
-	IndexRange ra = index_->body.equal_range(hash);
-	uint32 id     = b->id();
-	for (; ra.first != ra.second; ++ra.first) {
-		if (bodies_[ra.first->second] == b) {
-			id = ra.first->second;
-			index_->body.erase(ra.first);
-			break;
-		}
-	}
-	return id;
+uint32_t LogicProgram::removeBody(const PrgBody* b, uint32_t oldHash) {
+    uint32_t id = b->id();
+    for (auto [it, end] = index_->body.equal_range(oldHash); it != end; ++it) {
+        if (bodies_[it->second] == b) {
+            id = it->second;
+            index_->body.erase(it);
+            break;
+        }
+    }
+    return id;
 }
 
 PrgAtom* LogicProgram::mergeEqAtoms(PrgAtom* a, Id_t rootId) {
-	PrgAtom* root = getAtom(rootId = getRootId(rootId));
-	ValueRep mv   = getMergeValue(a, root);
-	assert(!a->eq() && !root->eq() && !a->frozen());
-	if (a->ignoreScc()) { root->setIgnoreScc(true); }
-	if (mv != a->value()    && !assignValue(a, mv, PrgEdge::noEdge()))   { return 0; }
-	if (mv != root->value() && !assignValue(root, mv, PrgEdge::noEdge())){ return 0; }
-	a->setEq(rootId);
-	incEqs(Var_t::Atom);
-	return root;
+    PrgAtom* root = getAtom(rootId = getRootId(rootId));
+    auto     mv   = getMergeValue(a, root);
+    assert(not a->eq() && not root->eq() && not a->frozen());
+    if (a->ignoreScc()) {
+        root->setIgnoreScc(true);
+    }
+    if (mv != a->value() && not assignValue(a, mv, PrgEdge::noEdge())) {
+        return nullptr;
+    }
+    if (mv != root->value() && not assignValue(root, mv, PrgEdge::noEdge())) {
+        return nullptr;
+    }
+    a->setEq(rootId);
+    incEqs(VarType::atom);
+    return root;
 }
 
 // returns whether posSize(root) <= posSize(body)
-bool LogicProgram::positiveLoopSafe(PrgBody* body, PrgBody* root) const {
-	uint32 i = 0, end = std::min(body->size(), root->size());
-	while (i != end && body->goal(i).sign() == root->goal(i).sign()) { ++i; }
-	return i == root->size() || root->goal(i).sign();
+bool LogicProgram::positiveLoopSafe(const PrgBody* body, const PrgBody* root) {
+    uint32_t i = 0, end = std::min(body->size(), root->size());
+    while (i != end && body->goal(i).sign() == root->goal(i).sign()) { ++i; }
+    return i == root->size() || root->goal(i).sign();
 }
 
 PrgBody* LogicProgram::mergeEqBodies(PrgBody* b, Id_t rootId, bool hashEq, bool atomsAssigned) {
-	PrgBody* root = getBody(rootId = getEqNode(bodies_, rootId));
-	bool     bp   = options().backprop != 0;
-	if (b == root) { return root; }
-	assert(!b->eq() && !root->eq() && (hashEq || b->literal() == root->literal()));
-	if (!b->simplifyHeads(*this, atomsAssigned) || (b->value() != root->value() && (!mergeValue(b, root) || !root->propagateValue(*this, bp) || !b->propagateValue(*this, bp)))) {
-		setConflict();
-		return 0;
-	}
-	if (hashEq || positiveLoopSafe(b, root)) {
-		b->setLiteral(root->literal());
-		if (!root->mergeHeads(*this, *b, atomsAssigned, !hashEq)) {
-			setConflict();
-			return 0;
-		}
-		incEqs(Var_t::Body);
-		b->setEq(rootId);
-		return root;
-	}
-	return b;
+    PrgBody* root = getBody(rootId = getEqNode(bodies_, rootId));
+    bool     bp   = options().backprop != 0;
+    if (b == root) {
+        return root;
+    }
+    assert(not b->eq() && not root->eq() && (hashEq || b->literal() == root->literal()));
+    if (not b->simplifyHeads(*this, atomsAssigned) ||
+        (b->value() != root->value() &&
+         (not mergeValue(b, root) || not root->propagateValue(*this, bp) || not b->propagateValue(*this, bp)))) {
+        setConflict();
+        return nullptr;
+    }
+    if (hashEq || positiveLoopSafe(b, root)) {
+        b->setLiteral(root->literal());
+        if (not root->mergeHeads(*this, *b, atomsAssigned, not hashEq)) {
+            setConflict();
+            return nullptr;
+        }
+        incEqs(VarType::body);
+        b->setEq(rootId);
+        return root;
+    }
+    return b;
 }
 
 const char* LogicProgram::findName(Atom_t x) const {
-	for (OutputTable::pred_iterator it = ctx()->output.pred_begin(), end = ctx()->output.pred_end(); it != end; ++it) {
-		if (it->user == x) { return it->name; }
-	}
-	for (ShowVec::const_iterator it = show_.begin(), end = show_.end(); it != end; ++it) {
-		if (it->first == x){ return it->second; }
-	}
-	return "";
+    for (const auto& pred : ctx()->output.pred_range()) {
+        if (pred.user == x) {
+            return pred.name.c_str();
+        }
+    }
+    auto it = std::ranges::find_if(show_, [x](const auto& sp) { return sp.first == x; });
+    return it != show_.end() ? it->second.c_str() : "";
 }
 VarVec& LogicProgram::getSupportedBodies(bool sorted) {
-	if (sorted) {
-		std::stable_sort(initialSupp_.begin(), initialSupp_.end(), LessBodySize(bodies_));
-	}
-	return initialSupp_;
+    if (sorted) {
+        std::ranges::stable_sort(initialSupp_, [&](Id_t lhs, Id_t rhs) {
+            const auto* bLhs = bodies_[lhs];
+            const auto* bRhs = bodies_[rhs];
+            return bLhs->size() < bRhs->size() || (bLhs->size() == bRhs->size() && bLhs->type() < bRhs->type());
+        });
+    }
+    return initialSupp_;
 }
 
 Atom_t LogicProgram::falseAtom() {
-	Atom_t aFalse = 0;
-	for (Var i = 1; i < atoms_.size() && !aFalse; ++i) {
-		if (atoms_[i]->value() == value_false || atomState_.isSet(i, AtomState::false_flag)) {
-			aFalse = i;
-		}
-	}
-	if (!aFalse) {
-		bool s = frozen();
-		setFrozen(false);
-		aFalse = newAtom();
-		assignValue(getAtom(aFalse), value_false, PrgEdge::noEdge());
-		setFrozen(s);
-	}
-	return aFalse;
+    for (auto i : irange(1u, size32(atoms_))) {
+        if (atoms_[i]->value() == value_false || atomState_.isSet(i, AtomState::false_flag)) {
+            return i;
+        }
+    }
+    bool s = frozen();
+    setFrozen(false);
+    auto aFalse = newAtom();
+    assignValue(getAtom(aFalse), value_false, PrgEdge::noEdge());
+    setFrozen(s);
+    return aFalse;
 }
 
-bool LogicProgram::extractCondition(Id_t id, Potassco::LitVec& out) const {
-	out.clear();
-	if (id == Clasp::Asp::falseId || (frozen() && getLiteral(id) == lit_false())) { return false; }
-	if (!id || isAtom(id)) {
-		out.assign(id != 0, Potassco::lit(id));
-		return true;
-	}
-	Id_t bId = nodeId(id);
-	POTASSCO_ASSERT(validBody(bId), "Invalid literal");
-	const PrgBody* B = getBody(getEqBody(bId));
-	out.reserve(B->size());
-	for (PrgBody::goal_iterator it = B->goals_begin(), end = B->goals_end(); it != end; ++it) {
-		out.push_back( toInt(*it) );
-	}
-	return true;
+bool LogicProgram::extractCondition(Id_t cId, Potassco::LitVec& cond) const {
+    cond.clear();
+    if (cId == false_id || (frozen() && getLiteral(cId) == lit_false)) {
+        return false;
+    }
+    if (not cId || isAtom(cId)) {
+        cond.assign(cId != 0, Potassco::lit(cId));
+        return true;
+    }
+    Id_t bId = nodeId(cId);
+    POTASSCO_ASSERT(validBody(bId), "Invalid literal");
+    const PrgBody* body = getBody(getEqBody(bId));
+    cond.reserve(body->size());
+    for (auto g : body->goals()) { cond.push_back(toInt(g)); }
+    return true;
 }
 #undef RT
 /////////////////////////////////////////////////////////////////////////////////////////
 // class LogicProgramAdapter
 /////////////////////////////////////////////////////////////////////////////////////////
-LogicProgramAdapter::LogicProgramAdapter(LogicProgram& prg) : lp_(&prg), inc_(false) {}
-void LogicProgramAdapter::initProgram(bool inc) {
-	inc_ = inc;
-}
+LogicProgramAdapter::LogicProgramAdapter(LogicProgram& prg, const Options& opts)
+    : lp_(&prg)
+    , opts_(opts)
+    , inc_(false) {}
+LogicProgramAdapter::LogicProgramAdapter(LogicProgram& prg) : LogicProgramAdapter(prg, {}) {}
+void LogicProgramAdapter::initProgram(bool inc) { inc_ = inc; }
 void LogicProgramAdapter::beginStep() {
-	if (inc_ || lp_->frozen()) { lp_->updateProgram(); }
+    if (inc_ || lp_->frozen()) {
+        lp_->updateProgram();
+    }
 }
 void LogicProgramAdapter::endStep() {
-
-}
-void LogicProgramAdapter::rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& body) {
-	lp_->addRule(ht, head, body);
-}
-void LogicProgramAdapter::rule(Potassco::Head_t ht, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& body) {
-	lp_->addRule(ht, head, bound, body);
-}
-void LogicProgramAdapter::minimize(Potassco::Weight_t prio, const Potassco::WeightLitSpan& lits) {
-	lp_->addMinimize(prio, lits);
-}
-void LogicProgramAdapter::project(const Potassco::AtomSpan& atoms) {
-	lp_->addProject(atoms);
-}
-void LogicProgramAdapter::output(const Potassco::StringSpan& str, const Potassco::LitSpan& cond) {
-	lp_->addOutput(ConstString(str), cond);
-}
-void LogicProgramAdapter::external(Potassco::Atom_t a, Potassco::Value_t v) {
-	lp_->addExternal(a, v);
-}
-void LogicProgramAdapter::assume(const Potassco::LitSpan& lits) {
-	lp_->addAssumption(lits);
-}
-void LogicProgramAdapter::heuristic(Potassco::Atom_t a, Potassco::Heuristic_t t, int bias, unsigned prio, const Potassco::LitSpan& cond) {
-	lp_->addDomHeuristic(a, t, bias, prio, cond);
-}
-void LogicProgramAdapter::acycEdge(int s, int t, const Potassco::LitSpan& cond) {
-	lp_->addAcycEdge(static_cast(s), static_cast(t), cond);
-}
-void LogicProgramAdapter::theoryTerm(Potassco::Id_t termId, int number) {
-	lp_->theoryData().addTerm(termId, number);
-}
-void LogicProgramAdapter::theoryTerm(Potassco::Id_t termId, const Potassco::StringSpan& name) {
-	lp_->theoryData().addTerm(termId, name);
-}
-void LogicProgramAdapter::theoryTerm(Potassco::Id_t termId, int cId, const Potassco::IdSpan& args) {
-	if (cId >= 0) { lp_->theoryData().addTerm(termId, static_cast(cId), args); }
-	else { lp_->theoryData().addTerm(termId, static_cast(cId), args); }
-}
-void LogicProgramAdapter::theoryElement(Potassco::Id_t elementId, const Potassco::IdSpan& terms, const Potassco::LitSpan& cond) {
-	lp_->theoryData().addElement(elementId, terms, lp_->newCondition(cond));
-}
-void LogicProgramAdapter::theoryAtom(Potassco::Id_t atomOrZero, Potassco::Id_t termId, const Potassco::IdSpan& elements) {
-	lp_->theoryData().addAtom(atomOrZero, termId, elements);
-}
-void LogicProgramAdapter::theoryAtom(Potassco::Id_t atomOrZero, Potassco::Id_t termId, const Potassco::IdSpan& elements, Potassco::Id_t op, Potassco::Id_t rhs) {
-	lp_->theoryData().addAtom(atomOrZero, termId, elements, op, rhs);
-}
-} } // end namespace Asp
-
+    if (inc_ && opts_.removeMinimize && lp_->ctx()->hasMinimize()) {
+        lp_->ctx()->removeMinimize();
+    }
+}
+void LogicProgramAdapter::rule(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan body) {
+    lp_->addRule(ht, head, body);
+}
+void LogicProgramAdapter::rule(HeadType ht, Potassco::AtomSpan head, Potassco::Weight_t bound, WeightLitSpan body) {
+    lp_->addRule(ht, head, bound, body);
+}
+void LogicProgramAdapter::minimize(Potassco::Weight_t prio, WeightLitSpan lits) { lp_->addMinimize(prio, lits); }
+void LogicProgramAdapter::project(Potassco::AtomSpan atoms) { lp_->addProject(atoms); }
+void LogicProgramAdapter::output(std::string_view str, Potassco::LitSpan cond) {
+    lp_->addOutput(Potassco::ConstString(str, Potassco::ConstString::create_shared), cond);
+}
+void LogicProgramAdapter::outputAtom(Atom_t atom, const Potassco::ConstString& n) { lp_->addOutput(n, atom); }
+void LogicProgramAdapter::external(Atom_t a, Potassco::TruthValue v) { lp_->addExternal(a, v); }
+void LogicProgramAdapter::assume(Potassco::LitSpan lits) { lp_->addAssumption(lits); }
+void LogicProgramAdapter::heuristic(Atom_t a, Potassco::DomModifier t, int bias, unsigned prio,
+                                    Potassco::LitSpan cond) {
+    lp_->addDomHeuristic(a, t, bias, prio, cond);
+}
+void LogicProgramAdapter::acycEdge(int s, int t, Potassco::LitSpan cond) {
+    lp_->addAcycEdge(static_cast(s), static_cast(t), cond);
+}
+void LogicProgramAdapter::theoryTerm(Id_t termId, int number) { lp_->theoryData().addTerm(termId, number); }
+void LogicProgramAdapter::theoryTerm(Id_t termId, std::string_view name) { lp_->theoryData().addTerm(termId, name); }
+void LogicProgramAdapter::theoryTerm(Id_t termId, int cId, Potassco::IdSpan args) {
+    if (cId >= 0) {
+        lp_->theoryData().addTerm(termId, static_cast(cId), args);
+    }
+    else {
+        lp_->theoryData().addTerm(termId, static_cast(cId), args);
+    }
+}
+void LogicProgramAdapter::theoryElement(Id_t elementId, Potassco::IdSpan terms, Potassco::LitSpan cond) {
+    lp_->theoryData().addElement(elementId, terms, lp_->newCondition(cond));
+}
+void LogicProgramAdapter::theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements) {
+    lp_->theoryData().addAtom(atomOrZero, termId, elements);
+}
+void LogicProgramAdapter::theoryAtom(Id_t atomOrZero, Id_t termId, Potassco::IdSpan elements, Id_t op, Id_t rhs) {
+    lp_->theoryData().addAtom(atomOrZero, termId, elements, op, rhs);
+}
+} // namespace Clasp::Asp
diff --git a/src/logic_program_types.cpp b/src/logic_program_types.cpp
index 6ca7e00..9b8d370 100644
--- a/src/logic_program_types.cpp
+++ b/src/logic_program_types.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,113 +22,115 @@
 // IN THE SOFTWARE.
 //
 #include 
+
+#include 
 #include 
 #include 
-#include 
-#include 
 #include 
+#include 
 
-namespace Clasp {
-namespace Asp {
-static_assert((bk_lib::detail::same_type::value), "unexpected weight type");
-static_assert((bk_lib::detail::same_type::value)       , "unexpected atom type");
-static_assert((bk_lib::detail::same_type::value)      , "unexpected id type");
-static_assert((bk_lib::detail::same_type::value)      , "unexpected literal type");
-static_assert((bk_lib::detail::same_type::value)   , "unexpected weight type");
+#include 
+
+namespace Clasp::Asp {
+static_assert(std::is_same_v, "unexpected weight type");
+static_assert(std::is_same_v, "unexpected atom type");
+static_assert(std::is_same_v, "unexpected id type");
+static_assert(std::is_same_v, "unexpected literal type");
+static_assert(std::is_same_v, "unexpected weight type");
+template 
+constexpr auto writable(SpanView in) {
+    return std::span(const_cast(in.data()), in.size());
+}
 /////////////////////////////////////////////////////////////////////////////////////////
 // class RuleTransform
 //
 // class for transforming extended rules to normal rules
 /////////////////////////////////////////////////////////////////////////////////////////
 struct RuleTransform::Impl {
-	Impl() : adapt_(0), prg_(0) { }
-	struct TodoItem {
-		TodoItem(uint32 i, weight_t w, Atom_t v) : idx(i), bound(w), head(v) {}
-		uint32   idx;
-		weight_t bound;
-		Atom_t   head;
-	};
-	struct CmpW {
-		template 
-		bool operator()(const P& lhs, const P& rhs) const {
-			return Potassco::weight(lhs) > Potassco::weight(rhs);
-		}
-	};
-	typedef PodQueue TodoQueue;
-	typedef Potassco::LitVec  LitVec;
-	typedef Potassco::WLitVec WLitVec;
-	ProgramAdapter* adapt_;
-	LogicProgram*   prg_;
-	LitVec          lits_;
-	WLitVec         agg_;
-	SumVec          sumR_;
-	VarVec          aux_;
-	TodoQueue       todo_;
-	weight_t        bound_;
-	Atom_t newAtom() const { return prg_ ? prg_->newAtom() : adapt_->newAtom(); }
-	uint32 addRule(const Rule& r) const {
-		if (prg_){ prg_->addRule(r); }
-		else     { adapt_->addRule(r); }
-		return 1;
-	}
-	uint32 addRule(Head_t ht, const Potassco::AtomSpan& head, const Potassco::LitSpan& b) const {
-		return addRule(Rule::normal(ht, head, b));
-	}
-	uint32 addRule(Atom_t h, const Potassco::LitSpan& b) const {
-		return addRule(Head_t::Disjunctive, Potassco::toSpan(&h, static_cast(h != 0)), b);
-	}
-	uint32 transform(Atom_t head, weight_t bound, const Potassco::WeightLitSpan& lits, Strategy s);
-	uint32 transformSelect(Atom_t head);
-	uint32 transformSplit(Atom_t head);
-	uint32 transformChoice(const Potassco::AtomSpan& r);
-	uint32 transformDisjunction(const Potassco::AtomSpan& r);
-	uint32 addRule(Atom_t head, bool add, uint32 idx, weight_t bound);
-	Atom_t getAuxVar(uint32 idx, weight_t bound) {
-		assert(bound > 0 && idx < agg_.size());
-		uint32 k = static_cast(bound - 1);
-		if (aux_[k] == 0) {
-			todo_.push(TodoItem(idx, bound, aux_[k] = newAtom()));
-		}
-		return aux_[k];
-	}
+    Impl() = default;
+    struct TodoItem {
+        TodoItem(uint32_t i, Weight_t w, Atom_t v) : idx(i), bound(w), head(v) {}
+        uint32_t idx;
+        Weight_t bound;
+        Atom_t   head;
+    };
+    using TodoQueue = PodQueue;
+    using LitVec    = Potassco::LitVec;
+    using WLitVec   = Potassco::WLitVec;
+    [[nodiscard]] Atom_t newAtom() const { return prg ? prg->newAtom() : adapt->newAtom(); }
+    // NOLINTBEGIN(modernize-use-nodiscard)
+    uint32_t addRule(const Rule& r) const {
+        if (prg) {
+            prg->addRule(r);
+        }
+        else {
+            adapt->addRule(r);
+        }
+        return 1;
+    }
+    uint32_t addRule(HeadType ht, Potassco::AtomSpan head, Potassco::LitSpan b) const {
+        return addRule(Rule::normal(ht, head, b));
+    }
+    uint32_t addRule(Atom_t h, Potassco::LitSpan b) const {
+        return addRule(HeadType::disjunctive, {&h, static_cast(h != 0)}, b);
+    }
+    // NOLINTEND(modernize-use-nodiscard)
+    uint32_t transform(Atom_t head, Weight_t bound, Potassco::WeightLitSpan lits, Strategy s);
+    uint32_t transformSelect(Atom_t head);
+    uint32_t transformSplit(Atom_t head);
+    uint32_t transformChoice(Potassco::AtomSpan);
+    uint32_t transformDisjunction(Potassco::AtomSpan);
+    uint32_t addRule(Atom_t head, bool add, uint32_t idx, Weight_t bound);
+    Atom_t   getAuxVar(uint32_t idx, Weight_t bound) {
+        assert(bound > 0 && idx < agg_.size());
+        auto k = static_cast(bound - 1);
+        if (aux_[k] == 0) {
+            todo_.push(TodoItem(idx, bound, aux_[k] = newAtom()));
+        }
+        return aux_[k];
+    }
+    ProgramAdapter* adapt{nullptr};
+    LogicProgram*   prg{nullptr};
+    LitVec          lits;
+
+private:
+    WLitVec   agg_;
+    SumVec    sumR_;
+    VarVec    aux_;
+    TodoQueue todo_;
+    Weight_t  bound_{0};
 };
 
-RuleTransform::RuleTransform(ProgramAdapter& prg) : impl_(new Impl()) {
-	impl_->adapt_ = &prg;
-}
-RuleTransform::RuleTransform(LogicProgram& prg) : impl_(new Impl()) {
-	impl_->prg_ = &prg;
-}
-RuleTransform::~RuleTransform() {
-	delete impl_;
-}
+RuleTransform::RuleTransform(ProgramAdapter& prg) : impl_(std::make_unique()) { impl_->adapt = &prg; }
+RuleTransform::RuleTransform(LogicProgram& prg) : impl_(std::make_unique()) { impl_->prg = &prg; }
+RuleTransform::~RuleTransform() = default;
 
-uint32 RuleTransform::transform(const Rule& r, Strategy s) {
-	if (r.sum()) {
-		Atom_t h = !Potassco::empty(r.head) ? r.head[0] : 0;
-		bool aux = r.ht == Head_t::Choice || size(r.head) > 1;
-		if (aux) {
-			h = impl_->newAtom();
-			Potassco::Lit_t bl = Potassco::lit(h);
-			impl_->addRule(r.ht, r.head, Potassco::toSpan(&bl, 1));
-		}
-		return uint32(aux) + impl_->transform(h, r.agg.bound, r.agg.lits, s);
-	}
-	else if (size(r.head) > static_cast(r.ht == Head_t::Disjunctive)) {
-		impl_->lits_.clear();
-		uint32 nAux = (size(r.cond) > 1) && (size(r.head) > 1) && s != strategy_no_aux;
-		if (nAux) {
-			// make body eq to a new aux atom
-			Atom_t auxB = impl_->newAtom();
-			impl_->addRule(auxB, r.cond);
-			impl_->lits_.push_back(Potassco::lit(auxB));
-		}
-		else {
-			impl_->lits_.assign(begin(r.cond), end(r.cond));
-		}
-		return nAux + (r.ht == Head_t::Choice ? impl_->transformChoice(r.head) : impl_->transformDisjunction(r.head));
-	}
-	return impl_->addRule(r);
+uint32_t RuleTransform::transform(const Rule& r, Strategy s) {
+    if (r.sum()) {
+        Atom_t h   = not r.head.empty() ? r.head[0] : 0;
+        bool   aux = r.ht == HeadType::choice || size(r.head) > 1;
+        if (aux) {
+            h                  = impl_->newAtom();
+            Potassco::Lit_t bl = Potassco::lit(h);
+            (void) impl_->addRule(r.ht, r.head, Potassco::toSpan(bl));
+        }
+        return static_cast(aux) + impl_->transform(h, r.agg.bound, r.agg.lits, s);
+    }
+    if (size(r.head) > static_cast(r.ht == HeadType::disjunctive)) {
+        impl_->lits.clear();
+        uint32_t nAux = (size(r.cond) > 1) && (size(r.head) > 1) && s != strategy_no_aux;
+        if (nAux) {
+            // make body eq to a new aux atom
+            Atom_t auxB = impl_->newAtom();
+            (void) impl_->addRule(auxB, r.cond);
+            impl_->lits.push_back(Potassco::lit(auxB));
+        }
+        else {
+            impl_->lits.assign(begin(r.cond), end(r.cond));
+        }
+        return nAux + (r.ht == HeadType::choice ? impl_->transformChoice(r.head) : impl_->transformDisjunction(r.head));
+    }
+    return impl_->addRule(r);
 }
 
 // A choice rule {h1,...hn} :- BODY is replaced with:
@@ -137,65 +139,65 @@ uint32 RuleTransform::transform(const Rule& r, Strategy s) {
 // ...
 // hn   :- BODY, not auxN.
 // auxN :- not hn.
-uint32 RuleTransform::Impl::transformChoice(const Potassco::AtomSpan& atoms) {
-	uint32 nRule = 0;
-	Potassco::Lit_t   bLit = 0;
-	Potassco::LitSpan bAux = Potassco::toSpan(&bLit, 1);
-	Atom_t hAux;
-	for (Potassco::AtomSpan::iterator it = Potassco::begin(atoms), end = Potassco::end(atoms); it != end; ++it) {
-		hAux = newAtom();
-		bLit = Potassco::neg(*it);
-		lits_.push_back(Potassco::neg(hAux));
-		nRule += addRule(*it, Potassco::toSpan(lits_));
-		nRule += addRule(hAux, bAux);
-		lits_.pop_back();
-	}
-	return nRule;
+uint32_t RuleTransform::Impl::transformChoice(Potassco::AtomSpan atoms) {
+    uint32_t nRule = 0;
+    auto     bLit  = static_cast(0);
+    auto     bAux  = Potassco::toSpan(bLit);
+    for (auto atom : atoms) {
+        auto hAux = newAtom();
+        bAux[0]   = Potassco::neg(atom);
+        lits.push_back(Potassco::neg(hAux));
+        nRule += addRule(atom, lits);
+        nRule += addRule(hAux, bAux);
+        lits.pop_back();
+    }
+    return nRule;
 }
 
 // A disjunctive rule h1|...|hn :- BODY is replaced with:
 // hi   :- BODY, {not hj | 1 <= j != i <= n}.
-uint32 RuleTransform::Impl::transformDisjunction(const Potassco::AtomSpan& atoms) {
-	uint32 bIdx = sizeVec(lits_);
-	for (Potassco::AtomSpan::iterator it = Potassco::begin(atoms) + 1, end = Potassco::end(atoms); it != end; ++it) {
-		lits_.push_back(Potassco::neg(*it));
-	}
-	uint32 nRule = 0;
-	for (Potassco::AtomSpan::iterator it = Potassco::begin(atoms), end = Potassco::end(atoms);;) {
-		nRule += addRule(*it, Potassco::toSpan(lits_));
-		if (++it == end) { break; }
-		lits_[bIdx++] = Potassco::neg(*(it-1));
-	}
-	return nRule;
+uint32_t RuleTransform::Impl::transformDisjunction(Potassco::AtomSpan atoms) {
+    uint32_t bIdx = size32(lits);
+    for (auto at : atoms.subspan(1)) { lits.push_back(Potassco::neg(at)); }
+    uint32_t nRule = 0;
+    for (auto it = atoms.begin(), end = atoms.end();;) {
+        nRule += addRule(*it, lits);
+        if (++it == end) {
+            break;
+        }
+        lits[bIdx++] = Potassco::neg(*(it - 1));
+    }
+    return nRule;
 }
 
-uint32 RuleTransform::Impl::transform(Atom_t head, weight_t bound, const Potassco::WeightLitSpan& wlits, Strategy s) {
-	bound_ = bound;
-	agg_.assign(begin(wlits), end(wlits));
-	if (!isSorted(agg_.begin(), agg_.end(), CmpW())) {
-		std::stable_sort(agg_.begin(), agg_.end(), CmpW());
-	}
-	wsum_t sum = 0;
-	sumR_.resize(agg_.size());
-	for (WLitVec::size_type i = agg_.size(); i--;) {
-		agg_[i].weight = std::min(agg_[i].weight, bound_);
-		sumR_[i] = (sum += agg_[i].weight);
-		POTASSCO_REQUIRE(agg_[i].weight >= 0 && sum <= CLASP_WEIGHT_T_MAX, "invalid weight rule");
-	}
-	if      (bound_ > sum) { return 0; }
-	else if (bound_ <= 0)  { return addRule(head, Potassco::toSpan()); }
-	else if ((sum - agg_.back().weight) < bound_) { // normal rule
-		lits_.clear();
-		for (WLitVec::const_iterator it = agg_.begin(), end = agg_.end(); it != end; ++it) {
-			lits_.push_back(lit(*it));
-		}
-		return addRule(head, Potassco::toSpan(lits_));
-	}
-	else {
-		return ((s == strategy_no_aux || (sum < 6 && s == strategy_default))
-			? transformSelect(head)
-			: transformSplit(head));
-	}
+uint32_t RuleTransform::Impl::transform(Atom_t head, Weight_t bound, Potassco::WeightLitSpan wlits, Strategy s) {
+    bound_ = bound;
+    agg_.assign(begin(wlits), end(wlits));
+    constexpr auto cmpW = [](const auto& lhs, const auto& rhs) {
+        return Potassco::weight(lhs) > Potassco::weight(rhs);
+    };
+    if (not std::ranges::is_sorted(agg_, cmpW)) {
+        std::ranges::stable_sort(agg_, cmpW);
+    }
+    Wsum_t sum = 0;
+    sumR_.resize(agg_.size());
+    for (auto i = agg_.size(); i--;) {
+        agg_[i].weight = std::min(agg_[i].weight, bound_);
+        sumR_[i]       = (sum += agg_[i].weight);
+        POTASSCO_CHECK_PRE(agg_[i].weight >= 0 && sum <= weight_max, "invalid weight rule");
+    }
+    if (bound_ > sum) {
+        return 0;
+    }
+    if (bound_ <= 0) {
+        return addRule(head, {});
+    }
+    if ((sum - agg_.back().weight) < bound_) { // normal rule
+        lits.clear();
+        for (const auto& p : agg_) { lits.push_back(lit(p)); }
+        return addRule(head, lits);
+    }
+    return s == strategy_no_aux || (sum < 6 && s == strategy_default) ? transformSelect(head) : transformSplit(head);
 }
 
 // Exponential transformation of cardinality and weight constraint.
@@ -207,27 +209,29 @@ uint32 RuleTransform::Impl::transform(Atom_t head, weight_t bound, const Potassc
 // h :- b, c.
 // h :- b, d.
 // h :- c, d.
-uint32 RuleTransform::Impl::transformSelect(Atom_t h) {
-	lits_.clear();
-	uint32 nRule = 0;
-	wsum_t cw = 0;
-	assert(sumR_[0] >= bound_ && cw < bound_);
-	aux_.clear();
-	for (uint32 it = 0, end = (uint32)agg_.size();;) {
-		while (cw < bound_) {
-			cw += Potassco::weight(agg_[it]);
-			lits_.push_back(Potassco::lit(agg_[it]));
-			aux_.push_back(it++);
-		}
-		nRule += addRule(h, Potassco::toSpan(lits_));
-		do {
-			if (aux_.empty()) { return nRule; }
-			it = aux_.back();
-			aux_.pop_back();
-			lits_.pop_back();
-			cw -= Potassco::weight(agg_[it]);
-		} while (++it == end || (cw + sumR_[it]) < bound_);
-	}
+uint32_t RuleTransform::Impl::transformSelect(Atom_t h) {
+    lits.clear();
+    uint32_t nRule = 0;
+    Wsum_t   cw    = 0;
+    assert(sumR_[0] >= bound_ && cw < bound_);
+    aux_.clear();
+    for (uint32_t it = 0, end = size32(agg_);;) {
+        while (cw < bound_) {
+            cw += Potassco::weight(agg_[it]);
+            lits.push_back(Potassco::lit(agg_[it]));
+            aux_.push_back(it++);
+        }
+        nRule += addRule(h, lits);
+        do {
+            if (aux_.empty()) {
+                return nRule;
+            }
+            it = aux_.back();
+            aux_.pop_back();
+            lits.pop_back();
+            cw -= Potassco::weight(agg_[it]);
+        } while (++it == end || (cw + sumR_[it]) < bound_);
+    }
 }
 // Quadratic transformation of cardinality and weight constraint.
 // Introduces aux atoms.
@@ -240,242 +244,273 @@ uint32 RuleTransform::Impl::transformSelect(Atom_t h) {
 // aux_1_2 :- c, d.
 // aux_2_1 :- c.
 // aux_2_1 :- d.
-uint32 RuleTransform::Impl::transformSplit(Atom_t h) {
-	const weight_t bound = bound_;
-	uint32 nRule = 0;
-	uint32 level = 0;
-	aux_.resize(bound, 0);
-	todo_.clear();
-	todo_.push(TodoItem(0, bound, h));
-	while (!todo_.empty()) {
-		TodoItem i = todo_.pop_ret();
-		if (i.idx > level) {
-			// We are about to start a new level of the tree - reset aux_
-			level = i.idx;
-			aux_.assign(bound, 0);
-		}
-		// For a todo item i with head h and lit x = agg_[i.idx] create at most two rules:
-		// r1: h :- x, aux(i.idx+1, i.bound-weight(x))
-		// r2: h :- aux(i.idx+1, i.bound).
-		// The first rule r1 represents the case where x is true, while
-		// the second rule encodes the case where the literal is false.
-		nRule += addRule(i.head, true,  i.idx, i.bound - agg_[i.idx].weight);
-		nRule += addRule(i.head, false, i.idx, i.bound);
-	}
-	return nRule;
+uint32_t RuleTransform::Impl::transformSplit(Atom_t h) {
+    const Weight_t bound = bound_;
+    uint32_t       nRule = 0;
+    uint32_t       level = 0;
+    aux_.resize(static_cast(bound), 0);
+    todo_.clear();
+    todo_.push(TodoItem(0, bound, h));
+    while (not todo_.empty()) {
+        TodoItem i = todo_.pop_ret();
+        if (i.idx > level) {
+            // We are about to start a new level of the tree - reset aux_
+            level = i.idx;
+            aux_.assign(static_cast(bound), 0);
+        }
+        // For a todo item i with head h and lit x = agg_[i.idx] create at most two rules:
+        // r1: h :- x, aux(i.idx+1, i.bound-weight(x))
+        // r2: h :- aux(i.idx+1, i.bound).
+        // The first rule r1 represents the case where x is true, while
+        // the second rule encodes the case where the literal is false.
+        nRule += addRule(i.head, true, i.idx, i.bound - agg_[i.idx].weight);
+        nRule += addRule(i.head, false, i.idx, i.bound);
+    }
+    return nRule;
 }
 
 // Creates a rule head :- agg_[idx], aux(idx+1, bound) or head :- aux(idx+1, bound) or depending on add.
-uint32 RuleTransform::Impl::addRule(Atom_t head, bool add, uint32 bIdx, weight_t bound) {
-	const weight_t minW = agg_.back().weight;
-	const wsum_t   maxW = sumR_[bIdx + 1];
-	if (bound <= 0) {
-		assert(add);
-		lits_.assign(1, agg_[bIdx].lit);
-		return addRule(head, Potassco::toSpan(lits_));
-	}
-	if ((maxW - minW) < bound) {
-		// remaining literals are all needed to satisfy bound
-		bIdx += static_cast(!add);
-		if (maxW >= bound) {
-			lits_.clear();
-			for (; bIdx != agg_.size(); ++bIdx) { lits_.push_back(agg_[bIdx].lit); }
-			return addRule(head, Potassco::toSpan(lits_));
-		}
-		return 0;
-	}
-	lits_.clear();
-	if (add) { lits_.push_back(agg_[bIdx].lit); }
-	lits_.push_back(Potassco::lit(getAuxVar(bIdx + 1, bound)));
-	return addRule(head, Potassco::toSpan(lits_));
+uint32_t RuleTransform::Impl::addRule(Atom_t head, bool add, uint32_t bIdx, Weight_t bound) {
+    const Weight_t minW = agg_.back().weight;
+    const Wsum_t   maxW = sumR_[bIdx + 1];
+    if (bound <= 0) {
+        assert(add);
+        lits.assign(1, agg_[bIdx].lit);
+        return addRule(head, lits);
+    }
+    if ((maxW - minW) < bound) {
+        // remaining literals are all needed to satisfy bound
+        bIdx += static_cast(not add);
+        if (maxW >= bound) {
+            lits.clear();
+            for (; bIdx != agg_.size(); ++bIdx) { lits.push_back(agg_[bIdx].lit); }
+            return addRule(head, lits);
+        }
+        return 0;
+    }
+    lits.clear();
+    if (add) {
+        lits.push_back(agg_[bIdx].lit);
+    }
+    lits.push_back(Potassco::lit(getAuxVar(bIdx + 1, bound)));
+    return addRule(head, lits);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class SccChecker
 //
 // SCC/cycle checking
 /////////////////////////////////////////////////////////////////////////////////////////
-SccChecker::SccChecker(LogicProgram& prg, AtomList& sccAtoms, uint32 startScc)
-	: prg_(&prg), sccAtoms_(&sccAtoms), count_(0), sccs_(startScc) {
-	for (uint32 i = 0; i != prg.numAtoms(); ++i) {
-		visit(prg.getAtom(i));
-	}
-	for (uint32 i = 0; i != prg.numBodies(); ++i) {
-		visit(prg.getBody(i));
-	}
+SccChecker::SccChecker(LogicProgram& prg, AtomList& sccAtoms, uint32_t startScc)
+    : prg_(&prg)
+    , sccAtoms_(&sccAtoms)
+    , count_(0)
+    , sccs_(startScc) {
+    for (auto* atom : prg.atoms()) { visitDfs(atom, PrgNode::atom); }
+    for (auto* body : prg.bodies()) { visitDfs(body, PrgNode::body); }
 }
 
 void SccChecker::visitDfs(PrgNode* node, NodeType t) {
-	if (!prg_ || !doVisit(node)) {
-		return;
-	}
-	callStack_.clear();
-	nodeStack_.clear();
-	count_ = 0;
-	addCall(node, t, 0);
-	while (!callStack_.empty()) {
-		Call c = callStack_.back();
-		callStack_.pop_back();
-		if (!recurse(c)) {
-			node = unpackNode(c.node);
-			if (c.min < node->id()) {
-				node->resetId( c.min, true );
-			}
-			else if (c.node == nodeStack_.back()) {
-				// node is trivially-connected; all such nodes are in the same Pseudo-SCC
-				if (isNode(nodeStack_.back(), PrgNode::Atom)) {
-					static_cast(node)->setScc(PrgNode::noScc);
-				}
-				node->resetId(PrgNode::noNode, true);
-				nodeStack_.pop_back();
-			}
-			else { // non-trivial SCC
-				PrgNode* succVertex;
-				do {
-					succVertex = unpackNode(nodeStack_.back());
-					if (isNode(nodeStack_.back(), PrgNode::Atom)) {
-						static_cast(succVertex)->setScc(sccs_);
-						sccAtoms_->push_back(static_cast(succVertex));
-					}
-					nodeStack_.pop_back();
-					succVertex->resetId(PrgNode::noNode, true);
-				} while (succVertex != node);
-				++sccs_;
-			}
-		}
-	}
+    if (not prg_ || not doVisit(node)) {
+        return;
+    }
+    callStack_.clear();
+    nodeStack_.clear();
+    count_ = 0;
+    addCall(node, t, 0);
+    while (not callStack_.empty()) {
+        Call c = callStack_.back();
+        callStack_.pop_back();
+        if (not recurse(c)) {
+            node = unpackNode(c.node);
+            if (c.min < node->id()) {
+                node->resetId(c.min, true);
+            }
+            else if (c.node == nodeStack_.back()) {
+                // node is trivially-connected; all such nodes are in the same Pseudo-SCC
+                if (isNode(nodeStack_.back(), PrgNode::atom)) {
+                    node_cast(node)->setScc(PrgNode::no_scc);
+                }
+                node->resetId(PrgNode::no_node, true);
+                nodeStack_.pop_back();
+            }
+            else { // non-trivial SCC
+                PrgNode* succVertex;
+                do {
+                    succVertex = unpackNode(nodeStack_.back());
+                    if (isNode(nodeStack_.back(), PrgNode::atom)) {
+                        node_cast(succVertex)->setScc(sccs_);
+                        sccAtoms_->push_back(node_cast(succVertex));
+                    }
+                    nodeStack_.pop_back();
+                    succVertex->resetId(PrgNode::no_node, true);
+                } while (succVertex != node);
+                ++sccs_;
+            }
+        }
+    }
 }
 
 bool SccChecker::recurse(Call& c) {
-	PrgNode* n = unpackNode(c.node);
-	if (!n->seen()) {
-		nodeStack_.push_back(c.node);
-		c.min = count_++;
-		n->resetId(c.min, true);
-	}
-	if (isNode(c.node, PrgNode::Body)) {
-		PrgBody* b = static_cast(n);
-		PrgHead* h = 0; NodeType t;
-		for (PrgBody::head_iterator it = b->heads_begin() + c.next, end = b->heads_end(); it != end; ++it) {
-			if   (it->isAtom()){ h = prg_->getAtom(it->node()); t = PrgNode::Atom; }
-			else               { h = prg_->getDisj(it->node()); t = PrgNode::Disj; }
-			if (doVisit(h, false) && onNode(h, t, c, static_cast(it-b->heads_begin()))) {
-				return true;
-			}
-		}
-	}
-	else if (isNode(c.node, PrgNode::Atom)) {
-		PrgAtom* a = static_cast(n);
-		for (PrgAtom::dep_iterator it = a->deps_begin() + c.next, end = a->deps_end(); it != end; ++it) {
-			if (it->sign()) continue;
-			PrgBody* bn = prg_->getBody(it->var());
-			if (doVisit(bn, false) && onNode(bn, PrgNode::Body, c, static_cast(it-a->deps_begin()))) {
-				return true;
-			}
-		}
-	}
-	else if (isNode(c.node, PrgNode::Disj)) {
-		PrgDisj* d = static_cast(n);
-		for (PrgDisj::atom_iterator it = d->begin() + c.next, end = d->end(); it != end; ++it) {
-			PrgAtom* a = prg_->getAtom(*it);
-			if (doVisit(a, false) && onNode(a, PrgNode::Atom, c, static_cast(it-d->begin()))) {
-				return true;
-			}
-		}
-	}
-	return false;
+    PrgNode* n = unpackNode(c.node);
+    if (not n->seen()) {
+        nodeStack_.push_back(c.node);
+        c.min = count_++;
+        n->resetId(c.min, true);
+    }
+    if (isNode(c.node, PrgNode::body)) {
+        auto*    b = node_cast(n);
+        NodeType t;
+        auto     off = c.next;
+        for (auto e : b->heads().subspan(off)) {
+            PrgHead* h;
+            if (e.isAtom()) {
+                h = prg_->getAtom(e.node());
+                t = PrgNode::atom;
+            }
+            else {
+                h = prg_->getDisj(e.node());
+                t = PrgNode::disj;
+            }
+            if (doVisit(h, false) && onNode(h, t, c, off)) {
+                return true;
+            }
+            ++off;
+        }
+    }
+    else if (isNode(c.node, PrgNode::atom)) {
+        auto* a   = node_cast(n);
+        auto  off = c.next;
+        for (auto dep : a->deps().subspan(off)) {
+            if (dep.sign()) {
+                continue;
+            }
+            PrgBody* bn = prg_->getBody(dep.var());
+            if (doVisit(bn, false) && onNode(bn, PrgNode::body, c, off)) {
+                return true;
+            }
+            ++off;
+        }
+    }
+    else if (isNode(c.node, PrgNode::disj)) {
+        auto* d   = node_cast(n);
+        auto  off = c.next;
+        for (auto e : d->atoms().subspan(off)) {
+            PrgAtom* a = prg_->getAtom(e);
+            if (doVisit(a, false) && onNode(a, PrgNode::atom, c, off)) {
+                return true;
+            }
+            ++off;
+        }
+    }
+    return false;
 }
 
-bool SccChecker::onNode(PrgNode* n, NodeType t, Call& c, uint32 data) {
-	if (!n->seen()) {
-		Call rec = {c.node, c.min, data};
-		callStack_.push_back(rec);
-		addCall(n, t, 0);
-		return true;
-	}
-	if (n->id() < c.min) {
-		c.min = n->id();
-	}
-	return false;
-}
-/////////////////////////////////////////////////////////////////////////////////////////
-PrgNode::PrgNode(uint32 id, bool checkScc)
-	: litId_(noLit), noScc_(uint32(!checkScc)), id_(id), val_(value_free), eq_(0), seen_(0) {
-	POTASSCO_CHECK(id < noNode, EOVERFLOW, "Id out of range");
-	static_assert(sizeof(PrgNode) == sizeof(uint64), "Unsupported Alignment");
+bool SccChecker::onNode(PrgNode* n, NodeType t, Call& c, uint32_t data) {
+    if (not n->seen()) {
+        Call rec = {c.node, c.min, data};
+        callStack_.push_back(rec);
+        addCall(n, t, 0);
+        return true;
+    }
+    if (n->id() < c.min) {
+        c.min = n->id();
+    }
+    return false;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgHead
 /////////////////////////////////////////////////////////////////////////////////////////
-PrgHead::PrgHead(uint32 id, NodeType t, uint32 data, bool checkScc)
-	: PrgNode(id, checkScc)
-	, data_(data), upper_(0), dirty_(0), freeze_(0), isAtom_(t == PrgNode::Atom)  {
-	struct X { uint64 x; EdgeVec y; uint32 z; };
-	static_assert(sizeof(PrgHead) == sizeof(X), "Unsupported Alignment");
+PrgHead::PrgHead(uint32_t id, NodeType t, uint32_t data, bool checkScc)
+    : PrgNode(id, checkScc)
+    , data_(data)
+    , upper_(0)
+    , dirty_(0)
+    , freeze_(0)
+    , isAtom_(t == PrgNode::atom) {
+    struct X {
+        uint64_t x;
+        EdgeVec  y;
+        uint32_t z;
+    };
+    static_assert(sizeof(PrgHead) == sizeof(X), "Unsupported Alignment");
 }
 // Adds the node with given id as a support to this head
 // and marks the head as dirty so that any duplicates/false/eq
 // supports are removed once simplify() is called.
 void PrgHead::addSupport(PrgEdge r, Simplify s) {
-	supports_.push_back(r);
-	if (s == force_simplify) { dirty_ = (supports_.size() > 1); }
+    supports_.push_back(r);
+    if (s == force_simplify) {
+        dirty_ = (supports_.size() > 1);
+    }
 }
 // Removes the given node from the set of supports of this head.
 void PrgHead::removeSupport(PrgEdge r) {
-	if (relevant()) {
-		supports_.erase(std::remove(supports_.begin(), supports_.end(), r), supports_.end());
-	}
-	dirty_ = 1;
+    if (relevant()) {
+        erase(supports_, r);
+    }
+    dirty_ = 1;
 }
 
 void PrgHead::clearSupports() {
-	supports_.clear();
-	upper_  = 0;
-	dirty_  = 0;
+    supports_.clear();
+    upper_ = 0;
+    dirty_ = 0;
 }
 // Simplifies the set of predecessors supporting this head.
 // Removes false/eq supports and returns the number of
 // different supporting literals in numDiffSupps.
-bool PrgHead::simplifySupports(LogicProgram& prg, bool strong, uint32* numDiffSupps) {
-	uint32 numLits = supports();
-	uint32 choices = 0;
-	if (dirty_ == 1) {
-		dirty_                  = 0;
-		numLits                 = 0;
-		SharedContext& ctx      = *prg.ctx();
-		EdgeVec::iterator it,n,j= supports_.begin();
-		for (it = supports_.begin(); it != supports_.end(); ++it) {
-			PrgNode* x = prg.getSupp(*it);
-			if (x->relevant() && x->value() != value_false && (!strong || x->hasVar())) {
-				if      (!x->seen()) { *j++ = *it; x->setSeen(true); }
-				else if (!choices)   { continue; }
-				else                 {
-					for (n = supports_.begin(); n != it && n->node() != it->node(); ++n) {;}
-					if (*it < *n)      { *n = *it; }
-					else               { continue; }
-				}
-				choices += (it->isBody() && it->isChoice());
-				if (strong && !ctx.marked(x->literal())) {
-					++numLits;
-					ctx.mark(x->literal());
-				}
-			}
-		}
-		supports_.erase(j, supports_.end());
-		uint32 dis = 0;
-		choices    = 0;
-		for (it = supports_.begin(); it != supports_.end(); ++it) {
-			PrgNode* x = prg.getSupp(*it);
-			x->setSeen(false);
-			if (strong && ctx.marked(x->literal())) { ctx.unmark(x->var()); }
-			if (it->isChoice()) {
-				++choices;
-				dis += it->isDisj();
-			}
-		}
-		numLits += choices;
-	}
-	if (numDiffSupps) { *numDiffSupps = numLits; }
-	return supports() > 0 || prg.assignValue(this, value_false, PrgEdge::noEdge());
+bool PrgHead::simplifySupports(LogicProgram& prg, bool strong, uint32_t* numDiffSupps) {
+    uint32_t numLits = numSupports();
+    if (dirty_ == 1) {
+        uint32_t choices = 0;
+        dirty_           = 0;
+        numLits          = 0;
+        auto& ctx        = *prg.ctx();
+        auto  j          = supports_.begin();
+        for (auto s : supports_) {
+            if (PrgNode* x = prg.getSupp(s);
+                x->relevant() && x->value() != value_false && (not strong || x->hasVar())) {
+                if (not x->seen()) {
+                    *j++ = s;
+                    x->setSeen(true);
+                }
+                else if (not choices) {
+                    continue;
+                }
+                else {
+                    if (auto n = std::find_if(supports_.begin(), j, [&s](PrgEdge e) { return e.node() == s.node(); });
+                        s < *n) {
+                        *n = s;
+                    }
+                    else {
+                        continue;
+                    }
+                }
+                choices += (s.isBody() && s.isChoice());
+                if (strong && not ctx.marked(x->literal())) {
+                    ++numLits;
+                    ctx.mark(x->literal());
+                }
+            }
+        }
+        supports_.erase(j, supports_.end());
+        choices = 0;
+        for (auto s : supports_) {
+            PrgNode* x = prg.getSupp(s);
+            x->setSeen(false);
+            if (strong && ctx.marked(x->literal())) {
+                ctx.unmark(x->var());
+            }
+            if (s.isChoice()) {
+                ++choices;
+            }
+        }
+        numLits += choices;
+    }
+    if (numDiffSupps) {
+        *numDiffSupps = numLits;
+    }
+    return not supports_.empty() || prg.assignValue(this, value_false, PrgEdge::noEdge());
 }
 
 // Assigns a variable to this head.
@@ -483,150 +518,151 @@ bool PrgHead::simplifySupports(LogicProgram& prg, bool strong, uint32* numDiffSu
 // More than one support or type not normal or no eq: new var
 // Exactly one support that is normal: use that
 void PrgHead::assignVar(LogicProgram& prg, PrgEdge support, bool allowEq) {
-	if (hasVar() || !relevant()) { return; }
-	uint32 numS = supports();
-	if (numS == 0 && support == PrgEdge::noEdge()) {
-		prg.assignValue(this, value_false, support);
-	}
-	else {
-		PrgNode* sup = prg.getSupp(support);
-		bool  newVar = numS > 1 || (!allowEq && Var_t::isAtom(prg.ctx()->varInfo(sup->var()).type()));
-		if (support.isNormal() && sup->hasVar() && (!newVar || sup->value() == value_true)) {
-			// head is equivalent to sup
-			setLiteral(sup->literal());
-			prg.ctx()->setVarEq(var(), true);
-			prg.incEqs(Var_t::Hybrid);
-		}
-		else {
-			setLiteral(posLit(prg.ctx()->addVar(Var_t::Atom, 0)));
-		}
-	}
+    if (hasVar() || not relevant()) {
+        return;
+    }
+    uint32_t numS = numSupports();
+    if (numS == 0 && not support) {
+        prg.assignValue(this, value_false, support);
+    }
+    else {
+        assert(support);
+        PrgNode* sup    = prg.getSupp(support);
+        bool     newVar = numS > 1 || (not allowEq && prg.ctx()->varInfo(sup->var()).atom());
+        if (support.isNormal() && sup->hasVar() && (not newVar || sup->value() == value_true)) {
+            // head is equivalent to sup
+            setLiteral(sup->literal());
+            prg.ctx()->setVarEq(var(), true);
+            prg.incEqs(VarType::hybrid);
+        }
+        else {
+            setLiteral(posLit(prg.ctx()->addVar(VarType::atom, 0)));
+        }
+    }
 }
 
 // Backpropagates the value of this head to its supports.
-bool PrgHead::backpropagate(LogicProgram& prg, ValueRep val, bool bpFull) {
-	bool ok = true;
-	if (val == value_false) {
-		// a false head can't have supports
-		EdgeVec temp; temp.swap(supports_);
-		markDirty();
-		for (EdgeIterator it = temp.begin(), end = temp.end(); it != end && ok; ++it) {
-			if (it->isBody()) {
-				ok = prg.getBody(it->node())->propagateAssigned(prg, this, it->type());
-			}
-			else { assert(it->isDisj());
-				ok = prg.getDisj(it->node())->propagateAssigned(prg, this, it->type());
-			}
-		}
-	}
-	else if (val != value_free && supports() == 1 && bpFull) {
-		// head must be true and only has one support, thus the only support must
-		// be true, too.
-		PrgBody* b   = 0;
-		if (supports_[0].isBody()) {
-			b   = prg.getBody(supports_[0].node());
-		}
-		else if (supports_[0].isDisj()) {
-			PrgDisj* d = prg.getDisj(supports_[0].node());
-			if (d->supports() == 1) { b = prg.getBody(d->supps_begin()->node()); }
-		}
-		ok = !b || b->value() == val || (b->assignValue(val) && b->propagateValue(prg, bpFull));
-	}
-	return ok;
+bool PrgHead::backpropagate(LogicProgram& prg, Val_t val, bool bpFull) {
+    bool ok = true;
+    if (val == value_false) {
+        // a false head can't have supports
+        EdgeVec temp;
+        temp.swap(supports_);
+        markDirty();
+        for (auto e : temp) {
+            if (e.isBody()) {
+                ok = prg.getBody(e.node())->propagateAssigned(prg, this, e.type());
+            }
+            else {
+                assert(e.isDisj() && isAtom());
+                ok = prg.getDisj(e.node())->propagateAssigned(prg, this, e.type());
+            }
+            if (not ok) {
+                break;
+            }
+        }
+    }
+    else if (val != value_free && numSupports() == 1 && bpFull) {
+        // head must be true and only has one support, thus the only support must
+        // be true, too.
+        PrgBody* b = nullptr;
+        if (supports_[0].isBody()) {
+            b = prg.getBody(supports_[0].node());
+        }
+        else if (supports_[0].isDisj()) {
+            if (PrgDisj* d = prg.getDisj(supports_[0].node()); d->numSupports() == 1) {
+                b = prg.getBody(d->support().node());
+            }
+        }
+        ok = not b || b->value() == val || (b->assignValue(val) && b->propagateValue(prg, bpFull));
+    }
+    return ok;
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgAtom
 /////////////////////////////////////////////////////////////////////////////////////////
-PrgAtom::PrgAtom(uint32 id, bool checkScc)
-	: PrgHead(id, PrgNode::Atom, PrgNode::noScc, checkScc) {
-	static_assert(sizeof(PrgAtom) == sizeof(PrgHead) + sizeof(LitVec), "Unsupported Alignment");
+PrgAtom::PrgAtom(uint32_t id, bool checkScc) : PrgHead(id, PrgNode::atom, PrgNode::no_scc, checkScc) {
+    static_assert(sizeof(PrgAtom) == sizeof(PrgHead) + sizeof(LitVec), "Unsupported Alignment");
 }
 
 void PrgAtom::setEqGoal(Literal x) {
-	if (eq()) {
-		POTASSCO_CHECK(!x.sign() || x.var() < noScc, EOVERFLOW, "Id out of range");
-		data_ = x.sign() ? x.var() : noScc;
-	}
+    if (eq()) {
+        POTASSCO_CHECK(not x.sign() || x.var() < no_scc, EOVERFLOW, "Id out of range");
+        data_ = x.sign() ? x.var() : no_scc;
+    }
 }
 Literal PrgAtom::eqGoal(bool sign) const {
-	if (!eq() || sign || data_ == noScc) {
-		return Literal(id(), sign);
-	}
-	return negLit(data_);
+    if (not eq() || sign || data_ == no_scc) {
+        return {id(), sign};
+    }
+    return negLit(data_);
 }
 
 // Adds a dependency between this atom and the body with
 // the given id. If pos is true, atom appears positively
 // in body, otherwise negatively.
-void PrgAtom::addDep(Var bodyId, bool pos) {
-	deps_.push_back(Literal(bodyId, !pos));
-}
+void PrgAtom::addDep(Var_t bodyId, bool pos) { deps_.push_back(Literal(bodyId, not pos)); }
 
 // Removes a dependency between this atom and the body with
 // the given id. If pos is true, atom appears positively
 // in body, otherwise negatively.
-void PrgAtom::removeDep(Var bodyId, bool pos) {
-	LitVec::iterator it = std::find(deps_.begin(), deps_.end(), Literal(bodyId, !pos));
-	if (it != deps_.end()) { deps_.erase(it); }
+void PrgAtom::removeDep(Var_t bodyId, bool pos) {
+    if (auto it = std::ranges::find(deps_, Literal(bodyId, not pos)); it != deps_.end()) {
+        deps_.erase(it);
+    }
 }
 
 // Removes the subset of dependencies given by d
 void PrgAtom::clearDeps(Dependency d) {
-	if (d == dep_all) {
-		deps_.clear();
-	}
-	else {
-		bool sign = d == dep_neg;
-		LitVec::iterator j = deps_.begin();
-		for (LitVec::iterator it = deps_.begin(), end = deps_.end(); it != end; ++it) {
-			if (it->sign() != sign) { *j++ = *it; }
-		}
-		deps_.erase(j, deps_.end());
-	}
+    if (d == dep_all) {
+        deps_.clear();
+    }
+    else {
+        erase_if(deps_, [sign = (d == dep_neg)](Literal x) { return x.sign() == sign; });
+    }
 }
 
 bool PrgAtom::hasDep(Dependency d) const {
-	if (d == dep_all) { return !deps_.empty(); }
-	for (LitVec::const_iterator it = deps_.begin(), end = deps_.end(); it != end; ++it) {
-		if (static_cast(it->sign()) == d) { return true; }
-	}
-	return false;
+    return std::ranges::any_of(deps_,
+                               [d](Literal lit) { return d == dep_all || static_cast(lit.sign()) == d; });
 }
 
 bool PrgAtom::inDisj() const {
-	for (EdgeIterator it= supports_.begin(), end = supports_.end(); it != end; ++it) {
-		if (it->isDisj()) { return true; }
-	}
-	return false;
+    return std::ranges::any_of(supports_, [](PrgEdge e) { return e.isDisj(); });
 }
 
 // Propagates the value of this atom to its dependent bodies
 // and, if backpropagation is enabled, to its supporting bodies/disjunctions.
 // PRE: value() != value_free
 bool PrgAtom::propagateValue(LogicProgram& prg, bool backprop) {
-	ValueRep val = value();
-	assert(val != value_free);
-	// propagate value forward
-	Literal dep = posLit(id());
-	for (dep_iterator it = deps_.begin(), end = deps_end(); it != end; ++it) {
-		if (!prg.getBody(it->var())->propagateAssigned(prg, dep ^ it->sign(), val)) {
-			return false;
-		}
-	}
-	if (inDisj() && prg.isFact(this)) {
-		// - atom is true, thus all disjunctive rules containing it are superfluous
-		EdgeVec temp; temp.swap(supports_);
-		EdgeVec::iterator j = temp.begin();
-		EdgeType t          = PrgEdge::Choice;
-		for (EdgeIterator it= temp.begin(), end = temp.end(); it != end; ++it) {
-			if      (!it->isDisj())                                            { *j++ = *it; }
-			else if (!prg.getDisj(it->node())->propagateAssigned(prg, this, t)){ return false; }
-		}
-		temp.erase(j, temp.end());
-		supports_.swap(temp);
-	}
-	return backpropagate(prg, val, backprop);
+    auto val = value();
+    assert(val != value_free);
+    // propagate value forward
+    Literal dep = posLit(id());
+    for (auto d : deps_) {
+        if (not prg.getBody(d.var())->propagateAssigned(prg, dep ^ d.sign(), val)) {
+            return false;
+        }
+    }
+    if (inDisj() && prg.isFact(this)) {
+        // - atom is true, thus all disjunctive rules containing it are superfluous
+        EdgeVec temp;
+        temp.swap(supports_);
+        auto j = temp.begin();
+        auto t = PrgEdge::choice;
+        for (auto e : temp) {
+            if (not e.isDisj()) {
+                *j++ = e;
+            }
+            else if (not prg.getDisj(e.node())->propagateAssigned(prg, this, t)) {
+                return false;
+            }
+        }
+        temp.erase(j, temp.end());
+        supports_.swap(temp);
+    }
+    return backpropagate(prg, val, backprop);
 }
 
 // Adds the atom-oriented nogoods for this atom in form of clauses.
@@ -635,219 +671,274 @@ bool PrgAtom::propagateValue(LogicProgram& prg, bool backprop) {
 // Furthermore, adds the clause [a ~Bj] representing tableau-rules FTA and BFA
 // if Bj supports a via a "normal" edge.
 bool PrgAtom::addConstraints(const LogicProgram& prg, ClauseCreator& gc) {
-	SharedContext& ctx  = *prg.ctx();
-	EdgeVec::iterator j = supports_.begin();
-	bool           nant = false;
-	gc.start().add(~literal());
-	for (EdgeVec::iterator it = supports_.begin(); it != supports_.end(); ++it) {
-		PrgNode* n = prg.getSupp(*it);
-		Literal  B = n->literal();
-		// consider only bodies which are part of the simplified program, i.e.
-		// are associated with a variable in the solver.
-		if (n->relevant() && n->hasVar()) {
-			*j++ = *it;
-			nant = nant || it->isChoice();
-			if (!it->isDisj()) { gc.add(B); }
-			if (it->isNormal() && !ctx.addBinary(literal(), ~B)) { // FTA/BFA
-				return false;
-			}
-		}
-	}
-	supports_.erase(j, supports_.end());
-	if (nant ||	hasDep(PrgAtom::dep_neg)) { ctx.setNant(var(), true); }
-	return gc.end(ClauseCreator::clause_force_simplify);
+    auto& ctx  = *prg.ctx();
+    auto  j    = supports_.begin();
+    auto  nant = false;
+    gc.start().add(~literal());
+    for (auto support : supports_) {
+        PrgNode* n    = prg.getSupp(support);
+        Literal  bLit = n->literal();
+        // consider only bodies which are part of the simplified program, i.e.
+        // are associated with a variable in the solver.
+        if (n->relevant() && n->hasVar()) {
+            *j++ = support;
+            nant = nant || support.isChoice();
+            if (not support.isDisj()) {
+                gc.add(bLit);
+            }
+            if (support.isNormal() && not ctx.addBinary(literal(), ~bLit)) { // FTA/BFA
+                return false;
+            }
+        }
+    }
+    supports_.erase(j, supports_.end());
+    if (nant || hasDep(PrgAtom::dep_neg)) {
+        ctx.setNant(var(), true);
+    }
+    return gc.end(ClauseCreator::clause_force_simplify).ok();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PrgBody
 /////////////////////////////////////////////////////////////////////////////////////////
-PrgBody::PrgBody(uint32 id, LogicProgram& prg, const Potassco::LitSpan& lits, uint32 pos, bool addDeps)
-	: PrgNode(id, true) {
-	init(Body_t::Normal, toU32(Potassco::size(lits)));
-	Norm* c = new (data_)Norm();
-	unsupp_ = pos;
-	// store B+ followed by B-
-	Literal* p[2] = {c->lits, c->lits + pos};
-	for (Potassco::LitSpan::iterator it = Potassco::begin(lits), end = Potassco::end(lits); it != end; ++it) {
-		POTASSCO_REQUIRE(*it != 0, "body not simplified");
-		Literal*& n = p[*it < 0];
-		*n = toLit(*it);
-		if (addDeps) { prg.getAtom(n->var())->addDep(id, !n->sign()); }
-		++n;
-	}
+[[nodiscard]] static Literal solverLiteral(const LogicProgram& prg, Literal goal) {
+    return prg.getAtom(goal.var())->literal() ^ goal.sign();
+}
+PrgBody::PrgBody(uint32_t id, BodyType t, uint32_t sz)
+    : PrgNode(id, true)
+    , size_(sz)
+    , head_(0)
+    , type_(to_underlying(t))
+    , sBody_(0)
+    , sHead_(0)
+    , freeze_(0)
+    , unsupp_(0)
+    , headData_() {
+    POTASSCO_CHECK_PRE(sz <= max_size, "body too large");
+}
+PrgBody::PrgBody(uint32_t id, const LogicProgram& prg, Potassco::LitSpan lits, uint32_t pos, bool addDeps)
+    : PrgBody(id, BodyType::normal, size32(lits)) {
+    auto c  = new (data_) Norm();
+    unsupp_ = static_cast(pos);
+    // store B+ followed by B-
+    Literal* p[2] = {c->lits, c->lits + pos};
+    for (auto lit : lits) {
+        POTASSCO_CHECK_PRE(lit != 0, "body not simplified");
+        Literal*& n = p[lit < 0];
+        *n          = toLit(lit);
+        if (addDeps) {
+            prg.getAtom(n->var())->addDep(id, not n->sign());
+        }
+        ++n;
+    }
 }
 
-PrgBody::PrgBody(uint32 id, LogicProgram& prg, const Potassco::Sum_t& sum, bool hasWeights, uint32 pos, bool addDeps)
-	: PrgNode(id, true) {
-	init(hasWeights ? Body_t::Sum : Body_t::Count, toU32(Potassco::size(sum.lits)));
-	Agg* a = new (data_) Agg();
-	if (!hasWeights) {
-		a->bound = sum.bound;
-		unsupp_  = sum.bound - static_cast(size_ - pos);
-	}
-	else {
-		a->sum  = SumData::create(size_, sum.bound, 0);
-		unsupp_ = sum.bound;
-	}
-	// store B+ followed by B- followed by optional weights
-	Literal* base = a->lits;
-	Literal* p[2] = {base, base + pos};
-	weight_t* weights = hasWeights ? a->sum->weights : 0;
-	for (Potassco::WeightLitSpan::iterator it = Potassco::begin(sum.lits), end = Potassco::end(sum.lits); it != end; ++it) {
-		POTASSCO_REQUIRE(it->lit != 0 && it->weight > 0, "body not simplified");
-		Literal*& n = p[it->lit < 0];
-		*n = toLit(Potassco::lit(*it));
-		if (weights) { weights[n - base] = it->weight; a->sum->sumW += it->weight; if (n->sign()) { unsupp_ -= it->weight; } }
-		if (addDeps) { prg.getAtom(n->var())->addDep(id, !n->sign()); }
-		++n;
-	}
-}
-void PrgBody::init(Body_t t, uint32 sz) {
-	POTASSCO_REQUIRE(sz <= maxSize, "body too large");
-	size_ = sz, head_ = 0, type_ = t, sBody_ = 0, sHead_ = 0, freeze_ = 0;
-}
-PrgBody* PrgBody::create(LogicProgram& prg, uint32 id, const Rule& r, uint32 pos, bool addDeps) {
-	static_assert(sizeof(PrgBody) == 24 && sizeof(Agg) == sizeof(void*), "unexpected alignment");
-	PrgBody* ret = 0;
-	if (r.normal()) {
-		size_t bytes = sizeof(PrgBody) + (Potassco::size(r.cond) * sizeof(Literal));
-		ret = new (::operator new(bytes)) PrgBody(id, prg, r.cond, pos, addDeps);
-	}
-	else {
-		const Potassco::Sum_t& sum = r.agg;
-		size_t bytes = sizeof(PrgBody) + (Potassco::size(r.agg.lits) * sizeof(Literal)) + sizeof(Agg);
-		ret = new (::operator new(bytes)) PrgBody(id, prg, sum, r.bt == Body_t::Sum, pos, addDeps);
-		POTASSCO_REQUIRE(ret->bound() > 0 && ret->sumW() > ret->bound(), "body not simplified");
-	}
-	if (ret->bound() == 0) {
-		ret->assignValue(value_true);
-		ret->markDirty();
-	}
-	return ret;
+PrgBody::PrgBody(uint32_t id, const LogicProgram& prg, const Potassco::Sum& sum, bool hasWeights, uint32_t pos,
+                 bool addDeps)
+    : PrgBody(id, hasWeights ? BodyType::sum : BodyType::count, size32(sum.lits)) {
+    Agg* a = new (data_) Agg();
+    if (not hasWeights) {
+        a->bound = sum.bound;
+        unsupp_  = sum.bound - static_cast(size_ - pos);
+    }
+    else {
+        a->sum  = SumData::create(size_, sum.bound, 0);
+        unsupp_ = sum.bound;
+    }
+    // store B+ followed by B- followed by optional weights
+    Literal*  base    = a->lits;
+    Literal*  p[2]    = {base, base + pos};
+    Weight_t* weights = hasWeights ? a->sum->weights : nullptr;
+    for (const auto& wl : sum.lits) {
+        POTASSCO_CHECK_PRE(wl.lit != 0 && wl.weight > 0, "body not simplified");
+        Literal*& n = p[wl.lit < 0];
+        *n          = toLit(Potassco::lit(wl));
+        if (weights) {
+            weights[n - base]  = wl.weight;
+            a->sum->sumW      += wl.weight;
+            if (n->sign()) {
+                unsupp_ -= wl.weight;
+            }
+        }
+        if (addDeps) {
+            prg.getAtom(n->var())->addDep(id, not n->sign());
+        }
+        ++n;
+    }
+}
+PrgBody* PrgBody::create(const LogicProgram& prg, uint32_t id, const Rule& r, uint32_t pos, bool addDeps) {
+    static_assert(sizeof(PrgBody) == 24 && sizeof(Agg) == sizeof(void*), "unexpected alignment");
+    PrgBody* ret;
+    if (r.normal()) {
+        size_t bytes = sizeof(PrgBody) + (r.cond.size() * sizeof(Literal));
+        ret          = new (::operator new(bytes)) PrgBody(id, prg, r.cond, pos, addDeps);
+    }
+    else {
+        const Potassco::Sum& sum   = r.agg;
+        size_t               bytes = sizeof(PrgBody) + (r.agg.lits.size() * sizeof(Literal)) + sizeof(Agg);
+        ret = new (::operator new(bytes)) PrgBody(id, prg, sum, r.bt == BodyType::sum, pos, addDeps);
+        POTASSCO_CHECK_PRE(ret->bound() > 0 && ret->sumW() > ret->bound(), "body not simplified");
+    }
+    if (ret->bound() == 0) {
+        ret->assignValue(value_true);
+        ret->markDirty();
+    }
+    return ret;
 }
 
 PrgBody::~PrgBody() {
-	clearHeads();
-	if (hasWeights()) {
-		sumData()->destroy();
-	}
+    clearHeads();
+    if (hasWeights()) {
+        sumData()->destroy();
+    }
 }
 
 void PrgBody::destroy() {
-	this->~PrgBody();
-	::operator delete(this);
+    this->~PrgBody();
+    ::operator delete(this);
 }
 
-PrgBody::SumData* PrgBody::SumData::create(uint32 size, weight_t b, weight_t s) {
-	uint32 bytes = sizeof(SumData) + (size * sizeof(weight_t));
-	SumData* ret = new (::operator new(bytes)) SumData();
-	ret->bound = b;
-	ret->sumW = s;
-	return ret;
-}
-void PrgBody::SumData::destroy() {
-	::operator delete(this);
-}
+PrgBody::SumData* PrgBody::SumData::create(uint32_t size, Weight_t b, Weight_t s) {
+    uint32_t bytes = sizeof(SumData) + (size * sizeof(Weight_t));
+    auto*    ret   = new (::operator new(bytes)) SumData();
+    ret->bound     = b;
+    ret->sumW      = s;
+    return ret;
+}
+void PrgBody::SumData::destroy() { ::operator delete(this); }
 
-uint32 PrgBody::findLit(const LogicProgram& prg, Literal p) const {
-	for (const Literal* it = goals_begin(), *end = it + size(); it != end; ++it) {
-		Literal x = prg.getAtom(it->var())->literal();
-		if (it->sign()) x = ~x;
-		if (x == p) return static_cast(it - goals_begin());
-	}
-	return varMax;
+uint32_t PrgBody::findLit(const LogicProgram& prg, Literal p) const {
+    auto r = goals();
+    if (auto it = std::ranges::find(r, p, [&](Literal goal) { return solverLiteral(prg, goal); }); it != r.end()) {
+        return static_cast(std::distance(r.begin(), it));
+    }
+    return var_max;
 }
 
 // Sets the unsupported counter back to
 // bound() - negWeight()
 bool PrgBody::resetSupported() {
-	unsupp_ = bound();
-	for (uint32 x = size(); x && goal(--x).sign(); ) {
-		unsupp_ -= weight(x);
-	}
-	return isSupported();
+    unsupp_ = bound();
+    for (uint32_t x = size(); x && goal(--x).sign();) { unsupp_ -= weight(x); }
+    return isSupported();
 }
 
 // Removes all heads from this body *without* notifying them
 void PrgBody::clearHeads() {
-	if (!isSmallHead()) { delete largeHead(); }
-	head_ = 0;
+    if (not isSmallHead()) {
+        delete largeHead();
+    }
+    head_ = 0;
 }
 
 // Makes h a head-successor of this body and adds this
 // body as a support for h.
 void PrgBody::addHead(PrgHead* h, EdgeType t) {
-	assert(relevant() && h->relevant());
-	PrgEdge fwdEdge = PrgEdge::newEdge(*h, t);
-	PrgEdge bwdEdge = PrgEdge::newEdge(*this, t);
-	uint32  numHeads= static_cast(heads_end() - heads_begin());
-	uint32  numSupps= h->supports();
-	bool    dup     = false;
-	if (numHeads && numSupps && std::min(numHeads, numSupps) < 10) {
-		if (numSupps < numHeads) { dup = std::find(h->supps_begin(), h->supps_end(), bwdEdge) != h->supps_end(); }
-		else                     { dup = std::find(heads_begin(), heads_end(), fwdEdge) != heads_end(); }
-	}
-	if (dup) { return; }
-	addHead(fwdEdge);
-	h->addSupport(bwdEdge);
-	// mark head-set as dirty
-	if (head_ > 1) {  sHead_ = 1; }
+    assert(relevant() && h->relevant());
+    PrgEdge fwdEdge  = PrgEdge::newEdge(*h, t);
+    PrgEdge bwdEdge  = PrgEdge::newEdge(*this, t);
+    auto    numHeads = size32(heads());
+    auto    numSupps = h->numSupports();
+    bool    dup      = false;
+    if (numHeads && numSupps && std::min(numHeads, numSupps) < 10) {
+        if (numSupps < numHeads) {
+            dup = contains(h->supports(), bwdEdge);
+        }
+        else {
+            dup = contains(heads(), fwdEdge);
+        }
+    }
+    if (dup) {
+        return;
+    }
+    addHead(fwdEdge);
+    h->addSupport(bwdEdge);
+    // mark head-set as dirty
+    if (head_ > 1) {
+        sHead_ = 1;
+    }
 }
 
 void PrgBody::addHead(PrgEdge h) {
-	if      (head_ < 2u)    { smallHead()[head_++] = h; }
-	else if (!isSmallHead()){ largeHead()->push_back(h);    }
-	else                    {
-		EdgeVec* t  = new EdgeVec(heads_begin(), heads_end());
-		headData_.ext = t;
-		head_ = 3u;
-		t->push_back(h);
-	}
+    if (head_ < 2u) {
+        smallHead()[head_++] = h;
+    }
+    else if (not isSmallHead()) {
+        largeHead()->push_back(h);
+    }
+    else {
+        auto  hs      = heads();
+        auto* t       = new EdgeVec(hs.begin(), hs.end());
+        headData_.ext = t;
+        head_         = 3u;
+        t->push_back(h);
+    }
 }
 
 void PrgBody::removeHead(PrgHead* h, EdgeType t) {
-	PrgEdge x = PrgEdge::newEdge(*h, t);
-	if (eraseHead(x)) {
-		h->removeSupport(PrgEdge::newEdge(*this, t)); // also remove back edge
-	}
+    PrgEdge x = PrgEdge::newEdge(*h, t);
+    if (eraseHead(x)) {
+        h->removeSupport(PrgEdge::newEdge(*this, t)); // also remove back edge
+    }
 }
 
-bool PrgBody::hasHead(PrgHead* h, EdgeType t) const {
-	if (!hasHeads()) { return false;  }
-	PrgEdge x = PrgEdge::newEdge(*h, t);
-	head_iterator it = sHead_ != 0 || isSmallHead() ? std::find(heads_begin(), heads_end(), x) : std::lower_bound(heads_begin(), heads_end(), x);
-	return it != heads_end() && *it == x;
+bool PrgBody::hasHead(const PrgHead* h, EdgeType t) const {
+    if (not hasHeads()) {
+        return false;
+    }
+    auto x  = PrgEdge::newEdge(*h, t);
+    auto hs = heads();
+    auto it = sHead_ != 0 || isSmallHead() ? std::ranges::find(hs, x) : std::ranges::lower_bound(hs, x);
+    return it != hs.end() && *it == x;
 }
 
 bool PrgBody::eraseHead(PrgEdge h) {
-	PrgEdge* it = const_cast(std::find(heads_begin(), heads_end(), h));
-	if      (it == heads_end()) { return false; }
-	else if (isSmallHead())     { *it = smallHead()[1]; --head_; }
-	else                        { largeHead()->erase(it); }
-	return true;
+    auto hs = heads();
+    if (auto it = std::ranges::find(hs, h); it != hs.end()) {
+        auto pos = std::distance(hs.begin(), it);
+        if (not isSmallHead()) {
+            largeHead()->erase(largeHead()->begin() + pos);
+        }
+        else {
+            smallHead()[pos] = smallHead()[1];
+            --head_;
+        }
+        return true;
+    }
+    return false;
 }
 
 bool PrgBody::toData(const LogicProgram& prg, Potassco::RuleBuilder& out) const {
-	Body_t bt = type();
-	weight_t sum = 0, bound = this->bound();
-	bt == Body_t::Normal ?  out.startBody() : out.startSum(bound);
-	for (uint32 i = 0, end = size(); i != end; ++i) {
-		Potassco::WeightLit_t wl = {toInt(goal(i)), weight(i)};
-		if (!prg.frozen() || prg.inProgram(Potassco::atom(wl))) {
-			out.addGoal(wl);
-			sum += wl.weight;
-		}
-		else if (wl.lit < 0)           { bound -= weight(i); }
-		else if (bt == Body_t::Normal) { return false; }
-	}
-	if (bt != Body_t::Normal) {
-		out.setBound(bound);
-		if (bound <= 0 || bound >= sum) {
-			if      (bound > sum) { return false; }
-			else if (bound <= 0)  { out.clearBody(); }
-			else                  { out.weaken(Body_t::Normal); }
-		}
-	}
-	return true;
+    BodyType bt  = type();
+    Weight_t sum = 0, bound = this->bound();
+    bt == BodyType::normal ? out.startBody() : out.startSum(bound);
+    for (auto i : irange(size())) {
+        Potassco::WeightLit wl = {toInt(goal(i)), weight(i)};
+        if (not prg.frozen() || prg.inProgram(Potassco::atom(wl))) {
+            out.addGoal(wl);
+            sum += wl.weight;
+        }
+        else if (wl.lit < 0) {
+            bound -= weight(i);
+        }
+        else if (bt == BodyType::normal) {
+            return false;
+        }
+    }
+    if (bt != BodyType::normal) {
+        out.setBound(bound);
+        if (bound <= 0 || bound >= sum) {
+            if (bound > sum) {
+                return false;
+            }
+            if (bound <= 0) {
+                out.clearBody();
+            }
+            else {
+                out.weaken(BodyType::normal);
+            }
+        }
+    }
+    return true;
 }
 
 // Simplifies the body by removing assigned atoms & replacing eq atoms.
@@ -856,258 +947,300 @@ bool PrgBody::toData(const LogicProgram& prg, Potassco::RuleBuilder& out) const
 // prg    The program containing this body
 // strong If true, treats atoms that have no variable associated as false.
 // eqId   The id of a body in prg that is equivalent to this body
-bool PrgBody::simplifyBody(LogicProgram& prg, bool strong, uint32* eqId) {
-	if (eqId)        { *eqId  = id(); }
-	if (sBody_ == 0) { return true;   }
-	// update body - compute old hash value
-	SharedContext& ctx = *prg.ctx();
-	uint32 oldHash     = 0;
-	weight_t bound     = this->bound();
-	weight_t w         = 1, *jw = hasWeights() ? sumData()->weights : 0;
-	Literal* lits      = goals_begin();
-	Literal* j         = lits;
-	AtomState& todo    = prg.atomState();
-	Var a;
-	bool mark, isEq;
-	int todos = 0;
-	for (Literal* it = j, *end = j + size(); it != end; ++it) {
-		a       = it->var();
-		isEq    = a != prg.getRootId(a);
-		oldHash+= hashLit(*it);
-		if (isEq) {
-			prg.getAtom(a)->removeDep(id(), !it->sign()); // remove old edge
-			*it = prg.getAtom(a)->eqGoal(it->sign());     // replace with eq goal
-			a   = it->var();                              // and check it
-		}
-		Literal aLit = it->sign() ? ~prg.getAtom(a)->literal() : prg.getAtom(a)->literal();
-		ValueRep v   = prg.getAtom(a)->value();
-		mark         = strong || prg.getAtom(a)->hasVar();
-		if (strong && !prg.getAtom(a)->hasVar()) {
-			v = value_false;
-		}
-		if (v == value_weak_true && it->sign()) {
-			v = value_true;
-		}
-		if (v == value_true || v == value_false) { // truth value is known - remove subgoal
-			if (v == trueValue(*it)) {
-				// subgoal is true: decrease necessary lower bound
-				bound -= weight(uint32(it - lits));
-			}
-			prg.getAtom(a)->removeDep(id(), !it->sign());
-		}
-		else if (!mark || !ctx.marked(aLit)) {
-			if (mark) { ctx.mark(aLit); }
-			if (isEq) { // remember to add edge for new goal later
-				todo.addToBody(Literal(*it));
-				++todos;
-			}
-			*j++ = *it;  // copy literal and optionally weight
-			if (jw) { *jw++ = weight(uint32(it - lits)); }
-		}
-		else { // body contains aLit more than once
-			if (type() != Body_t::Normal) { // merge subgoal
-				if (!jw) {
-					SumData* sum = SumData::create(size(), this->bound(), this->sumW());
-					std::fill_n(sum->weights, size(), w = 1);
-					aggData().sum = sum;
-					type_ = Body_t::Sum;
-					jw    = sum->weights + (it - lits);
-				}
-				else { w = weight(uint32(it - lits)); }
-				uint32 pos = findLit(prg, aLit);
-				sumData()->weights[pos] += w;
-			}
-			else { // ignore if normal
-				--bound;
-				if (!isEq) { // remove edge
-					if (todo.inBody(*it)) { todo.clearBody(*it); --todos; }
-					else                  { prg.getAtom(it->var())->removeDep(id(), !it->sign()); }
-				}
-			}
-		}
-	}
-	// unmark atoms, compute new hash value,
-	// and restore pos | neg partition in case
-	// we changed some positive goals to negative ones
-	size_          = j - lits;
-	if (jw) jw     = sumData()->weights;
-	uint32 newHash = 0;
-	weight_t sumW  = 0, reachW = 0;
-	for (uint32 p = 0, n = size_, i, h; p < n;) {
-		if      (!lits[p].sign())      { h = hashLit(lits[i = p++]);  }
-		else if (lits[n-1].sign())     { h = hashLit(lits[i = --n]);  }
-		else /* restore pos|neg order */ {
-			std::swap(lits[p], lits[n-1]);
-			if (jw) { std::swap(jw[p], jw[n-1]); }
-			continue;
-		}
-		a = lits[i].var();
-		if (todos && todo.inBody(lits[i])) {
-			prg.getAtom(a)->addDep(id(), !lits[i].sign());
-			todo.clearBody(lits[i]);
-			--todos;
-		}
-		Var v   = prg.getAtom(a)->var();
-		w       = !jw ? 1 : jw[i];
-		sumW   += w;
-		reachW += w;
-		if (ctx.marked(posLit(v)) && ctx.marked(negLit(v))) {
-			// body contains aLit and ~aLit
-			if  (!hasWeights()) { reachW -= 1; }
-			else {
-				Literal other = prg.getAtom(a)->literal() ^ !goal(i).sign();
-				uint32 pos    = findLit(prg, other);
-				assert(pos != varMax && pos != i);
-				reachW       -= std::min(w, jw[pos]);
-			}
-		}
-		ctx.unmark( v );
-		newHash += h;
-	}
-	bool ok = normalize(prg, bound, sumW, reachW, newHash);
-	if (ok) {
-		Var xId = id();
-		if (oldHash != newHash) {
-			xId = prg.update(this, oldHash, newHash);
-		}
-		if (eqId) { *eqId = xId != varMax ? xId : id(); }
-	}
-	if (strong) sBody_ = 0;
-	return ok && (value() == value_free || propagateValue(prg));
+bool PrgBody::simplifyBody(LogicProgram& prg, bool strong, uint32_t* eqId) {
+    if (eqId) {
+        *eqId = id();
+    }
+    if (sBody_ == 0) {
+        return true;
+    }
+    // update body - compute old hash value
+    SharedContext& ctx     = *prg.ctx();
+    uint32_t       oldHash = 0;
+    Weight_t       bound   = this->bound();
+    Weight_t*      jw      = hasWeights() ? sumData()->weights : nullptr;
+    Literal*       lits    = this->lits();
+    Literal*       j       = lits;
+    AtomState&     todo    = prg.atomState();
+    Var_t          a;
+    int            todos = 0;
+    for (Literal *it = j, *end = j + size(); it != end; ++it) {
+        a          = it->var();
+        bool isEq  = a != prg.getRootId(a);
+        oldHash   += hashLit(*it);
+        if (isEq) {
+            prg.getAtom(a)->removeDep(id(), not it->sign()); // remove old edge
+            *it = prg.getAtom(a)->eqGoal(it->sign());        // replace with eq goal
+            a   = it->var();                                 // and check it
+        }
+        Literal aLit = solverLiteral(prg, *it);
+        auto    v    = prg.getAtom(a)->value();
+        bool    mark = strong || prg.getAtom(a)->hasVar();
+        if (strong && not prg.getAtom(a)->hasVar()) {
+            v = value_false;
+        }
+        if (v == value_weak_true && it->sign()) {
+            v = value_true;
+        }
+        if (v == value_true || v == value_false) { // truth value is known - remove subgoal
+            if (v == trueValue(*it)) {
+                // subgoal is true: decrease necessary lower bound
+                bound -= weight(static_cast(it - lits));
+            }
+            prg.getAtom(a)->removeDep(id(), not it->sign());
+        }
+        else if (not mark || not ctx.marked(aLit)) {
+            if (mark) {
+                ctx.mark(aLit);
+            }
+            if (isEq) { // remember to add edge for new goal later
+                todo.addToBody(*it);
+                ++todos;
+            }
+            *j++ = *it; // copy literal and optionally weight
+            if (jw) {
+                *jw++ = weight(static_cast(it - lits));
+            }
+        }
+        else {                                // body contains aLit more than once
+            if (type() != BodyType::normal) { // merge subgoal
+                Weight_t w = 1;
+                if (not jw) {
+                    SumData* sum = SumData::create(size(), this->bound(), this->sumW());
+                    std::fill_n(sum->weights, size(), w);
+                    aggData().sum = sum;
+                    type_         = to_underlying(BodyType::sum);
+                    jw            = sum->weights + (it - lits);
+                }
+                else {
+                    w = weight(static_cast(it - lits));
+                }
+                uint32_t pos             = findLit(prg, aLit);
+                sumData()->weights[pos] += w;
+            }
+            else { // ignore if normal
+                --bound;
+                if (not isEq) { // remove edge
+                    if (todo.inBody(*it)) {
+                        todo.clearBody(*it);
+                        --todos;
+                    }
+                    else {
+                        prg.getAtom(it->var())->removeDep(id(), not it->sign());
+                    }
+                }
+            }
+        }
+    }
+    // unmark atoms, compute new hash value,
+    // and restore pos | neg partition in case
+    // we changed some positive goals to negative ones
+    size_ = static_cast(j - lits);
+    if (jw) {
+        jw = sumData()->weights;
+    }
+    uint32_t newHash = 0;
+    Weight_t sumW = 0, reachW = 0;
+    for (uint32_t p = 0, n = size_, i, h; p < n;) {
+        if (not lits[p].sign()) {
+            h = hashLit(lits[i = p++]);
+        }
+        else if (lits[n - 1].sign()) {
+            h = hashLit(lits[i = --n]);
+        }
+        else /* restore pos|neg order */ {
+            std::swap(lits[p], lits[n - 1]);
+            if (jw) {
+                std::swap(jw[p], jw[n - 1]);
+            }
+            continue;
+        }
+        a = lits[i].var();
+        if (todos && todo.inBody(lits[i])) {
+            prg.getAtom(a)->addDep(id(), not lits[i].sign());
+            todo.clearBody(lits[i]);
+            --todos;
+        }
+        auto v  = prg.getAtom(a)->var();
+        auto w  = not jw ? 1 : jw[i];
+        sumW   += w;
+        reachW += w;
+        if (ctx.marked(posLit(v)) && ctx.marked(negLit(v))) {
+            // body contains aLit and ~aLit
+            if (not hasWeights()) {
+                reachW -= 1;
+            }
+            else {
+                Literal  other = solverLiteral(prg, ~lits[i]);
+                uint32_t pos   = findLit(prg, other);
+                assert(pos != var_max && pos != i && jw);
+                reachW -= std::min(w, jw[pos]);
+            }
+        }
+        ctx.unmark(v);
+        newHash += h;
+    }
+    bool ok = normalize(prg, bound, sumW, reachW, newHash);
+    if (ok) {
+        auto xId = id();
+        if (oldHash != newHash) {
+            xId = prg.update(this, oldHash, newHash);
+        }
+        if (eqId) {
+            *eqId = xId != var_max ? xId : id();
+        }
+    }
+    if (strong) {
+        sBody_ = 0;
+    }
+    return ok && (value() == value_free || propagateValue(prg));
 }
 
-bool PrgBody::normalize(const LogicProgram& prg, weight_t bound, weight_t sumW, weight_t reachW, uint32& hashOut) {
-	Body_t nt = (sumW == bound || size() == 1) ? Body_t::Normal : type();
-	bool ok = true;
-	if (sumW >= bound && type() != Body_t::Normal) {
-		if (hasWeights()) {
-			sumData()->bound = bound;
-			sumData()->sumW  = sumW;
-		}
-		else {
-			aggData().bound = bound;
-		}
-	}
-	if (bound <= 0) {
-		for (uint32 i = 0, myId = id(); i != size_; ++i) {
-			prg.getAtom(goal(i).var())->removeDep(myId, !goal(i).sign());
-		}
-		size_ = 0; hashOut = 0, unsupp_ = 0;
-		nt    = Body_t::Normal;
-		ok    = assignValue(value_true);
-	}
-	else if (reachW < bound) {
-		ok     = assignValue(value_false);
-		sHead_ = 1;
-		markRemoved();
-	}
-	if (nt != type()) {
-		assert(nt == Body_t::Normal);
-		Literal* from = aggData().lits;
-		if (hasWeights()) {
-			sumData()->destroy();
-		}
-		Literal* to = (new (data_)Norm())->lits;
-		std::copy(from, from+size(), to);
-		type_ = nt;
-	}
-	return ok;
+bool PrgBody::normalize(const LogicProgram& prg, Weight_t bound, Weight_t sumW, Weight_t reachW, uint32_t& hashOut) {
+    BodyType nt = (sumW == bound || size() == 1) ? BodyType::normal : type();
+    bool     ok = true;
+    if (sumW >= bound && type() != BodyType::normal) {
+        if (hasWeights()) {
+            sumData()->bound = bound;
+            sumData()->sumW  = sumW;
+        }
+        else {
+            aggData().bound = bound;
+        }
+    }
+    if (bound <= 0) {
+        for (auto myId = id(); auto g : goals()) { prg.getAtom(g.var())->removeDep(myId, not g.sign()); }
+        size_   = 0;
+        hashOut = 0, unsupp_ = 0;
+        nt = BodyType::normal;
+        ok = assignValue(value_true);
+    }
+    else if (reachW < bound) {
+        ok     = assignValue(value_false);
+        sHead_ = 1;
+        markRemoved();
+    }
+    if (nt != type()) {
+        assert(nt == BodyType::normal);
+        Literal* from = aggData().lits;
+        if (hasWeights()) {
+            sumData()->destroy();
+        }
+        Literal* to = (new (data_) Norm())->lits;
+        std::copy_n(from, size(), to);
+        type_ = to_underlying(nt);
+    }
+    return ok;
 }
 
 // Marks the set of heads in rs and removes
 // any duplicate heads.
-void PrgBody::prepareSimplifyHeads(LogicProgram& prg, AtomState& rs) {
-	head_iterator end = heads_end();
-	uint32 size       = 0;
-	for (PrgEdge*  j  = const_cast(heads_begin()); j != end;) {
-		if (!rs.inHead(*j)) {
-			rs.addToHead(*j);
-			++j; ++size;
-		}
-		else {
-			prg.getHead(*j)->markDirty();
-			*j = *--end;
-		}
-	}
-	if (isSmallHead()) { head_ = size; }
-	else               { shrinkVecTo(*largeHead(), size); }
+void PrgBody::prepareSimplifyHeads(const LogicProgram& prg, AtomState& rs) {
+    auto hs   = writable(heads());
+    auto drop = 0u;
+    for (auto j = hs.begin(), end = hs.end(); j != end;) {
+        if (not rs.inHead(*j)) {
+            rs.addToHead(*j);
+            ++j;
+        }
+        else {
+            prg.getHead(*j)->markDirty();
+            *j = *--end;
+            ++drop;
+        }
+    }
+    if (drop) {
+        if (auto nHeads = size32(hs) - drop; isSmallHead()) {
+            head_ = nHeads;
+        }
+        else {
+            shrinkVecTo(*largeHead(), nHeads);
+        }
+    }
 }
 
 // Simplifies the heads of this body wrt target.
 // Removes superfluous/eq/unsupported heads and checks for self-blocking
 // situations.
 // PRE: prepareSimplifyHeads was called
-bool PrgBody::simplifyHeadsImpl(LogicProgram& prg, PrgBody& target, AtomState& rs, bool strong) {
-	PrgHead* cur;
-	PrgEdge* j     = const_cast(heads_begin());
-	uint32 newSize = 0;
-	bool merge     = this != ⌖
-	bool block     = value() == value_false || (merge && target.value() == value_false);
-	for (head_iterator it = heads_begin(), end = heads_end(); it != end; ++it) {
-		cur  = prg.getHead(*it);
-		block= block || target.blockedHead(*it, rs);
-		if (!cur->relevant() || (strong && !cur->hasVar())
-			|| block || target.superfluousHead(prg, cur, *it, rs) || cur->value() == value_false) {
-			// remove superfluous and unsupported heads
-			cur->removeSupport(PrgEdge::newEdge(*this, it->type()));
-			rs.clearHead(*it);
-			block = block || (cur->value() == value_false && it->type() == PrgEdge::Normal);
-		}
-		else {
-			*j++ = *it;
-			++newSize;
-			if (merge) { target.addHead(cur, it->type()); }
-		}
-	}
-	if (isSmallHead()) { head_ = newSize; }
-	else               { shrinkVecTo(*largeHead(), newSize); }
-	return !block;
+bool PrgBody::simplifyHeadsImpl(const LogicProgram& prg, PrgBody& target, AtomState& rs, bool strong) {
+    uint32_t newSize = 0;
+    bool     merge   = this != ⌖
+    bool     block   = value() == value_false || (merge && target.value() == value_false);
+    auto     hs      = writable(heads());
+    auto*    j       = hs.data();
+    for (auto h : hs) {
+        PrgHead* cur = prg.getHead(h);
+        block        = block || target.blockedHead(h, rs);
+        if (not cur->relevant() || (strong && not cur->hasVar()) || block || target.superfluousHead(prg, cur, h, rs) ||
+            cur->value() == value_false) {
+            // remove superfluous and unsupported heads
+            cur->removeSupport(PrgEdge::newEdge(*this, h.type()));
+            rs.clearHead(h);
+            block = block || (cur->value() == value_false && h.type() == PrgEdge::normal);
+        }
+        else {
+            *j++ = h;
+            ++newSize;
+            if (merge) {
+                target.addHead(cur, h.type());
+            }
+        }
+    }
+    if (isSmallHead()) {
+        head_ = newSize;
+    }
+    else {
+        shrinkVecTo(*largeHead(), newSize);
+    }
+    return not block;
 }
 
 bool PrgBody::simplifyHeads(LogicProgram& prg, bool strong) {
-	if (sHead_ == 0) { return true; }
-	return PrgBody::mergeHeads(prg, *this, strong);
+    if (sHead_ == 0) {
+        return true;
+    }
+    return PrgBody::mergeHeads(prg, *this, strong);
 }
 
 bool PrgBody::mergeHeads(LogicProgram& prg, PrgBody& heads, bool strong, bool simplify) {
-	AtomState& rs = prg.atomState();
-	bool       ok = true;
-	assert((this == &heads || heads.sHead_ == 0) && "Heads to merge not simplified!");
-	if (simplify || &heads == this) {
-		// mark the body literals so that we can easily detect superfluous atoms
-		// and selfblocking situations.
-		for (const Literal* it = goals_begin(), *end = it + size(); it != end; ++it) {
-			rs.addToBody(*it);
-		}
-		// remove duplicate/superfluous heads & check for blocked atoms
-		prepareSimplifyHeads(prg, rs);
-		if (this == &heads) {
-			ok = simplifyHeadsImpl(prg, *this, rs, strong);
-		}
-		else {
-			heads.prepareSimplifyHeads(prg, rs);
-			if (!simplifyHeadsImpl(prg, *this, rs, strong) && !assignValue(value_false)) {
-				clearRule(rs);
-				return false;
-			}
-			ok = heads.simplifyHeadsImpl(prg, *this, rs, strong);
-			if (!ok && (!heads.assignValue(value_false) || !heads.propagateValue(prg, false))) {
-				clearRule(rs);
-				return false;
-			}
-		}
-		// clear temporary flags & reestablish ordering
-		std::sort(const_cast(heads_begin()), const_cast(heads_end()));
-		clearRule(rs);
-		sHead_ = 0;
-	}
-	else if (relevant()) {
-		for (head_iterator it = heads.heads_begin(), end = heads.heads_end(); it != end; ++it) {
-			PrgHead* h = prg.getHead(*it);
-			if (h->relevant()) { addHead(h, it->type()); }
-		}
-	}
-	return ok || (assignValue(value_false) && propagateValue(prg));
+    AtomState& rs = prg.atomState();
+    bool       ok = true;
+    assert((this == &heads || heads.sHead_ == 0) && "Heads to merge not simplified!");
+    if (simplify || &heads == this) {
+        // mark the body literals so that we can easily detect superfluous atoms
+        // and selfblocking situations.
+        for (auto g : goals()) { rs.addToBody(g); }
+        // remove duplicate/superfluous heads & check for blocked atoms
+        prepareSimplifyHeads(prg, rs);
+        if (this == &heads) {
+            ok = simplifyHeadsImpl(prg, *this, rs, strong);
+        }
+        else {
+            heads.prepareSimplifyHeads(prg, rs);
+            if (not simplifyHeadsImpl(prg, *this, rs, strong) && not assignValue(value_false)) {
+                clearRule(rs);
+                return false;
+            }
+            ok = heads.simplifyHeadsImpl(prg, *this, rs, strong);
+            if (not ok && (not heads.assignValue(value_false) || not heads.propagateValue(prg, false))) {
+                clearRule(rs);
+                return false;
+            }
+        }
+        // clear temporary flags & reestablish ordering
+        std::ranges::sort(writable(this->heads()));
+        clearRule(rs);
+        sHead_ = 0;
+    }
+    else if (relevant()) {
+        for (auto e : heads.heads()) {
+            if (PrgHead* h = prg.getHead(e); h->relevant()) {
+                addHead(h, e.type());
+            }
+        }
+    }
+    return ok || (assignValue(value_false) && propagateValue(prg));
 }
 
 // Checks whether the head is superfluous w.r.t this body, i.e.
@@ -1115,232 +1248,251 @@ bool PrgBody::mergeHeads(LogicProgram& prg, PrgBody& heads, bool strong, bool si
 //  - it appears in the body and is a choice
 //  - it is a disjunction and one of the atoms is needed to satisfy the body
 bool PrgBody::superfluousHead(const LogicProgram& prg, const PrgHead* head, PrgEdge it, const AtomState& rs) const {
-	if (it.isAtom()) {
-		// the head is an atom
-		uint32 atomId = it.node();
-		weight_t    w = 1;
-		if (rs.inBody(posLit(atomId))) {
-			if (hasWeights()) {
-				const Literal* lits = aggData().lits;
-				const Literal* x    = std::find(lits, lits + size(), posLit(atomId));
-				assert(x != lits + size());
-				w = sumData()->weights[ x - lits ];
-			}
-			if (it.isChoice() || (sumW() - w) < bound()) {
-				return true;
-			}
-		}
-		return it.isChoice() && (rs.inBody(negLit(atomId)) || rs.inHead(atomId));
-	}
-	else { assert(it.isDisj());
-		// check each contained atom
-		const PrgDisj* dis = static_cast(head);
-		for (PrgDisj::atom_iterator aIt = dis->begin(), aEnd = dis->end(); aIt != aEnd; ++aIt) {
-			if (rs.inBody(posLit(*aIt)) || rs.inHead(*aIt)) {
-				return true;
-			}
-			if (prg.isFact(prg.getAtom(*aIt))) {
-				return true;
-			}
-		}
-		// check for subsumption
-		if (prg.options().iters == LogicProgram::AspOptions::MAX_EQ_ITERS) {
-			for (head_iterator hIt = heads_begin(), hEnd = heads_end(); hIt != hEnd; ++hIt) {
-				if (hIt->isDisj() && prg.getDisj(hIt->node())->size() < dis->size()) {
-					const PrgDisj* other = prg.getDisj(hIt->node());
-					for (PrgDisj::atom_iterator a = other->begin(), aEnd = other->end(); a != aEnd && other; ++a) {
-						if (std::find(dis->begin(), dis->end(), *a) == dis->end()) {
-							other = 0;
-						}
-					}
-					if (other && other->size() > 0) {
-						return true;
-					}
-				}
-			}
-		}
-	}
-	return false;
+    if (it.isAtom()) {
+        // the head is an atom
+        uint32_t atomId = it.node();
+        if (rs.inBody(posLit(atomId))) {
+            Weight_t w = 1;
+            if (hasWeights()) {
+                const Literal* lits = aggData().lits;
+                const Literal* x    = std::find(lits, lits + size(), posLit(atomId));
+                assert(x != lits + size());
+                w = sumData()->weights[x - lits];
+            }
+            if (it.isChoice() || (sumW() - w) < bound()) {
+                return true;
+            }
+        }
+        return it.isChoice() && (rs.inBody(negLit(atomId)) || rs.inHead(atomId));
+    }
+    else {
+        assert(it.isDisj());
+        // check each contained atom
+        const auto* dis = node_cast(head);
+        for (auto a : dis->atoms()) {
+            if (rs.inBody(posLit(a)) || rs.inHead(a)) {
+                return true;
+            }
+            if (prg.isFact(prg.getAtom(a))) {
+                return true;
+            }
+        }
+        // check for subsumption
+        if (prg.options().iters == LogicProgram::AspOptions::max_eq_iters) {
+            for (auto e : heads()) {
+                if (e.isDisj() && prg.getDisj(e.node())->size() < dis->size()) {
+                    const PrgDisj* other = prg.getDisj(e.node());
+                    for (auto a : other->atoms()) {
+                        if (not contains(dis->atoms(), a)) {
+                            other = nullptr;
+                            break;
+                        }
+                    }
+                    if (other && other->size() > 0) {
+                        return true;
+                    }
+                }
+            }
+        }
+    }
+    return false;
 }
 
 // Checks whether the rule it.node() :- *this is selfblocking, i.e.
 // from TB follows conflict
 bool PrgBody::blockedHead(PrgEdge it, const AtomState& rs) const {
-	if (it.isAtom() && it.isNormal() && rs.inBody(negLit(it.node()))) {
-		weight_t w = 1;
-		if (hasWeights()) {
-			const Literal* lits = aggData().lits;
-			const Literal* x    = std::find(lits, lits + size(), negLit(it.node()));
-			assert(x != lits + size());
-			w = sumData()->weights[ x - lits ];
-		}
-		return (sumW() - w) < bound();
-	}
-	return false;
+    if (it.isAtom() && it.isNormal() && rs.inBody(negLit(it.node()))) {
+        Weight_t w = 1;
+        if (hasWeights()) {
+            const Literal* lits = aggData().lits;
+            const Literal* x    = std::find(lits, lits + size(), negLit(it.node()));
+            assert(x != lits + size());
+            w = sumData()->weights[x - lits];
+        }
+        return (sumW() - w) < bound();
+    }
+    return false;
 }
 
 void PrgBody::assignVar(LogicProgram& prg) {
-	if (hasVar() || !relevant()) { return; }
-	uint32 size = this->size();
-	if (size == 0 || value() == value_true) {
-		setLiteral(lit_true());
-	}
-	else if (size == 1 && prg.getAtom(goal(0).var())->hasVar()) {
-		Literal x = prg.getAtom(goal(0).var())->literal();
-		setLiteral(goal(0).sign() ? ~x : x);
-		prg.ctx()->setVarEq(var(), true);
-		prg.incEqs(Var_t::Hybrid);
-	}
-	else if (value() != value_false) {
-		setLiteral(posLit(prg.ctx()->addVar(Var_t::Body, 0)));
-	}
-	else {
-		setLiteral(lit_false());
-	}
+    if (hasVar() || not relevant()) {
+        return;
+    }
+    if (uint32_t size = this->size(); size == 0 || value() == value_true) {
+        setLiteral(lit_true);
+    }
+    else if (size == 1 && prg.getAtom(goal(0).var())->hasVar()) {
+        Literal x = solverLiteral(prg, goal(0));
+        setLiteral(x);
+        prg.ctx()->setVarEq(var(), true);
+        prg.incEqs(VarType::hybrid);
+    }
+    else if (value() != value_false) {
+        setLiteral(posLit(prg.ctx()->addVar(VarType::body, 0)));
+    }
+    else {
+        setLiteral(lit_false);
+    }
 }
 
-bool PrgBody::propagateSupported(Var v) {
-	weight_t w = 1;
-	if (hasWeights()) {
-		uint32 pos = (uint32)std::distance(goals_begin(), std::find(goals_begin(), goals_end(), posLit(v)));
-		w          = weight(pos);
-	}
-	return (unsupp_ -= w) <= 0;
+bool PrgBody::propagateSupported(Var_t v) {
+    Weight_t w = 1;
+    if (hasWeights()) {
+        auto gs  = goals();
+        auto pos = std::distance(gs.begin(), std::ranges::find(gs, posLit(v)));
+        w        = weight(static_cast(pos));
+    }
+    return (unsupp_ -= w) <= 0;
 }
 
-bool PrgBody::propagateAssigned(LogicProgram& prg, Literal p, ValueRep v) {
-	if (!relevant()) return true;
-	assert(std::find(goals_begin(), goals_end(), p) != goals_end());
-	markDirty();
-	ValueRep x = v == value_weak_true ? value_true : v;
-	weight_t w = 1; // TODO: find weight of p for weight rule
-	if (x == falseValue(p) && (sumW() - w) < bound() && value() != value_false) {
-		return assignValue(value_false) && propagateValue(prg);
-	}
-	else if (x == trueValue(p) && (bound() - w) <= 0 && value() != value_weak_true) {
-		return assignValue(value_weak_true) && propagateValue(prg);
-	}
-	return true;
+bool PrgBody::propagateAssigned(LogicProgram& prg, Literal p, Val_t v) {
+    if (not relevant()) {
+        return true;
+    }
+    assert(contains(goals(), p));
+    markDirty();
+    auto     x = v == value_weak_true ? value_true : v;
+    Weight_t w = 1; // TODO: find weight of p for weight rule
+    if (x == falseValue(p) && (sumW() - w) < bound() && value() != value_false) {
+        return assignValue(value_false) && propagateValue(prg);
+    }
+    else if (x == trueValue(p) && (bound() - w) <= 0 && value() != value_weak_true) {
+        return assignValue(value_weak_true) && propagateValue(prg);
+    }
+    return true;
 }
 
-bool PrgBody::propagateAssigned(LogicProgram& prg, PrgHead* h, EdgeType t) {
-	if (!relevant()) return true;
-	markHeadsDirty();
-	if (h->value() == value_false && hasHead(h, t) && t == PrgEdge::Normal) {
-		return value() == value_false || (assignValue(value_false) && propagateValue(prg));
-	}
-	return true;
+bool PrgBody::propagateAssigned(LogicProgram& prg, const PrgHead* h, EdgeType t) {
+    if (not relevant()) {
+        return true;
+    }
+    markHeadsDirty();
+    if (h->value() == value_false && hasHead(h, t) && t == PrgEdge::normal) {
+        return value() == value_false || (assignValue(value_false) && propagateValue(prg));
+    }
+    return true;
 }
 
 bool PrgBody::propagateValue(LogicProgram& prg, bool backprop) {
-	ValueRep val = value();
-	assert(value() != value_free);
-	// propagate value forward
-	for (head_iterator h = heads_begin(), end = heads_end(); h != end; ++h) {
-		PrgHead* head = prg.getHead(*h);
-		PrgEdge  supp = PrgEdge::newEdge(*this, h->type());
-		if (val == value_false) {
-			head->removeSupport(supp);
-		}
-		else if (!h->isChoice() && head->value() != val && !prg.assignValue(head, val, supp)) {
-			return false;
-		}
-	}
-	if (val == value_false) { clearHeads(); }
-	// propagate value backward
-	if (backprop && relevant()) {
-		const uint32 W = hasWeights();
-		weight_t MAX_W = 1;
-		weight_t* wPos = W == 0 ? &MAX_W : sumData()->weights;
-		MAX_W          = *std::max_element(wPos, wPos + (size() * W));
-		weight_t bound = value()==value_false ? this->bound() : (sumW() - this->bound())+1;
-		if (MAX_W >= bound) {
-			ValueRep goalVal;
-			for (const Literal* it = goals_begin(), *end = goals_end(); it != end; ++it) {
-				if ((bound - *wPos) <= 0) {
-					if (!it->sign()) { goalVal = val; }
-					else             { goalVal = val == value_false ? value_weak_true : value_false; }
-					if (!prg.assignValue(prg.getAtom(it->var()), goalVal, PrgEdge::noEdge())) {
-						return false;
-					}
-				}
-				wPos += W;
-			}
-		}
-	}
-	return true;
-}
-bool PrgBody::propagateValue(LogicProgram& prg) {
-	return propagateValue(prg, prg.options().backprop != 0);
-}
+    auto val = value();
+    assert(value() != value_free);
+    // propagate value forward
+    for (auto h : heads()) {
+        PrgHead* head = prg.getHead(h);
+        PrgEdge  supp = PrgEdge::newEdge(*this, h.type());
+        if (val == value_false) {
+            head->removeSupport(supp);
+        }
+        else if (not h.isChoice() && head->value() != val && not prg.assignValue(head, val, supp)) {
+            return false;
+        }
+    }
+    if (val == value_false) {
+        clearHeads();
+    }
+    // propagate value backward
+    if (backprop && relevant()) {
+        const uint32_t wInc  = hasWeights();
+        Weight_t       maxW  = wInc == 0 ? 1 : *std::max_element(sumData()->weights, sumData()->weights + size());
+        Weight_t*      wPos  = wInc == 0 ? &maxW : sumData()->weights;
+        Weight_t       bound = value() == value_false ? this->bound() : (sumW() - this->bound()) + 1;
+        if (maxW >= bound) {
+            for (auto g : goals()) {
+                if ((bound - *wPos) <= 0) {
+                    auto goalVal = val;
+                    if (g.sign()) {
+                        goalVal = val == value_false ? value_weak_true : value_false;
+                    }
+                    if (not prg.assignValue(prg.getAtom(g.var()), goalVal, PrgEdge::noEdge())) {
+                        return false;
+                    }
+                }
+                wPos += wInc;
+            }
+        }
+    }
+    return true;
+}
+bool PrgBody::propagateValue(LogicProgram& prg) { return propagateValue(prg, prg.options().backprop != 0); }
 
 // Adds nogoods for the tableau-rules FFB and BTB as well as FTB, BFB.
 // For normal bodies, clauses are used, i.e:
 //   FFB and BTB:
-//     - a binary clause [~b s] for every positive subgoal of b
-//     - a binary clause [~b ~n] for every negative subgoal of b
+//     - a binary clause [~b s] for every positive subgoal 's' of 'b'
+//     - a binary clause [~b ~n] for every negative subgoal 'n' of 'b'
 //   FTB and BFB:
-//     - a clause [b ~s1...~sn n1...nn] where si is a positive and ni a negative subgoal
-// For count/sum bodies, a weight constraint is created
+//     - a clause [b ~s1...~sn n1...nn] where 'si' is a positive and 'ni' a negative subgoal
+// For count/sum bodies, a weight constraint is created.
 bool PrgBody::addConstraints(const LogicProgram& prg, ClauseCreator& gc) {
-	if (type() == Body_t::Normal) {
-		bool    taut= false;
-		Literal negB= ~literal();
-		gc.start().add(literal());
-		for (const Literal* it = goals_begin(), *end = goals_end(); it != end; ++it) {
-			Literal li = prg.getAtom(it->var())->literal() ^ it->sign();
-			if (li == literal()) { taut = true; continue; }
-			if (!prg.ctx()->addBinary(negB, li)) { // [~B li]
-				return false;
-			}
-			if (li.var() != negB.var()) { gc.add(~li); }  // [B v ~l1 v ... v ~ln]
-		}
-		return taut || gc.end();
-	}
-	WeightLitVec lits;
-	for (uint32 i = 0, end = size_; i != end; ++i) {
-		Literal eq = prg.getAtom(goal(i).var())->literal() ^ goal(i).sign();
-		lits.push_back(WeightLiteral(eq, weight(i)));
-	}
-	return WeightConstraint::create(*prg.ctx()->master(), literal(), lits, bound()).ok();
+    if (type() == BodyType::normal) {
+        bool    taut = false;
+        Literal negB = ~literal();
+        gc.start().add(literal());
+        for (auto g : goals()) {
+            Literal li = solverLiteral(prg, g);
+            if (li == literal()) {
+                taut = true;
+                continue;
+            }
+            if (not prg.ctx()->addBinary(negB, li)) { // [~B li]
+                return false;
+            }
+            if (li.var() != negB.var()) { // [B v ~l1 v ... v ~ln]
+                gc.add(~li);
+            }
+        }
+        return taut || gc.end();
+    }
+    WeightLitVec lits;
+    for (uint32_t idx = 0; auto g : goals()) {
+        Literal li = solverLiteral(prg, g);
+        lits.push_back(WeightLiteral{li, weight(idx)});
+        ++idx;
+    }
+    return WeightConstraint::create(*prg.ctx()->master(), literal(), lits, bound()).ok();
 }
 
 // Returns the SCC of body B, i.e.
-// - scc if exist atom a in B.heads(), a' in B+, s.th. a.scc == a'.scc
+// - scc if exist atom a in B.heads(), x in B+, s.th. a.scc == x.scc
 // - noScc otherwise
-uint32 PrgBody::scc(const LogicProgram& prg) const {
-	uint64 sccMask = 0;
-	uint32 end     = size();
-	uint32 scc     = PrgNode::noScc;
-	bool   large   = false;
-	for (uint32 i  = 0; i != end; ++i) {
-		if      (goal(i).sign()) {
-			end = i;
-			break;
-		}
-		else if ((scc = prg.getAtom(goal(i).var())->scc()) != PrgNode::noScc) {
-			sccMask |= uint64(1) << (scc & 63);
-			large   |= scc > 63;
-		}
-	}
-	if (sccMask != 0) {
-		PrgDisj::atom_iterator aIt = 0, aEnd = 0;
-		Var head;
-		for (head_iterator h = heads_begin(), hEnd = heads_end(); h != hEnd; ++h) {
-			if (h->isAtom()) { head = h->node(); aIt = &head, aEnd = aIt + 1; }
-			else             { PrgDisj* d = prg.getDisj(h->node()); aIt = d->begin(), aEnd = d->end(); }
-			for (; aIt != aEnd; ++aIt) {
-				scc = prg.getAtom(*aIt)->scc();
-				if (scc != PrgNode::noScc && (sccMask & (uint64(1) << (scc&63))) != 0) {
-					if (!large) { return scc; }
-					for (uint32 j = 0; j != end; ++j) {
-						if (scc == prg.getAtom(goal(j).var())->scc()) { return scc; }
-					}
-				}
-			}
-		}
-	}
-	return PrgNode::noScc;
+uint32_t PrgBody::scc(const LogicProgram& prg) const {
+    auto sccMask = static_cast(0);
+    auto end     = size();
+    auto large   = false;
+    for (auto i : irange(end)) {
+        if (goal(i).sign()) {
+            end = i;
+            break;
+        }
+        if (auto scc = prg.getAtom(goal(i).var())->scc(); scc != no_scc) {
+            sccMask |= static_cast(1) << (scc & 63);
+            large   |= scc > 63;
+        }
+    }
+    if (sccMask != 0) {
+        for (auto h : heads()) {
+            const auto head     = h.node();
+            auto       atomSpan = Potassco::toSpan(head);
+            if (not h.isAtom()) {
+                PrgDisj* d = prg.getDisj(head);
+                atomSpan   = d->atoms();
+            }
+            for (auto a : atomSpan) {
+                if (auto scc = prg.getAtom(a)->scc();
+                    scc != no_scc && (sccMask & (static_cast(1) << (scc & 63))) != 0) {
+                    if (not large) {
+                        return scc;
+                    }
+                    for (auto j : irange(end)) {
+                        if (scc == prg.getAtom(goal(j).var())->scc()) {
+                            return scc;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    return no_scc;
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
@@ -1348,61 +1500,60 @@ uint32 PrgBody::scc(const LogicProgram& prg) const {
 //
 // Head of a disjunctive rule
 /////////////////////////////////////////////////////////////////////////////////////////
-PrgDisj* PrgDisj::create(uint32 id, const Potassco::AtomSpan& head) {
-	void* m = ::operator new(sizeof(PrgDisj) + (Potassco::size(head)*sizeof(Var)));
-	return new (m) PrgDisj(id, head);
+PrgDisj* PrgDisj::create(uint32_t id, Potassco::AtomSpan head) {
+    void* m = ::operator new(sizeof(PrgDisj) + (head.size() * sizeof(Atom_t)));
+    return new (m) PrgDisj(id, head);
 }
 
-PrgDisj::PrgDisj(uint32 id, const Potassco::AtomSpan& head) : PrgHead(id, PrgNode::Disj, (uint32)Potassco::size(head)) {
-	std::copy(Potassco::begin(head), Potassco::end(head), atoms_);
-	std::sort(atoms_, atoms_+size());
+PrgDisj::PrgDisj(uint32_t id, Potassco::AtomSpan head) : PrgHead(id, disj, size32(head)) {
+    std::ranges::copy(head, atoms_);
+    std::sort(atoms_, atoms_ + size());
 }
-PrgDisj::~PrgDisj() {}
+PrgDisj::~PrgDisj() = default;
 void PrgDisj::destroy() {
-	this->~PrgDisj();
-	::operator delete(this);
+    this->~PrgDisj();
+    ::operator delete(this);
 }
 
-void PrgDisj::detach(LogicProgram& prg, bool full) {
-	PrgEdge edge = PrgEdge::newEdge(*this, PrgEdge::Choice);
-	for (atom_iterator it = begin(), end = this->end(); it != end; ++it) {
-		prg.getAtom(*it)->removeSupport(edge);
-	}
-	EdgeVec temp; temp.swap(supports_);
-	for (PrgDisj::sup_iterator it = temp.begin(), end = temp.end(); it != end; ++it) {
-		prg.getBody(it->node())->removeHead(this, PrgEdge::Normal);
-	}
-	if (full) {
-		clearSupports();
-		markRemoved();
-	}
-	else {
-		supports_.swap(temp);
-	}
+void PrgDisj::detach(const LogicProgram& prg, bool full) {
+    PrgEdge edge = PrgEdge::newEdge(*this, PrgEdge::choice);
+    for (auto a : atoms()) { prg.getAtom(a)->removeSupport(edge); }
+    EdgeVec temp;
+    temp.swap(supports_);
+    for (auto e : temp) { prg.getBody(e.node())->removeHead(this, PrgEdge::normal); }
+    if (full) {
+        clearSupports();
+        markRemoved();
+    }
+    else {
+        supports_.swap(temp);
+    }
 }
 
-bool PrgDisj::propagateAssigned(LogicProgram& prg, PrgHead* head, EdgeType t) {
-	assert(head->isAtom() && t == PrgEdge::Choice);
-	if (prg.isFact(static_cast(head)) || head->value() == value_false) {
-		atom_iterator it = std::find(begin(), end(), head->id());
-		if (it != end()) {
-			if      (head->value() == value_true) { detach(prg); }
-			else if (head->value() == value_false){
-				head->removeSupport(PrgEdge::newEdge(*this, t));
-				std::copy(it+1, end(), const_cast(it));
-				if (--data_ == 1) {
-					PrgAtom* last = prg.getAtom(*begin());
-					EdgeVec temp;
-					clearSupports(temp);
-					for (EdgeVec::const_iterator eIt = temp.begin(), eEnd = temp.end(); eIt != eEnd; ++eIt) {
-						prg.getBody(eIt->node())->removeHead(this, PrgEdge::Normal);
-						prg.getBody(eIt->node())->addHead(last, PrgEdge::Normal);
-					}
-					detach(prg);
-				}
-			}
-		}
-	}
-	return true;
-}
-} }
+bool PrgDisj::propagateAssigned(const LogicProgram& prg, PrgHead* head, EdgeType t) {
+    assert(head->isAtom() && t == PrgEdge::choice);
+    if (prg.isFact(node_cast(head)) || head->value() == value_false) {
+        auto as = writable(atoms());
+        if (auto it = std::ranges::find(as, head->id()); it != as.end()) {
+            if (head->value() == value_true) {
+                detach(prg);
+            }
+            else if (head->value() == value_false) {
+                head->removeSupport(PrgEdge::newEdge(*this, t));
+                std::copy(it + 1, as.end(), it);
+                if (--data_ == 1u) {
+                    PrgAtom* last = prg.getAtom(*as.begin());
+                    EdgeVec  temp;
+                    clearSupports(temp);
+                    for (auto e : temp) {
+                        prg.getBody(e.node())->removeHead(this, PrgEdge::normal);
+                        prg.getBody(e.node())->addHead(last, PrgEdge::normal);
+                    }
+                    detach(prg);
+                }
+            }
+        }
+    }
+    return true;
+}
+} // namespace Clasp::Asp
diff --git a/src/lookahead.cpp b/src/lookahead.cpp
index 70115ae..0fb223a 100644
--- a/src/lookahead.cpp
+++ b/src/lookahead.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2009-2017 Benjamin Kaufmann
+// Copyright (c) 2009-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,398 +22,409 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Lookahead scoring
 /////////////////////////////////////////////////////////////////////////////////////////
-uint32 ScoreLook::countNant(const Solver& s, const Literal* b, const Literal *e) const {
-	uint32 sc = 1;
-	for (; b != e; ++b) { sc += s.varInfo(b->var()).nant(); }
-	return sc;
+constexpr bool isAny(VarType x, VarType y) { return Potassco::test_any(+x, +y); }
+
+uint32_t ScoreLook::countNant(const Solver& s, LitView lits) {
+    return static_cast(1 + std::ranges::count_if(lits, [&](Literal x) { return s.varInfo(x.var()).nant(); }));
 }
-void ScoreLook::scoreLits(const Solver& s, const Literal* b, const Literal *e) {
-	assert(b < e);
-	uint32 sc = !nant ? uint32(e-b) : countNant(s, b, e);
-	Var v     = b->var();
-	assert(validVar(v));
-	score[v].setScore(*b, sc);
-	if (addDeps) {
-		if ((score[v].testedBoth() || mode == score_max) && greater(v, best)) {
-			best = v;
-		}
-		for (; b != e; ++b) {
-			v = b->var();
-			if (validVar(v) && (s.varInfo(v).type() & types) != 0) {
-				if (!score[v].seen()) { deps.push_back(v); }
-				score[v].setDepScore(*b, sc);
-				score[v].setSeen(*b);
-			}
-		}
-	}
+void ScoreLook::scoreLits(const Solver& s, LitView lits) {
+    assert(not lits.empty());
+    uint32_t sc = not nant ? size32(lits) : countNant(s, lits);
+    auto     v  = lits[0].var();
+    assert(validVar(v));
+    score[v].setScore(lits[0], sc);
+    if (addDeps) {
+        if ((score[v].testedBoth() || mode == score_max) && greater(v, best)) {
+            best = v;
+        }
+        for (auto lit : lits) {
+            v = lit.var();
+            if (validVar(v) && isAny(s.varInfo(v).type(), types)) {
+                if (not score[v].seen()) {
+                    deps.push_back(v);
+                }
+                score[v].setDepScore(lit, sc);
+            }
+        }
+    }
 }
 void ScoreLook::clearDeps() {
-	for (VarVec::size_type i = 0, end = deps.size(); i != end; ++i) {
-		score[deps[i]].clear();
-	}
-	deps.clear();
-	best  = 0;
-	limit = UINT32_MAX;
+    while (not deps.empty()) {
+        score[deps.back()] = VarScore();
+        deps.pop_back();
+    }
+    best  = 0;
+    limit = UINT32_MAX;
 }
-bool ScoreLook::greater(Var lhs, Var rhs) const {
-	uint32 rhsMax, rhsMin;
-	score[rhs].score(rhsMax, rhsMin);
-	return mode == score_max
-		? greaterMax(lhs, rhsMax)
-		: greaterMaxMin(lhs, rhsMax, rhsMin);
+bool ScoreLook::greater(Var_t lhs, Var_t rhs) const {
+    uint32_t rhsMax, rhsMin;
+    score[rhs].score(rhsMax, rhsMin);
+    return mode == score_max ? greaterMax(lhs, rhsMax) : greaterMaxMin(lhs, rhsMax, rhsMin);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Lookahead propagator
 /////////////////////////////////////////////////////////////////////////////////////////
 Lookahead::Lookahead(const Params& p)
-	: nodes_(2, LitNode(lit_true()))
-	, last_(head_id)    // circular list
-	, pos_(head_id)     // lookahead start pos
-	, top_(uint32(-2))
-	, limit_(p.lim) {
-	head()->next = head_id;
-	undo()->next = UINT32_MAX;
-	if (p.type != Var_t::Hybrid) {
-		score.mode = ScoreLook::score_max_min;
-	}
-	else {
-		score.mode = ScoreLook::score_max;
-	}
-	score.types = p.type;
-	if (p.topLevelImps) { head()->lit.flag(); }
-	score.nant = p.restrictNant;
+    : nodes_(2, LitNode(lit_true))
+    , last_(head_id) // circular list
+    , pos_(head_id)  // lookahead start pos
+    , top_(static_cast(-2))
+    , limit_(p.lim) {
+    head()->next = head_id;
+    undo()->next = UINT32_MAX;
+    if (p.type != VarType::hybrid) {
+        score.mode = ScoreLook::score_max_min;
+    }
+    else {
+        score.mode = ScoreLook::score_max;
+    }
+    score.types = p.type;
+    if (p.topLevelImps) {
+        head()->lit.flag();
+    }
+    score.nant = p.restrictNant;
 }
 
-Lookahead::~Lookahead() {}
+Lookahead::~Lookahead() = default;
 
 void Lookahead::detach(Solver& s) {
-	s.removePost(this);
-	while (saved_.size()>1) {
-		s.removeUndoWatch(uint32(saved_.size()-1), this);
-		saved_.pop_back();
-	}
+    s.removePost(this);
+    while (saved_.size() > 1) {
+        s.removeUndoWatch(size32(saved_) - 1, this);
+        saved_.pop_back();
+    }
 }
 void Lookahead::destroy(Solver* s, bool detach) {
-	if (s && detach) { Lookahead::detach(*s); }
-	PostPropagator::destroy(s, detach);
+    if (s && detach) {
+        Lookahead::detach(*s);
+    }
+    PostPropagator::destroy(s, detach);
 }
 
-uint32 Lookahead::priority() const { return priority_reserved_look; }
+uint32_t Lookahead::priority() const { return priority_reserved_look; }
 
 void Lookahead::clear() {
-	score.clearDeps();
-	while (!saved_.empty()) {
-		if (saved_.back() != UINT32_MAX) {
-			splice(saved_.back());
-		}
-		saved_.pop_back();
-	}
-	LookList(2, *head()).swap(nodes_);
-	head()->next = head_id;
-	undo()->next = UINT32_MAX;
-	last_        = head_id;
-	top_         = UINT32_MAX;
+    score.clearDeps();
+    while (not saved_.empty()) {
+        if (saved_.back() != UINT32_MAX) {
+            splice(saved_.back());
+        }
+        saved_.pop_back();
+    }
+    LookList(2, *head()).swap(nodes_);
+    head()->next = head_id;
+    undo()->next = UINT32_MAX;
+    last_        = head_id;
+    top_         = UINT32_MAX;
 }
 
 bool Lookahead::init(Solver& s) {
-	ScoreLook& sc = score;
-	sc.clearDeps();
-	Var start     = (uint32)sc.score.size();
-	sc.score.resize(s.numVars()+1);
-	const VarType types= sc.types;
-	const bool uniform = types != Var_t::Hybrid;
-	uint32 add    = 0;
-	uint32 nants  = 0;
-	for (Var v = start; v <= s.numVars(); ++v) {
-		if (s.value(v) == value_free && (s.varInfo(v).type() & types) != 0 ) {
-			++add;
-			nants += s.varInfo(v).nant();
-		}
-	}
-	nodes_.reserve(nodes_.size() + add);
-	for (Var v = start; v <= s.numVars(); ++v) {
-		if (s.value(v) == value_free && (s.varInfo(v).type() & types) != 0 ) {
-			append(Literal(v, s.varInfo(v).preferredSign()), uniform || s.varInfo(v).type() == Var_t::Hybrid);
-		}
-	}
-	if (add && score.nant) {
-		score.nant = add != nants && nants != 0;
-	}
-	return true;
+    ScoreLook& sc = score;
+    sc.clearDeps();
+    Var_t start = size32(sc.score);
+    sc.score.resize(s.numVars() + 1);
+    uint32_t add   = 0;
+    uint32_t nants = 0;
+    if (start < size32(sc.score)) {
+        const VarType types   = sc.types;
+        const bool    uniform = types != VarType::hybrid;
+        for (auto v : s.vars(start)) {
+            if (s.value(v) == value_free && isAny(s.varInfo(v).type(), types)) {
+                ++add;
+                nants += s.varInfo(v).nant();
+            }
+        }
+        nodes_.reserve(nodes_.size() + add);
+        for (auto v : s.vars(start)) {
+            if (s.value(v) == value_free && isAny(s.varInfo(v).type(), types)) {
+                append(Literal(v, s.varInfo(v).preferredSign()), uniform || s.varInfo(v).type() == VarType::hybrid);
+            }
+        }
+    }
+    if (add && score.nant) {
+        score.nant = add != nants && nants != 0;
+    }
+    return true;
 }
 
 void Lookahead::append(Literal p, bool testBoth) {
-	node(last_)->next = static_cast(nodes_.size());
-	nodes_.push_back(LitNode(p));
-	last_             = node(last_)->next;
-	node(last_)->next = head_id;
-	// remember to also test ~p by setting watched-flag
-	if (testBoth) { node(last_)->lit.flag(); }
+    node(last_)->next = static_cast(nodes_.size());
+    nodes_.push_back(LitNode(p));
+    last_             = node(last_)->next;
+    node(last_)->next = head_id;
+    // remember to also test ~p by setting watched-flag
+    if (testBoth) {
+        node(last_)->lit.flag();
+    }
 }
 
 // test p and optionally ~p if necessary
 bool Lookahead::test(Solver& s, Literal p) {
-	return (score.score[p.var()].seen(p) || s.test(p, this))
-		&& (!p.flagged() || score.score[p.var()].seen(~p) || s.test(~p, this))
-		&& (imps_.empty() || checkImps(s, p));
+    return (score.score[p.var()].seen(p) || s.test(p, this)) &&
+           (not p.flagged() || score.score[p.var()].seen(~p) || s.test(~p, this)) && (imps_.empty() || checkImps(s, p));
 }
 
 bool Lookahead::checkImps(Solver& s, Literal p) {
-	assert(!imps_.empty());
-	bool ok = true;
-	if (score.score[p.var()].testedBoth()) {
-		for (LitVec::const_iterator it = imps_.begin(), end = imps_.end(); it != end && ok; ++it) {
-			ok  = s.force(*it, lit_true());
-		}
-	}
-	imps_.clear();
-	return ok && (s.queueSize() == 0 || s.propagateUntil(this));
+    assert(not imps_.empty());
+    bool ok = not score.score[p.var()].testedBoth() ||
+              std::ranges::all_of(imps_, [&](Literal x) { return not s.hasConflict() && s.force(x, lit_true); });
+    imps_.clear();
+    return ok && (s.queueSize() == 0 || s.propagateUntil(this));
 }
 
 // failed-literal detection - stop on failed-literal
 bool Lookahead::propagateLevel(Solver& s) {
-	assert(!s.hasConflict());
-	saved_.resize(s.decisionLevel()+1, UINT32_MAX);
-	uint32 undoId = saved_[s.decisionLevel()];
-	if (undoId == UINT32_MAX) {
-		undoId = undo_id;
-		if (s.decisionLevel() != 0) {
-			s.addUndoWatch(s.decisionLevel(), this);
-		}
-	}
-	const uint32 lookLimit = 100;
-	score.clearDeps();
-	score.addDeps = true;
-	uint32& limit = score.limit;
-	Literal p     = node(pos_)->lit;
-	bool   ok     = s.value(p.var()) != value_free || test(s, p);
-	for (LitNode* r = node(pos_); r->next != pos_ && ok; ) {
-		if      (!s.clearSplitRequest()) { limit = UINT32_MAX; }
-		else if (limit == UINT32_MAX)    { limit = lookLimit; }
-		else if (--limit == 0)           { s.sharedContext()->report("Stopping lookahead", &s); break; }
-		p = node(r->next)->lit;
-		if (s.value(p.var()) == value_free) {
-			if (test(s, p)) { r   = node(r->next); }
-			else            { pos_= r->next; ok = false; }
-		}
-		else if (r->next != last_ && r->next != head_id) {
-			// unlink from candidate list
-			NodeId t       = r->next;
-			r->next        = node(t)->next;
-			// append to undo list
-			LitNode* u     = node(undoId);
-			node(t)->next  = u->next;
-			u->next        = t;
-			undoId         = t;
-		}
-		else { r = node(r->next); } // keep iterators valid; never unlink last node and dummy head
-	}
-	saved_.back() = undoId;
-	return ok;
+    assert(not s.hasConflict());
+    saved_.resize(s.decisionLevel() + 1, UINT32_MAX);
+    uint32_t undoId = saved_[s.decisionLevel()];
+    if (undoId == UINT32_MAX) {
+        undoId = undo_id;
+        if (s.decisionLevel() != 0) {
+            s.addUndoWatch(s.decisionLevel(), this);
+        }
+    }
+    static constexpr uint32_t look_limit = 100;
+    score.clearDeps();
+    score.addDeps   = true;
+    uint32_t& limit = score.limit;
+    Literal   p     = node(pos_)->lit;
+    bool      ok    = s.value(p.var()) != value_free || test(s, p);
+    for (LitNode* r = node(pos_); r->next != pos_ && ok;) {
+        if (not s.clearSplitRequest()) {
+            limit = UINT32_MAX;
+        }
+        else if (limit == UINT32_MAX) {
+            limit = look_limit;
+        }
+        else if (--limit == 0) {
+            s.sharedContext()->report("Stopping lookahead", &s);
+            break;
+        }
+        p = node(r->next)->lit;
+        if (s.value(p.var()) == value_free) {
+            if (test(s, p)) {
+                r = node(r->next);
+            }
+            else {
+                pos_ = r->next;
+                ok   = false;
+            }
+        }
+        else if (r->next != last_ && r->next != head_id) {
+            // unlink from candidate list
+            NodeId t = r->next;
+            r->next  = node(t)->next;
+            // append to undo list
+            LitNode* u    = node(undoId);
+            node(t)->next = u->next;
+            u->next       = t;
+            undoId        = t;
+        }
+        else {
+            r = node(r->next);
+        } // keep iterators valid; never unlink last node and dummy head
+    }
+    saved_.back() = undoId;
+    return ok;
 }
 
 bool Lookahead::propagateFixpoint(Solver& s, PostPropagator* ctx) {
-	if ((empty() || top_ == s.numAssignedVars()) && !score.deps.empty()) {
-		// nothing to lookahead
-		return true;
-	}
-	bool ok = true;
-	uint32 dl;
-	for (dl = s.decisionLevel(); !propagateLevel(s); dl = s.decisionLevel()) {
-		// some literal failed
-		// resolve and propagate conflict
-		assert(s.decisionLevel() >= dl);
-		if (!s.resolveConflict() || !s.propagateUntil(this)) {
-			ok = false;
-			score.clearDeps();
-			break;
-		}
-	}
-	if (ok && dl == 0 && score.limit > 0) {
-		// remember top-level size - no need to redo lookahead
-		// on level 0 unless we learn a new implication
-		assert(s.queueSize() == 0);
-		top_ = s.numAssignedVars();
-		LitVec().swap(imps_);
-	}
-	if (!ctx && limit_ && --limit_ == 0) {
-		this->destroy(&s, true);
-	}
-	return ok;
+    if ((empty() || top_ == s.numAssignedVars()) && not score.deps.empty()) {
+        // nothing to lookahead
+        return true;
+    }
+    bool     ok = true;
+    uint32_t dl;
+    for (dl = s.decisionLevel(); not propagateLevel(s); dl = s.decisionLevel()) {
+        // some literal failed
+        // resolve and propagate conflict
+        assert(s.decisionLevel() >= dl);
+        if (not s.resolveConflict() || not s.propagateUntil(this)) {
+            ok = false;
+            score.clearDeps();
+            break;
+        }
+    }
+    if (ok && dl == 0 && score.limit > 0) {
+        // remember top-level size - no need to redo lookahead
+        // on level 0 unless we learn a new implication
+        assert(s.queueSize() == 0);
+        top_ = s.numAssignedVars();
+        discardVec(imps_);
+    }
+    if (not ctx && limit_ && --limit_ == 0) {
+        this->destroy(&s, true);
+    }
+    return ok;
 }
 
 // splice list [undo_.next, ul] back into candidate list
 void Lookahead::splice(NodeId ul) {
-	assert(ul != UINT32_MAX);
-	if (ul != undo_id) {
-		assert(undo()->next != UINT32_MAX);
-		// unlink from undo list
-		LitNode* ulNode= node(ul);
-		NodeId   first = undo()->next;
-		undo()->next   = ulNode->next;
-		// splice into look-list
-		ulNode->next   = head()->next;
-		head()->next   = first;
-	}
+    assert(ul != UINT32_MAX);
+    if (ul != undo_id) {
+        assert(undo()->next != UINT32_MAX);
+        // unlink from undo list
+        LitNode* ulNode = node(ul);
+        NodeId   first  = undo()->next;
+        undo()->next    = ulNode->next;
+        // splice into look-list
+        ulNode->next = head()->next;
+        head()->next = first;
+    }
 }
 
 void Lookahead::undoLevel(Solver& s) {
-	if (s.decisionLevel() == saved_.size()) {
-		cancelPropagation();
-		const LitVec& a = s.trail();
-		score.scoreLits(s, &a[0]+s.levelStart(s.decisionLevel()), &a[0]+a.size());
-		if (s.decisionLevel() == static_cast(head()->lit.flagged())) {
-			const Literal* b = &a[0]+s.levelStart(s.decisionLevel());
-			if (b->flagged()) {
-				// remember current DL for b
-				uint32 dist = static_cast(((&a[0]+a.size()) - b));
-				imps_.assign(b+1, b + std::min(dist, uint32(2048)));
-			}
-			else if (score.score[b->var()].testedBoth()) {
-				// all true lits in imps_ follow from both *b and ~*b
-				// and are therefore implied
-				LitVec::iterator j = imps_.begin();
-				for (LitVec::iterator it = imps_.begin(), end = imps_.end(); it != end; ++it) {
-					if (s.isTrue(*it)) { *j++ = *it; }
-				}
-				imps_.erase(j, imps_.end());
-			}
-		}
-	}
-	else {
-		assert(saved_.size() >= s.decisionLevel()+1);
-		saved_.resize(s.decisionLevel()+1);
-		NodeId n = saved_.back();
-		saved_.pop_back();
-		splice(n);
-		assert(node(last_)->next == head_id);
-		score.clearDeps();
-	}
+    if (s.decisionLevel() == saved_.size()) {
+        cancelPropagation();
+        auto lits = s.levelLits(s.decisionLevel());
+        score.scoreLits(s, lits);
+        if (s.decisionLevel() == static_cast(head()->lit.flagged())) {
+            if (const Literal* b = lits.data(); b->flagged()) {
+                // remember current DL for b
+                auto dist = size32(lits);
+                imps_.assign(b + 1, b + std::min(dist, static_cast(2048)));
+            }
+            else if (score.score[b->var()].testedBoth() && not imps_.empty()) {
+                // all true lits in imps_ follow from both *b and ~*b
+                // and are therefore implied
+                erase_if(imps_, [&s](Literal lit) { return not s.isTrue(lit); });
+            }
+        }
+    }
+    else {
+        assert(saved_.size() >= s.decisionLevel() + 1);
+        saved_.resize(s.decisionLevel() + 1);
+        NodeId n = saved_.back();
+        saved_.pop_back();
+        splice(n);
+        assert(node(last_)->next == head_id);
+        score.clearDeps();
+    }
 }
 
 Literal Lookahead::heuristic(Solver& s) {
-	if (s.value(score.best) != value_free) {
-		// no candidate available
-		return lit_true();
-	}
-	ScoreLook& sc = score;
-	Literal choice= Literal(sc.best, sc.score[sc.best].prefSign());
-	if (!sc.deps.empty() && sc.mode == ScoreLook::score_max_min && sc.limit > 0) {
-		// compute heuristic values for candidates skipped during last lookahead
-		uint32 min, max;
-		sc.score[sc.best].score(max, min);
-		sc.addDeps = false;
-		bool ok    = true;
-		LitVec::size_type i = 0;
-		do {
-			Var v        = sc.deps[i];
-			VarScore& vs = sc.score[v];
-			if (s.value(v) == value_free) {
-				uint32 vMin, vMax;
-				vs.score(vMax, vMin);
-				if (vMin == 0 || vMin > min || (vMin == min && vMax > max)) {
-					uint32 neg = vs.score(negLit(v)) > 0 ? vs.score(negLit(v)) : max+1;
-					uint32 pos = vs.score(posLit(v)) > 0 ? vs.score(posLit(v)) : max+1;
-					if (!vs.tested(negLit(v))) {
-						ok  = s.test(negLit(v), this);
-						neg = vs.score(negLit(v));
-						--sc.limit;
-					}
-					if (ok && (neg > min || (neg == min && pos > max)) && !vs.tested(posLit(v)) && sc.limit > 0) {
-						ok = s.test(posLit(v), this);
-						--sc.limit;
-					}
-				}
-				if (vs.testedBoth() && sc.greaterMaxMin(v, max, min)) {
-					vs.score(max, min);
-					choice = Literal(v, vs.prefSign());
-				}
-			}
-		} while (++i != sc.deps.size() && ok && sc.limit > 0);
-		if (!ok) {
-			// One of the candidates failed. Since none of them failed
-			// during previous propagation, this indicates that
-			// either some post propagator has wrong priority or
-			// parallel solving is active and a stop conflict was set.
-			// Since we can't resolve the problem here, we simply return the
-			// literal that caused the conflict
-			assert(s.hasConflict());
-			return lit_false();
-		}
-	}
-	return choice;
+    if (s.value(score.best) != value_free) {
+        // no candidate available
+        return lit_true;
+    }
+    ScoreLook& sc     = score;
+    Literal    choice = Literal(sc.best, sc.score[sc.best].prefSign());
+    if (!sc.deps.empty() && sc.mode == ScoreLook::score_max_min && sc.limit > 0) {
+        // compute heuristic values for candidates skipped during last lookahead
+        uint32_t min, max;
+        sc.score[sc.best].score(max, min);
+        sc.addDeps           = false;
+        bool              ok = true;
+        LitVec::size_type i  = 0;
+        do {
+            auto      v  = sc.deps[i];
+            VarScore& vs = sc.score[v];
+            if (s.value(v) == value_free) {
+                uint32_t vMin, vMax;
+                vs.score(vMax, vMin);
+                if (vMin == 0 || vMin > min || (vMin == min && vMax > max)) {
+                    uint32_t neg = vs.score(negLit(v)) > 0 ? vs.score(negLit(v)) : max + 1;
+                    uint32_t pos = vs.score(posLit(v)) > 0 ? vs.score(posLit(v)) : max + 1;
+                    if (not vs.tested(negLit(v))) {
+                        ok  = s.test(negLit(v), this);
+                        neg = vs.score(negLit(v));
+                        --sc.limit;
+                    }
+                    if (ok && (neg > min || (neg == min && pos > max)) && not vs.tested(posLit(v)) && sc.limit > 0) {
+                        ok = s.test(posLit(v), this);
+                        --sc.limit;
+                    }
+                }
+                if (vs.testedBoth() && sc.greaterMaxMin(v, max, min)) {
+                    vs.score(max, min);
+                    choice = Literal(v, vs.prefSign());
+                }
+            }
+        } while (++i != sc.deps.size() && ok && sc.limit > 0);
+        if (not ok) {
+            // One of the candidates failed. Since none of them failed
+            // during previous propagation, this indicates that
+            // either some post propagator has wrong priority or
+            // parallel solving is active and a stop conflict was set.
+            // Since we can't resolve the problem here, we simply return the
+            // literal that caused the conflict
+            assert(s.hasConflict());
+            return lit_false;
+        }
+    }
+    return choice;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Lookahead heuristic
 /////////////////////////////////////////////////////////////////////////////////////////
-UnitHeuristic::UnitHeuristic() { }
+UnitHeuristic::UnitHeuristic() = default;
+Lookahead* UnitHeuristic::getLookahead(const Solver& s) {
+    return static_cast(s.getPost(Lookahead::priority_reserved_look));
+}
 void UnitHeuristic::endInit(Solver& s) {
-	Lookahead* look = static_cast(s.getPost(Lookahead::priority_reserved_look));
-	if (!look) { s.addPost(new Lookahead(Var_t::Atom)); }
+    if (not getLookahead(s)) {
+        s.addPost(new Lookahead(VarType::atom));
+    }
 }
 Literal UnitHeuristic::doSelect(Solver& s) {
-	Lookahead* look = static_cast(s.getPost(Lookahead::priority_reserved_look));
-	Literal x       = look ? look->heuristic(s) : lit_true();
-	if (x != lit_true()) { return x; }
-	return SelectFirst::doSelect(s);
+    auto* look = getLookahead(s);
+    if (Literal x = look ? look->heuristic(s) : lit_true; x != lit_true) {
+        return x;
+    }
+    return SelectFirst::doSelect(s);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Restricted Lookahead heuristic - lookahead and heuristic for a limited number of ops
 /////////////////////////////////////////////////////////////////////////////////////////
-class Restricted : public UnitHeuristic {
+class UnitHeuristic::Restricted final : public UnitHeuristic {
 public:
-	typedef LitVec::size_type size_t;
-	typedef ConstraintType    con_t;
-	Restricted(DecisionHeuristic* other)
-		: UnitHeuristic()
-		, other_(other)
-		, disabled_(false) {
-	}
-	Literal doSelect(Solver& s) {
-		return !disabled_ ? heuristic(s) : other_->doSelect(s);
-	}
-	// heuristic interface - forward to other
-	void startInit(const Solver& s)           { other_->startInit(s); }
-	void endInit(Solver& s)                   { other_->endInit(s); }
-	void setConfig(const HeuParams& p)        { other_->setConfig(p); }
-	void detach(Solver& s)                    { if (other_.is_owner()) { other_->detach(s); } }
-	void simplify(const Solver& s, size_t st) { other_->simplify(s, st); }
-	void undoUntil(const Solver& s, size_t st){ other_->undoUntil(s, st); }
-	void updateReason(const Solver& s, const LitVec& x, Literal r)            { other_->updateReason(s, x, r); }
-	bool bump(const Solver& s, const WeightLitVec& w, double d)               { return other_->bump(s, w, d); }
-	void newConstraint(const Solver& s, const Literal* p, size_t sz, con_t t) { other_->newConstraint(s, p, sz, t); }
-	void updateVar(const Solver& s, Var v, uint32 n)                          { other_->updateVar(s, v, n); }
-	Literal selectRange(Solver& s, const Literal* f, const Literal* l)        { return other_->selectRange(s, f, l); }
+    static inline SelectFirst ignore;
+    explicit Restricted(DecisionHeuristic* other) : other_(other) { assert(other_); }
+    ~Restricted() override {
+        if (other_ != &ignore) {
+            delete other_;
+        }
+    }
+    Literal doSelect(Solver& s) override {
+        auto choice = lit_true;
+        if (other_ != &ignore) {
+            if (auto* look = getLookahead(s); not look || not look->hasLimit()) {
+                choice = other_->doSelect(s);
+                if (auto* h = std::exchange(other_, &ignore); s.heuristic() == this) {
+                    s.setHeuristic(h);
+                }
+            }
+            else {
+                choice = look->heuristic(s);
+            }
+        }
+        if (choice == lit_true) {
+            choice = other_->doSelect(s);
+        }
+        return choice;
+    }
+    // heuristic interface - forward to other
+    void startInit(const Solver& s) override { other_->startInit(s); }
+    void endInit(Solver& s) override { other_->endInit(s); }
+    void setConfig(const HeuParams& p) override { other_->setConfig(p); }
+    void detach(Solver& s) override { other_->detach(s); }
+    void simplify(const Solver& s, LitView sp) override { other_->simplify(s, sp); }
+    void undo(const Solver& s, LitView undo) override { other_->undo(s, undo); }
+    void updateReason(const Solver& s, LitView x, Literal r) override { other_->updateReason(s, x, r); }
+    bool bump(const Solver& s, WeightLitView w, double d) override { return other_->bump(s, w, d); }
+    void newConstraint(const Solver& s, LitView lits, ConstraintType t) override { other_->newConstraint(s, lits, t); }
+    void updateVar(const Solver& s, Var_t v, uint32_t n) override { other_->updateVar(s, v, n); }
+    Literal selectRange(Solver& s, LitView range) override { return other_->selectRange(s, range); }
+
 private:
-	void disable(Solver& s) {
-		disabled_ = true;
-		if (s.heuristic() == this)
-			s.setHeuristic(other_.release(), Ownership_t::Acquire);
-	}
-	Literal heuristic(Solver& s) {
-		Literal choice;
-		Lookahead* look = static_cast(s.getPost(Lookahead::priority_reserved_look));
-		if (!look || !look->hasLimit()) {
-			choice = other_->doSelect(s);
-			disable(s);
-		}
-		else {
-			Literal p = look->heuristic(s);
-			choice = p != lit_true() ? p : other_->doSelect(s);
-		}
-		return choice;
-	}
-	typedef SingleOwnerPtr HeuPtr;
-	HeuPtr other_;
-	bool   disabled_;
+    DecisionHeuristic* other_;
 };
 
-UnitHeuristic* UnitHeuristic::restricted(DecisionHeuristic* other) {
-	return new Restricted(other);
-}
-}
+UnitHeuristic* UnitHeuristic::restricted(DecisionHeuristic* other) { return new Restricted(other); }
+} // namespace Clasp
diff --git a/src/minimize_constraint.cpp b/src/minimize_constraint.cpp
index e61aeb1..78f0470 100644
--- a/src/minimize_constraint.cpp
+++ b/src/minimize_constraint.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2010-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,257 +22,273 @@
 // IN THE SOFTWARE.
 //
 #include 
+
+#include 
 #include 
 #include 
-#include 
+
+#include 
+
 #include 
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // SharedMinimizeData
 /////////////////////////////////////////////////////////////////////////////////////////
-SharedMinimizeData::SharedMinimizeData(const SumVec& lhsAdjust, MinimizeMode m) : mode_(m) {
-	adjust_ = lhsAdjust;
-	lower_  = new LowerType[adjust_.size()];
-	count_  = 1;
-	resetBounds();
-	setMode(MinimizeMode_t::optimize);
-}
-SharedMinimizeData::~SharedMinimizeData() {
-	delete[] lower_;
-}
+SharedMinimizeData::SharedMinimizeData(SumView lhsAdjust, MinimizeMode m)
+    : adjust_(lhsAdjust.data(), lhsAdjust.data() + lhsAdjust.size())
+    , lower_(std::make_unique(lhsAdjust.size()))
+    , mode_(m)
+    , count_(1)
+    , optGen_(0) {
+    resetBounds();
+    setMode(MinimizeMode::optimize);
+}
+SharedMinimizeData::~SharedMinimizeData() = default;
 
 void SharedMinimizeData::destroy() const {
-	this->~SharedMinimizeData();
-	::operator delete(const_cast(this));
+    this->~SharedMinimizeData();
+    ::operator delete(const_cast(this));
 }
 
 void SharedMinimizeData::resetBounds() {
-	gCount_  = 0;
-	optGen_  = 0;
-	std::fill_n(lower_, numRules(), wsum_t(0));
-	up_[0].assign(numRules(), maxBound());
-	up_[1].assign(numRules(), maxBound());
-	const WeightLiteral* lit = lits;
-	for (weight_t wPos = 0, end = (weight_t)weights.size(), x; wPos != end; wPos = x+1) {
-		assert(weights[wPos].weight >= 0);
-		for (x = wPos; weights[x].next; ) { // compound weight - check for negative
-			if (weights[++x].weight < 0) {
-				while (lit->second != wPos) { ++lit; }
-				for (const WeightLiteral* t = lit; t->second == wPos; ++t) {
-					lower_[weights[x].level] += weights[x].weight;
-				}
-			}
-		}
-	}
+    gCount_.store(0);
+    optGen_ = 0;
+    for (auto& low : std::span{lower_.get(), numRules()}) { low.store(0); }
+    lowPos_.store(numRules());
+    up_[0].assign(numRules(), maxBound());
+    up_[1].assign(numRules(), maxBound());
+    const WeightLiteral* lit = lits;
+    for (auto wIt = weights.begin(), end = weights.end(); wIt != end; ++wIt) {
+        assert(wIt->weight >= 0);
+        auto wPos = wIt - weights.begin();
+        for (auto nLits = 0; wIt->next;) { // Any weight < 0? If so, reduce lower bound accordingly.
+            if (++wIt; wIt->weight < 0) {
+                if (nLits == 0) { // Get number of literals having this weight (i.e. all with same weight position).
+                    for (; lit->weight <= wPos; ++lit) { nLits += (lit->weight == wPos); }
+                }
+                assert(nLits > 0 && lit->weight > wPos);
+                lower_[wIt->level].add(static_cast(wIt->weight) * nLits);
+            }
+        }
+    }
 }
 
-bool SharedMinimizeData::setMode(MinimizeMode m, const wsum_t* bound, uint32 boundSize)  {
-	mode_ = m;
-	if (boundSize && bound) {
-		SumVec& opt = up_[0];
-		bool    ok  = false;
-		gCount_     = 0;
-		optGen_     = 0;
-		boundSize   = std::min(boundSize, numRules());
-		for (uint32 i = 0, end = boundSize; i != end; ++i) {
-			wsum_t B = bound[i], a = adjust(i);
-			B        = a >= 0 || (maxBound() + a) >= B ? B - a : maxBound();
-			wsum_t d = B - lower_[i];
-			if (d < 0 && !ok) { return false; }
-			opt[i]   = B;
-			ok       = ok || d > 0;
-		}
-		for (uint32 i = boundSize, end = (uint32)opt.size(); i != end; ++i) { opt[i] = maxBound(); }
-	}
-	return true;
+bool SharedMinimizeData::setMode(MinimizeMode m, SumView bound) {
+    mode_ = m;
+    if (not bound.empty()) {
+        SumVec& opt = up_[0];
+        bool    ok  = false;
+        gCount_.store(0);
+        optGen_        = 0;
+        auto boundSize = std::min(size32(bound), numRules());
+        for (uint32_t i : irange(boundSize)) {
+            auto b = bound[i], a = adjust(i);
+            b      = a >= 0 || (maxBound() + a) >= b ? b - a : maxBound();
+            auto d = b - lower_[i].load();
+            if (d < 0 && not ok) {
+                return false;
+            }
+            opt[i] = b;
+            ok     = ok || d > 0;
+        }
+        for (auto& b : drop(opt, boundSize)) { b = maxBound(); }
+    }
+    return true;
 }
 
 MinimizeConstraint* SharedMinimizeData::attach(Solver& s, const OptParams& params, bool addRef) {
-	if (addRef) this->share();
-	MinimizeConstraint* ret;
-	if (params.type == OptParams::type_bb || mode() == MinimizeMode_t::enumerate) {
-		ret = new DefaultMinimize(this, params);
-	}
-	else {
-		ret = new UncoreMinimize(this, params);
-	}
-	ret->attach(s);
-	return ret;
+    if (addRef) {
+        this->share();
+    }
+    MinimizeConstraint* ret;
+    if (params.type == OptParams::type_bb || mode() == MinimizeMode::enumerate) {
+        ret = new DefaultMinimize(this, params);
+    }
+    else {
+        ret = new UncoreMinimize(this, params);
+    }
+    ret->attach(s);
+    return ret;
 }
 
-const SumVec* SharedMinimizeData::setOptimum(const wsum_t* newOpt) {
-	if (optGen_) { return up_ + (optGen_&1u); }
-	uint32  g = gCount_;
-	uint32  n = 1u - (g & 1u);
-	SumVec& U = up_[n];
-	U.assign(newOpt, newOpt + numRules());
-	assert(mode() != MinimizeMode_t::enumerate || n == 1);
-	if (mode() != MinimizeMode_t::enumerate) {
-		if (++g == 0) { g = 2; }
-		gCount_  = g;
-	}
-	return &U;
-}
-void SharedMinimizeData::setLower(uint32 lev, wsum_t low) {
-	lower_[lev] = low;
-}
-wsum_t SharedMinimizeData::incLower(uint32 at, wsum_t low){
-	for (wsum_t stored;;) {
-		if ((stored = lower(at)) >= low) {
-			return stored;
-		}
-		if (compare_and_swap(lower_[at], stored, low) == stored) {
-			return low;
-		}
-	}
-}
-wsum_t SharedMinimizeData::lower(uint32 lev) const {
-	return lower_[lev];
-}
-wsum_t SharedMinimizeData::optimum(uint32 lev) const {
-	wsum_t o = sum(lev);
-	return o + (o != maxBound() ? adjust(lev) : 0);
-}
-void SharedMinimizeData::markOptimal() {
-	optGen_ = generation();
-}
-void SharedMinimizeData::sub(wsum_t* lhs, const LevelWeight* w, uint32& aLev) const {
-	if (w->level < aLev) { aLev = w->level; }
-	do { lhs[w->level] -= w->weight; } while (w++->next);
-}
-bool SharedMinimizeData::imp(wsum_t* lhs, const LevelWeight* w, const wsum_t* rhs, uint32& lev) const {
-	assert(lev <= w->level && std::equal(lhs, lhs+lev, rhs));
-	while (lev != w->level && lhs[lev] == rhs[lev]) { ++lev; }
-	for (uint32 i = lev, end = numRules(); i != end; ++i) {
-		wsum_t temp = lhs[i];
-		if (i == w->level) { temp += w->weight; if (w->next) ++w; }
-		if (temp != rhs[i]){ return temp > rhs[i]; }
-	}
-	return false;
+SumView SharedMinimizeData::setOptimum(const Wsum_t* newOpt) {
+    if (optGen_) {
+        return up_[(optGen_ & 1u)];
+    }
+    uint32_t g = gCount_.load();
+    uint32_t n = 1u - (g & 1u);
+    SumVec&  u = up_[n];
+    u.assign(newOpt, newOpt + numRules());
+    assert(mode() != MinimizeMode::enumerate || n == 1);
+    if (mode() != MinimizeMode::enumerate) {
+        if (++g == 0) {
+            g = 2;
+        }
+        gCount_.store(g);
+    }
+    return u;
+}
+void   SharedMinimizeData::setLower(uint32_t lev, Wsum_t low) { lower_[lev].store(low); }
+Wsum_t SharedMinimizeData::incLower(uint32_t lev, Wsum_t low) {
+    for (auto stored = lower(lev);;) {
+        if (stored >= low) {
+            return stored;
+        }
+        if (lower_[lev].compare_exchange_weak(stored, low)) {
+            auto storedLev = lowPos_.load();
+            while (storedLev == numRules() || lev > storedLev) { lowPos_.compare_exchange_weak(storedLev, lev); }
+            return low;
+        }
+    }
+}
+Wsum_t SharedMinimizeData::lower(uint32_t lev) const { return lower_[lev].load(); }
+Wsum_t SharedMinimizeData::optimum(uint32_t lev) const {
+    Wsum_t o = sum(lev);
+    return o + (o != maxBound() ? adjust(lev) : 0);
+}
+void SharedMinimizeData::markOptimal() { optGen_ = generation(); }
+void SharedMinimizeData::sub(Wsum_t* lhs, const LevelWeight* w, uint32_t& aLev) {
+    if (w->level < aLev) {
+        aLev = w->level;
+    }
+    do { lhs[w->level] -= w->weight; } while (w++->next);
+}
+bool SharedMinimizeData::imp(Wsum_t* lhs, const LevelWeight* w, const Wsum_t* rhs, uint32_t& lev) const {
+    assert(lev <= w->level && std::equal(lhs, lhs + lev, rhs));
+    while (lev != w->level && lhs[lev] == rhs[lev]) { ++lev; }
+    for (uint32_t i = lev, end = numRules(); i != end; ++i) {
+        Wsum_t temp = lhs[i];
+        if (i == w->level) {
+            temp += w->weight;
+            if (w->next) {
+                ++w;
+            }
+        }
+        if (temp != rhs[i]) {
+            return temp > rhs[i];
+        }
+    }
+    return false;
+}
+LowerBound SharedMinimizeData::lowerBound() const {
+    if (auto lev = lowPos_.load(); lev < numRules()) {
+        return {.level = lev, .bound = lower(lev) + adjust(lev)};
+    }
+    return {};
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // MinimizeConstraint
 /////////////////////////////////////////////////////////////////////////////////////////
 MinimizeConstraint::MinimizeConstraint(SharedData* d) : shared_(d) {}
 
-MinimizeConstraint::~MinimizeConstraint() {
-	assert(shared_ == 0 && "MinimizeConstraint not destroyed!");
-}
+MinimizeConstraint::~MinimizeConstraint() { assert(shared_ == nullptr && "MinimizeConstraint not destroyed!"); }
 bool MinimizeConstraint::prepare(Solver& s, bool useTag) {
-	POTASSCO_REQUIRE(!s.isFalse(tag_), "Tag literal must not be false!");
-	if (useTag && tag_ == lit_true())      { tag_ = posLit(s.pushTagVar(false)); }
-	if (s.isTrue(tag_) || s.hasConflict()){ return !s.hasConflict(); }
-	return useTag ? s.pushRoot(tag_) : s.force(tag_, 0);
+    POTASSCO_CHECK_PRE(not s.isFalse(tag_), "Tag literal must not be false!");
+    if (useTag && tag_ == lit_true) {
+        tag_ = posLit(s.pushTagVar(false));
+    }
+    if (s.isTrue(tag_) || s.hasConflict()) {
+        return not s.hasConflict();
+    }
+    return useTag ? s.pushRoot(tag_) : s.force(tag_, nullptr);
 }
 void MinimizeConstraint::destroy(Solver* s, bool d) {
-	shared_->release();
-	shared_ = 0;
-	Constraint::destroy(s, d);
-}
-void MinimizeConstraint::reportLower(Solver& s, uint32 lev, wsum_t low) const {
-	s.lower.level = lev;
-	s.lower.bound = low + shared_->adjust(lev);
+    shared_->release();
+    shared_ = nullptr;
+    Constraint::destroy(s, d);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultMinimize
 /////////////////////////////////////////////////////////////////////////////////////////
-union DefaultMinimize::UndoInfo {
-	UndoInfo() : rep(0) {}
-	struct {
-		uint32 idx    : 30; // index of literal on stack
-		uint32 newDL  :  1; // first literal of new DL?
-		uint32 idxSeen:  1; // literal with idx already propagated?
-	}      data;
-	uint32 rep;
-	uint32 index() const { return data.idx; }
-	bool   newDL() const { return data.newDL != 0u; }
+struct DefaultMinimize::UndoInfo {
+    uint32_t idx     : 30 {0}; // index of literal on stack
+    uint32_t newDL   : 1 {0};  // first literal of new DL?
+    uint32_t idxSeen : 1 {0};  // literal with idx already propagated?
 };
 DefaultMinimize::DefaultMinimize(SharedData* d, const OptParams& params)
-	: MinimizeConstraint(d)
-	, bounds_(0)
-	, pos_(d->lits)
-	, undo_(0)
-	, undoTop_(0)
-	, posTop_(0)
-	, size_(d->numRules())
-	, actLev_(0)
-	, step_() {
-	step_.type = params.algo;
-	if (step_.type == OptParams::bb_hier && d->numRules() == 1) {
-		step_.type = 0;
-	}
+    : MinimizeConstraint(d)
+    , bounds_(nullptr)
+    , pos_(d->lits)
+    , undo_(nullptr)
+    , undoTop_(0)
+    , posTop_(0)
+    , size_(d->numRules())
+    , actLev_(0) {
+    step_.type = params.algo;
+    if (step_.type == OptParams::bb_hier && d->numRules() == 1) {
+        step_.type = 0;
+    }
 }
 
-DefaultMinimize::~DefaultMinimize() {
-	delete [] bounds_;
-	delete [] undo_;
-}
+DefaultMinimize::~DefaultMinimize() = default;
 
 void DefaultMinimize::destroy(Solver* s, bool detach) {
-	if (s && detach) {
-		for (const WeightLiteral* it = shared_->lits; !isSentinel(it->first); ++it) {
-			s->removeWatch(it->first, this);
-		}
-		for (uint32 dl = 0; (dl = lastUndoLevel(*s)) != 0; ) {
-			s->removeUndoWatch(dl, this);
-			DefaultMinimize::undoLevel(*s);
-		}
-	}
-	MinimizeConstraint::destroy(s, detach);
+    if (s && detach) {
+        for (const auto& [lit, _] : *shared_) { s->removeWatch(lit, this); }
+        for (uint32_t dl = 0; (dl = lastUndoLevel(*s)) != 0;) {
+            s->removeUndoWatch(dl, this);
+            DefaultMinimize::undoLevel(*s);
+        }
+    }
+    MinimizeConstraint::destroy(s, detach);
 }
 
 bool DefaultMinimize::attach(Solver& s) {
-	assert(s.decisionLevel() == 0 && !bounds_);
-	uint32 numL = 0;
-	VarVec up;
-	for (const WeightLiteral* it = shared_->lits; !isSentinel(it->first); ++it, ++numL) {
-		if (s.value(it->first.var()) == value_free) {
-			s.addWatch(it->first, this, numL);
-		}
-		else if (s.isTrue(it->first)) {
-			up.push_back(numL);
-		}
-	}
-	bounds_ = new wsum_t[(numRules() * (3 + uint32(step_.type != 0)))]; // upper, sum, temp, lower
-	std::fill(this->opt(), this->sum(), SharedData::maxBound());
-	std::fill(this->sum(), this->end(), wsum_t(0));
-	stepInit(0);
-	// [0,numL+1)      : undo stack
-	// [numL+1, numL*2): pos  stack
-	undo_    = new UndoInfo[(numL*2)+1];
-	undoTop_ = 0;
-	posTop_  = numL+1;
-	actLev_  = 0;
-	for (WeightVec::size_type i = 0; i != up.size(); ++i) {
-		DefaultMinimize::propagate(s, shared_->lits[up[i]].first, up[i]);
-	}
-	return true;
+    assert(s.decisionLevel() == 0 && not bounds_);
+    uint32_t numL = 0;
+    VarVec   up;
+    for (const auto& [lit, _] : *shared_) {
+        if (s.value(lit.var()) == value_free) {
+            s.addWatch(lit, this, numL);
+        }
+        else if (s.isTrue(lit)) {
+            up.push_back(numL);
+        }
+        ++numL;
+    }
+    bounds_ = std::make_unique(numRules() *
+                                         (3 + static_cast(step_.type != 0))); // upper, sum, temp, lower
+    std::fill(this->opt(), this->sum(), SharedData::maxBound());
+    std::fill(this->sum(), this->end(), static_cast(0));
+    stepInit(0);
+    // [0,numL+1)      : undo stack
+    // [numL+1, numL*2): pos  stack
+    undo_    = std::make_unique((numL * 2) + 1);
+    undoTop_ = 0;
+    posTop_  = numL + 1;
+    actLev_  = 0;
+    for (auto x : up) { DefaultMinimize::propagate(s, shared_->lits[x].lit, x); }
+    return true;
 }
 
 // Returns the numerical highest decision level watched by this constraint.
-uint32 DefaultMinimize::lastUndoLevel(const Solver& s) const {
-	return undoTop_ != 0
-		? s.level(shared_->lits[undo_[undoTop_-1].index()].first.var())
-		: 0;
+uint32_t DefaultMinimize::lastUndoLevel(const Solver& s) const {
+    return undoTop_ != 0 ? s.level(shared_->lits[undo_[undoTop_ - 1].idx].lit.var()) : 0;
 }
-bool DefaultMinimize::litSeen(uint32 i) const { return undo_[i].data.idxSeen != 0; }
+bool DefaultMinimize::litSeen(uint32_t i) const { return undo_[i].idxSeen != 0; }
 
 // Pushes the literal at index idx onto the undo stack
 // and marks it as seen; if literal is first in current level
 // adds a new undo watch.
-void DefaultMinimize::pushUndo(Solver& s, uint32 idx) {
-	assert(idx >= static_cast(pos_ - shared_->lits));
-	undo_[undoTop_].data.idx  = idx;
-	undo_[undoTop_].data.newDL= 0;
-	if (lastUndoLevel(s) != s.decisionLevel()) {
-		// remember current "look at" position and start
-		// a new decision level on the undo stack
-		undo_[posTop_++].data.idx = static_cast(pos_-shared_->lits);
-		s.addUndoWatch(s.decisionLevel(), this);
-		undo_[undoTop_].data.newDL = 1;
-	}
-	undo_[idx].data.idxSeen   = 1;
-	++undoTop_;
+void DefaultMinimize::pushUndo(Solver& s, uint32_t idx) {
+    assert(idx >= static_cast(pos_ - shared_->lits));
+    undo_[undoTop_].idx   = idx;
+    undo_[undoTop_].newDL = 0;
+    if (lastUndoLevel(s) != s.decisionLevel()) {
+        // remember current "look at" position and start
+        // a new decision level on the undo stack
+        undo_[posTop_++].idx = static_cast(pos_ - shared_->lits);
+        s.addUndoWatch(s.decisionLevel(), this);
+        undo_[undoTop_].newDL = 1;
+    }
+    undo_[idx].idxSeen = 1;
+    ++undoTop_;
+}
+auto DefaultMinimize::viewUndo(const Solver& s, Literal p) const -> SpanView {
+    uint32_t stop = s.reasonData(p);
+    assert(stop <= undoTop_);
+    return std::span(undo_.get(), stop);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // MinimizeConstraint - arithmetic strategy implementation
@@ -282,1323 +298,1502 @@ void DefaultMinimize::pushUndo(Solver& s, uint32 idx) {
 /////////////////////////////////////////////////////////////////////////////////////////
 #define STRATEGY(x) shared_->x
 // set *lhs = *rhs, where lhs != rhs
-void DefaultMinimize::assign(wsum_t* lhs, wsum_t* rhs) const {
-	std::memcpy(lhs, rhs, size_*sizeof(wsum_t));
-}
+void DefaultMinimize::assign(Wsum_t* lhs, const Wsum_t* rhs) const { std::memcpy(lhs, rhs, size_ * sizeof(Wsum_t)); }
 // returns lhs > rhs
-bool DefaultMinimize::greater(wsum_t* lhs, wsum_t* rhs, uint32 len, uint32& aLev) const {
-	while (*lhs == *rhs && --len) { ++lhs, ++rhs; ++aLev; }
-	return *lhs > *rhs;
+bool DefaultMinimize::greater(Wsum_t* lhs, Wsum_t* rhs, uint32_t len, uint32_t& aLev) {
+    while (*lhs == *rhs && --len) {
+        ++lhs, ++rhs;
+        ++aLev;
+    }
+    return *lhs > *rhs;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
-Constraint::PropResult DefaultMinimize::propagate(Solver& s, Literal, uint32& data) {
-	pushUndo(s, data);
-	STRATEGY(add(sum(), shared_->lits[data]));
-	return PropResult(propagateImpl(s, propagate_new_sum), true);
+Constraint::PropResult DefaultMinimize::propagate(Solver& s, Literal, uint32_t& data) {
+    pushUndo(s, data);
+    STRATEGY(add(sum(), shared_->lits[data]));
+    return PropResult(propagateImpl(s, propagate_new_sum), true);
 }
 
 // Computes the set of literals implying p and returns
 // the highest decision level of that set.
 // PRE: p is implied on highest undo level
-uint32 DefaultMinimize::computeImplicationSet(const Solver& s, const WeightLiteral& p, uint32& undoPos) {
-	wsum_t* temp    = this->temp(), *opt = this->opt();
-	uint32 up       = undoTop_, lev = actLev_;
-	uint32 minLevel = std::max(s.level(tag_.var()), s.level(s.sharedContext()->stepLiteral().var()));
-	// start from current sum
-	assign(temp, sum());
-	// start with full set
-	for (UndoInfo u; up != 0; --up) {
-		u = undo_[up-1];
-		// subtract last element from set
-		STRATEGY(sub(temp, shared_->lits[u.index()], lev));
-		if (!STRATEGY(imp(temp, p, opt, lev))) {
-			// p is no longer implied after we removed last literal,
-			// hence [0, up) implies p @ level of last literal
-			undoPos = up;
-			return std::max(s.level(shared_->lits[u.index()].first.var()), minLevel);
-		}
-	}
-	undoPos = 0;
-	return minLevel;
+uint32_t DefaultMinimize::computeImplicationSet(const Solver& s, const WeightLiteral& p, uint32_t& undoPos) {
+    Wsum_t * temp = this->temp(), *opt = this->opt();
+    uint32_t up = undoTop_, lev = actLev_;
+    uint32_t minLevel = std::max(s.level(tag_.var()), s.level(s.sharedContext()->stepLiteral().var()));
+    // start from current sum
+    assign(temp, sum());
+    // start with full set
+    for (UndoInfo u; up != 0; --up) {
+        u = undo_[up - 1];
+        // subtract last element from set
+        STRATEGY(sub(temp, shared_->lits[u.idx], lev));
+        if (not STRATEGY(imp(temp, p, opt, lev))) {
+            // p is no longer implied after we removed last literal,
+            // hence [0, up) implies p @ level of last literal
+            undoPos = up;
+            return std::max(s.level(shared_->lits[u.idx].lit.var()), minLevel);
+        }
+    }
+    undoPos = 0;
+    return minLevel;
 }
 
 bool DefaultMinimize::propagateImpl(Solver& s, PropMode m) {
-	Iter it    = pos_;
-	uint32 idx = static_cast(it - shared_->lits);
-	uint32 DL  = s.decisionLevel();
-	// current implication level or "unknown" if
-	// we propagate a new optimum
-	uint32 impLevel = DL + (m == propagate_new_opt);
-	weight_t lastW  = -1;
-	uint32 undoPos  = undoTop_;
-	bool ok         = true;
-	actLev_         = std::min(actLev_, shared_->level(idx));
-	for (wsum_t* sum= this->sum(), *opt = this->opt(); ok && !isSentinel(it->first); ++it, ++idx) {
-		// skip propagated/false literals
-		if (litSeen(idx) || (m == propagate_new_sum && s.isFalse(it->first))) {
-			continue;
-		}
-		if (lastW != it->second) {
-			// check if the current weight is implied
-			if (!STRATEGY(imp(sum, *it, opt, actLev_))) {
-				// all good - current optimum is safe
-				pos_    = it;
-				return true;
-			}
-			// compute implication set and level of current weight
-			if (m == propagate_new_opt) {
-				impLevel = computeImplicationSet(s, *it, undoPos);
-			}
-			lastW = it->second;
-		}
-		assert(active());
-		// force implied literals
-		if (!s.isFalse(it->first) || (impLevel < DL && s.level(it->first.var()) > impLevel)) {
-			if (impLevel != DL) { DL = s.undoUntil(impLevel, Solver::undo_pop_bt_level); }
-			ok = s.force(~it->first, impLevel, this, undoPos);
-		}
-	}
-	return ok;
+    Iter     it  = pos_;
+    auto     idx = static_cast(it - shared_->lits);
+    uint32_t dl  = s.decisionLevel();
+    // current implication level or "unknown" if
+    // we propagate a new optimum
+    uint32_t impLevel = dl + (m == propagate_new_opt);
+    Weight_t lastW    = -1;
+    uint32_t undoPos  = undoTop_;
+    bool     ok       = true;
+    actLev_           = std::min(actLev_, shared_->level(idx));
+    for (Wsum_t *sum = this->sum(), *opt = this->opt(); ok && not isSentinel(it->lit); ++it, ++idx) {
+        // skip propagated/false literals
+        if (litSeen(idx) || (m == propagate_new_sum && s.isFalse(it->lit))) {
+            continue;
+        }
+        if (lastW != it->weight) {
+            // check if the current weight is implied
+            if (not STRATEGY(imp(sum, *it, opt, actLev_))) {
+                // all good - current optimum is safe
+                pos_ = it;
+                return true;
+            }
+            // compute implication set and level of current weight
+            if (m == propagate_new_opt) {
+                impLevel = computeImplicationSet(s, *it, undoPos);
+            }
+            lastW = it->weight;
+        }
+        assert(active());
+        // force implied literals
+        if (not s.isFalse(it->lit) || (impLevel < dl && s.level(it->lit.var()) > impLevel)) {
+            if (impLevel != dl) {
+                dl = s.undoUntil(impLevel, Solver::undo_pop_bt_level);
+            }
+            ok = s.force(~it->lit, impLevel, this, undoPos);
+        }
+    }
+    return ok;
 }
 
 // pops free literals from the undo stack and decreases current sum
 void DefaultMinimize::undoLevel(Solver&) {
-	assert(undoTop_ != 0 && posTop_ > undoTop_);
-	uint32 up  = undoTop_;
-	uint32 idx = undo_[--posTop_].index();
-	for (wsum_t* sum = this->sum();;) {
-		UndoInfo& u = undo_[--up];
-		undo_[u.index()].data.idxSeen = 0;
-		STRATEGY(sub(sum, shared_->lits[u.index()], actLev_));
-		if (u.newDL()) { break; }
-	}
-	undoTop_ = up;
-	Iter temp= shared_->lits + idx;
-	if (temp < pos_) {
-		pos_    = temp;
-		actLev_ = std::min(actLev_, shared_->level(idx));
-	}
+    assert(undoTop_ != 0 && posTop_ > undoTop_);
+    uint32_t up  = undoTop_;
+    uint32_t idx = undo_[--posTop_].idx;
+    for (Wsum_t* sum = this->sum();;) {
+        UndoInfo& u          = undo_[--up];
+        undo_[u.idx].idxSeen = 0;
+        STRATEGY(sub(sum, shared_->lits[u.idx], actLev_));
+        if (u.newDL) {
+            break;
+        }
+    }
+    undoTop_ = up;
+    if (Iter temp = shared_->lits + idx; temp < pos_) {
+        pos_    = temp;
+        actLev_ = std::min(actLev_, shared_->level(idx));
+    }
 }
 
 // computes the reason for p -
 // all literals that were propagated before p
 void DefaultMinimize::reason(Solver& s, Literal p, LitVec& lits) {
-	assert(s.isTrue(tag_));
-	uint32 stop = s.reasonData(p);
-	Literal   x = s.sharedContext()->stepLiteral();
-	assert(stop <= undoTop_);
-	if (!isSentinel(x) && s.isTrue(x)) { lits.push_back(x); }
-	if (s.level(tag_.var()))           { lits.push_back(tag_); }
-	for (uint32 i = 0; i != stop; ++i) {
-		UndoInfo u = undo_[i];
-		x = shared_->lits[u.index()].first;
-		lits.push_back(x);
-	}
+    assert(s.isTrue(tag_));
+    Literal x = s.sharedContext()->stepLiteral();
+    if (not isSentinel(x) && s.isTrue(x)) {
+        lits.push_back(x);
+    }
+    if (s.level(tag_.var())) {
+        lits.push_back(tag_);
+    }
+    for (const auto& u : viewUndo(s, p)) {
+        x = shared_->lits[u.idx].lit;
+        lits.push_back(x);
+    }
 }
 
 bool DefaultMinimize::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	assert(s.isTrue(tag_));
-	uint32 stop = s.reasonData(p);
-	Literal   x = s.sharedContext()->stepLiteral();
-	assert(stop <= undoTop_);
-	if (!s.ccMinimize(x, rec) || !s.ccMinimize(tag_, rec)) { return false; }
-	for (uint32 i = 0; i != stop; ++i) {
-		UndoInfo u = undo_[i];
-		x = shared_->lits[u.index()].first;
-		if (!s.ccMinimize(x, rec)) {
-			return false;
-		}
-	}
-	return true;
+    assert(s.isTrue(tag_));
+    Literal x = s.sharedContext()->stepLiteral();
+    if (not s.ccMinimize(x, rec) || not s.ccMinimize(tag_, rec)) {
+        return false;
+    }
+    for (const auto& u : viewUndo(s, p)) {
+        x = shared_->lits[u.idx].lit;
+        if (not s.ccMinimize(x, rec)) {
+            return false;
+        }
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultMinimize - bound management
 /////////////////////////////////////////////////////////////////////////////////////////
 // Stores the current sum as the shared optimum.
-void DefaultMinimize::commitUpperBound(const Solver&)  {
-	shared_->setOptimum(sum());
-	if (step_.type == OptParams::bb_inc) { step_.size *= 2; }
-}
-bool DefaultMinimize::commitLowerBound(Solver& s, bool upShared) {
-	bool act  = active() && shared_->checkNext();
-	bool more = step_.lev < size_ && (step_.size > 1 || step_.lev != size_-1);
-	if (act && step_.type && step_.lev < size_) {
-		uint32 x = step_.lev;
-		wsum_t L = opt()[x] + 1;
-		if (upShared) {
-			wsum_t sv = shared_->incLower(x, L);
-			if (sv == L) { reportLower(s, x, sv); }
-			else         { L = sv; }
-		}
-		stepLow()= L;
-		if (step_.type == OptParams::bb_inc){ step_.size = 1; }
-	}
-	return more;
+void DefaultMinimize::commitUpperBound(const Solver&) {
+    shared_->setOptimum(sum());
+    if (step_.type == OptParams::bb_inc) {
+        step_.size *= 2;
+    }
+}
+bool DefaultMinimize::commitLowerBound(Solver&, bool upShared) {
+    bool act  = active() && shared_->checkNext();
+    bool more = step_.lev < size_ && (step_.size > 1 || step_.lev != size_ - 1);
+    if (act && step_.type && step_.lev < size_) {
+        uint32_t x = step_.lev;
+        Wsum_t   l = opt()[x] + 1;
+        if (upShared) {
+            if (Wsum_t sv = shared_->incLower(x, l); sv != l) {
+                l = sv;
+            }
+        }
+        stepLow() = l;
+        if (step_.type == OptParams::bb_inc) {
+            step_.size = 1;
+        }
+    }
+    return more;
 }
 bool DefaultMinimize::handleUnsat(Solver& s, bool up, LitVec& out) {
-	bool more = shared_->optimize() && commitLowerBound(s, up);
-	uint32 dl = s.isTrue(tag_) ? s.level(tag_.var()) : 0;
-	relaxBound(false);
-	if (more && dl && dl <= s.rootLevel()) {
-		s.popRootLevel(s.rootLevel()-dl, &out); // pop and remember new path
-		return s.popRootLevel(1);               // pop tag - disable constraint
-	}
-	return false;
+    bool     more = shared_->optimize() && commitLowerBound(s, up);
+    uint32_t dl   = s.isTrue(tag_) ? s.level(tag_.var()) : 0;
+    relaxBound(false);
+    if (more && dl && dl <= s.rootLevel()) {
+        s.popRootLevel(s.rootLevel() - dl, &out); // pop and remember new path
+        return s.popRootLevel(1);                 // pop tag - disable constraint
+    }
+    return false;
 }
 
 // Disables the minimize constraint by clearing its upper bound.
 bool DefaultMinimize::relaxBound(bool full) {
-	if (active()) { std::fill(opt(), opt()+size_, SharedData::maxBound()); }
-	pos_       = shared_->lits;
-	actLev_    = 0;
-	if (full || !shared_->optimize()) { stepInit(0); }
-	return true;
+    if (active()) {
+        std::fill_n(opt(), size_, SharedData::maxBound());
+    }
+    pos_    = shared_->lits;
+    actLev_ = 0;
+    if (full || not shared_->optimize()) {
+        stepInit(0);
+    }
+    return true;
 }
 
-void DefaultMinimize::stepInit(uint32 n) {
-	step_.size = uint32(step_.type != OptParams::bb_dec);
-	if (step_.type) { step_.lev = n; if (n != size_) stepLow() = 0-shared_->maxBound(); }
-	else            { step_.lev = shared_->maxLevel(); }
+void DefaultMinimize::stepInit(uint32_t n) {
+    step_.size = static_cast(step_.type != OptParams::bb_dec);
+    if (step_.type) {
+        step_.lev = n;
+        if (n != size_) {
+            stepLow() = 0 - SharedMinimizeData::maxBound();
+        }
+    }
+    else {
+        step_.lev = shared_->maxLevel();
+    }
 }
 
 // Integrates new (tentative) bounds from the ones stored in the shared data object.
 bool DefaultMinimize::integrateBound(Solver& s) {
-	bool useTag = shared_->optimize() && (step_.type != 0 || shared_->mode() == MinimizeMode_t::enumOpt);
-	if (!prepare(s, useTag)) { return false; }
-	if (useTag && s.level(tag_.var()) == 0) {
-		step_.type = 0;
-		stepInit(0);
-	}
-	if ((active() && !shared_->checkNext())) { return !s.hasConflict(); }
-	WeightLiteral min(lit_true(), shared_->weights.empty() ? uint32(0) : (uint32)shared_->weights.size()-1);
-	while (!s.hasConflict() && updateBounds(shared_->checkNext())) {
-		uint32 x = 0;
-		uint32 dl= s.decisionLevel() + 1;
-		if (!STRATEGY(imp(sum(), min, opt(), actLev_)) || (dl = computeImplicationSet(s, min, x)) > s.rootLevel()) {
-			for (--dl; !s.hasConflict() || s.resolveConflict(); ) {
-				if      (s.undoUntil(dl, Solver::undo_pop_bt_level) > dl){ s.backtrack(); }
-				else if (propagateImpl(s, propagate_new_opt))            { return true;   }
-			}
-		}
-		if (!shared_->checkNext()) {
-			break;
-		}
-		if (!step_.type) { ++step_.lev; }
-		else             { stepLow() = ++opt()[step_.lev]; }
-	}
-	relaxBound(false);
-	if (!s.hasConflict()) {
-		s.undoUntil(0);
-		s.setStopConflict();
-	}
-	return false;
+    bool useTag = shared_->optimize() && (step_.type != 0 || shared_->mode() == MinimizeMode::enum_opt);
+    if (not prepare(s, useTag)) {
+        return false;
+    }
+    if (useTag && s.level(tag_.var()) == 0) {
+        step_.type = 0;
+        stepInit(0);
+    }
+    if ((active() && not shared_->checkNext())) {
+        return not s.hasConflict();
+    }
+
+    WeightLiteral min{lit_true, static_cast(shared_->weights.empty() ? 0u : size32(shared_->weights) - 1u)};
+    while (not s.hasConflict() && updateBounds(shared_->checkNext())) {
+        uint32_t x  = 0;
+        uint32_t dl = s.decisionLevel() + 1;
+        if (not STRATEGY(imp(sum(), min, opt(), actLev_)) || (dl = computeImplicationSet(s, min, x)) > s.rootLevel()) {
+            for (--dl; not s.hasConflict() || s.resolveConflict();) {
+                if (s.undoUntil(dl, Solver::undo_pop_bt_level) > dl) {
+                    s.backtrack();
+                }
+                else if (propagateImpl(s, propagate_new_opt)) {
+                    return true;
+                }
+            }
+        }
+        if (not shared_->checkNext()) {
+            break;
+        }
+        if (not step_.type) {
+            ++step_.lev;
+        }
+        else {
+            stepLow() = ++opt()[step_.lev];
+        }
+    }
+    relaxBound(false);
+    if (not s.hasConflict()) {
+        s.undoUntil(0);
+        s.setStopConflict();
+    }
+    return false;
 }
 
 // Loads bounds from the shared data object and (optionally) applies
 // the current step to get the next tentative bound to check.
 bool DefaultMinimize::updateBounds(bool applyStep) {
-	for (;;) {
-		const uint32  seq   = shared_->generation();
-		const wsum_t* upper = shared_->upper();
-		wsum_t*       myLow = step_.type ? end() : 0;
-		wsum_t*       bound = opt();
-		uint32        appLev= applyStep ? step_.lev : size_;
-		for (uint32 i = 0; i != size_; ++i) {
-			wsum_t U = upper[i], B = bound[i];
-			if (i != appLev) {
-				wsum_t L = shared_->lower(i);
-				if (myLow) {
-					if (i > step_.lev || L > myLow[i]) { myLow[i] = L; }
-					else                               { L = myLow[i]; }
-				}
-				if      (i > appLev) { bound[i] = SharedData::maxBound(); }
-				else if (U >= L)     { bound[i] = U; }
-				else                 { stepInit(size_); return false; }
-				continue;
-			}
-			if (step_.type) {
-				wsum_t L = (stepLow() = std::max(myLow[i], shared_->lower(i)));
-				if (U < L) { // path exhausted?
-					stepInit(size_);
-					return false;
-				}
-				if (B < L) { // tentative bound too strong?
-					return false;
-				}
-				if (B < U) { // existing step?
-					assert(std::count(bound+i+1, bound+size_, SharedData::maxBound()) == int(size_ - (i+1)));
-					return true;
-				}
-				if (U == L) {// done with current level?
-					bound[i] = U;
-					stepInit(++appLev);
-					continue;
-				}
-				assert(U > L && B > L);
-				wsum_t diff = U - L;
-				uint32 half = static_cast( (diff>>1) | (diff&1) );
-				if (step_.type == OptParams::bb_inc) {
-					step_.size = std::min(step_.size, half);
-				}
-				else if (step_.type == OptParams::bb_dec) {
-					if (!step_.size) { step_.size = static_cast(diff); }
-					else             { step_.size = half; }
-				}
-			}
-			assert(step_.size != 0);
-			bound[i] = U - step_.size;
-			actLev_  = 0;
-			pos_     = shared_->lits;
-		}
-		if (seq == shared_->generation()) { break; }
-	}
-	return step_.lev != size_ || !applyStep;
+    for (;;) {
+        const uint32_t seq    = shared_->generation();
+        const Wsum_t*  upper  = shared_->upper();
+        Wsum_t*        myLow  = step_.type ? end() : nullptr;
+        Wsum_t*        bound  = opt();
+        uint32_t       appLev = applyStep ? step_.lev : size_;
+        for (uint32_t i : irange(size_)) {
+            Wsum_t u = upper[i], b = bound[i];
+            if (i != appLev) {
+                Wsum_t l = shared_->lower(i);
+                if (myLow) {
+                    if (i > step_.lev || l > myLow[i]) {
+                        myLow[i] = l;
+                    }
+                    else {
+                        l = myLow[i];
+                    }
+                }
+                if (i > appLev) {
+                    bound[i] = SharedData::maxBound();
+                }
+                else if (u >= l) {
+                    bound[i] = u;
+                }
+                else {
+                    stepInit(size_);
+                    return false;
+                }
+                continue;
+            }
+            if (step_.type) {
+                Wsum_t l = (stepLow() = std::max(myLow[i], shared_->lower(i)));
+                if (u < l) { // path exhausted?
+                    stepInit(size_);
+                    return false;
+                }
+                if (b < l) { // tentative bound too strong?
+                    return false;
+                }
+                if (b < u) { // existing step?
+                    assert(std::count(bound + i + 1, bound + size_, SharedData::maxBound()) ==
+                           static_cast(size_ - (i + 1)));
+                    return true;
+                }
+                if (u == l) { // done with current level?
+                    bound[i] = u;
+                    stepInit(++appLev);
+                    continue;
+                }
+                assert(u > l && b > l);
+                Wsum_t diff = u - l;
+                auto   half = static_cast((diff >> 1) | (diff & 1));
+                if (step_.type == OptParams::bb_inc) {
+                    step_.size = std::min(step_.size, half);
+                }
+                else if (step_.type == OptParams::bb_dec) {
+                    if (not step_.size) {
+                        step_.size = static_cast(diff);
+                    }
+                    else {
+                        step_.size = half;
+                    }
+                }
+            }
+            assert(step_.size != 0);
+            bound[i] = u - step_.size;
+            actLev_  = 0;
+            pos_     = shared_->lits;
+        }
+        if (seq == shared_->generation()) {
+            break;
+        }
+    }
+    return step_.lev != size_ || not applyStep;
 }
 #undef STRATEGY
 /////////////////////////////////////////////////////////////////////////////////////////
 // MinimizeBuilder
 /////////////////////////////////////////////////////////////////////////////////////////
-MinimizeBuilder::MinimizeBuilder() { }
-void MinimizeBuilder::clear() {
-	LitVec().swap(lits_);
+static inline auto lw(const SharedMinimizeData::WeightVec& weights, Weight_t pos) {
+    assert(pos >= 0 && static_cast(pos) < weights.size());
+    return &weights[static_cast(pos)];
 }
-bool MinimizeBuilder::empty() const {
-	return lits_.empty();
+static inline auto lw(const SharedMinimizeData::WeightVec& weights, const WeightLiteral& wl) {
+    return lw(weights, wl.weight);
 }
-MinimizeBuilder& MinimizeBuilder::add(weight_t prio, const WeightLitVec& lits) {
-	for (WeightLitVec::const_iterator it = lits.begin(), end = lits.end(); it != end; ++it) {
-		add(prio, *it);
-	}
-	return *this;
+MinimizeBuilder::MinimizeBuilder() = default;
+void             MinimizeBuilder::clear() { discardVec(lits_); }
+bool             MinimizeBuilder::empty() const { return lits_.empty(); }
+MinimizeBuilder& MinimizeBuilder::add(Weight_t prio, WeightLitView lits) {
+    for (const auto& lit : lits) { add(prio, lit); }
+    return *this;
 }
-MinimizeBuilder& MinimizeBuilder::add(weight_t prio, WeightLiteral lit) {
-	lits_.push_back(MLit(lit, prio));
-	return *this;
+MinimizeBuilder& MinimizeBuilder::add(Weight_t prio, WeightLiteral lit) {
+    lits_.push_back(MLit(lit, prio));
+    return *this;
 }
-MinimizeBuilder& MinimizeBuilder::add(weight_t prio, weight_t w) {
-	lits_.push_back(MLit(WeightLiteral(lit_true(), w), prio));
-	return *this;
+MinimizeBuilder& MinimizeBuilder::add(Weight_t prio, Weight_t w) {
+    lits_.push_back(MLit(WeightLiteral{lit_true, w}, prio));
+    return *this;
 }
 MinimizeBuilder& MinimizeBuilder::add(const SharedData& con) {
-	if (con.numRules() == 1) {
-		const weight_t P = !con.prios.empty() ? con.prios[0] : 0;
-		for (const WeightLiteral* it = con.lits; !isSentinel(it->first); ++it) { add(P, *it); }
-	}
-	else {
-		for (const WeightLiteral* it = con.lits; !isSentinel(it->first); ++it) {
-			const SharedData::LevelWeight* w = &con.weights[it->second];
-			do {
-				add( w->level < con.prios.size() ? con.prios[w->level] : -static_cast(w->level), WeightLiteral(it->first, w->weight) );
-			} while (w++->next);
-		}
-	}
-	for (uint32 i = 0; i != con.numRules(); ++i) {
-		if (wsum_t w = con.adjust(i)) {
-			const weight_t P = i < con.prios.size() ? con.prios[i] : -static_cast(i);
-			while (w < CLASP_WEIGHT_T_MIN) { add(P, CLASP_WEIGHT_T_MIN); w -= CLASP_WEIGHT_T_MIN; }
-			while (w > CLASP_WEIGHT_T_MAX) { add(P, CLASP_WEIGHT_T_MAX); w -= CLASP_WEIGHT_T_MAX; }
-			add(P, static_cast(w));
-		}
-	}
-	return *this;
-}
-// Comparator for preparing levels: compare (prio, var, weight)
-bool MinimizeBuilder::CmpPrio::operator()(const MLit& lhs, const MLit& rhs) const {
-	if (lhs.prio != rhs.prio)           { return lhs.prio > rhs.prio; }
-	if (lhs.lit.var() != rhs.lit.var()) { return lhs.lit  < rhs.lit; }
-	return lhs.weight > rhs.weight;
-}
-// Comparator for merging levels: compare (var, level, weight)
-bool MinimizeBuilder::CmpLit::operator()(const MLit& lhs, const MLit& rhs) const {
-	if (lhs.lit.var() != rhs.lit.var()) { return lhs.lit  < rhs.lit; }
-	if (lhs.prio != rhs.prio)           { return lhs.prio < rhs.prio; }
-	return lhs.weight > rhs.weight;
-}
-// Comparator for sorting literals by final weight
-bool MinimizeBuilder::CmpWeight::operator()(const MLit& lhs, const MLit& rhs) const {
-	if (!weights) { return lhs.weight > rhs.weight; }
-	const SharedData::LevelWeight* wLhs = &(*weights)[lhs.weight];
-	const SharedData::LevelWeight* wRhs = &(*weights)[rhs.weight];
-	for (;; ++wLhs, ++wRhs) {
-		if (wLhs->level != wRhs->level)  {
-			return wLhs->level < wRhs->level ? wLhs->weight > 0 : 0 > wRhs->weight;
-		}
-		if (wLhs->weight != wRhs->weight){ return wLhs->weight > wRhs->weight; }
-		if (!wLhs->next) { return wRhs->next && (++wRhs)->weight < 0; }
-		if (!wRhs->next) { ++wLhs; break; }
-	}
-	return wLhs->weight > 0;
+    if (con.numRules() == 1) {
+        const Weight_t p = not con.prios.empty() ? con.prios[0] : 0;
+        for (const auto& wl : con) { add(p, wl); }
+    }
+    else {
+        for (const auto& wl : con) {
+            const auto* w = lw(con.weights, wl);
+            do {
+                add(w->level < con.prios.size() ? con.prios[w->level] : -static_cast(w->level),
+                    WeightLiteral{wl.lit, w->weight});
+            } while (w++->next);
+        }
+    }
+    for (uint32_t i : irange(con.numRules())) {
+        if (Wsum_t w = con.adjust(i)) {
+            const Weight_t p = i < con.prios.size() ? con.prios[i] : -static_cast(i);
+            while (w < weight_min) {
+                add(p, weight_min);
+                w -= weight_min;
+            }
+            while (w > weight_max) {
+                add(p, weight_max);
+                w -= weight_max;
+            }
+            add(p, static_cast(w));
+        }
+    }
+    return *this;
 }
 
 // Replaces integer priorities with increasing levels and merges duplicate/complementary literals.
 void MinimizeBuilder::prepareLevels(const Solver& s, SumVec& adjust, WeightVec& prios) {
-	// group first by decreasing priorities and then by variables
-	std::stable_sort(lits_.begin(), lits_.end(), CmpPrio());
-	prios.clear(); adjust.clear();
-	// assign levels and simplify lits
-	LitVec::iterator j = lits_.begin();
-	for (LitVec::const_iterator it = lits_.begin(), end = lits_.end(); it != end;) {
-		const weight_t P = it->prio, L = static_cast(prios.size());
-		wsum_t R = 0;
-		for (LitVec::const_iterator k; it != end && it->prio == P; it = k) {
-			Literal x(it->lit); // make literal unique wrt this level
-			wsum_t  w = it->weight;
-			for (k = it + 1; k != end && k->lit.var() == x.var() && k->prio == P; ++k) {
-				if (k->lit == x){ w += k->weight; }
-				else            { w -= k->weight; R += k->weight; }
-			}
-			if (w < 0){
-				R += w;
-				x  = ~x;
-				w  = -w;
-			}
-			if (w && s.value(x.var()) == value_free) {
-				POTASSCO_CHECK(static_cast(w) == w, EOVERFLOW, "MinimizeBuilder: weight too large");
-				*j++ = MLit(WeightLiteral(x, static_cast(w)), L);
-			}
-			else if (s.isTrue(x)) { R += w; }
-		}
-		prios.push_back(P);
-		adjust.push_back(R);
-	}
-	lits_.erase(j, lits_.end());
+    // group first by decreasing priorities and then by variables, i.e. compare (prio, var, weight)
+    std::ranges::stable_sort(lits_, [](const MLit& lhs, const MLit& rhs) {
+        if (lhs.prio != rhs.prio) {
+            return lhs.prio > rhs.prio;
+        }
+        if (lhs.lit.var() != rhs.lit.var()) {
+            return lhs.lit < rhs.lit;
+        }
+        return lhs.weight > rhs.weight;
+    });
+    prios.clear();
+    adjust.clear();
+    // assign levels and simplify lits
+    auto j = lits_.begin();
+    for (LitVec::const_iterator it = lits_.begin(), end = lits_.end(); it != end;) {
+        const Weight_t p = it->prio;
+        Wsum_t         r = 0;
+        for (LitVec::const_iterator k; it != end && it->prio == p; it = k) {
+            Literal x(it->lit); // make literal unique wrt this level
+            Wsum_t  w = it->weight;
+            for (k = it + 1; k != end && k->lit.var() == x.var() && k->prio == p; ++k) {
+                if (k->lit == x) {
+                    w += k->weight;
+                }
+                else {
+                    w -= k->weight;
+                    r += k->weight;
+                }
+            }
+            if (w < 0) {
+                r += w;
+                x  = ~x;
+                w  = -w;
+            }
+            if (w && s.value(x.var()) == value_free) {
+                POTASSCO_CHECK(static_cast(w) == w, EOVERFLOW, "MinimizeBuilder: weight too large");
+                *j++ = MLit(WeightLiteral{x, static_cast(w)}, static_cast(prios.size()));
+            }
+            else if (s.isTrue(x)) {
+                r += w;
+            }
+        }
+        prios.push_back(p);
+        adjust.push_back(r);
+    }
+    lits_.erase(j, lits_.end());
 }
 
 void MinimizeBuilder::mergeLevels(SumVec& adjust, SharedData::WeightVec& weights) {
-	// group first by variables and then by increasing levels
-	std::stable_sort(lits_.begin(), lits_.end(), CmpLit());
-	LitVec::iterator j = lits_.begin();
-	weights.clear(); weights.reserve(lits_.size());
-	for (LitVec::const_iterator it = lits_.begin(), end = lits_.end(), k; it != end; it = k) {
-		// handle first occurrence of var
-		assert(it->weight > 0 && "most important occurrence of lit must have positive weight");
-		weight_t wpos = (weight_t)weights.size();
-		weights.push_back(SharedData::LevelWeight(it->prio, it->weight));
-		// handle remaining occurrences with lower prios
-		for (k = it + 1; k != end && k->lit.var() == it->lit.var(); ++k) {
-			assert(k->prio > it->prio && "levels not prepared!");
-			weights.back().next = 1;
-			weights.push_back(SharedData::LevelWeight(k->prio, k->weight));
-			if (k->lit.sign() != it->lit.sign()) {
-				adjust[k->prio] += k->weight;
-				weights.back().weight = -k->weight;
-			}
-		}
-		(*j = *it).weight = wpos;
-		++j;
-	}
-	lits_.erase(j, lits_.end());
+    // group first by variables and then by increasing levels, i.e. compare (var, level, weight)
+    std::ranges::stable_sort(lits_, [](const MLit& lhs, const MLit& rhs) {
+        if (lhs.lit.var() != rhs.lit.var()) {
+            return lhs.lit < rhs.lit;
+        }
+        if (lhs.prio != rhs.prio) {
+            return lhs.prio < rhs.prio;
+        }
+        return lhs.weight > rhs.weight;
+    });
+    LitVec::iterator j = lits_.begin();
+    weights.clear();
+    weights.reserve(lits_.size());
+    for (LitVec::const_iterator it = lits_.begin(), end = lits_.end(), k; it != end; it = k) {
+        // handle first occurrence of var
+        assert(it->weight > 0 && "most important occurrence of lit must have positive weight");
+        assert(it->prio >= 0 && "levels not prepared!");
+        auto wpos = static_cast(weights.size());
+        weights.push_back(SharedData::LevelWeight(static_cast(it->prio), it->weight));
+        // handle remaining occurrences with lower prios
+        for (k = it + 1; k != end && k->lit.var() == it->lit.var(); ++k) {
+            assert(k->prio > it->prio && "levels not prepared!");
+            auto kLev           = static_cast(k->prio);
+            weights.back().next = 1;
+            weights.push_back(SharedData::LevelWeight(kLev, k->weight));
+            if (k->lit.sign() != it->lit.sign()) {
+                adjust[kLev]          += k->weight;
+                weights.back().weight  = -k->weight;
+            }
+        }
+        (*j = *it).weight = wpos;
+        ++j;
+    }
+    lits_.erase(j, lits_.end());
 }
 
-MinimizeBuilder::SharedData* MinimizeBuilder::createShared(SharedContext& ctx, const SumVec& adjust, const CmpWeight& cmp) {
-	const uint32 nLits = static_cast(lits_.size());
-	SharedData*  ret   = new (::operator new(sizeof(SharedData) + ((nLits + 1)*sizeof(WeightLiteral)))) SharedData(adjust);
-	// sort literals by decreasing weight
-	std::stable_sort(lits_.begin(), lits_.end(), cmp);
-	uint32   last = 0;
-	weight_t wIdx = 0;
-	for (uint32 i = 0; i != nLits; ++i) {
-		WeightLiteral x(lits_[i].lit, lits_[i].weight);
-		ctx.setFrozen(x.first.var(), true);
-		ret->lits[i] = x;
-		if (!cmp.weights) { continue; }
-		if (!i || cmp(lits_[last], lits_[i])) {
-			last = i;
-			wIdx = (weight_t)ret->weights.size();
-			for (const SharedData::LevelWeight* w = &(*cmp.weights)[x.second];; ++w) {
-				ret->weights.push_back(*w);
-				if (!w->next) { break; }
-			}
-		}
-		ret->lits[i].second = wIdx;
-	}
-	ret->lits[nLits] = WeightLiteral(lit_true(), (weight_t)ret->weights.size());
-	if (cmp.weights) {
-		ret->weights.push_back(SharedData::LevelWeight((uint32)adjust.size()-1, 0));
-	}
-	ret->resetBounds();
-	return ret;
+MinimizeBuilder::SharedData* MinimizeBuilder::createShared(SharedContext& ctx, SumView adjust,
+                                                           const SharedData::WeightVec* weights) {
+    const auto nLits = size32(lits_);
+    auto* ret = new (::operator new(sizeof(SharedData) + ((nLits + 1) * sizeof(WeightLiteral)))) SharedData(adjust);
+    // sort literals by decreasing weight
+    auto cmp = [weights](const MLit& lhs, const MLit& rhs) {
+        if (not weights) {
+            return lhs.weight > rhs.weight;
+        }
+        const auto* wLhs = lw(*weights, lhs.weight);
+        const auto* wRhs = lw(*weights, rhs.weight);
+        for (;; ++wLhs, ++wRhs) {
+            if (wLhs->level != wRhs->level) {
+                return wLhs->level < wRhs->level ? wLhs->weight > 0 : 0 > wRhs->weight;
+            }
+            if (wLhs->weight != wRhs->weight) {
+                return wLhs->weight > wRhs->weight;
+            }
+            if (not wLhs->next) {
+                return wRhs->next && (++wRhs)->weight < 0;
+            }
+            if (not wRhs->next) {
+                ++wLhs;
+                break;
+            }
+        }
+        return wLhs->weight > 0;
+    };
+    std::ranges::stable_sort(lits_, cmp);
+    WeightLiteral* out  = ret->lits;
+    const MLit*    last = nullptr;
+    Weight_t       wIdx = 0;
+    for (const auto& lit : lits_) {
+        WeightLiteral x{lit.lit, lit.weight};
+        ctx.setFrozen(x.lit.var(), true);
+        if (weights) {
+            if (not last || cmp(*last, lit)) {
+                last = &lit;
+                wIdx = static_cast(ret->weights.size());
+                for (const auto* w = lw(*weights, x);; ++w) {
+                    ret->weights.push_back(*w);
+                    if (not w->next) {
+                        break;
+                    }
+                }
+            }
+            x.weight = wIdx;
+        }
+        *out++ = x;
+    }
+    ret->lits[nLits] = WeightLiteral{lit_true, static_cast(ret->weights.size())};
+    if (weights) {
+        ret->weights.push_back(SharedData::LevelWeight(size32(adjust) - 1, 0));
+    }
+    ret->resetBounds();
+    return ret;
 }
 
 MinimizeBuilder::SharedData* MinimizeBuilder::build(SharedContext& ctx) {
-	POTASSCO_REQUIRE(!ctx.frozen());
-	if (!ctx.ok() || (ctx.master()->acquireProblemVars(), !ctx.master()->propagate()) || empty()) {
-		clear();
-		return 0;
-	}
-	typedef SharedData::WeightVec FlatVec;
-	WeightVec prios;
-	SumVec    adjust;
-	FlatVec   weights;
-	CmpWeight cmp(0);
-	prepareLevels(*ctx.master(), adjust, prios);
-	if (prios.size() > 1) {
-		mergeLevels(adjust, weights);
-		cmp.weights = &weights;
-	}
-	else if (prios.empty()) {
-		prios.assign(1, 0);
-		adjust.assign(1, 0);
-	}
-	SharedData* ret = createShared(ctx, adjust, cmp);
-	ret->prios.swap(prios);
-	clear();
-	return ret;
+    POTASSCO_CHECK_PRE(not ctx.frozen());
+    if (not ctx.ok() || (ctx.master()->acquireProblemVars(), not ctx.master()->propagate()) || empty()) {
+        clear();
+        return nullptr;
+    }
+    using FlatVec = SharedData::WeightVec;
+    WeightVec prios;
+    SumVec    adjust;
+    FlatVec   weights;
+    prepareLevels(*ctx.master(), adjust, prios);
+    if (prios.size() > 1) {
+        mergeLevels(adjust, weights);
+    }
+    else if (prios.empty()) {
+        prios.assign(1, 0);
+        adjust.assign(1, 0);
+    }
+    auto* ret = createShared(ctx, adjust, prios.size() > 1 ? &weights : nullptr);
+    ret->prios.swap(prios);
+    clear();
+    return ret;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // UncoreMinimize
 /////////////////////////////////////////////////////////////////////////////////////////
 UncoreMinimize::UncoreMinimize(SharedMinimizeData* d, const OptParams& params)
-	: MinimizeConstraint(d)
-	, enum_(0)
-	, sum_(new wsum_t[d->numRules()])
-	, auxInit_(UINT32_MAX)
-	, auxAdd_(0)
-	, freeOpen_(0)
-	, options_(params) {
-}
+    : MinimizeConstraint(d)
+    , enum_(nullptr)
+    , sum_(std::make_unique(d->numRules()))
+    , auxInit_(UINT32_MAX)
+    , auxAdd_(0)
+    , freeOpen_(0)
+    , options_(params) {}
 void UncoreMinimize::init() {
-	releaseLits();
-	fix_.clear();
-	eRoot_ = 0;
-	aTop_  = 0;
-	upper_ = shared_->upper(0);
-	lower_ = 0;
-	gen_   = 0;
-	level_ = 0;
-	next_  = 0;
-	disj_  = 0;
-	path_  = 1;
-	init_  = 1;
-	actW_  = 1;
-	nextW_ = 0;
+    releaseLits();
+    fix_.clear();
+    eRoot_ = 0;
+    aTop_  = 0;
+    upper_ = shared_->upper(0);
+    lower_ = 0;
+    gen_   = 0;
+    level_ = 0;
+    next_  = 0;
+    disj_  = 0;
+    path_  = 1;
+    init_  = 1;
+    actW_  = 1;
+    nextW_ = 0;
 }
 bool UncoreMinimize::attach(Solver& s) {
-	init();
-	initRoot(s);
-	auxInit_ = UINT32_MAX;
-	auxAdd_  = 0;
-	if (s.sharedContext()->concurrency() > 1 && shared_->mode() == MinimizeMode_t::enumOpt) {
-		enum_ = new DefaultMinimize(shared_->share(), OptParams());
-		enum_->attach(s);
-		enum_->relaxBound(true);
-	}
-	return true;
+    init();
+    initRoot(s);
+    auxInit_ = UINT32_MAX;
+    auxAdd_  = 0;
+    if (s.sharedContext()->concurrency() > 1 && shared_->mode() == MinimizeMode::enum_opt) {
+        enum_ = new DefaultMinimize(shared_->share(), OptParams());
+        enum_->attach(s);
+        enum_->relaxBound(true);
+    }
+    return true;
 }
 // Detaches the constraint and all cores from s and removes
 // any introduced aux vars.
 void UncoreMinimize::detach(Solver* s, bool b) {
-	releaseLits();
-	if (s && auxAdd_ && s->numAuxVars() == (auxInit_ + auxAdd_)) {
-		s->popAuxVar(auxAdd_, &closed_);
-		auxInit_ = UINT32_MAX;
-		auxAdd_  = 0;
-	}
-	Clasp::destroyDB(closed_, s, b);
-	fix_.clear();
+    releaseLits();
+    if (s && auxAdd_ && s->numAuxVars() == (auxInit_ + auxAdd_)) {
+        s->popAuxVar(auxAdd_, &closed_);
+        auxInit_ = UINT32_MAX;
+        auxAdd_  = 0;
+    }
+    Clasp::destroyDB(closed_, s, b);
+    fix_.clear();
 }
 // Destroys this object and optionally detaches it from the given solver.
 void UncoreMinimize::destroy(Solver* s, bool b) {
-	detach(s, b);
-	delete [] sum_;
-	if (enum_) { enum_->destroy(s, b); enum_ = 0; }
-	MinimizeConstraint::destroy(s, b);
+    detach(s, b);
+    sum_.reset();
+    if (enum_) {
+        enum_->destroy(s, b);
+        enum_ = nullptr;
+    }
+    MinimizeConstraint::destroy(s, b);
 }
-Constraint::PropResult UncoreMinimize::propagate(Solver& s, Literal p, uint32& other) {
-	return PropResult(s.force(Literal::fromId(other), Antecedent(p)), true);
+Constraint::PropResult UncoreMinimize::propagate(Solver& s, Literal p, uint32_t& other) {
+    return PropResult(s.force(Literal::fromId(other), Antecedent(p)), true);
 }
 bool UncoreMinimize::simplify(Solver& s, bool) {
-	if (s.decisionLevel() == 0) { simplifyDB(s, closed_, false); }
-	return false;
+    if (s.decisionLevel() == 0) {
+        simplifyDB(s, closed_, false);
+    }
+    return false;
 }
 void UncoreMinimize::reason(Solver& s, Literal, LitVec& out) {
-	uint32 r = initRoot(s);
-	for (uint32 i = 1; i <= r; ++i) {
-		out.push_back(s.decision(i));
-	}
+    uint32_t r = initRoot(s);
+    for (uint32_t i = 1; i <= r; ++i) { out.push_back(s.decision(i)); }
 }
 
 // Integrates any new information from this constraint into s.
 bool UncoreMinimize::integrate(Solver& s) {
-	assert(!s.isFalse(tag_));
-	bool useTag = (shared_->mode() == MinimizeMode_t::enumOpt) || s.sharedContext()->concurrency() > 1;
-	if (!prepare(s, useTag)) {
-		return false;
-	}
-	if (enum_ && !shared_->optimize() && !enum_->integrateBound(s)) {
-		return false;
-	}
-	for (uint32 gGen = shared_->generation(); gGen != gen_; ) {
-		gen_   = gGen;
-		upper_ = shared_->upper(level_);
-		gGen   = shared_->generation();
-	}
-	if (init_ && !initLevel(s)) {
-		return false;
-	}
-	if (next_ && !addNext(s)) {
-		return false;
-	}
-	if (path_ && !pushPath(s)) {
-		return false;
-	}
-	if (!validLowerBound()) {
-		next_ = 1;
-		s.setStopConflict();
-		return false;
-	}
-	return true;
+    assert(not s.isFalse(tag_));
+    bool useTag = (shared_->mode() == MinimizeMode::enum_opt) || s.sharedContext()->concurrency() > 1;
+    if (not prepare(s, useTag)) {
+        return false;
+    }
+    if (enum_ && not shared_->optimize() && not enum_->integrateBound(s)) {
+        return false;
+    }
+    for (uint32_t gGen = shared_->generation(); gGen != gen_;) {
+        gen_   = gGen;
+        upper_ = shared_->upper(level_);
+        gGen   = shared_->generation();
+    }
+    if (init_ && not initLevel(s)) {
+        return false;
+    }
+    if (next_ && not addNext(s)) {
+        return false;
+    }
+    if (path_ && not pushPath(s)) {
+        return false;
+    }
+    if (not validLowerBound()) {
+        next_ = 1;
+        s.setStopConflict();
+        return false;
+    }
+    return true;
 }
 
 // Initializes the next optimization level to look at.
 bool UncoreMinimize::initLevel(Solver& s) {
-	initRoot(s);
-	next_  = 0;
-	disj_  = 0;
-	actW_  = 1;
-	nextW_ = 0;
-	*sum_  = -1;
-	if (!fixLevel(s)) {
-		return false;
-	}
-	for (LitVec::const_iterator it = fix_.begin(), end = fix_.end(); it != end; ++it) {
-		if (!s.force(*it, eRoot_, this)) { return false; }
-	}
-	if (!shared_->optimize()) {
-		level_ = shared_->maxLevel();
-		lower_ = shared_->lower(level_);
-		upper_ = shared_->upper(level_);
-		path_  = 0;
-		init_  = 0;
-		return true;
-	}
-	weight_t maxW = 1;
-	uint32_t next = 1 - init_;
-	for (uint32 level = (level_ + next), n = 1; level <= shared_->maxLevel() && assume_.empty(); ++level) {
-		level_ = level;
-		lower_ = 0;
-		upper_ = shared_->upper(level_);
-		for (const WeightLiteral* it = shared_->lits; !isSentinel(it->first); ++it) {
-			if (weight_t w = shared_->weight(*it, level_)){
-				Literal x = it->first;
-				if (w < 0) {
-					lower_ += w;
-					w       = -w;
-					x       = ~x;
-				}
-				if (s.value(x.var()) == value_free || s.level(x.var()) > eRoot_) {
-					newAssumption(~x, w);
-					if (w > maxW){ maxW = w; }
-				}
-				else if (s.isTrue(x)) {
-					lower_ += w;
-				}
-			}
-		}
-		if (n == 1) {
-			if      (lower_ > upper_){ next = 1; break; }
-			else if (lower_ < upper_){ next = (n = 0);  }
-			else                     {
-				n    = shared_->checkNext() || level != shared_->maxLevel();
-				next = n;
-				while (!assume_.empty()) {
-					fixLit(s, assume_.back().lit);
-					assume_.pop_back();
-				}
-				litData_.clear();
-				assume_.clear();
-				maxW = 1;
-			}
-		}
-	}
-	init_ = 0;
-	path_ = 1;
-	disj_ = options_.hasOption(OptParams::usc_disjoint);
-	actW_ = options_.hasOption(OptParams::usc_stratify) ? maxW : 1;
-	if (next && !s.hasConflict()) {
-		s.force(~tag_, Antecedent(0));
-		disj_ = 0;
-	}
-	if (auxInit_ == UINT32_MAX) {
-		auxInit_ = s.numAuxVars();
-	}
-	return !s.hasConflict();
+    initRoot(s);
+    next_   = 0;
+    disj_   = 0;
+    actW_   = 1;
+    nextW_  = 0;
+    sum_[0] = -1;
+    if (not fixLevel(s)) {
+        return false;
+    }
+    for (auto lit : fix_) {
+        if (not s.force(lit, eRoot_, this)) {
+            return false;
+        }
+    }
+    if (not shared_->optimize()) {
+        level_ = shared_->maxLevel();
+        lower_ = shared_->lower(level_);
+        upper_ = shared_->upper(level_);
+        path_  = 0;
+        init_  = 0;
+        return true;
+    }
+    Weight_t maxW = 1;
+    uint32_t next = 1 - init_;
+    for (uint32_t level = (level_ + next), n = 1; level <= shared_->maxLevel() && assume_.empty(); ++level) {
+        level_ = level;
+        lower_ = 0;
+        upper_ = shared_->upper(level_);
+        for (const auto& wl : *shared_) {
+            if (Weight_t w = shared_->weight(wl, level_)) {
+                Literal x = wl.lit;
+                if (w < 0) {
+                    lower_ += w;
+                    w       = -w;
+                    x       = ~x;
+                }
+                if (s.value(x.var()) == value_free || s.level(x.var()) > eRoot_) {
+                    newAssumption(~x, w);
+                    if (w > maxW) {
+                        maxW = w;
+                    }
+                }
+                else if (s.isTrue(x)) {
+                    lower_ += w;
+                }
+            }
+        }
+        if (n == 1) {
+            if (lower_ > upper_) {
+                next = 1;
+                break;
+            }
+            else if (lower_ < upper_) {
+                next = (n = 0);
+            }
+            else {
+                n    = shared_->checkNext() || level != shared_->maxLevel();
+                next = n;
+                while (not assume_.empty()) {
+                    fixLit(s, assume_.back().lit);
+                    assume_.pop_back();
+                }
+                litData_.clear();
+                assume_.clear();
+                maxW = 1;
+            }
+        }
+    }
+    init_ = 0;
+    path_ = 1;
+    disj_ = options_.hasOption(OptParams::usc_disjoint);
+    actW_ = options_.hasOption(OptParams::usc_stratify) ? maxW : 1;
+    if (next && not s.hasConflict()) {
+        s.force(~tag_, Antecedent(nullptr));
+        disj_ = 0;
+    }
+    if (auxInit_ == UINT32_MAX) {
+        auxInit_ = s.numAuxVars();
+    }
+    return not s.hasConflict();
 }
 
 Literal UncoreMinimize::newLit(Solver& s) {
-	++auxAdd_;
-	return posLit(s.pushAuxVar());
-}
-UncoreMinimize::LitPair UncoreMinimize::newAssumption(Literal p, weight_t w) {
-	assert(w > 0);
-	if (nextW_ && w > nextW_) {
-		nextW_ = w;
-	}
-	litData_.push_back(LitData(w, true, 0));
-	assume_.push_back(LitPair(p, sizeVec(litData_)));
-	return assume_.back();
-}
-bool UncoreMinimize::push(Solver& s, Literal p, uint32 id) {
-	assert(conflict_.empty());
-	if (s.pushRoot(p)) {
-		return true;
-	}
-	else if (!s.hasConflict()) {
-		conflict_.assign(1, ~p);
-		conflict_.push_back(Literal::fromRep(id));
-		if (s.level(p.var()) > eRoot_) { s.force(p, Antecedent(0)); }
-		else                           { s.setStopConflict(); }
-	}
-	return false;
+    ++auxAdd_;
+    return posLit(s.pushAuxVar());
+}
+UncoreMinimize::LitPair UncoreMinimize::newAssumption(Literal p, Weight_t w) {
+    assert(w > 0);
+    if (nextW_ && w > nextW_) {
+        nextW_ = w;
+    }
+    litData_.push_back(LitData(w, true, 0));
+    assume_.push_back(LitPair(p, size32(litData_)));
+    return assume_.back();
+}
+bool UncoreMinimize::push(Solver& s, Literal p, uint32_t id) {
+    assert(conflict_.empty());
+    if (s.pushRoot(p)) {
+        return true;
+    }
+    else if (not s.hasConflict()) {
+        conflict_.assign(1, ~p);
+        conflict_.push_back(Literal::fromRep(id));
+        if (s.level(p.var()) > eRoot_) {
+            s.force(p, Antecedent(nullptr));
+        }
+        else {
+            s.setStopConflict();
+        }
+    }
+    return false;
 }
 // Pushes the active assumptions from the active optimization level to
 // the root path.
 bool UncoreMinimize::pushPath(Solver& s) {
-	assert(!next_);
-	for (bool push = path_ != 0; !s.hasConflict() && push; ) {
-		path_ = 0;
-		if (!s.propagate() || !s.simplify()) { path_ = 1;  return false; }
-		initRoot(s);
-		if (todo_.shrink()) {
-			return pushTrim(s);
-		}
-		wsum_t   fixW = upper_ - lower_, low = 0;
-		weight_t maxW = 0;
-		uint32 j = 0, i = 0, end = sizeVec(assume_);
-		bool  ok = true;
-		nextW_ = 0;
-		for (uint32 dl; i != end && ok; ++i) {
-			LitData& x = getData(assume_[i].id);
-			if (x.assume) {
-				Literal  p = assume_[i].lit;
-				weight_t w = x.weight;
-				assume_[j++] = assume_[i];
-				if (w < actW_) {
-					nextW_ = std::max(nextW_, w);
-				}
-				else if (w > fixW) {
-					--j;
-					ok = fixLit(s, p);
-					push = false;
-					x.assume = 0;
-					x.weight = 0;
-					if (hasCore(x)) { closeCore(s, x, false); }
-				}
-				else if (!s.isFalse(p) || s.level(p.var()) > eRoot_) {
-					maxW = std::max(maxW, w);
-					ok   = !push || this->push(s, p, assume_[i].id);
-				}
-				else {
-					LitPair core(~p, assume_[i].id);
-					--j;
-					dl   = s.decisionLevel();
-					ok   = addCore(s, &core, 1, w, true);
-					low += w;
-					fixW = fixW - w;
-					end  = sizeVec(assume_);
-					push = push && s.decisionLevel() == dl;
-				}
-			}
-		}
-		if (i != j) { moveDown(assume_, i, j); }
-		if (low) {
-			shared_->incLower(level_, lower_);
-		}
-		push  = !push || maxW > fixW;
-		aTop_ = s.rootLevel();
-		POTASSCO_REQUIRE(s.decisionLevel() == s.rootLevel(), "pushPath must be called on root level (%u:%u)", s.rootLevel(), s.decisionLevel());
-	}
-	return !s.hasConflict();
+    assert(not next_);
+    for (bool push = path_ != 0; not s.hasConflict() && push;) {
+        path_ = 0;
+        if (not s.propagate() || not s.simplify()) {
+            path_ = 1;
+            return false;
+        }
+        initRoot(s);
+        if (todo_.shrink()) {
+            return pushTrim(s);
+        }
+        Wsum_t   fixW = upper_ - lower_, low = 0;
+        Weight_t maxW = 0;
+        uint32_t j = 0, i = 0, end = size32(assume_);
+        bool     ok = true;
+        nextW_      = 0;
+        for (uint32_t dl; i != end && ok; ++i) {
+            LitData& x = getData(assume_[i].id);
+            if (x.assume) {
+                Literal  p   = assume_[i].lit;
+                Weight_t w   = x.weight;
+                assume_[j++] = assume_[i];
+                if (w < actW_) {
+                    nextW_ = std::max(nextW_, w);
+                }
+                else if (w > fixW) {
+                    --j;
+                    ok       = fixLit(s, p);
+                    push     = false;
+                    x.assume = 0;
+                    x.weight = 0;
+                    if (hasCore(x)) {
+                        closeCore(s, x, false);
+                    }
+                }
+                else if (not s.isFalse(p) || s.level(p.var()) > eRoot_) {
+                    maxW = std::max(maxW, w);
+                    ok   = not push || this->push(s, p, assume_[i].id);
+                }
+                else {
+                    LitPair core(~p, assume_[i].id);
+                    --j;
+                    dl    = s.decisionLevel();
+                    ok    = addCore(s, Potassco::toSpan(core), w, true);
+                    low  += w;
+                    fixW  = fixW - w;
+                    end   = size32(assume_);
+                    push  = push && s.decisionLevel() == dl;
+                }
+            }
+        }
+        if (i != j) {
+            moveDown(assume_, i, j);
+        }
+        if (low) {
+            shared_->incLower(level_, lower_);
+        }
+        push  = not push || maxW > fixW;
+        aTop_ = s.rootLevel();
+        POTASSCO_CHECK_PRE(s.decisionLevel() == s.rootLevel(), "pushPath must be called on root level (%u:%u)",
+                           s.rootLevel(), s.decisionLevel());
+    }
+    return not s.hasConflict();
 }
 
 // Removes invalid assumptions from the root path.
-bool UncoreMinimize::popPath(Solver& s, uint32 dl) {
-	POTASSCO_REQUIRE(dl <= aTop_ && eRoot_ <= aTop_ && s.rootLevel() <= aTop_, "You must not mess with my root level!");
-	if (dl < eRoot_) { dl = eRoot_; }
-	sum_[0] = -1;
-	path_   = 1;
-	return s.popRootLevel(s.rootLevel() - (aTop_ = dl));
+bool UncoreMinimize::popPath(Solver& s, uint32_t dl) {
+    POTASSCO_CHECK_PRE(dl <= aTop_ && eRoot_ <= aTop_ && s.rootLevel() <= aTop_,
+                       "You must not mess with my root level!");
+    if (dl < eRoot_) {
+        dl = eRoot_;
+    }
+    sum_[0] = -1;
+    path_   = 1;
+    return s.popRootLevel(s.rootLevel() - (aTop_ = dl));
 }
 
 bool UncoreMinimize::relax(Solver& s, bool reset) {
-	if (next_ && !reset) {
-		// commit cores of last model
-		if (todo_.shrink()) {
-			resetTrim(s);
-		}
-		addNext(s, false);
-	}
+    if (next_ && not reset) {
+        // commit cores of last model
+        if (todo_.shrink()) {
+            resetTrim(s);
+        }
+        addNext(s, false);
+    }
 
-	if (reset && shared_->optimize()) {
-		POTASSCO_ASSERT(!auxAdd_ || s.numAuxVars() == (auxInit_ + auxAdd_), "Cannot safely detach constraint");
-		detach(&s, true);
-		init();
-	}
-	else {
-		releaseLits();
-	}
-	if (!shared_->optimize()) {
-		gen_ = shared_->generation();
-	}
-	init_ = 1;
-	next_ = 0;
-	return !enum_ || enum_->relax(s, reset);
+    if (reset && shared_->optimize()) {
+        POTASSCO_ASSERT(not auxAdd_ || s.numAuxVars() == (auxInit_ + auxAdd_), "Cannot safely detach constraint");
+        detach(&s, true);
+        init();
+    }
+    else {
+        releaseLits();
+    }
+    if (not shared_->optimize()) {
+        gen_ = shared_->generation();
+    }
+    init_ = 1;
+    next_ = 0;
+    return not enum_ || enum_->relax(s, reset);
 }
 
 // Computes the costs of the current assignment.
-wsum_t* UncoreMinimize::computeSum(const Solver& s) const {
-	std::fill_n(sum_, shared_->numRules(), wsum_t(0));
-	for (const WeightLiteral* it = shared_->lits; !isSentinel(it->first); ++it) {
-		if (s.isTrue(it->first)) { shared_->add(sum_, *it); }
-	}
-	return sum_;
+Wsum_t* UncoreMinimize::computeSum(const Solver& s) const {
+    std::fill_n(sum_.get(), shared_->numRules(), static_cast(0));
+    for (const auto& wl : *shared_) {
+        if (s.isTrue(wl.lit)) {
+            shared_->add(sum_.get(), wl);
+        }
+    }
+    return sum_.get();
 }
 // Checks whether the current assignment in s is valid w.r.t the
 // bound stored in the shared data object.
 bool UncoreMinimize::valid(Solver& s) {
-	if (shared_->upper(level_) == SharedData::maxBound()){ return true; }
-	if (sum_[0] < 0) { computeSum(s); }
-	const wsum_t* rhs;
-	uint32 end = shared_->numRules();
-	wsum_t cmp = 0;
-	do {
-		gen_  = shared_->generation();
-		rhs   = shared_->upper();
-		upper_= rhs[level_];
-		for (uint32 i = level_; i != end && (cmp = sum_[i]-rhs[i]) == 0; ++i) { ; }
-	} while (gen_ != shared_->generation());
-	if (s.numFreeVars() != 0) { sum_[0] = -1; }
-	if (cmp < wsum_t(!shared_->checkNext())) {
-		return true;
-	}
-	next_ = 1;
-	s.setStopConflict();
- 	return false;
+    if (shared_->upper(level_) == SharedData::maxBound()) {
+        return true;
+    }
+    if (sum_[0] < 0) {
+        std::ignore = computeSum(s);
+    }
+    uint32_t end = shared_->numRules();
+    Wsum_t   cmp = 0;
+    do {
+        gen_            = shared_->generation();
+        const auto* rhs = shared_->upper();
+        upper_          = rhs[level_];
+        for (uint32_t i = level_; i != end && (cmp = sum_[i] - rhs[i]) == 0; ++i) {}
+    } while (gen_ != shared_->generation());
+    if (s.numFreeVars() != 0) {
+        sum_[0] = -1;
+    }
+    if (cmp < static_cast(not shared_->checkNext())) {
+        return true;
+    }
+    next_ = 1;
+    s.setStopConflict();
+    return false;
 }
 // Sets the current sum as the current shared optimum.
 bool UncoreMinimize::handleModel(Solver& s) {
-	if (!valid(s))  { return false; }
-	if (sum_[0] < 0){ computeSum(s);}
-	shared_->setOptimum(sum_);
-	next_ = shared_->checkNext();
-	gen_  = shared_->generation();
-	upper_= shared_->upper(level_);
-	POTASSCO_ASSERT(!next_ || disj_ || todo_.shrink() || nextW_ || lower_ == sum_[level_], "Unexpected lower bound on model!");
-	return true;
+    if (not valid(s)) {
+        return false;
+    }
+    if (sum_[0] < 0) {
+        std::ignore = computeSum(s);
+    }
+    shared_->setOptimum(sum_.get());
+    next_  = shared_->checkNext();
+    gen_   = shared_->generation();
+    upper_ = shared_->upper(level_);
+    POTASSCO_ASSERT(not next_ || disj_ || todo_.shrink() || nextW_ || lower_ == sum_[level_],
+                    "Unexpected lower bound on model!");
+    return true;
 }
 
 // Tries to recover from either a model or a root-level conflict.
 bool UncoreMinimize::handleUnsat(Solver& s, bool up, LitVec&) {
-	assert(s.hasConflict());
-	if (enum_) { enum_->relaxBound(true); }
-	bool trimCore = options_.trim != 0u;
-	do {
-		if (next_ == 0) {
-			if (s.hasStopConflict()) { return false; }
-			if (todo_.shrink()) {
-				lower_ -= todo_.weight();
-				todo_.clear(false);
-			}
-			uint32 cs = analyze(s);
-			if (!cs) {
-				todo_.clear();
-				return false;
-			}
-			lower_ += todo_.weight();
-			if (disj_) { // preprocessing: remove assumptions and remember core
-				todo_.terminate(); // null-terminate core
-				for (Todo::const_iterator it = todo_.end() - (cs + 1); it->id; ++it) {
-					getData(it->id).assume = 0;
-				}
-			}
-			else if (trimCore && validLowerBound() && todo_.shrinkNext(*this, value_false)) {
-				popPath(s, 0);
-			}
-			else {
-				resetTrim(s);
-			}
-			next_ = !validLowerBound();
-			if (up && shared_->incLower(level_, lower_) == lower_) {
-				reportLower(s, level_, lower_);
-			}
-		}
-		else {
-			s.clearStopConflict();
-			addNext(s);
-		}
-	} while (next_ || s.hasConflict());
-	return true;
+    assert(s.hasConflict());
+    if (enum_) {
+        enum_->relaxBound(true);
+    }
+    bool trimCore = options_.trim != 0u;
+    do {
+        if (next_ == 0) {
+            if (s.hasStopConflict()) {
+                return false;
+            }
+            if (todo_.shrink()) {
+                lower_ -= todo_.weight();
+                todo_.clear(false);
+            }
+            uint32_t cs = analyze(s);
+            if (not cs) {
+                todo_.clear();
+                return false;
+            }
+            lower_ += todo_.weight();
+            if (disj_) { // preprocessing: remove assumptions and remember core
+                for (const auto& x : todo_.last(cs)) { getData(x.id).assume = 0; }
+                todo_.terminate(); // null-terminate core
+            }
+            else if (trimCore && validLowerBound() && todo_.shrinkNext(*this, value_false)) {
+                popPath(s, 0);
+            }
+            else {
+                resetTrim(s);
+            }
+            next_ = not validLowerBound();
+            if (up) {
+                shared_->incLower(level_, lower_);
+            }
+        }
+        else {
+            s.clearStopConflict();
+            addNext(s);
+        }
+    } while (next_ || s.hasConflict());
+    return true;
 }
 
 bool UncoreMinimize::addNext(Solver& s, bool allowInit) {
-	popPath(s, 0);
-	const wsum_t cmp = (lower_ - upper_);
-	if (disj_) {
-		for (Todo::const_iterator it = todo_.begin(), end = todo_.end(), cs; (cs = it) != end; ++it) {
-			weight_t w = std::numeric_limits::max();
-			while (it->id) { w = std::min(w, getData(it->id).weight); ++it; }
-			if (!addCore(s, &*cs, uint32(it - cs), w, false)) { break; }
-		}
-		todo_.clear(false);
-	}
-	else if (todo_.shrink() && (!todo_.shrinkNext(*this, value_true) || cmp >= 0)) {
-		resetTrim(s);
-	}
-	next_ = 0;
-	disj_ = 0;
-	if (cmp >= 0) {
-		fixLevel(s);
-		if (cmp > 0) { s.hasConflict() || s.force(~tag_, Antecedent(0)); }
-		else if (level_ != shared_->maxLevel() || shared_->checkNext()) {
-			if      (allowInit) initLevel(s);
-			else if (level_ != shared_->maxLevel()) level_ += (1 - init_);
-		}
-	}
-	else if (!todo_.shrink() && nextW_) {
-		actW_ = nextW_;
-		disj_ = options_.hasOption(OptParams::usc_disjoint);
-	}
-	return !s.hasConflict();
+    popPath(s, 0);
+    const Wsum_t cmp = (lower_ - upper_);
+    if (disj_) {
+        for (auto cores = todo_.view(); not cores.empty();) {
+            // find end of next (null-terminated) core
+            auto cs   = 0u;
+            auto minW = weight_max;
+            for (; cores[cs].id; ++cs) { minW = std::min(minW, getData(cores[cs].id).weight); }
+            if (not addCore(s, cores.subspan(0, cs), minW, false)) {
+                break;
+            }
+            cores = cores.subspan(cs + 1); // pop core
+        }
+        todo_.clear(false);
+    }
+    else if (todo_.shrink() && (not todo_.shrinkNext(*this, value_true) || cmp >= 0)) {
+        resetTrim(s);
+    }
+    next_ = 0;
+    disj_ = 0;
+    if (cmp >= 0) {
+        fixLevel(s);
+        if (cmp > 0) {
+            s.hasConflict() || s.force(~tag_, Antecedent(nullptr));
+        }
+        else if (level_ != shared_->maxLevel() || shared_->checkNext()) {
+            if (allowInit) {
+                initLevel(s);
+            }
+            else if (level_ != shared_->maxLevel()) {
+                level_ += (1u - init_);
+            }
+        }
+    }
+    else if (not todo_.shrink() && nextW_) {
+        actW_ = nextW_;
+        disj_ = options_.hasOption(OptParams::usc_disjoint);
+    }
+    return not s.hasConflict();
 }
 
 // Analyzes the current root level conflict and stores the set of our assumptions
 // that caused the conflict in todo_.
-uint32 UncoreMinimize::analyze(Solver& s) {
-	uint32 cs    = 0;
-	uint32 minDL = s.decisionLevel();
-	if (!conflict_.empty()) {
-		LitPair p(conflict_[0], conflict_[1].rep());
-		assert(s.isTrue(p.lit));
-		todo_.add(p, getData(p.id).weight);
-		minDL = s.level(p.lit.var());
-		cs = 1;
-	}
-	conflict_.clear();
-	if (s.decisionLevel() <= eRoot_) {
-		return cs;
-	}
-	s.resolveToCore(conflict_);
-	for (LitVec::const_iterator it = conflict_.begin(), end = conflict_.end(); it != end; ++it) {
-		s.markSeen(*it);
-	}
-	// map marked root decisions back to our assumptions
-	uint32 roots = sizeVec(conflict_), dl;
-	cs += roots;
-	for (LitSet::iterator it = assume_.begin(), end = assume_.end(); it != end && roots; ++it) {
-		Literal p = it->lit;
-		if (s.seen(p) && (dl = s.level(p.var())) > eRoot_ && dl <= aTop_) {
-			assert(p == s.decision(dl) && getData(it->id).assume);
-			if (dl < minDL) { minDL = dl; }
-			todo_.add(LitPair(~p, it->id), getData(it->id).weight);
-			assert(s.isFalse(~p));
-			s.clearSeen(p.var());
-			--roots;
-		}
-	}
-	popPath(s, minDL - (minDL != 0));
-	if (roots) { // clear remaining levels - can only happen if someone messed with our assumptions
-		cs -= roots;
-		for (LitVec::const_iterator it = conflict_.begin(), end = conflict_.end(); it != end; ++it) { s.clearSeen(it->var()); }
-	}
-	conflict_.clear();
-	return cs;
+uint32_t UncoreMinimize::analyze(Solver& s) {
+    uint32_t cs    = 0;
+    uint32_t minDl = s.decisionLevel();
+    if (not conflict_.empty()) {
+        LitPair p(conflict_[0], conflict_[1].rep());
+        assert(s.isTrue(p.lit));
+        todo_.add(p, getData(p.id).weight);
+        minDl = s.level(p.lit.var());
+        cs    = 1;
+    }
+    conflict_.clear();
+    if (s.decisionLevel() <= eRoot_) {
+        return cs;
+    }
+    s.resolveToCore(conflict_);
+    for (auto lit : conflict_) { s.markSeen(lit); }
+    // map marked root decisions back to our assumptions
+    uint32_t roots  = size32(conflict_), dl;
+    cs             += roots;
+    for (auto it = assume_.begin(), end = assume_.end(); it != end && roots; ++it) {
+        if (Literal p = it->lit; s.seen(p) && (dl = s.level(p.var())) > eRoot_ && dl <= aTop_) {
+            assert(p == s.decision(dl) && getData(it->id).assume);
+            if (dl < minDl) {
+                minDl = dl;
+            }
+            todo_.add(LitPair(~p, it->id), getData(it->id).weight);
+            assert(s.isFalse(~p));
+            s.clearSeen(p.var());
+            --roots;
+        }
+    }
+    popPath(s, minDl - (minDl != 0));
+    if (roots) { // clear remaining levels - can only happen if someone messed with our assumptions
+        cs -= roots;
+        for (auto lit : conflict_) { s.clearSeen(lit.var()); }
+    }
+    conflict_.clear();
+    return cs;
 }
 
 // Eliminates the given core by adding suitable constraints to the solver.
-bool UncoreMinimize::addCore(Solver& s, const LitPair* lits, uint32 cs, weight_t w, bool updateLower) {
-	assert(s.decisionLevel() == s.rootLevel());
-	assert(w && cs);
-	// apply weight and check for subcores
-	if (updateLower) { lower_ += w; }
-	for (uint32 i = 0; i != cs; ++i) {
-		LitData& x = getData(lits[i].id);
-		if ( (x.weight -= w) <= 0) {
-			x.assume = 0;
-			x.weight = 0;
-		}
-		else if (disj_ && !x.assume) {
-			x.assume = 1;
-			assume_.push_back(LitPair(~lits[i].lit, lits[i].id));
-		}
-		if (x.weight == 0 && hasCore(x)) {
-			Core& core = getCore(x);
-			temp_.start(core.bound + 1);
-			for (uint32 k = 0, end = core.size(); k != end; ++k) {
-				Literal p = core.at(k);
-				while (s.topValue(p.var()) != s.value(p.var()) && s.rootLevel() > eRoot_) {
-					s.popRootLevel(s.rootLevel() - std::max(s.level(p.var())-1, eRoot_));
-					aTop_ = std::min(aTop_, s.rootLevel());
-				}
-				temp_.add(s, p);
-			}
-			weight_t cw = core.weight;
-			if (!closeCore(s, x, temp_.bound <= 1) || !addOllCon(s, temp_, cw)) {
-				return false;
-			}
-		}
-	}
-	if (cs == 1) {
-		return fixLit(s, lits[0].lit);
-	}
-	// add new core
-	switch (options_.algo) {
-		default:
-		case OptParams::usc_oll: return addOll(s, lits, cs, w);
-		case OptParams::usc_one: return addK(s, cs, lits, cs, w);
-		case OptParams::usc_k:   return addK(s, options_.kLim, lits, cs, w);
-		case OptParams::usc_pmr: return addPmr(s, lits, cs, w);
-	}
-}
-bool UncoreMinimize::addOll(Solver& s, const LitPair* lits, uint32 size, weight_t w) {
-	temp_.start(2);
-	for (uint32 i = 0; i != size; ++i) { temp_.add(s, lits[i].lit); }
-	if (!temp_.unsat()) {
-		return addOllCon(s, temp_, w);
-	}
-	Literal fix = !temp_.lits.empty() ? temp_.lits[0].first : lits[0].lit;
-	return temp_.bound < 2 || fixLit(s, fix);
-}
-bool UncoreMinimize::addK(Solver& s, uint32 k, const LitPair* lits, uint32 size, weight_t w) {
-	const bool concise = options_.hasOption(OptParams::usc_succinct);
-	const int x = k ? (size + (k-1)) / k
-	            : size <= 8 ? 1
-	            : (int)std::ceil(size / (((std::log10(size)*16)-2)/2.0));
-	k = (size + (x-1)) / x;
-	uint32 idx = 1;
-	Literal cp = ~lits[0].lit, bin[2];
-	do {
-		uint32 n = k, connect = uint32((idx + k) < size);
-		if (!connect) {
-			n = size - idx;
-		}
-		weight_t B = static_cast(n + connect);
-		temp_.start(B);
-		temp_.add(s, cp);
-		for (uint32 i = 0; i != n; ++i) {
-			temp_.add(s, ~lits[idx++].lit);
-		}
-		if (connect) {
-			bin[0] = newLit(s);
-			temp_.add(s, ~bin[0]);
-			cp = bin[0];
-		}
-		for (uint32 i = 0, b = connect; i != n; ++i, b = 1) {
-			Literal ri = newAssumption(newLit(s), w).lit;
-			bin[b] = ri;
-			temp_.add(s, ~ri);
-			if (b) { // bin[0] -> bin[1];
-				addImplication(s, bin[0], bin[1], concise);
-				bin[0] = bin[1];
-			}
-		}
-		if (!addConstraint(s, temp_.begin(), temp_.size(), temp_.bound)) {
-			return false;
-		}
-	} while (idx != size);
-	if (!concise && !s.hasConflict()) {
-		typedef ClauseCreator::Result Result;
-		const uint32 flags = ClauseCreator::clause_explicit | ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add;
-		for (uint32 i = 0; i != size; ++i)   { conflict_.push_back(lits[i].lit); }
-		for (uint32 i = 1; i <= eRoot_; ++i) { conflict_.push_back(~s.decision(i)); }
-		Result res = ClauseCreator::create(s, conflict_, flags, Constraint_t::Other);
-		if (res.local) { closed_.push_back(res.local); }
-		conflict_.clear();
-	}
-	return !s.hasConflict();
-}
-bool UncoreMinimize::addPmr(Solver& s, const LitPair* lits, uint32 size, weight_t w) {
-	assert(size > 1);
-	uint32 i   = size - 1;
-	Literal bp = lits[i].lit;
-	while (--i != 0) {
-		Literal an = lits[i].lit;
-		Literal bn = newLit(s);
-		Literal cn = newLit(s);
-		newAssumption(~cn, w);
-		if (!addPmrCon(comp_disj, s, bn, an, bp)) { return false; }
-		if (!addPmrCon(comp_conj, s, cn, an, bp)) { return false; }
-		bp = bn;
-	}
-	Literal an = lits[i].lit;
-	Literal cn = newLit(s);
-	newAssumption(~cn, w);
-	return addPmrCon(comp_conj, s, cn, an, bp);
+bool UncoreMinimize::addCore(Solver& s, LitView lits, Weight_t w, bool updateLower) {
+    assert(s.decisionLevel() == s.rootLevel());
+    assert(w && not lits.empty());
+    // apply weight and check for subcores
+    if (updateLower) {
+        lower_ += w;
+    }
+    for (const auto& [lit, id] : lits) {
+        LitData& x = getData(id);
+        if (x.weight -= w; x.weight <= 0) {
+            x.assume = 0;
+            x.weight = 0;
+        }
+        else if (disj_ && not x.assume) {
+            x.assume = 1;
+            assume_.push_back(LitPair(~lit, id));
+        }
+        if (x.weight == 0 && hasCore(x)) {
+            Core& core = getCore(x);
+            temp_.start(core.bound + 1);
+            for (uint32_t k : irange(core)) {
+                Literal p = core.at(k);
+                while (s.topValue(p.var()) != s.value(p.var()) && s.rootLevel() > eRoot_) {
+                    s.popRootLevel(s.rootLevel() - std::max(s.level(p.var()) - 1, eRoot_));
+                    aTop_ = std::min(aTop_, s.rootLevel());
+                }
+                temp_.add(s, p);
+            }
+            Weight_t cw = core.weight;
+            if (not closeCore(s, x, temp_.bound <= 1) || not addOllCon(s, temp_, cw)) {
+                return false;
+            }
+        }
+    }
+    if (lits.size() == 1) {
+        return fixLit(s, lits[0].lit);
+    }
+    // add new core
+    switch (options_.algo) {
+        default:
+        case OptParams::usc_oll: return addOll(s, lits, w);
+        case OptParams::usc_one: return addK(s, size32(lits), lits, w);
+        case OptParams::usc_k  : return addK(s, options_.kLim, lits, w);
+        case OptParams::usc_pmr: return addPmr(s, lits, w);
+    }
+}
+bool UncoreMinimize::addOll(Solver& s, LitView lits, Weight_t w) {
+    temp_.start(2);
+    for (const auto& [lit, _] : lits) { temp_.add(s, lit); }
+    if (not temp_.unsat()) {
+        return addOllCon(s, temp_, w);
+    }
+    Literal fix = not temp_.lits.empty() ? temp_.lits[0].lit : lits[0].lit;
+    return temp_.bound < 2 || fixLit(s, fix);
+}
+
+static constexpr auto clause_flags =
+    ClauseCreator::clause_explicit | ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add;
+static constexpr auto weight_flags = WeightConstraint::create_explicit | WeightConstraint::create_no_add |
+                                     WeightConstraint::create_no_freeze | WeightConstraint::create_no_share;
+
+bool UncoreMinimize::addK(Solver& s, uint32_t k, LitView lits, Weight_t w) {
+    const bool concise = options_.hasOption(OptParams::usc_succinct);
+    const auto size    = size32(lits);
+    const auto x       = k            ? (size + (k - 1)) / k
+                         : size <= 8u ? 1u
+                                      : static_cast(std::ceil(size / (((std::log10(size) * 16) - 2) / 2.0)));
+    k                  = (size + (x - 1)) / x;
+    uint32_t idx       = 1;
+    Literal  cp        = ~lits[0].lit, bin[2];
+    do {
+        uint32_t n = k, connect = idx + k < size;
+        if (not connect) {
+            n = size - idx;
+        }
+        temp_.start(static_cast(n + connect));
+        temp_.add(s, cp);
+        for (uint32_t i = 0; i != n; ++i) { temp_.add(s, ~lits[idx++].lit); }
+        if (connect) {
+            bin[0] = newLit(s);
+            temp_.add(s, ~bin[0]);
+            cp = bin[0];
+        }
+        for (uint32_t i = 0, b = connect; i != n; ++i, b = 1) {
+            Literal ri = newAssumption(newLit(s), w).lit;
+            bin[b]     = ri;
+            temp_.add(s, ~ri);
+            if (b) { // bin[0] -> bin[1];
+                addImplication(s, bin[0], bin[1], concise);
+                bin[0] = bin[1];
+            }
+        }
+        if (not addConstraint(s, temp_.data(), temp_.size(), temp_.bound)) {
+            return false;
+        }
+    } while (idx != size);
+    if (not concise && not s.hasConflict()) {
+        for (const auto& [lit, _] : lits) { conflict_.push_back(lit); }
+        for (uint32_t i = 1; i <= eRoot_; ++i) { conflict_.push_back(~s.decision(i)); }
+        if (auto res = ClauseCreator::create(s, conflict_, clause_flags, ConstraintType::other); res.local) {
+            closed_.push_back(res.local);
+        }
+        conflict_.clear();
+    }
+    return not s.hasConflict();
+}
+bool UncoreMinimize::addPmr(Solver& s, LitView lits, Weight_t w) {
+    assert(lits.size() > 1);
+    uint32_t i  = size32(lits) - 1;
+    Literal  bp = lits[i].lit;
+    while (--i != 0) {
+        Literal an = lits[i].lit;
+        Literal bn = newLit(s);
+        Literal cn = newLit(s);
+        newAssumption(~cn, w);
+        if (not addPmrCon(comp_disj, s, bn, an, bp)) {
+            return false;
+        }
+        if (not addPmrCon(comp_conj, s, cn, an, bp)) {
+            return false;
+        }
+        bp = bn;
+    }
+    Literal an = lits[i].lit;
+    Literal cn = newLit(s);
+    newAssumption(~cn, w);
+    return addPmrCon(comp_conj, s, cn, an, bp);
 }
 bool UncoreMinimize::addPmrCon(CompType c, Solver& s, Literal head, Literal body1, Literal body2) {
-	typedef ClauseCreator::Result Result;
-	const uint32 flags = ClauseCreator::clause_explicit | ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add;
-	const bool    sign = c == comp_conj;
-	uint32       first = 0, last = 3;
-	if (options_.hasOption(OptParams::usc_succinct)) {
-		first = c == comp_disj;
-		last  = first + (1 + (c == comp_disj));
-	}
-	Literal temp[3][3] = {
-		{ (~head)^ sign,   body1 ^ sign, body2 ^ sign },
-		{   head ^ sign, (~body1)^ sign, lit_false() },
-		{   head ^ sign, (~body2)^ sign, lit_false() }
-	};
-	for (uint32 i = first, sz = 3; i != last; ++i) {
-		Result res = ClauseCreator::create(s, ClauseRep::create(temp[i], sz, Constraint_t::Other), flags);
-		if (res.local) { closed_.push_back(res.local); }
-		if (!res.ok()) { return false; }
-		sz = 2;
-	}
-	return true;
-}
-bool UncoreMinimize::addOllCon(Solver& s, const WCTemp& wc, weight_t weight) {
-	typedef WeightConstraint::CPair ResPair;
-	weight_t B = wc.bound;
-	if (B <= 0) {
-		// constraint is sat and hence conflicting w.r.t new assumption -
-		// relax core
-		lower_ += ((1-B)*weight);
-		B       = 1;
-	}
-	if (static_cast(B) > static_cast(wc.lits.size())) {
-		// constraint is unsat and hence the new assumption is trivially satisfied
-		return true;
-	}
-	// create new var for this core
-	LitPair aux = newAssumption(newLit(s), weight);
-	WeightLitsRep rep = {const_cast(wc).begin(), wc.size(), B, (weight_t)wc.size()};
-	uint32       fset = WeightConstraint::create_explicit | WeightConstraint::create_no_add | WeightConstraint::create_no_freeze | WeightConstraint::create_no_share;
-	if (options_.hasOption(OptParams::usc_succinct)) { fset |= WeightConstraint::create_only_bfb; }
-	ResPair       res = WeightConstraint::create(s, ~aux.lit, rep, fset);
-	if (res.ok() && res.first()) {
-		getData(aux.id).coreId = allocCore(res.first(), B, weight, rep.bound != rep.reach);
-	}
-	return !s.hasConflict();
+    using Result     = ClauseCreator::Result;
+    const bool sign  = c == comp_conj;
+    uint32_t   first = 0, last = 3;
+    if (options_.hasOption(OptParams::usc_succinct)) {
+        first = c == comp_disj;
+        last  = first + (1 + (c == comp_disj));
+    }
+    Literal temp[3][3] = {{(~head) ^ sign, body1 ^ sign, body2 ^ sign},
+                          {head ^ sign, (~body1) ^ sign, lit_false},
+                          {head ^ sign, (~body2) ^ sign, lit_false}};
+    for (uint32_t i = first, sz = 3; i != last; ++i) {
+        Result res = ClauseCreator::create(s, ClauseRep::create({temp[i], sz}, ConstraintType::other), clause_flags);
+        if (res.local) {
+            closed_.push_back(res.local);
+        }
+        if (not res.ok()) {
+            return false;
+        }
+        sz = 2;
+    }
+    return true;
+}
+bool UncoreMinimize::addOllCon(Solver& s, WCTemp& wc, Weight_t weight) {
+    Weight_t b = wc.bound;
+    if (b <= 0) {
+        // constraint is sat and hence conflicting w.r.t new assumption -
+        // relax core
+        lower_ += ((1 - b) * weight);
+        b       = 1;
+    }
+    if (static_cast(b) > size32(wc.lits)) {
+        // constraint is unsat and hence the new assumption is trivially satisfied
+        return true;
+    }
+    // create new var for this core
+    LitPair       aux  = newAssumption(newLit(s), weight);
+    WeightLitsRep rep  = {wc.data(), wc.size(), b, static_cast(wc.size())};
+    auto          fset = weight_flags;
+    if (options_.hasOption(OptParams::usc_succinct)) {
+        fset |= WeightConstraint::create_only_bfb;
+    }
+    auto res = WeightConstraint::create(s, ~aux.lit, rep, fset);
+    if (res.ok() && res.first()) {
+        getData(aux.id).coreId = allocCore(res.first(), b, weight, rep.bound != rep.reach);
+    }
+    return not s.hasConflict();
 }
 // Adds implication: a -> b either via a single watch on a or as a clause -a v b.
 bool UncoreMinimize::addImplication(Solver& s, Literal a, Literal b, bool concise) {
-	if (concise) {
-		POTASSCO_ASSERT(s.auxVar(a.var()));
-		s.addWatch(a, this, b.id());
-	}
-	else {
-		typedef ClauseCreator::Result Result;
-		const uint32 flags = ClauseCreator::clause_explicit | ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add;
-		Literal clause[] = {~a, b};
-		Result res = ClauseCreator::create(s, ClauseRep::create(clause, 2, Constraint_t::Other), flags);
-		if (res.local) { closed_.push_back(res.local); }
-		if (!res.ok()) { return false; }
-	}
-	return true;
+    if (concise) {
+        POTASSCO_ASSERT(s.auxVar(a.var()));
+        s.addWatch(a, this, b.id());
+    }
+    else {
+        using Result     = ClauseCreator::Result;
+        Literal clause[] = {~a, b};
+        Result  res      = ClauseCreator::create(s, ClauseRep::create(clause, ConstraintType::other), clause_flags);
+        if (res.local) {
+            closed_.push_back(res.local);
+        }
+        if (not res.ok()) {
+            return false;
+        }
+    }
+    return true;
 }
 // Adds the cardinality constraint lits[0] + ... + lits[size-1] >= bound.
-bool UncoreMinimize::addConstraint(Solver& s, WeightLiteral* lits, uint32 size, weight_t bound) {
-	typedef WeightConstraint::CPair ResPair;
-	WeightLitsRep rep = {lits, size, bound, static_cast(size)};
-	const uint32 fset = WeightConstraint::create_explicit | WeightConstraint::create_no_add | WeightConstraint::create_no_freeze | WeightConstraint::create_no_share;
-	ResPair       res = WeightConstraint::create(s, lit_true(), rep, fset);
-	if (res.first()) {
-		closed_.push_back(res.first());
-	}
-	return res.ok();
+bool UncoreMinimize::addConstraint(Solver& s, WeightLiteral* lits, uint32_t size, Weight_t bound) {
+    using ResPair     = WeightConstraint::CPair;
+    WeightLitsRep rep = {lits, size, bound, static_cast(size)};
+    ResPair       res = WeightConstraint::create(s, lit_true, rep, weight_flags);
+    if (res.first()) {
+        closed_.push_back(res.first());
+    }
+    return res.ok();
 }
 
 // Computes the solver's initial root level, i.e. all assumptions that are not from us.
-uint32 UncoreMinimize::initRoot(Solver& s) {
-	if (eRoot_ == aTop_ && !s.hasStopConflict()) {
-		eRoot_ = s.rootLevel();
-		aTop_  = eRoot_;
-	}
-	return eRoot_;
+uint32_t UncoreMinimize::initRoot(const Solver& s) {
+    if (eRoot_ == aTop_ && not s.hasStopConflict()) {
+        eRoot_ = s.rootLevel();
+        aTop_  = eRoot_;
+    }
+    return eRoot_;
 }
 
 // Assigns p at the solver's initial root level.
 bool UncoreMinimize::fixLit(Solver& s, Literal p) {
-	assert(s.decisionLevel() >= eRoot_);
-	if (s.decisionLevel() > eRoot_ && (!s.isTrue(p) || s.level(p.var()) > eRoot_)) {
-		// go back to root level
-		s.popRootLevel(s.rootLevel() - eRoot_);
-		aTop_ = s.rootLevel();
-	}
-	if (eRoot_ && s.topValue(p.var()) != trueValue(p)) { fix_.push_back(p); }
-	return !s.hasConflict() && s.force(p, this);
+    assert(s.decisionLevel() >= eRoot_);
+    if (s.decisionLevel() > eRoot_ && (not s.isTrue(p) || s.level(p.var()) > eRoot_)) {
+        // go back to root level
+        s.popRootLevel(s.rootLevel() - eRoot_);
+        aTop_ = s.rootLevel();
+    }
+    if (eRoot_ && s.topValue(p.var()) != trueValue(p)) {
+        fix_.push_back(p);
+    }
+    return not s.hasConflict() && s.force(p, this);
 }
 // Fixes any remaining assumptions of the active optimization level.
 bool UncoreMinimize::fixLevel(Solver& s) {
-	for (LitSet::iterator it = assume_.begin(), end = assume_.end(); it != end; ++it) {
-		if (getData(it->id).assume) { fixLit(s, it->lit); }
-	}
-	releaseLits();
-	return !s.hasConflict();
+    for (const auto& [lit, id] : assume_) {
+        if (getData(id).assume) {
+            fixLit(s, lit);
+        }
+    }
+    releaseLits();
+    return not s.hasConflict();
 }
 void UncoreMinimize::releaseLits() {
-	// remaining cores are no longer open - move to closed list
-	for (CoreTable::iterator it = open_.begin(), end = open_.end(); it != end; ++it) {
-		if (it->con) { closed_.push_back(it->con); }
-	}
-	open_.clear();
-	litData_.clear();
-	assume_.clear();
-	todo_.clear();
-	freeOpen_ = 0;
+    // remaining cores are no longer open - move to closed list
+    for (const auto& c : open_) {
+        if (c.con) {
+            closed_.push_back(c.con);
+        }
+    }
+    open_.clear();
+    litData_.clear();
+    assume_.clear();
+    todo_.clear();
+    freeOpen_ = 0;
 }
 
-uint32 UncoreMinimize::allocCore(WeightConstraint* con, weight_t bound, weight_t weight, bool open) {
-	if (!open) {
-		closed_.push_back(con);
-		return 0;
-	}
-	if (freeOpen_) { // pop next slot from free list
-		uint32 fp = freeOpen_ - 1;
-		assert(open_[fp].con == 0 && open_[fp].bound == static_cast(0xDEADC0DE));
-		freeOpen_ = static_cast(open_[fp].weight);
-		open_[fp] = Core(con, bound, weight);
-		return fp + 1;
-	}
-	open_.push_back(Core(con, bound, weight));
-	return static_cast(open_.size());
+uint32_t UncoreMinimize::allocCore(WeightConstraint* con, Weight_t bound, Weight_t weight, bool open) {
+    if (not open) {
+        closed_.push_back(con);
+        return 0;
+    }
+    if (freeOpen_) { // pop next slot from free list
+        uint32_t fp = freeOpen_ - 1;
+        assert(open_[fp].con == nullptr && open_[fp].bound == static_cast(0xDEADC0DE));
+        freeOpen_ = static_cast(open_[fp].weight);
+        open_[fp] = Core(con, bound, weight);
+        return fp + 1;
+    }
+    open_.push_back(Core(con, bound, weight));
+    return size32(open_);
 }
 bool UncoreMinimize::closeCore(Solver& s, LitData& x, bool sat) {
-	if (uint32 coreId = x.coreId) {
-		Core& core = open_[coreId-1];
-		x.coreId = 0;
-		// close by moving to closed list
-		if (!sat) { closed_.push_back(core.con); }
-		else { fixLit(s, core.tag()); core.con->destroy(&s, true); }
-		// link slot to free list
-		core      = Core(0, static_cast(0xDEADC0DE), static_cast(freeOpen_));
-		freeOpen_ = coreId;
-	}
-	return !s.hasConflict();
+    if (uint32_t coreId = x.coreId) {
+        Core& core = open_[coreId - 1];
+        x.coreId   = 0;
+        // close by moving to closed list
+        if (not sat) {
+            closed_.push_back(core.con);
+        }
+        else {
+            fixLit(s, core.tag());
+            core.con->destroy(&s, true);
+        }
+        // link slot to free list
+        core      = Core(nullptr, static_cast(0xDEADC0DE), static_cast(freeOpen_));
+        freeOpen_ = coreId;
+    }
+    return not s.hasConflict();
 }
 bool UncoreMinimize::pushTrim(Solver& s) {
-	assert(!s.hasConflict() && s.rootLevel() == aTop_ && conflict_.empty());
-	uint32 top = aTop_;
-	todo_.shrinkPush(*this, s);
-	if ((aTop_ = s.rootLevel()) != top && !s.hasConflict() && options_.tLim) {
-		struct Limit : public PostPropagator {
-			Limit(UncoreMinimize& s, uint64 lim) : self(&s), limit(lim) {}
-			uint32 priority() const { return priority_reserved_ufs + 2; }
-			bool propagateFixpoint(Clasp::Solver& s, Clasp::PostPropagator* ctx) {
-				if (ctx || s.stats.conflicts < limit) { return true; }
-				s.setStopConflict();
-				self->next_ = 1;
-				self = 0;
-				s.removePost(this);
-				return false;
-			}
-			void undoLevel(Solver& s) {
-				if (self) { s.removePost(this); }
-				this->destroy();
-			}
-			UncoreMinimize* self;
-			uint64 limit;
-		}*limit = new Limit(*this, s.stats.conflicts + (uint64(1) << options_.tLim));
-		s.addPost(limit);
-		s.addUndoWatch(aTop_, limit);
-	}
-	else if (s.hasStopConflict() && conflict_.size() == 2) {
-		assert(getData(conflict_[1].rep()).assume);
-		lower_ -= todo_.weight();
-		todo_.clear(true);
-		s.clearStopConflict();
-		conflict_.clear();
-		popPath(s, 0);
-		pushPath(s);
-	}
-	return !s.hasConflict();
+    assert(not s.hasConflict() && s.rootLevel() == aTop_ && conflict_.empty());
+    uint32_t top = aTop_;
+    todo_.shrinkPush(*this, s);
+    if (aTop_ = s.rootLevel(); aTop_ != top && not s.hasConflict() && options_.tLim) {
+        struct Limit : PostPropagator {
+            Limit(UncoreMinimize& s, uint64_t lim) : self(&s), limit(lim) {}
+            [[nodiscard]] uint32_t priority() const override { return priority_reserved_ufs + 2; }
+            bool                   propagateFixpoint(Solver& s, PostPropagator* ctx) override {
+                if (ctx || s.stats.conflicts < limit) {
+                    return true;
+                }
+                s.setStopConflict();
+                self->next_ = 1;
+                self        = nullptr;
+                s.removePost(this);
+                return false;
+            }
+            void undoLevel(Solver& s) override {
+                if (self) {
+                    s.removePost(this);
+                }
+                this->destroy();
+            }
+            UncoreMinimize* self;
+            uint64_t        limit;
+        }* limit = new Limit(*this, s.stats.conflicts + (static_cast(1) << options_.tLim));
+        s.addPost(limit);
+        s.addUndoWatch(aTop_, limit);
+    }
+    else if (s.hasStopConflict() && conflict_.size() == 2) {
+        assert(getData(conflict_[1].rep()).assume);
+        lower_ -= todo_.weight();
+        todo_.clear(true);
+        s.clearStopConflict();
+        conflict_.clear();
+        popPath(s, 0);
+        pushPath(s);
+    }
+    return not s.hasConflict();
 }
 void UncoreMinimize::resetTrim(Solver& s) {
-	if (todo_.size()) {
-		addCore(s, &*todo_.begin(), todo_.size(), todo_.weight(), false);
-		todo_.clear();
-	}
-}
-uint32  UncoreMinimize::Core::size()       const { return con->size() - 1; }
-Literal UncoreMinimize::Core::at(uint32 i) const { return con->lit(i+1, WeightConstraint::FFB_BTB); }
-Literal UncoreMinimize::Core::tag()        const { return con->lit(0, WeightConstraint::FTB_BFB); }
-void    UncoreMinimize::WCTemp::add(Solver& s, Literal p) {
-	if (s.topValue(p.var()) == value_free) { lits.push_back(WeightLiteral(p, 1)); }
-	else if (s.isTrue(p)) { --bound; }
+    if (todo_.size()) {
+        addCore(s, todo_.view(), todo_.weight(), false);
+        todo_.clear();
+    }
+}
+uint32_t UncoreMinimize::Core::size() const { return con->size() - 1; }
+Literal  UncoreMinimize::Core::at(uint32_t i) const { return con->lit(i + 1, WeightConstraint::ffb_btb); }
+Literal  UncoreMinimize::Core::tag() const { return con->lit(0, WeightConstraint::ftb_bfb); }
+void     UncoreMinimize::WCTemp::add(const Solver& s, Literal p) {
+    if (s.topValue(p.var()) == value_free) {
+        lits.push_back(WeightLiteral{p, 1});
+    }
+    else if (s.isTrue(p)) {
+        --bound;
+    }
 }
 void UncoreMinimize::Todo::clear(bool withShrink) {
-	lits_.clear();
-	minW_ = CLASP_WEIGHT_T_MAX;
-	if (withShrink) { shrinkReset(); }
+    lits_.clear();
+    minW_ = weight_max;
+    if (withShrink) {
+        shrinkReset();
+    }
 }
 void UncoreMinimize::Todo::shrinkReset() {
-	core_.clear();
-	last_ = next_ = step_ = 0;
+    core_.clear();
+    last_ = next_ = step_ = 0;
 }
 void UncoreMinimize::Todo::terminate() {
-	lits_.push_back(LitPair(lit_true(), 0));
-	minW_ = CLASP_WEIGHT_T_MAX;
+    lits_.push_back(LitPair(lit_true, 0));
+    minW_ = weight_max;
 }
-void UncoreMinimize::Todo::add(const LitPair& x, weight_t w) {
-	lits_.push_back(x);
-	if (w < minW_) { minW_ = w; }
+void UncoreMinimize::Todo::add(const LitPair& x, Weight_t w) {
+    lits_.push_back(x);
+    if (w < minW_) {
+        minW_ = w;
+    }
 }
 void UncoreMinimize::Todo::shrinkPush(UncoreMinimize& self, Solver& s) {
-	const uint32 skip = step_ < sizeVec(core_) ? core_[step_].id : 0u;
-	uint32 n = next_;
-	for (Todo::const_iterator it = lits_.end(); n--;) {
-		const LitPair& x = *--it;
-		if (x.id != skip && !self.push(s, ~x.lit, x.id)) {
-			break;
-		}
-	}
-}
-bool UncoreMinimize::Todo::shrinkNext(UncoreMinimize& self, ValueRep result) {
-	if (self.options_.trim == OptParams::usc_trim_min) {
-		return subsetNext(self, result);
-	}
-	if (result == value_false) {
-		next_ = last_;
-		step_ = 0u;
-	}
-	else {
-		last_ = next_;
-	}
-	const uint32 t  = self.options_.trim;
-	const uint32 mx = size();
-	uint32 s = step_;
-	switch (t) {
-		default:
-		case OptParams::usc_trim_lin: step_ = s = 1;                break;
-		case OptParams::usc_trim_inv: step_ = s = (mx - next_) - 1; break;
-		case OptParams::usc_trim_bin: step_ = s = (mx - next_) / 2; break;
-		case OptParams::usc_trim_rgs: // fallthrough
-		case OptParams::usc_trim_exp:
-			if      (s == 0u)                      { step_ = s = uint32(last_ == 0u); }
-			else if ((next_ + s) < mx)             { step_ = s * 2; }
-			else if (t == OptParams::usc_trim_rgs) { step_ = 2; s = 1; }
-			else                                   { s = (mx - next_) / 2; }
-			break;
-	}
-	return s && (next_ += s) < mx;
-}
-bool UncoreMinimize::Todo::subsetNext(UncoreMinimize& self, ValueRep result) {
-	if      (result == value_true) { ++step_; }
-	else if (!core_.empty()) {
-		for (Todo::const_iterator it = lits_.begin(), end = lits_.end(); it != end; ++it) {
-			self.setFlag(it->id, true);
-		}
-		LitSet::iterator j = core_.begin();
-		uint32 marked = 0u;
-		for (LitSet::const_iterator it = j, s = j + step_, end = core_.end(); it != end; ++it) {
-			if (self.flagged(it->id)) {
-				self.setFlag(it->id, false);
-				++marked;
-				*j++ = *it;
-			}
-			else if (j < s) {
-				--s, --step_;
-			}
-		}
-		assert(marked == size());
-		core_.erase(j, core_.end());
-		next_ = marked;
-	}
-	else {
-		for (Todo::const_iterator it = lits_.end(), end = lits_.begin(); it != end;) { core_.push_back(*--it); }
-		step_ = 0;
-		next_ = size();
-	}
-	return step_ < size() && size() > 1;
-}
-} // end namespaces Clasp
+    const uint32_t skip = step_ < size32(core_) ? core_[step_].id : 0u;
+    uint32_t       n    = next_;
+    for (auto it = lits_.end(); n--;) {
+        const LitPair& x = *--it;
+        if (x.id != skip && not self.push(s, ~x.lit, x.id)) {
+            break;
+        }
+    }
+}
+bool UncoreMinimize::Todo::shrinkNext(UncoreMinimize& self, Val_t result) {
+    if (self.options_.trim == OptParams::usc_trim_min) {
+        return subsetNext(self, result);
+    }
+    if (result == value_false) {
+        next_ = last_;
+        step_ = 0u;
+    }
+    else {
+        last_ = next_;
+    }
+    const uint32_t t  = self.options_.trim;
+    const uint32_t mx = size();
+    uint32_t       s  = step_;
+    switch (t) {
+        default:
+        case OptParams::usc_trim_lin: step_ = s = 1; break;
+        case OptParams::usc_trim_inv: step_ = s = (mx - next_) - 1; break;
+        case OptParams::usc_trim_bin: step_ = s = (mx - next_) / 2; break;
+        case OptParams::usc_trim_rgs: // fallthrough
+        case OptParams::usc_trim_exp:
+            if (s == 0u) {
+                step_ = s = static_cast(last_ == 0u);
+            }
+            else if ((next_ + s) < mx) {
+                step_ = s * 2;
+            }
+            else if (t == OptParams::usc_trim_rgs) {
+                step_ = 2;
+                s     = 1;
+            }
+            else {
+                s = (mx - next_) / 2;
+            }
+            break;
+    }
+    return s && (next_ += s) < mx;
+}
+bool UncoreMinimize::Todo::subsetNext(UncoreMinimize& self, Val_t result) {
+    if (result == value_true) {
+        ++step_;
+    }
+    else if (not core_.empty()) {
+        for (const auto& lit : lits_) { self.setFlag(lit.id, true); }
+        auto     j      = core_.begin();
+        uint32_t marked = 0u;
+        for (LitSet::const_iterator it = j, s = j + step_, end = core_.end(); it != end; ++it) {
+            if (self.flagged(it->id)) {
+                self.setFlag(it->id, false);
+                ++marked;
+                *j++ = *it;
+            }
+            else if (j < s) {
+                --s, --step_;
+            }
+        }
+        assert(marked == size());
+        core_.erase(j, core_.end());
+        next_ = marked;
+    }
+    else {
+        for (auto it = lits_.end(), end = lits_.begin(); it != end;) { core_.push_back(*--it); }
+        step_ = 0;
+        next_ = size();
+    }
+    return step_ < size() && size() > 1;
+}
+} // namespace Clasp
diff --git a/src/model_enumerators.cpp b/src/model_enumerators.cpp
index 394308b..37ba130 100644
--- a/src/model_enumerators.cpp
+++ b/src/model_enumerators.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,338 +22,350 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
+#include 
+#include 
 #include 
+#include 
+
 #include 
-#include 
+#include 
+
 #include 
 #include 
+
 namespace Clasp {
 class ModelEnumerator::ModelFinder : public EnumerationConstraint {
 protected:
-	explicit ModelFinder() : EnumerationConstraint() {}
-	bool hasModel() const { return !solution.empty(); }
-	LitVec solution;
+    ModelFinder() = default;
+    [[nodiscard]] bool hasModel() const { return not solution.empty(); }
+    LitVec             solution{}; // NOLINT
 };
 /////////////////////////////////////////////////////////////////////////////////////////
 // strategy_record
 /////////////////////////////////////////////////////////////////////////////////////////
-class ModelEnumerator::RecordFinder : public ModelFinder {
+class ModelEnumerator::RecordFinder final : public ModelFinder {
 public:
-	RecordFinder() : ModelFinder() { }
-	ConPtr clone() { return new RecordFinder(); }
-	void   doCommitModel(Enumerator& ctx, Solver& s);
-	bool   doUpdate(Solver& s);
-	void   addDecisionNogood(const Solver& s);
-	void   addProjectNogood(const ModelEnumerator& ctx, const Solver& s, bool domain);
+    RecordFinder() = default;
+    ConPtr clone() override { return new RecordFinder(); }
+    void   doCommitModel(Enumerator&, Solver&) override;
+    bool   doUpdate(Solver& s) override;
+    void   addDecisionNogood(const Solver& s);
+    void   addProjectNogood(const ModelEnumerator& ctx, const Solver& s, bool domain);
 };
 
 bool ModelEnumerator::RecordFinder::doUpdate(Solver& s) {
-	if (hasModel()) {
-		ConstraintInfo e(Constraint_t::Other);
-		ClauseCreator::Result ret = ClauseCreator::create(s, solution, ClauseCreator::clause_no_add, e);
-		solution.clear();
-		if (ret.local) { add(ret.local);}
-		if (!ret.ok()) { return false;  }
-	}
-	return true;
+    if (hasModel()) {
+        auto e   = ConstraintInfo(ConstraintType::other);
+        auto ret = ClauseCreator::create(s, solution, ClauseCreator::clause_no_add, e);
+        solution.clear();
+        if (ret.local) {
+            add(ret.local);
+        }
+        if (not ret.ok()) {
+            return false;
+        }
+    }
+    return true;
 }
 void ModelEnumerator::RecordFinder::addDecisionNogood(const Solver& s) {
-	for (uint32 x = s.decisionLevel(); x != 0; --x) {
-		Literal d = s.decision(x);
-		if (!s.auxVar(d.var())) { solution.push_back(~d); }
-		else if (d != s.tagLiteral()) {
-			// Todo: set of vars could be reduced to those having the aux var in their reason set.
-			const LitVec& tr = s.trail();
-			const uint32  end = x != s.decisionLevel() ? s.levelStart(x+1) : (uint32)tr.size();
-			for (uint32 n = s.levelStart(x)+1; n != end; ++n) {
-				if (!s.auxVar(tr[n].var())) { solution.push_back(~tr[n]); }
-			}
-		}
-	}
+    for (uint32_t x = s.decisionLevel(); x != 0; --x) {
+        if (auto d = s.decision(x); not s.auxVar(d.var())) {
+            solution.push_back(~d);
+        }
+        else if (d != s.tagLiteral()) {
+            // Todo: set of vars could be reduced to those having the aux var in their reason set.
+            for (auto level = s.levelLits(x); Literal lit : level.subspan(1)) {
+                if (not s.auxVar(lit.var())) {
+                    solution.push_back(~lit);
+                }
+            }
+        }
+    }
 }
 void ModelEnumerator::RecordFinder::addProjectNogood(const ModelEnumerator& ctx, const Solver& s, bool domain) {
-	for (Var i = 1, end = s.numProblemVars(); i <= end; ++i) {
-		if (ctx.project(i)) {
-			Literal p = Literal(i, s.pref(i).sign());
-			if      (!domain || !s.pref(i).has(ValueSet::user_value)) { solution.push_back(~s.trueLit(i)); }
-			else if (p != s.trueLit(i))                               { solution.push_back(p); }
-		}
-	}
-	solution.push_back(~s.sharedContext()->stepLiteral());
+    for (auto v : s.problemVars()) {
+        if (ctx.project(v)) {
+            auto p = Literal(v, s.pref(v).sign());
+            if (not domain || not s.pref(v).has(ValueSet::user_value)) {
+                solution.push_back(~s.trueLit(v));
+            }
+            else if (p != s.trueLit(v)) {
+                solution.push_back(p);
+            }
+        }
+    }
+    solution.push_back(~s.sharedContext()->stepLiteral());
 }
 void ModelEnumerator::RecordFinder::doCommitModel(Enumerator& en, Solver& s) {
-	ModelEnumerator& ctx = static_cast(en);
-	assert(solution.empty() && "Update not called!");
-	solution.clear();
-	if (ctx.trivial()) {
-		return;
-	}
-	else if (!ctx.projectionEnabled()) {
-		addDecisionNogood(s);
-	}
-	else {
-		addProjectNogood(ctx, s, ctx.domRec());
-	}
-	if (solution.empty()) { solution.push_back(lit_false()); }
-	if (s.sharedContext()->concurrency() > 1) {
-		// parallel solving active - share solution nogood with other solvers
-		en.commitClause(solution);
-		solution.clear();
-	}
+    auto& ctx = static_cast(en);
+    assert(solution.empty() && "Update not called!");
+    solution.clear();
+    if (ctx.trivial()) {
+        return;
+    }
+    if (not ctx.projectionEnabled()) {
+        addDecisionNogood(s);
+    }
+    else {
+        addProjectNogood(ctx, s, ctx.domRec());
+    }
+    if (solution.empty()) {
+        solution.push_back(lit_false);
+    }
+    if (s.sharedContext()->concurrency() > 1) {
+        // parallel solving active - share solution nogood with other solvers
+        std::ignore = en.commitClause(solution);
+        solution.clear();
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // strategy_backtrack
 /////////////////////////////////////////////////////////////////////////////////////////
-class ModelEnumerator::BacktrackFinder : public ModelFinder {
+class ModelEnumerator::BacktrackFinder final : public ModelFinder {
 public:
-	explicit BacktrackFinder(uint32 projOpts) : ModelFinder(), opts(projOpts) {}
-	// EnumerationConstraint interface
-	ConPtr clone() { return new BacktrackFinder(opts); }
-	void   doCommitModel(Enumerator& ctx, Solver& s);
-	bool   doUpdate(Solver& s);
-	// Constraint interface
-	PropResult  propagate(Solver&, Literal, uint32&);
-	void        reason(Solver& s, Literal p, LitVec& x){
-		for (uint32 i = 1, end = s.level(p.var()); i <= end; ++i) {
-			x.push_back(s.decision(i));
-		}
-	}
-	bool simplify(Solver& s, bool reinit) {
-		for (ProjectDB::iterator it = projNogoods.begin(), end = projNogoods.end(); it != end; ++it) {
-			if (it->second && it->second->simplify(s, false)) {
-				s.removeWatch(it->first, this);
-				it->second->destroy(&s, false);
-				it->second = 0;
-			}
-		}
-		while (!projNogoods.empty() && projNogoods.back().second == 0) { projNogoods.pop_back(); }
-		return ModelFinder::simplify(s, reinit);
-	}
-	void destroy(Solver* s, bool detach) {
-		while (!projNogoods.empty()) {
-			NogoodPair x = projNogoods.back();
-			if (x.second) {
-				if (s) { s->removeWatch(x.first, this); }
-				x.second->destroy(s, detach);
-			}
-			projNogoods.pop_back();
-		}
-		ModelFinder::destroy(s, detach);
-	}
-	typedef std::pair NogoodPair;
-	typedef PodVector::type     ProjectDB;
-	ProjectDB projNogoods;
-	uint32    opts;
+    explicit BacktrackFinder(uint32_t projOpts) : opts(projOpts) {}
+    // EnumerationConstraint interface
+    ConPtr clone() override { return new BacktrackFinder(opts); }
+    void   doCommitModel(Enumerator& ctx, Solver& s) override;
+    bool   doUpdate(Solver& s) override;
+    // Constraint interface
+    PropResult propagate(Solver&, Literal, uint32_t&) override;
+    void       reason(Solver& s, Literal p, LitVec& x) override {
+        for (uint32_t i = 1, end = s.level(p.var()); i <= end; ++i) { x.push_back(s.decision(i)); }
+    }
+    bool simplify(Solver& s, bool reinit) override {
+        for (auto& [watch, con] : projNogoods) {
+            if (con && con->simplify(s, false)) {
+                s.removeWatch(watch, this);
+                con->destroy(&s, false);
+                con = nullptr;
+            }
+        }
+        while (not projNogoods.empty() && projNogoods.back().second == nullptr) { projNogoods.pop_back(); }
+        return ModelFinder::simplify(s, reinit);
+    }
+    void destroy(Solver* s, bool detach) override {
+        while (not projNogoods.empty()) {
+            if (auto& [watch, con] = projNogoods.back(); con) {
+                if (s) {
+                    s->removeWatch(watch, this);
+                }
+                con->destroy(s, detach);
+            }
+            projNogoods.pop_back();
+        }
+        ModelFinder::destroy(s, detach);
+    }
+    using NogoodPair = std::pair;
+    using ProjectDB  = PodVector_t;
+    ProjectDB projNogoods;
+    uint32_t  opts;
 };
 
-Constraint::PropResult ModelEnumerator::BacktrackFinder::propagate(Solver& s, Literal, uint32& pos) {
-	assert(pos < projNogoods.size() && projNogoods[pos].second != 0);
-	ClauseHead* c = static_cast(projNogoods[pos].second);
-	if (!c->locked(s)) {
-		c->destroy(&s, true);
-		projNogoods[pos].second = (c = 0);
-		while (!projNogoods.empty() && !projNogoods.back().second) {
-			projNogoods.pop_back();
-		}
-	}
-	return PropResult(true, c != 0);
+Constraint::PropResult ModelEnumerator::BacktrackFinder::propagate(Solver& s, Literal, uint32_t& pos) {
+    assert(pos < projNogoods.size() && projNogoods[pos].second != nullptr);
+    auto* c = static_cast(projNogoods[pos].second);
+    if (not c->locked(s)) {
+        c->destroy(&s, true);
+        projNogoods[pos].second = (c = nullptr);
+        while (not projNogoods.empty() && not projNogoods.back().second) { projNogoods.pop_back(); }
+    }
+    return PropResult(true, c != nullptr);
 }
 bool ModelEnumerator::BacktrackFinder::doUpdate(Solver& s) {
-	if (hasModel()) {
-		bool   ok = true;
-		uint32 sp = (opts & ModelEnumerator::project_save_progress) != 0 ? Solver::undo_save_phases : 0;
-		s.undoUntil(s.backtrackLevel(), sp|Solver::undo_pop_bt_level);
-		ClauseRep rep = ClauseCreator::prepare(s, solution, 0, Constraint_t::Conflict);
-		if (rep.size == 0 || s.isFalse(rep.lits[0])) { // The decision stack is already ordered.
-			ok = s.backtrack();
-		}
-		else if (rep.size == 1 || s.isFalse(rep.lits[1])) { // The projection nogood is unit. Force the single remaining literal from the current DL.
-			ok = s.force(rep.lits[0], this);
-		}
-		else if (!s.isTrue(rep.lits[0])) { // Shorten the projection nogood by assuming one of its literals to false.
-			uint32  f = static_cast(std::stable_partition(rep.lits+2, rep.lits+rep.size, std::not1(std::bind1st(std::mem_fun(&Solver::isFalse), &s))) - rep.lits);
-			Literal x = (opts & ModelEnumerator::project_use_heuristic) != 0 ? s.heuristic()->selectRange(s, rep.lits, rep.lits+f) : rep.lits[0];
-			Constraint* c = Clause::newContractedClause(s, rep, f, true);
-			POTASSCO_REQUIRE(c, "Invalid constraint!");
-			s.assume(~x);
-			// Remember that we must backtrack the current decision
-			// level in order to guarantee a different projected solution.
-			s.setBacktrackLevel(s.decisionLevel(), Solver::undo_pop_proj_level);
-			// Attach nogood to the current decision literal.
-			// Once we backtrack to x, the then obsolete nogood is destroyed
-			// keeping the number of projection nogoods linear in the number of (projection) atoms.
-			s.addWatch(x, this, (uint32)projNogoods.size());
-			projNogoods.push_back(NogoodPair(x, c));
-			ok = true;
-		}
-		solution.clear();
-		return ok;
-	}
-	if (optimize() || s.sharedContext()->concurrency() == 1 || disjointPath()) {
-		return true;
-	}
-	s.setStopConflict();
-	return false;
+    if (hasModel()) {
+        bool     ok = true;
+        uint32_t sp = Potassco::test_any(opts, project_save_progress) ? Solver::undo_save_phases : Solver::undo_default;
+        s.undoUntil(s.backtrackLevel(), sp | Solver::undo_pop_bt_level);
+        auto rep = ClauseCreator::prepare(s, solution, {}, ConstraintType::conflict);
+        if (rep.size == 0 || s.isFalse(rep.lits[0])) { // The decision stack is already ordered.
+            ok = s.backtrack();
+        }
+        else if (rep.size == 1 || s.isFalse(rep.lits[1])) { // The projection nogood is unit. Force the single remaining
+                                                            // literal from the current DL.
+            ok = s.force(rep.lits[0], this);
+        }
+        else if (not s.isTrue(rep.lits[0])) { // Shorten the projection nogood by assuming one of its literals to false.
+            auto f =
+                std::stable_partition(rep.lits + 2, rep.lits + rep.size, [&](Literal x) { return not s.isFalse(x); }) -
+                rep.lits;
+
+            auto  x = (opts & project_use_heuristic) != 0 ? s.heuristic()->selectRange(s, {rep.lits, rep.lits + f})
+                                                          : rep.lits[0];
+            auto* c = Clause::newContractedClause(s, rep, static_cast(f), true);
+            POTASSCO_CHECK_PRE(c, "Invalid constraint!");
+            s.assume(~x);
+            // Remember that we must backtrack the current decision
+            // level in order to guarantee a different projected solution.
+            s.setBacktrackLevel(s.decisionLevel(), Solver::undo_pop_proj_level);
+            // Attach nogood to the current decision literal.
+            // Once we backtrack to x, the then obsolete nogood is destroyed
+            // keeping the number of projection nogoods linear in the number of (projection) atoms.
+            s.addWatch(x, this, size32(projNogoods));
+            projNogoods.push_back(NogoodPair(x, c));
+            ok = true;
+        }
+        solution.clear();
+        return ok;
+    }
+    if (optimize() || s.sharedContext()->concurrency() == 1 || disjointPath()) {
+        return true;
+    }
+    s.setStopConflict();
+    return false;
 }
 
 void ModelEnumerator::BacktrackFinder::doCommitModel(Enumerator& ctx, Solver& s) {
-	ModelEnumerator& en = static_cast(ctx);
-	uint32           dl = s.decisionLevel();
-	solution.assign(1, dl ? ~s.decision(dl) : lit_false());
-	if (en.projectionEnabled()) {
-		// Remember the current projected assignment as a nogood.
-		solution.clear();
-		for (Var i = 1, end = s.numProblemVars(); i <= end; ++i) {
-			if (en.project(i)) {
-				solution.push_back(~s.trueLit(i));
-			}
-		}
-		// Tag solution
-		solution.push_back(~s.sharedContext()->stepLiteral());
-		// Remember initial decisions that are projection vars.
-		for (dl = s.rootLevel(); dl < s.decisionLevel(); ++dl) {
-			if (!en.project(s.decision(dl+1).var())) { break; }
-		}
-		s.setBacktrackLevel(dl, Solver::undo_pop_proj_level);
-	}
-	else {
-		s.setBacktrackLevel(dl);
-	}
+    auto&    en = static_cast(ctx);
+    uint32_t dl = s.decisionLevel();
+    solution.assign(1, dl ? ~s.decision(dl) : lit_false);
+    if (en.projectionEnabled()) {
+        // Remember the current projected assignment as a nogood.
+        solution.clear();
+        for (auto v : s.problemVars()) {
+            if (en.project(v)) {
+                solution.push_back(~s.trueLit(v));
+            }
+        }
+        // Tag solution
+        solution.push_back(~s.sharedContext()->stepLiteral());
+        // Remember initial decisions that are projection vars.
+        for (dl = s.rootLevel(); dl < s.decisionLevel(); ++dl) {
+            if (not en.project(s.decision(dl + 1).var())) {
+                break;
+            }
+        }
+        s.setBacktrackLevel(dl, Solver::undo_pop_proj_level);
+    }
+    else {
+        s.setBacktrackLevel(dl);
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class ModelEnumerator
 /////////////////////////////////////////////////////////////////////////////////////////
-ModelEnumerator::ModelEnumerator(Strategy st)
-	: Enumerator() {
-	std::memset(&opts_, 0, sizeof(Options));
-	setStrategy(st);
-	trivial_ = false;
-}
+ModelEnumerator::ModelEnumerator(Strategy st) { setStrategy(st); }
 
 Enumerator* EnumOptions::createModelEnumerator(const EnumOptions& opts) {
-	ModelEnumerator*          e = new ModelEnumerator();
-	ModelEnumerator::Strategy s = ModelEnumerator::strategy_auto;
-	if (opts.enumMode && opts.models()) {
-		s = opts.enumMode == enum_bt ? ModelEnumerator::strategy_backtrack : ModelEnumerator::strategy_record;
-	}
-	e->setStrategy(s, opts.project | (opts.enumMode == enum_dom_record ? ModelEnumerator::project_dom_lits : 0));
-	return e;
+    auto* e = new ModelEnumerator();
+    auto  s = ModelEnumerator::strategy_auto;
+    if (opts.enumMode && opts.models()) {
+        s = opts.enumMode == enum_bt ? ModelEnumerator::strategy_backtrack : ModelEnumerator::strategy_record;
+    }
+    e->setStrategy(s, opts.project | (opts.enumMode == enum_dom_record ? ModelEnumerator::project_dom_lits : 0));
+    return e;
 }
 
-ModelEnumerator::~ModelEnumerator() {}
+ModelEnumerator::~ModelEnumerator() = default;
 
-void ModelEnumerator::setStrategy(Strategy st, uint32 projection, char f) {
-	opts_.algo = st;
-	opts_.proj = projection;
-	filter_ = f;
-	if ((projection & 7u) != 0) {
-		opts_.proj |= uint32(project_enable_simple);
-	}
-	saved_ = opts_;
+void ModelEnumerator::setStrategy(Strategy st, uint32_t projection, char filter) {
+    opts_.algo = st;
+    opts_.proj = projection;
+    filter_    = filter;
+    if (Potassco::test_any(projection, 7u)) {
+        opts_.proj |= static_cast(project_enable_simple);
+    }
+    saved_ = opts_;
 }
 
 EnumerationConstraint* ModelEnumerator::doInit(SharedContext& ctx, SharedMinimizeData* opt, int numModels) {
-	opts_ = saved_;
-	initProjection(ctx);
-	if (ctx.concurrency() > 1 && !ModelEnumerator::supportsParallel()) {
-		opts_.algo = strategy_auto;
-	}
-	bool optOne  = opt && opt->mode() == MinimizeMode_t::optimize;
-	bool trivial = (optOne && !domRec()) || std::abs(numModels) == 1;
-	if (optOne && projectionEnabled()) {
-		for (const WeightLiteral* it =  minimizer()->lits; !isSentinel(it->first) && trivial; ++it) {
-			trivial = project(it->first.var());
-		}
-		if (!trivial) { ctx.warn("Projection: Optimization may depend on enumeration order."); }
-	}
-	if (opts_.algo == strategy_auto) { opts_.algo = trivial || (projectionEnabled() && ctx.concurrency() > 1) ? strategy_record : strategy_backtrack; }
-	trivial_ = trivial;
-	EnumerationConstraint* c = opts_.algo == strategy_backtrack
-		? static_cast(new BacktrackFinder(projectOpts()))
-		: static_cast(new RecordFinder());
-	if (projectionEnabled()) { setIgnoreSymmetric(true); }
-	return c;
+    opts_ = saved_;
+    initProjection(ctx);
+    if (ctx.concurrency() > 1 && not ModelEnumerator::supportsParallel()) {
+        opts_.algo = strategy_auto;
+    }
+    bool optOne  = opt && opt->mode() == MinimizeMode::optimize;
+    bool trivial = (optOne && not domRec()) || std::abs(numModels) == 1;
+    if (optOne && projectionEnabled()) {
+        for (const auto& [lit, _] : *minimizer()) {
+            if (trivial = trivial && project(lit.var()); not trivial) {
+                break;
+            }
+        }
+        if (not trivial) {
+            ctx.warn("Projection: Optimization may depend on enumeration order.");
+        }
+    }
+    if (opts_.algo == strategy_auto) {
+        opts_.algo = trivial || (projectionEnabled() && ctx.concurrency() > 1) ? strategy_record : strategy_backtrack;
+    }
+    trivial_ = trivial;
+    auto* c  = opts_.algo == strategy_backtrack ? static_cast(new BacktrackFinder(projectOpts()))
+                                                : static_cast(new RecordFinder());
+    if (projectionEnabled()) {
+        setIgnoreSymmetric(true);
+    }
+    return c;
 }
 
 void ModelEnumerator::initProjection(SharedContext& ctx) {
-	project_.clear();
-	if (!projectionEnabled()) { return; }
-	const OutputTable& out = ctx.output;
-	if (domRec()) {
-		const SolverParams&  p = ctx.configuration()->solver(0);
-		const DomainTable& dom = ctx.heuristic;
-		const Solver& s = *ctx.master();
-		if (p.heuId == Heuristic_t::Domain) {
-			for (uint32 i = 0, end = dom.assume ? sizeVec(*dom.assume) : 0; i != end; ++i) {
-				ctx.mark((*dom.assume)[i]);
-			}
-			DomainTable pref;
-			for (DomainTable::iterator it = dom.begin(), end = dom.end(); it != end; ++it) {
-				if ((it->comp() || it->type() == DomModType::Level) && (s.isTrue(it->cond()) || ctx.marked(it->cond()))) {
-					pref.add(it->var(), it->type(), it->bias(), it->prio(), lit_true());
-				}
-			}
-			pref.simplify();
-			for (DomainTable::iterator it = pref.begin(), end = pref.end(); it != end; ++it) {
-				if (it->bias() > 0) { addProject(ctx, it->var()); }
-			}
-			for (uint32 i = 0, end = dom.assume ? sizeVec(*dom.assume) : 0; i != end; ++i) {
-				ctx.unmark((*dom.assume)[i].var());
-			}
-			if ((p.heuristic.domMod & HeuParams::mod_level) != 0u) {
-				struct AddProject : DomainTable::DefaultAction {
-					AddProject(ModelEnumerator& e, SharedContext& c) : en(&e), ctx(&c) {}
-					void atom(Literal p, HeuParams::DomPref, uint32) { en->addProject(*ctx, p.var()); }
-					ModelEnumerator* en; SharedContext* ctx;
-				} act(*this, ctx);
-				DomainTable::applyDefault(ctx, act, p.heuristic.domPref);
-			}
-		}
-		if (project_.empty()) {
-			ctx.warn("domRec ignored: No domain atoms found.");
-			opts_.proj -= project_dom_lits;
-			initProjection(ctx);
-			return;
-		}
-		else if (ctx.concurrency() > 1) {
-			for (uint32 i = 1, end = ctx.concurrency(); i != end; ++i) {
-				const SolverParams pi = ctx.configuration()->solver(i);
-				if (pi.heuId != p.heuId || pi.heuristic.domMod != p.heuristic.domMod || (pi.heuristic.domPref && pi.heuristic.domPref != p.heuristic.domPref)) {
-					ctx.warn("domRec: Inconsistent domain heuristics, results undefined.");
-					break;
-				}
-			}
-		}
-	}
-	else if (out.projectMode() == ProjectMode_t::Output) {
-		// Mark all relevant output variables.
-		for (OutputTable::pred_iterator it = out.pred_begin(), end = out.pred_end(); it != end; ++it) {
-			if (*it->name != filter_) { addProject(ctx, it->cond.var()); }
-		}
-		for (OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) {
-			addProject(ctx, *it);
-		}
-	}
-	else {
-		// Mark explicitly requested variables only.
-		for (OutputTable::lit_iterator it = out.proj_begin(), end = out.proj_end(); it != end; ++it) {
-			addProject(ctx, it->var());
-		}
-	}
+    project_.clear();
+    if (domRec() && initDomRec(ctx)) {
+        return;
+    }
+    if (projectionEnabled()) {
+        if (const auto& out = ctx.output; out.projectMode() == ProjectMode::output) {
+            // Mark all relevant output variables.
+            for (const auto& p : out.pred_range()) {
+                if (*p.name.c_str() != filter_) {
+                    addProject(ctx, p.cond.var());
+                }
+            }
+            for (auto v : out.vars_range()) { addProject(ctx, v); }
+        }
+        else {
+            // Mark explicitly requested variables only.
+            for (auto lit : out.proj_range()) { addProject(ctx, lit.var()); }
+        }
+    }
 }
-
-void ModelEnumerator::addProject(SharedContext& ctx, Var v) {
-	const uint32 wIdx = v / 32;
-	const uint32 bIdx = v & 31;
-	if (wIdx >= project_.size()) { project_.resize(wIdx + 1, 0); }
-	store_set_bit(project_[wIdx], bIdx);
-	ctx.setFrozen(v, true);
-}
-bool ModelEnumerator::project(Var v) const {
-	const uint32 wIdx = v / 32;
-	const uint32 bIdx = v & 31;
-	return wIdx < project_.size() && test_bit(project_[wIdx], bIdx);
+bool ModelEnumerator::initDomRec(SharedContext& ctx) {
+    const auto& p   = ctx.configuration()->solver(0);
+    const auto& dom = ctx.heuristic;
+    const auto& s   = *ctx.master();
+    if (auto assume = dom.assume ? std::span(*dom.assume) : std::span(); p.heuId == HeuristicType::domain) {
+        for (auto lit : assume) { ctx.mark(lit); }
+        DomainTable pref;
+        for (const auto& d : dom) {
+            if ((d.comp() || d.type() == DomModType::level) && (s.isTrue(d.cond()) || ctx.marked(d.cond()))) {
+                pref.add(d.var(), d.type(), d.bias(), d.prio(), lit_true);
+            }
+        }
+        pref.simplify();
+        for (const auto& d : pref) {
+            if (d.bias() > 0) {
+                addProject(ctx, d.var());
+            }
+        }
+        for (auto lit : assume) { ctx.unmark(lit.var()); }
+        if ((p.heuristic.domMod & HeuParams::mod_level) != 0u) {
+            DomainTable::applyDefault(
+                ctx, [&](Literal lit, auto, auto) { addProject(ctx, lit.var()); }, p.heuristic.domPref);
+        }
+    }
+    if (project_.empty()) {
+        ctx.warn("domRec ignored: No domain atoms found.");
+        opts_.proj -= project_dom_lits;
+        return false;
+    }
+    if (ctx.concurrency() > 1) {
+        for (auto i : irange(1u, ctx.concurrency())) {
+            const SolverParams pi = ctx.configuration()->solver(i);
+            if (pi.heuId != p.heuId || pi.heuristic.domMod != p.heuristic.domMod ||
+                (pi.heuristic.domPref && pi.heuristic.domPref != p.heuristic.domPref)) {
+                ctx.warn("domRec: Inconsistent domain heuristics, results undefined.");
+                break;
+            }
+        }
+    }
+    return true;
 }
 
+void ModelEnumerator::addProject(SharedContext& ctx, Var_t v) {
+    project_.add(v);
+    ctx.setFrozen(v, true);
 }
+bool ModelEnumerator::project(Var_t v) const { return project_.contains(v); }
+
+} // namespace Clasp
diff --git a/src/parallel_solve.cpp b/src/parallel_solve.cpp
index 5b8823c..8d1d43a 100644
--- a/src/parallel_solve.cpp
+++ b/src/parallel_solve.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2010-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,573 +22,631 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
 #include 
+#include 
+#include 
 #include 
-#include 
-#include 
-#include 
-namespace Clasp { namespace mt {
+
+#include 
+
+#include 
+
+#include 
+#include 
+
+namespace Clasp::mt {
 /////////////////////////////////////////////////////////////////////////////////////////
-// ParallelSolve::Impl
+// ParallelSolve::SharedData
 /////////////////////////////////////////////////////////////////////////////////////////
 struct ParallelSolve::SharedData {
-	typedef PodQueue Queue;
-	typedef condition_variable      ConditionVar;
-	enum MsgFlag {
-		terminate_flag        = 1u, sync_flag  = 2u,  split_flag    = 4u,
-		restart_flag          = 8u, complete_flag = 16u,
-		interrupt_flag        = 32u, // set on terminate from outside
-		allow_split_flag      = 64u, // set if splitting mode is active
-		forbid_restart_flag   = 128u,// set if restarts are no longer allowed
-		cancel_restart_flag   = 256u,// set if current restart request was cancelled by some thread
-		restart_abandoned_flag= 512u,// set to signal that threads must not give up their gp
-	};
-	enum Message {
-		msg_terminate      = (terminate_flag),
-		msg_interrupt      = (terminate_flag | interrupt_flag),
-		msg_sync_restart   = (sync_flag | restart_flag),
-		msg_split          = split_flag
-	};
-	struct Generator {
-		enum State { start = 0, search = 1, model = 2, done = 3 };
-		Generator() : state(start) {}
-		mutex genM;
-		condition_variable cond;
-		State waitWhile(State st) {
-			State r;
-			for (unique_lock lock(genM); (r = state) == st;) { cond.wait(lock); }
-			return r;
-		}
-		void pushModel() {
-			notify(model);
-			waitWhile(model);
-		}
-		void notify(State st) {
-			unique_lock lock(genM);
-			state = st;
-			cond.notify_one();
-		}
-		State state;
-	};
-	SharedData() : path(0) { reset(0); control = 0; }
-	void reset(SharedContext* a_ctx) {
-		clearQueue();
-		syncT.reset();
-		msg.clear();
-		globalR.reset();
-		maxConflict = globalR.current();
-		threads     = a_ctx ? a_ctx->concurrency() : 0;
-		waiting     = 0;
-		errorSet    = 0;
-		initVec     = 0;
-		ctx         = a_ctx;
-		path        = 0;
-		nextId      = 1;
-		workReq     = 0;
-		restartReq  = 0;
-		generator   = 0;
-		errorCode   = 0;
-	}
-	void clearQueue() {
-		while (!workQ.empty()) { delete workQ.pop_ret(); }
-		workQ.clear();
-	}
-	const LitVec* requestWork(const Solver& s) {
-		const uint64 m(uint64(1) << s.id());
-		if ((initVec & m) == m) {
-			if (!allowSplit()) {
-				// portfolio mode - all solvers can start with initial path
-				initVec -= m;
-				return path;
-			}
-			if (initVec.exchange(0) != 0) {
-				// splitting mode - only one solver must start with initial path
-				return path;
-			}
-		}
-		if (!allowSplit()) { return 0; }
-		ctx->report(MessageEvent(s, "SPLIT", MessageEvent::sent));
-		// try to get work from split
-		const LitVec* ret = 0;
-		for (unique_lock lock(workM); !hasControl(uint32(terminate_flag|sync_flag));) {
-			if (!workQ.empty()) {
-				ret = workQ.pop_ret();
-				if (workQ.empty()) { workQ.clear(); }
-				break;
-			}
-			postMessage(msg_split, false);
-			if (!enterWait(lock))
-				break;
-		}
-		ctx->report("resume after wait", &s);
-		return ret;
-	}
-	void pushWork(const LitVec* v) {
-		unique_lock lock(workM);
-		workQ.push(v);
-		notifyWaitingThreads(&lock, 1);
-	}
-	void notifyWaitingThreads(unique_lock* lock = 0, int n = 0) {
-		assert(!lock || lock->owns_lock());
-		if (lock)
-			lock->unlock();
-		else
-			unique_lock preventLostWakeup(workM);
-		n == 1 ? workCond.notify_one() : workCond.notify_all();
-	}
-	bool enterWait(unique_lock& lock) {
-		assert(lock.owns_lock());
-		if ((waiting + 1) >= threads)
-			return false;
-		++waiting;
-		workCond.wait(lock);
-		--waiting;
-		return true;
-	}
-	bool waitSync() {
-		for (unique_lock lock(workM); synchronize();) {
-			if (!enterWait(lock)) {
-				assert(synchronize());
-				return true;
-			}
-		}
-		return false;
-	}
-	uint32 leaveAlgorithm() {
-		assert(threads);
-		unique_lock lock(workM);
-		uint32 res = --threads;
-		notifyWaitingThreads(&lock);
-		return res;
-	}
-	// MESSAGES
-	bool postMessage(Message m, bool notify);
-	bool hasMessage()  const { return (control & uint32(7)) != 0; }
-	bool synchronize() const { return (control & uint32(sync_flag))      != 0; }
-	bool terminate()   const { return (control & uint32(terminate_flag)) != 0; }
-	bool split()       const { return (control & uint32(split_flag))     != 0; }
-	void aboutToSplit()      { if (--workReq == 0) updateSplitFlag();  }
-	void updateSplitFlag();
-	// CONTROL FLAGS
-	bool hasControl(uint32 f) const { return (control & f) != 0;        }
-	bool interrupt()          const { return hasControl(interrupt_flag);}
-	bool complete()           const { return hasControl(complete_flag); }
-	bool restart()            const { return hasControl(restart_flag);  }
-	bool allowSplit()         const { return hasControl(allow_split_flag); }
-	bool allowRestart()       const { return !hasControl(forbid_restart_flag); }
-	bool setControl(uint32 flags)   { return (control.fetch_or(flags) & flags) != flags; }
-	bool clearControl(uint32 flags) { return (control.fetch_and(~flags) & flags) == flags; }
-	typedef SingleOwnerPtr GeneratorPtr;
-	Potassco::StringBuilder msg;  // global error message
-	ScheduleStrategy globalR;     // global restart strategy
-	uint64           maxConflict; // current restart limit
-	atomic   errorSet;    // bitmask of erroneous solvers
-	SharedContext*   ctx;         // shared context object
-	const LitVec*    path;        // initial guiding path - typically empty
-	atomic   initVec;     // vector of initial gp - represented as bitset
-	GeneratorPtr     generator;   // optional data for model generation
-	Timer  syncT;       // thread sync time
-	mutex            modelM;      // model-mutex
-	mutex            workM;       // work-mutex
-	ConditionVar     workCond;    // work-condition
-	Queue            workQ;       // work-queue (must be protected by workM)
-	uint32           waiting;     // number of worker threads waiting on workCond
-	uint32           nextId;      // next solver id to use
-	LowerBound       lower;       // last reported lower bound (if any)
-	atomic   threads;     // number of threads in the algorithm
-	atomic      workReq;     // > 0: someone needs work
-	atomic   restartReq;  // == numThreads(): restart
-	atomic   control;     // set of active message flags
-	atomic   modCount;    // counter for synchronizing models
-	int32            errorCode;   // global error code
+    static_assert(amc::is_trivially_relocatable_v);
+    using PathQueue    = std::pair, uint32_t>;
+    using ConditionVar = condition_variable;
+    enum MsgFlag : uint32_t {
+        terminate_flag         = 1u,
+        sync_flag              = 2u,
+        split_flag             = 4u,
+        restart_flag           = 8u,
+        complete_flag          = 16u,
+        interrupt_flag         = 32u,  // set on terminate from outside
+        allow_split_flag       = 64u,  // set if splitting mode is active
+        forbid_restart_flag    = 128u, // set if restarts are no longer allowed
+        cancel_restart_flag    = 256u, // set if current restart request was cancelled by some thread
+        restart_abandoned_flag = 512u, // set to signal that threads must not give up their gp
+    };
+    enum Message : uint32_t {
+        msg_terminate    = terminate_flag,
+        msg_interrupt    = terminate_flag | interrupt_flag,
+        msg_sync_restart = sync_flag | restart_flag,
+        msg_split        = split_flag
+    };
+    struct Generator {
+        enum State { start = 0, search = 1, model = 2, done = 3 };
+        mutex              genM;
+        condition_variable cond;
+        State              waitWhile(State st) {
+            State r;
+            for (unique_lock lock(genM); (r = state) == st;) { cond.wait(lock); }
+            return r;
+        }
+        void pushModel() {
+            notify(model);
+            waitWhile(model);
+        }
+        void notify(State st) {
+            unique_lock lock(genM);
+            state = st;
+            cond.notify_one();
+        }
+        State state{start};
+    };
+    SharedData() = default;
+    void reset(SharedContext& a_ctx) {
+        clearQueue();
+        syncT.reset();
+        msg.clear();
+        globalR.reset();
+        discardVec(path);
+        maxConflict = globalR.current();
+        threads     = a_ctx.concurrency();
+        waiting     = 0;
+        errorSet    = 0;
+        initVec     = 0;
+        ctx         = &a_ctx;
+        nextId      = 1;
+        workReq     = 0;
+        restartReq  = 0;
+        generator   = nullptr;
+        errorCode   = 0;
+    }
+    void clearQueue() {
+        workQ.first.clear();
+        workQ.second = 0;
+    }
+    bool requestWork(const Solver& s, Path& out) {
+        if (const auto m = Potassco::nth_bit(s.id()); Potassco::test_mask(initVec.load(), m)) {
+            // do not take over ownership of initial gp!
+            if (not allowSplit()) {
+                // portfolio mode - all solvers can start with initial path
+                initVec -= m;
+                out      = Path::borrow(path);
+                return true;
+            }
+            if (initVec.exchange(0) != 0) {
+                // splitting mode - only one solver must start with initial path
+                out = Path::borrow(path);
+                return true;
+            }
+        }
+        if (not allowSplit()) {
+            return false;
+        }
+        ctx->report(MessageEvent(s, "SPLIT", MessageEvent::sent));
+        // try to get work from split
+        bool ok = false;
+        for (unique_lock lock(workM); not hasControl(terminate_flag | sync_flag);) {
+            if (auto& [vec, pos] = workQ; not vec.empty()) {
+                out = std::move(vec[pos++]);
+                ok  = true;
+                if (pos == size32(vec)) {
+                    clearQueue();
+                }
+                break;
+            }
+            postMessage(msg_split, false);
+            if (not enterWait(lock)) {
+                break;
+            }
+        }
+        ctx->report("resume after wait", &s);
+        return ok;
+    }
+    void pushWork(Path gp) {
+        POTASSCO_ASSERT(gp.owner());
+        unique_lock lock(workM);
+        workQ.first.push_back(std::move(gp));
+        notifyWaitingThreads(&lock, 1);
+    }
+    void notifyWaitingThreads(unique_lock* lock = nullptr, int n = 0) {
+        assert(not lock || lock->owns_lock());
+        if (lock) {
+            lock->unlock();
+        }
+        else {
+            unique_lock preventLostWakeup(workM);
+        }
+        n == 1 ? workCond.notify_one() : workCond.notify_all();
+    }
+    bool enterWait(unique_lock& lock) {
+        assert(lock.owns_lock());
+        if ((waiting + 1) >= threads) {
+            return false;
+        }
+        ++waiting;
+        workCond.wait(lock);
+        --waiting;
+        return true;
+    }
+    bool waitSync() {
+        for (unique_lock lock(workM); synchronize();) {
+            if (not enterWait(lock)) {
+                assert(synchronize());
+                return true;
+            }
+        }
+        return false;
+    }
+    uint32_t leaveAlgorithm() {
+        assert(threads);
+        unique_lock lock(workM);
+        uint32_t    res = --threads;
+        notifyWaitingThreads(&lock);
+        return res;
+    }
+    // MESSAGES
+    bool               postMessage(Message m, bool notify);
+    [[nodiscard]] bool hasMessage() const { return Potassco::test_any(control.load(), 7u); }
+    [[nodiscard]] bool synchronize() const { return Potassco::test_any(control.load(), sync_flag); }
+    [[nodiscard]] bool terminate() const { return Potassco::test_any(control.load(), terminate_flag); }
+    [[nodiscard]] bool split() const { return Potassco::test_any(control.load(), split_flag); }
+    void               aboutToSplit() {
+        if (--workReq == 0) {
+            updateSplitFlag();
+        }
+    }
+    void updateSplitFlag();
+    // CONTROL FLAGS
+    [[nodiscard]] bool hasControl(uint32_t f) const { return Potassco::test_any(control.load(), f); }
+    [[nodiscard]] bool interrupt() const { return hasControl(interrupt_flag); }
+    [[nodiscard]] bool complete() const { return hasControl(complete_flag); }
+    [[nodiscard]] bool restart() const { return hasControl(restart_flag); }
+    [[nodiscard]] bool allowSplit() const { return hasControl(allow_split_flag); }
+    [[nodiscard]] bool allowRestart() const { return not hasControl(forbid_restart_flag); }
+    bool               setControl(uint32_t flags) { return (control.fetch_or(flags) & flags) != flags; }
+    bool               clearControl(uint32_t flags) { return (control.fetch_and(~flags) & flags) == flags; }
+    using GeneratorPtr = std::unique_ptr;
+    std::string           msg;            // global error message
+    ScheduleStrategy      globalR;        // global restart strategy
+    LitVec                path;           // initial guiding path - typically empty
+    uint64_t              maxConflict{0}; // current restart limit
+    std::atomic errorSet{0};    // bitmask of erroneous solvers
+    SharedContext*        ctx{nullptr};   // shared context object
+    std::atomic initVec{0};     // vector of initial gp - represented as bitset
+    GeneratorPtr          generator;      // optional data for model generation
+    Timer       syncT;          // thread sync time
+    mutex                 modelM;         // model-mutex
+    mutex                 workM;          // work-mutex
+    ConditionVar          workCond;       // work-condition
+    PathQueue             workQ;          // work-queue (must be protected by workM)
+    uint32_t              waiting{0};     // number of worker threads waiting on workCond
+    uint32_t              nextId{1};      // next solver id to use
+    LowerBound            lower;          // last reported lower bound (if any)
+    std::atomic threads{0};     // number of threads in the algorithm
+    std::atomic      workReq{0};     // > 0: someone needs work
+    std::atomic restartReq{0};  // == numThreads(): restart
+    std::atomic control{0};     // set of active message flags
+    std::atomic modCount{0};    // counter for synchronizing models
+    int32_t               errorCode{0};   // global error code
 };
 
-static void* alignedAllocChecked(size_t size, size_t align = 64) {
-	void* mem = Clasp::alignedAlloc(size, align);
-	POTASSCO_REQUIRE(mem, "alignedAlloc failed");
-	return mem;
-}
-
 // post message to all threads
 bool ParallelSolve::SharedData::postMessage(Message m, bool notifyWaiting) {
-	if (m == msg_split) {
-		if (++workReq == 1) { updateSplitFlag(); }
-		return true;
-	}
-	if (setControl(m)) {
-		// control message - notify all if requested
-		if (notifyWaiting) notifyWaitingThreads();
-		if ((uint32(m) & uint32(terminate_flag|sync_flag)) != 0) {
-			syncT.reset();
-			syncT.start();
-		}
-		return true;
-	}
-	return false;
+    if (m == msg_split) {
+        if (++workReq == 1) {
+            updateSplitFlag();
+        }
+        return true;
+    }
+    if (setControl(m)) {
+        // control message - notify all if requested
+        if (notifyWaiting) {
+            notifyWaitingThreads();
+        }
+        if ((static_cast(m) & (terminate_flag | sync_flag)) != 0) {
+            syncT.reset();
+            syncT.start();
+        }
+        return true;
+    }
+    return false;
 }
 
 void ParallelSolve::SharedData::updateSplitFlag() {
-	for (bool splitF;;) {
-		splitF = (workReq > 0);
-		if (split() == splitF) return;
-		if (splitF) control.fetch_or(uint32(split_flag));
-		else        control.fetch_and(~uint32(split_flag));
-	}
+    for (bool splitF;;) {
+        splitF = (workReq > 0);
+        if (split() == splitF) {
+            return;
+        }
+        if (splitF) {
+            control.fetch_or(split_flag);
+        }
+        else {
+            control.fetch_and(~split_flag);
+        }
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ParallelSolve
 /////////////////////////////////////////////////////////////////////////////////////////
+static constexpr uint32_t master_id = 0u;
 ParallelSolve::ParallelSolve(const ParallelSolveOptions& opts)
-	: SolveAlgorithm(opts.limit)
-	, shared_(new SharedData)
-	, thread_(0)
-	, distribution_(opts.distribute)
-	, maxRestarts_(0)
-	, intGrace_(1024)
-	, intTopo_(opts.integrate.topo)
-	, intFlags_(ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add)
-	, modeSplit_(opts.algorithm.mode == ParallelSolveOptions::Algorithm::mode_split) {
-	setRestarts(opts.restarts.maxR, opts.restarts.sched);
-	setIntegrate(opts.integrate.grace, opts.integrate.filter);
+    : SolveAlgorithm(opts.limit)
+    , shared_(std::make_unique())
+    , thread_(nullptr)
+    , distribution_(opts.distribute)
+    , maxRestarts_(0)
+    , intGrace_(1024)
+    , intTopo_(opts.integrate.topo)
+    , intFlags_(ClauseCreator::clause_not_root_sat | ClauseCreator::clause_no_add)
+    , modeSplit_(opts.algorithm.mode == ParallelSolveOptions::Algorithm::mode_split) {
+    setRestarts(opts.restarts.maxR, opts.restarts.sched);
+    setIntegrate(opts.integrate.grace, opts.integrate.filter);
 }
 
 ParallelSolve::~ParallelSolve() {
-	if (shared_->nextId > 1) {
-		// algorithm was not started but there may be active threads -
-		// force orderly shutdown
-		ParallelSolve::doInterrupt();
-		shared_->notifyWaitingThreads();
-		joinThreads();
-	}
-	destroyThread(masterId);
-	delete shared_;
+    if (shared_->nextId > 1) {
+        // algorithm was not started but there may be active threads -
+        // force orderly shutdown
+        ParallelSolve::doInterrupt();
+        shared_->notifyWaitingThreads();
+        joinThreads();
+    }
+    destroyThread(master_id);
+    shared_.reset();
 }
 
-bool ParallelSolve::beginSolve(SharedContext& ctx, const LitVec& path) {
-	assert(ctx.concurrency() && "Illegal number of threads");
-	if (shared_->terminate()) { return false; }
-	shared_->reset(&ctx);
-	if (!enumerator().supportsParallel() && numThreads() > 1) {
-		ctx.warn("Selected reasoning mode implies #Threads=1.");
-		shared_->threads = 1;
-		modeSplit_       = false;
-		ctx.setConcurrency(1, SharedContext::resize_reserve);
-	}
-	shared_->setControl(modeSplit_ ? SharedData::allow_split_flag : SharedData::forbid_restart_flag);
-	shared_->modCount = uint32(enumerator().optimize());
-	shared_->path = &path;
-	if (distribution_.types != 0 && ctx.distributor.get() == 0 && numThreads() > 1) {
-		if (distribution_.mode == ParallelSolveOptions::Distribution::mode_local) {
-			ctx.distributor.reset(new mt::LocalDistribution(distribution_, ctx.concurrency(), intTopo_));
-		}
-		else {
-			ctx.distributor.reset(new mt::GlobalDistribution(distribution_, ctx.concurrency(), intTopo_));
-		}
-	}
-	shared_->setControl(SharedData::sync_flag); // force initial sync with all threads
-	shared_->syncT.start();
-	reportProgress(MessageEvent(*ctx.master(), "SYNC", MessageEvent::sent));
-	assert(ctx.master()->id() == masterId);
-	allocThread(masterId, *ctx.master());
-	for (uint32 i = 1; i != ctx.concurrency(); ++i) {
-		uint32 id = shared_->nextId++;
-		allocThread(id, *ctx.solver(id));
-		// start in new thread
-		Clasp::mt::thread x(std::mem_fn(&ParallelSolve::solveParallel), this, id);
-		thread_[id]->setThread(x);
-	}
-	return true;
+bool ParallelSolve::beginSolve(SharedContext& ctx, LitView path) {
+    assert(ctx.concurrency() && "Illegal number of threads");
+    if (shared_->terminate()) {
+        return false;
+    }
+    shared_->reset(ctx);
+    if (not enumerator().supportsParallel() && numThreads() > 1) {
+        ctx.warn("Selected reasoning mode implies #Threads=1.");
+        shared_->threads = 1;
+        modeSplit_       = false;
+        ctx.setConcurrency(1, SharedContext::resize_reserve);
+    }
+    shared_->setControl(modeSplit_ ? SharedData::allow_split_flag : SharedData::forbid_restart_flag);
+    shared_->modCount = static_cast(enumerator().optimize());
+    shared_->path.assign(path.begin(), path.end());
+    if (distribution_.types != 0 && ctx.distributor.get() == nullptr && numThreads() > 1) {
+        auto topo = intTopo_;
+        if (distribution_.mode == ParallelSolveOptions::Distribution::mode_local) {
+            ctx.distributor = std::make_unique(distribution_, ctx.concurrency(), topo);
+        }
+        else {
+            ctx.distributor = std::make_unique(distribution_, ctx.concurrency(), topo);
+        }
+    }
+    shared_->setControl(SharedData::sync_flag); // force initial sync with all threads
+    shared_->syncT.start();
+    reportProgress(MessageEvent(*ctx.master(), "SYNC", MessageEvent::sent));
+    assert(ctx.master()->id() == master_id);
+    allocThread(master_id, *ctx.master());
+    for ([[maybe_unused]] auto i : irange(ctx.concurrency() - 1)) {
+        uint32_t id = shared_->nextId++;
+        allocThread(id, *ctx.solver(id));
+        // start in new thread
+        thread_[id]->setThread(Clasp::mt::thread(std::mem_fn(&ParallelSolve::solveParallel), this, id));
+    }
+    return true;
 }
 
-void ParallelSolve::setIntegrate(uint32 grace, uint8 filter) {
-	typedef ParallelSolveOptions::Integration Dist;
-	intGrace_ = grace;
-	intFlags_ = ClauseCreator::clause_no_add;
-	if (filter == Dist::filter_heuristic) { store_set_bit(intFlags_, 31); }
-	if (filter != Dist::filter_no)        { intFlags_ |= ClauseCreator::clause_not_root_sat; }
-	if (filter == Dist::filter_sat)       { intFlags_ |= ClauseCreator::clause_not_sat; }
+void ParallelSolve::setIntegrate(uint32_t grace, uint8_t filter) {
+    using Dist = ParallelSolveOptions::Integration;
+    intGrace_  = grace;
+    intFlags_  = ClauseCreator::clause_no_add;
+    if (filter == Dist::filter_heuristic) {
+        Potassco::store_set_bit(intFlags_, 31);
+    }
+    if (filter != Dist::filter_no) {
+        intFlags_ |= ClauseCreator::clause_not_root_sat;
+    }
+    if (filter == Dist::filter_sat) {
+        intFlags_ |= ClauseCreator::clause_not_sat;
+    }
 }
 
-void ParallelSolve::setRestarts(uint32 maxR, const ScheduleStrategy& rs) {
-	maxRestarts_         = maxR;
-	shared_->globalR     = maxR ? rs : ScheduleStrategy::none();
-	shared_->maxConflict = shared_->globalR.current();
+void ParallelSolve::setRestarts(uint32_t maxR, const ScheduleStrategy& rs) {
+    maxRestarts_         = maxR;
+    shared_->globalR     = maxR ? rs : ScheduleStrategy::none();
+    shared_->maxConflict = shared_->globalR.current();
 }
 
-uint32 ParallelSolve::numThreads() const { return shared_->threads; }
+uint32_t ParallelSolve::numThreads() const { return shared_->threads; }
 
-void ParallelSolve::allocThread(uint32 id, Solver& s) {
-	if (!thread_) {
-		uint32 n = numThreads();
-		thread_  = new ParallelHandler*[n];
-		std::fill(thread_, thread_+n, static_cast(0));
-	}
-	size_t sz   = ((sizeof(ParallelHandler)+63) / 64) * 64;
-	thread_[id] = new (alignedAllocChecked(sz)) ParallelHandler(*this, s);
+void ParallelSolve::allocThread(uint32_t id, Solver& s) {
+    if (not thread_) {
+        thread_ = std::make_unique(numThreads());
+    }
+    thread_[id] = std::make_unique(*this, s);
 }
 
-void ParallelSolve::destroyThread(uint32 id) {
-	if (thread_ && thread_[id]) {
-		assert(!thread_[id]->joinable() && "Shutdown not completed!");
-		thread_[id]->~ParallelHandler();
-		alignedFree(thread_[id]);
-		thread_[id] = 0;
-		if (id == masterId) {
-			delete [] thread_;
-			thread_ = 0;
-		}
-	}
+void ParallelSolve::destroyThread(uint32_t id) {
+    if (thread_ && thread_[id]) {
+        assert(not thread_[id]->joinable() && "Shutdown not completed!");
+        thread_[id].reset();
+        if (id == master_id) {
+            thread_.reset();
+        }
+    }
 }
 
-inline void ParallelSolve::reportProgress(const Event& ev) const {
-	return shared_->ctx->report(ev);
-}
+inline void ParallelSolve::reportProgress(const Event& ev) const { return shared_->ctx->report(ev); }
 inline void ParallelSolve::reportProgress(const Solver& s, const char* msg) const {
-	return shared_->ctx->report(msg, &s);
+    return shared_->ctx->report(msg, &s);
 }
 
 // joins with and destroys all active threads
 int ParallelSolve::joinThreads() {
-	uint32 winner = thread_[masterId]->winner() ? uint32(masterId) : UINT32_MAX;
-	// detach master only after all client threads are done
-	for (uint32 i = 1, end = shared_->nextId; i != end; ++i) {
-		thread_[i]->join();
-		if (thread_[i]->winner() && i < winner) {
-			winner = i;
-		}
-		Solver* s = &thread_[i]->solver();
-		reportProgress(*s, "joined");
-		destroyThread(i);
-		reportProgress(*s, "destroyed");
-	}
-	if (shared_->complete()) {
-		enumerator().commitComplete();
-	}
-	thread_[masterId]->handleTerminateMessage();
-	shared_->ctx->setWinner(winner);
-	shared_->nextId = 1;
-	shared_->syncT.stop();
-	reportProgress(MessageEvent(*shared_->ctx->master(), "TERMINATE", MessageEvent::completed, shared_->syncT.total()));
-	return !shared_->interrupt() ? thread_[masterId]->error() : shared_->errorCode;
+    uint32_t winner = thread_[master_id]->winner() ? master_id : UINT32_MAX;
+    // detach master only after all client threads are done
+    for (uint32_t i : irange(1u, shared_->nextId)) {
+        assert(thread_ && thread_[i]);
+        auto* handler = thread_[i].get();
+        handler->join();
+        if (handler->winner() && i < winner) {
+            winner = i;
+        }
+        Solver* s = &handler->solver();
+        reportProgress(*s, "joined");
+        destroyThread(i);
+        reportProgress(*s, "destroyed");
+    }
+    if (shared_->complete()) {
+        enumerator().commitComplete();
+    }
+    thread_[master_id]->handleTerminateMessage();
+    shared_->ctx->setWinner(winner);
+    shared_->nextId = 1;
+    shared_->syncT.stop();
+    reportProgress(MessageEvent(*shared_->ctx->master(), "TERMINATE", MessageEvent::completed, shared_->syncT.total()));
+    return not shared_->interrupt() ? thread_[master_id]->error() : shared_->errorCode;
 }
 
-void ParallelSolve::doStart(SharedContext& ctx, const LitVec& assume) {
-	if (beginSolve(ctx, assume)) {
-		// start computation in new thread
-		shared_->generator = new SharedData::Generator();
-		Clasp::mt::thread x(std::mem_fn(&ParallelSolve::solveParallel), this, static_cast(masterId));
-		thread_[masterId]->setThread(x);
-	}
+void ParallelSolve::doStart(SharedContext& ctx, LitView assume) {
+    if (beginSolve(ctx, assume)) {
+        // start computation in new thread
+        shared_->generator = std::make_unique();
+        thread_[master_id]->setThread(Clasp::mt::thread(std::mem_fn(&ParallelSolve::solveParallel), this, master_id));
+    }
 }
-int ParallelSolve::doNext(int) {
-	POTASSCO_REQUIRE(shared_->generator.get(), "Invalid operation");
-	int s = shared_->generator->state;
-	if (s != SharedData::Generator::done) {
-		shared_->generator->notify(SharedData::Generator::search);
-		if (shared_->generator->waitWhile(SharedData::Generator::search) == SharedData::Generator::model) {
-			return value_true;
-		}
-	}
-	return shared_->complete() ? value_false : value_free;
+Val_t ParallelSolve::doNext(Val_t) {
+    POTASSCO_CHECK_PRE(shared_->generator.get(), "Invalid operation");
+    if (int s = shared_->generator->state; s != SharedData::Generator::done) {
+        shared_->generator->notify(SharedData::Generator::search);
+        if (shared_->generator->waitWhile(SharedData::Generator::search) == SharedData::Generator::model) {
+            return value_true;
+        }
+    }
+    return shared_->complete() ? value_false : value_free;
 }
 void ParallelSolve::doStop() {
-	if (shared_->nextId <= 1) { return; }
-	reportProgress(*shared_->ctx->master(), "joining with other threads");
-	if (shared_->generator.get()) {
-		shared_->setControl(SharedData::terminate_flag);
-		shared_->generator->notify(SharedData::Generator::done);
-		thread_[masterId]->join();
-	}
-	int err = joinThreads();
-	shared_->generator = 0;
-	shared_->ctx->distributor.reset(0);
-	POTASSCO_CHECK(err == 0, err, shared_->msg.c_str());
+    if (shared_->nextId <= 1) {
+        return;
+    }
+    reportProgress(*shared_->ctx->master(), "joining with other threads");
+    if (shared_->generator.get()) {
+        shared_->setControl(SharedData::terminate_flag);
+        shared_->generator->notify(SharedData::Generator::done);
+        thread_[master_id]->join();
+    }
+    int err            = joinThreads();
+    shared_->generator = nullptr;
+    shared_->ctx->distributor.reset(nullptr);
+    POTASSCO_CHECK(err == 0, err, "%s", shared_->msg.c_str());
 }
 
 void ParallelSolve::doDetach() {
-	// detach master only after all client threads are done
-	thread_[masterId]->detach(*shared_->ctx, shared_->interrupt());
-	destroyThread(masterId);
+    // detach master only after all client threads are done
+    thread_[master_id]->detach(*shared_->ctx, shared_->interrupt());
+    destroyThread(master_id);
 }
 
 // Entry point for master solver
-bool ParallelSolve::doSolve(SharedContext& ctx, const LitVec& path) {
-	if (beginSolve(ctx, path)) {
-		solveParallel(masterId);
-		ParallelSolve::doStop();
-	}
-	return !shared_->complete();
+bool ParallelSolve::doSolve(SharedContext& ctx, LitView assume) {
+    if (beginSolve(ctx, assume)) {
+        solveParallel(master_id);
+        ParallelSolve::doStop();
+    }
+    return not shared_->complete();
 }
 
 // main solve loop executed by all threads
-void ParallelSolve::solveParallel(uint32 id) {
-	Solver& s = thread_[id]->solver();
-	SolverStats agg;
-	PathPtr a(0);
-	if (id == masterId && shared_->generator.get()) {
-		shared_->generator->waitWhile(SharedData::Generator::start);
-	}
-	try {
-		// establish solver<->handler connection and attach to shared context
-		// should this fail because of an initial conflict, we'll terminate
-		// in requestWork.
-		thread_[id]->attach(*shared_->ctx);
-		BasicSolve solve(s, limits());
-		agg.enable(s.stats);
-		for (GpType t; requestWork(s, a);) {
-			agg.accu(s.stats);
-			s.stats.reset();
-			thread_[id]->setGpType(t = ((a.is_owner() || modeSplit_) ? gp_split : gp_fixed));
-			reportProgress(s, "solving path...");
-			if (enumerator().start(s, *a, a.is_owner()) && thread_[id]->solveGP(solve, t, shared_->maxConflict) == value_free) {
-				terminate(s, false);
-			}
-			s.clearStopConflict();
-			s.undoUntil(0);
-			enumerator().end(s);
-			reportProgress(s, "done with path");
-		}
-	}
-	catch (const std::bad_alloc&)       { exception(id, a, ENOMEM,  "bad alloc"); }
-	catch (const std::logic_error& e)   { exception(id, a, Potassco::FailType::error_logic,   e.what()); }
-	catch (const std::exception& e)     { exception(id, a, Potassco::FailType::error_runtime, e.what()); }
-	catch (...)                         { exception(id, a, Potassco::FailType::error_runtime, "unknown");  }
-	assert(shared_->terminate() || thread_[id]->error());
-	int remaining = shared_->leaveAlgorithm();
-	// update stats
-	s.stats.accu(agg);
-	if (id != masterId) {
-		// remove solver<->handler connection and detach from shared context
-		// note: since detach can change the problem, we must not yet detach the master
-		// because some client might still be in the middle of an attach operation
-		thread_[id]->detach(*shared_->ctx, shared_->interrupt());
-		s.stats.addCpuTime(ThreadTime::getTime());
-	}
-	if (remaining == 0 && shared_->generator.get()) {
-		shared_->generator->notify(SharedData::Generator::done);
-	}
+void ParallelSolve::solveParallel(uint32_t id) {
+    Solver&     s = thread_[id]->solver();
+    SolverStats agg;
+    Path        a;
+    if (id == master_id && shared_->generator.get()) {
+        shared_->generator->waitWhile(SharedData::Generator::start);
+    }
+    try {
+        // establish solver<->handler connection and attach to shared context
+        // should this fail because of an initial conflict, we'll terminate
+        // in requestWork.
+        thread_[id]->attach(*shared_->ctx);
+        BasicSolve solve(s, limits());
+        agg.enable(s.stats);
+        for (GpType t; requestWork(s, a);) {
+            agg.accu(s.stats);
+            s.stats.reset();
+            thread_[id]->setGpType(t = ((a.owner() || modeSplit_) ? gp_split : gp_fixed));
+            reportProgress(s, "solving path...");
+            if (enumerator().start(s, a, a.owner()) &&
+                thread_[id]->solveGP(solve, t, shared_->maxConflict) == value_free) {
+                terminate(s, false);
+            }
+            s.clearStopConflict();
+            s.undoUntil(0);
+            enumerator().end(s);
+            reportProgress(s, "done with path");
+        }
+    }
+    catch (const std::bad_alloc&) {
+        exception(id, std::move(a), ENOMEM, "bad alloc");
+    }
+    catch (const std::logic_error& e) {
+        exception(id, std::move(a), Potassco::to_underlying(Potassco::Errc::invalid_argument), e.what());
+    }
+    catch (const std::exception& e) {
+        exception(id, std::move(a), Potassco::to_underlying(std::errc::interrupted), e.what());
+    }
+    catch (...) {
+        exception(id, std::move(a), Potassco::to_underlying(std::errc::interrupted), "unknown");
+    }
+    assert(shared_->terminate() || thread_[id]->error());
+    auto remaining = shared_->leaveAlgorithm();
+    // update stats
+    s.stats.accu(agg);
+    if (id != master_id) {
+        // remove solver<->handler connection and detach from shared context
+        // note: since detach can change the problem, we must not yet detach the master
+        // because some client might still be in the middle of an attach operation
+        thread_[id]->detach(*shared_->ctx, shared_->interrupt());
+        s.stats.addCpuTime(ThreadTime::getTime());
+    }
+    if (remaining == 0 && shared_->generator.get()) {
+        shared_->generator->notify(SharedData::Generator::done);
+    }
 }
 
-void ParallelSolve::exception(uint32 id, PathPtr& path, int e, const char* what) {
-	try {
-		if (!thread_[id]->setError(e) || e != ENOMEM || id == masterId) {
-			ParallelSolve::doInterrupt();
-			if (shared_->errorSet.fetch_or(bit_mask(id)) == 0) {
-				shared_->errorCode = e;
-				shared_->msg.appendFormat("[%u]: %s", id, what);
-			}
-		}
-		else if (path.get() && shared_->allowSplit()) {
-			shared_->pushWork(path.release());
-		}
-		reportProgress(thread_[id]->solver(), e == ENOMEM ? "Thread failed with out of memory" : "Thread failed with error");
-	}
-	catch(...) { ParallelSolve::doInterrupt(); }
+void ParallelSolve::exception(uint32_t id, Path path, int e, const char* what) {
+    try {
+        if (not thread_[id]->setError(e) || e != ENOMEM || id == master_id) {
+            ParallelSolve::doInterrupt();
+            if (shared_->errorSet.fetch_or(Potassco::nth_bit(id)) == 0) {
+                shared_->errorCode = e;
+                shared_->msg.append(1, '[').append(std::to_string(id)).append("]: ").append(what);
+            }
+        }
+        else if (path.owner() && shared_->allowSplit()) {
+            shared_->pushWork(std::move(path));
+        }
+        reportProgress(thread_[id]->solver(),
+                       e == ENOMEM ? "Thread failed with out of memory" : "Thread failed with error");
+    }
+    catch (...) {
+        ParallelSolve::doInterrupt();
+    }
 }
 
 // forced termination from outside
 bool ParallelSolve::doInterrupt() {
-	// do not notify blocked threads to avoid possible
-	// deadlock in semaphore!
-	shared_->postMessage(SharedData::msg_interrupt, false);
-	return true;
+    // do not notify blocked threads to avoid possible
+    // deadlock in semaphore!
+    shared_->postMessage(SharedData::msg_interrupt, false);
+    return true;
 }
 
 // tries to get new work for the given solver
-bool ParallelSolve::requestWork(Solver& s, PathPtr& out) {
-	const LitVec* a = 0;
-	for (int popped = 0; !shared_->terminate();) {
-		// only clear path and stop conflict - we don't propagate() here
-		// because we would then have to handle any eventual conflicts
-		if (++popped == 1 && !s.popRootLevel(s.rootLevel())) {
-			// s has a real top-level conflict - problem is unsat
-			terminate(s, true);
-		}
-		else if (shared_->synchronize()) {
-			// a synchronize request is active - we are fine with
-			// this but did not yet have a chance to react on it
-			waitOnSync(s);
-		}
-		else if (a || (a = shared_->requestWork(s)) != 0) {
-			assert(s.decisionLevel() == 0);
-			// got new work from work-queue
-			out = a;
-			// do not take over ownership of initial gp!
-			if (a == shared_->path) { out.release(); }
-			// propagate any new facts before starting new work
-			if (s.simplify())       { return true; }
-			// s now has a conflict - either an artificial stop conflict
-			// or a real conflict - we'll handle it in the next iteration
-			// via the call to popRootLevel()
-			popped = 0;
-		}
-		else if (!shared_->synchronize()) {
-			// no work left - quitting time?
-			terminate(s, true);
-		}
-	}
-	return false;
+bool ParallelSolve::requestWork(Solver& s, Path& out) {
+    bool gotWork = false;
+    for (int popped = 0; not shared_->terminate();) {
+        // only clear path and stop conflict - we don't propagate() here
+        // because we would then have to handle any eventual conflicts
+        if (++popped == 1 && not s.popRootLevel(s.rootLevel())) {
+            // s has a real top-level conflict - problem is unsat
+            terminate(s, true);
+        }
+        else if (shared_->synchronize()) {
+            // a synchronize request is active - we are fine with
+            // this but did not yet have a chance to react on it
+            waitOnSync(s);
+        }
+        else if (gotWork = gotWork || shared_->requestWork(s, out); gotWork) {
+            assert(s.decisionLevel() == 0);
+            // got new work from work-queue
+            // propagate any new facts before starting new work
+            if (s.simplify()) {
+                return true;
+            }
+            // s now has a conflict - either an artificial stop conflict
+            // or a real conflict - we'll handle it in the next iteration
+            // via the call to popRootLevel()
+            popped = 0;
+        }
+        else if (not shared_->synchronize()) {
+            // no work left - quitting time?
+            terminate(s, true);
+        }
+    }
+    return false;
 }
 
 // terminated from inside of algorithm
 // check if there is more to do
-void ParallelSolve::terminate(Solver& s, bool complete) {
-	if (!shared_->terminate()) {
-		if (enumerator().tentative() && complete) {
-			if (shared_->setControl(SharedData::sync_flag|SharedData::complete_flag)) {
-				thread_[s.id()]->setWinner();
-				reportProgress(MessageEvent(s, "SYNC", MessageEvent::sent));
-			}
-		}
-		else {
-			reportProgress(MessageEvent(s, "TERMINATE", MessageEvent::sent));
-			shared_->postMessage(SharedData::msg_terminate, true);
-			thread_[s.id()]->setWinner();
-			if (complete) { shared_->setControl(SharedData::complete_flag); }
-		}
-	}
+void ParallelSolve::terminate(const Solver& s, bool complete) {
+    if (not shared_->terminate()) {
+        if (enumerator().tentative() && complete) {
+            if (shared_->setControl(SharedData::sync_flag | SharedData::complete_flag)) {
+                thread_[s.id()]->setWinner();
+                reportProgress(MessageEvent(s, "SYNC", MessageEvent::sent));
+            }
+        }
+        else {
+            reportProgress(MessageEvent(s, "TERMINATE", MessageEvent::sent));
+            shared_->postMessage(SharedData::msg_terminate, true);
+            thread_[s.id()]->setWinner();
+            if (complete) {
+                shared_->setControl(SharedData::complete_flag);
+            }
+        }
+    }
 }
 
 // handles an active synchronization request
 // returns true to signal that s should restart otherwise false
-bool ParallelSolve::waitOnSync(Solver& s) {
-	if (!thread_[s.id()]->handleRestartMessage()) {
-		shared_->setControl(SharedData::cancel_restart_flag);
-	}
-	bool hasPath  = thread_[s.id()]->hasPath();
-	bool tentative= enumerator().tentative();
-	if (shared_->waitSync()) {
-		// last man standing - complete synchronization request
-		shared_->workReq     = 0;
-		shared_->restartReq  = 0;
-		bool restart         = shared_->hasControl(SharedData::restart_flag);
-		bool init            = true;
-		if (restart) {
-			init = shared_->allowRestart() && !shared_->hasControl(SharedData::cancel_restart_flag);
-			if (init) { shared_->globalR.next(); }
-			shared_->maxConflict = shared_->allowRestart() && shared_->globalR.idx < maxRestarts_
-				? shared_->globalR.current()
-				: UINT64_MAX;
-		}
-		else if (shared_->maxConflict != UINT64_MAX && !shared_->allowRestart()) {
-			shared_->maxConflict = UINT64_MAX;
-		}
-		if (init) { initQueue();  }
-		else      { shared_->setControl(SharedData::restart_abandoned_flag); }
-		if (tentative && shared_->complete()) {
-			if (enumerator().commitComplete()) { shared_->setControl(SharedData::terminate_flag); }
-			else                               { shared_->modCount = uint32(0); shared_->clearControl(SharedData::complete_flag); }
-		}
-		shared_->clearControl(SharedData::msg_split | SharedData::msg_sync_restart | SharedData::restart_abandoned_flag | SharedData::cancel_restart_flag);
-		shared_->syncT.lap();
-		reportProgress(MessageEvent(s, "SYNC", MessageEvent::completed, shared_->syncT.elapsed()));
-		assert(!shared_->synchronize());
-		// wake up all blocked threads
-		shared_->notifyWaitingThreads();
-	}
-	return shared_->terminate() || (hasPath && !shared_->hasControl(SharedData::restart_abandoned_flag));
+bool ParallelSolve::waitOnSync(const Solver& s) {
+    if (not thread_[s.id()]->handleRestartMessage()) {
+        shared_->setControl(SharedData::cancel_restart_flag);
+    }
+    bool hasPath   = thread_[s.id()]->hasPath();
+    bool tentative = enumerator().tentative();
+    if (shared_->waitSync()) {
+        // last man standing - complete synchronization request
+        shared_->workReq    = 0;
+        shared_->restartReq = 0;
+        bool restart        = shared_->hasControl(SharedData::restart_flag);
+        bool init           = true;
+        if (restart) {
+            init = shared_->allowRestart() && not shared_->hasControl(SharedData::cancel_restart_flag);
+            if (init) {
+                shared_->globalR.next();
+            }
+            shared_->maxConflict = shared_->allowRestart() && shared_->globalR.idx < maxRestarts_
+                                       ? shared_->globalR.current()
+                                       : UINT64_MAX;
+        }
+        else if (shared_->maxConflict != UINT64_MAX && not shared_->allowRestart()) {
+            shared_->maxConflict = UINT64_MAX;
+        }
+        if (init) {
+            initQueue();
+        }
+        else {
+            shared_->setControl(SharedData::restart_abandoned_flag);
+        }
+        if (tentative && shared_->complete()) {
+            if (enumerator().commitComplete()) {
+                shared_->setControl(SharedData::terminate_flag);
+            }
+            else {
+                shared_->modCount = 0u;
+                shared_->clearControl(SharedData::complete_flag);
+            }
+        }
+        shared_->clearControl(SharedData::msg_split | SharedData::msg_sync_restart |
+                              SharedData::restart_abandoned_flag | SharedData::cancel_restart_flag);
+        shared_->syncT.lap();
+        reportProgress(MessageEvent(s, "SYNC", MessageEvent::completed, shared_->syncT.elapsed()));
+        assert(not shared_->synchronize());
+        // wake up all blocked threads
+        shared_->notifyWaitingThreads();
+    }
+    return shared_->terminate() || (hasPath && not shared_->hasControl(SharedData::restart_abandoned_flag));
 }
 
 // If guiding path scheme is active only one
@@ -598,531 +656,628 @@ bool ParallelSolve::waitOnSync(Solver& s) {
 // TODO:
 //  heuristic for initial splits?
 void ParallelSolve::initQueue() {
-	shared_->clearQueue();
-	if (shared_->allowSplit() && modeSplit_ && !enumerator().supportsSplitting(*shared_->ctx)) {
-		shared_->ctx->warn("Selected strategies imply Mode=compete.");
-		shared_->clearControl(SharedData::allow_split_flag);
-		shared_->setControl(SharedData::forbid_restart_flag);
-		modeSplit_ = false;
-	}
-	shared_->initVec = UINT64_MAX;
-	assert(shared_->allowSplit() || shared_->hasControl(SharedData::forbid_restart_flag));
+    shared_->clearQueue();
+    if (shared_->allowSplit() && modeSplit_ && not enumerator().supportsSplitting(*shared_->ctx)) {
+        shared_->ctx->warn("Selected strategies imply Mode=compete.");
+        shared_->clearControl(SharedData::allow_split_flag);
+        shared_->setControl(SharedData::forbid_restart_flag);
+        modeSplit_ = false;
+    }
+    shared_->initVec = UINT64_MAX;
+    assert(shared_->allowSplit() || shared_->hasControl(SharedData::forbid_restart_flag));
 }
 
 // adds work to the work-queue
-void ParallelSolve::pushWork(LitVec* v) {
-	assert(v);
-	shared_->pushWork(v);
-}
+void ParallelSolve::pushWork(LitView path) { shared_->pushWork(Path::acquire(path)); }
 
 // called whenever some solver proved unsat
 bool ParallelSolve::commitUnsat(Solver& s) {
-	const int supUnsat = enumerator().unsatType();
-	if (supUnsat == Enumerator::unsat_stop || shared_->terminate() || shared_->synchronize()) {
-		return false;
-	}
-	unique_lock lock(shared_->modelM, defer_lock_t());
-	if (supUnsat == Enumerator::unsat_sync) {
-		lock.lock();
-	}
-	bool result = enumerator().commitUnsat(s);
-	if (lock.owns_lock()) {
-		lock.unlock();
-	}
-	if (!thread_[s.id()]->disjointPath()) {
-		if (result) {
-			++shared_->modCount;
-			if (s.lower.bound > 0) {
-				lock.lock();
-				if (s.lower.bound > shared_->lower.bound || s.lower.level > shared_->lower.level) {
-					shared_->lower = s.lower;
-					reportUnsat(s);
-					++shared_->modCount;
-				}
-				lock.unlock();
-			}
-		}
-		else { terminate(s, true);  }
-	}
-	return result;
+    const int supUnsat = enumerator().unsatType();
+    if (supUnsat == Enumerator::unsat_stop || shared_->terminate() || shared_->synchronize()) {
+        return false;
+    }
+    auto        fullPath = not thread_[s.id()]->disjointPath();
+    unique_lock lock(shared_->modelM, defer_lock_t());
+    if (supUnsat == Enumerator::unsat_sync) {
+        lock.lock();
+    }
+    if (not enumerator().commitUnsat(s)) {
+        if (fullPath) {
+            terminate(s, true);
+        }
+        return false;
+    }
+    if (fullPath && not shared_->terminate()) {
+        if (not lock.owns_lock()) {
+            lock.lock();
+        }
+        bool report = enumerator().lastModel().up;
+        if (auto lb = enumerator().lowerBound(); lb.bound > shared_->lower.bound || lb.level > shared_->lower.level) {
+            shared_->lower = lb;
+            report         = true;
+        }
+        not report || reportUnsat(s);
+        ++shared_->modCount;
+        lock.unlock();
+    }
+    return true;
 }
 
 // called whenever some solver has found a model
 bool ParallelSolve::commitModel(Solver& s) {
-	// grab lock - models must be processed sequentially
-	// in order to simplify printing and to avoid duplicates
-	// in all non-trivial enumeration modes
-	bool stop = false;
-	{lock_guard lock(shared_->modelM);
-	// first check if the model is still valid once all
-	// information is integrated into the solver
-	if (thread_[s.id()]->isModelLocked(s) && (stop=shared_->terminate()) == false && enumerator().commitModel(s)) {
-		if (enumerator().lastModel().num == 1 && !enumerator().supportsRestarts()) {
-			// switch to backtracking based splitting algorithm
-			// the solver's gp will act as the root for splitting and is
-			// from now on disjoint from all other gps
-			shared_->setControl(SharedData::forbid_restart_flag | SharedData::allow_split_flag);
-			thread_[s.id()]->setGpType(gp_split);
-			enumerator().setDisjoint(s, true);
-			shared_->initVec = 0;
-		}
-		if (shared_->generator.get()) {
-			shared_->generator->pushModel();
-		}
-		else if ((stop = !reportModel(s)) == true) {
-			// must be called while holding the lock - otherwise
-			// we have a race condition with solvers that
-			// are currently blocking on the mutex and we could enumerate
-			// more models than requested by the user
-			terminate(s, !moreModels(s));
-		}
-		++shared_->modCount;
-	}}
-	return !stop;
+    // grab lock - models must be processed sequentially
+    // in order to simplify printing and to avoid duplicates
+    // in all non-trivial enumeration modes
+    bool stop = false;
+    {
+        lock_guard lock(shared_->modelM);
+        // first check if the model is still valid once all
+        // information is integrated into the solver
+        if (thread_[s.id()]->isModelLocked(s) && (stop = shared_->terminate()) == false &&
+            enumerator().commitModel(s)) {
+            if (enumerator().lastModel().num == 1 && not enumerator().supportsRestarts()) {
+                // switch to backtracking based splitting algorithm
+                // the solver's gp will act as the root for splitting and is
+                // from now on disjoint from all other guiding paths
+                shared_->setControl(SharedData::forbid_restart_flag | SharedData::allow_split_flag);
+                thread_[s.id()]->setGpType(gp_split);
+                enumerator().setDisjoint(s, true);
+                shared_->initVec = 0;
+            }
+            if (shared_->generator.get()) {
+                shared_->generator->pushModel();
+            }
+            else if (stop = not reportModel(s); stop) {
+                // must be called while holding the lock - otherwise
+                // we have a race condition with solvers that
+                // are currently blocking on the mutex and we could enumerate
+                // more models than requested by the user
+                terminate(s, not moreModels(s));
+            }
+            ++shared_->modCount;
+        }
+    }
+    return not stop;
 }
 
-uint64 ParallelSolve::hasErrors() const {
-	return shared_->errorSet != 0u;
-}
-bool ParallelSolve::interrupted() const {
-	return shared_->interrupt();
-}
-void ParallelSolve::resetSolve() {
-	shared_->control = 0;
-}
-void ParallelSolve::enableInterrupts() {}
+uint64_t ParallelSolve::hasErrors() const { return shared_->errorSet != 0u; }
+bool     ParallelSolve::interrupted() const { return shared_->interrupt(); }
+void     ParallelSolve::resetSolve() { shared_->control = 0; }
+void     ParallelSolve::enableInterrupts() {}
 // updates s with new messages and uses s to create a new guiding path
 // if necessary and possible
 bool ParallelSolve::handleMessages(Solver& s) {
-	// check if there are new messages for s
-	if (!shared_->hasMessage()) {
-		// nothing to do
-		return true;
-	}
-	ParallelHandler* h = thread_[s.id()];
-	if (shared_->terminate()) {
-		reportProgress(MessageEvent(s, "TERMINATE", MessageEvent::received));
-		h->handleTerminateMessage();
-		s.setStopConflict();
-		return false;
-	}
-	if (shared_->synchronize()) {
-		reportProgress(MessageEvent(s, "SYNC", MessageEvent::received));
-		if (waitOnSync(s)) {
-			s.setStopConflict();
-			return false;
-		}
-		return true;
-	}
-	if (shared_->split() && s.requestSplit() && h->disjointPath()) {
-		// First declare split request as handled
-		// and only then do the actual split.
-		// This way, we minimize the chance for
-		// "over"-splitting, i.e. one split request handled
-		// by more than one thread.
-		shared_->aboutToSplit();
-		reportProgress(MessageEvent(s, "SPLIT", MessageEvent::received));
-		h->handleSplitMessage();
-		enumerator().setDisjoint(s, true);
-	}
-	return true;
+    // check if there are new messages for s
+    if (not shared_->hasMessage()) {
+        // nothing to do
+        return true;
+    }
+    ParallelHandler* h = thread_[s.id()].get();
+    if (shared_->terminate()) {
+        reportProgress(MessageEvent(s, "TERMINATE", MessageEvent::received));
+        h->handleTerminateMessage();
+        s.setStopConflict();
+        return false;
+    }
+    if (shared_->synchronize()) {
+        reportProgress(MessageEvent(s, "SYNC", MessageEvent::received));
+        if (waitOnSync(s)) {
+            s.setStopConflict();
+            return false;
+        }
+        return true;
+    }
+    if (shared_->split() && s.requestSplit() && h->disjointPath()) {
+        // First declare split request as handled
+        // and only then do the actual split.
+        // This way, we minimize the chance for
+        // "over"-splitting, i.e. one split request handled
+        // by more than one thread.
+        shared_->aboutToSplit();
+        reportProgress(MessageEvent(s, "SPLIT", MessageEvent::received));
+        h->handleSplitMessage();
+        enumerator().setDisjoint(s, true);
+    }
+    return true;
 }
 
-bool ParallelSolve::integrateModels(Solver& s, uint32& upCount) {
-	uint32 gCount = shared_->modCount;
-	return gCount == upCount || (enumerator().update(s) && (upCount = gCount) == gCount);
+bool ParallelSolve::integrateModels(Solver& s, uint32_t& upCount) {
+    uint32_t gCount = shared_->modCount;
+    return gCount == upCount || (enumerator().update(s) && (upCount = gCount) == gCount);
 }
 
 void ParallelSolve::requestRestart() {
-	if (shared_->allowRestart() && ++shared_->restartReq == numThreads()) {
-		shared_->postMessage(SharedData::msg_sync_restart, true);
-	}
+    if (shared_->allowRestart() && ++shared_->restartReq == numThreads()) {
+        shared_->postMessage(SharedData::msg_sync_restart, true);
+    }
 }
 
 SolveAlgorithm* ParallelSolveOptions::createSolveObject() const {
-	return numSolver() > 1 ? new ParallelSolve(*this) : BasicSolveOptions::createSolveObject();
+    return numSolver() > 1 ? new ParallelSolve(*this) : BasicSolveOptions::createSolveObject();
 }
 ////////////////////////////////////////////////////////////////////////////////////
 // ParallelHandler
 /////////////////////////////////////////////////////////////////////////////////////////
+static constexpr auto receive_buffer_size = 32u;
+static constexpr auto handler_align = std::max(alignof(ParallelHandler), static_cast(cache_line_size));
+
+void* ParallelHandler::operator new(std::size_t count) {
+    return ::operator new(count, std::align_val_t{handler_align});
+}
+void ParallelHandler::operator delete(void* ptr, std::size_t sz) {
+    ::operator delete(ptr, sz, std::align_val_t{handler_align});
+}
 ParallelHandler::ParallelHandler(ParallelSolve& ctrl, Solver& s)
-	: MessageHandler()
-	, ctrl_(&ctrl)
-	, solver_(&s)
-	, received_(0)
-	, recEnd_(0)
-	, intEnd_(0)
-	, error_(0)
-	, win_(0)
-	, up_(0) {
-	this->next = this;
+    : ctrl_(&ctrl)
+    , solver_(&s)
+    , received_(nullptr)
+    , recEnd_(0)
+    , intEnd_(0)
+    , error_(0)
+    , win_(0)
+    , up_(0)
+    , act_(0)
+    , lbd_(0) {
+    this->next = this;
 }
 
-ParallelHandler::~ParallelHandler() { clearDB(0); delete [] received_; }
+ParallelHandler::~ParallelHandler() { clearDB(nullptr); }
 
 // adds this as post propagator to its solver and attaches the solver to ctx.
 bool ParallelHandler::attach(SharedContext& ctx) {
-	assert(solver_);
-	gp_.reset();
-	error_ = 0;
-	win_   = 0;
-	up_    = 0;
-	act_   = 0;
-	lbd_   = solver_->searchConfig().reduce.strategy.glue != 0;
-	next   = 0;
-	if (!received_ && ctx.distributor.get()) {
-		received_ = new SharedLiterals*[RECEIVE_BUFFER_SIZE];
-	}
-	ctx.report("attach", solver_);
-	solver_->addPost(this);
-	return ctx.attach(solver_->id());
+    assert(solver_);
+    gp_    = {};
+    error_ = 0;
+    win_   = 0;
+    up_    = 0;
+    act_   = 0;
+    lbd_   = solver_->searchConfig().reduce.strategy.glue != 0;
+    next   = nullptr;
+    if (not received_ && ctx.distributor.get()) {
+        received_ = std::make_unique(receive_buffer_size);
+    }
+    ctx.report("attach", solver_);
+    solver_->addPost(this);
+    return ctx.attach(solver_->id());
+}
+
+int ParallelHandler::join() {
+    if (joinable()) {
+        thread_.join();
+    }
+    return error();
 }
 
 // removes this from the list of post propagators of its solver and detaches the solver from ctx.
 void ParallelHandler::detach(SharedContext& ctx, bool) {
-	handleTerminateMessage();
-	ctx.report("detach", solver_);
-	if (solver_->sharedContext() == &ctx) {
-		clearDB(!error() ? solver_ : 0);
-		ctx.report("detached db", solver_);
-		ctx.detach(*solver_, error() != 0);
-		ctx.report("detached ctx", solver_);
-	}
+    handleTerminateMessage();
+    ctx.report("detach", solver_);
+    if (solver_->sharedContext() == &ctx) {
+        clearDB(not error() ? solver_ : nullptr);
+        ctx.report("detached db", solver_);
+        ctx.detach(*solver_, error() != 0);
+        ctx.report("detached ctx", solver_);
+    }
+}
+void ParallelHandler::setThread(Clasp::mt::thread x) {
+    assert(not joinable() && x.joinable());
+    thread_ = std::move(x);
 }
-
 bool ParallelHandler::setError(int code) {
-	error_ = code;
-	return thread_.joinable() && !winner();
+    error_ = static_cast(code);
+    return thread_.joinable() && not winner();
 }
 
+void ParallelHandler::setWinner() { win_ = 1; }
+bool ParallelHandler::winner() const { return win_ != 0; }
+int  ParallelHandler::error() const { return static_cast(error_); }
+bool ParallelHandler::joinable() const { return thread_.joinable(); }
+
 void ParallelHandler::clearDB(Solver* s) {
-	for (ClauseDB::iterator it = integrated_.begin(), end = integrated_.end(); it != end; ++it) {
-		ClauseHead* c = static_cast(*it);
-		if (s) { s->addLearnt(c, c->size(), Constraint_t::Other); }
-		else   { c->destroy(); }
-	}
-	integrated_.clear();
-	intEnd_= 0;
-	for (uint32 i = 0; i != recEnd_; ++i) { received_[i]->release(); }
-	recEnd_= 0;
+    for (auto* con : integrated_) {
+        if (auto* c = static_cast(con); s) {
+            s->addLearnt(c, c->size(), ConstraintType::other);
+        }
+        else {
+            c->destroy();
+        }
+    }
+    integrated_.clear();
+    intEnd_ = 0;
+    for (uint32_t i : irange(recEnd_)) { received_[i]->release(); }
+    recEnd_ = 0;
 }
 
-ValueRep ParallelHandler::solveGP(BasicSolve& solve, GpType t, uint64 restart) {
-	ValueRep res = value_free;
-	Solver&  s   = solve.solver();
-	bool     fin = false;
-	gp_.reset(restart, t);
-	assert(act_ == 0);
-	do {
-		win_ = 0;
-		ctrl_->integrateModels(s, gp_.modCount);
-		up_ = act_ = 1; // activate enumerator and bounds
-		res = solve.solve();
-		up_ = act_ = 0; // de-activate enumerator and bounds
-		fin = true;
-		if      (res == value_true)  { if (ctrl_->commitModel(s)) { fin = false; } }
-		else if (res == value_false) { if (ctrl_->commitUnsat(s)) { fin = false; gp_.reset(restart, gp_.type); } }
-	} while (!fin);
-	return res;
+bool ParallelHandler::disjointPath() const { return gp_.type == ParallelSolve::gp_split; }
+bool ParallelHandler::hasPath() const { return gp_.type != ParallelSolve::gp_none; }
+void ParallelHandler::setGpType(GpType t) { gp_.type = t; }
+
+Val_t ParallelHandler::solveGP(BasicSolve& solve, GpType type, uint64_t restart) {
+    auto    res = value_free;
+    Solver& s   = solve.solver();
+    auto    fin = false;
+    gp_         = {.restart = restart, .modCount = 0, .type = type};
+    assert(act_ == 0);
+    do {
+        win_ = 0;
+        ctrl_->integrateModels(s, gp_.modCount);
+        up_ = act_ = 1; // activate enumerator and bounds
+        res        = solve.solve();
+        up_ = act_ = 0; // de-activate enumerator and bounds
+        fin        = true;
+        if (res == value_true) {
+            if (ctrl_->commitModel(s)) {
+                fin = false;
+            }
+        }
+        else if (res == value_false) {
+            if (ctrl_->commitUnsat(s)) {
+                fin          = false;
+                gp_.restart  = restart;
+                gp_.modCount = 0;
+            }
+        }
+    } while (not fin);
+    return res;
 }
 
+Solver& ParallelHandler::solver() { return *solver_; }
+
 // detach from solver, i.e. ignore any further messages
 void ParallelHandler::handleTerminateMessage() {
-	if (this->next != this) {
-		// mark removed propagator by creating "self-loop"
-		solver_->removePost(this);
-		this->next = this;
-	}
+    if (this->next != this) {
+        // mark removed propagator by creating "self-loop"
+        solver_->removePost(this);
+        this->next = this;
+    }
 }
 
 // split-off new guiding path and add it to solve object
 void ParallelHandler::handleSplitMessage() {
-	assert(solver_ && "ParallelHandler::handleSplitMessage(): not attached!");
-	Solver& s = *solver_;
-	SingleOwnerPtr newPath(new LitVec());
-	bool ok = s.split(*newPath);
-	POTASSCO_ASSERT(ok, "unexpected call to split");
-	ctrl_->pushWork(newPath.release());
+    assert(solver_ && "ParallelHandler::handleSplitMessage(): not attached!");
+    temp_.clear();
+    bool ok = solver_->split(temp_);
+    POTASSCO_ASSERT(ok, "unexpected call to split");
+    ctrl_->pushWork(temp_);
 }
 
 bool ParallelHandler::handleRestartMessage() {
-	// TODO
-	// we may want to implement some heuristic, like
-	// computing a local var order.
-	return true;
+    // TODO
+    // we may want to implement some heuristic, like
+    // computing a local var order.
+    static_cast(this);
+    return true;
 }
 
-bool ParallelHandler::simplify(Solver& s, bool sh) {
-	ClauseDB::size_type i, j, end = integrated_.size();
-	for (i = j = 0; i != end; ++i) {
-		Constraint* c = integrated_[i];
-		if (c->simplify(s, sh)) {
-			c->destroy(&s, false);
-			intEnd_ -= (i < intEnd_);
-		}
-		else                    {
-			integrated_[j++] = c;
-		}
-	}
-	shrinkVecTo(integrated_, j);
-	if (intEnd_ > sizeVec(integrated_)) intEnd_ = sizeVec(integrated_);
-	return false;
+bool ParallelHandler::simplify(Solver& s, bool shuffle) {
+    auto j = 0u;
+    for (auto i : irange(integrated_)) {
+        if (Constraint* c = integrated_[i]; c->simplify(s, shuffle)) {
+            c->destroy(&s, false);
+            intEnd_ -= (i < intEnd_);
+        }
+        else {
+            integrated_[j++] = c;
+        }
+    }
+    shrinkVecTo(integrated_, j);
+    if (intEnd_ > size32(integrated_)) {
+        intEnd_ = size32(integrated_);
+    }
+    return false;
 }
+void ParallelHandler::reset() { up_ = 1; }
 
 bool ParallelHandler::propagateFixpoint(Solver& s, PostPropagator* ctx) {
-	// Check for messages and integrate any new information from
-	// models/lemma exchange but only if path is set up.
-	// Skip updates if called from other post propagator so that we do not
-	// disturb any active propagation.
-	if (int up = (ctx == 0 && up_ != 0)) {
-		up_ ^= (uint32)s.updateMode();
-		up  += (act_ == 0 || (up_ && (s.stats.choices & 63) != 0));
-		if (s.stats.conflicts >= gp_.restart)  { ctrl_->requestRestart(); gp_.restart *= 2; }
-		for (uint32 cDL = s.decisionLevel();;) {
-			bool ok = ctrl_->handleMessages(s) && (up > 1 ? integrate(s) : ctrl_->integrateModels(s, gp_.modCount));
-			if (!ok)                         { return false; }
-			if (cDL != s.decisionLevel())    { // cancel active propagation on cDL
-				cancelPropagation();
-				cDL = s.decisionLevel();
-			}
-			if      (!s.queueSize())         { if (++up == 3) return true; }
-			else if (!s.propagateUntil(this)){ return false; }
-		}
-	}
-	return ctrl_->handleMessages(s);
+    // Check for messages and integrate any new information from
+    // models/lemma exchange but only if path is set up.
+    // Skip updates if called from other post propagator so that we do not
+    // disturb any active propagation.
+    if (int up = (ctx == nullptr && up_ != 0)) {
+        up_ ^= static_cast(s.updateMode());
+        up  += (act_ == 0 || (up_ && (s.stats.choices & 63) != 0));
+        if (s.stats.conflicts >= gp_.restart) {
+            ctrl_->requestRestart();
+            gp_.restart *= 2;
+        }
+        for (uint32_t dl = s.decisionLevel();;) {
+            if (bool ok = ctrl_->handleMessages(s) && (up > 1 ? integrate(s) : ctrl_->integrateModels(s, gp_.modCount));
+                not ok) {
+                return false;
+            }
+            if (dl != s.decisionLevel()) { // cancel active propagation on dl
+                cancelPropagation();
+                dl = s.decisionLevel();
+            }
+            if (not s.queueSize()) {
+                if (++up == 3) {
+                    return true;
+                }
+            }
+            else if (not s.propagateUntil(this)) {
+                return false;
+            }
+        }
+    }
+    return ctrl_->handleMessages(s);
 }
+bool ParallelHandler::handleMessages() { return ctrl_->handleMessages(solver()); }
 
 // checks whether s still has a model once all
 // information from previously found models were integrated
 bool ParallelHandler::isModel(Solver& s) {
-	assert(s.numFreeVars() == 0);
-	// either no unprocessed updates or still a model after
-	// updates were integrated
-	return ctrl_->integrateModels(s, gp_.modCount)
-		&& s.numFreeVars() == 0
-		&& s.queueSize()   == 0;
+    assert(s.numFreeVars() == 0);
+    // either no unprocessed updates or still a model after
+    // updates were integrated
+    return ctrl_->integrateModels(s, gp_.modCount) && s.numFreeVars() == 0 && s.queueSize() == 0;
 }
 
 bool ParallelHandler::isModelLocked(Solver& s) {
-	const uint32 current = gp_.modCount;
-	if (!isModel(s))
-		return false;
-	if (current == gp_.modCount)
-		return true;
-	for (PostPropagator* p = s.getPost(PostPropagator::priority_class_general); p; p = p->next) {
-		if (!p->isModel(s))
-			return false;
-	}
-	return true;
+    const uint32_t current = gp_.modCount;
+    if (not isModel(s)) {
+        return false;
+    }
+    if (current == gp_.modCount) {
+        return true;
+    }
+    for (PostPropagator* p = s.getPost(priority_class_general); p; p = p->next) {
+        if (not p->isModel(s)) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool ParallelHandler::integrate(Solver& s) {
-	uint32 rec = recEnd_ + s.receive(received_ + recEnd_, RECEIVE_BUFFER_SIZE - recEnd_);
-	if (!rec) { return true; }
-	ClauseCreator::Result ret;
-	uint32 dl       = s.decisionLevel(), added = 0, i = 0;
-	uint32 intFlags = ctrl_->integrateFlags();
-	recEnd_         = 0;
-	if (lbd_) {
-		intFlags |= ClauseCreator::clause_int_lbd;
-	}
-	do {
-		ret    = ClauseCreator::integrate(s, received_[i++], intFlags, Constraint_t::Other);
-		added += ret.status != ClauseCreator::status_subsumed;
-		if (ret.local) { add(ret.local); }
-		if (ret.unit()){ s.stats.addIntegratedAsserting(dl, s.decisionLevel()); dl = s.decisionLevel(); }
-		if (!ret.ok()) { while (i != rec) { received_[recEnd_++] = received_[i++]; } }
-	} while (i != rec);
-	s.stats.addIntegrated(added);
-	return !s.hasConflict();
+    uint32_t rec = recEnd_ + s.receive(received_.get() + recEnd_, receive_buffer_size - recEnd_);
+    if (not rec) {
+        return true;
+    }
+    uint32_t dl = s.decisionLevel(), added = 0, i = 0;
+    auto     intFlags = ClauseCreator::CreateFlag{ctrl_->integrateFlags()};
+    recEnd_           = 0;
+    if (lbd_) {
+        intFlags |= ClauseCreator::clause_int_lbd;
+    }
+    do {
+        auto ret  = ClauseCreator::integrate(s, received_[i++], intFlags, ConstraintType::other);
+        added    += ret.status != ClauseCreator::status_subsumed;
+        if (ret.local) {
+            add(ret.local);
+        }
+        if (ret.unit()) {
+            s.stats.addIntegratedAsserting(dl, s.decisionLevel());
+            dl = s.decisionLevel();
+        }
+        if (not ret.ok()) {
+            while (i != rec) { received_[recEnd_++] = received_[i++]; }
+        }
+    } while (i != rec);
+    s.stats.addIntegrated(added);
+    return not s.hasConflict();
 }
 
 void ParallelHandler::add(ClauseHead* h) {
-	if (intEnd_ < integrated_.size()) {
-		ClauseHead* o = (ClauseHead*)integrated_[intEnd_];
-		integrated_[intEnd_] = h;
-		assert(o);
-		if (!ctrl_->integrateUseHeuristic() || o->locked(*solver_) || o->activity().activity() > 0) {
-			solver_->addLearnt(o, o->size(), Constraint_t::Other);
-		}
-		else {
-			o->destroy(solver_, true);
-			solver_->stats.removeIntegrated();
-		}
-	}
-	else {
-		integrated_.push_back(h);
-	}
-	if (++intEnd_ >= ctrl_->integrateGrace()) {
-		intEnd_ = 0;
-	}
+    if (intEnd_ < integrated_.size()) {
+        auto* o              = static_cast(integrated_[intEnd_]);
+        integrated_[intEnd_] = h;
+        assert(o);
+        if (not ctrl_->integrateUseHeuristic() || o->locked(*solver_) || o->activity().activity() > 0) {
+            solver_->addLearnt(o, o->size(), ConstraintType::other);
+        }
+        else {
+            o->destroy(solver_, true);
+            solver_->stats.removeIntegrated();
+        }
+    }
+    else {
+        integrated_.push_back(h);
+    }
+    if (++intEnd_ >= ctrl_->integrateGrace()) {
+        intEnd_ = 0;
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Distribution
 /////////////////////////////////////////////////////////////////////////////////////////
-uint64 ParallelSolveOptions::initPeerMask(uint32 id, Integration::Topology topo, uint32 maxT)  {
-	if (topo == Integration::topo_all) { return Distributor::initSet(maxT) ^ Distributor::mask(id); }
-	if (topo == Integration::topo_ring){
-		uint32 prev = id > 0 ? id - 1 : maxT - 1;
-		uint32 next = (id + 1) % maxT;
-		return Distributor::mask(prev) | Distributor::mask(next);
-	}
-	const uint32 n = maxT;
-	const uint32 k = (1u << Clasp::log2(n));
-	const uint32 s = k ^ id;
-	const bool ext = topo == Integration::topo_cubex;
-	uint64 res = 0;
-	for (uint32 m = 1; m <= k; m *= 2) {
-		uint32 i = m ^ id;
-		if      (i < n)         { res |= Distributor::mask(i);   }
-		else if (ext && k != m) { res |= Distributor::mask(i^k); }
-	}
-	if (ext && s >= n) {
-		for(uint32 m = 1; m < k; m *= 2) {
-			uint32 i = m ^ s;
-			if (i < n) { res |= Distributor::mask(i); }
-		}
-	}
-	assert(!Distributor::inSet(res, id));
-	return res;
+SolverSet ParallelSolveOptions::initPeerSet(uint32_t id, Integration::Topology topo, uint32_t maxT) {
+    if (topo == Integration::topo_all) {
+        return fullPeerSet(id, maxT);
+    }
+    if (topo == Integration::topo_ring) {
+        auto prev = (id > 0 ? id : maxT) - 1;
+        auto next = (id + 1) % maxT;
+        return {prev, next};
+    }
+    const auto n   = maxT;
+    const auto k   = Potassco::bit_floor(maxT);
+    const auto ext = topo == Integration::topo_cubex && k != n ? k : 0u;
+    const auto s   = (k ^ id) >= n ? k ^ id : (k * 2);
+    SolverSet  res;
+    for (uint32_t m = 1u; m <= k; m *= 2) {
+        auto pos = m ^ id;
+        if (pos < n) {
+            res.add(pos);
+        }
+        if (m < ext) {
+            if (auto r = m ^ s; r < n) {
+                res.add(r);
+            }
+            if (pos >= n) {
+                res.add(pos ^ k);
+            }
+        }
+    }
+    assert(not res.contains(id));
+    return res;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // GlobalDistribution
 /////////////////////////////////////////////////////////////////////////////////////////
-GlobalDistribution::GlobalDistribution(const Policy& p, uint32 maxT, uint32 topo) : Distributor(p), queue_(0) {
-	typedef ParallelSolveOptions::Integration::Topology Topology;
-	assert(maxT <= ParallelSolveOptions::supportedSolvers());
-	Topology t = static_cast(topo);
-	queue_     = new Queue(maxT);
-	threadId_  = (ThreadInfo*)alignedAllocChecked((maxT * sizeof(ThreadInfo)));
-	for (uint32 i = 0; i != maxT; ++i) {
-		new (&threadId_[i]) ThreadInfo;
-		threadId_[i].id       = queue_->addThread();
-		threadId_[i].peerMask = ParallelSolveOptions::initPeerMask(i, t, maxT);
-	}
-}
-GlobalDistribution::~GlobalDistribution() {
-	static_assert(sizeof(ThreadInfo) == 64, "Invalid size");
-	release();
-}
-void GlobalDistribution::release() {
-	if (queue_) {
-		for (uint32 i = 0; i != queue_->maxThreads(); ++i) {
-			Queue::ThreadId& id = getThreadId(i);
-			for (ClausePair n; queue_->tryConsume(id, n); ) {
-				if (n.sender != i) { n.lits->release(); }
-			}
-			threadId_[i].~ThreadInfo();
-		}
-		delete queue_;
-		queue_ = 0;
-		alignedFree(threadId_);
-	}
-}
-void GlobalDistribution::publish(const Solver& s, SharedLiterals* n) {
-	assert(n->refCount() >= (queue_->maxThreads()-1));
-	queue_->publish(ClausePair(s.id(), n), getThreadId(s.id()));
+class GlobalDistribution::Queue {
+public:
+    struct ClausePair {
+        uint32_t        sender = UINT32_MAX;
+        SharedLiterals* lits   = nullptr;
+    };
+    using QueueType = MultiQueue;
+    struct alignas(cache_line_size) ThreadInfo {
+        SolverSet           peers;
+        QueueType::ThreadId id{};
+    };
+    static_assert(sizeof(ThreadInfo) >= cache_line_size, "Invalid size");
+    using ThreadId = QueueType::ThreadId;
+    explicit Queue(uint32_t maxT, ParallelSolveOptions::Integration::Topology topo)
+        : q_(maxT)
+        , thread_(std::make_unique(maxT)) {
+        for (uint32_t i : irange(maxT)) {
+            thread_[i].peers = ParallelSolveOptions::initPeerSet(i, topo, maxT);
+            thread_[i].id    = q_.addThread();
+        }
+    }
+    ~Queue() {
+        for (uint32_t i : irange(q_.maxThreads())) { clear(i); }
+    }
+    void publish(uint32_t tId, SharedLiterals* n) {
+        assert(n->refCount() >= (q_.maxThreads() - 1) && tId < q_.maxThreads());
+        q_.publish(ClausePair{tId, n});
+    }
+    uint32_t pop(uint32_t tId, SharedLiterals** out, uint32_t maxOut) {
+        assert(tId < q_.maxThreads());
+        uint32_t r        = 0;
+        auto& [peers, id] = thread_[tId];
+        for (const ClausePair* n; r != maxOut && (n = q_.tryConsume(id)) != nullptr;) {
+            if (n->sender == tId) {
+                continue;
+            }
+            if (not peers.contains(n->sender) && n->lits->size() > 1) {
+                n->lits->release();
+                continue;
+            }
+            out[r++] = n->lits;
+        }
+        return r;
+    }
+    void clear(uint32_t tId) {
+        auto& qId = thread_[tId].id;
+        while (const auto* n = q_.tryConsume(qId)) {
+            if (n->sender != tId) {
+                n->lits->release();
+            }
+        }
+    }
+
+private:
+    QueueType                     q_;
+    std::unique_ptr thread_;
+};
+
+GlobalDistribution::GlobalDistribution(const Policy& p, uint32_t maxT, uint32_t topo) : Distributor(p) {
+    assert(maxT <= ParallelSolveOptions::supportedSolvers());
+    queue_ = std::make_unique(maxT, static_cast(topo));
 }
-uint32 GlobalDistribution::receive(const Solver& in, SharedLiterals** out, uint32 maxn) {
-	uint32 r = 0;
-	Queue::ThreadId& id = getThreadId(in.id());
-	uint64 peers = getPeerMask(in.id());
-	for (ClausePair n; r != maxn && queue_->tryConsume(id, n); ) {
-		if (n.sender != in.id()) {
-			if (inSet(peers, n.sender))  { out[r++] = n.lits; }
-			else if (n.lits->size() == 1){ out[r++] = n.lits; }
-			else                         { n.lits->release(); }
-		}
-	}
-	return r;
+GlobalDistribution::~GlobalDistribution() = default;
+void     GlobalDistribution::publish(const Solver& s, SharedLiterals* n) { queue_->publish(s.id(), n); }
+uint32_t GlobalDistribution::receive(const Solver& in, SharedLiterals** out, uint32_t maxn) {
+    return queue_->pop(in.id(), out, maxn);
 }
-
 /////////////////////////////////////////////////////////////////////////////////////////
 // LocalDistribution
 /////////////////////////////////////////////////////////////////////////////////////////
-LocalDistribution::LocalDistribution(const Policy& p, uint32 maxT, uint32 topo) : Distributor(p), thread_(0), numThread_(0) {
-	typedef ParallelSolveOptions::Integration::Topology Topology;
-	assert(maxT <= ParallelSolveOptions::supportedSolvers());
-	Topology t = static_cast(topo);
-	thread_    = new ThreadData*[numThread_ = maxT];
-	size_t sz  = ((sizeof(ThreadData) + 63) / 64) * 64;
-	for (uint32 i = 0; i != maxT; ++i) {
-		ThreadData* ti = new (alignedAllocChecked(sz)) ThreadData;
-		ti->received.init(&ti->sentinel);
-		ti->peers = ParallelSolveOptions::initPeerMask(i, t, maxT);
-		ti->free  = 0;
-		thread_[i]= ti;
-	}
-}
-LocalDistribution::~LocalDistribution() {
-	while (numThread_) {
-		ThreadData* ti      = thread_[--numThread_];
-		thread_[numThread_] = 0;
-		for (QNode* n; (n = ti->received.pop()) != 0; ) {
-			static_cast(n->data)->release();
-		}
-		ti->~ThreadData();
-		alignedFree(ti);
-	}
-	for (MPSCPtrQueue::RawNode* n; (n = blocks_.tryPop()) != 0; ) {
-		alignedFree(n);
-	}
-	delete [] thread_;
-}
+struct LocalDistribution::ThreadData {
+    using Queue = MpScPtrQueue;
+    using QNode = Queue::Node;
+    static QNode* allocBlock(QNode*& blockStack, uint32_t numNodes) {
+        ++numNodes; // +1 for metadata
+        auto* mem   = ::operator new((numNodes) * sizeof(QNode), std::align_val_t{cache_line_size});
+        auto* block = new (mem) QNode[numNodes]{};
+        block->next.store(blockStack);
+        block->data = reinterpret_cast(static_cast(numNodes));
+        blockStack  = block;
+        return block + 1;
+    }
+    explicit ThreadData(QNode& sent, SolverSet s) : received(sent), peers(s) {}
+    ~ThreadData() {
+        while (auto* n = blocks) {
+            blocks = Queue::toNode(n->next.load());
+            std::destroy_n(n, reinterpret_cast(n->data));
+            ::operator delete(n, std::align_val_t{cache_line_size});
+        }
+    }
+    QNode* allocNode(uint32_t blockHint, SharedLiterals* clause) {
+        if (free == nullptr) {
+            blockHint = std::max(blockHint, 1u);
+            // alloc a new block of nodes and push to free list
+            auto* block = allocBlock(blocks, blockHint);
+            for (auto n = blockHint; n--;) {
+                block[n].next.store(free);
+                free = &block[n];
+            }
+        }
+        auto* n = free;
+        free    = Queue::toNode(n->next.load());
+        n->data = clause;
+        return n;
+    }
+    SharedLiterals* popRec() {
+        if (auto* n = received.pop()) {
+            n->next.store(free);
+            free = n;
+            return static_cast(std::exchange(free->data, nullptr));
+        }
+        return nullptr;
+    }
+    Queue     received; // queue holding received clauses
+    SolverSet peers;    // set of peers from which this thread receives clauses
+    QNode*    free{};   // local free list - only accessed by this thread
+    QNode*    blocks{}; // allocated block list - only accessed by this thread
+};
 
-void LocalDistribution::freeNode(uint32 tId, QNode* n) const {
-	if (n != &thread_[tId]->sentinel) {
-		n->next = thread_[tId]->free;
-		thread_[tId]->free = n;
-	}
+LocalDistribution::LocalDistribution(const Policy& p, uint32_t maxShare, uint32_t topo)
+    : Distributor(p)
+    , thread_(std::make_unique(maxShare))
+    , numThread_(maxShare) {
+    assert(maxShare <= ParallelSolveOptions::supportedSolvers());
+    auto  t          = static_cast(topo);
+    auto  blockStack = static_cast(nullptr);
+    auto* block      = ThreadData::allocBlock(blockStack, numThread_);
+    for (uint32_t i : irange(maxShare)) {
+        thread_[i] = std::make_unique(block[i], ParallelSolveOptions::initPeerSet(i, t, maxShare));
+    }
+    thread_[0]->blocks = blockStack;
 }
-
-LocalDistribution::QNode* LocalDistribution::allocNode(uint32 tId, SharedLiterals* clause) {
-	for (ThreadData* td = thread_[tId];;) {
-		if (QNode* n = td->free) {
-			td->free = static_cast(static_cast(n->next));
-			n->data  = clause;
-			return n;
-		}
-		// alloc a new block of node;
-		const uint32 nNodes = 128;
-		QNode* raw = (QNode*)alignedAllocChecked(sizeof(QNode) * nNodes);
-		// add nodes [1, nNodes) to free list
-		for (uint32 i = 1; i != nNodes-1; ++i) {
-			raw[i].next = &raw[i+1];
-		}
-		raw[nNodes-1].next = 0;
-		td->free = &raw[1];
-		// use first node to link to block list
-		blocks_.push(raw);
-	}
+LocalDistribution::~LocalDistribution() {
+    while (numThread_) {
+        auto& td = thread_[--numThread_];
+        while (auto* lits = td->popRec()) { lits->release(); }
+    }
+    thread_.reset();
 }
 
 void LocalDistribution::publish(const Solver& s, SharedLiterals* n) {
-	assert(n->refCount() >= (numThread_-1));
-	uint32 sender = s.id();
-	uint32 size   = n->size();
-	uint32 decRef = 0;
-	for (uint32 i = 0; i != numThread_; ++i) {
-		if (i == sender) { continue; }
-		if (size > 1 && !inSet(thread_[i]->peers, sender)) { ++decRef; }
-		else {
-			QNode* node = allocNode(sender, n);
-			thread_[i]->received.push(node);
-		}
-	}
-	if (decRef) { n->release(decRef); }
+    auto sender    = s.id();
+    auto peers     = n->size() > 1 ? thread_[sender]->peers : ParallelSolveOptions::fullPeerSet(sender, numThread_);
+    auto nPeers    = peers.count();
+    auto maxPeers  = (numThread_ - 1);
+    auto allocHint = std::min(nPeers * 16, 254u);
+    assert(nPeers <= maxPeers && n->refCount() >= maxPeers);
+    if (auto diff = static_cast(maxPeers - nPeers); diff > 0) {
+        n->release(diff);
+    }
+    for (uint32_t i = 0; nPeers; ++i) {
+        assert(i < numThread_);
+        if (peers.contains(i)) {
+            auto* node = thread_[sender]->allocNode(allocHint, n); // allocate from this thread
+            thread_[i]->received.push(node);
+            --nPeers;
+        }
+    }
 }
-uint32 LocalDistribution::receive(const Solver& in, SharedLiterals** out, uint32 maxn) {
-	MPSCPtrQueue& q = thread_[in.id()]->received;
-	QNode*        n = 0;
-	uint32        r = 0;
-	while (r != maxn && (n = q.pop()) != 0) {
-		out[r++] = static_cast(n->data);
-		freeNode(in.id(), n);
-	}
-	return r;
+uint32_t LocalDistribution::receive(const Solver& in, SharedLiterals** out, uint32_t maxn) {
+    auto&    td = thread_[in.id()];
+    uint32_t r  = 0;
+    while (r != maxn && (out[r] = td->popRec()) != nullptr) { ++r; }
+    return r;
 }
 
-} } // namespace Clasp::mt
-
+} // namespace Clasp::mt
diff --git a/src/parser.cpp b/src/parser.cpp
index bcc89d0..1659b74 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2014-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,266 +22,270 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
-#include 
+
 #include 
+#include 
 #include 
+#include 
 #include 
 #include 
-#include 
-#include 
-#include 
+
 #include 
 #include 
-#include POTASSCO_EXT_INCLUDE(unordered_map)
-#include 
-#include 
-#include 
+#include 
+
+#include 
+#include 
 #include 
-#ifdef _MSC_VER
-#pragma warning (disable : 4996)
-#endif
+#include 
+
 namespace Clasp {
+static_assert(std::is_same_v, "unexpected weight type");
+
 ProblemType detectProblemType(std::istream& in) {
-	for (std::istream::int_type x, line = 1, pos = 1; (x = in.peek()) != std::char_traits::eof();) {
-		char c = static_cast(x);
-		if (c == ' ' || c == '\t')  { in.get(); ++pos; continue; }
-		if (AspParser::accept(c))   { return Problem_t::Asp; }
-		if (DimacsReader::accept(c)){ return Problem_t::Sat; }
-		if (OpbReader::accept(c))   { return Problem_t::Pb;  }
-		POTASSCO_REQUIRE(c == '\n', "parse error in line %d:%d: '%c': unrecognized input format", (int)line,(int)pos, c);
-		in.get();
-		++line;
-	}
-	POTASSCO_REQUIRE(false, "bad input stream");
+    for (std::istream::int_type x, line = 1, pos = 1; (x = in.peek()) != std::char_traits::eof();) {
+        char c = static_cast(x);
+        if (c == ' ' || c == '\t') {
+            in.get();
+            ++pos;
+            continue;
+        }
+        if (AspParser::accept(c)) {
+            return ProblemType::asp;
+        }
+        if (DimacsReader::accept(c)) {
+            return ProblemType::sat;
+        }
+        if (OpbReader::accept(c)) {
+            return ProblemType::pb;
+        }
+        POTASSCO_CHECK(c == '\n', std::errc::not_supported,
+                       "parse error in line %d:%d: '%c': unrecognized input format", (int) line, (int) pos, c);
+        in.get();
+        ++line;
+    }
+    POTASSCO_ASSERT_NOT_REACHED("bad input stream");
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ProgramParser
 /////////////////////////////////////////////////////////////////////////////////////////
-ProgramParser::ProgramParser() : strat_(0) {}
-ProgramParser::~ProgramParser() {}
+ProgramParser::ProgramParser() : strat_(nullptr) {}
+ProgramParser::~ProgramParser() = default;
 bool ProgramParser::accept(std::istream& str, const ParserOptions& o) {
-	if ((strat_ = doAccept(str, o)) != 0) {
-		strat_->setMaxVar(VAR_MAX);
-		return true;
-	}
-	return false;
-}
-bool ProgramParser::isOpen() const {
-	return strat_ != 0;
-}
-bool ProgramParser::incremental() const {
-	return strat_ && strat_->incremental();
-}
-bool ProgramParser::parse() {
-	return strat_ && strat_->parse();
-}
-bool ProgramParser::more() {
-	return strat_ && strat_->more();
-}
+    if (strat_ = doAccept(str, o); strat_ != nullptr) {
+        return true;
+    }
+    return false;
+}
+bool ProgramParser::isOpen() const { return strat_ != nullptr; }
+bool ProgramParser::incremental() const { return strat_ && strat_->incremental(); }
+bool ProgramParser::parse() { return strat_ && strat_->parse(); }
+bool ProgramParser::more() { return strat_ && strat_->more(); }
 void ProgramParser::reset() {
-	if (strat_) { strat_->reset(); }
-	strat_ = 0;
+    if (strat_) {
+        strat_->reset();
+    }
+    strat_ = nullptr;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
-// AspParser::SmAdapter
-//
-// Callback interface for smodels parser
-/////////////////////////////////////////////////////////////////////////////////////////
-struct AspParser::SmAdapter : public Asp::LogicProgramAdapter, public Potassco::AtomTable {
-	typedef POTASSCO_EXT_NS::unordered_map StrMap;
-	typedef SingleOwnerPtr StrMapPtr;
-	SmAdapter(Asp::LogicProgram& prg) : Asp::LogicProgramAdapter(prg) {}
-	void endStep() {
-		Asp::LogicProgramAdapter::endStep();
-		if (inc_ && lp_->ctx()->hasMinimize()) {
-			lp_->ctx()->removeMinimize();
-		}
-		if (!inc_) { atoms_ = 0; }
-	}
-	void add(Potassco::Atom_t id, const Potassco::StringSpan& name, bool output) {
-		ConstString n(name);
-		if (atoms_.get()) { atoms_->insert(StrMap::value_type(n, id)); }
-		if (output) { lp_->addOutput(n, id); }
-	}
-	Potassco::Atom_t find(const Potassco::StringSpan& name) {
-		if (!atoms_.get()) { return 0; }
-		ConstString n(name);
-		StrMap::iterator it = atoms_->find(n);
-		return it != atoms_->end() ? it->second : 0;
-	}
-	StrMapPtr atoms_;
-};
-/////////////////////////////////////////////////////////////////////////////////////////
 // AspParser
 /////////////////////////////////////////////////////////////////////////////////////////
-AspParser::AspParser(Asp::LogicProgram& prg)
-	: lp_(&prg)
-	, in_(0)
-	, out_(0) {}
-AspParser::~AspParser() {
-	delete in_;
-	delete out_;
-}
+AspParser::AspParser(Asp::LogicProgram& prg) : lp_(&prg), in_(nullptr), out_(nullptr) {}
 bool AspParser::accept(char c) { return Potassco::BufferedStream::isDigit(c) || c == 'a'; }
 
 AspParser::StrategyType* AspParser::doAccept(std::istream& str, const ParserOptions& o) {
-	delete in_;
-	delete out_;
-	if (Potassco::BufferedStream::isDigit((char)str.peek())) {
-		out_ = new SmAdapter(*lp_);
-		Potassco::SmodelsInput::Options so;
-		so.enableClaspExt();
-		if (o.isEnabled(ParserOptions::parse_heuristic)) {
-			so.convertHeuristic();
-			static_cast(out_)->atoms_ = new AspParser::SmAdapter::StrMap();
-		}
-		if (o.isEnabled(ParserOptions::parse_acyc_edge)) {
-			so.convertEdges();
-		}
-		in_ = new Potassco::SmodelsInput(*out_, so, static_cast(out_));
-	}
-	else {
-		out_ = new Asp::LogicProgramAdapter(*lp_);
-		in_ = new Potassco::AspifInput(*out_);
-	}
-	return in_->accept(str) ? in_ : 0;
+    in_.reset();
+    out_.reset();
+    if (Potassco::BufferedStream::isDigit(static_cast(str.peek()))) {
+        Potassco::SmodelsInput::Options inOpts;
+        inOpts.enableClaspExt();
+        Asp::LogicProgramAdapter::Options lpOpts{};
+        lpOpts.removeMinimize = true;
+        if (o.isEnabled(ParserOptions::parse_acyc_edge)) {
+            inOpts.convertEdges();
+        }
+        if (o.isEnabled(ParserOptions::parse_heuristic)) {
+            inOpts.convertHeuristic();
+        }
+        out_ = std::make_unique(*lp_, lpOpts);
+        in_  = std::make_unique(*out_, inOpts);
+    }
+    else {
+        out_ = std::make_unique(*lp_);
+        in_  = std::make_unique(*out_);
+    }
+    in_->setMaxVar(var_max - 1);
+    return in_->accept(str) ? in_.get() : nullptr;
 }
 
 void AspParser::write(Asp::LogicProgram& prg, std::ostream& os) {
-	write(prg, os, prg.supportsSmodels() ? format_smodels : format_aspif);
+    write(prg, os, prg.supportsSmodels() ? format_smodels : format_aspif);
 }
 void AspParser::write(Asp::LogicProgram& prg, std::ostream& os, Format f) {
-	using namespace Potassco;
-	SingleOwnerPtr out;
-	if (f == format_aspif) {
-		out.reset(new Potassco::AspifOutput(os));
-	}
-	else {
-		out.reset(new Potassco::SmodelsOutput(os, true, prg.falseAtom()));
-	}
-	if (prg.startAtom() == 1) { out->initProgram(prg.isIncremental()); }
-	out->beginStep();
-	prg.accept(*out);
-	out->endStep();
+    using namespace Potassco;
+    std::unique_ptr out;
+    if (f == format_aspif) {
+        out = std::make_unique(os);
+    }
+    else {
+        out = std::make_unique(os, true, prg.falseAtom());
+    }
+    if (prg.startAtom() == 1) {
+        out->initProgram(prg.isIncremental());
+    }
+    out->beginStep();
+    prg.accept(*out);
+    out->endStep();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // clasp specific extensions for Dimacs/OPB
 /////////////////////////////////////////////////////////////////////////////////////////
-SatReader::SatReader() {}
+SatReader::SatReader() = default;
 bool SatReader::skipLines(char c) {
-	while (peek(true) == c) { skipLine(); }
-	return true;
-}
-Literal SatReader::matchLit(Var max) {
-	for (char c; (c = stream()->peek()) == ' ' || c == '\t';) { stream()->get(); }
-	bool sign = stream()->peek() == '-';
-	if (sign) { stream()->get(); }
-	if (stream()->peek() == 'x') { stream()->get(); }
-	int64 id;
-	require(stream()->match(id) && id >= 0 && id <= (int64)max, "identifier expected");
-	return Literal(static_cast(id), sign);
-}
-void SatReader::parseGraph(uint32 maxVar, const char* pre, ExtDepGraph& graph) {
-	int maxNode = matchPos("graph: positive number of nodes expected");
-	while (match(pre)) {
-		if      (match("node ")) { skipLine(); }
-		else if (match("arc "))  {
-			Literal lit = matchLit(maxVar);
-			Var beg = matchPos(maxNode, "graph: invalid start node");
-			Var end = matchPos(maxNode, "graph: invalid end node");
-			graph.addEdge(lit, beg, end);
-		}
-		else if (match("endgraph")) { return; }
-		else { break; }
-	}
-	require(false, "graph: endgraph expected");
+    while (skipWs() == c) { skipLine(); }
+    return true;
 }
-void SatReader::parseProject(uint32 maxVar, SharedContext& ctx) {
-	for (unsigned n = this->line(); (stream()->skipWs(), this->line() == n);) {
-		Literal x = matchLit(maxVar);
-		if (x == lit_true()) break;
-		require(!x.sign(), "project: positive literal expected");
-		ctx.output.addProject(x);
-	}
-}
-void SatReader::parseAssume(uint32 maxVar) {
-	for (unsigned n = this->line(); (stream()->skipWs(), this->line() == n);) {
-		Literal x = matchLit(maxVar);
-		if (x == lit_true()) { break; }
-		addAssumption(x);
-	}
-}
-void SatReader::parseHeuristic(uint32 maxVar, SharedContext& ctx) {
-	using Potassco::Heuristic_t;
-	Heuristic_t type = static_cast(matchPos(Heuristic_t::eMax, "heuristic: modifier expected"));
-	Literal atom = matchLit(maxVar);
-	require(!atom.sign(), "heuristic: positive literal expected");
-	int16    bias = (int16)matchInt(INT16_MIN, INT16_MAX, "heuristic: bias expected");
-	uint16   prio = (uint16)matchPos(UINT16_MAX, "heuristic: priority expected");
-	ctx.heuristic.add(atom.var(), type, bias, prio, matchLit(maxVar));
+bool SatReader::skipMatch(const std::string_view& word) { return skipWs() && match(word); }
+
+Wsum_t SatReader::matchWeightSum(Wsum_t min, const char* what) {
+    return matchNum(min, std::numeric_limits::max(), what);
 }
-void SatReader::parseOutput(uint32 maxVar, SharedContext& ctx) {
-	if (match("range ")) {
-		Literal lo = matchLit(maxVar);
-		Literal hi = matchLit(maxVar);
-		require(lo.var() <= hi.var(), "output: invalid range");
-		ctx.output.setVarRange(Range32(lo.var(), hi.var() + 1));
-	}
-	else {
-		Literal cond = matchLit(maxVar);
-		while (peek(false) == ' ') { stream()->get(); }
-		std::string name;
-		for (char c; (c = stream()->get()) != '\n' && c;) { name += c; }
-		name.erase(name.find_last_not_of(" \t")+1);
-		ctx.output.add(ConstString(Potassco::toSpan(name)), cond);
-	}
+
+Literal SatReader::matchExtLit() {
+    for (char c; (c = peek()) == ' ' || c == '\t';) { get(); }
+    bool sign = peek() == '-';
+    if (sign) {
+        get();
+    }
+    if (peek() == 'x') {
+        get();
+    }
+    return {matchAtomOrZero("identifier expected"), sign};
 }
-void SatReader::parseExt(const char* pre, uint32 maxVar, SharedContext& ctx) {
-	const bool acyc = options.isEnabled(ParserOptions::parse_acyc_edge);
-	const bool minw = options.isEnabled(ParserOptions::parse_minimize);
-	const bool proj = options.isEnabled(ParserOptions::parse_project);
-	const bool heur = options.isEnabled(ParserOptions::parse_heuristic);
-	const bool assu = options.isEnabled(ParserOptions::parse_assume);
-	uint32     outp = options.isEnabled(ParserOptions::parse_output);
-	for (ExtDepGraph* g = 0; match(pre);) {
-		if (acyc && match("graph ")) {
-			require(g == 0, "graph: only one graph supported");
-			g = ctx.extGraph.get();
-			if (!g) { ctx.extGraph = (g = new ExtDepGraph()); }
-			else { g->update(); }
-			parseGraph(maxVar, pre, *g);
-			g->finalize(ctx);
-		}
-		else if (minw && match("minweight ")) {
-			WeightLitVec min;
-			for (unsigned n = this->line(); (stream()->skipWs(), this->line() == n);) {
-				Literal lit = matchLit(maxVar);
-				if (lit == lit_true()) {
-					skipLine();
-					break;
-				}
-				min.push_back(WeightLiteral(lit, matchInt(CLASP_WEIGHT_T_MIN, CLASP_WEIGHT_T_MAX, "minweight: weight expected")));
-			}
-			addObjective(min);
-		}
-		else if (proj && match("project "))   { parseProject(maxVar, ctx); }
-		else if (heur && match("heuristic ")) { parseHeuristic(maxVar, ctx); }
-		else if (assu && match("assume "))    { parseAssume(maxVar); }
-		else if (outp && match("output "))    {
-			if (outp++ == 1) { ctx.output.setVarRange(Range32(0, 0)); }
-			parseOutput(maxVar, ctx);
-		}
-		else { skipLine(); }
-	}
+
+void SatReader::parseGraph(const char* pre, ExtDepGraph& graph) {
+    auto maxNode = matchUint("graph: positive number of nodes expected");
+    while (skipMatch(pre)) {
+        if (skipMatch("node ")) {
+            skipLine();
+        }
+        else if (skipMatch("arc ")) {
+            auto lit = matchExtLit();
+            auto beg = matchUint(0u, maxNode, "graph: invalid start node");
+            auto end = matchUint(0u, maxNode, "graph: invalid end node");
+            graph.addEdge(lit, beg, end);
+        }
+        else if (skipMatch("endgraph")) {
+            return;
+        }
+        else {
+            break;
+        }
+    }
+    require(false, "graph: endgraph expected");
+}
+void SatReader::parseProject(SharedContext& ctx) {
+    for (auto n = this->line(); (skipWs(), this->line() == n);) {
+        auto x = matchExtLit();
+        if (x == lit_true) {
+            break;
+        }
+        require(not x.sign(), "project: positive literal expected");
+        ctx.output.addProject(x);
+    }
+}
+void SatReader::parseAssume() {
+    for (auto n = this->line(); (skipWs(), this->line() == n);) {
+        auto x = matchExtLit();
+        if (x == lit_true) {
+            break;
+        }
+        addAssumption(x);
+    }
+}
+void SatReader::parseHeuristic(SharedContext& ctx) {
+    using Potassco::DomModifier;
+    auto type = matchEnum("heuristic: modifier expected");
+    auto atom = matchExtLit();
+    require(not atom.sign(), "heuristic: positive literal expected");
+    auto bias = static_cast(matchInt(INT16_MIN, INT16_MAX, "heuristic: bias expected"));
+    auto prio = static_cast(matchUint(0u, UINT16_MAX, "heuristic: priority expected"));
+    ctx.heuristic.add(atom.var(), type, bias, prio, matchExtLit());
+}
+void SatReader::parseOutput(SharedContext& ctx) {
+    if (skipMatch("range ")) {
+        auto lo = matchExtLit();
+        auto hi = matchExtLit();
+        require(lo.var() <= hi.var(), "output: invalid range");
+        ctx.output.setVarRange(Range32(lo.var(), hi.var() + 1));
+    }
+    else {
+        auto cond = matchExtLit();
+        while (peek() == ' ') { get(); }
+        std::string name;
+        for (char c; (c = get()) != '\n' && c;) { name += c; }
+        name.erase(name.find_last_not_of(" \t") + 1);
+        ctx.output.add(Potassco::ConstString(name, Potassco::ConstString::create_shared), cond);
+    }
+}
+void SatReader::parseExt(const char* pre, SharedContext& ctx) {
+    const bool acyc = options.isEnabled(ParserOptions::parse_acyc_edge);
+    const bool minw = options.isEnabled(ParserOptions::parse_minimize);
+    const bool proj = options.isEnabled(ParserOptions::parse_project);
+    const bool heur = options.isEnabled(ParserOptions::parse_heuristic);
+    const bool assu = options.isEnabled(ParserOptions::parse_assume);
+    uint32_t   outp = options.isEnabled(ParserOptions::parse_output);
+    for (ExtDepGraph* g = nullptr; skipMatch(pre);) {
+        if (acyc && skipMatch("graph ")) {
+            require(g == nullptr, "graph: only one graph supported");
+            if (not ctx.extGraph.get()) {
+                ctx.extGraph = std::make_unique();
+            }
+            else {
+                ctx.extGraph->update();
+            }
+            g = ctx.extGraph.get();
+            parseGraph(pre, *g);
+            g->finalize(ctx);
+        }
+        else if (minw && skipMatch("minweight ")) {
+            WeightLitVec min;
+            for (unsigned n = this->line(); (skipWs(), this->line() == n);) {
+                auto lit = matchExtLit();
+                if (lit == lit_true) {
+                    skipLine();
+                    break;
+                }
+                min.push_back(WeightLiteral{lit, matchWeight(false, "minweight: weight expected")});
+            }
+            addObjective(min);
+        }
+        else if (proj && skipMatch("project ")) {
+            parseProject(ctx);
+        }
+        else if (heur && skipMatch("heuristic ")) {
+            parseHeuristic(ctx);
+        }
+        else if (assu && skipMatch("assume ")) {
+            parseAssume();
+        }
+        else if (outp && skipMatch("output ")) {
+            if (outp++ == 1) {
+                ctx.output.setVarRange(Range32(0, 0));
+            }
+            parseOutput(ctx);
+        }
+        else {
+            skipLine();
+        }
+    }
 }
 
-SatParser::SatParser(SatBuilder& prg) : reader_(new DimacsReader(prg)) {}
-SatParser::SatParser(PBBuilder& prg)  : reader_(new OpbReader(prg)) {}
-SatParser::~SatParser() { delete reader_; }
+SatParser::SatParser(SatBuilder& prg) : reader_(std::make_unique(prg)) {}
+SatParser::SatParser(PBBuilder& prg) : reader_(std::make_unique(prg)) {}
 ProgramParser::StrategyType* SatParser::doAccept(std::istream& str, const ParserOptions& o) {
-	reader_->options = o;
-	return reader_->accept(str) ? reader_ : 0;
+    reader_->options = o;
+    return reader_->accept(str) ? reader_.get() : nullptr;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DimacsReader
@@ -290,207 +294,210 @@ DimacsReader::DimacsReader(SatBuilder& prg) : program_(&prg) {}
 
 // Parses the p line: p [w]cnf[+] #vars #clauses [max clause weight]
 bool DimacsReader::doAttach(bool& inc) {
-	inc = false;
-	if (!accept(peek(false))) { return false; }
-	skipLines('c');
-	require(match("p "), "missing problem line");
-	wcnf_ = match("w");
-	require(match("cnf", false), "unrecognized format, [w]cnf expected");
-	plus_ = match("+", false);
-	require(stream()->get() == ' ', "invalid problem line: expected ' ' after format");
-	numVar_     = matchPos(ProgramParser::VAR_MAX, "#vars expected");
-	uint32 numC = matchPos("#clauses expected");
-	wsum_t cw   = 0;
-	while (stream()->peek() == ' ')  { stream()->get(); };
-	if (wcnf_ && peek(false) != '\n'){ stream()->match(cw); }
-	while (stream()->peek() == ' ')  { stream()->get(); };
-	require(stream()->get() == '\n', "invalid extra characters in problem line");
-	program_->prepareProblem(numVar_, cw, numC);
-	if (options.anyOf(ParserOptions::parse_full)) {
-		parseExt("c ", numVar_, *program_->ctx());
-	}
-	return true;
+    inc = false;
+    if (not accept(peek())) {
+        return false;
+    }
+    skipLines('c');
+    require(skipMatch("p "), "missing problem line");
+    wcnf_ = skipMatch("w");
+    require(match("cnf"), "unrecognized format, [w]cnf expected");
+    plus_ = match("+");
+    require(get() == ' ', "invalid problem line: expected ' ' after format");
+    numVar_     = matchUint(0u, Clasp::var_max - 1, "#vars expected");
+    auto   numC = matchUint("#clauses expected");
+    Wsum_t cw   = 0;
+    while (peek() == ' ') { get(); };
+    if (wcnf_ && peek() != '\n') {
+        cw = matchWeightSum(0, "wcnf: max clause weight expected");
+    }
+    while (peek() == ' ') { get(); };
+    require(get() == '\n', "invalid extra characters in problem line");
+    setMaxVar(numVar_);
+    program_->prepareProblem(numVar_, cw, numC);
+    if (options.anyOf(ParserOptions::parse_full)) {
+        parseExt("c ", *program_->ctx());
+    }
+    return true;
 }
 bool DimacsReader::doParse() {
-	LitVec cc; WeightLitVec wlc;
-	const bool  wcnf = wcnf_;
-	const bool  card = plus_;
-	const int64 maxV = static_cast(numVar_);
-	for (int64 cw = (int64)options.isEnabled(ParserOptions::parse_maxsat), lit = 0; skipLines('c') && peek(true); cc.clear()) {
-		if (wcnf) { require(stream()->match(cw) && cw > 0, "wcnf: positive clause weight expected"); }
-		if (!card || peek(wcnf) != 'w') {
-			for (lit = -1; stream()->match(lit) && lit != 0;) {
-				require(lit >= -maxV && lit <= maxV, "invalid variable in clause");
-				cc.push_back(toLit(static_cast(lit)));
-			}
-			if (lit == 0) { program_->addClause(cc, cw); }
-			else {
-				require(plus_, "invalid character in clause - '0' expected");
-				wlc.clear();
-				for (LitVec::const_iterator it = cc.begin(), end = cc.end(); it != end; ++it) {
-					wlc.push_back(WeightLiteral(*it, 1));
-				}
-				parseConstraintRhs(wlc);
-			}
-		}
-		else {
-			parsePbConstraint(wlc, maxV);
-		}
-	}
-	return require(!more(), "unrecognized format");
-}
-void DimacsReader::addObjective(const WeightLitVec& vec) {
-	program_->addObjective(vec);
-}
-void DimacsReader::addAssumption(Literal x) {
-	program_->addAssumption(x);
-}
+    LitVec       cc;
+    WeightLitVec wlc;
+    const bool   wcnf   = wcnf_;
+    const bool   card   = plus_;
+    const auto   minLit = -static_cast(numVar_), maxLit = static_cast(numVar_);
+    setMaxVar(numVar_);
+    for (int64_t cw = options.isEnabled(ParserOptions::parse_maxsat); skipLines('c') && skipWs(); cc.clear()) {
+        if (wcnf) {
+            cw = matchWeightSum(1, "wcnf: positive clause weight expected");
+            skipWs();
+        }
+        if (not card || peek() != 'w') {
+            int64_t lit = -1;
+            while (stream()->readInt(lit) && lit != 0) {
+                require(lit >= minLit && lit <= maxLit, "invalid variable in clause");
+                cc.push_back(toLit(static_cast(lit)));
+            }
+            if (lit == 0) {
+                program_->addClause(cc, cw);
+            }
+            else {
+                require(plus_, "invalid character in clause - '0' expected");
+                wlc.clear();
+                for (auto p : cc) { wlc.push_back(WeightLiteral{p, 1}); }
+                parseConstraintRhs(wlc);
+            }
+        }
+        else {
+            parsePbConstraint(wlc);
+        }
+    }
+    require(not more(), "unrecognized format");
+    return true;
+}
+void DimacsReader::addObjective(WeightLitView vec) { program_->addObjective(vec); }
+void DimacsReader::addAssumption(Literal x) { program_->addAssumption(x); }
 void DimacsReader::parseConstraintRhs(WeightLitVec& lhs) {
-	char c = stream()->get();
-	require((c == '<' || c == '>') && match("= "), "constraint operator '<=' or '>=' expected");
-	int64_t bound;
-	require(stream()->match(bound), "constraint bound expected");
-	require(bound >= CLASP_WEIGHT_T_MIN && bound <= CLASP_WEIGHT_T_MAX, "invalid constraint bound");
-	if (c == '<') {
-		bound *= -1;
-		for (WeightLitVec::iterator it = lhs.begin(), end = lhs.end(); it != end; ++it) {
-			it->second *= -1;
-		}
-	}
-	program_->addConstraint(lhs, static_cast(bound));
-}
-void DimacsReader::parsePbConstraint(WeightLitVec& constraint, int64_t maxV) {
-	constraint.clear();
-	require(match("w"), "'w' expected");
-	for (int64_t weight, lit; stream()->match(weight); ) {
-		require(weight >= CLASP_WEIGHT_T_MIN && weight <= CLASP_WEIGHT_T_MAX, "invalid constraint weight");
-		match("*");
-		require(stream()->match(lit), "literal expected");
-		require(lit >= -maxV && lit <= maxV && lit != 0, "invalid variable in constraint");
-		constraint.push_back(WeightLiteral(toLit(static_cast(lit)), static_cast(weight)));
-	}
-	parseConstraintRhs(constraint);
+    auto c = get();
+    require((c == '<' || c == '>') && match("= "), "constraint operator '<=' or '>=' expected");
+    auto bound = matchWeight(false, "constraint bound expected");
+    if (c == '<') {
+        bound *= -1;
+        for (auto& wl : lhs) { wl.weight *= -1; }
+    }
+    program_->addConstraint(lhs, bound);
+}
+void DimacsReader::parsePbConstraint(WeightLitVec& constraint) {
+    constraint.clear();
+    require(skipMatch("w"), "'w' expected");
+    constexpr auto stop = std::string_view{"<>", 2};
+    do {
+        auto w = matchWeight(false, "invalid constraint weight");
+        skipMatch("*");
+        auto l = matchLit();
+        constraint.push_back({toLit(l), w});
+    } while (stop.find(skipWs()) == std::string_view::npos);
+    parseConstraintRhs(constraint);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // OpbReader
 /////////////////////////////////////////////////////////////////////////////////////////
 OpbReader::OpbReader(PBBuilder& prg) : program_(&prg) {}
 
-void OpbReader::addObjective(const WeightLitVec& vec) {
-	program_->addObjective(vec);
-}
-void OpbReader::addAssumption(Literal x) {
-	program_->addAssumption(x);
-}
+void OpbReader::addObjective(WeightLitView vec) { program_->addObjective(vec); }
+void OpbReader::addAssumption(Literal x) { program_->addAssumption(x); }
 
-// * #variable= int #constraint= int [#product= int sizeproduct= int] [#soft= int mincost= int maxcost= int sumcost= int]
-// where [] indicate optional parts, i.e.
+// * #variable= int #constraint= int [#product= int sizeproduct= int] [#soft= int mincost= int maxcost= int sumcost=
+// int] where [] indicate optional parts, i.e.
 //  LIN-PBO: * #variable= int #constraint= int
 //  NLC-PBO: * #variable= int #constraint= int #product= int sizeproduct= int
 //  LIN-WBO: * #variable= int #constraint= int #soft= int mincost= int maxcost= int sumcost= int
-//  NLC-WBO: * #variable= int #constraint= int #product= int sizeproduct= int #soft= int mincost= int maxcost= int sumcost= int
+//  NLC-WBO: * #variable= int #constraint= int #product= int sizeproduct= int #soft= int mincost= int maxcost= int
+//  sumcost= int
 bool OpbReader::doAttach(bool& inc) {
-	inc = false;
-	if (!accept(peek(false))) { return false; }
-	require(match("* #variable="), "missing problem line '* #variable='");
-	unsigned numV = matchPos(ProgramParser::VAR_MAX, "number of vars expected");
-	require(match("#constraint="), "bad problem line: missing '#constraint='");
-	unsigned numC = matchPos("number of constraints expected");
-	unsigned numProd = 0, sizeProd = 0, numSoft = 0;
-	minCost_ = 0, maxCost_ = 0;
-	if (match("#product=")) { // NLC instance
-		numProd = matchPos();
-		require(match("sizeproduct="), "'sizeproduct=' expected");
-		sizeProd= matchPos();
-		(void)sizeProd;
-	}
-	if (match("#soft=")) { // WBO instance
-		numSoft = matchPos();
-		require(match("mincost="), "'mincost=' expected");
-		minCost_= (weight_t)matchPos(CLASP_WEIGHT_T_MAX, "invalid min costs");
-		require(match("maxcost="), "'maxcost=' expected");
-		maxCost_= (weight_t)matchPos(CLASP_WEIGHT_T_MAX, "invalid max costs");
-		require(match("sumcost="), "'sumcost=' expected");
-		wsum_t sum;
-		require(stream()->match(sum) && sum > 0, "positive integer expected");
-	}
-	program_->prepareProblem(numV, numProd, numSoft, numC);
-	return true;
+    inc = false;
+    if (not accept(peek())) {
+        return false;
+    }
+    require(skipMatch("* #variable="), "missing problem line '* #variable='");
+    auto numV = matchUint(0u, Clasp::var_max - 1, "number of vars expected");
+    require(skipMatch("#constraint="), "bad problem line: missing '#constraint='");
+    auto     numC    = matchUint("number of constraints expected");
+    unsigned numProd = 0, numSoft = 0;
+    minCost_ = 0, maxCost_ = 0;
+    if (skipMatch("#product=")) {
+        // NLC instance
+        numProd = matchUint();
+        require(skipMatch("sizeproduct="), "'sizeproduct=' expected");
+        std::ignore = matchUint();
+    }
+    if (skipMatch("#soft=")) { // WBO instance
+        numSoft = matchUint();
+        require(skipMatch("mincost="), "'mincost=' expected");
+        minCost_ = matchWeight(true, "invalid min costs");
+        require(skipMatch("maxcost="), "'maxcost=' expected");
+        maxCost_ = matchWeight(true, "invalid max costs");
+        require(skipMatch("sumcost="), "'sumcost=' expected");
+        std::ignore = matchWeightSum(1, "positive integer expected");
+    }
+    setMaxVar(numV);
+    program_->prepareProblem(numV, numProd, numSoft, numC);
+    return true;
 }
 bool OpbReader::doParse() {
-	if (options.anyOf(ParserOptions::parse_full - ParserOptions::parse_minimize)) {
-		options.assign(ParserOptions::parse_minimize, false);
-		parseExt("* ", program_->numVars(), *program_->ctx());
-	}
-	skipLines('*');
-	parseOptObjective();
-	for (;;) {
-		skipLines('*');
-		if (!more()) { return true; }
-		parseConstraint();
-	}
+    if (options.anyOf(ParserOptions::parse_full - ParserOptions::parse_minimize)) {
+        options.assign(ParserOptions::parse_minimize, false);
+        parseExt("* ", *program_->ctx());
+    }
+    skipLines('*');
+    parseOptObjective();
+    for (;;) {
+        skipLines('*');
+        if (not more()) {
+            return true;
+        }
+        parseConstraint();
+    }
 }
 // ::= "min:"    ";"
 // OR
 //   ::= "soft:" [] ";"
 void OpbReader::parseOptObjective() {
-	if (match("min:")) {
-		parseSum();
-		program_->addObjective(active_.lits);
-	}
-	else if (match("soft:")) {
-		wsum_t softCost;
-		require(stream()->match(softCost) && softCost > 0, "positive integer expected");
-		require(match(";"), "semicolon missing after constraint");
-		program_->setSoftBound(softCost);
-	}
+    if (skipMatch("min:")) {
+        parseSum();
+        program_->addObjective(active_.lits);
+    }
+    else if (skipMatch("soft:")) {
+        auto softCost = matchWeightSum(1, "positive integer expected");
+        require(skipMatch(";"), "semicolon missing after constraint");
+        program_->setSoftBound(softCost);
+    }
 }
 
 // ::=      ";"
 // OR
 // ::= "["    "]" 
 void OpbReader::parseConstraint() {
-	weight_t cost = 0;
-	if (match("[")) {
-		cost = matchInt(minCost_, maxCost_, "invalid soft constraint cost");
-		require(match("]"), "invalid soft constraint");
-	}
-	parseSum();
-	active_.eq = match("=");
-	require(active_.eq || match(">=", false), "relational operator expected");
-	active_.bound = matchInt(CLASP_WEIGHT_T_MIN, CLASP_WEIGHT_T_MAX, "invalid coefficient on rhs of constraint");
-	require(match(";"), "semicolon missing after constraint");
-	program_->addConstraint(active_.lits, active_.bound, active_.eq, cost);
+    Weight_t cost = 0;
+    if (skipMatch("[")) {
+        cost = matchInt(minCost_, maxCost_, "invalid soft constraint cost");
+        require(skipMatch("]"), "invalid soft constraint");
+    }
+    parseSum();
+    active_.eq = skipMatch("=");
+    require(active_.eq || match(">="), "relational operator expected");
+    active_.bound = matchWeight(false, "invalid coefficient on rhs of constraint");
+    require(skipMatch(";"), "semicolon missing after constraint");
+    program_->addConstraint(active_.lits, active_.bound, active_.eq, cost);
 }
 
 // ::=  |  
 // ::=    
 void OpbReader::parseSum() {
-	active_.lits.clear();
-	while (!match(";")) {
-		int coeff = matchInt(INT_MIN+1, INT_MAX, "coefficient expected");
-		parseTerm();
-		Literal x = active_.term.size() == 1 ? active_.term[0] : program_->addProduct(active_.term);
-		active_.lits.push_back(WeightLiteral(x, coeff));
-		char p = peek(true);
-		if (p == '>' || p == '=') break;
-	}
+    active_.lits.clear();
+    while (not skipMatch(";")) {
+        auto coeff = matchInt(INT_MIN + 1, INT_MAX, "coefficient expected");
+        parseTerm();
+        Literal x = active_.term.size() == 1 ? active_.term[0] : program_->addProduct(active_.term);
+        active_.lits.push_back(WeightLiteral{x, coeff});
+        if (auto p = skipWs(); p == '>' || p == '=') {
+            break;
+        }
+    }
 }
 // ::=
 // OR
 // ::=  |  + 
 void OpbReader::parseTerm() {
-	active_.term.clear();
-	char peek;
-	do  {
-		match("*");             // optionally
-		bool sign = match("~"); // optionally
-		require(match("x"), "identifier expected");
-		Var var   = matchAtom();
-		require(var <= program_->numVars(), "identifier out of range");
-		active_.term.push_back(Literal(var, sign));
-		peek = this->peek(true);
-	} while (peek == '*' || peek == '~' || peek == 'x');
+    active_.term.clear();
+    char peek;
+    do {
+        skipMatch("*");             // optionally
+        bool sign = skipMatch("~"); // optionally
+        require(skipMatch("x"), "identifier expected");
+        active_.term.push_back(Literal(matchAtom(), sign));
+        peek = skipWs();
+    } while (peek == '*' || peek == '~' || peek == 'x');
 }
 
-}
+} // namespace Clasp
diff --git a/src/program_builder.cpp b/src/program_builder.cpp
index f403c4f..9c71d5f 100644
--- a/src/program_builder.cpp
+++ b/src/program_builder.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,386 +22,425 @@
 // IN THE SOFTWARE.
 //
 #include 
+
+#include 
+#include 
 #include 
 #include 
-#include 
 #include 
-#include 
-#include POTASSCO_EXT_INCLUDE(unordered_map)
+
+#include 
+
 #include 
+#include 
 namespace Clasp {
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // class ProgramBuilder
 /////////////////////////////////////////////////////////////////////////////////////////
-ProgramBuilder::ProgramBuilder() : ctx_(0), frozen_(true) {}
-ProgramBuilder::~ProgramBuilder() {}
+ProgramBuilder::ProgramBuilder() : ctx_(nullptr), frozen_(true) {}
+ProgramBuilder::~ProgramBuilder() = default;
 bool ProgramBuilder::ok() const { return ctx_ && ctx_->ok(); }
 bool ProgramBuilder::startProgram(SharedContext& ctx) {
-	ctx.report(Event::subsystem_load);
-	ctx_    = &ctx;
-	frozen_ = ctx.frozen();
-	return ctx_->ok() && doStartProgram();
+    ctx.report(Event::subsystem_load);
+    ctx_    = &ctx;
+    frozen_ = ctx.frozen();
+    return ctx_->ok() && doStartProgram();
 }
 bool ProgramBuilder::updateProgram() {
-	POTASSCO_REQUIRE(ctx_, "startProgram() not called!");
-	bool up = frozen();
-	bool ok = ctx_->ok() && ctx_->unfreeze() && doUpdateProgram() && (ctx_->setSolveMode(SharedContext::solve_multi), true);
-	frozen_ = ctx_->frozen();
-	if (up && !frozen()){ ctx_->report(Event::subsystem_load); }
-	return ok;
+    POTASSCO_CHECK_PRE(ctx_, "startProgram() not called!");
+    bool up = frozen();
+    bool ok =
+        ctx_->ok() && ctx_->unfreeze() && doUpdateProgram() && (ctx_->setSolveMode(SharedContext::solve_multi), true);
+    frozen_ = ctx_->frozen();
+    if (up && not frozen()) {
+        ctx_->report(Event::subsystem_load);
+    }
+    return ok;
 }
 bool ProgramBuilder::endProgram() {
-	POTASSCO_REQUIRE(ctx_, "startProgram() not called!");
-	bool ok = ctx_->ok();
-	if (ok && !frozen_) {
-		ctx_->report(Event::subsystem_prepare);
-		ok = doEndProgram();
-		frozen_ = true;
-	}
-	return ok;
+    POTASSCO_CHECK_PRE(ctx_, "startProgram() not called!");
+    bool ok = ctx_->ok();
+    if (ok && not frozen_) {
+        ctx_->report(Event::subsystem_prepare);
+        ok      = doEndProgram();
+        frozen_ = true;
+    }
+    return ok;
 }
 void ProgramBuilder::getAssumptions(LitVec& out) const {
-	POTASSCO_REQUIRE(ctx_ && frozen());
-	doGetAssumptions(out);
+    POTASSCO_CHECK_PRE(ctx_ && frozen());
+    doGetAssumptions(out);
 }
 void ProgramBuilder::getWeakBounds(SumVec& out) const {
-	POTASSCO_REQUIRE(ctx_ && frozen());
-	doGetWeakBounds(out);
+    POTASSCO_CHECK_PRE(ctx_ && frozen());
+    doGetWeakBounds(out);
 }
 ProgramParser& ProgramBuilder::parser() {
-	if (!parser_.get()) {
-		parser_.reset(doCreateParser());
-	}
-	return *parser_;
+    if (not parser_) {
+        parser_.reset(doCreateParser());
+    }
+    return *parser_;
 }
 bool ProgramBuilder::parseProgram(std::istream& input) {
-	POTASSCO_REQUIRE(ctx_ && !frozen());
-	ProgramParser& p = parser();
-	POTASSCO_REQUIRE(p.accept(input), "unrecognized input format");
-	return p.parse();
-}
-void ProgramBuilder::addMinLit(weight_t prio, WeightLiteral x) {
-	ctx_->addMinimize(x, prio);
+    POTASSCO_CHECK_PRE(ctx_ && not frozen());
+    ProgramParser& p = parser();
+    POTASSCO_CHECK_PRE(p.accept(input), "unrecognized input format");
+    return p.parse();
 }
+void ProgramBuilder::addMinLit(Weight_t prio, WeightLiteral x) { ctx_->addMinimize(x, prio); }
 void ProgramBuilder::markOutputVariables() const {
-	const OutputTable& out = ctx_->output;
-	for (OutputTable::range_iterator it = out.vars_begin(), end = out.vars_end(); it != end; ++it) {
-		ctx_->setOutput(*it, true);
-	}
-	for (OutputTable::pred_iterator it = out.pred_begin(), end = out.pred_end(); it != end; ++it) {
-		ctx_->setOutput(it->cond.var(), true);
-	}
-}
-void ProgramBuilder::doGetWeakBounds(SumVec&) const  {}
+    const OutputTable& out = ctx_->output;
+    for (auto v : out.vars_range()) { ctx_->setOutput(v, true); }
+    for (const auto& pred : out.pred_range()) { ctx_->setOutput(pred.cond.var(), true); }
+}
+void ProgramBuilder::doGetWeakBounds(SumVec&) const {}
 /////////////////////////////////////////////////////////////////////////////////////////
 // class SatBuilder
 /////////////////////////////////////////////////////////////////////////////////////////
-SatBuilder::SatBuilder() : ProgramBuilder(), hardWeight_(0), vars_(0), pos_(0) {}
 bool SatBuilder::markAssigned() {
-	if (pos_ == ctx()->master()->trail().size()) { return true; }
-	bool ok = ctx()->ok() && ctx()->master()->propagate();
-	for (const LitVec& trail = ctx()->master()->trail(); pos_ < trail.size(); ++pos_) {
-		markLit(~trail[pos_]);
-	}
-	return ok;
-}
-void SatBuilder::prepareProblem(uint32 numVars, wsum_t cw, uint32 clauseHint) {
-	POTASSCO_REQUIRE(ctx(), "startProgram() not called!");
-	Var start = ctx()->addVars(numVars, Var_t::Atom, VarInfo::Input | VarInfo::Nant);
-	ctx()->output.setVarRange(Range32(start, start + numVars));
-	ctx()->startAddConstraints(std::min(clauseHint, uint32(10000)));
-	varState_.resize(start + numVars);
-	vars_       = ctx()->numVars();
-	hardWeight_ = cw;
-	markAssigned();
-}
-bool SatBuilder::addObjective(const WeightLitVec& min) {
-	for (WeightLitVec::const_iterator it = min.begin(), end = min.end(); it != end; ++it) {
-		addMinLit(0, *it);
-		markOcc(~it->first);
-	}
-	return ctx()->ok();
-}
-void SatBuilder::addProject(Var v) {
-	ctx()->output.addProject(posLit(v));
-}
+    if (pos_ == ctx()->master()->numAssignedVars()) {
+        return true;
+    }
+    bool ok = ctx()->ok() && ctx()->master()->propagate();
+    for (auto lit : ctx()->master()->trailView(pos_)) {
+        markLit(~lit);
+        ++pos_;
+    }
+    return ok;
+}
+void SatBuilder::prepareProblem(uint32_t numVars, Wsum_t cw, uint32_t clauseHint) {
+    POTASSCO_CHECK_PRE(ctx(), "startProgram() not called!");
+    auto start = ctx()->addVars(numVars, VarType::atom, VarInfo::flag_input | VarInfo::flag_nant);
+    ctx()->output.setVarRange(Range32(start, start + numVars));
+    ctx()->startAddConstraints(std::min(clauseHint, 10000u));
+    varState_.resize(start + numVars);
+    vars_       = ctx()->numVars();
+    hardWeight_ = cw;
+    markAssigned();
+}
+bool SatBuilder::addObjective(WeightLitView min) {
+    for (const auto& lit : min) {
+        addMinLit(0, lit);
+        markOcc(~lit.lit);
+    }
+    return ctx()->ok();
+}
+void SatBuilder::addProject(Var_t v) { ctx()->output.addProject(posLit(v)); }
 void SatBuilder::addAssumption(Literal x) {
-	assume_.push_back(x);
-	markOcc(x);
-	ctx()->setFrozen(x.var(), true);
-}
-bool SatBuilder::addClause(LitVec& clause, wsum_t cw) {
-	if (!ctx()->ok() || satisfied(clause)) { return ctx()->ok(); }
-	POTASSCO_REQUIRE(cw >= 0 && (cw <= std::numeric_limits::max() || cw == hardWeight_), "Clause weight out of bounds");
-	if (cw == hardWeight_) {
-		return ClauseCreator::create(*ctx()->master(), clause, Constraint_t::Static).ok() && markAssigned();
-	}
-	else {
-		// Store weight, relaxation var, and (optionally) clause
-		softClauses_.push_back(Literal::fromRep((uint32)cw));
-		if      (clause.size() > 1){ softClauses_.push_back(posLit(++vars_)); softClauses_.insert(softClauses_.end(), clause.begin(), clause.end()); }
-		else if (!clause.empty())  { softClauses_.push_back(~clause.back());  }
-		else                       { softClauses_.push_back(lit_true()); }
-		softClauses_.back().flag(); // mark end of clause
-	}
-	return true;
+    assume_.push_back(x);
+    markOcc(x);
+    ctx()->setFrozen(x.var(), true);
+}
+bool SatBuilder::addClause(LitVec& clause, Wsum_t cw) {
+    if (not ctx()->ok() || satisfied(clause)) {
+        return ctx()->ok();
+    }
+    POTASSCO_CHECK_PRE(cw >= 0 && (cw <= std::numeric_limits::max() || cw == hardWeight_),
+                       "Clause weight out of bounds");
+    if (cw == hardWeight_) {
+        return ClauseCreator::create(*ctx()->master(), clause, {}, ConstraintType::static_).ok() && markAssigned();
+    }
+    // Store weight, relaxation var, and (optionally) clause
+    softClauses_.push_back(Literal::fromRep(static_cast(cw)));
+    if (clause.size() > 1) {
+        softClauses_.push_back(posLit(++vars_));
+        softClauses_.insert(softClauses_.end(), clause.begin(), clause.end());
+    }
+    else if (not clause.empty()) {
+        softClauses_.push_back(~clause.back());
+    }
+    else {
+        softClauses_.push_back(lit_true);
+    }
+    softClauses_.back().flag(); // mark end of clause
+    return true;
 }
 bool SatBuilder::satisfied(LitVec& cc) {
-	bool sat = false;
-	LitVec::iterator j = cc.begin();
-	for (LitVec::const_iterator it = cc.begin(), end = cc.end(); it != end; ++it) {
-		Literal x = *it;
-		uint32  m = 1+x.sign();
-		uint32  n = uint32(varState_[it->var()] & 3u) + m;
-		if      (n == m) { varState_[it->var()] |= m; x.unflag(); *j++ = x; }
-		else if (n == 3u){ sat = true; break; }
-	}
-	cc.erase(j, cc.end());
-	for (LitVec::const_iterator it = cc.begin(), end = cc.end(); it != end; ++it) {
-		if (!sat) { markOcc(*it); }
-		varState_[it->var()] &= ~3u;
-	}
-	return sat;
-}
-bool SatBuilder::addConstraint(WeightLitVec& lits, weight_t bound) {
-	if (!ctx()->ok()) { return false; }
-	WeightLitsRep rep = WeightLitsRep::create(*ctx()->master(), lits, bound);
-	if (rep.open()) {
-		for (const WeightLiteral* x = rep.lits, *end = rep.lits + rep.size; x != end; ++x) {
-			markOcc(x->first);
-		}
-	}
-	return WeightConstraint::create(*ctx()->master(), lit_true(), rep, 0u).ok();
+    bool sat = false;
+    auto j   = cc.begin();
+    for (auto x : cc) {
+        auto m = trueValue(x);
+        if (auto p = varState_[x.var()] & 3u; p == 0) {
+            varState_[x.var()] |= m;
+            x.unflag();
+            *j++ = x;
+        }
+        else if (p != m) {
+            sat = true;
+            break;
+        }
+    }
+    cc.erase(j, cc.end());
+    for (auto x : cc) {
+        Potassco::store_clear_mask(varState_[x.var()], 3u);
+        if (not sat) {
+            markOcc(x);
+        }
+    }
+    return sat;
+}
+bool SatBuilder::addConstraint(WeightLitVec& lits, Weight_t bound) {
+    if (not ctx()->ok()) {
+        return false;
+    }
+    auto rep = WeightLitsRep::create(*ctx()->master(), lits, bound);
+    if (rep.open()) {
+        for (const auto& [lit, _] : rep.literals()) { markOcc(lit); }
+    }
+    return WeightConstraint::create(*ctx()->master(), lit_true, rep, {}).ok();
 }
 bool SatBuilder::doStartProgram() {
-	vars_ = ctx()->numVars();
-	pos_  = 0;
-	assume_.clear();
-	return markAssigned();
-}
-ProgramParser* SatBuilder::doCreateParser() {
-	return new SatParser(*this);
-}
-bool SatBuilder::doEndProgram() {
-	bool ok = ctx()->ok();
-	if (!softClauses_.empty() && ok) {
-		ctx()->setPreserveModels(true);
-		uint32 softVars = vars_ - ctx()->numVars();
-		ctx()->addVars(softVars, Var_t::Atom, VarInfo::Nant);
-		ctx()->startAddConstraints();
-		LitVec cc;
-		for (LitVec::const_iterator it = softClauses_.begin(), end = softClauses_.end(); it != end && ok; ++it) {
-			weight_t w     = (weight_t)it->rep();
-			Literal  relax = *++it;
-			if (!relax.flagged()) {
-				cc.assign(1, relax);
-				do { cc.push_back(*++it); } while (!cc.back().flagged());
-				cc.back().unflag();
-				ok = ClauseCreator::create(*ctx()->master(), cc, Constraint_t::Static).ok();
-			}
-			addMinLit(0, WeightLiteral(relax.unflag(), w));
-		}
-		LitVec().swap(softClauses_);
-	}
-	if (ok) {
-		const uint32 seen = 12;
-		const bool   elim = !ctx()->preserveModels();
-		for (Var v = 1; v != (Var)varState_.size() && ok; ++v) {
-			uint32 m = varState_[v];
-			if ( (m & seen) != seen ) {
-				if      (m)   { ctx()->setNant(v, false); ctx()->master()->setPref(v, ValueSet::def_value, ValueRep(m >> 2)); }
-				else if (elim){ ctx()->eliminate(v); }
-			}
-		}
-		markOutputVariables();
-	}
-	return ok;
+    vars_ = ctx()->numVars();
+    pos_  = 0;
+    assume_.clear();
+    return markAssigned();
+}
+ProgramParser* SatBuilder::doCreateParser() { return new SatParser(*this); }
+bool           SatBuilder::doEndProgram() {
+    bool ok = ctx()->ok();
+    if (not softClauses_.empty() && ok) {
+        ctx()->setPreserveModels(true);
+        uint32_t softVars = vars_ - ctx()->numVars();
+        ctx()->addVars(softVars, VarType::atom, VarInfo::flag_nant);
+        ctx()->startAddConstraints();
+        LitVec cc;
+        for (auto it = softClauses_.begin(), end = softClauses_.end(); it != end && ok; ++it) {
+            auto    w     = static_cast(it->rep());
+            Literal relax = *++it;
+            if (not relax.flagged()) {
+                cc.assign(1, relax);
+                do { cc.push_back(*++it); } while (not cc.back().flagged());
+                cc.back().unflag();
+                ok = ClauseCreator::create(*ctx()->master(), cc, {}, ConstraintType::static_).ok();
+            }
+            addMinLit(0, WeightLiteral{relax.unflag(), w});
+        }
+        discardVec(softClauses_);
+    }
+    if (ok) {
+        constexpr uint32_t seen = 12;
+        const bool         elim = not ctx()->preserveModels();
+        for (auto v : irange(1u, size32(varState_))) {
+            if (uint32_t m = varState_[v]; not Potassco::test_mask(m, seen)) {
+                if (m) {
+                    ctx()->setNant(v, false);
+                    ctx()->master()->setPref(v, ValueSet::def_value, static_cast(m >> 2));
+                }
+                else if (elim) {
+                    ctx()->eliminate(v);
+                }
+            }
+        }
+        markOutputVariables();
+    }
+    return ok;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class PBBuilder
 /////////////////////////////////////////////////////////////////////////////////////////
-struct PBBuilder::ProductIndex : POTASSCO_EXT_NS::unordered_map {};
+struct PBBuilder::ProductIndex : std::unordered_map {};
 
-PBBuilder::PBBuilder() : auxVar_(1) {
-	products_ = new ProductIndex();
-}
-PBBuilder::~PBBuilder() { products_.reset(0); }
-void PBBuilder::prepareProblem(uint32 numVars, uint32 numProd, uint32 numSoft, uint32 numCons) {
-	POTASSCO_REQUIRE(ctx(), "startProgram() not called!");
-	Var out = ctx()->addVars(numVars, Var_t::Atom, VarInfo::Nant | VarInfo::Input);
-	auxVar_ = ctx()->addVars(numProd + numSoft, Var_t::Atom, VarInfo::Nant);
-	endVar_ = auxVar_ + numProd + numSoft;
-	ctx()->output.setVarRange(Range32(out, out + numVars));
-	ctx()->startAddConstraints(numCons);
-}
-uint32 PBBuilder::getAuxVar() {
-	POTASSCO_REQUIRE(ctx()->validVar(auxVar_), "Variables out of bounds");
-	return auxVar_++;
-}
-bool PBBuilder::addConstraint(WeightLitVec& lits, weight_t bound, bool eq, weight_t cw) {
-	if (!ctx()->ok()) { return false; }
-	Var eqVar = 0;
-	if (cw > 0) { // soft constraint
-		if (lits.size() != 1) {
-			eqVar = getAuxVar();
-			addMinLit(0, WeightLiteral(negLit(eqVar), cw));
-		}
-		else {
-			if (lits[0].second < 0)    { bound += (lits[0].second = -lits[0].second); lits[0].first = ~lits[0].first; }
-			if (lits[0].second < bound){ lits[0].first = lit_false(); }
-			addMinLit(0, WeightLiteral(~lits[0].first, cw));
-			return true;
-		}
-	}
-	return WeightConstraint::create(*ctx()->master(), posLit(eqVar), lits, bound, !eq ? 0 : WeightConstraint::create_eq_bound).ok();
+PBBuilder::PBBuilder() : products_(std::make_unique()) {}
+PBBuilder::~PBBuilder() = default;
+void PBBuilder::prepareProblem(uint32_t numVars, uint32_t numProd, uint32_t numSoft, uint32_t numCons) {
+    POTASSCO_CHECK_PRE(ctx(), "startProgram() not called!");
+    auto out = ctx()->addVars(numVars, VarType::atom, VarInfo::flag_nant | VarInfo::flag_input);
+    auxVar_  = ctx()->addVars(numProd + numSoft, VarType::atom, VarInfo::flag_nant);
+    endVar_  = auxVar_ + numProd + numSoft;
+    ctx()->output.setVarRange(Range32(out, out + numVars));
+    ctx()->startAddConstraints(numCons);
+}
+uint32_t PBBuilder::nextAuxVar() {
+    POTASSCO_CHECK_PRE(ctx()->validVar(auxVar_), "Variables out of bounds");
+    return auxVar_++;
+}
+bool PBBuilder::addConstraint(WeightLitVec& lits, Weight_t bound, bool eq, Weight_t cw) {
+    if (not ctx()->ok()) {
+        return false;
+    }
+    Var_t eqVar = 0;
+    if (cw > 0) { // soft constraint
+        if (lits.size() != 1) {
+            eqVar = nextAuxVar();
+            addMinLit(0, WeightLiteral{negLit(eqVar), cw});
+        }
+        else {
+            if (lits[0].weight < 0) {
+                bound       += (lits[0].weight = -lits[0].weight);
+                lits[0].lit  = ~lits[0].lit;
+            }
+            if (lits[0].weight < bound) {
+                lits[0].lit = lit_false;
+            }
+            addMinLit(0, WeightLiteral{~lits[0].lit, cw});
+            return true;
+        }
+    }
+    return WeightConstraint::create(*ctx()->master(), posLit(eqVar), lits, bound,
+                                    not eq ? WeightConstraint::CreateFlag{} : WeightConstraint::create_eq_bound)
+        .ok();
 }
 
-bool PBBuilder::addObjective(const WeightLitVec& min) {
-	for (WeightLitVec::const_iterator it = min.begin(), end = min.end(); it != end; ++it) {
-		addMinLit(0, *it);
-	}
-	return ctx()->ok();
-}
-void PBBuilder::addProject(Var v) {
-	ctx()->output.addProject(posLit(v));
+bool PBBuilder::addObjective(WeightLitView min) {
+    for (const auto& lit : min) { addMinLit(0, lit); }
+    return ctx()->ok();
 }
+void PBBuilder::addProject(Var_t v) { ctx()->output.addProject(posLit(v)); }
 void PBBuilder::addAssumption(Literal x) {
-	assume_.push_back(x);
-	ctx()->setFrozen(x.var(), true);
+    assume_.push_back(x);
+    ctx()->setFrozen(x.var(), true);
 }
-bool PBBuilder::setSoftBound(wsum_t b) {
-	if (b > 0) { soft_ = b-1; }
-	return true;
+bool PBBuilder::setSoftBound(Wsum_t b) {
+    if (b > 0) {
+        soft_ = b - 1;
+    }
+    return true;
 }
 
 void PBBuilder::doGetWeakBounds(SumVec& out) const {
-	if (soft_ != std::numeric_limits::max()) {
-		if      (out.empty())   { out.push_back(soft_); }
-		else if (out[0] > soft_){ out[0] = soft_; }
-	}
+    if (soft_ != std::numeric_limits::max()) {
+        if (out.empty()) {
+            out.push_back(soft_);
+        }
+        else if (out[0] > soft_) {
+            out[0] = soft_;
+        }
+    }
 }
 
 Literal PBBuilder::addProduct(LitVec& lits) {
-	if (!ctx()->ok()) { return lit_false(); }
-	prod_.lits.reserve(lits.size() + 1);
-	if (productSubsumed(lits, prod_)){
-		return lits[0];
-	}
-	Literal& eq = (*products_)[prod_];
-	if (eq != lit_true()) {
-		return eq;
-	}
-	eq = posLit(getAuxVar());
-	addProductConstraints(eq, lits);
-	return eq;
+    if (not ctx()->ok()) {
+        return lit_false;
+    }
+    prod_.lits.reserve(lits.size() + 1);
+    if (productSubsumed(lits, prod_)) {
+        return lits[0];
+    }
+    Literal& eq = (*products_)[prod_];
+    if (eq != lit_true) {
+        return eq;
+    }
+    eq = posLit(nextAuxVar());
+    addProductConstraints(eq, lits);
+    return eq;
 }
 bool PBBuilder::productSubsumed(LitVec& lits, PKey& prod) {
-	Literal last       = lit_true();
-	LitVec::iterator j = lits.begin();
-	Solver& s          = *ctx()->master();
-	uint32  abst       = 0;
-	prod.lits.assign(1, lit_true()); // room for abst
-	for (LitVec::const_iterator it = lits.begin(), end = lits.end(); it != end; ++it) {
-		if (s.isFalse(*it) || ~*it == last) { // product is always false
-			lits.assign(1, lit_false());
-			return true;
-		}
-		else if (last.var() > it->var()) { // not sorted - redo with sorted product
-			std::sort(lits.begin(), lits.end());
-			return productSubsumed(lits, prod);
-		}
-		else if (!s.isTrue(*it) && last != *it) {
-			prod.lits.push_back(*it);
-			abst += hashLit(*it);
-			last  = *it;
-			*j++  = last;
-		}
-	}
-	prod.lits[0].rep() = abst;
-	lits.erase(j, lits.end());
-	if (lits.empty()) { lits.assign(1, lit_true()); }
-	return lits.size() < 2;
+    for (auto& s = *ctx()->master();;) {
+        auto j      = lits.begin();
+        auto last   = lit_true;
+        auto abst   = 0u;
+        auto sorted = true;
+        prod.lits.assign(1, lit_true); // room for abst
+        for (auto lit : lits) {
+            if (s.isFalse(lit) || ~lit == last) { // product is always false
+                lits.assign(1, lit_false);
+                return true;
+            }
+            else if (last.var() > lit.var()) { // not sorted - redo with sorted product
+                sorted = false;
+                break;
+            }
+            else if (not s.isTrue(lit) && last != lit) {
+                prod.lits.push_back(lit);
+                abst += hashLit(lit);
+                last  = lit;
+                *j++  = last;
+            }
+        }
+        if (sorted) {
+            prod.lits[0].rep() = abst;
+            lits.erase(j, lits.end());
+            if (lits.empty()) {
+                lits.assign(1, lit_true);
+            }
+            return lits.size() < 2;
+        }
+        std::ranges::sort(lits);
+    }
 }
 void PBBuilder::addProductConstraints(Literal eqLit, LitVec& lits) {
-	Solver& s = *ctx()->master();
-	assert(s.value(eqLit.var()) == value_free);
-	bool ok   = ctx()->ok();
-	for (LitVec::iterator it = lits.begin(), end = lits.end(); it != end && ok; ++it) {
-		assert(s.value(it->var()) == value_free);
-		ok    = ctx()->addBinary(~eqLit, *it);
-		*it   = ~*it;
-	}
-	lits.push_back(eqLit);
-	if (ok) { ClauseCreator::create(s, lits, ClauseCreator::clause_no_prepare); }
+    Solver& s = *ctx()->master();
+    assert(s.value(eqLit.var()) == value_free);
+    bool ok = ctx()->ok();
+    for (auto it = lits.begin(), end = lits.end(); it != end && ok; ++it) {
+        assert(s.value(it->var()) == value_free);
+        ok  = ctx()->addBinary(~eqLit, *it);
+        *it = ~*it;
+    }
+    lits.push_back(eqLit);
+    if (ok) {
+        ClauseCreator::create(s, lits, ClauseCreator::clause_no_prepare);
+    }
 }
 
 bool PBBuilder::doStartProgram() {
-	auxVar_ = ctx()->numVars() + 1;
-	soft_   = std::numeric_limits::max();
-	assume_.clear();
-	return true;
+    auxVar_ = ctx()->numVars() + 1;
+    soft_   = std::numeric_limits::max();
+    assume_.clear();
+    return true;
 }
 bool PBBuilder::doEndProgram() {
-	while (auxVar_ != endVar_) {
-		if (!ctx()->addUnary(negLit(getAuxVar()))) { return false; }
-	}
-	markOutputVariables();
-	return true;
-}
-ProgramParser* PBBuilder::doCreateParser() {
-	return new SatParser(*this);
-}
+    while (auxVar_ != endVar_) {
+        if (not ctx()->addUnary(negLit(nextAuxVar()))) {
+            return false;
+        }
+    }
+    markOutputVariables();
+    return true;
+}
+ProgramParser* PBBuilder::doCreateParser() { return new SatParser(*this); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // class BasicProgramAdapter
 /////////////////////////////////////////////////////////////////////////////////////////
-BasicProgramAdapter::BasicProgramAdapter(ProgramBuilder& prg) : prg_(&prg), inc_(false) {
-	int t = prg_->type();
-	POTASSCO_REQUIRE(t == Problem_t::Sat || t == Problem_t::Pb, "unknown program type");
+BasicProgramAdapter::BasicProgramAdapter(ProgramBuilder& prg) : prg_(&prg), sat_(true), inc_(false) {
+    sat_ = dynamic_cast(prg_) != nullptr;
+    POTASSCO_CHECK_PRE(sat_ || dynamic_cast(prg_), "unsupported program type");
 }
 void BasicProgramAdapter::initProgram(bool inc) { inc_ = inc; }
-void BasicProgramAdapter::beginStep() { if (inc_ || prg_->frozen()) { prg_->updateProgram(); } }
-
-void BasicProgramAdapter::rule(Potassco::Head_t, const Potassco::AtomSpan& head, const Potassco::LitSpan& body) {
-	using namespace Potassco;
-	POTASSCO_REQUIRE(empty(head), "unsupported rule type");
-	if (prg_->type() == Problem_t::Sat) {
-		clause_.clear();
-		for (LitSpan::iterator it = begin(body), end = Potassco::end(body); it != end; ++it) { clause_.push_back(~toLit(*it)); }
-		static_cast(*prg_).addClause(clause_);
-	}
-	else {
-		constraint_.clear();
-		for (LitSpan::iterator it = begin(body), end = Potassco::end(body); it != end; ++it) { constraint_.push_back(WeightLiteral(~toLit(*it), 1)); }
-		static_cast(*prg_).addConstraint(constraint_, 1);
-	}
-}
-void BasicProgramAdapter::rule(Potassco::Head_t, const Potassco::AtomSpan& head, Potassco::Weight_t bound, const Potassco::WeightLitSpan& body) {
-	using namespace Potassco;
-	POTASSCO_REQUIRE(empty(head), "unsupported rule type");
-	constraint_.clear();
-	Potassco::Weight_t sum = 0;
-	for (WeightLitSpan::iterator it = begin(body), end = Potassco::end(body); it != end; ++it) {
-		constraint_.push_back(WeightLiteral(~toLit(it->lit), it->weight));
-		sum += it->weight;
-	}
-	if (prg_->type() == Problem_t::Sat) {
-		static_cast(*prg_).addConstraint(constraint_, (sum - bound) + 1);
-	}
-	else {
-		static_cast(*prg_).addConstraint(constraint_, (sum - bound) + 1);
-	}
-}
-void BasicProgramAdapter::minimize(Potassco::Weight_t prio, const Potassco::WeightLitSpan& lits) {
-	POTASSCO_REQUIRE(prio == 0, "unsupported rule type");
-	using namespace Potassco;
-	constraint_.clear();
-	for (WeightLitSpan::iterator it = begin(lits), end = Potassco::end(lits); it != end; ++it) { constraint_.push_back(WeightLiteral(toLit(it->lit), it->weight)); }
-	if (prg_->type() == Problem_t::Sat) {
-		static_cast(*prg_).addObjective(constraint_);
-	}
-	else {
-		static_cast(*prg_).addObjective(constraint_);
-	}
+void BasicProgramAdapter::beginStep() {
+    if (inc_ || prg_->frozen()) {
+        prg_->updateProgram();
+    }
 }
+
+template 
+void BasicProgramAdapter::withPrg(C&& call) const {
+    // NOLINTNEXTLINE(*-pro-type-static-cast-downcast)
+    sat_ ? call(static_cast(*prg_)) : call(static_cast(*prg_));
 }
+
+void BasicProgramAdapter::rule(Potassco::HeadType, Potassco::AtomSpan head, Potassco::LitSpan body) {
+    POTASSCO_CHECK_PRE(head.empty(), "unsupported rule type");
+    clause_.clear();
+    constraint_.clear();
+    withPrg([&](P& builder) {
+        if constexpr (std::is_same_v) {
+            for (auto lit : body) { clause_.push_back(~toLit(lit)); }
+            builder.addClause(clause_);
+        }
+        else {
+            for (auto lit : body) { constraint_.push_back(WeightLiteral{~toLit(lit), 1}); }
+            builder.addConstraint(constraint_, 1);
+        }
+    });
+}
+void BasicProgramAdapter::rule(Potassco::HeadType, Potassco::AtomSpan head, Potassco::Weight_t bound,
+                               Potassco::WeightLitSpan body) {
+    POTASSCO_CHECK_PRE(head.empty(), "unsupported rule type");
+    constraint_.clear();
+    Wsum_t newBound = -bound + 1;
+    for (const auto& [lit, weight] : body) {
+        constraint_.push_back(WeightLiteral{~toLit(lit), weight});
+        newBound += weight;
+    }
+    POTASSCO_CHECK(std::in_range(newBound), EOVERFLOW, "weight overflow");
+    withPrg([&](auto& builder) { builder.addConstraint(constraint_, static_cast(newBound)); });
+}
+void BasicProgramAdapter::minimize(Potassco::Weight_t prio, Potassco::WeightLitSpan lits) {
+    POTASSCO_CHECK_PRE(prio == 0, "unsupported rule type");
+    constraint_.clear();
+    for (const auto& [lit, weight] : lits) { constraint_.push_back(WeightLiteral{toLit(lit), weight}); }
+    withPrg([&](auto& builder) { builder.addObjective(constraint_); });
+}
+} // namespace Clasp
diff --git a/src/satelite.cpp b/src/satelite.cpp
index 9afd6cd..ce3bf8b 100644
--- a/src/satelite.cpp
+++ b/src/satelite.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,133 +22,131 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
 
+#include 
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // SatElite preprocessing
 //
 /////////////////////////////////////////////////////////////////////////////////////////
-SatElite::SatElite()
-	: occurs_(0)
-	, elimHeap_(LessOccCost(occurs_))
-	, qFront_(0)
-	, facts_(0) {
-}
+SatElite::SatElite() : occurs_(nullptr), elimHeap_(LessOccCost(occurs_)), facts_(0), timeout_{} {}
 
-SatElite::~SatElite() {
-	SatElite::doCleanUp();
-}
+SatElite::~SatElite() { SatElite::doCleanUp(); }
 
-void SatElite::reportProgress(Progress::EventOp id, uint32 curr, uint32 max) {
-	ctx_->report(Progress(this, id, curr, max));
+void SatElite::reportProgress(Progress::EventOp id, uint32_t curr, uint32_t max) {
+    ctx_->report(Progress(this, id, curr, max));
 }
 
-Clasp::SatPreprocessor* SatElite::clone() {
-	SatElite* cp = new SatElite();
-	return cp;
-}
+SatPreprocessor* SatElite::clone() { return new SatElite(); }
 
 void SatElite::doCleanUp() {
-	delete [] occurs_;  occurs_ = 0;
-	ClauseList().swap(resCands_);
-	VarVec().swap(occT_[pos]);
-	VarVec().swap(occT_[neg]);
-	LitVec().swap(resolvent_);
-	VarVec().swap(queue_);
-	elimHeap_.clear();
-	qFront_ = facts_ = 0;
+    occurs_.reset();
+    discardVec(resCands_);
+    discardVec(occT_[occ_pos]);
+    discardVec(occT_[occ_neg]);
+    discardVec(resolvent_);
+    queue_ = IdQueue();
+    elimHeap_.clear();
+    facts_ = 0;
 }
 
 SatPreprocessor::Clause* SatElite::popSubQueue() {
-	if (Clause* c = clause( queue_[qFront_++] )) {
-		c->setInQ(false);
-		return c;
-	}
-	return 0;
+    if (Clause* c = clause(queue_.pop_ret())) {
+        c->setInQ(false);
+        return c;
+    }
+    return nullptr;
 }
 
-void SatElite::addToSubQueue(uint32 clauseId) {
-	assert(clause(clauseId) != 0);
-	if (!clause(clauseId)->inQ()) {
-		queue_.push_back(clauseId);
-		clause(clauseId)->setInQ(true);
-	}
+void SatElite::addToSubQueue(uint32_t clauseId) {
+    POTASSCO_ASSERT(clause(clauseId) != nullptr);
+    if (not clause(clauseId)->inQ()) {
+        queue_.push(clauseId);
+        clause(clauseId)->setInQ(true);
+    }
 }
 
-void SatElite::attach(uint32 clauseId, bool initialClause) {
-	Clause& c = *clause(clauseId);
-	c.abstraction() = 0;
-	for (uint32 i = 0; i != c.size(); ++i) {
-		Var v = c[i].var();
-		occurs_[v].add(clauseId, c[i].sign());
-		occurs_[v].unmark();
-		c.abstraction() |= Clause::abstractLit(c[i]);
-		if (elimHeap_.is_in_queue(v)) {
-			elimHeap_.decrease(v);
-		}
-		else if (initialClause) {
-			updateHeap(v);
-		}
-	}
-	occurs_[c[0].var()].addWatch(clauseId);
-	addToSubQueue(clauseId);
-	stats.clAdded += !initialClause;
+void SatElite::attach(uint32_t clauseId, bool initialClause) {
+    Clause& c       = *clause(clauseId);
+    c.abstraction() = 0;
+    for (auto lit : c.lits()) {
+        auto v = lit.var();
+        occurs_[v].add(clauseId, lit.sign());
+        occurs_[v].unmark();
+        c.abstraction() |= Clause::abstractLit(lit);
+        if (elimHeap_.is_in_queue(v)) {
+            elimHeap_.decrease(v);
+        }
+        else if (initialClause) {
+            updateHeap(v);
+        }
+    }
+    occurs_[c[0].var()].addWatch(clauseId);
+    addToSubQueue(clauseId);
+    stats.clAdded += not initialClause;
 }
 
-void SatElite::detach(uint32 id) {
-	Clause& c = *clause(id);
-	occurs_[c[0].var()].removeWatch(id);
-	for (uint32 i = 0; i != c.size(); ++i) {
-		Var v = c[i].var();
-		occurs_[v].remove(id, c[i].sign(), false);
-		updateHeap(v);
-	}
-	destroyClause(id);
+void SatElite::detach(uint32_t id) {
+    Clause& c = *clause(id);
+    occurs_[c[0].var()].removeWatch(id);
+    for (auto lit : c.lits()) {
+        auto v = lit.var();
+        occurs_[v].remove(id, lit.sign(), false);
+        updateHeap(v);
+    }
+    destroyClause(id);
 }
 
-void SatElite::bceVeRemove(uint32 id, bool freeId, Var ev, bool blocked) {
-	Clause& c  = *clause(id);
-	occurs_[c[0].var()].removeWatch(id);
-	uint32 pos = 0;
-	for (uint32 i = 0; i != c.size(); ++i) {
-		Var v = c[i].var();
-		if (v != ev) {
-			occurs_[v].remove(id, c[i].sign(), freeId);
-			updateHeap(v);
-		}
-		else {
-			occurs_[ev].remove(id, c[i].sign(), false);
-			pos = i;
-		}
-	}
-	std::swap(c[0], c[pos]);
-	c.setMarked(blocked);
-	eliminateClause(id);
+void SatElite::bceVeRemove(uint32_t id, bool freeId, Var_t ev, bool blocked) {
+    Clause& c = *clause(id);
+    occurs_[c[0].var()].removeWatch(id);
+    uint32_t rp = 0, n = 0;
+    for (auto& lit : c.lits()) {
+        if (auto v = lit.var(); v != ev) {
+            occurs_[v].remove(id, lit.sign(), freeId);
+            updateHeap(v);
+        }
+        else {
+            occurs_[ev].remove(id, lit.sign(), false);
+            rp = n;
+        }
+        ++n;
+    }
+    std::swap(c[0], c[rp]);
+    c.setMarked(blocked);
+    eliminateClause(id);
 }
 
 bool SatElite::initPreprocess(Options& opts) {
-	reportProgress(Progress::event_algorithm, 0,100);
-	opts_     = &opts;
-	occurs_   = new OccurList[ctx_->numVars()+1];
-	qFront_   = 0;
-	occurs_[0].bce = (opts.type == Options::sat_pre_full);
-	return true;
+    reportProgress(Progress::event_algorithm, 0, 100);
+    opts_   = &opts;
+    occurs_ = std::make_unique(ctx_->numVars() + 1);
+    queue_.clear();
+    occurs_[0].bce = (opts.type == Options::sat_pre_full);
+    return true;
 }
 bool SatElite::doPreprocess() {
-	// 1. add clauses to occur lists
-	for (uint32 i  = 0, end = numClauses(); i != end; ++i) {
-		attach(i, true);
-	}
-	// 2. remove subsumed clauses, eliminate vars by clause distribution
-	timeout_ = opts_->limTime ? time(0) + opts_->limTime : std::numeric_limits::max();
-	for (uint32 i = 0, end = opts_->limIters ? opts_->limIters : UINT32_MAX; queue_.size()+elimHeap_.size() > 0; ++i) {
-		if (!backwardSubsume())   { return false; }
-		if (timeout() || i == end){ break;        }
-		if (!eliminateVars())     { return false; }
-	}
-	reportProgress(Progress::event_algorithm, 100,100);
-	return true;
+    // 1. add clauses to occur lists
+    for (uint32_t i : irange(numClauses())) { attach(i, true); }
+    // 2. remove subsumed clauses, eliminate vars by clause distribution
+    timeout_ = opts_->limTime ? time(nullptr) + opts_->limTime : std::numeric_limits::max();
+    for (uint32_t i = 0, end = opts_->limIters ? opts_->limIters : UINT32_MAX; queue_.size() + elimHeap_.size() > 0;
+         ++i) {
+        if (not backwardSubsume()) {
+            return false;
+        }
+        if (timeout() || i == end) {
+            break;
+        }
+        if (not eliminateVars()) {
+            return false;
+        }
+    }
+    reportProgress(Progress::event_algorithm, 100, 100);
+    return true;
 }
 
 // (Destructive) unit propagation on clauses.
@@ -156,120 +154,144 @@ bool SatElite::doPreprocess() {
 // Pre:   Assignment is propagated w.r.t other non-clause constraints
 // Post:  Assignment is fully propagated and no clause contains an assigned literal
 bool SatElite::propagateFacts() {
-	Solver* s = ctx_->master();
-	assert(s->queueSize() == 0);
-	while (facts_ != s->numAssignedVars()) {
-		Literal l     = s->trail()[facts_++];
-		OccurList& ov = occurs_[l.var()];
-		ClRange cls   = occurs_[l.var()].clauseRange();
-		for (ClIter x = cls.first; x != cls.second; ++x) {
-			if      (clause(x->var()) == 0)          { continue; }
-			else if (x->sign() == l.sign())          { detach(x->var()); }
-			else if (!strengthenClause(x->var(), ~l)){ return false; }
-		}
-		ov.clear();
-		ov.mark(!l.sign());
-	}
-	assert(s->queueSize() == 0);
-	return true;
+    Solver* s = ctx_->master();
+    assert(s->queueSize() == 0);
+    while (facts_ != s->numAssignedVars()) {
+        Literal    l  = s->trailLit(facts_++);
+        OccurList& ov = occurs_[l.var()];
+        for (auto x : ov.clauseRange()) {
+            if (clause(x.var())) {
+                if (x.sign() == l.sign()) {
+                    detach(x.var());
+                }
+                else if (not strengthenClause(x.var(), ~l)) {
+                    return false;
+                }
+            }
+        }
+        ov.clear();
+        ov.mark(not l.sign());
+    }
+    assert(s->queueSize() == 0);
+    return true;
 }
 
 // Backward subsumption and self-subsumption resolution until fixpoint
 bool SatElite::backwardSubsume() {
-	if (!propagateFacts()) return false;
-	while (qFront_ != queue_.size()) {
-		if ((qFront_ & 8191) == 0) {
-			if (timeout()) break;
-			if (queue_.size() > 1000) reportProgress(Progress::event_subsumption, qFront_, queue_.size());
-		}
-		if (peekSubQueue() == 0) { ++qFront_; continue; }
-		Clause& c = *popSubQueue();
-		// Try to minimize effort by testing against the var in c that occurs least often;
-		Literal best = c[0];
-		for (uint32 i = 1; i < c.size(); ++i) {
-			if (occurs_[c[i].var()].numOcc() < occurs_[best.var()].numOcc()) {
-				best  = c[i];
-			}
-		}
-		// Test against all clauses containing best
-		ClWList& cls = occurs_[best.var()].refs;
-		Literal res  = lit_false();
-		uint32  j    = 0;
-		// must use index access because cls might change!
-		for (uint32 i = 0, end = cls.left_size(); i != end; ++i) {
-			Literal cl     = cls.left(i);
-			uint32 otherId = cl.var();
-			Clause* other  = clause(otherId);
-			if (other && other!= &c && (res = subsumes(c, *other, best.sign()==cl.sign()?lit_true():best)) != lit_false()) {
-				if (res == lit_true()) {
-					// other is subsumed - remove it
-					detach(otherId);
-					other = 0;
-				}
-				else {
-					// self-subsumption resolution; other is subsumed by c\{res} U {~res}
-					// remove ~res from other, add it to subQ so that we can check if it now subsumes c
-					res = ~res;
-					occurs_[res.var()].remove(otherId, res.sign(), res.var() != best.var());
-					updateHeap(res.var());
-					if (!strengthenClause(otherId, res))              { return false; }
-					if (res.var() == best.var() || !clause(otherId))  { other = 0; }
-				}
-			}
-			if (other && j++ != i)  { cls.left(j-1) = cl; }
-		}
-		cls.shrink_left(cls.left_begin()+j);
-		occurs_[best.var()].dirty = 0;
-		assert(occurs_[best.var()].numOcc() == (uint32)cls.left_size());
-		if (!propagateFacts()) return false;
-	}
-	queue_.clear();
-	qFront_ = 0;
-	return true;
+    if (not propagateFacts()) {
+        return false;
+    }
+    while (not queue_.empty()) {
+        if (auto qf = toU32(queue_.qFront); (qf & 8191) == 0) {
+            if (timeout()) {
+                break;
+            }
+            if (auto max = size32(queue_.vec); max > 1000) {
+                reportProgress(Progress::event_subsumption, qf, max);
+            }
+        }
+        Clause* c = popSubQueue();
+        if (c == nullptr) {
+            continue;
+        }
+        // Try to minimize effort by testing against the var in c that occurs least often;
+        Literal best = *std::ranges::min_element(c->lits(), [&](Literal lhs, Literal rhs) {
+            return occurs_[lhs.var()].numOcc() < occurs_[rhs.var()].numOcc();
+        });
+        // Test against all clauses containing best
+        ClWList& cls = occurs_[best.var()].refs;
+        Literal  res;
+        uint32_t j = 0;
+        // must use index access because cls might change!
+        for (uint32_t i : irange(cls.left_size())) {
+            Literal  cl      = cls.left(i);
+            uint32_t otherId = cl.var();
+            Clause*  other   = clause(otherId);
+            if (other && other != c &&
+                (res = subsumes(*c, *other, best.sign() == cl.sign() ? lit_true : best)) != lit_false) {
+                if (res == lit_true) {
+                    // other is subsumed - remove it
+                    detach(otherId);
+                    other = nullptr;
+                }
+                else {
+                    // self-subsumption resolution; other is subsumed by c\{res} U {~res}
+                    // remove ~res from other, add it to subQ so that we can check if it now subsumes c
+                    res = ~res;
+                    occurs_[res.var()].remove(otherId, res.sign(), res.var() != best.var());
+                    updateHeap(res.var());
+                    if (not strengthenClause(otherId, res)) {
+                        return false;
+                    }
+                    if (res.var() == best.var() || not clause(otherId)) {
+                        other = nullptr;
+                    }
+                }
+            }
+            if (other && j++ != i) {
+                cls.left(j - 1) = cl;
+            }
+        }
+        cls.shrink_left(cls.left_begin() + j);
+        occurs_[best.var()].dirty = 0;
+        assert(occurs_[best.var()].numOcc() == toU32(cls.left_size()));
+        if (not propagateFacts()) {
+            return false;
+        }
+    }
+    queue_.clear();
+    return true;
 }
 
 // checks if 'c' subsumes 'other', and at the same time, if it can be used to
 // simplify 'other' by subsumption resolution.
 // Return:
-//  - lit_false() - No subsumption or simplification
-//  - lit_true() - 'c' subsumes 'other'
+//  - lit_false - No subsumption or simplification
+//  - lit_true - 'c' subsumes 'other'
 //  - l         - The literal l can be deleted from 'other'
 Literal SatElite::subsumes(const Clause& c, const Clause& other, Literal res) const {
-	if (other.size() < c.size() || (c.abstraction() & ~other.abstraction()) != 0) {
-		return lit_false();
-	}
-	if (c.size() < 10 || other.size() < 10) {
-		for (uint32 i = 0; i != c.size(); ++i) {
-			for (uint32 j = 0; j != other.size(); ++j) {
-				if (c[i].var() == other[j].var()) {
-					if (c[i].sign() == other[j].sign())     { goto found; }
-					else if (res != lit_true() && res!=c[i]) { return lit_false(); }
-					res = c[i];
-					goto found;
-				}
-			}
-			return lit_false();
-		found:;
-		}
-	}
-	else {
-		markAll(&other[0], other.size());
-		for (uint32 i = 0; i != c.size(); ++i) {
-			if (occurs_[c[i].var()].litMark == 0) { res = lit_false(); break; }
-			if (occurs_[c[i].var()].marked(!c[i].sign())) {
-				if (res != lit_true()&&res!=c[i]) { res = lit_false(); break; }
-				res = c[i];
-			}
-		}
-		unmarkAll(&other[0], other.size());
-	}
-	return res;
+    if (other.size() < c.size() || (c.abstraction() & ~other.abstraction()) != 0) {
+        return lit_false;
+    }
+    auto otherLits = other.lits();
+    if (c.size() < 10 || other.size() < 10) {
+        for (auto lhs : c.lits()) {
+            auto oIt = std::ranges::find_if(otherLits, [v = lhs.var()](Literal x) { return x.var() == v; });
+            if (oIt != otherLits.end()) {
+                if (lhs.sign() == oIt->sign()) {
+                    continue;
+                }
+                if (res == lit_true || res == lhs) {
+                    res = lhs;
+                    continue;
+                }
+            }
+            return lit_false;
+        }
+    }
+    else {
+        markAll(otherLits);
+        for (auto lit : c.lits()) {
+            if (occurs_[lit.var()].litMark == 0) {
+                res = lit_false;
+                break;
+            }
+            if (occurs_[lit.var()].marked(not lit.sign())) {
+                if (res != lit_true && res != lit) {
+                    res = lit_false;
+                    break;
+                }
+                res = lit;
+            }
+        }
+        unmarkAll(otherLits);
+    }
+    return res;
 }
 
-uint32 SatElite::findUnmarkedLit(const Clause& c, uint32 x) const {
-	for (; x != c.size() && occurs_[c[x].var()].marked(c[x].sign()); ++x)
-		;
-	return x;
+uint32_t SatElite::findUnmarkedLit(const Clause& c, uint32_t x) const {
+    while (x != c.size() && occurs_[c[x].var()].marked(c[x].sign())) { ++x; }
+    return x;
 }
 
 // checks if 'cl' is subsumed by one of the existing clauses and at the same time
@@ -280,343 +302,362 @@ uint32 SatElite::findUnmarkedLit(const Clause& c, uint32 x) const {
 // Pre: All literals of l are marked, i.e.
 // for each literal l in cl, occurs_[l.var()].marked(l.sign()) == true
 bool SatElite::subsumed(LitVec& cl) {
-	Literal l;
-	uint32 x = 0;
-	uint32 str = 0;
-	LitVec::size_type j = 0;
-	for (LitVec::size_type i = 0; i != cl.size(); ++i) {
-		l = cl[i];
-		if (occurs_[l.var()].litMark == 0) { --str; continue; }
-		ClWList& cls   = occurs_[l.var()].refs; // right: all clauses watching either l or ~l
-		WIter wj       = cls.right_begin();
-		for (WIter w = wj, end = cls.right_end(); w != end; ++w) {
-			Clause& c = *clause(*w);
-			if (c[0] == l)  {
-				if ( (x = findUnmarkedLit(c, 1)) == c.size() ) {
-					while (w != end) { *wj++ = *w++; }
-					cls.shrink_right( wj );
-					return true;
-				}
-				c[0] = c[x];
-				c[x] = l;
-				occurs_[c[0].var()].addWatch(*w);
-				if (occurs_[c[0].var()].litMark != 0 && findUnmarkedLit(c, x+1) == c.size()) {
-					occurs_[c[0].var()].unmark();  // no longer part of cl
-					++str;
-				}
-			}
-			else if ( findUnmarkedLit(c, 1) == c.size() ) {
-				occurs_[l.var()].unmark(); // no longer part of cl
-				while (w != end) { *wj++ = *w++; }
-				cls.shrink_right( wj );
-				goto removeLit;
-			}
-			else { *wj++ = *w; }
-		}
-		cls.shrink_right(wj);
-		if (j++ != i) { cl[j-1] = cl[i]; }
-removeLit:;
-	}
-	cl.erase(cl.begin()+j, cl.end());
-	if (str > 0) {
-		for (LitVec::size_type i = 0; i != cl.size();) {
-			if (occurs_[cl[i].var()].litMark == 0) {
-				cl[i] = cl.back();
-				cl.pop_back();
-				if (--str == 0) break;
-			}
-			else { ++i; }
-		}
-	}
-	return false;
+    auto str = 0u;
+    auto j   = 0u;
+    for (auto i : irange(cl)) {
+        Literal l = cl[i];
+        if (occurs_[l.var()].litMark == 0) {
+            --str;
+            continue;
+        }
+        ClWList& cls = occurs_[l.var()].refs; // right: all clauses watching either l or ~l
+        WIter    wj  = cls.right_begin();
+        for (WIter w = wj, end = cls.right_end(); w != end; ++w) {
+            Clause& c = *clause(*w);
+            if (c[0] == l) {
+                auto x = findUnmarkedLit(c, 1);
+                if (x == c.size()) {
+                    while (w != end) { *wj++ = *w++; }
+                    cls.shrink_right(wj);
+                    return true;
+                }
+                c[0] = c[x];
+                c[x] = l;
+                occurs_[c[0].var()].addWatch(*w);
+                if (occurs_[c[0].var()].litMark != 0 && findUnmarkedLit(c, x + 1) == c.size()) {
+                    occurs_[c[0].var()].unmark(); // no longer part of cl
+                    ++str;
+                }
+            }
+            else if (findUnmarkedLit(c, 1) == c.size()) {
+                occurs_[l.var()].unmark(); // no longer part of cl
+                while (w != end) { *wj++ = *w++; }
+                cls.shrink_right(wj);
+                goto removeLit;
+            }
+            else {
+                *wj++ = *w;
+            }
+        }
+        cls.shrink_right(wj);
+        if (j++ != i) {
+            cl[j - 1] = cl[i];
+        }
+    removeLit:;
+    }
+    shrinkVecTo(cl, j);
+    if (str > 0) {
+        auto it = cl.begin();
+        do {
+            it = std::find_if(it, cl.end(), [&](Literal x) { return occurs_[x.var()].litMark == 0; });
+            POTASSCO_ASSERT(it != cl.end());
+            *it = cl.back();
+            cl.pop_back();
+        } while (--str);
+    }
+    return false;
 }
 
 // Pre: c contains l
 // Pre: c was already removed from l's occur-list
-bool SatElite::strengthenClause(uint32 clauseId, Literal l) {
-	Clause& c = *clause(clauseId);
-	if (c[0] == l) {
-		occurs_[c[0].var()].removeWatch(clauseId);
-		// Note: Clause::strengthen shifts literals after l to the left. Thus,
-		// c[1] will be c[0] after strengthen
-		occurs_[c[1].var()].addWatch(clauseId);
-	}
-	++stats.litsRemoved;
-	c.strengthen(l);
-	if (c.size() == 1) {
-		Literal unit = c[0];
-		detach(clauseId);
-		return ctx_->addUnary(unit) && ctx_->master()->propagate();
-	}
-	addToSubQueue(clauseId);
-	return true;
+bool SatElite::strengthenClause(uint32_t clauseId, Literal l) {
+    Clause& c = *clause(clauseId);
+    if (c[0] == l) {
+        occurs_[c[0].var()].removeWatch(clauseId);
+        // Note: Clause::strengthen shifts literals after l to the left. Thus,
+        // c[1] will be c[0] after strengthen
+        occurs_[c[1].var()].addWatch(clauseId);
+    }
+    ++stats.litsRemoved;
+    c.strengthen(l);
+    if (c.size() == 1) {
+        Literal unit = c[0];
+        detach(clauseId);
+        return ctx_->addUnary(unit) && ctx_->master()->propagate();
+    }
+    addToSubQueue(clauseId);
+    return true;
 }
 
 // Split occurrences of v into pos and neg and
 // mark all clauses containing v
-SatElite::ClRange SatElite::splitOcc(Var v, bool mark) {
-	ClRange cls      = occurs_[v].clauseRange();
-	occurs_[v].dirty = 0;
-	occT_[pos].clear(); occT_[neg].clear();
-	ClIter j = cls.first;
-	for (ClIter x = j; x != cls.second; ++x) {
-		if (Clause* c = clause(x->var())) {
-			assert(c->marked() == false);
-			c->setMarked(mark);
-			int sign = (int)x->sign();
-			occT_[sign].push_back(x->var());
-			if (j != x) *j = *x;
-			++j;
-		}
-	}
-	occurs_[v].refs.shrink_left(j);
-	return occurs_[v].clauseRange();
+SatElite::ClRange SatElite::splitOcc(Var_t v, bool mark) {
+    ClRange cls      = occurs_[v].clauseRange();
+    occurs_[v].dirty = 0;
+    occT_[occ_pos].clear();
+    occT_[occ_neg].clear();
+    ClIter j = cls.data();
+    for (auto x : cls) {
+        if (Clause* c = clause(x.var())) {
+            assert(c->marked() == false);
+            c->setMarked(mark);
+            int sign = x.sign();
+            occT_[sign].push_back(x.var());
+            *j++ = x;
+        }
+    }
+    occurs_[v].refs.shrink_left(j);
+    return occurs_[v].clauseRange();
 }
 
-void SatElite::markAll(const Literal* lits, uint32 size) const {
-	for (uint32 i = 0; i != size; ++i) {
-		occurs_[lits[i].var()].mark(lits[i].sign());
-	}
+void SatElite::markAll(LitView lits) const {
+    for (auto lit : lits) { occurs_[lit.var()].mark(lit.sign()); }
 }
-void SatElite::unmarkAll(const Literal* lits, uint32 size) const {
-	for (uint32 i = 0; i != size; ++i) {
-		occurs_[lits[i].var()].unmark();
-	}
+void SatElite::unmarkAll(LitView lits) const {
+    for (auto lit : lits) { occurs_[lit.var()].unmark(); }
 }
 
 // Run variable and/or blocked clause elimination on var v.
 // If the number of non-trivial resolvents is <= maxCnt,
 // v is eliminated by clause distribution. If bce is enabled,
 // clauses blocked on a literal of v are removed.
-bool SatElite::bceVe(Var v, uint32 maxCnt) {
-	Solver* s = ctx_->master();
-	if (s->value(v) != value_free) return true;
-	assert(!ctx_->varInfo(v).frozen() && !ctx_->eliminated(v));
-	resCands_.clear();
-	// distribute clauses on v
-	// check if number of clauses decreases if we'd eliminate v
-	uint32 bce     = opts_->bce();
-	ClRange cls    = splitOcc(v, bce > 1);
-	uint32 cnt     = 0;
-	uint32 markMax = ((uint32)occT_[neg].size() * (bce>1));
-	uint32 blocked = 0;
-	bool stop      = false;
-	Clause* lhs, *rhs;
-	for (VarVec::const_iterator i = occT_[pos].begin(); i != occT_[pos].end() && !stop; ++i) {
-		lhs         = clause(*i);
-		markAll(&(*lhs)[0], lhs->size());
-		lhs->setMarked(bce != 0);
-		for (VarVec::const_iterator j = occT_[neg].begin(); j != occT_[neg].end(); ++j) {
-			if (!trivialResolvent(*(rhs = clause(*j)), v)) {
-				markMax -= rhs->marked();
-				rhs->setMarked(false); // not blocked on v
-				lhs->setMarked(false); // not blocked on v
-				if (++cnt <= maxCnt) {
-					resCands_.push_back(lhs);
-					resCands_.push_back(rhs);
-				}
-				else if (!markMax) {
-					stop = (bce == 0);
-					break;
-				}
-			}
-		}
-		unmarkAll(&(*lhs)[0], lhs->size());
-		if (lhs->marked()) {
-			occT_[pos][blocked++] = *i;
-		}
-	}
-	if (cnt <= maxCnt) {
-		// eliminate v by clause distribution
-		ctx_->eliminate(v);  // mark var as eliminated
-		// remove old clauses, store them in the elimination table so that
-		// (partial) models can be extended.
-		for (ClIter it = cls.first; it != cls.second; ++it) {
-			// reuse first cnt ids for resolvents
-			if (clause(it->var())) {
-				bool freeId = (cnt && cnt--);
-				bceVeRemove(it->var(), freeId, v, false);
-			}
-		}
-		// add non trivial resolvents
-		assert( resCands_.size() % 2 == 0 );
-		ClIter it = cls.first;
-		for (VarVec::size_type i = 0; i != resCands_.size(); i+=2, ++it) {
-			if (!addResolvent(it->var(), *resCands_[i], *resCands_[i+1])) {
-				return false;
-			}
-		}
-		assert(occurs_[v].numOcc() == 0);
-		// release memory
-		occurs_[v].clear();
-	}
-	else if ( (blocked + markMax) > 0 ) {
-		// remove blocked clauses
-		for (uint32 i = 0; i != blocked; ++i) {
-			bceVeRemove(occT_[pos][i], false, v, true);
-		}
-		for (VarVec::const_iterator it = occT_[neg].begin(); markMax; ++it) {
-			if ( (rhs = clause(*it))->marked() ) {
-				bceVeRemove(*it, false, v, true);
-				--markMax;
-			}
-		}
-	}
-	return opts_->limIters != 0 || backwardSubsume();
+bool SatElite::bceVe(Var_t v, uint32_t maxCnt) {
+    Solver* s = ctx_->master();
+    if (s->value(v) != value_free) {
+        return true;
+    }
+    assert(not ctx_->varInfo(v).frozen() && not ctx_->eliminated(v));
+    resCands_.clear();
+    // distribute clauses on v
+    // check if number of clauses decreases if we'd eliminate v
+    uint32_t bce     = opts_->bce();
+    ClRange  cls     = splitOcc(v, bce > 1);
+    uint32_t cnt     = 0;
+    uint32_t markMax = size32(occT_[occ_neg]) * (bce > 1);
+    uint32_t blocked = 0;
+    bool     stop    = false;
+    for (auto pId : occT_[occ_pos]) {
+        auto* lhs = clause(pId);
+        markAll(lhs->lits());
+        lhs->setMarked(bce != 0);
+        for (auto nId : occT_[occ_neg]) {
+            if (auto* rhs = clause(nId); not trivialResolvent(*rhs, v)) {
+                markMax -= rhs->marked();
+                rhs->setMarked(false); // not blocked on v
+                lhs->setMarked(false); // not blocked on v
+                if (++cnt <= maxCnt) {
+                    resCands_.push_back(lhs);
+                    resCands_.push_back(rhs);
+                }
+                else if (not markMax) {
+                    stop = (bce == 0);
+                    break;
+                }
+            }
+        }
+        unmarkAll(lhs->lits());
+        if (lhs->marked()) {
+            occT_[occ_pos][blocked++] = pId;
+        }
+        if (stop) {
+            break;
+        }
+    }
+    if (cnt <= maxCnt) {
+        // eliminate v by clause distribution
+        ctx_->eliminate(v); // mark var as eliminated
+        // remove old clauses, store them in the elimination table so that
+        // (partial) models can be extended.
+        for (auto x : cls) {
+            // reuse first cnt ids for resolvents
+            if (clause(x.var())) {
+                bool freeId = (cnt && cnt--);
+                bceVeRemove(x.var(), freeId, v, false);
+            }
+        }
+        // add non trivial resolvents
+        assert(resCands_.size() % 2 == 0);
+        auto it = cls.begin();
+        for (auto i = 0u; i != size32(resCands_); i += 2, ++it) {
+            if (not addResolvent(it->var(), *resCands_[i], *resCands_[i + 1])) {
+                return false;
+            }
+        }
+        assert(occurs_[v].numOcc() == 0);
+        // release memory
+        occurs_[v].clear();
+    }
+    else if ((blocked + markMax) > 0) {
+        // remove blocked clauses
+        for (auto id : std::span(occT_[occ_pos].data(), blocked)) { bceVeRemove(id, false, v, true); }
+        for (auto it = occT_[occ_neg].data(); markMax; ++it) {
+            if (clause(*it)->marked()) {
+                bceVeRemove(*it, false, v, true);
+                --markMax;
+            }
+        }
+    }
+    return opts_->limIters != 0 || backwardSubsume();
 }
 
 bool SatElite::bce() {
-	uint32 ops = 0;
-	for (ClWList& bce= occurs_[0].refs; bce.right_size() != 0; ++ops) {
-		Var v          = *(bce.right_end()-1);
-		bce.pop_right();
-		occurs_[v].bce=0;
-		if ((ops & 1023) == 0)   {
-			if (timeout())         { bce.clear(); return true; }
-			if ((ops & 8191) == 0) { reportProgress(Progress::event_bce, ops, 1+bce.size()); }
-		}
-		if (!cutoff(v) && !bceVe(v, 0)) { return false; }
-	}
-	return true;
+    uint32_t ops = 0;
+    for (ClWList& bce = occurs_[0].refs; bce.right_size() != 0; ++ops) {
+        Var_t v = *(bce.right_end() - 1);
+        bce.pop_right();
+        occurs_[v].bce = 0;
+        if ((ops & 1023) == 0) {
+            if (timeout()) {
+                bce.clear();
+                return true;
+            }
+            if ((ops & 8191) == 0) {
+                reportProgress(Progress::event_bce, ops, 1 + bce.size());
+            }
+        }
+        if (not cutoff(v) && not bceVe(v, 0)) {
+            return false;
+        }
+    }
+    return true;
 }
 
 bool SatElite::eliminateVars() {
-	Var     v          = 0;
-	uint32  occ        = 0;
-	if (!bce()) return false;
-	for (uint32 ops = 0; !elimHeap_.empty(); ++ops) {
-		v   = elimHeap_.top();  elimHeap_.pop();
-		occ = occurs_[v].numOcc();
-		if ((ops & 1023) == 0)   {
-			if (timeout())         { elimHeap_.clear(); return true; }
-			if ((ops & 8191) == 0) { reportProgress(Progress::event_var_elim, ops, 1+elimHeap_.size()); }
-		}
-		if (!cutoff(v) && !bceVe(v, occ)) {
-			return false;
-		}
-	}
-	return opts_->limIters != 0 || bce();
+    if (not bce()) {
+        return false;
+    }
+    for (uint32_t ops = 0; not elimHeap_.empty(); ++ops) {
+        auto v = elimHeap_.top();
+        elimHeap_.pop();
+        auto occ = occurs_[v].numOcc();
+        if ((ops & 1023) == 0) {
+            if (timeout()) {
+                elimHeap_.clear();
+                return true;
+            }
+            if ((ops & 8191) == 0) {
+                reportProgress(Progress::event_var_elim, ops, 1 + size32(elimHeap_));
+            }
+        }
+        if (not cutoff(v) && not bceVe(v, occ)) {
+            return false;
+        }
+    }
+    return opts_->limIters != 0 || bce();
 }
 
 // returns true if the result of resolving c1 (implicitly given) and c2 on v yields a tautologous clause
-bool SatElite::trivialResolvent(const Clause& c2, Var v) const {
-	for (uint32 i = 0, end = c2.size(); i != end; ++i) {
-		Literal x = c2[i];
-		if (occurs_[x.var()].marked(!x.sign()) && x.var() != v) {
-			return true;
-		}
-	}
-	return false;
+bool SatElite::trivialResolvent(const Clause& c2, Var_t v) const {
+    return std::ranges::any_of(c2.lits(),
+                               [&](Literal x) { return occurs_[x.var()].marked(not x.sign()) && x.var() != v; });
 }
 
 // Pre: lhs and rhs can be resolved on lhs[0].var()
 // Pre: trivialResolvent(lhs, rhs, lhs[0].var()) == false
-bool SatElite::addResolvent(uint32 id, const Clause& lhs, const Clause& rhs) {
-	resolvent_.clear();
-	Solver* s = ctx_->master();
-	assert(lhs[0] == ~rhs[0]);
-	uint32 i, end;
-	Literal l;
-	for (i = 1, end = lhs.size(); i != end; ++i) {
-		l = lhs[i];
-		if (!s->isFalse(l)) {
-			if (s->isTrue(l)) goto unmark;
-			occurs_[l.var()].mark(l.sign());
-			resolvent_.push_back(l);
-		}
-	}
-	for (i = 1, end = rhs.size(); i != end; ++i) {
-		l = rhs[i];
-		if (!s->isFalse(l) && !occurs_[l.var()].marked(l.sign())) {
-			if (s->isTrue(l)) goto unmark;
-			occurs_[l.var()].mark(l.sign());
-			resolvent_.push_back(l);
-		}
-	}
-	if (!subsumed(resolvent_))  {
-		if (resolvent_.empty())   {
-			return s->force(negLit(0));
-		}
-		if (resolvent_.size()==1) {
-			occurs_[resolvent_[0].var()].unmark();
-			return s->force(resolvent_[0]) && s->propagate() && propagateFacts();
-		}
-		setClause(id, resolvent_);
-		attach(id, false);
-		return true;
-	}
+bool SatElite::addResolvent(uint32_t id, const Clause& lhs, const Clause& rhs) {
+    resolvent_.clear();
+    Solver* s = ctx_->master();
+    assert(lhs[0] == ~rhs[0]);
+    uint32_t i, end;
+    Literal  l;
+    for (i = 1, end = lhs.size(); i != end; ++i) {
+        l = lhs[i];
+        if (not s->isFalse(l)) {
+            if (s->isTrue(l)) {
+                goto unmark;
+            }
+            occurs_[l.var()].mark(l.sign());
+            resolvent_.push_back(l);
+        }
+    }
+    for (i = 1, end = rhs.size(); i != end; ++i) {
+        l = rhs[i];
+        if (not s->isFalse(l) && not occurs_[l.var()].marked(l.sign())) {
+            if (s->isTrue(l)) {
+                goto unmark;
+            }
+            occurs_[l.var()].mark(l.sign());
+            resolvent_.push_back(l);
+        }
+    }
+    if (not subsumed(resolvent_)) {
+        if (resolvent_.empty()) {
+            return s->force(negLit(0));
+        }
+        if (resolvent_.size() == 1) {
+            occurs_[resolvent_[0].var()].unmark();
+            return s->force(resolvent_[0]) && s->propagate() && propagateFacts();
+        }
+        setClause(id, resolvent_);
+        attach(id, false);
+        return true;
+    }
 unmark:
-	if (!resolvent_.empty()) {
-		unmarkAll(&resolvent_[0], resolvent_.size());
-	}
-	return true;
+    if (not resolvent_.empty()) {
+        unmarkAll(resolvent_);
+    }
+    return true;
 }
 
 // extends the model given in assign by the vars that were eliminated
 void SatElite::doExtendModel(ValueVec& m, LitVec& unconstr) {
-	if (!elimTop_) return;
-	const ValueRep value_eliminated = 4u;
-	// compute values of eliminated vars / blocked literals by "unit propagating"
-	// eliminated/blocked clauses in reverse order
-	uint32 uv = 0;
-	uint32 us = unconstr.size();
-	Clause* r = elimTop_;
-	do {
-		Literal x  = (*r)[0];
-		Var last   = x.var();
-		bool check = true;
-		if (!r->marked()) {
-			// eliminated var - compute the implied value
-			m[last] = value_eliminated;
-		}
-		if (uv != us && unconstr[uv].var() == last) {
-			// last is unconstrained w.r.t the current model -
-			// set remembered value
-			check   = false;
-			m[last] = trueValue(unconstr[uv]);
-			++uv;
-		}
-		do {
-			Clause& c = *r;
-			if (m[x.var()] != trueValue(x) && check) {
-				for (uint32 i = 1, end = c.size(); i != end; ++i) {
-					ValueRep vi = m[c[i].var()] & 3u;
-					if (vi != falseValue(c[i])) {
-						x = c[i];
-						break;
-					}
-				}
-				if (x == c[0]) {
-					// all lits != x are false
-					// clause is unit or conflicting
-					assert(c.marked() || m[x.var()] != falseValue(x));
-					m[x.var()] = trueValue(x);
-					check      = false;
-				}
-			}
-			r = r->next();
-		} while (r && (x = (*r)[0]).var() == last);
-		if (m[last] == value_eliminated) {
-			// last seems unconstrained w.r.t the model
-			m[last] |= value_true;
-			unconstr.push_back(posLit(last));
-		}
-	} while (r);
-	// check whether newly added unconstrained vars are really unconstrained w.r.t the model
-	// or if they are implied by some blocked clause.
-	LitVec::iterator j = unconstr.begin()+us;
-	for (LitVec::iterator it = j, end = unconstr.end(); it != end; ++it) {
-		if ((m[it->var()] & value_eliminated) != 0) {
-			// var is unconstrained - assign to true and remember it
-			// so that we can later enumerate the model containing ~var
-			m[it->var()] = value_true;
-			*j++ = *it;
-		}
-	}
-	unconstr.erase(j, unconstr.end());
+    if (not elimTop_) {
+        return;
+    }
+    constexpr auto value_eliminated = static_cast(4u);
+    // compute values of eliminated vars / blocked literals by "unit propagating"
+    // eliminated/blocked clauses in reverse order
+    uint32_t uv = 0;
+    uint32_t us = size32(unconstr);
+    Clause*  r  = elimTop_;
+    do {
+        Literal x     = (*r)[0];
+        auto    last  = x.var();
+        bool    check = true;
+        if (not r->marked()) {
+            // eliminated var - compute the implied value
+            m[last] = value_eliminated;
+        }
+        if (uv != us && unconstr[uv].var() == last) {
+            // last is unconstrained w.r.t the current model -
+            // set remembered value
+            check   = false;
+            m[last] = trueValue(unconstr[uv]);
+            ++uv;
+        }
+        do {
+            Clause& c = *r;
+            if (m[x.var()] != trueValue(x) && check) {
+                for (uint32_t i = 1, end = c.size(); i != end; ++i) {
+                    auto vi = static_cast(m[c[i].var()] & 3u);
+                    if (vi != falseValue(c[i])) {
+                        x = c[i];
+                        break;
+                    }
+                }
+                if (x == c[0]) {
+                    // all lits != x are false
+                    // clause is unit or conflicting
+                    assert(c.marked() || m[x.var()] != falseValue(x));
+                    m[x.var()] = trueValue(x);
+                    check      = false;
+                }
+            }
+            r = r->next();
+        } while (r && (x = (*r)[0]).var() == last);
+        if (m[last] == value_eliminated) {
+            // last seems unconstrained w.r.t the model
+            m[last] |= value_true;
+            unconstr.push_back(posLit(last));
+        }
+    } while (r);
+    // check whether newly added unconstrained vars are really unconstrained w.r.t the model
+    // or if they are implied by some blocked clause.
+    auto j = unconstr.begin() + us;
+    for (auto x : drop(unconstr, us)) {
+        if (Potassco::test_any(m[x.var()], value_eliminated)) {
+            // var is unconstrained - assign to true and remember it
+            // so that we can later enumerate the model containing ~var
+            m[x.var()] = value_true;
+            *j++       = x;
+        }
+    }
+    unconstr.erase(j, unconstr.end());
 }
 SatPreprocessor* SatPreParams::create(const SatPreParams& opts) {
-	if (opts.type != 0) { return new SatElite(); }
-	return 0;
-}
+    if (opts.type != 0) {
+        return new SatElite();
+    }
+    return nullptr;
 }
+} // namespace Clasp
diff --git a/src/shared_context.cpp b/src/shared_context.cpp
index a22e5ae..a4421dc 100644
--- a/src/shared_context.cpp
+++ b/src/shared_context.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2010-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,39 +22,48 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
-#include 
-#include 
 #include 
-#include 
+#include 
+#include 
+#include 
 #if CLASP_HAS_THREADS
 #include 
 #endif
+
+#include 
+
+#include 
+
 namespace Clasp {
-#define PS_STATS( APPLY ) \
-	APPLY(vars               , VALUE(vars.num))            \
-	APPLY(vars_eliminated    , VALUE(vars.eliminated))     \
-	APPLY(vars_frozen        , VALUE(vars.frozen))         \
-	APPLY(constraints        , VALUE(constraints.other))   \
-	APPLY(constraints_binary , VALUE(constraints.binary))  \
-	APPLY(constraints_ternary, VALUE(constraints.ternary)) \
-	APPLY(acyc_edges         , VALUE(acycEdges))           \
-	APPLY(complexity         , VALUE(complexity))
+#define PS_STATS(APPLY)                                                                                                \
+    APPLY(vars, VALUE(vars.num))                                                                                       \
+    APPLY(vars_eliminated, VALUE(vars.eliminated))                                                                     \
+    APPLY(vars_frozen, VALUE(vars.frozen))                                                                             \
+    APPLY(constraints, VALUE(constraints.other))                                                                       \
+    APPLY(constraints_binary, VALUE(constraints.binary))                                                               \
+    APPLY(constraints_ternary, VALUE(constraints.ternary))                                                             \
+    APPLY(acyc_edges, VALUE(acycEdges))                                                                                \
+    APPLY(complexity, VALUE(complexity))
 
-static const char* const stats_s[] = {
-#define KEY(X,Y) #X,
-	PS_STATS(KEY)
+static constexpr const char* const stats_s[] = {
+#define KEY(X, Y) #X,
+    PS_STATS(KEY)
 #undef KEY
-	"ctx"
-};
-uint32 ProblemStats::size()             { return (sizeof(stats_s)/sizeof(stats_s[0])) - 1; }
-const char* ProblemStats::key(uint32 i) { POTASSCO_CHECK(i < size(), ERANGE); return stats_s[i]; }
+        "ctx"};
+uint32_t    ProblemStats::size() { return toU32(std::size(stats_s)) - 1; }
+const char* ProblemStats::key(uint32_t i) {
+    POTASSCO_CHECK(i < size(), ERANGE);
+    return stats_s[i];
+}
 StatisticObject ProblemStats::at(const char* key) const {
-#define VALUE(X) StatisticObject::value(&X)
-#define APPLY(x, y) if (std::strcmp(key, #x) == 0) return y;
-	PS_STATS(APPLY)
-	POTASSCO_CHECK(false, ERANGE);
+#define VALUE(X) StatisticObject::value(&(X))
+#define APPLY(x, y)                                                                                                    \
+    if (std::strcmp(key, #x) == 0)                                                                                     \
+        return y;
+    PS_STATS(APPLY)
+    POTASSCO_CHECK(false, ERANGE);
 #undef VALUE
 #undef APPLY
 }
@@ -62,209 +71,199 @@ StatisticObject ProblemStats::at(const char* key) const {
 /////////////////////////////////////////////////////////////////////////////////////////
 // EventHandler
 /////////////////////////////////////////////////////////////////////////////////////////
-uint32 Event::nextId() { static uint32 id_s = 0; return id_s++; }
-EventHandler::EventHandler(Event::Verbosity verbosity) : verb_(0), sys_(0){
-	if (uint32 x = verbosity) {
-		uint32 r = (x | (x<<4) | (x<<8) | (x<<12));
-		verb_ = static_cast(r);
-	}
-}
-EventHandler::~EventHandler() {}
+uint32_t Event::nextId() {
+    static uint32_t id_s = 0;
+    return id_s++;
+}
+EventHandler::EventHandler(Event::Verbosity verbosity) : verb_(0), sys_(0) {
+    if (uint32_t x = verbosity) {
+        uint32_t r = (x | (x << 4) | (x << 8) | (x << 12));
+        verb_      = static_cast(r);
+    }
+}
 void EventHandler::setVerbosity(Event::Subsystem sys, Event::Verbosity verb) {
-	uint32 s = (uint32(sys)<(r);
+    uint32_t s = static_cast(sys) << verb_shift;
+    uint32_t r = verb_;
+    Potassco::store_clear_mask(r, verb_mask << s);
+    Potassco::store_set_mask(r, static_cast(verb) << s);
+    verb_ = static_cast(r);
 }
 bool EventHandler::setActive(Event::Subsystem sys) {
-	if (sys == static_cast(sys_)) { return false; }
-	sys_ = static_cast(sys);
-	return true;
-}
-Event::Subsystem EventHandler::active() const {
-	return static_cast(sys_);
+    if (sys == static_cast(sys_)) {
+        return false;
+    }
+    sys_ = static_cast(sys);
+    return true;
 }
+Event::Subsystem EventHandler::active() const { return static_cast(sys_); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ShortImplicationsGraph::ImplicationList
 /////////////////////////////////////////////////////////////////////////////////////////
 #if CLASP_HAS_THREADS
-ShortImplicationsGraph::Block::Block() {
-	for (int i = 0; i != block_cap; ++i) { data[i] = lit_true(); }
-	size_lock = 0;
-	next      = 0;
-}
-void ShortImplicationsGraph::Block::addUnlock(uint32 lockedSize, const Literal* x, uint32 xs) {
-	std::copy(x, x+xs, data+lockedSize);
-	size_lock = ((lockedSize+xs) << 1);
-}
-bool ShortImplicationsGraph::Block::tryLock(uint32& size) {
-	uint32 s = size_lock.fetch_or(1);
-	if ((s & 1) == 0) {
-		size = s >> 1;
-		return true;
-	}
-	return false;
-}
-
-#define FOR_EACH_LEARNT(x, Y) \
-	for (Block* b = (x).learnt; b ; b = b->next) \
-		for (const Literal* Y = b->begin(), *endof = b->end(); Y != endof; Y += 2 - Y->flagged())
-
-
-ShortImplicationsGraph::ImplicationList::~ImplicationList() {
-	clear(true);
+ShortImplicationsGraph::Block::Block(Block* n, const Literal* x, uint32_t xs) : next_(n), sizeLock_(xs << size_shift) {
+    std::copy_n(x, xs, data_);
+}
+bool ShortImplicationsGraph::Block::tryLock(uint32_t& size) {
+    if (uint32_t s = sizeLock_.fetch_or(lock_mask, std::memory_order_acquire); not Potassco::test_mask(s, lock_mask)) {
+        size = s >> size_shift;
+        return true;
+    }
+    return false;
+}
+bool ShortImplicationsGraph::Block::addUnlock(uint32_t lockedSize, const Literal* x, uint32_t xs) {
+    if ((lockedSize + xs) <= block_cap) {
+        std::copy_n(x, xs, data_ + lockedSize);
+        sizeLock_.store((lockedSize + xs) << size_shift, std::memory_order_release);
+        return true;
+    }
+    return false;
+}
+ShortImplicationsGraph::ImplicationList::~ImplicationList() { resetLearnt(); }
+void ShortImplicationsGraph::ImplicationList::resetLearnt() {
+    for (Block* x = learnt.exchange(nullptr, std::memory_order_acquire); x;) {
+        Block* t = std::exchange(x, x->next());
+        delete t;
+    }
+}
+void ShortImplicationsGraph::ImplicationList::reset() {
+    ImpListBase::reset();
+    resetLearnt();
 }
 
-void ShortImplicationsGraph::ImplicationList::clear(bool b) {
-	ImpListBase::clear(b);
-	for (Block* x = learnt; x; ) {
-		Block* t = x;
-		x = x->next;
-		delete t;
-	}
-	learnt = 0;
-}
-void ShortImplicationsGraph::ImplicationList::simplifyLearnt(const Solver& s) {
-	Block* x = learnt;
-	learnt   = 0;
-	while (x) {
-		for (const Literal* Y = x->begin(), *endof = x->end(); Y != endof; Y += 2 - Y->flagged()) {
-			Literal p = Y[0], q = !Y->flagged() ? Y[1] : lit_false();
-			if (!s.isTrue(p) && !s.isTrue(q)) {
-				addLearnt(p, q);
-			}
-		}
-		Block* t = x;
-		x = x->next;
-		delete t;
-	}
-}
-void ShortImplicationsGraph::ImplicationList::addLearnt(Literal p, Literal q) {
-	Literal nc[2] = {p, q};
-	uint32  ns    = 1 + !isSentinel(q);
-	if (ns == 1) { nc[0].flag(); }
-	for (Block* x;;) {
-		if ((x = learnt) != 0) {
-			uint32 lockedSize;
-			if (x->tryLock(lockedSize)) {
-				if ( (lockedSize + ns) <=  Block::block_cap ) {
-					x->addUnlock(lockedSize, nc, ns);
-				}
-				else {
-					Block* t = new Block();
-					t->addUnlock(0, nc, ns);
-					t->next   = x; // x is full and remains locked forever
-					x = learnt= t; // publish new block - unlocks x and learnt
-				}
-				return;
-			}
-			else {
-				Clasp::mt::this_thread::yield();
-			}
-		}
-		else {
-			x = new Block();
-			if (compare_and_swap(learnt, static_cast(0), x) != 0) {
-				delete x;
-			}
-		}
-	}
+void ShortImplicationsGraph::ImplicationList::addLearnt(Literal q, Literal r) {
+    Literal  nc[2] = {q, r};
+    uint32_t ns    = 1 + not isSentinel(r);
+    nc[ns - 1].flag(); // mark end of clause
+    for (Block* x = learnt.load();;) {
+        if (x != nullptr) {
+            if (uint32_t lockedSize; x->tryLock(lockedSize)) [[likely]] {
+                if (not x->addUnlock(lockedSize, nc, ns)) {
+                    auto* t = new Block(x, nc, ns); // x is full and remains locked forever
+                    learnt.store(t);                // publish new (unlocked) block
+                }
+                return;
+            }
+            // some other thread is currently adding to this list...
+            mt::this_thread::yield();
+            x = learnt.load(); // ...reload - x might be full and no longer the active block
+        }
+        else if (auto* n = new Block(x, nc, ns); learnt.compare_exchange_weak(x, n)) {
+            return; // won the race and published our block as first block
+        }
+        else { // some thread allocated and published a first block before us
+            assert(x != nullptr);
+            delete n;
+        }
+    }
 }
 
 bool ShortImplicationsGraph::ImplicationList::hasLearnt(Literal q, Literal r) const {
-	const bool binary = isSentinel(r);
-	FOR_EACH_LEARNT(*this, imp) {
-		if (imp[0] == q || imp[0] == r) {
-			// binary clause subsumes new bin/tern clause
-			if (imp->flagged())                          { return true; }
-			// existing ternary clause subsumes new tern clause
-			if (!binary && (imp[1] == q || imp[1] == r)) { return true; }
-		}
-	}
-	return false;
+    return not forEachLearnt(lit_true, [&, binary = isSentinel(r)](Literal, Literal q0, Literal r0 = lit_false) {
+        if (q0 == q || q0 == r) {
+            // binary clause subsumes new bin/tern clause
+            if (r0 == lit_false) {
+                return false;
+            }
+            // existing ternary clause subsumes new tern clause
+            if (not binary && (r0 == q || r0 == r)) {
+                return false;
+            }
+        }
+        return true;
+    });
 }
 
-void ShortImplicationsGraph::ImplicationList::move(ImplicationList& other) {
-	ImpListBase::move(other);
-	delete static_cast(learnt);
-	learnt = static_cast(other.learnt);
-	other.learnt = 0;
-}
 #endif
 /////////////////////////////////////////////////////////////////////////////////////////
 // ShortImplicationsGraph
 /////////////////////////////////////////////////////////////////////////////////////////
-ShortImplicationsGraph::ShortImplicationsGraph() {
-	bin_[0]  = bin_[1]  = 0;
-	tern_[0] = tern_[1] = 0;
-	shared_  = false;
-}
-ShortImplicationsGraph::~ShortImplicationsGraph() {
-	PodVector::destruct(graph_);
-}
-void ShortImplicationsGraph::resize(uint32 nodes) {
-	if (nodes <= graph_.size()) {
-		while (graph_.size() != nodes) {
-			graph_.back().clear(true);
-			graph_.pop_back();
-		}
-	}
-	else if (graph_.capacity() >= nodes) {
-		graph_.resize(nodes);
-	}
-	else {
-		ImpLists temp; temp.resize(nodes);
-		for (ImpLists::size_type i = 0; i != graph_.size(); ++i) {
-			temp[i].move(graph_[i]);
-		}
-		graph_.swap(temp);
-	}
+ShortImplicationsGraph::~ShortImplicationsGraph() { PodVector::destruct(graph_); }
+void ShortImplicationsGraph::resize(uint32_t nodes) {
+    if (nodes <= graph_.size()) {
+        while (graph_.size() != nodes) {
+            graph_.back().reset();
+            graph_.pop_back();
+        }
+    }
+    else if (graph_.capacity() >= nodes) {
+        graph_.resize(nodes);
+    }
+    else {
+        // NOTE: We can't simply resize graph here because ImplicationList is actually not trivially relocatable.
+        ImpLists temp;
+        temp.resize(nodes);
+        for (auto i : irange(graph_)) { temp[i] = std::move(graph_[i]); }
+        graph_.swap(temp);
+    }
 }
 
-uint32 ShortImplicationsGraph::numEdges(Literal p) const { return graph_[p.id()].size(); }
+uint32_t ShortImplicationsGraph::numEdges(Literal p) const { return graph_[p.id()].size(); }
 
-bool ShortImplicationsGraph::add(ImpType t, bool learnt, const Literal* lits) {
-	uint32& stats= (t == ternary_imp ? tern_ : bin_)[learnt];
-	Literal p = lits[0], q = lits[1], r = (t == ternary_imp ? lits[2] : lit_false());
-	p.unflag(), q.unflag(), r.unflag();
-	if (!shared_) {
-		if (learnt) { p.flag(), q.flag(), r.flag(); }
-		if (t == binary_imp) {
-			getList(~p).push_left(q);
-			getList(~q).push_left(p);
-		}
-		else {
-			getList(~p).push_right(std::make_pair(q, r));
-			getList(~q).push_right(std::make_pair(p, r));
-			getList(~r).push_right(std::make_pair(p, q));
-		}
-		++stats;
-		return true;
-	}
+bool ShortImplicationsGraph::add(LitView lits, bool learnt) {
+    POTASSCO_ASSERT(lits.size() > 1 && lits.size() < 4);
+    bool      tern  = lits.size() == 3u;
+    uint32_t& stats = (tern ? tern_ : bin_)[learnt];
+    Literal   p = lits[0], q = lits[1], r = (tern ? lits[2] : lit_false);
+    p.unflag(), q.unflag(), r.unflag();
+    if (not shared_) {
+        bool simp = learnt && simp_ == ContextParams::simp_learnt;
+        if (simp && contains(getList(~p).left_view(), q)) {
+            return true;
+        }
+        if (learnt) {
+            p.flag(), q.flag(), r.flag();
+        }
+        if (not tern) {
+            getList(~p).push_left(q);
+            getList(~q).push_left(p);
+        }
+        else {
+            if (simp && contains(getList(~p).right_view(), Tern{q, r})) {
+                return true;
+            }
+            getList(~p).push_right({q, r});
+            getList(~q).push_right({p, r});
+            getList(~r).push_right({p, q});
+        }
+        ++stats;
+        return true;
+    }
 #if CLASP_HAS_THREADS
-	else if (learnt && !getList(~p).hasLearnt(q, r)) {
-		getList(~p).addLearnt(q, r);
-		getList(~q).addLearnt(p, r);
-		if (t == ternary_imp) {
-			getList(~r).addLearnt(p, q);
-		}
-		++stats;
-		return true;
-	}
+    else if (learnt && not getList(~p).hasLearnt(q, r)) {
+        getList(~p).addLearnt(q, r);
+        getList(~q).addLearnt(p, r);
+        if (tern) {
+            getList(~r).addLearnt(p, q);
+        }
+        ++stats;
+        return true;
+    }
 #endif
-	return false;
+    return false;
 }
 
-void ShortImplicationsGraph::remove_bin(ImplicationList& w, Literal p) {
-	w.erase_left_unordered(std::find(w.left_begin(), w.left_end(), p));
-	w.try_shrink();
-}
-void ShortImplicationsGraph::remove_tern(ImplicationList& w, Literal p) {
-	w.erase_right_unordered(std::find_if(w.right_begin(), w.right_end(), PairContains(p)));
-	w.try_shrink();
+void ShortImplicationsGraph::removeBin(Literal other, Literal sat) {
+    --bin_[other.flagged()];
+    auto& w = getList(~other);
+    w.erase_left_unordered(std::find(w.left_begin(), w.left_end(), sat));
+    w.try_shrink();
 }
 
+void ShortImplicationsGraph::removeTern(const Solver& s, const Tern& t, Literal p) {
+    assert(s.value(p.var()) != value_free);
+    --tern_[t[0].flagged()];
+    for (auto lit : t) {
+        auto& w = getList(~lit);
+        w.erase_right_unordered(
+            std::find_if(w.right_begin(), w.right_end(), [p](const Tern& x) { return x[0] == p || x[1] == p; }));
+        w.try_shrink();
+    }
+    if (s.isFalse(p) && s.value(t[0].var()) == value_free && s.value(t[1].var()) == value_free) {
+        // clause is binary on dl 0
+        add(t, t[0].flagged());
+    }
+    // else: clause is SAT
+}
 // Removes all binary clauses containing p - those are now SAT.
 // Binary clauses containing ~p are unit and therefore likewise SAT. Those
 // are removed when their second literal is processed.
@@ -275,911 +274,944 @@ void ShortImplicationsGraph::remove_tern(ImplicationList& w, Literal p) {
 // All conditional binary-clauses are replaced with real binary clauses.
 // Note: clauses containing p watch ~p. Those containing ~p watch p.
 void ShortImplicationsGraph::removeTrue(const Solver& s, Literal p) {
-	assert(!shared_);
-	typedef ImplicationList SWL;
-	SWL& negPList = graph_[(~p).id()];
-	SWL& pList    = graph_[ (p).id()];
-	// remove every binary clause containing p -> clause is satisfied
-	for (SWL::left_iterator it = negPList.left_begin(), end = negPList.left_end(); it != end; ++it) {
-		--bin_[it->flagged()];
-		remove_bin(graph_[(~*it).id()], p);
-	}
-	// remove every ternary clause containing p -> clause is satisfied
-	for (SWL::right_iterator it = negPList.right_begin(), end = negPList.right_end(); it != end; ++it) {
-		--tern_[it->first.flagged()];
-		remove_tern(graph_[ (~it->first).id() ], p);
-		remove_tern(graph_[ (~it->second).id() ], p);
-	}
+    POTASSCO_ASSERT(not shared_);
 #if CLASP_HAS_THREADS
-	FOR_EACH_LEARNT(negPList, imp) {
-		graph_[(~imp[0]).id()].simplifyLearnt(s);
-		if (!imp->flagged()){
-			--tern_[1];
-			graph_[(~imp[1]).id()].simplifyLearnt(s);
-		}
-		if (imp->flagged()) { --bin_[1]; }
-	}
+    for (auto lit : {p, ~p}) {
+        getList(~lit).forEachLearnt(lit, [&](Literal p0, Literal q, Literal r = lit_false) {
+            for (auto x : {q, r}) {
+                if (auto& xl = getList(~x); xl.learnt) {
+                    // promote entries from learnt blocks to base list
+                    std::ignore = xl.forEachLearnt(x, [&](Literal, Literal l1, Literal l2 = lit_false) {
+                        if (s.value(l1.var()) == value_free) {
+                            if (l2 == lit_false) {
+                                xl.push_left(l1.flag());
+                            }
+                            else if (s.value(l2.var()) == value_free) {
+                                xl.push_right({l1.flag(), l2.flag()});
+                            }
+                        }
+                        // else: entry is no longer relevant or will be re-added later.
+                        return true;
+                    });
+                    xl.resetLearnt();
+                }
+            }
+            if (r != lit_false) {
+                removeTern(s, {q.flag(), r.flag()}, p0);
+            }
+            else if (p == p0) {
+                removeBin(q.flag(), p0);
+            }
+            return true;
+        });
+    }
 #endif
-	// transform ternary clauses containing ~p to binary clause
-	for (SWL::right_iterator it = pList.right_begin(), end = pList.right_end(); it != end; ++it) {
-		Literal q = it->first;
-		Literal r = it->second;
-		--tern_[q.flagged()];
-		remove_tern(graph_[(~q).id()], ~p);
-		remove_tern(graph_[(~r).id()], ~p);
-		if (s.value(q.var()) == value_free && s.value(r.var()) == value_free) {
-			// clause is binary on dl 0
-			Literal imp[2] = {q,r};
-			add(binary_imp, false, imp);
-		}
-		// else: clause is SAT and removed when the satisfied literal is processed
-	}
-	graph_[(~p).id()].clear(true);
-	graph_[ (p).id()].clear(true);
-}
-#undef FOR_EACH_LEARNT
-struct ShortImplicationsGraph::Propagate {
-	Propagate(Solver& a_s) : s(&a_s) {}
-	bool unary(Literal p, Literal x) const { return s->isTrue(x) || s->force(x, Antecedent(p)); }
-	bool binary(Literal p, Literal x, Literal y) const {
-		ValueRep vx = s->value(x.var()), vy;
-		if (vx != trueValue(x) && (vy=s->value(y.var())) != trueValue(y) && vx + vy) {
-			return vx != 0 ? s->force(y, Antecedent(p, ~x)) : s->force(x, Antecedent(p, ~y));
-		}
-		return true;
-	}
-	Solver* s;
-};
-struct ShortImplicationsGraph::ReverseArc {
-	ReverseArc(const Solver& a_s, uint32 m, Antecedent& o) : s(&a_s), out(&o), maxL(m) {}
-	bool unary(Literal, Literal x) const {
-		if (!isRevLit(*s, x, maxL)) { return true; }
-		*out = Antecedent(~x);
-		return false;
-	}
-	bool binary(Literal, Literal x, Literal y) const {
-		if (!isRevLit(*s, x, maxL) || !isRevLit(*s, y, maxL)) { return true; }
-		*out = Antecedent(~x, ~y);
-		return false;
-	}
-	const Solver* s; Antecedent* out; uint32 maxL;
-};
-bool ShortImplicationsGraph::propagate(Solver& s, Literal p) const { return forEach(p, Propagate(s)); }
-bool ShortImplicationsGraph::reverseArc(const Solver& s, Literal p, uint32 maxLev, Antecedent& out) const { return !forEach(p, ReverseArc(s, maxLev, out)); }
-bool ShortImplicationsGraph::propagateBin(Assignment& out, Literal p, uint32 level) const {
-	const ImplicationList& x = graph_[p.id()];
-	Antecedent ante(p);
-	for (ImplicationList::const_left_iterator it = x.left_begin(), end = x.left_end(); it != end; ++it) {
-		if (!out.assign(*it, level, p)) { return false; }
-	}
-	return true;
+    auto& negPList = getList(~p);
+    auto& pList    = getList(p);
+    // remove every binary clause containing p -> clause is satisfied
+    for (auto x : negPList.left_view()) { removeBin(x, p); }
+    // remove every ternary clause containing p -> clause is satisfied
+    for (const auto& t : negPList.right_view()) { removeTern(s, t, p); }
+    // transform ternary clauses containing ~p to binary clause
+    for (const auto& t : pList.right_view()) { removeTern(s, t, ~p); }
+    negPList.reset();
+    pList.reset();
+}
+
+bool ShortImplicationsGraph::propagate(Solver& s, Literal p) const {
+    return forEach(p, [&s](Literal p0, Literal q, Literal r = lit_false) {
+        if (auto vq = s.value(q.var()); vq != trueValue(q)) {
+            if (r == lit_false) {
+                return s.force(q, Antecedent(p0));
+            }
+            if (auto vr = s.value(r.var()); vr != trueValue(r) && vr + vq) {
+                return vq ? s.force(r, Antecedent(p0, ~q)) : s.force(q, Antecedent(p0, ~r));
+            }
+        }
+        return true;
+    });
+}
+bool ShortImplicationsGraph::reverseArc(const Solver& s, Literal p, uint32_t maxLev, Antecedent& out) const {
+    return not forEach(p, [&](Literal, Literal q, Literal r = lit_false) {
+        if (not isRevLit(s, q, maxLev)) {
+            return true;
+        }
+        if (r == lit_false) {
+            out = Antecedent(~q);
+            return false;
+        }
+        if (not isRevLit(s, r, maxLev)) {
+            return true;
+        }
+        out = Antecedent(~q, ~r);
+        return false;
+    });
+}
+bool ShortImplicationsGraph::propagateBin(Assignment& out, Literal p, uint32_t level) const {
+    for (const auto& lit : graph_[p.id()].left_view()) {
+        if (not out.assign(lit, level, p)) {
+            return false;
+        }
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // SatPreprocessor
 /////////////////////////////////////////////////////////////////////////////////////////
-SatPreprocessor::SatPreprocessor() : ctx_(0), opts_(0), elimTop_(0), seen_(1,1) {}
-SatPreprocessor::~SatPreprocessor() {
-	discardClauses(true);
-}
-void SatPreprocessor::discardClauses(bool full) {
-	for (ClauseList::size_type i = 0; i != clauses_.size(); ++i) {
-		if (clauses_[i]) { clauses_[i]->destroy(); }
-	}
-	ClauseList().swap(clauses_);
-	if (Clause* r = (full ? elimTop_ : 0)) {
-		do {
-			Clause* t = r;
-			 r = r->next();
-			 t->destroy();
-		} while (r);
-		elimTop_ = 0;
-	}
-	if (full) { seen_ = Range32(1,1); }
-}
-void SatPreprocessor::cleanUp(bool full) {
-	if (ctx_) { seen_.hi = ctx_->numVars() + 1; }
-	doCleanUp();
-	discardClauses(full);
+SatPreprocessor::SatPreprocessor() : ctx_(nullptr), opts_(nullptr), elimTop_(nullptr), seen_(1, 1) {}
+SatPreprocessor::~SatPreprocessor() { discardClauses(true); }
+void SatPreprocessor::discardClauses(bool discardEliminated) {
+    for (auto* clause : clauses_) {
+        if (clause) {
+            clause->destroy();
+        }
+    }
+    discardVec(clauses_);
+    if (Clause* r = (discardEliminated ? elimTop_ : nullptr)) {
+        do {
+            Clause* t = r;
+            r         = r->next();
+            t->destroy();
+        } while (r);
+        elimTop_ = nullptr;
+    }
+    if (discardEliminated) {
+        seen_ = Range32(1, 1);
+    }
+}
+void SatPreprocessor::cleanUp(bool discardEliminated) {
+    if (ctx_) {
+        seen_.hi = ctx_->numVars() + 1;
+    }
+    doCleanUp();
+    discardClauses(discardEliminated);
 }
 
-bool SatPreprocessor::addClause(const Literal* lits, uint32 size) {
-	if (size > 1) {
-		clauses_.push_back( Clause::newClause(lits, size) );
-	}
-	else if (size == 1) {
-		units_.push_back(lits[0]);
-	}
-	else {
-		return false;
-	}
-	return true;
+bool SatPreprocessor::addClause(LitView clause) {
+    if (clause.empty()) {
+        return false;
+    }
+    clause.size() > 1 ? clauses_.push_back(Clause::newClause(clause)) : units_.push_back(clause[0]);
+    return true;
 }
 
 void SatPreprocessor::freezeSeen() {
-	if (!ctx_->validVar(seen_.lo)) { seen_.lo = 1; }
-	if (!ctx_->validVar(seen_.hi)) { seen_.hi = ctx_->numVars() + 1; }
-	for (Var v = seen_.lo; v != seen_.hi; ++v) {
-		if (!ctx_->eliminated(v)) { ctx_->setFrozen(v, true); }
-	}
-	seen_.lo = seen_.hi;
+    if (not ctx_->validVar(seen_.lo)) {
+        seen_.lo = 1;
+    }
+    if (not ctx_->validVar(seen_.hi)) {
+        seen_.hi = ctx_->numVars() + 1;
+    }
+    for (auto v : irange(seen_.lo, seen_.hi)) {
+        assert(v >= seen_.lo && v < seen_.hi);
+        if (not ctx_->eliminated(v)) {
+            ctx_->setFrozen(v, true);
+        }
+    }
+    seen_.lo = seen_.hi;
 }
 
 bool SatPreprocessor::preprocess(SharedContext& ctx, Options& opts) {
-	ctx_  = &ctx;
-	opts_ = &opts;
-	Solver* s = ctx_->master();
-	struct OnExit {
-		SharedContext*   ctx;
-		SatPreprocessor* self;
-		SatPreprocessor* rest;
-		OnExit(SatPreprocessor* s, SharedContext* c) : ctx(c), self(s), rest(0) {
-			if (ctx && ctx->satPrepro.get() == s) { rest = ctx->satPrepro.release(); }
-		}
-		~OnExit() {
-			if (self) self->cleanUp();
-			if (rest) ctx->satPrepro.reset(rest);
-		}
-	} onExit(this, &ctx);
-	for (LitVec::const_iterator it = units_.begin(), end = units_.end(); it != end; ++it) {
-		if (!ctx.addUnary(*it)) return false;
-	}
-	units_.clear();
-	// skip preprocessing if other constraints are UNSAT
-	if (!s->propagate()) return false;
-	if (ctx.preserveModels()) {
-		opts.disableBce();
-	}
-	if (ctx.preserveShown()) {
-		for (OutputTable::pred_iterator it = ctx.output.pred_begin(), end = ctx.output.pred_end(); it != end; ++it) {
-			ctx.setFrozen(it->cond.var(), true);
-		}
-		for (OutputTable::range_iterator it = ctx.output.vars_begin(), end = ctx.output.vars_end(); it != end; ++it) {
-			ctx.setFrozen(*it, true);
-		}
-	}
-	if (ctx.preserveHeuristic()) {
-		for (DomainTable::iterator it = ctx.heuristic.begin(), end = ctx.heuristic.end(); it != end; ++it) {
-			if (!ctx.master()->isFalse(it->cond())) {
-				ctx.setFrozen(it->var(), true);
-			}
-		}
-		struct Freeze : DomainTable::DefaultAction {
-			explicit Freeze(SharedContext& c) : ctx(&c) {}
-			void atom(Literal p, HeuParams::DomPref, uint32) { ctx->setFrozen(p.var(), true); }
-			SharedContext* ctx;
-		} act(ctx);
-		DomainTable::applyDefault(ctx, act, ctx.defaultDomPref());
-	}
+    ctx_         = &ctx;
+    opts_        = &opts;
+    Solver* s    = ctx_->master();
+    auto    prev = std::move(ctx.satPrepro);
+    POTASSCO_SCOPE_EXIT({
+        cleanUp();
+        if (not ctx.satPrepro) {
+            ctx.satPrepro = std::move(prev);
+        }
+    });
+    for (auto lit : units_) {
+        if (not ctx.addUnary(lit)) {
+            return false;
+        }
+    }
+    units_.clear();
+    // skip preprocessing if other constraints are UNSAT
+    if (not s->propagate()) {
+        return false;
+    }
+    if (ctx.preserveModels()) {
+        opts.disableBce();
+    }
+    if (ctx.preserveShown()) {
+        for (const auto& pred : ctx.output.pred_range()) { ctx.setFrozen(pred.cond.var(), true); }
+        for (auto v : ctx.output.vars_range()) { ctx.setFrozen(v, true); }
+    }
+    if (ctx.preserveHeuristic()) {
+        for (const auto& x : ctx.heuristic) {
+            if (not ctx.master()->isFalse(x.cond())) {
+                ctx.setFrozen(x.var(), true);
+            }
+        }
+        DomainTable::applyDefault(
+            ctx, [&ctx](Literal p, HeuParams::DomPref, uint32_t) { ctx.setFrozen(p.var(), true); },
+            ctx.defaultDomPref());
+    }
 
-	// preprocess only if not too many vars are frozen or not too many clauses
-	bool limFrozen = false;
-	if (opts.limFrozen != 0 && ctx_->stats().vars.frozen) {
-		uint32 varFrozen = ctx_->stats().vars.frozen;
-		for (LitVec::const_iterator it = s->trail().begin(), end = s->trail().end(); it != end; ++it) {
- 			varFrozen -= (ctx_->varInfo(it->var()).frozen());
- 		}
-		limFrozen = ((varFrozen / double(s->numFreeVars())) * 100.0) > double(opts.limFrozen);
-	}
-	// 1. remove SAT-clauses, strengthen clauses w.r.t false literals, attach
-	if (opts.type != 0 && !opts.clauseLimit(numClauses()) && !limFrozen && initPreprocess(opts)) {
-		ClauseList::size_type j = 0;
-		for (ClauseList::size_type i = 0; i != clauses_.size(); ++i) {
-			Clause* c   = clauses_[i]; assert(c);
-			clauses_[i] = 0;
-			c->simplify(*s);
-			Literal x   = (*c)[0];
-			if (s->value(x.var()) == value_free) {
-				clauses_[j++] = c;
-			}
-			else {
-				c->destroy();
-				if (!ctx.addUnary(x)) { return false; }
-			}
-		}
-		clauses_.erase(clauses_.begin()+j, clauses_.end());
-		// 2. run preprocessing
-		freezeSeen();
-		if (!s->propagate() || !doPreprocess()) {
-			return false;
-		}
-	}
-	// simplify other constraints w.r.t any newly derived top-level facts
-	if (!s->simplify()) return false;
-	// 3. move preprocessed clauses to ctx
-	for (ClauseList::size_type i = 0; i != clauses_.size(); ++i) {
-		if (Clause* c = clauses_[i]) {
-			if (!ClauseCreator::create(*s, ClauseRep::create(&(*c)[0], c->size()), 0)) {
-				return false;
-			}
-			clauses_[i] = 0;
-			c->destroy();
-		}
-	}
-	ClauseList().swap(clauses_);
-	return true;
+    // preprocess only if not too many vars are frozen or not too many clauses
+    bool limFrozen = false;
+    if (double limit = opts.limFrozen; limit != 0 && ctx_->stats().vars.frozen) {
+        uint32_t varFrozen = ctx_->stats().vars.frozen;
+        for (auto lit : s->trailView()) { varFrozen -= (ctx_->varInfo(lit.var()).frozen()); }
+        limFrozen = percent(varFrozen, s->numFreeVars()) > limit;
+    }
+    // 1. remove SAT-clauses, strengthen clauses w.r.t false literals, attach
+    if (opts.type != 0 && not opts.clauseLimit(numClauses()) && not limFrozen && initPreprocess(opts)) {
+        ClauseList::size_type j = 0;
+        for (Clause*& clause : clauses_) {
+            auto* c = std::exchange(clause, nullptr);
+            assert(c);
+            c->simplify(*s);
+            Literal x = (*c)[0];
+            if (s->value(x.var()) == value_free) {
+                clauses_[j++] = c;
+            }
+            else {
+                c->destroy();
+                if (not ctx.addUnary(x)) {
+                    return false;
+                }
+            }
+        }
+        shrinkVecTo(clauses_, j);
+        // 2. run preprocessing
+        freezeSeen();
+        if (not s->propagate() || not doPreprocess()) {
+            return false;
+        }
+    }
+    // simplify other constraints w.r.t any newly derived top-level facts
+    if (not s->simplify()) {
+        return false;
+    }
+    // 3. move preprocessed clauses to ctx
+    for (Clause*& c : clauses_) {
+        if (c) {
+            if (not ClauseCreator::create(*s, ClauseRep::create({&(*c)[0], c->size()}), {})) {
+                return false;
+            }
+            std::exchange(c, nullptr)->destroy();
+        }
+    }
+    discardVec(clauses_);
+    return true;
 }
 bool SatPreprocessor::preprocess(SharedContext& ctx) {
-	SatPreParams opts = ctx.configuration()->context().satPre;
-	return preprocess(ctx, opts);
+    SatPreParams opts = ctx.configuration()->context().satPre;
+    return preprocess(ctx, opts);
 }
 void SatPreprocessor::extendModel(ValueVec& m, LitVec& open) {
-	if (!open.empty()) {
-		// flip last unconstrained variable to get "next" model
-		open.back() = ~open.back();
-	}
-	doExtendModel(m, open);
-	// remove unconstrained vars already flipped
-	while (!open.empty() && open.back().sign()) {
-		open.pop_back();
-	}
-}
-SatPreprocessor::Clause* SatPreprocessor::Clause::newClause(const Literal* lits, uint32 size) {
-	assert(size > 0);
-	void* mem = ::operator new( sizeof(Clause) + (size-1)*sizeof(Literal) );
-	return new (mem) Clause(lits, size);
-}
-SatPreprocessor::Clause::Clause(const Literal* lits, uint32 size) : size_(size), inQ_(0), marked_(0) {
-	std::memcpy(lits_, lits, size*sizeof(Literal));
+    if (not open.empty()) {
+        // flip last unconstrained variable to get "next" model
+        open.back() = ~open.back();
+    }
+    doExtendModel(m, open);
+    // remove unconstrained vars already flipped
+    while (not open.empty() && open.back().sign()) { open.pop_back(); }
+}
+SatPreprocessor::Clause* SatPreprocessor::Clause::newClause(LitView lits) {
+    assert(not lits.empty());
+    void* mem = ::operator new(sizeof(Clause) + (lits.size() - 1) * sizeof(Literal));
+    return new (mem) Clause(lits.data(), size32(lits));
+}
+SatPreprocessor::Clause::Clause(const Literal* lits, uint32_t size) : size_(size), inQ_(0), marked_(0) {
+    std::memcpy(lits_, lits, size * sizeof(Literal));
 }
 void SatPreprocessor::Clause::strengthen(Literal p) {
-	uint64 a = 0;
-	uint32 i, end;
-	for (i   = 0; lits_[i] != p; ++i) { a |= Clause::abstractLit(lits_[i]); }
-	for (end = size_-1; i < end; ++i) { lits_[i] = lits_[i+1]; a |= Clause::abstractLit(lits_[i]); }
-	--size_;
-	data_.abstr = a;
+    uint64_t a = 0;
+    uint32_t i;
+    for (i = 0; lits_[i] != p; ++i) { a |= abstractLit(lits_[i]); }
+    for (uint32_t end = size_ - 1; i < end; ++i) {
+        lits_[i]  = lits_[i + 1];
+        a        |= abstractLit(lits_[i]);
+    }
+    --size_;
+    data_.abstr = a;
 }
 void SatPreprocessor::Clause::simplify(Solver& s) {
-	uint32 i;
-	for (i = 0; i != size_ && s.value(lits_[i].var()) == value_free; ++i) {;}
-	if      (i == size_)        { return; }
-	else if (s.isTrue(lits_[i])){ std::swap(lits_[i], lits_[0]); return;  }
-	uint32 j = i++;
-	for (; i != size_; ++i) {
-		if (s.isTrue(lits_[i]))   { std::swap(lits_[i], lits_[0]); return;  }
-		if (!s.isFalse(lits_[i])) { lits_[j++] = lits_[i]; }
-	}
-	size_ = j;
+    uint32_t i;
+    for (i = 0; i != size_ && s.value(lits_[i].var()) == value_free; ++i) { ; }
+    if (i == size_) {
+        return;
+    }
+    if (s.isTrue(lits_[i])) {
+        std::swap(lits_[i], lits_[0]);
+        return;
+    }
+    uint32_t j = i++;
+    for (; i != size_; ++i) {
+        if (s.isTrue(lits_[i])) {
+            std::swap(lits_[i], lits_[0]);
+            return;
+        }
+        if (not s.isFalse(lits_[i])) {
+            lits_[j++] = lits_[i];
+        }
+    }
+    size_ = j;
 }
 void SatPreprocessor::Clause::destroy() {
-	void* mem = this;
-	this->~Clause();
-	::operator delete(mem);
+    void* mem = this;
+    this->~Clause();
+    ::operator delete(mem);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
-// ConstString
-/////////////////////////////////////////////////////////////////////////////////////////
-struct StrRef {
-	typedef Atomic_t::type RefCount;
-	static uint64      empty() { return 0; }
-	static uint64      create(const char* str, std::size_t len) {
-		char*   mem = (char*)malloc(sizeof(RefCount) + len + 1);
-		RefCount* p = new (mem) RefCount();
-		std::memcpy(mem + sizeof(RefCount), str, len);
-		mem[sizeof(RefCount) + len] = 0;
-		*p = 1;
-		return static_cast(reinterpret_cast(mem));
-	}
-	static RefCount*   asShared(uint64 ref) {
-		return ref ? reinterpret_cast(static_cast(ref)) : 0;
-	}
-	static uint64      share(uint64 ref) {
-		if (RefCount* p = asShared(ref)) { ++*p; }
-		return ref;
-	}
-	static uint64      release(uint64 ref) {
-		RefCount* p = asShared(ref);
-		if (p && !--*p) {
-			p->~RefCount();
-			free((void*)p);
-		}
-		return 0;
-	}
-	static const char* get(uint64 ref) {
-		return ref ? reinterpret_cast(static_cast(ref + sizeof(RefCount))) : "";
-	}
-};
-ConstString::ConstString(const char* str) : ref_(str && *str ? StrRef::create(str, std::strlen(str)) : StrRef::empty()) {}
-ConstString::ConstString(const StrView& str) : ref_(str.size ? StrRef::create(str.first, str.size) : StrRef::empty()) {}
-ConstString::ConstString(const ConstString& other) : ref_(StrRef::share(other.ref_)) { }
-ConstString::~ConstString() {  StrRef::release(ref_); }
-void ConstString::swap(ConstString& rhs) { std::swap(ref_, rhs.ref_); }
-ConstString& ConstString::operator=(const ConstString& rhs) {
-	ConstString temp(rhs);
-	swap(temp);
-	return *this;
-}
-const char* ConstString::c_str() const { return StrRef::get(ref_); }
-/////////////////////////////////////////////////////////////////////////////////////////
 // OutputTable
 /////////////////////////////////////////////////////////////////////////////////////////
-OutputTable::OutputTable() : theory(0), vars_(0, 0), projMode_(0), hide_(0) {}
+OutputTable::OutputTable() : theory(nullptr), vars_(0, 0), projMode_(ProjectMode::implicit), hide_(0) {}
 OutputTable::~OutputTable() {
-	PodVector::destruct(facts_);
-	PodVector::destruct(preds_);
+    PodVector::destruct(facts_);
+    PodVector::destruct(preds_);
 }
 
-void OutputTable::setFilter(char c) {
-	hide_ = c;
-}
-bool OutputTable::filter(const NameType& n) const {
-	char c = *n;
-	return c == hide_ || !c;
-}
+void OutputTable::setFilter(char c) { hide_ = c; }
+bool OutputTable::filter(const std::string_view& n) const { return n.empty() || n.starts_with(hide_); }
+bool OutputTable::filter(const NameType& n) const { return filter(n.view()); }
 bool OutputTable::add(const NameType& fact) {
-	if (!filter(fact)) {
-		facts_.push_back(fact);
-		return true;
-	}
-	return false;
+    if (not filter(fact)) {
+        facts_.push_back(fact);
+        return true;
+    }
+    return false;
 }
-
-bool OutputTable::add(const NameType& n, Literal c, uint32 u) {
-	if (!filter(n)) {
-		PredType p = {n, c, u};
-		preds_.push_back(p);
-		return true;
-	}
-	return false;
+bool OutputTable::add(const std::string_view& fact) {
+    return not filter(fact) && add(NameType(fact, NameType::create_shared));
 }
 
-void OutputTable::setVarRange(const RangeType& r) {
-	POTASSCO_ASSERT(r.lo <= r.hi);
-	vars_ = r;
-}
-void OutputTable::setProjectMode(ProjectMode m) {
-	projMode_ = m;
-}
-void OutputTable::addProject(Literal x) {
-	proj_.push_back(x);
+bool OutputTable::add(const NameType& n, Literal c, uint32_t u) {
+    if (not filter(n)) {
+        PredType p = {n, c, u};
+        preds_.push_back(p);
+        return true;
+    }
+    return false;
 }
-void OutputTable::clearProject() {
-	proj_.clear();
+bool OutputTable::add(const std::string_view& n, Literal c, uint32_t u) {
+    return not filter(n) && add(NameType(n, NameType::create_shared), c, u);
 }
-uint32 OutputTable::size() const {
-	return numFacts() + numPreds() + numVars();
-}
-OutputTable::Theory::~Theory() {}
+
+void     OutputTable::setVarRange(const Range32& r) { vars_ = r; }
+void     OutputTable::setProjectMode(ProjectMode m) { projMode_ = m; }
+void     OutputTable::addProject(Literal x) { proj_.push_back(x); }
+void     OutputTable::clearProject() { proj_.clear(); }
+uint32_t OutputTable::size() const { return numFacts() + numPreds() + numVars(); }
+OutputTable::Theory::~Theory() = default;
 /////////////////////////////////////////////////////////////////////////////////////////
 // DomainTable
 /////////////////////////////////////////////////////////////////////////////////////////
-DomainTable::ValueType::ValueType(Var v, DomModType t, int16 bias, uint16 prio, Literal cond)
-	: cond_(cond.id())
-	, comp_(t == DomModType::True || t == DomModType::False)
-	, var_(v)
-	, type_(uint32(t) <= 3u ? t : uint32(t == DomModType::False))
-	, bias_(bias)
-	, prio_(prio) {
-}
-DomModType DomainTable::ValueType::type() const { return static_cast(comp_ == 0 ? type_ : uint32(DomModType::True + type_)); }
-DomainTable::DomainTable() : assume(0), seen_(0) {}
-void DomainTable::add(Var v, DomModType t, int16 b, uint16 p, Literal c) {
-	if (c != lit_false() && (t != DomModType::Init || c == lit_true())) {
-		entries_.push_back(ValueType(v, t, b, p, c));
-	}
-}
-uint32 DomainTable::simplify() {
-	if (seen_ >= size()) { return size(); }
-	std::stable_sort(entries_.begin() + seen_, entries_.end(), cmp);
-	DomVec::iterator j = entries_.begin() + seen_;
-	for (DomVec::const_iterator it = j, end = entries_.end(), n; it != end; it = n) {
-		Var     v = it->var();
-		Literal c = it->cond();
-		for (n = it + 1; n != end && n->var() == v && n->cond() == c; ) { ++n; }
-		if ((n - it) == 1) {
-			*j++ = *it;
-		}
-		else {
-			static_assert(DomModType::Level == 0 && DomModType::Sign == 1 && DomModType::True == 4, "check enumeration constants");
-			enum { n_simp = 4u };
-			int const mod_level = DomModType::Level, mod_sign = DomModType::Sign;
-			int16 const NO_BIAS = INT16_MAX;
-			uint16 prio[n_simp] ={0, 0, 0, 0};
-			int16  bias[n_simp] ={NO_BIAS, NO_BIAS, NO_BIAS, NO_BIAS};
-			for (; it != n; ++it) {
-				if (!it->comp() && it->prio() >= prio[it->type()]) {
-					bias[it->type()] = it->bias();
-					prio[it->type()] = it->prio();
-				}
-				else if (it->comp()) {
-					if (it->prio() >= prio[mod_level]) {
-						bias[mod_level] = it->bias();
-						prio[mod_level] = it->prio();
-					}
-					if (it->prio() >= prio[mod_sign]) {
-						bias[mod_sign] = it->type() == DomModType::True ? 1 : -1;
-						prio[mod_sign] = it->prio();
-					}
-				}
-			}
-			int s = 0;
-			if (bias[mod_level] != NO_BIAS && bias[mod_sign] != NO_BIAS && bias[mod_sign] && prio[mod_level] == prio[mod_sign]) {
-				*j++ = ValueType(v, bias[mod_sign] > 0 ? DomModType::True : DomModType::False, bias[mod_level], prio[mod_level], c);
-				s = mod_sign + 1;
-			}
-			for (int t = s; t != n_simp; ++t) {
-				if (bias[t] != NO_BIAS) {
-					*j++ = ValueType(v, static_cast(t), bias[t], prio[t], c);
-				}
-			}
-		}
-	}
-	entries_.erase(j, entries_.end());
-	if (entries_.capacity() > static_cast(entries_.size() * 1.75)) {
-		DomVec(entries_).swap(entries_);
-	}
-	return (seen_ = size());
+DomainTable::ValueType::ValueType(Var_t v, DomModType t, int16_t bias, uint16_t prio, Literal cond)
+    : cond_(cond.id())
+    , comp_(t == DomModType::true_ || t == DomModType::false_)
+    , var_(v)
+    , type_(t <= 3u ? +t : static_cast(t == DomModType::false_))
+    , bias_(bias)
+    , prio_(prio) {}
+DomModType DomainTable::ValueType::type() const {
+    return static_cast(comp_ == 0 ? type_ : +DomModType::true_ + type_);
+}
+DomainTable::DomainTable() : assume(nullptr), seen_(0) {}
+void DomainTable::add(Var_t v, DomModType t, int16_t b, uint16_t p, Literal c) {
+    if (c != lit_false && (t != DomModType::init || c == lit_true)) {
+        entries_.push_back(ValueType(v, t, b, p, c));
+    }
+}
+uint32_t DomainTable::simplify() {
+    if (seen_ >= size()) {
+        return size();
+    }
+    std::stable_sort(entries_.begin() + seen_, entries_.end(), [](const ValueType& lhs, const ValueType& rhs) {
+        return lhs.cond() < rhs.cond() || (lhs.cond() == rhs.cond() && lhs.var() < rhs.var());
+    });
+    DomVec::iterator j = entries_.begin() + seen_;
+    for (DomVec::const_iterator it = j, end = entries_.end(), n; it != end; it = n) {
+        auto    v = it->var();
+        Literal c = it->cond();
+        for (n = it + 1; n != end && n->var() == v && n->cond() == c;) { ++n; }
+        if ((n - it) == 1) {
+            *j++ = *it;
+        }
+        else {
+            static_assert(DomModType::level == 0 && DomModType::sign == 1 && DomModType::true_ == 4,
+                          "check enumeration constants");
+            static constexpr auto n_simp    = 4u;
+            constexpr auto        mod_level = +DomModType::level, mod_sign = +DomModType::sign;
+            constexpr int16_t     no_bias      = INT16_MAX;
+            uint16_t              prio[n_simp] = {0, 0, 0, 0};
+            int16_t               bias[n_simp] = {no_bias, no_bias, no_bias, no_bias};
+            for (; it != n; ++it) {
+                if (not it->comp() && it->prio() >= prio[+it->type()]) {
+                    bias[+it->type()] = it->bias();
+                    prio[+it->type()] = it->prio();
+                }
+                else if (it->comp()) {
+                    if (it->prio() >= prio[mod_level]) {
+                        bias[mod_level] = it->bias();
+                        prio[mod_level] = it->prio();
+                    }
+                    if (it->prio() >= prio[mod_sign]) {
+                        bias[mod_sign] = it->type() == DomModType::true_ ? 1 : -1;
+                        prio[mod_sign] = it->prio();
+                    }
+                }
+            }
+            int s = 0;
+            if (bias[mod_level] != no_bias && bias[mod_sign] != no_bias && bias[mod_sign] &&
+                prio[mod_level] == prio[mod_sign]) {
+                *j++ = ValueType(v, bias[mod_sign] > 0 ? DomModType::true_ : DomModType::false_, bias[mod_level],
+                                 prio[mod_level], c);
+                s    = mod_sign + 1;
+            }
+            for (int t = s; t != n_simp; ++t) {
+                if (bias[t] != no_bias) {
+                    *j++ = ValueType(v, static_cast(t), bias[t], prio[t], c);
+                }
+            }
+        }
+    }
+    entries_.erase(j, entries_.end());
+    if (entries_.capacity() > static_cast(entries_.size() * 1.75)) {
+        DomVec(entries_).swap(entries_);
+    }
+    return (seen_ = size());
 }
 void DomainTable::reset() {
-	DomVec().swap(entries_);
-	assume = 0;
-	seen_  = 0;
-}
-DomainTable::DefaultAction::~DefaultAction() {}
-void DomainTable::applyDefault(const SharedContext& ctx, DefaultAction& act, uint32 defFilter) {
-	if ((defFilter & HeuParams::pref_show) != 0 || !defFilter) {
-		const HeuParams::DomPref pref = defFilter ? HeuParams::pref_show : HeuParams::pref_atom;
-		OutputTable::RangeType   vars = defFilter ? ctx.output.vars_range() : Range32(1, ctx.numVars()+1);
-		for (OutputTable::pred_iterator it = ctx.output.pred_begin(), end = ctx.output.pred_end(); it != end; ++it) {
-			if (defFilter || (it->cond.sign() && it->user && Potassco::atom(Potassco::lit(it->user)) < Asp::PrgNode::noNode)) {
-				act.atom(it->cond, pref, pref);
-			}
-		}
-		for (Var v = vars.lo; v != vars.hi; ++v) {
-			if (Var_t::isAtom(ctx.varInfo(v).type())) { act.atom(posLit(v), pref, pref); }
-		}
-	}
-	if ((defFilter & HeuParams::pref_min) != 0 && ctx.minimizeNoCreate()) {
-		weight_t lastW = -1; uint32 strat = HeuParams::pref_show;
-		for (const WeightLiteral* it = ctx.minimizeNoCreate()->lits; !isSentinel(it->first); ++it) {
-			if (it->second != lastW && strat > HeuParams::pref_disj) { --strat; lastW = it->second; }
-			act.atom(it->first, HeuParams::pref_min, strat);
-		}
-	}
-	const uint32 gs = (uint32(HeuParams::pref_scc) | HeuParams::pref_hcc | HeuParams::pref_disj) & defFilter;
-	if (ctx.sccGraph.get() && gs && ((gs & HeuParams::pref_scc) != 0 || ctx.sccGraph->numNonHcfs())) {
-		for (uint32 i = 0; i != ctx.sccGraph->numAtoms(); ++i) {
-			const PrgDepGraph::AtomNode& a = ctx.sccGraph->getAtom(i);
-			if      ((gs & HeuParams::pref_disj) != 0 && a.inDisjunctive()) { act.atom(a.lit, HeuParams::pref_disj, 3u); }
-			else if ((gs & HeuParams::pref_hcc)  != 0 && a.inNonHcf())      { act.atom(a.lit, HeuParams::pref_hcc, 2u);  }
-			else if ((gs & HeuParams::pref_scc)  != 0)                      { act.atom(a.lit, HeuParams::pref_scc, 1u);  }
-		}
-	}
-}
-bool   DomainTable::empty() const { return entries_.empty(); }
-uint32 DomainTable::size()  const { return static_cast(entries_.size()); }
+    discardVec(entries_);
+    assume = nullptr;
+    seen_  = 0;
+}
+void DomainTable::applyDefault(const SharedContext& ctx, const DefaultAction& act, uint32_t defFilter) {
+    if (not act) {
+        return;
+    }
+
+    if ((defFilter & HeuParams::pref_show) != 0 || not defFilter) {
+        auto pref = defFilter ? HeuParams::pref_show : HeuParams::pref_atom;
+        auto vars = defFilter ? ctx.output.vars_range() : ctx.vars();
+        for (const auto& pred : ctx.output.pred_range()) {
+            if (defFilter ||
+                (pred.cond.sign() && pred.user && Potassco::atom(Potassco::lit(pred.user)) < Asp::PrgNode::no_node)) {
+                act(pred.cond, pref, pref);
+            }
+        }
+        for (auto v : vars) {
+            if (ctx.varInfo(v).atom()) {
+                act(posLit(v), pref, pref);
+            }
+        }
+    }
+    if ((defFilter & HeuParams::pref_min) != 0 && ctx.minimizeNoCreate()) {
+        Weight_t lastW = -1;
+        uint32_t strat = HeuParams::pref_show;
+        for (const auto& wl : *ctx.minimizeNoCreate()) {
+            if (wl.weight != lastW && strat > HeuParams::pref_disj) {
+                --strat;
+                lastW = wl.weight;
+            }
+            act(wl.lit, HeuParams::pref_min, strat);
+        }
+    }
+    const uint32_t gs =
+        static_cast(HeuParams::pref_scc | HeuParams::pref_hcc | HeuParams::pref_disj) & defFilter;
+    if (ctx.sccGraph.get() && gs && ((gs & HeuParams::pref_scc) != 0 || ctx.sccGraph->numNonHcfs())) {
+        for (uint32_t i : irange(ctx.sccGraph->numAtoms())) {
+            const PrgDepGraph::AtomNode& a = ctx.sccGraph->getAtom(i);
+            if ((gs & HeuParams::pref_disj) != 0 && a.inDisjunctive()) {
+                act(a.lit, HeuParams::pref_disj, 3u);
+            }
+            else if ((gs & HeuParams::pref_hcc) != 0 && a.inNonHcf()) {
+                act(a.lit, HeuParams::pref_hcc, 2u);
+            }
+            else if ((gs & HeuParams::pref_scc) != 0) {
+                act(a.lit, HeuParams::pref_scc, 1u);
+            }
+        }
+    }
+}
+bool                  DomainTable::empty() const { return entries_.empty(); }
+uint32_t              DomainTable::size() const { return size32(entries_); }
 DomainTable::iterator DomainTable::begin() const { return entries_.begin(); }
-DomainTable::iterator DomainTable::end()   const { return entries_.end(); }
+DomainTable::iterator DomainTable::end() const { return entries_.end(); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // SharedContext::Minimize
 /////////////////////////////////////////////////////////////////////////////////////////
 struct SharedContext::Minimize {
-	typedef SingleOwnerPtr ProductPtr;
-	void add(weight_t p, const WeightLiteral& lit) {
-		builder.add(p, lit);
-	}
-	bool reset() const {
-		if (product.get()) { product->resetBounds(); }
-		return true;
-	}
-	SharedMinimizeData* get(SharedContext& ctx) {
-		if (builder.empty()) { return product.get(); }
-		if (product.get()) {
-			builder.add(*product);
-			product = 0;
-		}
-		return (product = builder.build(ctx)).get();
-	}
-	MinimizeBuilder builder;
-	ProductPtr      product;
+    using ProductPtr = std::unique_ptr;
+    void               add(Weight_t p, const WeightLiteral& lit) { builder.add(p, lit); }
+    [[nodiscard]] bool reset() const {
+        if (product.get()) {
+            product->resetBounds();
+        }
+        return true;
+    }
+    SharedMinimizeData* get(SharedContext& ctx) {
+        if (builder.empty()) {
+            return product.get();
+        }
+        if (product) {
+            builder.add(*product);
+            product = nullptr;
+        }
+        product.reset(builder.build(ctx));
+        return product.get();
+    }
+    MinimizeBuilder builder;
+    ProductPtr      product;
 };
 /////////////////////////////////////////////////////////////////////////////////////////
 // SharedContext
 /////////////////////////////////////////////////////////////////////////////////////////
-static BasicSatConfig config_def_s;
-SharedContext::SharedContext()
-	: mini_(0), progress_(0), lastTopLevel_(0) {
-	static_assert(sizeof(Share) == sizeof(uint32_t), "unexpected size");
-	// sentinel always present
-	setFrozen(addVar(Var_t::Atom, 0), true);
-	stats_.vars.num = 0;
-	config_ = &config_def_s;
-	config_.release();
-	pushSolver();
-}
-uint32 SharedContext::defaultDomPref() const {
-	const SolverParams& sp = config_->solver(0);
-	return sp.heuId == Heuristic_t::Domain && sp.heuristic.domMod != HeuParams::mod_none
-		? sp.heuristic.domPref
-		: set_bit(0u, 31);
-}
-bool SharedContext::ok() const { return master()->decisionLevel() || !master()->hasConflict() || master()->hasStopConflict(); }
-void SharedContext::enableStats(uint32 lev) {
-	if (lev > 0) { master()->stats.enableExtended(); }
+static BasicSatConfig g_config_def;
+SharedContext::SharedContext() : mini_(nullptr), progress_(nullptr), lastTopLevel_(0) {
+    static_assert(sizeof(Share) == sizeof(uint32_t), "unexpected size");
+    // sentinel always present
+    setFrozen(addVar(VarType::atom, 0), true);
+    stats_.vars.num = 0;
+    config_         = &g_config_def;
+    pushSolver();
+}
+uint32_t SharedContext::defaultDomPref() const {
+    const SolverParams& sp = config_->solver(0);
+    return sp.heuId == HeuristicType::domain && sp.heuristic.domMod != HeuParams::mod_none ? sp.heuristic.domPref
+                                                                                           : Potassco::set_bit(0u, 31);
+}
+bool SharedContext::ok() const {
+    return master()->decisionLevel() || not master()->hasConflict() || master()->hasStopConflict();
+}
+void SharedContext::enableStats(uint32_t lev) {
+    if (lev > 0) {
+        master()->stats.enableExtended();
+    }
 }
 SharedContext::~SharedContext() {
-	while (!solvers_.empty()) { delete solvers_.back(); solvers_.pop_back(); }
-	delete mini_;
+    while (not solvers_.empty()) {
+        delete solvers_.back();
+        solvers_.pop_back();
+    }
 }
 
 void SharedContext::reset() {
-	this->~SharedContext();
-	new (this) SharedContext();
+    this->~SharedContext();
+    new (this) SharedContext();
 }
 
-void SharedContext::setConcurrency(uint32 n, ResizeMode mode) {
-	if (n <= 1) { share_.count = 1; }
-	else        { share_.count = n; solvers_.reserve(n); }
-	while (solvers_.size() < share_.count && (mode & resize_push) != 0u) {
-		pushSolver();
-	}
-	while (solvers_.size() > share_.count && (mode & resize_pop) != 0u) {
-		delete solvers_.back();
-		solvers_.pop_back();
-	}
-	if ((share_.shareM & ContextParams::share_auto) != 0) {
-		setShareMode(ContextParams::share_auto);
-	}
+void SharedContext::setConcurrency(uint32_t n, ResizeMode mode) {
+    if (n <= 1) {
+        share_.count = 1;
+    }
+    else {
+        share_.count = n;
+        solvers_.reserve(n);
+    }
+    while (solvers_.size() < share_.count && Potassco::test(mode, resize_push)) { pushSolver(); }
+    while (solvers_.size() > share_.count && Potassco::test(mode, resize_pop)) {
+        delete solvers_.back();
+        solvers_.pop_back();
+    }
+    if ((share_.shareM & ContextParams::share_auto) != 0) {
+        setShareMode(ContextParams::share_auto);
+    }
 }
 
 void SharedContext::setShareMode(ContextParams::ShareMode m) {
-	if ( (share_.shareM = static_cast(m)) == ContextParams::share_auto && share_.count > 1) {
-		share_.shareM |= static_cast(ContextParams::share_all);
-	}
+    if (share_.shareM = static_cast(m); m == ContextParams::share_auto && share_.count > 1) {
+        share_.shareM |= static_cast(ContextParams::share_all);
+    }
 }
-void SharedContext::setShortMode(ContextParams::ShortMode m) {
-	share_.shortM = static_cast(m);
+void SharedContext::setShortMode(ContextParams::ShortMode m, ContextParams::ShortSimpMode x) {
+    share_.shortM = static_cast(m);
+    btig_.setSimpMode(x);
 }
 
-void SharedContext::setPreproMode(uint32 m, bool b) {
-	share_.satPreM &= ~m;
-	if (b) { share_.satPreM |= m; }
+void SharedContext::setPreproMode(uint32_t m, bool b) {
+    share_.satPreM &= ~m;
+    if (b) {
+        share_.satPreM |= m;
+    }
 }
 
 Solver& SharedContext::pushSolver() {
-	uint32 id    = (uint32)solvers_.size();
-	share_.count = std::max(share_.count, id + 1);
-	Solver* s    = new Solver(this, id);
-	solvers_.push_back(s);
-	return *s;
+    auto id      = size32(solvers_);
+    share_.count = std::max(share_.count, id + 1);
+    auto* s      = new Solver(this, id);
+    solvers_.push_back(s);
+    return *s;
 }
 
-void SharedContext::setConfiguration(Configuration* c, Ownership_t::Type ownership) {
-	bool own = ownership == Ownership_t::Acquire;
-	if (c == 0) { c = &config_def_s; own = false; }
-	report(Event::subsystem_facade);
-	if (config_.get() != c) {
-		config_ = c;
-		if (!own) config_.release();
-		config_->prepare(*this);
-		const ContextParams& opts = config_->context();
-		setShareMode(static_cast(opts.shareMode));
-		setShortMode(static_cast(opts.shortMode));
-		share_.seed   = opts.seed;
-		if (satPrepro.get() == 0 && opts.satPre.type != SatPreParams::sat_pre_no) {
-			satPrepro.reset(SatPreParams::create(opts.satPre));
-		}
-		enableStats(opts.stats);
-		// force update on next call to Solver::startInit()
-		for (uint32 i = 0; i != solvers_.size(); ++i) {
-			solvers_[i]->resetConfig();
-		}
-	}
-	else if (own != config_.is_owner()) {
-		if (own) config_.acquire();
-		else     config_.release();
-	}
+void SharedContext::setConfiguration(Configuration* c) {
+    auto* nc = c ? c : &g_config_def;
+    report(Event::subsystem_facade);
+    auto configChanged = config_ != nc;
+    config_            = nc;
+    if (configChanged) {
+        config_->prepare(*this);
+        const ContextParams& opts = config_->context();
+        setShareMode(static_cast(opts.shareMode));
+        setShortMode(static_cast(opts.shortMode),
+                     static_cast(opts.shortSimp));
+        share_.seed = opts.seed;
+        if (satPrepro.get() == nullptr && opts.satPre.type != SatPreParams::sat_pre_no) {
+            satPrepro.reset(SatPreParams::create(opts.satPre));
+        }
+        enableStats(opts.stats);
+        // force update on next call to Solver::startInit()
+        for (auto* s : solvers_) { s->resetConfig(); }
+    }
 }
 
 bool SharedContext::unfreeze() {
-	if (frozen()) {
-		share_.frozen = 0;
-		share_.winner = 0;
-		heuristic.assume = 0;
-		btig_.markShared(false);
-		return master()->popRootLevel(master()->rootLevel())
-		  &&   btig_.propagate(*master(), lit_true()) // any newly learnt facts
-		  &&   unfreezeStep()
-		  &&   (!mini_ || mini_->reset());
-	}
-	return true;
+    if (frozen()) {
+        share_.frozen    = 0;
+        share_.winner    = 0;
+        heuristic.assume = nullptr;
+        btig_.markShared(false);
+        return master()->popRootLevel(master()->rootLevel()) &&
+               btig_.propagate(*master(), lit_true) // any newly learnt facts
+               && unfreezeStep() && (not mini_ || mini_->reset());
+    }
+    return true;
 }
 
 bool SharedContext::unfreezeStep() {
-	POTASSCO_ASSERT(!frozen());
-	Var tag = step_.var();
-	for (SolverVec::size_type i = solvers_.size(); i--;) {
-		Solver& s = *solvers_[i];
-		if (!s.validVar(tag)) { continue; }
-		s.endStep(lastTopLevel_, configuration()->solver(s.id()));
-	}
-	if (tag) {
-		varInfo_[tag] = VarInfo();
-		step_ = lit_false();
-		popVars(1);
-		++stats_.vars.num;
-	}
-	return !master()->hasConflict();
+    POTASSCO_ASSERT(not frozen());
+    auto tag = step_.var();
+    for (auto i = size32(solvers_); i--;) {
+        Solver& s = *solvers_[i];
+        if (not s.validVar(tag)) {
+            continue;
+        }
+        s.endStep(lastTopLevel_, configuration()->solver(s.id()));
+    }
+    if (tag) {
+        varInfo_[tag] = VarInfo();
+        step_         = lit_false;
+        popVars(1);
+        ++stats_.vars.num;
+    }
+    return not master()->hasConflict();
 }
 
-Var SharedContext::addVars(uint32 nVars, VarType t, uint8 flags) {
-	flags &= ~3u;
-	flags |= VarInfo::flags(t);
-	varInfo_.insert(varInfo_.end(), nVars, VarInfo(flags));
-	stats_.vars.num += nVars;
-	return static_cast(varInfo_.size() - nVars);
+Var_t SharedContext::addVars(uint32_t nVars, VarType t, uint8_t flags) {
+    static constexpr auto flags_for = [](VarType in) {
+        switch (in) {
+            default             : return static_cast(0);
+            case VarType::body  : return VarInfo::flag_body;
+            case VarType::hybrid: return VarInfo::flag_eq;
+        }
+    };
+    Potassco::store_clear_mask(flags, VarInfo::flag_pos | VarInfo::flag_neg);
+    Potassco::store_set_mask(flags, flags_for(t));
+    varInfo_.insert(varInfo_.end(), nVars, VarInfo(flags));
+    stats_.vars.num += nVars;
+    return static_cast(varInfo_.size() - nVars);
 }
 
-void SharedContext::popVars(uint32 nVars) {
-	POTASSCO_REQUIRE(!frozen(), "Cannot pop vars from frozen program");
-	POTASSCO_CHECK(nVars <= numVars(), EINVAL, POTASSCO_FUNC_NAME);
-	uint32 newVars = numVars() - nVars;
-	uint32 comVars = master()->numVars();
-	if (newVars >= comVars) {
-		// vars not yet committed
-		varInfo_.erase(varInfo_.end() - nVars, varInfo_.end());
-		stats_.vars.num -= nVars;
-	}
-	else {
-		for (Var v = numVars(); v && nVars; --nVars, --v) {
-			stats_.vars.eliminated -= eliminated(v);
-			stats_.vars.frozen -= varInfo(v).frozen();
-			--stats_.vars.num;
-			varInfo_.pop_back();
-		}
-		btig_.resize((numVars()+1)<<1);
-		for (SolverVec::size_type i = solvers_.size(); i--;) {
-			solvers_[i]->updateVars();
-		}
-		lastTopLevel_ = std::min(lastTopLevel_, master()->assign_.front);
-	}
+void SharedContext::popVars(uint32_t nVars) {
+    POTASSCO_CHECK_PRE(not frozen(), "Cannot pop vars from frozen program");
+    POTASSCO_CHECK(nVars <= numVars(), EINVAL, POTASSCO_FUNC_NAME);
+    uint32_t newVars = numVars() - nVars;
+    uint32_t comVars = master()->numVars();
+    if (newVars >= comVars) {
+        // vars not yet committed
+        varInfo_.erase(varInfo_.end() - nVars, varInfo_.end());
+        stats_.vars.num -= nVars;
+    }
+    else {
+        for (Var_t v = numVars(); v && nVars; --nVars, --v) {
+            stats_.vars.eliminated -= eliminated(v);
+            stats_.vars.frozen     -= varInfo(v).frozen();
+            --stats_.vars.num;
+            varInfo_.pop_back();
+        }
+        btig_.resize((numVars() + 1) << 1);
+        for (auto i = size32(solvers_); i--;) { solvers_[i]->updateVars(); }
+        lastTopLevel_ = std::min(lastTopLevel_, master()->assign_.front);
+    }
 }
 
 void SharedContext::setSolveMode(SolveMode m) { share_.solveM = m; }
-void SharedContext::requestStepVar() { if (step_ == lit_true()) { step_ = lit_false(); } }
-void SharedContext::setFrozen(Var v, bool b) {
-	assert(validVar(v));
-	if (v && b != varInfo_[v].has(VarInfo::Frozen)) {
-		varInfo_[v].toggle(VarInfo::Frozen);
-		b ? ++stats_.vars.frozen : --stats_.vars.frozen;
-	}
+void SharedContext::requestStepVar() {
+    if (step_ == lit_true) {
+        step_ = lit_false;
+    }
+}
+void SharedContext::setFrozen(Var_t v, bool b) {
+    assert(validVar(v));
+    if (v && b != varInfo_[v].has(VarInfo::flag_frozen)) {
+        varInfo_[v].toggle(VarInfo::flag_frozen);
+        b ? ++stats_.vars.frozen : --stats_.vars.frozen;
+    }
 }
 
-bool SharedContext::eliminated(Var v) const {
-	assert(validVar(v));
-	return !master()->assign_.valid(v);
+bool SharedContext::eliminated(Var_t v) const {
+    assert(validVar(v));
+    return not master()->assign_.valid(v);
 }
 
-void SharedContext::eliminate(Var v) {
-	assert(validVar(v) && !frozen() && master()->decisionLevel() == 0);
-	if (!eliminated(v)) {
-		++stats_.vars.eliminated;
-		// eliminate var from assignment - no longer a decision variable!
-		master()->assign_.eliminate(v);
-	}
+void SharedContext::eliminate(Var_t v) {
+    assert(validVar(v) && not frozen() && master()->decisionLevel() == 0);
+    if (not eliminated(v)) {
+        ++stats_.vars.eliminated;
+        // eliminate var from assignment - no longer a decision variable!
+        master()->assign_.eliminate(v);
+    }
 }
 
 Literal SharedContext::addStepLit() {
-	VarInfo nv; nv.set(VarInfo::Frozen);
-	varInfo_.push_back(nv);
-	btig_.resize((numVars() + 1) << 1);
-	return posLit(master()->pushAuxVar());
-}
-Solver& SharedContext::startAddConstraints(uint32 constraintGuess) {
-	if (!unfreeze()) { return *master(); }
-	btig_.resize((numVars() + 1 + uint32(step_ == lit_false() || solveMode() == solve_multi))<<1);
-	master()->startInit(constraintGuess, configuration()->solver(0));
-	return *master();
-}
-bool SharedContext::addUnary(Literal x) {
-	POTASSCO_REQUIRE(!frozen() || !isShared());
-	master()->acquireProblemVar(x.var());
-	return master()->force(x);
-}
-bool SharedContext::addBinary(Literal x, Literal y) {
-	POTASSCO_REQUIRE(allowImplicit(Constraint_t::Static));
-	Literal lits[2] = {x, y};
-	return ClauseCreator::create(*master(), ClauseRep(lits, 2), ClauseCreator::clause_force_simplify);
-}
-bool SharedContext::addTernary(Literal x, Literal y, Literal z) {
-	POTASSCO_REQUIRE(allowImplicit(Constraint_t::Static));
-	Literal lits[3] = {x, y, z};
-	return ClauseCreator::create(*master(), ClauseRep(lits, 3), ClauseCreator::clause_force_simplify);
-}
-void SharedContext::add(Constraint* c) {
-	POTASSCO_REQUIRE(!frozen());
-	master()->add(c);
-}
-void SharedContext::addMinimize(WeightLiteral x, weight_t p) {
-	if (!mini_) { mini_ = new Minimize(); }
-	mini_->add(p, x);
-}
-bool SharedContext::hasMinimize() const {
-	return mini_ != 0;
-}
-void SharedContext::removeMinimize() {
-	delete mini_;
-	mini_ = 0;
-}
-SharedMinimizeData* SharedContext::minimize() {
-	return mini_ ? mini_->get(*this) : 0;
-}
-SharedMinimizeData* SharedContext::minimizeNoCreate() const {
-	return mini_ ? mini_->product.get() : 0;
-}
-int SharedContext::addImp(ImpGraph::ImpType t, const Literal* lits, ConstraintType ct) {
-	if (!allowImplicit(ct)) { return -1; }
-	bool learnt = ct != Constraint_t::Static;
-	if (!learnt && !frozen() && satPrepro.get()) {
-		satPrepro->addClause(lits, static_cast(t));
-		return 1;
-	}
-	return int(btig_.add(t, learnt, lits));
+    VarInfo nv;
+    nv.set(VarInfo::flag_frozen);
+    varInfo_.push_back(nv);
+    btig_.resize((numVars() + 1) << 1);
+    return posLit(master()->pushAuxVar());
+}
+Solver& SharedContext::startAddConstraints(uint32_t constraintGuess) {
+    if (not unfreeze()) {
+        return *master();
+    }
+    btig_.resize((numVars() + 1 + static_cast(step_ == lit_false || solveMode() == solve_multi)) << 1);
+    master()->startInit(constraintGuess, configuration()->solver(0));
+    return *master();
+}
+bool SharedContext::addUnary(Literal x) { // NOLINT(readability-make-member-function-const)
+    POTASSCO_CHECK_PRE(not frozen() || not isShared());
+    master()->acquireProblemVar(x.var());
+    return master()->force(x);
+}
+bool SharedContext::addBinary(Literal x, Literal y) { // NOLINT(readability-make-member-function-const)
+    POTASSCO_CHECK_PRE(allowImplicit(ConstraintType::static_));
+    Literal lits[2] = {x, y};
+    return ClauseCreator::create(*master(), ClauseRep::create(lits), ClauseCreator::clause_force_simplify).ok();
+}
+bool SharedContext::addTernary(Literal x, Literal y, Literal z) { // NOLINT(readability-make-member-function-const)
+    POTASSCO_CHECK_PRE(allowImplicit(ConstraintType::static_));
+    Literal lits[3] = {x, y, z};
+    return ClauseCreator::create(*master(), ClauseRep::create(lits), ClauseCreator::clause_force_simplify).ok();
+}
+void SharedContext::add(Constraint* c) { // NOLINT(readability-make-member-function-const)
+    POTASSCO_CHECK_PRE(not frozen());
+    master()->add(c);
+}
+void SharedContext::addMinimize(WeightLiteral x, Weight_t p) {
+    if (not mini_) {
+        mini_ = std::make_unique();
+    }
+    mini_->add(p, x);
+}
+bool                SharedContext::hasMinimize() const { return mini_ != nullptr; }
+void                SharedContext::removeMinimize() { mini_.reset(); }
+SharedMinimizeData* SharedContext::minimize() { return mini_ ? mini_->get(*this) : nullptr; }
+SharedMinimizeData* SharedContext::minimizeNoCreate() const { return mini_ ? mini_->product.get() : nullptr; }
+int                 SharedContext::addImp(LitView lits, ConstraintType ct) {
+    if (not allowImplicit(ct)) {
+        return -1;
+    }
+    bool learnt = ct != ConstraintType::static_;
+    if (not learnt && not frozen() && satPrepro.get()) {
+        satPrepro->addClause(lits);
+        return 1;
+    }
+    return static_cast(btig_.add(lits, learnt));
 }
 
-uint32 SharedContext::numConstraints() const { return numBinary() + numTernary() + sizeVec(master()->constraints_); }
+uint32_t SharedContext::numConstraints() const { return numBinary() + numTernary() + size32(master()->constraints_); }
 
 bool SharedContext::endInit(bool attachAll) {
-	assert(!frozen());
-	report(Event::subsystem_prepare);
-	initStats(*master());
-	heuristic.simplify();
-	SatPrePtr temp;
-	satPrepro.swap(temp);
-	bool ok = !master()->hasConflict() && master()->preparePost() && (!temp.get() || temp->preprocess(*this)) && master()->endInit();
-	satPrepro.swap(temp);
-	master()->dbIdx_ = (uint32)master()->constraints_.size();
-	lastTopLevel_    = (uint32)master()->assign_.front;
-	stats_.constraints.other  = sizeVec(master()->constraints_);
-	stats_.constraints.binary = btig_.numBinary();
-	stats_.constraints.ternary= btig_.numTernary();
-	stats_.acycEdges          = extGraph.get() ? extGraph->edges() : 0;
-	stats_.complexity         = std::max(stats_.complexity, problemComplexity());
-	if (ok && step_ == lit_false()) {
-		step_ = addStepLit();
-	}
-	btig_.markShared(concurrency() > 1);
-	share_.frozen = 1;
-	if (ok && master()->getPost(PostPropagator::priority_class_general))
-		ok = master()->propagate() && master()->simplify();
-	for (uint32 i = ok && attachAll ? 1 : concurrency(); i != concurrency(); ++i) {
-		if (!hasSolver(i)) { pushSolver(); }
-		if (!attach(i))    { ok = false; break; }
-	}
-	return ok || (detach(*master(), false), master()->setStopConflict(), false);
+    assert(not frozen());
+    report(Event::subsystem_prepare);
+    initStats(*master());
+    heuristic.simplify();
+    SatPrePtr temp = std::move(satPrepro);
+    bool      ok   = not master()->hasConflict() && master()->preparePost() && (not temp || temp->preprocess(*this)) &&
+              master()->endInit();
+    satPrepro                  = std::move(temp);
+    master()->dbIdx_           = size32(master()->constraints_);
+    lastTopLevel_              = master()->assign_.front;
+    stats_.constraints.other   = size32(master()->constraints_);
+    stats_.constraints.binary  = btig_.numBinary();
+    stats_.constraints.ternary = btig_.numTernary();
+    stats_.acycEdges           = extGraph.get() ? extGraph->edges() : 0;
+    stats_.complexity          = std::max(stats_.complexity, problemComplexity());
+    if (ok && step_ == lit_false) {
+        step_ = addStepLit();
+    }
+    btig_.markShared(concurrency() > 1);
+    share_.frozen = 1;
+    if (ok && master()->getPost(PostPropagator::priority_class_general)) {
+        ok = master()->propagate() && master()->simplify();
+    }
+    if (ok && attachAll) {
+        for (uint32_t i : irange(1u, concurrency())) {
+            if (not hasSolver(i)) {
+                pushSolver();
+            }
+            if (not attach(i)) {
+                ok = false;
+                break;
+            }
+        }
+    }
+    return ok || (detach(*master(), false), master()->setStopConflict(), false);
 }
 
 bool SharedContext::attach(Solver& other) {
-	assert(frozen() && other.shared_ == this);
-	if (other.validVar(step_.var())) {
-		if (!other.popRootLevel(other.rootLevel())){ return false; }
-		if (&other == master())                    { return true;  }
-	}
-	initStats(other);
-	// 1. clone vars & assignment
-	Var lastVar = other.numVars();
-	other.startInit(static_cast(master()->constraints_.size()), configuration()->solver(other.id()));
-	if (other.hasConflict()) { return false; }
-	Antecedent null;
-	for (LitVec::size_type i = 0, end = master()->trail().size(); i != end; ++i) {
-		Literal x = master()->trail()[i];
-		if (master()->auxVar(x.var())) { continue;  }
-		if (!other.force(x, null))     { return false; }
-	}
-	for (Var v = satPrepro.get() ? lastVar+1 : varMax, end = master()->numVars(); v <= end; ++v) {
-		if (eliminated(v) && other.value(v) == value_free) {
-			other.assign_.eliminate(v);
-		}
-	}
-	if (other.constraints_.empty()) { other.lastSimp_ = master()->lastSimp_; }
-	// 2. clone & attach constraints
-	if (!other.cloneDB(master()->constraints_)) { return false; }
-	Constraint* c = master()->enumerationConstraint();
-	other.setEnumerationConstraint( c ? c->cloneAttach(other) : 0 );
-	// 3. endInit
-	return (other.preparePost() && other.endInit()) || (detach(other, false), false);
+    assert(frozen() && other.shared_ == this);
+    if (other.validVar(step_.var())) {
+        if (not other.popRootLevel(other.rootLevel())) {
+            return false;
+        }
+        if (&other == master()) {
+            return true;
+        }
+    }
+    initStats(other);
+    // 1. clone vars & assignment
+    Var_t lastVar = other.numVars();
+    other.startInit(size32(master()->constraints_), configuration()->solver(other.id()));
+    if (other.hasConflict()) {
+        return false;
+    }
+    for (auto x : master()->trailView()) {
+        if (master()->auxVar(x.var())) {
+            continue;
+        }
+        if (Antecedent null; not other.force(x, null)) {
+            return false;
+        }
+    }
+    for (Var_t v = satPrepro.get() ? lastVar + 1 : var_max, end = master()->numVars(); v <= end; ++v) {
+        if (eliminated(v) && other.value(v) == value_free) {
+            other.assign_.eliminate(v);
+        }
+    }
+    if (other.constraints_.empty()) {
+        other.lastSimp_ = master()->lastSimp_;
+    }
+    // 2. clone & attach constraints
+    if (not other.cloneDB(master()->constraints_)) {
+        return false;
+    }
+    Constraint* c = master()->enumerationConstraint();
+    other.setEnumerationConstraint(c ? c->cloneAttach(other) : nullptr);
+    // 3. endInit
+    return (other.preparePost() && other.endInit()) || (detach(other, false), false);
 }
 
 void SharedContext::detach(Solver& s, bool reset) {
-	assert(s.shared_ == this);
-	if (reset) { s.reset(); }
-	s.setEnumerationConstraint(0);
-	s.popAuxVar();
+    assert(s.shared_ == this);
+    if (reset) {
+        s.reset();
+    }
+    s.setEnumerationConstraint(nullptr);
+    s.popAuxVar();
 }
 void SharedContext::initStats(Solver& s) const {
-	s.stats.enable(master()->stats);
-	s.stats.reset();
+    s.stats.enable(master()->stats);
+    s.stats.reset();
 }
-SolverStats& SharedContext::solverStats(uint32 sId) const {
-	POTASSCO_ASSERT(hasSolver(sId), "solver id out of range");
-	return solver(sId)->stats;
+SolverStats& SharedContext::solverStats(uint32_t sId) const {
+    POTASSCO_ASSERT(hasSolver(sId), "solver id out of range");
+    return solver(sId)->stats;
 }
 const SolverStats& SharedContext::accuStats(SolverStats& out) const {
-	for (uint32 i = 0; i != solvers_.size(); ++i) {
-		out.accu(solvers_[i]->stats, true);
-	}
-	return out;
+    for (auto s : solvers_) { out.accu(s->stats, true); }
+    return out;
 }
 void SharedContext::warn(const char* what) const {
-	if (progress_) {
-		progress_->dispatch(LogEvent(progress_->active(), Event::verbosity_quiet, LogEvent::Warning, 0, what));
-	}
+    if (progress_) {
+        progress_->dispatch(LogEvent(progress_->active(), Event::verbosity_quiet, LogEvent::warning, nullptr, what));
+    }
+}
+POTASSCO_ATTRIBUTE_FORMAT(2, 3) void SharedContext::warnFmt(const char* fmt, ...) const {
+    if (progress_ && fmt && *fmt) {
+        va_list args;
+        va_start(args, fmt);
+        char msg[1024];
+        std::vsnprintf(msg, std::size(msg), fmt, args);
+        va_end(args);
+        warn(msg);
+    }
 }
 void SharedContext::report(const char* what, const Solver* s) const {
-	if (progress_) {
-		progress_->dispatch(LogEvent(progress_->active(), Event::verbosity_high, LogEvent::Message, s, what));
-	}
+    if (progress_) {
+        progress_->dispatch(LogEvent(progress_->active(), Event::verbosity_high, LogEvent::message, s, what));
+    }
 }
 void SharedContext::report(Event::Subsystem sys) const {
-	if (progress_ && progress_->setActive(sys)) {
-		const char* m = "";
-		Event::Verbosity v = Event::verbosity_high;
-		switch(sys) {
-			default: return;
-			case Event::subsystem_load:    m = "Reading";       break;
-			case Event::subsystem_prepare: m = "Preprocessing"; break;
-			case Event::subsystem_solve:   m = "Solving"; v = Event::verbosity_low; break;
-		}
-		progress_->onEvent(LogEvent(sys, v, LogEvent::Message, 0, m));
-	}
-}
-void SharedContext::simplify(LitVec::size_type trailStart, bool shuffle) {
-	if (!isShared() && trailStart < master()->trail().size()) {
-		for (const LitVec& trail = master()->trail(); trailStart != trail.size(); ++trailStart) {
-			Literal p = trail[trailStart];
-			if (p.id() < btig_.size()) { btig_.removeTrue(*master(), p); }
-		}
-	}
-	Solver::ConstraintDB& db = master()->constraints_;
-	if (concurrency() == 1 || master()->dbIdx_ == 0) {
-		Clasp::simplifyDB(*master(), db, shuffle);
-	}
-	else {
-		uint32 rem = 0;
-		for (Solver::ConstraintDB::size_type i = 0, end = db.size(); i != end; ++i) {
-			Constraint* c = db[i];
-			if (c->simplify(*master(), shuffle)) { c->destroy(master(), false); db[i] = 0; ++rem; }
-		}
-		if (rem) {
-			for (SolverVec::size_type s = 1; s != solvers_.size(); ++s) {
-				Solver& x = *solvers_[s];
-				POTASSCO_ASSERT(x.dbIdx_ <= db.size(), "Invalid DB idx!");
-				if      (x.dbIdx_ == db.size()) { x.dbIdx_ -= rem; }
-				else if (x.dbIdx_ != 0)         { x.dbIdx_ -= (uint32)std::count_if(db.begin(), db.begin()+x.dbIdx_, IsNull()); }
-			}
-			db.erase(std::remove_if(db.begin(), db.end(), IsNull()), db.end());
-		}
-	}
-	master()->dbIdx_ = sizeVec(db);
-}
-void SharedContext::removeConstraint(uint32 idx, bool detach) {
-	Solver::ConstraintDB& db = master()->constraints_;
-	POTASSCO_REQUIRE(idx < db.size());
-	Constraint* c = db[idx];
-	for (SolverVec::size_type s = 1; s != solvers_.size(); ++s) {
-		Solver& x = *solvers_[s];
-		x.dbIdx_ -= (idx < x.dbIdx_);
-	}
-	db.erase(db.begin()+idx);
-	master()->dbIdx_ = sizeVec(db);
-	c->destroy(master(), detach);
+    if (progress_ && progress_->setActive(sys)) {
+        const char*      m;
+        Event::Verbosity v = Event::verbosity_high;
+        switch (sys) {
+            default                      : return;
+            case Event::subsystem_load   : m = "Reading"; break;
+            case Event::subsystem_prepare: m = "Preprocessing"; break;
+            case Event::subsystem_solve:
+                m = "Solving";
+                v = Event::verbosity_low;
+                break;
+        }
+        progress_->onEvent(LogEvent(sys, v, LogEvent::message, nullptr, m));
+    }
+}
+void SharedContext::simplify(LitView assigned, bool shuffle) {
+    if (not isShared() && not assigned.empty()) {
+        for (auto p : assigned) {
+            if (p.id() < btig_.size()) {
+                btig_.removeTrue(*master(), p);
+            }
+        }
+    }
+    auto& db = master()->constraints_;
+    if (concurrency() == 1 || master()->dbIdx_ == 0) {
+        simplifyDB(*master(), db, shuffle);
+    }
+    else {
+        uint32_t rem = 0;
+        for (Constraint*& con : db) {
+            if (con->simplify(*master(), shuffle)) {
+                con->destroy(master(), false);
+                con = nullptr;
+                ++rem;
+            }
+        }
+        if (rem) {
+            constexpr auto isNull = [](const Constraint* c) { return c == nullptr; };
+            for (auto* s : drop(solvers_, 1u)) {
+                POTASSCO_ASSERT(s->dbIdx_ <= db.size(), "Invalid DB idx!");
+                if (s->dbIdx_ == db.size()) {
+                    s->dbIdx_ -= rem;
+                }
+                else if (s->dbIdx_ != 0) {
+                    s->dbIdx_ -= static_cast(std::count_if(db.begin(), db.begin() + s->dbIdx_, isNull));
+                }
+            }
+            erase_if(db, isNull);
+        }
+    }
+    master()->dbIdx_ = size32(db);
+}
+void SharedContext::removeConstraint(uint32_t idx, bool detach) {
+    Solver::ConstraintDB& db = master()->constraints_;
+    POTASSCO_CHECK_PRE(idx < db.size());
+    Constraint* c = db[idx];
+    for (auto* s : drop(solvers_, 1u)) { s->dbIdx_ -= (idx < s->dbIdx_); }
+    db.erase(db.begin() + idx);
+    master()->dbIdx_ = size32(db);
+    c->destroy(master(), detach);
 }
 
-
-uint32 SharedContext::problemComplexity() const {
-	if (isExtended()) {
-		uint32 r = numBinary() + numTernary();
-		for (uint32 i = 0; i != master()->constraints_.size(); ++i) {
-			r += master()->constraints_[i]->estimateComplexity(*master());
-		}
-		return r;
-	}
-	return numConstraints();
+uint32_t SharedContext::problemComplexity() const {
+    if (isExtended()) {
+        uint32_t r = numBinary() + numTernary();
+        for (const auto* constraint : master()->constraints_) { r += constraint->estimateComplexity(*master()); }
+        return r;
+    }
+    return numConstraints();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Distributor
 /////////////////////////////////////////////////////////////////////////////////////////
-Distributor::Distributor(const Policy& p) : policy_(p)  {}
-Distributor::~Distributor() {}
+Distributor::Distributor(const Policy& p) : policy_(p) {}
+Distributor::~Distributor() = default;
 
-}
+} // namespace Clasp
diff --git a/src/solve_algorithms.cpp b/src/solve_algorithms.cpp
index 47dc77c..f9e1709 100644
--- a/src/solve_algorithms.cpp
+++ b/src/solve_algorithms.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,470 +22,595 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
-#include 
+#include 
 #include 
+
+#include 
+
+#include 
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // Basic solve
 /////////////////////////////////////////////////////////////////////////////////////////
 struct BasicSolve::State {
-	typedef BasicSolveEvent EventType;
-	typedef SingleOwnerPtr BlockPtr;
-	typedef SingleOwnerPtr DynPtr;
-	State(Solver& s, const SolveParams& p);
-	ValueRep solve(Solver& s, const SolveParams& p, SolveLimits& lim);
-	uint64           dbGrowNext;
-	double           dbMax;
-	double           dbHigh;
-	ScheduleStrategy dbRed;
-	BlockPtr         rsBlock;
-	DynPtr           dynRestart;
-	uint32           nRestart;
-	uint32           nGrow;
-	uint32           dbRedInit;
-	uint32           dbPinned;
-	uint32           rsShuffle;
-	bool             resetState;
+    using EventType = BasicSolveEvent;
+    using BlockPtr  = std::unique_ptr;
+    using DynPtr    = std::unique_ptr;
+    State(Solver& s, const SolveParams& p);
+    Val_t            solve(Solver& s, const SolveParams& p, SolveLimits& lim);
+    uint64_t         dbGrowNext;
+    double           dbMax;
+    double           dbHigh;
+    ScheduleStrategy dbRed;
+    BlockPtr         rsBlock;
+    DynPtr           dynRestart;
+    uint32_t         nRestart{0};
+    uint32_t         nGrow{0};
+    uint32_t         dbRedInit;
+    uint32_t         dbPinned{0};
+    uint32_t         rsShuffle;
+    bool             resetState{false};
 };
 
-BasicSolve::BasicSolve(Solver& s, const SolveLimits& lim) : solver_(&s), params_(&s.searchConfig()), limits_(lim), state_(0) {}
+BasicSolve::BasicSolve(Solver& s, const SolveLimits& lim) : BasicSolve(s, s.searchConfig(), lim) {}
 BasicSolve::BasicSolve(Solver& s, const SolveParams& p, const SolveLimits& lim)
-	: solver_(&s)
-	, params_(&p)
-	, limits_(lim)
-	, state_(0) {
-}
+    : solver_(&s)
+    , params_(&p)
+    , limits_(lim) {}
 
-BasicSolve::~BasicSolve(){ delete state_; }
-void BasicSolve::reset(bool reinit) {
-	if (!state_ || reinit) {
-		delete state_;
-		state_ = 0;
-	}
-	else {
-		state_->~State();
-		new (state_) State(*solver_, *params_);
-	}
-}
+BasicSolve::~BasicSolve() = default;
+void BasicSolve::reset() { state_.reset(); }
 void BasicSolve::reset(Solver& s, const SolveParams& p, const SolveLimits& lim) {
-	solver_ = &s;
-	params_ = &p;
-	limits_ = lim;
-	reset(true);
+    solver_ = &s;
+    params_ = &p;
+    limits_ = lim;
+    reset();
 }
 
-ValueRep BasicSolve::solve() {
-	if (limits_.reached())                       { return value_free;  }
-	if (!state_ && !params_->randomize(*solver_)){ return value_false; }
-	if (!state_)                                 { state_ = new State(*solver_, *params_); }
-	return state_->solve(*solver_, *params_, limits_);
+Val_t BasicSolve::solve() {
+    if (limits_.reached()) {
+        return value_free;
+    }
+    if (not state_ && not params_->randomize(*solver_)) {
+        return value_false;
+    }
+    if (not state_) {
+        state_ = std::make_unique(*solver_, *params_);
+    }
+    return state_->solve(*solver_, *params_, limits_);
 }
 
-bool BasicSolve::satisfiable(const LitVec& path, bool init) {
-	if (!solver_->clearAssumptions() || !solver_->pushRoot(path)){ return false; }
-	if (init && !params_->randomize(*solver_))                   { return false; }
-	State temp(*solver_, *params_);
-	SolveLimits limits;
-	return temp.solve(*solver_, *params_, limits) == value_true;
+bool BasicSolve::satisfiable(LitView path, bool init) const {
+    if (not solver_->clearAssumptions() || not solver_->pushRoot(path)) {
+        return false;
+    }
+    if (init && not params_->randomize(*solver_)) {
+        return false;
+    }
+    State       temp(*solver_, *params_);
+    SolveLimits limits;
+    return temp.solve(*solver_, *params_, limits) == value_true;
 }
 
-bool BasicSolve::assume(const LitVec& path) {
-	return solver_->pushRoot(path);
-}
+bool BasicSolve::assume(LitView assumptions) { return solver_->pushRoot(assumptions); }
 
 BasicSolve::State::State(Solver& s, const SolveParams& p)
-	: dbGrowNext(p.reduce.growSched.current()), dbRed(p.reduce.cflSched), nRestart(0), nGrow(0)
-	, dbRedInit(p.reduce.cflInit(*s.sharedContext())), dbPinned(0), rsShuffle(p.restart.shuffle), resetState(false) {
-	Range32 dbLim= p.reduce.sizeInit(*s.sharedContext());
-	dbMax        = dbLim.lo;
-	dbHigh       = dbLim.hi;
-	if (dbLim.lo < s.numLearntConstraints()) {
-		dbMax = std::min(dbHigh, double(s.numLearntConstraints() + p.reduce.initRange.lo));
-	}
-	if (dbRedInit && dbRed.type != ScheduleStrategy::Luby) {
-		if (dbRedInit < dbRed.base) {
-			dbRedInit  = std::min(dbRed.base, std::max(dbRedInit,(uint32)5000));
-			dbRed.grow = dbRedInit != dbRed.base ? std::min(dbRed.grow, dbRedInit/2.0f) : dbRed.grow;
-			dbRed.base = dbRedInit;
-		}
-		dbRedInit = 0;
-	}
-	if (p.restart.rsSched.isDynamic()) {
-		const RestartSchedule& r = p.restart.rsSched;
-		dynRestart.reset(new DynamicLimit(r.k(), r.base, r.fastAvg(), r.keepAvg(), r.slowAvg(), r.slowWin(), r.adjustLim()));
-	}
-	if (p.restart.block.fscale > 0 && p.restart.block.window > 0) {
-		const RestartParams::Block& block = p.restart.block;
-		rsBlock.reset(new BlockLimit(block.window, block.scale(), static_cast(block.avg)));
-		rsBlock->inc  = std::max(p.restart.base(), uint32(50));
-		rsBlock->next = std::max(block.window, block.first);
-	}
-	s.stats.lastRestart = s.stats.analyzed;
+    : dbGrowNext(p.reduce.growSched.current())
+    , dbRed(p.reduce.cflSched)
+    , dbRedInit(p.reduce.cflInit(*s.sharedContext()))
+    , rsShuffle(p.restart.shuffle) {
+    auto dbLim = p.reduce.sizeInit(*s.sharedContext());
+    dbMax      = dbLim.lo;
+    dbHigh     = dbLim.hi;
+    if (dbLim.lo < s.numLearntConstraints()) {
+        dbMax = std::min(dbHigh, static_cast(s.numLearntConstraints() + p.reduce.initRange.lo));
+    }
+    if (dbRedInit && dbRed.type != ScheduleStrategy::sched_luby) {
+        if (dbRedInit < dbRed.base) {
+            dbRedInit = std::min(dbRed.base, std::max(dbRedInit, 5000u));
+            if (dbRedInit != dbRed.base) {
+                dbRed.grow = std::min(dbRed.grow, static_cast(dbRedInit) / 2.0f);
+                dbRed.base = dbRedInit;
+            }
+        }
+        dbRedInit = 0;
+    }
+    if (p.restart.rsSched.isDynamic()) {
+        const RestartSchedule& r = p.restart.rsSched;
+        dynRestart = std::make_unique(r.k(), r.base, r.fastAvg(), r.keepAvg(), r.slowAvg(), r.slowWin(),
+                                                    r.adjustLim());
+    }
+    if (p.restart.block.fscale > 0 && p.restart.block.window > 0) {
+        const RestartParams::Block& block = p.restart.block;
+        rsBlock = std::make_unique(block.window, block.scale(), static_cast(block.avg));
+        rsBlock->inc  = std::max(p.restart.base(), 50u);
+        rsBlock->next = std::max(block.window, block.first);
+    }
+    s.stats.lastRestart = s.stats.analyzed;
 }
 
-ValueRep BasicSolve::State::solve(Solver& s, const SolveParams& p, SolveLimits& lim) {
-	assert(!lim.reached());
-	const uint32 resetMode = s.enumerationConstraint() ? static_cast(s.enumerationConstraint())->resetMode() : 0u;
-	if (s.hasConflict() && s.decisionLevel() == s.rootLevel()) {
-		resetState = resetState || (resetMode & value_false) != 0;
-		return value_false;
-	}
-	struct ConflictLimits {
-		uint64 reduce;  // current reduce limit
-		uint64 grow;    // current limit for next growth operation
-		uint64 restart; // current restart limit
-		uint64 global;  // current global limit
-		uint64 min()      const { return std::min(std::min(reduce, grow), std::min(restart, global)); }
-		void  update(uint64 x)  { reduce -= x; grow -= x; restart -= x; global -= x; }
-	};
-	if (resetState) {
-		this->~State();
-		new (this) State(s, p);
-	}
-	WeightLitVec inDegree;
-	SearchLimits     sLimit;
-	RestartSchedule  rs     = p.restart.rsSched;
-	ScheduleStrategy dbGrow = p.reduce.growSched;
-	Solver::DBInfo   db     = {0,0,dbPinned};
-	ValueRep         result = value_free;
-	ConflictLimits   cLimit = {dbRed.current() + dbRedInit, dbGrowNext, UINT64_MAX, lim.conflicts};
-	uint64      limRestarts = lim.restarts;
-	if (!dbGrow.disabled())  { dbGrow.advanceTo(nGrow); }
-	if (nRestart == UINT32_MAX && p.restart.update() == RestartParams::seq_disable) {
-		sLimit = SearchLimits();
-	}
-	else if (rs.isDynamic() && dynRestart.get()) {
-		if (!nRestart) dynRestart->resetAdjust(rs.k(), DynamicLimit::lbd_limit, rs.adjustLim(), true);
-		sLimit.restart.dynamic   = dynRestart.get();
-		sLimit.restart.conflicts = dynRestart->adjust.limit - std::min(dynRestart->adjust.samples, dynRestart->adjust.limit - 1);
-	}
-	else {
-		rs.advanceTo(!rs.disabled() ? nRestart : 0);
-		sLimit.restart.conflicts = rs.current();
-	}
-	sLimit.restart.local = p.restart.local();
-	sLimit.restart.block = rsBlock.get();
-	if (p.reduce.memMax) {
-		sLimit.memory = static_cast(p.reduce.memMax)<<20;
-	}
-	uint64 n = 0;
-	for (EventType progress(s, EventType::event_restart, 0, 0); cLimit.global; ) {
-		cLimit.restart   = !p.restart.local() ? sLimit.restart.conflicts : UINT64_MAX;
-		sLimit.used      = 0;
-		sLimit.learnts   = (uint32)std::min(dbMax + (db.pinned*p.reduce.strategy.noGlue), dbHigh);
-		sLimit.conflicts = cLimit.min(); assert(sLimit.conflicts);
-		progress.cLimit  = sLimit.conflicts;
-		progress.lLimit  = sLimit.learnts;
-		if (progress.op) { s.sharedContext()->report(progress); progress.op = (uint32)EventType::event_none; }
-		result = s.search(sLimit, p.randProb);
-		cLimit.update(n = std::min(sLimit.used, sLimit.conflicts)); // number of conflicts in this iteration
-		if (result != value_free) {
-			progress.op = static_cast(EventType::event_exit);
-			if (result == value_true && p.restart.update() != RestartParams::seq_continue) {
-				if      (p.restart.update() == RestartParams::seq_repeat) { nRestart = 0; }
-				else if (p.restart.update() == RestartParams::seq_disable){ nRestart = UINT32_MAX; }
-			}
-			if (!dbGrow.disabled()) { dbGrowNext = std::max(cLimit.grow, uint64(1)); }
-			s.sharedContext()->report(progress);
-			break;
-		}
-		if (s.restartReached(sLimit)) {
-			// restart reached - do restart
-			++nRestart;
-			if (p.restart.counterRestart && (nRestart % p.restart.counterRestart) == 0 ) {
-				inDegree.clear();
-				s.heuristic()->bump(s, inDegree, p.restart.counterBump / (double)s.inDegree(inDegree));
-			}
-			if (sLimit.restart.dynamic) {
-				n = sLimit.restart.dynamic->runLen();
-				sLimit.restart.conflicts = sLimit.restart.dynamic->restart(rs.lbdLim(), rs.k());
-			}
-			else {
-				sLimit.restart.conflicts = n = rs.next();
-			}
-			s.restart();
-			if (p.reduce.strategy.fRestart){ db        = s.reduceLearnts(p.reduce.fRestart(), p.reduce.strategy); }
-			if (nRestart == rsShuffle)     { rsShuffle+= p.restart.shuffleNext; s.shuffleOnNextSimplify();}
-			if (--limRestarts == 0)        { break; }
-			s.stats.lastRestart = s.stats.analyzed;
-			progress.op         = (uint32)EventType::event_restart;
-		}
-		else if (!p.restart.local()) {
-			sLimit.restart.conflicts -= std::min(n, sLimit.restart.conflicts);
-		}
-		if (cLimit.reduce == 0 || s.reduceReached(sLimit)) {
-			// reduction reached - remove learnt constraints
-			db              = s.reduceLearnts(p.reduce.fReduce(), p.reduce.strategy);
-			cLimit.reduce   = dbRedInit + (cLimit.reduce == 0 ? dbRed.next() : dbRed.current());
-			progress.op     = std::max(progress.op, (uint32)EventType::event_deletion);
-			if (s.reduceReached(sLimit) || db.pinned >= dbMax) {
-				ReduceStrategy t; t.algo = 2; t.score = 2; t.glue = 0;
-				db.pinned /= 2;
-				db.size    = s.reduceLearnts(0.5f, t).size;
-				if (db.size >= sLimit.learnts) { dbMax = std::min(dbMax + std::max(100.0, s.numLearntConstraints()/10.0), dbHigh); }
-			}
-		}
-		if (cLimit.grow == 0 || (dbGrow.defaulted() && progress.op == (uint32)EventType::event_restart)) {
-			// grow sched reached - increase max db size
-			if (cLimit.grow == 0)                      { cLimit.grow = n = dbGrow.next(); ++nGrow; }
-			if ((s.numLearntConstraints() + n) > dbMax){ dbMax  *= p.reduce.fGrow; progress.op = std::max(progress.op, (uint32)EventType::event_grow); }
-			if (dbMax > dbHigh)                        { dbMax   = dbHigh; cLimit.grow = UINT64_MAX; dbGrow = ScheduleStrategy::none(); }
-		}
-	}
-	dbPinned            = db.pinned;
-	resetState          = (resetMode & result) != 0u;
-	s.stats.lastRestart = s.stats.analyzed - s.stats.lastRestart;
-	if (lim.enabled()) {
-		if (lim.conflicts != UINT64_MAX) { lim.conflicts = cLimit.global; }
-		if (lim.restarts  != UINT64_MAX) { lim.restarts  = limRestarts;   }
-	}
-	return result;
+Val_t BasicSolve::State::solve(Solver& s, const SolveParams& p, SolveLimits& lim) {
+    assert(not lim.reached());
+    const uint32_t resetMode = value_false | (s.strategies().resetOnModel ? value_true : 0u);
+    if (s.hasConflict() && s.decisionLevel() == s.rootLevel()) {
+        resetState = resetState || Potassco::test_any(resetMode, value_false);
+        return value_false;
+    }
+    struct ConflictLimits {
+        [[nodiscard]] uint64_t min() const { return std::min({reduce, grow, restart, global}); }
+        void                   update(uint64_t x) {
+            reduce  -= x;
+            grow    -= x;
+            restart -= x;
+            global  -= x;
+        }
+        uint64_t reduce;  // current reduce limit
+        uint64_t grow;    // current limit for next growth operation
+        uint64_t restart; // current restart limit
+        uint64_t global;  // current global limit
+    };
+    if (resetState) {
+        this->~State();
+        new (this) State(s, p);
+    }
+    SearchLimits     sLimit;
+    RestartSchedule  rs          = p.restart.rsSched;
+    ScheduleStrategy dbGrow      = p.reduce.growSched;
+    Solver::DBInfo   db          = {0, 0, dbPinned};
+    auto             result      = value_free;
+    ConflictLimits   cLimit      = {dbRed.current() + dbRedInit, dbGrowNext, UINT64_MAX, lim.conflicts};
+    uint64_t         limRestarts = lim.restarts;
+    if (not dbGrow.disabled()) {
+        dbGrow.advanceTo(nGrow);
+    }
+    if (nRestart == UINT32_MAX && p.restart.update() == RestartParams::seq_disable) {
+        sLimit = SearchLimits();
+    }
+    else if (rs.isDynamic() && dynRestart.get()) {
+        if (not nRestart) {
+            dynRestart->resetAdjust(rs.k(), DynamicLimit::lbd_limit, rs.adjustLim(), true);
+        }
+        sLimit.restart.dynamic = dynRestart.get();
+        sLimit.restart.conflicts =
+            dynRestart->adjust.limit - std::min(dynRestart->adjust.samples, dynRestart->adjust.limit - 1);
+    }
+    else {
+        rs.advanceTo(not rs.disabled() ? nRestart : 0);
+        sLimit.restart.conflicts = rs.current();
+    }
+    sLimit.restart.local = p.restart.local();
+    sLimit.restart.block = rsBlock.get();
+    if (p.reduce.memMax) {
+        sLimit.memory = static_cast(p.reduce.memMax) << 20;
+    }
+    for (EventType progress(s, EventType::event_restart, 0, 0); cLimit.global;) {
+        cLimit.restart   = not p.restart.local() ? sLimit.restart.conflicts : UINT64_MAX;
+        sLimit.used      = 0;
+        sLimit.learnts   = static_cast(std::min(dbMax + (db.pinned * p.reduce.strategy.noGlue), dbHigh));
+        sLimit.conflicts = cLimit.min();
+        assert(sLimit.conflicts);
+        progress.cLimit = sLimit.conflicts;
+        progress.lLimit = sLimit.learnts;
+        if (progress.op) {
+            s.sharedContext()->report(progress);
+            progress.op = EventType::event_none;
+        }
+        result = s.search(sLimit, p.randProb);
+        auto n = std::min(sLimit.used, sLimit.conflicts); // number of conflicts in this iteration
+        cLimit.update(n);
+        if (result != value_free) {
+            progress.op = EventType::event_exit;
+            if (result == value_true && p.restart.update() != RestartParams::seq_continue) {
+                if (p.restart.update() == RestartParams::seq_repeat) {
+                    nRestart = 0;
+                }
+                else if (p.restart.update() == RestartParams::seq_disable) {
+                    nRestart = UINT32_MAX;
+                }
+            }
+            if (not dbGrow.disabled()) {
+                dbGrowNext = std::max(cLimit.grow, static_cast(1));
+            }
+            s.sharedContext()->report(progress);
+            break;
+        }
+        if (s.restartReached(sLimit)) {
+            // restart reached - do restart
+            ++nRestart;
+            if (p.restart.counterRestart && (nRestart % p.restart.counterRestart) == 0) {
+                s.counterBumpVars(p.restart.counterBump);
+            }
+            if (sLimit.restart.dynamic) {
+                n                        = sLimit.restart.dynamic->runLen();
+                sLimit.restart.conflicts = sLimit.restart.dynamic->restart(rs.lbdLim(), rs.k());
+            }
+            else {
+                sLimit.restart.conflicts = n = rs.next();
+            }
+            s.restart();
+            if (p.reduce.strategy.fRestart) {
+                db = s.reduceLearnts(p.reduce.fRestart(), p.reduce.strategy);
+            }
+            if (nRestart == rsShuffle) {
+                rsShuffle += p.restart.shuffleNext;
+                s.shuffleOnNextSimplify();
+            }
+            if (--limRestarts == 0) {
+                break;
+            }
+            s.stats.lastRestart = s.stats.analyzed;
+            progress.op         = EventType::event_restart;
+        }
+        else if (not p.restart.local()) {
+            sLimit.restart.conflicts -= std::min(n, sLimit.restart.conflicts);
+        }
+        if (cLimit.reduce == 0 || s.reduceReached(sLimit)) {
+            // reduction reached - remove learnt constraints
+            db            = s.reduceLearnts(p.reduce.fReduce(), p.reduce.strategy);
+            cLimit.reduce = dbRedInit + (cLimit.reduce == 0 ? dbRed.next() : dbRed.current());
+            progress.op   = std::max(progress.op, static_cast(EventType::event_deletion));
+            if (s.reduceReached(sLimit) || db.pinned >= dbMax) {
+                ReduceStrategy t;
+                t.algo     = 2;
+                t.score    = 2;
+                t.glue     = 0;
+                db.pinned /= 2;
+                db.size    = s.reduceLearnts(0.5f, t).size;
+                if (db.size >= sLimit.learnts) {
+                    dbMax = std::min(dbMax + std::max(100.0, s.numLearntConstraints() / 10.0), dbHigh);
+                }
+            }
+        }
+        if (cLimit.grow == 0 || (dbGrow.defaulted() && progress.op == EventType::event_restart)) {
+            // grow sched reached - increase max db size
+            if (cLimit.grow == 0) {
+                cLimit.grow = n = dbGrow.next();
+                ++nGrow;
+            }
+            if ((s.numLearntConstraints() + n) > static_cast(dbMax)) {
+                dbMax       *= p.reduce.fGrow;
+                progress.op  = std::max(progress.op, static_cast(EventType::event_grow));
+            }
+            if (dbMax > dbHigh) {
+                dbMax       = dbHigh;
+                cLimit.grow = UINT64_MAX;
+                dbGrow      = ScheduleStrategy::none();
+            }
+        }
+    }
+    dbPinned            = db.pinned;
+    resetState          = Potassco::test_any(resetMode, result);
+    s.stats.lastRestart = s.stats.analyzed - s.stats.lastRestart;
+    if (lim.enabled()) {
+        if (lim.conflicts != UINT64_MAX) {
+            lim.conflicts = cLimit.global;
+        }
+        if (lim.restarts != UINT64_MAX) {
+            lim.restarts = limRestarts;
+        }
+    }
+    return result;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
-// SolveAlgorithm
+// Path
 /////////////////////////////////////////////////////////////////////////////////////////
-SolveAlgorithm::SolveAlgorithm(const SolveLimits& lim)
-	: limits_(lim), ctx_(0), enum_(0), onModel_(0), enumLimit_(UINT64_MAX), time_(0.0), last_(0), reportM_(true)  {
-}
-SolveAlgorithm::~SolveAlgorithm() {}
-void SolveAlgorithm::setEnumerator(Enumerator& e) {
-	enum_.reset(&e);
-	enum_.release();
-}
-void SolveAlgorithm::setOptLimit(const SumVec& bound) {
-	optLimit_ = bound;
-}
-bool SolveAlgorithm::hasLimit(const Model& m) const {
-	if (!enum_->tentative() && m.num >= enumLimit_) return true;
-	if (enum_->optimize() && m.costs && !optLimit_.empty()) {
-		for (std::size_t i = 0, end = std::min(optLimit_.size(), m.costs->size()); i != end; ++i) {
-			if (optLimit_[i] != (*m.costs)[i]) {
-				return (*m.costs)[i] < optLimit_[i];
-			}
-		}
-		return true;
-	}
-	return false;
-}
-
-
-const Model& SolveAlgorithm::model() const {
-	return enum_->lastModel();
+SolveAlgorithm::Path SolveAlgorithm::Path::acquire(LitView path) {
+    auto p = std::make_unique(path.size());
+    std::ranges::copy(path, p.get());
+    Ptr fp(p.release());
+    fp.set<0>();
+    return {fp, path.size()};
+}
+SolveAlgorithm::Path SolveAlgorithm::Path::borrow(LitView path) { return {Ptr(path.data()), path.size()}; }
+SolveAlgorithm::Path::~Path() {
+    if (owner()) {
+        delete[] lits_.get();
+    }
+}
+SolveAlgorithm::Path::Path(Path&& other) noexcept
+    : lits_(std::exchange(other.lits_, {}))
+    , size_(std::exchange(other.size_, 0)) {}
+SolveAlgorithm::Path& SolveAlgorithm::Path::operator=(Path&& other) noexcept {
+    if (this != &other) {
+        Path t(std::move(*this));
+        lits_ = std::exchange(other.lits_, {});
+        size_ = std::exchange(other.size_, 0);
+    }
+    return *this;
 }
-const LitVec* SolveAlgorithm::unsatCore() const {
-	return core_.get();
-}
-bool SolveAlgorithm::interrupt() {
-	return doInterrupt();
-}
-bool SolveAlgorithm::attach(SharedContext& ctx, ModelHandler* onModel) {
-	POTASSCO_REQUIRE(!ctx_, "SolveAlgorithm is already running!");
-	if (!ctx.frozen()) { ctx.endInit(); }
-	ctx.report(Event::subsystem_solve);
-	if (ctx.master()->hasConflict() || !limits_.conflicts || interrupted()) {
-		last_  = !ctx.ok() ? value_false : value_free;
-		return false;
-	}
-	ctx_     = &ctx;
-	time_    = ThreadTime::getTime();
-	onModel_ = onModel;
-	last_    = value_free;
-	core_.reset(0);
-	if (!enum_.get()) { enum_ = EnumOptions::nullEnumerator(); }
-	return true;
+/////////////////////////////////////////////////////////////////////////////////////////
+// SolveAlgorithm
+/////////////////////////////////////////////////////////////////////////////////////////
+static constexpr auto value_stop = static_cast(value_false | value_true);
+SolveAlgorithm::SolveAlgorithm(const SolveLimits& limit)
+    : limits_(limit)
+    , ctx_(nullptr)
+    , enum_(nullptr)
+    , onModel_(nullptr)
+    , enumLimit_(UINT64_MAX)
+    , time_(0.0)
+    , last_(value_free)
+    , reportM_(true) {}
+SolveAlgorithm::~SolveAlgorithm() = default;
+void SolveAlgorithm::setOptLimit(SumView bound) { optLimit_.assign(bound.data(), bound.data() + bound.size()); }
+auto SolveAlgorithm::model() const -> const Model& {
+    POTASSCO_CHECK_PRE(enum_, "SolveAlgorithm is not active!");
+    return enum_->lastModel();
+}
+auto SolveAlgorithm::unsatCore() const -> LitView { return core_; }
+bool SolveAlgorithm::interrupt() { return doInterrupt(); }
+bool SolveAlgorithm::attach(Enumerator& en, SharedContext& ctx, ModelHandler* onModel) {
+    POTASSCO_CHECK_PRE(not ctx_, "SolveAlgorithm is already running!");
+    if (not ctx.frozen()) {
+        ctx.endInit();
+    }
+    ctx.report(Event::subsystem_solve);
+    if (ctx.master()->hasConflict() || not limits_.conflicts || interrupted()) {
+        last_ = not ctx.ok() ? value_false : value_free;
+        return false;
+    }
+    ctx_     = &ctx;
+    enum_    = &en;
+    time_    = ThreadTime::getTime();
+    onModel_ = onModel;
+    last_    = value_free;
+    path_    = {};
+    discardVec(core_);
+    return true;
 }
 void SolveAlgorithm::detach() {
-	if (ctx_) {
-		if (enum_->enumerated() == 0 && !interrupted()) {
-			Solver* s = ctx_->master();
-			Literal step = ctx_->stepLiteral();
-			s->popRootLevel(s->rootLevel());
-			core_ = new LitVec();
-			for (LitVec::const_iterator it = path_->begin(); it != path_->end(); ++it) {
-				if (s->isTrue(*it) || *it == step)
-					continue;
-				if (!s->isTrue(step) && !s->pushRoot(step))
-					break;
-				core_->push_back(*it);
-				if (!s->pushRoot(*it)) {
-					if (!s->isFalse(*it)) {
-						core_->clear();
-						s->resolveToCore(*core_);
-						if (!core_->empty() && (*core_)[0] == step) {
-							core_->front() = core_->back();
-							core_->pop_back();
-						}
-					}
-					break;
-				}
-			}
-			s->popRootLevel(s->rootLevel());
-		}
-		doDetach();
-		ctx_->master()->stats.addCpuTime(ThreadTime::getTime() - time_);
-		onModel_ = 0;
-		ctx_     = 0;
-		path_    = 0;
-	}
+    if (ctx_) {
+        if (enum_->enumerated() == 0 && not interrupted()) {
+            Solver* s    = ctx_->master();
+            Literal step = ctx_->stepLiteral();
+            s->popRootLevel(s->rootLevel());
+            core_.clear();
+            for (auto lit : path_) {
+                if (s->isTrue(lit) || lit == step) {
+                    continue;
+                }
+                if (not s->isTrue(step) && not s->pushRoot(step)) {
+                    break;
+                }
+                core_.push_back(lit);
+                if (not s->pushRoot(lit)) {
+                    if (not s->isFalse(lit)) {
+                        core_.clear();
+                        s->resolveToCore(core_);
+                        if (not core_.empty() && core_.front() == step) {
+                            core_.front() = core_.back();
+                            core_.pop_back();
+                        }
+                    }
+                    break;
+                }
+            }
+            s->popRootLevel(s->rootLevel());
+        }
+        doDetach();
+        ctx_->master()->stats.addCpuTime(ThreadTime::getTime() - time_);
+        onModel_ = nullptr;
+        ctx_     = nullptr;
+        path_    = {};
+    }
 }
-
-bool SolveAlgorithm::solve(SharedContext& ctx, const LitVec& assume, ModelHandler* onModel) {
-	struct Scoped {
-		Scoped(SolveAlgorithm* s) : self(s) {}
-		~Scoped() { self->detach(); }
-		bool solve(const LitVec& assume) {
-			if (self->maxModels() != UINT64_MAX) {
-				if (self->enumerator().optimize() && !self->enumerator().tentative()) {
-					self->ctx().warn("#models not 0: optimality of last model not guaranteed.");
-				}
-				if (self->enumerator().lastModel().consequences()) {
-					self->ctx().warn("#models not 0: last model may not cover consequences.");
-				}
-			}
-			self->path_.reset(&assume);
-			self->path_.release();
-			return self->doSolve(self->ctx(), assume);
-		}
-		SolveAlgorithm* self;
-	};
-	return attach(ctx, onModel) ? Scoped(this).solve(assume) : ctx.ok();
+bool SolveAlgorithm::hasLimit(const Model& m) const {
+    if (not enum_->tentative() && m.num >= enumLimit_) {
+        return true;
+    }
+    if (enum_->optimize() && m.hasCosts() && not optLimit_.empty()) {
+        for (auto i : irange(std::min(size32(optLimit_), size32(m.costs)))) {
+            if (optLimit_[i] != m.costs[i]) {
+                return m.costs[i] < optLimit_[i];
+            }
+        }
+        return true;
+    }
+    return false;
+}
+bool SolveAlgorithm::solve(Enumerator& en, SharedContext& ctx, LitView assume, ModelHandler* onModel) {
+    POTASSCO_SCOPE_EXIT({ detach(); });
+    if (not attach(en, ctx, onModel)) {
+        return last_ != value_false;
+    }
+    if (maxModels() != UINT64_MAX) {
+        if (en.optimize() && not en.tentative()) {
+            ctx.warn("#models not 0: optimality of last model not guaranteed.");
+        }
+        if (en.lastModel().consequences()) {
+            ctx.warn("#models not 0: last model may not cover consequences.");
+        }
+    }
+    return doSolve(ctx, path_ = Path::borrow(assume));
 }
 
-void SolveAlgorithm::start(SharedContext& ctx, const LitVec& assume, ModelHandler* onModel) {
-	if (attach(ctx, onModel)) {
-		doStart(ctx, *(path_ = new LitVec(assume)));
-	}
+void SolveAlgorithm::start(Enumerator& en, SharedContext& ctx, LitView assume, ModelHandler* onModel) {
+    if (attach(en, ctx, onModel)) {
+        doStart(ctx, path_ = Path::acquire(assume));
+    }
 }
 bool SolveAlgorithm::next() {
-	if (!ctx_) { return false; }
-	if (last_ != value_stop && (last_ != value_true || !enum_->commitSymmetric(*ctx_->solver(model().sId)))) {
-		last_ = doNext(last_);
-	}
-	if (last_ == value_true) {
-		if (!reportModel(*ctx_->solver(model().sId), false)) { last_ = value_stop; }
-		return true;
-	}
-	else {
-		stop();
-		return false;
-	}
-}
-bool SolveAlgorithm::more() {
-	return last_ != value_false;
-}
+    if (not ctx_) {
+        return false;
+    }
+    if (last_ != value_stop && (last_ != value_true || not enum_->commitSymmetric(*ctx_->solver(model().sId)))) {
+        last_ = doNext(last_);
+    }
+    if (last_ == value_true) {
+        if (not reportModel(*ctx_->solver(model().sId), false)) {
+            last_ = value_stop;
+        }
+        return true;
+    }
+    stop();
+    return false;
+}
+bool SolveAlgorithm::more() const { return last_ != value_false; }
 void SolveAlgorithm::stop() {
-	if (ctx_) {
-		doStop();
-		detach();
-	}
+    if (ctx_) {
+        doStop();
+        detach();
+    }
 }
 bool SolveAlgorithm::reportModel(Solver& s, bool sym) const {
-	for (const Model& m = model();;) {
-		bool r1 = !onModel_ || onModel_->onModel(s, m);
-		bool r2 = !reportM_ || s.sharedContext()->report(s, m);
-		if (!r1 || !r2 || hasLimit(m) || interrupted()) { return false; }
-		if (!sym || !enum_->commitSymmetric(s))         { return true;  }
-	}
+    for (const auto& m = model();;) {
+        bool r1 = not onModel_ || onModel_->onModel(s, m);
+        bool r2 = not reportM_ || s.sharedContext()->report(s, m);
+        if (not r1 || not r2 || hasLimit(m) || interrupted()) {
+            return false;
+        }
+        if (not sym || not enum_->commitSymmetric(s)) {
+            return true;
+        }
+    }
 }
 
-bool SolveAlgorithm::reportModel(Solver& s) const {
-	return reportModel(s, true);
-}
+bool SolveAlgorithm::reportModel(Solver& s) const { return reportModel(s, true); }
 bool SolveAlgorithm::reportUnsat(Solver& s) const {
-	const Model&  m = model();
-	EventHandler* h = s.sharedContext()->eventHandler();
-	bool r1 = !onModel_ || onModel_->onUnsat(s, m);
-	bool r2 = !h || h->onUnsat(s, m);
-	return r1 && r2;
+    const auto& m  = model();
+    auto*       h  = s.sharedContext()->eventHandler();
+    bool        r1 = not onModel_ || onModel_->onUnsat(s, m);
+    bool        r2 = not h || h->onUnsat(s, m);
+    enum_->clearUpdate();
+    return r1 && r2;
 }
 bool SolveAlgorithm::moreModels(const Solver& s) const {
-	return s.decisionLevel() != 0 || !s.symmetric().empty() || (!s.sharedContext()->preserveModels() && s.sharedContext()->numEliminatedVars());
+    return s.decisionLevel() > 0 ||
+           (not s.sharedContext()->preserveModels() && s.sharedContext()->numEliminatedVars()) ||
+           (enum_ && enum_->hasSymmetric(s));
 }
-void SolveAlgorithm::doStart(SharedContext&, const LitVec&) {
-	POTASSCO_REQUIRE(false, "Iterative model generation not supported by this algorithm!");
+void SolveAlgorithm::doStart(SharedContext&, LitView) {
+    POTASSCO_CHECK_PRE(false, "Iterative model generation not supported by this algorithm!");
 }
-int SolveAlgorithm::doNext(int) {
-	POTASSCO_REQUIRE(false, "Iterative model generation not supported by this algorithm!");
+Val_t SolveAlgorithm::doNext(Val_t) {
+    POTASSCO_CHECK_PRE(false, "Iterative model generation not supported by this algorithm!");
 }
 void SolveAlgorithm::doStop() {}
 /////////////////////////////////////////////////////////////////////////////////////////
 // SequentialSolve
 /////////////////////////////////////////////////////////////////////////////////////////
 namespace {
-struct InterruptHandler : public MessageHandler {
-	InterruptHandler(Solver* s, volatile int* t) : solver(s), term(t) {
-		if (s && t) { s->addPost(this); }
-	}
-	~InterruptHandler()  { if (solver) { solver->removePost(this); solver = 0; } }
-	bool handleMessages(){ return !*term || (solver->setStopConflict(), false); }
-	bool propagateFixpoint(Solver&, PostPropagator*){ return InterruptHandler::handleMessages(); }
-	Solver*       solver;
-	volatile int* term;
+struct InterruptHandler final : MessageHandler {
+    InterruptHandler(Solver* s, volatile int* t) : solver(s), term(t) {
+        if (s && t) {
+            s->addPost(this);
+        }
+    }
+    ~InterruptHandler() override {
+        if (solver) {
+            solver->removePost(this);
+            solver = nullptr;
+        }
+    }
+    bool          handleMessages() override { return !*term || (solver->setStopConflict(), false); }
+    bool          propagateFixpoint(Solver&, PostPropagator*) override { return InterruptHandler::handleMessages(); }
+    Solver*       solver;
+    volatile int* term;
 };
-}
-SequentialSolve::SequentialSolve(const SolveLimits& limit)
-	: SolveAlgorithm(limit)
-	, solve_(0)
-	, term_(-1) {
-}
-void SequentialSolve::resetSolve()       { if (term_ > 0) { term_ = 0; } }
-bool SequentialSolve::doInterrupt()      { return term_ >= 0 && ++term_ != 0; }
-void SequentialSolve::enableInterrupts() { if (term_ < 0) { term_ = 0; } }
-bool SequentialSolve::interrupted() const{ return term_ > 0; }
-void SequentialSolve::doStart(SharedContext& ctx, const LitVec& gp) {
-	solve_.reset(new BasicSolve(*ctx.master(), ctx.configuration()->search(0), limits()));
-	if (!enumerator().start(solve_->solver(), gp)) { SequentialSolve::doStop(); }
-}
-int SequentialSolve::doNext(int last) {
-	if (interrupted() || !solve_.get()) { return solve_.get() ? value_free : value_false; }
-	Solver& s = solve_->solver();
-	for (InterruptHandler term(term_ >= 0 ? &s : (Solver*)0, &term_);;) {
-		if (last != value_free) { enumerator().update(s); }
-		last = solve_->solve();
- 		if (last != value_true) {
- 			if      (last == value_free || term_ > 0) { return value_free; }
-			else if (enumerator().commitUnsat(s))     { reportUnsat(s); }
-			else if (enumerator().commitComplete())   { break; }
- 			else {
-				enumerator().end(s);
-				if (!enumerator().start(s, path())) { break; }
-				last = value_free;
- 			}
- 		}
-		else if (enumerator().commitModel(s)) { break; }
- 	}
- 	return last;
+} // namespace
+SequentialSolve::SequentialSolve(const SolveLimits& limit) : SolveAlgorithm(limit), solve_(nullptr), term_(-1) {}
+void SequentialSolve::resetSolve() {
+    if (term_ > 0) {
+        term_ = 0;
+    }
+}
+bool SequentialSolve::doInterrupt() {
+    if (term_ >= 0) {
+        term_ |= 1;
+        return true;
+    }
+    return false;
+}
+void SequentialSolve::enableInterrupts() {
+    if (term_ < 0) {
+        term_ = 0;
+    }
+}
+bool SequentialSolve::interrupted() const { return term_ > 0; }
+void SequentialSolve::doStart(SharedContext& ctx, LitView assume) {
+    solve_ = std::make_unique(*ctx.master(), ctx.configuration()->search(0), limits());
+    if (not enumerator().start(solve_->solver(), assume)) {
+        SequentialSolve::doStop();
+    }
+}
+Val_t SequentialSolve::doNext(Val_t last) {
+    if (interrupted() || not solve_.get()) {
+        return solve_.get() ? value_free : value_false;
+    }
+    Solver& s = solve_->solver();
+    for (InterruptHandler term(term_ >= 0 ? &s : static_cast(nullptr), &term_);;) {
+        if (last != value_free) {
+            enumerator().update(s);
+        }
+        last = solve_->solve();
+        if (last != value_true) {
+            if (last == value_free || term_ > 0) {
+                return value_free;
+            }
+            if (enumerator().commitUnsat(s)) {
+                reportUnsat(s);
+            }
+            else if (enumerator().commitComplete() || not restart(s, path())) {
+                break;
+            }
+            else {
+                last = value_free;
+            }
+        }
+        else if (enumerator().commitModel(s)) {
+            break;
+        }
+    }
+    return last;
 }
 void SequentialSolve::doStop() {
-	if (solve_.get()) {
-		enumerator().end(solve_->solver());
-		solve_ = 0;
-	}
-}
-void SequentialSolve::doDetach() {
-	ctx().detach(*ctx().master());
+    if (solve_.get()) {
+        enumerator().end(solve_->solver());
+        solve_.reset();
+    }
 }
+void SequentialSolve::doDetach() { ctx().detach(*ctx().master()); }
 
-bool SequentialSolve::doSolve(SharedContext& ctx, const LitVec& gp) {
-	// Add assumptions - if this fails, the problem is unsat
-	// under the current assumptions but not necessarily unsat.
-	Solver& s = *ctx.master();
-	bool more = !interrupted() && ctx.attach(s) && enumerator().start(s, gp);
-	InterruptHandler term(term_ >= 0 ? &s : (Solver*)0, &term_);
-	for (BasicSolve solve(s, ctx.configuration()->search(0), limits()); more;) {
-		ValueRep res;
-		while ((res = solve.solve()) == value_true && (!enumerator().commitModel(s) || reportModel(s))) {
-			enumerator().update(s);
-		}
-		if      (res != value_false)           { more = (res == value_free || moreModels(s)); break; }
-		else if (interrupted())                { more = true; break; }
-		else if (enumerator().commitUnsat(s))  { reportUnsat(s); enumerator().update(s); }
-		else if (enumerator().commitComplete()){ more = false; break; }
-		else                                   { enumerator().end(s); more = enumerator().start(s, gp); }
-	}
-	enumerator().end(s);
-	return more;
-}
+bool SequentialSolve::doSolve(SharedContext& ctx, LitView assume) {
+    // Add assumptions - if this fails, the problem is unsat
+    // under the current assumptions but not necessarily unsat.
+    Solver&          s    = *ctx.master();
+    bool             more = not interrupted() && ctx.attach(s) && enumerator().start(s, assume);
+    InterruptHandler term(term_ >= 0 ? &s : static_cast(nullptr), &term_);
+    for (BasicSolve solve(s, ctx.configuration()->search(0), limits()); more;) {
+        Val_t res;
+        while ((res = solve.solve()) == value_true && (not enumerator().commitModel(s) || reportModel(s))) {
+            enumerator().update(s);
+        }
+        if (res != value_false) {
+            more = res == value_free || moreModels(s);
+            break;
+        }
+        if (interrupted()) {
+            more = true;
+            break;
+        }
+        if (enumerator().commitUnsat(s)) {
+            reportUnsat(s);
+            enumerator().update(s);
+        }
+        else if (enumerator().commitComplete()) {
+            more = false;
+            break;
+        }
+        else {
+            more = restart(s, assume);
+        }
+    }
+    enumerator().end(s);
+    return more;
+}
+bool SequentialSolve::restart(Solver& s, LitView assume) {
+    enumerator().end(s);
+    return enumerator().start(s, assume);
 }
+
+} // namespace Clasp
diff --git a/src/solver.cpp b/src/solver.cpp
index 86c7b40..ce330a8 100644
--- a/src/solver.cpp
+++ b/src/solver.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,662 +22,755 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
-#include POTASSCO_EXT_INCLUDE(unordered_set)
+
+#include 
+
+#include 
 namespace Clasp {
-namespace {
-	typedef POTASSCO_EXT_NS::unordered_set ConstraintSet;
-	struct InSet {
-		bool operator()(Constraint* c)        const { return set->find(c) != set->end(); }
-		bool operator()(const ClauseWatch& w) const { return (*this)(w.head); }
-		bool operator()(const GenericWatch&w) const { return (*this)(w.con);  }
-		const ConstraintSet* set;
-	};
-}
-DecisionHeuristic::~DecisionHeuristic() {}
-static SelectFirst null_heuristic_g;
+DecisionHeuristic::~DecisionHeuristic() = default;
+static SelectFirst g_null_heuristic;
 /////////////////////////////////////////////////////////////////////////////////////////
 // CCMinRecursive
 /////////////////////////////////////////////////////////////////////////////////////////
 struct CCMinRecursive {
-	enum State { state_open = 0, state_removable = 1, state_poison = 2 };
-	uint32 encodeState(State st)     const { return open + uint32(st); }
-	State  decodeState(uint32 epoch) const { return epoch <= open ? state_open : static_cast(epoch - open); }
-	void    push(Literal p) { todo.push_back(p); }
-	Literal pop()           { Literal p = todo.back(); todo.pop_back(); return p; }
-	LitVec todo;
-	uint32 open;
+    enum State { state_open = 0, state_removable = 1, state_poison = 2 };
+    [[nodiscard]] uint32_t encodeState(State st) const { return open + static_cast(st); }
+    [[nodiscard]] State    decodeState(uint32_t epoch) const {
+        return epoch <= open ? state_open : static_cast(epoch - open);
+    }
+    void    push(Literal p) { todo.push_back(p); }
+    Literal pop() {
+        Literal p = todo.back();
+        todo.pop_back();
+        return p;
+    }
+    LitVec   todo;
+    uint32_t open = 0;
 };
 /////////////////////////////////////////////////////////////////////////////////////////
 // SelectFirst selection strategy
 /////////////////////////////////////////////////////////////////////////////////////////
 // selects the first free literal
 Literal SelectFirst::doSelect(Solver& s) {
-	for (Var i = 1; i <= s.numVars(); ++i) {
-		if (s.value(i) == value_free) {
-			return selectLiteral(s, i, 0);
-		}
-	}
-	assert(!"SelectFirst::doSelect() - precondition violated!\n");
-	return Literal();
+    for (auto v : s.vars()) {
+        if (s.value(v) == value_free) {
+            return selectLiteral(s, v, 0);
+        }
+    }
+    POTASSCO_ASSERT_NOT_REACHED("SelectFirst::doSelect() - precondition violated!\n");
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Dirty list
 /////////////////////////////////////////////////////////////////////////////////////////
 struct Solver::Dirty {
-	static const std::size_t min_size = static_cast(4);
-	Dirty() : last(0) {}
-	bool add(Literal p, WatchList& wl, Constraint* c) {
-		if (wl.right_size() <= min_size) { return false; }
-		uintp o = wl.left_size() > 0 ? reinterpret_cast(wl.left_begin()->head) : 0;
-		if (add(wl.right_begin()->con, o, c)) { dirty.push_left(p); }
-		return true;
-	}
-	bool add(Literal p, WatchList& wl, ClauseHead* c) {
-		if (wl.left_size() <= min_size) { return false; }
-		uintp o = wl.right_size() > 0 ? reinterpret_cast(wl.right_begin()->con) : 0;
-		if (add(wl.left_begin()->head, o, c)) { dirty.push_left(p); }
-		return true;
-	}
-	bool add(uint32 dl, ConstraintDB& wl, Constraint* c) {
-		if (wl.size() <= min_size) { return false; }
-		if (add(wl[0], 0, c)) { dirty.push_right(dl); }
-		return true;
-	}
-	template 
-	bool add(T*& list, uintp other, Constraint* c) {
-		other |= reinterpret_cast(list);
-		list = reinterpret_cast( set_bit(reinterpret_cast(list), 0) );
-		if (c != last) { cons.insert(last = c); }
-		return !test_bit(other, 0);
-	}
-	template 
-	bool test_and_clear(T*& x) const {
-		uintp old = reinterpret_cast(x);
-		return test_bit(old, 0) && (x = reinterpret_cast(clear_bit(old, 0))) != 0;
-	}
-	void cleanup(Watches& watches, DecisionLevels& levels) {
-		InSet inCons = { &cons };
-		const uint32 maxId = (uint32)watches.size();
-		for (DirtyList::left_iterator it = dirty.left_begin(), end = dirty.left_end(); it != end; ++it) {
-			uint32 id = it->id();
-			if (id >= maxId)
-				continue;
-			WatchList& wl = watches[id];
-			if (wl.left_size() && test_and_clear(wl.left_begin()->head)) { wl.shrink_left(std::remove_if(wl.left_begin(), wl.left_end(), inCons)); }
-			if (wl.right_size()&& test_and_clear(wl.right_begin()->con)) { wl.shrink_right(std::remove_if(wl.right_begin(), wl.right_end(), inCons)); }
-		}
-		ConstraintDB* db = 0;
-		for (DirtyList::right_iterator it = dirty.right_begin(), end = dirty.right_end(); it != end; ++it) {
-			if (*it < levels.size() && !(db = levels[*it].undo)->empty() && test_and_clear(*db->begin())) {
-				db->erase(std::remove_if(db->begin(), db->end(), inCons), db->end());
-			}
-		}
-		dirty.clear();
-		cons.clear();
-		last = 0;
-	}
-	typedef bk_lib::left_right_sequence DirtyList;
-	DirtyList     dirty;
-	ConstraintSet cons;
-	Constraint*   last;
+    static constexpr auto min_size = static_cast(4);
+    Dirty()                        = default;
+    bool add(Literal p, WatchList& wl, Constraint* c) {
+        if (wl.right_size() <= min_size) {
+            return false;
+        }
+        uintptr_t o = wl.left_size() > 0 ? reinterpret_cast(wl.left_begin()->head) : 0;
+        if (add(wl.right_begin()->con, o, c)) {
+            dirty.push_left(p);
+        }
+        return true;
+    }
+    bool add(Literal p, WatchList& wl, ClauseHead* c) {
+        if (wl.left_size() <= min_size) {
+            return false;
+        }
+        uintptr_t o = wl.right_size() > 0 ? reinterpret_cast(wl.right_begin()->con) : 0;
+        if (add(wl.left_begin()->head, o, c)) {
+            dirty.push_left(p);
+        }
+        return true;
+    }
+    bool add(uint32_t dl, ConstraintDB& wl, Constraint* c) {
+        if (wl.size() <= min_size) {
+            return false;
+        }
+        if (add(wl[0], 0, c)) {
+            dirty.push_right(dl);
+        }
+        return true;
+    }
+    template 
+    bool add(T*& list, uintptr_t other, Constraint* c) {
+        other |= reinterpret_cast(list);
+        list   = reinterpret_cast(Potassco::set_bit(reinterpret_cast(list), 0));
+        if (c != last) {
+            cons.insert(last = c);
+        }
+        return not Potassco::test_bit(other, 0);
+    }
+    template 
+    bool test_and_clear(T*& x) const {
+        auto old = reinterpret_cast(x);
+        return Potassco::test_bit(old, 0) && (x = reinterpret_cast(Potassco::clear_bit(old, 0))) != nullptr;
+    }
+    template 
+    static constexpr auto getCon(const T& x) -> Constraint* {
+        if constexpr (std::is_same_v) {
+            return x.head;
+        }
+        else if constexpr (std::is_same_v) {
+            return x.con;
+        }
+        else {
+            return x;
+        }
+    }
+    void cleanup(Watches& watches, DecisionLevels& levels) {
+        auto       inCons = [this](const auto& w) { return cons.contains(getCon(w)); };
+        const auto maxId  = size32(watches);
+        for (auto lit : dirty.left_view()) {
+            uint32_t id = lit.id();
+            if (id >= maxId) {
+                continue;
+            }
+            WatchList& wl = watches[id];
+            if (wl.left_size() && test_and_clear(wl.left_begin()->head)) {
+                wl.shrink_left(std::remove_if(wl.left_begin(), wl.left_end(), inCons));
+            }
+            if (wl.right_size() && test_and_clear(wl.right_begin()->con)) {
+                wl.shrink_right(std::remove_if(wl.right_begin(), wl.right_end(), inCons));
+            }
+        }
+        ConstraintDB* db = nullptr;
+        for (auto x : dirty.right_view()) {
+            if (x < levels.size() && not(db = levels[x].undo)->empty() && test_and_clear(*db->begin())) {
+                erase_if(*db, inCons);
+            }
+        }
+        dirty.clear();
+        cons.clear();
+        last = nullptr;
+    }
+    using DirtyList     = bk_lib::left_right_sequence;
+    using ConstraintSet = std::unordered_set;
+    DirtyList     dirty;
+    ConstraintSet cons;
+    Constraint*   last{nullptr};
 };
 /////////////////////////////////////////////////////////////////////////////////////////
 // Solver: Construction/Destruction/Setup
 /////////////////////////////////////////////////////////////////////////////////////////
-#define FOR_EACH_POST(x, head) \
-	for (PostPropagator** __r__ = (head), *x; (x = *__r__) != 0; __r__ = (x == *__r__) ? &x->next : __r__)
-
-static PostPropagator* sent_list;
-Solver::Solver(SharedContext* ctx, uint32 id)
-	: shared_(ctx)
-	, heuristic_(&null_heuristic_g, Ownership_t::Retain)
-	, ccMin_(0)
-	, postHead_(&sent_list)
-	, undoHead_(0)
-	, enum_(0)
-	, memUse_(0)
-	, lazyRem_(0)
-	, dynLimit_(0)
-	, ccInfo_(Constraint_t::Conflict)
-	, dbIdx_(0)
-	, lastSimp_(0)
-	, shufSimp_(0)
-	, initPost_(0)
-	, splitReq_(false) {
-	Var trueVar = assign_.addVar();
-	assign_.setValue(trueVar, value_true);
-	markSeen(trueVar);
-	strategy_.id = id;
-}
-
-Solver::~Solver() {
-	freeMem();
+static PostPropagator* g_sent_list;
+Solver::Solver(SharedContext* ctx, uint32_t id)
+    : shared_(ctx)
+    , heuristic_(&g_null_heuristic)
+    , ccMin_(nullptr)
+    , postHead_(&g_sent_list)
+    , undoHead_(nullptr)
+    , enum_(nullptr)
+    , memUse_(0)
+    , lazyRem_(nullptr)
+    , dynLimit_(nullptr)
+    , ccInfo_(ConstraintType::conflict)
+    , dbIdx_(0)
+    , lastSimp_(0)
+    , shufSimp_(0)
+    , initPost_(0)
+    , splitReq_(false) {
+    auto trueVar = assign_.addVar();
+    assign_.setValue(trueVar, value_true);
+    markSeen(trueVar);
+    strategy_.id = id;
 }
 
+Solver::~Solver() { freeMem(); }
+
 void Solver::freeMem() {
-	std::for_each( constraints_.begin(), constraints_.end(), DestroyObject());
-	std::for_each( learnts_.begin(), learnts_.end(), DestroyObject() );
-	constraints_.clear();
-	learnts_.clear();
-	post_.clear();
-	if (enum_) { enum_->destroy(); }
-	resetHeuristic(0);
-	PodVector::destruct(watches_);
-	// free undo lists
-	// first those still in use
-	for (DecisionLevels::size_type i = 0; i != levels_.size(); ++i) {
-		delete levels_[i].undo;
-	}
-	// then those in the free list
-	for (ConstraintDB* x = undoHead_; x; ) {
-		ConstraintDB* t = x;
-		x = (ConstraintDB*)x->front();
-		delete t;
-	}
-	delete ccMin_;
-	ccMin_  = 0;
-	memUse_ = 0;
-}
-
-SatPreprocessor*    Solver::satPrepro()     const { return shared_->satPrepro.get(); }
-const SolveParams&  Solver::searchConfig()  const { return shared_->configuration()->search(id()); }
+    Clasp::destroyDB(constraints_, nullptr, false);
+    Clasp::destroyDB(learnts_, nullptr, false);
+    post_.clear();
+    if (auto e = std::exchange(enum_, nullptr)) {
+        e->destroy();
+    }
+    resetHeuristic(nullptr);
+    PodVector::destruct(watches_);
+    // free undo lists
+    // first those still in use
+    for (auto& level : levels_) { delete level.undo; }
+    // then those in the free list
+    for (ConstraintDB* x = undoHead_; x;) {
+        ConstraintDB* t = x;
+        x               = reinterpret_cast(x->front());
+        delete t;
+    }
+    ccMin_.reset();
+    memUse_ = 0;
+}
+
+SatPreprocessor*   Solver::satPrepro() const { return shared_->satPrepro.get(); }
+const SolveParams& Solver::searchConfig() const { return shared_->configuration()->search(id()); }
 
 void Solver::reset() {
-	SharedContext* myCtx = shared_;
-	uint32         myId  = strategy_.id;
-	this->~Solver();
-	new (this) Solver(myCtx, myId);
-}
-void Solver::setHeuristic(DecisionHeuristic* h, Ownership_t::Type t) {
-	POTASSCO_REQUIRE(h, "Heuristic must not be null");
-	resetHeuristic(this, h, t);
-}
-void Solver::resetHeuristic(Solver* s, DecisionHeuristic* h, Ownership_t::Type t) {
-	if (s && heuristic_.get()) { heuristic_->detach(*this); }
-	if (!h) { h = &null_heuristic_g; t = Ownership_t::Retain; }
-	HeuristicPtr(h, t).swap(heuristic_);
+    SharedContext* myCtx = shared_;
+    uint32_t       myId  = strategy_.id;
+    this->~Solver();
+    new (this) Solver(myCtx, myId);
+}
+void Solver::setHeuristic(DecisionHeuristic* h) {
+    POTASSCO_CHECK_PRE(h, "Heuristic must not be null");
+    resetHeuristic(this, h);
+}
+void Solver::resetHeuristic(Solver* s, DecisionHeuristic* h) {
+    POTASSCO_ASSERT(heuristic_);
+    if (s) {
+        heuristic_->detach(*s);
+    }
+    if (heuristic_ != &g_null_heuristic) {
+        delete heuristic_;
+    }
+    heuristic_ = h ? h : &g_null_heuristic;
 }
 void Solver::resetConfig() {
-	if (strategy_.hasConfig) {
-		if (PostPropagator* pp = getPost(PostPropagator::priority_reserved_look)) { pp->destroy(this, true); }
-		delete ccMin_;
-		ccMin_ = 0;
-	}
-	strategy_.hasConfig = 0;
-}
-void Solver::startInit(uint32 numConsGuess, const SolverParams& params) {
-	assert(!lazyRem_ && decisionLevel() == 0);
-	if (watches_.empty()) {
-		assign_.trail.reserve(shared_->numVars() + 2);
-		watches_.reserve((shared_->numVars() + 2)<<1);
-		assign_.reserve(shared_->numVars() + 2);
-	}
-	updateVars();
-	// pre-allocate some memory
-	constraints_.reserve(numConsGuess/2);
-	levels_.reserve(25);
-	if (undoHead_ == 0)  {
-		for (uint32 i = 0; i != 25; ++i) {
-			undoFree(new ConstraintDB(10));
-		}
-	}
-	if (!popRootLevel(rootLevel())) { return; }
-	if (!strategy_.hasConfig) {
-		uint32 id           = this->id();
-		uint32 hId          = strategy_.heuId; // remember active heuristic
-		strategy_           = static_cast(params);
-		strategy_.id        = id; // keep id
-		strategy_.hasConfig = 1;  // strategy is now "up to date"
-		if      (!params.ccMinRec)  { delete ccMin_; ccMin_ = 0; }
-		else if (!ccMin_)           { ccMin_ = new CCMinRecursive; }
-		if (id == params.id || !shared_->seedSolvers()) {
-			rng.srand(params.seed);
-		}
-		else {
-			RNG x(14182940); for (uint32 i = 0; i != id; ++i) { x.rand(); }
-			rng.srand(x.seed());
-		}
-		if (hId != params.heuId) { // heuristic has changed
-			resetHeuristic(this);
-		}
-		else if (heuristic_.is_owner()) {
-			heuristic_->setConfig(params.heuristic);
-		}
-	}
-	if (heuristic_.get() == &null_heuristic_g) {
-		heuristic_.reset(shared_->configuration()->heuristic(id()));
-	}
-	postHead_ = &sent_list; // disable post propagators during setup
-	heuristic_->startInit(*this);
+    if (strategy_.hasConfig) {
+        if (PostPropagator* pp = getPost(PostPropagator::priority_reserved_look)) {
+            pp->destroy(this, true);
+        }
+        ccMin_.reset();
+    }
+    strategy_.hasConfig = 0;
+}
+void Solver::startInit(uint32_t numConsGuess, const SolverParams& params) {
+    assert(not lazyRem_ && decisionLevel() == 0);
+    if (watches_.empty()) {
+        assign_.trail.reserve(shared_->numVars() + 2);
+        watches_.reserve((shared_->numVars() + 2) << 1);
+        assign_.reserve(shared_->numVars() + 2);
+    }
+    updateVars();
+    // pre-allocate some memory
+    constraints_.reserve(numConsGuess / 2);
+    levels_.reserve(25);
+    if (undoHead_ == nullptr) {
+        for ([[maybe_unused]] auto _ : irange(25u)) { undoFree(new ConstraintDB(10)); }
+    }
+    if (not popRootLevel(rootLevel())) {
+        return;
+    }
+    if (not strategy_.hasConfig) {
+        uint32_t id         = this->id();
+        uint32_t hId        = strategy_.heuId; // remember active heuristic
+        strategy_           = static_cast(params);
+        strategy_.id        = id; // keep id
+        strategy_.hasConfig = 1;  // strategy is now "up to date"
+        if (not params.ccMinRec) {
+            ccMin_.reset();
+        }
+        else if (not ccMin_) {
+            ccMin_ = std::make_unique();
+        }
+        if (id == params.id || not shared_->seedSolvers()) {
+            rng.srand(params.seed);
+        }
+        else {
+            Rng x(14182940);
+            for ([[maybe_unused]] auto _ : irange(id)) { x.rand(); }
+            rng.srand(x.seed());
+        }
+        if (hId != params.heuId) { // heuristic has changed
+            resetHeuristic(this);
+        }
+        else if (heuristic_ != &g_null_heuristic) {
+            heuristic_->setConfig(params.heuristic);
+        }
+    }
+    if (heuristic_ == &g_null_heuristic) {
+        heuristic_ = shared_->configuration()->heuristic(id());
+    }
+    postHead_ = &g_sent_list; // disable post propagators during setup
+    heuristic_->startInit(*this);
 }
 
 void Solver::updateVars() {
-	if (numVars() > shared_->numVars()) {
-		popVars(numVars() - shared_->numVars(), false, 0);
-	}
-	else {
-		assign_.resize(shared_->numVars() + 1);
-		watches_.resize(assign_.numVars()<<1);
-	}
+    if (numVars() > shared_->numVars()) {
+        popVars(numVars() - shared_->numVars(), false, nullptr);
+    }
+    else {
+        assign_.resize(shared_->numVars() + 1);
+        watches_.resize(assign_.numVars() << 1);
+    }
 }
 
 bool Solver::cloneDB(const ConstraintDB& db) {
-	while (dbIdx_ < (uint32)db.size() && !hasConflict()) {
-		if (Constraint* c = db[dbIdx_++]->cloneAttach(*this)) {
-			constraints_.push_back(c);
-		}
-	}
-	return !hasConflict();
+    while (dbIdx_ < size32(db) && not hasConflict()) {
+        if (Constraint* c = db[dbIdx_++]->cloneAttach(*this)) {
+            constraints_.push_back(c);
+        }
+    }
+    return not hasConflict();
 }
 bool Solver::preparePost() {
-	if (hasConflict()) { return false; }
-	if (initPost_ == 0){
-		initPost_ = 1;
-		FOR_EACH_POST(x, post_.head()) {
-			if (!x->init(*this)) { return false; }
-		}
-	}
-	return shared_->configuration()->addPost(*this);
+    if (hasConflict()) {
+        return false;
+    }
+    if (initPost_ == 0) {
+        initPost_ = 1;
+        if (not post_.init(*this)) {
+            return false;
+        }
+    }
+    return shared_->configuration()->addPost(*this);
 }
 
 bool Solver::endInit() {
-	if (hasConflict()) { return false; }
-	heuristic_->endInit(*this);
-	if (strategy_.signFix) {
-		for (Var v = 1; v <= numVars(); ++v) {
-			Literal x = DecisionHeuristic::selectLiteral(*this, v, 0);
-			setPref(v, ValueSet::user_value, x.sign() ? value_false : value_true);
-		}
-	}
-	postHead_ = post_.head(); // enable all post propagators
-	return propagate() && simplify();
-}
-
-bool Solver::endStep(uint32 top, const SolverParams& params) {
-	initPost_ = 0; // defer calls to PostPropagator::init()
-	if (!popRootLevel(rootLevel())) { return false; }
-	popAuxVar();
-	Literal x = shared_->stepLiteral();
-	top = std::min(top, (uint32)lastSimp_);
-	if (PostPropagator* pp = getPost(PostPropagator::priority_reserved_look)) {
-		pp->destroy(this, true);
-	}
-	if ((value(x.var()) != value_free || force(~x)) && simplify() && this != shared_->master() && shared_->ok()) {
-		Solver& m = *shared_->master();
-		for (uint32 end = (uint32)assign_.trail.size(); top < end; ++top) {
-			Literal u = assign_.trail[top];
-			if (u.var() != x.var() && !m.force(u)) { break; }
-		}
-	}
-	if (params.forgetLearnts())   { reduceLearnts(1.0f); }
-	if (params.forgetHeuristic()) { resetHeuristic(this); }
-	if (params.forgetSigns())     { resetPrefs(); }
-	if (params.forgetActivities()){ resetLearntActivities(); }
-	return true;
-}
-
-void Solver::add(Constraint* c) {
-	constraints_.push_back(c);
+    if (hasConflict()) {
+        return false;
+    }
+    heuristic_->endInit(*this);
+    if (strategy_.signFix) {
+        for (auto v : vars()) {
+            Literal x = DecisionHeuristic::selectLiteral(*this, v, 0);
+            setPref(v, ValueSet::user_value, x.sign() ? value_false : value_true);
+        }
+    }
+    postHead_ = post_.head(); // enable all post propagators
+    return propagate() && simplify();
+}
+
+bool Solver::endStep(uint32_t top, const SolverParams& params) {
+    initPost_ = 0; // defer calls to PostPropagator::init()
+    if (not popRootLevel(rootLevel())) {
+        return false;
+    }
+    popAuxVar();
+    Literal x = shared_->stepLiteral();
+    top       = std::min(top, lastSimp_);
+    if (PostPropagator* pp = getPost(PostPropagator::priority_reserved_look)) {
+        pp->destroy(this, true);
+    }
+    if ((value(x.var()) != value_free || force(~x)) && simplify() && this != shared_->master() && shared_->ok()) {
+        Solver& m = *shared_->master();
+        for (auto end = size32(assign_.trail); top < end; ++top) {
+            Literal u = assign_.trail[top];
+            if (u.var() != x.var() && not m.force(u)) {
+                break;
+            }
+        }
+    }
+    if (params.forgetLearnts()) {
+        reduceLearnts(1.0f);
+    }
+    if (params.forgetHeuristic()) {
+        resetHeuristic(this);
+    }
+    if (params.forgetSigns()) {
+        resetPrefs();
+    }
+    if (params.forgetActivities()) {
+        resetLearntActivities();
+    }
+    return true;
 }
+
+void Solver::add(Constraint* c) { constraints_.push_back(c); }
 bool Solver::add(const ClauseRep& c, bool isNew) {
-	typedef ShortImplicationsGraph::ImpType ImpType;
-	if (c.prep == 0) {
-		return ClauseCreator::create(*this, c, ClauseCreator::clause_force_simplify).ok();
-	}
-	int added = 0;
-	if (c.size > 1) {
-		if (allowImplicit(c)) { added = shared_->addImp(static_cast(c.size), c.lits, c.info.type()); }
-		else                  { return ClauseCreator::create(*this, c, ClauseCreator::clause_explicit).ok(); }
-	}
-	else {
-		Literal u = c.size ? c.lits[0] : lit_false();
-		uint32  ts= sizeVec(trail());
-		force(u);
-		added     = int(ts != trail().size());
-	}
-	if (added > 0 && isNew && c.info.learnt()) {
-		stats.addLearnt(c.size, c.info.type());
-		distribute(c.lits, c.size, c.info);
-	}
-	return !hasConflict();
+    if (c.prep == 0) {
+        return ClauseCreator::create(*this, c, ClauseCreator::clause_force_simplify).ok();
+    }
+    int added = 0;
+    if (c.size > 1) {
+        if (allowImplicit(c)) {
+            added = shared_->addImp({c.lits, c.size}, c.info.type());
+        }
+        else {
+            return ClauseCreator::create(*this, c, ClauseCreator::clause_explicit).ok();
+        }
+    }
+    else {
+        Literal  u  = c.size ? c.lits[0] : lit_false;
+        uint32_t ts = numAssignedVars();
+        force(u);
+        added = static_cast(ts != numAssignedVars());
+    }
+    if (added > 0 && isNew && c.info.learnt()) {
+        stats.addLearnt(c.size, c.info.type());
+        distribute(c.literals(), c.info);
+    }
+    return not hasConflict();
 }
 bool Solver::addPost(PostPropagator* p, bool init) {
-	post_.add(p);
-	return !init || p->init(*this);
-}
-bool Solver::addPost(PostPropagator* p)   { return addPost(p, initPost_ != 0); }
-void Solver::removePost(PostPropagator* p){ post_.remove(p); }
-PostPropagator* Solver::getPost(uint32 prio) const { return post_.find(prio); }
-uint32 Solver::receive(SharedLiterals** out, uint32 maxOut) const {
-	if (shared_->distributor.get()) {
-		return shared_->distributor->receive(*this, out, maxOut);
-	}
-	return 0;
+    post_.add(p);
+    return not init || p->init(*this);
+}
+bool            Solver::addPost(PostPropagator* p) { return addPost(p, initPost_ != 0); }
+void            Solver::removePost(PostPropagator* p) { post_.remove(p); }
+PostPropagator* Solver::getPost(uint32_t prio) const { return post_.find(prio); }
+uint32_t        Solver::receive(SharedLiterals** out, uint32_t maxOut) const {
+    if (shared_->distributor.get()) {
+        return shared_->distributor->receive(*this, out, maxOut);
+    }
+    return 0;
 }
 
 void Solver::restart() {
-	undoUntil(0);
-	++stats.restarts;
-	ccInfo_.score().bumpActivity();
+    undoUntil(0);
+    ++stats.restarts;
+    ccInfo_.score().bumpActivity();
 }
 
-SharedLiterals* Solver::distribute(const Literal* lits, uint32 size, const ConstraintInfo& extra) {
-	if (shared_->distributor.get() && !extra.aux() && (size <= 3 || shared_->distributor->isCandidate(size, extra.lbd(), extra.type()))) {
-		uint32 initialRefs = shared_->concurrency() - (size <= ClauseHead::MAX_SHORT_LEN || !shared_->physicalShare(extra.type()));
-		SharedLiterals* x  = SharedLiterals::newShareable(lits, size, extra.type(), initialRefs);
-		shared_->distributor->publish(*this, x);
-		stats.addDistributed(extra.lbd(), extra.type());
-		return initialRefs == shared_->concurrency() ? x : 0;
-	}
-	return 0;
+SharedLiterals* Solver::distribute(LitView lits, const ConstraintInfo& extra) {
+    if (not shared_->distributor || extra.aux()) {
+        return nullptr;
+    }
+    if (auto size = size32(lits); shared_->distributor->isCandidate(size, extra)) {
+        uint32_t initialRefs =
+            shared_->concurrency() - (size <= ClauseHead::max_short_len || not shared_->physicalShare(extra.type()));
+        auto* x = SharedLiterals::newShareable(lits, extra.type(), initialRefs);
+        shared_->distributor->publish(*this, x);
+        stats.addDistributed(extra.lbd(), extra.type());
+        return initialRefs == shared_->concurrency() ? x : nullptr;
+    }
+    return nullptr;
 }
 void Solver::setEnumerationConstraint(Constraint* c) {
-	if (enum_) enum_->destroy(this, true);
-	enum_ = c;
-}
-
-uint32 Solver::numConstraints() const {
-	return static_cast(constraints_.size())
-		+ (shared_ ? shared_->numBinary()+shared_->numTernary() : 0);
-}
-
-Var Solver::pushAuxVar() {
-	assert(!lazyRem_);
-	Var aux = assign_.addVar();
-	setPref(aux, ValueSet::def_value, value_false);
-	watches_.insert(watches_.end(), 2, WatchList());
-	heuristic_->updateVar(*this, aux, 1);
-	return aux;
-}
-
-void Solver::acquireProblemVar(Var var) {
-	if (validVar(var) || shared_->frozen() || numProblemVars() <= numVars() || !shared_->ok())
-		return;
-	shared_->startAddConstraints();
-}
-
-void Solver::popAuxVar(uint32 num, ConstraintDB* auxCons) {
-	num = numVars() >= shared_->numVars() ? std::min(numVars() - shared_->numVars(), num) : 0;
-	if (!num) { return; }
-	shared_->report("removing aux vars", this);
-	Dirty dirty;
-	lazyRem_ = &dirty;
-	popVars(num, true, auxCons);
-	lazyRem_ = 0;
-	shared_->report("removing aux watches", this);
-	dirty.cleanup(watches_, levels_);
-}
-Literal Solver::popVars(uint32 num, bool popLearnt, ConstraintDB* popAux) {
-	Literal pop = posLit(assign_.numVars() - num);
-	uint32  dl  = decisionLevel() + 1;
-	for (ImpliedList::iterator it = impliedLits_.begin(); it != impliedLits_.end(); ++it) {
-		if (!(it->lit < pop)) { dl = std::min(dl, it->level); }
-	}
-	for (Var v = pop.var(), end = pop.var()+num; v != end; ++v) {
-		if (value(v) != value_free) { dl = std::min(dl, level(v)); }
-	}
-	// 1. remove aux vars from assignment and watch lists
-	if (dl > rootLevel()) {
-		undoUntil(dl-1, undo_pop_proj_level);
-	}
-	else {
-		popRootLevel((rootLevel() - dl) + 1);
-		if (dl == 0) { // top-level has aux vars - cleanup manually
-			uint32 j = shared_->numUnary();
-			uint32 nUnits = assign_.units(), nFront = assign_.front, nSimps = lastSimp_;
-			for (uint32 i = j, end = sizeVec(assign_.trail), endUnits = nUnits, endFront = nFront, endSimps = lastSimp_; i != end; ++i) {
-				if (assign_.trail[i] < pop) { assign_.trail[j++] = assign_.trail[i]; }
-				else {
-					nUnits -= (i < endUnits);
-					nFront -= (i < endFront);
-					nSimps -= (i < endSimps);
-				}
-			}
-			shrinkVecTo(assign_.trail, j);
-			assign_.front = nFront;
-			assign_.setUnits(nUnits);
-			lastSimp_ = nSimps;
-		}
-	}
-	for (uint32 n = num; n--;) {
-		watches_.back().clear(true);
-		watches_.pop_back();
-		watches_.back().clear(true);
-		watches_.pop_back();
-	}
-	// 2. remove learnt constraints over aux
-	if (popLearnt) {
-		shared_->report("removing aux constraints", this);
-		ConstraintDB::size_type i, j, end = learnts_.size();
-		LitVec cc;
-		for (i = j = 0; i != end; ++i) {
-			learnts_[j++] = learnts_[i];
-			ClauseHead* c = learnts_[i]->clause();
-			if (c && c->aux()) {
-				cc.clear();
-				c->toLits(cc);
-				if (std::find_if(cc.begin(), cc.end(), std::not1(std::bind2nd(std::less(), pop))) != cc.end()) {
-					c->destroy(this, true);
-					--j;
-				}
-			}
-		}
-		learnts_.erase(learnts_.begin()+j, learnts_.end());
-	}
-	if (popAux) { destroyDB(*popAux); }
-	// 3. remove vars from solver and heuristic
-	assign_.resize(assign_.numVars()-num);
-	if (!validVar(tag_.var())) { tag_ = lit_true(); }
-	heuristic_->updateVar(*this, pop.var(), num);
-	return pop;
-}
-
-bool Solver::pushRoot(const LitVec& path, bool pushStep) {
-	// make sure we are on the current (fully propagated) root level
-	if (!popRootLevel(0) || !simplify() || !propagate()) { return false; }
-	// push path
-	if (pushStep && !pushRoot(shared_->stepLiteral())) { return false; }
-	stats.addPath(path.size());
-	for (LitVec::const_iterator it = path.begin(), end = path.end(); it != end; ++it) {
-		if (!pushRoot(*it)) { return false; }
-	}
-	ccInfo_.setActivity(1);
-	return true;
+    if (auto prev = std::exchange(enum_, c); prev && prev != c) {
+        prev->destroy(this, true);
+    }
+}
+
+uint32_t Solver::numConstraints() const {
+    return size32(constraints_) + (shared_ ? shared_->numBinary() + shared_->numTernary() : 0);
+}
+
+Var_t Solver::pushAuxVar() {
+    assert(not lazyRem_);
+    auto aux = assign_.addVar();
+    setPref(aux, ValueSet::def_value, value_false);
+    watches_.insert(watches_.end(), 2, WatchList());
+    heuristic_->updateVar(*this, aux, 1);
+    return aux;
+}
+
+void Solver::acquireProblemVar(Var_t var) {
+    if (validVar(var) || shared_->frozen() || numProblemVars() <= numVars() || not shared_->ok()) {
+        return;
+    }
+    shared_->startAddConstraints();
+}
+
+void Solver::popAuxVar(uint32_t num, ConstraintDB* auxCons) {
+    num = numVars() >= shared_->numVars() ? std::min(numVars() - shared_->numVars(), num) : 0;
+    if (not num) {
+        return;
+    }
+    shared_->report("removing aux vars", this);
+    Dirty dirty;
+    lazyRem_ = &dirty;
+    popVars(num, true, auxCons);
+    lazyRem_ = nullptr;
+    shared_->report("removing aux watches", this);
+    dirty.cleanup(watches_, levels_);
+}
+Literal Solver::popVars(uint32_t num, bool popLearnt, ConstraintDB* popAux) {
+    Literal  pop = posLit(assign_.numVars() - num);
+    uint32_t dl  = decisionLevel() + 1;
+    for (const auto& impliedLit : impliedLits_) {
+        if (impliedLit.lit >= pop) {
+            dl = std::min(dl, impliedLit.level);
+        }
+    }
+    for (auto v = pop.var(), end = pop.var() + num; v != end; ++v) {
+        if (value(v) != value_free) {
+            dl = std::min(dl, level(v));
+        }
+    }
+    // 1. remove aux vars from assignment and watch lists
+    if (dl > rootLevel()) {
+        undoUntil(dl - 1, undo_pop_proj_level);
+    }
+    else {
+        popRootLevel((rootLevel() - dl) + 1);
+        if (dl == 0) { // top-level has aux vars - cleanup manually
+            uint32_t j      = shared_->numUnary();
+            uint32_t nUnits = assign_.units(), nFront = assign_.front, nSimps = lastSimp_;
+            for (uint32_t i = j, end = size32(assign_.trail), endUnits = nUnits, endFront = nFront,
+                          endSimps = lastSimp_;
+                 i != end; ++i) {
+                if (assign_.trail[i] < pop) {
+                    assign_.trail[j++] = assign_.trail[i];
+                }
+                else {
+                    nUnits -= (i < endUnits);
+                    nFront -= (i < endFront);
+                    nSimps -= (i < endSimps);
+                }
+            }
+            shrinkVecTo(assign_.trail, j);
+            assign_.front = nFront;
+            assign_.setUnits(nUnits);
+            lastSimp_ = nSimps;
+        }
+    }
+    for (uint32_t n = num; n--;) {
+        releaseVec(watches_.back());
+        watches_.pop_back();
+        releaseVec(watches_.back());
+        watches_.pop_back();
+    }
+    // 2. remove learnt constraints over aux
+    if (popLearnt) {
+        shared_->report("removing aux constraints", this);
+        ConstraintDB::size_type os = 0;
+        LitVec                  cc;
+        for (Constraint* con : learnts_) {
+            if (ClauseHead* clause = con->clause(); clause && clause->aux()) {
+                cc.clear();
+                clause->toLits(cc);
+                if (std::ranges::any_of(cc, [&pop](Literal x) { return x >= pop; })) {
+                    con->destroy(this, true);
+                    continue;
+                }
+            }
+            learnts_[os++] = con;
+        }
+        shrinkVecTo(learnts_, os);
+    }
+    if (popAux) {
+        destroyDB(*popAux);
+    }
+    // 3. remove vars from solver and heuristic
+    assign_.resize(assign_.numVars() - num);
+    if (not validVar(tag_.var())) {
+        tag_ = lit_true;
+    }
+    heuristic_->updateVar(*this, pop.var(), num);
+    return pop;
+}
+
+bool Solver::pushRoot(LitView path, bool pushStep) {
+    // make sure we are on the current (fully propagated) root level
+    if (not popRootLevel(0) || not simplify() || not propagate()) {
+        return false;
+    }
+    // push path
+    if (pushStep && not pushRoot(shared_->stepLiteral())) {
+        return false;
+    }
+    stats.addPath(path.size());
+    for (auto lit : path) {
+        if (not pushRoot(lit)) {
+            return false;
+        }
+    }
+    ccInfo_.setActivity(1);
+    return true;
 }
 
 bool Solver::pushRoot(Literal x) {
-	if (hasConflict())                 { return false; }
-	if (decisionLevel()!= rootLevel()) { popRootLevel(0);  }
-	if (queueSize() && !propagate())   { return false;    }
-	if (value(x.var()) != value_free)  { return isTrue(x);}
-	assume(x); --stats.choices;
-	pushRootLevel();
-	return propagate();
-}
-
-bool Solver::popRootLevel(uint32 n, LitVec* popped, bool aux)  {
-	clearStopConflict();
-	uint32 newRoot = levels_.root - std::min(n, rootLevel());
-	if (popped && newRoot < rootLevel()) {
-		for (uint32 i = newRoot+1; i <= rootLevel(); ++i) {
-			Literal x = decision(i);
-			if (aux || !auxVar(x.var())) { popped->push_back(x); }
-		}
-	}
-	if (n) { ccInfo_.setActivity(1); }
-	levels_.root = newRoot;
-	levels_.flip = rootLevel();
-	levels_.mode = 0;
-	impliedLits_.front = 0;
-	bool tagActive     = isTrue(tagLiteral());
-	// go back to new root level and re-assert still implied literals
-	undoUntil(rootLevel(), undo_pop_proj_level);
-	if (tagActive && !isTrue(tagLiteral())) {
-		removeConditional();
-	}
-	return !hasConflict();
-}
-
-bool Solver::clearAssumptions()  {
-	return popRootLevel(rootLevel())
-		&& simplify();
+    if (hasConflict()) {
+        return false;
+    }
+    if (decisionLevel() != rootLevel()) {
+        popRootLevel(0);
+    }
+    if (queueSize() && not propagate()) {
+        return false;
+    }
+    if (value(x.var()) != value_free) {
+        return isTrue(x);
+    }
+    assume(x);
+    --stats.choices;
+    pushRootLevel();
+    return propagate();
+}
+
+bool Solver::popRootLevel(uint32_t n, LitVec* popped, bool aux) {
+    clearStopConflict();
+    uint32_t newRoot = levels_.root - std::min(n, rootLevel());
+    if (popped && newRoot < rootLevel()) {
+        for (uint32_t i : irange(newRoot + 1, rootLevel() + 1)) {
+            Literal x = decision(i);
+            if (aux || not auxVar(x.var())) {
+                popped->push_back(x);
+            }
+        }
+    }
+    if (n) {
+        ccInfo_.setActivity(1);
+    }
+    levels_.root       = newRoot;
+    levels_.flip       = rootLevel();
+    levels_.mode       = 0;
+    impliedLits_.front = 0;
+    bool tagActive     = isTrue(tagLiteral());
+    // go back to new root level and re-assert still implied literals
+    undoUntil(rootLevel(), undo_pop_proj_level);
+    if (tagActive && not isTrue(tagLiteral())) {
+        removeConditional();
+    }
+    return not hasConflict();
 }
 
+bool Solver::clearAssumptions() { return popRootLevel(rootLevel()) && simplify(); }
+
 void Solver::clearStopConflict() {
-	if (hasStopConflict()) {
-		levels_.root  = conflict_[1].rep();
-		levels_.flip  = conflict_[2].rep();
-		assign_.front = conflict_[3].rep();
-		conflict_.clear();
-	}
+    if (hasStopConflict()) {
+        levels_.root  = conflict_[1].rep();
+        levels_.flip  = conflict_[2].rep();
+        assign_.front = conflict_[3].rep();
+        conflict_.clear();
+    }
 }
 
 void Solver::setStopConflict() {
-	if (!hasConflict()) {
-		// we use the nogood {FALSE} to represent the unrecoverable conflict -
-		// note that {FALSE} can otherwise never be a violated nogood because
-		// TRUE is always true in every solver
-		conflict_.push_back(lit_false());
-		// remember the current root-level
-		conflict_.push_back(Literal::fromRep(rootLevel()));
-		// remember the current bt-level
-		conflict_.push_back(Literal::fromRep(backtrackLevel()));
-		// remember the current propagation queue
-		conflict_.push_back(Literal::fromRep(assign_.front));
-	}
-	// artificially increase root level -
-	// this way, the solver is prevented from resolving the conflict
-	pushRootLevel(decisionLevel());
+    if (not hasConflict()) {
+        // we use the nogood {FALSE} to represent the unrecoverable conflict -
+        // note that {FALSE} can otherwise never be a violated nogood because
+        // TRUE is always true in every solver
+        conflict_.push_back(lit_false);
+        // remember the current root-level
+        conflict_.push_back(Literal::fromRep(rootLevel()));
+        // remember the current bt-level
+        conflict_.push_back(Literal::fromRep(backtrackLevel()));
+        // remember the current propagation queue
+        conflict_.push_back(Literal::fromRep(assign_.front));
+    }
+    // artificially increase root level -
+    // this way, the solver is prevented from resolving the conflict
+    pushRootLevel(decisionLevel());
 }
 
 void Solver::copyGuidingPath(LitVec& gpOut) {
-	uint32 aux = rootLevel()+1;
-	gpOut.clear();
-	for (uint32 i = 1, end = rootLevel()+1; i != end; ++i) {
-		Literal x = decision(i);
-		if      (!auxVar(x.var())) { gpOut.push_back(x); }
-		else if (i < aux)          { aux = i; }
-	}
-	for (ImpliedList::iterator it = impliedLits_.begin(); it != impliedLits_.end(); ++it) {
-		if (it->level <= rootLevel() && (it->ante.ante().isNull() || it->level < aux) && !auxVar(it->lit.var())) {
-			gpOut.push_back(it->lit);
-		}
-	}
+    uint32_t aux = rootLevel() + 1;
+    gpOut.clear();
+    for (uint32_t i : irange(1u, rootLevel() + 1)) {
+        Literal x = decision(i);
+        if (not auxVar(x.var())) {
+            gpOut.push_back(x);
+        }
+        else if (i < aux) {
+            aux = i;
+        }
+    }
+    for (const auto& lit : impliedLits_) {
+        if (lit.level <= rootLevel() && (lit.ante.ante().isNull() || lit.level < aux) && not auxVar(lit.lit.var())) {
+            gpOut.push_back(lit.lit);
+        }
+    }
 }
 bool Solver::splittable() const {
-	if (decisionLevel() == rootLevel() || frozenLevel(rootLevel()+1)) { return false; }
-	if (numAuxVars()) { // check if gp would contain solver local aux var
-		uint32 minAux = rootLevel() + 2;
-		for (uint32 i = 1; i != minAux; ++i) {
-			if (auxVar(decision(i).var()) && decision(i) != tag_) { return false; }
-		}
-		for (ImpliedList::iterator it = impliedLits_.begin(); it != impliedLits_.end(); ++it) {
-			if (it->ante.ante().isNull() && it->level < minAux && auxVar(it->lit.var()) && it->lit != tag_) { return false; }
-		}
-	}
-	return true;
+    if (decisionLevel() == rootLevel() || frozenLevel(rootLevel() + 1)) {
+        return false;
+    }
+    if (numAuxVars()) { // check if gp would contain solver local aux var
+        uint32_t minAux = rootLevel() + 2;
+        for (uint32_t i : irange(1u, minAux)) {
+            if (auxVar(decision(i).var()) && decision(i) != tag_) {
+                return false;
+            }
+        }
+        for (const auto& lit : impliedLits_) {
+            if (lit.ante.ante().isNull() && lit.level < minAux && auxVar(lit.lit.var()) && lit.lit != tag_) {
+                return false;
+            }
+        }
+    }
+    return true;
 }
 bool Solver::split(LitVec& out) {
-	if (!splittable()) { return false; }
-	copyGuidingPath(out);
-	pushRootLevel();
-	out.push_back(~decision(rootLevel()));
-	splitReq_ = false;
-	stats.addSplit();
-	return true;
+    if (not splittable()) {
+        return false;
+    }
+    copyGuidingPath(out);
+    pushRootLevel();
+    out.push_back(~decision(rootLevel()));
+    splitReq_ = false;
+    stats.addSplit();
+    return true;
 }
 bool Solver::requestSplit() {
-	splitReq_ = true;
-	bool res  = splittable();
-	if (!res && decisionLevel() > rootLevel() && !frozenLevel(rootLevel()+1)) {
-		splitReq_ = false; // solver can't split because split would contain aux vars
-	}
-	return res;
-}
-bool Solver::clearSplitRequest() {
-	if (splitReq_) {
-		splitReq_ = false;
-		return true;
-	}
-	return false;
-}
+    splitReq_ = true;
+    bool res  = splittable();
+    if (not res && decisionLevel() > rootLevel() && not frozenLevel(rootLevel() + 1)) {
+        splitReq_ = false; // solver can't split because split would contain aux vars
+    }
+    return res;
+}
+bool Solver::clearSplitRequest() { return std::exchange(splitReq_, false); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Solver: Watch management
 ////////////////////////////////////////////////////////////////////////////////////////
-uint32 Solver::numWatches(Literal p) const {
-	assert( validVar(p.var()) );
-	if (!validWatch(p)) return 0;
-	uint32 n = static_cast(watches_[p.id()].size());
-	if (!auxVar(p.var())){
-		n += shared_->shortImplications().numEdges(p);
-	}
-	return n;
+uint32_t Solver::numWatches(Literal p) const {
+    assert(validVar(p.var()));
+    if (not validWatch(p)) {
+        return 0;
+    }
+    auto n = size32(watches_[p.id()]);
+    if (not auxVar(p.var())) {
+        n += shared_->shortImplications().numEdges(p);
+    }
+    return n;
 }
 
-bool Solver::hasWatch(Literal p, Constraint* c) const {
-	return getWatch(p, c) != 0;
-}
+bool Solver::hasWatch(Literal p, Constraint* c) const { return getWatch(p, c) != nullptr; }
 
 bool Solver::hasWatch(Literal p, ClauseHead* h) const {
-	if (!validWatch(p)) return false;
-	const WatchList& pList = watches_[p.id()];
-	return !pList.empty() && std::find_if(pList.left_begin(), pList.left_end(), ClauseWatch::EqHead(h)) != pList.left_end();
+    return validWatch(p) && std::ranges::any_of(watches_[p.id()].left_view(), ClauseWatch::EqHead(h));
 }
 
 GenericWatch* Solver::getWatch(Literal p, Constraint* c) const {
-	if (!validWatch(p)) return 0;
-	const WatchList& pList = watches_[p.id()];
-	WatchList::const_right_iterator it = std::find_if(pList.right_begin(), pList.right_end(), GenericWatch::EqConstraint(c));
-	return it != pList.right_end()
-		? &const_cast(*it)
-		: 0;
+    if (not validWatch(p)) {
+        return nullptr;
+    }
+    const auto& pList = watches_[p.id()];
+    auto        it    = std::find_if(pList.right_begin(), pList.right_end(), GenericWatch::EqConstraint(c));
+    return it != pList.right_end() ? &const_cast(*it) : nullptr;
 }
 
 void Solver::removeWatch(const Literal& p, Constraint* c) {
-	if (!validWatch(p)) { return; }
-	WatchList& pList = watches_[p.id()];
-	if (!lazyRem_ || !lazyRem_->add(p, pList, c)) {
-		pList.erase_right(std::find_if(pList.right_begin(), pList.right_end(), GenericWatch::EqConstraint(c)));
-	}
+    if (not validWatch(p)) {
+        return;
+    }
+    auto& pList = watches_[p.id()];
+    if (not lazyRem_ || not lazyRem_->add(p, pList, c)) {
+        pList.erase_right(std::find_if(pList.right_begin(), pList.right_end(), GenericWatch::EqConstraint(c)));
+    }
 }
 
 void Solver::removeWatch(const Literal& p, ClauseHead* h) {
-	if (!validWatch(p)) { return; }
-	WatchList& pList = watches_[p.id()];
-	if (!lazyRem_ || !lazyRem_->add(p, pList, h)) {
-		pList.erase_left(std::find_if(pList.left_begin(), pList.left_end(), ClauseWatch::EqHead(h)));
-	}
-}
-
-bool Solver::removeUndoWatch(uint32 dl, Constraint* c) {
-	assert(dl != 0 && dl <= decisionLevel() );
-	if (levels_[dl-1].undo) {
-		ConstraintDB& uList = *levels_[dl-1].undo;
-		if (!lazyRem_ || !lazyRem_->add(dl - 1, uList, c)) {
-			ConstraintDB::iterator it = std::find(uList.begin(), uList.end(), c);
-			if (it != uList.end()) {
-				*it = uList.back();
-				uList.pop_back();
-				return true;
-			}
-		}
-	}
-	return false;
+    if (not validWatch(p)) {
+        return;
+    }
+    auto& pList = watches_[p.id()];
+    if (not lazyRem_ || not lazyRem_->add(p, pList, h)) {
+        pList.erase_left(std::find_if(pList.left_begin(), pList.left_end(), ClauseWatch::EqHead(h)));
+    }
+}
+
+bool Solver::removeUndoWatch(uint32_t dl, Constraint* c) {
+    assert(dl != 0 && dl <= decisionLevel());
+    if (levels_[dl - 1].undo) {
+        auto& uList = *levels_[dl - 1].undo;
+        if (not lazyRem_ || not lazyRem_->add(dl - 1, uList, c)) {
+            if (auto it = std::ranges::find(uList, c); it != uList.end()) {
+                *it = uList.back();
+                uList.pop_back();
+                return true;
+            }
+        }
+    }
+    return false;
 }
 void Solver::destroyDB(ConstraintDB& db) {
-	if (!db.empty()) {
-		Dirty dirty;
-		if (!lazyRem_) { lazyRem_ = &dirty; }
-		for (ConstraintDB::const_iterator it = db.begin(), end = db.end(); it != end; ++it) {
-			(*it)->destroy(this, true);
-		}
-		db.clear();
-		if (lazyRem_ == &dirty) {
-			lazyRem_ = 0;
-			dirty.cleanup(watches_, levels_);
-		}
-	}
+    if (not db.empty()) {
+        Dirty dirty;
+        if (not lazyRem_) {
+            lazyRem_ = &dirty;
+        }
+        for (auto* it : db) { it->destroy(this, true); }
+        db.clear();
+        if (lazyRem_ == &dirty) {
+            lazyRem_ = nullptr;
+            dirty.cleanup(watches_, levels_);
+        }
+    }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Solver: Basic DPLL-functions
@@ -686,646 +779,691 @@ void Solver::destroyDB(ConstraintDB& db) {
 // removes all satisfied binary and ternary clauses as well
 // as all constraints for which Constraint::simplify returned true.
 bool Solver::simplify() {
-	if (decisionLevel() != 0) return true;
-	if (hasConflict())        return false;
-	if (lastSimp_ != (uint32)assign_.trail.size()) {
-		uint32 old = lastSimp_;
-		if (!simplifySAT()) { return false; }
-		assert(lastSimp_ == (uint32)assign_.trail.size());
-		heuristic_->simplify(*this, old);
-	}
-	if (shufSimp_) { simplifySAT(); }
-	return true;
-}
-Var  Solver::pushTagVar(bool pushToRoot) {
-	if (isSentinel(tag_)) { tag_ = posLit(pushAuxVar()); }
-	if (pushToRoot)       { pushRoot(tag_); }
-	return tag_.var();
+    if (decisionLevel() != 0) {
+        return true;
+    }
+    if (hasConflict()) {
+        return false;
+    }
+    if (lastSimp_ != size32(assign_.trail)) {
+        uint32_t old = lastSimp_;
+        if (not simplifySat()) {
+            return false;
+        }
+        assert(lastSimp_ == size32(assign_.trail) && lastSimp_ >= old);
+        heuristic_->simplify(*this, trailView(old));
+    }
+    if (shufSimp_) {
+        simplifySat();
+    }
+    return true;
+}
+Var_t Solver::pushTagVar(bool pushToRoot) {
+    if (isSentinel(tag_)) {
+        tag_ = posLit(pushAuxVar());
+    }
+    if (pushToRoot) {
+        pushRoot(tag_);
+    }
+    return tag_.var();
 }
 void Solver::removeConditional() {
-	Literal p = ~tagLiteral();
-	if (!isSentinel(p)) {
-		ConstraintDB::size_type i, j, end = learnts_.size();
-		for (i = j = 0; i != end; ++i) {
-			ClauseHead* c = learnts_[i]->clause();
-			if (!c || !c->tagged()) {
-				learnts_[j++] = learnts_[i];
-			}
-			else {
-				c->destroy(this, true);
-			}
-		}
-		learnts_.erase(learnts_.begin()+j, learnts_.end());
-	}
+    if (Literal p = ~tagLiteral(); isSentinel(p)) {
+        return;
+    }
+    erase_if(learnts_, [&](Constraint* con) {
+        if (ClauseHead* clause = con->clause(); clause && clause->tagged()) {
+            con->destroy(this, true);
+            return true;
+        }
+        return false;
+    });
 }
 
 void Solver::strengthenConditional() {
-	Literal p = ~tagLiteral();
-	if (!isSentinel(p)) {
-		ConstraintDB::size_type i, j, end = learnts_.size();
-		for (i = j = 0; i != end; ++i) {
-			ClauseHead* c = learnts_[i]->clause();
-			if (!c || !c->tagged() || !c->strengthen(*this, p, true).second) {
-				learnts_[j++] = learnts_[i];
-			}
-			else {
-				assert((decisionLevel() == rootLevel() || !c->locked(*this)) && "Solver::strengthenConditional(): must not remove locked constraint!");
-				c->destroy(this, false);
-			}
-		}
-		learnts_.erase(learnts_.begin()+j, learnts_.end());
-	}
-}
-
-bool Solver::simplifySAT() {
-	if (queueSize() > 0 && !propagate()) {
-		return false;
-	}
-	assert(assign_.qEmpty());
-	uint32 start  = lastSimp_;
-	assign_.front = start;
-	lastSimp_     = (uint32)assign_.trail.size();
-	for (Literal p; !assign_.qEmpty(); ) {
-		p = assign_.qPop();
-		releaseVec(watches_[p.id()]);
-		releaseVec(watches_[(~p).id()]);
-	}
-	bool shuffle = shufSimp_ != 0;
-	shufSimp_    = 0;
-	if (shuffle) {
-		std::random_shuffle(constraints_.begin(), constraints_.end(), rng);
-		std::random_shuffle(learnts_.begin(), learnts_.end(), rng);
-	}
-	if (isMaster()) { shared_->simplify(start, shuffle); }
-	else            { simplifyDB(*this, constraints_, shuffle); }
-	simplifyDB(*this, learnts_, shuffle);
-	FOR_EACH_POST(x, postHead_) {
-		if (x->simplify(*this, shuffle)) {
-			post_.remove(x);
-			x->destroy(this, false);
-		}
-	}
-	if (enum_ && enum_->simplify(*this, shuffle)) {
-		enum_->destroy(this, false);
-		enum_ = 0;
-	}
-	return true;
-}
-
-void Solver::setConflict(Literal p, const Antecedent& a, uint32 data) {
-	++stats.conflicts;
-	conflict_.push_back(~p);
-	if (searchMode() != SolverStrategies::no_learning && !a.isNull()) {
-		if (data == UINT32_MAX) {
-			a.reason(*this, p, conflict_);
-		}
-		else {
-			// temporarily replace old data with new data
-			uint32 saved = assign_.data(p.var());
-			assign_.setData(p.var(), data);
-			// extract conflict using new data
-			a.reason(*this, p, conflict_);
-			// restore old data
-			assign_.setData(p.var(), saved);
-		}
-	}
+    if (Literal p = ~tagLiteral(); not isSentinel(p)) {
+        erase_if(learnts_, [&](Constraint* con) {
+            if (ClauseHead* clause = con->clause();
+                clause && clause->tagged() && clause->strengthen(*this, p, true).removeClause) {
+                assert((decisionLevel() == rootLevel() || not con->locked(*this)) &&
+                       "Solver::strengthenConditional(): must not remove locked constraint!");
+                con->destroy(this, true);
+                return true;
+            }
+            return false;
+        });
+    }
+}
+
+bool Solver::simplifySat() {
+    if (queueSize() > 0 && not propagate()) {
+        return false;
+    }
+    assert(assign_.qEmpty());
+    uint32_t start = lastSimp_;
+    assign_.front  = start;
+    lastSimp_      = size32(assign_.trail);
+    for (Literal p; not assign_.qEmpty();) {
+        p = assign_.qPop();
+        releaseVec(watches_[p.id()]);
+        releaseVec(watches_[(~p).id()]);
+    }
+    bool shuffle = shufSimp_ != 0;
+    shufSimp_    = 0;
+    if (shuffle) {
+        rng.shuffle(constraints_.begin(), constraints_.end());
+        rng.shuffle(learnts_.begin(), learnts_.end());
+    }
+    if (isMaster()) {
+        shared_->simplify(trailView(start), shuffle);
+    }
+    else {
+        simplifyDB(*this, constraints_, shuffle);
+    }
+    simplifyDB(*this, learnts_, shuffle);
+    if (postHead_ == post_.head()) {
+        post_.simplify(*this, shuffle);
+    }
+    if (enum_ && enum_->simplify(*this, shuffle)) {
+        enum_->destroy(this, false);
+        enum_ = nullptr;
+    }
+    return true;
+}
+
+void Solver::setConflict(Literal p, const Antecedent& a, uint32_t data) {
+    ++stats.conflicts;
+    conflict_.push_back(~p);
+    if (searchMode() != SolverStrategies::no_learning && not a.isNull()) {
+        if (data == UINT32_MAX) {
+            a.reason(*this, p, conflict_);
+        }
+        else {
+            // temporarily replace old data with new data
+            uint32_t saved = assign_.data(p.var());
+            assign_.setData(p.var(), data);
+            // extract conflict using new data
+            a.reason(*this, p, conflict_);
+            // restore old data
+            assign_.setData(p.var(), saved);
+        }
+    }
 }
 bool Solver::force(const ImpliedLiteral& p) {
-	// Already implied?
-	if (isTrue(p.lit)) {
-		if (level(p.lit.var()) <= p.level) { return true; }
-		if (ImpliedLiteral* x = impliedLits_.find(p.lit)) {
-			if (x->level > p.level) {
-				*x = p;
-				setReason(p.lit, p.ante.ante(), p.ante.data());
-			}
-			return true;
-		}
-	}
-	if (undoUntil(p.level) != p.level) {
-		// Logically the implication is on level p.level.
-		// Store enough information so that p can be re-assigned once we backtrack.
-		impliedLits_.add(decisionLevel(), p);
-	}
-	return (isTrue(p.lit) && setReason(p.lit, p.ante.ante(), p.ante.data())) || force(p.lit, p.ante.ante(), p.ante.data());
+    // Already implied?
+    if (isTrue(p.lit)) {
+        if (level(p.lit.var()) <= p.level) {
+            return true;
+        }
+        if (auto* x = impliedLits_.find(p.lit); x) {
+            if (x->level > p.level) {
+                *x = p;
+                setReason(p.lit, p.ante.ante(), p.ante.data());
+            }
+            return true;
+        }
+    }
+    if (undoUntil(p.level) != p.level) {
+        // Logically the implication is on level p.level.
+        // Store enough information so that p can be re-assigned once we backtrack.
+        impliedLits_.add(decisionLevel(), p);
+    }
+    return (isTrue(p.lit) && setReason(p.lit, p.ante.ante(), p.ante.data())) ||
+           force(p.lit, p.ante.ante(), p.ante.data());
 }
 
 bool Solver::assume(const Literal& p) {
-	if (value(p.var()) == value_free) {
-		assert(decisionLevel() != assign_.maxLevel());
-		++stats.choices;
-		levels_.push_back(DLevel(numAssignedVars(), 0));
-		return assign_.assign(p, decisionLevel(), Antecedent());
-	}
-	return isTrue(p);
+    if (value(p.var()) == value_free) {
+        assert(decisionLevel() != assign_.maxLevel());
+        ++stats.choices;
+        levels_.push_back(DLevel(numAssignedVars(), nullptr));
+        return assign_.assign(p, decisionLevel(), Antecedent());
+    }
+    return isTrue(p);
 }
 
 void Solver::cancelPropagation() {
-	assign_.qReset();
-	for (PostPropagator* r = *postHead_; r; r = r->next) { r->reset(); }
+    assign_.qReset();
+    for (auto* r = *postHead_; r; r = r->next) { r->reset(); }
 }
 
 bool Solver::propagate() {
-	if (unitPropagate() && postPropagate(postHead_, 0)) {
-		assert(queueSize() == 0);
-		return true;
-	}
-	cancelPropagation();
-	return false;
-}
-
-bool Solver::propagateFrom(PostPropagator* p) {
-	assert((p && *postHead_) && "OP not allowed during init!");
-	assert(queueSize() == 0);
-	for (PostPropagator** r = postHead_; *r;) {
-		if      (*r != p)             { r = &(*r)->next; }
-		else if (postPropagate(r, 0)) { break; }
-		else {
-			cancelPropagation();
-			return false;
-		}
-	}
-	assert(queueSize() == 0);
-	return true;
+    if (unitPropagate() && postPropagate(postHead_, nullptr)) {
+        assert(queueSize() == 0);
+        return true;
+    }
+    cancelPropagation();
+    return false;
+}
+
+bool Solver::propagateFrom(const PostPropagator* p) {
+    assert((p && *postHead_) && "OP not allowed during init!");
+    assert(queueSize() == 0);
+    for (PostPropagator** r = postHead_; *r;) {
+        if (*r != p) {
+            r = &(*r)->next;
+        }
+        else if (postPropagate(r, nullptr)) {
+            break;
+        }
+        else {
+            cancelPropagation();
+            return false;
+        }
+    }
+    assert(queueSize() == 0);
+    return true;
 }
 
 bool Solver::propagateUntil(PostPropagator* p) {
-	assert((!p || *postHead_) && "OP not allowed during init!");
-	return unitPropagate() && (p == *postHead_ || postPropagate(postHead_, p));
-}
-
-Constraint::PropResult ClauseHead::propagate(Solver& s, Literal p, uint32&) {
-	Literal* head = head_;
-	uint32 wLit   = (head[1] == ~p); // pos of false watched literal
-	if (s.isTrue(head[1-wLit])) {
-		return Constraint::PropResult(true, true);
-	}
-	else if (!s.isFalse(head[2])) {
-		assert(!isSentinel(head[2]) && "Invalid ClauseHead!");
-		head[wLit] = head[2];
-		head[2]    = ~p;
-		s.addWatch(~head[wLit], ClauseWatch(this));
-		return Constraint::PropResult(true, false);
-	}
-	else if (updateWatch(s, wLit)) {
-		assert(!s.isFalse(head_[wLit]));
-		s.addWatch(~head[wLit], ClauseWatch(this));
-		return Constraint::PropResult(true, false);
-	}
-	return PropResult(s.force(head_[1^wLit], this), true);
+    assert((not p || *postHead_) && "OP not allowed during init!");
+    return unitPropagate() && (p == *postHead_ || postPropagate(postHead_, p));
+}
+
+Constraint::PropResult ClauseHead::propagate(Solver& s, Literal p, uint32_t&) {
+    Literal* head = head_;
+    uint32_t wLit = (head[1] == ~p); // pos of false watched literal
+    if (s.isTrue(head[1 - wLit])) {
+        return PropResult(true, true);
+    }
+    if (not s.isFalse(head[2])) {
+        assert(not isSentinel(head[2]) && "Invalid ClauseHead!");
+        head[wLit] = head[2];
+        head[2]    = ~p;
+        s.addWatch(~head[wLit], ClauseWatch(this));
+        return PropResult(true, false);
+    }
+    if (updateWatch(s, wLit)) {
+        assert(not s.isFalse(head_[wLit]));
+        s.addWatch(~head[wLit], ClauseWatch(this));
+        return PropResult(true, false);
+    }
+    return PropResult(s.force(head_[1 ^ wLit], this), true);
 }
 
 bool Solver::unitPropagate() {
-	assert(!hasConflict());
-	Literal p, q, r;
-	uint32 idx, ignore, DL = decisionLevel();
-	const ShortImplicationsGraph& btig = shared_->shortImplications();
-	const uint32 maxIdx = btig.size();
-	while ( !assign_.qEmpty() ) {
-		p             = assign_.qPop();
-		idx           = p.id();
-		WatchList& wl = watches_[idx];
-		// first: short clause BCP
-		if (idx < maxIdx && !btig.propagate(*this, p)) {
-			return false;
-		}
-		// second: clause BCP
-		if (wl.left_size() != 0) {
-			WatchList::left_iterator it, end, j = wl.left_begin();
-			Constraint::PropResult res;
-			for (it = wl.left_begin(), end = wl.left_end();  it != end;  ) {
-				ClauseWatch& w = *it++;
-				res = w.head->ClauseHead::propagate(*this, p, ignore);
-				if (res.keepWatch) {
-					*j++ = w;
-				}
-				if (!res.ok) {
-					wl.shrink_left(std::copy(it, end, j));
-					return false;
-				}
-			}
-			wl.shrink_left(j);
-		}
-		// third: general constraint BCP
-		if (wl.right_size() != 0) {
-			WatchList::right_iterator it, end, j = wl.right_begin();
-			Constraint::PropResult res;
-			for (it = wl.right_begin(), end = wl.right_end(); it != end; ) {
-				GenericWatch& w = *it++;
-				res = w.propagate(*this, p);
-				if (res.keepWatch) {
-					*j++ = w;
-				}
-				if (!res.ok) {
-					wl.shrink_right(std::copy(it, end, j));
-					return false;
-				}
-			}
-			wl.shrink_right(j);
-		}
-	}
-	return DL || assign_.markUnits();
+    assert(not hasConflict());
+    uint32_t                      ignore, dl = decisionLevel();
+    const ShortImplicationsGraph& btig   = shared_->shortImplications();
+    const uint32_t                maxIdx = btig.size();
+    while (not assign_.qEmpty()) {
+        Literal    p   = assign_.qPop();
+        uint32_t   idx = p.id();
+        WatchList& wl  = watches_[idx];
+        // first: short clause BCP
+        if (idx < maxIdx && not btig.propagate(*this, p)) {
+            return false;
+        }
+        // second: clause BCP
+        if (wl.left_size() != 0) {
+            auto j = wl.left_begin();
+            for (auto it = j, end = wl.left_end(); it != end;) {
+                ClauseWatch& w   = *it++;
+                auto         res = w.head->ClauseHead::propagate(*this, p, ignore);
+                if (res.keepWatch) {
+                    *j++ = w;
+                }
+                if (not res.ok) {
+                    wl.shrink_left(std::copy(it, end, j));
+                    return false;
+                }
+            }
+            wl.shrink_left(j);
+        }
+        // third: general constraint BCP
+        if (wl.right_size() != 0) {
+            auto j = wl.right_begin();
+            for (auto it = j, end = wl.right_end(); it != end;) {
+                GenericWatch& w   = *it++;
+                auto          res = w.propagate(*this, p);
+                if (res.keepWatch) {
+                    *j++ = w;
+                }
+                if (not res.ok) {
+                    wl.shrink_right(std::copy(it, end, j));
+                    return false;
+                }
+            }
+            wl.shrink_right(j);
+        }
+    }
+    return dl || assign_.markUnits();
 }
 
 bool Solver::postPropagate(PostPropagator** start, PostPropagator* stop) {
-	for (PostPropagator** r = start, *t; *r != stop;) {
-		t = *r;
-		if (!t->propagateFixpoint(*this, stop)) { return false; }
-		assert(queueSize() == 0);
-		if (t == *r) { r = &t->next; }
-		// else: t was removed during propagate
-	}
-	return true;
+    for (PostPropagator **r = start, *t; *r != stop;) {
+        t = *r;
+        if (not t->propagateFixpoint(*this, stop)) {
+            return false;
+        }
+        assert(queueSize() == 0);
+        if (t == *r) {
+            r = &t->next;
+        }
+        // else: t was removed during propagate
+    }
+    return true;
 }
 
 bool Solver::test(Literal p, PostPropagator* c) {
-	assert(value(p.var()) == value_free && !hasConflict());
-	assume(p); --stats.choices;
-	uint32 pLevel = decisionLevel();
-	freezeLevel(pLevel); // can't split-off this level
-	if (propagateUntil(c)) {
-		assert(decisionLevel() == pLevel && "Invalid PostPropagators");
-		if (c) c->undoLevel(*this);
-		undoUntil(pLevel-1);
-		return true;
-	}
-	assert(decisionLevel() == pLevel && "Invalid PostPropagators");
-	unfreezeLevel(pLevel);
-	cancelPropagation();
-	return false;
+    assert(value(p.var()) == value_free && not hasConflict());
+    assume(p);
+    --stats.choices;
+    uint32_t pLevel = decisionLevel();
+    freezeLevel(pLevel); // can't split-off this level
+    if (propagateUntil(c)) {
+        assert(decisionLevel() == pLevel && "Invalid PostPropagators");
+        if (c) {
+            c->undoLevel(*this);
+        }
+        undoUntil(pLevel - 1);
+        return true;
+    }
+    assert(decisionLevel() == pLevel && "Invalid PostPropagators");
+    unfreezeLevel(pLevel);
+    cancelPropagation();
+    return false;
 }
 
 bool Solver::resolveConflict() {
-	assert(hasConflict());
-	if (decisionLevel() > rootLevel()) {
-		if (decisionLevel() != backtrackLevel() && searchMode() != SolverStrategies::no_learning) {
-			uint32 uipLevel = analyzeConflict();
-			uint32 dl       = decisionLevel();
-			stats.addConflict(dl, uipLevel, backtrackLevel(), ccInfo_.lbd());
-			if (dynLimit_) { dynLimit_->update(dl, ccInfo_.lbd()); }
-			if (shared_->reportMode()) {
-				sharedContext()->report(NewConflictEvent(*this, cc_, ccInfo_));
-			}
-			undoUntil( uipLevel );
-			return ClauseCreator::create(*this, cc_, ClauseCreator::clause_no_prepare, ccInfo_);
-		}
-		else {
-			return backtrack();
-		}
-	}
-	return false;
+    assert(hasConflict());
+    if (decisionLevel() > rootLevel()) {
+        if (decisionLevel() != backtrackLevel() && searchMode() != SolverStrategies::no_learning) {
+            uint32_t uipLevel = analyzeConflict();
+            uint32_t dl       = decisionLevel();
+            stats.addConflict(dl, uipLevel, backtrackLevel(), ccInfo_.lbd());
+            if (dynLimit_) {
+                dynLimit_->update(dl, ccInfo_.lbd());
+            }
+            if (shared_->reportMode()) {
+                sharedContext()->report(NewConflictEvent(*this, cc_, ccInfo_));
+            }
+            undoUntil(uipLevel);
+            return ClauseCreator::create(*this, cc_, ClauseCreator::clause_no_prepare, ccInfo_).ok();
+        }
+        else {
+            return backtrack();
+        }
+    }
+    return false;
 }
 
 bool Solver::backtrack() {
-	Literal lastChoiceInverted;
-	do {
-		if (decisionLevel() == rootLevel()) {
-			setStopConflict();
-			return false;
-		}
-		lastChoiceInverted = ~decision(decisionLevel());
-		undoUntil(decisionLevel() - 1, undo_pop_proj_level);
-		setBacktrackLevel(decisionLevel(), undo_pop_bt_level);
-	} while (hasConflict() || !force(lastChoiceInverted, 0));
-	// remember flipped literal for copyGuidingPath()
-	impliedLits_.add(decisionLevel(), ImpliedLiteral(lastChoiceInverted, decisionLevel(), 0));
-	return true;
+    Literal lastChoiceInverted;
+    do {
+        if (decisionLevel() == rootLevel()) {
+            setStopConflict();
+            return false;
+        }
+        lastChoiceInverted = ~decision(decisionLevel());
+        undoUntil(decisionLevel() - 1, undo_pop_proj_level);
+        setBacktrackLevel(decisionLevel(), undo_pop_bt_level);
+    } while (hasConflict() || not force(lastChoiceInverted, nullptr));
+    // remember flipped literal for copyGuidingPath()
+    impliedLits_.add(decisionLevel(), ImpliedLiteral(lastChoiceInverted, decisionLevel(), nullptr));
+    return true;
 }
 
 bool ImpliedList::assign(Solver& s) {
-	assert(front <= lits.size());
-	bool ok             = !s.hasConflict();
-	const uint32 DL     = s.decisionLevel();
-	VecType::iterator j = lits.begin() + front;
-	for (VecType::iterator it = j, end = lits.end(); it != end; ++it) {
-		if(it->level <= DL) {
-			ok = ok && s.force(it->lit, it->ante.ante(), it->ante.data());
-			if (it->level < DL || it->ante.ante().isNull()) { *j++ = *it; }
-		}
-	}
-	lits.erase(j, lits.end());
-	level = DL * uint32(!lits.empty());
-	front = level > s.rootLevel() ? front  : sizeVec(lits);
-	return ok;
-}
-bool Solver::isUndoLevel() const {
-	return decisionLevel() > backtrackLevel();
-}
-uint32 Solver::undoUntilImpl(uint32 level, bool forceSave) {
-	level      = std::max( level, backtrackLevel() );
-	if (level >= decisionLevel()) { return decisionLevel(); }
-	uint32& n  = (levels_.jump = decisionLevel() - level);
-	bool sp    = forceSave || (strategy_.saveProgress > 0 && ((uint32)strategy_.saveProgress) <= n);
-	bool ok    = conflict_.empty() && levels_.back().freeze == 0;
-	conflict_.clear();
-	heuristic_->undoUntil( *this, levels_[level].trailPos);
-	undoLevel(sp && ok);
-	while (--n) { undoLevel(sp); }
-	return level;
-}
-uint32 Solver::undoUntil(uint32 level, uint32 mode) {
-	assert(backtrackLevel() >= rootLevel());
-	if (level < backtrackLevel() && mode >= levels_.mode) {
-		levels_.flip = std::max(rootLevel(), level);
-	}
-	level = undoUntilImpl(level, (mode & undo_save_phases) != 0);
-	if (impliedLits_.active(level)) {
-		impliedLits_.assign(*this);
-	}
-	return level;
-}
-uint32 Solver::estimateBCP(const Literal& p, int rd) const {
-	if (value(p.var()) != value_free) return 0;
-	LitVec::size_type first = assign_.assigned();
-	LitVec::size_type i     = first;
-	Solver& self            = const_cast(*this);
-	self.assign_.setValue(p.var(), trueValue(p));
-	self.assign_.trail.push_back(p);
-	const ShortImplicationsGraph& btig = shared_->shortImplications();
-	const uint32 maxIdx = btig.size();
-	do {
-		Literal x = assign_.trail[i++];
-		if (x.id() < maxIdx && !btig.propagateBin(self.assign_, x, 0)) {
-			break;
-		}
-	} while (i < assign_.assigned() && rd-- != 0);
-	i = assign_.assigned()-first;
-	while (self.assign_.assigned() != first) {
-		self.assign_.undoLast();
-	}
-	return (uint32)i;
-}
-
-uint32 Solver::inDegree(WeightLitVec& out) {
-	if (decisionLevel() == 0) { return 1; }
-	assert(!hasConflict());
-	out.reserve((numAssignedVars()-levelStart(1))/10);
-	uint32 maxIn = 1;
-	uint32 i = sizeVec(assign_.trail), stop = levelStart(1);
-	for (LitVec temp; i-- != stop; ) {
-		Literal x    = assign_.trail[i];
-		uint32  xLev = assign_.level(x.var());
-		uint32  xIn  = 0;
-		Antecedent xAnte = assign_.reason(x.var());
-		if (!xAnte.isNull() && xAnte.type() != Antecedent::Binary) {
-			xAnte.reason(*this, x, temp);
-			for (LitVec::const_iterator it = temp.begin(); it != temp.end(); ++it) {
-				xIn += level(it->var()) != xLev;
-			}
-			if (xIn) {
-				out.push_back(WeightLiteral(x, xIn));
-				maxIn = std::max(xIn, maxIn);
-			}
-			temp.clear();
-		}
-	}
-	return maxIn;
+    assert(front <= size32(lits));
+    bool           ok = not s.hasConflict();
+    const uint32_t dl = s.decisionLevel();
+    auto           j  = lits.begin() + front;
+    for (auto x : std::ranges::subrange(j, lits.end())) {
+        if (x.level <= dl) {
+            ok = ok && s.force(x.lit, x.ante.ante(), x.ante.data());
+            if (x.level < dl || x.ante.ante().isNull()) {
+                *j++ = x;
+            }
+        }
+    }
+    lits.erase(j, lits.end());
+    level = dl * static_cast(not lits.empty());
+    front = level > s.rootLevel() ? front : size32(lits);
+    return ok;
+}
+bool     Solver::isUndoLevel() const { return decisionLevel() > backtrackLevel(); }
+uint32_t Solver::undoUntilImpl(uint32_t level, bool forceSave) {
+    level = std::max(level, backtrackLevel());
+    if (level >= decisionLevel()) {
+        return decisionLevel();
+    }
+    uint32_t& n  = (levels_.jump = decisionLevel() - level);
+    bool      sp = forceSave || (strategy_.saveProgress > 0 && strategy_.saveProgress <= n);
+    bool      ok = conflict_.empty() && levels_.back().freeze == 0;
+    conflict_.clear();
+    heuristic_->undo(*this, trailView(levels_[level].trailPos));
+    undoLevel(sp && ok);
+    while (--n) { undoLevel(sp); }
+    return level;
+}
+uint32_t Solver::undoUntil(uint32_t level, uint32_t mode) {
+    assert(backtrackLevel() >= rootLevel());
+    if (level < backtrackLevel() && mode >= levels_.mode) {
+        levels_.flip = std::max(rootLevel(), level);
+    }
+    level = undoUntilImpl(level, (mode & undo_save_phases) != 0);
+    if (impliedLits_.active(level)) {
+        impliedLits_.assign(*this);
+    }
+    return level;
+}
+uint32_t Solver::estimateBCP(Literal p, int maxRecursionDepth) const {
+    if (value(p.var()) != value_free) {
+        return 0;
+    }
+    auto  first = assign_.assigned();
+    auto  i     = first;
+    auto& self  = const_cast(*this);
+    self.assign_.setValue(p.var(), trueValue(p));
+    self.assign_.trail.push_back(p);
+    const auto&    btig   = shared_->shortImplications();
+    const uint32_t maxIdx = btig.size();
+    do {
+        Literal x = assign_.trail[i++];
+        if (x.id() < maxIdx && not btig.propagateBin(self.assign_, x, 0)) {
+            break;
+        }
+    } while (i < assign_.assigned() && maxRecursionDepth-- != 0);
+    i = assign_.assigned() - first;
+    while (self.assign_.assigned() != first) { self.assign_.undoLast(); }
+    return i;
+}
+
+uint32_t Solver::inDegree(WeightLitVec& out) {
+    if (decisionLevel() == 0) {
+        return 1;
+    }
+    assert(not hasConflict());
+    out.reserve((numAssignedVars() - levelStart(1)) / 10);
+    uint32_t maxIn = 1;
+    uint32_t i = size32(assign_.trail), stop = levelStart(1);
+    for (LitVec temp; i-- != stop;) {
+        Literal  x    = assign_.trail[i];
+        uint32_t xLev = assign_.level(x.var());
+        if (auto xAnte = assign_.reason(x.var()); not xAnte.isNull() && xAnte.type() != Antecedent::binary) {
+            uint32_t xIn = 0;
+            xAnte.reason(*this, x, temp);
+            for (auto lit : temp) { xIn += level(lit.var()) != xLev; }
+            if (xIn) {
+                out.push_back(WeightLiteral{x, static_cast(std::min(xIn, weight_max))});
+                maxIn = std::max(xIn, maxIn);
+            }
+            temp.clear();
+        }
+    }
+    return maxIn;
+}
+void Solver::counterBumpVars(uint32_t bump) {
+    bumpAct_.clear();
+    auto maxIn = inDegree(bumpAct_);
+    heuristic_->bump(*this, bumpAct_, bump / static_cast(maxIn));
+    bumpAct_.clear();
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Solver: Private helper functions
 ////////////////////////////////////////////////////////////////////////////////////////
 Solver::ConstraintDB* Solver::allocUndo(Constraint* c) {
-	if (undoHead_ == 0) {
-		return new ConstraintDB(1, c);
-	}
-	assert(undoHead_->size() == 1);
-	ConstraintDB* r = undoHead_;
-	undoHead_ = (ConstraintDB*)undoHead_->front();
-	r->clear();
-	r->push_back(c);
-	return r;
+    if (undoHead_ == nullptr) {
+        return new ConstraintDB(1, c);
+    }
+    assert(undoHead_->size() == 1);
+    ConstraintDB* r = undoHead_;
+    undoHead_       = reinterpret_cast(undoHead_->front());
+    r->clear();
+    r->push_back(c);
+    return r;
 }
 void Solver::undoFree(ConstraintDB* x) {
-	// maintain a single-linked list of undo lists
-	x->clear();
-	x->push_back((Constraint*)undoHead_);
-	undoHead_ = x;
+    // maintain a single-linked list of undo lists
+    x->clear();
+    x->push_back(reinterpret_cast(undoHead_));
+    undoHead_ = x;
 }
 // removes the current decision level
 void Solver::undoLevel(bool sp) {
-	assert(decisionLevel() != 0 && levels_.back().trailPos != assign_.trail.size() && "Decision Level must not be empty");
-	assign_.undoTrail(levels_.back().trailPos, sp);
-	if (levels_.back().undo) {
-		const ConstraintDB& undoList = *levels_.back().undo;
-		for (ConstraintDB::size_type i = 0, end = undoList.size(); i != end; ++i) {
-			undoList[i]->undoLevel(*this);
-		}
-		undoFree(levels_.back().undo);
-	}
-	levels_.pop_back();
+    assert(decisionLevel() != 0 && levels_.back().trailPos != size32(assign_.trail) &&
+           "Decision Level must not be empty");
+    assign_.undoTrail(levels_.back().trailPos, sp);
+    if (levels_.back().undo) {
+        for (auto* c : *levels_.back().undo) { c->undoLevel(*this); }
+        undoFree(levels_.back().undo);
+    }
+    levels_.pop_back();
 }
 
 inline ClauseHead* clause(const Antecedent& ante) {
-	return ante.isNull() || ante.type() != Antecedent::Generic ? 0 : ante.constraint()->clause();
-}
-
-bool Solver::resolveToFlagged(const LitVec& in, uint8 vf, LitVec& out, uint32& outLbd) const {
-	return const_cast(*this).resolveToFlagged(in, vf, out, outLbd);
-}
-bool Solver::resolveToFlagged(const LitVec& in, const uint8 vf, LitVec& out, uint32& outLbd) {
-	const LitVec& trail = assign_.trail;
-	const LitVec* rhs   = ∈
-	LitVec temp; out.clear();
-	bool ok = true, first = true;
-	for (LitVec::size_type tp = trail.size(), resolve = 0;; first = false) {
-		Literal p; Var v;
-		for (LitVec::const_iterator it = rhs->begin(), end = rhs->end(); it != end; ++it) {
-			p = *it ^ first; v = p.var();
-			if (!seen(v)) {
-				markSeen(v);
-				if      (varInfo(v).hasAll(vf)) { markLevel(level(v)); out.push_back(~p); }
-				else if (!reason(p).isNull())   { ++resolve; }
-				else                            { clearSeen(v); ok = false; break; }
-			}
-		}
-		if (resolve-- == 0) { break; }
-		// find next literal to resolve
-		while (!seen(trail[--tp]) || varInfo(trail[tp].var()).hasAll(vf)) { ; }
-		clearSeen((p = trail[tp]).var());
-		reason(p, temp);
-		rhs = &temp;
-	}
-	LitVec::size_type outSize = out.size();
-	if (ok && !first) {
-		const uint32 ccAct = strategy_.ccMinKeepAct;
-		const uint32 antes = SolverStrategies::all_antes;
-		strategy_.ccMinKeepAct = 1;
-		if (ccMin_) { ccMinRecurseInit(*ccMin_); }
-		for (LitVec::size_type i = 0; i != outSize;) {
-			if (!ccRemovable(~out[i], antes, ccMin_)) { ++i; }
-			else {
-				std::swap(out[i], out[--outSize]);
-			}
-		}
-		strategy_.ccMinKeepAct = ccAct;
-	}
-	POTASSCO_ASSERT(!ok || outSize != 0, "Invalid empty clause - was %u!\n", out.size());
-	outLbd = 0;
-	for (uint32 i = 0, dl, root = 0; i != outSize; ++i) {
-		Var v = out[i].var();
-		dl    = level(v);
-		clearSeen(v);
-		if (dl && hasLevel(dl)) {
-			unmarkLevel(dl);
-			outLbd += (dl > rootLevel()) || (++root == 1);
-		}
-	}
-	for (Var v; outSize != out.size(); out.pop_back()) {
-		clearSeen(v = out.back().var());
-		unmarkLevel(level(v));
-	}
-	return ok;
+    return ante.isNull() || ante.type() != Antecedent::generic ? nullptr : ante.constraint()->clause();
+}
+
+bool Solver::resolveToFlagged(LitView in, uint8_t vf, LitVec& out, uint32_t& outLbd) const {
+    return const_cast(*this).resolveToFlagged(in, vf, out, outLbd);
+}
+bool Solver::resolveToFlagged(LitView in, const uint8_t vf, LitVec& out, uint32_t& outLbd) {
+    const LitVec& trail = assign_.trail;
+    LitView       rhs   = in;
+    LitVec        temp;
+    out.clear();
+    bool ok = true, first = true;
+    for (uint32_t tp = size32(trail), resolve = 0u;; first = false) {
+        Literal p;
+        for (auto lit : rhs) {
+            p = lit ^ first;
+            if (auto v = p.var(); not seen(v)) {
+                markSeen(v);
+                if (varInfo(v).hasAll(vf)) {
+                    markLevel(level(v));
+                    out.push_back(~p);
+                }
+                else if (not reason(p).isNull()) {
+                    ++resolve;
+                }
+                else {
+                    clearSeen(v);
+                    ok = false;
+                    break;
+                }
+            }
+        }
+        if (resolve-- == 0) {
+            break;
+        }
+        // find next literal to resolve
+        while (not seen(trail[--tp]) || varInfo(trail[tp].var()).hasAll(vf)) { ; }
+        clearSeen((p = trail[tp]).var());
+        reason(p, temp);
+        rhs = temp;
+    }
+    auto outSize = size32(out);
+    if (ok && not first) {
+        const uint32_t     ccAct = strategy_.ccMinKeepAct;
+        constexpr uint32_t antes = SolverStrategies::all_antes;
+        strategy_.ccMinKeepAct   = 1;
+        if (ccMin_) {
+            ccMinRecurseInit(*ccMin_);
+        }
+        for (decltype(outSize) i = 0; i != outSize;) {
+            if (not ccRemovable(~out[i], antes, ccMin_.get())) {
+                ++i;
+            }
+            else {
+                std::swap(out[i], out[--outSize]);
+            }
+        }
+        strategy_.ccMinKeepAct = ccAct;
+    }
+    POTASSCO_ASSERT(not ok || outSize != 0, "Invalid empty clause - was %u!\n", size32(out));
+    outLbd = 0;
+    for (uint32_t i = 0, onRoot = 0, rootLev = rootLevel(); auto lit : out) {
+        auto v  = lit.var();
+        auto dl = level(v);
+        clearSeen(v);
+        if (dl && hasLevel(dl)) {
+            unmarkLevel(dl);
+            outLbd += i < outSize && (dl > rootLev || ++onRoot == 1);
+        }
+        ++i;
+    }
+    shrinkVecTo(out, outSize);
+    return ok;
 }
 void Solver::resolveToCore(LitVec& out) {
-	POTASSCO_REQUIRE(hasConflict() && !hasStopConflict(), "Function requires valid conflict");
-	// move conflict to cc_
-	cc_.clear();
-	cc_.swap(conflict_);
-	if (searchMode() == SolverStrategies::no_learning) {
-		for (uint32 i = 1, end = decisionLevel() + 1; i != end; ++i) { cc_.push_back(decision(i)); }
-	}
-	const LitVec& trail = assign_.trail;
-	const LitVec* r = &cc_;
-	// resolve all-last uip
-	for (uint32 marked = 0, tPos = (uint32)trail.size();; r = &conflict_) {
-		for (LitVec::const_iterator it = r->begin(), end = r->end(); it != end; ++it) {
-			if (!seen(it->var())) {
-				assert(level(it->var()) <= decisionLevel());
-				markSeen(it->var());
-				++marked;
-			}
-		}
-		if (marked-- == 0) { break; }
-		// search for the last marked literal
-		while (!seen(trail[--tPos].var())) { ; }
-		Literal p = trail[tPos];
-		uint32 dl = level(p.var());
-		assert(dl);
-		clearSeen(p.var());
-		conflict_.clear();
-		if      (!reason(p).isNull()) { reason(p).reason(*this, p, conflict_); }
-		else if (p == decision(dl))   { out.push_back(p); }
-	}
-	// restore original conflict
-	cc_.swap(conflict_);
+    POTASSCO_CHECK_PRE(hasConflict() && not hasStopConflict(), "Function requires valid conflict");
+    // move conflict to cc_
+    cc_.clear();
+    cc_.swap(conflict_);
+    if (searchMode() == SolverStrategies::no_learning) {
+        for (uint32_t i : irange(decisionLevel())) { cc_.push_back(decision(i + 1)); }
+    }
+    const LitVec& trail = assign_.trail;
+    const LitVec* r     = &cc_;
+    // resolve all-last uip
+    for (uint32_t marked = 0, tPos = size32(trail);; r = &conflict_) {
+        for (auto p : *r) {
+            if (not seen(p.var())) {
+                assert(level(p.var()) <= decisionLevel());
+                markSeen(p.var());
+                ++marked;
+            }
+        }
+        if (marked-- == 0) {
+            break;
+        }
+        // search for the last marked literal
+        while (not seen(trail[--tPos].var())) { ; }
+        Literal  p  = trail[tPos];
+        uint32_t dl = level(p.var());
+        assert(dl);
+        clearSeen(p.var());
+        conflict_.clear();
+        if (not reason(p).isNull()) {
+            reason(p).reason(*this, p, conflict_);
+        }
+        else if (p == decision(dl)) {
+            out.push_back(p);
+        }
+    }
+    // restore original conflict
+    cc_.swap(conflict_);
 }
 
 // computes the First-UIP clause and stores it in cc_, where cc_[0] is the asserting literal (inverted UIP)
 // and cc_[1] is a literal from the asserting level (if > 0)
 // RETURN: asserting level of the derived conflict clause
-uint32 Solver::analyzeConflict() {
-	// must be called here, because we unassign vars during analyzeConflict
-	heuristic_->undoUntil( *this, levels_.back().trailPos );
-	uint32 onLevel  = 0;        // number of literals from the current DL in resolvent
-	uint32 resSize  = 0;        // size of current resolvent
-	Literal p;                  // literal to be resolved out next
-	cc_.assign(1, p);           // will later be replaced with asserting literal
-	Antecedent lhs, rhs, last;  // resolve operands
-	const bool doOtfs = strategy_.otfs > 0;
-	for (bumpAct_.clear();;) {
-		uint32 lhsSize = resSize;
-		uint32 rhsSize = 0;
-		heuristic_->updateReason(*this, conflict_, p);
-		for (LitVec::size_type i = 0; i != conflict_.size(); ++i) {
-			Literal& q = conflict_[i];
-			uint32 cl  = level(q.var());
-			rhsSize   += (cl != 0);
-			if (!seen(q.var())) {
-				++resSize;
-				assert(isTrue(q) && "Invalid literal in reason set!");
-				assert(cl > 0 && "Top-Level implication not marked!");
-				markSeen(q.var());
-				if (cl == decisionLevel()) {
-					++onLevel;
-				}
-				else {
-					cc_.push_back(~q);
-					markLevel(cl);
-				}
-			}
-		}
-		if (resSize != lhsSize) { lhs = 0; }
-		if (rhsSize != resSize) { rhs = 0; }
-		if (doOtfs && (!rhs.isNull() || !lhs.isNull())) {
-			// resolvent subsumes rhs and possibly also lhs
-			otfs(lhs, rhs, p, onLevel == 1);
-		}
-		assert(onLevel > 0 && "CONFLICT MUST BE ANALYZED ON CONFLICT LEVEL!");
-		// search for the last assigned literal that needs to be analyzed...
-		while (!seen(assign_.last().var())) {
-			assign_.undoLast();
-		}
-		p   = assign_.last();
-		rhs = reason(p);
-		clearSeen(p.var());
-		if (--onLevel == 0) {
-			break;
-		}
-		--resSize; // p will be resolved out next
-		last = rhs;
-		reason(p, conflict_);
-	}
-	cc_[0] = ~p; // store the 1-UIP
-	assert(decisionLevel() == level(cc_[0].var()));
-	ClauseHead* lastRes = 0;
-	if (strategy_.otfs > 1 || !lhs.isNull()) {
-		if (!lhs.isNull()) {
-			lastRes = clause(lhs);
-		}
-		else if (cc_.size() <= (conflict_.size()+1)) {
-			lastRes = clause(last);
-		}
-	}
-	if (strategy_.bumpVarAct && reason(p).learnt()) {
-		bumpAct_.push_back(WeightLiteral(p, reason(p).constraint()->activity().lbd()));
-	}
-	return simplifyConflictClause(cc_, ccInfo_, lastRes);
+uint32_t Solver::analyzeConflict() {
+    // must be called here, because we unassign vars during analyzeConflict
+    heuristic_->undo(*this, trailView(levels_.back().trailPos));
+    uint32_t onLevel = 0;      // number of literals from the current DL in resolvent
+    uint32_t resSize = 0;      // size of current resolvent
+    Literal  p;                // literal to be resolved out next
+    cc_.assign(1, p);          // will later be replaced with asserting literal
+    Antecedent lhs, rhs, last; // resolve operands
+    const bool doOtfs = strategy_.otfs > 0;
+    for (bumpAct_.clear();;) {
+        uint32_t lhsSize = resSize;
+        uint32_t rhsSize = 0;
+        heuristic_->updateReason(*this, conflict_, p);
+        for (auto q : conflict_) {
+            uint32_t cl  = level(q.var());
+            rhsSize     += (cl != 0);
+            if (not seen(q.var())) {
+                ++resSize;
+                assert(isTrue(q) && "Invalid literal in reason set!");
+                assert(cl > 0 && "Top-Level implication not marked!");
+                markSeen(q.var());
+                if (cl == decisionLevel()) {
+                    ++onLevel;
+                }
+                else {
+                    cc_.push_back(~q);
+                    markLevel(cl);
+                }
+            }
+        }
+        if (resSize != lhsSize) {
+            lhs = nullptr;
+        }
+        if (rhsSize != resSize) {
+            rhs = nullptr;
+        }
+        if (doOtfs && (not rhs.isNull() || not lhs.isNull())) {
+            // resolvent subsumes rhs and possibly also lhs
+            otfs(lhs, rhs, p, onLevel == 1);
+        }
+        assert(onLevel > 0 && "CONFLICT MUST BE ANALYZED ON CONFLICT LEVEL!");
+        // search for the last assigned literal that needs to be analyzed...
+        while (not seen(assign_.last().var())) { assign_.undoLast(); }
+        p   = assign_.last();
+        rhs = reason(p);
+        clearSeen(p.var());
+        if (--onLevel == 0) {
+            break;
+        }
+        --resSize; // p will be resolved out next
+        last = rhs;
+        reason(p, conflict_);
+    }
+    cc_[0] = ~p; // store the 1-UIP
+    assert(decisionLevel() == level(cc_[0].var()));
+    ClauseHead* lastRes = nullptr;
+    if (strategy_.otfs > 1 || not lhs.isNull()) {
+        if (not lhs.isNull()) {
+            lastRes = clause(lhs);
+        }
+        else if (cc_.size() <= (conflict_.size() + 1)) {
+            lastRes = clause(last);
+        }
+    }
+    if (strategy_.bumpVarAct && reason(p).learnt()) {
+        bumpAct_.push_back(WeightLiteral{p, static_cast(reason(p).constraint()->activity().lbd())});
+    }
+    return simplifyConflictClause(cc_, ccInfo_, lastRes);
 }
 
 void Solver::otfs(Antecedent& lhs, const Antecedent& rhs, Literal p, bool final) {
-	ClauseHead* cLhs = clause(lhs), *cRhs = clause(rhs);
-	ClauseHead::BoolPair x;
-	if (cLhs) {
-		x = cLhs->strengthen(*this, ~p, !final);
-		if (!x.first || x.second) {
-			cLhs = !x.first ? 0 : otfsRemove(cLhs, 0);
-		}
-	}
-	lhs = cLhs;
-	if (cRhs) {
-		x = cRhs->strengthen(*this, p, !final);
-		if (!x.first || (x.second && otfsRemove(cRhs, 0) == 0)) {
-			if (x.first && reason(p) == cRhs) { setReason(p, 0); }
-			cRhs = 0;
-		}
-		if (cLhs && cRhs) {
-			// lhs and rhs are now equal - only one of them is needed
-			if (!cLhs->learnt()) {
-				std::swap(cLhs, cRhs);
-			}
-			otfsRemove(cLhs, 0);
-		}
-		lhs = cRhs;
-	}
+    ClauseHead *cLhs = clause(lhs), *cRhs = clause(rhs);
+    if (cLhs) {
+        auto x = cLhs->strengthen(*this, ~p, not final);
+        if (not x.litRemoved || x.removeClause) {
+            cLhs = not x.litRemoved ? nullptr : otfsRemove(cLhs, nullptr);
+        }
+    }
+    lhs = cLhs;
+    if (cRhs) {
+        auto x = cRhs->strengthen(*this, p, not final);
+        if (not x.litRemoved || (x.removeClause && otfsRemove(cRhs, nullptr) == nullptr)) {
+            if (x.litRemoved && reason(p) == cRhs) {
+                setReason(p, nullptr);
+            }
+            cRhs = nullptr;
+        }
+        if (cLhs && cRhs) {
+            // lhs and rhs are now equal - only one of them is needed
+            if (not cLhs->learnt()) {
+                std::swap(cLhs, cRhs);
+            }
+            otfsRemove(cLhs, nullptr);
+        }
+        lhs = cRhs;
+    }
 }
 
 ClauseHead* Solver::otfsRemove(ClauseHead* c, const LitVec* newC) {
-	bool remStatic = !newC || (newC->size() <= 3 && shared_->allowImplicit(Constraint_t::Conflict));
-	if (c->learnt() || remStatic) {
-		ConstraintDB& db = (c->learnt() ? learnts_ : constraints_);
-		ConstraintDB::iterator it;
-		if ((it = std::find(db.begin(), db.end(), c)) != db.end()) {
-			if (isMaster() && &db == &constraints_) {
-				shared_->removeConstraint(static_cast(it - db.begin()), true);
-			}
-			else {
-				db.erase(it);
-				c->destroy(this, true);
-			}
-			c = 0;
-		}
-	}
-	return c;
+    bool remStatic = not newC || (newC->size() <= 3 && shared_->allowImplicit(ConstraintType::conflict));
+    if (c->learnt() || remStatic) {
+        ConstraintDB& db = (c->learnt() ? learnts_ : constraints_);
+        if (auto it = std::ranges::find(db, c); it != db.end()) {
+            if (isMaster() && &db == &constraints_) {
+                shared_->removeConstraint(static_cast(it - db.begin()), true);
+            }
+            else {
+                db.erase(it);
+                c->destroy(this, true);
+            }
+            c = nullptr;
+        }
+    }
+    return c;
 }
 
 // minimizes the conflict clause in cc w.r.t selected strategies.
@@ -1335,82 +1473,83 @@ ClauseHead* Solver::otfsRemove(ClauseHead* c, const LitVec* newC) {
 //  - all decision levels of literals in cc are marked
 //  - rhs is 0 or a clause that might be subsumed by cc
 // RETURN: finalizeConflictClause(cc, info)
-uint32 Solver::simplifyConflictClause(LitVec& cc, ConstraintInfo& info, ClauseHead* rhs) {
-	// 1. remove redundant literals from conflict clause
-	temp_.clear();
-	uint32 onAssert = ccMinimize(cc, temp_, strategy_.ccMinAntes, ccMin_);
-	uint32 jl       = cc.size() > 1 ? level(cc[1].var()) : 0;
-	// clear seen flags of removed literals - keep levels marked
-	for (LitVec::size_type x = 0, stop = temp_.size(); x != stop; ++x) {
-		clearSeen(temp_[x].var());
-	}
-	// 2. check for inverse arcs
-	if (onAssert == 1 && strategy_.reverseArcs > 0) {
-		uint32 maxN = (uint32)strategy_.reverseArcs;
-		if      (maxN > 2) maxN = UINT32_MAX;
-		else if (maxN > 1) maxN = static_cast(cc.size() / 2);
-		markSeen(cc[0].var());
-		Antecedent ante = ccHasReverseArc(cc[1], jl, maxN);
-		if (!ante.isNull()) {
-			// resolve with inverse arc
-			conflict_.clear();
-			ante.reason(*this, ~cc[1], conflict_);
-			ccResolve(cc, 1, conflict_);
-		}
-		clearSeen(cc[0].var());
-	}
-	// 3. check if final clause subsumes rhs
-	if (rhs) {
-		conflict_.clear();
-		rhs->toLits(conflict_);
-		uint32 open   = (uint32)cc.size();
-		markSeen(cc[0].var());
-		for (LitVec::const_iterator it = conflict_.begin(), end = conflict_.end(); it != end && open; ++it) {
-			// NOTE: at this point the DB might not be fully simplified,
-			//       e.g. because of mt or lookahead, hence we must explicitly
-			//       check for literals assigned on DL 0
-			open -= level(it->var()) > 0 && seen(it->var());
-		}
-		rhs = open ? 0 : otfsRemove(rhs, &cc);
-		if (rhs) { // rhs is subsumed by cc but could not be removed.
-			// TODO: we could reuse rhs instead of learning cc
-			//       but this would complicate the calling code.
-			ClauseHead::BoolPair r(true, false);
-			if (cc_.size() < conflict_.size()) {
-				//     For now, we only try to strengthen rhs.
-				for (LitVec::const_iterator it = conflict_.begin(), end = conflict_.end(); it != end && r.first; ++it) {
-					if (!seen(it->var()) || level(it->var()) == 0) {
-						r = rhs->strengthen(*this, *it, false);
-					}
-				}
-				if (!r.first) { rhs = 0; }
-			}
-		}
-		clearSeen(cc[0].var());
-	}
-	// 4. finalize
-	uint32 repMode = cc.size() < std::max(strategy_.compress, decisionLevel()+1) ? 0 : strategy_.ccRepMode;
-	jl = finalizeConflictClause(cc, info, repMode);
-	// 5. bump vars implied by learnt constraints with small lbd
-	if (!bumpAct_.empty()) {
-		WeightLitVec::iterator j = bumpAct_.begin();
-		weight_t newLbd = info.lbd();
-		for (WeightLitVec::iterator it = bumpAct_.begin(), end = bumpAct_.end(); it != end; ++it) {
-			if (it->second < newLbd) {
-				it->second = 1 + (it->second <= 2);
-				*j++ = *it;
-			}
-		}
-		bumpAct_.erase(j, bumpAct_.end());
-		heuristic_->bump(*this, bumpAct_, 1.0);
-	}
-	bumpAct_.clear();
-	// 6. clear level flags of redundant literals
-	for (LitVec::size_type x = 0, stop = temp_.size(); x != stop; ++x) {
-		unmarkLevel(level(temp_[x].var()));
-	}
-	temp_.clear();
-	return jl;
+uint32_t Solver::simplifyConflictClause(LitVec& cc, ConstraintInfo& info, ClauseHead* rhs) {
+    // 1. remove redundant literals from conflict clause
+    temp_.clear();
+    uint32_t onAssert = ccMinimize(cc, temp_, strategy_.ccMinAntes, ccMin_.get());
+    uint32_t jl       = cc.size() > 1 ? level(cc[1].var()) : 0;
+    // clear seen flags of removed literals - keep levels marked
+    for (auto x : temp_) { clearSeen(x.var()); }
+    // 2. check for inverse arcs
+    if (onAssert == 1 && strategy_.reverseArcs > 0) {
+        auto maxN = strategy_.reverseArcs;
+        if (maxN > 2) {
+            maxN = UINT32_MAX;
+        }
+        else if (maxN > 1) {
+            maxN = size32(cc) >> 1;
+        }
+        markSeen(cc[0].var());
+        if (Antecedent ante = ccHasReverseArc(cc[1], jl, maxN); not ante.isNull()) {
+            // resolve with inverse arc
+            conflict_.clear();
+            ante.reason(*this, ~cc[1], conflict_);
+            ccResolve(cc, 1, conflict_);
+        }
+        clearSeen(cc[0].var());
+    }
+    // 3. check if final clause subsumes rhs
+    if (rhs) {
+        conflict_.clear();
+        rhs->toLits(conflict_);
+        auto open = size32(cc);
+        markSeen(cc[0].var());
+        for (auto it = conflict_.begin(), end = conflict_.end(); it != end && open; ++it) {
+            // NOTE: at this point the DB might not be fully simplified,
+            //       e.g. because of mt or lookahead, hence we must explicitly
+            //       check for literals assigned on DL 0
+            open -= level(it->var()) > 0 && seen(it->var());
+        }
+        rhs = open ? nullptr : otfsRemove(rhs, &cc);
+        if (rhs) { // rhs is subsumed by cc but could not be removed.
+            // TODO: we could reuse rhs instead of learning cc
+            //       but this would complicate the calling code.
+            if (cc_.size() < conflict_.size()) {
+                bool litRemoved = true;
+                //     For now, we only try to strengthen rhs.
+                for (auto it = conflict_.begin(), end = conflict_.end(); it != end && litRemoved; ++it) {
+                    if (not seen(it->var()) || level(it->var()) == 0) {
+                        litRemoved = rhs->strengthen(*this, *it, false).litRemoved;
+                    }
+                }
+                if (not litRemoved) {
+                    rhs = nullptr;
+                }
+            }
+        }
+        clearSeen(cc[0].var());
+    }
+    // 4. finalize
+    uint32_t repMode = cc.size() < std::max(strategy_.compress, decisionLevel() + 1) ? 0 : strategy_.ccRepMode;
+    jl               = finalizeConflictClause(cc, info, repMode);
+    // 5. bump vars implied by learnt constraints with small lbd
+    if (not bumpAct_.empty()) {
+        auto j      = bumpAct_.begin();
+        auto newLbd = info.lbd();
+        for (auto& wl : bumpAct_) {
+            if (std::cmp_less(wl.weight, newLbd)) {
+                wl.weight = 1 + (wl.weight <= 2);
+                *j++      = wl;
+            }
+        }
+        bumpAct_.erase(j, bumpAct_.end());
+        heuristic_->bump(*this, bumpAct_, 1.0);
+    }
+    bumpAct_.clear();
+    // 6. clear level flags of redundant literals
+    for (auto x : temp_) { unmarkLevel(level(x.var())); }
+    temp_.clear();
+    return jl;
 }
 
 // conflict clause minimization
@@ -1423,77 +1562,83 @@ uint32 Solver::simplifyConflictClause(LitVec& cc, ConstraintInfo& info, ClauseHe
 //  - if (cc.size() > 1): cc[1] is a literal from the asserting level
 // RETURN
 //  - the number of literals from the asserting level
-uint32 Solver::ccMinimize(LitVec& cc, LitVec& removed, uint32 antes, CCMinRecursive* ccMin) {
-	if (ccMin) { ccMinRecurseInit(*ccMin); }
-	// skip the asserting literal
-	LitVec::size_type j = 1;
-	uint32 assertLevel  = 0;
-	uint32 assertPos    = 1;
-	uint32 onAssert     = 0;
-	uint32 varLevel     = 0;
-	for (LitVec::size_type i = 1; i != cc.size(); ++i) {
-		if (antes == SolverStrategies::no_antes || !ccRemovable(~cc[i], antes, ccMin)) {
-			if ( (varLevel = level(cc[i].var())) > assertLevel ) {
-				assertLevel = varLevel;
-				assertPos   = static_cast(j);
-				onAssert    = 0;
-			}
-			onAssert += (varLevel == assertLevel);
-			cc[j++] = cc[i];
-		}
-		else {
-			removed.push_back(cc[i]);
-		}
-	}
-	cc.erase(cc.begin()+j, cc.end());
-	if (assertPos != 1) {
-		std::swap(cc[1], cc[assertPos]);
-	}
-	return onAssert;
-}
-void Solver::ccMinRecurseInit(CCMinRecursive& ccMin) {
-	ccMin.open = incEpoch(numVars() + 1, 2) - 2;
-}
+uint32_t Solver::ccMinimize(LitVec& cc, LitVec& removed, uint32_t antes, CCMinRecursive* ccMin) {
+    if (ccMin) {
+        ccMinRecurseInit(*ccMin);
+    }
+    // skip the asserting literal
+    auto assertLevel = 0u;
+    auto assertPos   = 1u;
+    auto onAssert    = 0u;
+    auto j           = 1u;
+    for (auto lit : std::ranges::subrange(cc.begin() + 1, cc.end())) {
+        if (antes == SolverStrategies::no_antes || not ccRemovable(~lit, antes, ccMin)) {
+            auto varLevel = level(lit.var());
+            if (varLevel > assertLevel) {
+                assertLevel = varLevel;
+                assertPos   = j;
+                onAssert    = 0;
+            }
+            onAssert += (varLevel == assertLevel);
+            cc[j++]   = lit;
+        }
+        else {
+            removed.push_back(lit);
+        }
+    }
+    shrinkVecTo(cc, j);
+    if (assertPos != 1) {
+        std::swap(cc[1], cc[assertPos]);
+    }
+    return onAssert;
+}
+void Solver::ccMinRecurseInit(CCMinRecursive& ccMin) { ccMin.open = incEpoch(numVars() + 1, 2) - 2; }
 bool Solver::ccMinRecurse(CCMinRecursive& ccMin, Literal p) const {
-	CCMinRecursive::State st = ccMin.decodeState(epoch_[p.var()]);
-	if (st == CCMinRecursive::state_poison) { return false; }
-	if (st == CCMinRecursive::state_open)   { ccMin.push(p.unflag()); }
-	return true;
+    switch (ccMin.decodeState(epoch_[p.var()])) {
+        case CCMinRecursive::state_poison: return false;
+        case CCMinRecursive::state_open  : ccMin.push(p.unflag()); break;
+        default                          : break;
+    }
+    return true;
 }
 
 // returns true if p is redundant in current conflict clause
-bool Solver::ccRemovable(Literal p, uint32 antes, CCMinRecursive* ccMin) {
-	const Antecedent& ante = reason(p);
-	if (ante.isNull() || !(antes <= (uint32)ante.type())) {
-		return false;
-	}
-	if (!ccMin) { return ante.minimize(*this, p, 0); }
-	// recursive minimization
-	assert(ccMin->todo.empty());
-	CCMinRecursive::State dfsState = CCMinRecursive::state_removable;
-	ccMin->push(p.unflag());
-	for (Literal x;; ) {
-		x = ccMin->pop();
-		assert(!seen(x.var()) || x == p);
-		if (x.flagged()) {
-			if (x == p) return dfsState == CCMinRecursive::state_removable;
-			epoch_[x.var()] = ccMin->encodeState(dfsState);
-		}
-		else if (dfsState != CCMinRecursive::state_poison) {
-			CCMinRecursive::State temp = ccMin->decodeState(epoch_[x.var()]);
-			if (temp == CCMinRecursive::state_open) {
-				assert(value(x.var()) != value_free && hasLevel(level(x.var())));
-				ccMin->push(x.flag());
-				const Antecedent& next = reason(x);
-				if (next.isNull() || !(antes <= (uint32)next.type()) || !next.minimize(*this, x, ccMin)) {
-					dfsState = CCMinRecursive::state_poison;
-				}
-			}
-			else if (temp == CCMinRecursive::state_poison) {
-				dfsState = temp;
-			}
-		}
-	}
+bool Solver::ccRemovable(Literal p, uint32_t antes, CCMinRecursive* ccMin) {
+    const Antecedent& ante = reason(p);
+    if (ante.isNull() || antes > static_cast(ante.type())) {
+        return false;
+    }
+    if (not ccMin) {
+        return ante.minimize(*this, p, nullptr);
+    }
+    // recursive minimization
+    assert(ccMin->todo.empty());
+    CCMinRecursive::State dfsState = CCMinRecursive::state_removable;
+    ccMin->push(p.unflag());
+    for (Literal x;;) {
+        x = ccMin->pop();
+        assert(not seen(x.var()) || x == p);
+        if (x.flagged()) {
+            if (x == p) {
+                return dfsState == CCMinRecursive::state_removable;
+            }
+            epoch_[x.var()] = ccMin->encodeState(dfsState);
+        }
+        else if (dfsState != CCMinRecursive::state_poison) {
+            CCMinRecursive::State temp = ccMin->decodeState(epoch_[x.var()]);
+            if (temp == CCMinRecursive::state_open) {
+                assert(value(x.var()) != value_free && hasLevel(level(x.var())));
+                ccMin->push(x.flag());
+                const Antecedent& next = reason(x);
+                if (next.isNull() || antes > static_cast(next.type()) || not next.minimize(*this, x, ccMin)) {
+                    dfsState = CCMinRecursive::state_poison;
+                }
+            }
+            else if (temp == CCMinRecursive::state_poison) {
+                dfsState = temp;
+            }
+        }
+    }
 }
 
 // checks whether there is a valid "inverse arc" for the given literal p that can be used
@@ -1503,36 +1648,35 @@ bool Solver::ccRemovable(Literal p, uint32 antes, CCMinRecursive* ccMin) {
 //  - p is a literal of the current conflict clause and level(p) == maxLevel
 // RETURN
 //  - An antecedent that is an "inverse arc" for p or null if no such antecedent exists.
-Antecedent Solver::ccHasReverseArc(Literal p, uint32 maxLevel, uint32 maxNew) {
-	assert(seen(p.var()) && isFalse(p) && level(p.var()) == maxLevel);
-	const ShortImplicationsGraph& btig = shared_->shortImplications();
-	Antecedent ante;
-	if (p.id() < btig.size() && btig.reverseArc(*this, p, maxLevel, ante)) { return ante; }
-	WatchList& wl = watches_[p.id()];
-	for (WatchList::left_iterator it = wl.left_begin(), end = wl.left_end();  it != end;  ++it) {
-		if (it->head->isReverseReason(*this, ~p, maxLevel, maxNew)) {
-			return it->head;
-		}
-	}
-	return ante;
+Antecedent Solver::ccHasReverseArc(Literal p, uint32_t maxLevel, uint32_t maxNew) {
+    assert(seen(p.var()) && isFalse(p) && level(p.var()) == maxLevel);
+    const auto& btig = shared_->shortImplications();
+    Antecedent  ante;
+    if (p.id() < btig.size() && btig.reverseArc(*this, p, maxLevel, ante)) {
+        return ante;
+    }
+    for (const auto& w : watches_[p.id()].left_view()) {
+        if (w.head->isReverseReason(*this, ~p, maxLevel, maxNew)) {
+            return w.head;
+        }
+    }
+    return ante;
 }
 
 // removes cc[pos] by resolving cc with reason
-void Solver::ccResolve(LitVec& cc, uint32 pos, const LitVec& reason) {
-	heuristic_->updateReason(*this, reason, cc[pos]);
-	Literal x;
-	for (LitVec::size_type i = 0; i != reason.size(); ++i) {
-		x = reason[i];
-		assert(isTrue(x));
-		if (!seen(x.var())) {
-			markLevel(level(x.var()));
-			cc.push_back(~x);
-		}
-	}
-	clearSeen(cc[pos].var());
-	unmarkLevel(level(cc[pos].var()));
-	cc[pos] = cc.back();
-	cc.pop_back();
+void Solver::ccResolve(LitVec& cc, uint32_t pos, const LitVec& reason) {
+    heuristic_->updateReason(*this, reason, cc[pos]);
+    for (auto x : reason) {
+        assert(isTrue(x));
+        if (not seen(x.var())) {
+            markLevel(level(x.var()));
+            cc.push_back(~x);
+        }
+    }
+    clearSeen(cc[pos].var());
+    unmarkLevel(level(cc[pos].var()));
+    cc[pos] = cc.back();
+    cc.pop_back();
 }
 
 // computes asserting level and lbd of cc and clears flags.
@@ -1540,371 +1684,424 @@ void Solver::ccResolve(LitVec& cc, uint32 pos, const LitVec& reason) {
 //  - literals and decision levels in cc are no longer marked
 //  - if cc.size() > 1: cc[1] is a literal from the asserting level
 // RETURN: asserting level of conflict clause.
-uint32 Solver::finalizeConflictClause(LitVec& cc, ConstraintInfo& info, uint32 ccRepMode) {
-	// 2. clear flags and compute lbd
-	uint32  lbd         = 1;
-	uint32  onRoot      = 0;
-	uint32  varLevel    = 0;
-	uint32  assertLevel = 0;
-	uint32  assertPos   = 1;
-	uint32  maxVar      = cc[0].var();
-	Literal tagLit      = ~tagLiteral();
-	bool    tagged      = false;
-	for (LitVec::size_type i = 1; i != cc.size(); ++i) {
-		Var v = cc[i].var();
-		clearSeen(v);
-		if (cc[i] == tagLit) { tagged = true; }
-		if (v > maxVar)      { maxVar = v;    }
-		if ( (varLevel = level(v)) > assertLevel ) {
-			assertLevel = varLevel;
-			assertPos   = static_cast(i);
-		}
-		if (hasLevel(varLevel)) {
-			unmarkLevel(varLevel);
-			lbd += (varLevel > rootLevel()) || (++onRoot == 1);
-		}
-	}
-	if (assertPos != 1) { std::swap(cc[1], cc[assertPos]); }
-	if (ccRepMode == SolverStrategies::cc_rep_dynamic) {
-		ccRepMode = double(lbd)/double(decisionLevel()) > .66 ? SolverStrategies::cc_rep_decision : SolverStrategies::cc_rep_uip;
-	}
-	if (ccRepMode) {
-		maxVar = cc[0].var(), tagged = false, lbd = 1;
-		if (ccRepMode == SolverStrategies::cc_rep_decision) {
-			// replace cc with decision sequence
-			cc.resize(assertLevel+1);
-			for (uint32 i = assertLevel; i;){
-				Literal x = ~decision(i--);
-				cc[lbd++] = x;
-				if (x == tagLit)     { tagged = true; }
-				if (x.var() > maxVar){ maxVar = x.var(); }
-			}
-		}
-		else {
-			// replace cc with all uip clause
-			uint32 marked = sizeVec(cc) - 1;
-			while (cc.size() > 1) { markSeen(~cc.back()); cc.pop_back(); }
-			for (LitVec::const_iterator tr = assign_.trail.end(), next, stop; marked;) {
-				while (!seen(*--tr)) { ; }
-				bool n = --marked != 0 && !reason(*tr).isNull();
-				clearSeen(tr->var());
-				if (n) { for (next = tr, stop = assign_.trail.begin() + levelStart(level(tr->var())); next-- != stop && !seen(*next);) { ; } }
-				if (!n || level(next->var()) != level(tr->var())) {
-					cc.push_back(~*tr);
-					if (tr->var() == tagLit.var()){ tagged = true; }
-					if (tr->var() > maxVar)       { maxVar = tr->var(); }
-				}
-				else {
-					for (reason(*tr, conflict_); !conflict_.empty(); conflict_.pop_back()) {
-						if (!seen(conflict_.back())) { ++marked; markSeen(conflict_.back()); }
-					}
-				}
-			}
-			lbd = sizeVec(cc);
-		}
-	}
-	info.setScore(makeScore(ccInfo_.activity(), lbd));
-	info.setTagged(tagged);
-	info.setAux(auxVar(maxVar));
-	return assertLevel;
+uint32_t Solver::finalizeConflictClause(LitVec& cc, ConstraintInfo& info, uint32_t ccRepMode) {
+    // 2. clear flags and compute lbd
+    uint32_t lbd         = 1;
+    uint32_t onRoot      = 0;
+    uint32_t varLevel    = 0;
+    uint32_t assertLevel = 0;
+    uint32_t assertPos   = 1;
+    uint32_t maxVar      = cc[0].var();
+    Literal  tagLit      = ~tagLiteral();
+    bool     tagged      = false;
+    for (uint32_t i : irange(1u, size32(cc))) {
+        auto v = cc[i].var();
+        clearSeen(v);
+        if (cc[i] == tagLit) {
+            tagged = true;
+        }
+        if (v > maxVar) {
+            maxVar = v;
+        }
+        varLevel = level(v);
+        if (varLevel > assertLevel) {
+            assertLevel = varLevel;
+            assertPos   = i;
+        }
+        if (hasLevel(varLevel)) {
+            unmarkLevel(varLevel);
+            lbd += (varLevel > rootLevel()) || (++onRoot == 1);
+        }
+    }
+    if (assertPos != 1) {
+        std::swap(cc[1], cc[assertPos]);
+    }
+    if (ccRepMode == SolverStrategies::cc_rep_dynamic) {
+        ccRepMode =
+            ratio(lbd, decisionLevel()) > .66 ? SolverStrategies::cc_rep_decision : SolverStrategies::cc_rep_uip;
+    }
+    if (ccRepMode) {
+        maxVar = cc[0].var(), tagged = false, lbd = 1;
+        if (ccRepMode == SolverStrategies::cc_rep_decision) {
+            // replace cc with decision sequence
+            cc.resize(assertLevel + 1);
+            for (uint32_t i = assertLevel; i;) {
+                Literal x = ~decision(i--);
+                cc[lbd++] = x;
+                if (x == tagLit) {
+                    tagged = true;
+                }
+                if (x.var() > maxVar) {
+                    maxVar = x.var();
+                }
+            }
+        }
+        else {
+            // replace cc with all uip clause
+            uint32_t marked = size32(cc) - 1;
+            while (cc.size() > 1) {
+                markSeen(~cc.back());
+                cc.pop_back();
+            }
+            for (auto tr = assign_.trail.end(); marked;) {
+                while (not seen(*--tr)) {}
+                bool n = --marked != 0 && not reason(*tr).isNull();
+                clearSeen(tr->var());
+                auto next = tr;
+                if (n) {
+                    for (auto stop = assign_.trail.begin() + levelStart(level(tr->var()));
+                         next-- != stop && not seen(*next);) {}
+                }
+                if (not n || level(next->var()) != level(tr->var())) {
+                    cc.push_back(~*tr);
+                    if (tr->var() == tagLit.var()) {
+                        tagged = true;
+                    }
+                    if (tr->var() > maxVar) {
+                        maxVar = tr->var();
+                    }
+                }
+                else {
+                    for (reason(*tr, conflict_); not conflict_.empty(); conflict_.pop_back()) {
+                        if (not seen(conflict_.back())) {
+                            ++marked;
+                            markSeen(conflict_.back());
+                        }
+                    }
+                }
+            }
+            lbd = size32(cc);
+        }
+    }
+    info.setScore(ConstraintScore(ccInfo_.activity(), lbd));
+    info.setTagged(tagged);
+    info.setAux(auxVar(maxVar));
+    return assertLevel;
 }
 
 // (inefficient) default implementation
 bool Constraint::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	LitVec temp;
-	reason(s, p, temp);
-	for (LitVec::size_type i = 0; i != temp.size(); ++i) {
-		if (!s.ccMinimize(temp[i], rec)) {
-			return false;
-		}
-	}
-	return true;
+    LitVec temp;
+    reason(s, p, temp);
+    return std::ranges::all_of(temp, [&](Literal x) { return s.ccMinimize(x, rec); });
 }
 
 // Selects next branching literal
 bool Solver::decideNextBranch(double f) {
-	if (f <= 0.0 || rng.drand() >= f || numFreeVars() == 0) {
-		return heuristic_->select(*this);
-	}
-	// select randomly
-	Literal choice;
-	uint32 maxVar = numVars() + 1;
-	for (uint32 v = rng.irand(maxVar);;) {
-		if (value(v) == value_free) {
-			choice    = heuristic_->selectLiteral(*this, v, 0);
-			break;
-		}
-		if (++v == maxVar) { v = 1; }
-	}
-	return assume(choice);
+    if (f <= 0.0 || rng.drand() >= f || numFreeVars() == 0) {
+        return heuristic_->select(*this);
+    }
+    // select randomly
+    Literal  choice;
+    uint32_t maxVar = numVars() + 1;
+    for (uint32_t v = rng.irand(maxVar);;) {
+        if (value(v) == value_free) {
+            choice = DecisionHeuristic::selectLiteral(*this, v, 0);
+            break;
+        }
+        if (++v == maxVar) {
+            v = 1;
+        }
+    }
+    return assume(choice);
 }
 void Solver::resetLearntActivities() {
-	for (ConstraintDB::size_type i = 0, end = learnts_.size(); i != end; ++i) {
-		learnts_[i]->resetActivity();
-	}
+    for (auto* learnt : learnts_) { learnt->resetActivity(); }
 }
 // Removes up to remFrac% of the learnt nogoods but
 // keeps those that are locked or are highly active.
-Solver::DBInfo Solver::reduceLearnts(float remFrac, const ReduceStrategy& rs) {
-	uint32 oldS = numLearntConstraints();
-	uint32 remM = static_cast(oldS * std::max(0.0f, remFrac));
-	DBInfo r    = {0,0,0};
-	CmpScore cmp(learnts_, (ReduceStrategy::Score)rs.score, rs.glue, rs.protect);
-	if (remM >= oldS || !remM || rs.algo == ReduceStrategy::reduce_sort) {
-		r = reduceSortInPlace(remM, cmp, false);
-	}
-	else if (rs.algo == ReduceStrategy::reduce_stable) { r = reduceSort(remM, cmp);  }
-	else if (rs.algo == ReduceStrategy::reduce_heap)   { r = reduceSortInPlace(remM, cmp, true);}
-	else                                               { r = reduceLinear(remM, cmp); }
-	stats.addDeleted(oldS - r.size);
-	shrinkVecTo(learnts_, r.size);
-	return r;
+Solver::DBInfo Solver::reduceLearnts(double remFrac, const ReduceStrategy& rs) {
+    auto     oldS = numLearntConstraints();
+    auto     remM = static_cast(oldS * std::clamp(remFrac, 0.0, 1.0));
+    DBInfo   r{};
+    CmpScore cmp(learnts_, static_cast(rs.score), rs.glue, rs.protect);
+    if (remM >= oldS || not remM || rs.algo == ReduceStrategy::reduce_sort) {
+        r = reduceSortInPlace(remM, cmp, false);
+    }
+    else if (rs.algo == ReduceStrategy::reduce_stable) {
+        r = reduceSort(remM, cmp);
+    }
+    else if (rs.algo == ReduceStrategy::reduce_heap) {
+        r = reduceSortInPlace(remM, cmp, true);
+    }
+    else {
+        r = reduceLinear(remM, cmp);
+    }
+    stats.addDeleted(oldS - r.size);
+    shrinkVecTo(learnts_, r.size);
+    return r;
 }
 // Removes up to maxR of the learnt nogoods.
 // Keeps those that are locked or have a high activity and
 // does not reorder learnts_.
-Solver::DBInfo Solver::reduceLinear(uint32 maxR, const CmpScore& sc) {
-	// compute average activity
-	uint64 scoreSum = 0;
-	for (LitVec::size_type i = 0; i != learnts_.size(); ++i) {
-		scoreSum += sc.score(learnts_[i]->activity());
-	}
-	double avgAct = (scoreSum / (double) numLearntConstraints());
-	// constraints with score > 1.5 times the average are "active"
-	double scoreThresh = avgAct * 1.5;
-	double scoreMax    = (double)sc.score(makeScore(Clasp::ACT_MAX, 1));
-	if (scoreThresh > scoreMax) {
-		scoreThresh = (scoreMax + (scoreSum / (double) numLearntConstraints())) / 2.0;
-	}
-	// remove up to maxR constraints but keep "active" and locked once
-	DBInfo res = {0,0,0};
-	typedef ConstraintScore ScoreType;
-	for (LitVec::size_type i = 0; i != learnts_.size(); ++i) {
-		Constraint* c = learnts_[i];
-		ScoreType a   = c->activity();
-		bool isLocked = c->locked(*this);
-		bool isGlue   = sc.score(a) > scoreThresh || sc.isGlue(a);
-		if (maxR == 0 || isLocked || isGlue || sc.isFrozen(a)) {
-			res.pinned += isGlue;
-			res.locked += isLocked;
-			learnts_[res.size++] = c;
-			c->decreaseActivity();
-		}
-		else {
-			--maxR;
-			c->destroy(this, true);
-		}
-	}
-	return res;
+Solver::DBInfo Solver::reduceLinear(uint32_t maxR, const CmpScore& sc) {
+    // compute average activity
+    uint64_t scoreSum = 0;
+    for (const auto* learnt : learnts_) { scoreSum += sc.score(learnt->activity()); }
+    double avgAct = ratio(scoreSum, numLearntConstraints());
+    // constraints with score > 1.5 times the average are "active"
+    double scoreThresh = avgAct * 1.5;
+    double scoreMax    = sc.score(ConstraintScore(Clasp::act_max, 1));
+    if (scoreThresh > scoreMax) {
+        scoreThresh = (scoreMax + ratio(scoreSum, numLearntConstraints())) / 2.0;
+    }
+    // remove up to maxR constraints but keep "active" and locked once
+    DBInfo res{};
+    for (Constraint* c : learnts_) {
+        bool isLocked = c->locked(*this);
+        auto a        = c->activity();
+        bool isGlue   = sc.score(a) > scoreThresh || sc.isGlue(a);
+        if (maxR == 0 || isLocked || isGlue || sc.isFrozen(a)) {
+            res.pinned           += isGlue;
+            res.locked           += isLocked;
+            learnts_[res.size++]  = c;
+            c->decreaseActivity();
+        }
+        else {
+            --maxR;
+            c->destroy(this, true);
+        }
+    }
+    return res;
 }
 
 // Sorts learnt constraints by score and removes the
 // maxR constraints with the lowest score without
 // reordering learnts_.
-Solver::DBInfo Solver::reduceSort(uint32 maxR, const CmpScore& sc) {
-	typedef PodVector::type HeapType;
-	DBInfo   res  = {0,0,0};
-	HeapType heap;
-	heap.reserve(maxR = std::min(maxR, (uint32)learnts_.size()));
-	bool isGlue, isLocked;
-	for (LitVec::size_type i = 0; i != learnts_.size(); ++i) {
-		Constraint* c = learnts_[i];
-		CmpScore::ViewPair vp(toU32(i), c->activity());
-		res.pinned += (isGlue   = sc.isGlue(vp.second));
-		res.locked += (isLocked = c->locked(*this));
-		if (!isLocked && !isGlue && !sc.isFrozen(vp.second)) {
-			if (maxR) { // populate heap with first maxR constraints
-				heap.push_back(vp);
-				if (--maxR == 0) { std::make_heap(heap.begin(), heap.end(), sc); }
-			}
-			else if (sc(vp, heap[0])) { // replace max element in heap
-				std::pop_heap(heap.begin(), heap.end(), sc);
-				heap.back() = vp;
-				std::push_heap(heap.begin(), heap.end(), sc);
-			}
-		}
-	}
-	// Remove all constraints in heap - those are "inactive".
-	for (HeapType::const_iterator it = heap.begin(), end = heap.end(); it != end; ++it) {
-		learnts_[it->first]->destroy(this, true);
-		learnts_[it->first] = 0;
-	}
-	// Cleanup db and decrease activity of remaining constraints.
-	uint32 j = 0;
-	for (LitVec::size_type i = 0; i != learnts_.size(); ++i) {
-		if (Constraint* c = learnts_[i]) {
-			c->decreaseActivity();
-			learnts_[j++] = c;
-		}
-	}
-	res.size = j;
-	return res;
+Solver::DBInfo Solver::reduceSort(uint32_t maxR, const CmpScore& sc) {
+    using HeapType = PodVector_t;
+    DBInfo   res{};
+    HeapType heap;
+    heap.reserve(maxR = std::min(maxR, size32(learnts_)));
+    bool isGlue, isLocked;
+    auto cmp = std::cref(sc);
+    for (auto idx = 0u; Constraint * c : learnts_) {
+        CmpScore::ViewPair vp(idx++, c->activity());
+        res.pinned += (isGlue = sc.isGlue(vp.second));
+        res.locked += (isLocked = c->locked(*this));
+        if (not isLocked && not isGlue && not sc.isFrozen(vp.second)) {
+            if (maxR) { // populate heap with first maxR constraints
+                heap.push_back(vp);
+                if (--maxR == 0) {
+                    std::ranges::make_heap(heap, cmp);
+                }
+            }
+            else if (cmp(vp, heap[0])) { // replace max element in heap
+                std::ranges::pop_heap(heap, cmp);
+                heap.back() = vp;
+                std::ranges::push_heap(heap, cmp);
+            }
+        }
+    }
+    // Remove all constraints in heap - those are "inactive".
+    for (const auto& [idx, _] : heap) {
+        learnts_[idx]->destroy(this, true);
+        learnts_[idx] = nullptr;
+    }
+    // Cleanup db and decrease activity of remaining constraints.
+    uint32_t j = 0;
+    for (Constraint* c : learnts_) {
+        if (c) {
+            c->decreaseActivity();
+            learnts_[j++] = c;
+        }
+    }
+    res.size = j;
+    return res;
 }
 
 // Sorts the learnt db by score and removes the first
 // maxR constraints (those with the lowest score).
-Solver::DBInfo Solver::reduceSortInPlace(uint32 maxR, const CmpScore& sc, bool partial) {
-	DBInfo res = {0,0,0};
-	ConstraintDB::iterator nEnd = learnts_.begin();
-	maxR = std::min(maxR, (uint32)learnts_.size());
-	bool isGlue, isLocked;
-	typedef ConstraintScore ScoreType;
-	if (!partial) {
-		// sort whole db and remove first maxR constraints
-		if (maxR && maxR != learnts_.size()) std::stable_sort(learnts_.begin(), learnts_.end(), sc);
-		for (ConstraintDB::iterator it = learnts_.begin(), end = learnts_.end(); it != end; ++it) {
-			Constraint* c = *it;
-			ScoreType a = c->activity();
-			res.pinned += (isGlue = sc.isGlue(a));
-			res.locked += (isLocked = c->locked(*this));
-			if (!maxR || isLocked || isGlue || sc.isFrozen(a)) {
-				c->decreaseActivity();
-				*nEnd++ = c;
-			}
-			else {
-				c->destroy(this, true);
-				--maxR;
-			}
-		}
-	}
-	else {
-		ConstraintDB::iterator hBeg = learnts_.begin();
-		ConstraintDB::iterator hEnd = learnts_.begin();
-		for (ConstraintDB::iterator it = learnts_.begin(), end = learnts_.end(); it != end; ++it) {
-			Constraint* c = *it;
-			ScoreType a = c->activity();
-			res.pinned += (isGlue = sc.isGlue(a));
-			res.locked += (isLocked = c->locked(*this));
-			if      (isLocked || isGlue || sc.isFrozen(a)) { continue; }
-			else if (maxR) {
-				*it     = *hEnd;
-				*hEnd++ = c;
-				if (--maxR == 0) { std::make_heap(hBeg, hEnd, sc); }
-			}
-			else if (sc(c, learnts_[0])) {
-				std::pop_heap(hBeg, hEnd, sc);
-				*it      = *(hEnd-1);
-				*(hEnd-1)= c;
-				std::push_heap(hBeg, hEnd, sc);
-			}
-		}
-		// remove all constraints in heap
-		for (ConstraintDB::iterator it = hBeg; it != hEnd; ++it) {
-			(*it)->destroy(this, true);
-		}
-		// copy remaining constraints down
-		for (ConstraintDB::iterator it = hEnd, end = learnts_.end(); it != end; ++it) {
-			Constraint* c = *it;
-			c->decreaseActivity();
-			*nEnd++ = c;
-		}
-	}
-	res.size = static_cast(std::distance(learnts_.begin(), nEnd));
-	return res;
-}
-uint32 Solver::incEpoch(uint32 size, uint32 n) {
-	if (size > epoch_.size())         { epoch_.resize(size, 0u); }
-	if ((UINT32_MAX - epoch_[0]) < n) { epoch_.assign(epoch_.size(), 0u); }
-	return epoch_[0] += n;
-}
-uint32 Solver::countLevels(const Literal* first, const Literal* last, uint32 maxLevel) {
-	if (maxLevel < 2) { return uint32(maxLevel && first != last); }
-	POTASSCO_ASSERT(!ccMin_ || ccMin_->todo.empty(), "Must not be called during minimization!");
-	uint32 n = 0;
-	for (uint32 epoch = incEpoch(sizeVec(levels_) + 1); first != last; ++first) {
-		assert(value(first->var()) != value_free);
-		uint32& levEpoch = epoch_[level(first->var())];
-		if (levEpoch != epoch) {
-			levEpoch = epoch;
-			if (++n == maxLevel) { break; }
-		}
-	}
-	return n;
-}
-
-void Solver::updateBranch(uint32 n) {
-	int32 dl = (int32)decisionLevel(), xl = static_cast(cflStamp_.size())-1;
-	if      (xl > dl) { do { n += cflStamp_.back(); cflStamp_.pop_back(); } while (--xl != dl); }
-	else if (dl > xl) { cflStamp_.insert(cflStamp_.end(), dl - xl, 0); }
-	cflStamp_.back() += n;
+Solver::DBInfo Solver::reduceSortInPlace(uint32_t maxR, const CmpScore& sc, bool partial) {
+    DBInfo res{};
+    auto   nEnd = learnts_.begin();
+    maxR        = std::min(maxR, size32(learnts_));
+    bool isGlue, isLocked;
+    if (not partial) {
+        // sort whole db and remove first maxR constraints
+        if (maxR && maxR != learnts_.size()) {
+            std::ranges::stable_sort(learnts_, sc);
+        }
+        for (Constraint* c : learnts_) {
+            auto a      = c->activity();
+            res.pinned += (isGlue = sc.isGlue(a));
+            res.locked += (isLocked = c->locked(*this));
+            if (not maxR || isLocked || isGlue || sc.isFrozen(a)) {
+                c->decreaseActivity();
+                *nEnd++ = c;
+            }
+            else {
+                c->destroy(this, true);
+                --maxR;
+            }
+        }
+    }
+    else {
+        auto hBeg = learnts_.begin();
+        auto hEnd = learnts_.begin();
+        auto cmp  = std::cref(sc);
+        for (auto& learnt : learnts_) {
+            Constraint* c  = learnt;
+            auto        a  = c->activity();
+            res.pinned    += (isGlue = sc.isGlue(a));
+            res.locked    += (isLocked = c->locked(*this));
+            if (isLocked || isGlue || sc.isFrozen(a)) {
+                continue;
+            }
+            if (maxR) {
+                learnt  = *hEnd;
+                *hEnd++ = c;
+                if (--maxR == 0) {
+                    std::make_heap(hBeg, hEnd, cmp);
+                }
+            }
+            else if (cmp(c, learnts_[0])) {
+                std::pop_heap(hBeg, hEnd, cmp);
+                learnt      = *(hEnd - 1);
+                *(hEnd - 1) = c;
+                std::push_heap(hBeg, hEnd, cmp);
+            }
+        }
+        // remove all constraints in heap
+        for (auto* c : std::ranges::subrange(hBeg, hEnd)) { c->destroy(this, true); }
+        // copy remaining constraints down
+        for (auto* c : std::ranges::subrange(hEnd, learnts_.end())) {
+            c->decreaseActivity();
+            *nEnd++ = c;
+        }
+    }
+    res.size = static_cast(std::distance(learnts_.begin(), nEnd));
+    return res;
+}
+uint32_t Solver::incEpoch(uint32_t size, uint32_t n) {
+    if (size > size32(epoch_)) {
+        epoch_.resize(size, 0u);
+    }
+    if ((UINT32_MAX - epoch_[0]) < n) {
+        epoch_.assign(epoch_.size(), 0u);
+    }
+    return epoch_[0] += n;
+}
+uint32_t Solver::countLevels(LitView lits, uint32_t maxLevel) {
+    if (maxLevel < 2) {
+        return static_cast(maxLevel && not lits.empty());
+    }
+    POTASSCO_ASSERT(not ccMin_ || ccMin_->todo.empty(), "Must not be called during minimization!");
+    uint32_t n     = 0;
+    uint32_t epoch = incEpoch(size32(levels_) + 1);
+    for (auto lit : lits) {
+        assert(value(lit.var()) != value_free);
+        if (uint32_t& levEpoch = epoch_[level(lit.var())]; levEpoch != epoch) {
+            levEpoch = epoch;
+            if (++n == maxLevel) {
+                break;
+            }
+        }
+    }
+    return n;
+}
+
+void Solver::updateBranch(uint32_t n) {
+    int32_t dl = static_cast(decisionLevel()), xl = static_cast(size32(cflStamp_)) - 1;
+    if (xl > dl) {
+        do {
+            n += cflStamp_.back();
+            cflStamp_.pop_back();
+        } while (--xl != dl);
+    }
+    else if (dl > xl) {
+        cflStamp_.insert(cflStamp_.end(), static_cast(dl - xl), 0);
+    }
+    cflStamp_.back() += n;
 }
 bool Solver::reduceReached(const SearchLimits& limits) const {
-	return numLearntConstraints() > limits.learnts || memUse_ > limits.memory;
+    return numLearntConstraints() > limits.learnts || memUse_ > limits.memory;
 }
 bool Solver::restartReached(const SearchLimits& limits) const {
-	uint64 n = !limits.restart.local || cflStamp_.empty() ? limits.used : cflStamp_.back();
-	return n >= limits.restart.conflicts || (limits.restart.dynamic && limits.restart.dynamic->reached());
+    uint64_t n = not limits.restart.local || cflStamp_.empty() ? limits.used : cflStamp_.back();
+    return n >= limits.restart.conflicts || (limits.restart.dynamic && limits.restart.dynamic->reached());
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // The basic DPLL-like search-function
 /////////////////////////////////////////////////////////////////////////////////////////
-ValueRep Solver::search(SearchLimits& limit, double rf) {
-	assert(!isFalse(tagLiteral()));
-	SearchLimits::BlockPtr block = limit.restart.block;
-	rf = std::max(0.0, std::min(1.0, rf));
-	lower.reset();
-	if (limit.restart.local && decisionLevel() == rootLevel()) { cflStamp_.assign(decisionLevel()+1, 0); }
-	dynLimit_ = limit.restart.dynamic;
-	struct AtExit {
-		~AtExit() { self->dynLimit_ = 0; }
-		Solver* self;
-	} atExit = {this};
-	do {
-		for (bool conflict = hasConflict() || !propagate() || !simplify(), local = limit.restart.local;;) {
-			if (conflict) {
-				uint32 n = 1, ts;
-				do {
-					if (block && block->push(ts = numAssignedVars()) && ts > block->scaled()) {
-						if (limit.restart.dynamic) { limit.restart.dynamic->block(); }
-						else                       { limit.restart.conflicts += block->inc; }
-						block->next = block->n + block->inc;
-						++stats.blRestarts;
-					}
-				} while (resolveConflict() && !propagate() && (++n, true));
-				limit.used += n;
-				if (local) { updateBranch(n); }
-				if (hasConflict() || (decisionLevel() == 0 && !simplify())) { return value_false; }
-				if (numFreeVars()) {
-					if (limit.used >= limit.conflicts) { return value_free; }
-					if (restartReached(limit))         { return value_free; }
-					if (reduceReached(limit))          { return value_free; }
-				}
-			}
-			if (decideNextBranch(rf)) { conflict = !propagate(); }
-			else                      { break; }
-		}
-	} while (!isModel());
-	temp_.clear();
-	model.clear(); model.reserve(numVars()+1);
-	for (Var v = 0; v <= numVars(); ++v) { model.push_back(value(v)); }
-	if (satPrepro()) { satPrepro()->extendModel(model, temp_); }
-	return value_true;
-}
-ValueRep Solver::search(uint64 maxC, uint32 maxL, bool local, double rp) {
-	SearchLimits limit;
-	limit.restart.conflicts = maxC;
-	limit.restart.local     = local;
-	limit.learnts = maxL;
-	return search(limit, rp);
+Val_t Solver::search(SearchLimits& limit, double rf) {
+    assert(not isFalse(tagLiteral()));
+    auto* block = limit.restart.block;
+    rf          = std::max(0.0, std::min(1.0, rf));
+    if (limit.restart.local && decisionLevel() == rootLevel()) {
+        cflStamp_.assign(decisionLevel() + 1, 0);
+    }
+    dynLimit_ = limit.restart.dynamic;
+    POTASSCO_SCOPE_EXIT({ dynLimit_ = nullptr; });
+    do {
+        for (bool conflict = hasConflict() || not propagate() || not simplify(), local = limit.restart.local;;) {
+            if (conflict) {
+                uint32_t n = 1, ts;
+                do {
+                    if (block && block->push(ts = numAssignedVars()) && ts > block->scaled()) {
+                        if (limit.restart.dynamic) {
+                            limit.restart.dynamic->block();
+                        }
+                        else {
+                            limit.restart.conflicts += block->inc;
+                        }
+                        block->next = block->n + block->inc;
+                        ++stats.blRestarts;
+                    }
+                } while (resolveConflict() && not propagate() && (++n, true));
+                limit.used += n;
+                if (local) {
+                    updateBranch(n);
+                }
+                if (hasConflict() || (decisionLevel() == 0 && not simplify())) {
+                    return value_false;
+                }
+                if (numFreeVars()) {
+                    if (limit.used >= limit.conflicts) {
+                        return value_free;
+                    }
+                    if (restartReached(limit)) {
+                        return value_free;
+                    }
+                    if (reduceReached(limit)) {
+                        return value_free;
+                    }
+                }
+            }
+            if (decideNextBranch(rf)) {
+                conflict = not propagate();
+            }
+            else {
+                break;
+            }
+        }
+    } while (not isModel());
+    return value_true;
+}
+Val_t Solver::search(uint64_t maxC, uint32_t maxL, bool local, double rp) {
+    SearchLimits limit;
+    limit.restart.conflicts = maxC;
+    limit.restart.local     = local;
+    limit.learnts           = maxL;
+    return search(limit, rp);
 }
 bool Solver::isModel() {
-	if (hasConflict()) { return false; }
-	FOR_EACH_POST(x, post_.head()) {
-		if (!x->isModel(*this)) { return false; }
-	}
-	return !enumerationConstraint() || enumerationConstraint()->valid(*this);
+    if (hasConflict() || not post_.isModel(*this)) {
+        return false;
+    }
+    return not enumerationConstraint() || enumerationConstraint()->valid(*this);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Free functions
 /////////////////////////////////////////////////////////////////////////////////////////
 void destroyDB(Solver::ConstraintDB& db, Solver* s, bool detach) {
-	if (s && detach) {
-		s->destroyDB(db);
-		return;
-	}
-	while (!db.empty()) {
-		db.back()->destroy(s, detach);
-		db.pop_back();
-	}
-}
-}
+    if (s && detach) {
+        s->destroyDB(db);
+        return;
+    }
+    while (not db.empty()) {
+        db.back()->destroy(s, detach);
+        db.pop_back();
+    }
+}
+} // namespace Clasp
diff --git a/src/solver_strategies.cpp b/src/solver_strategies.cpp
index 7020407..f655546 100644
--- a/src/solver_strategies.cpp
+++ b/src/solver_strategies.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2014-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,404 +22,437 @@
 // IN THE SOFTWARE.
 //
 #include 
-#include 
+
 #include 
 #include 
+#include 
+
+#include 
+
 #include 
-#if defined(__GNUC__) && __GNUC__ >= 8
-#pragma GCC diagnostic ignored "-Wclass-memaccess"
-#endif
+
 namespace Clasp {
+static_assert(sizeof(ReduceStrategy) == sizeof(uint32_t), "invalid bitset");
 /////////////////////////////////////////////////////////////////////////////////////////
 // SolverStrategies / SolverParams
 /////////////////////////////////////////////////////////////////////////////////////////
-SolverStrategies::SolverStrategies() {
-	struct X { uint32 z[2]; };
-	static_assert(sizeof(SolverStrategies) == sizeof(X), "Unsupported Padding");
-	std::memset(this, 0, sizeof(SolverStrategies));
-}
 void SolverStrategies::prepare() {
-	if (search == SolverStrategies::no_learning) {
-		compress    = 0;
-		saveProgress= 0;
-		reverseArcs = 0;
-		otfs        = 0;
-		updateLbd   = 0;
-		ccMinAntes  = SolverStrategies::no_antes;
-		bumpVarAct  = 0;
-	}
-}
-HeuParams::HeuParams() {
-	std::memset(this, 0, sizeof(HeuParams));
-	moms = 1;
-}
-OptParams::OptParams(Type t) {
-	std::memset(this, 0, sizeof(OptParams));
-	type = t;
-}
-SolverParams::SolverParams() {
-	struct X { uint32 strat[2]; uint32 self[5]; };
-	static_assert(sizeof(SolverParams) == sizeof(X), "Unsupported Padding");
-	std::memset(&seed, 0, sizeof(uint32)*2);
-	seed = RNG().seed();
-}
-uint32 SolverParams::prepare() {
-	uint32 res = 0;
-	if (search == SolverStrategies::no_learning && Heuristic_t::isLookback(heuId)) {
-		heuId = Heuristic_t::None;
-		res  |= 1;
-	}
-	if (heuId == Heuristic_t::Unit) {
-		if (!Lookahead::isType(lookType)) { res |= 2; lookType = Var_t::Atom; }
-		lookOps = 0;
-	}
-	if (heuId != Heuristic_t::Domain && (heuristic.domPref || heuristic.domMod)) {
-		res |= 4;
-		heuristic.domPref= 0;
-		heuristic.domMod = 0;
-	}
-	SolverStrategies::prepare();
-	return res;
+    static_assert(sizeof(SolverStrategies) == sizeof(uint64_t), "Unsupported Padding");
+    if (search == no_learning) {
+        compress     = 0;
+        saveProgress = 0;
+        reverseArcs  = 0;
+        otfs         = 0;
+        updateLbd    = 0;
+        ccMinAntes   = no_antes;
+        bumpVarAct   = 0;
+    }
+}
+
+uint32_t SolverParams::prepare() {
+    struct X {
+        uint32_t strat[2];
+        uint32_t self[5];
+    };
+    static_assert(sizeof(SolverParams) == sizeof(X), "Unsupported Padding");
+    uint32_t res = 0;
+    if (search == no_learning && isLookbackHeuristic(heuId)) {
+        heuId  = +HeuristicType::none;
+        res   |= 1;
+    }
+    if (heuId == HeuristicType::unit) {
+        if (not Lookahead::isType(lookType)) {
+            res      |= 2;
+            lookType  = +VarType::atom;
+        }
+        lookOps = 0;
+    }
+    if (heuId != HeuristicType::domain && (heuristic.domPref || heuristic.domMod)) {
+        res               |= 4;
+        heuristic.domPref  = 0;
+        heuristic.domMod   = 0;
+    }
+    SolverStrategies::prepare();
+    return res;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ScheduleStrategy
 /////////////////////////////////////////////////////////////////////////////////////////
-double growR(uint32 idx, double g) { return pow(g, (double)idx); }
-double addR(uint32 idx, double a)  { return a * idx; }
-uint32 lubyR(uint32 idx)           {
-	uint32 i = idx + 1;
-	while ((i & (i+1)) != 0) {
-		i    -= ((1u << log2(i)) - 1);
-	}
-	return (i+1)>>1;
-}
-ScheduleStrategy::ScheduleStrategy(Type t, uint32 b, double up, uint32 lim)
-	: base(b), type(t), idx(0), len(lim), grow(0.0)  {
-	if      (t == Geometric)  { grow = static_cast(std::max(1.0, up)); }
-	else if (t == Arithmetic) { grow = static_cast(std::max(0.0, up)); }
-	else if (t == Luby && lim){ len  = std::max(uint32(2), (static_cast(std::pow(2.0, std::ceil(log(double(lim))/log(2.0)))) - 1)*2); }
+double   growR(uint32_t idx, double g) { return pow(g, (double) idx); }
+double   addR(uint32_t idx, double a) { return a * idx; }
+uint32_t lubyR(uint32_t idx) {
+    uint32_t i = idx + 1;
+    while ((i & (i + 1)) != 0) { i -= ((1u << Potassco::log2(i)) - 1); }
+    return (i + 1) >> 1;
+}
+ScheduleStrategy::ScheduleStrategy(Type t, uint32_t b, double up, uint32_t lim)
+    : base(b)
+    , type(t)
+    , idx(0)
+    , len(lim)
+    , grow(0.0) {
+    if (t == sched_geom) {
+        grow = static_cast(std::max(1.0, up));
+    }
+    else if (t == sched_arith) {
+        grow = static_cast(std::max(0.0, up));
+    }
+    else if (t == sched_luby && lim) {
+        len = std::max(2u, (static_cast(std::pow(2.0, std::ceil(std::log2(lim)))) - 1) * 2);
+    }
 }
 
 static uint64_t saturate(double d) {
-	return d < static_cast(UINT64_MAX) ? static_cast(d) : UINT64_MAX;
+    return d < static_cast(UINT64_MAX) ? static_cast(d) : UINT64_MAX;
 }
 
-uint64 ScheduleStrategy::current() const {
-	if      (base == 0)          return UINT64_MAX;
-	else if (type == Geometric)  return saturate(growR(idx, grow) * base);
-	else if (type == Arithmetic) return static_cast(addR(idx, grow)  + base);
-	else if (type == Luby)       return static_cast(lubyR(idx)) * base;
-	else                         return base;
-}
-uint64 ScheduleStrategy::next() {
-	if (++idx != len) { return current(); }
-	// length reached or overflow
-	len = (len + !!idx) << uint32(type == Luby);
-	idx = 0;
-	return current();
-}
-void ScheduleStrategy::advanceTo(uint32 n) {
-	if (!len || n < len)       {
-		idx = n;
-		return;
-	}
-	if (type != Luby) {
-		double dLen = len;
-		uint32 x    = uint32(sqrt(dLen * (4.0 * dLen - 4.0) + 8.0 * double(n+1))-2*dLen+1)/2;
-		idx         = n - uint32(x*dLen+double(x-1.0)*x/2.0);
-		len        += x;
-		return;
-	}
-	while (n >= len) {
-		n   -= len++;
-		len *= 2;
-	}
-	idx = n;
-}
-RestartSchedule RestartSchedule::dynamic(uint32 base, float k, uint32 lim, AvgType fast, Keep keep, AvgType slow, uint32 slowW) {
-	RestartSchedule sched;
-	sched.base = base;
-	sched.type = 3u;
-	sched.grow = k;
-	sched.len  = lim;
-	sched.idx  = uint32(fast) | (uint32(slow) << 3u) | ((keep & 3u) << 6u) | (std::min(slowW, (1u<<24)-1) << 8u);
-	return sched;
+uint64_t ScheduleStrategy::current() const {
+    if (base == 0) {
+        return UINT64_MAX;
+    }
+    switch (type) {
+        case sched_geom : return saturate(growR(idx, grow) * base);
+        case sched_arith: return static_cast(addR(idx, grow) + base);
+        case sched_luby : return static_cast(lubyR(idx)) * base;
+        default         : return base;
+    }
+}
+uint64_t ScheduleStrategy::next() {
+    if (++idx != len) {
+        return current();
+    }
+    // length reached or overflow
+    len = (len + !!idx) << static_cast(type == sched_luby);
+    idx = 0;
+    return current();
+}
+void ScheduleStrategy::advanceTo(uint32_t n) {
+    if (not len || n < len) {
+        idx = n;
+        return;
+    }
+    if (type != sched_luby) {
+        double   dLen  = len;
+        uint32_t x     = static_cast(sqrt(dLen * (4.0 * dLen - 4.0) + 8.0 * (n + 1.0)) - 2 * dLen + 1) / 2;
+        idx            = n - static_cast(x * dLen + (x - 1.0) * x / 2.0);
+        len           += x;
+        return;
+    }
+    while (n >= len) {
+        n   -= len++;
+        len *= 2;
+    }
+    idx = n;
+}
+RestartSchedule RestartSchedule::dynamic(uint32_t base, float k, uint32_t lim, AvgType fast, Keep keep, AvgType slow,
+                                         uint32_t slowW) {
+    RestartSchedule sched;
+    sched.base = base;
+    sched.type = 3u;
+    sched.grow = k;
+    sched.len  = lim;
+    sched.idx  = static_cast(fast) | (static_cast(slow) << 3u) | ((keep & 3u) << 6u) |
+                (std::min(slowW, (1u << 24) - 1) << 8u);
+    return sched;
 }
 MovingAvg::Type       RestartSchedule::fastAvg() const { return static_cast(idx & 7u); }
 MovingAvg::Type       RestartSchedule::slowAvg() const { return static_cast((idx >> 3u) & 7u); }
 RestartSchedule::Keep RestartSchedule::keepAvg() const { return static_cast((idx >> 6u) & 3u); }
-uint32                RestartSchedule::slowWin() const { return idx >> 8u; }
+uint32_t              RestartSchedule::slowWin() const { return idx >> 8u; }
 /////////////////////////////////////////////////////////////////////////////////////////
 // RestartParams
 /////////////////////////////////////////////////////////////////////////////////////////
 RestartParams::RestartParams()
-	: rsSched()
-	, block()
-	, counterRestart(0), counterBump(9973)
-	, shuffle(0), shuffleNext(0)
-	, upRestart(0), cntLocal(0) {
-	static_assert(sizeof(RestartParams) == sizeof(ScheduleStrategy) + (3 * sizeof(uint32)) + sizeof(float), "Invalid structure alignment");
+    : rsSched()
+    , block()
+    , counterRestart(0)
+    , counterBump(9973)
+    , shuffle(0)
+    , shuffleNext(0)
+    , upRestart(0)
+    , cntLocal(0) {
+    static_assert(sizeof(RestartParams) == sizeof(ScheduleStrategy) + (3 * sizeof(uint32_t)) + sizeof(float),
+                  "Invalid structure alignment");
 }
 void RestartParams::disable() {
-	std::memset(this, 0, sizeof(RestartParams));
+    *this                                   = {};
+    static_cast(rsSched) = ScheduleStrategy::none();
 }
-uint32 RestartParams::prepare(bool withLookback) {
-	if (!withLookback || disabled()) {
-		disable();
-	}
-	return 0;
+uint32_t RestartParams::prepare(bool withLookback) {
+    if (not withLookback || disabled()) {
+        disable();
+    }
+    return 0;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DynamicLimit
 /////////////////////////////////////////////////////////////////////////////////////////
-DynamicLimit::Global::Global(MovingAvg::Type type, uint32 size)
-	: lbd(size, type)
-	, cfl(size, type) {
-}
+DynamicLimit::Global::Global(MovingAvg::Type type, uint32_t size) : lbd(size, type), cfl(size, type) {}
 
-static uint32 verifySize(uint32 size) {
-	POTASSCO_REQUIRE(size != 0, "size must be > 0");
-	return size;
+static uint32_t verifySize(uint32_t size) {
+    POTASSCO_CHECK_PRE(size != 0, "size must be > 0");
+    return size;
 }
 
-DynamicLimit::DynamicLimit(float k, uint32 size, MovingAvg::Type fast, Keep keep, MovingAvg::Type slow, uint32 slowSize, uint32 adjustLim)
-	: global_(slow, slowSize || slow == MovingAvg::avg_sma ? slowSize : 200 * verifySize(size))
-	, avg_(verifySize(size), fast)
-	, num_(0)
-	, keep_(keep) {
-	resetAdjust(k, lbd_limit, adjustLim);
+DynamicLimit::DynamicLimit(float k, uint32_t size, MovingAvg::Type fast, Keep keep, MovingAvg::Type slow,
+                           uint32_t slowSize, uint32_t adjustLimit)
+    : global_(slow, slowSize || slow == MovingAvg::avg_sma ? slowSize : 200 * verifySize(size))
+    , avg_(verifySize(size), fast)
+    , num_(0)
+    , keep_(keep) {
+    resetAdjust(k, lbd_limit, adjustLimit);
 }
 
-void DynamicLimit::resetAdjust(float k, Type t, uint32 uLimit, bool resetAvg) {
-	std::memset(&adjust, 0, sizeof(adjust));
-	adjust.limit = uLimit;
-	adjust.rk = k;
-	adjust.type = t;
-	if (resetAvg) {
-		num_ = 0;
-		avg_.clear();
-	}
-}
-void DynamicLimit::block() {
-	resetRun(RestartSchedule::keep_block);
-}
+void DynamicLimit::resetAdjust(float k, Type type, uint32_t lim, bool resetAvg) {
+    adjust = {.limit = lim, .restarts = 0, .samples = 0, .rk = k, .type = type};
+    if (resetAvg) {
+        num_ = 0;
+        avg_.clear();
+    }
+}
+void DynamicLimit::block() { resetRun(Keep::keep_block); }
 
 void DynamicLimit::resetRun(Keep k) {
-	num_ = 0;
-	if ((keep_ & k) == 0)
-		avg_.clear();
+    num_ = 0;
+    if ((keep_ & k) == 0) {
+        avg_.clear();
+    }
 }
 void DynamicLimit::reset() {
-	global_.reset();
-	resetRun(RestartSchedule::keep_never);
-}
-void DynamicLimit::update(uint32 dl, uint32 lbd) {
-	// update global avg
-	++adjust.samples;
-	global_.cfl.push(dl);
-	global_.lbd.push(lbd);
-	// update moving avg
-	++num_;
-	uint32 v = adjust.type == lbd_limit ? lbd : dl;
-	avg_.push(v);
-}
-uint32 DynamicLimit::restart(uint32 maxLBD, float k) {
-	++adjust.restarts;
-	if (adjust.limit != UINT32_MAX && adjust.samples >= adjust.limit) {
-		Type   nt   = maxLBD && global_.avg(lbd_limit) > maxLBD ? level_limit : lbd_limit;
-		float  rk   = adjust.rk;
-		uint32 uLim = adjust.limit;
-		if (nt == adjust.type) {
-			double rLen = adjust.avgRestart();
-			bool   sx   = num_ >= adjust.limit;
-			if      (rLen >= 16000.0) { rk += 0.1f;  uLim = 16000; }
-			else if (sx)              { rk += 0.05f; uLim = std::max(uint32(16000), uLim-10000); }
-			else if (rLen >= 4000.0)  { rk += 0.05f; }
-			else if (rLen >= 1000.0)  { uLim += 10000u; }
-			else if (rk > k)          { rk -= 0.05f; }
-		}
-		resetAdjust(rk, nt, uLim);
-	}
-	resetRun(RestartSchedule::keep_restart);
-	return adjust.limit;
-}
-BlockLimit::BlockLimit(uint32 windowSize, double R, MovingAvg::Type at)
-	: avg(windowSize, at)
-	, next(windowSize)
-	, n(0)
-	, inc(50)
-	, r(static_cast(R)) {
-	static_assert(sizeof(BlockLimit) == 12*sizeof(uint32), "unexpected size");
+    global_.reset();
+    resetRun(Keep::keep_never);
+}
+void DynamicLimit::update(uint32_t dl, uint32_t lbd) {
+    // update global avg
+    ++adjust.samples;
+    global_.cfl.push(dl);
+    global_.lbd.push(lbd);
+    // update moving avg
+    ++num_;
+    uint32_t v = adjust.type == lbd_limit ? lbd : dl;
+    avg_.push(v);
+}
+uint32_t DynamicLimit::restart(uint32_t maxLbd, float k) {
+    ++adjust.restarts;
+    if (adjust.limit != UINT32_MAX && adjust.samples >= adjust.limit) {
+        Type     nt   = maxLbd && global_.avg(lbd_limit) > maxLbd ? level_limit : lbd_limit;
+        float    rk   = adjust.rk;
+        uint32_t uLim = adjust.limit;
+        if (nt == adjust.type) {
+            double rLen = adjust.avgRestart();
+            bool   sx   = num_ >= adjust.limit;
+            if (rLen >= 16000.0) {
+                rk   += 0.1f;
+                uLim  = 16000;
+            }
+            else if (sx) {
+                rk   += 0.05f;
+                uLim  = std::max(16000u, uLim - 10000);
+            }
+            else if (rLen >= 4000.0) {
+                rk += 0.05f;
+            }
+            else if (rLen >= 1000.0) {
+                uLim += 10000u;
+            }
+            else if (rk > k) {
+                rk -= 0.05f;
+            }
+        }
+        resetAdjust(rk, nt, uLim);
+    }
+    resetRun(Keep::keep_restart);
+    return adjust.limit;
+}
+BlockLimit::BlockLimit(uint32_t windowSize, double rf, MovingAvg::Type at)
+    : avg(windowSize, at)
+    , next(windowSize)
+    , n(0)
+    , inc(50)
+    , r(static_cast(rf)) {
+    static_assert(sizeof(BlockLimit) == 12 * sizeof(uint32_t), "unexpected size");
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ReduceParams
 /////////////////////////////////////////////////////////////////////////////////////////
-uint32 ReduceParams::getLimit(uint32 base, double f, const Range32& r) {
-	base = (f != 0.0 ? (uint32)std::min(base*f, double(UINT32_MAX)) : UINT32_MAX);
-	return r.clamp( base );
-}
-uint32 ReduceParams::getBase(const SharedContext& ctx) const {
-	uint32 st = strategy.estimate != ReduceStrategy::est_dynamic || ctx.isExtended() ? strategy.estimate : (uint32)ReduceStrategy::est_num_constraints;
-	switch(st) {
-		default:
-		case ReduceStrategy::est_dynamic        : {
-			uint32 m = std::min(ctx.stats().vars.num, ctx.stats().numConstraints());
-			uint32 M = std::max(ctx.stats().vars.num, ctx.stats().numConstraints());
-			return M > (m * 10) ? M : m;
-		}
-		case ReduceStrategy::est_con_complexity : return ctx.stats().complexity;
-		case ReduceStrategy::est_num_constraints: return ctx.stats().numConstraints();
-		case ReduceStrategy::est_num_vars       : return ctx.stats().vars.num;
-	}
+uint32_t ReduceParams::getLimit(uint32_t base, double f, const Range32& r) {
+    base = (f != 0.0 ? static_cast(std::min(base * f, static_cast(UINT32_MAX))) : UINT32_MAX);
+    return r.clamp(base);
+}
+uint32_t ReduceParams::getBase(const SharedContext& ctx) const {
+    uint32_t st = strategy.estimate != ReduceStrategy::est_dynamic || ctx.isExtended()
+                      ? strategy.estimate
+                      : static_cast(ReduceStrategy::est_num_constraints);
+    switch (st) {
+        default:
+        case ReduceStrategy::est_dynamic: {
+            uint32_t mi = std::min(ctx.stats().vars.num, ctx.stats().numConstraints());
+            uint32_t ma = std::max(ctx.stats().vars.num, ctx.stats().numConstraints());
+            return ma > (mi * 10) ? ma : mi;
+        }
+        case ReduceStrategy::est_con_complexity : return ctx.stats().complexity;
+        case ReduceStrategy::est_num_constraints: return ctx.stats().numConstraints();
+        case ReduceStrategy::est_num_vars       : return ctx.stats().vars.num;
+    }
 }
 void ReduceParams::disable() {
-	cflSched  = ScheduleStrategy::none();
-	growSched = ScheduleStrategy::none();
-	strategy.fReduce = 0;
-	fGrow     = 0.0f; fInit = 0.0f; fMax = 0.0f;
-	initRange = Range(UINT32_MAX, UINT32_MAX);
-	maxRange  = UINT32_MAX;
-	memMax    = 0;
+    cflSched         = ScheduleStrategy::none();
+    growSched        = ScheduleStrategy::none();
+    strategy.fReduce = 0;
+    fGrow            = 0.0f;
+    fInit            = 0.0f;
+    fMax             = 0.0f;
+    initRange        = Range32(UINT32_MAX, UINT32_MAX);
+    maxRange         = UINT32_MAX;
+    memMax           = 0;
 }
 Range32 ReduceParams::sizeInit(const SharedContext& ctx) const {
-	if (!growSched.disabled() || growSched.defaulted()) {
-		uint32 base = getBase(ctx);
-		uint32 lo   = std::min(getLimit(base, fInit, initRange), maxRange);
-		uint32 hi   = getLimit(base, fMax, Range32(lo, maxRange));
-		return Range32(lo, hi);
-	}
-	return Range32(maxRange, maxRange);
-}
-uint32 ReduceParams::cflInit(const SharedContext& ctx) const {
-	return cflSched.disabled() ? 0 : getLimit(getBase(ctx), fInit, initRange);
-}
-uint32 ReduceParams::prepare(bool withLookback) {
-	if (!withLookback || fReduce() == 0.0f) {
-		disable();
-		return 0;
-	}
-	if (cflSched.defaulted() && growSched.disabled() && !growSched.defaulted()) {
-		cflSched = ScheduleStrategy::arith(4000, 600);
-	}
-	if (fMax != 0.0f) { fMax = std::max(fMax, fInit); }
-	return 0;
+    if (not growSched.disabled() || growSched.defaulted()) {
+        uint32_t base = getBase(ctx);
+        uint32_t lo   = std::min(getLimit(base, fInit, initRange), maxRange);
+        uint32_t hi   = getLimit(base, fMax, Range32(lo, maxRange));
+        return {lo, hi};
+    }
+    return {maxRange, maxRange};
+}
+uint32_t ReduceParams::cflInit(const SharedContext& ctx) const {
+    return cflSched.disabled() ? 0 : getLimit(getBase(ctx), fInit, initRange);
+}
+uint32_t ReduceParams::prepare(bool withLookback) {
+    if (not withLookback || fReduce() == 0.0f) {
+        disable();
+        return 0;
+    }
+    if (cflSched.defaulted() && growSched.disabled() && not growSched.defaulted()) {
+        cflSched = ScheduleStrategy::arith(4000, 600);
+    }
+    if (fMax != 0.0f) {
+        fMax = std::max(fMax, fInit);
+    }
+    return 0;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // SolveParams
 /////////////////////////////////////////////////////////////////////////////////////////
-SolveParams::SolveParams()
-	: randRuns(0u), randConf(0u)
-	, randProb(0.0f) {
-}
-uint32 SolveParams::prepare(bool withLookback) {
-	return restart.prepare(withLookback) | reduce.prepare(withLookback);
+SolveParams::SolveParams() : randRuns(0u), randConf(0u), randProb(0.0f) {}
+uint32_t SolveParams::prepare(bool withLookback) {
+    return restart.prepare(withLookback) | reduce.prepare(withLookback);
 }
 bool SolveParams::randomize(Solver& s) const {
-	for (uint32 r = 0, c = randConf; r != randRuns && c; ++r) {
-		if (s.search(c, UINT32_MAX, false, 1.0) != value_free) { return !s.hasConflict(); }
-		s.undoUntil(0);
-	}
-	return true;
+    for (uint32_t r = 0, c = randConf; r != randRuns && c; ++r) {
+        if (s.search(c, UINT32_MAX, false, 1.0) != value_free) {
+            return not s.hasConflict();
+        }
+        s.undoUntil(0);
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Configurations
 /////////////////////////////////////////////////////////////////////////////////////////
-Configuration::~Configuration() {}
+Configuration::~Configuration() = default;
 Configuration* Configuration::config(const char* n) {
-	return !n || !*n || ((*n == '.' || *n == '/') && !n[1]) ? this : 0;
+    return not n || !*n || ((*n == '.' || *n == '/') && not n[1]) ? this : nullptr;
 }
 bool UserConfiguration::addPost(Solver& s) const {
-	const SolverOpts& x = solver(s.id());
-	bool  ok            = true;
-	if (Lookahead::isType(x.lookType)) {
-		PostPropagator* pp = s.getPost(PostPropagator::priority_reserved_look);
-		if (pp) { pp->destroy(&s, true); }
-		Lookahead::Params p(static_cast(x.lookType));
-		p.nant(x.heuristic.nant != 0);
-		p.limit(x.lookOps);
-		ok = s.addPost(new Lookahead(p));
-	}
-	return ok;
+    const SolverOpts& x  = solver(s.id());
+    bool              ok = true;
+    if (Lookahead::isType(x.lookType)) {
+        if (PostPropagator* pp = s.getPost(PostPropagator::priority_reserved_look)) {
+            pp->destroy(&s, true);
+        }
+        Lookahead::Params p(static_cast(x.lookType));
+        p.nant(x.heuristic.nant != 0);
+        p.limit(x.lookOps);
+        ok = s.addPost(new Lookahead(p));
+    }
+    return ok;
 }
 
-BasicSatConfig::HeuristicCreator::~HeuristicCreator() {}
-
 BasicSatConfig::BasicSatConfig() {
-	solver_.push_back(SolverParams());
-	search_.push_back(SolveParams());
+    solver_.push_back(SolverParams());
+    search_.push_back(SolveParams());
 }
 void BasicSatConfig::prepare(SharedContext& ctx) {
-	uint32 warn = 0;
-	for (uint32 i = 0, end = solver_.size(), mod = search_.size(); i != end; ++i) {
-		warn |= solver_[i].prepare();
-		warn |= search_[i%mod].prepare(solver_[i].search != SolverStrategies::no_learning);
-		if (solver_[i].updateLbd == SolverStrategies::lbd_fixed && search_[i%mod].reduce.strategy.protect) { warn |= 8; }
-	}
-	if ((warn & 1) != 0) { ctx.warn("Selected heuristic requires lookback strategy!"); }
-	if ((warn & 2) != 0) { ctx.warn("Heuristic 'Unit' implies lookahead. Using 'atom'."); }
-	if ((warn & 4) != 0) { ctx.warn("Domain options require heuristic 'Domain'!"); }
-	if ((warn & 8) != 0) { ctx.warn("Deletion protection requires LBD updates, which are off!"); }
-}
-DecisionHeuristic* BasicSatConfig::heuristic(uint32 i)  const {
-	const SolverParams& p = BasicSatConfig::solver(i);
-	Heuristic_t::Type hId = static_cast(p.heuId);
-	if (hId == Heuristic_t::Default && p.search == SolverStrategies::use_learning) hId = Heuristic_t::Berkmin;
-	POTASSCO_REQUIRE(p.search == SolverStrategies::use_learning || !Heuristic_t::isLookback(hId), "Selected heuristic requires lookback!");
-	DecisionHeuristic* h = 0;
-	if (heu_.get()) { h = heu_->create(hId, p.heuristic); }
-	if (!h) { h = Heuristic_t::create(hId, p.heuristic); }
-	if (Lookahead::isType(p.lookType) && p.lookOps > 0 && hId != Heuristic_t::Unit) {
-		h = UnitHeuristic::restricted(h);
-	}
-	return h;
-}
-SolverParams& BasicSatConfig::addSolver(uint32 i) {
-	while (i >= solver_.size()) {
-		solver_.push_back(SolverParams().setId(static_cast(solver_.size())));
-	}
-	return solver_[i];
-}
-SolveParams& BasicSatConfig::addSearch(uint32 i) {
-	if (i >= search_.size()) { search_.resize(i+1); }
-	return search_[i];
+    uint32_t warn = 0;
+    for (uint32_t i = 0, end = size32(solver_), mod = size32(search_); i != end; ++i) {
+        warn |= solver_[i].prepare();
+        warn |= search_[i % mod].prepare(solver_[i].search != SolverStrategies::no_learning);
+        if (solver_[i].updateLbd == SolverStrategies::lbd_fixed && search_[i % mod].reduce.strategy.protect) {
+            warn |= 8;
+        }
+    }
+    if ((warn & 1) != 0) {
+        ctx.warn("Selected heuristic requires lookback strategy!");
+    }
+    if ((warn & 2) != 0) {
+        ctx.warn("Heuristic 'Unit' implies lookahead. Using 'atom'.");
+    }
+    if ((warn & 4) != 0) {
+        ctx.warn("Domain options require heuristic 'Domain'!");
+    }
+    if ((warn & 8) != 0) {
+        ctx.warn("Deletion protection requires LBD updates, which are off!");
+    }
+}
+DecisionHeuristic* BasicSatConfig::heuristic(uint32_t i) const {
+    const SolverParams& p   = BasicSatConfig::solver(i);
+    auto                hId = static_cast(p.heuId);
+    if (hId == HeuristicType::def && p.search == SolverStrategies::use_learning) {
+        hId = HeuristicType::berkmin;
+    }
+    POTASSCO_CHECK_PRE(p.search == SolverStrategies::use_learning || not isLookbackHeuristic(hId),
+                       "Selected heuristic requires lookback!");
+    DecisionHeuristic* h = nullptr;
+    if (heu_) {
+        h = heu_(hId, p.heuristic);
+    }
+    if (not h) {
+        h = createHeuristic(hId, p.heuristic);
+    }
+    if (Lookahead::isType(p.lookType) && p.lookOps > 0 && hId != HeuristicType::unit) {
+        h = UnitHeuristic::restricted(h);
+    }
+    return h;
+}
+SolverParams& BasicSatConfig::addSolver(uint32_t i) {
+    while (i >= solver_.size()) { solver_.push_back(SolverParams().setId(size32(solver_))); }
+    return solver_[i];
+}
+SolveParams& BasicSatConfig::addSearch(uint32_t i) {
+    if (i >= search_.size()) {
+        search_.resize(i + 1);
+    }
+    return search_[i];
 }
 
 void BasicSatConfig::reset() {
-	static_cast(*this) = ContextParams();
-	BasicSatConfig::resize(1, 1);
-	solver_[0] = SolverParams();
-	search_[0] = SolveParams();
-}
-void BasicSatConfig::resize(uint32 solver, uint32 search) {
-	solver_.resize(solver);
-	search_.resize(search);
-}
-void BasicSatConfig::setHeuristicCreator(HeuristicCreator* hc, Ownership_t::Type owner) {
-	HeuFactory(hc, owner).swap(heu_);
-}
-///////////////////////////////////////////////////////////////////////////////
-// SearchLimits
-///////////////////////////////////////////////////////////////////////////////
-SearchLimits::SearchLimits() {
-	std::memset(this, 0, sizeof(SearchLimits));
-	restart.conflicts = UINT64_MAX;
-	conflicts = UINT64_MAX;
-	memory = UINT64_MAX;
-	learnts = UINT32_MAX;
+    static_cast(*this) = ContextParams();
+    BasicSatConfig::resize(1, 1);
+    solver_[0] = SolverParams();
+    search_[0] = SolveParams();
+}
+void BasicSatConfig::resize(uint32_t solver, uint32_t search) {
+    solver_.resize(solver);
+    search_.resize(search);
 }
+void BasicSatConfig::setHeuristicCreator(HeuristicCreator hc) { heu_ = std::move(hc); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // Heuristics
 /////////////////////////////////////////////////////////////////////////////////////////
-DecisionHeuristic* Heuristic_t::create(Type id, const HeuParams& p) {
-	if (id == Berkmin) { return new ClaspBerkmin(p); }
-	if (id == Vmtf)    { return new ClaspVmtf(p); }
-	if (id == Unit)    { return new UnitHeuristic(); }
-	if (id == Vsids)   { return new ClaspVsids(p); }
-	if (id == Domain)  { return new DomainHeuristic(p); }
-	POTASSCO_REQUIRE(id == Default || id == None, "Unknown heuristic id!");
-	return new SelectFirst();
+DecisionHeuristic* createHeuristic(HeuristicType type, const HeuParams& p) {
+    switch (type) {
+        case HeuristicType::berkmin: return new ClaspBerkmin(p);
+        case HeuristicType::vsids  : return new ClaspVsids(p);
+        case HeuristicType::vmtf   : return new ClaspVmtf(p);
+        case HeuristicType::domain : return new DomainHeuristic(p);
+        case HeuristicType::unit   : return new UnitHeuristic();
+        default:
+            POTASSCO_CHECK_PRE(type == HeuristicType::def || type == HeuristicType::none, "Unknown heuristic id!");
+            return new SelectFirst();
+    }
 }
 
-ModelHandler::~ModelHandler() {}
+ModelHandler::~ModelHandler() = default;
 bool ModelHandler::onUnsat(const Solver&, const Model&) { return true; }
-}
+} // namespace Clasp
diff --git a/src/solver_types.cpp b/src/solver_types.cpp
index 887f9af..4b92e28 100644
--- a/src/solver_types.cpp
+++ b/src/solver_types.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,9 +22,12 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
-#include 
 #include 
+
+#include 
+
 #include 
 #if defined(__GNUC__) && __GNUC__ >= 8
 #pragma GCC diagnostic ignored "-Wclass-memaccess"
@@ -36,23 +39,30 @@ namespace Clasp {
 #define NO_ARG
 #define CLASP_STAT_ACCU(m, k, a, accu) accu;
 #define CLASP_STAT_KEY(m, k, a, accu)  k,
-#define CLASP_STAT_GET(m, k, a, accu) if (std::strcmp(key, k) == 0) { return a; }
-#define CLASP_DEFINE_ISTATS_COMMON(T, STATS, name) \
-	static const char* const T ## _s[] = { STATS(CLASP_STAT_KEY, NO_ARG, NO_ARG) name };\
-	uint32 T::size()                     { return (sizeof(T ## _s)/sizeof(T ## _s[0]))-1; } \
-	const char* T::key(uint32 i)         { POTASSCO_CHECK(i < size(), ERANGE); return T ## _s[i]; } \
-	void T::accu(const T& o)             { STATS(CLASP_STAT_ACCU, (*this), o);}
+#define CLASP_STAT_GET(m, k, a, accu)                                                                                  \
+    if (std::strcmp(key, k) == 0) {                                                                                    \
+        return a;                                                                                                      \
+    }
+// NOLINTBEGIN(*-macro-parentheses)
+#define CLASP_DEFINE_ISTATS_COMMON(T, STATS, name)                                                                     \
+    static constexpr const char* const T##_s[] = {STATS(CLASP_STAT_KEY, NO_ARG, NO_ARG) name};                         \
+    uint32_t                           T::size() { return (sizeof(T##_s) / sizeof(T##_s[0])) - 1; }                    \
+    const char*                        T::key(uint32_t i) {                                                            \
+        POTASSCO_CHECK(i < size(), ERANGE);                                                     \
+        return T##_s[i];                                                                        \
+    }                                                                                                                  \
+    void T::accu(const T& o) { STATS(CLASP_STAT_ACCU, (*this), o); }
+// NOLINTEND(*-macro-parentheses)
 /////////////////////////////////////////////////////////////////////////////////////////
 // CoreStats
 /////////////////////////////////////////////////////////////////////////////////////////
 CLASP_DEFINE_ISTATS_COMMON(CoreStats, CLASP_CORE_STATS, "core")
 StatisticObject CoreStats::at(const char* key) const {
-#define VALUE(X) StatisticObject::value(&X)
-	CLASP_CORE_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
+#define VALUE(X) StatisticObject::value(&X) // NOLINT(*-macro-parentheses)
+    CLASP_CORE_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
 #undef VALUE
-	POTASSCO_CHECK(false, ERANGE);
+    POTASSCO_CHECK(false, ERANGE);
 }
-void CoreStats::reset() { std::memset(this, 0, sizeof(*this)); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // JumpStats
 /////////////////////////////////////////////////////////////////////////////////////////
@@ -60,93 +70,91 @@ void CoreStats::reset() { std::memset(this, 0, sizeof(*this)); }
 CLASP_DEFINE_ISTATS_COMMON(JumpStats, CLASP_JUMP_STATS, "jumps")
 #undef MAX_MEM
 StatisticObject JumpStats::at(const char* key) const {
-#define VALUE(X) StatisticObject::value(&X)
-	CLASP_JUMP_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
+#define VALUE(X) StatisticObject::value(&X) // NOLINT(*-macro-parentheses)
+    CLASP_JUMP_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
 #undef VALUE
-	POTASSCO_CHECK(false, ERANGE);
+    POTASSCO_CHECK(false, ERANGE);
 }
-void JumpStats::reset() { std::memset(this, 0, sizeof(*this)); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ExtendedStats
 /////////////////////////////////////////////////////////////////////////////////////////
 CLASP_DEFINE_ISTATS_COMMON(ExtendedStats, CLASP_EXTENDED_STATS, "extra")
 namespace {
-double _lemmas(const ExtendedStats* self)     { return static_cast(self->lemmas()); }
-double _learntLits(const ExtendedStats* self) { return static_cast(self->learntLits()); }
-}
+double lemmas_thunk(const ExtendedStats* self) { return static_cast(self->lemmas()); }
+double learntLits_thunk(const ExtendedStats* self) { return static_cast(self->learntLits()); }
+} // namespace
 StatisticObject ExtendedStats::at(const char* key) const {
-#define VALUE(X) StatisticObject::value(&X)
-#define MEM_FUN(X) StatisticObject::value(this)
-#define MAP(X) StatisticObject::map(&X)
-	CLASP_EXTENDED_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
+#define VALUE(X)   StatisticObject::value(&X) // NOLINT(*-macro-parentheses)
+#define MEM_FUN(X) StatisticObject::value(this)
+#define MAP(X)     StatisticObject::map(&X) // NOLINT(*-macro-parentheses)
+    CLASP_EXTENDED_STATS(CLASP_STAT_GET, NO_ARG, NO_ARG);
 #undef VALUE
 #undef MEM_FUN
 #undef MAP
-	POTASSCO_CHECK(false, ERANGE);
-}
-void ExtendedStats::reset() {
-	std::memset(this, 0, sizeof(ExtendedStats) - sizeof(JumpStats));
-	jumps.reset();
+    POTASSCO_CHECK(false, ERANGE);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // SolverStats
 /////////////////////////////////////////////////////////////////////////////////////////
-SolverStats::SolverStats() : extra(0), multi(0) {}
-SolverStats::SolverStats(const SolverStats& o) : CoreStats(o), extra(0), multi(0) {
-	if (o.extra && enableExtended()) { extra->accu(*o.extra); }
-}
-SolverStats::~SolverStats() {
-	delete extra;
+SolverStats::SolverStats(const SolverStats& o) : CoreStats(o) {
+    if (o.extra && enableExtended()) {
+        extra->accu(*o.extra);
+    }
 }
+SolverStats::~SolverStats() { delete extra; }
 bool SolverStats::enableExtended() {
-	return extra != 0 || (extra = new (std::nothrow) ExtendedStats()) != 0;
+    return extra != nullptr || (extra = new (std::nothrow) ExtendedStats()) != nullptr;
 }
 void SolverStats::reset() {
-	CoreStats::reset();
-	if (extra) extra->reset();
+    static_cast(*this) = {};
+    if (extra) {
+        *extra = {};
+    }
 }
 void SolverStats::accu(const SolverStats& o) {
-	CoreStats::accu(o);
-	if (extra && o.extra) extra->accu(*o.extra);
+    CoreStats::accu(o);
+    if (extra && o.extra) {
+        extra->accu(*o.extra);
+    }
 }
 void SolverStats::accu(const SolverStats& o, bool enableRhs) {
-	if (enableRhs) { enable(o); }
-	accu(o);
-}
-void SolverStats::flush() const {
-	if (multi) {
-		multi->enable(*this);
-		multi->accu(*this);
-		multi->flush();
-	}
+    if (enableRhs) {
+        enable(o);
+    }
+    accu(o);
+}
+void SolverStats::flush() const { // NOLINT(*-no-recursion)
+    if (multi) {
+        multi->enable(*this);
+        multi->accu(*this);
+        multi->flush();
+    }
 }
 void SolverStats::swapStats(SolverStats& o) {
-	std::swap(static_cast(*this), static_cast(o));
-	std::swap(extra, o.extra);
-}
-uint32 SolverStats::size() const {
-	return CoreStats::size() + (extra != 0);
+    std::swap(static_cast(*this), static_cast(o));
+    std::swap(extra, o.extra);
 }
-const char* SolverStats::key(uint32 i) const {
-	POTASSCO_CHECK(i < size(), ERANGE);
-	return i < CoreStats::size() ? CoreStats::key(i) : "extra";
+uint32_t    SolverStats::size() const { return CoreStats::size() + (extra != nullptr); }
+const char* SolverStats::key(uint32_t i) const {
+    POTASSCO_CHECK(i < size(), ERANGE);
+    return i < CoreStats::size() ? CoreStats::key(i) : "extra";
 }
-template 
-static bool matchPath(const char*& path, const char (&key)[n]) {
-	std::size_t kLen = n-1;
-	return std::strncmp(path, key, kLen) == 0 && (!path[kLen] || path[kLen++] == '.') && (path += kLen) != 0;
+template 
+static bool matchPath(const char*& path, const char (&key)[N]) {
+    std::size_t kLen = N - 1;
+    return std::strncmp(path, key, kLen) == 0 && (not path[kLen] || path[kLen++] == '.') && (path += kLen) != nullptr;
 }
 StatisticObject SolverStats::at(const char* k) const {
-	if (extra && matchPath(k, "extra")) {
-		return !*k ? StatisticObject::map(extra) : extra->at(k);
-	}
-	else {
-		return CoreStats::at(k);
-	}
+    if (extra && matchPath(k, "extra")) {
+        return !*k ? StatisticObject::map(extra) : extra->at(k);
+    }
+    return CoreStats::at(k);
 }
-void SolverStats::addTo(const char* key, StatsMap& solving, StatsMap* accu) const {
-	solving.add(key, StatisticObject::map(this));
-	if (accu && this->multi) { multi->addTo(key, *accu, 0); }
+void SolverStats::addTo(const char* key, StatsMap& solving, StatsMap* accu) const { // NOLINT(*-no-recursion)
+    solving.add(key, StatisticObject::map(this));
+    if (accu && this->multi) {
+        multi->addTo(key, *accu, nullptr);
+    }
 }
 #undef NO_ARG
 #undef CLASP_STAT_ACCU
@@ -156,66 +164,63 @@ void SolverStats::addTo(const char* key, StatsMap& solving, StatsMap* accu) cons
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClauseHead
 /////////////////////////////////////////////////////////////////////////////////////////
-ClauseHead::ClauseHead(const InfoType& init) : info_(init){
-	static_assert(sizeof(ClauseHead)<=32, "Unsupported Alignment");
-	head_[2] = lit_false();
-}
-void ClauseHead::resetScore(ScoreType sc) {
-	info_.setScore(sc);
+ClauseHead::ClauseHead(const InfoType& init) : info_(init) {
+    static_assert(sizeof(ClauseHead) <= 32, "Unsupported Alignment");
+    head_[2] = lit_false;
 }
+void ClauseHead::resetScore(ScoreType sc) { info_.setScore(sc); }
 void ClauseHead::attach(Solver& s) {
-	assert(head_[0] != head_[1] && head_[1] != head_[2]);
-	s.addWatch(~head_[0], ClauseWatch(this));
-	s.addWatch(~head_[1], ClauseWatch(this));
+    assert(head_[0] != head_[1] && head_[1] != head_[2]);
+    s.addWatch(~head_[0], ClauseWatch(this));
+    s.addWatch(~head_[1], ClauseWatch(this));
 }
 
 void ClauseHead::detach(Solver& s) {
-	s.removeWatch(~head_[0], this);
-	s.removeWatch(~head_[1], this);
+    s.removeWatch(~head_[0], this);
+    s.removeWatch(~head_[1], this);
 }
 
 bool ClauseHead::locked(const Solver& s) const {
-	return (s.isTrue(head_[0]) && s.reason(head_[0]) == this)
-	  ||   (s.isTrue(head_[1]) && s.reason(head_[1]) == this);
+    return (s.isTrue(head_[0]) && s.reason(head_[0]) == this) || (s.isTrue(head_[1]) && s.reason(head_[1]) == this);
 }
 
 bool ClauseHead::satisfied(const Solver& s) const {
-	return s.isTrue(head_[0]) || s.isTrue(head_[1]) || s.isTrue(head_[2]);
+    return s.isTrue(head_[0]) || s.isTrue(head_[1]) || s.isTrue(head_[2]);
 }
 
 bool ClauseHead::toImplication(Solver& s) {
-	uint32 sz     = isSentinel(head_[1]) ? 1 : 2 + (!s.isFalse(head_[2]) || s.level(head_[2].var()) > 0);
-	ClauseRep rep = ClauseRep::create(head_, sz, InfoType(ClauseHead::type()).setLbd(2).setTagged(tagged()));
-	bool implicit = s.allowImplicit(rep);
-	bool locked   = ClauseHead::locked(s) && s.decisionLevel() > 0;
-	rep.prep      = 1;
-	if ((locked || !implicit) && sz > 1) { return false; }
-	s.add(rep, false);
-	detach(s);
-	return true;
+    uint32_t  sz       = isSentinel(head_[1]) ? 1 : 2 + (not s.isFalse(head_[2]) || s.level(head_[2].var()) > 0);
+    ClauseRep rep      = ClauseRep::create({head_, sz}, InfoType(ClauseHead::type()).setLbd(2).setTagged(tagged()));
+    bool      implicit = s.allowImplicit(rep);
+    bool      locked   = ClauseHead::locked(s) && s.decisionLevel() > 0;
+    if ((locked || not implicit) && sz > 1) {
+        return false;
+    }
+    rep.prep = 1;
+    s.add(rep, false);
+    detach(s);
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // SmallClauseAlloc
 /////////////////////////////////////////////////////////////////////////////////////////
-SmallClauseAlloc::SmallClauseAlloc() : blocks_(0), freeList_(0) { }
+SmallClauseAlloc::SmallClauseAlloc() : blocks_(nullptr), freeList_(nullptr) {}
 SmallClauseAlloc::~SmallClauseAlloc() {
-	Block* r = blocks_;
-	while (r) {
-		Block* t = r;
-		r = r->next;
-		::operator delete(t);
-	}
+    Block* r = blocks_;
+    while (r) {
+        Block* t = r;
+        r        = r->next;
+        ::operator delete(t);
+    }
 }
 
 void SmallClauseAlloc::allocBlock() {
-	Block* r = (Block*)::operator new(sizeof(Block));
-	for (uint32 i = 0; i < Block::num_chunks-1; ++i) {
-		r->chunk[i].next = &r->chunk[i+1];
-	}
-	r->chunk[Block::num_chunks-1].next = freeList_;
-	freeList_ = r->chunk;
-	r->next   = blocks_;
-	blocks_   = r;
+    auto* r = static_cast(::operator new(sizeof(Block)));
+    for (uint32_t i = 0; i < Block::num_chunks - 1; ++i) { r->chunk[i].next = &r->chunk[i + 1]; }
+    r->chunk[Block::num_chunks - 1].next = freeList_;
+    freeList_                            = r->chunk;
+    r->next                              = blocks_;
+    blocks_                              = r;
 }
 
-}
+} // namespace Clasp
diff --git a/src/statistics.cpp b/src/statistics.cpp
index 577a80f..bdd9bbc 100644
--- a/src/statistics.cpp
+++ b/src/statistics.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2016-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,387 +22,359 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
-#include 
-#include 
-#include 
-#include POTASSCO_EXT_INCLUDE(unordered_map)
-#include POTASSCO_EXT_INCLUDE(unordered_set)
-#include 
+
+#include 
+
+#include 
 #include 
-#ifdef _MSC_VER
-#pragma warning (disable : 4996)
-#endif
+#include 
+#include 
+
 namespace Clasp {
 /////////////////////////////////////////////////////////////////////////////////////////
 // StatisticObject
 /////////////////////////////////////////////////////////////////////////////////////////
-StatisticObject::I      StatisticObject::empty_s(Potassco::Statistics_t::Empty);
-StatisticObject::RegVec StatisticObject::types_s(1, &StatisticObject::empty_s);
+StatisticObject::V      StatisticObject::s_empty_(+[](const void*) { return 0.0; });
+StatisticObject::RegVec StatisticObject::s_types_(1, &StatisticObject::s_empty_);
 
 StatisticObject::StatisticObject() : handle_(0) {}
-StatisticObject::StatisticObject(const void* obj, uint32 type) {
-	handle_  = static_cast(type) << 48;
-	handle_ |= static_cast(reinterpret_cast(obj));
+StatisticObject::StatisticObject(uint64_t h) : handle_(h) {}
+StatisticObject::StatisticObject(const void* obj, uint32_t type) {
+    handle_  = static_cast(type) << 48;
+    handle_ |= static_cast(reinterpret_cast(obj));
 }
 const void* StatisticObject::self() const {
-	static const uint64 ptrMask = bit_mask(48) - 1;
-	return reinterpret_cast(static_cast(handle_ & ptrMask));
-}
-std::size_t StatisticObject::typeId() const {
-	return static_cast(handle_ >> 48);
-}
-const StatisticObject::I* StatisticObject::tid() const {
-	return types_s.at(static_cast(handle_ >> 48));
-}
-StatisticObject::Type StatisticObject::type() const {
-	return !empty() ? tid()->type : Potassco::Statistics_t::Empty;
-}
-bool StatisticObject::empty() const {
-	return handle_ == 0;
-}
-uint32 StatisticObject::size() const {
-	switch (type()) {
-		default: POTASSCO_REQUIRE(false, "invalid object");
-		case Potassco::Statistics_t::Empty: return 0;
-		case Potassco::Statistics_t::Value: return 0;
-		case Potassco::Statistics_t::Map:   return static_cast(tid())->size(self());
-		case Potassco::Statistics_t::Array: return static_cast(tid())->size(self());
-	}
-}
-const char* StatisticObject::key(uint32 i) const {
-	POTASSCO_REQUIRE(type() == Potassco::Statistics_t::Map, "type error");
-	return static_cast(tid())->key(self(), i);
+    static constexpr auto ptr_mask = Potassco::bit_max(48);
+    return reinterpret_cast(static_cast(handle_ & ptr_mask));
+}
+std::size_t StatisticObject::typeId() const { return static_cast(handle_ >> 48); }
+auto        StatisticObject::tid() const -> const I* { return s_types_.at(static_cast(handle_ >> 48)); }
+auto        StatisticObject::type() const -> Type { return handle_ ? tid()->type : Type::value; }
+uint32_t    StatisticObject::size() const {
+    switch (type()) {
+        default         : POTASSCO_ASSERT_NOT_REACHED("invalid object");
+        case Type::value: return 0;
+        case Type::array: return tid()->as()->size(self());
+        case Type::map  : return tid()->as()->size(self());
+    }
+}
+const char* StatisticObject::key(uint32_t i) const {
+    POTASSCO_CHECK_PRE(type() == Type::map, "type error");
+    return tid()->as()->key(self(), i);
 }
 StatisticObject StatisticObject::at(const char* k) const {
-	POTASSCO_REQUIRE(type() == Potassco::Statistics_t::Map, "type error");
-	return static_cast(tid())->at(self(), k);
+    POTASSCO_CHECK_PRE(type() == Type::map, "type error");
+    return tid()->as()->at(self(), k);
 }
-StatisticObject StatisticObject::operator[](uint32 i) const {
-	POTASSCO_REQUIRE(type() == Potassco::Statistics_t::Array, "type error");
-	return static_cast(tid())->at(self(), i);
+StatisticObject StatisticObject::operator[](uint32_t i) const {
+    POTASSCO_CHECK_PRE(type() == Type::array, "type error");
+    return tid()->as()->at(self(), i);
 }
 double StatisticObject::value() const {
-	POTASSCO_REQUIRE(type() == Potassco::Statistics_t::Value, "type error");
-	return static_cast(tid())->value(self());
-}
-std::size_t StatisticObject::hash() const {
-	return POTASSCO_EXT_NS::hash()(toRep());
-}
-uint64 StatisticObject::toRep() const {
-	return handle_;
+    POTASSCO_CHECK_PRE(type() == Type::value, "type error");
+    return tid()->as()->value(self());
 }
-StatisticObject StatisticObject::fromRep(uint64 x) {
-	if (!x) { return StatisticObject(0, 0); }
-	StatisticObject r;
-	r.handle_ = x;
-	POTASSCO_REQUIRE(r.tid() != 0 && (reinterpret_cast(r.self()) & 3u) == 0, "invalid key");
-	return r;
+std::size_t     StatisticObject::hash() const { return std::hash()(toRep()); }
+uint64_t        StatisticObject::toRep() const { return handle_; }
+StatisticObject StatisticObject::fromRep(uint64_t x) {
+    StatisticObject r(x);
+    POTASSCO_CHECK_PRE(r.tid() != nullptr && not Potassco::test_any(reinterpret_cast(r.self()), 3u),
+                       "invalid key");
+    return r;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // StatsMap
 /////////////////////////////////////////////////////////////////////////////////////////
 StatisticObject StatsMap::at(const char* k) const {
-	if (const StatisticObject* o = find(k)) { return *o; }
-	POTASSCO_CHECK(false, ERANGE, "StatsMap::at with key '%s'", k);
+    if (const auto* o = find(k)) {
+        return *o;
+    }
+    POTASSCO_CHECK(false, ERANGE, "StatsMap::at with key '%s'", k);
 }
 const StatisticObject* StatsMap::find(const char* k) const {
-	for (MapType::const_iterator it = keys_.begin(), end = keys_.end(); it != end; ++it) {
-		if (std::strcmp(it->first, k) == 0) { return &it->second; }
-	}
-	return 0;
+    auto it = std::ranges::find_if(keys_, [k](const auto& st) { return std::strcmp(st.first, k) == 0; });
+    return it != keys_.end() ? &it->second : nullptr;
 }
 bool StatsMap::add(const char* k, const StatisticObject& o) {
-	return !find(k) && (keys_.push_back(MapType::value_type(k, o)), true);
-}
-void StatsMap::push(const char* k, const StatisticObject& o) {
-	keys_.push_back(MapType::value_type(k, o));
+    return not find(k) && (keys_.push_back(MapType::value_type(k, o)), true);
 }
+void StatsMap::push(const char* k, const StatisticObject& o) { keys_.push_back(MapType::value_type(k, o)); }
 /////////////////////////////////////////////////////////////////////////////////////////
 // ClaspStatistics
 /////////////////////////////////////////////////////////////////////////////////////////
 struct ClaspStatistics::Impl {
-	typedef POTASSCO_EXT_NS::unordered_set KeySet;
-	typedef POTASSCO_EXT_NS::unordered_set StringSet;
-	struct Map : Clasp::StatsMap { const static std::size_t id_s; };
-	struct Arr : PodVector::type {
-		StatisticObject toStats() const { return StatisticObject::array(this); }
-		const static std::size_t id_s;
-	};
-	struct Val {
-		Val(double d) : value(d) {}
-		operator double() const { return value;  }
-		double value;
-		const static std::size_t id_s;
-	};
+    using KeySet    = std::unordered_set;
+    using StringSet = std::unordered_set, std::equal_to<>>;
+    struct Map : StatsMap {
+        static const std::size_t id_s;
+    };
+    struct Arr : PodVector_t {
+        static const std::size_t id_s;
 
-	Impl() : root_(0), gc_(0), rem_(0) {}
+        [[nodiscard]] StatisticObject toStats() const { return StatisticObject::array(this); }
+    };
+    struct Val {
+        static const std::size_t id_s;
 
-	~Impl() {
-		for (StringSet::iterator it = strings_.begin(), end = strings_.end(); it != end; ++it) {
-			delete[] *it;
-		}
-		for (KeySet::iterator it = objects_.begin(), end = objects_.end(); it != end; ++it) {
-			destroyIfWritable(it);
-		}
-	}
+        Val(double d) : value(d) {} // NOLINT(*-explicit-constructor)
+        explicit operator double() const { return value; }
+        double   value;
+    };
 
-	Key_t add(const StatisticObject& o) {
-		return *objects_.insert(o.toRep()).first;
-	}
+    Impl() = default;
 
-	void destroyIfWritable(KeySet::iterator it) {
-		StatisticObject obj = StatisticObject::fromRep(*it);
-		size_t typeId = obj.typeId();
-		if      (typeId == Map::id_s) { delete static_cast(obj.self()); }
-		else if (typeId == Arr::id_s) { delete static_cast(obj.self()); }
-		else if (typeId == Val::id_s) { delete static_cast(obj.self()); }
-	}
-	StatisticObject newWritable(Type type) {
-		StatisticObject obj;
-		switch (type) {
-			case Potassco::Statistics_t::Value:
-				obj = StatisticObject::value(new Val(0.0));
-				break;
-			case Potassco::Statistics_t::Map:
-				obj = StatisticObject::map(new Map());
-				break;
-			case Potassco::Statistics_t::Array:
-				obj = StatisticObject::array(new Arr());
-				break;
-			default:
-				POTASSCO_REQUIRE(false, "unsupported statistic object type");
-				return obj;
-		}
-		add(obj);
-		return obj;
-	}
+    ~Impl() {
+        for (auto key : objects) { destroyIfWritable(key); }
+    }
 
-	bool writable(Key_t k) const {
-		size_t typeId = StatisticObject::fromRep(k).typeId();
-		return (typeId == Map::id_s || typeId == Arr::id_s || typeId == Val::id_s) && objects_.count(k) != 0;
-	}
+    Key_t add(const StatisticObject& o) { return *objects.insert(o.toRep()).first; }
 
-	bool remove(const StatisticObject& o) {
-		KeySet::iterator it = objects_.find(o.toRep());
-		if (it != objects_.end() && !emptyKey(*it)) {
-			destroyIfWritable(it);
-			objects_.erase(it);
-			return true;
-		}
-		return false;
-	}
+    static void destroyIfWritable(Key_t k) {
+        try {
+            StatisticObject obj = StatisticObject::fromRep(k);
+            if (size_t typeId = obj.typeId(); typeId == Map::id_s) {
+                delete static_cast(obj.self());
+            }
+            else if (typeId == Arr::id_s) {
+                delete static_cast(obj.self());
+            }
+            else if (typeId == Val::id_s) {
+                delete static_cast(obj.self());
+            }
+        }
+        catch (const std::exception&) {
+            POTASSCO_ASSERT_NOT_REACHED("unexpected exception");
+        }
+    }
+    StatisticObject newWritable(Type type) {
+        StatisticObject obj;
+        switch (type) {
+            case Type::value: obj = StatisticObject::value(new Val(0.0)); break;
+            case Type::map  : obj = StatisticObject::map(new Map()); break;
+            case Type::array: obj = StatisticObject::array(new Arr()); break;
+            default         : POTASSCO_ASSERT_NOT_REACHED("unsupported statistic object type");
+        }
+        add(obj);
+        return obj;
+    }
 
-	StatisticObject get(Key_t k) const {
-		KeySet::const_iterator it = objects_.find(k);
-		POTASSCO_REQUIRE(it != objects_.end(), "invalid key");
-		return StatisticObject::fromRep(k);
-	}
+    bool writable(Key_t k) const {
+        auto typeId = StatisticObject::fromRep(k).typeId();
+        return (typeId == Map::id_s || typeId == Arr::id_s || typeId == Val::id_s) && objects.contains(k);
+    }
 
-	template 
-	T* writable(Key_t k) const {
-		StatisticObject obj = StatisticObject::fromRep(k);
-		POTASSCO_REQUIRE(writable(k), "key not writable");
-		POTASSCO_REQUIRE(T::id_s == obj.typeId(), "type error");
-		return static_cast(const_cast(obj.self()));
-	}
+    bool remove(const StatisticObject& o) {
+        if (auto it = objects.find(o.toRep()); it != objects.end() && not emptyKey(*it)) {
+            destroyIfWritable(*it);
+            objects.erase(it);
+            return true;
+        }
+        return false;
+    }
 
-	void update(const StatisticObject& root) {
-		KeySet seen;
-		visit(root, seen);
-		if (objects_.size() != seen.size()) {
-			for (KeySet::iterator it = objects_.begin(), end = objects_.end(); it != end; ++it) {
-				if (seen.count(*it) == 0) {
-					destroyIfWritable(it);
-				}
-			}
-			objects_.swap(seen);
-		}
-	}
+    StatisticObject get(Key_t k) const {
+        POTASSCO_CHECK_PRE(objects.contains(k), "invalid key");
+        return StatisticObject::fromRep(k);
+    }
 
-	void visit(const StatisticObject& obj, KeySet& visited) {
-		KeySet::iterator it = objects_.find(obj.toRep());
-		if (it == objects_.end() || !visited.insert(*it).second) { return; }
-		switch (obj.type()) {
-			case Potassco::Statistics_t::Map:
-				for (uint32 i = 0, end = obj.size(); i != end; ++i) { visit(obj.at(obj.key(i)), visited); }
-				break;
-			case Potassco::Statistics_t::Array:
-				for (uint32 i = 0, end = obj.size(); i != end; ++i) { visit(obj[i], visited); }
-				break;
-			default: break;
-		}
-	}
+    template 
+    T* writable(Key_t k) const {
+        StatisticObject obj = StatisticObject::fromRep(k);
+        POTASSCO_CHECK_PRE(writable(k), "key not writable");
+        POTASSCO_CHECK_PRE(T::id_s == obj.typeId(), "type error");
+        return static_cast(const_cast(obj.self()));
+    }
 
-	const char* string(const char* s) {
-		StringSet::iterator it = strings_.find(s);
-		if (it != strings_.end())
-			return *it;
-		return *strings_.insert(it, std::strcpy(new char[std::strlen(s) + 1], s));
-	}
-	static Key_t key(const StatisticObject& obj) { return static_cast(obj.toRep()); }
-	static bool  emptyKey(Key_t k)               { return k == 0; }
+    void update(const StatisticObject& obj) {
+        KeySet seen;
+        visit(obj, seen);
+        if (objects.size() != seen.size()) {
+            for (auto o : objects) {
+                if (not seen.contains(o)) {
+                    destroyIfWritable(o);
+                }
+            }
+            objects.swap(seen);
+        }
+    }
 
-	KeySet    objects_;
-	StringSet strings_;
-	Key_t     root_;
-	uint32    gc_;
-	uint32    rem_;
-};
+    void visit(const StatisticObject& obj, KeySet& visited) {
+        if (auto it = objects.find(obj.toRep()); it == objects.end() || not visited.insert(*it).second) {
+            return;
+        }
+        switch (obj.type()) {
+            case Type::map:
+                for (auto i : irange(obj)) { visit(obj.at(obj.key(i)), visited); }
+                break;
+            case Type::array:
+                for (auto i : irange(obj)) { visit(obj[i], visited); }
+                break;
+            default: break;
+        }
+    }
 
-const std::size_t ClaspStatistics::Impl::Map::id_s = StatisticObject::map(static_cast(0)).typeId();
-const std::size_t ClaspStatistics::Impl::Arr::id_s = StatisticObject::array(static_cast(0)).typeId();
-const std::size_t ClaspStatistics::Impl::Val::id_s = StatisticObject::value(static_cast(0)).typeId();
+    const char* string(const char* s) {
+        auto view = std::string_view{s};
+        auto it   = strings.find(view);
+        if (it != strings.end()) {
+            return it->c_str();
+        }
+        return strings.insert(it, Potassco::ConstString(view, Potassco::ConstString::create_unique))->c_str();
+    }
+    static Key_t key(const StatisticObject& obj) { return static_cast(obj.toRep()); }
+    static bool  emptyKey(Key_t k) { return k == 0; }
 
-ClaspStatistics::ClaspStatistics() : impl_(new Impl()) {}
-ClaspStatistics::ClaspStatistics(StatisticObject obj) : impl_(new Impl()) {
-	impl_->root_ = impl_->add(obj);
-}
-ClaspStatistics::~ClaspStatistics() {
-	delete impl_;
-}
-StatisticObject ClaspStatistics::getObject(Key_t k) const {
-	return impl_->get(k);
-}
-ClaspStatistics::Key_t ClaspStatistics::root() const {
-	return impl_->root_;
-}
-Potassco::Statistics_t ClaspStatistics::type(Key_t key) const {
-	return getObject(key).type();
-}
-size_t ClaspStatistics::size(Key_t key) const {
-	return getObject(key).size();
-}
-bool ClaspStatistics::writable(Key_t key) const {
-	return impl_->writable(key);
-}
-ClaspStatistics::Key_t ClaspStatistics::at(Key_t arrK, size_t index) const {
-	return impl_->add(getObject(arrK)[toU32(index)]);
-}
-const char* ClaspStatistics::key(Key_t mapK, size_t i) const {
-	return getObject(mapK).key(toU32(i));
-}
-ClaspStatistics::Key_t ClaspStatistics::get(Key_t key, const char* path) const {
-	return impl_->add(!std::strchr(path, '.')
-		? getObject(key).at(path)
-		: findObject(key, path));
+    KeySet    objects;
+    StringSet strings;
+    Key_t     root{0};
+    uint32_t  gc{0};
+    uint32_t  rem{0};
+};
+const std::size_t ClaspStatistics::Impl::Map::id_s = StatisticObject::map(static_cast(nullptr)).typeId();
+const std::size_t ClaspStatistics::Impl::Arr::id_s = StatisticObject::array(static_cast(nullptr)).typeId();
+const std::size_t ClaspStatistics::Impl::Val::id_s = StatisticObject::value(static_cast(nullptr)).typeId();
+
+ClaspStatistics::ClaspStatistics() : impl_(std::make_unique()) {}
+ClaspStatistics::ClaspStatistics(StatisticObject root) : ClaspStatistics() { impl_->root = impl_->add(root); }
+ClaspStatistics::~ClaspStatistics() = default;
+auto   ClaspStatistics::getObject(Key_t k) const -> StatisticObject { return impl_->get(k); }
+auto   ClaspStatistics::root() const -> Key_t { return impl_->root; }
+auto   ClaspStatistics::type(Key_t key) const -> Type { return getObject(key).type(); }
+size_t ClaspStatistics::size(Key_t key) const { return getObject(key).size(); }
+bool   ClaspStatistics::writable(Key_t key) const { return impl_->writable(key); }
+auto ClaspStatistics::at(Key_t arrK, size_t index) const -> Key_t { return impl_->add(getObject(arrK)[toU32(index)]); }
+auto ClaspStatistics::key(Key_t mapK, size_t i) const -> const char* { return getObject(mapK).key(toU32(i)); }
+auto ClaspStatistics::get(Key_t mapK, const char* path) const -> Key_t {
+    return impl_->add(not std::strchr(path, '.') ? getObject(mapK).at(path) : findObject(mapK, path));
 }
 bool ClaspStatistics::find(Key_t mapK, const char* element, Key_t* outKey) const {
-	try {
-		if (!writable(mapK) || std::strchr(element, '.')) {
-			return !findObject(mapK, element, outKey).empty();
-		}
-		Impl::Map* map = impl_->writable(mapK);
-		if (const StatisticObject* obj = map->find(element)) {
-			if (outKey) { *outKey = impl_->add(*obj); }
-			return true;
-		}
-	}
-	catch (const std::exception&) {}
-	return false;
-}
-double ClaspStatistics::value(Key_t key) const {
-	return getObject(key).value();
-}
-ClaspStatistics::Key_t ClaspStatistics::changeRoot(Key_t k) {
-	Key_t old = impl_->root_;
-	impl_->root_ = impl_->add(getObject(k));
-	return old;
+    try {
+        if (not writable(mapK) || std::strchr(element, '.')) {
+            findObject(mapK, element, outKey);
+            return true;
+        }
+        auto* map = impl_->writable(mapK);
+        if (const StatisticObject* obj = map->find(element)) {
+            if (outKey) {
+                *outKey = impl_->add(*obj);
+            }
+            return true;
+        }
+    }
+    catch (const std::exception&) { // NOLINT(*-empty-catch)
+        // fallthrough
+    }
+    return false;
+}
+double ClaspStatistics::value(Key_t key) const { return getObject(key).value(); }
+auto   ClaspStatistics::changeRoot(Key_t newRoot) -> Key_t {
+    auto old    = impl_->root;
+    impl_->root = impl_->add(getObject(newRoot));
+    return old;
 }
 StatsMap* ClaspStatistics::makeRoot() {
-	Impl::Map* root = new Impl::Map();
-	impl_->root_ = impl_->add(StatisticObject::map(root));
-	return root;
+    auto* root  = new Impl::Map();
+    impl_->root = impl_->add(StatisticObject::map(root));
+    return root;
 }
 bool ClaspStatistics::removeStat(const StatisticObject& obj, bool recurse) {
-	bool ret = impl_->remove(obj);
-	if (ret && recurse) {
-		if (obj.type() == Potassco::Statistics_t::Map) {
-			for (uint32 i = 0, end = obj.size(); i != end; ++i) {
-				removeStat(obj.at(obj.key(i)), true);
-			}
-		}
-		else if (obj.type() == Potassco::Statistics_t::Array) {
-			for (uint32 i = 0, end = obj.size(); i != end; ++i) {
-				removeStat(obj[i], true);
-			}
-		}
-	}
-	return ret;
+    bool ret = impl_->remove(obj);
+    if (ret && recurse) {
+        if (obj.type() == Type::map) {
+            for (auto i : irange(obj)) { removeStat(obj.at(obj.key(i)), true); }
+        }
+        else if (obj.type() == Type::array) {
+            for (auto i : irange(obj)) { removeStat(obj[i], true); }
+        }
+    }
+    return ret;
 }
 bool ClaspStatistics::removeStat(Key_t k, bool recurse) {
-	try { return removeStat(impl_->get(k), recurse); }
-	catch (const std::logic_error&) { return false; }
+    try {
+        return removeStat(impl_->get(k), recurse);
+    }
+    catch (const std::logic_error&) {
+        return false;
+    }
 }
 void ClaspStatistics::update() {
-	if (impl_->root_) { impl_->update(getObject(impl_->root_)); }
-}
-StatisticObject ClaspStatistics::findObject(Key_t root, const char* path, Key_t* res) const {
-	StatisticObject o = getObject(root);
-	StatisticObject::Type t = o.type();
-	char temp[1024]; const char* top, *parent = path;
-	for (int pos; path && *path;) {
-		top = path;
-		if ((path = std::strchr(path, '.')) != 0) {
-			std::size_t len = static_cast(path++ - top);
-			POTASSCO_ASSERT(len < 1024, "invalid key");
-			top = (const char*)std::memcpy(temp, top, len);
-			temp[len] = 0;
-		}
-		if      (t == Potassco::Statistics_t::Map) { o = o.at(top); }
-		else if (t == Potassco::Statistics_t::Array && Potassco::match(top, pos) && pos >= 0) {
-			o = o[uint32(pos)];
-		}
-		else {
-			POTASSCO_CHECK(false, ERANGE, "invalid path: '%s' at key '%s'", parent, top);
-		}
-		t = o.type();
-	}
-	if (res) { *res = impl_->add(o); }
-	return o;
+    if (impl_->root) {
+        impl_->update(getObject(impl_->root));
+    }
 }
-ClaspStatistics::Key_t ClaspStatistics::push(Key_t key, Type type) {
-	Impl::Arr* arr = impl_->writable(key);
-	StatisticObject obj = impl_->newWritable(type);
-	arr->push_back(obj);
-	return impl_->key(obj);
+
+static bool getIndex(const char* top, uint32_t& idx) {
+    idx = 0;
+    while (std::isdigit(static_cast(*top))) {
+        idx *= 10;
+        idx += static_cast(*top++ - '0');
+    }
+    return not *top;
 }
-ClaspStatistics::Key_t ClaspStatistics::add(Key_t mapK, const char* name, Type type) {
-	Impl::Map* map = impl_->writable(mapK);
-	if (const StatisticObject* stat = map->find(name)) {
-		POTASSCO_REQUIRE(stat->type() == type, "redefinition error");
-		return impl_->key(*stat);
-	}
-	StatisticObject stat = impl_->newWritable(type);
-	map->push(impl_->string(name), stat);
-	return impl_->key(stat);
+
+StatisticObject ClaspStatistics::findObject(Key_t root, const char* path, Key_t* res) const {
+    auto o = getObject(root);
+    auto t = o.type();
+    char temp[1024];
+    for (const char* parent = path; path && *path;) {
+        const char* top = path;
+        if (path = std::strchr(path, '.'); path != nullptr) {
+            auto len = static_cast(path++ - top);
+            POTASSCO_ASSERT(len < 1024, "invalid key");
+            top       = static_cast(std::memcpy(temp, top, len));
+            temp[len] = 0;
+        }
+        if (t == Type::map) {
+            o = o.at(top);
+        }
+        else if (uint32_t pos; t == Type::array && getIndex(top, pos)) {
+            o = o[pos];
+        }
+        else {
+            POTASSCO_CHECK(false, ERANGE, "invalid path: '%s' at key '%s'", parent, top);
+        }
+        t = o.type();
+    }
+    POTASSCO_ASSERT(o.toRep() != 0);
+    if (res) {
+        *res = impl_->add(o);
+    }
+    return o;
+}
+auto ClaspStatistics::push(Key_t key, Type type) -> Key_t {
+    auto* arr = impl_->writable(key);
+    auto  obj = impl_->newWritable(type);
+    arr->push_back(obj);
+    return Impl::key(obj);
+}
+auto ClaspStatistics::add(Key_t mapK, const char* name, Type type) -> Key_t {
+    auto* map = impl_->writable(mapK);
+    if (const StatisticObject* stat = map->find(name)) {
+        POTASSCO_CHECK_PRE(stat->type() == type, "redefinition error");
+        return Impl::key(*stat);
+    }
+    auto stat = impl_->newWritable(type);
+    map->push(impl_->string(name), stat);
+    return Impl::key(stat);
 }
 void ClaspStatistics::set(Key_t key, double value) {
-	Impl::Val* val = impl_->writable(key);
-	*val = value;
+    auto* val = impl_->writable(key);
+    *val      = value;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // StatsVisitor
 /////////////////////////////////////////////////////////////////////////////////////////
-StatsVisitor::~StatsVisitor() {}
-bool StatsVisitor::visitGenerator(Operation) {
-	return true;
-}
-bool StatsVisitor::visitThreads(Operation) {
-	return true;
-}
-bool StatsVisitor::visitTester(Operation) {
-	return true;
-}
-bool StatsVisitor::visitHccs(Operation) {
-	return true;
-}
-void StatsVisitor::visitHcc(uint32, const ProblemStats& p, const SolverStats& s) {
-	visitProblemStats(p);
-	visitSolverStats(s);
-}
-void StatsVisitor::visitThread(uint32, const SolverStats& stats) {
-	visitSolverStats(stats);
-}
+StatsVisitor::~StatsVisitor() = default;
+bool StatsVisitor::visitGenerator(Operation) { return true; }
+bool StatsVisitor::visitThreads(Operation) { return true; }
+bool StatsVisitor::visitTester(Operation) { return true; }
+bool StatsVisitor::visitHccs(Operation) { return true; }
+void StatsVisitor::visitHcc(uint32_t, const ProblemStats& p, const SolverStats& s) {
+    visitProblemStats(p);
+    visitSolverStats(s);
+}
+void StatsVisitor::visitThread(uint32_t, const SolverStats& stats) { visitSolverStats(stats); }
 
-}
+} // namespace Clasp
diff --git a/src/timer.cpp b/src/timer.cpp
index ce87fab..9161815 100644
--- a/src/timer.cpp
+++ b/src/timer.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -23,85 +23,84 @@
 //
 #include 
 
+#include 
+
 #ifdef _WIN32
 #define WIN32_LEAN_AND_MEAN // exclude APIs such as Cryptography, DDE, RPC, Shell, and Windows Sockets.
 #define NOMINMAX            // do not let windows.h define macros min and max
 #include         // GetProcessTimes, GetCurrentProcess, FILETIME
-#define TICKS_PER_SEC 10000000
 
 namespace Clasp {
+using DurationType = std::chrono::duration>;
+static DurationType toDuration(const FILETIME& t) {
+    union Convert {
+        FILETIME time;
+        __int64  asUint;
+    };
+    return DurationType(Convert(t).asUint);
+}
+static double toSeconds(DurationType d) { return std::chrono::duration_cast>(d).count(); }
 
 double RealTime::getTime() {
-	union Convert {
-		FILETIME time;
-		__int64  asUint;
-	} now;
-	GetSystemTimeAsFileTime(&now.time);
-	return static_cast(now.asUint/static_cast(TICKS_PER_SEC));
+    FILETIME now;
+    GetSystemTimeAsFileTime(&now);
+    return toSeconds(toDuration(now));
 }
 
 double ProcessTime::getTime() {
-	FILETIME ignoreStart, ignoreExit;
-	union Convert {
-		FILETIME time;
-		__int64  asUint;
-	} user, system;
-	GetProcessTimes(GetCurrentProcess(), &ignoreStart, &ignoreExit, &user.time, &system.time);
-	return (user.asUint + system.asUint) / double(TICKS_PER_SEC);
+    FILETIME ignoreStart, ignoreExit, user, system;
+    GetProcessTimes(GetCurrentProcess(), &ignoreStart, &ignoreExit, &user, &system);
+    return toSeconds(toDuration(user) + toDuration(system));
 }
 
 double ThreadTime::getTime() {
-	FILETIME ignoreStart, ignoreExit;
-	union Convert {
-		FILETIME time;
-		__int64  asUint;
-	} user, system;
-	GetThreadTimes(GetCurrentThread(), &ignoreStart, &ignoreExit, &user.time, &system.time);
-	return (user.asUint + system.asUint) / double(TICKS_PER_SEC);
+    FILETIME ignoreStart, ignoreExit, user, system;
+    GetThreadTimes(GetCurrentThread(), &ignoreStart, &ignoreExit, &user, &system);
+    return toSeconds(toDuration(user) + toDuration(system));
 }
 
-}
+} // namespace Clasp
 #else
-#include    // times()
-#include     // gettimeofday()
-#include // getrusage
-#include 
+#include  // getrusage
+#include      // gettimeofday()
 #ifdef __APPLE__
 #include 
 #include 
 #endif
 namespace Clasp {
 
+template 
+static double toSeconds(T seconds, U micros) {
+    using S = std::chrono::duration;
+    return (S(seconds) + std::chrono::duration_cast(std::chrono::microseconds(micros))).count();
+}
+
+static double toSeconds(const timeval& t) { return toSeconds(t.tv_sec, t.tv_usec); }
+
 double RealTime::getTime() {
-	struct timeval now;
-	return gettimeofday(&now, 0) == 0
-		? static_cast(now.tv_sec) + static_cast(now.tv_usec / 1000000.0)
-		: 0.0;
+    struct timeval now = {};
+    return gettimeofday(&now, nullptr) == 0 ? toSeconds(now) : 0.0;
 }
 inline double rusageTime(int who) {
-	struct rusage usage;
-	getrusage(who, &usage);
-	return(static_cast(usage.ru_utime.tv_sec) + static_cast(usage.ru_utime.tv_usec / 1000000.0))
-		+ (static_cast(usage.ru_stime.tv_sec) + static_cast(usage.ru_stime.tv_usec / 1000000.0));
-}
-double ProcessTime::getTime() {
-	return rusageTime(RUSAGE_SELF);
+    struct rusage usage = {};
+    getrusage(who, &usage);
+    return toSeconds(usage.ru_utime) + toSeconds(usage.ru_stime);
 }
+double ProcessTime::getTime() { return rusageTime(RUSAGE_SELF); }
 double ThreadTime::getTime() {
-	double res = 0.0;
+    double res = 0.0;
 #if defined(RUSAGE_THREAD)
-	res = rusageTime(RUSAGE_THREAD);
+    res = rusageTime(RUSAGE_THREAD);
 #elif __APPLE__
-	struct thread_basic_info t_info;
-	mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
-	if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t)&t_info, &t_info_count) == KERN_SUCCESS) {
-		time_value_add(&t_info.user_time, &t_info.system_time);
-		res = static_cast(t_info.user_time.seconds) + static_cast(t_info.user_time.microseconds / static_cast(TIME_MICROS_MAX));
-	}
+    struct thread_basic_info t_info;
+    mach_msg_type_number_t   t_info_count = TASK_BASIC_INFO_COUNT;
+    if (thread_info(mach_thread_self(), THREAD_BASIC_INFO, (thread_info_t) &t_info, &t_info_count) == KERN_SUCCESS) {
+        time_value_add(&t_info.user_time, &t_info.system_time);
+        res = toSeconds(t_info.user_time.seconds, t_info.user_time.microseconds);
+    }
 #endif
-	return res;
+    return res;
 }
 
-}
+} // namespace Clasp
 #endif
-
diff --git a/src/unfounded_check.cpp b/src/unfounded_check.cpp
index 8a3ce51..f87b20b 100644
--- a/src/unfounded_check.cpp
+++ b/src/unfounded_check.cpp
@@ -1,7 +1,7 @@
 //
 // Copyright (c) 2010-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -22,7 +22,9 @@
 // IN THE SOFTWARE.
 //
 #include 
+
 #include 
+
 #include 
 #include 
 namespace Clasp {
@@ -70,386 +72,414 @@ namespace Clasp {
 // - If the condition cannot be restored, the body is marked as invalid source.
 
 DefaultUnfoundedCheck::DefaultUnfoundedCheck(DependencyGraph& g, ReasonStrategy st)
-	: solver_(0)
-	, graph_(&g)
-	, mini_(0)
-	, reasons_(0)
-	, strategy_(st) {
-	mini_.release();
-}
+    : solver_(nullptr)
+    , graph_(&g)
+    , mini_(nullptr)
+    , reasons_(nullptr)
+    , strategy_(st) {}
 DefaultUnfoundedCheck::~DefaultUnfoundedCheck() {
-	for (ExtVec::size_type i = 0; i != extended_.size(); ++i) {
-		::operator delete(extended_[i]);
-	}
-	delete [] reasons_;
+    for (auto* ext : extended_) { ::operator delete(ext); }
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultUnfoundedCheck - Initialization
 /////////////////////////////////////////////////////////////////////////////////////////
-void DefaultUnfoundedCheck::addWatch(Literal p, uint32 data, WatchType type) {
-	assert(data < varMax);
-	solver_->addWatch(p, this, static_cast((data << 2) | type));
+void DefaultUnfoundedCheck::addWatch(Literal p, uint32_t data, WatchType type) {
+    assert(data < var_max);
+    solver_->addWatch(p, this, (data << 2) | type);
 }
 
 void DefaultUnfoundedCheck::setReasonStrategy(ReasonStrategy rs) {
-	strategy_ = rs;
-	if (strategy_ == only_reason && solver_ && !reasons_) {
-		reasons_ = new LitVec[solver_->numVars()];
-	}
+    strategy_ = rs;
+    if (strategy_ == only_reason && solver_ && not reasons_) {
+        reasons_ = std::make_unique(solver_->numVars());
+    }
 }
 // inits unfounded set checker with graph, i.e.
 // - creates data objects for bodies and atoms
 // - adds necessary watches to the solver
 // - initializes source pointers
 bool DefaultUnfoundedCheck::init(Solver& s) {
-	assert(!solver_ || solver_ == &s);
-	delete[] reasons_;
-	reasons_ = 0;
-	solver_  = &s;
-	setReasonStrategy(s.searchMode() != SolverStrategies::no_learning ? strategy_ : no_reason);
-	// process any leftovers from previous steps
-	while (findUfs(s, false) != ufs_none) {
-		while (!ufs_.empty()) {
-			if (!s.force(~graph_->getAtom(ufs_.front()).lit, 0)) { return false; }
-			atoms_[ufs_.pop_ret()].ufs = 0;
-		}
-	}
-	AtomVec::size_type startAtom = atoms_.size();
-	// set up new atoms
-	atoms_.resize(graph_->numAtoms());
-	AtomData& sentinel = atoms_[DependencyGraph::sentinel_atom];
-	sentinel.resurrectSource();
-	sentinel.todo = 1;
-	sentinel.ufs  = 1;
-	// set up new bodies
-	for (uint32 i = (uint32)bodies_.size(); i != graph_->numBodies(); ++i) {
-		bodies_.push_back(BodyData());
-		BodyPtr n(getBody(i));
-		if (!n.node->extended()) {
-			initBody(n);
-		}
-		else {
-			initExtBody(n);
-		}
-		// when a body becomes false, it can no longer be used as source
-		addWatch(~n.node->lit, n.id, watch_source_false);
-	}
-	// check for initially unfounded atoms
-	propagateSource();
-	for (AtomVec::size_type i = startAtom, end = atoms_.size(); i != end; ++i) {
-		const AtomNode& a = graph_->getAtom(NodeId(i));
-		if (!atoms_[i].hasSource() && !solver_->force(~a.lit, 0)) {
-			return false;
-		}
-		if (a.inChoice()) {
-			addWatch(~a.lit, NodeId(i), watch_head_false);
-		}
-	}
-	if (graph_->numNonHcfs() != 0) {
-		mini_ = new MinimalityCheck(s.searchConfig().fwdCheck);
-		if (const uint32 sd = mini_->fwd.signDef) {
-			for (AtomVec::size_type i = startAtom, end = atoms_.size(); i != end; ++i) {
-				const AtomNode& a = graph_->getAtom(NodeId(i));
-				if (a.inDisjunctive() && solver_->value(a.lit.var()) == value_free) {
-					ValueRep v = falseValue(a.lit);
-					if (sd == SolverStrategies::sign_pos || (sd == SolverStrategies::sign_rnd && (i & 1) != 0)) {
-						v ^= static_cast(3u);
-					}
-					solver_->setPref(a.lit.var(), ValueSet::def_value, v);
-				}
-			}
-		}
-	}
-	return true;
+    assert(not solver_ || solver_ == &s);
+    reasons_.reset();
+    solver_ = &s;
+    setReasonStrategy(s.searchMode() != SolverStrategies::no_learning ? strategy_ : no_reason);
+    // process any leftovers from previous steps
+    while (findUfs(s, false) != ufs_none) {
+        while (not ufs_.empty()) {
+            if (not s.force(~graph_->getAtom(ufs_.front()).lit, nullptr)) {
+                return false;
+            }
+            atoms_[ufs_.pop_ret()].ufs = 0;
+        }
+    }
+    auto startAtom = size32(atoms_);
+    // set up new atoms
+    atoms_.resize(graph_->numAtoms());
+    AtomData& sentinel = atoms_[DependencyGraph::sentinel_atom];
+    sentinel.resurrectSource();
+    sentinel.todo = 1;
+    sentinel.ufs  = 1;
+    // set up new bodies
+    for (auto i : irange(size32(bodies_), graph()->numBodies())) {
+        bodies_.push_back(BodyData());
+        BodyPtr n(getBody(i));
+        if (not n.node->extended()) {
+            initBody(n);
+        }
+        else {
+            initExtBody(n);
+        }
+        // when a body becomes false, it can no longer be used as source
+        addWatch(~n.node->lit, n.id, watch_source_false);
+    }
+    // check for initially unfounded atoms
+    propagateSource();
+    for (NodeId nId : irange(startAtom, size32(atoms_))) {
+        const AtomNode& a = graph_->getAtom(nId);
+        if (not atoms_[nId].hasSource() && not solver_->force(~a.lit, nullptr)) {
+            return false;
+        }
+        if (a.inChoice()) {
+            addWatch(~a.lit, nId, watch_head_false);
+        }
+    }
+    if (graph_->numNonHcfs() != 0) {
+        mini_ = std::make_unique(s.searchConfig().fwdCheck);
+        if (const uint32_t sd = mini_->fwd.signDef) {
+            for (NodeId i : irange(startAtom, size32(atoms_))) {
+                const AtomNode& a = graph_->getAtom(i);
+                if (a.inDisjunctive() && solver_->value(a.lit.var()) == value_free) {
+                    auto p = a.lit;
+                    if (sd == SolverStrategies::sign_pos || (sd == SolverStrategies::sign_rnd && (i & 1) != 0)) {
+                        p = ~p;
+                    }
+                    solver_->setPref(a.lit.var(), ValueSet::def_value, falseValue(p));
+                }
+            }
+        }
+    }
+    return true;
 }
 
 // initializes a "normal" body, i.e. a body where lower(B) == size(B)
 void DefaultUnfoundedCheck::initBody(const BodyPtr& n) {
-	assert(n.id < bodies_.size());
-	BodyData& data = bodies_[n.id];
-	// initialize lower to the number of predecessors from same scc that currently
-	// have no source. Once lower is 0, the body can source successors in its scc
-	data.lower_or_ext  = n.node->num_preds();
-	initSuccessors(n, data.lower_or_ext);
+    assert(n.id < bodies_.size());
+    BodyData& data = bodies_[n.id];
+    // initialize lower to the number of predecessors from same scc that currently
+    // have no source. Once lower is 0, the body can source successors in its scc
+    data.lowerOrExt = n.node->countPreds();
+    initSuccessors(n, static_cast(data.lowerOrExt));
 }
 
 // initializes an "extended" body, i.e. a count/sum
 // creates & populates WS and adds watches to all subgoals
 void DefaultUnfoundedCheck::initExtBody(const BodyPtr& n) {
-	assert(n.id < bodies_.size() && n.node->extended());
-	BodyData& data = bodies_[n.id];
-	uint32 preds   = n.node->num_preds();
-	ExtData* extra = new (::operator new(sizeof(ExtData) + (ExtData::flagSize(preds)*sizeof(uint32)))) ExtData(n.node->ext_bound(), preds);
-
-	InitExtWatches addWatches = { this, &n, extra };
-	graph_->visitBodyLiterals(*n.node, addWatches);
-
-	data.lower_or_ext = (uint32)extended_.size();
-	extended_.push_back(extra);
-	initSuccessors(n, extra->lower);
+    assert(n.id < bodies_.size() && n.node->extended());
+    BodyData& data  = bodies_[n.id];
+    uint32_t  preds = n.node->countPreds();
+    uint32_t  sets  = (preds + ExtData::SetType::max_count - 1) / ExtData::SetType::max_count;
+    auto*     extra = new (::operator new(sizeof(ExtData) + (sets * sizeof(ExtData::SetType)))) ExtData();
+    extra->lower    = n.node->extBound();
+    extra->slack    = -extra->lower;
+    std::uninitialized_fill_n(extra->flags, sets, ExtData::SetType());
+
+    for (unsigned pos = 0; const auto& x : n.node->predecessors()) {
+        auto weight   = x.weight();
+        auto lit      = x.lit(*graph_);
+        extra->slack += weight;
+        addExtWatch(~lit, n, (pos << 1) + static_cast(x.ext()));
+        if (x.ext() && not solver_->isFalse(lit)) {
+            extra->addToWs(pos, weight);
+        }
+        ++pos;
+    }
+
+    data.lowerOrExt = size32(extended_);
+    extended_.push_back(extra);
+    initSuccessors(n, extra->lower);
 }
 
 // set n as source for its heads if possible and necessary
-void DefaultUnfoundedCheck::initSuccessors(const BodyPtr& n, weight_t lower) {
-	if (!solver_->isFalse(n.node->lit)) {
-		for (const NodeId* x = n.node->heads_begin(); x != n.node->heads_end(); ++x) {
-			const AtomNode& a = graph_->getAtom(*x);
-			if (a.scc != n.node->scc || lower <= 0) {
-				setSource(*x, n);
-			}
-		}
-	}
+void DefaultUnfoundedCheck::initSuccessors(const BodyPtr& n, Weight_t lower) {
+    if (not solver_->isFalse(n.node->lit)) {
+        for (auto aId : n.node->heads()) {
+            const AtomNode& a = graph_->getAtom(aId);
+            if (a.scc != n.node->scc || lower <= 0) {
+                setSource(aId, n);
+            }
+        }
+    }
 }
 
 // watches needed to implement extended rules
-void DefaultUnfoundedCheck::addExtWatch(Literal p, const BodyPtr& B, uint32 data) {
-	addWatch(p, static_cast(watches_.size()), watch_subgoal_false);
-	ExtWatch w = { B.id, data };
-	watches_.push_back(w);
+void DefaultUnfoundedCheck::addExtWatch(Literal p, const BodyPtr& n, uint32_t data) {
+    addWatch(p, size32(watches_), watch_subgoal_false);
+    ExtWatch w = {n.id, data};
+    watches_.push_back(w);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultUnfoundedCheck - Base interface
 /////////////////////////////////////////////////////////////////////////////////////////
 void DefaultUnfoundedCheck::reason(Solver&, Literal p, LitVec& r) {
-	LitVec::const_iterator it, end;
-	if (!activeClause_.empty() && activeClause_[0] == p) {
-		it  = activeClause_.begin()+1;
-		end = activeClause_.end();
-	}
-	else {
-		assert(strategy_ == only_reason && reasons_);
-		it  = reasons_[p.var()-1].begin();
-		end = reasons_[p.var()-1].end();
-	}
-	for (; it != end; ++it) r.push_back( ~*it );
+    LitView lits;
+    if (not activeClause_.empty() && activeClause_[0] == p) {
+        lits = {activeClause_.begin() + 1, activeClause_.end()};
+    }
+    else {
+        assert(strategy_ == only_reason && reasons_);
+        lits = reasons_[p.var() - 1];
+    }
+    for (auto lit : lits) { r.push_back(~lit); }
 }
 
 bool DefaultUnfoundedCheck::propagateFixpoint(Solver& s, PostPropagator* ctx) {
-	bool checkMin = ctx == 0 && mini_.get() && mini_->partialCheck(s.decisionLevel());
-	for (UfsType t; (t = findUfs(s, checkMin)) != ufs_none; ) {
-		if (!falsifyUfs(t)) {
-			resetTodo();
-			return false;
-		}
-	}
-	return true;
+    bool checkMin = ctx == nullptr && mini_.get() && mini_->partialCheck(s.decisionLevel());
+    for (UfsType t; (t = findUfs(s, checkMin)) != ufs_none;) {
+        if (not falsifyUfs(t)) {
+            resetTodo();
+            return false;
+        }
+    }
+    return true;
 }
 
 void DefaultUnfoundedCheck::reset() {
-	assert(loopAtoms_.empty() && sourceQ_.empty() && ufs_.empty() && todo_.empty());
-	// remember assignments from top-level -
-	// the reset may come from a stop request and we might
-	// want to continue later
-	if (solver_->decisionLevel()) {
-		invalidQ_.clear();
-	}
+    assert(loopAtoms_.empty() && sourceQ_.empty() && ufs_.empty() && todo_.empty());
+    // remember assignments from top-level -
+    // the reset may come from a stop request and we might
+    // want to continue later
+    if (solver_->decisionLevel()) {
+        invalid_.clear();
+    }
 }
 bool DefaultUnfoundedCheck::valid(Solver& s) {
-	if (!mini_.get() || findNonHcfUfs(s) == ufs_none) { return true; }
-	falsifyUfs(ufs_non_poly);
-	return false;
-}
-bool DefaultUnfoundedCheck::isModel(Solver& s) {
-	return DefaultUnfoundedCheck::valid(s);
+    if (not mini_.get() || findNonHcfUfs(s) == ufs_none) {
+        return true;
+    }
+    falsifyUfs(ufs_non_poly);
+    return false;
 }
+bool DefaultUnfoundedCheck::isModel(Solver& s) { return DefaultUnfoundedCheck::valid(s); }
 bool DefaultUnfoundedCheck::simplify(Solver& s, bool) {
-	graph_->simplify(s);
-	if (mini_.get()) { mini_->scc = 0; }
-	return false;
+    graph_->simplify(s);
+    if (mini_.get()) {
+        mini_->scc = 0;
+    }
+    return false;
 }
 void DefaultUnfoundedCheck::destroy(Solver* s, bool detach) {
-	if (s && detach) {
-		s->removePost(this);
-		for (uint32 i = 0; i != (uint32)bodies_.size(); ++i) {
-			BodyPtr n(getBody(i));
-			s->removeWatch(~n.node->lit, this);
-			if (n.node->extended()) {
-				RemExtWatches remW = { static_cast(this), s };
-				graph_->visitBodyLiterals(*n.node, remW);
-			}
-		}
-		for (AtomVec::size_type i = 0, end = atoms_.size(); i != end; ++i) {
-			const AtomNode& a = graph_->getAtom(NodeId(i));
-			if (a.inChoice()) { s->removeWatch(~a.lit, this); }
-		}
-	}
-	PostPropagator::destroy(s, detach);
+    if (s && detach) {
+        s->removePost(this);
+        for (uint32_t i : irange(bodies_)) {
+            BodyPtr n(getBody(i));
+            s->removeWatch(~n.node->lit, this);
+            if (n.node->extended()) {
+                for (const auto& x : n.node->predecessors()) { s->removeWatch(~x.lit(*graph_), this); }
+            }
+        }
+        for (uint32_t i : irange(atoms_)) {
+            if (const AtomNode& a = graph_->getAtom(i); a.inChoice()) {
+                s->removeWatch(~a.lit, this);
+            }
+        }
+    }
+    PostPropagator::destroy(s, detach);
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultUnfoundedCheck - source pointer propagation
 /////////////////////////////////////////////////////////////////////////////////////////
+// atom has a new source, check if successor bodies are now a valid source
+void DefaultUnfoundedCheck::addSource(const AtomNode& atom) {
+    for (const auto& x : atom.successors()) {
+        BodyData&       data = bodies_[x.id()];
+        const BodyNode& node = graph_->getBody(x.id());
+        Weight_t        bnd  = 0;
+        if (not node.extended()) { // normal body
+            bnd = static_cast(--data.lowerOrExt);
+        }
+        else { // extended body
+            auto* ext = extended_[data.lowerOrExt];
+            if (ext->lower > 0 || data.watches == 0) {
+                // currently not a source - safely add pred to our watch set
+                auto pos = x.position();
+                ext->addToWs(pos, node.predWeight(pos, false));
+            }
+            bnd = ext->lower;
+        }
+        if (bnd <= 0 && not solver_->isFalse(node.lit)) {
+            forwardSource({&node, x.id()});
+        }
+    }
+}
+// atom lost its source, check if successor bodies are still a valid source
+void DefaultUnfoundedCheck::removeSource(const AtomNode& atom, bool addTodo) {
+    for (const auto& x : atom.successors()) {
+        BodyData&       data = bodies_[x.id()];
+        const BodyNode& node = graph_->getBody(x.id());
+        assert(x.normal() == not node.extended());
+        if (x.normal()) { // normal body
+            if (++data.lowerOrExt == 1 && data.watches != 0) {
+                forwardUnsource({&node, x.id()}, addTodo);
+            }
+        }
+        else { // extended body
+            auto* ext = extended_[data.lowerOrExt];
+            auto  pos = x.position();
+            ext->removeFromWs(pos, node.predWeight(pos, false));
+            if (ext->lower > 0 && data.watches != 0) {
+                // extended bodies don't always become false if a predecessor becomes false
+                // eagerly enqueue all successors watching this body
+                forwardUnsource({&node, x.id()}, true);
+            }
+        }
+    }
+}
+
 // propagates recently set source pointers within one strong component.
 void DefaultUnfoundedCheck::propagateSource() {
-	for (LitVec::size_type i = 0; i < sourceQ_.size(); ++i) {
-		NodeId atom = sourceQ_[i];
-		if (atoms_[atom].hasSource()) {
-			// propagate a newly added source-pointer
-			graph_->getAtom(atom).visitSuccessors(AddSource(this));
-		}
-		else {
-			graph_->getAtom(atom).visitSuccessors(RemoveSource(this));
-		}
-	}
-	sourceQ_.clear();
+    while (not sourceQ_.empty()) {
+        if (auto atom = sourceQ_.pop_ret(); atoms_[atom].hasSource()) {
+            // propagate a newly added source-pointer
+            addSource(graph_->getAtom(atom));
+        }
+        else {
+            removeSource(graph_->getAtom(atom), false);
+        }
+    }
+    sourceQ_.clear();
 }
 
 // replaces current source of atom with n
 void DefaultUnfoundedCheck::updateSource(AtomData& atom, const BodyPtr& n) {
-	if (atom.watch() != AtomData::nill_source) {
-		--bodies_[atom.watch()].watches;
-	}
-	atom.setSource(n.id);
-	++bodies_[n.id].watches;
-}
-
-// an atom in extended body n has a new source, check if n is now a valid source
-void DefaultUnfoundedCheck::AddSource::operator()(NodeId bodyId, uint32 idx) const {
-	BodyPtr n(self->getBody(bodyId));
-	ExtData* ext = self->extended_[self->bodies_[bodyId].lower_or_ext];
-	if (ext->lower > 0 || self->bodies_[n.id].watches == 0) {
-		// currently not a source - safely add pred to our watch set
-		ext->addToWs(idx, n.node->pred_weight(idx, false));
-	}
-	if (!self->solver_->isFalse(n.node->lit) && ext->lower <= 0) {
-		// valid source - propagate to heads
-		self->forwardSource(n);
-	}
-}
-// an atom in extended body n has lost its source, check if n is no longer a valid source
-void DefaultUnfoundedCheck::RemoveSource::operator()(NodeId bodyId, uint32 idx) const {
-	BodyPtr n(self->getBody(bodyId));
-	ExtData* ext = self->extended_[self->bodies_[bodyId].lower_or_ext];
-	ext->removeFromWs(idx, n.node->pred_weight(idx, false));
-	if (ext->lower > 0 && self->bodies_[n.id].watches > 0) {
-		// extended bodies don't always become false if a predecessor becomes false
-		// eagerly enqueue all successors watching this body
-		self->forwardUnsource(n, true);
-	}
+    if (atom.watch() != AtomData::nil_source) {
+        --bodies_[atom.watch()].watches;
+    }
+    atom.setSource(n.id);
+    ++bodies_[n.id].watches;
 }
 
 // n is a valid source again, forward propagate this information to its heads
 void DefaultUnfoundedCheck::forwardSource(const BodyPtr& n) {
-	for (const NodeId* x = n.node->heads_begin(); x != n.node->heads_end(); ++x) {
-		setSource(*x, n);
-	}
+    for (auto x : n.node->heads()) {
+        if (x != DependencyGraph::sentinel_atom) {
+            setSource(x, n);
+        }
+    }
 }
 
 // n is no longer a valid source, forward propagate this information to its heads
 void DefaultUnfoundedCheck::forwardUnsource(const BodyPtr& n, bool add) {
-	for (const NodeId* x = n.node->heads_begin(), *end = n.node->heads_end(); x != end; ++x) {
-		// Treat disjunctions as separate rules when it comes to source pointer propagation.
-		// E.g. a | b :- B is (source) propagated as:
-		// a :- B, and
-		// b :- B
-		if (*x != DependencyGraph::sentinel_atom) {
-			NodeId aId = *x;
-			if (graph_->getAtom(aId).scc != n.node->scc) {
-				break;
-			}
-			if (atoms_[aId].hasSource() && atoms_[aId].watch() == n.id) {
-				atoms_[aId].markSourceInvalid();
-				sourceQ_.push_back(aId);
-			}
-			if (add && atoms_[aId].watch() == n.id) {
-				pushTodo(aId);
-			}
-		}
-	}
+    // Treat disjunctions as separate rules when it comes to source pointer propagation.
+    // E.g. a | b :- B is (source) propagated as:
+    // a :- B, and
+    // b :- B
+    for (auto aId : n.node->heads()) {
+        if (aId != DependencyGraph::sentinel_atom) {
+            if (graph_->getAtom(aId).scc != n.node->scc) {
+                break;
+            }
+            if (atoms_[aId].hasSource() && atoms_[aId].watch() == n.id) {
+                atoms_[aId].markSourceInvalid();
+                sourceQ_.push(aId);
+            }
+            if (add && atoms_[aId].watch() == n.id) {
+                pushTodo(aId);
+            }
+        }
+    }
 }
 
 // sets body as source for head if necessary.
 // PRE: value(body) != value_false
 // POST: source(head) != 0
 void DefaultUnfoundedCheck::setSource(NodeId head, const BodyPtr& body) {
-	assert(!solver_->isFalse(body.node->lit));
-	// For normal rules from not false B follows not false head, but
-	// for choice rules this is not the case. Therefore, the
-	// check for isFalse(head) is needed so that we do not inadvertently
-	// source a head that is currently false.
-	if (!atoms_[head].hasSource() && !solver_->isFalse(graph_->getAtom(head).lit)) {
-		updateSource(atoms_[head], body);
-		sourceQ_.push_back(head);
-	}
+    assert(not solver_->isFalse(body.node->lit));
+    // For normal rules from not false B follows not false head, but
+    // for choice rules this is not the case. Therefore, the
+    // check for isFalse(head) is needed so that we do not inadvertently
+    // source a head that is currently false.
+    if (not atoms_[head].hasSource() && not solver_->isFalse(graph_->getAtom(head).lit)) {
+        updateSource(atoms_[head], body);
+        sourceQ_.push(head);
+    }
 }
 
 // This function is called for each body that became invalid during propagation.
 // Heads having the body as source have their source invalidated and are added
 // to the todo queue. Furthermore, source pointer removal is propagated forward
 void DefaultUnfoundedCheck::removeSource(NodeId bodyId) {
-	const BodyNode& body = graph_->getBody(bodyId);
-	for (const NodeId* x = body.heads_begin(); x != body.heads_end(); ++x) {
-		if (atoms_[*x].watch() == bodyId) {
-			if (atoms_[*x].hasSource()) {
-				atoms_[*x].markSourceInvalid();
-				sourceQ_.push_back(*x);
-			}
-			pushTodo(*x);
-		}
-	}
-	propagateSource();
+    const BodyNode& body = graph_->getBody(bodyId);
+    for (auto x : body.heads()) {
+        if (atoms_[x].watch() == bodyId) {
+            if (atoms_[x].hasSource()) {
+                atoms_[x].markSourceInvalid();
+                sourceQ_.push(x);
+            }
+            pushTodo(x);
+        }
+    }
+    propagateSource();
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultUnfoundedCheck - Finding & propagating unfounded sets
 /////////////////////////////////////////////////////////////////////////////////////////
-void DefaultUnfoundedCheck::updateAssignment(Solver& s) {
-	assert(sourceQ_.empty() && ufs_.empty() && pickedExt_.empty());
-	for (VarVec::const_iterator it = invalidQ_.begin(), end = invalidQ_.end(); it != end; ++it) {
-		uint32 index = (*it) >> 2;
-		uint32 type  = (*it) & 3u;
-		if (type == watch_source_false) {
-			// a body became false - update atoms having the body as source
-			removeSource(index);
-		}
-		else if (type == watch_head_false) {
-			// an atom in the head of a choice rule became false
-			// normally head false -> body false and hence the head has its source automatically removed
-			// for choice rules we must force source removal explicitly
-			if (atoms_[index].hasSource() && !s.isFalse(graph_->getBody(atoms_[index].watch()).lit)) {
-				atoms_[index].markSourceInvalid();
-				graph_->getAtom(index).visitSuccessors(RemoveSource(this, true));
-				propagateSource();
-			}
-		}
-		else if (type == watch_head_true) {
-			// TODO: approx. ufs for disjunctive lp
-		}
-		else if (type == watch_subgoal_false) { // a literal p relevant to an extended body became false
-			assert(index < watches_.size());
-			const ExtWatch& w    = watches_[index];
-			const BodyNode& body = graph_->getBody(w.bodyId);
-			ExtData*        ext  = extended_[bodies_[w.bodyId].lower_or_ext];
-			ext->removeFromWs(w.data>>1, body.pred_weight(w.data>>1, test_bit(w.data, 0) != 0));
-			if (ext->lower > 0 && bodies_[w.bodyId].watches && !bodies_[w.bodyId].picked && !s.isFalse(body.lit)) {
-				// The body is not a valid source but at least one head atom
-				// still depends on it: mark body as invalid source
-				removeSource(w.bodyId);
-				pickedExt_.push_back(w.bodyId);
-				bodies_[w.bodyId].picked = 1;
-			}
-		}
-	}
-	for (VarVec::size_type i = 0, end = pickedExt_.size(); i != end; ++i) {
-		bodies_[pickedExt_[i]].picked = 0;
-	}
-	pickedExt_.clear();
-	invalidQ_.clear();
+void DefaultUnfoundedCheck::updateAssignment(const Solver& s) {
+    assert(sourceQ_.empty() && ufs_.empty() && pickedExt_.empty());
+    for (auto inv : invalid_) {
+        uint32_t index = inv >> 2;
+        uint32_t type  = inv & 3u;
+        if (type == watch_source_false) {
+            // a body became false - update atoms having the body as source
+            removeSource(index);
+        }
+        else if (type == watch_head_false) {
+            // an atom in the head of a choice rule became false
+            // normally head false -> body false and hence the head has its source automatically removed
+            // for choice rules we must force source removal explicitly
+            if (atoms_[index].hasSource() && not s.isFalse(graph_->getBody(atoms_[index].watch()).lit)) {
+                atoms_[index].markSourceInvalid();
+                removeSource(graph_->getAtom(index), true);
+                propagateSource();
+            }
+        }
+        else if (type == watch_head_true) {
+            // TODO: approx. ufs for disjunctive lp
+        }
+        else if (type == watch_subgoal_false) { // a literal p relevant to an extended body became false
+            assert(index < watches_.size());
+            const auto& [bodyId, data] = watches_[index];
+            const auto& body           = graph_->getBody(bodyId);
+            ExtData*    ext            = extended_[bodies_[bodyId].lowerOrExt];
+            ext->removeFromWs(data >> 1, body.predWeight(data >> 1, Potassco::test_bit(data, 0)));
+            if (ext->lower > 0 && bodies_[bodyId].watches && not bodies_[bodyId].picked && not s.isFalse(body.lit)) {
+                // The body is not a valid source but at least one head atom
+                // still depends on it: mark body as invalid source
+                removeSource(bodyId);
+                pickedExt_.push_back(bodyId);
+                bodies_[bodyId].picked = 1;
+            }
+        }
+    }
+    for (auto e : pickedExt_) { bodies_[e].picked = 0; }
+    pickedExt_.clear();
+    invalid_.clear();
 }
 
 DefaultUnfoundedCheck::UfsType DefaultUnfoundedCheck::findUfs(Solver& s, bool checkMin) {
-	// first: remove all sources that were recently falsified
-	updateAssignment(s);
-	// second: try to re-establish sources.
-	while (!todo_.empty()) {
-		NodeId head       = todo_.pop_ret();
-		atoms_[head].todo = 0;
-		if (!atoms_[head].hasSource() && !s.isFalse(graph_->getAtom(head).lit) && !findSource(head)) {
-			return ufs_poly;  // found an unfounded set - contained in unfounded_
-		}
-		assert(sourceQ_.empty());
-	}
-	todo_.clear();
-	return !checkMin ? ufs_none : findNonHcfUfs(s);
+    // first: remove all sources that were recently falsified
+    updateAssignment(s);
+    // second: try to re-establish sources.
+    while (not todo_.empty()) {
+        NodeId head       = todo_.pop_ret();
+        atoms_[head].todo = 0;
+        if (not atoms_[head].hasSource() && not s.isFalse(graph_->getAtom(head).lit) && not findSource(head)) {
+            return ufs_poly; // found an unfounded set - contained in unfounded_
+        }
+        assert(sourceQ_.empty());
+    }
+    todo_.clear();
+    return not checkMin ? ufs_none : findNonHcfUfs(s);
 }
 
 // searches a new source for the atom node head.
@@ -457,348 +487,374 @@ DefaultUnfoundedCheck::UfsType DefaultUnfoundedCheck::findUfs(Solver& s, bool ch
 // Otherwise, the function returns false and unfounded_ contains head
 // as well as atoms with no source that circularly depend on head.
 bool DefaultUnfoundedCheck::findSource(NodeId headId) {
-	assert(ufs_.empty() && invalidQ_.empty());
-	const NodeId* bodyIt, *bodyEnd;
-	uint32 newSource = 0;
-	pushUfs(headId); // unfounded, unless we find a new source
-	while (!ufs_.empty()) {
-		headId         = ufs_.pop_ret(); // still marked and in vector!
-		AtomData& head = atoms_[headId];
-		if (!head.hasSource()) {
-			const AtomNode& headNode = graph_->getAtom(headId);
-			for (bodyIt = headNode.bodies_begin(), bodyEnd = headNode.bodies_end(); bodyIt != bodyEnd; ++bodyIt) {
-				BodyPtr bodyNode(getBody(*bodyIt));
-				if (!solver_->isFalse(bodyNode.node->lit)) {
-					if (bodyNode.node->scc != headNode.scc || isValidSource(bodyNode)) {
-						head.ufs = 0;               // found a new source,
-						setSource(headId, bodyNode);// set the new source
-						propagateSource();          // and propagate it forward
-						++newSource;
-						break;
-					}
-					else { addUnsourced(bodyNode); }
-				}
-			}
-			if (!head.hasSource()) { // still no source - check again once we are done
-				invalidQ_.push_back(headId);
-			}
-		}
-		else {  // head has a source and is thus not unfounded
-			++newSource;
-			head.ufs = 0;
-		}
-	} // while unfounded_.emtpy() == false
-	ufs_.rewind();
-	if (newSource != 0) {
-		// some atoms in unfounded_ have a new source
-		// clear queue and check possible candidates for atoms that are still unfounded
-		uint32 visited = sizeVec(ufs_.vec);
-		ufs_.clear();
-		if (visited != newSource) {
-			// add elements that are still unfounded
-			for (VarVec::iterator it = invalidQ_.begin(), end = invalidQ_.end(); it != end; ++it) {
-				if ( (atoms_[*it].ufs = (1u - atoms_[*it].validS)) == 1 ) { ufs_.push(*it); }
-			}
-		}
-	}
-	invalidQ_.clear();
-	return ufs_.empty();
+    assert(ufs_.empty() && invalid_.empty());
+    uint32_t newSource = 0;
+    pushUfs(headId); // unfounded, unless we find a new source
+    while (not ufs_.empty()) {
+        headId         = ufs_.pop_ret(); // still marked and in vector!
+        AtomData& head = atoms_[headId];
+        if (not head.hasSource()) {
+            const AtomNode& headNode = graph_->getAtom(headId);
+            for (auto bId : headNode.bodies()) {
+                BodyPtr bodyNode(getBody(bId));
+                if (not solver_->isFalse(bodyNode.node->lit)) {
+                    if (bodyNode.node->scc != headNode.scc || isValidSource(bodyNode)) {
+                        head.ufs = 0;                // found a new source,
+                        setSource(headId, bodyNode); // set the new source
+                        propagateSource();           // and propagate it forward
+                        ++newSource;
+                        break;
+                    }
+                    else {
+                        addUnsourced(bodyNode);
+                    }
+                }
+            }
+            if (not head.hasSource()) { // still no source - check again once we are done
+                invalid_.push_back(headId);
+            }
+        }
+        else { // head has a source and is thus not unfounded
+            ++newSource;
+            head.ufs = 0;
+        }
+    } // while unfounded_.emtpy() == false
+    ufs_.rewind();
+    if (newSource != 0) {
+        // some atoms in unfounded_ have a new source
+        // clear queue and check possible candidates for atoms that are still unfounded
+        uint32_t visited = size32(ufs_.vec);
+        ufs_.clear();
+        if (visited != newSource) {
+            // add elements that are still unfounded
+            for (auto inv : invalid_) {
+                if (atoms_[inv].ufs = 1u - atoms_[inv].validS; atoms_[inv].ufs) {
+                    ufs_.push(inv);
+                }
+            }
+        }
+    }
+    invalid_.clear();
+    return ufs_.empty();
 }
 
 // checks whether the body can source its heads
 bool DefaultUnfoundedCheck::isValidSource(const BodyPtr& n) {
-	if (!n.node->extended()) {
-		return bodies_[n.id].lower_or_ext == 0;
-	}
-	ExtData* ext = extended_[bodies_[n.id].lower_or_ext];
-	if (ext->lower > 0) {
-		// Since n is currently not a source,
-		// we here know that no literal with a source can depend on this body.
-		// Hence, we can safely add all those literals to WS.
-
-		// We check all internal literals here because there may be atoms
-		// that were sourced *after* we established the watch set.
-		const uint32 inc = n.node->pred_inc();
-		const NodeId* x  = n.node->preds();
-		uint32       p   = 0;
-		for (; *x != idMax; x += inc, ++p) {
-			if (atoms_[*x].hasSource() && !ext->inWs(p) && !solver_->isFalse(graph_->getAtom(*x).lit)) {
-				ext->addToWs(p, n.node->pred_weight(p, false));
-			}
-		}
-		// We check all external literals here because we do not update
-		// the body on backtracking. Therefore, some external literals that were false
-		// may now be true/free.
-		for (++x; *x != idMax; x += inc, ++p) {
-			if (!solver_->isFalse(Literal::fromRep(*x)) && !ext->inWs(p)) {
-				ext->addToWs(p, n.node->pred_weight(p, true));
-			}
-		}
-	}
-	return ext->lower <= 0;
+    if (not n.node->extended()) {
+        return bodies_[n.id].lowerOrExt == 0;
+    }
+    ExtData* ext = extended_[bodies_[n.id].lowerOrExt];
+    if (ext->lower > 0) {
+        // Since n is currently not a source, we here know that no literal with a source can depend on this body.
+        // Hence, we can safely add all those literals to WS.
+
+        // We check all internal literals here because there may be atoms
+        // that were sourced *after* we established the watch set.
+        // We check all external literals here because we do not update the body on backtracking.
+        // Hence, some external literals that were false may now be true/free.
+        for (uint32_t pos = 0; const auto& x : n.node->predecessors()) {
+            if (not ext->inWs(pos) && (x.ext() || atoms_[x.id()].hasSource()) && not solver_->isFalse(x.lit(*graph_))) {
+                ext->addToWs(pos, x.weight());
+            }
+            ++pos;
+        }
+    }
+    return ext->lower <= 0;
 }
 
 // enqueues all predecessors of this body that currently lack a source
 // PRE: isValidSource(n) == false
 void DefaultUnfoundedCheck::addUnsourced(const BodyPtr& n) {
-	const uint32 inc = n.node->pred_inc();
-	for (const NodeId* x = n.node->preds(); *x != idMax; x += inc) {
-		if (!atoms_[*x].hasSource() && !solver_->isFalse(graph_->getAtom(*x).lit)) {
-			pushUfs(*x);
-		}
-	}
+    for (const auto& x : n.node->predecessors(true)) {
+        if (not atoms_[x.id()].hasSource() && not solver_->isFalse(graph_->getAtomLit(x.id()))) {
+            pushUfs(x.id());
+        }
+    }
 }
 
 // falsifies the atoms one by one from the unfounded set stored in unfounded_
 bool DefaultUnfoundedCheck::falsifyUfs(UfsType t) {
-	activeClause_.clear();
-	for (uint32 rDL = 0; !ufs_.empty(); ) {
-		Literal a = graph_->getAtom(ufs_.front()).lit;
-		if (!solver_->isFalse(a) && !(assertAtom(a, t) && solver_->propagateUntil(this))) {
-			if (t == ufs_non_poly) {
-				mini_->schedNext(solver_->decisionLevel(), false);
-			}
-			break;
-		}
-		assert(solver_->isFalse(a));
-		atoms_[ufs_.pop_ret()].ufs = 0;
-		if      (ufs_.qFront == 1)               { rDL = solver_->decisionLevel(); }
-		else if (solver_->decisionLevel() != rDL){ break; /* atoms may no longer be unfounded after backtracking */ }
-	}
-	if (!loopAtoms_.empty()) {
-		createLoopFormula();
-	}
-	resetUfs();
-	activeClause_.clear();
-	return !solver_->hasConflict();
+    activeClause_.clear();
+    for (uint32_t dl = 0; not ufs_.empty();) {
+        Literal a = graph_->getAtom(ufs_.front()).lit;
+        if (not solver_->isFalse(a) && !(assertAtom(a, t) && solver_->propagateUntil(this))) {
+            if (t == ufs_non_poly) {
+                mini_->schedNext(solver_->decisionLevel(), false);
+            }
+            break;
+        }
+        assert(solver_->isFalse(a));
+        atoms_[ufs_.pop_ret()].ufs = 0;
+        if (ufs_.qFront == 1) {
+            dl = solver_->decisionLevel();
+        }
+        else if (solver_->decisionLevel() != dl) {
+            break; /* atoms may no longer be unfounded after backtracking */
+        }
+    }
+    if (not loopAtoms_.empty()) {
+        createLoopFormula();
+    }
+    resetUfs();
+    activeClause_.clear();
+    return not solver_->hasConflict();
 }
 
 // asserts an unfounded atom using the selected reason strategy
 bool DefaultUnfoundedCheck::assertAtom(Literal a, UfsType t) {
-	if (solver_->isTrue(a) || strategy_ == distinct_reason || activeClause_.empty()) {
-		// Conflict, first atom of unfounded set, or distinct reason for each atom requested -
-		// compute reason for a being unfounded.
-		// We must flush any not yet created loop formula here - the
-		// atoms in loopAtoms_ depend on the current reason which is about to be replaced.
-		if (!loopAtoms_.empty()) {
-			createLoopFormula();
-		}
-		activeClause_.assign(1, ~a);
-		computeReason(t);
-	}
-	activeClause_[0] = ~a;
-	bool tainted = info_.tagged() || info_.aux();
-	bool noClause = solver_->isTrue(a) || strategy_ == no_reason || strategy_ == only_reason || (strategy_ == shared_reason && activeClause_.size() > 3 && !tainted);
-	if (noClause) {
-		if (!solver_->force(~a, this))  { return false; }
-		if (strategy_ == only_reason)   { reasons_[a.var()-1].assign(activeClause_.begin()+1, activeClause_.end()); }
-		else if (strategy_ != no_reason){ loopAtoms_.push_back(~a); }
-		return true;
-	}
-	else { // learn nogood and assert ~a
-		return ClauseCreator::create(*solver_, activeClause_, ClauseCreator::clause_no_prepare, info_).ok();
-	}
+    if (solver_->isTrue(a) || strategy_ == distinct_reason || activeClause_.empty()) {
+        // Conflict, first atom of unfounded set, or distinct reason for each atom requested -
+        // compute reason for a being unfounded.
+        // We must flush any not yet created loop formula here - the
+        // atoms in loopAtoms_ depend on the current reason which is about to be replaced.
+        if (not loopAtoms_.empty()) {
+            createLoopFormula();
+        }
+        activeClause_.assign(1, ~a);
+        computeReason(t);
+    }
+    activeClause_[0] = ~a;
+    bool tainted     = info_.tagged() || info_.aux();
+    bool noClause    = solver_->isTrue(a) || strategy_ == no_reason || strategy_ == only_reason ||
+                    (strategy_ == shared_reason && activeClause_.size() > 3 && not tainted);
+    if (noClause) {
+        if (not solver_->force(~a, this)) {
+            return false;
+        }
+        if (strategy_ == only_reason) {
+            reasons_[a.var() - 1].assign(activeClause_.begin() + 1, activeClause_.end());
+        }
+        else if (strategy_ != no_reason) {
+            loopAtoms_.push_back(~a);
+        }
+        return true;
+    }
+    else { // learn nogood and assert ~a
+        return ClauseCreator::create(*solver_, activeClause_, ClauseCreator::clause_no_prepare, info_).ok();
+    }
 }
 void DefaultUnfoundedCheck::createLoopFormula() {
-	assert(activeClause_.size() > 3 && !loopAtoms_.empty());
-	Antecedent ante;
-	activeClause_[0] = loopAtoms_[0];
-	if (loopAtoms_.size() == 1) {
-		ante = ClauseCreator::create(*solver_, activeClause_, ClauseCreator::clause_no_prepare, info_).local;
-	}
-	else {
-		assert(!info_.tagged() && !info_.aux());
-		ante = LoopFormula::newLoopFormula(*solver_
-			, ClauseRep::prepared(&activeClause_[0], (uint32)activeClause_.size(), info_)
-			, &loopAtoms_[0], (uint32)loopAtoms_.size());
-		solver_->addLearnt(ante.constraint(), uint32(loopAtoms_.size() + activeClause_.size()), Constraint_t::Loop);
-	}
-	do {
-		Literal p = loopAtoms_.back();
-		assert(solver_->isTrue(p));
-		if (solver_->reason(p).asUint() != ante.asUint()) {
-			assert(solver_->reason(p) == this);
-			solver_->setReason(p, ante);
-		}
-		loopAtoms_.pop_back();
-	} while (!loopAtoms_.empty());
+    assert(activeClause_.size() > 3 && not loopAtoms_.empty());
+    Antecedent ante;
+    activeClause_[0] = loopAtoms_[0];
+    if (loopAtoms_.size() == 1) {
+        ante = ClauseCreator::create(*solver_, activeClause_, ClauseCreator::clause_no_prepare, info_).local;
+    }
+    else {
+        assert(not info_.tagged() && not info_.aux());
+        ante = LoopFormula::newLoopFormula(*solver_, ClauseRep::prepared(activeClause_, info_), loopAtoms_);
+        solver_->addLearnt(ante.constraint(), size32(loopAtoms_) + size32(activeClause_), ConstraintType::loop);
+    }
+    do {
+        Literal p = loopAtoms_.back();
+        assert(solver_->isTrue(p));
+        if (solver_->reason(p).asUint() != ante.asUint()) {
+            assert(solver_->reason(p) == this);
+            solver_->setReason(p, ante);
+        }
+        loopAtoms_.pop_back();
+    } while (not loopAtoms_.empty());
 }
 
 // computes the reason why a set of atoms is unfounded
 void DefaultUnfoundedCheck::computeReason(UfsType t) {
-	if (strategy_ == no_reason) { return; }
-	uint32 ufsScc = graph_->getAtom(ufs_.front()).scc;
-	for (VarVec::size_type i = ufs_.qFront; i != ufs_.vec.size(); ++i) {
-		const AtomNode& atom = graph_->getAtom(ufs_.vec[i]);
-		if (!solver_->isFalse(atom.lit)) {
-			assert(atom.scc == ufsScc);
-			for (const NodeId* x = atom.bodies_begin(); x != atom.bodies_end(); ++x) {
-				BodyPtr body(getBody(*x));
-				if (t == ufs_poly || !body.node->delta()){ addIfReason(body, ufsScc); }
-				else                                     { addDeltaReason(body, ufsScc); }
-			}
-		}
-	}
-	for (VarVec::size_type i = 0; i != pickedExt_.size(); ++i) { bodies_[pickedExt_[i]].picked = 0; }
-	pickedExt_.clear();
-	info_     = ConstraintInfo(Constraint_t::Loop);
-	uint32 rc = !solver_->isFalse(activeClause_[0]) && activeClause_.size() > 100 && activeClause_.size() > (solver_->decisionLevel() * 10);
-	uint32 dl = solver_->finalizeConflictClause(activeClause_, info_, rc);
-	uint32 cDL= solver_->decisionLevel();
-	assert((t == ufs_non_poly || dl == cDL) && "Loop nogood must contain a literal from current DL!");
-	if (dl < cDL && solver_->isUndoLevel()) {
-		// cancel active propagation on cDL
-		cancelPropagation();
-		invalidQ_.clear();
-		solver_->undoUntil(dl);
-	}
+    if (strategy_ == no_reason) {
+        return;
+    }
+    uint32_t ufsScc = graph_->getAtom(ufs_.front()).scc;
+    for (auto i = ufs_.qFront; i != ufs_.vec.size(); ++i) {
+        if (const auto& atom = graph_->getAtom(ufs_.vec[i]); not solver_->isFalse(atom.lit)) {
+            assert(atom.scc == ufsScc);
+            for (auto bId : atom.bodies()) {
+                if (BodyPtr body(getBody(bId)); t == ufs_poly || not body.node->delta()) {
+                    addIfReason(body, ufsScc);
+                }
+                else {
+                    addDeltaReason(body, ufsScc);
+                }
+            }
+        }
+    }
+    for (auto ext : pickedExt_) { bodies_[ext].picked = 0; }
+    pickedExt_.clear();
+    info_       = ConstraintInfo(ConstraintType::loop);
+    uint32_t rc = not solver_->isFalse(activeClause_[0]) && activeClause_.size() > 100 &&
+                  activeClause_.size() > (solver_->decisionLevel() * 10);
+    uint32_t dl      = solver_->finalizeConflictClause(activeClause_, info_, rc);
+    uint32_t current = solver_->decisionLevel();
+    POTASSCO_DEBUG_ASSERT(t == ufs_non_poly || dl == current,
+                          "Loop nogood must contain a literal from current DL! (%u;%u)", current, dl);
+    if (dl < current && solver_->isUndoLevel()) {
+        // cancel active propagation on current level
+        cancelPropagation();
+        invalid_.clear();
+        solver_->undoUntil(dl);
+    }
 }
 // check whether n is external to the current unfounded set, i.e.
 // does not depend on the atoms from the unfounded set
-bool DefaultUnfoundedCheck::isExternal(const BodyPtr& n, weight_t& S) const {
-#define IN_UFS(id) ( (atoms_[(id)].ufs) && !solver_->isFalse(graph_->getAtom((id)).lit) )
-	if (!n.node->sum()) {
-		for (const NodeId* x = n.node->preds(); *x != idMax && S >= 0; ++x) {
-			if (IN_UFS(*x)) { S -= 1; }
-		}
-	}
-	else {
-		for (const NodeId* x = n.node->preds(); *x != idMax && S >= 0; x+=2) {
-			if (IN_UFS(*x)) { S -= static_cast(x[1]); }
-		}
-	}
-	return S >= 0;
-#undef IN_UFS
+bool DefaultUnfoundedCheck::isExternal(const BodyPtr& n, Weight_t& slack) const {
+    for (const auto& x : n.node->predecessors(true)) {
+        if (atoms_[x.id()].ufs && not solver_->isFalse(graph_->getAtomLit(x.id())) && (slack -= x.weight()) < 0) {
+            return false;
+        }
+    }
+    return slack >= 0;
 }
 
 // check if n is part of the reason for the current unfounded set
-void DefaultUnfoundedCheck::addIfReason(const BodyPtr& n, uint32 uScc) {
-	bool inF = solver_->isFalse(n.node->lit);
-	weight_t S;
-	if (!n.node->extended() || n.node->scc != uScc) {
-		if (inF && !solver_->seen(n.node->lit) && (n.node->scc != uScc || isExternal(n, (S=0)))) {
-			addReasonLit(n.node->lit);
-		}
-	}
-	else if (bodies_[n.id].picked == 0) {
-		if (isExternal(n, S = extended_[bodies_[n.id].lower_or_ext]->slack)) {
-			AddReasonLit addFalseLits = { this, n.node, S };
-			if (inF) { addReasonLit(n.node->lit); }
-			else     { graph_->visitBodyLiterals(*n.node, addFalseLits); }
-		}
-		bodies_[n.id].picked = 1;
-		pickedExt_.push_back(n.id);
-	}
-}
-
-void DefaultUnfoundedCheck::addDeltaReason(const BodyPtr& n, uint32 uScc) {
-	if (bodies_[n.id].picked != 0) return;
-	uint32 bodyAbst = solver_->isFalse(n.node->lit) ? solver_->level(n.node->lit.var()) : solver_->decisionLevel() + 1;
-	for (const NodeId* x = n.node->heads_begin(); x != n.node->heads_end(); ++x) {
-		if (*x != DependencyGraph::sentinel_atom) { // normal head
-			if (graph_->getAtom(*x).scc == uScc) {
-				addIfReason(n, uScc);
-			}
-			continue;
-		}
-		else { // disjunctive head
-			uint32  reasonAbst= bodyAbst;
-			Literal reasonLit = n.node->lit;
-			bool    inUfs     = false;
-			Literal aLit;
-			for (++x; *x; ++x) {
-				if (atoms_[*x].ufs == 1) {
-					inUfs = true;
-				}
-				else if (solver_->isTrue(aLit = graph_->getAtom(*x).lit) && solver_->level(aLit.var()) < reasonAbst) {
-					reasonLit  = ~aLit;
-					reasonAbst = solver_->level(reasonLit.var());
-				}
-			}
-			if (inUfs && reasonAbst && reasonAbst <= solver_->decisionLevel()) {
-				addReasonLit(reasonLit);
-			}
-		}
-	}
-	bodies_[n.id].picked = 1;
-	pickedExt_.push_back(n.id);
+void DefaultUnfoundedCheck::addIfReason(const BodyPtr& n, uint32_t uScc) {
+    bool     inF   = solver_->isFalse(n.node->lit);
+    Weight_t slack = 0;
+    if (not n.node->extended() || n.node->scc != uScc) {
+        if (inF && not solver_->seen(n.node->lit) && (n.node->scc != uScc || isExternal(n, slack))) {
+            addReasonLit(n.node->lit);
+        }
+    }
+    else if (bodies_[n.id].picked == 0) {
+        slack = extended_[bodies_[n.id].lowerOrExt]->slack;
+        if (isExternal(n, slack)) {
+            if (inF) {
+                addReasonLit(n.node->lit);
+            }
+            else {
+                assert(slack >= 0);
+                for (const auto& x : n.node->predecessors()) {
+                    if (auto lit = x.lit(*graph_); solver_->isFalse(lit)) {
+                        addReasonLit(lit);
+                        if (slack -= x.weight(); slack < 0) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        bodies_[n.id].picked = 1;
+        pickedExt_.push_back(n.id);
+    }
+}
+
+void DefaultUnfoundedCheck::addDeltaReason(const BodyPtr& n, uint32_t uScc) {
+    if (bodies_[n.id].picked != 0) {
+        return;
+    }
+    uint32_t bodyAbst =
+        solver_->isFalse(n.node->lit) ? solver_->level(n.node->lit.var()) : solver_->decisionLevel() + 1;
+    auto headSpan = n.node->heads();
+    for (auto it = headSpan.begin(), end = headSpan.end(); it != end; ++it) {
+        if (*it != DependencyGraph::sentinel_atom) { // normal head
+            if (graph_->getAtom(*it).scc == uScc) {
+                addIfReason(n, uScc);
+            }
+        }
+        else { // disjunctive head
+            uint32_t reasonAbst = bodyAbst;
+            Literal  reasonLit  = n.node->lit;
+            bool     inUfs      = false;
+            for (++it; *it; ++it) {
+                if (atoms_[*it].ufs == 1) {
+                    inUfs = true;
+                }
+                else if (Literal aLit = graph_->getAtom(*it).lit;
+                         solver_->isTrue(aLit) && solver_->level(aLit.var()) < reasonAbst) {
+                    reasonLit  = ~aLit;
+                    reasonAbst = solver_->level(reasonLit.var());
+                }
+            }
+            if (inUfs && reasonAbst && reasonAbst <= solver_->decisionLevel()) {
+                addReasonLit(reasonLit);
+            }
+        }
+    }
+    bodies_[n.id].picked = 1;
+    pickedExt_.push_back(n.id);
 }
 
 void DefaultUnfoundedCheck::addReasonLit(Literal p) {
-	if (!solver_->seen(p)) {
-		solver_->markSeen(p);
-		solver_->markLevel(solver_->level(p.var()));
-		activeClause_.push_back(p);
-		if (solver_->level(p.var()) > solver_->level(activeClause_[1].var())) {
-			std::swap(activeClause_[1], activeClause_.back());
-		}
-	}
+    if (not solver_->seen(p)) {
+        solver_->markSeen(p);
+        solver_->markLevel(solver_->level(p.var()));
+        activeClause_.push_back(p);
+        if (solver_->level(p.var()) > solver_->level(activeClause_[1].var())) {
+            std::swap(activeClause_[1], activeClause_.back());
+        }
+    }
 }
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // DefaultUnfoundedCheck - Minimality check for disjunctive logic programs
 /////////////////////////////////////////////////////////////////////////////////////////
 DefaultUnfoundedCheck::UfsType DefaultUnfoundedCheck::findNonHcfUfs(Solver& s) {
-	assert(invalidQ_.empty() && loopAtoms_.empty());
-	typedef DependencyGraph::NonHcfIter HccIter;
-	HccIter hIt  = graph_->nonHcfBegin() + mini_->scc;
-	HccIter hEnd = graph_->nonHcfEnd();
-	for (uint32 checks = graph_->numNonHcfs(); checks--;) {
-		s.stats.addTest(s.numFreeVars() != 0);
-		(*hIt)->assumptionsFromAssignment(s, loopAtoms_);
-		if (!(*hIt)->test(s, loopAtoms_, invalidQ_) || s.hasConflict()) {
-			uint32 pos = 0, minDL = UINT32_MAX;
-			for (VarVec::const_iterator it = invalidQ_.begin(), end = invalidQ_.end(); it != end; ++it) {
-				if (s.isTrue(graph_->getAtom(*it).lit) && s.level(graph_->getAtom(*it).lit.var()) < minDL) {
-					minDL = s.level(graph_->getAtom(*it).lit.var());
-					pos   = (uint32)ufs_.vec.size();
-				}
-				pushUfs(*it);
-			}
-			if (pos) {
-				std::swap(ufs_.vec.front(), ufs_.vec[pos]);
-			}
-			invalidQ_.clear();
-			loopAtoms_.clear();
-			mini_->scc = static_cast(hIt - graph_->nonHcfBegin());
-			return ufs_non_poly;
-		}
-		if (++hIt == hEnd) { hIt = graph_->nonHcfBegin(); }
-		loopAtoms_.clear();
-	}
-	mini_->schedNext(s.decisionLevel(), true);
-	return ufs_none;
-}
-
-DefaultUnfoundedCheck::MinimalityCheck::MinimalityCheck(const FwdCheck& afwd) : fwd(afwd), high(UINT32_MAX), low(0), next(0), scc(0) {
-	if (fwd.highPct > 100) { fwd.highPct  = 100; }
-	if (fwd.highStep == 0) { fwd.highStep = ~fwd.highStep; }
-	high = fwd.highStep;
-}
-
-bool DefaultUnfoundedCheck::MinimalityCheck::partialCheck(uint32 level) {
-	if (level < low) {
-		next -= (low - level);
-		low   = level;
-	}
-	return !next || next == level;
-}
-
-void DefaultUnfoundedCheck::MinimalityCheck::schedNext(uint32 level, bool ok) {
-	low  = 0;
-	next = UINT32_MAX;
-	if (!ok) {
-		high = level;
-		next = 0;
-	}
-	else if (fwd.highPct != 0) {
-		double p = fwd.highPct / 100.0;
-		high     = std::max(high, level);
-		low      = level;
-		if (low >= high) {
-			high  += fwd.highStep;
-		}
-		next     = low + (uint32)std::ceil((high - low) * p);
-	}
-}
-}
+    assert(invalid_.empty() && loopAtoms_.empty() && (not graph_->numNonHcfs() || mini_->scc < graph_->numNonHcfs()));
+    auto components = graph_->nonHcfs();
+    for (uint32_t checks = graph_->numNonHcfs(), n = mini_->scc, maxIdx = size32(components); checks--;) {
+        s.stats.addTest(s.numFreeVars() != 0);
+        const auto& comp = components[n];
+        comp->assumptionsFromAssignment(s, loopAtoms_);
+        if (not comp->test(s, loopAtoms_, invalid_) || s.hasConflict()) {
+            uint32_t pos = 0, minLev = UINT32_MAX;
+            for (auto inv : invalid_) {
+                if (s.isTrue(graph_->getAtom(inv).lit) && s.level(graph_->getAtom(inv).lit.var()) < minLev) {
+                    minLev = s.level(graph_->getAtom(inv).lit.var());
+                    pos    = size32(ufs_.vec);
+                }
+                pushUfs(inv);
+            }
+            if (pos) {
+                std::swap(ufs_.vec.front(), ufs_.vec[pos]);
+            }
+            invalid_.clear();
+            loopAtoms_.clear();
+            mini_->scc = n;
+            return ufs_non_poly;
+        }
+        if (++n == maxIdx) {
+            n = 0;
+        }
+        loopAtoms_.clear();
+    }
+    mini_->schedNext(s.decisionLevel(), true);
+    return ufs_none;
+}
+
+DefaultUnfoundedCheck::MinimalityCheck::MinimalityCheck(const FwdCheck& afwd)
+    : fwd(afwd)
+    , high(UINT32_MAX)
+    , low(0)
+    , next(0)
+    , scc(0) {
+    if (fwd.highPct > 100) {
+        fwd.highPct = 100;
+    }
+    if (fwd.highStep == 0) {
+        fwd.highStep = ~fwd.highStep;
+    }
+    high = fwd.highStep;
+}
+
+bool DefaultUnfoundedCheck::MinimalityCheck::partialCheck(uint32_t level) {
+    if (level < low) {
+        next -= (low - level);
+        low   = level;
+    }
+    return not next || next == level;
+}
+
+void DefaultUnfoundedCheck::MinimalityCheck::schedNext(uint32_t level, bool ok) {
+    low  = 0;
+    next = UINT32_MAX;
+    if (not ok) {
+        high = level;
+        next = 0;
+    }
+    else if (fwd.highPct != 0) {
+        double p = fwd.highPct / 100.0;
+        high     = std::max(high, level);
+        low      = level;
+        if (low >= high) {
+            high += fwd.highStep;
+        }
+        next = low + static_cast(std::ceil((high - low) * p));
+    }
+}
+} // namespace Clasp
diff --git a/src/weight_constraint.cpp b/src/weight_constraint.cpp
index ca9cf52..9726570 100644
--- a/src/weight_constraint.cpp
+++ b/src/weight_constraint.cpp
@@ -1,7 +1,7 @@
 //
-// Copyright (c) 2006-2017 Benjamin Kaufmann
+// Copyright (c) 2006-present Benjamin Kaufmann
 //
-// This file is part of Clasp. See http://www.cs.uni-potsdam.de/clasp/
+// This file is part of Clasp. See https://potassco.org/clasp/
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to
@@ -23,9 +23,13 @@
 //
 
 #include 
+
 #include 
 #include 
 #include 
+
+#include 
+
 #include 
 
 #if defined(__GNUC__) && __GNUC__ >= 8
@@ -39,381 +43,420 @@ namespace Clasp {
 // Removes assigned and merges duplicate/complementary literals.
 // return: achievable weight
 // post  : lits is sorted by decreasing weights
-WeightLitsRep WeightLitsRep::create(Solver& s, WeightLitVec& lits, weight_t bound) {
-	// Step 0: Ensure s has all relevant problem variables
-	if (s.numProblemVars() > s.numVars() && !lits.empty()) {
-		s.acquireProblemVar(std::max_element(lits.begin(), lits.end())->first.var());
-	}
-	// Step 1: remove assigned/superfluous literals and merge duplicate/complementary ones
-	LitVec::size_type j = 0, other;
-	const weight_t MAX_W= std::numeric_limits::max();
-	for (LitVec::size_type i = 0; i != lits.size(); ++i) {
-		Literal x = lits[i].first.unflag();
-		if (lits[i].second != 0 && s.topValue(x.var()) == value_free) {
-			if (lits[i].second < 0) {
-				lits[i].second = -lits[i].second;
-				lits[i].first  = x = ~lits[i].first;
-				POTASSCO_REQUIRE(bound < 0 || (MAX_W-bound) >= lits[i].second, "bound out of range");
-				bound         += lits[i].second;
-			}
-			if (!s.seen(x.var())) { // first time we see x, keep and mark x
-				if (i != j) lits[j] = lits[i];
-				s.markSeen(x);
-				++j;
-			}
-			else if (!s.seen(~x)) { // multi-occurrences of x, merge
-				for (other = 0; other != j && lits[other].first != x; ++other) { ; }
-				lits[other].second += lits[i].second;
-			}
-			else {                  // complementary literals ~x x
-				for (other = 0; other != j && lits[other].first != ~x; ++other) { ; }
-				bound              -= lits[i].second; // decrease by min(w(~x), w(x)) ; assume w(~x) > w(x)
-				lits[other].second -= lits[i].second; // keep ~x,
-				if (lits[other].second < 0) {         // actually, w(x) > w(~x),
-					lits[other].first  = x;             // replace ~x with x
-					lits[other].second = -lits[other].second;
-					s.clearSeen(x.var());
-					s.markSeen(x);
-					bound += lits[other].second;        // and correct the bound
-				}
-				else if (lits[other].second == 0) {   // w(~x) == w(x) - drop both lits
-					s.clearSeen(x.var());
-					std::memmove(&lits[0]+other, &lits[0]+other+1, (j-other-1)*sizeof(lits[other]));
-					--j;
-				}
-			}
-		}
-		else if (s.isTrue(x)) { bound -= lits[i].second; }
-	}
-	lits.erase(lits.begin()+j, lits.end());
-	// Step 2: compute min,max, achievable weight and clear flags set in step 1
-	weight_t sumW = 0;
-	weight_t minW = MAX_W, maxW = 1;
-	weight_t B    = std::max(bound, 1);
-	for (LitVec::size_type i = 0; i != lits.size(); ++i) {
-		assert(lits[i].second > 0);
-		s.clearSeen(lits[i].first.var());
-		if (lits[i].second > maxW) { maxW = lits[i].second = std::min(lits[i].second, B);  }
-		if (lits[i].second < minW) { minW = lits[i].second; }
-		POTASSCO_CHECK((MAX_W - sumW) >= lits[i].second, EOVERFLOW, "Sum of weights out of range");
-		sumW += lits[i].second;
-	}
-	// Step 3: sort by decreasing weight
-	if (maxW != minW) {
-		std::stable_sort(lits.begin(), lits.end(), compose22(
-			std::greater(),
-			select2nd(),
-			select2nd()));
-	}
-	else if (minW != 1) {
-		// disguised cardinality constraint
-		bound = (bound+(minW-1))/minW;
-		sumW  = (sumW+(minW-1))/minW;
-		for (LitVec::size_type i = 0; i != lits.size(); ++i) { lits[i].second = 1; }
-	}
-	WeightLitsRep result = { !lits.empty() ? &lits[0] : 0, (uint32)lits.size(), bound, sumW };
-	return result;
+WeightLitsRep WeightLitsRep::create(Solver& s, WeightLitVec& lits, Weight_t bound) {
+    // Step 0: Ensure s has all relevant problem variables
+    if (s.numProblemVars() > s.numVars() && not lits.empty()) {
+        s.acquireProblemVar(std::ranges::max_element(lits)->lit.var());
+    }
+    // Step 1: remove assigned/superfluous literals and merge duplicate/complementary ones
+    auto           oEnd  = lits.begin(); // [lits.begin(), oEnd) is the output range
+    constexpr auto max_w = std::numeric_limits::max();
+    for (auto& [x, weight] : lits) {
+        if (weight < 0) {
+            weight = -weight;
+            x      = ~x;
+            POTASSCO_CHECK_PRE(bound < 0 || (max_w - bound) >= weight, "bound out of range");
+            bound += weight;
+        }
+        if (weight == 0 || s.topValue(x.var()) != value_free) {
+            bound -= weight * s.isTrue(x);
+        }
+        else if (not s.seen(x.var())) { // first time we see x, keep and mark x
+            *oEnd++ = {x.unflag(), weight};
+            s.markSeen(x);
+        }
+        else if (not s.seen(~x)) { // multi-occurrences of x, merge
+            auto oIt = std::find_if(lits.begin(), oEnd, [c = x](const auto& o) { return o.lit == c; });
+            POTASSCO_ASSERT(oIt != oEnd);
+            oIt->weight += weight;
+        }
+        else { // complementary literals ~x x
+            auto oIt = std::find_if(lits.begin(), oEnd, [c = ~x](const auto& o) { return o.lit == c; });
+            POTASSCO_ASSERT(oIt != oEnd);
+            bound       -= weight;        // decrease by min(w(~x), w(x)) ; assume w(~x) > w(x)
+            oIt->weight -= weight;        // keep ~x,
+            if (oIt->weight < 0) {        // actually, w(x) > w(~x),
+                oIt->lit    = x.unflag(); // replace ~x with x
+                oIt->weight = -oIt->weight;
+                s.clearSeen(x.var());
+                s.markSeen(x);
+                bound += oIt->weight; // and correct the bound
+            }
+            else if (oIt->weight == 0) { // w(~x) == w(x) - drop both lits
+                s.clearSeen(x.var());
+                std::copy(oIt + 1, oEnd--, oIt);
+            }
+        }
+    }
+    lits.erase(oEnd, lits.end());
+    // Step 2: compute min,max, achievable weight and clear flags set in step 1
+    Weight_t sumW = 0;
+    Weight_t minW = max_w, maxW = 1;
+    Weight_t bnd = std::max(bound, 1);
+    for (auto& [lit, weight] : lits) {
+        assert(weight > 0);
+        s.clearSeen(lit.var());
+        if (weight > maxW) {
+            maxW = weight = std::min(weight, bnd);
+        }
+        if (weight < minW) {
+            minW = weight;
+        }
+        POTASSCO_CHECK((max_w - sumW) >= weight, EOVERFLOW, "Sum of weights out of range");
+        sumW += weight;
+    }
+    // Step 3: sort by decreasing weight
+    if (maxW != minW) {
+        std::ranges::stable_sort(lits.begin(), lits.end(), std::greater{},
+                                 [](const WeightLiteral& lit) { return lit.weight; });
+    }
+    else if (minW != 1) {
+        // disguised cardinality constraint
+        bound = (bound + (minW - 1)) / minW;
+        sumW  = (sumW + (minW - 1)) / minW;
+        for (auto& [_, weight] : lits) { weight = 1; }
+    }
+    return {.lits = lits.data(), .size = size32(lits), .bound = bound, .reach = sumW};
 }
 
 // Propagates top-level assignment.
-bool WeightLitsRep::propagate(Solver& s, Literal W) {
-	if      (sat())  { return s.force(W); } // trivially SAT
-	else if (unsat()){ return s.force(~W);} // trivially UNSAT
-	else if (s.topValue(W.var()) == value_free) {
-		return true;
-	}
-	// backward propagate
-	bool bpTrue = s.isTrue(W);
-	weight_t B  = bpTrue ? (reach-bound)+1 : bound;
-	while (lits->second >= B) {
-		reach -= lits->second;
-		if (!s.force(bpTrue ? lits->first : ~lits->first, 0))        { return false; }
-		if ((bpTrue && (bound -= lits->second) <= 0) || --size == 0) { return true;  }
-		++lits;
-	}
-	if (lits->second > 1 && lits->second == lits[size-1].second) {
-		B     = lits->second;
-		bound = (bound + (B-1)) / B;
-		reach = (reach + (B-1)) / B;
-		for (uint32 i = 0; i != size && lits[i].second != 1; ++i) { lits[i].second = 1; }
-	}
-	return true;
+bool WeightLitsRep::propagate(Solver& s, Literal w) {
+    if (sat()) { // trivially SAT
+        return s.force(w);
+    }
+    if (unsat()) { // trivially UNSAT
+        return s.force(~w);
+    }
+    if (s.topValue(w.var()) == value_free) {
+        return true;
+    }
+    // backward propagate
+    bool     bpTrue = s.isTrue(w);
+    Weight_t bnd    = bpTrue ? (reach - bound) + 1 : bound;
+    while (lits->weight >= bnd) {
+        const auto& [lit, weight]  = *lits;
+        reach                     -= weight;
+        if (not s.force(bpTrue ? lit : ~lit, nullptr)) {
+            return false;
+        }
+        if ((bpTrue && (bound -= weight) <= 0) || --size == 0) {
+            return true;
+        }
+        ++lits;
+    }
+    if (lits->weight > 1 && lits->weight == lits[size - 1].weight) {
+        bnd   = lits->weight;
+        bound = (bound + (bnd - 1)) / bnd;
+        reach = (reach + (bnd - 1)) / bnd;
+        for (uint32_t i = 0; i != size && lits[i].weight != 1; ++i) { lits[i].weight = 1; }
+    }
+    return true;
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // WeightConstraint::WL
 /////////////////////////////////////////////////////////////////////////////////////////
-typedef Clasp::Atomic_t::type RCType;
-WeightConstraint::WL::WL(uint32 s, bool shared, bool hasW) : sz(s), rc(shared), w(hasW) { }
-uint8* WeightConstraint::WL::address() { return reinterpret_cast(this) - (sizeof(uint32) * rc); }
-WeightConstraint::WL* WeightConstraint::WL::clone(){
-	if (shareable()) {
-		++*reinterpret_cast(address());
-		return this;
-	}
-	else {
-		uint32 litSize = (size() << uint32(weights()))*sizeof(Literal);
-		WL* x          = new (::operator new(sizeof(WL) + litSize)) WL(size(), false, weights());
-		std::memcpy(x->lits, this->lits, litSize);
-		return x;
-	}
+WeightConstraint::WL::WL(uint32_t s, bool shared, bool hasW) : sz(s), rc(shared), w(hasW) {}
+uint8_t* WeightConstraint::WL::address() { return reinterpret_cast(this) - (sizeof(uint32_t) * rc); }
+WeightConstraint::WL* WeightConstraint::WL::clone() {
+    if (shareable()) {
+        reinterpret_cast(address())->add();
+        return this;
+    }
+    uint32_t litSize = (size() << static_cast(weights())) * sizeof(Literal);
+    WL*      x       = new (::operator new(sizeof(WL) + litSize)) WL(size(), false, weights());
+    std::memcpy(x->lits, this->lits, litSize);
+    return x;
 }
 void WeightConstraint::WL::release() {
-	unsigned char* x = address();
-	if (!shareable() || --*reinterpret_cast(x) == 0) {
-		::operator delete(x);
-	}
+    unsigned char* x = address();
+    if (not shareable() || reinterpret_cast(x)->release()) {
+        ::operator delete(x);
+    }
 }
-uint32 WeightConstraint::WL::refCount() const {
-	assert(shareable());
-	return *reinterpret_cast(const_cast(this)->address());
+uint32_t WeightConstraint::WL::refCount() const {
+    assert(shareable());
+    return *reinterpret_cast(const_cast(this)->address());
 }
 /////////////////////////////////////////////////////////////////////////////////////////
 // WeightConstraint
 /////////////////////////////////////////////////////////////////////////////////////////
-WeightConstraint::CPair WeightConstraint::create(Solver& s, Literal W, WeightLitVec& lits, weight_t bound, uint32 flags) {
-	bool const    eq  = (flags & create_eq_bound) != 0;
-	WeightLitsRep rep = WeightLitsRep::create(s, lits, bound + static_cast(eq));
-	CPair res;
-	if (eq) {
-		res.con[1] = WeightConstraint::doCreate(s, ~W, rep, flags);
-		rep.bound -= 1;
-		if (!res.ok()) { return res; }
-		// redo coefficient reduction
-		for (unsigned i = 0; i != rep.size && rep.lits[i].second > rep.bound; ++i) {
-			rep.reach -= rep.lits[i].second;
-			rep.reach += (rep.lits[i].second = rep.bound);
-		}
-	}
-	res.con[0] = WeightConstraint::doCreate(s, W, rep, flags);
-	return res;
+WeightConstraint::CPair WeightConstraint::create(Solver& s, Literal w, WeightLitVec& lits, Weight_t bound,
+                                                 CreateFlag flags) {
+    bool const    eq  = Potassco::test(flags, create_eq_bound);
+    WeightLitsRep rep = WeightLitsRep::create(s, lits, bound + static_cast(eq));
+    CPair         res;
+    if (eq) {
+        res.con_[1]  = WeightConstraint::doCreate(s, ~w, rep, flags);
+        rep.bound   -= 1;
+        if (not res.ok()) {
+            return res;
+        }
+        // redo coefficient reduction
+        for (unsigned i = 0; i != rep.size && rep.lits[i].weight > rep.bound; ++i) {
+            rep.reach -= rep.lits[i].weight;
+            rep.reach += (rep.lits[i].weight = rep.bound);
+        }
+    }
+    res.con_[0] = WeightConstraint::doCreate(s, w, rep, flags);
+    return res;
 }
-WeightConstraint::CPair WeightConstraint::create(Solver& s, Literal W, WeightLitsRep& rep, uint32 flags) {
-	CPair res;
-	res.con[0] = doCreate(s, W, rep, flags);
-	return res;
+WeightConstraint::CPair WeightConstraint::create(Solver& s, Literal w, WeightLitsRep& rep, CreateFlag flags) {
+    CPair res;
+    res.con_[0] = doCreate(s, w, rep, flags);
+    return res;
 }
 
-WeightConstraint* WeightConstraint::doCreate(Solver& s, Literal W, WeightLitsRep& rep, uint32 flags) {
-	WeightConstraint* conflict = (WeightConstraint*)0x1;
-	const uint32 onlyOne = create_only_btb|create_only_bfb;
-	uint32 act  = 3u;
-	if ((flags & onlyOne) && (flags & onlyOne) != onlyOne) {
-		act = (flags & create_only_bfb) != 0u;
-	}
-	bool addSat = (flags&create_sat) != 0 && rep.size;
-	s.acquireProblemVar(W.var());
-	if (!rep.propagate(s, W))                 { return conflict; }
-	if (rep.unsat() || (rep.sat() && !addSat)){ return 0; }
-	if ((rep.bound == 1 || rep.bound == rep.reach) && (flags & create_explicit) == 0 && act == 3u) {
-		LitVec clause; clause.reserve(1 + rep.size);
-		Literal bin[2];
-		bool disj = rep.bound == 1; // con == disjunction or con == conjunction
-		bool sat  = false;
-		clause.push_back(W ^ disj);
-		for (LitVec::size_type i = 0; i != rep.size; ++i) {
-			bin[0] = ~clause[0];
-			bin[1] = rep.lits[i].first ^ disj;
-			if (bin[0] != ~bin[1]) {
-				if (bin[0] != bin[1])                 { clause.push_back(~bin[1]); }
-				if (!s.add(ClauseRep::create(bin, 2))){ return conflict; }
-			}
-			else { sat = true; }
-		}
-		return (sat || ClauseCreator::create(s, clause, 0)) ? 0 : conflict;
-	}
-	assert(rep.open() || (rep.sat() && addSat));
-	if (!s.sharedContext()->physicalShareProblem()) { flags |= create_no_share; }
-	if (s.sharedContext()->frozen())                { flags |= (create_no_freeze|create_no_share); }
-	bool   hasW = rep.hasWeights();
-	uint32 size = 1 + rep.size;
-	uint32 nb   = sizeof(WeightConstraint) + (size+uint32(hasW))*sizeof(UndoInfo);
-	uint32 wls  = sizeof(WL) + (size << uint32(hasW))*sizeof(Literal);
-	void*  m    = 0;
-	WL*    sL   = 0;
-	if ((flags & create_no_share) != 0) {
-		nb = ((nb + 3) / 4)*4;
-		m  = ::operator new (nb + wls);
-		sL = new (reinterpret_cast(m)+nb) WL(size, false, hasW);
-	}
-	else {
-		static_assert(sizeof(RCType) == sizeof(uint32), "Invalid size!");
-		m = ::operator new(nb);
-		uint8* t = (uint8*)::operator new(wls + sizeof(uint32));
-		*(new (t) RCType) = 1;
-		sL = new (t+sizeof(uint32)) WL(size, true, hasW);
-	}
-	assert(m && (reinterpret_cast(m) & 7u) == 0);
-	SharedContext*  ctx = (flags & create_no_freeze) == 0 ? const_cast(s.sharedContext()) : 0;
-	WeightConstraint* c = new (m) WeightConstraint(s, ctx, W, rep, sL, act);
-	if (!c->integrateRoot(s)) {
-		c->destroy(&s, true);
-		return conflict;
-	}
-	if ((flags & create_no_add) == 0) { s.add(c); }
-	return c;
+WeightConstraint* WeightConstraint::doCreate(Solver& s, Literal w, WeightLitsRep& rep, CreateFlag flags) {
+    auto*          conflict = reinterpret_cast(0x1);
+    constexpr auto onlyOne  = create_only_btb | create_only_bfb;
+    uint32_t       act      = 3u;
+    if (auto x = (flags & onlyOne); x && x != onlyOne) {
+        act = Potassco::test(flags, create_only_bfb);
+    }
+    bool addSat = Potassco::test(flags, create_sat) && rep.size;
+    s.acquireProblemVar(w.var());
+    if (not rep.propagate(s, w)) {
+        return conflict;
+    }
+    if (rep.unsat() || (rep.sat() && not addSat)) {
+        return nullptr;
+    }
+    if (Potassco::test(flags, create_no_add)) {
+        flags |= create_explicit;
+    }
+    if ((rep.bound == 1 || rep.bound == rep.reach) && not Potassco::test(flags, create_explicit) && act == 3u) {
+        LitVec clause;
+        clause.reserve(1 + rep.size);
+        Literal bin[2];
+        bool    disj = rep.bound == 1; // con == disjunction or con == conjunction
+        bool    sat  = false;
+        clause.push_back(w ^ disj);
+        for (const auto& [lit, _] : rep.literals()) {
+            bin[0] = ~clause[0];
+            bin[1] = lit ^ disj;
+            if (bin[0] != ~bin[1]) {
+                if (bin[0] != bin[1]) {
+                    clause.push_back(~bin[1]);
+                }
+                if (not s.add(ClauseRep::create(bin))) {
+                    return conflict;
+                }
+            }
+            else {
+                sat = true;
+            }
+        }
+        return sat || ClauseCreator::create(s, clause, {}) ? nullptr : conflict;
+    }
+    assert(rep.open() || (rep.sat() && addSat));
+    if (not s.sharedContext()->physicalShareProblem()) {
+        flags |= create_no_share;
+    }
+    if (s.sharedContext()->frozen()) {
+        flags |= (create_no_freeze | create_no_share);
+    }
+    bool     hasW = rep.hasWeights();
+    uint32_t size = 1 + rep.size;
+    uint32_t nb   = sizeof(WeightConstraint) + (size + static_cast(hasW)) * sizeof(UndoInfo);
+    uint32_t wls  = sizeof(WL) + (size << static_cast(hasW)) * sizeof(Literal);
+    void*    m;
+    WL*      sL;
+    if (Potassco::test(flags, create_no_share)) {
+        nb = ((nb + 3) / 4) * 4;
+        m  = ::operator new(nb + wls);
+        sL = new (static_cast(m) + nb) WL(size, false, hasW);
+    }
+    else {
+        static_assert(sizeof(RefCount) == sizeof(uint32_t), "Invalid size!");
+        m       = ::operator new(nb);
+        auto* t = static_cast(::operator new(wls + sizeof(uint32_t)));
+        new (t) RefCount(1);
+        sL = new (t + sizeof(uint32_t)) WL(size, true, hasW);
+    }
+    assert(m && (reinterpret_cast(m) & 7u) == 0);
+    auto* ctx = not Potassco::test(flags, create_no_freeze) ? const_cast(s.sharedContext()) : nullptr;
+    auto* c   = new (m) WeightConstraint(s, ctx, w, rep, sL, act);
+    if (not c->integrateRoot(s)) {
+        c->destroy(&s, true);
+        return conflict;
+    }
+    if (not Potassco::test(flags, create_no_add)) {
+        s.add(c);
+    }
+    return c;
 }
-WeightConstraint::WeightConstraint(Solver& s, SharedContext* ctx, Literal W, const WeightLitsRep& rep, WL* out, uint32 act) {
-	typedef unsigned char Byte_t;
-	const bool hasW = rep.hasWeights();
-	lits_           = out;
-	active_         = act;
-	ownsLit_        = !out->shareable();
-	Literal* p      = lits_->lits;
-	Literal* h      = new (reinterpret_cast(undo_)) Literal(W);
-	weight_t w      = 1;
-	bound_[FFB_BTB]	= (rep.reach-rep.bound)+1; // ffb-btb
-	bound_[FTB_BFB]	= rep.bound;               // ftb-bfb
-	*p++            = ~W;                      // store constraint literal
-	if (hasW) *p++  = Literal::fromRep(w);     // and weight if necessary
-	if (ctx) ctx->setFrozen(W.var(), true);    // exempt from variable elimination
-	if (s.topValue(W.var()) != value_free) {   // only one direction is relevant
-		active_ = FFB_BTB+s.isFalse(W);
-	}
-	watched_        = 3u - (active_ != 3u || ctx == 0);
-	WeightLiteral*x = rep.lits;
-	for (uint32 sz = rep.size, j = 1; sz--; ++j, ++x) {
-		h    = new (h + 1) Literal(x->first);
-		*p++ = x->first;                         // store constraint literal
-		w    = x->second;                        // followed by weight
-		if (hasW) *p++= Literal::fromRep(w);     // if necessary
-		addWatch(s, j, FTB_BFB);                 // watches  lits[idx]
-		addWatch(s, j, FFB_BTB);                 // watches ~lits[idx]
-		if (ctx) ctx->setFrozen(h->var(), true); // exempt from variable elimination
-	}
-	// init heuristic
-	h -= rep.size;
-	uint32 off = active_ != NOT_ACTIVE;
-	assert((void*)h == (void*)undo_);
-	s.heuristic()->newConstraint(s, h+off, rep.size+(1-off), Constraint_t::Static);
-	// init undo stack
-	up_                 = undoStart();     // undo stack is initially empty
-	undo_[0].data       = 0;
-	undo_[up_].data     = 0;
-	setBpIndex(1);                         // where to start back propagation
-	if (s.topValue(W.var()) == value_free){
-		addWatch(s, 0, FTB_BFB);             // watch con in both phases
-		addWatch(s, 0, FFB_BTB);             // in order to allow for backpropagation
-	}
-	else {
-		uint32 d = active_;                  // propagate con
-		WeightConstraint::propagate(s, ~lit(0, (ActiveConstraint)active_), d);
-	}
+WeightConstraint::WeightConstraint(Solver& s, SharedContext* ctx, Literal con, const WeightLitsRep& rep, WL* out,
+                                   uint32_t act) {
+    using Byte_t    = unsigned char;
+    const bool hasW = rep.hasWeights();
+    lits_           = out;
+    active_         = act;
+    ownsLit_        = not out->shareable();
+    Literal* p      = lits_->lits;
+    auto*    h      = new (reinterpret_cast(undo_)) Literal(con);
+    bound_[ffb_btb] = (rep.reach - rep.bound) + 1; // ffb-btb
+    bound_[ftb_bfb] = rep.bound;                   // ftb-bfb
+    *p++            = ~con;                        // store constraint literal
+    if (hasW) {
+        *p++ = Literal::fromRep(1u); // and weight if necessary
+    }
+    if (ctx) {
+        ctx->setFrozen(con.var(), true); // exempt from variable elimination
+    }
+    if (s.topValue(con.var()) != value_free) { // only one direction is relevant
+        active_ = ffb_btb + s.isFalse(con);
+    }
+    watched_ = 3u - (active_ != 3u || ctx == nullptr);
+    for (uint32_t j = 1; const auto& [lit, weight] : rep.literals()) {
+        h    = new (h + 1) Literal(lit);
+        *p++ = lit; // store constraint literal
+        if (hasW) { // followed by weight if necessary
+            *p++ = Literal::fromRep(static_cast(weight));
+        }
+        addWatch(s, j, ftb_bfb); // watches  lits[idx]
+        addWatch(s, j, ffb_btb); // watches ~lits[idx]
+        if (ctx) {
+            ctx->setFrozen(h->var(), true); // exempt from variable elimination
+        }
+        ++j;
+    }
+    // init heuristic
+    h            -= rep.size;
+    uint32_t off  = active_ != not_active;
+    assert(static_cast(h) == static_cast(undo_));
+    s.heuristic()->newConstraint(s, {h + off, rep.size + (1 - off)}, ConstraintType::static_);
+    // init undo stack
+    up_             = undoStart(); // undo stack is initially empty
+    undo_[0].data   = 0;
+    undo_[up_].data = 0;
+    setBpIndex(1); // where to start back propagation
+    if (s.topValue(con.var()) == value_free) {
+        addWatch(s, 0, ftb_bfb); // watch con in both phases
+        addWatch(s, 0, ffb_btb); // in order to allow for backpropagation
+    }
+    else {
+        uint32_t d = active_; // propagate con
+        WeightConstraint::propagate(s, ~lit(0, static_cast(active_)), d);
+    }
 }
 
 WeightConstraint::WeightConstraint(Solver& s, const WeightConstraint& other) {
-	typedef unsigned char Byte_t;
-	lits_        = other.lits_->clone();
-	ownsLit_     = 0;
-	Literal* heu = new (reinterpret_cast(undo_))Literal(~lits_->lit(0));
-	bound_[0]	   = other.bound_[0];
-	bound_[1]	   = other.bound_[1];
-	active_      = other.active_;
-	watched_     = other.watched_;
-	if (s.value(heu->var()) == value_free) {
-		addWatch(s, 0, FTB_BFB);  // watch con in both phases
-		addWatch(s, 0, FFB_BTB);  // in order to allow for backpropagation
-	}
-	for (uint32 i = 1, end = size(); i < end; ++i) {
-		heu = new (heu + 1) Literal(lits_->lit(i));
-		if (s.value(heu->var()) == value_free) {
-			addWatch(s, i, FTB_BFB);  // watches  lits[i]
-			addWatch(s, i, FFB_BTB);  // watches ~lits[i]
-		}
-	}
-	// Initialize heuristic with literals (no weights) in constraint.
-	uint32 off = active_ != NOT_ACTIVE;
-	heu -= (size() - 1);
-	assert((void*)heu == (void*)undo_);
-	s.heuristic()->newConstraint(s, heu+off, size()-off, Constraint_t::Static);
-	// Init undo stack
-	std::memcpy(undo_, other.undo_, sizeof(UndoInfo)*(size()+isWeight()));
-	up_ = other.up_;
+    using Byte_t = unsigned char;
+    lits_        = other.lits_->clone();
+    ownsLit_     = 0;
+    auto* heu    = new (reinterpret_cast(undo_)) Literal(~lits_->lit(0));
+    bound_[0]    = other.bound_[0];
+    bound_[1]    = other.bound_[1];
+    active_      = other.active_;
+    watched_     = other.watched_;
+    if (s.value(heu->var()) == value_free) {
+        addWatch(s, 0, ftb_bfb); // watch con in both phases
+        addWatch(s, 0, ffb_btb); // in order to allow for backpropagation
+    }
+    for (uint32_t i : irange(1u, size())) {
+        heu = new (heu + 1) Literal(lits_->lit(i));
+        if (s.value(heu->var()) == value_free) {
+            addWatch(s, i, ftb_bfb); // watches  lits[i]
+            addWatch(s, i, ffb_btb); // watches ~lits[i]
+        }
+    }
+    // Initialize heuristic with literals (no weights) in constraint.
+    uint32_t off  = active_ != not_active;
+    heu          -= (size() - 1);
+    assert(static_cast(heu) == static_cast(undo_));
+    s.heuristic()->newConstraint(s, {heu + off, size() - off}, ConstraintType::static_);
+    // Init undo stack
+    std::memcpy(undo_, other.undo_, sizeof(UndoInfo) * (size() + isWeight()));
+    up_ = other.up_;
 }
 
-WeightConstraint::~WeightConstraint() {}
 Constraint* WeightConstraint::cloneAttach(Solver& other) {
-	void* m = ::operator new(sizeof(WeightConstraint) + (size()+isWeight())*sizeof(UndoInfo));
-	return new (m) WeightConstraint(other, *this);
+    void* m = ::operator new(sizeof(WeightConstraint) + (size() + isWeight()) * sizeof(UndoInfo));
+    return new (m) WeightConstraint(other, *this);
 }
 
 bool WeightConstraint::integrateRoot(Solver& s) {
-	if (!s.decisionLevel() || highestUndoLevel(s) >= s.rootLevel() || s.hasConflict()) { return !s.hasConflict(); }
-	// check if constraint has assigned literals
-	uint32 low = s.decisionLevel(), vDL;
-	uint32 np  = 0;
-	for (uint32 i = 0, end = size(); i != end; ++i) {
-		Var v = lits_->var(i);
-		if (s.value(v) != value_free && (vDL = s.level(v)) != 0) {
-			++np;
-			s.markSeen(v);
-			low = std::min(low, vDL);
-		}
-	}
-	// propagate assigned literals in assignment order
-	const LitVec& trail = s.trail();
-	const uint32  end   = sizeVec(trail) - s.queueSize();
-	GenericWatch* w     = 0;
-	for (uint32 i = s.levelStart(low); i != end && np; ++i) {
-		Literal p = trail[i];
-		if (s.seen(p) && np--) {
-			s.clearSeen(p.var());
-			if (!s.hasConflict() && (w = s.getWatch(trail[i], this)) != 0) {
-				w->propagate(s, p);
-			}
-		}
-	}
-	for (uint32 i = end; i != trail.size() && np; ++i) {
-		if (s.seen(trail[i].var())) { --np; s.clearSeen(trail[i].var()); }
-	}
-	return !s.hasConflict();
+    if (not s.decisionLevel() || highestUndoLevel(s) >= s.rootLevel() || s.hasConflict()) {
+        return not s.hasConflict();
+    }
+    // check if constraint has assigned literals
+    uint32_t low = s.decisionLevel(), dl;
+    uint32_t np  = 0;
+    for (uint32_t i : irange(size())) {
+        if (auto v = lits_->var(i); s.value(v) != value_free && (dl = s.level(v)) != 0) {
+            ++np;
+            s.markSeen(v);
+            low = std::min(low, dl);
+        }
+    }
+    if (np) { // propagate assigned literals in assignment order
+        const auto assigned = s.trailView(s.levelStart(low));
+        for (auto idx = 0u, qStart = size32(assigned) - s.queueSize(); auto p : assigned) {
+            if (s.seen(p)) {
+                s.clearSeen(p.var());
+                if (auto* w = not s.hasConflict() && idx < qStart ? s.getWatch(p, this) : nullptr; w) {
+                    w->propagate(s, p);
+                }
+                if (--np == 0) {
+                    break;
+                }
+            }
+            ++idx;
+        }
+    }
+    return not s.hasConflict();
 }
-void WeightConstraint::addWatch(Solver& s, uint32 idx, ActiveConstraint c) {
-	// Add watch only if c is relevant.
-	if (uint32(c^1) != active_) {
-		// Use LSB to store the constraint that watches the literal.
-		s.addWatch(~lit(idx, c), this, (idx<<1)+c);
-	}
+void WeightConstraint::addWatch(Solver& s, uint32_t idx, ActiveConstraint c) {
+    // Add watch only if c is relevant.
+    if ((c ^ 1u) != active_) {
+        // Use LSB to store the constraint that watches the literal.
+        s.addWatch(~lit(idx, c), this, (idx << 1) + c);
+    }
 }
 
 void WeightConstraint::destroy(Solver* s, bool detach) {
-	if (s && detach) {
-		for (uint32 i = 0, end = size(); i != end; ++i) {
-			s->removeWatch( lits_->lit(i), this );
-			s->removeWatch(~lits_->lit(i), this );
-		}
-		for (uint32 last = 0, dl; (dl = highestUndoLevel(*s)) != 0; --up_) {
-			if (dl != last) { s->removeUndoWatch(last = dl, this); }
-		}
-	}
-	if (ownsLit_ == 0) { lits_->release(); }
-	void* mem = static_cast(this);
-	this->~WeightConstraint();
-	::operator delete(mem);
+    if (s && detach) {
+        for (auto i : irange(size())) {
+            s->removeWatch(lits_->lit(i), this);
+            s->removeWatch(~lits_->lit(i), this);
+        }
+        for (uint32_t last = 0, dl; (dl = highestUndoLevel(*s)) != 0; --up_) {
+            if (dl != last) {
+                s->removeUndoWatch(last = dl, this);
+            }
+        }
+    }
+    if (ownsLit_ == 0) {
+        lits_->release();
+    }
+    void* mem = static_cast(this);
+    this->~WeightConstraint();
+    ::operator delete(mem);
 }
 
-void WeightConstraint::setBpIndex(uint32 n){
-	if (isWeight()) undo_[0].data = (n<<1)+(undo_[0].data&1);
+void WeightConstraint::setBpIndex(uint32_t n) {
+    if (isWeight()) {
+        undo_[0].data = (n << 1) + (undo_[0].data & 1);
+    }
 }
 
 // Returns the numerical highest decision level watched by this constraint.
-uint32 WeightConstraint::highestUndoLevel(Solver& s) const {
-	return up_ != undoStart()
-		? s.level(lits_->var(undoTop().idx()))
-		: 0;
+uint32_t WeightConstraint::highestUndoLevel(const Solver& s) const {
+    return up_ != undoStart() ? s.level(lits_->var(undoTop().idx())) : 0;
 }
 
 // Updates the bound of sub-constraint c and adds the literal at index idx to the
 // undo stack. If the current decision level is not watched, an undo watch is added
 // so that the bound can be adjusted once the solver backtracks.
-void WeightConstraint::updateConstraint(Solver& s, uint32 level, uint32 idx, ActiveConstraint c) {
-	bound_[c] -= weight(idx);
-	if (highestUndoLevel(s) != level) {
-		s.addUndoWatch(level, this);
-	}
-	undo_[up_].data = (idx<<2) + (c<<1) + (undo_[up_].data & 1);
-	++up_;
-	assert(!litSeen(idx));
-	toggleLitSeen(idx);
+void WeightConstraint::updateConstraint(Solver& s, uint32_t level, uint32_t idx, ActiveConstraint c) {
+    bound_[c] -= weight(idx);
+    if (highestUndoLevel(s) != level) {
+        s.addUndoWatch(level, this);
+    }
+    undo_[up_].data = (idx << 2) + (c << 1) + (undo_[up_].data & 1);
+    ++up_;
+    assert(not litSeen(idx));
+    toggleLitSeen(idx);
 }
 
 // Since clasp uses an eager assignment strategy where literals are assigned as soon
@@ -436,175 +479,165 @@ void WeightConstraint::updateConstraint(Solver& s, uint32 level, uint32 idx, Act
 //   propagation is stopped. Without the distinction between processed and unprocessed
 //   lits we would have to skip ~c. We would then have to manually trigger the conflict
 //   {b, ~Body, c} in step 3, when propagate(c) sets the bound to -1.
-Constraint::PropResult WeightConstraint::propagate(Solver& s, Literal p, uint32& d) {
-	// determine the affected constraint and its body literal
-	ActiveConstraint c = (ActiveConstraint)(d&1);
-	const uint32   idx = d >> 1;
-	const Literal body = lit(0, c);
-	const uint32 level = s.level(p.var());
-	if ( uint32(c^1) == active_ || s.isTrue(body) ) {
-		// the other constraint is active or this constraint is already satisfied;
-		// nothing to do
-		return PropResult(true, true);
-	}
-	if (idx == 0 && level <= s.rootLevel() && watched_ == 3u) {
-		watched_ = c;
-		for (uint32 i = 1, end = size(); i != end; ++i) {
-			s.removeWatch(lit(i, c), this);
-		}
-	}
-	// the constraint is not yet satisfied; update it and
-	// check if we can now propagate any literals.
-	updateConstraint(s, level, idx, c);
-	if (bound_[c] <= 0 || (isWeight() && litSeen(0))) {
-		uint32 reasonData = !isWeight() ? UINT32_MAX : up_;
-		if (!litSeen(0)) {
-			// forward propagate constraint to true
-			active_ = c;
-			return PropResult(s.force(body, this, reasonData), true);
-		}
-		else {
-			// backward propagate false constraint
-			uint32 n = getBpIndex();
-			for (const uint32 end = size(); n != end && (bound_[c] - weight(n)) < 0; ++n) {
-				if (!litSeen(n)) {
-					active_   = c;
-					Literal x = lit(n, c);
-					if (!s.force(x, this, reasonData)) {
-						return PropResult(false, true);
-					}
-				}
-			}
-			assert(n == 1 || n == size() || isWeight());
-			setBpIndex(n);
-		}
-	}
-	return PropResult(true, true);
+Constraint::PropResult WeightConstraint::propagate(Solver& s, Literal p, uint32_t& d) {
+    // determine the affected constraint and its body literal
+    auto           c     = static_cast(d & 1);
+    const uint32_t idx   = d >> 1;
+    const Literal  body  = lit(0, c);
+    const uint32_t level = s.level(p.var());
+    if ((c ^ 1u) == active_ || s.isTrue(body)) {
+        // the other constraint is active or this constraint is already satisfied;
+        // nothing to do
+        return PropResult(true, true);
+    }
+    if (idx == 0 && level <= s.rootLevel() && watched_ == 3u) {
+        watched_ = c;
+        for (uint32_t i : irange(1u, size())) { s.removeWatch(lit(i, c), this); }
+    }
+    // the constraint is not yet satisfied; update it and
+    // check if we can now propagate any literals.
+    updateConstraint(s, level, idx, c);
+    if (bound_[c] <= 0 || (isWeight() && litSeen(0))) {
+        uint32_t reasonData = not isWeight() ? UINT32_MAX : up_;
+        if (not litSeen(0)) {
+            // forward propagate constraint to true
+            active_ = c;
+            return PropResult(s.force(body, this, reasonData), true);
+        }
+        // backward propagate false constraint
+        uint32_t n = getBpIndex();
+        for (const uint32_t end = size(); n != end && (bound_[c] - weight(n)) < 0; ++n) {
+            if (not litSeen(n)) {
+                active_   = c;
+                Literal x = lit(n, c);
+                if (not s.force(x, this, reasonData)) {
+                    return PropResult(false, true);
+                }
+            }
+        }
+        assert(n == 1 || n == size() || isWeight());
+        setBpIndex(n);
+    }
+    return PropResult(true, true);
 }
 
 // Builds the reason for p from the undo stack of this constraint.
 // The reason will only contain literals that were processed by the
 // active sub-constraint.
 void WeightConstraint::reason(Solver& s, Literal p, LitVec& r) {
-	assert(active_ != NOT_ACTIVE);
-	Literal x;
-	uint32 stop = !isWeight() ? up_ : s.reasonData(p);
-	assert(stop <= up_);
-	for (uint32 i = undoStart(); i != stop; ++i) {
-		UndoInfo u = undo_[i];
-		// Consider only lits that are relevant to the active constraint
-		if (u.constraint() == (ActiveConstraint)active_) {
-			x = lit(u.idx(), u.constraint());
-			r.push_back( ~x );
-		}
-	}
+    assert(active_ != not_active);
+    uint32_t stop = not isWeight() ? up_ : s.reasonData(p);
+    assert(stop <= up_);
+    for (uint32_t i : irange(undoStart(), stop)) {
+        UndoInfo u = undo_[i];
+        // Consider only lits that are relevant to the active constraint
+        if (u.constraint() == static_cast(active_)) {
+            Literal x = lit(u.idx(), u.constraint());
+            r.push_back(~x);
+        }
+    }
 }
 
 bool WeightConstraint::minimize(Solver& s, Literal p, CCMinRecursive* rec) {
-	assert(active_ != NOT_ACTIVE);
-	Literal x;
-	uint32 stop = !isWeight() ? up_ : s.reasonData(p);
-	assert(stop <= up_);
-	for (uint32 i = undoStart(); i != stop; ++i) {
-		UndoInfo u = undo_[i];
-		// Consider only lits that are relevant to the active constraint
-		if (u.constraint() == (ActiveConstraint)active_) {
-			x = lit(u.idx(), u.constraint());
-			if (!s.ccMinimize(~x, rec)) {
-				return false;
-			}
-		}
-	}
-	return true;
+    assert(active_ != not_active);
+    uint32_t stop = not isWeight() ? up_ : s.reasonData(p);
+    assert(stop <= up_);
+    for (uint32_t i = undoStart(); i != stop; ++i) {
+        // Consider only lits that are relevant to the active constraint
+        if (UndoInfo u = undo_[i]; u.constraint() == static_cast(active_)) {
+            if (Literal x = lit(u.idx(), u.constraint()); not s.ccMinimize(~x, rec)) {
+                return false;
+            }
+        }
+    }
+    return true;
 }
 
 // undoes processed assignments
 void WeightConstraint::undoLevel(Solver& s) {
-	setBpIndex(1);
-	for (UndoInfo u; up_ != undoStart() && s.value(lits_->var((u=undoTop()).idx())) == value_free;) {
-		assert(litSeen(u.idx()));
-		toggleLitSeen(u.idx());
-		bound_[u.constraint()] += weight(u.idx());
-		--up_;
-	}
-	if (!litSeen(0)) {
-		active_ = NOT_ACTIVE;
-		if (watched_ < 2u) {
-			ActiveConstraint other = static_cast(watched_^1);
-			for (uint32 i = 1, end = size(); i != end; ++i) {
-				addWatch(s, i, other);
-			}
-			watched_ = 3u;
-		}
-	}
+    setBpIndex(1);
+    for (UndoInfo u; up_ != undoStart() && s.value(lits_->var((u = undoTop()).idx())) == value_free;) {
+        assert(litSeen(u.idx()));
+        toggleLitSeen(u.idx());
+        bound_[u.constraint()] += weight(u.idx());
+        --up_;
+    }
+    if (not litSeen(0)) {
+        active_ = not_active;
+        if (watched_ < 2u) {
+            auto other = static_cast(watched_ ^ 1);
+            for (uint32_t i : irange(1u, size())) { addWatch(s, i, other); }
+            watched_ = 3u;
+        }
+    }
 }
 
 bool WeightConstraint::simplify(Solver& s, bool) {
-	if (bound_[0] <= 0 || bound_[1] <= 0) {
-		for (uint32 i = 0, end = size(); i != end; ++i) {
-			s.removeWatch( lits_->lit(i), this );
-			s.removeWatch(~lits_->lit(i), this );
-		}
-		return true;
-	}
-	else if (s.value(lits_->var(0)) != value_free && (active_ == NOT_ACTIVE || isWeight())) {
-		if (active_ == NOT_ACTIVE) {
-			Literal W = ~lits_->lit(0);
-			active_   = FFB_BTB+s.isFalse(W);
-		}
-		for (uint32 i = 0, end = size(); i != end; ++i) {
-			s.removeWatch(lit(i, (ActiveConstraint)active_), this);
-		}
-	}
-	if (lits_->unique() && size() > 4 && (up_ - undoStart()) > size()/2) {
-		Literal*     lits = lits_->lits;
-		const uint32 inc  = 1 + lits_->weights();
-		uint32       end  = lits_->size()*inc;
-		uint32 i, j, idx  = 1;
-		// find first assigned literal - must be there otherwise undo stack would be empty
-		for (i = inc; s.value(lits[i].var()) == value_free; i += inc) {
-			assert(!litSeen(idx));
-			++idx;
-		}
-		// move unassigned literals down
-		// update watches because indices have changed
-		for (j = i, i += inc; i != end; i += inc) {
-			if (s.value(lits[i].var()) == value_free) {
-				lits[j] = lits[i];
-				if (lits_->weights()) { lits[j+1] = lits[i+1]; }
-				undo_[idx].data = 0;
-				assert(!litSeen(idx));
-				if (Clasp::GenericWatch* w = s.getWatch(lits[i], this)) {
-					w->data = (idx<<1) + 1;
-				}
-				if (Clasp::GenericWatch* w = s.getWatch(~lits[i], this)) {
-					w->data = (idx<<1) + 0;
-				}
-				j += inc;
-				++idx;
-			}
-			else {
-				s.removeWatch(lits[i], this);
-				s.removeWatch(~lits[i], this);
-			}
-		}
-		// clear undo stack & update to new size
-		up_ = undoStart();
-		setBpIndex(1);
-		lits_->sz = idx;
-	}
-	return false;
+    if (bound_[0] <= 0 || bound_[1] <= 0) {
+        for (uint32_t i : irange(size())) {
+            s.removeWatch(lits_->lit(i), this);
+            s.removeWatch(~lits_->lit(i), this);
+        }
+        return true;
+    }
+    if (s.value(lits_->var(0)) != value_free && (active_ == not_active || isWeight())) {
+        if (active_ == not_active) {
+            Literal con = ~lits_->lit(0);
+            active_     = ffb_btb + s.isFalse(con);
+        }
+        for (uint32_t i : irange(size())) { s.removeWatch(lit(i, static_cast(active_)), this); }
+    }
+    if (lits_->unique() && size() > 4 && (up_ - undoStart()) > size() / 2) {
+        Literal*       lits = lits_->lits;
+        const uint32_t inc  = 1 + lits_->weights();
+        uint32_t       end  = lits_->size() * inc;
+        uint32_t       i, j, idx = 1;
+        // find first assigned literal - must be there otherwise undo stack would be empty
+        for (i = inc; s.value(lits[i].var()) == value_free; i += inc) {
+            assert(not litSeen(idx));
+            ++idx;
+        }
+        // move unassigned literals down
+        // update watches because indices have changed
+        for (j = i, i += inc; i != end; i += inc) {
+            if (s.value(lits[i].var()) == value_free) {
+                lits[j] = lits[i];
+                if (lits_->weights()) {
+                    lits[j + 1] = lits[i + 1];
+                }
+                undo_[idx].data = 0;
+                assert(not litSeen(idx));
+                if (auto* w = s.getWatch(lits[i], this); w) {
+                    w->data = (idx << 1) + 1;
+                }
+                if (auto* w = s.getWatch(~lits[i], this); w) {
+                    w->data = (idx << 1) + 0;
+                }
+                j += inc;
+                ++idx;
+            }
+            else {
+                s.removeWatch(lits[i], this);
+                s.removeWatch(~lits[i], this);
+            }
+        }
+        // clear undo stack & update to new size
+        up_ = undoStart();
+        setBpIndex(1);
+        lits_->sz = idx;
+    }
+    return false;
 }
 
-uint32 WeightConstraint::estimateComplexity(const Solver& s) const {
-	weight_t B = std::min(bound_[0], bound_[1]);
-	uint32 r   = 2;
-	for (uint32 i = 1; i != size() && B > 0; ++i) {
-		if (s.value(lits_->var(i)) == value_free) {
-			++r;
-			B -= weight(i);
-		}
-	}
-	return r;
-}
+uint32_t WeightConstraint::estimateComplexity(const Solver& s) const {
+    auto     bnd = std::min(bound_[0], bound_[1]);
+    uint32_t r   = 2;
+    for (uint32_t i = 1; i != size() && bnd > 0; ++i) {
+        if (s.value(lits_->var(i)) == value_free) {
+            ++r;
+            bnd -= weight(i);
+        }
+    }
+    return r;
 }
+} // namespace Clasp
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index bf14896..44ea5ea 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,13 +1,26 @@
-enable_testing()
-file(GLOB src    *.cpp)
-file(GLOB header *.h)
-add_executable(test_clasp ${header} ${src})
-target_link_libraries(test_clasp libclasp)
-# Catch2 requires C++11
-set_target_properties(test_clasp PROPERTIES
-    CXX_STANDARD 11
-    CXX_EXTENSIONS OFF
-    CXX_STANDARD_REQUIRED YES
+add_executable(test_clasp lpcompare.h)
+target_sources(test_clasp PRIVATE
+    clause_creator_test.cpp
+    clause_test.cpp
+    cli_test.cpp
+    decision_heuristic_test.cpp
+    dependency_graph_test.cpp
+    dlp_builder_test.cpp
+    enumerator_test.cpp
+    facade_test.cpp
+    literal_test.cpp
+    minimize_test.cpp
+    parser_test.cpp
+    program_builder_test.cpp
+    rule_test.cpp
+    satelite_test.cpp
+    solver_test.cpp
+    unfounded_check_test.cpp
+    weight_constraint_test.cpp
 )
+
+target_link_libraries(test_clasp PRIVATE libclasp Catch2::Catch2WithMain potassco_default_warnings)
+target_include_directories(test_clasp PRIVATE $)
 set_target_properties(test_clasp PROPERTIES FOLDER test)
-add_test(NAME test_clasp COMMAND test_clasp)
+
+catch_discover_tests(test_clasp PROPERTIES TIMEOUT 30)
diff --git a/tests/catch.hpp b/tests/catch.hpp
deleted file mode 100644
index db1fed3..0000000
--- a/tests/catch.hpp
+++ /dev/null
@@ -1,17966 +0,0 @@
-/*
- *  Catch v2.13.8
- *  Generated: 2022-01-03 21:20:09.589503
- *  ----------------------------------------------------------
- *  This file has been merged from multiple headers. Please don't edit it directly
- *  Copyright (c) 2022 Two Blue Cubes Ltd. All rights reserved.
- *
- *  Distributed under the Boost Software License, Version 1.0. (See accompanying
- *  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
- */
-#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
-#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED
-// start catch.hpp
-
-
-#define CATCH_VERSION_MAJOR 2
-#define CATCH_VERSION_MINOR 13
-#define CATCH_VERSION_PATCH 8
-
-#ifdef __clang__
-#    pragma clang system_header
-#elif defined __GNUC__
-#    pragma GCC system_header
-#endif
-
-// start catch_suppress_warnings.h
-
-#ifdef __clang__
-#   ifdef __ICC // icpc defines the __clang__ macro
-#       pragma warning(push)
-#       pragma warning(disable: 161 1682)
-#   else // __ICC
-#       pragma clang diagnostic push
-#       pragma clang diagnostic ignored "-Wpadded"
-#       pragma clang diagnostic ignored "-Wswitch-enum"
-#       pragma clang diagnostic ignored "-Wcovered-switch-default"
-#    endif
-#elif defined __GNUC__
-     // Because REQUIREs trigger GCC's -Wparentheses, and because still
-     // supported version of g++ have only buggy support for _Pragmas,
-     // Wparentheses have to be suppressed globally.
-#    pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details
-
-#    pragma GCC diagnostic push
-#    pragma GCC diagnostic ignored "-Wunused-variable"
-#    pragma GCC diagnostic ignored "-Wpadded"
-#endif
-// end catch_suppress_warnings.h
-#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER)
-#  define CATCH_IMPL
-#  define CATCH_CONFIG_ALL_PARTS
-#endif
-
-// In the impl file, we want to have access to all parts of the headers
-// Can also be used to sanely support PCHs
-#if defined(CATCH_CONFIG_ALL_PARTS)
-#  define CATCH_CONFIG_EXTERNAL_INTERFACES
-#  if defined(CATCH_CONFIG_DISABLE_MATCHERS)
-#    undef CATCH_CONFIG_DISABLE_MATCHERS
-#  endif
-#  if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER)
-#    define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER
-#  endif
-#endif
-
-#if !defined(CATCH_CONFIG_IMPL_ONLY)
-// start catch_platform.h
-
-// See e.g.:
-// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html
-#ifdef __APPLE__
-#  include 
-#  if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \
-      (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1)
-#    define CATCH_PLATFORM_MAC
-#  elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1)
-#    define CATCH_PLATFORM_IPHONE
-#  endif
-
-#elif defined(linux) || defined(__linux) || defined(__linux__)
-#  define CATCH_PLATFORM_LINUX
-
-#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__)
-#  define CATCH_PLATFORM_WINDOWS
-#endif
-
-// end catch_platform.h
-
-#ifdef CATCH_IMPL
-#  ifndef CLARA_CONFIG_MAIN
-#    define CLARA_CONFIG_MAIN_NOT_DEFINED
-#    define CLARA_CONFIG_MAIN
-#  endif
-#endif
-
-// start catch_user_interfaces.h
-
-namespace Catch {
-    unsigned int rngSeed();
-}
-
-// end catch_user_interfaces.h
-// start catch_tag_alias_autoregistrar.h
-
-// start catch_common.h
-
-// start catch_compiler_capabilities.h
-
-// Detect a number of compiler features - by compiler
-// The following features are defined:
-//
-// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported?
-// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported?
-// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported?
-// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled?
-// ****************
-// Note to maintainers: if new toggles are added please document them
-// in configuration.md, too
-// ****************
-
-// In general each macro has a _NO_ form
-// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature.
-// Many features, at point of detection, define an _INTERNAL_ macro, so they
-// can be combined, en-mass, with the _NO_ forms later.
-
-#ifdef __cplusplus
-
-#  if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L)
-#    define CATCH_CPP14_OR_GREATER
-#  endif
-
-#  if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)
-#    define CATCH_CPP17_OR_GREATER
-#  endif
-
-#endif
-
-// Only GCC compiler should be used in this block, so other compilers trying to
-// mask themselves as GCC should be ignored.
-#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__)
-#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" )
-#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( "GCC diagnostic pop" )
-
-#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__)
-
-#endif
-
-#if defined(__clang__)
-
-#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" )
-#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  _Pragma( "clang diagnostic pop" )
-
-// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug
-// which results in calls to destructors being emitted for each temporary,
-// without a matching initialization. In practice, this can result in something
-// like `std::string::~string` being called on an uninitialized value.
-//
-// For example, this code will likely segfault under IBM XL:
-// ```
-// REQUIRE(std::string("12") + "34" == "1234")
-// ```
-//
-// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented.
-#  if !defined(__ibmxl__) && !defined(__CUDACC__)
-#    define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */
-#  endif
-
-#    define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
-         _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \
-         _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"")
-
-#    define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \
-         _Pragma( "clang diagnostic ignored \"-Wparentheses\"" )
-
-#    define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \
-         _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" )
-
-#    define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \
-         _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" )
-
-#    define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \
-         _Pragma( "clang diagnostic ignored \"-Wunused-template\"" )
-
-#endif // __clang__
-
-////////////////////////////////////////////////////////////////////////////////
-// Assume that non-Windows platforms support posix signals by default
-#if !defined(CATCH_PLATFORM_WINDOWS)
-    #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// We know some environments not to support full POSIX signals
-#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__)
-    #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
-#endif
-
-#ifdef __OS400__
-#       define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS
-#       define CATCH_CONFIG_COLOUR_NONE
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// Android somehow still does not support std::to_string
-#if defined(__ANDROID__)
-#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
-#    define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// Not all Windows environments support SEH properly
-#if defined(__MINGW32__)
-#    define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// PS4
-#if defined(__ORBIS__)
-#    define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// Cygwin
-#ifdef __CYGWIN__
-
-// Required for some versions of Cygwin to declare gettimeofday
-// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin
-#   define _BSD_SOURCE
-// some versions of cygwin (most) do not support std::to_string. Use the libstd check.
-// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813
-# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \
-           && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF))
-
-#    define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING
-
-# endif
-#endif // __CYGWIN__
-
-////////////////////////////////////////////////////////////////////////////////
-// Visual C++
-#if defined(_MSC_VER)
-
-// Universal Windows platform does not support SEH
-// Or console colours (or console at all...)
-#  if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP)
-#    define CATCH_CONFIG_COLOUR_NONE
-#  else
-#    define CATCH_INTERNAL_CONFIG_WINDOWS_SEH
-#  endif
-
-#  if !defined(__clang__) // Handle Clang masquerading for msvc
-
-// MSVC traditional preprocessor needs some workaround for __VA_ARGS__
-// _MSVC_TRADITIONAL == 0 means new conformant preprocessor
-// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor
-#    if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL)
-#      define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
-#    endif // MSVC_TRADITIONAL
-
-// Only do this if we're not using clang on Windows, which uses `diagnostic push` & `diagnostic pop`
-#    define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) )
-#    define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION  __pragma( warning(pop) )
-#  endif // __clang__
-
-#endif // _MSC_VER
-
-#if defined(_REENTRANT) || defined(_MSC_VER)
-// Enable async processing, as -pthread is specified or no additional linking is required
-# define CATCH_INTERNAL_CONFIG_USE_ASYNC
-#endif // _MSC_VER
-
-////////////////////////////////////////////////////////////////////////////////
-// Check if we are compiled with -fno-exceptions or equivalent
-#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND)
-#  define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-// DJGPP
-#ifdef __DJGPP__
-#  define CATCH_INTERNAL_CONFIG_NO_WCHAR
-#endif // __DJGPP__
-
-////////////////////////////////////////////////////////////////////////////////
-// Embarcadero C++Build
-#if defined(__BORLANDC__)
-    #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-
-// Use of __COUNTER__ is suppressed during code analysis in
-// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly
-// handled by it.
-// Otherwise all supported compilers support COUNTER macro,
-// but user still might want to turn it off
-#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L )
-    #define CATCH_INTERNAL_CONFIG_COUNTER
-#endif
-
-////////////////////////////////////////////////////////////////////////////////
-
-// RTX is a special version of Windows that is real time.
-// This means that it is detected as Windows, but does not provide
-// the same set of capabilities as real Windows does.
-#if defined(UNDER_RTSS) || defined(RTX64_BUILD)
-    #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH
-    #define CATCH_INTERNAL_CONFIG_NO_ASYNC
-    #define CATCH_CONFIG_COLOUR_NONE
-#endif
-
-#if !defined(_GLIBCXX_USE_C99_MATH_TR1)
-#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER
-#endif
-
-// Various stdlib support checks that require __has_include
-#if defined(__has_include)
-  // Check if string_view is available and usable
-  #if __has_include() && defined(CATCH_CPP17_OR_GREATER)
-  #    define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW
-  #endif
-
-  // Check if optional is available and usable
-  #  if __has_include() && defined(CATCH_CPP17_OR_GREATER)
-  #    define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL
-  #  endif // __has_include() && defined(CATCH_CPP17_OR_GREATER)
-
-  // Check if byte is available and usable
-  #  if __has_include() && defined(CATCH_CPP17_OR_GREATER)
-  #    include 
-  #    if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0)
-  #      define CATCH_INTERNAL_CONFIG_CPP17_BYTE
-  #    endif
-  #  endif // __has_include() && defined(CATCH_CPP17_OR_GREATER)
-
-  // Check if variant is available and usable
-  #  if __has_include() && defined(CATCH_CPP17_OR_GREATER)
-  #    if defined(__clang__) && (__clang_major__ < 8)
-         // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852
-         // fix should be in clang 8, workaround in libstdc++ 8.2
-  #      include 
-  #      if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
-  #        define CATCH_CONFIG_NO_CPP17_VARIANT
-  #      else
-  #        define CATCH_INTERNAL_CONFIG_CPP17_VARIANT
-  #      endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9)
-  #    else
-  #      define CATCH_INTERNAL_CONFIG_CPP17_VARIANT
-  #    endif // defined(__clang__) && (__clang_major__ < 8)
-  #  endif // __has_include() && defined(CATCH_CPP17_OR_GREATER)
-#endif // defined(__has_include)
-
-#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER)
-#   define CATCH_CONFIG_COUNTER
-#endif
-#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH)
-#   define CATCH_CONFIG_WINDOWS_SEH
-#endif
-// This is set by default, because we assume that unix compilers are posix-signal-compatible by default.
-#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS)
-#   define CATCH_CONFIG_POSIX_SIGNALS
-#endif
-// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions.
-#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR)
-#   define CATCH_CONFIG_WCHAR
-#endif
-
-#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING)
-#    define CATCH_CONFIG_CPP11_TO_STRING
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL)
-#  define CATCH_CONFIG_CPP17_OPTIONAL
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW)
-#  define CATCH_CONFIG_CPP17_STRING_VIEW
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT)
-#  define CATCH_CONFIG_CPP17_VARIANT
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE)
-#  define CATCH_CONFIG_CPP17_BYTE
-#endif
-
-#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT)
-#  define CATCH_INTERNAL_CONFIG_NEW_CAPTURE
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE)
-#  define CATCH_CONFIG_NEW_CAPTURE
-#endif
-
-#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
-#  define CATCH_CONFIG_DISABLE_EXCEPTIONS
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN)
-#  define CATCH_CONFIG_POLYFILL_ISNAN
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC)  && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC)
-#  define CATCH_CONFIG_USE_ASYNC
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE)
-#  define CATCH_CONFIG_ANDROID_LOGWRITE
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER)
-#  define CATCH_CONFIG_GLOBAL_NEXTAFTER
-#endif
-
-// Even if we do not think the compiler has that warning, we still have
-// to provide a macro that can be used by the code.
-#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION)
-#   define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION
-#endif
-#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION)
-#   define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
-#endif
-#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS)
-#   define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS
-#endif
-#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS)
-#   define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS
-#endif
-#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS)
-#   define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS
-#endif
-#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS)
-#   define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS
-#endif
-
-// The goal of this macro is to avoid evaluation of the arguments, but
-// still have the compiler warn on problems inside...
-#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN)
-#   define CATCH_INTERNAL_IGNORE_BUT_WARN(...)
-#endif
-
-#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10)
-#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
-#elif defined(__clang__) && (__clang_major__ < 5)
-#   undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
-#endif
-
-#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS)
-#   define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS
-#endif
-
-#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS)
-#define CATCH_TRY if ((true))
-#define CATCH_CATCH_ALL if ((false))
-#define CATCH_CATCH_ANON(type) if ((false))
-#else
-#define CATCH_TRY try
-#define CATCH_CATCH_ALL catch (...)
-#define CATCH_CATCH_ANON(type) catch (type)
-#endif
-
-#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR)
-#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
-#endif
-
-// end catch_compiler_capabilities.h
-#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line
-#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line )
-#ifdef CATCH_CONFIG_COUNTER
-#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ )
-#else
-#  define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ )
-#endif
-
-#include 
-#include 
-#include 
-
-// We need a dummy global operator<< so we can bring it into Catch namespace later
-struct Catch_global_namespace_dummy {};
-std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy);
-
-namespace Catch {
-
-    struct CaseSensitive { enum Choice {
-        Yes,
-        No
-    }; };
-
-    class NonCopyable {
-        NonCopyable( NonCopyable const& )              = delete;
-        NonCopyable( NonCopyable && )                  = delete;
-        NonCopyable& operator = ( NonCopyable const& ) = delete;
-        NonCopyable& operator = ( NonCopyable && )     = delete;
-
-    protected:
-        NonCopyable();
-        virtual ~NonCopyable();
-    };
-
-    struct SourceLineInfo {
-
-        SourceLineInfo() = delete;
-        SourceLineInfo( char const* _file, std::size_t _line ) noexcept
-        :   file( _file ),
-            line( _line )
-        {}
-
-        SourceLineInfo( SourceLineInfo const& other )            = default;
-        SourceLineInfo& operator = ( SourceLineInfo const& )     = default;
-        SourceLineInfo( SourceLineInfo&& )              noexcept = default;
-        SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default;
-
-        bool empty() const noexcept { return file[0] == '\0'; }
-        bool operator == ( SourceLineInfo const& other ) const noexcept;
-        bool operator < ( SourceLineInfo const& other ) const noexcept;
-
-        char const* file;
-        std::size_t line;
-    };
-
-    std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info );
-
-    // Bring in operator<< from global namespace into Catch namespace
-    // This is necessary because the overload of operator<< above makes
-    // lookup stop at namespace Catch
-    using ::operator<<;
-
-    // Use this in variadic streaming macros to allow
-    //    >> +StreamEndStop
-    // as well as
-    //    >> stuff +StreamEndStop
-    struct StreamEndStop {
-        std::string operator+() const;
-    };
-    template
-    T const& operator + ( T const& value, StreamEndStop ) {
-        return value;
-    }
-}
-
-#define CATCH_INTERNAL_LINEINFO \
-    ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) )
-
-// end catch_common.h
-namespace Catch {
-
-    struct RegistrarForTagAliases {
-        RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo );
-    };
-
-} // end namespace Catch
-
-#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \
-    CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \
-    CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \
-    namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \
-    CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION
-
-// end catch_tag_alias_autoregistrar.h
-// start catch_test_registry.h
-
-// start catch_interfaces_testcase.h
-
-#include 
-
-namespace Catch {
-
-    class TestSpec;
-
-    struct ITestInvoker {
-        virtual void invoke () const = 0;
-        virtual ~ITestInvoker();
-    };
-
-    class TestCase;
-    struct IConfig;
-
-    struct ITestCaseRegistry {
-        virtual ~ITestCaseRegistry();
-        virtual std::vector const& getAllTests() const = 0;
-        virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0;
-    };
-
-    bool isThrowSafe( TestCase const& testCase, IConfig const& config );
-    bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config );
-    std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config );
-    std::vector const& getAllTestCasesSorted( IConfig const& config );
-
-}
-
-// end catch_interfaces_testcase.h
-// start catch_stringref.h
-
-#include 
-#include 
-#include 
-#include 
-
-namespace Catch {
-
-    /// A non-owning string class (similar to the forthcoming std::string_view)
-    /// Note that, because a StringRef may be a substring of another string,
-    /// it may not be null terminated.
-    class StringRef {
-    public:
-        using size_type = std::size_t;
-        using const_iterator = const char*;
-
-    private:
-        static constexpr char const* const s_empty = "";
-
-        char const* m_start = s_empty;
-        size_type m_size = 0;
-
-    public: // construction
-        constexpr StringRef() noexcept = default;
-
-        StringRef( char const* rawChars ) noexcept;
-
-        constexpr StringRef( char const* rawChars, size_type size ) noexcept
-        :   m_start( rawChars ),
-            m_size( size )
-        {}
-
-        StringRef( std::string const& stdString ) noexcept
-        :   m_start( stdString.c_str() ),
-            m_size( stdString.size() )
-        {}
-
-        explicit operator std::string() const {
-            return std::string(m_start, m_size);
-        }
-
-    public: // operators
-        auto operator == ( StringRef const& other ) const noexcept -> bool;
-        auto operator != (StringRef const& other) const noexcept -> bool {
-            return !(*this == other);
-        }
-
-        auto operator[] ( size_type index ) const noexcept -> char {
-            assert(index < m_size);
-            return m_start[index];
-        }
-
-    public: // named queries
-        constexpr auto empty() const noexcept -> bool {
-            return m_size == 0;
-        }
-        constexpr auto size() const noexcept -> size_type {
-            return m_size;
-        }
-
-        // Returns the current start pointer. If the StringRef is not
-        // null-terminated, throws std::domain_exception
-        auto c_str() const -> char const*;
-
-    public: // substrings and searches
-        // Returns a substring of [start, start + length).
-        // If start + length > size(), then the substring is [start, size()).
-        // If start > size(), then the substring is empty.
-        auto substr( size_type start, size_type length ) const noexcept -> StringRef;
-
-        // Returns the current start pointer. May not be null-terminated.
-        auto data() const noexcept -> char const*;
-
-        constexpr auto isNullTerminated() const noexcept -> bool {
-            return m_start[m_size] == '\0';
-        }
-
-    public: // iterators
-        constexpr const_iterator begin() const { return m_start; }
-        constexpr const_iterator end() const { return m_start + m_size; }
-    };
-
-    auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&;
-    auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&;
-
-    constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef {
-        return StringRef( rawChars, size );
-    }
-} // namespace Catch
-
-constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef {
-    return Catch::StringRef( rawChars, size );
-}
-
-// end catch_stringref.h
-// start catch_preprocessor.hpp
-
-
-#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__
-#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__)))
-#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__)))
-#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__)))
-#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__)))
-#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__)))
-
-#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
-#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__
-// MSVC needs more evaluations
-#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__)))
-#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__))
-#else
-#define CATCH_RECURSE(...)  CATCH_RECURSION_LEVEL5(__VA_ARGS__)
-#endif
-
-#define CATCH_REC_END(...)
-#define CATCH_REC_OUT
-
-#define CATCH_EMPTY()
-#define CATCH_DEFER(id) id CATCH_EMPTY()
-
-#define CATCH_REC_GET_END2() 0, CATCH_REC_END
-#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2
-#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1
-#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT
-#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0)
-#define CATCH_REC_NEXT(test, next)  CATCH_REC_NEXT1(CATCH_REC_GET_END test, next)
-
-#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
-#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ )
-#define CATCH_REC_LIST2(f, x, peek, ...)   f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ )
-
-#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
-#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ )
-#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...)   f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ )
-
-// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results,
-// and passes userdata as the first parameter to each invocation,
-// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c)
-#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
-
-#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0))
-
-#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param)
-#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__
-#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__
-#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF
-#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__)
-#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
-#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__
-#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param))
-#else
-// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF
-#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__)
-#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__
-#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1)
-#endif
-
-#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__
-#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name)
-
-#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__)
-
-#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR
-#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper())
-#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))
-#else
-#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper()))
-#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)))
-#endif
-
-#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\
-    CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__)
-
-#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0)
-#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1)
-#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2)
-#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3)
-#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4)
-#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5)
-#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6)
-#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7)
-#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8)
-#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9)
-#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10)
-
-#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N
-
-#define INTERNAL_CATCH_TYPE_GEN\
-    template struct TypeList {};\
-    template\
-    constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\
-    template class...> struct TemplateTypeList{};\
-    template class...Cs>\
-    constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\
-    template\
-    struct append;\
-    template\
-    struct rewrap;\
-    template class, typename...>\
-    struct create;\
-    template class, typename>\
-    struct convert;\
-    \
-    template \
-    struct append { using type = T; };\
-    template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\
-    struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\
-    template< template class L1, typename...E1, typename...Rest>\
-    struct append, TypeList, Rest...> { using type = L1; };\
-    \
-    template< template class Container, template class List, typename...elems>\
-    struct rewrap, List> { using type = TypeList>; };\
-    template< template class Container, template class List, class...Elems, typename...Elements>\
-    struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\
-    \
-    template