diff --git a/Makefile b/Makefile index 2da81734b..a546f65fd 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ MODULES := \ src/opt/cut src/opt/fxu src/opt/fxch src/opt/rwr src/opt/mfs src/opt/sim \ src/opt/ret src/opt/fret src/opt/res src/opt/lpk src/opt/nwk src/opt/rwt src/opt/rar \ src/opt/cgt src/opt/csw src/opt/dar src/opt/dau src/opt/dsc src/opt/sfm src/opt/sbd \ - src/sat/bsat src/sat/xsat src/sat/satoko src/sat/csat src/sat/msat src/sat/psat src/sat/cnf src/sat/bmc src/sat/glucose src/sat/glucose2 src/sat/kissat \ + src/sat/bsat src/sat/xsat src/sat/satoko src/sat/csat src/sat/msat src/sat/psat src/sat/cnf src/sat/bmc src/sat/glucose src/sat/glucose2 src/sat/kissat src/sat/cadical \ src/bool/bdc src/bool/deco src/bool/dec src/bool/kit src/bool/lucky \ src/bool/rsb src/bool/rpo \ src/proof/pdr src/proof/abs src/proof/live src/proof/ssc src/proof/int \ diff --git a/abclib.dsp b/abclib.dsp index 7f6af6f81..921030b34 100644 --- a/abclib.dsp +++ b/abclib.dsp @@ -2886,6 +2886,374 @@ SOURCE=.\src\sat\kissat\watch.c SOURCE=.\src\sat\kissat\weaken.c # End Source File # End Group +# Begin Group "cadical" + +# PROP Default_Filter "" +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_kitten.c +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_analyze.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_arena.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_assume.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_averages.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_backtrack.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_backward.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_bins.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_block.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_ccadical.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_checker.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_clause.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_collect.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_compact.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_condition.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_config.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_congruence.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_constrain.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_contract.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_cover.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_decide.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_decompose.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_deduplicate.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_definition.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_drattracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_elim.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_elimfast.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_ema.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_extend.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_external.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_external_propagate.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_factor.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_file.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_flags.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_flip.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_format.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_frattracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_gates.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_idruptracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_instantiate.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_internal.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_ipasir.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_lidruptracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_limit.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_logging.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_lookahead.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_lratchecker.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_lrattracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_lucky.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_minimize.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_occs.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_options.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_parse.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_phases.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_probe.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_profile.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_proof.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_propagate.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_queue.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_random.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_reap.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_reduce.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_rephase.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_report.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_resources.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_restart.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_restore.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_score.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_shrink.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_signal.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_solution.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_solver.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_stable.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_stats.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_subsume.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_sweep.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_terminal.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_ternary.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_tier.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_transred.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_unstable.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_util.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_var.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_veripbtracer.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_version.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_vivify.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_walk.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadical_watch.cpp +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadicalSolver.c +# End Source File +# Begin Source File + +SOURCE=.\src\sat\cadical\cadicalTest.c +# End Source File +# End Group # End Group # Begin Group "opt" diff --git a/src/base/abci/abc.c b/src/base/abci/abc.c index 7ec94e2cb..c126df352 100644 --- a/src/base/abci/abc.c +++ b/src/base/abci/abc.c @@ -56155,6 +56155,9 @@ int Abc_CommandAbc9Test( Abc_Frame_t * pAbc, int argc, char ** argv ) } } + extern void cadical_solver_test(); + cadical_solver_test(); + return 0; extern void kissat_solver_test(); kissat_solver_test(); return 0; diff --git a/src/sat/cadical/LICENSE b/src/sat/cadical/LICENSE new file mode 100644 index 000000000..ead29d7a3 --- /dev/null +++ b/src/sat/cadical/LICENSE @@ -0,0 +1,28 @@ +MIT License + +Copyright (c) 2016-2021 Armin Biere, Johannes Kepler University Linz, Austria +Copyright (c) 2020-2021 Mathias Fleury, Johannes Kepler University Linz, Austria +Copyright (c) 2020-2021 Nils Froleyks, Johannes Kepler University Linz, Austria +Copyright (c) 2022-2024 Katalin Fazekas, Vienna University of Technology, Austria +Copyright (c) 2021-2024 Armin Biere, University of Freiburg, Germany +Copyright (c) 2021-2024 Mathias Fleury, University of Freiburg, Germany +Copyright (c) 2023-2024 Florian Pollitt, University of Freiburg, Germany +Copyright (c) 2024-2024 Tobias Faller, University of Freiburg, Germany + +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. diff --git a/src/sat/cadical/VERSION b/src/sat/cadical/VERSION new file mode 100644 index 000000000..a1bba8921 --- /dev/null +++ b/src/sat/cadical/VERSION @@ -0,0 +1 @@ +2.2.0-rc1 diff --git a/src/sat/cadical/arena.hpp b/src/sat/cadical/arena.hpp new file mode 100644 index 000000000..522fce36e --- /dev/null +++ b/src/sat/cadical/arena.hpp @@ -0,0 +1,111 @@ +#ifndef _arena_hpp_INCLUDED +#define _arena_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// This memory allocation arena provides fixed size pre-allocated memory for +// the moving garbage collector 'copy_non_garbage_clauses' in 'collect.cpp' +// to hold clauses which should survive garbage collection. + +// The advantage of using a pre-allocated arena is that the allocation order +// of the clauses can be adapted in such a way that clauses watched by the +// same literal are allocated consecutively. This improves locality during +// propagation and thus is more cache friendly. A similar technique is +// implemented in MiniSAT and Glucose and gives substantial speed-up in +// propagations per second even though it might even almost double peek +// memory usage. Note that in MiniSAT this arena is actually required for +// MiniSAT to be able to use 32 bit clauses references instead of 64 bit +// pointers. This would restrict the maximum number of clauses and thus is +// a restriction we do not want to use anymore. + +// New learned clauses are allocated in CaDiCaL outside of this arena and +// moved to the arena during garbage collection. The additional 'to' space +// required for such a moving garbage collector is only allocated for those +// clauses surviving garbage collection, which usually needs much less +// memory than all clauses. The net effect is that in our implementation +// the moving garbage collector using this arena only needs roughly 50% more +// memory than allocating the clauses directly. Both implementations can be +// compared by varying the 'opts.arenatype' option (which also controls the +// allocation order of clauses during moving them). + +// The standard sequence of using the arena is as follows: +// +// Arena arena; +// ... +// arena.prepare (bytes); +// q1 = arena.copy (p1, bytes1); +// ... +// qn = arena.copy (pn, bytesn); +// CADICAL_assert (bytes1 + ... + bytesn <= bytes); +// arena.swap (); +// ... +// if (!arena.contains (q)) delete q; +// ... +// arena.prepare (bytes); +// q1 = arena.copy (p1, bytes1); +// ... +// qn = arena.copy (pn, bytesn); +// CADICAL_assert (bytes1 + ... + bytesn <= bytes); +// arena.swap (); +// ... +// +// One has to be really careful with 'qi' references to arena memory. + +struct Internal; + +class Arena { + + Internal *internal; + + struct { + char *start, *top, *end; + } from, to; + +public: + Arena (Internal *); + ~Arena (); + + // Prepare 'to' space to hold that amount of memory. Precondition is that + // the 'to' space is empty. The following sequence of 'copy' operations + // can use as much memory in sum as pre-allocated here. + // + void prepare (size_t bytes); + + // Does the memory pointed to by 'p' belong to this arena? More precisely + // to the 'from' space, since that is the only one remaining after 'swap'. + // + bool contains (void *p) const { + char *c = (char *) p; + return (from.start <= c && c < from.top) || + (to.start <= c && c < to.top); + } + + // Allocate that amount of memory in 'to' space. This assumes the 'to' + // space has been prepared to hold enough memory with 'prepare'. Then + // copy the memory pointed to by 'p' of size 'bytes'. Note that it does + // not matter whether 'p' is in 'from' or allocated outside of the arena. + // + char *copy (const char *p, size_t bytes) { + char *res = to.top; + to.top += bytes; + CADICAL_assert (to.top <= to.end); + memcpy (res, p, bytes); + return res; + } + + // Completely delete 'from' space and then replace 'from' by 'to' (by + // pointer swapping). Everything previously allocated (in 'from') and not + // explicitly copied to 'to' with 'copy' becomes invalid. + // + void swap (); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/averages.hpp b/src/sat/cadical/averages.hpp new file mode 100644 index 000000000..3ebdcddfa --- /dev/null +++ b/src/sat/cadical/averages.hpp @@ -0,0 +1,43 @@ +#ifndef _averages_hpp_INCLUDED +#define _averages_hpp_INCLUDED + +#include "global.h" + +#include "ema.hpp" // alphabetically after 'averages.hpp' + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Averages { + + int64_t swapped; + + struct { + + struct { + EMA fast; // average fast (small window) moving glucose level + EMA slow; // average slow (large window) moving glucose level + } glue; + + struct { + EMA fast; // average fast (small window) moving trail level + EMA slow; // average slow (large window) moving trail level + } trail; + + EMA decisions; + + EMA size; // average learned clause size + EMA jump; // average (potential non-chronological) back-jump level + EMA level; // average back track level after conflict + + } current, saved; + + Averages () : swapped (0) {} +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/bins.hpp b/src/sat/cadical/bins.hpp new file mode 100644 index 000000000..aeefeceeb --- /dev/null +++ b/src/sat/cadical/bins.hpp @@ -0,0 +1,28 @@ +#ifndef _bins_hpp_INCLUDED +#define _bins_hpp_INCLUDED + +#include "global.h" + +#include "util.hpp" // Alphabetically after 'bins'. + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +struct Bin { + int lit; + int64_t id; +}; + +typedef vector Bins; + +inline void shrink_bins (Bins &bs) { shrink_vector (bs); } +inline void erase_bins (Bins &bs) { erase_vector (bs); } + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/block.hpp b/src/sat/cadical/block.hpp new file mode 100644 index 000000000..202355f82 --- /dev/null +++ b/src/sat/cadical/block.hpp @@ -0,0 +1,43 @@ +#ifndef _block_hpp_INCLUDED +#define _block_hpp_INCLUDED + +#include "global.h" + +#include "heap.hpp" // Alphabetically after 'block.hpp'. + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct block_more_occs_size { + Internal *internal; + block_more_occs_size (Internal *i) : internal (i) {} + bool operator() (unsigned a, unsigned b); +}; + +typedef heap BlockSchedule; + +class Blocker { + + friend struct Internal; + + vector candidates; + vector reschedule; + BlockSchedule schedule; + + Blocker (Internal *i) : schedule (block_more_occs_size (i)) {} + + void erase () { + erase_vector (candidates); + erase_vector (reschedule); + schedule.erase (); + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/cadical.hpp b/src/sat/cadical/cadical.hpp new file mode 100644 index 000000000..c9ed41332 --- /dev/null +++ b/src/sat/cadical/cadical.hpp @@ -0,0 +1,1351 @@ +#ifndef _cadical_hpp_INCLUDED +#define _cadical_hpp_INCLUDED + +#include "global.h" + +#include +#include +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +/*========================================================================*/ + +// This provides the actual API of the CaDiCaL solver, which is implemented +// in the class 'Solver' below. Beside its constructor and destructor most +// important is the IPASIR part which you can find between 'BEGIN IPASIR' +// and 'END IPASIR' comments below. The following '[Example]' below might +// also be a good starting point to understand the API. + +/*========================================================================*/ + +// The SAT competition standardized the exit code of SAT solvers to the +// following which then is also used return code for 'solve' functions. +// In the following example we use those constants for brevity though. + +enum Status { + SATISFIABLE = 10, + UNSATISFIABLE = 20, + UNKNOWN = 0, +}; + +/*========================================================================*/ + +// [Example] +// +// The internal solver state follows the IPASIR API model used in the +// incremental track of the SAT competition. State transitions are +// triggered by member function calls, declared and described below. +// +// Consider the following code (from 'test/api/example.cpp') of API usage: +// +// CaDiCaL::Solver * solver = new CaDiCaL::Solver; +// +// // ------------------------------------------------------------------ +// // Encode Problem and check without assumptions. +// +// enum { TIE = 1, SHIRT = 2 }; +// +// solver->add (-TIE), solver->add (SHIRT), solver->add (0); +// solver->add (TIE), solver->add (SHIRT), solver->add (0); +// solver->add (-TIE), solver->add (-SHIRT), solver->add (0); +// +// int res = solver->solve (); // Solve instance. +// CADICAL_assert (res == 10); // Check it is 'SATISFIABLE'. +// +// res = solver->val (TIE); // Obtain assignment of 'TIE'. +// CADICAL_assert (res < 0); // Check 'TIE' assigned to 'false'. +// +// res = solver->val (SHIRT); // Obtain assignment of 'SHIRT'. +// CADICAL_assert (res > 0); // Check 'SHIRT' assigned to 'true'. +// +// // ------------------------------------------------------------------ +// // Incrementally solve again under one assumption. +// +// solver->assume (TIE); // Now force 'TIE' to true. +// +// res = solver->solve (); // Solve again incrementally. +// CADICAL_assert (res == 20); // Check it is 'UNSATISFIABLE'. +// +// res = solver->failed (TIE); // Check 'TIE' responsible. +// CADICAL_assert (res); // Yes, 'TIE' in core. +// +// res = solver->failed (SHIRT); // Check 'SHIRT' responsible. +// CADICAL_assert (!res); // No, 'SHIRT' not in core. +// +// // ------------------------------------------------------------------ +// // Incrementally solve once more under another assumption. +// +// solver->assume (-SHIRT); // Now force 'SHIRT' to false. +// +// res = solver->solve (); // Solve again incrementally. +// CADICAL_assert (res == 20); // Check it is 'UNSATISFIABLE'. +// +// res = solver->failed (TIE); // Check 'TIE' responsible. +// CADICAL_assert (!res); // No, 'TIE' not in core. +// +// res = solver->failed (-SHIRT); // Check '!SHIRT' responsible. +// CADICAL_assert (res); // Yes, '!SHIRT' in core. +// +// // ------------------------------------------------------------------ +// +// delete solver; + +/*========================================================================*/ + +// [States and Transitions] +// +// Compared to IPASIR we also use an 'ADDING' state in which the solver +// stays while adding non-zero literals until the clause is completed +// through adding a zero literal. The additional 'INITIALIZING', +// 'CONFIGURING' and 'DELETING' states are also not part of IPASIR but also +// useful for testing and debugging. +// +// We have the following transitions which are all synchronous except for +// the reentrant 'terminate' call: +// +// new +// INITIALIZING --------------------------> CONFIGURING +// +// set / trace +// CONFIGURING --------------------------> CONFIGURING +// +// add (non zero literal) +// VALID --------------------------> ADDING +// +// add (zero literal) +// VALID --------------------------> STEADY +// +// assume (non zero literal) +// READY --------------------------> STEADY +// +// solve +// READY --------------------------> SOLVING +// +// (internal) +// SOLVING --------------------------> SOLVED +// +// val (non zero literal) +// SATISFIED --------------------------> SATISFIED +// +// failed (non zero literal) +// UNSATISFIED --------------------------> UNSATISFIED +// +// implied (non zero literal) +// INCONCLUSIVE --------------------------> INCONCLUSIVE +// +// delete +// VALID --------------------------> DELETING +// +// where +// +// SOLVED = SATISFIED | UNSATISFIED | INCONCLUSIVE +// READY = CONFIGURING | STEADY | SOLVED +// VALID = READY | ADDING +// INVALID = INITIALIZING | DELETING +// +// The 'SOLVING' state is only visible in different contexts, i.e., from +// another thread or from a signal handler. It is used to implement +// 'terminate'. Here is the only asynchronous transition: +// +// terminate (asynchronously) +// SOLVING -------------------------> STEADY +// +// The important behaviour to remember is that adding, assuming or +// constraining a literal (immediately) destroys the satisfying assignment +// in the 'SATISFIED' state and vice versa resets all assumptions in the +// 'UNSATISFIED' state. This is exactly the behaviour required by the IPASIR +// interface. +// +// Furthermore, the model can only be queried through 'val' in the +// 'SATISFIED' state, while extracting failed assumptions with 'failed' only +// in the 'UNSATISFIED' state. Solving can only be started in the 'STEADY ' +// or 'CONFIGURING' state or after the previous call to 'solve' yielded an +// 'INCONCLUSIVE , 'SATISFIED' or 'UNSATISFIED' state. +// +// All literals have to be valid literals too, i.e., 32-bit integers +// different from 'INT_MIN'. If any of these requirements is violated the +// solver aborts with an 'API contract violation' message. +// +// HINT: If you do not understand why a contract is violated you can run +// 'mobical' on the failing API call trace. Point the environment variable +// 'CADICAL_API_TRACE' to the file where you want to save the trace during +// execution of your program linking against the library. You probably need +// for 'mobical' to use the option '--do-not-enforce-contracts' though to +// force running into the same contract violation. +// +// Additional API calls (like 'freeze' and 'melt') do not change the state +// of the solver and are all described below. + +/*========================================================================*/ + +// States are represented by a bit-set in order to combine them. + +enum State { + INITIALIZING = 1, // during initialization (invalid) + CONFIGURING = 2, // configure options (with 'set') + STEADY = 4, // ready to call 'solve' + ADDING = 8, // adding clause literals (zero missing) + SOLVING = 16, // while solving (within 'solve') + SATISFIED = 32, // satisfiable allows 'val' + UNSATISFIED = 64, // unsatisfiable allows 'failed' + DELETING = 128, // during and after deletion (invalid) + INCONCLUSIVE = 256, // unknown allows 'implied' + + // These combined states are used to check contracts. + + READY = CONFIGURING | STEADY | SATISFIED | UNSATISFIED | INCONCLUSIVE, + VALID = READY | ADDING, + INVALID = INITIALIZING | DELETING +}; + +/*------------------------------------------------------------------------*/ + +// Opaque classes needed in the API and declared in the same namespace. + +class File; +class Testing; +struct Internal; +struct External; + +/*------------------------------------------------------------------------*/ + +// Forward declaration of call-back classes. See bottom of this file. + +class Learner; +class FixedAssignmentListener; +class Terminator; +class ClauseIterator; +class WitnessIterator; +class ExternalPropagator; +class Tracer; +struct InternalTracer; +class FileTracer; +class StatTracer; + +/*------------------------------------------------------------------------*/ + +class Solver { + +public: + // ====== BEGIN IPASIR =================================================== + + // This section implements the corresponding IPASIR functionality. + + Solver (); + ~Solver (); + + static const char *signature (); // name of this library + + // Core functionality as in the IPASIR incremental SAT solver interface. + // (recall 'READY = CONFIGURING | STEADY | SATISFIED | UNSATISFIED'). + // Further note that 'lit' is required to be different from 'INT_MIN' and + // different from '0' except for 'add'. + + // Add valid literal to clause or zero to terminate clause. + // + // require (VALID) // recall 'VALID = READY | ADDING' + // if (lit) ensure (ADDING) // and thus VALID but not READY + // if (!lit) ensure (STEADY ) // and thus READY + // + void add (int lit); + + // Here are functions simplifying clause addition. The given literals + // should all be valid (different from 'INT_MIN' and different from '0'). + // + // require (VALID) + // ensure (STEADY ) + // + void clause (int); // Add unit clause. + void clause (int, int); // Add binary clause. + void clause (int, int, int); // Add ternary clause. + void clause (int, int, int, int); // Add quaternary clause. + void clause (int, int, int, int, int); // Add quinternary clause. + void clause (const std::vector &); // Add literal vector as clause. + void clause (const int *, size_t); // Add literal array as clause. + + // This function can be used to check if the formula is already + // inconsistent (contains the empty clause or was proven to be + // root-level unsatisfiable). + + bool inconsistent (); + + // Assume valid non zero literal for next call to 'solve'. These + // assumptions are reset after the call to 'solve' as well as after + // returning from 'simplify' and 'lookahead. + // + // require (READY) + // ensure (STEADY ) + // + void assume (int lit); + + // Try to solve the current formula. Returns + // + // 0 = UNKNOWN (limit reached or interrupted through 'terminate') + // 10 = SATISFIABLE + // 20 = UNSATISFIABLE + // + // require (READY) + // ensure (INCONCLUSIVE | SATISFIED | UNSATISFIED) + // + // Note, that while in this call the solver actually transitions to state + // 'SOLVING', which however is only visible from a different context, + // i.e., from a different thread or from a signal handler. Only right + // before returning from this call it goes into a 'READY' state. + // + int solve (); + + // Get value (-lit=false, lit=true) of valid non-zero literal. + // + // require (SATISFIED) + // ensure (SATISFIED) + // + int val (int lit); + + // Try to flip the value of the given literal without falsifying the + // formula. Returns 'true' if this was successful. Otherwise the model is + // not changed and 'false' is returned. If a literal was eliminated or + // substituted flipping will fail on that literal and in particular the + // solver will not taint it nor restore any clauses. + // + // The 'flip' function can only flip the value of a variables not acting + // as witness on the reconstruction stack. + // + // As a side effect of calling this function first all assigned variables + // are propagated again without using blocking literal. Thus the first + // call to this function after obtaining a model adds a substantial + // overhead. Subsequent calls will not need to properly propagate again. + // + // Furthermore if the reconstruction stack is non-empty and has been + // traversed to reconstruct a full extended model for eliminated + // variables (and to satisfy removed blocked clauses), the values of these + // witness variables obtained via 'val' before become invalid. The user + // thus will need to call 'val' again after calling 'flip' which will + // trigger then a traversal of the reconstruction stack. + // + // So try to avoid mixing 'flip' and 'val' (for efficiency only). + // Further, this functionality is currently not supported in the presence + // of an external propagator. + // + // require (SATISFIED) + // ensure (SATISFIED) + // + bool flip (int lit); + + // Same as 'flip' without actually flipping it. This functionality is + // currently not supported in the presence of an external propagator. + // + // require (SATISFIED) + // ensure (SATISFIED) + // + bool flippable (int lit); + + // Determine whether the valid non-zero literal is in the core. + // Returns 'true' if the literal is in the core and 'false' otherwise. + // Note that the core does not have to be minimal. + // + // require (UNSATISFIED) + // ensure (UNSATISFIED) + // + bool failed (int lit); + + // Add call-back which is checked regularly for termination. There can + // only be one terminator connected. If a second (non-zero) one is added + // the first one is implicitly disconnected. + // + // require (VALID) + // ensure (VALID) + // + void connect_terminator (Terminator *terminator); + void disconnect_terminator (); + + // Add call-back which allows to export learned clauses. + // + // require (VALID) + // ensure (VALID) + // + void connect_learner (Learner *learner); + void disconnect_learner (); + + // ====== END IPASIR ===================================================== + + // Add call-back which allows to observe when a variable is fixed. + // + // require (VALID) + // ensure (VALID) + // + void connect_fixed_listener (FixedAssignmentListener *fixed_listener); + void disconnect_fixed_listener (); + + // ====== BEGIN IPASIR-UP ================================================ + + // Add call-back which allows to learn, propagate and backtrack based on + // external constraints. Only one external propagator can be connected + // and after connection every related variables must be 'observed' (use + // 'add_observed_var' function). + // Disconnection of the external propagator resets all the observed + // variables. + // + // require (VALID) + // ensure (VALID) + // + void connect_external_propagator (ExternalPropagator *propagator); + void disconnect_external_propagator (); + + // Mark as 'observed' those variables that are relevant to the external + // propagator. External propagation, clause addition during search and + // notifications are all over these observed variables. + // A variable can not be observed without having an external propagator + // connected. Observed variables are "frozen" internally, and so + // inprocessing will not consider them as candidates for elimination. + // An observed variable is allowed to be a fresh variable and it can be + // added also during solving. + // + // require (VALID_OR_SOLVING) + // ensure (VALID_OR_SOLVING) + // + void add_observed_var (int var); + + // Removes the 'observed' flag from the given variable. A variable can be + // set unobserved only between solve calls, not during it (to guarantee + // that no yet unexplained external propagation involves it). + // + // require (VALID) + // ensure (VALID) + // + void remove_observed_var (int var); + + // Removes all the 'observed' flags from the variables. Disconnecting the + // propagator invokes this step as well. + // + // require (VALID) + // ensure (VALID) + // + void reset_observed_vars (); + + // Get reason of valid observed literal (true = it is an observed variable + // and it got assigned by a decision during the CDCL loop. Otherwise: + // false. + // + // require (VALID_OR_SOLVING) + // ensure (VALID_OR_SOLVING) + // + bool is_decision (int lit); + + // Force solve to backtrack to certain decision level. Can be called only + // during 'cb_decide' of a connected External Propagator. + // Invoking in any other time will not have an effect. + // If the call had an effect, the External Propagator will be notified + // about the backtrack via 'notify_backtrack'. + // + // require (SOLVING) + // ensure (SOLVING) + // + void force_backtrack (size_t new_level); + + // ====== END IPASIR-UP ================================================== + + //------------------------------------------------------------------------ + // Adds a literal to the constraint clause. Same functionality as 'add' + // but the clause only exists for the next call to solve (same lifetime as + // assumptions). Only one constraint may exists at a time. A new + // constraint replaces the old. The main application of this functionality + // is the model checking algorithm IC3. See our FMCAD'21 paper + // [FroleyksBiere-FMCAD'19] for more details. + // + // Add valid literal to the constraint clause or zero to terminate it. + // + // require (VALID) // recall 'VALID = READY | + // ADDING' if (lit) ensure (ADDING) // and thus VALID but not + // READY if (!lit) && !adding_clause ensure (STEADY ) // and thus READY + // + void constrain (int lit); + + // Determine whether the constraint was used to proof the + // unsatisfiability. Note that the formula might still be unsatisfiable + // without the constraint. + // + // require (UNSATISFIED) + // ensure (UNSATISFIED) + // + bool constraint_failed (); + + // Collects a subset of those literals that are implied by unit + // propagation by assuming the currently defined (potentially empty) set + // of assumptions (see IPASIR assume(lit)) function. In case unit + // propgation over the defined set of assumptions (or over the clause + // database on its own) leads to conflict, the function returns 20 and the + // content of 'implicants' is undefined. In case unit propagation happens + // to satisfy all the clauses (not probable, but not impossible), the + // function returns 10 and 'implicants' is a solution of the current + // formula under the current assumptions (after solution reconstruction). + // In any other case, the function returns 0 (indicating 'UNKNOWN') and + // 'implicants' lists the non-conflicting current value of the trail. + + // Returns + // + // 0 = UNKNOWN (unit propagation did not lead to a conflict nor to a + // complete assignment, or limit reached or interrupted + // through 'terminate') + // 10 = SATISFIABLE + // 20 = UNSATISFIABLE + + // require (READY) + // ensure (INCONCLUSIVE | SATISFIED | UNSATISFIED) + int propagate (); + + // + // require (INCONCLUSIVE) + // ensure (INCONCLUSIVE) + // + void implied (std::vector &implicants); + + //------------------------------------------------------------------------ + // This function determines a good splitting literal. The result can be + // zero if the formula is proven to be satisfiable or unsatisfiable. This + // can then be checked by 'state ()'. If the formula is empty and + // the function is not able to determine satisfiability also zero is + // returned but the state remains steady. + // + // require (READY) + // ensure (INCONCLUSIVE |SATISFIED|UNSATISFIED) + // + int lookahead (void); + + struct CubesWithStatus { + int status; + std::vector> cubes; + }; + + CubesWithStatus generate_cubes (int, int min_depth = 0); + + void reset_assumptions (); + void reset_constraint (); + + // Return the current state of the solver as defined above. + // + const State &state () const { return _state; } + + // Similar to 'state ()' but using the standard competition exit codes of + // '10' for 'SATISFIABLE', '20' for 'UNSATISFIABLE' and '0' otherwise. + // + int status () const { + if (_state == SATISFIED) + return 10; + else if (_state == UNSATISFIED) + return 20; + else + return 0; + } + + /*----------------------------------------------------------------------*/ + + static const char *version (); // return version string + + /*----------------------------------------------------------------------*/ + // Copy 'this' into a fresh 'other'. The copy procedure is not a deep + // clone, but only copies irredundant clauses and units. It also makes + // sure that witness reconstruction works with the copy as with the + // original formula such that both solvers have the same models. + // Assumptions are not copied. Options however are copied as well as + // flags which remember the current state of variables in preprocessing. + // + // require (READY) // for 'this' + // ensure (READY) // for 'this' + // + // other.require (CONFIGURING) + // other.ensure (CONFIGURING | STEADY ) + // + void copy (Solver &other) const; + + /*----------------------------------------------------------------------*/ + // Variables are usually added and initialized implicitly whenever a + // literal is used as an argument except for the functions 'val', 'fixed', + // 'failed' and 'frozen'. However, the library internally keeps a maximum + // variable index, which can be queried. + // With factor (BVA) the solver might also add new variables. In that case + // the user is required to use this to check which variables are currently + // free before adding new variables of their own. + // The alternative is to reserve variables in batches with + // 'reserve_difference'. Using 'reserve' in combination with any technique + // that could add variables (currently only factor) is not advised. + // + // require (VALID | SOLVING) + // ensure (VALID | SOLVING) + // + int vars (); + + // Increase the maximum variable index explicitly. This function makes + // sure that at least 'min_max_var' variables are initialized. Since it + // might need to reallocate tables, it destroys a satisfying assignment + // and has the same state transition and conditions as 'assume' etc. + // + // require (READY) + // ensure (STEADY ) + // + void reserve (int min_max_var); + + // Increase the maximum variable index by a number of new variables. + // initializes 'number_of_vars' new variables and protects them from + // being used by the solver as extension variables (BVA). + // It returns the new maximum variable index which is the highest + // variable name of the consecutive range of newly reserved variables. + // It has the same state transition and conditions as 'reserve' above. + // + // require (READY) + // ensure (STEADY ) + // + int reserve_difference (int number_of_vars); + +#ifndef CADICAL_NTRACING + //------------------------------------------------------------------------ + // This function can be used to write API calls to a file. The same + // format is used which 'mobical' can read, execute and also shrink + // through delta debugging. + // + // Tracing API calls can also be achieved by using the environment + // variable 'CADICAL_API_TRACE'. That alternative is useful if you do not + // want to change the source code using the solver, e.g., if you only have + // a binary with the solver linked in. However, that method only allows + // to trace one solver instance, while with the following function API + // tracing can be enabled for different solver instances individually. + // + // The solver will flush the file after every trace API call but does not + // close it during deletion. It remains owned by the user of the library. + // + // require (VALID) + // ensure (VALID) + // + void trace_api_calls (FILE *file); +#endif + + //------------------------------------------------------------------------ + // Option handling. + + // Determine whether 'name' is a valid option name. + // + static bool is_valid_option (const char *name); + + // Determine whether 'name' enables a specific preprocessing technique. + // + static bool is_preprocessing_option (const char *name); + + // Determine whether 'arg' is a valid long option of the form '--', + // '--=' or '--no-' similar to 'set_long_option' below. + // Legal values are 'true', 'false', or '[-][e]'. + + static bool is_valid_long_option (const char *arg); + + // Get the current value of the option 'name'. If 'name' is invalid then + // zero is returned. Here '--...' arguments as invalid options. + // + int get (const char *name); + + // Set the default verbose message prefix (default "c "). + // + void prefix (const char *verbose_message_prefix); + + // Explicit version of setting an option. If the option '' exists + // and '' can be parsed then 'true' is returned. If the option value + // is out of range the actual value is computed as the closest (minimum or + // maximum) value possible, but still 'true' is returned. + // + // require (CONFIGURING) + // ensure (CONFIGURING) + // + // Thus options can only bet set right after initialization. + // + bool set (const char *name, int val); + + // This function accepts options in command line syntax: + // + // '--=', '--', or '--no-' + // + // It actually calls the previous 'set' function after parsing 'arg'. The + // same values are expected as for 'is_valid_long_option' above and as + // with 'set' any value outside of the range of legal values for a + // particular option are set to either the minimum or maximum depending on + // which side of the valid interval they lie. + // + // require (CONFIGURING) + // ensure (CONFIGURING) + // + bool set_long_option (const char *arg); + + // Determine whether 'name' is a valid configuration. + // + static bool is_valid_configuration (const char *); + + // Overwrite (some) options with the forced values of the configuration. + // The result is 'true' iff the 'name' is a valid configuration. + // + // require (CONFIGURING) + // ensure (CONFIGURING) + // + bool configure (const char *); + + // Increase preprocessing and inprocessing limits by '10^'. Values + // below '0' are ignored and values above '9' are reduced to '9'. + // + // require (READY) + // ensure (READY) + // + void optimize (int val); + + // Specify search limits, where currently 'name' can be "conflicts", + // "decisions", "preprocessing", or "localsearch". The first two limits + // are unbounded by default. Thus using a negative limit for conflicts or + // decisions switches back to the default of unlimited search (for that + // particular limit). The preprocessing limit determines the number of + // preprocessing rounds, which is zero by default. Similarly, the local + // search limit determines the number of local search rounds (also zero by + // default). As with 'set', the return value denotes whether the limit + // 'name' is valid. These limits are only valid for the next 'solve' or + // 'simplify' call and reset to their default after 'solve' returns (as + // well as overwritten and reset during calls to 'simplify' and + // 'lookahead'). We actually also have an internal "terminate" limit + // which however should only be used for testing and debugging. + // + // require (READY) + // ensure (READY) + // + bool limit (const char *arg, int val); + bool is_valid_limit (const char *arg); + + // The number of currently active variables and clauses can be queried by + // these functions. Variables become active if a clause is added with it. + // They become inactive if they are eliminated or fixed at the root level + // Clauses become inactive if they are satisfied, subsumed, eliminated. + // Redundant clauses are reduced regularly and thus the 'redundant' + // function is less useful. + // + // require (VALID) + // ensure (VALID) + // + int active () const; // Number of active variables. + int64_t redundant () const; // Number of active redundant clauses. + int64_t irredundant () const; // Number of active irredundant clauses. + + //------------------------------------------------------------------------ + // This function executes the given number of preprocessing rounds. It is + // similar to 'solve' with 'limits ("preprocessing", rounds)' except that + // no CDCL nor local search, nor lucky phases are executed. The result + // values are also the same: 0=UNKNOWN, 10=SATISFIABLE, 20=UNSATISFIABLE. + // As 'solve' it resets current assumptions and limits before returning. + // The numbers of rounds should not be negative. If the number of rounds + // is zero only clauses are restored (if necessary) and top level unit + // propagation is performed, which both take some time. + // + // require (READY) + // ensure (INCONCLUSIVE | SATISFIED | UNSATISFIED) + // + int simplify (int rounds = 3); + + //------------------------------------------------------------------------ + // Force termination of 'solve' asynchronously. + // + // require (SOLVING | READY) + // ensure (INCONCLUSIVE ) // actually not immediately (synchronously) + // + void terminate (); + + //------------------------------------------------------------------------ + + // We have the following common reference counting functions, which avoid + // to restore clauses but require substantial user guidance. This was the + // only way to use inprocessing in incremental SAT solving in Lingeling + // (and before in MiniSAT's 'freeze' / 'thaw') and which did not use + // automatic clause restoring. In general this is slower than + // restoring clauses and should not be used. + // + // In essence the user freezes variables which potentially are still + // needed in clauses added or assumptions used after the next 'solve' + // call. As in Lingeling you can freeze a variable multiple times, but + // then have to melt it the same number of times again in order to enable + // variable eliminating on it etc. The arguments can be literals + // (negative indices) but conceptually variables are frozen. + // + // In the old way of doing things without restore you should not use a + // variable incrementally (in 'add' or 'assume'), which was used before + // and potentially could have been eliminated in a previous 'solve' call. + // This can lead to spurious satisfying assignment. In order to check + // this API contract one can use the 'checkfrozen' option. This has the + // drawback that restoring clauses implicitly would fail with a fatal + // error message even if in principle the solver could just restore + // clauses. Thus this option is disabled by default. + // + // See our SAT'19 paper [FazekasBiereScholl-SAT'19] for more details. + // + // require (VALID) + // ensure (VALID) + // + bool frozen (int lit) const; + void freeze (int lit); + void melt (int lit); // Also needs 'require (frozen (lit))'. + + //------------------------------------------------------------------------ + + // Root level assigned variables can be queried with this function. + // It returns '1' if the literal is implied by the formula, '-1' if its + // negation is implied, or '0' if this is unclear at this point. + // + // require (VALID) + // ensure (VALID) + // + int fixed (int lit) const; + + //------------------------------------------------------------------------ + // Force the default decision phase of a variable to a certain value. + // + void phase (int lit); + void unphase (int lit); + + //------------------------------------------------------------------------ + + // Enables clausal proof tracing in DRAT format and returns 'true' if + // successfully opened for writing. Writing proofs has to be enabled + // before calling 'solve', 'add' and 'dimacs', that is in state + // 'CONFIGURING'. Otherwise only partial proofs would be written. + // + // require (CONFIGURING) + // ensure (CONFIGURING) + // + bool trace_proof (FILE *file, const char *name); // Write DRAT proof. + bool trace_proof (const char *path); // Open & write proof. + + // Flushing the proof trace file eventually calls 'fflush' on the actual + // file or pipe and thus if this function returns all the proof steps + // should have been written (with the same guarantees as 'fflush'). + // + // The additional optional argument forces to print the number of addition + // and deletion steps in the proof even if the verbosity level is zero but + // not if quiet is set as well. The default for the stand-alone solver is + // to print this information (in the 'closing proof' section) but for API + // usage of the library we want to stay silent unless explicitly requested + // or verbosity is non-zero (and as explained quiet is not set). + // + // This function can be called multiple times. + // + // require (VALID) + // ensure (VALID) + // + void flush_proof_trace (bool print = false); + + // Close proof trace early. Similar to 'flush' we allow the user to + // control with 'print' in a more fine-grained way whether statistics + // about the size of the written proof file and if compressed on-the-fly + // the number of actual bytes written (including deflation percentage) are + // printed. Before actually closing (or detaching in case of writing to + // '') we check whether 'flush_proof_trace' was called since the + // last time a proof step (addition or deletion) was traced. If this is + // not the case we would call 'flush_proof_trace' with the same 'print' + // argument. + // + // require (VALID) + // ensure (VALID) + // + void close_proof_trace (bool print = false); + + // Enables clausal proof tracing with or without antecedents using + // the Tracer interface defined in 'tracer.hpp' + // + // InternalTracer, StatTracer and FileTracer for internal use + // + // require (CONFIGURING) + // ensure (CONFIGURING) + // + void connect_proof_tracer (Tracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (InternalTracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (StatTracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (FileTracer *tracer, bool antecedents, + bool finalize_clauses = false); + + // Triggers the conclusion of incremental proofs. + // if the solver is SATISFIED it will trigger extend () + // and give the model to the proof tracer through conclude_sat () + // if the solver is UNSATISFIED it will trigger failing () + // which will learn new clauses as explained below: + // In case of failed assumptions will provide a core negated + // as a clause through the proof tracer interface. + // With a failing constraint these can be multiple clauses. + // Then it will trigger a conclude_unsat event with the id(s) + // of the newly learnt clauses or the id of the global conflict. + // In case the solver is in UNKNOWN, it will collect the currently + // entrailed literals and add them to the proof. + // + // require (SATISFIED || UNSATISFIED || UNKNOWN) + // ensure (SATISFIED || UNSATISFIED || UNKNOWN) + // + void conclude (); + + // Disconnect proof tracer. If this is not done before deleting + // the tracer will be deleted. Returns true if successful. + // + // require (VALID) + // ensure (VALID) + // + bool disconnect_proof_tracer (Tracer *tracer); + bool disconnect_proof_tracer (StatTracer *tracer); + bool disconnect_proof_tracer (FileTracer *tracer); + + //------------------------------------------------------------------------ + + static void usage (); // print usage information for long options + + static void configurations (); // print configuration usage options + + // require (!DELETING) + // ensure (!DELETING) + // + void statistics (); // print statistics + void resources (); // print resource usage (time and memory) + + // require (VALID) + // ensure (VALID) + // + void options (); // print current option and value list + + //------------------------------------------------------------------------ + // Traverse irredundant clauses or the extension stack in reverse order. + // + // The return value is false if traversal is aborted early due to one of + // the visitor functions returning false. See description of the + // iterators below for more details on how to use these functions. + // + // require (VALID) + // ensure (VALID) + // + bool traverse_clauses (ClauseIterator &) const; + bool traverse_witnesses_backward (WitnessIterator &) const; + bool traverse_witnesses_forward (WitnessIterator &) const; + + //------------------------------------------------------------------------ + // Files with explicit path argument support compressed input and output + // if appropriate helper functions 'gzip' etc. are available. They are + // called through opening a pipe to an external command. + // + // If the 'strict' argument is zero then the number of variables and + // clauses specified in the DIMACS headers are ignored, i.e., the header + // 'p cnf 0 0' is always legal. If the 'strict' argument is larger '1' + // strict formatting of the header is required, i.e., single spaces + // everywhere and no trailing white space. + // + // Returns zero if successful and otherwise an error message. + // + // require (VALID) + // ensure (VALID) + // + const char *read_dimacs (FILE *file, const char *name, int &vars, + int strict = 1); + + const char *read_dimacs (const char *path, int &vars, int strict = 1); + + // The following routines work the same way but parse both DIMACS and + // INCCNF files (with 'p inccnf' header and 'a ' lines). If the + // parser finds and 'p inccnf' header or cubes then '*incremental' is set + // to true and the cubes are stored in the given vector (each cube + // terminated by a zero). + + const char *read_dimacs (FILE *file, const char *name, int &vars, + int strict, bool &incremental, + std::vector &cubes); + + const char *read_dimacs (const char *path, int &vars, int strict, + bool &incremental, std::vector &cubes); + + //------------------------------------------------------------------------ + // Write current irredundant clauses and all derived unit clauses + // to a file in DIMACS format. Clauses on the extension stack are + // not included, nor any redundant clauses. + // + // The 'min_max_var' parameter gives a lower bound on the number '' + // of variables used in the DIMACS 'p cnf ...' header. + // + // Returns zero if successful and otherwise an error message. + // + // require (VALID) + // ensure (VALID) + // + const char *write_dimacs (const char *path, int min_max_var = 0); + + // The extension stack for reconstruction a solution can be written too. + // + const char *write_extension (const char *path); + + // Print build configuration to a file with prefix 'c '. If the file + // is '' or '' then terminal color codes might be used. + // + static void build (FILE *file, const char *prefix = "c "); + +private: + //==== start of state ==================================================== + + // The solver is in the state ADDING if either the current clause or the + // constraint (or both) is not yet terminated. + bool adding_clause; + bool adding_constraint; + + State _state; // API states as discussed above. + + /*----------------------------------------------------------------------*/ + + // The 'Solver' class is a 'facade' object for 'External'. It exposes the + // public API of 'External' but hides everything else (except for the some + // private functions). It is supposed to make it easier to understand the + // API and use the solver through the API. + + // This approach has the benefit of decoupling this header file from all + // internal data structures, which is particularly useful if the rest of + // the source is not available. For instance if only a CaDiCaL library is + // installed in a system, then only this header file has to be installed + // too, and still allows to compile and link against the library. + + /*----------------------------------------------------------------------*/ + + // More precisely the CaDiCaL code is split into three layers: + // + // Solver: facade object providing the actual API of the solver + // External: communication layer between 'Solver' and 'Internal' + // Internal: the actual solver code + // + // The 'External' and 'Internal' layers are declared and implemented in + // the corresponding '{external,internal}.{hpp,cpp}' files (as expected), + // while the 'Solver' facade class is defined in 'cadical.hpp' (here) but + // implemented in 'solver.cpp'. The reason for this naming mismatch is, + // that we want to use 'cadical.hpp' for the library header (this header + // file) and call the binary of the stand alone SAT also 'cadical', which + // is more naturally implemented in 'cadical.cpp'. + // + // Separating 'External' from 'Internal' also allows us to map external + // literals to internal literals, which is useful with many fixed or + // eliminated variables (during 'compact' the internal variable range is + // reduced and external variables are remapped). Such an approach is also + // necessary, if we want to use extended resolution in the future (such as + // bounded variable addition). + // + Internal *internal; // Hidden internal solver. + External *external; // Hidden API to internal solver mapping. + + friend class Testing; // Access to 'internal' for testing only! + +#ifndef CADICAL_NTRACING + // The API calls to the solver can be traced by setting the environment + // variable 'CADICAL_API_TRACE' to point to the path of a file to which + // API calls are written. The same format is used which 'mobical' can + // read, execute and also shrink through delta debugging. + // + // The environment variable is read in the constructor and the trace is + // opened for writing and then closed again in the destructor. + // + // Alternatively one case use 'trace_api_calls'. Both + // + bool close_trace_api_file; // Close file if owned by solver it. + FILE *trace_api_file; // Also acts as flag that we are tracing. + + static bool tracing_api_through_environment; + + //===== end of state ==================================================== + + void trace_api_call (const char *) const; + void trace_api_call (const char *, int) const; + void trace_api_call (const char *, const char *) const; + void trace_api_call (const char *, const char *, int) const; +#endif + + void transition_to_steady_state (); + + //------------------------------------------------------------------------ + // Used in the stand alone solver application 'App' and the model based + // tester 'Mobical'. So only these two classes need direct access to the + // otherwise more application specific functions listed here together with + // the internal DIMACS parser. + + friend class App; + friend class Mobical; + friend class Parser; + + // Read solution in competition format for debugging and testing. + // + // require (VALID) + // ensure (VALID) + // + const char *read_solution (const char *path); + + // Cross-compilation with 'MinGW' needs some work-around for 'printf' + // style printing of 64-bit numbers including warning messages. The + // followings lines are copies of similar code in 'inttypes.hpp' but we + // want to keep the 'cadical.hpp' header file stand-alone. + +#ifndef PRINTF_FORMAT +#ifdef __MINGW32__ +#define __USE_MINGW_ANSI_STDIO 1 +#define PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#else +#define PRINTF_FORMAT printf +#endif +#endif + + // This gives warning messages for wrong 'printf' style format string + // usage. Apparently (on 'gcc 9' at least) the first argument is 'this' + // here. + // + // TODO: support for other compilers (beside 'gcc' and 'clang'). + + /* +#define CADICAL_ATTRIBUTE_FORMAT(FORMAT_POSITION, \ + VARIADIC_ARGUMENT_POSITION) \ + __attribute__ ((format (PRINTF_FORMAT, FORMAT_POSITION, \ + VARIADIC_ARGUMENT_POSITION))) + */ +#define CADICAL_ATTRIBUTE_FORMAT(FORMAT_POSITION, VARIADIC_ARGUMENT_POSITION) + + // Messages in a common style. + // + // require (VALID | DELETING) + // ensure (VALID | DELETING) + // + void section (const char *); // print section header + void message (const char *, ...) // ordinary message + CADICAL_ATTRIBUTE_FORMAT (2, 3); + + void message (); // empty line - only prefix + void error (const char *, ...) // produce error message + CADICAL_ATTRIBUTE_FORMAT (2, 3); + + // Explicit verbose level ('section' and 'message' use '0'). + // + // require (VALID | DELETING) + // ensure (VALID | DELETING) + // + void verbose (int level, const char *, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + + // Factoring out common code to both 'read_dimacs' functions above. + // + // require (VALID) + // ensure (VALID) + // + const char *read_dimacs (File *, int &, int strict, bool *incremental = 0, + std::vector * = 0); + + // Factored out common code for 'solve', 'simplify' and 'lookahead'. + // + int call_external_solve_and_check_results (bool preprocess_only); + + //------------------------------------------------------------------------ + // Print DIMACS file to '' for debugging and testing purposes, + // including derived units and assumptions. Since it will print in terms + // of internal literals it is otherwise not really useful. To write a + // DIMACS formula in terms of external variables use 'write_dimacs'. + // + // require (!INITIALIZING) + // ensure (!INITIALIZING) + // + void dump_cnf (); + friend struct DumpCall; // Mobical calls 'dump_cnf' in 'DumpCall::execute' + + /*----------------------------------------------------------------------*/ + + // Used in mobical to test external propagation internally. + // These functions should not be called for any other purposes. + // + ExternalPropagator *get_propagator (); + bool observed (int lit); + bool is_witness (int lit); + + friend struct LemmaCall; + friend struct ObserveCall; + friend struct DisconnectCall; + friend class MockPropagator; +}; + +/*========================================================================*/ + +// Connected terminators are checked for termination regularly. If the +// 'terminate' function of the terminator returns true the solver is +// terminated synchronously as soon it calls this function. + +class Terminator { +public: + virtual ~Terminator () {} + virtual bool terminate () = 0; +}; + +// Connected learners which can be used to export learned clauses. +// The 'learning' can check the size of the learn clause and only if it +// returns true then the individual literals of the learned clause are given +// to the learn through 'learn' one by one terminated by a zero literal. + +class Learner { +public: + virtual ~Learner () {} + virtual bool learning (int size) = 0; + virtual void learn (int lit) = 0; +}; + +// Connected listener gets notified whenever the truth value of a variable +// is fixed (for example during inprocessing or due to some derived unit +// clauses). + +class FixedAssignmentListener { +public: + virtual ~FixedAssignmentListener () {} + + virtual void notify_fixed_assignment (int) = 0; +}; + +/*------------------------------------------------------------------------*/ + +// Allows to connect an external propagator to propagate values to variables +// with an external clause as a reason or to learn new clauses during the +// CDCL loop (without restart). + +class ExternalPropagator { + +public: + bool is_lazy = false; // lazy propagator only checks complete assignments + bool are_reasons_forgettable = + false; // Reason external clauses can be deleted + + virtual ~ExternalPropagator () {} + + // Notify the propagator about assignments to observed variables. + // The notification is not necessarily eager. It usually happens before + // the call of propagator callbacks and when a driving clause is leading + // to an assignment. + // + // virtual void notify_assignment (int lit, bool is_fixed) = 0; + virtual void notify_assignment (const std::vector &lits) = 0; + virtual void notify_new_decision_level () = 0; + virtual void notify_backtrack (size_t new_level) = 0; + + // Check by the external propagator the found complete solution (after + // solution reconstruction). If it returns false, the propagator should + // provide an external clause during the next callback or introduce new + // observed variables during this callback. + // + virtual bool cb_check_found_model (const std::vector &model) = 0; + + // Ask the external propagator for the next decision literal. If it + // returns 0, the solver makes its own choice. + // + virtual int cb_decide () { return 0; }; + + // Ask the external propagator if there is an external propagation to make + // under the current assignment. It returns either a literal to be + // propagated or 0, indicating that there is no external propagation under + // the current assignment. + // + virtual int cb_propagate () { return 0; }; + + // Ask the external propagator for the reason clause of a previous + // external propagation step (done by cb_propagate). The clause must be + // added literal-by-literal closed with a 0. Further, the clause must + // contain the propagated literal. + // + // The clause will be learned as an Irredundant Non-Forgettable Clause + // (see below at 'cb_has_external_clause' more details about it). + // + virtual int cb_add_reason_clause_lit (int propagated_lit) { + (void) propagated_lit; + return 0; + }; + + // The following two functions are used to add external clauses to the + // solver during the CDCL loop. The external clause is added + // literal-by-literal and learned by the solver as an irredundant + // (original) input clause. The clause can be arbitrary, but if it is + // root-satisfied or tautology, the solver will ignore it without learning + // it. Root-falsified literals are eagerly removed from the clause. + // Falsified clauses trigger conflict analysis, propagating clauses + // trigger propagation. In case chrono is 0, the solver backtracks to + // propagate the new literal on the right decision level, otherwise it + // potentially will be an out-of-order assignment on the current level. + // Unit clauses always (unless root-satisfied, see above) trigger + // backtracking (independently from the value of the chrono option and + // independently from being falsified or satisfied or unassigned) to level + // 0. Empty clause (or root falsified clause, see above) makes the problem + // unsat and stops the search immediately. A literal 0 must close the + // clause. + // + // The external propagator indicates that there is a clause to add. + // The parameter of the function allows the user to indicate that how + // 'forgettable' is the external clause. Forgettable clauses are allowed + // to be removed by the SAT solver during clause database reduction. + // However, it is up to the solver to decide when actually the clause is + // deleted. For example, unit clauses, even forgettable ones, will not be + // deleted. In case the clause is not 'forgettable' (the parameter is + // false), the solver considers the clause to be irredundant. + // + // In case the solver produces incremental proofs, these external clauses + // are added to the proof during solving at real-time, i.e., the proof + // checker can ignore them until that point (so added as input clause, but + // input after the query line). + // + // Reason clauses of external propagation steps are assumed to be + // forgettable, parameter 'reason_forgettable' can be used to change it. + // + // Currently, every external clause is expected to be over observed + // (therefore frozen) variables, hence no tainting or restore steps + // are performed upon their addition. This will be changed in later + // versions probably. + // + virtual bool cb_has_external_clause (bool &is_forgettable) = 0; + + // The actual function called to add the external clause. + // + virtual int cb_add_external_clause_lit () = 0; +}; + +/*------------------------------------------------------------------------*/ + +// Allows to traverse all remaining irredundant clauses. Satisfied and +// eliminated clauses are not included, nor any derived units unless such +// a unit literal is frozen. Falsified literals are skipped. If the solver +// is inconsistent only the empty clause is traversed. +// +// If 'clause' returns false traversal aborts early. + +class ClauseIterator { +public: + virtual ~ClauseIterator () {} + virtual bool clause (const std::vector &) = 0; +}; + +/*------------------------------------------------------------------------*/ + +// Allows to traverse all clauses on the extension stack together with their +// witness cubes. If the solver is inconsistent, i.e., an empty clause is +// found and the formula is unsatisfiable, then nothing is traversed. +// +// The clauses traversed in 'traverse_clauses' together with the clauses on +// the extension stack are logically equivalent to the original clauses. +// See our SAT'19 paper for more details. +// +// The witness literals can be used to extend and fix an assignment on the +// remaining clauses to satisfy the clauses on the extension stack too. +// +// All derived units of non-frozen variables are included too. +// +// If 'witness' returns false traversal aborts early. + +class WitnessIterator { +public: + virtual ~WitnessIterator () {} + virtual bool witness (const std::vector &clause, + const std::vector &witness, + int64_t id = 0) = 0; +}; + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/cadicalSolver.c b/src/sat/cadical/cadicalSolver.c new file mode 100644 index 000000000..9caea6605 --- /dev/null +++ b/src/sat/cadical/cadicalSolver.c @@ -0,0 +1,305 @@ +/**CFile**************************************************************** + + FileName [cadicalSolver.c] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [SAT solver CaDiCaL by Armin Biere, University of Freiburg] + + Synopsis [https://github.com/arminbiere/cadical] + + Author [Integrated into ABC by Yukio Miyasaka] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: cadicalSolver.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#include "ccadical.h" +#include "cadicalSolver.h" + +ABC_NAMESPACE_IMPL_START + +//////////////////////////////////////////////////////////////////////// +/// DECLARATIONS /// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/// FUNCTION DEFINITIONS /// +//////////////////////////////////////////////////////////////////////// + +/**Function************************************************************* + + Synopsis [allocate solver] + + Description [] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +cadical_solver* cadical_solver_new(void) { + cadical_solver* s = (cadical_solver*)malloc(sizeof(cadical_solver)); + s->p = (void*)ccadical_init(); + s->nVars = 0; + s->vAssumptions = NULL; + s->vCore = NULL; + return s; +} + +/**Function************************************************************* + + Synopsis [delete solver] + + Description [] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void cadical_solver_delete(cadical_solver* s) { + ccadical_release((CCaDiCaL*)s->p); + if(s->vAssumptions) { + Vec_IntFree(s->vAssumptions); + } + if(s->vCore) { + Vec_IntFree(s->vCore); + } + free(s); +} + +/**Function************************************************************* + + Synopsis [add clause] + + Description [cadical takes x and -x as a literal for a variable x > 0, + where 0 is an indicator of the end of a clause. + since variables start from 0 in abc, a variable v is + translated into v + 1 in cadical.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_addclause(cadical_solver* s, int* begin, int* end) { + for(;begin != end; begin++) { + if(*begin & 1) { + ccadical_add((CCaDiCaL*)s->p, -(1 + ((*begin) >> 1))); + } else { + ccadical_add((CCaDiCaL*)s->p, 1 + ((*begin) >> 1) ); + } + } + ccadical_add((CCaDiCaL*)s->p, 0); + return !ccadical_is_inconsistent((CCaDiCaL*)s->p); +} + +/**Function************************************************************* + + Synopsis [solve with resource limits] + + Description [assumptions and inspection limits are not supported.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_solve(cadical_solver* s, int* begin, int* end, ABC_INT64_T nConfLimit, ABC_INT64_T nInsLimit, ABC_INT64_T nConfLimitGlobal, ABC_INT64_T nInsLimitGlobal) { + // inspection limits are not supported + assert(nInsLimit == 0); + assert(nInsLimitGlobal == 0); + // set conflict limits + if(nConfLimit) + ccadical_limit((CCaDiCaL*)s->p, "conflicts", nConfLimit); + if(nConfLimitGlobal && (nConfLimit == 0 || nConfLimit > nConfLimitGlobal)) + ccadical_limit((CCaDiCaL*)s->p, "conflicts", nConfLimitGlobal); + // assumptions + if(begin != end) { + // save + if(s->vAssumptions == NULL) { + s->vAssumptions = Vec_IntAllocArrayCopy(begin, end - begin); + } else { + Vec_IntClear(s->vAssumptions); + Vec_IntGrow(s->vAssumptions, end - begin); + Vec_IntPushArray(s->vAssumptions, begin, end - begin); + } + // assume + for(;begin != end; begin++) { + if(*begin & 1) { + ccadical_assume((CCaDiCaL*)s->p, -(1 + ((*begin) >> 1))); + } else { + ccadical_assume((CCaDiCaL*)s->p, 1 + ((*begin) >> 1) ); + } + } + } + // solve + int res = ccadical_solve((CCaDiCaL*)s->p); + // translate this cadical return value into a corresponding ABC status value + switch(res) { + case 0: // UNDETERMINED + return 0; + case 10: // SATISFIABLE + return 1; + case 20: // UNSATISFIABLE + return -1; + default: + assert(0); + } + return 0; +} + +/**Function************************************************************* + + Synopsis [get unsat core] + + Description [following minisat, return number of literals in core, + which consists of responsible assumptions, negated. + array will be freed by solver.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_final(cadical_solver* s, int** ppArray) { + int v, i; + if(s->vCore == NULL) { + s->vCore = Vec_IntAlloc(Vec_IntSize(s->vAssumptions)); + } else { + Vec_IntClear(s->vCore); + } + Vec_IntForEachEntry(s->vAssumptions, v, i) { + int failed; + if(v & 1) { + failed = ccadical_failed((CCaDiCaL*)s->p, -(1 + (v >> 1))); + } else { + failed = ccadical_failed((CCaDiCaL*)s->p, 1 + (v >> 1) ); + } + if(failed) { + Vec_IntPush(s->vCore, Abc_LitNot(v)); + } + } + *ppArray = Vec_IntArray(s->vCore); + return Vec_IntSize(s->vCore); +} + +/**Function************************************************************* + + Synopsis [get number of variables] + + Description [emulated using "nVars".] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_nvars(cadical_solver* s) { + return s->nVars; +} + +/**Function************************************************************* + + Synopsis [add new variable] + + Description [emulated using "nVars".] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_addvar(cadical_solver* s) { + return s->nVars++; +} + +/**Function************************************************************* + + Synopsis [set number of variables] + + Description [not only emulate with "nVars" but also reserve memory.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +void cadical_solver_setnvars(cadical_solver* s,int n) { + s->nVars = n; + ccadical_reserve((CCaDiCaL*)s->p, n); +} + +/**Function************************************************************* + + Synopsis [get value of variable] + + Description [cadical returns x (true) or -x (false) for a variable x. + note a variable v was translated into v + 1 in cadical.] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +int cadical_solver_get_var_value(cadical_solver* s, int v) { + return ccadical_val((CCaDiCaL*)s->p, v + 1) > 0; +} + + +/**Function************************************************************* + + Synopsis [Solves the given CNF using cadical.] + + Description [] + + SideEffects [] + + SeeAlso [] + +***********************************************************************/ +Vec_Int_t * cadical_solve_cnf( Cnf_Dat_t * pCnf, char * pArgs, int nConfs, int nTimeLimit, int fSat, int fUnsat, int fPrintCex, int fVerbose ) +{ + abctime clk = Abc_Clock(); + Vec_Int_t * vRes = NULL; + int i, * pBeg, * pEnd, RetValue; + if ( fVerbose ) + printf( "CNF stats: Vars = %6d. Clauses = %7d. Literals = %8d. ", pCnf->nVars, pCnf->nClauses, pCnf->nLiterals ); + cadical_solver *pSat = cadical_solver_new(); + cadical_solver_setnvars(pSat, pCnf->nVars); + assert(cadical_solver_nvars(pSat) == pCnf->nVars); + Cnf_CnfForClause( pCnf, pBeg, pEnd, i ) { + if ( !cadical_solver_addclause(pSat, pBeg, pEnd) ) + { + assert( 0 ); // if it happens, can return 1 (unsatisfiable) + return NULL; + } + } + RetValue = cadical_solver_solve(pSat, NULL, NULL, 0, 0, 0, 0); + if ( RetValue == 1 ) + printf( "Result: Satisfiable. " ); + else if ( RetValue == -1 ) + printf( "Result: Unsatisfiable. " ); + else + printf( "Result: Undecided. " ); + if ( RetValue == 1 ) { + vRes = Vec_IntAlloc( pCnf->nVars ); + for ( i = 0; i < pCnf->nVars; i++ ) + Vec_IntPush( vRes, cadical_solver_get_var_value(pSat, i) ); + } + cadical_solver_delete(pSat); + Abc_PrintTime( 1, "Time", Abc_Clock() - clk ); + return vRes; +} + + +//////////////////////////////////////////////////////////////////////// +/// END OF FILE /// +//////////////////////////////////////////////////////////////////////// + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadicalSolver.h b/src/sat/cadical/cadicalSolver.h new file mode 100644 index 000000000..0192ada3c --- /dev/null +++ b/src/sat/cadical/cadicalSolver.h @@ -0,0 +1,76 @@ +/**CFile**************************************************************** + + FileName [cadicalSolver.h] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [SAT solver CaDiCaL by Armin Biere, University of Freiburg] + + Synopsis [https://github.com/arminbiere/cadical] + + Author [Integrated into ABC by Yukio Miyasaka] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: cadicalSolver.h,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#ifndef ABC_SAT_CADICAL_SOLVER_H_ +#define ABC_SAT_CADICAL_SOLVER_H_ + +//////////////////////////////////////////////////////////////////////// +/// INCLUDES /// +//////////////////////////////////////////////////////////////////////// + +#include "aig/gia/gia.h" +#include "sat/cnf/cnf.h" + +//////////////////////////////////////////////////////////////////////// +/// PARAMETERS /// +//////////////////////////////////////////////////////////////////////// + +ABC_NAMESPACE_HEADER_START + +//////////////////////////////////////////////////////////////////////// +/// BASIC TYPES /// +//////////////////////////////////////////////////////////////////////// + +typedef struct cadical_solver_ cadical_solver; +struct cadical_solver_ +{ + void* p; + int nVars; + Vec_Int_t* vAssumptions; + Vec_Int_t* vCore; +}; + + +//////////////////////////////////////////////////////////////////////// +/// MACRO DEFINITIONS /// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/// FUNCTION DECLARATIONS /// +//////////////////////////////////////////////////////////////////////// + +extern cadical_solver* cadical_solver_new(void); +extern void cadical_solver_delete(cadical_solver* s); +extern int cadical_solver_addclause(cadical_solver* s, int* begin, int* end); +extern int cadical_solver_solve(cadical_solver* s, int* begin, int* end, ABC_INT64_T nConfLimit, ABC_INT64_T nInsLimit, ABC_INT64_T nConfLimitGlobal, ABC_INT64_T nInsLimitGlobal); +extern int cadical_solver_final(cadical_solver* s, int** ppArray); +extern int cadical_solver_nvars(cadical_solver* s); +extern int cadical_solver_addvar(cadical_solver* s); +extern void cadical_solver_setnvars(cadical_solver* s,int n); +extern int cadical_solver_get_var_value(cadical_solver* s, int v); +extern Vec_Int_t * cadical_solve_cnf( Cnf_Dat_t * pCnf, char * pArgs, int nConfs, int nTimeLimit, int fSat, int fUnsat, int fPrintCex, int fVerbose ); + +ABC_NAMESPACE_HEADER_END + +#endif + +//////////////////////////////////////////////////////////////////////// +/// END OF FILE /// +//////////////////////////////////////////////////////////////////////// diff --git a/src/sat/cadical/cadicalTest.c b/src/sat/cadical/cadicalTest.c new file mode 100644 index 000000000..f3919160b --- /dev/null +++ b/src/sat/cadical/cadicalTest.c @@ -0,0 +1,210 @@ +/**CFile**************************************************************** + + FileName [cadicalTest.c] + + SystemName [ABC: Logic synthesis and verification system.] + + PackageName [] + + Synopsis [] + + Author [Alan Mishchenko] + + Affiliation [UC Berkeley] + + Date [Ver. 1.0. Started - June 20, 2005.] + + Revision [$Id: cadicalTest.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $] + +***********************************************************************/ + +#include "cadicalSolver.h" + +ABC_NAMESPACE_IMPL_START + +//////////////////////////////////////////////////////////////////////// +/// DECLARATIONS /// +//////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////// +/// FUNCTION DEFINITIONS /// +//////////////////////////////////////////////////////////////////////// +void cadical_solver_test() { + int RetValue; + int Lits[3]; + // test 1 + { + cadical_solver *pSat = cadical_solver_new(); + int a = cadical_solver_addvar(pSat); + int b = cadical_solver_addvar(pSat); + int c = cadical_solver_addvar(pSat); + assert(cadical_solver_nvars(pSat) == 3); + Lits[0] = Abc_Var2Lit(a, 0); + Lits[1] = Abc_Var2Lit(b, 0); + Lits[2] = Abc_Var2Lit(c, 0); + printf("adding (a, b, c)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 3); + assert(RetValue); + Lits[0] = Abc_Var2Lit(a, 0); + Lits[1] = Abc_Var2Lit(b, 1); + printf("adding (a, !b)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 2); + assert(RetValue); + Lits[0] = Abc_Var2Lit(a, 1); + printf("adding (!a)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + assert(RetValue); + RetValue = cadical_solver_solve(pSat, NULL, NULL, 0, 0, 0, 0); + printf("solved: %d\n", RetValue); + assert(RetValue == 1); + int a_val = cadical_solver_get_var_value(pSat, a); + int b_val = cadical_solver_get_var_value(pSat, b); + int c_val = cadical_solver_get_var_value(pSat, c); + printf("a = %d, b = %d, c = %d\n", a_val, b_val, c_val); + assert(a_val == 0); + assert(b_val == 0); + assert(c_val == 1); + cadical_solver_delete(pSat); + printf("test 1 passed\n"); + } + // test 2 + { + cadical_solver *pSat = cadical_solver_new(); + cadical_solver_setnvars(pSat, 2); + assert(cadical_solver_nvars(pSat) == 2); + Lits[0] = Abc_Var2Lit(0, 0); + Lits[1] = Abc_Var2Lit(1, 0); + printf("adding (x0, x1)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 2); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 0); + Lits[1] = Abc_Var2Lit(1, 1); + printf("adding (x0, !x1)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 2); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 1); + Lits[1] = Abc_Var2Lit(1, 1); + printf("adding (!x0, !x1)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 2); + assert(RetValue); + RetValue = cadical_solver_solve(pSat, NULL, NULL, 0, 0, 0, 0); + printf("solved: %d\n", RetValue); + assert(RetValue == 1); + printf("x0 = %d, x1 = %d\n", cadical_solver_get_var_value(pSat, 0), cadical_solver_get_var_value(pSat, 1)); + assert(cadical_solver_get_var_value(pSat, 0) == 1); + assert(cadical_solver_get_var_value(pSat, 1) == 0); + cadical_solver_delete(pSat); + printf("test 2 passed\n"); + } + // test 3 + { + cadical_solver *pSat = cadical_solver_new(); + cadical_solver_setnvars(pSat, 3); + assert(cadical_solver_nvars(pSat) == 3); + Lits[0] = Abc_Var2Lit(0, 1); + Lits[1] = Abc_Var2Lit(1, 0); + Lits[2] = Abc_Var2Lit(2, 1); + printf("adding (!x0, x1, !x2)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 3); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 0); + printf("adding (x0)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + assert(RetValue); + Lits[0] = Abc_Var2Lit(1, 1); + printf("adding (!x1)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + assert(RetValue); + Lits[0] = Abc_Var2Lit(2, 0); + printf("adding (x2)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + RetValue = cadical_solver_solve(pSat, NULL, NULL, 0, 0, 0, 0); + printf("solved: %d\n", RetValue); + assert(RetValue == -1); + cadical_solver_delete(pSat); + printf("test 3 passed\n"); + } + // test 4 + { + cadical_solver *pSat = cadical_solver_new(); + cadical_solver_setnvars(pSat, 3); + assert(cadical_solver_nvars(pSat) == 3); + Lits[0] = Abc_Var2Lit(0, 1); + Lits[1] = Abc_Var2Lit(1, 0); + Lits[2] = Abc_Var2Lit(2, 1); + printf("adding (!x0, x1, !x2)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 3); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 0); + printf("adding (x0)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + assert(RetValue); + Lits[0] = Abc_Var2Lit(1, 1); + printf("assume (!x1)\n"); + RetValue = cadical_solver_solve(pSat, Lits, Lits + 1, 0, 0, 0, 0); + printf("solved: %d\n", RetValue); + assert(RetValue == 1); + printf("x0 = %d, x1 = %d, x2 = %d\n", cadical_solver_get_var_value(pSat, 0), cadical_solver_get_var_value(pSat, 1), cadical_solver_get_var_value(pSat, 2)); + assert(cadical_solver_get_var_value(pSat, 0) == 1); + assert(cadical_solver_get_var_value(pSat, 1) == 0); + assert(cadical_solver_get_var_value(pSat, 2) == 0); + cadical_solver_delete(pSat); + printf("test 4 passed\n"); + } + // test 5 + { + cadical_solver *pSat = cadical_solver_new(); + cadical_solver_setnvars(pSat, 3); + assert(cadical_solver_nvars(pSat) == 3); + Lits[0] = Abc_Var2Lit(0, 1); + Lits[1] = Abc_Var2Lit(1, 0); + Lits[2] = Abc_Var2Lit(2, 1); + printf("adding (!x0, x1, !x2)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 3); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 1); + Lits[1] = Abc_Var2Lit(1, 1); + Lits[2] = Abc_Var2Lit(2, 1); + printf("adding (!x0, !x1, !x2)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 3); + assert(RetValue); + Lits[0] = Abc_Var2Lit(0, 0); + printf("adding (x0)\n"); + RetValue = cadical_solver_addclause(pSat, Lits, Lits + 1); + assert(RetValue); + Lits[0] = Abc_Var2Lit(1, 0); + Lits[1] = Abc_Var2Lit(2, 0); + printf("assume (x1, x2)\n"); + RetValue = cadical_solver_solve(pSat, Lits, Lits + 2, 0, 0, 0, 0); + printf("solved: %d\n", RetValue); + assert(RetValue == -1); + int *pCore; + int nSize = cadical_solver_final(pSat, &pCore); + printf("core: "); + for(int i = 0; i < nSize; i++) { + if(i) { + printf(", "); + } + if(Abc_LitIsCompl(pCore[i])) { + printf("!"); + } + printf("x%d", Abc_Lit2Var(pCore[i])); + } + printf("\n"); + int neg_x2_in_core = 0; + for(int i = 0; i < nSize; i++) { + if(Abc_LitIsCompl(pCore[i]) && Abc_Lit2Var(pCore[i]) == 2) { + neg_x2_in_core = 1; + } + } + assert(neg_x2_in_core); + cadical_solver_delete(pSat); + printf("test 5 passed\n"); + } +} + +//////////////////////////////////////////////////////////////////////// +/// END OF FILE /// +//////////////////////////////////////////////////////////////////////// + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_analyze.cpp b/src/sat/cadical/cadical_analyze.cpp new file mode 100644 index 000000000..74b67fb06 --- /dev/null +++ b/src/sat/cadical/cadical_analyze.cpp @@ -0,0 +1,1285 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Code for conflict analysis, i.e., to generate the first UIP clause. The +// main function is 'analyze' below. It further uses 'minimize' to minimize +// the first UIP clause, which is in 'minimize.cpp'. An important side +// effect of conflict analysis is to update the decision queue by bumping +// variables. Similarly analyzed clauses are bumped to mark them as active. + +/*------------------------------------------------------------------------*/ + +void Internal::learn_empty_clause () { + CADICAL_assert (!unsat); + build_chain_for_empty (); + LOG ("learned empty clause"); + external->check_learned_empty_clause (); + int64_t id = ++clause_id; + if (proof) { + proof->add_derived_empty_clause (id, lrat_chain); + } + unsat = true; + conflict_id = id; + marked_failed = true; + conclusion.push_back (id); + lrat_chain.clear (); +} + +void Internal::learn_unit_clause (int lit) { + CADICAL_assert (!unsat); + LOG ("learned unit clause %d, stored at position %d", lit, vlit (lit)); + external->check_learned_unit_clause (lit); + int64_t id = ++clause_id; + if (lrat || frat) { + const unsigned uidx = vlit (lit); + unit_clauses (uidx) = id; + } + if (proof) { + proof->add_derived_unit_clause (id, lit, lrat_chain); + } + mark_fixed (lit); +} + +/*------------------------------------------------------------------------*/ + +// Move bumped variables to the front of the (VMTF) decision queue. The +// 'bumped' time stamp is updated accordingly. It is used to determine +// whether the 'queue.assigned' pointer has to be moved in 'unassign'. + +void Internal::bump_queue (int lit) { + CADICAL_assert (opts.bump); + const int idx = vidx (lit); + if (!links[idx].next) + return; + queue.dequeue (links, idx); + queue.enqueue (links, idx); + CADICAL_assert (stats.bumped != INT64_MAX); + btab[idx] = ++stats.bumped; + LOG ("moved to front variable %d and bumped to %" PRId64 "", idx, + btab[idx]); + if (!vals[idx]) + update_queue_unassigned (idx); +} + +/*------------------------------------------------------------------------*/ + +// It would be better to use 'isinf' but there are some historical issues +// with this function. On some platforms it is a macro and even for C++ it +// changed the scope (in pre 5.0 gcc) from '::isinf' to 'std::isinf'. I do +// not want to worry about these strange incompatibilities and thus use the +// same trick as in older solvers (since the MiniSAT team invented EVSIDS) +// and simply put a hard limit here. It is less elegant but easy to port. + +static inline bool evsids_limit_hit (double score) { + CADICAL_assert (sizeof (score) == 8); // assume IEEE 754 64-bit double + return score > 1e150; // MAX_DOUBLE is around 1.8e308 +} + +/*------------------------------------------------------------------------*/ + +// Classical exponential VSIDS as pioneered by MiniSAT. + +void Internal::rescale_variable_scores () { + stats.rescored++; + double divider = score_inc; + for (auto idx : vars) { + const double tmp = stab[idx]; + if (tmp > divider) + divider = tmp; + } + PHASE ("rescore", stats.rescored, "rescoring %d variable scores by 1/%g", + max_var, divider); + CADICAL_assert (divider > 0); + double factor = 1.0 / divider; + for (auto idx : vars) + stab[idx] *= factor; + score_inc *= factor; + PHASE ("rescore", stats.rescored, + "new score increment %g after %" PRId64 " conflicts", score_inc, + stats.conflicts); +} + +void Internal::bump_variable_score (int lit) { + CADICAL_assert (opts.bump); + int idx = vidx (lit); + double old_score = score (idx); + CADICAL_assert (!evsids_limit_hit (old_score)); + double new_score = old_score + score_inc; + if (evsids_limit_hit (new_score)) { + LOG ("bumping %g score of %d hits EVSIDS score limit", old_score, idx); + rescale_variable_scores (); + old_score = score (idx); + CADICAL_assert (!evsids_limit_hit (old_score)); + new_score = old_score + score_inc; + } + CADICAL_assert (!evsids_limit_hit (new_score)); + LOG ("new %g score of %d", new_score, idx); + score (idx) = new_score; + if (scores.contains (idx)) + scores.update (idx); +} + +// Important variables recently used in conflict analysis are 'bumped', + +void Internal::bump_variable (int lit) { + if (use_scores ()) + bump_variable_score (lit); + else + bump_queue (lit); +} + +// After every conflict the variable score increment is increased by a +// factor (if we are currently using scores). + +void Internal::bump_variable_score_inc () { + CADICAL_assert (use_scores ()); + CADICAL_assert (!evsids_limit_hit (score_inc)); + double f = 1e3 / opts.scorefactor; + double new_score_inc = score_inc * f; + if (evsids_limit_hit (new_score_inc)) { + LOG ("bumping %g increment by %g hits EVSIDS score limit", score_inc, + f); + rescale_variable_scores (); + new_score_inc = score_inc * f; + } + CADICAL_assert (!evsids_limit_hit (new_score_inc)); + LOG ("bumped score increment from %g to %g with factor %g", score_inc, + new_score_inc, f); + score_inc = new_score_inc; +} + +/*------------------------------------------------------------------------*/ + +struct analyze_bumped_rank { + Internal *internal; + analyze_bumped_rank (Internal *i) : internal (i) {} + typedef uint64_t Type; + Type operator() (const int &a) const { return internal->bumped (a); } +}; + +struct analyze_bumped_smaller { + Internal *internal; + analyze_bumped_smaller (Internal *i) : internal (i) {} + bool operator() (const int &a, const int &b) const { + const auto s = analyze_bumped_rank (internal) (a); + const auto t = analyze_bumped_rank (internal) (b); + return s < t; + } +}; + +/*------------------------------------------------------------------------*/ + +void Internal::bump_variables () { + + CADICAL_assert (opts.bump); + + START (bump); + + if (!use_scores ()) { + + // Variables are bumped in the order they are in the current decision + // queue. This maintains relative order between bumped variables in + // the queue and seems to work best. We also experimented with + // focusing on variables of the last decision level, but results were + // mixed. + + MSORT (opts.radixsortlim, analyzed.begin (), analyzed.end (), + analyze_bumped_rank (this), analyze_bumped_smaller (this)); + } + + for (const auto &lit : analyzed) + bump_variable (lit); + + if (use_scores ()) + bump_variable_score_inc (); + + STOP (bump); +} + +/*------------------------------------------------------------------------*/ + +// We use the glue time stamp table 'gtab' for fast glue computation. + +int Internal::recompute_glue (Clause *c) { + int res = 0; + const int64_t stamp = ++stats.recomputed; + for (const auto &lit : *c) { + int level = var (lit).level; + CADICAL_assert (gtab[level] <= stamp); + if (gtab[level] == stamp) + continue; + gtab[level] = stamp; + res++; + } + return res; +} + +// Clauses resolved since the last reduction are marked as 'used', their +// glue is recomputed and they are promoted if the glue shrinks. Note that +// promotion from 'tier3' to 'tier2' will set 'used' to '2'. + +inline void Internal::bump_clause (Clause *c) { + LOG (c, "bumping"); + c->used = max_used; + if (c->hyper) + return; + if (!c->redundant) + return; + int new_glue = recompute_glue (c); + if (new_glue < c->glue) + promote_clause (c, new_glue); + + const size_t glue = + std::min ((size_t) c->glue, stats.used[stable].size () - 1); + ++stats.used[stable][glue]; + ++stats.bump_used[stable]; +} + +void Internal::bump_clause2 (Clause *c) { bump_clause (c); } +/*------------------------------------------------------------------------*/ + +// During conflict analysis literals not seen yet either become part of the +// first unique implication point (UIP) clause (if on lower decision level), +// are dropped (if fixed), or are resolved away (if on the current decision +// level and different from the first UIP). At the same time we update the +// number of seen literals on a decision level. This helps conflict clause +// minimization. The number of seen levels is the glucose level (also +// called 'glue', or 'LBD'). + +inline void Internal::analyze_literal (int lit, int &open, + int &resolvent_size, + int &antecedent_size) { + CADICAL_assert (lit); + Var &v = var (lit); + Flags &f = flags (lit); + + if (!v.level) { + if (f.seen || !lrat) + return; + f.seen = true; + unit_analyzed.push_back (lit); + CADICAL_assert (val (lit) < 0); + int64_t id = unit_id (-lit); + unit_chain.push_back (id); + return; + } + ++antecedent_size; + if (f.seen) + return; + + // before marking as seen, get reason and check for missed unit + + CADICAL_assert (val (lit) < 0); + CADICAL_assert (v.level <= level); + if (v.reason == external_reason) { + CADICAL_assert (!opts.exteagerreasons); + v.reason = learn_external_reason_clause (-lit, 0, true); + if (!v.reason) { // actually a unit + --antecedent_size; + LOG ("%d unit after explanation", -lit); + if (f.seen || !lrat) + return; + f.seen = true; + unit_analyzed.push_back (lit); + CADICAL_assert (val (lit) < 0); + const unsigned uidx = vlit (-lit); + int64_t id = unit_clauses (uidx); + CADICAL_assert (id); + unit_chain.push_back (id); + return; + } + } + + f.seen = true; + analyzed.push_back (lit); + + CADICAL_assert (v.reason != external_reason); + if (v.level < level) + clause.push_back (lit); + Level &l = control[v.level]; + if (!l.seen.count++) { + LOG ("found new level %d contributing to conflict", v.level); + levels.push_back (v.level); + } + if (v.trail < l.seen.trail) + l.seen.trail = v.trail; + ++resolvent_size; + LOG ("analyzed literal %d assigned at level %d", lit, v.level); + if (v.level == level) + open++; +} + +inline void Internal::analyze_reason (int lit, Clause *reason, int &open, + int &resolvent_size, + int &antecedent_size) { + CADICAL_assert (reason); + CADICAL_assert (reason != external_reason); + bump_clause (reason); + if (lrat) + lrat_chain.push_back (reason->id); + for (const auto &other : *reason) + if (other != lit) + analyze_literal (other, open, resolvent_size, antecedent_size); +} + +/*------------------------------------------------------------------------*/ + +// This is an idea which was implicit in MapleCOMSPS 2016 for 'limit = 1'. +// See also the paragraph on 'bumping reason side literals' in their SAT'16 +// paper [LiangGaneshPoupartCzarnecki-SAT'16]. Reason side bumping was +// performed exactly when 'LRB' based decision heuristics was used, which in +// the original version was enabled after 10000 conflicts until a time limit +// of 2500 seconds was reached (half of the competition time limit). The +// Maple / Glucose / MiniSAT evolution winning the SAT race in 2019 made +// the schedule of reason side bumping deterministic, i.e., avoiding a time +// limit, by switching between 'LRB' and 'VSIDS' in an interval of initially +// 30 million propagations, which then is increased geometrically by 10%. + +inline bool Internal::bump_also_reason_literal (int lit) { + CADICAL_assert (lit); + CADICAL_assert (val (lit) < 0); + Flags &f = flags (lit); + if (f.seen) + return false; + const Var &v = var (lit); + if (!v.level) + return false; + f.seen = true; + analyzed.push_back (lit); + LOG ("bumping also reason literal %d assigned at level %d", lit, v.level); + return true; +} + +// We experimented with deeper reason bumping without much success though. + +inline void Internal::bump_also_reason_literals (int lit, int depth_limit, + size_t analyzed_limit) { + CADICAL_assert (lit); + CADICAL_assert (depth_limit > 0); + const Var &v = var (lit); + CADICAL_assert (val (lit)); + if (!v.level) + return; + Clause *reason = v.reason; + if (!reason || reason == external_reason) + return; + stats.ticks.search[stable]++; + for (const auto &other : *reason) { + if (other == lit) + continue; + if (!bump_also_reason_literal (other)) + continue; + if (depth_limit < 2) + continue; + bump_also_reason_literals (-other, depth_limit - 1, analyzed_limit); + if (analyzed.size () > analyzed_limit) + break; + } +} + +inline void Internal::bump_also_all_reason_literals () { + CADICAL_assert (opts.bump); + if (!opts.bumpreason) + return; + if (averages.current.decisions > opts.bumpreasonrate) { + LOG ("decisions per conflict rate %g > limit %d", + (double) averages.current.decisions, opts.bumpreasonrate); + return; + } + if (delay[stable].bumpreasons.limit) { + LOG ("delaying reason bumping %" PRId64 " more times", + delay[stable].bumpreasons.limit); + delay[stable].bumpreasons.limit--; + return; + } + CADICAL_assert (opts.bumpreasondepth > 0); + const int depth_limit = opts.bumpreasondepth + stable; + size_t saved_analyzed = analyzed.size (); + size_t analyzed_limit = saved_analyzed * opts.bumpreasonlimit; + for (const auto &lit : clause) + if (analyzed.size () <= analyzed_limit) + bump_also_reason_literals (-lit, depth_limit, analyzed_limit); + else + break; + if (analyzed.size () > analyzed_limit) { + LOG ("not bumping reason side literals as limit exhausted"); + for (size_t i = saved_analyzed; i != analyzed.size (); i++) { + const int lit = analyzed[i]; + Flags &f = flags (lit); + CADICAL_assert (f.seen); + f.seen = false; + } + delay[stable].bumpreasons.interval++; + analyzed.resize (saved_analyzed); + } else { + LOG ("bumping reasons up to depth %d", opts.bumpreasondepth); + delay[stable].bumpreasons.interval /= 2; + } + LOG ("delay internal %" PRId64, delay[stable].bumpreasons.interval); + delay[stable].bumpreasons.limit = delay[stable].bumpreasons.interval; +} + +/*------------------------------------------------------------------------*/ + +void Internal::clear_unit_analyzed_literals () { + LOG ("clearing %zd unit analyzed literals", unit_analyzed.size ()); + for (const auto &lit : unit_analyzed) { + Flags &f = flags (lit); + CADICAL_assert (f.seen); + CADICAL_assert (!var (lit).level); + f.seen = false; + CADICAL_assert (!f.keep); + CADICAL_assert (!f.poison); + CADICAL_assert (!f.removable); + } + unit_analyzed.clear (); +} + +void Internal::clear_analyzed_literals () { + LOG ("clearing %zd analyzed literals", analyzed.size ()); + for (const auto &lit : analyzed) { + Flags &f = flags (lit); + CADICAL_assert (f.seen); + f.seen = false; + CADICAL_assert (!f.keep); + CADICAL_assert (!f.poison); + CADICAL_assert (!f.removable); + } + analyzed.clear (); +#if 0 // to expensive, even for debugging mode + if (unit_analyzed.size ()) + return; + for (auto idx : vars) { + Flags &f = flags (idx); + CADICAL_assert (!f.seen); + } +#endif +} + +void Internal::clear_analyzed_levels () { + LOG ("clearing %zd analyzed levels", levels.size ()); + for (const auto &l : levels) + if (l < (int) control.size ()) + control[l].reset (); + levels.clear (); +} + +/*------------------------------------------------------------------------*/ + +// Smaller level and trail. Comparing literals on their level is necessary +// for chronological backtracking, since trail order might in this case not +// respect level order. + +struct analyze_trail_negative_rank { + Internal *internal; + analyze_trail_negative_rank (Internal *s) : internal (s) {} + typedef uint64_t Type; + Type operator() (int a) { + Var &v = internal->var (a); + uint64_t res = v.level; + res <<= 32; + res |= v.trail; + return ~res; + } +}; + +struct analyze_trail_larger { + Internal *internal; + analyze_trail_larger (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return analyze_trail_negative_rank (internal) (a) < + analyze_trail_negative_rank (internal) (b); + } +}; + +/*------------------------------------------------------------------------*/ + +// Generate new driving clause and compute jump level. + +Clause *Internal::new_driving_clause (const int glue, int &jump) { + + const size_t size = clause.size (); + Clause *res; + + if (!size) { + + jump = 0; + res = 0; + + } else if (size == 1) { + + iterating = true; + jump = 0; + res = 0; + + } else { + + CADICAL_assert (clause.size () > 1); + + // We have to get the last assigned literals into the watch position. + // Sorting all literals with respect to reverse assignment order is + // overkill but seems to get slightly faster run-time. For 'minimize' + // we sort the literals too heuristically along the trail order (so in + // the opposite order) with the hope to hit the recursion limit less + // frequently. Thus sorting effort is doubled here. + // + MSORT (opts.radixsortlim, clause.begin (), clause.end (), + analyze_trail_negative_rank (this), analyze_trail_larger (this)); + + jump = var (clause[1]).level; + res = new_learned_redundant_clause (glue); + res->used = 1 + (glue <= opts.reducetier2glue); + } + + LOG ("jump level %d", jump); + + return res; +} + +/*------------------------------------------------------------------------*/ + +// determine the OTFS level for OTFS. Unlike the find_conflict_level, we do +// not have to fix the clause + +inline int Internal::otfs_find_backtrack_level (int &forced) { + CADICAL_assert (opts.otfs); + int res = 0; + + for (const auto &lit : *conflict) { + const int tmp = var (lit).level; + if (tmp == level) { + forced = lit; + } else if (tmp > res) { + res = tmp; + LOG ("bt level is now %d due to %d", res, lit); + } + } + return res; +} + +/*------------------------------------------------------------------------*/ + +// If chronological backtracking is enabled we need to find the actual +// conflict level and then potentially can also reuse the conflict clause +// as driving clause instead of deriving a redundant new driving clause +// (forcing 'forced') if the number 'count' of literals in conflict assigned +// at the conflict level is exactly one. + +inline int Internal::find_conflict_level (int &forced) { + + CADICAL_assert (conflict); + CADICAL_assert (opts.chrono || opts.otfs || external_prop); + + int res = 0, count = 0; + + forced = 0; + + for (const auto &lit : *conflict) { + const int tmp = var (lit).level; + if (tmp > res) { + res = tmp; + forced = lit; + count = 1; + } else if (tmp == res) { + count++; + if (res == level && count > 1) + break; + } + } + + LOG ("%d literals on actual conflict level %d", count, res); + + const int size = conflict->size; + int *lits = conflict->literals; + + // Move the two highest level literals to the front. + // + for (int i = 0; i < 2; i++) { + + const int lit = lits[i]; + + int highest_position = i; + int highest_literal = lit; + int highest_level = var (highest_literal).level; + + for (int j = i + 1; j < size; j++) { + const int other = lits[j]; + const int tmp = var (other).level; + if (highest_level >= tmp) + continue; + highest_literal = other; + highest_position = j; + highest_level = tmp; + if (highest_level == res) + break; + } + + // No unwatched higher assignment level literal. + // + if (highest_position == i) + continue; + + if (highest_position > 1) { + LOG (conflict, "unwatch %d in", lit); + remove_watch (watches (lit), conflict); + } + + lits[highest_position] = lit; + lits[i] = highest_literal; + + if (highest_position > 1) + watch_literal (highest_literal, lits[!i], conflict); + } + + // Only if the number of highest level literals in the conflict is one + // then we can reuse the conflict clause as driving clause for 'forced'. + // + if (count != 1) + forced = 0; + + return res; +} + +/*------------------------------------------------------------------------*/ + +inline int Internal::determine_actual_backtrack_level (int jump) { + + int res; + + CADICAL_assert (level > jump); + + if (!opts.chrono) { + res = jump; + LOG ("chronological backtracking disabled using jump level %d", res); + } else if (opts.chronoalways) { + stats.chrono++; + res = level - 1; + LOG ("forced chronological backtracking to level %d", res); + } else if (jump >= level - 1) { + res = jump; + LOG ("jump level identical to chronological backtrack level %d", res); + } else if ((size_t) jump < assumptions.size ()) { + res = jump; + LOG ("using jump level %d since it is lower than assumption level %zd", + res, assumptions.size ()); + } else if (level - jump > opts.chronolevelim) { + stats.chrono++; + res = level - 1; + LOG ("back-jumping over %d > %d levels prohibited" + "thus backtracking chronologically to level %d", + level - jump, opts.chronolevelim, res); + } else if (opts.chronoreusetrail) { + int best_idx = 0, best_pos = 0; + + if (use_scores ()) { + for (size_t i = control[jump + 1].trail; i < trail.size (); i++) { + const int idx = abs (trail[i]); + if (best_idx && !score_smaller (this) (best_idx, idx)) + continue; + best_idx = idx; + best_pos = i; + } + LOG ("best variable score %g", score (best_idx)); + } else { + for (size_t i = control[jump + 1].trail; i < trail.size (); i++) { + const int idx = abs (trail[i]); + if (best_idx && bumped (best_idx) >= bumped (idx)) + continue; + best_idx = idx; + best_pos = i; + } + LOG ("best variable bumped %" PRId64 "", bumped (best_idx)); + } + CADICAL_assert (best_idx); + LOG ("best variable %d at trail position %d", best_idx, best_pos); + + // Now find the frame and decision level in the control stack of that + // best variable index. Note that, as in 'reuse_trail', the frame + // 'control[i]' for decision level 'i' contains the trail before that + // decision level, i.e., the decision 'control[i].decision' sits at + // 'control[i].trail' in the trail and we thus have to check the level + // of the control frame one higher than at the result level. + // + res = jump; + while (res < level - 1 && control[res + 1].trail <= best_pos) + res++; + + if (res == jump) + LOG ("default non-chronological back-jumping to level %d", res); + else { + stats.chrono++; + LOG ("chronological backtracking to level %d to reuse trail", res); + } + + } else { + res = jump; + LOG ("non-chronological back-jumping to level %d", res); + } + + return res; +} + +/*------------------------------------------------------------------------*/ + +void Internal::eagerly_subsume_recently_learned_clauses (Clause *c) { + CADICAL_assert (opts.eagersubsume); + LOG (c, "trying eager subsumption with"); + mark (c); + int64_t lim = stats.eagertried + opts.eagersubsumelim; + const auto begin = clauses.begin (); + auto it = clauses.end (); +#ifdef LOGGING + int64_t before = stats.eagersub; +#endif + while (it != begin && stats.eagertried++ <= lim) { + Clause *d = *--it; + if (c == d) + continue; + if (d->garbage) + continue; + if (!d->redundant) + continue; + int needed = c->size; + for (auto &lit : *d) { + if (marked (lit) <= 0) + continue; + if (!--needed) + break; + } + if (needed) + continue; + LOG (d, "eager subsumed"); + stats.eagersub++; + stats.subsumed++; + mark_garbage (d); + } + unmark (c); +#ifdef LOGGING + uint64_t subsumed = stats.eagersub - before; + if (subsumed) + LOG ("eagerly subsumed %" PRIu64 " clauses", subsumed); +#endif +} + +/*------------------------------------------------------------------------*/ + +Clause *Internal::on_the_fly_strengthen (Clause *new_conflict, int uip) { + CADICAL_assert (new_conflict); + CADICAL_assert (new_conflict->size > 2); + LOG (new_conflict, "applying OTFS on lit %d", uip); + auto sorted = std::vector (); + sorted.reserve (new_conflict->size); + CADICAL_assert (sorted.empty ()); + ++stats.otfs.strengthened; + + int *lits = new_conflict->literals; + + CADICAL_assert (lits[0] == uip || lits[1] == uip); + const int other_init = lits[0] ^ lits[1] ^ uip; + + CADICAL_assert (mini_chain.empty ()); + + const int old_size = new_conflict->size; + int new_size = 0; + for (int i = 0; i < old_size; ++i) { + const int other = lits[i]; + sorted.push_back (other); + if (var (other).level) + lits[new_size++] = other; + } + + LOG (new_conflict, "removing all units in"); + + CADICAL_assert (lits[0] == uip || lits[1] == uip); + const int other = lits[0] ^ lits[1] ^ uip; + lits[0] = other; + lits[1] = lits[--new_size]; + LOG (new_conflict, "putting uip at pos 1"); + + if (other_init != other) + remove_watch (watches (other_init), new_conflict); + remove_watch (watches (uip), new_conflict); + + CADICAL_assert (!lrat || lrat_chain.back () == new_conflict->id); + if (lrat) { + CADICAL_assert (!lrat_chain.empty ()); + for (const auto &id : unit_chain) { + mini_chain.push_back (id); + } + const auto end = lrat_chain.rend (); + const auto begin = lrat_chain.rbegin (); + for (auto i = begin; i != end; i++) { + const auto id = *i; + mini_chain.push_back (id); + } + lrat_chain.clear (); + clear_unit_analyzed_literals (); + unit_chain.clear (); + } + CADICAL_assert (unit_analyzed.empty ()); + // sort the clause + { + int highest_pos = 0; + int highest_level = 0; + for (int i = 1; i < new_size; i++) { + const unsigned other = lits[i]; + CADICAL_assert (val (other) < 0); + const int level = var (other).level; + CADICAL_assert (level); + LOG ("checking %d", other); + if (level <= highest_level) + continue; + highest_pos = i; + highest_level = level; + } + LOG ("highest lit is %d", lits[highest_pos]); + if (highest_pos != 1) + swap (lits[1], lits[highest_pos]); + LOG ("removing %d literals", new_conflict->size - new_size); + + if (new_size == 1) { + LOG (new_conflict, "new size = 1, so interrupting"); + CADICAL_assert (!opts.exteagerreasons); + return 0; + } else { + otfs_strengthen_clause (new_conflict, uip, new_size, sorted); + CADICAL_assert (new_size == new_conflict->size); + } + } + + if (other_init != other) + watch_literal (other, lits[1], new_conflict); + else { + update_watch_size (watches (other), lits[1], new_conflict); + } + watch_literal (lits[1], other, new_conflict); + + LOG (new_conflict, "strengthened clause by OTFS"); + sorted.clear (); + + return new_conflict; +} + +/*------------------------------------------------------------------------*/ +inline void Internal::otfs_subsume_clause (Clause *subsuming, + Clause *subsumed) { + stats.subsumed++; + CADICAL_assert (subsuming->size <= subsumed->size); + LOG (subsumed, "subsumed"); + if (subsumed->redundant) + stats.subred++; + else + stats.subirr++; + if (subsumed->redundant || !subsuming->redundant) { + mark_garbage (subsumed); + return; + } + LOG ("turning redundant subsuming clause into irredundant clause"); + subsuming->redundant = false; + if (proof) + proof->strengthen (subsuming->id); + mark_garbage (subsumed); + stats.current.irredundant++; + stats.added.irredundant++; + stats.irrlits += subsuming->size; + CADICAL_assert (stats.current.redundant > 0); + stats.current.redundant--; + CADICAL_assert (stats.added.redundant > 0); + stats.added.redundant--; + // ... and keep 'stats.added.total'. +} + +/*------------------------------------------------------------------------*/ + +// Candidate clause 'c' is strengthened by removing 'lit' and units. +// +void Internal::otfs_strengthen_clause (Clause *c, int lit, int new_size, + const std::vector &old) { + stats.strengthened++; + CADICAL_assert (c->size > 2); + (void) shrink_clause (c, new_size); + if (proof) { + proof->otfs_strengthen_clause (c, old, mini_chain); + } + if (!c->redundant) { + mark_removed (lit); + } + mini_chain.clear (); + c->used = true; + LOG (c, "strengthened"); + external->check_shrunken_clause (c); +} + +/*------------------------------------------------------------------------*/ + +// If the average number of decisions per conflict (analysis actually so not +// taking OTFS conflicts into account) is high we do not bump reasons. This +// is the function which updates the exponential moving decision rate +// average. + +void Internal::update_decision_rate_average () { + int64_t current = stats.decisions; + int64_t decisions = current - saved_decisions; + UPDATE_AVERAGE (averages.current.decisions, decisions); + saved_decisions = current; +} + +/*------------------------------------------------------------------------*/ + +// This is the main conflict analysis routine. It assumes that a conflict +// was found. Then we derive the 1st UIP clause, optionally minimize it, +// add it as learned clause, and then uses the clause for conflict directed +// back-jumping and flipping the 1st UIP literal. In combination with +// chronological backtracking (see discussion above) the algorithm becomes +// slightly more involved. + +void Internal::analyze () { + + START (analyze); + + CADICAL_assert (conflict); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (unit_chain.empty ()); + CADICAL_assert (unit_analyzed.empty ()); + CADICAL_assert (clause.empty ()); + + // First update moving averages of trail height at conflict. + // + UPDATE_AVERAGE (averages.current.trail.fast, num_assigned); + UPDATE_AVERAGE (averages.current.trail.slow, num_assigned); + update_decision_rate_average (); + + /*----------------------------------------------------------------------*/ + + if (external_prop && !external_prop_is_lazy && opts.exteagerreasons) { + explain_external_propagations (); + } + + if (opts.chrono || external_prop) { + + int forced; + + const int conflict_level = find_conflict_level (forced); + + // In principle we can perform conflict analysis as in non-chronological + // backtracking except if there is only one literal with the maximum + // assignment level in the clause. Then standard conflict analysis is + // unnecessary and we can use the conflict as a driving clause. In the + // pseudo code of the SAT'18 paper on chronological backtracking this + // corresponds to the situation handled in line 4-6 in Alg. 1, except + // that the pseudo code in the paper only backtracks while we eagerly + // assign the single literal on the highest decision level. + + if (forced) { + + CADICAL_assert (forced); + CADICAL_assert (conflict_level > 0); + LOG ("single highest level literal %d", forced); + + // The pseudo code in the SAT'18 paper actually backtracks to the + // 'second highest decision' level, while their code backtracks + // to 'conflict_level-1', which is more in the spirit of chronological + // backtracking anyhow and thus we also do the latter. + // + backtrack (conflict_level - 1); + + // if we are on decision level 0 search assign will learn unit + // so we need a valid chain here (of course if we are not on decision + // level 0 this will not result in a valid chain). + // we can just use build_chain_for_units in propagate + // + build_chain_for_units (forced, conflict, 0); + + LOG ("forcing %d", forced); + search_assign_driving (forced, conflict); + + conflict = 0; + STOP (analyze); + return; + } + + // Backtracking to the conflict level is in the pseudo code in the + // SAT'18 chronological backtracking paper, but not in their actual + // implementation. In principle we do not need to backtrack here. + // However, as a side effect of backtracking to the conflict level we + // set 'level' to the conflict level which then allows us to reuse the + // old 'analyze' code as is. The alternative (which we also tried but + // then abandoned) is to use 'conflict_level' instead of 'level' in the + // analysis, which however requires to pass it to the 'analyze_reason' + // and 'analyze_literal' functions. + // + backtrack (conflict_level); + } + + // Actual conflict on root level, thus formula unsatisfiable. + // + if (!level) { + learn_empty_clause (); + if (external->learner) + external->export_learned_empty_clause (); + STOP (analyze); + return; + } + + /*----------------------------------------------------------------------*/ + + // First derive the 1st UIP clause by going over literals assigned on the + // current decision level. Literals in the conflict are marked as 'seen' + // as well as all literals in reason clauses of already 'seen' literals on + // the current decision level. Thus the outer loop starts with the + // conflict clause as 'reason' and then uses the 'reason' of the next + // seen literal on the trail assigned on the current decision level. + // During this process maintain the number 'open' of seen literals on the + // current decision level with not yet processed 'reason'. As soon 'open' + // drops to one, we have found the first unique implication point. This + // is sound because the topological order in which literals are processed + // follows the assignment order and a more complex algorithm to find + // articulation points is not necessary. + // + Clause *reason = conflict; + LOG (reason, "analyzing conflict"); + + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + + const auto &t = &trail; + int i = t->size (); // Start at end-of-trail. + int open = 0; // Seen but not processed on this level. + int uip = 0; // The first UIP literal. + int resolvent_size = 0; // without the uip + int antecedent_size = 1; // with the uip and without unit literals + int conflict_size = 0; // without the uip and without unit literals + int resolved = 0; // number of resolution (0 = clause in CNF) + const bool otfs = opts.otfs; + + for (;;) { + antecedent_size = 1; // for uip + analyze_reason (uip, reason, open, resolvent_size, antecedent_size); + if (resolved == 0) + conflict_size = antecedent_size - 1; + CADICAL_assert (resolvent_size == open + (int) clause.size ()); + + if (otfs && resolved > 0 && antecedent_size > 2 && + resolvent_size < antecedent_size) { + CADICAL_assert (reason != conflict); + LOG (analyzed, "found candidate for OTFS conflict"); + LOG (clause, "found candidate for OTFS conflict"); + LOG (reason, "found candidate (size %d) for OTFS resolvent", + antecedent_size); + const int other = reason->literals[0] ^ reason->literals[1] ^ uip; + CADICAL_assert (other != uip); + reason = on_the_fly_strengthen (reason, uip); + if (opts.bump) + bump_variables (); + + CADICAL_assert (conflict_size); + if (!reason) { + uip = -other; + CADICAL_assert (open == 1); + LOG ("clause is actually unit %d, stopping", -uip); + reverse (begin (mini_chain), end (mini_chain)); + for (auto id : mini_chain) + lrat_chain.push_back (id); + mini_chain.clear (); + clear_analyzed_levels (); + CADICAL_assert (!opts.exteagerreasons); + clause.clear (); + break; + } + CADICAL_assert (conflict_size >= 2); + + if (resolved == 1 && resolvent_size < conflict_size) { + // here both clauses are part of the CNF, so one subsumes the other + otfs_subsume_clause (reason, conflict); + LOG (reason, "changing conflict to"); + --conflict_size; + CADICAL_assert (conflict_size == reason->size); + ++stats.otfs.subsumed; + ++stats.subsumed; + } + + LOG (reason, "changing conflict to"); + conflict = reason; + if (open == 1) { + int forced = 0; + const int conflict_level = otfs_find_backtrack_level (forced); + int new_level = determine_actual_backtrack_level (conflict_level); + UPDATE_AVERAGE (averages.current.level, new_level); + backtrack (new_level); + + LOG ("forcing %d", forced); + search_assign_driving (forced, conflict); + + // Clean up. + // + conflict = 0; + clear_analyzed_literals (); + clear_analyzed_levels (); + clause.clear (); + STOP (analyze); + return; + } + + stats.conflicts++; + + clear_analyzed_literals (); + clear_analyzed_levels (); + clause.clear (); + resolvent_size = 0; + antecedent_size = 1; + resolved = 0; + open = 0; + analyze_reason (0, reason, open, resolvent_size, antecedent_size); + conflict_size = antecedent_size - 1; + CADICAL_assert (open > 1); + } + + ++resolved; + + uip = 0; + while (!uip) { + CADICAL_assert (i > 0); + const int lit = (*t)[--i]; + if (!flags (lit).seen) + continue; + if (var (lit).level == level) + uip = lit; + } + if (!--open) + break; + reason = var (uip).reason; + if (reason == external_reason) { + CADICAL_assert (!opts.exteagerreasons); + reason = learn_external_reason_clause (-uip, 0, true); + var (uip).reason = reason; + } + CADICAL_assert (reason != external_reason); + LOG (reason, "analyzing %d reason", uip); + CADICAL_assert (resolvent_size); + --resolvent_size; + } + LOG ("first UIP %d", uip); + clause.push_back (-uip); + + // Update glue and learned (1st UIP literals) statistics. + // + int size = (int) clause.size (); + const int glue = (int) levels.size () - 1; + LOG (clause, "1st UIP size %d and glue %d clause", size, glue); + UPDATE_AVERAGE (averages.current.glue.fast, glue); + UPDATE_AVERAGE (averages.current.glue.slow, glue); + stats.learned.literals += size; + stats.learned.clauses++; + CADICAL_assert (glue < size); + + // up to this point lrat_chain contains the proof for current clause in + // reversed order. in minimize and shrink the clause is changed and + // therefore lrat_chain has to be extended. Unfortunately we cannot create + // the chain directly during minimization (or shrinking) but afterwards we + // can calculate it pretty easily and even better the same algorithm works + // for both shrinking and minimization. + + // Minimize the 1st UIP clause as pioneered by Niklas Soerensson in + // MiniSAT and described in our joint SAT'09 paper. + // + if (size > 1) { + if (opts.shrink) + shrink_and_minimize_clause (); + else if (opts.minimize) + minimize_clause (); + + size = (int) clause.size (); + + // Update decision heuristics. + // + if (opts.bump) { + bump_also_all_reason_literals (); + bump_variables (); + } + + if (external->learner) + external->export_learned_large_clause (clause); + } else if (external->learner) + external->export_learned_unit_clause (-uip); + + // Update actual size statistics. + // + stats.units += (size == 1); + stats.binaries += (size == 2); + UPDATE_AVERAGE (averages.current.size, size); + + // reverse lrat_chain. We could probably work with reversed iterators + // (views) to be more efficient but we would have to distinguish in proof + // + if (lrat) { + LOG (unit_chain, "unit chain: "); + for (auto id : unit_chain) + lrat_chain.push_back (id); + unit_chain.clear (); + reverse (lrat_chain.begin (), lrat_chain.end ()); + } + + // Determine back-jump level, learn driving clause, backtrack and assign + // flipped 1st UIP literal. + // + int jump; + Clause *driving_clause = new_driving_clause (glue, jump); + UPDATE_AVERAGE (averages.current.jump, jump); + + int new_level = determine_actual_backtrack_level (jump); + UPDATE_AVERAGE (averages.current.level, new_level); + backtrack (new_level); + + // It should hold that (!level <=> size == 1) + // and (!uip <=> size == 0) + // this means either we have already learned a clause => size >= 2 + // in this case we will not learn empty clause or unit here + // or we haven't actually learned a clause in new_driving_clause + // then lrat_chain is still valid and we will learn a unit or empty clause + // + if (uip) { + search_assign_driving (-uip, driving_clause); + } else + learn_empty_clause (); + + if (stable) + reluctant.tick (); // Reluctant has its own 'conflict' counter. + + // Clean up. + // + clear_analyzed_literals (); + clear_unit_analyzed_literals (); + clear_analyzed_levels (); + clause.clear (); + conflict = 0; + + lrat_chain.clear (); + STOP (analyze); + + if (driving_clause && opts.eagersubsume) + eagerly_subsume_recently_learned_clauses (driving_clause); + + if (lim.recompute_tier <= stats.conflicts) + recompute_tier (); +} + +// We wait reporting a learned unit until propagation of that unit is +// completed. Otherwise the 'i' report gives the number of remaining +// variables before propagating the unit (and hides the actual remaining +// variables after propagating it). + +void Internal::iterate () { + iterating = false; + report ('i'); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_arena.cpp b/src/sat/cadical/cadical_arena.cpp new file mode 100644 index 000000000..a8a9b183f --- /dev/null +++ b/src/sat/cadical/cadical_arena.cpp @@ -0,0 +1,36 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +Arena::Arena (Internal *i) { + memset (this, 0, sizeof *this); + internal = i; +} + +Arena::~Arena () { + delete[] from.start; + delete[] to.start; +} + +void Arena::prepare (size_t bytes) { + LOG ("preparing 'to' space of arena with %zd bytes", bytes); + CADICAL_assert (!to.start); + to.top = to.start = new char[bytes]; + to.end = to.start + bytes; +} + +void Arena::swap () { + delete[] from.start; + LOG ("delete 'from' space of arena with %zd bytes", + (size_t) (from.end - from.start)); + from = to; + to.start = to.top = to.end = 0; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_assume.cpp b/src/sat/cadical/cadical_assume.cpp new file mode 100644 index 000000000..e7d5b79b7 --- /dev/null +++ b/src/sat/cadical/cadical_assume.cpp @@ -0,0 +1,613 @@ +#include "global.h" + +#include "internal.hpp" +#include "options.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Failed literal handling as pioneered by MiniSAT. This first function +// adds an assumption literal onto the assumption stack. + +void Internal::assume (int lit) { + if (level && !opts.ilbassumptions) + backtrack (); + else if (val (lit) < 0) + backtrack (max (0, var (lit).level - 1)); + Flags &f = flags (lit); + const unsigned char bit = bign (lit); + if (f.assumed & bit) { + LOG ("ignoring already assumed %d", lit); + return; + } + LOG ("assume %d", lit); + f.assumed |= bit; + assumptions.push_back (lit); + freeze (lit); +} + +// for LRAT we actually need to implement recursive DFS +// for non-lrat use BFS. TODO: maybe derecursify to avoid stack overflow +// +void Internal::assume_analyze_literal (int lit) { + CADICAL_assert (lit); + Flags &f = flags (lit); + if (f.seen) + return; + f.seen = true; + analyzed.push_back (lit); + Var &v = var (lit); + CADICAL_assert (val (lit) < 0); + if (v.reason == external_reason) { + v.reason = wrapped_learn_external_reason_clause (-lit); + CADICAL_assert (v.reason || !v.level); + } + CADICAL_assert (v.reason != external_reason); + if (!v.level) { + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + return; + } + if (v.reason) { + CADICAL_assert (v.level); + LOG (v.reason, "analyze reason"); + for (const auto &other : *v.reason) { + assume_analyze_literal (other); + } + lrat_chain.push_back (v.reason->id); + return; + } + CADICAL_assert (assumed (-lit)); + LOG ("failed assumption %d", -lit); + clause.push_back (lit); +} + +void Internal::assume_analyze_reason (int lit, Clause *reason) { + CADICAL_assert (reason); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (reason != external_reason); + CADICAL_assert (lrat); + for (const auto &other : *reason) + if (other != lit) + assume_analyze_literal (other); + lrat_chain.push_back (reason->id); +} + +// Find all failing assumptions starting from the one on the assumption +// stack with the lowest decision level. This goes back to MiniSAT and is +// called 'analyze_final' there. + +void Internal::failing () { + + START (analyze); + + LOG ("analyzing failing assumptions"); + + CADICAL_assert (analyzed.empty ()); + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (!marked_failed); + CADICAL_assert (!conflict_id); + + if (!unsat_constraint) { + // Search for failing assumptions in the (internal) assumption stack. + + // There are in essence three cases: (1) An assumption is falsified on + // the root-level and then 'failed_unit' is set to that assumption, (2) + // two clashing assumptions are assumed and then 'failed_clashing' is + // set to the second assumed one, or otherwise (3) there is a failing + // assumption 'first_failed' with minimum (non-zero) decision level + // 'failed_level'. + + int failed_unit = 0; + int failed_clashing = 0; + int first_failed = 0; + int failed_level = INT_MAX; + int efailed = 0; + + for (auto &elit : external->assumptions) { + int lit = external->e2i[abs (elit)]; + if (elit < 0) + lit = -lit; + if (val (lit) >= 0) + continue; + const Var &v = var (lit); + if (!v.level) { + failed_unit = lit; + efailed = elit; + break; + } + if (failed_clashing) + continue; + if (v.reason == external_reason) { + Var &ev = var (lit); + ev.reason = learn_external_reason_clause (-lit); + if (!ev.reason) { + ev.level = 0; + failed_unit = lit; + efailed = elit; + break; + } + ev.level = 0; + // Recalculate assignment level + for (const auto &other : *ev.reason) { + if (other == -lit) + continue; + CADICAL_assert (val (other)); + int tmp = var (other).level; + if (tmp > ev.level) + ev.level = tmp; + } + if (!ev.level) { + failed_unit = lit; + efailed = elit; + break; + } + } + CADICAL_assert (v.reason != external_reason); + if (!v.reason) { + failed_clashing = lit; + efailed = elit; + } else if (!first_failed || v.level < failed_level) { + first_failed = lit; + efailed = elit; + failed_level = v.level; + } + } + + CADICAL_assert (clause.empty ()); + + // Get the 'failed' assumption from one of the three cases. + int failed; + if (failed_unit) + failed = failed_unit; + else if (failed_clashing) + failed = failed_clashing; + else + failed = first_failed; + CADICAL_assert (failed); + CADICAL_assert (efailed); + + // In any case mark literal 'failed' as failed assumption. + { + Flags &f = flags (failed); + const unsigned bit = bign (failed); + CADICAL_assert (!(f.failed & bit)); + f.failed |= bit; + } + + // First case (1). + if (failed_unit) { + CADICAL_assert (failed == failed_unit); + LOG ("root-level falsified assumption %d", failed); + if (proof) { + if (lrat) { + unsigned eidx = (efailed > 0) + 2u * (unsigned) abs (efailed); + CADICAL_assert ((size_t) eidx < external->ext_units.size ()); + const int64_t id = external->ext_units[eidx]; + if (id) { + lrat_chain.push_back (id); + } else { + int64_t id = unit_id (-failed_unit); + lrat_chain.push_back (id); + } + } + proof->add_assumption_clause (++clause_id, -efailed, lrat_chain); + conclusion.push_back (clause_id); + lrat_chain.clear (); + } + goto DONE; + } + + // Second case (2). + if (failed_clashing) { + CADICAL_assert (failed == failed_clashing); + LOG ("clashing assumptions %d and %d", failed, -failed); + Flags &f = flags (-failed); + const unsigned bit = bign (-failed); + CADICAL_assert (!(f.failed & bit)); + f.failed |= bit; + if (proof) { + vector clash = {externalize (failed), externalize (-failed)}; + proof->add_assumption_clause (++clause_id, clash, lrat_chain); + conclusion.push_back (clause_id); + } + goto DONE; + } + + // Fall through to third case (3). + LOG ("starting with assumption %d falsified on minimum decision level " + "%d", + first_failed, failed_level); + + CADICAL_assert (first_failed); + CADICAL_assert (failed_level > 0); + + // The 'analyzed' stack serves as working stack for a BFS through the + // implication graph until decisions, which are all assumptions, or + // units are reached. This is simpler than corresponding code in + // 'analyze'. + { + LOG ("failed assumption %d", first_failed); + Flags &f = flags (first_failed); + CADICAL_assert (!f.seen); + f.seen = true; + CADICAL_assert (f.failed & bign (first_failed)); + analyzed.push_back (-first_failed); + clause.push_back (-first_failed); + } + } else { + // unsat_constraint + // The assumptions necessary to fail each literal in the constraint are + // collected. + for (auto lit : constraint) { + lit *= -1; + CADICAL_assert (lit != INT_MIN); + flags (lit).seen = true; + analyzed.push_back (lit); + } + } + + { + // used for unsat_constraint lrat + vector> constraint_chains; + vector> constraint_clauses; + vector sum_constraints; + vector econstraints; + for (auto &elit : external->constraint) { + int lit = external->e2i[abs (elit)]; + if (elit < 0) + lit = -lit; + if (!lit) + continue; + Flags &f = flags (lit); + if (f.seen) + continue; + if (std::find (econstraints.begin (), econstraints.end (), elit) != + econstraints.end ()) + continue; + econstraints.push_back (elit); + } + + // no LRAT do bfs as it was before + if (!lrat) { + size_t next = 0; + while (next < analyzed.size ()) { + const int lit = analyzed[next++]; + CADICAL_assert (val (lit) > 0); + Var &v = var (lit); + if (!v.level) + continue; + if (v.reason == external_reason) { + v.reason = wrapped_learn_external_reason_clause (lit); + if (!v.reason) { + v.level = 0; + continue; + } + } + CADICAL_assert (v.reason != external_reason); + if (v.reason) { + CADICAL_assert (v.level); + LOG (v.reason, "analyze reason"); + for (const auto &other : *v.reason) { + Flags &f = flags (other); + if (f.seen) + continue; + f.seen = true; + CADICAL_assert (val (other) < 0); + analyzed.push_back (-other); + } + } else { + CADICAL_assert (assumed (lit)); + LOG ("failed assumption %d", lit); + clause.push_back (-lit); + Flags &f = flags (lit); + const unsigned bit = bign (lit); + CADICAL_assert (!(f.failed & bit)); + f.failed |= bit; + } + } + clear_analyzed_literals (); + } else if (!unsat_constraint) { // LRAT for case (3) + CADICAL_assert (clause.size () == 1); + const int lit = clause[0]; + Var &v = var (lit); + CADICAL_assert (v.reason); + if (v.reason == external_reason) { // does this even happen? + v.reason = wrapped_learn_external_reason_clause (lit); + } + CADICAL_assert (v.reason != external_reason); + if (v.reason) + assume_analyze_reason (lit, v.reason); + else { + int64_t id = unit_id (lit); + lrat_chain.push_back (id); + } + for (auto &lit : clause) { + Flags &f = flags (lit); + const unsigned bit = bign (-lit); + if (!(f.failed & bit)) + f.failed |= bit; + } + clear_analyzed_literals (); + } else { // LRAT for unsat_constraint + CADICAL_assert (clause.empty ()); + clear_analyzed_literals (); + for (auto lit : constraint) { + // make sure nothing gets marked failed twice + // also might shortcut the case where + // lrat_chain is empty because clause is tautological + CADICAL_assert (lit != INT_MIN); + assume_analyze_literal (lit); + vector empty; + vector empty2; + constraint_chains.push_back (empty); + constraint_clauses.push_back (empty2); + for (auto ign : clause) { + constraint_clauses.back ().push_back (ign); + Flags &f = flags (ign); + const unsigned bit = bign (-ign); + if (!(f.failed & bit)) { + sum_constraints.push_back (ign); + CADICAL_assert (!(f.failed & bit)); + f.failed |= bit; + } + } + clause.clear (); + clear_analyzed_literals (); + for (auto p : lrat_chain) { + constraint_chains.back ().push_back (p); + } + lrat_chain.clear (); + } + for (auto &lit : sum_constraints) + clause.push_back (lit); + } + clear_analyzed_literals (); + + // Doing clause minimization here does not do anything because + // the clause already contains only one literal of each level + // and minimization can never reduce the number of levels + + VERBOSE (1, "found %zd failed assumptions %.0f%%", clause.size (), + percent (clause.size (), assumptions.size ())); + + // We do not actually need to learn this clause, since the conflict is + // forced already by some other clauses. There is also no bumping + // of variables nor clauses necessary. But we still want to check + // correctness of the claim that the determined subset of failing + // assumptions are a high-level core or equivalently their negations + // form a unit-implied clause. + // + if (!unsat_constraint) { + external->check_learned_clause (); + if (proof) { + vector eclause; + for (auto &lit : clause) + eclause.push_back (externalize (lit)); + proof->add_assumption_clause (++clause_id, eclause, lrat_chain); + conclusion.push_back (clause_id); + } + } else { + CADICAL_assert (!lrat || (constraint.size () == constraint_clauses.size () && + constraint.size () == constraint_chains.size ())); + for (auto p = constraint.rbegin (); p != constraint.rend (); p++) { + const auto &lit = *p; + if (lrat) { + clause.clear (); + for (auto &ign : constraint_clauses.back ()) + clause.push_back (ign); + constraint_clauses.pop_back (); + } + clause.push_back (-lit); + external->check_learned_clause (); + if (proof) { + if (lrat) { + for (auto p : constraint_chains.back ()) { + lrat_chain.push_back (p); + } + constraint_chains.pop_back (); + LOG (lrat_chain, "assume proof chain with constraints"); + } + vector eclause; + for (auto &lit : clause) + eclause.push_back (externalize (lit)); + proof->add_assumption_clause (++clause_id, eclause, lrat_chain); + conclusion.push_back (clause_id); + lrat_chain.clear (); + } + clause.pop_back (); + } + if (proof) { + for (auto &elit : econstraints) { + if (lrat) { + unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit); + CADICAL_assert ((size_t) eidx < external->ext_units.size ()); + const int64_t id = external->ext_units[eidx]; + if (id) { + lrat_chain.push_back (id); + } else { + int lit = external->e2i[abs (elit)]; + if (elit < 0) + lit = -lit; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + } + } + proof->add_assumption_clause (++clause_id, -elit, lrat_chain); + conclusion.push_back (clause_id); + lrat_chain.clear (); + } + } + } + lrat_chain.clear (); + clause.clear (); + } + +DONE: + + STOP (analyze); +} + +bool Internal::failed (int lit) { + if (!marked_failed) { + if (!conflict_id) + failing (); + marked_failed = true; + } + conclude_unsat (); + Flags &f = flags (lit); + const unsigned bit = bign (lit); + return (f.failed & bit) != 0; +} + +void Internal::conclude_unsat () { + if (!proof || concluded) + return; + concluded = true; + if (!marked_failed) { + CADICAL_assert (conclusion.empty ()); + if (!conflict_id) + failing (); + marked_failed = true; + } + ConclusionType con; + if (conflict_id) + con = CONFLICT; + else if (unsat_constraint) + con = CONSTRAINT; + else + con = ASSUMPTIONS; + proof->conclude_unsat (con, conclusion); +} + +void Internal::reset_concluded () { + if (proof) + proof->reset_assumptions (); + if (concluded) { + LOG ("reset concluded"); + concluded = false; + } + if (conflict_id) { + CADICAL_assert (conclusion.size () == 1); + return; + } + conclusion.clear (); +} + +// Add the start of each incremental phase (leaving the state +// 'UNSATISFIABLE' actually) we reset all assumptions. + +void Internal::reset_assumptions () { + for (const auto &lit : assumptions) { + Flags &f = flags (lit); + const unsigned char bit = bign (lit); + f.assumed &= ~bit; + f.failed &= ~bit; + melt (lit); + } + LOG ("cleared %zd assumptions", assumptions.size ()); + assumptions.clear (); + marked_failed = true; +} + +struct sort_assumptions_positive_rank { + Internal *internal; + + // Decision level could be 'INT_MAX' and thus 'level + 1' could overflow. + // Therefore we carefully have to use 'unsigned' for levels below. + + const unsigned max_level; + + sort_assumptions_positive_rank (Internal *s) + : internal (s), max_level (s->level + 1u) {} + + typedef uint64_t Type; + + // Set assumptions first, then sorted by position on the trail + // unset literals are sorted by literal value. + + Type operator() (const int &a) const { + const int val = internal->val (a); + const bool assigned = (val != 0); + const Var &v = internal->var (a); + uint64_t res = (assigned ? (unsigned) v.level : max_level); + res <<= 32; + res |= (assigned ? v.trail : abs (a)); + return res; + } +}; + +struct sort_assumptions_smaller { + Internal *internal; + sort_assumptions_smaller (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return sort_assumptions_positive_rank (internal) (a) < + sort_assumptions_positive_rank (internal) (b); + } +}; + +// Sort the assumptions by the current position on the trail and backtrack +// to the first place where the assumptions and the current trail differ. + +void Internal::sort_and_reuse_assumptions () { + CADICAL_assert (opts.ilbassumptions); + if (assumptions.empty ()) + return; + MSORT (opts.radixsortlim, assumptions.begin (), assumptions.end (), + sort_assumptions_positive_rank (this), + sort_assumptions_smaller (this)); + + unsigned max_level = 0; + // assumptions are sorted by level, with unset at the end + for (auto lit : assumptions) { + if (val (lit)) + max_level = var (lit).level; + else + break; + } + + const unsigned size = min (level + 1u, max_level + 1); + CADICAL_assert ((size_t) level == control.size () - 1); + LOG (assumptions, "sorted assumptions"); + int target = 0; + for (unsigned i = 1, j = 0; i < size;) { + const Level &l = control[i]; + const int lit = l.decision; + const int alit = assumptions[j]; + const int lev = i; + target = lev; + if (val (alit) > 0 && + var (alit).level < lev) { // we can ignore propagated assumptions + LOG ("ILB skipping propagation %d", alit); + ++j; + continue; + } + if (!lit) { // skip fake decisions + target = lev - 1; + break; + } + ++i, ++j; + CADICAL_assert (var (lit).level == lev); + if (l.decision == alit) { + continue; + } + target = lev - 1; + LOG ("first different literal %d on the trail and %d from the " + "assumptions", + lit, alit); + break; + } + if (target < level) + backtrack (target); + LOG ("assumptions allow for reuse of trail up to level %d", level); + // COVER (target > 1); + if ((size_t) level > assumptions.size ()) + stats.assumptionsreused += assumptions.size (); + else + stats.assumptionsreused += level; +} +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_averages.cpp b/src/sat/cadical/cadical_averages.cpp new file mode 100644 index 000000000..d40a98cdb --- /dev/null +++ b/src/sat/cadical/cadical_averages.cpp @@ -0,0 +1,40 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::init_averages () { + + LOG ("initializing averages"); + + INIT_EMA (averages.current.jump, opts.emajump); + INIT_EMA (averages.current.level, opts.emalevel); + INIT_EMA (averages.current.size, opts.emasize); + + INIT_EMA (averages.current.glue.fast, opts.emagluefast); + INIT_EMA (averages.current.glue.slow, opts.emaglueslow); + + INIT_EMA (averages.current.decisions, opts.emadecisions); + + INIT_EMA (averages.current.trail.fast, opts.ematrailfast); + INIT_EMA (averages.current.trail.slow, opts.ematrailslow); + + CADICAL_assert (!averages.swapped); +} + +void Internal::swap_averages () { + LOG ("saving current averages"); + swap (averages.current, averages.saved); + if (!averages.swapped) + init_averages (); + else + LOG ("swapping in previously saved averages"); + averages.swapped++; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_backtrack.cpp b/src/sat/cadical/cadical_backtrack.cpp new file mode 100644 index 000000000..b3c519ba2 --- /dev/null +++ b/src/sat/cadical/cadical_backtrack.cpp @@ -0,0 +1,179 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// The global assignment stack can only be (partially) reset through +// 'backtrack' which is the only function using 'unassign' (inlined and thus +// local to this file). It turns out that 'unassign' does not need a +// specialization for 'probe' nor 'vivify' and thus it is shared. + +inline void Internal::unassign (int lit) { + CADICAL_assert (val (lit) > 0); + set_val (lit, 0); + + int idx = vidx (lit); + LOG ("unassign %d @ %d", lit, var (idx).level); + num_assigned--; + + // In the standard EVSIDS variable decision heuristic of MiniSAT, we need + // to push variables which become unassigned back to the heap. + // + if (!scores.contains (idx)) + scores.push_back (idx); + + // For VMTF we need to update the 'queue.unassigned' pointer in case this + // variable sits after the variable to which 'queue.unassigned' currently + // points. See our SAT'15 paper for more details on this aspect. + // + if (queue.bumped < btab[idx]) + update_queue_unassigned (idx); +} + +/*------------------------------------------------------------------------*/ + +// Update the current target maximum assignment and also the very best +// assignment. Whether a trail produces a conflict is determined during +// propagation. Thus that all functions in the 'search' loop after +// propagation can assume that 'no_conflict_until' is valid. If a conflict +// is found then the trail before the last decision is used (see the end of +// 'propagate'). During backtracking we can then save this largest +// propagation conflict free assignment. It is saved as both 'target' +// assignment for picking decisions in 'stable' mode and if it is the +// largest ever such assignment also as 'best' assignment. This 'best' +// assignment can then be used in future stable decisions after the next +// 'rephase_best' overwrites saved phases with it. + +void Internal::update_target_and_best () { + + bool reset = (rephased && stats.conflicts > last.rephase.conflicts); + + if (reset) { + target_assigned = 0; + if (rephased == 'B') + best_assigned = 0; // update it again + } + + if (no_conflict_until > target_assigned) { + copy_phases (phases.target); + target_assigned = no_conflict_until; + LOG ("new target trail level %zu", target_assigned); + } + + if (no_conflict_until > best_assigned) { + copy_phases (phases.best); + best_assigned = no_conflict_until; + LOG ("new best trail level %zu", best_assigned); + } + + if (reset) { + report (rephased); + rephased = 0; + } +} + +/*------------------------------------------------------------------------*/ + +void Internal::backtrack (int new_level) { + CADICAL_assert (new_level <= level); + if (new_level == level) + return; + + update_target_and_best (); + backtrack_without_updating_phases (new_level); +} + +void Internal::backtrack_without_updating_phases (int new_level) { + + CADICAL_assert (new_level <= level); + if (new_level == level) + return; + + stats.backtracks++; + + CADICAL_assert (num_assigned == trail.size ()); + + const size_t assigned = control[new_level + 1].trail; + + LOG ("backtracking to decision level %d with decision %d and trail %zd", + new_level, control[new_level].decision, assigned); + + const size_t end_of_trail = trail.size (); + size_t i = assigned, j = i; + +#ifdef LOGGING + int unassigned = 0; +#endif + int reassigned = 0; + + notify_backtrack (new_level); + if (external_prop && !external_prop_is_lazy && !private_steps && + notified > assigned) { + LOG ("external propagator is notified about some unassignments (trail: " + "%zd, notified: %zd).", + trail.size (), notified); + notified = assigned; + } + + while (i < end_of_trail) { + int lit = trail[i++]; + Var &v = var (lit); + if (v.level > new_level) { + unassign (lit); +#ifdef LOGGING + unassigned++; +#endif + } else { + // This is the essence of the SAT'18 paper on chronological + // backtracking. It is possible to just keep out-of-order assigned + // literals on the trail without breaking the solver (after some + // modifications to 'analyze' - see 'opts.chrono' guarded code there). + CADICAL_assert (opts.chrono || external_prop || did_external_prop); +#ifdef LOGGING + if (!v.level) + LOG ("reassign %d @ 0 unit clause %d", lit, lit); + else + LOG (v.reason, "reassign %d @ %d", lit, v.level); +#endif + trail[j] = lit; + v.trail = j++; + reassigned++; + } + } + trail.resize (j); + LOG ("unassigned %d literals %.0f%%", unassigned, + percent (unassigned, unassigned + reassigned)); + LOG ("reassigned %d literals %.0f%%", reassigned, + percent (reassigned, unassigned + reassigned)); + + if (propagated > assigned) + propagated = assigned; + if (propagated2 > assigned) + propagated2 = assigned; + if (no_conflict_until > assigned) + no_conflict_until = assigned; + + propergated = 0; // Always go back to root-level. + + CADICAL_assert (notified <= assigned + reassigned); + if (reassigned) { + notify_assignments (); + } + + control.resize (new_level + 1); + level = new_level; + if (tainted_literal) { + CADICAL_assert (opts.ilb); + if (!val (tainted_literal)) { + tainted_literal = 0; + } + } + CADICAL_assert (num_assigned == trail.size ()); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_backward.cpp b/src/sat/cadical/cadical_backward.cpp new file mode 100644 index 000000000..5778029a5 --- /dev/null +++ b/src/sat/cadical/cadical_backward.cpp @@ -0,0 +1,237 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Provide eager backward subsumption for resolved clauses. + +// The eliminator maintains a queue of clauses that are new and have to be +// checked to subsume or strengthen other (longer or same size) clauses. + +void Eliminator::enqueue (Clause *c) { + if (!internal->opts.elimbackward) + return; + if (c->enqueued) + return; + LOG (c, "backward enqueue"); + backward.push (c); + c->enqueued = true; +} + +Clause *Eliminator::dequeue () { + if (backward.empty ()) + return 0; + Clause *res = backward.front (); + backward.pop (); + CADICAL_assert (res->enqueued); + res->enqueued = false; + LOG (res, "backward dequeue"); + return res; +} + +Eliminator::~Eliminator () { + while (dequeue ()) + ; +} + +/*------------------------------------------------------------------------*/ + +void Internal::elim_backward_clause (Eliminator &eliminator, Clause *c) { + CADICAL_assert (opts.elimbackward); + CADICAL_assert (!c->redundant); + if (c->garbage) + return; + LOG (c, "attempting backward subsumption and strengthening with"); + size_t len = UINT_MAX; + unsigned size = 0; + int best = 0; + bool satisfied = false; + CADICAL_assert (mini_chain.empty ()); + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + size_t l = occs (lit).size (); + LOG ("literal %d occurs %zd times", lit, l); + if (l < len) + best = lit, len = l; + mark (lit); + size++; + } + if (satisfied) { + LOG ("clause actually already satisfied"); + elim_update_removed_clause (eliminator, c); + mark_garbage (c); + } else if (len > (size_t) opts.elimocclim) { + LOG ("skipping backward subsumption due to too many occurrences"); + } else { + CADICAL_assert (len); + LOG ("literal %d has smallest number of occurrences %zd", best, len); + LOG ("marked %d literals in clause of size %d", size, c->size); + for (auto &d : occs (best)) { + if (d == c) + continue; + if (d->garbage) + continue; + if ((unsigned) d->size < size) + continue; + int negated = 0; + unsigned found = 0; + satisfied = false; + for (const auto &lit : *d) { + signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + tmp = marked (lit); + if (!tmp) + continue; + if (tmp < 0) { + if (negated) { + size = UINT_MAX; + break; + } else + negated = lit; + } + if (++found == size) + break; + } + if (satisfied) { + LOG (d, "found satisfied clause"); + elim_update_removed_clause (eliminator, d); + mark_garbage (d); + } else if (found == size) { + if (!negated) { + LOG (d, "found subsumed clause"); + elim_update_removed_clause (eliminator, d); + mark_garbage (d); + stats.subsumed++; + stats.elimbwsub++; + } else { + int unit = 0; + CADICAL_assert (minimize_chain.empty ()); + CADICAL_assert (analyzed.empty ()); + CADICAL_assert (lrat_chain.empty ()); + // figure out wether we strengthen c or get a new unit + for (const auto &lit : *d) { + const signed char tmp = val (lit); + if (tmp < 0) { + if (!lrat) + continue; + Flags &f = flags (lit); + CADICAL_assert (!f.seen); + if (f.seen) + continue; + f.seen = true; + analyzed.push_back (lit); + continue; + } + if (tmp > 0) { + satisfied = true; + break; + } + if (lit == negated) + continue; + if (unit) { + unit = INT_MIN; + continue; // needed to guarantee d is not satsified + } else + unit = lit; + } + if (lrat && !satisfied) { + // if we found a unit we need to add all unit ids from + // {c\d}U{d\c} otherwise just the unit ids from {c\d} + for (const auto &lit : *c) { + const signed char tmp = val (lit); + CADICAL_assert (tmp <= 0); + if (tmp >= 0) + continue; + Flags &f = flags (lit); + if (f.seen && unit && unit == INT_MIN) { + f.seen = false; + continue; + } else if (!f.seen) { + f.seen = true; + analyzed.push_back (lit); + } + } + if (unit == INT_MIN) { // we do not need units from {d\c} + for (const auto &lit : *d) { + flags (lit).seen = false; + } + } + for (const auto &lit : analyzed) { + Flags &f = flags (lit); + if (!f.seen) { + f.seen = true; + continue; + } + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + } + clear_analyzed_literals (); + lrat_chain.push_back (d->id); + lrat_chain.push_back (c->id); + } else if (lrat) + clear_analyzed_literals (); + if (satisfied) { + CADICAL_assert (lrat_chain.empty ()); + mark_garbage (d); + elim_update_removed_clause (eliminator, d); + } else if (unit && unit != INT_MIN) { + CADICAL_assert (unit); + LOG (d, "unit %d through hyper unary resolution with", unit); + assign_unit (unit); + elim_propagate (eliminator, unit); + lrat_chain.clear (); + break; + } else if (occs (negated).size () <= (size_t) opts.elimocclim) { + strengthen_clause (d, negated); + remove_occs (occs (negated), d); + elim_update_removed_lit (eliminator, negated); + stats.elimbwstr++; + CADICAL_assert (negated != best); + eliminator.enqueue (d); + } + lrat_chain.clear (); + } + } + } + } + mini_chain.clear (); + unmark (c); +} + +/*------------------------------------------------------------------------*/ + +void Internal::elim_backward_clauses (Eliminator &eliminator) { + if (!opts.elimbackward) { + CADICAL_assert (eliminator.backward.empty ()); + return; + } + START (backward); + LOG ("attempting backward subsumption and strengthening with %zd clauses", + eliminator.backward.size ()); + Clause *c; + while (!unsat && (c = eliminator.dequeue ())) + elim_backward_clause (eliminator, c); + STOP (backward); +} + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_bins.cpp b/src/sat/cadical/cadical_bins.cpp new file mode 100644 index 000000000..fba96f216 --- /dev/null +++ b/src/sat/cadical/cadical_bins.cpp @@ -0,0 +1,28 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Binary implication graph lists. + +void Internal::init_bins () { + CADICAL_assert (big.empty ()); + if (big.size () < 2 * vsize) + big.resize (2 * vsize, Bins ()); + LOG ("initialized binary implication graph"); +} + +void Internal::reset_bins () { + CADICAL_assert (!big.empty ()); + erase_vector (big); + LOG ("reset binary implication graph"); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_block.cpp b/src/sat/cadical/cadical_block.cpp new file mode 100644 index 000000000..12f852f08 --- /dev/null +++ b/src/sat/cadical/cadical_block.cpp @@ -0,0 +1,830 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This implements an inprocessing version of blocked clause elimination and +// is assumed to be triggered just before bounded variable elimination. It +// has a separate 'block' flag while variable elimination uses 'elim'. +// Thus it only tries to block clauses on a literal which was removed in an +// irredundant clause in negated form before and has not been tried to use +// as blocking literal since then. + +/*------------------------------------------------------------------------*/ + +inline bool block_more_occs_size::operator() (unsigned a, unsigned b) { + size_t s = internal->noccs (-internal->u2i (a)); + size_t t = internal->noccs (-internal->u2i (b)); + if (s > t) + return true; + if (s < t) + return false; + s = internal->noccs (internal->u2i (a)); + t = internal->noccs (internal->u2i (b)); + if (s > t) + return true; + if (s < t) + return false; + return a > b; +} + +/*------------------------------------------------------------------------*/ + +// Determine whether 'c' is blocked on 'lit', by first marking all its +// literals and then checking all resolvents with negative clauses (with +// '-lit') are tautological. We use a move-to-front scheme for both the +// occurrence list of negative clauses (with '-lit') and then for literals +// within each such clause. The clause move-to-front scheme has the goal to +// find non-tautological clauses faster in the future, while the literal +// move-to-front scheme has the goal to faster find the matching literal, +// which makes the resolvent tautological (again in the future). + +bool Internal::is_blocked_clause (Clause *c, int lit) { + + LOG (c, "trying to block on %d", lit); + + CADICAL_assert (c->size >= opts.blockminclslim); + CADICAL_assert (c->size <= opts.blockmaxclslim); + CADICAL_assert (active (lit)); + CADICAL_assert (!val (lit)); + CADICAL_assert (!c->garbage); + CADICAL_assert (!c->redundant); + CADICAL_assert (!level); + + mark (c); // First mark all literals in 'c'. + + Occs &os = occs (-lit); + LOG ("resolving against at most %zd clauses with %d", os.size (), -lit); + + bool res = true; // Result is true if all resolvents tautological. + + // Can not use 'auto' here since we update 'os' during traversal. + // + const auto end_of_os = os.end (); + auto i = os.begin (); + + Clause *prev_d = 0; // Previous non-tautological clause. + + for (; i != end_of_os; i++) { + // Move the first clause with non-tautological resolvent to the front of + // the occurrence list to improve finding it faster later. + // + Clause *d = *i; + + CADICAL_assert (!d->garbage); + CADICAL_assert (!d->redundant); + CADICAL_assert (d->size <= opts.blockmaxclslim); + + *i = prev_d; // Move previous non-tautological clause + prev_d = d; // backwards but remember clause at this position. + + LOG (d, "resolving on %d against", lit); + stats.blockres++; + + int prev_other = 0; // Previous non-tautological literal. + + // No 'auto' since we update literals of 'd' during traversal. + // + const const_literal_iterator end_of_d = d->end (); + literal_iterator l; + + for (l = d->begin (); l != end_of_d; l++) { + // Same move-to-front mechanism for literals within a clause. It + // moves the first negatively marked literal to the front to find it + // faster in the future. + // + const int other = *l; + *l = prev_other; + prev_other = other; + if (other == -lit) + continue; + CADICAL_assert (other != lit); + CADICAL_assert (active (other)); + CADICAL_assert (!val (other)); + if (marked (other) < 0) { + LOG ("found tautological literal %d", other); + d->literals[0] = other; // Move to front of 'd'. + break; + } + } + + if (l == end_of_d) { + LOG ("no tautological literal found"); + // + // Since we did not find a tautological literal we restore the old + // order of literals in the clause. + // + const const_literal_iterator begin_of_d = d->begin (); + while (l-- != begin_of_d) { + const int other = *l; + *l = prev_other; + prev_other = other; + } + res = false; // Now 'd' is a witness that 'c' is not blocked. + os[0] = d; // Move it to the front of the occurrence list. + break; + } + } + unmark (c); // ... all literals of the candidate clause. + + // If all resolvents are tautological and thus the clause is blocked we + // restore the old order of clauses in the occurrence list of '-lit'. + // + if (res) { + CADICAL_assert (i == end_of_os); + const auto boc = os.begin (); + while (i != boc) { + Clause *d = *--i; + *i = prev_d; + prev_d = d; + } + } + + return res; +} + +/*------------------------------------------------------------------------*/ + +void Internal::block_schedule (Blocker &blocker) { + // Set skip flags for all literals in too large clauses. + // + for (const auto &c : clauses) { + + if (c->garbage) + continue; + if (c->redundant) + continue; + if (c->size <= opts.blockmaxclslim) + continue; + + for (const auto &lit : *c) + mark_skip (-lit); + } + + // Connect all literal occurrences in irredundant clauses. + // + for (const auto &c : clauses) { + + if (c->garbage) + continue; + if (c->redundant) + continue; + + for (const auto &lit : *c) { + CADICAL_assert (active (lit)); + CADICAL_assert (!val (lit)); + occs (lit).push_back (c); + } + } + + // We establish the invariant that 'noccs' gives the number of actual + // occurrences of 'lit' in non-garbage clauses, while 'occs' might still + // refer to garbage clauses, thus 'noccs (lit) <= occs (lit).size ()'. It + // is expensive to remove references to garbage clauses from 'occs' during + // blocked clause elimination, but decrementing 'noccs' is cheap. + + for (auto lit : lits) { + if (!active (lit)) + continue; + CADICAL_assert (!val (lit)); + Occs &os = occs (lit); + noccs (lit) = os.size (); + } + + // Now we fill the schedule (priority queue) of candidate literals to be + // tried as blocking literals. It is probably slightly faster to do this + // in one go after all occurrences have been determined, instead of + // filling the priority queue during pushing occurrences. Filling the + // schedule can not be fused with the previous loop (easily) since we + // first have to initialize 'noccs' for both 'lit' and '-lit'. + +#ifndef CADICAL_QUIET + int skipped = 0; +#endif + + for (auto idx : vars) { + if (!active (idx)) + continue; + if (frozen (idx)) { +#ifndef CADICAL_QUIET + skipped += 2; +#endif + continue; + } + CADICAL_assert (!val (idx)); + for (int sign = -1; sign <= 1; sign += 2) { + const int lit = sign * idx; + if (marked_skip (lit)) { +#ifndef CADICAL_QUIET + skipped++; +#endif + continue; + } + if (!marked_block (lit)) + continue; + unmark_block (lit); + LOG ("scheduling %d with %" PRId64 " positive and %" PRId64 + " negative occurrences", + lit, noccs (lit), noccs (-lit)); + blocker.schedule.push_back (vlit (lit)); + } + } + + PHASE ("block", stats.blockings, + "scheduled %zd candidate literals %.2f%% (%d skipped %.2f%%)", + blocker.schedule.size (), + percent (blocker.schedule.size (), 2.0 * active ()), skipped, + percent (skipped, 2.0 * active ())); +} + +/*------------------------------------------------------------------------*/ + +// A literal is pure if it only occurs positive. Then all clauses in which +// it occurs are blocked on it. This special case can be implemented faster +// than trying to block literals with at least one negative occurrence and +// is thus handled separately. It also allows to avoid pushing blocked +// clauses onto the extension stack. + +void Internal::block_pure_literal (Blocker &blocker, int lit) { + if (frozen (lit)) + return; + CADICAL_assert (active (lit)); + + Occs &pos = occs (lit); + Occs &nos = occs (-lit); + + CADICAL_assert (!noccs (-lit)); +#ifndef CADICAL_NDEBUG + for (const auto &c : nos) + CADICAL_assert (c->garbage); +#endif + stats.blockpurelits++; + LOG ("found pure literal %d", lit); +#ifdef LOGGING + int64_t pured = 0; +#endif + for (const auto &c : pos) { + if (c->garbage) + continue; + CADICAL_assert (!c->redundant); + LOG (c, "pure literal %d in", lit); + blocker.reschedule.push_back (c); + if (proof) { + proof->weaken_minus (c); + } + external->push_clause_on_extension_stack (c, lit); + stats.blockpured++; + mark_garbage (c); +#ifdef LOGGING + pured++; +#endif + } + + erase_vector (pos); + erase_vector (nos); + + mark_pure (lit); + stats.blockpured++; + LOG ("blocking %" PRId64 " clauses on pure literal %d", pured, lit); +} + +/*------------------------------------------------------------------------*/ + +// If there is only one negative clause with '-lit' it is faster to mark it +// instead of marking all the positive clauses with 'lit' one after the +// other and then resolving against the negative clause. + +void Internal::block_literal_with_one_negative_occ (Blocker &blocker, + int lit) { + CADICAL_assert (active (lit)); + CADICAL_assert (!frozen (lit)); + CADICAL_assert (noccs (lit) > 0); + CADICAL_assert (noccs (-lit) == 1); + + Occs &nos = occs (-lit); + CADICAL_assert (nos.size () >= 1); + + Clause *d = 0; + for (const auto &c : nos) { + if (c->garbage) + continue; + CADICAL_assert (!d); + d = c; +#ifndef CADICAL_NDEBUG + break; +#endif + } + CADICAL_assert (d); + nos.resize (1); + nos[0] = d; + + if (d && d->size > opts.blockmaxclslim) { + LOG (d, "skipped common antecedent"); + return; + } + + CADICAL_assert (!d->garbage); + CADICAL_assert (!d->redundant); + CADICAL_assert (d->size <= opts.blockmaxclslim); + + LOG (d, "common %d antecedent", lit); + mark (d); + int64_t blocked = 0; +#ifdef LOGGING + int64_t skipped = 0; +#endif + Occs &pos = occs (lit); + + // Again no 'auto' since 'pos' is update during traversal. + // + const auto eop = pos.end (); + auto j = pos.begin (), i = j; + + for (; i != eop; i++) { + Clause *c = *j++ = *i; + + if (c->garbage) { + j--; + continue; + } + if (c->size > opts.blockmaxclslim) { +#ifdef LOGGING + skipped++; +#endif + continue; + } + if (c->size < opts.blockminclslim) { +#ifdef LOGGING + skipped++; +#endif + continue; + } + + LOG (c, "trying to block on %d", lit); + + // We use the same literal move-to-front strategy as in + // 'is_blocked_clause'. See there for more explanations. + + int prev_other = 0; // Previous non-tautological literal. + + // No 'auto' since literals of 'c' are updated during traversal. + // + const const_literal_iterator end_of_c = c->end (); + literal_iterator l; + + for (l = c->begin (); l != end_of_c; l++) { + const int other = *l; + *l = prev_other; + prev_other = other; + if (other == lit) + continue; + CADICAL_assert (other != -lit); + CADICAL_assert (active (other)); + CADICAL_assert (!val (other)); + if (marked (other) < 0) { + LOG ("found tautological literal %d", other); + c->literals[0] = other; // Move to front of 'c'. + break; + } + } + + if (l == end_of_c) { + LOG ("no tautological literal found"); + + // Restore old literal order in the clause because. + + const const_literal_iterator begin_of_c = c->begin (); + while (l-- != begin_of_c) { + const int other = *l; + *l = prev_other; + prev_other = other; + } + + continue; // ... with next candidate 'c' in 'pos'. + } + + blocked++; + LOG (c, "blocked"); + if (proof) { + proof->weaken_minus (c); + } + external->push_clause_on_extension_stack (c, lit); + blocker.reschedule.push_back (c); + mark_garbage (c); + j--; + } + if (j == pos.begin ()) + erase_vector (pos); + else + pos.resize (j - pos.begin ()); + + stats.blocked += blocked; + LOG ("blocked %" PRId64 " clauses on %d (skipped %" PRId64 ")", blocked, + lit, skipped); + + unmark (d); +} + +/*------------------------------------------------------------------------*/ + +// Determine the set of candidate clauses with 'lit', which are checked to +// be blocked by 'lit'. Filter out too large and small clauses and which do +// not have any negated other literal in any of the clauses with '-lit'. + +size_t Internal::block_candidates (Blocker &blocker, int lit) { + + CADICAL_assert (blocker.candidates.empty ()); + + Occs &pos = occs (lit); // Positive occurrences of 'lit'. + Occs &nos = occs (-lit); + + CADICAL_assert ((size_t) noccs (lit) <= pos.size ()); + CADICAL_assert ((size_t) noccs (-lit) == nos.size ()); // Already flushed. + + // Mark all literals in clauses with '-lit'. Note that 'mark2' uses + // separate bits for 'lit' and '-lit'. + // + for (const auto &c : nos) + mark2 (c); + + const auto eop = pos.end (); + auto j = pos.begin (), i = j; + + for (; i != eop; i++) { + Clause *c = *j++ = *i; + if (c->garbage) { + j--; + continue; + } + CADICAL_assert (!c->redundant); + if (c->size > opts.blockmaxclslim) + continue; + if (c->size < opts.blockminclslim) + continue; + const const_literal_iterator eoc = c->end (); + const_literal_iterator l; + for (l = c->begin (); l != eoc; l++) { + const int other = *l; + if (other == lit) + continue; + CADICAL_assert (other != -lit); + CADICAL_assert (active (other)); + CADICAL_assert (!val (other)); + if (marked2 (-other)) + break; + } + if (l != eoc) + blocker.candidates.push_back (c); + } + if (j == pos.begin ()) + erase_vector (pos); + else + pos.resize (j - pos.begin ()); + + CADICAL_assert (pos.size () == (size_t) noccs (lit)); // Now also flushed. + + for (const auto &c : nos) + unmark (c); + + return blocker.candidates.size (); +} + +/*------------------------------------------------------------------------*/ + +// Try to find a clause with '-lit' which does not have any literal in +// clauses with 'lit'. If such a clause exists no candidate clause can be +// blocked on 'lit' since all candidates would produce a non-tautological +// resolvent with that clause. + +Clause *Internal::block_impossible (Blocker &blocker, int lit) { + CADICAL_assert (noccs (-lit) > 1); + CADICAL_assert (blocker.candidates.size () > 1); + + for (const auto &c : blocker.candidates) + mark2 (c); + + Occs &nos = occs (-lit); + Clause *res = 0; + + for (const auto &c : nos) { + CADICAL_assert (!c->garbage); + CADICAL_assert (!c->redundant); + CADICAL_assert (c->size <= opts.blockmaxclslim); + const const_literal_iterator eoc = c->end (); + const_literal_iterator l; + for (l = c->begin (); l != eoc; l++) { + const int other = *l; + if (other == -lit) + continue; + CADICAL_assert (other != lit); + CADICAL_assert (active (other)); + CADICAL_assert (!val (other)); + if (marked2 (-other)) + break; + } + if (l == eoc) + res = c; + } + + for (const auto &c : blocker.candidates) + unmark (c); + + if (res) { + LOG (res, "common non-tautological resolvent producing"); + blocker.candidates.clear (); + } + + return res; +} + +/*------------------------------------------------------------------------*/ + +// In the general case we have at least two negative occurrences. + +void Internal::block_literal_with_at_least_two_negative_occs ( + Blocker &blocker, int lit) { + CADICAL_assert (active (lit)); + CADICAL_assert (!frozen (lit)); + CADICAL_assert (noccs (lit) > 0); + CADICAL_assert (noccs (-lit) > 1); + + Occs &nos = occs (-lit); + CADICAL_assert ((size_t) noccs (-lit) <= nos.size ()); + + int max_size = 0; + + // Flush all garbage clauses in occurrence list 'nos' of '-lit' and + // determine the maximum size of negative clauses (with '-lit'). + // + const auto eon = nos.end (); + auto j = nos.begin (), i = j; + for (; i != eon; i++) { + Clause *c = *j++ = *i; + if (c->garbage) + j--; + else if (c->size > max_size) + max_size = c->size; + } + if (j == nos.begin ()) + erase_vector (nos); + else + nos.resize (j - nos.begin ()); + + CADICAL_assert (nos.size () == (size_t) noccs (-lit)); + CADICAL_assert (nos.size () > 1); + + // If the maximum size of a negative clause (with '-lit') exceeds the + // maximum clause size limit ignore this candidate literal. + // + if (max_size > opts.blockmaxclslim) { + LOG ("maximum size %d of clauses with %d exceeds clause size limit %d", + max_size, -lit, opts.blockmaxclslim); + return; + } + + LOG ("maximum size %d of clauses with %d", max_size, -lit); + + // We filter candidate clauses with positive occurrence of 'lit' in + // 'blocker.candidates' and return if no candidate clause remains. + // Candidates should be small enough and should have at least one literal + // which occurs negated in one of the clauses with '-lit'. + // + size_t candidates = block_candidates (blocker, lit); + if (!candidates) { + LOG ("no candidate clauses found"); + return; + } + + LOG ("found %zd candidate clauses", candidates); + + // We further search for a clause with '-lit' that has no literal + // negated in any of the candidate clauses (except 'lit'). If such a + // clause exists, we know that none of the candidates is blocked. + // + if (candidates > 1 && block_impossible (blocker, lit)) { + LOG ("impossible to block any candidate clause on %d", lit); + CADICAL_assert (blocker.candidates.empty ()); + return; + } + + LOG ("trying to block %zd clauses out of %" PRId64 " with literal %d", + candidates, noccs (lit), lit); + + int64_t blocked = 0; + + // Go over all remaining candidates and try to block them on 'lit'. + // + for (const auto &c : blocker.candidates) { + CADICAL_assert (!c->garbage); + CADICAL_assert (!c->redundant); + if (!is_blocked_clause (c, lit)) + continue; + blocked++; + LOG (c, "blocked"); + if (proof) { + proof->weaken_minus (c); + } + external->push_clause_on_extension_stack (c, lit); + blocker.reschedule.push_back (c); + mark_garbage (c); + } + + LOG ("blocked %" PRId64 + " clauses on %d out of %zd candidates in %zd occurrences", + blocked, lit, blocker.candidates.size (), occs (lit).size ()); + + blocker.candidates.clear (); + stats.blocked += blocked; + if (blocked) + flush_occs (lit); +} + +/*------------------------------------------------------------------------*/ + +// Reschedule literals in a clause (except 'lit') which was blocked. + +void Internal::block_reschedule_clause (Blocker &blocker, int lit, + Clause *c) { +#ifdef CADICAL_NDEBUG + (void) lit; +#endif + CADICAL_assert (c->garbage); + + for (const auto &other : *c) { + + int64_t &n = noccs (other); + CADICAL_assert (n > 0); + n--; + + LOG ("updating %d with %" PRId64 " positive and %" PRId64 + " negative occurrences", + other, noccs (other), noccs (-other)); + + if (blocker.schedule.contains (vlit (-other))) + blocker.schedule.update (vlit (-other)); + else if (active (other) && !frozen (other) && !marked_skip (-other)) { + LOG ("rescheduling to block clauses on %d", -other); + blocker.schedule.push_back (vlit (-other)); + } + + if (blocker.schedule.contains (vlit (other))) { + CADICAL_assert (other != lit); + blocker.schedule.update (vlit (other)); + } + } +} + +// Reschedule all literals in clauses blocked by 'lit' (except 'lit'). + +void Internal::block_reschedule (Blocker &blocker, int lit) { + while (!blocker.reschedule.empty ()) { + Clause *c = blocker.reschedule.back (); + blocker.reschedule.pop_back (); + block_reschedule_clause (blocker, lit, c); + } +} + +/*------------------------------------------------------------------------*/ + +void Internal::block_literal (Blocker &blocker, int lit) { + CADICAL_assert (!marked_skip (lit)); + + if (!active (lit)) + return; // Pure literal '-lit'. + if (frozen (lit)) + return; + + CADICAL_assert (!val (lit)); + + // If the maximum number of a negative clauses (with '-lit') exceeds the + // occurrence limit ignore this candidate literal. + // + if (noccs (-lit) > opts.blockocclim) + return; + + LOG ("blocking literal candidate %d " + "with %" PRId64 " positive and %" PRId64 " negative occurrences", + lit, noccs (lit), noccs (-lit)); + + stats.blockcands++; + + CADICAL_assert (blocker.reschedule.empty ()); + CADICAL_assert (blocker.candidates.empty ()); + + if (!noccs (-lit)) + block_pure_literal (blocker, lit); + else if (!noccs (lit)) { + // Rare situation, where the clause length limit was hit for 'lit' and + // '-lit' is skipped and then it becomes pure. Can be ignored. We also + // so it once happening for a 'elimboundmin=-1' and zero positive and + // one negative occurrence. + } else if (noccs (-lit) == 1) + block_literal_with_one_negative_occ (blocker, lit); + else + block_literal_with_at_least_two_negative_occs (blocker, lit); + + // Done with blocked clause elimination on this literal and we do not + // have to try blocked clause elimination on it again until irredundant + // clauses with its negation are removed. + // + CADICAL_assert (!frozen (lit)); // just to be sure ... + unmark_block (lit); +} + +/*------------------------------------------------------------------------*/ + +bool Internal::block () { + + if (!opts.block) + return false; + if (unsat) + return false; + if (!stats.current.irredundant) + return false; + if (terminated_asynchronously ()) + return false; + + if (propagated < trail.size ()) { + LOG ("need to propagate %zd units first", trail.size () - propagated); + init_watches (); + connect_watches (); + if (!propagate ()) { + LOG ("propagating units results in empty clause"); + learn_empty_clause (); + CADICAL_assert (unsat); + } + clear_watches (); + reset_watches (); + if (unsat) + return false; + } + + START_SIMPLIFIER (block, BLOCK); + + stats.blockings++; + + LOG ("block-%" PRId64 "", stats.blockings); + + CADICAL_assert (!level); + CADICAL_assert (!watching ()); + CADICAL_assert (!occurring ()); + + mark_satisfied_clauses_as_garbage (); + + init_occs (); // Occurrence lists for all literals. + init_noccs (); // Number of occurrences to avoid flushing garbage clauses. + + Blocker blocker (this); + block_schedule (blocker); + + int64_t blocked = stats.blocked; + int64_t resolutions = stats.blockres; + int64_t purelits = stats.blockpurelits; + int64_t pured = stats.blockpured; + + while (!terminated_asynchronously () && !blocker.schedule.empty ()) { + int lit = u2i (blocker.schedule.front ()); + blocker.schedule.pop_front (); + block_literal (blocker, lit); + block_reschedule (blocker, lit); + } + + blocker.erase (); + reset_noccs (); + reset_occs (); + + resolutions = stats.blockres - resolutions; + blocked = stats.blocked - blocked; + + PHASE ("block", stats.blockings, + "blocked %" PRId64 " clauses in %" PRId64 " resolutions", blocked, + resolutions); + + pured = stats.blockpured - pured; + purelits = stats.blockpurelits - purelits; + + if (pured) + mark_redundant_clauses_with_eliminated_variables_as_garbage (); + + if (purelits) + PHASE ("block", stats.blockings, + "found %" PRId64 " pure literals in %" PRId64 " clauses", + purelits, pured); + else + PHASE ("block", stats.blockings, "no pure literals found"); + + report ('b', !opts.reportall && !blocked); + + STOP_SIMPLIFIER (block, BLOCK); + + return blocked; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_ccadical.cpp b/src/sat/cadical/cadical_ccadical.cpp new file mode 100644 index 000000000..ae5c1093f --- /dev/null +++ b/src/sat/cadical/cadical_ccadical.cpp @@ -0,0 +1,211 @@ +#include "global.h" + +#include "cadical.hpp" + +#include +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +struct Wrapper : Learner, Terminator { + + Solver *solver; + struct { + void *state; + int (*function) (void *); + } terminator; + + struct { + void *state; + int max_length; + int *begin_clause, *end_clause, *capacity_clause; + void (*function) (void *, int *); + } learner; + + bool terminate () { + if (!terminator.function) + return false; + return terminator.function (terminator.state); + } + + bool learning (int size) { + if (!learner.function) + return false; + return size <= learner.max_length; + } + + void learn (int lit) { + if (learner.end_clause == learner.capacity_clause) { + size_t count = learner.end_clause - learner.begin_clause; + size_t size = count ? 2 * count : 1; + learner.begin_clause = + (int *) realloc (learner.begin_clause, size * sizeof (int)); + learner.end_clause = learner.begin_clause + count; + learner.capacity_clause = learner.begin_clause + size; + } + *learner.end_clause++ = lit; + if (lit) + return; + learner.function (learner.state, learner.begin_clause); + learner.end_clause = learner.begin_clause; + } + + Wrapper () : solver (new Solver ()) { + memset (&terminator, 0, sizeof terminator); + memset (&learner, 0, sizeof learner); + } + + ~Wrapper () { + terminator.function = 0; + if (learner.begin_clause) + free (learner.begin_clause); + delete solver; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#include "ccadical.h" + +ABC_NAMESPACE_IMPL_START + +using namespace CaDiCaL; + +const char *ccadical_signature (void) { return Solver::signature (); } + +CCaDiCaL *ccadical_init (void) { return (CCaDiCaL *) new Wrapper (); } + +void ccadical_release (CCaDiCaL *wrapper) { delete (Wrapper *) wrapper; } + +void ccadical_constrain (CCaDiCaL *wrapper, int lit) { + ((Wrapper *) wrapper)->solver->constrain (lit); +} + +int ccadical_constraint_failed (CCaDiCaL *wrapper) { + return ((Wrapper *) wrapper)->solver->constraint_failed (); +} + +void ccadical_set_option (CCaDiCaL *wrapper, const char *name, int val) { + ((Wrapper *) wrapper)->solver->set (name, val); +} + +void ccadical_limit (CCaDiCaL *wrapper, const char *name, int val) { + ((Wrapper *) wrapper)->solver->limit (name, val); +} + +int ccadical_get_option (CCaDiCaL *wrapper, const char *name) { + return ((Wrapper *) wrapper)->solver->get (name); +} + +void ccadical_add (CCaDiCaL *wrapper, int lit) { + ((Wrapper *) wrapper)->solver->add (lit); +} + +void ccadical_assume (CCaDiCaL *wrapper, int lit) { + ((Wrapper *) wrapper)->solver->assume (lit); +} + +int ccadical_solve (CCaDiCaL *wrapper) { + return ((Wrapper *) wrapper)->solver->solve (); +} + +int ccadical_simplify (CCaDiCaL *wrapper) { + return ((Wrapper *) wrapper)->solver->simplify (); +} + +int ccadical_val (CCaDiCaL *wrapper, int lit) { + return ((Wrapper *) wrapper)->solver->val (lit); +} + +int ccadical_failed (CCaDiCaL *wrapper, int lit) { + return ((Wrapper *) wrapper)->solver->failed (lit); +} + +void ccadical_print_statistics (CCaDiCaL *wrapper) { + ((Wrapper *) wrapper)->solver->statistics (); +} + +void ccadical_terminate (CCaDiCaL *wrapper) { + ((Wrapper *) wrapper)->solver->terminate (); +} + +int64_t ccadical_active (CCaDiCaL *wrapper) { + return ((Wrapper *) wrapper)->solver->active (); +} + +int64_t ccadical_irredundant (CCaDiCaL *wrapper) { + return ((Wrapper *) wrapper)->solver->irredundant (); +} + +int ccadical_fixed (CCaDiCaL *wrapper, int lit) { + return ((Wrapper *) wrapper)->solver->fixed (lit); +} + +void ccadical_set_terminate (CCaDiCaL *ptr, void *state, + int (*terminate) (void *)) { + Wrapper *wrapper = (Wrapper *) ptr; + wrapper->terminator.state = state; + wrapper->terminator.function = terminate; + if (terminate) + wrapper->solver->connect_terminator (wrapper); + else + wrapper->solver->disconnect_terminator (); +} + +void ccadical_set_learn (CCaDiCaL *ptr, void *state, int max_length, + void (*learn) (void *state, int *clause)) { + Wrapper *wrapper = (Wrapper *) ptr; + wrapper->learner.state = state; + wrapper->learner.max_length = max_length; + wrapper->learner.function = learn; + if (learn) + wrapper->solver->connect_learner (wrapper); + else + wrapper->solver->disconnect_learner (); +} + +void ccadical_freeze (CCaDiCaL *ptr, int lit) { + ((Wrapper *) ptr)->solver->freeze (lit); +} + +void ccadical_melt (CCaDiCaL *ptr, int lit) { + ((Wrapper *) ptr)->solver->melt (lit); +} + +int ccadical_frozen (CCaDiCaL *ptr, int lit) { + return ((Wrapper *) ptr)->solver->frozen (lit); +} + +int ccadical_trace_proof (CCaDiCaL *ptr, FILE *file, const char *path) { + return ((Wrapper *) ptr)->solver->trace_proof (file, path); +} + +void ccadical_close_proof (CCaDiCaL *ptr) { + ((Wrapper *) ptr)->solver->close_proof_trace (); +} + +void ccadical_conclude (CCaDiCaL *ptr) { + ((Wrapper *) ptr)->solver->conclude (); +} + +int ccadical_vars (CCaDiCaL *ptr) { + return ((Wrapper *) ptr)->solver->vars (); +} + +int ccadical_reserve_difference (CCaDiCaL *ptr, int number_of_vars) { + return ((Wrapper *) ptr)->solver->reserve_difference (number_of_vars); +} + +void ccadical_reserve(CCaDiCaL *ptr, int min_max_var) { + ((Wrapper *) ptr)->solver->reserve(min_max_var); +} + +int ccadical_is_inconsistent(CCaDiCaL *ptr) { + return ((Wrapper *) ptr)->solver->inconsistent (); +} + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_checker.cpp b/src/sat/cadical/cadical_checker.cpp new file mode 100644 index 000000000..63a5f2b10 --- /dev/null +++ b/src/sat/cadical/cadical_checker.cpp @@ -0,0 +1,654 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +inline unsigned Checker::l2u (int lit) { + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + unsigned res = 2 * (abs (lit) - 1); + if (lit < 0) + res++; + return res; +} + +inline signed char Checker::val (int lit) { + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + CADICAL_assert (abs (lit) < size_vars); + CADICAL_assert (vals[lit] == -vals[-lit]); + return vals[lit]; +} + +signed char &Checker::mark (int lit) { + const unsigned u = l2u (lit); + CADICAL_assert (u < marks.size ()); + return marks[u]; +} + +inline CheckerWatcher &Checker::watcher (int lit) { + const unsigned u = l2u (lit); + CADICAL_assert (u < watchers.size ()); + return watchers[u]; +} + +/*------------------------------------------------------------------------*/ + +CheckerClause *Checker::new_clause () { + const size_t size = simplified.size (); + CADICAL_assert (size > 1), CADICAL_assert (size <= UINT_MAX); + const size_t bytes = sizeof (CheckerClause) + (size - 2) * sizeof (int); + CheckerClause *res = (CheckerClause *) new char[bytes]; + DeferDeleteArray delete_res ((char *) res); + res->next = 0; + res->hash = last_hash; + res->size = size; + int *literals = res->literals, *p = literals; + for (const auto &lit : simplified) + *p++ = lit; + num_clauses++; + + // First two literals are used as watches and should not be false. + // + for (unsigned i = 0; i < 2; i++) { + int lit = literals[i]; + if (!val (lit)) + continue; + for (unsigned j = i + 1; j < size; j++) { + int other = literals[j]; + if (val (other)) + continue; + swap (literals[i], literals[j]); + break; + } + } + CADICAL_assert (!val (literals[0])); + CADICAL_assert (!val (literals[1])); + watcher (literals[0]).push_back (CheckerWatch (literals[1], res)); + watcher (literals[1]).push_back (CheckerWatch (literals[0], res)); + + delete_res.release (); + return res; +} + +void Checker::delete_clause (CheckerClause *c) { + if (c->size) { + CADICAL_assert (c->size > 1); + CADICAL_assert (num_clauses); + num_clauses--; + } else { + CADICAL_assert (num_garbage); + num_garbage--; + } + delete[] (char *) c; +} + +void Checker::enlarge_clauses () { + CADICAL_assert (num_clauses == size_clauses); + const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1; + LOG ("CHECKER enlarging clauses of checker from %" PRIu64 " to %" PRIu64, + (uint64_t) size_clauses, (uint64_t) new_size_clauses); + CheckerClause **new_clauses; + new_clauses = new CheckerClause *[new_size_clauses]; + clear_n (new_clauses, new_size_clauses); + for (uint64_t i = 0; i < size_clauses; i++) { + for (CheckerClause *c = clauses[i], *next; c; c = next) { + next = c->next; + const uint64_t h = reduce_hash (c->hash, new_size_clauses); + c->next = new_clauses[h]; + new_clauses[h] = c; + } + } + delete[] clauses; + clauses = new_clauses; + size_clauses = new_size_clauses; +} + +bool Checker::clause_satisfied (CheckerClause *c) { + for (unsigned i = 0; i < c->size; i++) + if (val (c->literals[i]) > 0) + return true; + return false; +} + +// The main reason why we have an explicit garbage collection phase is that +// removing clauses from watcher lists eagerly might lead to an accumulated +// quadratic algorithm. Thus we delay removing garbage clauses from watcher +// lists until garbage collection (even though we remove garbage clauses on +// the fly during propagation too). We also remove satisfied clauses. +// +void Checker::collect_garbage_clauses () { + + stats.collections++; + + for (size_t i = 0; i < size_clauses; i++) { + CheckerClause **p = clauses + i, *c; + while ((c = *p)) { + if (clause_satisfied (c)) { + c->size = 0; // mark as garbage + *p = c->next; + c->next = garbage; + garbage = c; + num_garbage++; + CADICAL_assert (num_clauses); + num_clauses--; + } else + p = &c->next; + } + } + + LOG ("CHECKER collecting %" PRIu64 " garbage clauses %.0f%%", num_garbage, + percent (num_garbage, num_clauses)); + + for (int lit = -size_vars + 1; lit < size_vars; lit++) { + if (!lit) + continue; + CheckerWatcher &ws = watcher (lit); + const auto end = ws.end (); + auto j = ws.begin (), i = j; + for (; i != end; i++) { + CheckerWatch &w = *i; + if (w.clause->size) + *j++ = w; + } + if (j == ws.end ()) + continue; + if (j == ws.begin ()) + erase_vector (ws); + else + ws.resize (j - ws.begin ()); + } + + for (CheckerClause *c = garbage, *next; c; c = next) + next = c->next, delete_clause (c); + + CADICAL_assert (!num_garbage); + garbage = 0; +} + +/*------------------------------------------------------------------------*/ + +Checker::Checker (Internal *i) + : internal (i), size_vars (0), vals (0), inconsistent (false), + num_clauses (0), num_garbage (0), size_clauses (0), clauses (0), + garbage (0), next_to_propagate (0), last_hash (0) { + + // Initialize random number table for hash function. + // + Random random (42); + for (unsigned n = 0; n < num_nonces; n++) { + uint64_t nonce = random.next (); + if (!(nonce & 1)) + nonce++; + CADICAL_assert (nonce), CADICAL_assert (nonce & 1); + nonces[n] = nonce; + } + + memset (&stats, 0, sizeof (stats)); // Initialize statistics. +} + +void Checker::connect_internal (Internal *i) { + internal = i; + LOG ("CHECKER connected to internal"); +} + +Checker::~Checker () { + LOG ("CHECKER delete"); + vals -= size_vars; + delete[] vals; + for (size_t i = 0; i < size_clauses; i++) + for (CheckerClause *c = clauses[i], *next; c; c = next) + next = c->next, delete_clause (c); + for (CheckerClause *c = garbage, *next; c; c = next) + next = c->next, delete_clause (c); + delete[] clauses; +} + +/*------------------------------------------------------------------------*/ + +// The simplicity for accessing 'vals' and 'watchers' directly through a +// signed integer literal, comes with the price of slightly more complex +// code in deleting and enlarging the checker data structures. + +void Checker::enlarge_vars (int64_t idx) { + + CADICAL_assert (0 < idx), CADICAL_assert (idx <= INT_MAX); + + int64_t new_size_vars = size_vars ? 2 * size_vars : 2; + while (idx >= new_size_vars) + new_size_vars *= 2; + LOG ("CHECKER enlarging variables of checker from %" PRId64 " to %" PRId64 + "", + size_vars, new_size_vars); + + signed char *new_vals; + new_vals = new signed char[2 * new_size_vars]; + clear_n (new_vals, 2 * new_size_vars); + new_vals += new_size_vars; + if (size_vars) // To make sanitizer happy (without '-O'). + memcpy ((void *) (new_vals - size_vars), (void *) (vals - size_vars), + 2 * size_vars); + vals -= size_vars; + delete[] vals; + vals = new_vals; + size_vars = new_size_vars; + + watchers.resize (2 * new_size_vars); + marks.resize (2 * new_size_vars); + + CADICAL_assert (idx < new_size_vars); +} + +inline void Checker::import_literal (int lit) { + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + int idx = abs (lit); + if (idx >= size_vars) + enlarge_vars (idx); + simplified.push_back (lit); + unsimplified.push_back (lit); +} + +void Checker::import_clause (const vector &c) { + for (const auto &lit : c) + import_literal (lit); +} + +struct lit_smaller { + bool operator() (int a, int b) const { + int c = abs (a), d = abs (b); + if (c < d) + return true; + if (c > d) + return false; + return a < b; + } +}; + +bool Checker::tautological () { + sort (simplified.begin (), simplified.end (), lit_smaller ()); + const auto end = simplified.end (); + auto j = simplified.begin (); + int prev = 0; + for (auto i = j; i != end; i++) { + int lit = *i; + if (lit == prev) + continue; // duplicated literal + if (lit == -prev) + return true; // tautological clause + const signed char tmp = val (lit); + if (tmp > 0) + return true; // satisfied literal and clause + *j++ = prev = lit; + } + simplified.resize (j - simplified.begin ()); + return false; +} + +/*------------------------------------------------------------------------*/ + +uint64_t Checker::reduce_hash (uint64_t hash, uint64_t size) { + CADICAL_assert (size > 0); + unsigned shift = 32; + uint64_t res = hash; + while ((((uint64_t) 1) << shift) > size) { + res ^= res >> shift; + shift >>= 1; + } + res &= size - 1; + CADICAL_assert (res < size); + return res; +} + +uint64_t Checker::compute_hash () { + unsigned j = last_id % num_nonces; + uint64_t tmp = nonces[j] * last_id; + return last_hash = tmp; +} + +CheckerClause **Checker::find () { + stats.searches++; + CheckerClause **res, *c; + const uint64_t hash = compute_hash (); + const unsigned size = simplified.size (); + const uint64_t h = reduce_hash (hash, size_clauses); + for (const auto &lit : simplified) + mark (lit) = true; + for (res = clauses + h; (c = *res); res = &c->next) { + if (c->hash == hash && c->size == size) { + bool found = true; + const int *literals = c->literals; + for (unsigned i = 0; found && i != size; i++) + found = mark (literals[i]); + if (found) + break; + } + stats.collisions++; + } + for (const auto &lit : simplified) + mark (lit) = false; + return res; +} + +void Checker::insert () { + stats.insertions++; + if (num_clauses == size_clauses) + enlarge_clauses (); + const uint64_t h = reduce_hash (compute_hash (), size_clauses); + CheckerClause *c = new_clause (); + c->next = clauses[h]; + clauses[h] = c; +} + +/*------------------------------------------------------------------------*/ + +inline void Checker::assign (int lit) { + CADICAL_assert (!val (lit)); + vals[lit] = 1; + vals[-lit] = -1; + trail.push_back (lit); +} + +inline void Checker::assume (int lit) { + signed char tmp = val (lit); + if (tmp > 0) + return; + CADICAL_assert (!tmp); + stats.assumptions++; + assign (lit); +} + +void Checker::backtrack (unsigned previously_propagated) { + + CADICAL_assert (previously_propagated <= trail.size ()); + + while (trail.size () > previously_propagated) { + int lit = trail.back (); + CADICAL_assert (val (lit) > 0); + CADICAL_assert (val (-lit) < 0); + vals[lit] = vals[-lit] = 0; + trail.pop_back (); + } + + trail.resize (previously_propagated); + next_to_propagate = previously_propagated; + CADICAL_assert (trail.size () == next_to_propagate); +} + +/*------------------------------------------------------------------------*/ + +// This is a standard propagation routine without using blocking literals +// nor without saving the last replacement position. + +bool Checker::propagate () { + bool res = true; + while (res && next_to_propagate < trail.size ()) { + int lit = trail[next_to_propagate++]; + stats.propagations++; + CADICAL_assert (val (lit) > 0); + CADICAL_assert (abs (lit) < size_vars); + CheckerWatcher &ws = watcher (-lit); + const auto end = ws.end (); + auto j = ws.begin (), i = j; + for (; res && i != end; i++) { + CheckerWatch &w = *j++ = *i; + const int blit = w.blit; + CADICAL_assert (blit != -lit); + const signed char blit_val = val (blit); + if (blit_val > 0) + continue; + const unsigned size = w.size; + if (size == 2) { // not precise since + if (blit_val < 0) + res = false; // clause might be garbage + else + assign (w.blit); // but still sound + } else { + CADICAL_assert (size > 2); + CheckerClause *c = w.clause; + if (!c->size) { + j--; + continue; + } // skip garbage clauses + CADICAL_assert (size == c->size); + int *lits = c->literals; + int other = lits[0] ^ lits[1] ^ (-lit); + CADICAL_assert (other != -lit); + signed char other_val = val (other); + if (other_val > 0) { + j[-1].blit = other; + continue; + } + lits[0] = other, lits[1] = -lit; + unsigned k; + int replacement = 0; + signed char replacement_val = -1; + for (k = 2; k < size; k++) + if ((replacement_val = val (replacement = lits[k])) >= 0) + break; + if (replacement_val >= 0) { + watcher (replacement).push_back (CheckerWatch (-lit, c)); + swap (lits[1], lits[k]); + j--; + } else if (!other_val) + assign (other); + else + res = false; + } + } + while (i != end) + *j++ = *i++; + ws.resize (j - ws.begin ()); + } + return res; +} + +bool Checker::check () { + stats.checks++; + if (inconsistent) + return true; + unsigned previously_propagated = next_to_propagate; + for (const auto &lit : simplified) + assume (-lit); + bool res = !propagate (); + backtrack (previously_propagated); + return res; +} + +bool Checker::check_blocked () { + for (const auto &lit : unsimplified) { + mark (-lit) = true; + } + vector not_blocked; + for (size_t i = 0; i < size_clauses; i++) { + for (CheckerClause *c = clauses[i], *next; c; c = next) { + next = c->next; + unsigned count = 0; + int first; + for (int *i = c->literals; i < c->literals + c->size; i++) { + const int lit = *i; + if (val (lit) > 0) { + LOG (c->literals, c->size, "satisfied clause"); + count = 2; + break; + } + if (mark (lit)) { + count++; + LOG (c->literals, c->size, "clause"); + first = lit; + } + } + if (count == 1) + not_blocked.push_back (first); + } + } + for (const auto &lit : not_blocked) { + mark (lit) = false; + } + bool blocked = false; + for (const auto &lit : unsimplified) { + if (mark (-lit)) + blocked = true; + mark (-lit) = false; + } + return blocked; +} + +/*------------------------------------------------------------------------*/ + +void Checker::add_clause (const char *type) { +#ifndef LOGGING + (void) type; +#endif + + // If there are enough garbage clauses collect them first. + if (num_garbage > 0.5 * max ((size_t) size_clauses, (size_t) size_vars)) + collect_garbage_clauses (); + + int unit = 0; + for (const auto &lit : simplified) { + const signed char tmp = val (lit); + if (tmp < 0) + continue; + CADICAL_assert (!tmp); + if (unit) { + unit = INT_MIN; + break; + } + unit = lit; + } + + if (simplified.empty ()) { + LOG ("CHECKER added empty %s clause", type); + inconsistent = true; + } + if (!unit) { + LOG ("CHECKER added and checked falsified %s clause", type); + inconsistent = true; + } else if (unit != INT_MIN) { + LOG ("CHECKER added and checked %s unit clause %d", type, unit); + assign (unit); + stats.units++; + if (!propagate ()) { + LOG ("CHECKER inconsistent after propagating %s unit", type); + inconsistent = true; + } + } else + insert (); +} + +void Checker::add_original_clause (int64_t id, bool, const vector &c, + bool) { + if (inconsistent) + return; + START (checking); + LOG (c, "CHECKER addition of original clause"); + stats.added++; + stats.original++; + import_clause (c); + last_id = id; + if (tautological ()) + LOG ("CHECKER ignoring satisfied original clause"); + else + add_clause ("original"); + simplified.clear (); + unsimplified.clear (); + STOP (checking); +} + +void Checker::add_derived_clause (int64_t id, bool, const vector &c, + const vector &) { + if (inconsistent) + return; + START (checking); + LOG (c, "CHECKER addition of derived clause"); + stats.added++; + stats.derived++; + import_clause (c); + last_id = id; + if (tautological ()) + LOG ("CHECKER ignoring satisfied derived clause"); + else if (!check () && !check_blocked ()) { // needed for ER proof support + fatal_message_start (); + fputs ("failed to check derived clause:\n", stderr); + for (const auto &lit : unsimplified) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } else + add_clause ("derived"); + simplified.clear (); + unsimplified.clear (); + STOP (checking); +} + +/*------------------------------------------------------------------------*/ + +void Checker::delete_clause (int64_t id, bool, const vector &c) { + if (inconsistent) + return; + START (checking); + LOG (c, "CHECKER checking deletion of clause"); + stats.deleted++; + simplified.clear (); // Can be non-empty if clause allocation fails. + unsimplified.clear (); // Can be non-empty if clause allocation fails. + import_clause (c); + last_id = id; + if (!tautological ()) { + CheckerClause **p = find (), *d = *p; + if (d) { + CADICAL_assert (d->size > 1); + // Remove from hash table, mark as garbage, connect to garbage list. + num_garbage++; + CADICAL_assert (num_clauses); + num_clauses--; + *p = d->next; + d->next = garbage; + garbage = d; + d->size = 0; + } else { + fatal_message_start (); + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : unsimplified) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + } + simplified.clear (); + unsimplified.clear (); + STOP (checking); +} + +void Checker::add_assumption_clause (int64_t id, const vector &c, + const vector &chain) { + add_derived_clause (id, true, c, chain); + delete_clause (id, true, c); +} + +/*------------------------------------------------------------------------*/ + +void Checker::dump () { + int max_var = 0; + for (uint64_t i = 0; i < size_clauses; i++) + for (CheckerClause *c = clauses[i]; c; c = c->next) + for (unsigned i = 0; i < c->size; i++) + if (abs (c->literals[i]) > max_var) + max_var = abs (c->literals[i]); + printf ("p cnf %d %" PRIu64 "\n", max_var, num_clauses); + for (uint64_t i = 0; i < size_clauses; i++) + for (CheckerClause *c = clauses[i]; c; c = c->next) { + for (unsigned i = 0; i < c->size; i++) + printf ("%d ", c->literals[i]); + printf ("0\n"); + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_clause.cpp b/src/sat/cadical/cadical_clause.cpp new file mode 100644 index 000000000..1c84994a6 --- /dev/null +++ b/src/sat/cadical/cadical_clause.cpp @@ -0,0 +1,649 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Signed marking or unmarking of a clause or the global 'clause'. + +void Internal::mark (Clause *c) { + for (const auto &lit : *c) + mark (lit); +} + +void Internal::mark2 (Clause *c) { + for (const auto &lit : *c) + mark2 (lit); +} + +void Internal::unmark (Clause *c) { + for (const auto &lit : *c) + unmark (lit); +} + +void Internal::mark_clause () { + for (const auto &lit : clause) + mark (lit); +} + +void Internal::unmark_clause () { + for (const auto &lit : clause) + unmark (lit); +} + +/*------------------------------------------------------------------------*/ + +// Mark the variables of an irredundant clause to 'have been removed', which +// will trigger these variables to be considered again in the next bounded +// variable elimination phase. This is called from 'mark_garbage' below. +// Note that 'mark_removed (int lit)' will also mark the blocking flag of +// '-lit' to trigger reconsidering blocking clauses on '-lit'. + +void Internal::mark_removed (Clause *c, int except) { + LOG (c, "marking removed"); + CADICAL_assert (!c->redundant); + for (const auto &lit : *c) + if (lit != except) + mark_removed (lit); +} + +// Mark the variables of a (redundant or irredundant) clause to 'have been +// added', which triggers clauses with such a variables, to be considered +// both as a subsumed or subsuming clause in the next subsumption phase. +// This function is called from 'new_clause' below as well as in situations +// where a clause is shrunken (and thus needs to be at least considered +// again to subsume a larger clause). We also use this to tell +// 'ternary' preprocessing reconsider clauses on an added literal as well as +// trying to block clauses on it. + +inline void Internal::mark_added (int lit, int size, bool redundant) { + mark_subsume (lit); + if (size == 3) + mark_ternary (lit); + if (!redundant) + mark_block (lit); + if (!redundant || size == 2) + mark_factor (lit); +} + +void Internal::mark_added (Clause *c) { + LOG (c, "marking added"); + CADICAL_assert (likely_to_be_kept_clause (c)); + for (const auto &lit : *c) + mark_added (lit, c->size, c->redundant); +} + +/*------------------------------------------------------------------------*/ + +Clause *Internal::new_clause (bool red, int glue) { + + CADICAL_assert (clause.size () <= (size_t) INT_MAX); + const int size = (int) clause.size (); + CADICAL_assert (size >= 2); + + if (glue > size) + glue = size; + + size_t bytes = Clause::bytes (size); + Clause *c = (Clause *) new char[bytes]; + DeferDeleteArray clause_delete ((char *) c); + + c->id = ++clause_id; + + c->conditioned = false; + c->covered = false; + c->enqueued = false; + c->frozen = false; + c->garbage = false; + c->gate = false; + c->hyper = false; + c->instantiated = false; + c->moved = false; + c->reason = false; + c->redundant = red; + c->transred = false; + c->subsume = false; + c->swept = false; + c->flushed = false; + c->vivified = false; + c->vivify = false; + c->used = 0; + + c->glue = glue; + c->size = size; + c->pos = 2; + + for (int i = 0; i < size; i++) + c->literals[i] = clause[i]; + + // Just checking that we did not mess up our sophisticated memory layout. + // This might be compiler dependent though. Crucial for correctness. + // + CADICAL_assert (c->bytes () == bytes); + + stats.current.total++; + stats.added.total++; + + if (red) { + stats.current.redundant++; + stats.added.redundant++; + } else { + stats.irrlits += size; + stats.current.irredundant++; + stats.added.irredundant++; + } + + clauses.push_back (c); + clause_delete.release (); + LOG (c, "new pointer %p", (void *) c); + + if (likely_to_be_kept_clause (c)) + mark_added (c); + + return c; +} + +/*------------------------------------------------------------------------*/ + +void Internal::promote_clause (Clause *c, int new_glue) { + CADICAL_assert (c->redundant); + const int tier1limit = tier1[false]; + const int tier2limit = max (tier1limit, tier2[false]); + if (!c->redundant) + return; + if (c->hyper) + return; + int old_glue = c->glue; + if (new_glue >= old_glue) + return; + if (old_glue > tier1limit && new_glue <= tier1limit) { + LOG (c, "promoting with new glue %d to tier1", new_glue); + stats.promoted1++; + c->used = max_used; + } else if (old_glue > tier2limit && new_glue <= tier2limit) { + LOG (c, "promoting with new glue %d to tier2", new_glue); + stats.promoted2++; + } else if (old_glue <= tier2limit) + LOG (c, "keeping with new glue %d in tier2", new_glue); + else + LOG (c, "keeping with new glue %d in tier3", new_glue); + stats.improvedglue++; + c->glue = new_glue; +} +/*------------------------------------------------------------------------*/ + +void Internal::promote_clause_glue_only (Clause *c, int new_glue) { + CADICAL_assert (c->redundant); + if (c->hyper) + return; + int old_glue = c->glue; + const int tier1limit = tier1[false]; + const int tier2limit = max (tier1limit, tier2[false]); + if (new_glue >= old_glue) + return; + if (new_glue <= tier1limit) { + LOG (c, "promoting with new glue %d to tier1", new_glue); + stats.promoted1++; + c->used = max_used; + } else if (old_glue > tier2limit && new_glue <= tier2limit) { + LOG (c, "promoting with new glue %d to tier2", new_glue); + stats.promoted2++; + } else if (old_glue <= tier2limit) + LOG (c, "keeping with new glue %d in tier2", new_glue); + else + LOG (c, "keeping with new glue %d in tier3", new_glue); + stats.improvedglue++; + c->glue = new_glue; +} + +/*------------------------------------------------------------------------*/ + +// Shrinking a clause, e.g., removing one or more literals, requires to fix +// the 'pos' field, if it exists and points after the new last literal. We +// also have adjust the global statistics counter of irredundant literals +// for irredundant clauses, and also adjust the glue value of redundant +// clauses if the size becomes smaller than the glue. Also mark the +// literals in the resulting clause as 'added'. The result is the number of +// (aligned) removed bytes, resulting from shrinking the clause. +// +size_t Internal::shrink_clause (Clause *c, int new_size) { + if (opts.check && is_external_forgettable (c->id)) + mark_garbage_external_forgettable (c->id); + CADICAL_assert (new_size >= 2); + int old_size = c->size; + CADICAL_assert (new_size < old_size); +#ifndef CADICAL_NDEBUG + for (int i = c->size; i < new_size; i++) + c->literals[i] = 0; +#endif + + if (c->pos >= new_size) + c->pos = 2; + + size_t old_bytes = c->bytes (); + c->size = new_size; + size_t new_bytes = c->bytes (); + size_t res = old_bytes - new_bytes; + + if (c->redundant) + promote_clause_glue_only (c, min (c->size - 1, c->glue)); + else { + int delta_size = old_size - new_size; + CADICAL_assert (stats.irrlits >= delta_size); + stats.irrlits -= delta_size; + } + + if (likely_to_be_kept_clause (c)) + mark_added (c); + + return res; +} + +// This is the 'raw' deallocation of a clause. If the clause is in the +// arena nothing happens. If the clause is not in the arena its memory is +// reclaimed immediately. + +void Internal::deallocate_clause (Clause *c) { + char *p = (char *) c; + if (arena.contains (p)) + return; + LOG (c, "deallocate pointer %p", (void *) c); + delete[] p; +} + +void Internal::delete_clause (Clause *c) { + LOG (c, "delete pointer %p", (void *) c); + size_t bytes = c->bytes (); + stats.collected += bytes; + if (c->garbage) { + CADICAL_assert (stats.garbage.bytes >= (int64_t) bytes); + stats.garbage.bytes -= bytes; + CADICAL_assert (stats.garbage.clauses > 0); + stats.garbage.clauses--; + CADICAL_assert (stats.garbage.literals >= c->size); + stats.garbage.literals -= c->size; + + // See the discussion in 'propagate' on avoiding to eagerly trace binary + // clauses as deleted (produce 'd ...' lines) as soon they are marked + // garbage. We avoid this and only trace them as deleted when they are + // actually deleted here. This allows the solver to propagate binary + // garbage clauses without producing incorrect 'd' lines. The effect + // from the proof perspective is that the deletion of these binary + // clauses occurs later in the proof file. + // + if (proof && c->size == 2 && !c->flushed) { + proof->delete_clause (c); + } + } + deallocate_clause (c); +} + +// We want to eagerly update statistics as soon clauses are marked garbage. +// Otherwise 'report' for instance gives wrong numbers after 'subsume' +// before the next 'reduce'. Thus we factored out marking and accounting +// for garbage clauses. +// +// Eagerly deleting clauses instead is problematic, since references to +// these clauses need to be flushed, which is too costly to do eagerly. +// +// We also update garbage statistics at this point. This helps to +// determine whether the garbage collector should be called during for +// instance bounded variable elimination, which usually generates lots of +// garbage clauses. +// +// In order not to miss any update to these clause statistics we call +// 'check_clause_stats' after garbage collection in debugging mode. +// +void Internal::mark_garbage (Clause *c) { + + CADICAL_assert (!c->garbage); + + // Delay tracing deletion of binary clauses. See the discussion above in + // 'delete_clause' and also in 'propagate'. + // + if (proof && (c->size != 2 || !watching ())) { + c->flushed = true; + proof->delete_clause (c); + } + + // Because of the internal model checking, external forgettable clauses + // must be marked as removed already upon mark_garbage, can not wait until + // actual deletion. + if (opts.check && is_external_forgettable (c->id)) + mark_garbage_external_forgettable (c->id); + + CADICAL_assert (stats.current.total > 0); + stats.current.total--; + + size_t bytes = c->bytes (); + if (c->redundant) { + CADICAL_assert (stats.current.redundant > 0); + stats.current.redundant--; + } else { + CADICAL_assert (stats.current.irredundant > 0); + stats.current.irredundant--; + CADICAL_assert (stats.irrlits >= c->size); + stats.irrlits -= c->size; + mark_removed (c); + } + stats.garbage.bytes += bytes; + stats.garbage.clauses++; + stats.garbage.literals += c->size; + c->garbage = true; + c->used = 0; + + LOG (c, "marked garbage pointer %p", (void *) c); +} + +/*------------------------------------------------------------------------*/ + +// Almost the same function as 'search_assign' except that we do not pretend +// to learn a new unit clause (which was confusing in log files). + +void Internal::assign_original_unit (int64_t id, int lit) { + CADICAL_assert (!level || opts.chrono); + CADICAL_assert (!unsat); + const int idx = vidx (lit); + CADICAL_assert (!vals[idx]); + CADICAL_assert (!flags (idx).eliminated ()); + Var &v = var (idx); + v.level = 0; + v.trail = (int) trail.size (); + v.reason = 0; + const signed char tmp = sign (lit); + set_val (idx, tmp); + trail.push_back (lit); + num_assigned++; + const unsigned uidx = vlit (lit); + if (lrat || frat) + unit_clauses (uidx) = id; + LOG ("original unit assign %d", lit); + CADICAL_assert (num_assigned == trail.size () || level); + mark_fixed (lit); + if (level) + return; + if (propagate ()) + return; + CADICAL_assert (conflict); + LOG ("propagation of original unit results in conflict"); + learn_empty_clause (); +} + +// New clause added through the API, e.g., while parsing a DIMACS file. +// Also used by external_propagate in various different modes. +// clause, original, lrat_chain and external->eclause are set. +// from_propagator and force_no_backtrack change the behaviour. +// sometimes the pointer to the new clause is needed, therefore it is +// made sure that newest_clause points to the new clause upon return. +// +// TODO: Find another name for 'tainted' in the context of ilb, tainted +// is reconstruction related already and they should not mix. +void Internal::add_new_original_clause (int64_t id) { + + if (!from_propagator && level && !opts.ilb) { + backtrack (); + } else if (tainted_literal) { + CADICAL_assert (val (tainted_literal)); + int new_level = var (tainted_literal).level - 1; + CADICAL_assert (new_level >= 0); + backtrack (new_level); + } + CADICAL_assert (!tainted_literal); + LOG (original, "original clause"); + CADICAL_assert (clause.empty ()); + bool skip = false; + unordered_set learned_levels; + size_t unassigned = 0; + newest_clause = 0; + if (unsat) { + LOG ("skipping clause since formula is already inconsistent"); + skip = true; + } else { + CADICAL_assert (clause.empty ()); + for (const auto &lit : original) { + int tmp = marked (lit); + if (tmp > 0) { + LOG ("removing duplicated literal %d", lit); + } else if (tmp < 0) { + LOG ("tautological since both %d and %d occur", -lit, lit); + skip = true; + } else { + mark (lit); + tmp = fixed (lit); + if (tmp < 0) { + LOG ("removing falsified literal %d", lit); + if (lrat) { + int elit = externalize (lit); + unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit); + if (!external->ext_units[eidx]) { + int64_t uid = unit_id (-lit); + lrat_chain.push_back (uid); + } + } + } else if (tmp > 0) { + LOG ("satisfied since literal %d true", lit); + skip = true; + } else { + clause.push_back (lit); + CADICAL_assert (flags (lit).status != Flags::UNUSED); + tmp = val (lit); + if (tmp) + learned_levels.insert (var (lit).level); + else + unassigned++; + } + } + } + for (const auto &lit : original) + unmark (lit); + } + if (skip) { + if (from_propagator) { + stats.ext_prop.elearn_conf++; + + // In case it was a skipped external forgettable, we need to mark it + // immediately as removed + + if (opts.check && is_external_forgettable (id)) + mark_garbage_external_forgettable (id); + } + if (proof) { + proof->delete_external_original_clause (id, false, external->eclause); + } + } else { + int64_t new_id = id; + const size_t size = clause.size (); + if (original.size () > size) { + new_id = ++clause_id; + if (proof) { + if (lrat) + lrat_chain.push_back (id); + proof->add_derived_clause (new_id, false, clause, lrat_chain); + proof->delete_external_original_clause (id, false, + external->eclause); + } + external->check_learned_clause (); + + if (from_propagator) { + // The original form of the added clause is immediately forgotten + // TODO: shall we save and check the simplified form? (one with + // new_id) + if (opts.check && is_external_forgettable (id)) + mark_garbage_external_forgettable (id); + } + } + external->eclause.clear (); + lrat_chain.clear (); + if (!size) { + if (from_propagator) + stats.ext_prop.elearn_conf++; + CADICAL_assert (!unsat); + if (!original.size ()) + VERBOSE (1, "found empty original clause"); + else + VERBOSE (1, "found falsified original clause"); + unsat = true; + conflict_id = new_id; + marked_failed = true; + conclusion.push_back (new_id); + } else if (size == 1) { + if (force_no_backtrack) { + CADICAL_assert (level); + const int idx = vidx (clause[0]); + CADICAL_assert (val (clause[0]) >= 0); + CADICAL_assert (!flags (idx).eliminated ()); + Var &v = var (idx); + CADICAL_assert (val (clause[0])); + v.level = 0; + v.reason = 0; + const unsigned uidx = vlit (clause[0]); + if (lrat || frat) + unit_clauses (uidx) = new_id; + mark_fixed (clause[0]); + } else { + const int lit = clause[0]; + CADICAL_assert (!val (lit) || var (lit).level); + if (val (lit) < 0) + backtrack (var (lit).level - 1); + CADICAL_assert (val (lit) >= 0); + handle_external_clause (0); + assign_original_unit (new_id, lit); + } + } else { + move_literals_to_watch (); +#ifndef CADICAL_NDEBUG + check_watched_literal_invariants (); +#endif + int glue = (int) (learned_levels.size () + unassigned); + CADICAL_assert (glue <= (int) clause.size ()); + bool clause_redundancy = from_propagator && ext_clause_forgettable; + Clause *c = new_clause (clause_redundancy, glue); + c->id = new_id; + clause_id--; + watch_clause (c); + clause.clear (); + original.clear (); + handle_external_clause (c); + newest_clause = c; + } + } + clause.clear (); + lrat_chain.clear (); +} + +// Add learned new clause during conflict analysis and watch it. Requires +// that the clause is at least of size 2, and the first two literals +// are assigned at the highest decision level. +// +Clause *Internal::new_learned_redundant_clause (int glue) { + CADICAL_assert (clause.size () > 1); +#ifndef CADICAL_NDEBUG + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (var (clause[0]).level >= var (clause[i]).level), + CADICAL_assert (var (clause[1]).level >= var (clause[i]).level); +#endif + external->check_learned_clause (); + Clause *res = new_clause (true, glue); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + CADICAL_assert (watching ()); + watch_clause (res); + return res; +} + +// Add hyper binary resolved clause during 'probing'. +// +Clause *Internal::new_hyper_binary_resolved_clause (bool red, int glue) { + external->check_learned_clause (); + Clause *res = new_clause (red, glue); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + CADICAL_assert (watching ()); + watch_clause (res); + return res; +} + +// Add hyper ternary resolved clause during 'ternary'. +// +Clause *Internal::new_hyper_ternary_resolved_clause (bool red) { + external->check_learned_clause (); + size_t size = clause.size (); + Clause *res = new_clause (red, size); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + CADICAL_assert (!watching ()); + return res; +} + +Clause *Internal::new_factor_clause () { + external->check_learned_clause (); + stats.factor_added++; + stats.literals_factored += clause.size (); + Clause *res = new_clause (false, 0); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + CADICAL_assert (!watching ()); + CADICAL_assert (occurring ()); + for (const auto &lit : *res) { + occs (lit).push_back (res); + } + return res; +} + +// Add hyper ternary resolved clause during 'congruence' and watch it +// +Clause * +Internal::new_hyper_ternary_resolved_clause_and_watch (bool red, + bool full_watching) { + external->check_learned_clause (); + size_t size = clause.size (); + Clause *res = new_clause (red, size); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + if (full_watching) { + CADICAL_assert (watching ()); + watch_clause (res); + } + return res; +} + +// Add a new clause with same glue and redundancy as 'orig' but literals are +// assumed to be in 'clause' in 'decompose' and 'vivify'. +// +Clause *Internal::new_clause_as (const Clause *orig) { + external->check_learned_clause (); + const int new_glue = orig->glue; + Clause *res = new_clause (orig->redundant, new_glue); + if (proof) { + proof->add_derived_clause (res, lrat_chain); + } + CADICAL_assert (watching ()); + watch_clause (res); + return res; +} + +// Add resolved clause during resolution, e.g., bounded variable +// elimination, but do not connect its occurrences here. +// +Clause *Internal::new_resolved_irredundant_clause () { + external->check_learned_clause (); + if (proof) { + proof->add_derived_clause (clause_id + 1, false, clause, lrat_chain); + } + Clause *res = new_clause (false); + CADICAL_assert (!watching ()); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_collect.cpp b/src/sat/cadical/cadical_collect.cpp new file mode 100644 index 000000000..c36e063b2 --- /dev/null +++ b/src/sat/cadical/cadical_collect.cpp @@ -0,0 +1,551 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Returns the positive number '1' ( > 0) if the given clause is root level +// satisfied or the negative number '-1' ( < 0) if it is not root level +// satisfied but contains a root level falsified literal. Otherwise, if it +// contains neither a satisfied nor falsified literal, then '0' is returned. + +int Internal::clause_contains_fixed_literal (Clause *c) { + int satisfied = 0, falsified = 0; + for (const auto &lit : *c) { + const int tmp = fixed (lit); + if (tmp > 0) { + LOG (c, "root level satisfied literal %d in", lit); + satisfied++; + } + if (tmp < 0) { + LOG (c, "root level falsified literal %d in", lit); + falsified++; + } + } + if (satisfied) + return 1; + else if (falsified) + return -1; + else + return 0; +} + +// Assume that the clause is not root level satisfied but contains a literal +// set to false (root level falsified literal), so it can be shrunken. The +// clause data is not actually reallocated at this point to avoid dealing +// with issues of special policies for watching binary clauses or whether a +// clause is extended or not. Only its size field is adjusted accordingly +// after flushing out root level falsified literals. + +void Internal::remove_falsified_literals (Clause *c) { + const const_literal_iterator end = c->end (); + const_literal_iterator i; + int num_non_false = 0; + for (i = c->begin (); num_non_false < 2 && i != end; i++) + if (fixed (*i) >= 0) + num_non_false++; + if (num_non_false < 2) + return; + if (proof) { + // Flush changes the clause id, external forgettables need to be + // marked here (or the new id could be used instead of old one) + if (opts.check && is_external_forgettable (c->id)) + mark_garbage_external_forgettable (c->id); + proof->flush_clause (c); + } + literal_iterator j = c->begin (); + for (i = j; i != end; i++) { + const int lit = *j++ = *i, tmp = fixed (lit); + CADICAL_assert (tmp <= 0); + if (tmp >= 0) + continue; + LOG ("flushing %d", lit); + j--; + } + stats.collected += shrink_clause (c, j - c->begin ()); +} + +// If there are new units (fixed variables) since the last garbage +// collection we go over all clauses, mark satisfied ones as garbage and +// flush falsified literals. Otherwise if no new units have been generated +// since the last garbage collection just skip this step. + +void Internal::mark_satisfied_clauses_as_garbage () { + + if (last.collect.fixed >= stats.all.fixed) + return; + last.collect.fixed = stats.all.fixed; + + LOG ("marking satisfied clauses and removing falsified literals"); + + for (const auto &c : clauses) { + if (c->garbage) + continue; + const int tmp = clause_contains_fixed_literal (c); + if (tmp > 0) + mark_garbage (c); + else if (tmp < 0) + remove_falsified_literals (c); + } +} + +/*------------------------------------------------------------------------*/ + +// Reason clauses can not be collected. +// +// We protect reasons before and release protection after garbage collection +// (actually within garbage collection). +// +// For 'reduce' we still need to make sure that all clauses which should not +// be removed are marked as such and thus we need to call it before marking +// clauses to be flushed. + +void Internal::protect_reasons () { + LOG ("protecting reason clauses of all assigned variables on trail"); + CADICAL_assert (!protected_reasons); +#ifdef LOGGING + size_t count = 0; +#endif + for (const auto &lit : trail) { + if (!active (lit)) + continue; + CADICAL_assert (val (lit)); + Var &v = var (lit); + CADICAL_assert (v.level > 0); + Clause *reason = v.reason; + if (!reason) + continue; + if (reason == external_reason) + continue; + LOG (reason, "protecting assigned %d reason %p", lit, (void *) reason); + CADICAL_assert (!reason->reason); + reason->reason = true; +#ifdef LOGGING + count++; +#endif + } + LOG ("protected %zd reason clauses referenced on trail", count); + protected_reasons = true; +} + +/*------------------------------------------------------------------------*/ + +// After garbage collection we reset the 'reason' flag of the reasons +// of assigned literals on the trail. + +void Internal::unprotect_reasons () { + LOG ("unprotecting reasons clauses of all assigned variables on trail"); + CADICAL_assert (protected_reasons); +#ifdef LOGGING + size_t count = 0; +#endif + for (const auto &lit : trail) { + if (!active (lit)) + continue; + CADICAL_assert (val (lit)); + Var &v = var (lit); + CADICAL_assert (v.level > 0); + Clause *reason = v.reason; + if (!reason) + continue; + if (reason == external_reason) + continue; + LOG (reason, "unprotecting assigned %d reason %p", lit, + (void *) reason); + CADICAL_assert (reason->reason); + reason->reason = false; +#ifdef LOGGING + count++; +#endif + } + LOG ("unprotected %zd reason clauses referenced on trail", count); + protected_reasons = false; +} + +/*------------------------------------------------------------------------*/ + +// Update occurrence lists before deleting garbage clauses in the context of +// preprocessing, e.g., during bounded variable elimination 'elim'. The +// result is the number of remaining clauses, which in this context means +// the number of non-garbage clauses. + +size_t Internal::flush_occs (int lit) { + Occs &os = occs (lit); + const const_occs_iterator end = os.end (); + occs_iterator j = os.begin (); + const_occs_iterator i; + size_t res = 0; + Clause *c; + for (i = j; i != end; i++) { + c = *i; + if (c->collect ()) + continue; + *j++ = c->moved ? c->copy : c; + // CADICAL_assert (!c->redundant); // -> not true in sweeping + res++; + } + os.resize (j - os.begin ()); + shrink_occs (os); + return res; +} + +// Update watch lists before deleting garbage clauses in the context of +// 'reduce' where we watch and no occurrence lists. We have to protect +// reason clauses not be collected and thus we have this additional check +// hidden in 'Clause.collect', which for the root level context of +// preprocessing is actually redundant. + +inline void Internal::flush_watches (int lit, Watches &saved) { + CADICAL_assert (saved.empty ()); + Watches &ws = watches (lit); + const const_watch_iterator end = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i; + for (i = j; i != end; i++) { + Watch w = *i; + Clause *c = w.clause; + if (c->collect ()) + continue; + if (c->moved) + c = w.clause = c->copy; + w.size = c->size; + const int new_blit_pos = (c->literals[0] == lit); + LOG (c, "clause in flush_watch starting from %d", lit); + CADICAL_assert (c->literals[!new_blit_pos] == lit); /*FW1*/ + w.blit = c->literals[new_blit_pos]; + if (w.binary ()) + *j++ = w; + else + saved.push_back (w); + } + ws.resize (j - ws.begin ()); + for (const auto &w : saved) + ws.push_back (w); + saved.clear (); + shrink_vector (ws); +} + +void Internal::flush_all_occs_and_watches () { + if (occurring ()) + for (auto idx : vars) + flush_occs (idx), flush_occs (-idx); + + if (watching ()) { + Watches tmp; + for (auto idx : vars) + flush_watches (idx, tmp), flush_watches (-idx, tmp); + } +} + +/*------------------------------------------------------------------------*/ + +void Internal::update_reason_references () { + LOG ("update assigned reason references"); +#ifdef LOGGING + size_t count = 0; +#endif + for (auto &lit : trail) { + if (!active (lit)) + continue; + Var &v = var (lit); + Clause *c = v.reason; + if (!c) + continue; + if (c == external_reason) + continue; + LOG (c, "updating assigned %d reason", lit); + CADICAL_assert (c->reason); + CADICAL_assert (c->moved); + Clause *d = c->copy; + v.reason = d; +#ifdef LOGGING + count++; +#endif + } + LOG ("updated %zd assigned reason references", count); +} + +/*------------------------------------------------------------------------*/ + +// This is a simple garbage collector which does not move clauses. It needs +// less space than the arena based clause allocator, but is not as cache +// efficient, since the copying garbage collector can put clauses together +// which are likely accessed after each other. + +void Internal::delete_garbage_clauses () { + + flush_all_occs_and_watches (); + + LOG ("deleting garbage clauses"); +#ifndef CADICAL_QUIET + int64_t collected_bytes = 0, collected_clauses = 0; +#endif + const auto end = clauses.end (); + auto j = clauses.begin (), i = j; + while (i != end) { + Clause *c = *j++ = *i++; + if (!c->collect ()) + continue; +#ifndef CADICAL_QUIET + collected_bytes += c->bytes (); + collected_clauses++; +#endif + delete_clause (c); + j--; + } + clauses.resize (j - clauses.begin ()); + shrink_vector (clauses); + + PHASE ("collect", stats.collections, + "collected %" PRId64 " bytes of %" PRId64 " garbage clauses", + collected_bytes, collected_clauses); +} + +/*------------------------------------------------------------------------*/ + +// This is the start of the copying garbage collector using the arena. At +// the core is the following function, which copies a clause to the 'to' +// space of the arena. Be careful if this clause is a reason of an +// assignment. In that case update the reason reference. +// +void Internal::copy_clause (Clause *c) { + LOG (c, "moving"); + CADICAL_assert (!c->moved); + char *p = (char *) c; + char *q = arena.copy (p, c->bytes ()); + c->copy = (Clause *) q; + c->moved = true; + LOG ("copied clause[%" PRId64 "] from %p to %p", c->id, (void *) c, + (void *) c->copy); +} + +// This is the moving garbage collector. + +void Internal::copy_non_garbage_clauses () { + + size_t collected_clauses = 0, collected_bytes = 0; + size_t moved_clauses = 0, moved_bytes = 0; + + // First determine 'moved_bytes' and 'collected_bytes'. + // + for (const auto &c : clauses) + if (!c->collect ()) + moved_bytes += c->bytes (), moved_clauses++; + else + collected_bytes += c->bytes (), collected_clauses++; + + PHASE ("collect", stats.collections, + "moving %zd bytes %.0f%% of %zd non garbage clauses", moved_bytes, + percent (moved_bytes, collected_bytes + moved_bytes), + moved_clauses); + (void) moved_clauses, (void) collected_clauses, (void) collected_bytes; + // Prepare 'to' space of size 'moved_bytes'. + // + arena.prepare (moved_bytes); + + // Keep clauses in arena in the same order. + // + if (opts.arenacompact) + for (const auto &c : clauses) + if (!c->collect () && arena.contains (c)) + copy_clause (c); + + if (opts.arenatype == 1 || !watching ()) { + + // Localize according to current clause order. + + // If the option 'opts.arenatype == 1' is set, then this means the + // solver uses the original order of clauses. If there are no watches, + // we can not use the watched based copying policies below. This + // happens if garbage collection is triggered during bounded variable + // elimination. + + // Copy clauses according to the order of calling 'copy_clause', which + // in essence just gives a compacting garbage collector, since their + // relative order is kept, and actually already gives the largest + // benefit due to better cache locality. + + for (const auto &c : clauses) + if (!c->moved && !c->collect ()) + copy_clause (c); + + } else if (opts.arenatype == 2) { + + // Localize according to (original) variable order. + + // This is almost the version used by MiniSAT and descendants. + // Our version uses saved phases too. + + for (int sign = -1; sign <= 1; sign += 2) + for (auto idx : vars) + for (const auto &w : watches (sign * likely_phase (idx))) + if (!w.clause->moved && !w.clause->collect ()) + copy_clause (w.clause); + + } else { + + // Localize according to decision queue order. + + // This is the default for search. It allocates clauses in the order of + // the decision queue and also uses saved phases. It seems faster than + // the MiniSAT version and thus we keep 'opts.arenatype == 3'. + + CADICAL_assert (opts.arenatype == 3); + + for (int sign = -1; sign <= 1; sign += 2) + for (int idx = queue.last; idx; idx = link (idx).prev) + for (const auto &w : watches (sign * likely_phase (idx))) + if (!w.clause->moved && !w.clause->collect ()) + copy_clause (w.clause); + } + + // Do not forget to move clauses which are not watched, which happened in + // a rare situation, and now is only left as defensive code. + // + for (const auto &c : clauses) + if (!c->collect () && !c->moved) + copy_clause (c); + + flush_all_occs_and_watches (); + update_reason_references (); + + // Replace and flush clause references in 'clauses'. + // + const auto end = clauses.end (); + auto j = clauses.begin (), i = j; + for (; i != end; i++) { + Clause *c = *i; + if (c->collect ()) + delete_clause (c); + else + CADICAL_assert (c->moved), *j++ = c->copy, deallocate_clause (c); + } + clauses.resize (j - clauses.begin ()); + if (clauses.size () < clauses.capacity () / 2) + shrink_vector (clauses); + + if (opts.arenasort) + rsort (clauses.begin (), clauses.end (), pointer_rank ()); + + // Release 'from' space completely and then swap 'to' with 'from'. + // + arena.swap (); + + PHASE ("collect", stats.collections, + "collected %zd bytes %.0f%% of %zd garbage clauses", + collected_bytes, + percent (collected_bytes, collected_bytes + moved_bytes), + collected_clauses); +} + +/*------------------------------------------------------------------------*/ + +// Maintaining clause statistics is complex and error prone but necessary +// for proper scheduling of garbage collection, particularly during bounded +// variable elimination. With this function we can check whether these +// statistics are updated correctly. + +void Internal::check_clause_stats () { +#ifndef CADICAL_NDEBUG + int64_t irredundant = 0, redundant = 0, total = 0, irrlits = 0; + for (const auto &c : clauses) { + if (c->garbage) + continue; + if (c->redundant) + redundant++; + else + irredundant++; + if (!c->redundant) + irrlits += c->size; + total++; + } + CADICAL_assert (stats.current.irredundant == irredundant); + CADICAL_assert (stats.current.redundant == redundant); + CADICAL_assert (stats.current.total == total); + CADICAL_assert (stats.irrlits == irrlits); +#endif +} + +/*------------------------------------------------------------------------*/ + +// only delete binary clauses from watch list that are already mark as +// deleted. +void Internal::remove_garbage_binaries () { + if (unsat) + return; + START (collect); + + if (!protected_reasons) + protect_reasons (); + int backtrack_level = level + 1; + Watches saved; + for (auto v : vars) { + for (auto lit : {-v, v}) { + CADICAL_assert (saved.empty ()); + Watches &ws = watches (lit); + const const_watch_iterator end = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i; + for (i = j; i != end; i++) { + Watch w = *i; + *j++ = w; + Clause *c = w.clause; + COVER (!w.binary () && c->size == 2); + if (!w.binary ()) + continue; + if (c->reason && c->garbage) { + COVER (true); + CADICAL_assert (c->size == 2); + backtrack_level = + min (backtrack_level, var (c->literals[0]).level); + LOG ("need to backtrack to before level %d", backtrack_level); + --j; + continue; + } + if (!c->collect ()) + continue; + LOG (c, "removing from watch list"); + --j; + } + ws.resize (j - ws.begin ()); + shrink_vector (ws); + } + } + delete_garbage_clauses (); + unprotect_reasons (); + if (backtrack_level - 1 < level) + backtrack (backtrack_level - 1); + STOP (collect); +} + +/*------------------------------------------------------------------------*/ + +bool Internal::arenaing () { return opts.arena && (stats.collections > 1); } + +void Internal::garbage_collection () { + if (unsat) + return; + START (collect); + report ('G', 1); + stats.collections++; + mark_satisfied_clauses_as_garbage (); + if (!protected_reasons) + protect_reasons (); + if (arenaing ()) + copy_non_garbage_clauses (); + else + delete_garbage_clauses (); + check_clause_stats (); + check_var_stats (); + unprotect_reasons (); + report ('C', 1); + STOP (collect); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_compact.cpp b/src/sat/cadical/cadical_compact.cpp new file mode 100644 index 000000000..6747218cb --- /dev/null +++ b/src/sat/cadical/cadical_compact.cpp @@ -0,0 +1,557 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Compacting removes holes generated by inactive variables (fixed, +// eliminated, substituted or pure) by mapping active variables indices down +// to a contiguous interval of indices. + +/*------------------------------------------------------------------------*/ + +bool Internal::compacting () { + if (level) + return false; + if (!opts.compact) + return false; + if (stats.conflicts < lim.compact) + return false; + int inactive = max_var - active (); + CADICAL_assert (inactive >= 0); + if (!inactive) + return false; + if (inactive < opts.compactmin) + return false; + return inactive >= (1e-3 * opts.compactlim) * max_var; +} + +/*------------------------------------------------------------------------*/ + +struct Mapper { + + Internal *internal; + int new_max_var; // New 'max_var' after compacting. + int *table; // Old variable index to new literal map. + int first_fixed; // First fixed variable index. + int map_first_fixed; // Mapped literal of first fixed variable. + signed char first_fixed_val; // Value of first fixed variable. + size_t new_vsize; + + /*----------------------------------------------------------------------*/ + // We produce a compacting garbage collector like map of old 'src' to + // new 'dst' variables. Inactive variables are just skipped except for + // fixed ones which will be mapped to the first fixed variable (in the + // appropriate phase). This avoids to handle the case 'fixed value' + // separately as it is done in Lingeling, where fixed variables are + // mapped to the internal variable '1'. + // + Mapper (Internal *i) + : internal (i), new_max_var (0), first_fixed (0), map_first_fixed (0), + first_fixed_val (0) { + table = new int[internal->max_var + 1u]; + clear_n (table, internal->max_var + 1u); + + CADICAL_assert (!internal->level); + + for (auto src : internal->vars) { + const Flags &f = internal->flags (src); + if (f.active ()) + table[src] = ++new_max_var; + else if (f.fixed () && !first_fixed) + table[first_fixed = src] = map_first_fixed = ++new_max_var; + } + + first_fixed_val = first_fixed ? internal->val (first_fixed) : 0; + new_vsize = new_max_var + 1u; + } + + ~Mapper () { delete[] table; } + + /*----------------------------------------------------------------------*/ + // Map old variable indices. A result of zero means not mapped. + // + int map_idx (int src) { + CADICAL_assert (0 < src); + CADICAL_assert (src <= internal->max_var); + const int res = table[src]; + CADICAL_assert (res <= new_max_var); + return res; + } + + /*----------------------------------------------------------------------*/ + // The 'map_idx' above is just a look-up into the 'table'. Here we have + // to care about signedness of 'src', and in addition that fixed variables + // have all to be mapped to the first fixed variable 'first_fixed'. + // + int map_lit (int src) { + int res = map_idx (abs (src)); + if (!res) { + const signed char tmp = internal->val (src); + if (tmp) { + CADICAL_assert (first_fixed); + res = map_first_fixed; + if (tmp != first_fixed_val) + res = -res; + } + } else if ((src) < 0) + res = -res; + CADICAL_assert (abs (res) <= new_max_var); + return res; + } + + /*----------------------------------------------------------------------*/ + // Map positive variable indices in vector. + // + template void map_vector (vector &v) { + for (auto src : internal->vars) { + const int dst = map_idx (src); + if (!dst) + continue; + CADICAL_assert (0 < dst); + CADICAL_assert (dst <= src); + v[dst] = v[src]; + } + v.resize (new_vsize); + shrink_vector (v); + } + + /*----------------------------------------------------------------------*/ + // Map positive and negative variable indices in two-sided vector. + // + template void map2_vector (vector &v) { + for (auto src : internal->vars) { + const int dst = map_idx (src); + if (!dst) + continue; + CADICAL_assert (0 < dst); + CADICAL_assert (dst <= src); + v[2 * dst] = v[2 * src]; + v[2 * dst + 1] = v[2 * src + 1]; + } + v.resize (2 * new_vsize); + shrink_vector (v); + } + + /*----------------------------------------------------------------------*/ + // Map a vector of literals, flush inactive literals, then resize and + // shrink it to fit the new size after flushing. + // + void map_flush_and_shrink_lits (vector &v) { + const auto end = v.end (); + auto j = v.begin (), i = j; + for (; i != end; i++) { + const int src = *i; + int dst = map_idx (abs (src)); + CADICAL_assert (abs (dst) <= abs (src)); + if (!dst) + continue; + if (src < 0) + dst = -dst; + *j++ = dst; + } + v.resize (j - v.begin ()); + shrink_vector (v); + } +}; + +/*------------------------------------------------------------------------*/ + +static signed char *ignore_clang_analyze_memory_leak_warning; + +void Internal::compact () { + + START (compact); + + CADICAL_assert (active () < max_var); + + stats.compacts++; + + CADICAL_assert (!level); + CADICAL_assert (!unsat); + CADICAL_assert (!conflict); + CADICAL_assert (clause.empty ()); + CADICAL_assert (levels.empty ()); + CADICAL_assert (analyzed.empty ()); + CADICAL_assert (minimized.empty ()); + CADICAL_assert (control.size () == 1); + CADICAL_assert (propagated == trail.size ()); + + garbage_collection (); + + Mapper mapper (this); + + if (mapper.first_fixed) + LOG ("found first fixed %d", + sign (mapper.first_fixed_val) * mapper.first_fixed); + else + LOG ("no variable fixed"); + + if (!assumptions.empty ()) { + CADICAL_assert (!external->assumptions.empty ()); + LOG ("temporarily reset internal assumptions"); + reset_assumptions (); + } + + const bool is_constraint = !constraint.empty (); + if (is_constraint) { + CADICAL_assert (!external->constraint.empty ()); + LOG ("temporarily reset internal constraint"); + reset_constraint (); + } + + /*======================================================================*/ + // In this first part we only map stuff without reallocation / shrinking. + /*======================================================================*/ + + // Flush the external indices. This has to occur before we map 'vals'. + // Also fixes external units. + // + for (auto eidx : external->vars) { + int src = external->e2i[eidx]; + if (!src) { + continue; + } + if (lrat || frat) { + CADICAL_assert (eidx > 0); + CADICAL_assert (external->ext_units.size () >= (size_t) 2 * eidx + 1); + int64_t id1 = external->ext_units[2 * eidx]; + int64_t id2 = external->ext_units[2 * eidx + 1]; + CADICAL_assert (!id1 || !id2); + if (!id1 && !id2) { + int64_t new_id1 = unit_clauses (2 * src); + int64_t new_id2 = unit_clauses (2 * src + 1); + external->ext_units[2 * eidx] = new_id1; + external->ext_units[2 * eidx + 1] = new_id2; + } + } + int dst = mapper.map_lit (src); + LOG ("compact %" PRId64 + " maps external %d to internal %d from internal %d", + stats.compacts, eidx, dst, src); + external->e2i[eidx] = dst; + } + + // Delete garbage units. Needs to occur before resizing unit_clauses + // + if (lrat || frat) { + for (auto src : internal->vars) { + const int dst = mapper.map_idx (src); + CADICAL_assert (dst <= src); + const signed char tmp = internal->val (src); + if (!dst && !tmp) { + unit_clauses (2 * src) = 0; + unit_clauses (2 * src + 1) = 0; + continue; + } + if (!tmp || src == mapper.first_fixed) { + CADICAL_assert (0 < dst); + if (dst == src) + continue; + CADICAL_assert (!unit_clauses (2 * dst) && !unit_clauses (2 * dst + 1)); + unit_clauses (2 * dst) = unit_clauses (2 * src); + unit_clauses (2 * dst + 1) = unit_clauses (2 * src + 1); + unit_clauses (2 * src) = 0; + unit_clauses (2 * src + 1) = 0; + continue; + } + int64_t id = unit_clauses (2 * src); + int lit = src; + if (!id) { + id = unit_clauses (2 * src + 1); + lit = -lit; + } + unit_clauses (2 * src) = 0; + unit_clauses (2 * src + 1) = 0; + CADICAL_assert (id); + } + unit_clauses_idx.resize (2 * mapper.new_vsize); + shrink_vector (unit_clauses_idx); + } + // Map the literals in all clauses. + // + for (const auto &c : clauses) { + CADICAL_assert (!c->garbage); + for (auto &src : *c) { + CADICAL_assert (!val (src)); + int dst; + dst = mapper.map_lit (src); + CADICAL_assert (dst || c->garbage); + src = dst; + } + } + + // Map the blocking literals in all watches. + // + if (!wtab.empty ()) + for (auto lit : lits) + for (auto &w : watches (lit)) + w.blit = mapper.map_lit (w.blit); + + // We first flush inactive variables and map the links in the queue. This + // has to be done before we map the actual links data structure 'links'. + { + int prev = 0, mapped_prev = 0, next; + for (int idx = queue.first; idx; idx = next) { + next = links[idx].next; + if (idx == mapper.first_fixed) + continue; + const int dst = mapper.map_idx (idx); + if (!dst) + continue; + CADICAL_assert (active (idx)); + if (prev) + links[prev].next = dst; + else + queue.first = dst; + links[idx].prev = mapped_prev; + mapped_prev = dst; + prev = idx; + } + if (prev) + links[prev].next = 0; + else + queue.first = 0; + queue.unassigned = queue.last = mapped_prev; + } + + /*======================================================================*/ + // In the second part we map, flush and shrink arrays. + /*======================================================================*/ + + CADICAL_assert (trail.size () == num_assigned); + mapper.map_flush_and_shrink_lits (trail); + propagated = trail.size (); + num_assigned = trail.size (); + if (mapper.first_fixed) { + CADICAL_assert (trail.size () == 1); + var (mapper.first_fixed).trail = 0; // before mapping 'vtab' + } else + CADICAL_assert (trail.empty ()); + + if (!probes.empty ()) + mapper.map_flush_and_shrink_lits (probes); + + if (!sweep_schedule.empty ()) + mapper.map_flush_and_shrink_lits (sweep_schedule); + + /*======================================================================*/ + // In the third part we map stuff and also reallocate memory. + /*======================================================================*/ + + // Now we continue in reverse order of allocated bytes, e.g., see + // 'Internal::enlarge' which reallocates in order of allocated bytes. + + mapper.map_vector (ftab); + mapper.map_vector (parents); + mapper.map_vector (marks); + mapper.map_vector (phases.saved); + mapper.map_vector (phases.forced); + mapper.map_vector (phases.target); + mapper.map_vector (phases.best); + mapper.map_vector (phases.prev); + mapper.map_vector (phases.min); + + // Special code for 'frozentab'. + // + for (auto src : vars) { + const int dst = abs (mapper.map_lit (src)); + if (!dst) + continue; + if (src == dst) + continue; + CADICAL_assert (dst < src); + if ((size_t) src >= frozentab.size ()) + break; + if ((size_t) dst >= frozentab.size ()) + break; + frozentab[dst] += frozentab[src]; + frozentab[src] = 0; + } + frozentab.resize (min (frozentab.size (), mapper.new_vsize)); + shrink_vector (frozentab); + + // Special code for 'relevanttab'. + // + if (external) { + for (auto src : vars) { + const int dst = abs (mapper.map_lit (src)); + if (!dst) + continue; + if (src == dst) + continue; + CADICAL_assert (dst < src); + + relevanttab[dst] += relevanttab[src]; + relevanttab[src] = 0; + } + relevanttab.resize (mapper.new_vsize); + shrink_vector (relevanttab); + } + + /*----------------------------------------------------------------------*/ + + if (!external->assumptions.empty ()) { + + for (const auto &elit : external->assumptions) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + CADICAL_assert (eidx <= external->max_var); + int ilit = external->e2i[eidx]; + CADICAL_assert (ilit); // Because we froze all!!! + if (elit < 0) + ilit = -ilit; + assume (ilit); + } + + PHASE ("compact", stats.compacts, "reassumed %zd external assumptions", + external->assumptions.size ()); + } + + // Special case for 'val' as for 'val' we trade branch less code for + // memory and always allocated an [-maxvar,...,maxvar] array. + { + signed char *new_vals = new signed char[2 * mapper.new_vsize]; + ignore_clang_analyze_memory_leak_warning = new_vals; + new_vals += mapper.new_vsize; + for (auto src : vars) + new_vals[-mapper.map_idx (src)] = vals[-src]; + for (auto src : vars) + new_vals[mapper.map_idx (src)] = vals[src]; + new_vals[0] = 0; + vals -= vsize; + delete[] vals; + vals = new_vals; + vsize = mapper.new_vsize; + } + + // 'constrain' uses 'val', so this code has to be after remapping that + if (is_constraint) { + CADICAL_assert (!level); + CADICAL_assert (!external->constraint.back ()); + for (auto elit : external->constraint) { + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + CADICAL_assert (eidx <= external->max_var); + int ilit = external->e2i[eidx]; + CADICAL_assert (!ilit == !elit); + if (elit < 0) + ilit = -ilit; + LOG ("re adding lit external %d internal %d to constraint", elit, + ilit); + constrain (ilit); + } + PHASE ("compact", stats.compacts, + "added %zd external literals to constraint", + external->constraint.size () - 1); + } + + mapper.map_vector (i2e); + mapper.map2_vector (ptab); + mapper.map_vector (btab); + mapper.map_vector (gtab); + mapper.map_vector (links); + mapper.map_vector (vtab); + if (!ntab.empty ()) + mapper.map2_vector (ntab); + if (!wtab.empty ()) + mapper.map2_vector (wtab); + if (!otab.empty ()) + mapper.map2_vector (otab); + if (!rtab.empty ()) + mapper.map2_vector (rtab); + if (!big.empty ()) + mapper.map2_vector (big); + + /*======================================================================*/ + // In the fourth part we map the binary heap for scores. + /*======================================================================*/ + + // The simplest way to map a binary heap is to get all elements from the + // heap and reinsert them. This could be slightly improved in terms of + // speed if we add a 'flush (int * map)' function to 'Heap', but that is + // pretty complicated and would require that the 'Heap' knows that mapped + // elements with 'zero' destination should be flushed. + + vector saved; + CADICAL_assert (saved.empty ()); + if (!scores.empty ()) { + while (!scores.empty ()) { + const int src = scores.front (); + scores.pop_front (); + const int dst = mapper.map_idx (src); + if (!dst) + continue; + if (src == mapper.first_fixed) + continue; + saved.push_back (dst); + } + scores.erase (); + } + mapper.map_vector (stab); + if (!saved.empty ()) { + for (const auto idx : saved) + scores.push_back (idx); + scores.shrink (); + } + + /*----------------------------------------------------------------------*/ + + PHASE ("compact", stats.compacts, + "reducing internal variables from %d to %d", max_var, + mapper.new_max_var); + + /*----------------------------------------------------------------------*/ + + // Need to adjust the target and best assigned counters too. + + size_t new_target_assigned = 0, new_best_assigned = 0; + + for (auto idx : Range (mapper.new_max_var)) { + if (phases.target[idx]) + new_target_assigned++; + if (phases.best[idx]) + new_best_assigned++; + } + + LOG ("reset target assigned from %zd to %zd", target_assigned, + new_target_assigned); + LOG ("reset best assigned from %zd to %zd", best_assigned, + new_best_assigned); + + target_assigned = new_target_assigned; + best_assigned = new_best_assigned; + no_conflict_until = 0; + notified = 0; + + INIT_EMA (averages.current.trail.fast, opts.ematrailfast); + INIT_EMA (averages.current.trail.slow, opts.ematrailslow); + + /*----------------------------------------------------------------------*/ + + max_var = mapper.new_max_var; + + stats.unused = 0; + stats.inactive = stats.now.fixed = mapper.first_fixed ? 1 : 0; + stats.now.substituted = stats.now.eliminated = stats.now.pure = 0; + + check_var_stats (); + + int64_t delta = opts.compactint * (stats.compacts + 1); + lim.compact = stats.conflicts + delta; + + PHASE ("compact", stats.compacts, + "new compact limit %" PRId64 " after %" PRId64 " conflicts", + lim.compact, delta); + + STOP (compact); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_condition.cpp b/src/sat/cadical/cadical_condition.cpp new file mode 100644 index 000000000..8c2f72cf7 --- /dev/null +++ b/src/sat/cadical/cadical_condition.cpp @@ -0,0 +1,946 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Globally blocked clause elimination (which we call here 'conditioning') +// is described first in the PhD thesis of Benjamin Kiesl from 2019. An +// extended version, which in particular describes the algorithm implemented +// below is in our invited ATVA'19 paper [KieslHeuleBiere-ATVA'19]. This +// accordingly needs witnesses consisting potentially of more than one +// literal. It is the first technique implemented in CaDiCaL with this +// feature (PR clause elimination thus should work in principle too). + +// Basically globally blocked clauses are like set blocked clauses, except +// that the witness cube (of literals to be flipped during reconstruction) +// can contain variables which are not in the blocked clause. This +// can simulate some interesting global optimizations like 'headlines' from +// the FAN algorithm for ATPG. The technique was actually motivated to +// simulate this optimization. It turns out that globally blocked clauses +// can be seen as 'conditional autarkies', where in essence the condition +// cube is the negation of the globally blocked redundant clause (it +// needs to contain one autarky literal though) and the autarky part +// represents the witness. + +/*------------------------------------------------------------------------*/ + +// Elimination of globally blocked clauses is first tried in regular +// intervals in terms of the number of conflicts. Then the main heuristics +// is to trigger 'condition' if the decision level is above the current +// moving average of the back jump level. + +// TODO We might need to consider less frequent conditioning. + +bool Internal::conditioning () { + + if (!opts.condition) + return false; + if (!preprocessing && !opts.inprocessing) + return false; + if (preprocessing) + CADICAL_assert (lim.preprocessing); + + // Triggered in regular 'opts.conditionint' conflict intervals. + // + if (lim.condition > stats.conflicts) + return false; + + if (!level) + return false; // One decision necessary. + + if (level <= averages.current.jump) + return false; // Main heuristic. + + if (!stats.current.irredundant) + return false; + double remain = active (); + if (!remain) + return false; + double ratio = stats.current.irredundant / remain; + return ratio <= opts.conditionmaxrat; +} + +/*------------------------------------------------------------------------*/ + +// We start with the current assignment and then temporarily unassign +// literals. They are reassigned afterwards. The global state of the CDCL +// solver should not change though. Thus we copied from 'search_unassign' +// in 'backtrack.cpp' what is needed to unassign literals and then from +// 'search_assign' in 'propagate.cpp' what is needed for reassigning +// literals, but restricted the copied code to only updating the actual +// assignment (in 'vals') and not changing anything else. + +// We use temporarily unassigning for two purposes. First, if a conditional +// literal does not occur negated in a candidate clause it is unassigned. +// Second, as a minor optimization, we first unassign all root-level +// assigned (fixed) literals, to avoid checking the decision level of +// literals during the procedure. + +void Internal::condition_unassign (int lit) { + LOG ("condition unassign %d", lit); + CADICAL_assert (val (lit) > 0); + set_val (lit, 0); +} + +void Internal::condition_assign (int lit) { + LOG ("condition assign %d", lit); + CADICAL_assert (!val (lit)); + set_val (lit, 1); +} + +/*------------------------------------------------------------------------*/ + +// The current partition into conditional part and autarky part during +// refinement is represented through a conditional bit in 'marks'. + +inline bool Internal::is_conditional_literal (int lit) const { + return val (lit) > 0 && getbit (lit, 0); +} + +inline bool Internal::is_autarky_literal (int lit) const { + return val (lit) > 0 && !getbit (lit, 0); +} + +inline void Internal::mark_as_conditional_literal (int lit) { + LOG ("marking %d as conditional literal", lit); + CADICAL_assert (val (lit) > 0); + setbit (lit, 0); + CADICAL_assert (is_conditional_literal (lit)); + CADICAL_assert (!is_autarky_literal (lit)); +} + +inline void Internal::unmark_as_conditional_literal (int lit) { + LOG ("unmarking %d as conditional literal", lit); + CADICAL_assert (is_conditional_literal (lit)); + unsetbit (lit, 0); +} + +/*------------------------------------------------------------------------*/ + +// We also need to know the literals which are in the current clause. These +// are just marked (also in 'marks' but with the (signed) upper two bits). +// We need a signed mark here, since we have to distinguish positive and +// negative occurrences of literals in the candidate clause. + +inline bool Internal::is_in_candidate_clause (int lit) const { + return marked67 (lit) > 0; +} + +inline void Internal::mark_in_candidate_clause (int lit) { + LOG ("marking %d as literal of the candidate clause", lit); + mark67 (lit); + CADICAL_assert (is_in_candidate_clause (lit)); + CADICAL_assert (!is_in_candidate_clause (-lit)); +} + +inline void Internal::unmark_in_candidate_clause (int lit) { + LOG ("unmarking %d as literal of the candidate clause", lit); + CADICAL_assert (is_in_candidate_clause (lit)); + unmark67 (lit); +} + +/*------------------------------------------------------------------------*/ + +struct less_conditioned { + bool operator() (Clause *a, Clause *b) { + return !a->conditioned && b->conditioned; + } +}; + +// This is the function for eliminating globally blocked clauses. It is +// triggered during CDCL search according to 'conditioning' above and uses +// the current assignment as basis to find globally blocked clauses. + +long Internal::condition_round (long delta) { + + long limit; +#ifndef CADICAL_QUIET + long props = 0; +#endif + if (LONG_MAX - delta < stats.condprops) + limit = LONG_MAX; + else + limit = stats.condprops + delta; + + size_t initial_trail_level = trail.size (); + int initial_level = level; + + LOG ("initial trail level %zd", initial_trail_level); + + protect_reasons (); + +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + int additionally_assigned = 0; +#endif + + for (auto idx : vars) { + const signed char tmp = val (idx); + Var &v = var (idx); + if (tmp) { + if (v.level) { + const int lit = tmp < 0 ? -idx : idx; + if (!active (idx)) { + LOG ("temporarily unassigning inactive literal %d", lit); + condition_unassign (lit); + } + if (frozen (idx)) { + LOG ("temporarily unassigning frozen literal %d", lit); + condition_unassign (lit); + } + } + } else if (frozen (idx)) { + LOG ("keeping frozen literal %d unassigned", idx); + } else if (!active (idx)) { + LOG ("keeping inactive literal %d unassigned", idx); + } else { // if (preprocessing) { + if (initial_level == level) { + level++; + LOG ("new condition decision level"); + } + const int lit = decide_phase (idx, true); + condition_assign (lit); + v.level = level; + trail.push_back (lit); +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + additionally_assigned++; +#endif + } + } + LOG ("assigned %d additional literals", additionally_assigned); + + // We compute statistics about the size of the assignments. + // + // The initial assignment consists of the non-root-level assigned literals + // split into a conditional and an autarky part. The conditional part + // consists of literals assigned true and occurring negated in a clause + // (touch the clause), which does not contain another literal assigned to + // true. This initial partition is the same for all refinements used in + // checking whether a candidate clause is globally blocked. + // + // For each candidate clause some of the conditional literals have to be + // unassigned, and the autarky is shrunken by turning some of the autarky + // literals into conditional literals (which might get unassigned in a + // later refinement though). + // + // The fix-point of this procedure produces a final assignment, which + // consists of the remaining assigned literals, again split into a + // conditional and an autarky part. + // + struct { + size_t assigned, conditional, autarky; + } initial, remain; + + initial.assigned = 0; + for (auto idx : vars) { + const signed char tmp = val (idx); + if (!tmp) + continue; + if (!var (idx).level) + continue; + LOG ("initial assignment %ds", tmp < 0 ? -idx : idx); + initial.assigned++; + } + + PHASE ("condition", stats.conditionings, "initial assignment of size %zd", + initial.assigned); + + // For each candidate clause we refine the assignment (monotonically), + // by unassigning some conditional literals and turning some autarky + // literals into conditionals. + // + // As the conditional part is usually smaller than the autarky part our + // implementation only explicitly maintains the initial conditional part, + // with conditional bit set to true through 'mark_as_conditional_literal'. + // The autarky part consists of all literals assigned true which do not + // have their conditional bit set to true. Since in both cases the + // literal has to be assigned true, we only need a single bit for both the + // literal as well as its negation (it does not have to be 'signed'). + // + vector conditional; + + vector candidates; // Gather candidate clauses. +#ifndef CADICAL_QUIET + size_t watched = 0; // Number of watched clauses. +#endif + + initial.autarky = initial.assigned; // Initially all are in autarky + initial.conditional = 0; // and none in conditional part. + + // Upper bound on the number of watched clauses. In principle one could + // use 'SIZE_MAX' but this is not available by default (yet). + // + const size_t size_max = clauses.size () + 1; + + // Initialize additional occurrence lists. + // + init_occs (); + + // Number of previously conditioned and unconditioned candidates. + // + size_t conditioned = 0, unconditioned = 0; + + // Now go over all (non-garbage) irredundant clauses and check whether + // they are candidates, have to be watched, or whether they force the + // negation of some of their literals to be conditional initially. + // + for (const auto &c : clauses) { + if (c->garbage) + continue; // Can already be ignored. + if (c->redundant) + continue; // Ignore redundant clauses too. + + // First determine the following numbers for the candidate clause + // (restricted to non-root-level assignments). + // + int positive = 0; // Number true literals. + int negative = 0; // Number false literals. + int watch = 0; // True Literal to watch. + // + size_t minsize = size_max; // Number of occurrences of 'watch'. + // + // But also ignore root-level satisfied but not yet garbage clauses. + // + bool satisfied = false; // Root level satisfied. + // + for (const_literal_iterator l = c->begin (); + !satisfied && l != c->end (); l++) { + const int lit = *l; + const signed char tmp = val (lit); + if (tmp && !var (lit).level) + satisfied = (tmp > 0); + else if (tmp < 0) + negative++; + else if (tmp > 0) { + const size_t size = occs (lit).size (); + if (size < minsize) + watch = lit, minsize = size; + positive++; + } + } + if (satisfied) { // Ignore root-level satisfied clauses. + mark_garbage (c); // But mark them as garbage already now. + continue; // ... with next clause 'c'. + } + + // Candidates are clauses with at least a positive literal in it. + // + if (positive > 0) { + LOG (c, "found %d positive literals in candidate", positive); + candidates.push_back (c); + if (c->conditioned) + conditioned++; + else + unconditioned++; + } + + // Only one positive literal in each clauses with also at least one + // negative literal has to be watched in occurrence lists. These + // watched clauses will be checked to contain only negative literals as + // soon such a positive literal is unassigned. If this is the case + // these false literals have to be unassigned and potentially new + // conditional literals have to be determined. + // + // Note that only conditional literals are unassigned. However it does + // not matter that we might also watch autarky literals, because either + // such an autarky literal remains a witness that the clause is + // satisfied as long it remains an autarky literal. Otherwise at one + // point it becomes conditional and is unassigned, but then a + // replacement watch will be searched. + // + if (negative > 0 && positive > 0) { + LOG (c, "found %d negative literals in candidate", negative); + CADICAL_assert (watch); + CADICAL_assert (val (watch) > 0); + Occs &os = occs (watch); + CADICAL_assert (os.size () == minsize); + os.push_back (c); +#ifndef CADICAL_QUIET + watched++; +#endif + LOG (c, "watching %d with %zd occurrences in", watch, minsize); + } + + // The initial global conditional part for the current assignment is + // extracted from clauses with only negative literals. It is the same + // for all considered candidate clauses. These negative literals make up + // the global conditional part, are marked here. + // + if (negative > 0 && !positive) { + + size_t new_conditionals = 0; + + for (const_literal_iterator l = c->begin (); l != c->end (); l++) { + const int lit = *l; + signed char tmp = val (lit); + if (!tmp) + continue; + CADICAL_assert (tmp < 0); + if (!var (lit).level) + continue; // Not unassigned yet! + if (is_conditional_literal (-lit)) + continue; + mark_as_conditional_literal (-lit); + conditional.push_back (-lit); + new_conditionals++; + } + if (new_conditionals > 0) + LOG (c, "marked %zu negations of literals as conditional in", + new_conditionals); + + initial.conditional += new_conditionals; + CADICAL_assert (initial.autarky >= new_conditionals); + initial.autarky -= new_conditionals; + } + + } // End of loop over all clauses to collect candidates etc. + + PHASE ("condition", stats.conditionings, "found %zd candidate clauses", + candidates.size ()); + PHASE ("condition", stats.conditionings, + "watching %zu literals and clauses", watched); + PHASE ("condition", stats.conditionings, + "initially %zd conditional literals %.0f%%", initial.conditional, + percent (initial.conditional, initial.assigned)); + PHASE ("condition", stats.conditionings, + "initially %zd autarky literals %.0f%%", initial.autarky, + percent (initial.autarky, initial.assigned)); +#ifdef LOGGING + for (size_t i = 0; i < conditional.size (); i++) { + LOG ("initial conditional %d", conditional[i]); + CADICAL_assert (is_conditional_literal (conditional[i])); + } + for (size_t i = 0; i < trail.size (); i++) + if (is_autarky_literal (trail[i])) + LOG ("initial autarky %d", trail[i]); +#endif + CADICAL_assert (initial.conditional == conditional.size ()); + CADICAL_assert (initial.assigned == initial.conditional + initial.autarky); + + stats.condassinit += initial.assigned; + stats.condcondinit += initial.conditional; + stats.condautinit += initial.autarky; + stats.condassvars += active (); + + // To speed-up and particularly simplify the code we unassign all + // root-level variables temporarily, actually all inactive assigned + // variables. This allows us to avoid tests on whether an assigned + // literal is actually root-level assigned and thus should be ignored (not + // considered to be assigned). For this to work we have to ignore root + // level satisfied clauses as done above. These are neither candidates + // nor have to be watched. Remaining originally root-level assigned + // literals in clauses are only set to false. + // + for (const auto &lit : trail) + if (fixed (lit)) + condition_unassign (lit); + + // Stack to save temporarily unassigned (conditional) literals. + // + vector unassigned; + + // Make sure to focus on clauses not tried before by marking clauses which + // have been checked before using the 'conditioned' bit of clauses. If all + // candidates have their bit set, we have to reset it. Since the + // assignment might be completely different then last time and thus also + // the set of candidates this method does not really exactly lead to a + // round robin scheme of scheduling clauses. + // + // TODO consider computing conditioned and unconditioned over all clauses. + // + CADICAL_assert (conditioned + unconditioned == candidates.size ()); + if (conditioned && unconditioned) { + stable_sort (candidates.begin (), candidates.end (), + less_conditioned ()); + PHASE ("condition", stats.conditionings, + "focusing on %zd candidates %.0f%% not tried last time", + unconditioned, percent (unconditioned, candidates.size ())); + } else if (conditioned && !unconditioned) { + for (auto const &c : candidates) { + CADICAL_assert (c->conditioned); + c->conditioned = false; // Reset 'conditioned' bit. + } + PHASE ("condition", stats.conditionings, + "all %zd candidates tried before", conditioned); + } else { + CADICAL_assert (!conditioned); + PHASE ("condition", stats.conditionings, "all %zd candidates are fresh", + unconditioned); + } + + // TODO prune assignments further! + // And thus might result in less watched clauses. + // So watching should be done here and not earlier. + // Also, see below, we might need to consider the negation of unassigned + // literals in candidate clauses as being watched. + + // Now try to block all candidate clauses. + // + long blocked = 0; // Number of Successfully blocked clauses. + // +#ifndef CADICAL_QUIET + size_t untried = candidates.size (); +#endif + for (const auto &c : candidates) { + + if (initial.autarky <= 0) + break; + + if (c->reason) + continue; + + bool terminated_or_limit_hit = true; + if (terminated_asynchronously ()) + LOG ("asynchronous termination detected"); + else if (stats.condprops >= limit) + LOG ("condition propagation limit %ld hit", limit); + else + terminated_or_limit_hit = false; + + if (terminated_or_limit_hit) { + PHASE ("condition", stats.conditionings, + "%zd candidates %.0f%% not tried after %ld propagations", + untried, percent (untried, candidates.size ()), props); + break; + } +#ifndef CADICAL_QUIET + untried--; +#endif + CADICAL_assert (!c->garbage); + CADICAL_assert (!c->redundant); + + LOG (c, "candidate"); + c->conditioned = 1; // Next time later. + + // We watch an autarky literal in the clause, and can stop trying to + // globally block the clause as soon it turns into a conditional + // literal and we can not find another one. If the fix-point assignment + // is reached and we still have an autarky literal left the watched one + // is reported as witness for this clause being globally blocked. + // + int watched_autarky_literal = 0; + + // First mark all true literals in the candidate clause and find an + // autarky literal which witnesses that this clause has still a chance + // to be globally blocked. + // + for (const_literal_iterator l = c->begin (); l != c->end (); l++) { + const int lit = *l; + mark_in_candidate_clause (lit); + if (watched_autarky_literal) + continue; + if (!is_autarky_literal (lit)) + continue; + watched_autarky_literal = lit; + + // TODO assign non-assigned literals to false? + // Which might need to trigger watching additional clauses. + } + + if (!watched_autarky_literal) { + LOG ("no initial autarky literal found"); + for (const_literal_iterator l = c->begin (); l != c->end (); l++) + unmark_in_candidate_clause (*l); + continue; + } + + stats.condcands++; // Only now ... + + LOG ("watching first autarky literal %d", watched_autarky_literal); + + // Save assignment sizes for statistics, logging and checking. + // + remain = initial; + + // Position of next conditional and unassigned literal to process in the + // 'conditional' and the 'unassigned' stack. + // + struct { + size_t conditional, unassigned; + } next = {0, 0}; + + CADICAL_assert (unassigned.empty ()); + CADICAL_assert (conditional.size () == initial.conditional); + + while (watched_autarky_literal && stats.condprops < limit && + next.conditional < conditional.size ()) { + + CADICAL_assert (next.unassigned == unassigned.size ()); + + const int conditional_lit = conditional[next.conditional++]; + LOG ("processing next conditional %d", conditional_lit); + CADICAL_assert (is_conditional_literal (conditional_lit)); + + if (is_in_candidate_clause (-conditional_lit)) { + LOG ("conditional %d negated in candidate clause", conditional_lit); + continue; + } + + LOG ("conditional %d does not occur negated in candidate clause", + conditional_lit); + + condition_unassign (conditional_lit); + CADICAL_assert (!is_conditional_literal (conditional_lit)); + unassigned.push_back (conditional_lit); + + CADICAL_assert (remain.assigned > 0); + CADICAL_assert (remain.conditional > 0); + remain.conditional--; + remain.assigned--; + + while (watched_autarky_literal && stats.condprops < limit && + next.unassigned < unassigned.size ()) { + const int unassigned_lit = unassigned[next.unassigned++]; + LOG ("processing next unassigned %d", unassigned_lit); + CADICAL_assert (!val (unassigned_lit)); +#ifndef CADICAL_QUIET + props++; +#endif + stats.condprops++; + + Occs &os = occs (unassigned_lit); + if (os.empty ()) + continue; + + // Traverse all watched clauses of 'unassigned_lit' and find + // replacement watches or if none is found turn the negation of all + // false autarky literals in that clause into conditional literals. + // If one of those autarky literals is the watched autarky literal + // in the candidate clause, that one has to be updated too. + // + // We expect that this loop is a hot-spot for the procedure and thus + // are more careful about accessing end points for iterating. + // + auto i = os.begin (), j = i; + for (; watched_autarky_literal && j != os.end (); j++) { + Clause *d = *i++ = *j; + + int replacement = 0; // New watched literal in 'd'. + int negative = 0; // Negative autarky literals in 'd'. + + for (const_literal_iterator l = d->begin (); l != d->end (); + l++) { + const int lit = *l; + const signed char tmp = val (lit); + if (tmp > 0) + replacement = lit; + if (tmp < 0 && is_autarky_literal (-lit)) + negative++; + } + + if (replacement) { + LOG ("found replacement %d for unassigned %d", replacement, + unassigned_lit); + LOG (d, "unwatching %d in", unassigned_lit); + i--; // Drop watch! + LOG (d, "watching %d in", replacement); + + CADICAL_assert (replacement != unassigned_lit); + occs (replacement).push_back (d); + + continue; // ... with next watched clause 'd'. + } + + LOG ("no replacement found for unassigned %d", unassigned_lit); + + // Keep watching 'd' by 'unassigned_lit' if no replacement found. + + if (!negative) { + LOG (d, "no negative autarky literals left in"); + continue; // ... with next watched clause 'd'. + } + + LOG (d, "found %d negative autarky literals in", negative); + + for (const_literal_iterator l = d->begin (); + watched_autarky_literal && l != d->end (); l++) { + const int lit = *l; + if (!is_autarky_literal (-lit)) + continue; + mark_as_conditional_literal (-lit); + conditional.push_back (-lit); + + remain.conditional++; + CADICAL_assert (remain.autarky > 0); + remain.autarky--; + + if (-lit != watched_autarky_literal) + continue; + + LOG ("need to replace autarky literal %d in candidate", -lit); + replacement = 0; + + // TODO save starting point because we only move it forward? + + for (const_literal_iterator k = c->begin (); + !replacement && k != c->end (); k++) { + const int other = *k; + if (is_autarky_literal (other)) + replacement = other; + } + watched_autarky_literal = replacement; + + if (replacement) { + LOG (c, "watching autarky %d instead %d in candidate", + replacement, watched_autarky_literal); + watched_autarky_literal = replacement; + } else { + LOG ("failed to find an autarky replacement"); + watched_autarky_literal = 0; // Breaks out of 4 loops!!!!! + } + } // End of loop of turning autarky literals into conditionals. + } // End of loop of all watched clauses of an unassigned literal. + // + // We might abort the occurrence traversal early but already + // removed some watches, thus have to just copy the rest. + // + if (i < j) { + while (j != os.end ()) + *i++ = *j++; + LOG ("flushed %zd occurrences of %d", os.end () - i, + unassigned_lit); + os.resize (i - os.begin ()); + } + } // End of loop which goes over all unprocessed unassigned literals. + } // End of loop which goes over all unprocessed conditional literals. + + // We are still processing the candidate 'c' and now have reached a + // final fix-point assignment partitioned into a conditional and an + // autarky part, or during unassigned literals figured that there is no + // positive autarky literal left in 'c'. + + LOG ("remaining assignment of size %zd", remain.assigned); + LOG ("remaining conditional part of size %zd", remain.conditional); + LOG ("remaining autarky part of size %zd", remain.autarky); + // + CADICAL_assert (remain.assigned - remain.conditional == remain.autarky); + // +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + // + // This is a sanity check, that the size of our implicit representation + // of the autarky part matches our 'remain' counts. We need the same + // code for determining autarky literals as in the loop below which adds + // autarky literals to the extension stack. + // + struct { + size_t assigned, conditional, autarky; + } check; + check.assigned = check.conditional = check.autarky = 0; + for (size_t i = 0; i < trail.size (); i++) { + const int lit = trail[i]; + if (val (lit)) { + check.assigned++; + if (is_conditional_literal (lit)) { + LOG ("remaining conditional %d", lit); + CADICAL_assert (!is_autarky_literal (lit)); + check.conditional++; + } else { + CADICAL_assert (is_autarky_literal (lit)); + LOG ("remaining autarky %d", lit); + check.autarky++; + } + } else { + CADICAL_assert (!is_autarky_literal (lit)); + CADICAL_assert (!is_conditional_literal (lit)); + } + } + CADICAL_assert (remain.assigned == check.assigned); + CADICAL_assert (remain.conditional == check.conditional); + CADICAL_assert (remain.autarky == check.autarky); +#endif + + // Success if an autarky literal is left in the clause and + // we did not abort the loop too early because the propagation + // limit was hit. + // + if (watched_autarky_literal && stats.condprops < limit) { + CADICAL_assert (is_autarky_literal (watched_autarky_literal)); + CADICAL_assert (is_in_candidate_clause (watched_autarky_literal)); + + blocked++; + stats.conditioned++; + LOG (c, "positive autarky literal %d globally blocks", + watched_autarky_literal); + + LOG ("remaining %zd assigned literals %.0f%%", remain.assigned, + percent (remain.assigned, initial.assigned)); + LOG ("remaining %zd conditional literals %.0f%%", remain.conditional, + percent (remain.conditional, remain.assigned)); + LOG ("remaining %zd autarky literals %.0f%%", remain.autarky, + percent (remain.autarky, remain.assigned)); + + // A satisfying assignment of a formula after removing a globally + // blocked clause might not satisfy that clause. As for variable + // elimination and classical blocked clauses, we thus maintain an + // extension stack for reconstructing an assignment which both + // satisfies the remaining formula as well as the clause. + // + // For globally blocked clauses we simply have to flip all literals in + // the autarky part and thus save the autarky on the extension stack + // in addition to the removed clause. In the classical situation (in + // bounded variable elimination etc.) we simply save one literal on + // the extension stack. + // + // TODO find a way to shrink the autarky part or some other way to + // avoid pushing too many literals on the extension stack. + // + external->push_zero_on_extension_stack (); + for (const auto &lit : trail) + if (is_autarky_literal (lit)) + external->push_witness_literal_on_extension_stack (lit); + if (proof) + proof->weaken_minus (c); + external->push_clause_on_extension_stack (c); + + mark_garbage (c); + + stats.condassrem += remain.assigned; + stats.condcondrem += remain.conditional; + stats.condautrem += remain.autarky; + stats.condassirem += initial.assigned; + } + + // In this last part specific to one candidate clause, we have to get + // back to the initial assignment and reset conditionals. First we + // assign all the unassigned literals (if necessary). + // + if (!unassigned.empty ()) { + LOG ("reassigning %zd literals", unassigned.size ()); + while (!unassigned.empty ()) { + const int lit = unassigned.back (); + unassigned.pop_back (); + condition_assign (lit); + } + } + + // Then we remove from the conditional stack autarky literals which + // became conditional and also reset their 'conditional' bit. + // + if (initial.conditional < conditional.size ()) { + LOG ("flushing %zd autarky literals from conditional stack", + conditional.size () - initial.conditional); + while (initial.conditional < conditional.size ()) { + const int lit = conditional.back (); + conditional.pop_back (); + unmark_as_conditional_literal (lit); + } + } + + // Finally unmark all literals in the candidate clause. + // + for (const_literal_iterator l = c->begin (); l != c->end (); l++) + unmark_in_candidate_clause (*l); + + } // End of loop over all candidate clauses. + + PHASE ("condition", stats.conditionings, + "globally blocked %ld clauses %.0f%%", blocked, + percent (blocked, candidates.size ())); + + // Unmark initial conditional variables. + // + for (const auto &lit : conditional) + unmark_as_conditional_literal (lit); + + erase_vector (unassigned); + erase_vector (conditional); + erase_vector (candidates); + + // Unassign additionally assigned literals. + // +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + int additionally_unassigned = 0; +#endif + while (trail.size () > initial_trail_level) { + int lit = trail.back (); + trail.pop_back (); + condition_unassign (lit); +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + additionally_unassigned++; +#endif + } + LOG ("unassigned %d additionally assigned literals", + additionally_unassigned); + CADICAL_assert (additionally_unassigned == additionally_assigned); + + if (level > initial_level) { + LOG ("reset condition decision level"); + level = initial_level; + } + + reset_occs (); + delete_garbage_clauses (); + + // Reassign previously assigned variables again. + // + LOG ("reassigning previously assigned variables"); + for (size_t i = 0; i < initial_trail_level; i++) { + const int lit = trail[i]; + const signed char tmp = val (lit); + CADICAL_assert (tmp >= 0); + if (!tmp) + condition_assign (lit); + } + +#ifndef CADICAL_NDEBUG + for (const auto &lit : trail) + CADICAL_assert (!marked (lit)); +#endif + + unprotect_reasons (); + + return blocked; +} + +void Internal::condition (bool update_limits) { + + if (unsat) + return; + if (!stats.current.irredundant) + return; + + START_SIMPLIFIER (condition, CONDITION); + stats.conditionings++; + + // Propagation limit to avoid too much work in 'condition'. We mark + // tried candidate clauses after giving up, such that next time we run + // 'condition' we can try them. + // + long limit = stats.propagations.search; + limit *= opts.conditioneffort; + limit /= 1000; + if (limit < opts.conditionmineff) + limit = opts.conditionmineff; + if (limit > opts.conditionmaxeff) + limit = opts.conditionmaxeff; + CADICAL_assert (stats.current.irredundant); + limit *= 2.0 * active () / (double) stats.current.irredundant; + limit = max (limit, 2l * active ()); + + PHASE ("condition", stats.conditionings, + "started after %" PRIu64 " conflicts limited by %ld propagations", + stats.conflicts, limit); + + long blocked = condition_round (limit); + + STOP_SIMPLIFIER (condition, CONDITION); + report ('g', !blocked); + + if (!update_limits) + return; + + long delta = opts.conditionint * (stats.conditionings + 1); + lim.condition = stats.conflicts + delta; + + PHASE ("condition", stats.conditionings, + "next limit at %" PRIu64 " after %ld conflicts", lim.condition, + delta); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_config.cpp b/src/sat/cadical/cadical_config.cpp new file mode 100644 index 000000000..d4268790c --- /dev/null +++ b/src/sat/cadical/cadical_config.cpp @@ -0,0 +1,107 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +struct NameVal { + const char *name; + int val; +}; + +/*------------------------------------------------------------------------*/ + +// These are dummy configurations, which require additional code. + +static NameVal default_config[1]; // With '-pedantic' just '[]' or +static NameVal plain_config[1]; // '[0]' gave a warning. + +/*------------------------------------------------------------------------*/ + +// Here we have the pre-defined default configurations. + +static NameVal sat_config[] = { + {"elimeffort", 10}, + {"stabilizeonly", 1}, + {"subsumeeffort", 60}, +}; + +static NameVal unsat_config[] = { + {"stabilize", 0}, + {"walk", 0}, +}; + +/*------------------------------------------------------------------------*/ + +#define CONFIGS \ +\ + CONFIG (default, "set default advanced internal options") \ + CONFIG (plain, "disable all internal preprocessing options") \ + CONFIG (sat, "set internal options to target satisfiable instances") \ + CONFIG (unsat, "set internal options to target unsatisfiable instances") + +static const char *configs[] = { +#define CONFIG(N, D) #N, + CONFIGS +#undef CONFIG +}; + +static size_t num_configs = sizeof configs / sizeof *configs; + +/*------------------------------------------------------------------------*/ + +bool Config::has (const char *name) { +#define CONFIG(N, D) \ + if (!strcmp (name, #N)) \ + return true; + CONFIGS +#undef CONFIG + return false; +} + +bool Config::set (Options &opts, const char *name) { + if (!strcmp (name, "default")) { + opts.reset_default_values (); + return true; + } + if (!strcmp (name, "plain")) { + opts.disable_preprocessing (); + return true; + } +#define CONFIG(N, D) \ + do { \ + if (strcmp (name, #N)) \ + break; \ + const NameVal *BEGIN = N##_config; \ + const NameVal *END = BEGIN + sizeof N##_config / sizeof (NameVal); \ + for (const NameVal *P = BEGIN; P != END; P++) { \ + CADICAL_assert (Options::has (P->name)); \ + opts.set (P->name, P->val); \ + } \ + return true; \ + } while (0); + CONFIGS +#undef CONFIG + return false; +} + +/*------------------------------------------------------------------------*/ + +void Config::usage () { +#define CONFIG(N, D) printf (" %-14s " D "\n", "--" #N); + CONFIGS +#undef CONFIG +} + +/*------------------------------------------------------------------------*/ + +const char **Config::begin () { return configs; } +const char **Config::end () { return &configs[num_configs]; } + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_congruence.cpp b/src/sat/cadical/cadical_congruence.cpp new file mode 100644 index 000000000..8fbceee24 --- /dev/null +++ b/src/sat/cadical/cadical_congruence.cpp @@ -0,0 +1,7567 @@ +#include "global.h" + +#include "congruence.hpp" +#include "internal.hpp" +#include +#include +#include +#include +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +Closure::Closure (Internal *i) + : internal (i), table (128, Hash (nonces)) +#ifdef LOGGING + , + fresh_id (internal->clause_id) +#endif +{ +} + +char &Closure::lazy_propagated (int lit) { + return lazy_propagated_idx[internal->vidx (lit)]; +} + +void update_ite_flags (Gate *g) { + int8_t f = g->degenerated_ite; + const int lhs = g->lhs; + const int cond = g->rhs [0]; + const int then_lit = g->rhs[1]; + const int else_lit = g->rhs[2]; + + if (lhs == cond) { + f |= Special_ITE_GATE::NO_NEG_THEN; + f |= Special_ITE_GATE::NO_PLUS_ELSE; + } + if (lhs == -cond) { + f |= Special_ITE_GATE::NO_PLUS_THEN; + f |= Special_ITE_GATE::NO_NEG_ELSE; + } + if (lhs == then_lit) { + f |= Special_ITE_GATE::NO_PLUS_THEN; + f |= Special_ITE_GATE::NO_NEG_THEN; + } + if (lhs == else_lit) { + f |= Special_ITE_GATE::NO_PLUS_ELSE; + f |= Special_ITE_GATE::NO_NEG_ELSE; + } + g->degenerated_ite = f; + CADICAL_assert (lhs != -then_lit); + CADICAL_assert (lhs != -else_lit); + CADICAL_assert (cond != then_lit); + CADICAL_assert (cond != else_lit); + CADICAL_assert (cond != -then_lit); + CADICAL_assert (cond != -else_lit); +} + +void check_correct_ite_flags (const Gate *const g) { +#ifndef CADICAL_NDEBUG + const int8_t f = g->degenerated_ite; + const int lhs = g->lhs; + const int cond = g->rhs [0]; + const int then_lit = g->rhs[1]; + const int else_lit = g->rhs[2]; + CADICAL_assert (g->pos_lhs_ids.size () == 4); + if (g->pos_lhs_ids[0].clause == nullptr) + CADICAL_assert ((f & Special_ITE_GATE::NO_PLUS_THEN)); + if (g->pos_lhs_ids[1].clause == nullptr) + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_THEN); + if (g->pos_lhs_ids[2].clause == nullptr) + CADICAL_assert (f & Special_ITE_GATE::NO_PLUS_ELSE); + if (g->pos_lhs_ids[3].clause == nullptr) + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_ELSE); + if (lhs == cond) { + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_THEN); + CADICAL_assert (f & Special_ITE_GATE::NO_PLUS_ELSE); + } + if (lhs == -cond) { + CADICAL_assert (f & Special_ITE_GATE::NO_PLUS_THEN); + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_ELSE); + } + if (lhs == then_lit) { + CADICAL_assert (f & Special_ITE_GATE::NO_PLUS_THEN); + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_THEN); + } + if (lhs == else_lit) { + CADICAL_assert (f & Special_ITE_GATE::NO_PLUS_ELSE); + CADICAL_assert (f & Special_ITE_GATE::NO_NEG_ELSE); + } + CADICAL_assert (lhs != -then_lit); + CADICAL_assert (lhs != -else_lit); + CADICAL_assert (cond != then_lit); + CADICAL_assert (cond != else_lit); + CADICAL_assert (cond != -then_lit); + CADICAL_assert (cond != -else_lit); +#else + (void)g; +#endif +} + +/*------------------------------------------------------------------------*/ + +static size_t hash_lits (std::array &nonces, + const vector &lits) { + size_t hash = 0; + const auto end_nonces = end (nonces); + const auto begin_nonces = begin (nonces); + auto n = begin_nonces; + for (auto lit : lits) { + hash += lit; + hash *= *n++; + hash = (hash << 4) | (hash >> 60); + if (n == end_nonces) + n = begin_nonces; + } + hash ^= hash >> 32; + return hash; +} + +size_t Hash::operator() (const Gate *const g) const { + CADICAL_assert (hash_lits (nonces, g->rhs) == g->hash); + return g->hash; +} + +bool gate_contains (Gate *g, int lit) { + return find (begin (g->rhs), end (g->rhs), lit) != end (g->rhs); +} +/*------------------------------------------------------------------------*/ +struct compact_binary_rank { + typedef uint64_t Type; + uint64_t operator() (const CompactBinary &a) { + return ((uint64_t) a.lit1 << 32) + a.lit2; + }; +}; + +struct compact_binary_order { + bool operator() (const CompactBinary &a, const CompactBinary &b) { + return compact_binary_rank () (a) < compact_binary_rank () (b); + }; +}; + +bool Closure::find_binary (int lit, int other) const { + const auto offsize = + offsetsize[internal->vlit (lit)]; // in C++17: [offset, size] = + const auto offset = offsize.first; + const auto size = offsize.second; + const auto begin = std::begin (binaries) + offset; + const auto end = std::begin (binaries) + size; + CADICAL_assert (end <= std::end (binaries)); + const CompactBinary target = CompactBinary (nullptr, 0, lit, other); + auto it = std::lower_bound (begin, end, target, compact_binary_order ()); + // search_binary only returns a bool + bool found = (it != end && it->lit1 == lit && it->lit2 == other); + if (found) { + LOG ("found binary [%zd] %d %d", it->id, lit, other); + if (internal->lrat) + lrat_chain.push_back (it->id); + } + return found; +} + +void Closure::extract_binaries () { + if (!internal->opts.congruencebinaries) + return; + START (extractbinaries); + offsetsize.resize (internal->max_var * 2 + 3, make_pair (0, 0)); + + // in kissat this is done during watch clearing. TODO: consider doing this + // too. + for (Clause *c : internal->clauses) { + if (c->garbage) + continue; + if (c->redundant && c->size != 2) + continue; + if (c->size > 2) + continue; + CADICAL_assert (c->size == 2); + const int lit = c->literals[0]; + const int other = c->literals[1]; + const bool already_sorted = + internal->vlit (lit) < internal->vlit (other); + binaries.push_back (CompactBinary (c, c->id, + already_sorted ? lit : other, + already_sorted ? other : lit)); + } + + MSORT (internal->opts.radixsortlim, begin (binaries), end (binaries), + compact_binary_rank (), compact_binary_order ()); + + { + const size_t size = binaries.size (); + size_t i = 0; + while (i < size) { + CompactBinary bin = binaries[i]; + const int lit = bin.lit1; + size_t j = i; + while (j < size && binaries[j].lit1 == lit) { + ++j; + } + CADICAL_assert (j >= i); + CADICAL_assert (j <= size); + offsetsize[internal->vlit (lit)] = make_pair (i, j); + i = j; + } + } + + size_t extracted = 0, already_present = 0, duplicated = 0; + + const size_t size = internal->clauses.size (); + for (size_t i = 0; i < size; ++i) { + Clause *d = internal->clauses[i]; // binary clauses are appended, so + // reallocation possible + if (d->garbage) + continue; + if (d->redundant) + continue; + if (d->size != 3) + continue; + const int *lits = d->literals; + const int a = lits[0]; + const int b = lits[1]; + const int c = lits[2]; // obfuscating d->literals[2] which triggers an error in pedandic mode + if (internal->val (a)) + continue; + if (internal->val (b)) + continue; + if (internal->val (c)) + continue; + int l = 0, k = 0; + if (find_binary (-a, b) || find_binary (-a, c)) { + l = b, k = c; + } else if (find_binary (-b, a) || find_binary (-b, c)) { + l = a, k = c; + } else if (find_binary (-c, a) || find_binary (-c, b)) { + l = a, k = b; + } else + continue; + LOG (d, "strengthening"); + if (!find_binary (l, k)) { + if (internal->lrat) + lrat_chain.push_back (d->id); + add_binary_clause (l, k); + ++extracted; + } else { + ++already_present; + if (internal->lrat) + lrat_chain.clear (); + } + } + lrat_chain.clear (); + + offsetsize.clear (); + + // kissat has code to remove duplicates, which we have already removed + // before starting congruence + MSORT (internal->opts.radixsortlim, begin (binaries), end (binaries), + compact_binary_rank (), compact_binary_order ()); + const size_t new_size = binaries.size (); + { + size_t i = 0; + for (size_t j = 1; j < new_size; ++j) { + CADICAL_assert (i < j); + if (binaries[i].lit1 == binaries[j].lit1 && + binaries[i].lit2 == binaries[j].lit2) { + // subsuming later clause + subsume_clause (binaries[i].clause, + binaries[j].clause); // the local one is specialized + ++duplicated; + } else { + binaries[++i] = binaries[j]; + } + } + CADICAL_assert (i <= new_size); + binaries.resize (i); + } + binaries.clear (); + STOP (extractbinaries); + LOG ("extracted %zu binaries (plus %zu already present and %zu " + "duplicates)", + extracted, already_present, duplicated); +} + +/*------------------------------------------------------------------------*/ +// marking structure for congruence closure, by reference +signed char &Closure::marked (int lit) { + CADICAL_assert (internal->vlit (lit) < marks.size ()); + return marks[internal->vlit (lit)]; +} + +void Closure::unmark_all () { + for (auto lit : internal->analyzed) { + CADICAL_assert (marked (lit)); + marked (lit) = 0; + } + internal->analyzed.clear (); +} + +void Closure::set_mu1_reason (int lit, Clause *c) { + CADICAL_assert (marked (lit) & 1); + LOG (c, "mu1 %d -> %zd", lit, c->id); + mu1_ids[internal->vlit (lit)] = LitClausePair (lit, c); +} + +void Closure::set_mu2_reason (int lit, Clause *c) { + CADICAL_assert (marked (lit) & 2); + if (!internal->lrat) + return; + LOG (c, "mu2 %d -> %zd", lit, c->id); + mu2_ids[internal->vlit (lit)] = LitClausePair (lit, c); +} + +void Closure::set_mu4_reason (int lit, Clause *c) { + CADICAL_assert (marked (lit) & 4); + if (!internal->lrat) + return; + LOG (c, "mu4 %d -> %zd", lit, c->id); + mu4_ids[internal->vlit (lit)] = LitClausePair (lit, c); +} + +LitClausePair Closure::marked_mu1 (int lit) { + return mu1_ids[internal->vlit (lit)]; +} + +LitClausePair Closure::marked_mu2 (int lit) { + return mu2_ids[internal->vlit (lit)]; +} + +LitClausePair Closure::marked_mu4 (int lit) { + return mu4_ids[internal->vlit (lit)]; +} + +struct sort_literals_by_var_rank { + CaDiCaL::Internal *internal; + sort_literals_by_var_rank (Internal *i) : internal (i) {} + + typedef uint64_t Type; + + Type operator() (const int &a) const { return internal->vlit (a); } +}; +struct sort_literals_by_var_rank_except { + CaDiCaL::Internal *internal; + int lhs; + int except; + sort_literals_by_var_rank_except (Internal *i, int my_lhs, int except2) + : internal (i), lhs (my_lhs), except (except2) {} + sort_literals_by_var_rank_except (Internal *i, int my_lhs) + : internal (i), lhs (my_lhs), except (0) {} + typedef uint64_t Type; + Type operator() (const int &a) const { + Type res = 0; + if (abs (a) == abs (except)) + res = 1 - (a > 0); + else if (abs (a) == abs (lhs)) + res = 3 - (a > 0); + else + res = internal->vlit (a) + 2; // probably +2 enough + return ~res; + } +}; + +struct sort_literals_by_var_smaller_except { + CaDiCaL::Internal *internal; + int lhs; + int except; + sort_literals_by_var_smaller_except (Internal *i, int my_lhs, int except2) + : internal (i), lhs (my_lhs), except (except2) {} + sort_literals_by_var_smaller_except (Internal *i, int my_lhs) + : internal (i), lhs (my_lhs), except (0) {} + bool operator() (const int &a, const int &b) const { + return sort_literals_by_var_rank_except (internal, lhs, except) (a) < + sort_literals_by_var_rank_except (internal, lhs, except) (b); + if (abs (a) == abs (except) && abs (b) != abs (except)) + return false; + if (abs (a) != abs (except) && abs (b) == abs (except)) + return true; + if (abs (a) == abs (lhs) && abs (b) != abs (lhs)) + return false; + if (abs (a) != abs (lhs) && abs (b) == abs (lhs)) + return true; + return sort_literals_by_var_rank (internal) (a) > + sort_literals_by_var_rank (internal) (b); + } +}; +struct sort_literals_by_var_smaller { + CaDiCaL::Internal *internal; + sort_literals_by_var_smaller (Internal *i) : internal (i) {} + bool operator() (const int &a, const int &b) const { + return sort_literals_by_var_rank (internal) (a) < + sort_literals_by_var_rank (internal) (b); + } +}; + +void Closure::sort_literals_by_var_except (vector &rhs, int lhs, + int except2) { + MSORT (internal->opts.radixsortlim, begin (rhs), end (rhs), + sort_literals_by_var_rank_except (internal, lhs, except2), + sort_literals_by_var_smaller_except (internal, lhs, except2)); +} +void Closure::sort_literals_by_var (vector &rhs) { + MSORT (internal->opts.radixsortlim, begin (rhs), end (rhs), + sort_literals_by_var_rank (internal), + sort_literals_by_var_smaller (internal)); +} +/*------------------------------------------------------------------------*/ +int &Closure::representative (int lit) { + CADICAL_assert (internal->vlit (lit) < representant.size ()); + return representant[internal->vlit (lit)]; +} +int Closure::representative (int lit) const { + CADICAL_assert (internal->vlit (lit) < representant.size ()); + return representant[internal->vlit (lit)]; +} + +int &Closure::eager_representative (int lit) { + CADICAL_assert (internal->vlit (lit) < eager_representant.size ()); + return eager_representant[internal->vlit (lit)]; +} + +int Closure::eager_representative (int lit) const { + CADICAL_assert (internal->vlit (lit) < eager_representant.size ()); + return eager_representant[internal->vlit (lit)]; +} + +int Closure::find_lrat_representative_with_marks (int lit) { + int res = lit; + int nxt = lit; + do { + res = nxt; + nxt = representative (nxt); + if (nxt != res) { + LOG ("%d has reason %" PRIu64, res, representative_id (res)); + lrat_chain.push_back (representative_id (res)); + } + } while (nxt != res || marked (nxt) || marked (-nxt)); + + return nxt; +} +int Closure::find_representative (int lit) { + int res = lit; + int nxt = lit; + do { + res = nxt; + nxt = representative (nxt); + } while (nxt != res); + + return res; +} + +int Closure::find_representative_and_compress (int lit, bool update_eager) { + LOG ("finding representative of %d", lit); + int res = lit; + int nxt = lit; + int path_length = 0; + do { + res = nxt; + nxt = representative (nxt); + ++path_length; + LOG ("updating %d -> %d", res, nxt); + } while (nxt != res); + + if (path_length > 2) { + LOG ("learning new rewriting from %d to %d (current path length: %d)", + lit, res, path_length); + if (update_eager) + eager_representative (lit) = res; + if (internal->lrat) { + produce_representative_lrat (lit); + Clause *equiv = add_tmp_binary_clause (-lit, res); + + if (equiv) { + representative_id (lit) = equiv->id; + if (update_eager) + eager_representative_id (lit) = equiv->id; + } + } + if (internal->lrat) + lrat_chain.clear (); + } else if (path_length == 2) { + if (update_eager) { + LOG ("updating information %d -> %d in eager", lit, res); + eager_representative (lit) = res; + if (internal->lrat) + eager_representative_id (lit) = representative_id (lit); + CADICAL_assert (!internal->lrat || eager_representative_id (lit)); + } + } + + if (lit != res) { + representative (lit) = res; + } + LOG ("representative of %d is %d", lit, res); + return res; +} + +void Closure::push_lrat_unit (int lit) { + if (!internal->lrat) + return; + CADICAL_assert (internal->val (lit) > 0); + LRAT_ID id = internal->unit_id (lit); + lrat_chain.push_back (id); +} + +int Closure::find_eager_representative (int lit) { + int res = lit; + int nxt = lit; + do { + res = nxt; + nxt = eager_representative (nxt); + } while (nxt != res); + + return res; +} + +int Closure::find_eager_representative_and_compress (int lit) { + if (!internal->lrat) + return find_representative (lit); + int res = lit; + int nxt = lit; + int path_length = 0; + do { + res = nxt; + nxt = eager_representative (nxt); + ++path_length; + } while (nxt != res); + + // CADICAL_assert (res == find_representative (lit)); + // we have to do path compression to support LRAT proofs + if (path_length > 2) { + LOG ("learning new rewriting from %d to %d (current path length: %d)", + lit, res, path_length); + std::vector tmp_lrat_chain; + if (internal->lrat) { + tmp_lrat_chain = std::move (lrat_chain); + lrat_chain.clear (); + produce_eager_representative_lrat (lit); + } + eager_representative (lit) = res; + Clause *equiv = add_tmp_binary_clause (-lit, res); + equiv->hyper = true; + + if (internal->lrat && equiv) { + eager_representative_id (lit) = equiv->id; + } + if (internal->lrat) { + lrat_chain = std::move (tmp_lrat_chain); + } + } else if (path_length == 2) { + LOG ("duplicated information %d -> %d to eager with clause %" PRIu64, + lit, res, eager_representative_id (lit)); + CADICAL_assert (eager_representative (lit) == res); + CADICAL_assert (!internal->lrat || eager_representative_id (lit)); + } + CADICAL_assert (internal->clause.empty ()); + return res; +} + +void Closure::find_representative_and_compress_both (int lit) { + find_representative_and_compress (lit, false); + find_representative_and_compress (-lit, false); +} + +void Closure::import_lazy_and_find_eager_representative_and_compress_both ( + int lit) { + find_representative_and_compress (lit); + find_eager_representative_and_compress (lit); + find_representative_and_compress (-lit); + find_eager_representative_and_compress (-lit); +} + +void Closure::produce_representative_lrat (int lit) { + CADICAL_assert (internal->lrat); + LOG ("production of LRAT chain for %d with representative %" PRIu64, lit, + representative_id (lit)); + CADICAL_assert (internal->lrat); + CADICAL_assert (lrat_chain.empty ()); + int res = lit; + int nxt = lit; + CADICAL_assert (nxt != representative (nxt)); + do { + res = nxt; + nxt = representative (nxt); + if (nxt != res) { + LOG ("%d has reason %" PRIu64, res, representative_id (res)); + lrat_chain.push_back (representative_id (res)); + } + } while (nxt != res); +} + +void Closure::produce_eager_representative_lrat (int lit) { + CADICAL_assert (internal->lrat); + LOG ("production of LRAT chain for %d with representative %" PRIu64, lit, + eager_representative_id (lit)); + CADICAL_assert (internal->lrat); + CADICAL_assert (lrat_chain.empty ()); + int res = lit; + int nxt = lit; + CADICAL_assert (nxt != eager_representative (nxt)); + do { + res = nxt; + nxt = eager_representative (nxt); + if (nxt != res) { + LOG ("%d has reason %" PRIu64, res, eager_representative_id (res)); + lrat_chain.push_back (eager_representative_id (res)); + } + } while (nxt != res); +} + +LRAT_ID Closure::find_representative_lrat (int lit) { + if (!internal->lrat) + return 0; + int res = lit; +#ifndef CADICAL_NDEBUG + int nxt = representative (res); + CADICAL_assert (nxt == representative (res)); +#endif + LOG ("checking for existing LRAT chain for %d with clause %" PRIu64, lit, + eager_representative_id (res)); + CADICAL_assert (representative_id (res)); + return representative_id (res); +} + +LRAT_ID Closure::find_eager_representative_lrat (int lit) { + if (!internal->lrat) + return 0; + int res = lit; +#ifndef CADICAL_NDEBUG + int nxt = eager_representative (res); + CADICAL_assert (nxt == eager_representative (res)); +#endif + LOG ("checking for existing LRAT chain for %d with clause %" PRIu64, lit, + eager_representative_id (res)); + CADICAL_assert (eager_representative_id (res)); + return eager_representative_id (res); +} + +LRAT_ID &Closure::eager_representative_id (int lit) { + CADICAL_assert (internal->vlit (lit) < eager_representant_id.size ()); + return eager_representant_id[internal->vlit (lit)]; +} +LRAT_ID Closure::eager_representative_id (int lit) const { + CADICAL_assert (internal->vlit (lit) < eager_representant_id.size ()); + return eager_representant_id[internal->vlit (lit)]; +} + +LRAT_ID &Closure::representative_id (int lit) { + CADICAL_assert (internal->vlit (lit) < representant_id.size ()); + return representant_id[internal->vlit (lit)]; +} +LRAT_ID Closure::representative_id (int lit) const { + CADICAL_assert (internal->vlit (lit) < representant_id.size ()); + return representant_id[internal->vlit (lit)]; +} + +void Closure::mark_garbage (Gate *g) { + LOG (g, "marking as garbage"); + CADICAL_assert (!g->garbage); + g->garbage = true; + garbage.push_back (g); +} + +bool Closure::remove_gate (GatesTable::iterator git) { + CADICAL_assert (git != end (table)); + CADICAL_assert (!internal->unsat); + (*git)->indexed = false; + LOG ((*git), "removing from hash table"); + table.erase (git); + return true; +} + +bool Closure::remove_gate (Gate *g) { + if (!g->indexed) + return false; + CADICAL_assert (!internal->unsat); + CADICAL_assert (table.find (g) != end (table)); + table.erase (table.find (g)); + g->indexed = false; + LOG (g, "removing from hash table"); + return true; +} + +void Closure::index_gate (Gate *g) { + CADICAL_assert (!g->indexed); + CADICAL_assert (!internal->unsat); + CADICAL_assert (g->arity () > 1); + CADICAL_assert (g->hash == hash_lits (nonces, g->rhs)); + LOG (g, "adding to hash table"); + table.insert (g); + g->indexed = true; +} + +void Closure::produce_rewritten_clause_lrat_and_clean ( + std::vector &litIds, int except_lhs, bool remove_units) { + CADICAL_assert (internal->lrat_chain.empty ()); + for (auto &litId : litIds) { + CADICAL_assert (litId.clause); + litId.clause = produce_rewritten_clause_lrat (litId.clause, except_lhs, + remove_units); + litId.current_lit = find_eager_representative (litId.current_lit); + } + litIds.erase ( + std::remove_if (begin (litIds), end (litIds), + [] (const LitClausePair &p) { return !p.clause; }), + end (litIds)); +} + +void Closure::produce_rewritten_clause_lrat_and_clean ( + std::vector &litIds, int except_lhs, + size_t &old_position1, size_t &old_position2, bool remove_units) { + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (old_position1 != old_position2); + size_t j = 0; + for (size_t i = 0; i < litIds.size (); ++i) { + CADICAL_assert (j <= i); + litIds[j].clause = produce_rewritten_clause_lrat ( + litIds[i].clause, except_lhs, remove_units); + litIds[j].current_lit = + find_eager_representative (litIds[i].current_lit); + if (i == old_position1) { + if (litIds[j].clause) + old_position1 = j; + else + old_position1 = -1; + } + if (i == old_position2) { + if (litIds[j].clause) + old_position2 = j; + else + old_position2 = -1; + } + if (litIds[j].clause) + ++j; + } + litIds.resize (j); +} + +void Closure::compute_rewritten_clause_lrat_simple (Clause *c, int except) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + bool changed = false; + bool tautology = false; + for (auto lit : *c) { + LOG ("checking if %d is required", lit); + if (internal->marked2 (lit)) { + continue; + } + if (internal->marked2 (-lit)) { + tautology = true; + break; + } + if (lit == except || lit == -except) { + internal->mark2 (lit); + clause.push_back (lit); + continue; + } + if (internal->val (lit) < 0) { +#if 1 + LOG ("found unit %d, removing it", -lit); + LRAT_ID id = internal->unit_id (-lit); + lrat_chain.push_back (id); + changed = true; + continue; +#else + LOG ("found unit %d, but ignoring it", -lit); +#endif + } + if (internal->val (lit) > 0) { + LOG ("found positive unit, so clause is subsumed by unit"); + tautology = true; + } + const int other = find_eager_representative_and_compress (lit); + const bool marked = internal->marked2 (other); + const bool neg_marked = internal->marked2 (-other); + if (!marked) + internal->mark2 (other); + if (neg_marked) { + tautology = true; + LOG ("tautology due to %d -> %d", lit, other); + } else if (lit == other && marked) { + changed = true; + LOG ("%d -> %d already in", lit, other); + } else if (lit != other) { + if (!marked) + clause.push_back (other); + changed = true; + lrat_chain.push_back (eager_representative_id (lit)); + } else if (!marked) + clause.push_back (lit); + } + + for (auto lit : *c) { + internal->unmark (lit); + } + + for (auto lit : clause) { + internal->unmark (lit); + } + + lrat_chain.push_back (c->id); + if (tautology) { + LOG ("generated clause is a tautology"); + lrat_chain.clear (); + clause.clear (); + } else if (changed && clause.size () == 1) { + LOG (lrat_chain, "LRAT chain"); + } else { + LOG (c, "oops this should not happen"); + CADICAL_assert (false); + } +} + +Clause *Closure::new_tmp_clause (std::vector &clause) { + CADICAL_assert (internal->lrat); + CADICAL_assert (!clause.empty ()); + CADICAL_assert (!lrat_chain.empty ()); + bool clear = false; + + LOG (clause, "learn new tmp clause"); + CADICAL_assert (clause.size () >= 2); + internal->external->check_learned_clause (); + + CADICAL_assert (internal->clause.size () <= (size_t) INT_MAX); + const int size = (int) clause.size (); + CADICAL_assert (size >= 2); + + size_t bytes = Clause::bytes (size); + Clause *c = (Clause *) new char[bytes]; + DeferDeleteArray clause_delete ((char *) c); + + c->id = ++internal->clause_id; + + c->conditioned = false; + c->covered = false; + c->enqueued = false; + c->frozen = false; + c->garbage = false; + c->gate = false; + c->hyper = false; + c->instantiated = false; + c->moved = false; + c->reason = false; + c->redundant = false; + c->transred = false; + c->subsume = false; + c->swept = false; + c->flushed = false; + c->vivified = false; + c->vivify = false; + c->used = 0; + + c->glue = size; + c->size = size; + c->pos = 2; + + for (int i = 0; i < size; i++) + c->literals[i] = clause[i]; + + // Just checking that we did not mess up our sophisticated memory layout. + // This might be compiler dependent though. Crucial for correctness. + // + CADICAL_assert (c->bytes () == bytes); + + clause_delete.release (); + LOG (c, "new pointer %p", (void *) c); + + if (clear) + clause.clear (); + + if (internal->proof) { + internal->proof->add_derived_clause (c, lrat_chain); + } + extra_clauses.push_back (c); + CADICAL_assert (internal->lrat_chain.empty ()); + return c; +} + +Clause *Closure::new_clause () { + CADICAL_assert (internal->clause.empty () || clause.empty ()); + bool clear = false; + if (internal->clause.empty ()) { + swap (internal->clause, clause); + clear = true; + } + + Clause *c = internal->new_clause (false); + + if (clear) + internal->clause.clear (); + + if (internal->proof) { + internal->proof->add_derived_clause (c, lrat_chain); + } + + return c; +} + +// TODO we here duplicate the arguments of push_id_and_rewriting_lrat but we +// probably do not need that. +void Closure::produce_rewritten_clause_lrat ( + std::vector &litIds, int except_lhs, bool remove_units) { + CADICAL_assert (internal->lrat_chain.empty ()); + for (auto &litId : litIds) { + if (!litId.clause) + continue; + litId.clause = produce_rewritten_clause_lrat (litId.clause, except_lhs, + remove_units); + litId.current_lit = find_eager_representative (litId.current_lit); + } +} + +Clause *Closure::produce_rewritten_clause_lrat (Clause *c, int except_lhs, + bool remove_units, bool fail_on_unit) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + auto tmp_lrat (std::move (lrat_chain)); + lrat_chain.clear (); + LOG (c, "rewriting clause for LRAT proof, except for rewriting %d", + except_lhs); + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + bool changed = false; + bool tautology = false; + for (auto lit : *c) { + LOG ("checking if %d is required", lit); + if (internal->marked2 (lit)) { + continue; + } + if (internal->marked2 (-lit)) { + tautology = true; + break; + } + if (lit == except_lhs || lit == -except_lhs) { + internal->mark2 (lit); + clause.push_back (lit); + continue; + } + if (internal->val (lit) < 0) { + if (remove_units || lazy_propagated (lit)) { + LOG ("found unit %d, removing it", -lit); + LRAT_ID id = internal->unit_id (-lit); + lrat_chain.push_back (id); + changed = true; + continue; + } else + LOG ("found unit %d, but ignoring it", -lit); + } + if (internal->val (lit) > 0) { + LOG ("found positive unit %d, so clause is subsumed by unit", lit); + if (remove_units || lazy_propagated (lit)) + tautology = true; + } + const int other = find_eager_representative_and_compress (lit); + const bool marked = internal->marked2 (other); + const bool neg_marked = internal->marked2 (-other); + if (!marked) + internal->mark2 (other); + if (neg_marked) { + tautology = true; + LOG ("tautology due to %d -> %d", lit, other); + } else if (lit == other && marked) { + changed = true; + LOG ("%d -> %d already in", lit, other); + } else if (lit != other) { + if (!marked) + clause.push_back (other); + changed = true; + lrat_chain.push_back (eager_representative_id (lit)); + } else if (!marked) + clause.push_back (lit); + } + + for (auto lit : *c) { + internal->unmark (lit); + } + + for (auto lit : clause) { + internal->unmark (lit); + } + + lrat_chain.push_back (c->id); + Clause *d; + CADICAL_assert (internal->clause.empty ()); + if (tautology) { + LOG ("generated clause is a tautology"); + d = nullptr; + lrat_chain.clear (); + } else if (changed && clause.size () == 1) { + LOG (lrat_chain, "LRAT chain"); + if (fail_on_unit) { + d = nullptr; + CADICAL_assert (false && "rewriting produced a unit clause"); + } else { + d = c; + } + } else if (changed) { + LOG (lrat_chain, "LRAT Chain"); + d = new_tmp_clause (clause); + LOG (d, "rewritten clause to"); + } else { + LOG (c, "clause is unchanged, so giving up"); + lrat_chain.clear (); + d = c; + } + clause.clear (); + lrat_chain = std::move (tmp_lrat); + CADICAL_assert (internal->clause.empty ()); + return d; +} + +void Closure::push_id_on_chain (std::vector &chain, + Rewrite rewrite, int lit) { + LOG ("adding reason %zd for rewriting %d marked", + lit == rewrite.src ? rewrite.id1 : rewrite.id2, lit); + chain.push_back (lit == rewrite.src ? rewrite.id1 : rewrite.id2); +} + +void Closure::push_id_and_rewriting_lrat_unit (Clause *c, Rewrite rewrite1, + std::vector &chain, + bool insert_id_after, + Rewrite rewrite2, + int except_lhs, + int except_lhs2) { + LOG (c, + "computing normalized LRAT chain for clause to produce unit, " + "rewriting except for %d (%" PRIu64 ", %" PRIu64 ") and %d (%" PRIu64 + ", %" PRIu64 ") and skipping %d and %d", + rewrite1.src, rewrite1.id1, rewrite1.id2, rewrite2.src, rewrite2.id1, + rewrite2.id2, except_lhs, except_lhs2); + CADICAL_assert (c); + std::vector units, rewriting; + for (auto other : *c) { + // unclear how to achieve this in the simplify context where other == + // g->lhs might be set CADICAL_assert (internal->val (other) <= 0 || other == + // except); + if (other == except_lhs || other == -except_lhs) { + // do nothing; + } else if (other == except_lhs2 || other == -except_lhs2) { + // do nothing; + } else if (internal->val (other) < 0) { + LOG ("found unit %d", -other); + LRAT_ID id = internal->unit_id (-other); + units.push_back (id); + } else if (other == rewrite1.src && rewrite1.id1) { + push_id_on_chain (rewriting, rewrite1, other); + } else if (other == -rewrite1.src && rewrite1.id2) { + push_id_on_chain (rewriting, rewrite1, other); + } else if (other == rewrite2.src && rewrite2.id1) { + push_id_on_chain (rewriting, rewrite2, other); + } else if (other == -rewrite2.src && rewrite2.id2) { + push_id_on_chain (rewriting, rewrite2, other); + } else if (other != find_eager_representative_and_compress (other)) { +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + const int rewritten_other = eager_representative (other); + CADICAL_assert (other != rewritten_other); + LOG ("reason for representative of %d %d is %" PRIu64 "", other, + rewritten_other, find_eager_representative_lrat (other)); +#endif + rewriting.push_back (find_eager_representative_lrat (other)); + } else { + LOG ("no rewriting needed for %d", other); + } + } + for (auto id : units) + chain.push_back (id); + + if (!insert_id_after) + chain.push_back (c->id); + for (auto id : rewriting) + chain.push_back (id); + + if (insert_id_after) + chain.push_back (c->id); +} + +// Note: it is important that the Rewrite takes over the normal rewriting, +// because we can force rewriting that way that have not been done eagerly +// yet. +void Closure::push_id_and_rewriting_lrat_full (Clause *c, Rewrite rewrite1, + std::vector &chain, + bool insert_id_after, + Rewrite rewrite2, + int except_lhs, + int except_lhs2) { + LOG (c, + "computing normalized LRAT chain for clause, rewriting except for " + "%d (%" PRIu64 ", %" PRIu64 ") and %d (%" PRIu64 ", %" PRIu64 + ") and skipping %d and %d", + rewrite1.src, rewrite1.id1, rewrite1.id2, rewrite2.src, rewrite2.id1, + rewrite2.id2, except_lhs, except_lhs2); + CADICAL_assert (c); + if (!insert_id_after) + chain.push_back (c->id); + for (auto other : *c) { + // unclear how to achieve this in the simplify context where other == + // g->lhs might be set CADICAL_assert (internal->val (other) <= 0 || other == + // except); + if (other == except_lhs) { + // do nothing; + } else if (other == except_lhs2) { + // do nothing; + } else if (internal->val (other) < 0) { + LOG ("found unit %d", -other); + LRAT_ID id = internal->unit_id (-other); + CADICAL_assert (id); + chain.push_back (id); + } else if (other == rewrite1.src && rewrite1.id1) { + push_id_on_chain (chain, rewrite1, other); + } else if (other == -rewrite1.src && rewrite1.id2) { + push_id_on_chain (chain, rewrite1, other); + } else if (other == rewrite2.src && rewrite2.id1) { + push_id_on_chain (chain, rewrite2, other); + } else if (other == -rewrite2.src && rewrite2.id2) { + push_id_on_chain (chain, rewrite2, other); + } else { + CADICAL_assert (other == find_eager_representative (other)); + LOG ("no rewriting needed for %d", other); + } + } + if (insert_id_after) + chain.push_back (c->id); +} + +// Note: it is important that the Rewrite takes over the normal rewriting, +// because we can force rewriting that way that have not been done eagerly +// yet. +void Closure::push_id_on_chain (std::vector &chain, Clause *c) { + CADICAL_assert (c); + chain.push_back (c->id); + LOG (lrat_chain, "chain"); +} + +void Closure::push_id_on_chain (std::vector &chain, + const std::vector &reasons) { + for (auto litId : reasons) { + LOG (litId.clause, "found lrat in gate %d from %zd", litId.current_lit, + litId.clause->id); + push_id_on_chain (chain, litId.clause); + } + LOG (lrat_chain, "chain from %zd reasons", reasons.size ()); +} + +void Closure::learn_congruence_unit_when_lhs_set (Gate *g, int src, + LRAT_ID id1, LRAT_ID id2, + int dst) { + if (!internal->lrat) + return; + LOG ("calculating LRAT chain learn_congruence_unit_when_lhs_set"); + CADICAL_assert (!g->pos_lhs_ids.empty ()); + CADICAL_assert (internal->analyzed.empty ()); + CADICAL_assert (internal->val (g->lhs) < 0); + switch (g->tag) { + case Gate_Type::And_Gate: + LOG (lrat_chain, "lrat"); + LOG (lrat_chain, "lrat"); + for (auto litId : g->neg_lhs_ids) + push_id_and_rewriting_lrat_unit ( + litId.clause, Rewrite (src, dst, id1, id2), lrat_chain); + LOG (lrat_chain, "lrat"); + break; + default: + CADICAL_assert (false); + } +} + +// Something very important here: as we are producing a unit, we cannot +// simplify or rewrite the clauses as this will produce units. +void Closure::learn_congruence_unit_falsifies_lrat_chain ( + Gate *g, int src, int dst, int clashing, int falsified, int unit) { + if (!internal->lrat) + return; + CADICAL_assert (!g->pos_lhs_ids.empty ()); + CADICAL_assert (internal->analyzed.empty ()); + CADICAL_assert (lrat_chain.empty ()); + std::vector proof_chain; + switch (g->tag) { + case Gate_Type::And_Gate: + if (clashing) { + LOG ("clashing %d where -lhs=%d", clashing, -g->lhs); + // Example: -2 = 1&3 and 3=2 + // The proof consists in taking the binary clause of the clashing + // literal + if (clashing == -g->lhs) { + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, + "found lrat in gate %d from %zd (looking for %d)", + litId.current_lit, litId.clause->id, falsified); + if (litId.current_lit == clashing) { + push_id_and_rewriting_lrat_unit ( + litId.clause, Rewrite (), proof_chain, true, Rewrite (), + g->degenerated_and_neg || g->degenerated_and_pos ? 0 : -g->lhs); + } + } + } else { + // Example: 3 = (-1&2) and 2=1 + // The proof consists in taking the binary clause with the rewrites + // Example where the rewrite must be before: + // 2: 3v2 + // 9: -2v1 + // 6: 3v1 + // The chain cannot start by 9 + if (g->degenerated_and_neg || g->degenerated_and_pos) { + LOG ("%d %d %d", src, dst, g->lhs); + if (src == g->lhs || dst == g->lhs) { + LOG ("degenerated AND gate with dst=lhs"); + for (const auto &litId : g->pos_lhs_ids) { + LOG (litId.clause, "definition clause %d ->", + litId.current_lit); + if (litId.current_lit == clashing) { + push_id_and_rewriting_lrat_unit (litId.clause, Rewrite (), + proof_chain, true, + Rewrite (), 0); + LOG (proof_chain, "produced lrat chain so far"); + } + } + CADICAL_assert (!proof_chain.empty ()); + } else { + LOG ("degenerated AND gate with conflict without LHS"); + for (const auto &litId : g->pos_lhs_ids) { + LOG (litId.clause, "definition clause %d ->", + litId.current_lit); + push_id_and_rewriting_lrat_unit (litId.clause, Rewrite (), + proof_chain, false, + Rewrite (), -g->lhs); + LOG (proof_chain, "produced lrat chain so far"); + } + } + } else { + LOG ("normal AND gate"); + for (const auto &litId : g->pos_lhs_ids) { + push_id_and_rewriting_lrat_unit (litId.clause, Rewrite (), + proof_chain, false, Rewrite (), + g->degenerated_and_neg || g->degenerated_and_pos ? 0 : -g->lhs); + LOG (proof_chain, "produced lrat chain so far"); + } + } + } + LOG (proof_chain, "produced lrat chain"); + } else if (falsified) { + LOG ("falsifies %d", falsified); + // Example is 3=(1&2) with 2=false or 3=(1&4) with 4=2 and 2=false + // (can happen when the unit was derived in the middle of the + // rewriting) + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, + "found lrat in gate %d from %zd (looking for %d)", + litId.current_lit, litId.clause->id, falsified); + if (litId.current_lit == falsified || + (litId.current_lit == src && dst == falsified)) { + push_id_and_rewriting_lrat_unit (litId.clause, Rewrite (), + proof_chain, true, Rewrite (), + -dst, -g->lhs); + } + } + } else { + CADICAL_assert (unit); + // Example is 1 = 2&3 where 2 and 3 are false + for (auto litId : g->neg_lhs_ids) { + push_id_and_rewriting_lrat_unit (litId.clause, Rewrite (), + proof_chain); + } + LOG (proof_chain, "produced lrat chain"); + break; + } + lrat_chain = std::move (proof_chain); + break; + default: + CADICAL_assert (false); + } + (void) unit; +} + +bool Closure::fully_propagate () { + if (internal->unsat) + return false; + LOG ("fully propagating"); + CADICAL_assert (internal->watching ()); + CADICAL_assert (full_watching); + bool no_conflict = internal->propagate (); + + if (no_conflict) + return true; + internal->learn_empty_clause (); + if (internal->lrat) + lrat_chain.clear (); + + return false; +} +bool Closure::learn_congruence_unit (int lit, bool delay_propagation, bool force_propagation) { + if (internal->unsat) + return false; + const signed char val_lit = internal->val (lit); + if (val_lit > 0) { + LOG ("already set lit %d", lit); + if (internal->lrat) + lrat_chain.clear (); + if (force_propagation) + return fully_propagate(); + return true; + } + LOG ("adding unit %s", LOGLIT (lit)); + ++internal->stats.congruence.units; + CADICAL_assert (!internal->lrat || !lrat_chain.empty ()); + if (val_lit < 0) { + if (internal->lrat) { + CADICAL_assert (internal->lrat_chain.empty ()); + LRAT_ID id = internal->unit_id (-lit); + internal->lrat_chain.push_back (id); + for (auto id : lrat_chain) + internal->lrat_chain.push_back (id); + lrat_chain.clear (); + } + internal->learn_empty_clause (); + return false; + } + + LOG (lrat_chain, "assigning due to LRAT chain"); + swap (lrat_chain, internal->lrat_chain); + internal->assign_unit (lit); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + if (delay_propagation) + return false; + else return fully_propagate (); +} + +// for merging the literals there are many cases +// TODO: LRAT does not work if the LHS is not in normal form and if the +// representative is also in the gate. +bool Closure::merge_literals_lrat ( + Gate *g, Gate *h, int lit, int other, + const std::vector &extra_reasons_lit, + const std::vector &extra_reasons_ulit) { + CADICAL_assert (!internal->unsat); + CADICAL_assert (g->lhs == lit); + CADICAL_assert (g == h || h->lhs == other); + (void) g, (void) h; + LOG ("merging literals %s and %s", LOGLIT(lit), LOGLIT(other)); + // TODO: this should not update_eager but still calculate the LRAT chain + // below! + const int repr_lit = find_representative_and_compress (lit, false); + const int repr_other = find_representative_and_compress (other, false); + find_representative_and_compress (-lit, false); + find_representative_and_compress (-other, false); + LOG ("merging literals %d [=%d] and %d [=%d]", lit, repr_lit, other, + repr_other); + + if (repr_lit == repr_other) { + LOG ("already merged %d and %d", lit, other); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + const int val_lit = internal->val (lit); + const int val_other = internal->val (other); + if (val_lit) { + if (val_lit == val_other) { + LOG ("not merging lits %d and %d assigned to same value", lit, other); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + } + + // For LRAT we need to distinguish more cases for a more regular + // reconstruction. + // + // 1. if lit = -other, then we learn lit and -lit to derive false + // + // 2. otherwise, we learn the new clauses lit = -other (which are two real + // clauses). + // + // 2a. if repr_lit = -repr_other, we learn the units repr_lit and + // -repr_lit to derive false + // + // 2b. otherwise, we learn the equivalences repr_lit = -repr_other + // (which are two real clauses) + // + // Without LRAT this is easier, as we directly learn the conclusion + // (either false or the equivalence). The steps can also not be merged + // because repr_lit can appear in the gate and hence in the resolution + // chain. + int smaller_repr = repr_lit; + int larger_repr = repr_other; + int smaller = lit; + int larger = other; + const std::vector *smaller_chain = &extra_reasons_ulit; + const std::vector *larger_chain = &extra_reasons_lit; + + if (abs (smaller_repr) > abs (larger_repr)) { + swap (smaller_repr, larger_repr); + swap (smaller, larger); + swap (smaller_chain, larger_chain); + } + + CADICAL_assert (find_representative (smaller_repr) == smaller_repr); + CADICAL_assert (find_representative (larger_repr) == larger_repr); + if (lit == -other) { + CADICAL_assert (chain.empty ()); + LOG ("merging clashing %d and %d", lit, other); + if (internal->proof) { + if (internal->lrat) { + for (auto id : *smaller_chain) + lrat_chain.push_back (id); + } + unsimplified.push_back (smaller); + LRAT_ID id = simplify_and_add_to_proof_chain (unsimplified); + + if (internal->lrat) { + internal->lrat_chain.push_back (id); + for (auto id : *larger_chain) + internal->lrat_chain.push_back (id); + LOG (internal->lrat_chain, "lrat chain"); + } + } + internal->learn_empty_clause (); + delete_proof_chain (); + return false; + } + + LOG ("merging %d and %d first and then the equivalences of %d and %d " + "with LRAT", + lit, other, repr_lit, repr_other); + Clause *eq1_tmp = nullptr; + if (internal->lrat) { + lrat_chain = *smaller_chain; + eq1_tmp = add_tmp_binary_clause (-larger, smaller); + } + CADICAL_assert (!internal->lrat || eq1_tmp); + + Clause *eq2_tmp = nullptr; + if (internal->lrat) { + lrat_chain = *larger_chain; + LOG (lrat_chain, "lrat chain"); + + eq2_tmp = add_tmp_binary_clause (larger, -smaller); + // the order in the clause is important for the + // repr_lit == -repr_other to get the right chain + } + CADICAL_assert (!internal->lrat || eq2_tmp); + if (internal->lrat) + lrat_chain.clear (); + + if (repr_lit == -repr_other) { + // now derive empty clause + Rewrite rew1, rew2; + if (internal->lrat) { + // no need to calculate push_id_and_rewriting_lrat here because all + // the job is done by the arguments already + rew1 = Rewrite (lit == repr_lit ? 0 : lit, repr_lit, + lit == repr_lit ? 0 : representative_id (lit), + lit == repr_lit ? 0 : representative_id (-lit)); + rew2 = Rewrite (other == repr_other ? 0 : other, repr_other, + other == repr_other ? 0 : representative_id (other), + other == repr_other ? 0 : representative_id (-other)); + push_id_and_rewriting_lrat_unit (eq1_tmp, rew1, lrat_chain, true, + rew2); + swap (lrat_chain, internal->lrat_chain); + } + internal->assign_unit (-larger_repr); + if (internal->lrat) { + internal->lrat_chain.clear (); + + if (larger != larger_repr) + push_lrat_unit (-larger_repr); + push_id_and_rewriting_lrat_unit ( + eq2_tmp, rew1, lrat_chain, true, rew2, + larger != larger_repr ? larger_repr : 0); + LOG (lrat_chain, "lrat chain"); + swap (lrat_chain, internal->lrat_chain); + } + internal->learn_empty_clause (); + if (internal->lrat) + internal->lrat_chain.clear (); + return false; + } + + if (val_lit) { + if (val_lit == val_other) { + LOG ("not merging lits %d and %d assigned to same value", lit, other); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + if (val_lit == -val_other) { + LOG ("merging lits %d and %d assigned to inconsistent value", lit, + other); + CADICAL_assert (lrat_chain.empty ()); + if (internal->lrat) { + Clause *c = val_lit ? eq2_tmp : eq1_tmp; + int pos = val_lit ? other : lit; + int neg = val_lit ? -lit : -other; + push_lrat_unit (pos); + push_lrat_unit (neg); + push_id_on_chain (lrat_chain, c); + } + internal->learn_empty_clause (); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + CADICAL_assert (!val_other); + LOG ("merging assigned %d and unassigned %d", lit, other); + CADICAL_assert (lrat_chain.empty ()); + const int unit = (val_lit < 0) ? -other : other; + if (internal->lrat) { + Clause *c; + if (lit == smaller) { + if (val_lit < 0) + c = eq1_tmp; + else + c = eq2_tmp; + } else { + if (val_lit < 0) + c = eq2_tmp; + else + c = eq1_tmp; + } + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), unit); + } + learn_congruence_unit (unit); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + if (!val_lit && val_other) { + LOG ("merging assigned %d and unassigned %d", lit, other); + CADICAL_assert (lrat_chain.empty ()); + const int unit = (val_other < 0) ? -lit : lit; + if (internal->lrat) { + Clause *c; + if (lit == smaller) { + if (val_lit < 0) + c = eq1_tmp; + else + c = eq2_tmp; + } else { + if (val_lit < 0) + c = eq2_tmp; + else + c = eq1_tmp; + } + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), lit, unit); + } + learn_congruence_unit (unit); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + Clause *eq1_repr, *eq2_repr; + if (smaller_repr != smaller || larger != larger_repr) { + if (internal->lrat) { + lrat_chain.clear (); + Rewrite rew1 = Rewrite ( + smaller_repr != smaller ? smaller : 0, + smaller_repr != smaller ? smaller_repr : 0, + smaller_repr != smaller ? representative_id (smaller) : 0, + smaller_repr != smaller ? representative_id (-smaller) : 0); + Rewrite rew2 = + Rewrite (larger_repr != larger ? larger : 0, + larger_repr != larger ? larger_repr : 0, + larger_repr != larger ? representative_id (larger) : 0, + larger_repr != larger ? representative_id (-larger) : 0); + push_id_and_rewriting_lrat_full (eq1_tmp, rew1, lrat_chain, true, + rew2); + } + eq1_repr = learn_binary_tmp_or_full_clause (-larger_repr, smaller_repr); + } else { + if (internal->lrat) + eq1_repr = maybe_promote_tmp_binary_clause (eq1_tmp); + else + eq1_repr = maybe_add_binary_clause (-larger_repr, smaller_repr); + } + + if (internal->lrat) { + lrat_chain.clear (); + } + + if (smaller_repr != smaller || larger != larger_repr) { + + if (internal->lrat) { + lrat_chain.clear (); + // eq2 = larger, -smaller + Rewrite rew1 = Rewrite ( + smaller_repr != smaller ? smaller : 0, + smaller_repr != smaller ? smaller_repr : 0, + smaller_repr != smaller ? representative_id (smaller) : 0, + smaller_repr != smaller ? representative_id (-smaller) : 0); + Rewrite rew2 = + Rewrite (larger_repr != larger ? larger : 0, + larger_repr != larger ? larger_repr : 0, + larger_repr != larger ? representative_id (larger) : 0, + larger_repr != larger ? representative_id (-larger) : 0); + push_id_and_rewriting_lrat_full (eq2_tmp, rew1, lrat_chain, true, + rew2); + } + + eq2_repr = learn_binary_tmp_or_full_clause (larger_repr, -smaller_repr); + + } else { + if (internal->lrat) + eq2_repr = maybe_promote_tmp_binary_clause (eq2_tmp); + else + eq2_repr = maybe_add_binary_clause (larger_repr, -smaller_repr); + } + lrat_chain.clear (); + + if (internal->lrat) { + representative_id (larger_repr) = eq1_repr->id; + CADICAL_assert (std::find (begin (*eq1_repr), end (*eq1_repr), -larger_repr) != + end (*eq1_repr)); + representative_id (-larger_repr) = eq2_repr->id; + CADICAL_assert (std::find (begin (*eq2_repr), end (*eq2_repr), larger_repr) != + end (*eq2_repr)); + } + LOG ("updating %d -> %d", larger_repr, smaller_repr); + representative (larger_repr) = smaller_repr; + representative (-larger_repr) = -smaller_repr; + schedule_literal (larger_repr); + ++internal->stats.congruence.congruent; + CADICAL_assert (lrat_chain.empty ()); + return true; +} + +// Variant when there are no gates +// TODO: this is exactly the same as the other function without the gates. +// Kill the arguments! +bool Closure::merge_literals_lrat ( + int lit, int other, const std::vector &extra_reasons_lit, + const std::vector &extra_reasons_ulit) { + CADICAL_assert (!internal->unsat); + LOG ("merging literals %s and %s", LOGLIT (lit), LOGLIT (other)); + // TODO: this should not update_eager but still calculate the LRAT chain + // below! + const int repr_lit = find_representative_and_compress (lit, false); + const int repr_other = find_representative_and_compress (other, false); + find_representative_and_compress (-lit, false); + find_representative_and_compress (-other, false); + LOG ("merging literals %s [=%d] and %s [=%d]", LOGLIT (lit), repr_lit, + LOGLIT (other), repr_other); + LOG (lrat_chain, "lrat chain beginning of merge"); + + if (repr_lit == repr_other) { + LOG ("already merged %s and %s", LOGLIT (lit), LOGLIT (other)); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + const int val_lit = internal->val (lit); + const int val_other = internal->val (other); + + // For LRAT we need to distinguish more cases for a more regular + // reconstruction. + // + // 1. if lit = -other, then we learn lit and -lit to derive false + // + // 2. otherwise, we learn the new clauses lit = -other (which are two real + // clauses). + // + // 2a. if repr_lit = -repr_other, we learn the units repr_lit and + // -repr_lit to derive false + // + // 2b. otherwise, we learn the equivalences repr_lit = -repr_other + // (which are two real clauses) + // + // Without LRAT this is easier, as we directly learn the conclusion + // (either false or the equivalence). The steps can also not be merged + // because repr_lit can appear in the gate and hence in the resolution + // chain. + int smaller_repr = repr_lit; + int larger_repr = repr_other; + int val_smaller = val_lit; + int val_larger = val_other; + int smaller = lit; + int larger = other; + const std::vector *smaller_chain = &extra_reasons_ulit; + const std::vector *larger_chain = &extra_reasons_lit; + + if (abs (smaller_repr) > abs (larger_repr)) { + swap (smaller_repr, larger_repr); + swap (smaller, larger); + swap (smaller_chain, larger_chain); + swap (val_smaller, val_larger); + } + + CADICAL_assert (find_representative (smaller_repr) == smaller_repr); + CADICAL_assert (find_representative (larger_repr) == larger_repr); + if (lit == -other) { + LOG ("merging clashing %d and %d", lit, other); + if (!val_smaller) { + if (internal->lrat) + internal->lrat_chain = *smaller_chain; + internal->assign_unit (smaller); + if (internal->lrat) + internal->lrat_chain.clear (); + + push_lrat_unit (smaller); + if (internal->lrat) { + CADICAL_assert (internal->lrat_chain.empty ()); + swap (internal->lrat_chain, lrat_chain); + for (auto id : *larger_chain) + internal->lrat_chain.push_back (id); + LOG (internal->lrat_chain, "lrat chain"); + } + internal->learn_empty_clause (); + return false; + } else { + if (internal->lrat) + internal->lrat_chain = + (val_smaller < 0 ? *smaller_chain : *larger_chain); + if (internal->lrat) + internal->lrat_chain.push_back ( + internal->unit_id (val_smaller > 0 ? smaller : -smaller)); + internal->learn_empty_clause (); + return false; + } + } + + if (val_lit && val_lit == val_other) { + LOG ("not merging lits %d and %d assigned to same value", lit, other); + return false; + } + + if (val_lit && val_lit == -val_other) { + if (internal->lrat) { + internal->lrat_chain.push_back ( + internal->unit_id (val_smaller < 0 ? -smaller : smaller)); + internal->lrat_chain.push_back ( + internal->unit_id (val_larger < 0 ? -larger : larger)); + for (auto id : (val_smaller < 0 ? *smaller_chain : *larger_chain)) { + internal->lrat_chain.push_back(id); + } + } + internal->learn_empty_clause (); + return false; + } + + LOG ("merging %d and %d first and then the equivalences of %d and %d " + "with LRAT", + lit, other, repr_lit, repr_other); + Clause *eq1_tmp = nullptr; + if (internal->lrat) { + lrat_chain = *smaller_chain; + eq1_tmp = add_tmp_binary_clause (-larger, smaller); + CADICAL_assert (eq1_tmp); + } + CADICAL_assert (!internal->lrat || eq1_tmp); + + Clause *eq2_tmp = nullptr; + if (internal->lrat) { + lrat_chain = *larger_chain; + LOG (lrat_chain, "lrat chain"); + // the order in the clause is important for the + // repr_lit == -repr_other to get the right chain + eq2_tmp = add_tmp_binary_clause (larger, -smaller); + CADICAL_assert (eq2_tmp); + } + + CADICAL_assert (!internal->lrat || eq2_tmp); + if (internal->lrat) + lrat_chain.clear (); + + if (repr_lit == -repr_other) { + // now derive empty clause + Rewrite rew1, rew2; + if (internal->lrat) { + // no need to calculate push_id_and_rewriting_lrat here because all + // the job is done by the arguments already + rew1 = Rewrite (lit == repr_lit ? 0 : lit, repr_lit, + lit == repr_lit ? 0 : representative_id (lit), + lit == repr_lit ? 0 : representative_id (-lit)); + rew2 = Rewrite (other == repr_other ? 0 : other, repr_other, + other == repr_other ? 0 : representative_id (other), + other == repr_other ? 0 : representative_id (-other)); + push_id_and_rewriting_lrat_unit (eq1_tmp, rew1, lrat_chain, true, + rew2); + swap (lrat_chain, internal->lrat_chain); + } + CADICAL_assert (val_larger == internal->val (larger_repr)); + if (!val_larger) { + // not assigned, first assign one + internal->assign_unit (-larger_repr); + if (internal->lrat) { + internal->lrat_chain.clear (); + + if (larger != larger_repr) + push_lrat_unit (-larger_repr); + // no need to calculate push_id_and_rewriting_lrat here because all + // the job is done by the arguments already + push_id_and_rewriting_lrat_unit ( + eq2_tmp, rew1, lrat_chain, true, rew2, + larger != larger_repr ? larger_repr : 0); + LOG (lrat_chain, "lrat chain"); + swap (lrat_chain, internal->lrat_chain); + } + } else { + // otherwise, no need to + if (internal->lrat) + lrat_chain.push_back (internal->unit_id (larger_repr)); + } + internal->learn_empty_clause (); + if (internal->lrat) + internal->lrat_chain.clear (); + return false; + } + + if (val_lit) { + if (val_lit == val_other) { + LOG ("not merging lits %d and %d assigned to same value", lit, other); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + if (val_lit == -val_other) { + LOG ("merging lits %d and %d assigned to inconsistent value", lit, + other); + CADICAL_assert (lrat_chain.empty ()); + if (internal->lrat) { + Clause *c = val_lit ? eq2_tmp : eq1_tmp; + int pos = val_lit ? other : lit; + int neg = val_lit ? -lit : -other; + push_lrat_unit (pos); + push_lrat_unit (neg); + push_id_on_chain (lrat_chain, c); + } + internal->learn_empty_clause (); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + CADICAL_assert (!val_other); + LOG ("merging assigned %d and unassigned %d", lit, other); + CADICAL_assert (lrat_chain.empty ()); + const int unit = (val_lit < 0) ? -other : other; + if (internal->lrat) { + Clause *c; + if (lit == smaller) { + if (val_lit < 0) + c = eq1_tmp; + else + c = eq2_tmp; + } else { + if (val_lit < 0) + c = eq2_tmp; + else + c = eq1_tmp; + } + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), unit); + } + learn_congruence_unit (unit); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + if (!val_lit && val_other) { + LOG ("merging assigned %d and unassigned %d", lit, other); + CADICAL_assert (lrat_chain.empty ()); + const int unit = (val_other < 0) ? -lit : lit; + if (internal->lrat) { + Clause *c; + if (lit == smaller) { + CADICAL_assert (other == larger); + if (val_other > 0) + c = eq1_tmp; + else + c = eq2_tmp; + } else { + if (val_other > 0) + c = eq2_tmp; + else + c = eq1_tmp; + } + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), lit, unit); + } + learn_congruence_unit (unit); + if (internal->lrat) + lrat_chain.clear (); + return false; + } + + Clause *eq1_repr, *eq2_repr; + if (smaller_repr != smaller || larger != larger_repr) { + if (internal->lrat) { + lrat_chain.clear (); + Rewrite rew1 = Rewrite ( + smaller_repr != smaller ? smaller : 0, + smaller_repr != smaller ? smaller_repr : 0, + smaller_repr != smaller ? representative_id (smaller) : 0, + smaller_repr != smaller ? representative_id (-smaller) : 0); + Rewrite rew2 = + Rewrite (larger_repr != larger ? larger : 0, + larger_repr != larger ? larger_repr : 0, + larger_repr != larger ? representative_id (larger) : 0, + larger_repr != larger ? representative_id (-larger) : 0); + push_id_and_rewriting_lrat_full (eq1_tmp, rew1, lrat_chain, true, + rew2); + } + eq1_repr = learn_binary_tmp_or_full_clause (-larger_repr, smaller_repr); + } else { + if (internal->lrat) + eq1_repr = maybe_promote_tmp_binary_clause (eq1_tmp); + else + eq1_repr = maybe_add_binary_clause (-larger_repr, smaller_repr); + } + + if (internal->lrat) { + lrat_chain.clear (); + } + + if (smaller_repr != smaller || larger != larger_repr) { + + if (internal->lrat) { + lrat_chain.clear (); + // eq2 = larger, -smaller + Rewrite rew1 = Rewrite ( + smaller_repr != smaller ? smaller : 0, + smaller_repr != smaller ? smaller_repr : 0, + smaller_repr != smaller ? representative_id (smaller) : 0, + smaller_repr != smaller ? representative_id (-smaller) : 0); + Rewrite rew2 = + Rewrite (larger_repr != larger ? larger : 0, + larger_repr != larger ? larger_repr : 0, + larger_repr != larger ? representative_id (larger) : 0, + larger_repr != larger ? representative_id (-larger) : 0); + push_id_and_rewriting_lrat_full (eq2_tmp, rew1, lrat_chain, true, + rew2); + } + eq2_repr = learn_binary_tmp_or_full_clause (larger_repr, -smaller_repr); + } else { + if (internal->lrat) + eq2_repr = maybe_promote_tmp_binary_clause (eq2_tmp); + else + eq2_repr = maybe_add_binary_clause (larger_repr, -smaller_repr); + } + lrat_chain.clear (); + + if (internal->lrat) { + representative_id (larger_repr) = eq1_repr->id; + CADICAL_assert (std::find (begin (*eq1_repr), end (*eq1_repr), -larger_repr) != + end (*eq1_repr)); + representative_id (-larger_repr) = eq2_repr->id; + check_not_tmp_binary_clause (eq1_repr); + check_not_tmp_binary_clause (eq2_repr); + CADICAL_assert (std::find (begin (*eq2_repr), end (*eq2_repr), larger_repr) != + end (*eq2_repr)); + } + LOG ("updating %d -> %d", larger_repr, smaller_repr); + representative (larger_repr) = smaller_repr; + representative (-larger_repr) = -smaller_repr; + schedule_literal (larger_repr); + ++internal->stats.congruence.congruent; + CADICAL_assert (lrat_chain.empty ()); + return true; +} + +inline void Closure::promote_clause (Clause *c) { + if (internal->lrat) + check_not_tmp_binary_clause (c); + if (!c) + return; + if (!c->redundant) + return; + LOG (c, "turning redundant subsuming clause into irredundant clause"); + c->redundant = false; + if (internal->proof) + internal->proof->strengthen (c->id); + internal->stats.current.irredundant++; + internal->stats.added.irredundant++; + internal->stats.irrlits += c->size; + CADICAL_assert (internal->stats.current.redundant > 0); + internal->stats.current.redundant--; + CADICAL_assert (internal->stats.added.redundant > 0); + internal->stats.added.redundant--; + // ... and keep 'stats.added.total'. +} + +// This function is rather tricky for LRAT. If you have 2 = 1 and 3=4 you +// cannot add 2=3. You really to connect the representatives directly +// therefore you actually need to learn the clauses 2->3->4 and -2->1 and +// vice-versa +bool Closure::merge_literals_equivalence (int lit, int other, Clause *c1, + Clause *c2) { + CADICAL_assert (!internal->unsat); + LRAT_ID id1 = c1 ? c1->id : 0; + LRAT_ID id2 = c2 ? c2->id : 0; + if (internal->lrat) { + CADICAL_assert (c1); + CADICAL_assert (c2); + CADICAL_assert (c1->size == 2); + CADICAL_assert (c2->size == 2); + CADICAL_assert (c1->literals[0] == lit || c1->literals[1] == lit); + CADICAL_assert (c2->literals[0] == other || c2->literals[1] == other); + CADICAL_assert (c1->literals[0] == -other || c1->literals[1] == -other); + CADICAL_assert (c2->literals[0] == -lit || c2->literals[1] == -lit); + check_not_tmp_binary_clause (c1); + check_not_tmp_binary_clause (c2); + } + int repr_lit = find_representative (lit); + int repr_other = find_representative (other); + find_representative_and_compress_both (lit); + find_representative_and_compress_both (other); + LOG ("merging literals %d [=%d] and %d [=%d] lrat", lit, repr_lit, other, + repr_other); + + if (repr_lit == repr_other) { + LOG ("already merged %d and %d", lit, other); + return false; + } + const int val_lit = internal->val (lit); + const int val_other = internal->val (other); + + if (val_lit) { + if (val_lit == val_other) { + LOG ("not merging lits %d and %d assigned to same value", lit, other); + return false; + } + if (val_lit == -val_other) { + if (internal->lrat) + lrat_chain.push_back (internal->unit_id (lit)), + lrat_chain.push_back (internal->unit_id (other)); + LOG ("merging lits %d and %d assigned to inconsistent value", lit, + other); + internal->learn_empty_clause (); + return false; + } + + CADICAL_assert (!val_other); + LOG ("merging assigned %d and unassigned %d", lit, other); + const int unit = (val_lit < 0) ? -other : other; + if (internal->lrat) + lrat_chain.push_back (internal->unit_id (unit)); + if (val_lit < 0) + lrat_chain.push_back (c2->id); + else + lrat_chain.push_back (c1->id); + learn_congruence_unit (unit); + return false; + } + + if (!val_lit && val_other) { + LOG ("merging assigned %d and unassigned %d", other, lit); + const int unit = (val_other < 0) ? -lit : lit; + if (internal->lrat) + lrat_chain.push_back ( + internal->unit_id (val_other < 0 ? -other : other)); + if (val_lit < 0) + lrat_chain.push_back (c1->id); + else + lrat_chain.push_back (c2->id); + learn_congruence_unit (unit); + return false; + } + + int smaller_repr = repr_lit; + int larger_repr = repr_other; + int smaller = lit; + int larger = other; + + if (abs (smaller_repr) > abs (larger_repr)) { + swap (smaller_repr, larger_repr); + swap (smaller, larger); + } + + CADICAL_assert (find_representative (smaller_repr) == smaller_repr); + CADICAL_assert (find_representative (larger_repr) == larger_repr); + + if (repr_lit == -repr_other) { + LOG ("merging clashing %d [=%d] and %d[=%d], smaller: %d", lit, + repr_lit, other, repr_other, smaller); + if (internal->lrat) { + Rewrite rew1 = + Rewrite (lit, lit == repr_lit ? 0 : repr_lit, + lit == repr_lit ? 0 : find_representative_lrat (lit), + lit == repr_lit ? 0 : find_representative_lrat (-lit)); + Rewrite rew2 = Rewrite ( + other, other == repr_other ? 0 : repr_other, + other == repr_other ? 0 : find_representative_lrat (other), + other == repr_other ? 0 : find_representative_lrat (-other)); + push_id_and_rewriting_lrat_unit (c1, rew1, lrat_chain, true, rew2); + LOG (lrat_chain, "lrat chain"); + swap (internal->lrat_chain, lrat_chain); + } + internal->assign_unit (repr_lit); + if (internal->lrat) { + lrat_chain.clear (); + LRAT_ID id = internal->unit_id (repr_lit); + CADICAL_assert (id); + lrat_chain.push_back (id); + if (lit != repr_lit) { + const LRAT_ID repr_id2 = find_representative_lrat (-lit); + lrat_chain.push_back (repr_id2); + } + lrat_chain.push_back (id2); + if (other != repr_other) { + const LRAT_ID repr_larger_id2 = find_representative_lrat (other); + lrat_chain.push_back (repr_larger_id2); + } + LOG (lrat_chain, "lrat chain"); + swap (internal->lrat_chain, lrat_chain); + } + internal->learn_empty_clause (); + return false; + } + + LOG ("merging %d [=%d] and %d [=%d]", lit, repr_lit, other, repr_other); + promote_clause (c1), promote_clause (c2); + bool learn_clause = (lit != repr_lit) || (other != repr_other); + if (learn_clause) { + if (internal->lrat) { + if (lit != repr_lit) { + LOG ("adding chain for lit %d -> %d", lit, repr_lit); + lrat_chain.push_back (find_representative_lrat (lit)); + } + if (other != repr_other) { + LOG ("adding chain for lit %d -> %d", -other, -repr_other); + lrat_chain.push_back (find_representative_lrat (-other)); + } + lrat_chain.push_back (id1); + } + Clause *eq1 = learn_binary_tmp_or_full_clause (repr_lit, -repr_other); + + if (internal->lrat) { + lrat_chain.clear (); + if (lit != repr_lit) + lrat_chain.push_back (find_representative_lrat (-lit)); + if (other != repr_other) + lrat_chain.push_back (find_representative_lrat (other)); + lrat_chain.push_back (id2); + } + Clause *eq2 = learn_binary_tmp_or_full_clause (-repr_lit, repr_other); + if (internal->lrat) { + lrat_chain.clear (); + if (smaller_repr == repr_lit) { + CADICAL_assert (larger_repr == repr_other); + representative_id (-larger_repr) = eq2->id; + CADICAL_assert (std::find (eq2->begin (), eq2->end (), larger_repr) != + eq2->end ()); + representative_id (larger_repr) = eq1->id; + CADICAL_assert (std::find (eq1->begin (), eq1->end (), -larger_repr) != + eq1->end ()); + } else { + CADICAL_assert (larger_repr == repr_lit); + representative_id (-larger_repr) = eq1->id; + CADICAL_assert (std::find (eq1->begin (), eq1->end (), larger_repr) != + eq1->end ()); + representative_id (larger_repr) = eq2->id; + CADICAL_assert (std::find (eq2->begin (), eq2->end (), -larger_repr) != + eq2->end ()); + } + } + + } else if (internal->lrat) { + LOG ("not learning new clause, using already existing one"); + if (smaller_repr == repr_lit) { + LOG ("setting ids of %d: %" PRIu64 "; %d: %" PRIu64 " (case 1)", + larger, id1, -larger, id2); + representative_id (-larger_repr) = id2; + representative_id (larger_repr) = id1; + } else { + LOG ("setting ids of %d: %" PRIu64 "; %d: %" PRIu64 " (case 2)", + larger, id2, -larger, id1); + representative_id (-larger_repr) = id1; + representative_id (larger_repr) = id2; + } + } + + LOG ("updating %d -> %d", larger_repr, smaller_repr); + representative (larger_repr) = smaller_repr; + representative (-larger_repr) = -smaller_repr; + schedule_literal (larger_repr); + ++internal->stats.congruence.congruent; + return true; +} + +/*------------------------------------------------------------------------*/ +GOccs &Closure::goccs (int lit) { return gtab[internal->vlit (lit)]; } + +void Closure::connect_goccs (Gate *g, int lit) { + LOG (g, "connect %d to", lit); + // incorrect for ITE + // CADICAL_assert (std::find(begin (goccs (lit)), end (goccs (lit)), g) == + // std::end (goccs (lit))); + goccs (lit).push_back (g); +} + +uint64_t &Closure::largecount (int lit) { + CADICAL_assert (internal->vlit (lit) < glargecounts.size ()); + return glargecounts[internal->vlit (lit)]; +} + +/*------------------------------------------------------------------------*/ +// Initialization + +void Closure::init_closure () { + representant.resize (2 * internal->max_var + 3); + eager_representant.resize (2 * internal->max_var + 3); + if (internal->lrat) { + eager_representant_id.resize (2 * internal->max_var + 3); + representant_id.resize (2 * internal->max_var + 3); + lazy_propagated_idx.resize (internal->max_var + 1, false); + for (auto lit : internal->trail) + lazy_propagated (lit) = true; + } + marks.resize (2 * internal->max_var + 3); + mu1_ids.resize (2 * internal->max_var + 3); + if (internal->lrat) { + mu2_ids.resize (2 * internal->max_var + 3); + mu4_ids.resize (2 * internal->max_var + 3); + } +#ifndef CADICAL_NDEBUG + for (auto &it : mu1_ids) + it.current_lit = 0, it.clause = nullptr; + for (auto &it : mu2_ids) + it.current_lit = 0, it.clause = nullptr; + for (auto &it : mu4_ids) + it.current_lit = 0, it.clause = nullptr; +#endif + scheduled.resize (internal->max_var + 1); + gtab.resize (2 * internal->max_var + 3); + for (auto v : internal->vars) { + representative (v) = v; + representative (-v) = -v; + eager_representative (v) = v; + eager_representative (-v) = -v; + } + units = internal->propagated; + Random rand (internal->stats.congruence.rounds); + for (auto &n : nonces) { + n = 1 | rand.next (); + } +#ifdef LOGGING + fresh_id = internal->clause_id; +#endif + internal->init_noccs (); + internal->init_occs (); +} + +void Closure::init_and_gate_extraction () { + LOG ("[gate-extraction]"); + for (Clause *c : internal->clauses) { + if (c->garbage) + continue; + if (c->redundant && c->size != 2) + continue; + if (c->size > 2) + continue; + CADICAL_assert (c->size == 2); + const int lit = c->literals[0]; + const int other = c->literals[1]; + internal->noccs (lit)++; + internal->noccs (other)++; + internal->watch_clause (c); + } +} + +/*------------------------------------------------------------------------*/ +void Closure::check_and_gate_implied (Gate *g) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (g->tag == Gate_Type::And_Gate); + if (internal->lrat) + return; + LOG (g, "checking implied"); + const int lhs = g->lhs; + const int not_lhs = -lhs; + for (auto other : g->rhs) + check_binary_implied (not_lhs, other); + internal->clause = g->rhs; + check_implied (); + internal->clause.clear (); +} + +void Closure::delete_proof_chain () { + if (!internal->proof) { + CADICAL_assert (chain.empty ()); + return; + } + if (chain.empty ()) + return; + LOG ("starting deletion of proof chain"); + auto &clause = internal->clause; + CADICAL_assert (clause.empty ()); + uint32_t id1 = UINT32_MAX, id2 = UINT32_MAX; + LRAT_ID id = 0; + + LOG (chain, "chain:"); + for (auto lit : chain) { + LOG ("reading %d from chain", lit); + if (id1 == UINT32_MAX) { + id1 = lit; + id = (LRAT_ID) id1; + continue; + } + if (id2 == UINT32_MAX) { + id2 = lit; + id = ((LRAT_ID) id1 << 32) + id2; + continue; + } + if (lit) { // parsing the id first + LOG ("found %d as literal in chain", lit); + clause.push_back (lit); + } else { + CADICAL_assert (id); + internal->proof->delete_clause (id, false, clause); + clause.clear (); + id = 0, id1 = UINT32_MAX, id2 = UINT32_MAX; + } + } + CADICAL_assert (clause.empty ()); + chain.clear (); + LOG ("finished deletion of proof chain"); +} + +/*------------------------------------------------------------------------*/ +// Simplification +bool Closure::skip_and_gate (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::And_Gate); + if (g->garbage) + return true; + const int lhs = g->lhs; + if (internal->val (lhs) > 0) { + mark_garbage (g); + return true; + } + + CADICAL_assert (g->arity () > 1); + return false; +} + +bool Closure::skip_xor_gate (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::XOr_Gate); + if (g->garbage) + return true; + CADICAL_assert (g->arity () > 1); + return false; +} + +void Closure::shrink_and_gate (Gate *g, int falsifies, int clashing) { + if (falsifies) { + g->rhs.resize (1); + g->rhs[0] = falsifies; + g->hash = hash_lits (nonces, g->rhs); + } else if (clashing) { + LOG (g, "gate before clashing on %d", clashing); + g->rhs.resize (2); + g->rhs[0] = clashing; + g->rhs[1] = -clashing; + g->hash = hash_lits (nonces, g->rhs); + LOG (g, "gate after clashing on %d", clashing); + } + g->shrunken = true; +} + +void Closure::update_and_gate_unit_build_lrat_chain ( + Gate *g, int src, LRAT_ID id1, LRAT_ID id2, int dst, + std::vector &extra_reasons_lit, + std::vector &extra_reasons_ulit) { + LOG ("generate chain for gate boiling down to unit"); + if (g->neg_lhs_ids.size () != 1) { + + if (g->degenerated_and_neg || g->degenerated_and_pos) { + // can happen for 4 = AND 3 4 (degenerated with only the clause -4 3) + // with a rewriting 4 -> 1 (unchanged clause) + // and later 1 -> 3 (unchanged clause) + // but you do not know anymore from the gate that it is degenerated + CADICAL_assert (g->pos_lhs_ids.size () == 1); + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[0].clause, Rewrite (), + extra_reasons_ulit, true, Rewrite ()); + return; + } + CADICAL_assert (g->lhs == g->rhs[0] || (g->lhs == src && g->rhs[0] == dst)); + CADICAL_assert (g->pos_lhs_ids.size () <= 1); // either degenerated or empty A = A + return; + } + CADICAL_assert (g->neg_lhs_ids.size () == 1); + CADICAL_assert (!g->pos_lhs_ids.empty ()); + + const int repr_lit = find_representative (g->lhs); + const int repr_other = find_representative (g->rhs[0]); + if (repr_lit == repr_other) { + LOG ("skipping already merged"); + return; + } + + push_id_and_rewriting_lrat_unit (g->neg_lhs_ids[0].clause, Rewrite (), + extra_reasons_ulit, true, Rewrite (), + g->lhs, -dst); + LOG (extra_reasons_ulit, "lrat chain for negative side"); + + lrat_chain.clear (); + + CADICAL_assert (!g->pos_lhs_ids.empty ()); + for (auto litId : g->pos_lhs_ids) + push_id_and_rewriting_lrat_unit ( + litId.clause, Rewrite (src, dst, id1, id2), extra_reasons_lit, true, + Rewrite (), -g->lhs, dst); + LOG (extra_reasons_lit, "lrat chain for positive side"); +} + +void Closure::update_and_gate_build_lrat_chain ( + Gate *g, Gate *h, std::vector &extra_reasons_lit, + std::vector &extra_reasons_ulit, bool remove_units) { + CADICAL_assert (g != h); + LOG (g, "merging"); + LOG (h, "with"); + // If the LHS are identical, do not even attempt to build the LRAT chain + if (find_representative (g->lhs) == find_representative (h->lhs)) + return; + // set to same value, don't do anything + if (internal->val (g->lhs) && internal->val (g->lhs) == internal->val (h->lhs)) + return; + const bool g_tautology = gate_contains (g, g->lhs); + const bool h_tautology = gate_contains (h, h->lhs); + if (g_tautology && h_tautology) { + LOG ("both gates are a tautology"); + // special case: actually we have an equivalence due to binary clauses + // and all gate clauses (except one binary) are actually tautologies + for (auto &litId : g->pos_lhs_ids) { + if (litId.current_lit == h->lhs) { + CADICAL_assert (extra_reasons_lit.empty ()); + LOG (litId.clause, "binary clause to push into the reason"); + litId.clause = produce_rewritten_clause_lrat (litId.clause, g->lhs, + remove_units); + CADICAL_assert (litId.clause); + extra_reasons_lit.push_back (litId.clause->id); + } + } + CADICAL_assert (!extra_reasons_lit.empty ()); + CADICAL_assert (extra_reasons_lit.size () == 1); + + for (auto &litId : h->pos_lhs_ids) { + if (litId.current_lit == g->lhs) { + CADICAL_assert (extra_reasons_ulit.empty ()); + CADICAL_assert (litId.clause); + LOG (litId.clause, "binary clause to push into the reason"); + litId.clause = produce_rewritten_clause_lrat (litId.clause, h->lhs, + remove_units); + CADICAL_assert (litId.clause); + extra_reasons_ulit.push_back (litId.clause->id); + } + } + CADICAL_assert (!extra_reasons_ulit.empty ()); + CADICAL_assert (extra_reasons_ulit.size () == 1); + return; + } + if (g_tautology || h_tautology) { + // special case: actually we have an equivalence due to binary clauses + // and some of the clauses from the gate are actually tautologies + CADICAL_assert (g_tautology != h_tautology); + Gate *tauto = (g_tautology ? g : h); + Gate *other = (g_tautology ? h : g); + LOG (tauto, "one gate is a tautology"); + CADICAL_assert (tauto != other); + CADICAL_assert (tauto == h || tauto == g); + + auto &extra_reasons_tauto = + (!g_tautology ? extra_reasons_lit : extra_reasons_ulit); + auto &extra_reasons_other = + (!g_tautology ? extra_reasons_ulit : extra_reasons_lit); + + // one direction: the binary clause already exists + for (auto &litId : other->pos_lhs_ids) { + if (litId.current_lit == tauto->lhs) { + CADICAL_assert (litId.clause); + CADICAL_assert (extra_reasons_tauto.empty ()); + LOG (litId.clause, "binary clause to push into the reason"); + litId.clause = produce_rewritten_clause_lrat ( + litId.clause, other->lhs, remove_units); + CADICAL_assert (litId.clause); + extra_reasons_tauto.push_back (litId.clause->id); + } + } + CADICAL_assert (!extra_reasons_tauto.empty ()); + + // other direction, we have to resolve + LOG (tauto, "now the other direction"); + for (auto &litId : tauto->pos_lhs_ids) { + CADICAL_assert (litId.clause); + LOG (litId.clause, + "binary clause from %d to push into the reason [avoiding %d]", + litId.current_lit, tauto->lhs); + if (litId.current_lit != tauto->lhs) { + LOG (litId.clause, "binary clause to push into the reason"); + CADICAL_assert (litId.clause); + litId.clause = produce_rewritten_clause_lrat ( + litId.clause, tauto->lhs, remove_units); + if (!litId.clause) // degenerated but does not know yet + continue; + CADICAL_assert (litId.clause); + extra_reasons_other.push_back (litId.clause->id); + } + tauto->pos_lhs_ids.erase (std::remove_if (begin (tauto->pos_lhs_ids), + end (tauto->pos_lhs_ids), + [] (const LitClausePair &p) { + return !p.clause; + }), + end (tauto->pos_lhs_ids)); + } + CADICAL_assert (!extra_reasons_other.empty ()); + produce_rewritten_clause_lrat_and_clean (other->neg_lhs_ids, other->lhs, + remove_units); + push_id_on_chain (extra_reasons_other, other->neg_lhs_ids); + CADICAL_assert (!extra_reasons_tauto.empty ()); + CADICAL_assert (!extra_reasons_other.empty ()); + return; + } + // default: resolve all clauses + // first rewrite + // TODO: do we really need dest as second exclusion? + produce_rewritten_clause_lrat_and_clean (h->pos_lhs_ids, -h->lhs, + remove_units); + CADICAL_assert (internal->clause.empty ()); + produce_rewritten_clause_lrat_and_clean (h->neg_lhs_ids, -h->lhs, + remove_units); + CADICAL_assert (internal->clause.empty ()); + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, -g->lhs, + remove_units); + CADICAL_assert (internal->clause.empty ()); + produce_rewritten_clause_lrat_and_clean (g->neg_lhs_ids, -g->lhs, + remove_units); + CADICAL_assert (internal->clause.empty ()); + + push_id_on_chain (extra_reasons_ulit, h->pos_lhs_ids); + push_id_on_chain (extra_reasons_ulit, g->neg_lhs_ids[0].clause); + lrat_chain.clear (); + LOG (extra_reasons_ulit, "lrat chain for negative side"); + + push_id_on_chain (extra_reasons_lit, g->pos_lhs_ids); + push_id_on_chain (extra_reasons_lit, h->neg_lhs_ids); + + lrat_chain.clear (); + LOG (extra_reasons_lit, "lrat chain for positive side"); + CADICAL_assert (!extra_reasons_lit.empty ()); + CADICAL_assert (!extra_reasons_ulit.empty ()); +} + +void Closure::update_and_gate (Gate *g, GatesTable::iterator it, int src, + int dst, LRAT_ID id1, LRAT_ID id2, + int falsifies, int clashing) { + LOG (g, "update and gate of arity %ld", g->arity ()); + CADICAL_assert (lrat_chain.empty ()); + bool garbage = true; + if (falsifies || clashing) { + if (internal->lrat) + learn_congruence_unit_falsifies_lrat_chain (g, src, dst, clashing, + falsifies, 0); + int unit = -g->lhs; + if (unit == src) + unit = dst; + else if (unit == -src) + unit = -dst; + learn_congruence_unit (unit); + if (internal->lrat) + lrat_chain.clear (); + } else if (g->arity () == 1) { + const signed char v = internal->val (g->lhs); + if (v > 0) { + if (internal->lrat) + learn_congruence_unit_falsifies_lrat_chain (g, src, dst, 0, 0, + g->lhs); + learn_congruence_unit (g->rhs[0]); + if (internal->lrat) + lrat_chain.clear (); + } else if (v < 0) { + if (internal->lrat) + learn_congruence_unit_when_lhs_set (g, src, id1, id2, dst); + learn_congruence_unit (-g->rhs[0]); + if (internal->lrat) + lrat_chain.clear (); + } else { + std::vector extra_reasons_lit; + std::vector extra_reasons_ulit; + if (internal->lrat) + update_and_gate_unit_build_lrat_chain (g, src, id1, id2, g->rhs[0], + extra_reasons_lit, + extra_reasons_ulit); + if (merge_literals_lrat (g, g, g->lhs, g->rhs[0], extra_reasons_lit, + extra_reasons_ulit)) { + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_and; + } + } + } else { + CADICAL_assert (g->arity () > 1); + sort_literals_by_var (g->rhs); + Gate *h = find_and_lits (g->rhs, g); + CADICAL_assert (g != h); + if (h) { + CADICAL_assert (garbage); + std::vector extra_reasons_lit2; + std::vector extra_reasons_ulit2; + if (internal->lrat) + update_and_gate_build_lrat_chain (g, h, extra_reasons_lit2, + extra_reasons_ulit2); + if (merge_literals_lrat (g, h, g->lhs, h->lhs, extra_reasons_lit2, + extra_reasons_ulit2)) + ++internal->stats.congruence.ands; + } else { + if (g->indexed) { + LOG (g, "removing from table"); + (void) table.erase (it); + } + g->hash = hash_lits (nonces, g->rhs); + LOG (g, "inserting gate into table"); + CADICAL_assert (table.count (g) == 0); + table.insert (g); + g->indexed = true; + garbage = false; + if (internal->lrat) + lrat_chain.clear (); + } + } + + if (garbage && !internal->unsat) + mark_garbage (g); +} + +void Closure::update_xor_gate (Gate *g, GatesTable::iterator git) { + CADICAL_assert (g->tag == Gate_Type::XOr_Gate); + CADICAL_assert (!internal->unsat && chain.empty ()); + LOG (g, "updating"); + bool garbage = true; + // TODO Florian LRAT for learn_congruence_unit + CADICAL_assert (g->arity () == 0 || internal->clause.empty ()); + CADICAL_assert (clause.empty ()); + if (g->arity () == 0) { + if (internal->clause.size ()) { + CADICAL_assert (!internal->proof || (internal->clause.size () == 1 && + internal->clause.back () == -g->lhs)); + CADICAL_assert (!internal->lrat || lrat_chain.size ()); + internal->clause.clear (); + + } else if (internal->lrat) { + simplify_unit_xor_lrat_clauses (g->pos_lhs_ids, g->lhs); + CADICAL_assert (clause.size () && clause.back () == -g->lhs); + clause.clear (); + } + + if (internal->lrat && internal->val (-g->lhs) < 0) { + internal->lrat_chain.push_back (internal->unit_id (g->lhs)); + for (auto id : lrat_chain) + internal->lrat_chain.push_back (id); + lrat_chain.clear (); + internal->learn_empty_clause(); + } else + learn_congruence_unit (-g->lhs); + + CADICAL_assert (clause.empty ()); + } else if (g->arity () == 1) { + std::vector reasons_implication, reasons_back; + if (internal->lrat) { + vector first; + simplify_and_sort_xor_lrat_clauses (g->pos_lhs_ids, first, g->lhs); + g->pos_lhs_ids = first; + CADICAL_assert (g->pos_lhs_ids.size () == 2); + reasons_implication.push_back (g->pos_lhs_ids[0].clause->id); + reasons_back.push_back (g->pos_lhs_ids[1].clause->id); + } + const signed char v = internal->val (g->lhs); + if (v > 0) { + if (internal->lrat) { + push_lrat_unit (g->lhs); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + } + learn_congruence_unit (g->rhs[0]); + } else if (v < 0) { + if (internal->lrat) { + push_lrat_unit (-g->lhs); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (-g->rhs[0]); + } else if (merge_literals_lrat ( + g->lhs, g->rhs[0], reasons_implication, + reasons_back)) { // TODO Florian merge with LRAT + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_and; + } + CADICAL_assert (clause.empty ()); + } else { + Gate *h = find_xor_gate (g); + if (h) { + CADICAL_assert (garbage); + std::vector reasons_implication, reasons_back; + add_xor_matching_proof_chain (g, g->lhs, h->pos_lhs_ids, h->lhs, + reasons_implication, reasons_back); + if (merge_literals_lrat (g->lhs, h->lhs, reasons_implication, + reasons_back)) + ++internal->stats.congruence.xors; + delete_proof_chain (); + } else { + if (g->indexed) { + remove_gate (git); + } + g->hash = hash_lits (nonces, g->rhs); + LOG (g, "reinserting in table"); + table.insert (g); + g->indexed = true; + CADICAL_assert (table.find (g) != end (table)); + garbage = false; + } + CADICAL_assert (clause.empty ()); + } + if (garbage && !internal->unsat) + mark_garbage (g); + CADICAL_assert (clause.empty ()); +} + +void Closure::simplify_and_gate (Gate *g) { + if (skip_and_gate (g)) + return; + GatesTable::iterator git = (g->indexed ? table.find (g) : end (table)); + CADICAL_assert (!g->indexed || git != end (table)); + LOG (g, "simplifying"); + int falsifies = 0; + std::vector::iterator it = begin (g->rhs); + bool ulhs_in_rhs = false; + for (auto lit : g->rhs) { + const signed char v = internal->val (lit); + if (v > 0) { + continue; + } + if (v < 0) { + falsifies = lit; + continue; + } + if (lit == -g->lhs) + ulhs_in_rhs = true; + *it++ = lit; + if (lit == g->lhs) + g->degenerated_and_pos = true; + if (lit == -g->lhs) + g->degenerated_and_neg = true; + } + + if (internal->lrat) { // updating reasons + size_t i = 0, size = g->pos_lhs_ids.size (); + for (size_t j = 0; j < size; ++j) { + LOG ("looking at %d [%ld %ld]", g->pos_lhs_ids[j].current_lit, i, j); + g->pos_lhs_ids[i] = g->pos_lhs_ids[j]; + if (!g->degenerated_and_pos && + internal->val (g->pos_lhs_ids[i].current_lit) && + g->pos_lhs_ids[i].current_lit != falsifies) + continue; + LOG ("keeping %d [%ld %ld]", g->pos_lhs_ids[i].current_lit, i, j); + ++i; + } + LOG ("resizing to %ld", i); + g->pos_lhs_ids.resize (i); + } + + CADICAL_assert (it <= end (g->rhs)); // can be equal when ITE are converted to + // ands leading to + CADICAL_assert (it >= begin (g->rhs)); + LOG (g, "shrunken"); + + g->shrunken = true; + g->rhs.resize (it - std::begin (g->rhs)); + g->hash = hash_lits (nonces, g->rhs); + + LOG (g, "shrunken"); + shrink_and_gate (g, falsifies); + std::vector reasons_lrat_src, reasons_lrat_usrc; + + update_and_gate (g, git, 0, 0, 0, 0, falsifies, 0); + ++internal->stats.congruence.simplified_ands; + ++internal->stats.congruence.simplified; + + if (ulhs_in_rhs) { // missing in Kissat, TODO: port back + CADICAL_assert (gate_contains (g, -g->lhs)); + if (internal->lrat) { + for (auto litId : g->pos_lhs_ids) { + if (litId.current_lit == g->lhs) { + compute_rewritten_clause_lrat_simple (litId.clause, 0); + break; + } + } + } + learn_congruence_unit (-g->lhs); + } +} + +bool Closure::simplify_gate (Gate *g) { + switch (g->tag) { + case Gate_Type::And_Gate: + simplify_and_gate (g); + break; + case Gate_Type::XOr_Gate: + simplify_xor_gate (g); + break; + case Gate_Type::ITE_Gate: + simplify_ite_gate (g); + break; + default: + CADICAL_assert (false); + break; + } + CADICAL_assert (lrat_chain.empty ()); + return !internal->unsat; +} + +bool Closure::simplify_gates (int lit) { + const auto &occs = goccs (lit); + for (Gate *g : occs) { + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (clause.empty ()); + if (!simplify_gate (g)) + return false; + } + return true; +} +/*------------------------------------------------------------------------*/ +// AND gates + +Gate *Closure::find_and_lits (const vector &rhs, Gate *except) { + CADICAL_assert (is_sorted (begin (rhs), end (rhs), + sort_literals_by_var_smaller (internal))); + return find_gate_lits (rhs, Gate_Type::And_Gate, except); +} + +// search for the gate in the hash-table. We cannot use find, as we might +// be changing a gate, so there might be 2 gates with the same LHS (the one +// we are changing and the other we are looking for) +Gate *Closure::find_gate_lits (const vector &rhs, Gate_Type typ, + Gate *except) { + Gate *g = new Gate; + g->tag = typ; + g->rhs = rhs; + g->hash = hash_lits (nonces, g->rhs); + g->lhs = 0; + g->garbage = false; +#ifdef LOGGING + g->id = 0; +#endif + const auto &its = table.equal_range (g); + Gate *h = nullptr; + for (auto it = its.first; it != its.second; ++it) { + LOG ((*it), "checking gate in the table"); + if (*it == except) + continue; + CADICAL_assert ((*it)->lhs != g->lhs); + if ((*it)->tag != g->tag) + continue; + if ((*it)->rhs != g->rhs) + continue; + h = *it; + break; + } + + if (h) { + LOG (g, "searching"); + LOG (h, "already existing"); + delete g; + return h; + } + + else { + LOG (g->rhs, "gate not found in table"); + delete g; + return nullptr; + } +} + +Gate *Closure::new_and_gate (Clause *base_clause, int lhs) { + rhs.clear (); + auto &lits = this->lits; + + for (auto lit : lits) { + if (lhs != lit) { + CADICAL_assert (lhs != -lit); + rhs.push_back (-lit); + } + } + + CADICAL_assert (rhs.size () + 1 == lits.size ()); + sort_literals_by_var (this->rhs); + + Gate *h = find_and_lits (this->rhs); + Gate *g = new Gate; + g->lhs = lhs; + g->tag = Gate_Type::And_Gate; + if (internal->lrat) { + g->neg_lhs_ids.push_back (LitClausePair (lhs, base_clause)); + for (auto i : lrat_chain_and_gate) + g->pos_lhs_ids.push_back (i); +#ifdef LOGGING + std::vector result; + transform (begin (g->pos_lhs_ids), end (g->pos_lhs_ids), + back_inserter (result), + [] (const LitClausePair &x) { return x.clause->id; }); + LOG (result, "lrat chain positive (%d):", lhs); + result.clear (); + transform (begin (g->neg_lhs_ids), end (g->neg_lhs_ids), + back_inserter (result), + [] (const LitClausePair &x) { return x.clause->id; }); + LOG (result, "lrat chain negative (%d):", lhs); +#endif + } + + if (internal->lrat) + lrat_chain_and_gate.clear (); + + if (h) { + std::vector reasons_lrat_src, reasons_lrat_usrc; + if (internal->lrat) + merge_and_gate_lrat_produce_lrat (g, h, reasons_lrat_src, + reasons_lrat_usrc); + if (merge_literals_lrat (g, h, lhs, h->lhs, reasons_lrat_src, + reasons_lrat_usrc)) { + LOG ("found merged literals"); + ++internal->stats.congruence.ands; + } + return nullptr; + } else { + g->rhs = {rhs}; + CADICAL_assert (!internal->lrat || + g->pos_lhs_ids.size () == + g->arity ()); // otherwise we need intermediate clauses + g->garbage = false; + g->indexed = true; + g->shrunken = false; + g->hash = hash_lits (nonces, g->rhs); + + table.insert (g); + ++internal->stats.congruence.gates; +#ifdef LOGGING + g->id = fresh_id++; +#endif + LOG (g, "creating new"); + for (auto lit : g->rhs) { + connect_goccs (g, lit); + } + } + return g; +} + +Gate *Closure::find_first_and_gate (Clause *base_clause, int lhs) { + CADICAL_assert (internal->analyzed.empty ()); + const int not_lhs = -lhs; + LOG ("trying to find AND gate with first LHS %d", (lhs)); + LOG ("negated LHS %d occurs in %zd binary clauses", (not_lhs), + internal->occs (not_lhs).size ()); + unsigned matched = 0; + + const size_t arity = lits.size () - 1; + + for (auto w : internal->watches (not_lhs)) { + LOG (w.clause, "checking clause for candidates"); + CADICAL_assert (w.binary ()); + CADICAL_assert (w.clause->size == 2); + CADICAL_assert (w.clause->literals[0] == -lhs || w.clause->literals[1] == -lhs); + const int other = w.blit; + signed char &mark = marked (other); + if (mark) { + LOG ("marking %d mu2", other); + ++matched; + CADICAL_assert (~(mark & 2)); + mark |= 2; + internal->analyzed.push_back (other); + set_mu2_reason (other, w.clause); + if (internal->lrat) + lrat_chain_and_gate.push_back (LitClausePair (other, w.clause)); + } + } + + LOG ("found %zd initial LHS candidates", internal->analyzed.size ()); + if (matched < arity) { + if (internal->lrat) + lrat_chain_and_gate.clear (); + return nullptr; + } + + Gate *g = new_and_gate (base_clause, lhs); + + if (internal->lrat) { + lrat_chain_and_gate.clear (); + } + return g; +} + +Clause *Closure::learn_binary_tmp_or_full_clause (int a, int b) { + Clause *eq1; + if (internal->lrat) { + eq1 = add_tmp_binary_clause (a, b); + eq1 = maybe_promote_tmp_binary_clause (eq1); + } else + eq1 = maybe_add_binary_clause (a, b); + return eq1; +} + +Clause *Closure::maybe_add_binary_clause (int a, int b) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (!internal->lrat); + CADICAL_assert (lrat_chain.empty ()); + LOG ("learning binary clause %d %d", a, b); + if (internal->unsat) + return nullptr; + if (a == -b) + return nullptr; + if (!internal->lrat) { + const signed char a_value = internal->val (a); + if (a_value > 0) + return nullptr; + const signed char b_value = internal->val (b); + if (b_value > 0) + return nullptr; + int unit = 0; + if (a == b) + unit = a; + else if (a_value < 0 && !b_value) { + unit = b; + } else if (!a_value && b_value < 0) + unit = a; + if (unit) { + LOG ("clause reduced to unit %d", unit); + learn_congruence_unit (unit); + return nullptr; + } + CADICAL_assert (!a_value), CADICAL_assert (!b_value); + } + return add_binary_clause (a, b); +} + +Clause *Closure::add_binary_clause (int a, int b) { + CADICAL_assert (internal->clause.empty ()); + internal->clause.push_back (a); + internal->clause.push_back (b); + if (internal->lrat) { + CADICAL_assert (lrat_chain.size () >= 1); + CADICAL_assert (internal->lrat_chain.empty ()); + swap (internal->lrat_chain, lrat_chain); + } + LOG (internal->lrat_chain, "chain"); + Clause *res = internal->new_hyper_ternary_resolved_clause_and_watch ( + false, full_watching); + const bool already_sorted = internal->vlit (a) < internal->vlit (b); + binaries.push_back (CompactBinary (res, res->id, already_sorted ? a : b, + already_sorted ? b : a)); + if (!full_watching) + new_unwatched_binary_clauses.push_back (res); + LOG (res, "learning clause"); + internal->clause.clear (); + if (internal->lrat) { + internal->lrat_chain.clear (); + } + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + return res; +} + +void Closure::check_not_tmp_binary_clause (Clause *c) { +#ifndef CADICAL_NDEBUG + CADICAL_assert (internal->lrat); + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (c->size == 2); + if (internal->val (c->literals[0]) || internal->val (c->literals[1])) + return; + CADICAL_assert (std::find (begin (extra_clauses), end (extra_clauses), c) == + end (extra_clauses)); +#else + (void) c; +#endif +}; + +Clause *Closure::maybe_promote_tmp_binary_clause (Clause *c) { + CADICAL_assert (internal->lrat); + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (c->size == 2); + LOG (c, "promoting tmp"); +#ifndef CADICAL_NDEBUG + CADICAL_assert (std::find (begin (extra_clauses), end (extra_clauses), c) != + end (extra_clauses)); +#endif + if (internal->val (c->literals[0]) || internal->val (c->literals[1])) + return c; + lrat_chain.push_back (c->id); + Clause *res = add_binary_clause (c->literals[0], c->literals[1]); + LOG (res, "promoted to"); + return res; +}; + +Clause *Closure::add_tmp_binary_clause (int a, int b) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (internal->lrat); + LOG ("learning tmp binary clause %d %d", a, b); + if (internal->unsat) + return nullptr; + if (a == -b) + return nullptr; + CADICAL_assert (internal->clause.empty ()); + internal->clause.push_back (a); + internal->clause.push_back (b); + if (internal->lrat) { + CADICAL_assert (lrat_chain.size () >= 1); + CADICAL_assert (internal->lrat_chain.empty ()); + } + LOG (lrat_chain, "chain"); + Clause *res = new_tmp_clause (internal->clause); + internal->clause.clear (); + if (internal->lrat) { + lrat_chain.clear (); + } + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (internal->lrat_chain.empty ()); + LOG (res, "promoted to"); + return res; +} + +Gate *Closure::find_remaining_and_gate (Clause *base_clause, int lhs) { + const int not_lhs = -lhs; + + if (marked (not_lhs) < 2) { + LOG ("skipping no-candidate LHS %d (%d)", lhs, marked (not_lhs)); + return nullptr; + } + + LOG ("trying to find AND gate with remaining LHS %d", (lhs)); + LOG ("negated LHS %d occurs times in %zd binary clauses", (not_lhs), + internal->noccs (-lhs)); + + const size_t arity = lits.size () - 1; + size_t matched = 0; + CADICAL_assert (1 < arity); + + for (auto w : internal->watches (not_lhs)) { + CADICAL_assert (w.binary ()); +#ifdef LOGGING + Clause *c = w.clause; + LOG (c, "checking"); + CADICAL_assert (c->size == 2); + CADICAL_assert (c->literals[0] == not_lhs || c->literals[1] == not_lhs); +#endif + const int other = w.blit; + signed char &mark = marked (other); + if (!mark) + continue; + ++matched; + if (!(mark & 2)) { + lrat_chain_and_gate.push_back (LitClausePair (other, w.clause)); + LOG ("pushing %d -> %zd", other, w.clause->id); + continue; + } + LOG ("marking %d mu4", other); + CADICAL_assert (!(mark & 4)); + mark |= 4; + lrat_chain_and_gate.push_back (LitClausePair (other, w.clause)); + if (internal->lrat) + set_mu4_reason (other, w.clause); + } + + { + auto q = begin (internal->analyzed); + CADICAL_assert (!internal->analyzed.empty ()); + CADICAL_assert (marked (not_lhs) == 3); + for (auto lit : internal->analyzed) { + signed char &mark = marked (lit); + if (lit == not_lhs) { + mark = 1; + continue; + } + + CADICAL_assert ((mark & 3) == 3); + if (mark & 4) { + mark = 3; + *q = lit; + ++q; + LOG ("keeping LHS candidate %d", -lit); + } else { + LOG ("dropping LHS candidate %d", -lit); + mark = 1; + } + } + CADICAL_assert (q != end (internal->analyzed)); + CADICAL_assert (marked (not_lhs) == 1); + internal->analyzed.resize (q - begin (internal->analyzed)); + LOG ("after filtering %zu LHS candidate remain", + internal->analyzed.size ()); + } + + if (matched < arity) { + if (internal->lrat) + lrat_chain_and_gate.clear (); + return nullptr; + } + + if (!internal->lrat) + lrat_chain_and_gate.clear (); + return new_and_gate (base_clause, lhs); +} + +struct congruence_occurrences_rank { + Internal *internal; + congruence_occurrences_rank (Internal *s) : internal (s) {} + typedef uint64_t Type; + Type operator() (int a) { + uint64_t res = internal->noccs (-a); + res <<= 32; + res |= a; + return res; + } +}; + +struct congruence_occurrences_larger { + Internal *internal; + congruence_occurrences_larger (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return congruence_occurrences_rank (internal) (a) < + congruence_occurrences_rank (internal) (b); + } +}; + +void Closure::extract_and_gates_with_base_clause (Clause *c) { + CADICAL_assert (!c->garbage); + CADICAL_assert (lrat_chain.empty ()); + LOG (c, "extracting and gates with clause"); + unsigned size = 0; + const unsigned arity_limit = + min (internal->opts.congruenceandarity, MAX_ARITY); + const unsigned size_limit = arity_limit + 1; + size_t max_negbincount = 0; + lits.clear (); + + for (int lit : *c) { + signed char v = internal->val (lit); + if (v < 0) { + // push_lrat_unit (-lit); + continue; + } + if (v > 0) { + CADICAL_assert (!internal->level); + LOG (c, "found satisfied clause"); + internal->mark_garbage (c); + if (internal->lrat) + lrat_chain.clear (); + return; + } + if (++size > size_limit) { + LOG (c, "clause is actually too large, thus skipping"); + if (internal->lrat) + lrat_chain.clear (); + return; + } + const size_t count = internal->noccs (-lit); + if (!count) { + LOG (c, + "%d negated does not occur in any binary clause, thus skipping", + lit); + if (internal->lrat) + lrat_chain.clear (); + return; + } + + if (count > max_negbincount) + max_negbincount = count; + lits.push_back (lit); + } + + if (size < 3) { + LOG (c, "is actually too small, thus skipping"); + if (internal->lrat) + lrat_chain.clear (); + CADICAL_assert (lrat_chain.empty ()); + return; + } + + const size_t arity = size - 1; + if (max_negbincount < arity) { + LOG (c, + "all literals have less than %lu negated occurrences" + "thus skipping", + arity); + if (internal->lrat) + lrat_chain.clear (); + return; + } + + internal->analyzed.clear (); + size_t reduced = 0; + const size_t clause_size = lits.size (); + for (size_t i = 0; i < clause_size; ++i) { + const int lit = lits[i]; + const unsigned count = internal->noccs (-lit); + marked (-lit) = 1; + set_mu1_reason (-lit, c); + if (count < arity) { + if (reduced < i) { + lits[i] = lits[reduced]; + lits[reduced++] = lit; + } else if (reduced == i) + ++reduced; + } + } + const size_t reduced_size = clause_size - reduced; + CADICAL_assert (reduced_size); + LOG (c, "trying as base arity %lu AND gate", arity); + CADICAL_assert (begin (lits) + reduced_size <= end (lits)); + MSORT (internal->opts.radixsortlim, begin (lits), + begin (lits) + reduced_size, + congruence_occurrences_rank (internal), + congruence_occurrences_larger (internal)); + bool first = true; + unsigned extracted = 0; + + for (size_t i = 0; i < clause_size; ++i) { + CADICAL_assert (lrat_chain.empty ()); + if (internal->unsat) + break; + if (c->garbage) + break; + const int lhs = lits[i]; + LOG ("trying LHS candidate literal %d with %ld negated occurrences", + (lhs), internal->noccs (-lhs)); + + if (first) { + first = false; + CADICAL_assert (internal->analyzed.empty ()); + if (find_first_and_gate (c, lhs) != nullptr) { + CADICAL_assert (lrat_chain.empty ()); + ++extracted; + } + } else if (internal->analyzed.empty ()) { + LOG ("early abort AND gate search"); + break; + } else if (find_remaining_and_gate (c, lhs)) { + CADICAL_assert (lrat_chain.empty ()); + ++extracted; + } + } + + unmark_all (); + LOG (lits, "finish unmarking"); + for (auto lit : lits) { + marked (-lit) = 0; + } + lrat_chain_and_gate.clear (); + if (extracted) + LOG (c, "extracted %u with arity %lu AND base", extracted, arity); +} + +void Closure::reset_and_gate_extraction () { + internal->clear_noccs (); + internal->clear_watches (); +} + +void Closure::extract_and_gates () { + CADICAL_assert (!full_watching); + if (!internal->opts.congruenceand) + return; + START (extractands); + marks.resize (internal->max_var * 2 + 3); + init_and_gate_extraction (); + + const size_t size = internal->clauses.size (); + for (size_t i = 0; i < size && !internal->terminated_asynchronously (); + ++i) { // we can learn new binary clauses, but no for loop + CADICAL_assert (lrat_chain.empty ()); + Clause *c = internal->clauses[i]; + if (c->garbage) + continue; + if (c->size == 2) + continue; + if (c->hyper) + continue; + if (c->redundant) + continue; + extract_and_gates_with_base_clause (c); + CADICAL_assert (lrat_chain.empty ()); + } + + reset_and_gate_extraction (); + STOP (extractands); +} + +/*------------------------------------------------------------------------*/ +// XOR gates + +uint64_t &Closure::new_largecounts (int lit) { + CADICAL_assert (internal->vlit (lit) < gnew_largecounts.size ()); + return gnew_largecounts[internal->vlit (lit)]; +} + +uint64_t &Closure::largecounts (int lit) { + CADICAL_assert (internal->vlit (lit) < glargecounts.size ()); + return glargecounts[internal->vlit (lit)]; +} + +bool parity_lits (const vector &lits) { + unsigned res = 0; + for (auto lit : lits) + res ^= (lit < 0); + return res; +} + +void inc_lits (vector &lits) { + bool carry = true; + for (size_t i = 0; i < lits.size () && carry; ++i) { + int lit = lits[i]; + carry = (lit < 0); + lits[i] = -lit; + } +} + +void Closure::check_ternary (int a, int b, int c) { + CADICAL_assert (internal->clause.empty ()); + if (internal->lrat) + return; + auto &clause = internal->clause; + CADICAL_assert (clause.empty ()); + clause.push_back (a); + clause.push_back (b); + clause.push_back (c); + internal->external->check_learned_clause (); + if (internal->proof) { + const LRAT_ID id = internal->clause_id++; + internal->proof->add_derived_clause (id, false, clause, {}); + internal->proof->delete_clause (id, false, clause); + } + + clause.clear (); +} + +void Closure::check_binary_implied (int a, int b) { + CADICAL_assert (internal->clause.empty ()); + if (internal->lrat) + return; + auto &clause = internal->clause; + CADICAL_assert (clause.empty ()); + clause.push_back (a); + clause.push_back (b); + check_implied (); + clause.clear (); +} + +void Closure::check_implied () { + if (internal->lrat) + return; + internal->external->check_learned_clause (); +} + +void Closure::add_xor_shrinking_proof_chain (Gate *g, int pivot) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (clause.empty ()); + if (!internal->proof) + return; + LOG (g, "starting XOR shrinking proof chain"); + vector first; + vector newclauses; + if (internal->lrat) { + simplify_and_sort_xor_lrat_clauses (g->pos_lhs_ids, first, g->lhs, + pivot); + gate_sort_lrat_reasons (first, pivot, g->lhs); + } + + auto &clause = internal->clause; + + const int lhs = g->lhs; + clause.push_back (-lhs); + for (auto lit : g->rhs) + clause.push_back (lit); + + const bool parity = (lhs > 0); + CADICAL_assert (parity == parity_lits (clause)); + const size_t size = clause.size (); + const unsigned end = 1u << (size - 1); + CADICAL_assert (!internal->lrat || first.size () == 2 * end); +#ifdef LOGGING + for (auto pair : first) { + LOG (pair.clause, "key %d", pair.current_lit); + } +#endif + // TODO Florian adjust indices of first depending on order... + // + for (unsigned i = 0; i != end; ++i) { + while (i && parity != parity_lits (clause)) + inc_lits (clause); + LOG (clause, "xor shrinking clause"); + if (!internal->lrat) { + clause.push_back (pivot); + check_and_add_to_proof_chain (clause); + clause.pop_back (); + clause.push_back (-pivot); + check_and_add_to_proof_chain (clause); + clause.pop_back (); + } + if (internal->lrat) { + CADICAL_assert (lrat_chain.empty ()); + lrat_chain.push_back (first[2 * i].clause->id); + lrat_chain.push_back (first[2 * i + 1].clause->id); + } + if (clause.size () > 1) { + if (internal->lrat) { + Clause *c = new_tmp_clause (clause); + newclauses.push_back (LitClausePair (0, c)); + lrat_chain.clear (); + } else { + check_and_add_to_proof_chain (clause); + } + } + if (clause.size () == 1) + return; + inc_lits (clause); + } + g->pos_lhs_ids.swap (newclauses); + + clause.clear (); +} + +void Closure::check_xor_gate_implied (Gate const *const g) { + CADICAL_assert (internal->clause.empty ()); + CADICAL_assert (g->tag == Gate_Type::XOr_Gate); + if (internal->lrat) { + return; + } + const int lhs = g->lhs; + LOG (g, "checking implied"); + auto &clause = internal->clause; + CADICAL_assert (clause.empty ()); + for (auto other : g->rhs) { + CADICAL_assert (other > 0); + clause.push_back (other); + } + clause.push_back (-lhs); + const unsigned arity = g->arity (); + const unsigned end = 1u << arity; + const bool parity = (lhs > 0); + + for (unsigned i = 0; i != end; ++i) { + while (i && parity_lits (clause) != parity) + inc_lits (clause); + internal->external->check_learned_clause (); + if (internal->proof) { + internal->proof->add_derived_clause (internal->clause_id, false, + clause, {}); + internal->proof->delete_clause (internal->clause_id, false, clause); + } + inc_lits (clause); + } + clause.clear (); +} + +Gate *Closure::find_xor_lits (const vector &rhs) { + CADICAL_assert (is_sorted (begin (rhs), end (rhs), + sort_literals_by_var_smaller (internal))); + return find_gate_lits (rhs, Gate_Type::XOr_Gate); +} + +Gate *Closure::find_xor_gate (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::XOr_Gate); + CADICAL_assert (is_sorted (begin (g->rhs), end (g->rhs), + sort_literals_by_var_smaller (internal))); + return find_gate_lits (g->rhs, Gate_Type::XOr_Gate); +} + +void Closure::reset_xor_gate_extraction () { internal->clear_occs (); } + +bool Closure::normalize_ite_lits_gate (Gate *g) { + auto &rhs = g->rhs; + CADICAL_assert (rhs.size () == 3); + if (internal->lrat) + check_correct_ite_flags (g); + LOG (rhs, "RHS = "); + if (rhs[0] < 0) { + rhs[0] = -rhs[0]; + std::swap (rhs[1], rhs[2]); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 4); + std::swap (g->pos_lhs_ids[0], g->pos_lhs_ids[2]); + std::swap (g->pos_lhs_ids[1], g->pos_lhs_ids[3]); + const int8_t flag = g->degenerated_ite; + const int8_t plus_then = flag & NO_PLUS_THEN; + const int8_t neg_then = flag & NO_NEG_THEN; + const int8_t plus_else = flag & NO_PLUS_ELSE; + const int8_t neg_else = flag & NO_NEG_ELSE; + g->degenerated_ite = (plus_then ? Special_ITE_GATE::NO_PLUS_ELSE + : Special_ITE_GATE::NORMAL) | + (neg_then ? Special_ITE_GATE::NO_NEG_ELSE + : Special_ITE_GATE::NORMAL) | + (plus_else ? Special_ITE_GATE::NO_PLUS_THEN + : Special_ITE_GATE::NORMAL) | + (neg_else ? Special_ITE_GATE::NO_NEG_THEN + : Special_ITE_GATE::NORMAL); + CADICAL_assert (g->pos_lhs_ids[0].current_lit == rhs[1]); + CADICAL_assert (g->pos_lhs_ids[2].current_lit == rhs[2]); + if (internal->lrat) + check_correct_ite_flags (g); + } + } + if (rhs[1] > 0) + return false; + if (internal->lrat) + check_correct_ite_flags (g); + rhs[1] = -rhs[1]; + rhs[2] = -rhs[2]; + LOG (rhs, "RHS = "); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 4); + std::swap (g->pos_lhs_ids[0], g->pos_lhs_ids[1]); + std::swap (g->pos_lhs_ids[2], g->pos_lhs_ids[3]); + const int8_t flag = g->degenerated_ite; + const int8_t plus_then = flag & NO_PLUS_THEN; + const int8_t neg_then = flag & NO_NEG_THEN; + const int8_t plus_else = flag & NO_PLUS_ELSE; + const int8_t neg_else = flag & NO_NEG_ELSE; + g->degenerated_ite = (plus_then ? Special_ITE_GATE::NO_NEG_THEN + : Special_ITE_GATE::NORMAL) | + (neg_then ? Special_ITE_GATE::NO_PLUS_THEN + : Special_ITE_GATE::NORMAL) | + (plus_else ? Special_ITE_GATE::NO_NEG_ELSE + : Special_ITE_GATE::NORMAL) | + (neg_else ? Special_ITE_GATE::NO_PLUS_ELSE + : Special_ITE_GATE::NORMAL); + CADICAL_assert (g->pos_lhs_ids[0].current_lit == rhs[1]); + CADICAL_assert (g->pos_lhs_ids[2].current_lit == rhs[2]); + // incorrect as we have not negated the LHS yet! + // check_correct_ite_flags (g); + } + + LOG (g->rhs, "g/RHS = "); + return true; +} + +#ifndef CADICAL_NDEBUG +bool is_tautological_ite_gate (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::ITE_Gate); + CADICAL_assert (g->rhs.size () == 3); + const int cond_lit = g->rhs[0]; + const int then_lit = g->rhs[1]; + const int else_lit = g->rhs[2]; + return cond_lit == then_lit || cond_lit == else_lit; +} +#endif + +Gate *Closure::find_ite_gate (Gate *g, bool &negate_lhs) { + negate_lhs = normalize_ite_lits_gate (g); + LOG (g, "post normalize"); + return find_gate_lits (g->rhs, Gate_Type::ITE_Gate, g); +} + +LRAT_ID Closure::check_and_add_to_proof_chain (vector &clause) { + internal->external->check_learned_clause (); + const LRAT_ID id = ++internal->clause_id; + if (internal->proof) { + if (internal->lrat) { + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (lrat_chain.size () >= 1); + } + internal->proof->add_derived_clause (id, true, clause, lrat_chain); + lrat_chain.clear (); + } + return id; +} + +void Closure::add_clause_to_chain (std::vector unsimplified, + LRAT_ID id) { + const uint32_t id2_higher = (id >> 32); + const uint32_t id2_lower = (uint32_t) (id & (LRAT_ID) (uint32_t) (-1)); + CADICAL_assert (id == ((LRAT_ID) id2_higher << 32) + (LRAT_ID) id2_lower); + chain.push_back (id2_higher); + chain.push_back (id2_lower); + LOG (unsimplified, "pushing to chain"); + chain.insert (end (chain), begin (unsimplified), end (unsimplified)); + chain.push_back (0); +} + +LRAT_ID Closure::simplify_and_add_to_proof_chain (vector &unsimplified, + LRAT_ID delete_id) { + vector &clause = internal->clause; + CADICAL_assert (clause.empty ()); +#ifndef CADICAL_NDEBUG + for (auto lit : unsimplified) { + CADICAL_assert (!(marked (lit) & 4)); + } +#endif + + bool trivial = false; + for (auto lit : unsimplified) { + signed char &lit_mark = marked (lit); + if (lit_mark & 4) + continue; + signed char ¬_lit_mark = marked (-lit); + if (not_lit_mark & 4) { + trivial = true; + break; + } + lit_mark |= 4; + clause.push_back (lit); + } + for (auto lit : clause) { + signed char &mark = marked (lit); + CADICAL_assert (mark & 4); + mark &= ~4u; + } + + LRAT_ID id = 0; + if (!trivial) { + if (delete_id) { + if (internal->proof) { + internal->proof->delete_clause (delete_id, true, clause); + lrat_chain.clear (); + } + } else { + id = check_and_add_to_proof_chain (clause); + add_clause_to_chain (clause, id); + } + } else { + LOG ("skipping trivial proof"); + lrat_chain.clear (); + } + clause.clear (); + return id; +} +/*------------------------------------------------------------------------*/ +void Closure::add_ite_turned_and_binary_clauses (Gate *g) { + if (!internal->proof) + return; + if (internal->lrat) + return; + LOG ("starting ITE turned AND supporting binary clauses"); + CADICAL_assert (unsimplified.empty ()); + CADICAL_assert (chain.empty ()); + int not_lhs = -g->lhs; + unsimplified.push_back (not_lhs); + unsimplified.push_back (g->rhs[0]); + simplify_and_add_to_proof_chain (unsimplified); + unsimplified.pop_back (); + unsimplified.push_back (g->rhs[1]); + simplify_and_add_to_proof_chain (unsimplified); + unsimplified.clear (); +} +void Closure::simplify_unit_xor_lrat_clauses ( + const vector &source, int lhs) { + CADICAL_assert (internal->lrat); + for (auto pair : source) { + compute_rewritten_clause_lrat_simple (pair.clause, lhs); + if (lrat_chain.size ()) + break; + } + CADICAL_assert (clause.size () == 1); +} +void Closure::simplify_and_sort_xor_lrat_clauses ( + const vector &source, vector &target, + int lhs, int except2, bool flip) { + CADICAL_assert (internal->lrat); + for (auto pair : source) { + Clause *c = produce_rewritten_clause_lrat (pair.clause, lhs); + if (c) { + target.push_back (LitClausePair (0, c)); + } + } + gate_sort_lrat_reasons (target, lhs, except2, flip); +} +void Closure::add_xor_matching_proof_chain ( + Gate *g, int lhs1, const vector &clauses2, int lhs2, + vector &to_lrat, vector &back_lrat) { + if (lhs1 == lhs2) + return; + if (!internal->proof) + return; + CADICAL_assert (unsimplified.empty ()); + unsimplified = g->rhs; + vector first; + vector second; + if (internal->lrat) { + simplify_and_sort_xor_lrat_clauses (g->pos_lhs_ids, first, lhs1); + simplify_and_sort_xor_lrat_clauses (clauses2, second, lhs2, 0, 1); + g->pos_lhs_ids = first; + } + LOG ("starting XOR matching proof"); + // for lrat + vector first_ids; + vector second_ids; + for (auto pair : first) { + bool first = pair.current_lit & 1; + int rest = pair.current_lit >> 1; + rest &= ~(1 << (g->rhs.size () - 1)); + if (first == (lhs1 > 0)) { + first_ids.push_back (LitIdPair (rest, pair.clause->id)); + } else { + second_ids.push_back (LitIdPair (rest, pair.clause->id)); + } + LOG (pair.clause, "key %d", pair.current_lit); + } + for (auto pair : second) { + bool first = pair.current_lit & 1; + int rest = pair.current_lit >> 1; + rest &= ~(1 << (g->rhs.size () - 1)); + if (first == (lhs2 < 0)) { + first_ids.push_back (LitIdPair (rest, pair.clause->id)); + } else { + second_ids.push_back (LitIdPair (rest, pair.clause->id)); + } + LOG (pair.clause, "key %d", pair.current_lit); + } + // TODO Florian: resort and ids after every round + do { + vector first_tmp; + vector second_tmp; + CADICAL_assert (!unsimplified.empty ()); + unsimplified.pop_back (); + const size_t size = unsimplified.size (); + CADICAL_assert (size < 32); + const size_t off = 1u << size; + for (size_t i = 0; i != off; ++i) { + int32_t n = 0; + if (internal->lrat) { + n = number_from_xor_reason_reversed (unsimplified); + CADICAL_assert (lrat_chain.empty ()); + for (auto pair : first_ids) { + if (pair.lit == n) + lrat_chain.push_back (pair.id); + } + CADICAL_assert (lrat_chain.size () == 2); + } + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + const LRAT_ID id1 = simplify_and_add_to_proof_chain (unsimplified); + unsimplified.resize (unsimplified.size () - 2); + if (internal->lrat) { + int32_t rest = n &= ~(1 << (unsimplified.size () - 1)); + first_tmp.push_back (LitIdPair (rest, id1)); + n = number_from_xor_reason_reversed (unsimplified); + lrat_chain.clear (); + for (auto pair : second_ids) { + if (pair.lit == n) + lrat_chain.push_back (pair.id); + } + CADICAL_assert (lrat_chain.size () == 2); + } + unsimplified.push_back (lhs1); + unsimplified.push_back (-lhs2); + const LRAT_ID id2 = simplify_and_add_to_proof_chain (unsimplified); + unsimplified.resize (unsimplified.size () - 2); + if (internal->lrat) { + lrat_chain.clear (); + int32_t rest = n &= ~(1 << (unsimplified.size () - 1)); + second_tmp.push_back (LitIdPair (rest, id2)); + } + inc_lits (unsimplified); + } + if (internal->lrat) { + first_ids.swap (first_tmp); + second_ids.swap (second_tmp); + } + } while (!unsimplified.empty ()); + if (internal->lrat) { + CADICAL_assert (first_ids.size () == 1); + CADICAL_assert (second_ids.size () == 1); + to_lrat.push_back (first_ids.back ().id); + back_lrat.push_back (second_ids.back ().id); + } + CADICAL_assert (!internal->lrat || to_lrat.size () == 1); + CADICAL_assert (!internal->lrat || back_lrat.size () == 1); + LOG ("finished XOR matching proof"); + CADICAL_assert (unsimplified.empty ()); +} + +// this function needs to either put the clauses from +// lrat_chain_and_gate into g->pos_neg_ids or clear it or do something with +// it if you merge gates. +Gate *Closure::new_xor_gate (const vector &glauses, + int lhs) { + rhs.clear (); + + for (auto lit : lits) { + if (lhs != lit && -lhs != lit) { + CADICAL_assert (lit > 0); + rhs.push_back (lit); + } + } + CADICAL_assert (rhs.size () + 1 == lits.size ()); + sort_literals_by_var (rhs); + Gate *g = find_xor_lits (this->rhs); + if (g) { + check_xor_gate_implied (g); + std::vector reasons_implication, reasons_back; + add_xor_matching_proof_chain (g, g->lhs, glauses, lhs, + reasons_implication, reasons_back); + if (merge_literals_lrat (g->lhs, lhs, reasons_implication, + reasons_back)) { + ++internal->stats.congruence.xors; + } + delete_proof_chain (); + CADICAL_assert (internal->unsat || chain.empty ()); + } else { + g = new Gate; + g->lhs = lhs; + g->tag = Gate_Type::XOr_Gate; + g->rhs = {rhs}; + g->garbage = false; + g->indexed = true; + g->shrunken = false; + g->hash = hash_lits (nonces, g->rhs); + for (auto pair : glauses) + g->pos_lhs_ids.push_back (pair); + table.insert (g); + ++internal->stats.congruence.gates; +#ifdef LOGGING + g->id = fresh_id++; +#endif + LOG (g, "creating new"); + check_xor_gate_implied (g); + for (auto lit : g->rhs) { + connect_goccs (g, lit); + } + } + return g; +} +uint32_t +Closure::number_from_xor_reason_reversed (const std::vector &rhs) { + uint32_t n = 0; + CADICAL_assert (is_sorted (rhs.rbegin (), rhs.rend (), + sort_literals_by_var_smaller_except (internal, 0, 0))); + CADICAL_assert (rhs.size () <= 32); + for (auto r = rhs.rbegin (); r != rhs.rend (); r++) { + int lit = *r; + n *= 2; + n += !(lit > 0); + } + return n; +} + +uint32_t Closure::number_from_xor_reason (const std::vector &rhs, + int lhs, int except, bool flip) { + uint32_t n = 0; + CADICAL_assert (is_sorted ( + begin (rhs), end (rhs), + sort_literals_by_var_smaller_except (internal, lhs, except))); + (void) lhs, (void) except; + CADICAL_assert (rhs.size () <= 32); + for (auto lit : rhs) { + n *= 2; + n += !(lit > 0) ^ flip; + flip = 0; + } + return n; +} + +// this is how I planned to sort it and produce the number +// Look at this first +void Closure::gate_sort_lrat_reasons (LitClausePair &litId, int lhs, + int except2, bool flip) { + CADICAL_assert (clause.empty ()); + std::copy (begin (*litId.clause), end (*litId.clause), + back_inserter (clause)); + sort_literals_by_var_except (clause, lhs, except2); + litId.current_lit = number_from_xor_reason (clause, lhs, except2, flip); + clause.clear (); +} + +struct smaller_pair_first_rank { + typedef size_t Type; + Type operator() (const LitClausePair &a) { return a.current_lit; } +}; + +// this is how I planned to sort it and produce the number +// Look at this first +void Closure::gate_sort_lrat_reasons (std::vector &xs, + int lhs, int except2, bool flip) { + CADICAL_assert (clause.empty ()); + CADICAL_assert (!xs.empty ()); + for (auto &litId : xs) { + gate_sort_lrat_reasons (litId, lhs, except2, flip); + } + + rsort (begin (xs), end (xs), smaller_pair_first_rank ()); + +#ifndef CADICAL_NDEBUG + std::for_each (begin (xs), end (xs), [&xs] (const LitClausePair &x) { + CADICAL_assert (x.clause->size == xs[1].clause->size); + }); +#endif +} + +void Closure::init_xor_gate_extraction (std::vector &candidates) { + const unsigned arity_limit = internal->opts.congruencexorarity; + CADICAL_assert (arity_limit < 32); // we use unsigned int. + const unsigned size_limit = arity_limit + 1; + glargecounts.resize (2 * internal->vsize, 0); + + for (auto c : internal->clauses) { + LOG (c, "considering clause for XOR"); + if (c->redundant) + continue; + if (c->garbage) + continue; + if (c->size < 3) + continue; + unsigned size = 0; + for (auto lit : *c) { + const signed char v = internal->val (lit); + if (v < 0) + continue; + if (v > 0) { + LOG (c, "satisfied by %d", lit); + internal->mark_garbage (c); + goto CONTINUE_COUNTING_NEXT_CLAUSE; + } + if (size == size_limit) + goto CONTINUE_COUNTING_NEXT_CLAUSE; + ++size; + } + + if (size < 3) + continue; + for (auto lit : *c) { + if (internal->val (lit)) + continue; + ++largecounts (lit); + } + + LOG (c, "considering clause for XOR as candidate"); + candidates.push_back (c); + CONTINUE_COUNTING_NEXT_CLAUSE:; + } + + LOG ("considering %zd out of %zd", candidates.size (), + internal->irredundant ()); + const unsigned rounds = internal->opts.congruencexorcounts; +#ifdef LOGGING + const size_t original_size = candidates.size (); +#endif + LOG ("resizing glargecounts to size %zd", glargecounts.size ()); + for (unsigned round = 0; round < rounds; ++round) { + LOG ("round %d of XOR extraction", round); + size_t removed = 0; + gnew_largecounts.resize (2 * internal->vsize); + unsigned cand_size = candidates.size (); + size_t j = 0; + for (size_t i = 0; i < cand_size; ++i) { + Clause *c = candidates[i]; + LOG (c, "considering"); + unsigned size = 0; + for (auto lit : *c) { + if (!internal->val (lit)) + ++size; + } + CADICAL_assert (3 <= size); + CADICAL_assert (size <= size_limit); + const unsigned arity = size - 1; + const unsigned needed_clauses = 1u << (arity - 1); + for (auto lit : *c) { + if (largecounts (lit) < needed_clauses) { + LOG (c, "not enough occurrences, so ignoring"); + removed++; + goto CONTINUE_WITH_NEXT_CANDIDATE_CLAUSE; + } + } + for (auto lit : *c) + if (!internal->val (lit)) + new_largecounts (lit)++; + candidates[j++] = candidates[i]; + + CONTINUE_WITH_NEXT_CANDIDATE_CLAUSE:; + } + candidates.resize (j); + glargecounts = std::move (gnew_largecounts); + gnew_largecounts.clear (); + LOG ("moving counts %zd", glargecounts.size ()); + if (!removed) + break; + + LOG ("after round %d, %zd (%ld %%) remain", round, candidates.size (), + candidates.size () / (1 + original_size) * 100); + } + + for (auto c : candidates) { + for (auto lit : *c) + internal->occs (lit).push_back (c); + } +} + +Clause *Closure::find_large_xor_side_clause (std::vector &lits) { + unsigned least_occurring_literal = 0; + unsigned count_least_occurring = UINT_MAX; + const size_t size_lits = lits.size (); +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + const unsigned arity = size_lits - 1; +#endif +#ifndef CADICAL_NDEBUG + const unsigned count_limit = 1u << (arity - 1); +#endif + LOG (lits, "trying to find arity %u XOR side clause", arity); + for (auto lit : lits) { + CADICAL_assert (!internal->val (lit)); + marked (lit) = 1; + unsigned count = largecount (lit); + CADICAL_assert (count_limit <= count); + if (count >= count_least_occurring) + continue; + count_least_occurring = count; + least_occurring_literal = lit; + } + Clause *res = 0; + CADICAL_assert (least_occurring_literal); + LOG ("searching XOR side clause watched by %d#%u", + least_occurring_literal, count_least_occurring); + LOG ("searching for size %ld", size_lits); + for (auto c : internal->occs (least_occurring_literal)) { + LOG (c, "checking"); + CADICAL_assert (c->size != 2); // TODO kissat has break + if (c->garbage) + continue; + if ((size_t) c->size < size_lits) + continue; + size_t found = 0; + for (auto other : *c) { + const signed char value = internal->val (other); + if (value < 0) + continue; + if (value > 0) { + LOG (c, "found satisfied %d in", other); + internal->mark_garbage (c); + CADICAL_assert (c->garbage); + break; + } + if (marked (other)) + found++; + else { + LOG ("not marked %d", other); + found = 0; + break; + } + } + if (found == size_lits && !c->garbage) { + res = c; + break; + } else { + LOG ("too few literals"); + } + } + for (auto lit : lits) + marked (lit) = 0; + if (res) + LOG (res, "found matching XOR side"); + else + LOG ("no matching XOR side clause found"); + return res; +} + +void Closure::extract_xor_gates_with_base_clause (Clause *c) { + LOG (c, "checking clause"); + lits.clear (); + int smallest = 0; + int largest = 0; + const unsigned arity_limit = internal->opts.congruencexorarity; + const unsigned size_limit = arity_limit + 1; + unsigned negated = 0, size = 0; + bool first = true; + for (auto lit : *c) { + const signed char v = internal->val (lit); + if (v < 0) + continue; + if (v > 0) { + internal->mark_garbage (c); + return; + } + if (size == size_limit) { + LOG (c, "size limit reached"); + return; + } + + if (first) { + largest = smallest = lit; + first = false; + } else { + CADICAL_assert (smallest); + CADICAL_assert (largest); + if (internal->vlit (lit) < internal->vlit (smallest)) { + LOG ("new smallest %d", lit); + smallest = lit; + } + if (internal->vlit (lit) > internal->vlit (largest)) { + if (largest < 0) { + LOG (c, "not largest %d (largest: %d) occurs negated in XOR base", + lit, largest); + return; + } + largest = lit; + } + } + if (lit < 0 && internal->vlit (lit) < internal->vlit (largest)) { + LOG (c, "negated literal %d not largest in XOR base", lit); + return; + } + if (lit < 0 && negated++) { + LOG (c, "more than one negated literal in XOR base"); + return; + } + lits.push_back (lit); + ++size; + } + CADICAL_assert (size == lits.size ()); + if (size < 3) { + LOG (c, "short XOR base clause"); + return; + } + + LOG ("double checking if possible"); + const unsigned arity = size - 1; + const unsigned needed_clauses = 1u << (arity - 1); + for (auto lit : lits) { + for (int sign = 0; sign != 2; ++sign, lit = -lit) { + unsigned count = largecount (lit); + if (count >= needed_clauses) + continue; + LOG (c, + "literal %d in XOR base clause only occurs %u times in large " + "clause thus skipping", + lit, count); + return; + } + } + + LOG ("checking for XOR side clauses"); + CADICAL_assert (smallest && largest); + const unsigned end = 1u << arity; + CADICAL_assert (negated == parity_lits (lits)); + unsigned found = 0; + vector glauses; + glauses.push_back (LitClausePair (0, c)); + for (unsigned i = 0; i != end; ++i) { + while (i && parity_lits (lits) != negated) + inc_lits (lits); + if (i) { + // the clause must be stored + // you can use `lrat_chain_and_gate` for this + Clause *d = find_large_xor_side_clause (lits); + if (!d) + return; + CADICAL_assert (!d->redundant); + glauses.push_back (LitClausePair (i, d)); + } else + CADICAL_assert (!c->redundant); + inc_lits (lits); + ++found; + } + + while (parity_lits (lits) != negated) + inc_lits (lits); + LOG (lits, "found all needed %u matching clauses:", found); + CADICAL_assert (found == 1u << arity); + if (negated) { + auto p = begin (lits); + int lit; + while ((lit = *p) > 0) + p++; + LOG ("flipping RHS literal %d", (lit)); + *p = -lit; + } + LOG (lits, "normalized negations"); + unsigned extracted = 0; + for (auto lhs : lits) { + if (!negated) + lhs = -lhs; + Gate *g = new_xor_gate (glauses, lhs); + if (g) + extracted++; + if (internal->unsat) + break; + } + if (!extracted) + LOG ("no arity %u XOR gate extracted", arity); +} +void Closure::extract_xor_gates () { + CADICAL_assert (!full_watching); + if (!internal->opts.congruencexor) + return; + START (extractxors); + LOG ("starting extracting XOR"); + std::vector candidates = {}; + init_xor_gate_extraction (candidates); + for (auto c : candidates) { + if (internal->unsat) + break; + if (c->garbage) + continue; + extract_xor_gates_with_base_clause (c); + } + reset_xor_gate_extraction (); + STOP (extractxors); +} + +/*------------------------------------------------------------------------*/ +void Closure::find_units () { + size_t units = 0; + for (auto v : internal->vars) { + RESTART: + if (!internal->flags (v).active ()) + continue; + for (int sgn = -1; sgn < 1; sgn += 2) { + int lit = v * sgn; + for (auto w : internal->watches (lit)) { + if (!w.binary ()) + continue; // todo check that binaries first + const int other = w.blit; + if (marked (-other)) { + LOG (w.clause, "binary clause %d %d and %d %d give unit %d", lit, + other, lit, -other, lit); + ++units; + if (internal->lrat) { + lrat_chain.push_back (w.clause->id); + lrat_chain.push_back (marked_mu1 (-other).clause->id); + } + bool failed = !learn_congruence_unit (lit); + unmark_all (); + if (failed) + return; + else + goto RESTART; + } + if (marked (other)) + continue; + marked (other) = 1; + set_mu1_reason (other, w.clause); + internal->analyzed.push_back (other); + } + unmark_all (); + } + CADICAL_assert (internal->analyzed.empty ()); + } + LOG ("found %zd units", units); +} + +void Closure::find_equivalences () { + CADICAL_assert (!internal->unsat); + + for (auto v : internal->vars) { + RESTART: + if (!internal->flags (v).active ()) + continue; + int lit = v; + for (auto w : internal->watches (lit)) { + if (!w.binary ()) + break; + CADICAL_assert (w.size == 2); + const int other = w.blit; + if (internal->vlit (lit) > internal->vlit (other)) + continue; + if (marked (other)) + continue; + internal->analyzed.push_back (other); + marked (other) = true; + set_mu1_reason (other, w.clause); + } + + if (internal->analyzed.empty ()) + continue; + + for (auto w : internal->watches (-lit)) { + if (!w.binary ()) + break; // binary clauses are first + const int other = w.blit; + if (internal->vlit (-lit) > internal->vlit (other)) + continue; + CADICAL_assert (-lit != other); + LOG ("binary clause %d %d", -lit, other); + if (marked (-other)) { + int lit_repr = find_representative (lit); + int other_repr = find_representative (other); + LOG ("found equivalence %d %d with %d and %d as the representative", + lit, other, lit_repr, other_repr); + if (lit_repr != other_repr) { + // if (internal->lrat) { + // // This cannot work + // // if you have 2 = 1 and 3=4 + // // you cannot add 2=3. You really to connect the + // representatives directly + // // therefore you actually need to learn the clauses 2->3->4 + // and -2->1 and vice-versa eager_representative_id (other) = + // marked_mu1 (-other).clause->id; eager_representative_id + // (-other) = w.clause->id; CADICAL_assert (eager_representative_id + // (other) != -1); LOG ("lrat: %d (%zd) %d (%zd)", other, + // eager_representative_id (other), -other, + // eager_representative_id (-other)); + // } + promote_clause (marked_mu1 (-other).clause); + promote_clause (w.clause); + LOG (w.clause, "merging"); + LOG (marked_mu1 (-other).clause, "with"); + if (merge_literals_equivalence ( + lit, other, + internal->lrat ? marked_mu1 (-other).clause : nullptr, + w.clause)) { + ++internal->stats.congruence.congruent; + } + unmark_all (); + if (internal->unsat) + return; + else + goto RESTART; + } + } + } + unmark_all (); + } + CADICAL_assert (internal->analyzed.empty ()); + LOG ("found %zd equivalences", schedule.size ()); +} + +/*------------------------------------------------------------------------*/ +// Initialization + +void Closure::rewrite_and_gate (Gate *g, int dst, int src, LRAT_ID id1, + LRAT_ID id2) { + if (skip_and_gate (g)) + return; + if (!gate_contains (g, src)) + return; + if (internal->val (src)) { + // In essence the code below does the same thing as simplify_and_gate + // but the necessary LRAT chain are different. + simplify_and_gate (g); + return; + } + CADICAL_assert (src); + CADICAL_assert (dst); + CADICAL_assert (internal->val (src) == internal->val (dst)); + GatesTable::iterator git = (g->indexed ? table.find (g) : end (table)); + LOG (g, "rewriting %d into %d in", src, dst); + int clashing = 0, falsifies = 0; + unsigned dst_count = 0, not_dst_count = 0; + auto q = begin (g->rhs); + for (int &lit : g->rhs) { + if (lit == src) + lit = dst; + if (lit == -g->lhs) { + LOG ("found negated LHS literal %d", lit); + clashing = lit; + g->degenerated_and_neg = true; + break; + } + if (lit == g->lhs) + g->degenerated_and_pos = true; + const signed char val = internal->val (lit); + if (val > 0) { + continue; + } + if (val < 0) { + LOG ("found falsifying literal %d", (lit)); + falsifies = lit; + break; + } + if (lit == dst) { + if (not_dst_count) { + LOG ("clashing literals %d and %d", (-dst), (dst)); + clashing = -dst; + break; + } + if (dst_count++) + continue; + } + if (lit == -dst) { + if (dst_count) { + CADICAL_assert (!not_dst_count); + LOG ("clashing literals %d and %d", (dst), (-dst)); + clashing = -dst; + break; + } + CADICAL_assert (!not_dst_count); + ++not_dst_count; + } + *q++ = lit; + } + LOG (lrat_chain, "lrat chain after rewriting"); + + if (internal->lrat) { // updating reasons in the chain. +#ifdef LOGGING + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, "%d ->", litId.current_lit); + } +#endif + // We remove all assigned literals except the falsified literal such + // that we can produce an LRAT chain + size_t i = 0, size = g->pos_lhs_ids.size (); + bool found = false; + CADICAL_assert (!falsifies || !clashing); + const int orig_falsifies = falsifies == dst ? src : falsifies; + const int orig_clashing = + clashing == -dst ? -src : (clashing == dst ? src : clashing); + int keep_clashing = clashing; + LOG ("keeping chain for falsifies: %d aka %d and clashing: %d aka %d", + falsifies, orig_falsifies, clashing, orig_clashing); + for (size_t j = 0; j < size; ++j) { + LOG (g->pos_lhs_ids[j].clause, "looking at %d [%zd %zd] with val %d", + g->pos_lhs_ids[j].current_lit, i, j, + internal->val (g->pos_lhs_ids[i].current_lit)); + g->pos_lhs_ids[i] = g->pos_lhs_ids[j]; + if (keep_clashing && g->pos_lhs_ids[i].current_lit != orig_clashing && + g->pos_lhs_ids[i].current_lit != -orig_clashing && + g->pos_lhs_ids[i].current_lit != keep_clashing && + g->pos_lhs_ids[i].current_lit != -keep_clashing) + continue; + if (internal->val (g->pos_lhs_ids[i].current_lit) && + g->pos_lhs_ids[i].current_lit != src && + g->pos_lhs_ids[i].current_lit != orig_falsifies) + continue; + if (g->pos_lhs_ids[i].current_lit == dst) { + if (!found) + found = true; + else + continue; // we have already one defining clause + } + + LOG ("maybe keeping %d [%zd %zd], src: %d, found: %d", + g->pos_lhs_ids[i].current_lit, i, j, src, found); + if (g->pos_lhs_ids[i].current_lit == src) { + if (!found) + g->pos_lhs_ids[i].current_lit = dst, found = true; + else + continue; // we have already one defining clause + } + LOG ("keeping %d [%zd %zd]", g->pos_lhs_ids[i].current_lit, i, j); + ++i; + } + LOG ("resizing to %zd", i); + CADICAL_assert (i); + g->pos_lhs_ids.resize (i); + } + + if (q != end (g->rhs)) { + g->rhs.resize (q - begin (g->rhs)); + g->shrunken = true; + } + CADICAL_assert (dst_count <= 2); + CADICAL_assert (not_dst_count <= 1); + + std::vector reasons_lrat_src, reasons_lrat_usrc; + shrink_and_gate (g, falsifies, clashing); + LOG (g, "rewritten as"); + CADICAL_assert (!internal->lrat || !g->pos_lhs_ids.empty ()); + // check_and_gate_implied (g); + update_and_gate (g, git, src, dst, id1, id2, falsifies, clashing); + ++internal->stats.congruence.rewritten_ands; +} + +bool Closure::rewrite_gate (Gate *g, int dst, int src, LRAT_ID id1, + LRAT_ID id2) { + switch (g->tag) { + case Gate_Type::And_Gate: + rewrite_and_gate (g, dst, src, id1, id2); + break; + case Gate_Type::XOr_Gate: + rewrite_xor_gate (g, dst, src); + break; + case Gate_Type::ITE_Gate: + rewrite_ite_gate (g, dst, src); + break; + default: + CADICAL_assert (false); + break; + } + CADICAL_assert (internal->unsat || lrat_chain.empty ()); + return !internal->unsat; +} + +bool Closure::rewrite_gates (int dst, int src, LRAT_ID id1, LRAT_ID id2) { + const auto &occs = goccs (src); + for (auto g : occs) { + CADICAL_assert (lrat_chain.empty ()); + if (!rewrite_gate (g, dst, src, id1, id2)) + return false; + else if (!g->garbage && gate_contains (g, dst)) + goccs (dst).push_back (g); + } + goccs (src).clear (); + +#ifndef CADICAL_NDEBUG + for (const auto &occs : gtab) { + for (auto g : occs) { + CADICAL_assert (g); + CADICAL_assert (g->garbage || !gate_contains (g, src)); + } + } +#endif + CADICAL_assert (lrat_chain.empty ()); + return true; +} + +bool Closure::rewriting_lhs (Gate *g, int dst) { + if (dst != g->lhs && dst != -g->lhs) + return false; + mark_garbage (g); + return true; +} + +// update to produce proofs +void Closure::rewrite_xor_gate (Gate *g, int dst, int src) { + if (skip_xor_gate (g)) + return; + if (rewriting_lhs (g, dst)) + return; + if (!gate_contains (g, src)) + return; + LOG (g, "rewriting (%d -> %d)", src, dst); + check_xor_gate_implied (g); + GatesTable::iterator git = (g->indexed ? table.find (g) : end (table)); + size_t j = 0, dst_count = 0; + bool original_dst_negated = (dst < 0); + dst = abs (dst); + unsigned negate = original_dst_negated; + const size_t size = g->rhs.size (); + for (size_t i = 0; i < size; ++i) { + int lit = g->rhs[i]; + CADICAL_assert (lit > 0); + if (lit == src) + lit = dst; + const signed char v = internal->val (lit); + if (v > 0) { + negate ^= true; + } + if (v) + continue; + if (lit == dst) + dst_count++; + LOG ("keeping value %d", lit); + g->rhs[j++] = lit; + } + if (negate) { + LOG ("flipping LHS %d", g->lhs); + g->lhs = -g->lhs; + } + CADICAL_assert (dst_count <= 2); + if (dst_count == 2) { + LOG ("destination found twice, removing"); + size_t k = 0; + for (size_t i = 0; i < j; ++i) { + const int lit = g->rhs[i]; + if (lit != dst) + g->rhs[k++] = g->rhs[i]; + } + CADICAL_assert (k == j - 2); + g->rhs.resize (k); + g->shrunken = true; + CADICAL_assert (is_sorted (begin (g->rhs), end (g->rhs), + sort_literals_by_var_smaller (internal))); + g->hash = hash_lits (nonces, g->rhs); + } else if (j != size) { + g->shrunken = true; + g->rhs.resize (j); + sort_literals_by_var (g->rhs); + g->hash = hash_lits ( + nonces, + g->rhs); // all but one (the dst) is sorted correctly actually + } else { + CADICAL_assert (j == size); + sort_literals_by_var (g->rhs); + } + + CADICAL_assert (clause.empty ()); + // LRAT for add_xor_shrinking_proof_chain + // this should be unnecessary... + // TODO check if really unnecessary + if (dst_count > 1) + add_xor_shrinking_proof_chain (g, dst); + CADICAL_assert (internal->clause.size () <= 1); + update_xor_gate (g, git); + + if (!g->garbage && !internal->unsat && original_dst_negated && + dst_count == 1) { + connect_goccs (g, dst); + } + + check_xor_gate_implied (g); + // TODO stats +} + +// update to produce proofs +void Closure::simplify_xor_gate (Gate *g) { + LOG (g, "simplifying"); + if (skip_xor_gate (g)) + return; + check_xor_gate_implied (g); + unsigned negate = 0; + GatesTable::iterator git = (g->indexed ? table.find (g) : end (table)); + const size_t size = g->rhs.size (); + size_t j = 0; + for (size_t i = 0; i < size; ++i) { + int lit = g->rhs[i]; + CADICAL_assert (lit > 0); + const signed char v = internal->val (lit); + if (v > 0) + negate ^= 1; + if (!v) { + g->rhs[j++] = lit; + } + } + if (negate) { + LOG ("flipping LHS literal %d", (g->lhs)); + g->lhs = -(g->lhs); + } + if (j != size) { + LOG ("shrunken gate"); + g->shrunken = true; + g->rhs.resize (j); + CADICAL_assert (is_sorted (begin (g->rhs), end (g->rhs), + sort_literals_by_var_smaller (internal))); + g->hash = hash_lits (nonces, g->rhs); + } else { + CADICAL_assert (g->hash == hash_lits (nonces, g->rhs)); + } + + check_xor_gate_implied (g); + CADICAL_assert (clause.empty ()); + update_xor_gate (g, git); + LOG (g, "simplified"); + check_xor_gate_implied (g); + internal->stats.congruence.simplified++; + internal->stats.congruence.simplified_xors++; +} + +/*------------------------------------------------------------------------*/ +// propagation of clauses and simplification +void Closure::schedule_literal (int lit) { + const int idx = abs (lit); + if (scheduled[idx]) + return; + scheduled[idx] = true; + schedule.push (lit); + CADICAL_assert (lit != find_representative (lit)); + LOG ("scheduled literal %d", lit); +} + +bool Closure::propagate_unit (int lit) { + LOG ("propagation of congruence unit %d", lit); + if (internal->lrat) + lazy_propagated (lit) = true; + return simplify_gates (lit) && simplify_gates (-lit); +} + +bool Closure::propagate_units () { + while (units != + internal->trail + .size ()) { // units are added during propagation, so reloading + LOG ("propagating %d over gates", internal->trail[units]); + if (!propagate_unit (internal->trail[units++])) + return false; + } + return true; +} + +// The replacement has to be done eagerly, not lazily to make sure that the +// gates are in normalized form. Otherwise, some merges might be missed. +bool Closure::propagate_equivalence (int lit) { + if (internal->val (lit)) + return true; + LOG ("propagating literal %d", lit); + import_lazy_and_find_eager_representative_and_compress_both (lit); + const int repr = find_eager_representative_and_compress (lit); + const LRAT_ID id1 = find_eager_representative_lrat (lit); + const LRAT_ID id2 = find_eager_representative_lrat (-lit); + CADICAL_assert (lrat_chain.empty ()); + return rewrite_gates (repr, lit, id1, id2) && + rewrite_gates (-repr, -lit, id2, id1); +} + +size_t Closure::propagate_units_and_equivalences () { + START (congruencemerge); + size_t propagated = 0; + LOG ("propagating at least %zd units", schedule.size ()); + CADICAL_assert (lrat_chain.empty ()); + while (propagate_units () && !schedule.empty ()) { + CADICAL_assert (!internal->unsat); + CADICAL_assert (lrat_chain.empty ()); + ++propagated; + int lit = schedule.front (); + schedule.pop (); + scheduled[abs (lit)] = false; + if (!propagate_equivalence (lit)) + break; + } + + CADICAL_assert (internal->unsat || schedule.empty ()); + CADICAL_assert (internal->unsat || lrat_chain.empty ()); + + LOG ("propagated %zu congruence units", units); + LOG ("propagated %zu congruence equivalences", propagated); + +#ifndef CADICAL_NDEBUG + if (!internal->unsat) { + for (const auto &occs : gtab) { + for (auto g : occs) { + if (g->garbage) + continue; + CADICAL_assert (g->tag == Gate_Type::ITE_Gate || + g->tag == Gate_Type::XOr_Gate || + !gate_contains (g, -g->lhs)); + // TODO: this would be nice to have! + // CADICAL_assert (g->tag != Gate_Type::ITE_Gate || (g->rhs.size() == 3 + // && g->rhs[1] != -g->lhs && g->rhs[2] != -g->lhs)); + // CADICAL_assert (table.count(g) == 1); + for (auto lit : g->rhs) { + CADICAL_assert (!internal->val (lit)); + CADICAL_assert (representative (lit) == lit); + } + } + } + for (Gate *g : table) { + if (g->garbage) + continue; + if (g->tag == Gate_Type::And_Gate) { + // CADICAL_assert (find_and_lits(g->arity, g->rhs)); + } + } + } +#endif + STOP (congruencemerge); + return propagated; +} + +std::string string_of_gate (Gate_Type t) { + switch (t) { + case Gate_Type::And_Gate: + return "And"; + case Gate_Type::XOr_Gate: + return "XOr"; + case Gate_Type::ITE_Gate: + return "ITE"; + default: + return "buggy"; + } +} + +void Closure::reset_closure () { + scheduled.clear (); + for (Gate *g : table) { + CADICAL_assert (g->indexed); + LOG (g, "deleting"); + if (!g->garbage) + delete g; + } + table.clear (); + + for (auto &occ : gtab) { + occ.clear (); + } + gtab.clear (); + + for (auto gate : garbage) + delete gate; + garbage.clear (); + + if (internal->lrat) { + CADICAL_assert (internal->proof); + for (auto c : extra_clauses) { + CADICAL_assert (!c->garbage); + internal->proof->delete_clause (c); + delete c; + } + extra_clauses.clear (); + } else { + CADICAL_assert (extra_clauses.empty ()); + } +} + +void Closure::reset_extraction () { + full_watching = true; + if (!internal->unsat && !internal->propagate ()) { + internal->learn_empty_clause (); + } + +#if 0 + // remove delete watched clauses from the watch list + for (auto v : internal->vars) { + for (auto sgn = -1; sgn <= 1; sgn += 2) { + const int lit = v * sgn; + auto &watchers = internal->watches (lit); + const size_t size = watchers.size (); + size_t j = 0; + for (size_t i = 0; i != size; ++i) { + const auto w = watchers[i]; + watchers[j] = watchers[i]; + if (!w.clause->garbage) + ++j; + } + watchers.resize(j); + } + } + // watch the remaining non-watched clauses + for (auto c : new_unwatched_binary_clauses) + internal->watch_clause (c); + new_unwatched_binary_clauses.clear(); + for (auto c : internal->clauses) { + if (c->garbage) + continue; + if (c->size != 2) + internal->watch_clause (c); + } +#else // simpler implementation + new_unwatched_binary_clauses.clear (); + internal->clear_watches (); + internal->connect_watches (); +#endif +} + +void Closure::forward_subsume_matching_clauses () { + START (congruencematching); + reset_closure (); + std::vector matchable; + matchable.resize (internal->max_var + 1); + size_t count_matchable = 0; + + for (auto idx : internal->vars) { + if (!internal->flags (idx).active ()) + continue; + const int lit = idx; + const int repr = find_representative (lit); + if (lit == repr) + continue; + const int repr_idx = abs (repr); + if (!matchable[idx]) { + LOG ("matchable %d", idx); + matchable[idx] = true; + count_matchable++; + } + + if (!matchable[repr_idx]) { + LOG ("matchable %d", repr_idx); + matchable[repr_idx] = true; + count_matchable++; + } + } + + LOG ("found %.0f%%", + (double) count_matchable / + (double) (internal->max_var ? internal->max_var : 1)); + std::vector candidates; + auto &analyzed = internal->analyzed; + + for (auto *c : internal->clauses) { + if (c->garbage) + continue; + if (c->redundant) + continue; + if (c->size == 2) + continue; + CADICAL_assert (analyzed.empty ()); + bool contains_matchable = false; + for (auto lit : *c) { + const signed char v = internal->val (lit); + if (v < 0) + continue; + if (v > 0) { + LOG (c, "mark satisfied"); + internal->mark_garbage (c); + break; + } + if (!contains_matchable) { + const int idx = abs (lit); + if (matchable[idx]) + contains_matchable = true; + } + + const int repr = find_representative (lit); + CADICAL_assert (!internal->val (repr)); + if (marked (repr)) + continue; + const int not_repr = -repr; + if (marked (not_repr)) { + LOG (c, "matches both %d and %d", (lit), (not_repr)); + internal->mark_garbage (c); + break; + } + marked (repr) = 1; + analyzed.push_back (repr); + } + + for (auto lit : analyzed) + marked (lit) = 0; + analyzed.clear (); + if (c->garbage) + continue; + if (!contains_matchable) { + LOG ("no matching variable"); + continue; + } + LOG (c, "candidate"); + candidates.push_back (c); + } + + rsort (begin (candidates), end (candidates), smaller_clause_size_rank ()); + size_t tried = 0, subsumed = 0; + internal->init_occs (); + for (auto c : candidates) { + CADICAL_assert (c->size != 2); + // TODO if terminated + ++tried; + if (find_subsuming_clause (c)) { + ++subsumed; + } + } + LOG ("[congruence] subsumed %.0f%%", + (double) subsumed / (double) (tried ? tried : 1)); + STOP (congruencematching); +} + +/*------------------------------------------------------------------------*/ +// Candidate clause 'subsumed' is subsumed by 'subsuming'. We need to copy +// the function because 'congruence' is too early to include the version +// from subsume + +void Closure::subsume_clause (Clause *subsuming, Clause *subsumed) { + // CADICAL_assert (!subsuming->redundant); + // CADICAL_assert (!subsumed->redundant); + auto &stats = internal->stats; + stats.subsumed++; + CADICAL_assert (subsuming->size <= subsumed->size); + LOG (subsumed, "subsumed"); + if (subsumed->redundant) + stats.subred++; + else + stats.subirr++; + if (subsumed->redundant || !subsuming->redundant) { + internal->mark_garbage (subsumed); + return; + } + LOG ("turning redundant subsuming clause into irredundant clause"); + subsuming->redundant = false; + if (internal->proof) + internal->proof->strengthen (subsuming->id); + internal->mark_garbage (subsumed); + stats.current.irredundant++; + stats.added.irredundant++; + stats.irrlits += subsuming->size; + CADICAL_assert (stats.current.redundant > 0); + stats.current.redundant--; + CADICAL_assert (stats.added.redundant > 0); + stats.added.redundant--; + // ... and keep 'stats.added.total'. +} + +bool Closure::find_subsuming_clause (Clause *subsumed) { + CADICAL_assert (!subsumed->garbage); + Clause *subsuming = nullptr; + for (auto lit : *subsumed) { + CADICAL_assert (internal->val (lit) <= 0); + const int repr_lit = find_representative (lit); + const signed char repr_val = internal->val (repr_lit); + CADICAL_assert (repr_val <= 0); + if (repr_val < 0) + continue; + if (marked (repr_lit)) + continue; + CADICAL_assert (!marked (-repr_lit)); + marked (repr_lit) = 1; + } + int least_occuring_lit = 0; + size_t count_least_occurring = INT_MAX; + LOG (subsumed, "trying to forward subsume"); + + for (auto lit : *subsumed) { + const int repr_lit = find_representative (lit); + const size_t count = internal->occs (lit).size (); + CADICAL_assert (count <= UINT_MAX); + if (count < count_least_occurring) { + count_least_occurring = count; + least_occuring_lit = repr_lit; + } + for (auto d : internal->occs (lit)) { + CADICAL_assert (!d->garbage); + CADICAL_assert (subsumed != d); + if (!subsumed->redundant && d->redundant) + continue; + for (auto other : *d) { + const signed char v = internal->val (other); + if (v < 0) + continue; + CADICAL_assert (!v); + const int repr_other = find_representative (other); + if (!marked (repr_other)) + goto CONTINUE_WITH_NEXT_CLAUSE; + LOG ("subsuming due to %d -> %d", other, repr_other); + } + subsuming = d; + goto FOUND_SUBSUMING; + + CONTINUE_WITH_NEXT_CLAUSE:; + } + } + CADICAL_assert (least_occuring_lit); + +FOUND_SUBSUMING: + for (auto lit : *subsumed) { + const int repr_lit = find_representative (lit); + const signed char v = internal->val (lit); + if (!v) + marked (repr_lit) = 0; + } + if (subsuming) { + LOG (subsumed, "subsumed"); + LOG (subsuming, "subsuming"); + subsume_clause (subsuming, subsumed); + ++internal->stats.congruence.subsumed; + return true; + } else { + internal->occs (least_occuring_lit).push_back (subsumed); + return false; + } +} + +/*------------------------------------------------------------------------*/ +static bool skip_ite_gate (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::ITE_Gate); + if (g->garbage) + return true; + return false; +} + +void Closure::produce_ite_merge_then_else_reasons ( + Gate *g, int src, int dst, std::vector &reasons_implication, + std::vector &reasons_back) { + CADICAL_assert (!g->garbage); + if (!internal->lrat) + return; + check_correct_ite_flags (g); + // no merge is happening actually + CADICAL_assert (g->rhs[1] == find_eager_representative(g->rhs[1]) || g->rhs[2] == find_eager_representative(g->rhs[2])); + if (find_eager_representative (g->lhs) == g->rhs[1] || find_eager_representative (g->lhs) == g->rhs[2]) + return; + if ((g->rhs[1] == src && g->lhs == dst && g->rhs[2] == g->lhs) || + (g->rhs[2] == src && g->lhs == dst && g->rhs[1] == g->lhs) || + (g->rhs[1] == -src && g->lhs == -dst && g->rhs[2] == g->lhs) || + (g->rhs[2] == -src && g->lhs == -dst && g->rhs[1] == g->lhs)) + return; + check_ite_lrat_reasons (g, false); + CADICAL_assert (g->rhs.size () == 3); + CADICAL_assert (src == g->rhs[1] || src == g->rhs[2]); + CADICAL_assert (dst == g->rhs[1] || dst == g->rhs[2]); + const int8_t flag = g->degenerated_ite; + CADICAL_assert (!ite_flags_no_then_clauses (flag)); // e = lhs: already merged + CADICAL_assert (!ite_flags_no_else_clauses (flag)); // t = lhs: already merged + produce_rewritten_clause_lrat (g->pos_lhs_ids, g->lhs, false); + if (ite_flags_neg_cond_lhs (flag)) { + LOG ("degenerated case with lhs = -cond"); + LOG (g->pos_lhs_ids[0].clause, "1:"); + LOG (g->pos_lhs_ids[1].clause, "2:"); + reasons_back.push_back (g->pos_lhs_ids[0].clause->id); + reasons_implication.push_back (g->pos_lhs_ids[1].clause->id); + return; + } + if (ite_flags_cond_lhs (flag)) { + LOG ("degenerated case with lhs = cond"); + CADICAL_assert (g->pos_lhs_ids[0].clause); + CADICAL_assert (g->pos_lhs_ids[3].clause); + reasons_back.push_back (g->pos_lhs_ids[3].clause->id); + reasons_implication.push_back (g->pos_lhs_ids[0].clause->id); + return; + } + reasons_implication.push_back (g->pos_lhs_ids[0].clause->id); + reasons_implication.push_back (g->pos_lhs_ids[2].clause->id); + reasons_back.push_back (g->pos_lhs_ids[1].clause->id); + reasons_back.push_back (g->pos_lhs_ids[3].clause->id); +} + +void Closure::rewrite_ite_gate_update_lrat_reasons (Gate *g, int src, + int dst) { + if (!internal->lrat) + return; + LOG (g, "updating lrat from"); + for (auto &litId : g->pos_lhs_ids) { + CADICAL_assert (litId.clause); + if (litId.current_lit == src) + litId.current_lit = dst; + if (litId.current_lit == -src) + litId.current_lit = -dst; + } + check_ite_lrat_reasons (g, false); +} + +bool Closure::rewrite_ite_gate_to_and ( + Gate *g, int src, int dst, size_t idx1, size_t idx2, + int cond_lit_to_learn_if_degenerated) { + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (!g->garbage); + LOG (g, "rewriting to proper AND"); + if (internal->val (g->lhs) > 0) { + { + const int lit = g->rhs[0]; + const char v = internal->val (lit); + if (v > 0) { + } else if (!v) { + if (internal->lrat) { + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[idx1].clause, + Rewrite (), lrat_chain); + } + learn_congruence_unit (cond_lit_to_learn_if_degenerated); + } else { + if (internal->lrat) + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[idx1].clause, + Rewrite (), lrat_chain); + push_lrat_unit (-lit); + internal->learn_empty_clause (); + return true; + } + } + if (!internal->unsat) { + const int lit = g->rhs[1]; + const char v = internal->val (lit); + CADICAL_assert (dst == g->rhs[0] || dst == g->rhs[1] || -dst == g->rhs[0] || + -dst == g->rhs[1]); + const int other = (dst == g->rhs[0] || dst == g->rhs[1]) + ? dst ^ g->rhs[0] ^ g->rhs[1] + : (-dst) ^ g->rhs[0] ^ g->rhs[1]; + if (v > 0) { + // already set by propagation + return true; + } else if (!v) { + if (internal->lrat) { + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[idx2].clause, + Rewrite (), lrat_chain); + } + learn_congruence_unit (other); + } else { + if (internal->lrat) { + push_lrat_unit (cond_lit_to_learn_if_degenerated); + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[idx2].clause, + Rewrite (), lrat_chain); + } + internal->learn_empty_clause (); + return true; + } + } + return true; + } + if (!internal->lrat) + return false; + LOG ("updating flags"); + g->degenerated_and_neg = (g->rhs[1] == -g->lhs || g->rhs[0] == -g->lhs); + g->degenerated_and_pos = (g->rhs[0] == g->lhs || g->rhs[1] == g->lhs); + CADICAL_assert (g->rhs.size () == 3); + CADICAL_assert (g->pos_lhs_ids.size () == 4); + CADICAL_assert (idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (idx2 < g->pos_lhs_ids.size ()); + int lit = g->pos_lhs_ids[idx2].current_lit, other = g->lhs; + // TODO: remove argument + (void) src; + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, g->lhs, idx1, + idx2, false); + + if ((idx1 == (size_t) -1 || idx2 == (size_t) -1)) { + // degenerated and gate + return false; + } + + CADICAL_assert (idx1 != (size_t) -1); + CADICAL_assert (idx2 != (size_t) -1); + CADICAL_assert (idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (idx2 < g->pos_lhs_ids.size ()); + Clause *c = g->pos_lhs_ids[idx1].clause; + CADICAL_assert (c->size == 2); + Clause *d = g->pos_lhs_ids[idx2].clause; + CADICAL_assert (c != d); + CADICAL_assert (c); + CADICAL_assert (d); + g->pos_lhs_ids.erase (std::remove_if (begin (g->pos_lhs_ids), + end (g->pos_lhs_ids), + [d] (const LitClausePair &p) { + return p.clause == d || !p.clause; + }), + end (g->pos_lhs_ids)); + CADICAL_assert (g->pos_lhs_ids.size () == 2); + CADICAL_assert (lit); + CADICAL_assert (other); + CADICAL_assert (lit != dst); + CADICAL_assert (other != dst); + CADICAL_assert (lit != other); + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + Clause *e = learn_binary_tmp_or_full_clause (lit, -other); + CADICAL_assert (e); + + auto long_clause = + std::find_if (begin (g->pos_lhs_ids), end (g->pos_lhs_ids), + [] (LitClausePair l) { return l.clause->size == 3; }); + CADICAL_assert (long_clause != end (g->pos_lhs_ids)); + LOG (long_clause->clause, "new long clause"); + g->neg_lhs_ids.push_back (*long_clause); + g->pos_lhs_ids.erase (long_clause); + + CADICAL_assert (g->pos_lhs_ids.size () == 1); + + (void) maybe_promote_tmp_binary_clause (g->pos_lhs_ids[0].clause); + g->pos_lhs_ids.push_back ({lit, e}); +#ifndef CADICAL_NDEBUG + for (auto litId : g->pos_lhs_ids) { + bool found = false; + CADICAL_assert (litId.clause); + for (auto other : *litId.clause) { + found = (find_eager_representative (other) == litId.current_lit); + if (found) + break; + } + CADICAL_assert (found); + } + for (auto id : g->pos_lhs_ids) { + LOG (id.clause, "clause after rewriting:"); + CADICAL_assert (id.clause->size == 2); + } + +#endif + return false; +} + +void Closure::produce_ite_merge_lhs_then_else_reasons ( + Gate *g, std::vector &reasons_implication, + std::vector &reasons_back, std::vector &reasons_unit, + bool rewritting_then, bool &learn_units) { + + const size_t idx1 = rewritting_then ? 0 : 2; + const size_t idx2 = idx1 + 1; + const size_t other_idx1 = rewritting_then ? 2 : 0; + const size_t other_idx2 = other_idx1 + 1; + const int cond_lit = g->rhs[0]; + const int lit_to_merge = g->rhs[rewritting_then ? 2 : 1]; + const int other_lit = g->rhs[rewritting_then ? 1 : 2]; + const int repr_cond_lit = find_eager_representative (g->rhs[0]); + const int repr_lit_to_merge = find_eager_representative (lit_to_merge); + const int repr_other_lit = find_eager_representative (other_lit); + const int repr_lhs = find_eager_representative(g->lhs); + if (!internal->proof) + return; + + + LOG ("cond: %d, merging %d and rewriting to %d", cond_lit, lit_to_merge, + other_lit); + if (internal->lrat) { + CADICAL_assert (internal->lrat); + CADICAL_assert (g->pos_lhs_ids.size () == 4); + + if (repr_lhs == -repr_other_lit) { + LOG ("special case: %s=%s, checking if other: %s %s", LOGLIT (g->lhs), + LOGLIT (-lit_to_merge), LOGLIT (cond_lit), LOGLIT (other_lit)); + CADICAL_assert (repr_lit_to_merge != -repr_lhs); // should have been rewritten before + + if (rewritting_then && repr_cond_lit == repr_lhs) { + LOG ("t=-lhs/c=lhs"); + learn_units = true; + // is a unit + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[0].clause, + Rewrite (), lrat_chain); + unsimplified.push_back (-cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + // don't bother finding out which one is used + reasons_implication.push_back (id_unit); + g->pos_lhs_ids[3].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[3].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[3].clause); + reasons_implication.push_back (g->pos_lhs_ids[3].clause->id); + unsimplified.clear (); + return; + } + if (!rewritting_then && repr_cond_lit == repr_lhs) { + LOG ("e=-lhs/c=lhs"); + learn_units = true; + // is a unit + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[3].clause, + Rewrite (), lrat_chain); + unsimplified.push_back (cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + // don't bother finding out which one is used + reasons_implication.push_back (id_unit); + g->pos_lhs_ids[0].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[0].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[0].clause); + reasons_implication.push_back (g->pos_lhs_ids[0].clause->id); + unsimplified.clear (); + return; + } + if (!rewritting_then && repr_cond_lit == -repr_lhs) { + LOG ("e=-lhs/c=-lhs"); + learn_units = true; + // TODO: this function does not work to produce units for this case + // c LOG 0 rewriting 4 by 3 in gate[42] (arity: 3) -3 := ITE 3 7 + // ... + // c LOG 0 clause[50] 4 -3 + // c LOG 0 clause[44] 5 3 + // c LOG 0 clause[2] -3 -4 -5 + // the first two are rewriting, but they are not ordered properly + // and we need the '5' clause to come after + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[2].clause, + Rewrite (), lrat_chain); + unsimplified.push_back (cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + g->pos_lhs_ids[1].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[1].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[1].clause); + + // don't bother finding out which one is used + reasons_implication.push_back (id_unit); + reasons_implication.push_back (g->pos_lhs_ids[1].clause->id); + unsimplified.clear (); + return; + } + if (rewritting_then && repr_cond_lit == -repr_lhs) { + LOG ("t=-lhs/c=-lhs"); + learn_units = true; + push_id_and_rewriting_lrat_unit (g->pos_lhs_ids[1].clause, + Rewrite (), lrat_chain); + unsimplified.push_back (-cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + g->pos_lhs_ids[2].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[2].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[2].clause); + + reasons_implication.push_back (id_unit); + reasons_implication.push_back (g->pos_lhs_ids[2].clause->id); + unsimplified.clear (); + return; + } + if (rewritting_then && repr_lit_to_merge == repr_lhs) { + LOG ("t=-lhs/e=lhs from rewriting then"); + learn_units = true; + g->pos_lhs_ids[idx1].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx1].clause, g->lhs, false); + g->pos_lhs_ids[idx2].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx2].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[idx1].clause); + CADICAL_assert (g->pos_lhs_ids[idx2].clause); + lrat_chain.push_back (g->pos_lhs_ids[idx1].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[idx2].clause->id); + unsimplified.push_back (-cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + unsimplified.clear (); + + return; + } + if (!rewritting_then && repr_lit_to_merge == repr_lhs) { + LOG ("t=-lhs/e=lhs from rewriting else"); + learn_units = true; + g->pos_lhs_ids[idx1].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx1].clause, g->lhs, false); + g->pos_lhs_ids[idx2].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx2].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[idx1].clause); + CADICAL_assert (g->pos_lhs_ids[idx2].clause); + lrat_chain.push_back (g->pos_lhs_ids[idx1].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[idx2].clause->id); + unsimplified.push_back (cond_lit); + LRAT_ID id_unit = simplify_and_add_to_proof_chain (unsimplified); + reasons_unit = {id_unit}; + unsimplified.clear (); + return; + } + + if (other_lit == repr_lhs) { + LOG ("TODO FIX ME t=-lhs/e=lhs"); + learn_units = true; + // in the other direction we are merging a literal with itself + g->pos_lhs_ids[idx1].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx1].clause, g->lhs, false); + g->pos_lhs_ids[idx2].clause = produce_rewritten_clause_lrat ( + g->pos_lhs_ids[idx2].clause, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids[idx1].clause); + CADICAL_assert (g->pos_lhs_ids[idx2].clause); + reasons_unit.push_back (g->pos_lhs_ids[idx1].clause->id); + reasons_unit.push_back (g->pos_lhs_ids[idx2].clause->id); + return; + } + // if (other_lit == -g->lhs) { + // CADICAL_assert (false); + // } + // if (other_lit == g->lhs) { + // CADICAL_assert (false); + // } + } + + LOG ("normal path"); + produce_rewritten_clause_lrat (g->pos_lhs_ids, g->lhs, false); + CADICAL_assert (g->pos_lhs_ids.size () == 4); + + reasons_unit.push_back (g->pos_lhs_ids[idx1].clause->id); + reasons_unit.push_back (g->pos_lhs_ids[idx2].clause->id); + + // already merged: only unit is important + if (!rewritting_then && repr_lhs == repr_lit_to_merge) { + return; + } + + reasons_implication.push_back (g->pos_lhs_ids[other_idx1].clause->id); + reasons_implication.push_back (g->pos_lhs_ids[idx1].clause->id); + reasons_implication.push_back (g->pos_lhs_ids[idx2].clause->id); + + reasons_back.push_back (g->pos_lhs_ids[other_idx2].clause->id); + reasons_back.push_back (g->pos_lhs_ids[idx1].clause->id); + reasons_back.push_back (g->pos_lhs_ids[idx2].clause->id); + } else { + LOG ("learn extra clauses XXXXXXXXXXXXXXXXXXXXXXXXX"); + const int lhs = g->lhs; + const int cond = g->rhs[0]; + if (rewritting_then) { + unsimplified.push_back (-cond); + unsimplified.push_back (lhs); + simplify_and_add_to_proof_chain (unsimplified); + unsimplified.push_back (-cond); + unsimplified.push_back (-lhs); + simplify_and_add_to_proof_chain (unsimplified); + } else { + unsimplified.push_back (cond); + unsimplified.push_back (-lhs); + simplify_and_add_to_proof_chain (unsimplified); + unsimplified.push_back (cond); + unsimplified.push_back (lhs); + simplify_and_add_to_proof_chain (unsimplified); + } + unsimplified.clear (); + } +} + +void Closure::rewrite_ite_gate (Gate *g, int dst, int src) { + CADICAL_assert (unsimplified.empty ()); + if (skip_ite_gate (g)) + return; + if (!gate_contains (g, src)) + return; + LOG (g, "rewriting %d by %d in", src, dst); + CADICAL_assert (!g->shrunken); + CADICAL_assert (g->rhs.size () == 3); + CADICAL_assert (!internal->lrat || g->pos_lhs_ids.size () == 4); + auto &rhs = g->rhs; + const int lhs = g->lhs; + const int cond = g->rhs[0]; + const int then_lit = g->rhs[1]; + const int else_lit = g->rhs[2]; + const int not_lhs = -(lhs); + const int not_dst = -(dst); + const int not_cond = -(cond); + const int not_then_lit = -(then_lit); + const int not_else_lit = -(else_lit); + Gate_Type new_tag = Gate_Type::And_Gate; + + bool garbage = false; + bool shrink = true; + const auto git = g->indexed ? table.find (g) : end (table); + CADICAL_assert (!g->indexed || git != end (table)); + CADICAL_assert (*git == g); + if (internal->val (cond) && internal->val (then_lit) && + internal->val (else_lit)) { // propagation has set all value anyway + LOG (g, "all values are set"); + CADICAL_assert (internal->val (g->lhs)); + garbage = true; + } else if (internal->val (g->lhs) && internal->val (cond)) { + LOG (g, "all values are set 2"); + CADICAL_assert (internal->val (g->lhs)); + garbage = true; + } + // this code is taken one-to-one from kissat + else if (src == cond) { + if (dst == then_lit) { + // then_lit ? then_lit : else_lit + // then_lit & then_lit | !then_lit & else_lit + // then_lit | !then_lit & else_lit + // then_lit | else_lit + // !(!then_lit & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_then_lit; + rhs[1] = not_else_lit; + if (then_lit == lhs || else_lit == lhs) + garbage = true; + else + garbage = rewrite_ite_gate_to_and (g, src, dst, 1, 3, -dst); + } else if (not_dst == then_lit) { + // !then_lit ? then_lit : else_lit + // !then_lit & then_lit | then_lit & else_lit + // then_lit & else_lit + rhs[0] = else_lit; + CADICAL_assert (rhs[1] == then_lit); + garbage = rewrite_ite_gate_to_and (g, src, dst, 0, 2, -dst); + } else if (dst == else_lit) { + // else_list ? then_lit : else_lit + // else_list & then_lit | !else_list & else_lit + // else_list & then_lit + rhs[0] = else_lit; + CADICAL_assert (rhs[1] == then_lit); + garbage = rewrite_ite_gate_to_and (g, src, dst, 2, 0, dst); + } else if (not_dst == else_lit) { + // !else_list ? then_lit : else_lit + // !else_list & then_lit | else_lit & else_lit + // !else_list & then_lit | else_lit + // then_lit | else_lit + // !(!then_lit & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_then_lit; + rhs[1] = not_else_lit; + if (then_lit == lhs || else_lit == lhs) + garbage = true; + else + garbage = rewrite_ite_gate_to_and (g, src, dst, 3, 1, dst); + } else { + shrink = false; + rhs[0] = dst; + rewrite_ite_gate_update_lrat_reasons (g, src, dst); + } + } else if (src == then_lit) { + if (not_dst == g->lhs) { // TODO not in kissat + rhs[1] = dst; + check_ite_implied (g->lhs, cond, then_lit, else_lit); + std::vector reasons_implication, reasons_back, reasons_unit; + LOG ("%d = %d ?", g->lhs, -g->rhs[0]); + bool learn_units_instead_of_equivalence = false; + produce_ite_merge_lhs_then_else_reasons ( + g, reasons_implication, reasons_back, reasons_unit, true, + learn_units_instead_of_equivalence); + if (learn_units_instead_of_equivalence) { // it is too hard to produce + // LRAT chains + // in this case + + if (internal->lrat) + lrat_chain = reasons_unit; + learn_congruence_unit (-cond, true); + if (-else_lit == lhs) { + if (internal->lrat) + lrat_chain = reasons_implication; + learn_congruence_unit (cond == -lhs ? -else_lit : else_lit, false, true); + } else fully_propagate (); + } else { + if (merge_literals_lrat (g->lhs, else_lit, reasons_implication, + reasons_back)) { + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_ites; + } + if (!internal->unsat) { + if (internal->lrat) + lrat_chain = reasons_unit; + learn_congruence_unit (-cond); + } + } + delete_proof_chain (); + garbage = true; + } else if (dst == cond) { + // cond ? cond : else_lit + // cond & cond | !cond & else_lit + // cond | !cond & else_lit + // cond | else_lit + // !(!cond & !else_lit) + g->lhs = not_lhs; + rhs[0] = not_cond; + rhs[1] = not_else_lit; + if (else_lit == lhs || cond == lhs) + garbage = true; + else + garbage = rewrite_ite_gate_to_and (g, src, dst, 1, 3, -cond); + } else if (not_dst == cond) { + // cond ? !cond : else_lit + // cond & !cond | !cond & else_lit + // !cond & else_lit + rhs[0] = not_cond; + rhs[1] = else_lit; + garbage = rewrite_ite_gate_to_and (g, src, dst, 0, 2, -cond); + } else if (dst == else_lit) { + // cond ? else_lit : else_lit + // else_lit + std::vector reasons_implication, reasons_back; + produce_ite_merge_then_else_reasons (g, src, dst, reasons_implication, + reasons_back); + if (merge_literals_lrat (lhs, else_lit, reasons_implication, + reasons_back)) { + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_ites; + } + delete_proof_chain (); + garbage = true; + } else if (not_dst == else_lit) { + // cond ? !else_lit : else_lit + // cond & !else_lit | !cond & else_lit + // cond ^ else_lit + if (g->lhs == cond) { + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, g->lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (-else_lit); + garbage = true; + } else { + LOG ("changing to xor"); + new_tag = Gate_Type::XOr_Gate; + CADICAL_assert (rhs[0] == cond); + rhs[1] = else_lit; + CADICAL_assert (!internal->lrat || !g->pos_lhs_ids.empty ()); + { +#ifdef LOGGING + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, "%d ->", litId.current_lit); + } +#endif + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, g->lhs, + true); +#ifdef LOGGING + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, "%d ->", litId.current_lit); + } +#endif + } + } + } else { + shrink = false; + rhs[1] = dst; + rewrite_ite_gate_update_lrat_reasons (g, src, dst); + } + } else { + CADICAL_assert (src == else_lit); + if (not_dst == g->lhs) { // TODO not in kissat + rhs[2] = dst; + std::vector reasons_implication, reasons_back, reasons_unit; + bool learn_units_instead_of_equivalence = false; + produce_ite_merge_lhs_then_else_reasons ( + g, reasons_implication, reasons_back, reasons_unit, false, + learn_units_instead_of_equivalence); + if (learn_units_instead_of_equivalence) { // Too hard to produce LRAT + if (internal->lrat) + lrat_chain = reasons_unit; + learn_congruence_unit (cond, true); + if (then_lit != lhs) { + LOG ("special case, learning %d",cond == -lhs ? -then_lit : then_lit); + if (internal->lrat) + lrat_chain = reasons_implication; + learn_congruence_unit (cond == -lhs ? -then_lit : then_lit, false, true); + } else fully_propagate (); + } else { + if (merge_literals_lrat (lhs, then_lit, reasons_implication, + reasons_back)) { + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_ites; + } + if (!internal->unsat) { + if (internal->lrat) + lrat_chain = reasons_unit; + learn_congruence_unit (cond); + } + } + delete_proof_chain (); + garbage = true; + } else if (dst == cond) { + // cond ? then_lit : cond + // cond & then_lit | !cond & cond + // cond & then_lit + CADICAL_assert (rhs[0] == cond); + CADICAL_assert (rhs[1] == then_lit); + garbage = rewrite_ite_gate_to_and (g, src, dst, 2, 0, cond); + } else if (not_dst == cond) { + // cond ? then_lit : !cond + // cond & then_lit | !cond & !cond + // cond & then_lit | !cond + // then_lit | !cond + // !(!then_lit & cond) + g->lhs = not_lhs; + CADICAL_assert (rhs[0] == cond); + rhs[1] = not_then_lit; + if (then_lit == lhs || cond == lhs) + garbage = true; + else + garbage = rewrite_ite_gate_to_and (g, src, dst, 3, 1, cond); + } else if (dst == then_lit) { + // cond ? then_lit : then_lit + // then_lit + std::vector reasons_implication, reasons_back; + produce_ite_merge_then_else_reasons (g, src, dst, reasons_implication, + reasons_back); + if (merge_literals_lrat (lhs, then_lit, reasons_implication, + reasons_back)) { + ++internal->stats.congruence.unaries; + ++internal->stats.congruence.unary_ites; + } + garbage = true; + } else if (not_dst == then_lit) { + // cond ? then_lit : !then_lit + // cond & then_lit | !cond & !then_lit + // !(cond ^ then_lit) + if (lhs == cond) { + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (then_lit); + garbage = true; + } else if (not_lhs == cond) { + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (-then_lit); + garbage = true; + } else if (not_lhs == then_lit) { + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (cond); + garbage = true; + } else { + new_tag = Gate_Type::XOr_Gate; + g->lhs = not_lhs; + CADICAL_assert (rhs[0] == cond); + CADICAL_assert (rhs[1] == then_lit); + CADICAL_assert (rhs[0] != g->lhs); + CADICAL_assert (rhs[1] != g->lhs); + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + } + } else { + shrink = false; + rhs[2] = dst; + rewrite_ite_gate_update_lrat_reasons (g, src, dst); + } + } + if (!garbage && !internal->unsat) { + CADICAL_assert (new_tag != Gate_Type::ITE_Gate || g->lhs != -rhs[1]); + CADICAL_assert (new_tag != Gate_Type::ITE_Gate || g->lhs != -rhs[2]); + if (shrink) { + if (new_tag == Gate_Type::XOr_Gate) { + bool negate_lhs = false; + if (rhs[0] < 0) { + rhs[0] = -rhs[0]; + negate_lhs = !negate_lhs; + } + if (rhs[1] < 0) { + rhs[1] = -rhs[1]; + negate_lhs = !negate_lhs; + } + if (negate_lhs) + g->lhs = -g->lhs; + } + if (internal->vlit (rhs[0]) > + internal->vlit ( + rhs[1])) { // unlike kissat, we need to do it after negating + std::swap (rhs[0], rhs[1]); + CADICAL_assert (new_tag != Gate_Type::ITE_Gate); + } + CADICAL_assert (internal->vlit (rhs[0]) < internal->vlit (rhs[1])); + CADICAL_assert (!g->shrunken); + g->shrunken = true; + rhs[2] = 0; + g->tag = new_tag; + rhs.resize (2); + CADICAL_assert (rhs[0] != -rhs[1]); + if (new_tag == Gate_Type::XOr_Gate) { + if (rhs[0] == -g->lhs || rhs[1] == -g->lhs) { + LOG (g, "special XOR:"); + const int unit = rhs[0] ^ -g->lhs ^ rhs[1]; + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (unit); + garbage = true; + } else if (rhs[0] == g->lhs || rhs[1] == g->lhs) { + LOG (g, "special XOR:"); + const int unit = rhs[0] ^ g->lhs ^ rhs[1]; + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, not_lhs, + false); + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids.size () == 2); + lrat_chain.push_back (g->pos_lhs_ids[0].clause->id); + lrat_chain.push_back (g->pos_lhs_ids[1].clause->id); + } + learn_congruence_unit (-unit); + garbage = true; + } else { + int i = 0; + bool negated = false; + for (int j = 0; j < 2; ++i, ++j) { + CADICAL_assert (i <= j); + const int lit = rhs[i] = rhs[j]; + const char v = internal->val (lit); + if (v > 0) { + --i; + negated = !negated; + } else if (v < 0) { + --i; + } + } + CADICAL_assert (i <= 2); + rhs.resize (i); + if (negated) { + g->lhs = -g->lhs; + } + if (i != 2) { + LOG (g, "removed units"); + } + if (!i) + garbage = true; + else if (i == 1) { +#ifdef LOGGING + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, "%d ->", litId.current_lit); + } +#endif + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, g->lhs); + CADICAL_assert (!internal->lrat || g->pos_lhs_ids.size () == 2); + Clause *c1 = nullptr, *c2 = nullptr; + if (internal->lrat) { + CADICAL_assert (g->pos_lhs_ids[0].clause); + bool rhs_as_src_first = + g->pos_lhs_ids[0].clause->literals[0] == g->lhs || + g->pos_lhs_ids[0].clause->literals[1] == g->lhs; + c1 = (rhs_as_src_first ? g->pos_lhs_ids[0].clause + : g->pos_lhs_ids[1].clause); + c2 = (rhs_as_src_first ? g->pos_lhs_ids[1].clause + : g->pos_lhs_ids[0].clause); + c1 = maybe_promote_tmp_binary_clause (c1); + c2 = maybe_promote_tmp_binary_clause (c2); + } else { + maybe_add_binary_clause (-g->lhs, g->rhs[0]); + maybe_add_binary_clause (g->lhs, -g->rhs[0]); + } + merge_literals_equivalence (g->lhs, g->rhs[0], c1, c2); + garbage = true; + } + } + if (!garbage) { + CADICAL_assert (rhs[0] != g->lhs); + CADICAL_assert (rhs[1] != g->lhs); + CADICAL_assert (rhs[0] != -g->lhs); + CADICAL_assert (rhs[1] != -g->lhs); + } + } + + if (!garbage) { + g->hash = hash_lits (nonces, g->rhs); + LOG (g, "rewritten"); + + if (internal->lrat) { + if (new_tag == Gate_Type::XOr_Gate) { +#ifndef CADICAL_NDEBUG + std::for_each (begin (g->pos_lhs_ids), end (g->pos_lhs_ids), + [g] (LitClausePair l) { + CADICAL_assert ((size_t) l.clause->size == + 1 + g->arity ()); + }); +#endif + } else if (new_tag == Gate_Type::And_Gate) { + // we have to get rid of one clause, two become binaries, and + // becomes ternary + +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + for (auto id : g->pos_lhs_ids) { + LOG (id.clause, "clause after rewriting:"); + CADICAL_assert (id.clause->size == 2); + } +#endif + CADICAL_assert (g->pos_lhs_ids.size () == 2 || + gate_contains (g, g->lhs)); + CADICAL_assert (g->neg_lhs_ids.size () == 1 || + gate_contains (g, g->lhs)); + CADICAL_assert (g->arity () == 2); +#ifndef CADICAL_NDEBUG + std::for_each ( + begin (g->pos_lhs_ids), end (g->pos_lhs_ids), + [] (LitClausePair l) { CADICAL_assert (l.clause->size == 2); }); +#endif + } else { + CADICAL_assert (false); +#ifdef WIN32 + __assume(false); +#else + __builtin_unreachable (); +#endif + } + } + Gate *h; + if (new_tag == Gate_Type::And_Gate) { + check_and_gate_implied (g); + h = find_and_lits (rhs); + } else { + CADICAL_assert (new_tag == Gate_Type::XOr_Gate); + check_xor_gate_implied (g); + h = find_xor_gate (g); + } + if (h) { + garbage = true; + if (new_tag == Gate_Type::XOr_Gate) { + std::vector reasons_implication, reasons_back; + add_xor_matching_proof_chain (g, g->lhs, h->pos_lhs_ids, h->lhs, + reasons_implication, + reasons_back); + if (merge_literals_lrat (g->lhs, h->lhs, reasons_implication, + reasons_back)) + ++internal->stats.congruence.xors; + } else { + add_ite_turned_and_binary_clauses (g); + std::vector reasons_implication, reasons_back; + if (internal->lrat) + merge_and_gate_lrat_produce_lrat (g, h, reasons_implication, + reasons_back, false); + if (merge_literals_lrat (g->lhs, h->lhs, reasons_implication, + reasons_back)) + ++internal->stats.congruence.ands; + } + delete_proof_chain (); + } else { + garbage = false; + if (g->indexed) + remove_gate (git); + index_gate (g); + CADICAL_assert (g->arity () == 2); + for (auto lit : g->rhs) + if (lit != dst) + if (lit != cond && lit != then_lit && lit != else_lit) + connect_goccs (g, lit); + if (g->tag == Gate_Type::And_Gate && !internal->lrat) + for (auto lit : g->rhs) + maybe_add_binary_clause (-g->lhs, lit); + } + } + } else { + LOG (g, "rewritten"); + if (internal->lrat) + update_ite_flags (g), check_correct_ite_flags(g); + CADICAL_assert (rhs[0] != rhs[1]); + CADICAL_assert (rhs[0] != rhs[2]); + CADICAL_assert (rhs[1] != rhs[2]); + CADICAL_assert (rhs[0] != -(rhs[1])); + CADICAL_assert (rhs[0] != -(rhs[2])); + CADICAL_assert (rhs[1] != -(rhs[2])); + check_ite_gate_implied (g); + check_ite_lrat_reasons (g, false); + bool negate_lhs; + Gate *h = find_ite_gate (g, negate_lhs); + CADICAL_assert (lhs == g->lhs); + CADICAL_assert (not_lhs == -(g->lhs)); + if (negate_lhs) + g->lhs = -lhs; + check_ite_lrat_reasons (g); + if (internal->lrat) + check_correct_ite_flags (g); + if (h) { + garbage = true; + check_ite_gate_implied (g); + check_ite_lrat_reasons (g, false); + check_ite_gate_implied (h); + check_ite_lrat_reasons (h, false); + int normalized_lhs = negate_lhs ? not_lhs : lhs; + std::vector extra_reasons_lit, extra_reasons_ulit; + add_ite_matching_proof_chain (g, h, normalized_lhs, h->lhs, + extra_reasons_lit, + extra_reasons_ulit); + if (merge_literals_lrat (normalized_lhs, h->lhs, extra_reasons_lit, + extra_reasons_ulit)) + ++internal->stats.congruence.ites; + delete_proof_chain (); + CADICAL_assert (internal->unsat || chain.empty ()); + } else { + garbage = false; + if (g->indexed) + remove_gate (git); + LOG (g, "normalized"); + g->hash = hash_lits (nonces, g->rhs); + index_gate (g); + CADICAL_assert (g->arity () == 3); + for (auto lit : g->rhs) + if (lit != dst) + if (lit != cond && lit != then_lit && lit != else_lit) + connect_goccs (g, lit); + } + } + } + if (garbage && !internal->unsat) + mark_garbage (g); + + CADICAL_assert (chain.empty ()); +} + +void Closure::simplify_ite_gate_produce_unit_lrat (Gate *g, int lit, + size_t idx1, + size_t idx2) { + if (!internal->lrat) + return; + // TODO + if (internal->val (lit) > 0) + return; + CADICAL_assert (internal->lrat); + CADICAL_assert (g); + CADICAL_assert (idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (idx2 < g->pos_lhs_ids.size ()); + CADICAL_assert (g->pos_lhs_ids.size () == 4); + + CADICAL_assert (idx1 != idx2); + Clause *c = g->pos_lhs_ids[idx1].clause; + Clause *d = g->pos_lhs_ids[idx2].clause; + + if (g->lhs == -g->rhs[0]) { + LOG ("special case of LHS=-cond where only one clause in LRAT is needed is needed"); + size_t idx = (internal->val (g->rhs[1]) < 0 ? idx2 : idx1); + c = produce_rewritten_clause_lrat (g->pos_lhs_ids[idx].clause, g->lhs, false, false); + CADICAL_assert (c); + // not possible to do this in a single lrat chain + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), g->lhs); + CADICAL_assert (d); + return; + } + if (g->lhs == g->rhs[0]) { + LOG ("special case of LHS=cond where only one clause in LRAT is needed is needed"); + size_t idx = (internal->val (g->rhs[1]) > 0 ? idx2 : idx1); + c = produce_rewritten_clause_lrat (g->pos_lhs_ids[idx].clause, g->lhs, false, false); + CADICAL_assert (c); + // not possible to do this in a single lrat chain + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain, true, + Rewrite (), g->lhs); + CADICAL_assert (d); + return; + } + + c = produce_rewritten_clause_lrat (c, g->lhs, true); + if (c) { + lrat_chain.push_back (c->id); + d = produce_rewritten_clause_lrat (d, g->lhs, true); + if (d) + lrat_chain.push_back (d->id); + } else if (!c) { + push_id_and_rewriting_lrat_unit (d, Rewrite (), lrat_chain, true, + Rewrite (), g->lhs); + CADICAL_assert (d); + lrat_chain.push_back (d->id); + } +} + +// TODO merge +void Closure::merge_and_gate_lrat_produce_lrat ( + Gate *g, Gate *h, std::vector &reasons_lrat_src, + std::vector &reasons_lrat_usrc, bool remove_units) { + CADICAL_assert (internal->lrat); + CADICAL_assert (g->tag == Gate_Type::And_Gate); + CADICAL_assert (h->tag == Gate_Type::And_Gate); + CADICAL_assert (g->neg_lhs_ids.size () <= 1); + update_and_gate_build_lrat_chain (g, h, reasons_lrat_src, + reasons_lrat_usrc, remove_units); +} + +// odd copy of rewrite_ite_gate_lrat_and +bool Closure::simplify_ite_gate_to_and (Gate *g, size_t idx1, size_t idx2, + int removed_lit) { + CADICAL_assert (internal->lrat_chain.empty ()); + CADICAL_assert (g->rhs.size () == 3); +#ifdef LOGGING + for (auto litId : g->pos_lhs_ids) { + LOG (litId.clause, "%d ->", litId.current_lit); + } +#endif + if (g->lhs == -g->rhs[0] || g->lhs == -g->rhs[1]) { + if (internal->lrat) { + Clause *c = g->pos_lhs_ids[idx1].clause; + push_id_and_rewriting_lrat_unit (c, Rewrite (), lrat_chain); + CADICAL_assert (!lrat_chain.empty ()); + } + learn_congruence_unit (-g->lhs); + return true; + } + if (!internal->lrat) + return false; + g->degenerated_and_neg = (g->degenerated_and_neg || g->rhs[1] == -g->lhs || g->rhs[0] == -g->lhs); + g->degenerated_and_pos = (g->degenerated_and_pos || g->rhs[0] == g->lhs || g->rhs[1] == g->lhs); + + CADICAL_assert (g->pos_lhs_ids.size () == 4); + CADICAL_assert (idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (idx2 < g->pos_lhs_ids.size ()); + int lit = g->pos_lhs_ids[idx2].current_lit, other = g->lhs; + size_t new_idx1 = idx1; + size_t new_idx2 = idx2; + produce_rewritten_clause_lrat_and_clean (g->pos_lhs_ids, g->lhs, new_idx1, + new_idx2); + + if (g->pos_lhs_ids.size () == 1) { + LOG (g, "degenerated AND gate"); + const int replacement_lit = (g->rhs[1] == g->lhs ? g->rhs[0] : g->rhs[1]); + for (auto &litId : g->pos_lhs_ids) { + CADICAL_assert (litId.clause); + LOG (litId.clause, "%d ->", litId.current_lit); + if (litId.current_lit == removed_lit) + litId.current_lit = -replacement_lit; + if (litId.current_lit == -removed_lit) + litId.current_lit = replacement_lit; + LOG (litId.clause, "%d ->", litId.current_lit); + // TODO we need a replacement index + CADICAL_assert (std::find (begin (*litId.clause), end (*litId.clause), litId.current_lit) != + end (*litId.clause)); + CADICAL_assert (std::find (begin (g->rhs), end (g->rhs), litId.current_lit) != + end (g->rhs)); + } + return false; + } + CADICAL_assert (new_idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (new_idx2 < g->pos_lhs_ids.size ()); + Clause *c = g->pos_lhs_ids[new_idx1].clause; + CADICAL_assert (c->size == 2); + CADICAL_assert (new_idx1 != new_idx2); + Clause *d = g->pos_lhs_ids[new_idx2].clause; + CADICAL_assert (c != d); + CADICAL_assert (c); + CADICAL_assert (d); + g->pos_lhs_ids.erase (std::remove_if (begin (g->pos_lhs_ids), + end (g->pos_lhs_ids), + [d] (const LitClausePair &p) { + return p.clause == d; + }), + end (g->pos_lhs_ids)); + CADICAL_assert (g->pos_lhs_ids.size () == 2); + CADICAL_assert (lit); + CADICAL_assert (other); + CADICAL_assert (lit != other); + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + Clause *e = add_tmp_binary_clause (lit, -other); + + auto long_clause = + std::find_if (begin (g->pos_lhs_ids), end (g->pos_lhs_ids), + [] (LitClausePair l) { return l.clause->size == 3; }); + CADICAL_assert (long_clause != end (g->pos_lhs_ids)); + LOG (long_clause->clause, "new long clause"); + g->neg_lhs_ids.push_back (*long_clause); + g->pos_lhs_ids.erase (long_clause); + CADICAL_assert (g->pos_lhs_ids.size () == 1); + g->pos_lhs_ids.push_back ({lit, e}); + + const int first_lit = g->rhs[0]; + const int second_lit = g->rhs[1]; + for (auto &litId : g->pos_lhs_ids) { + CADICAL_assert (litId.clause); + LOG (litId.clause, "%s ->", LOGLIT (litId.current_lit)); + if (internal->val (litId.current_lit)) { + CADICAL_assert (litId.clause->size == 2); + int replacement_lit = 0; + for (int i = 0; i < 2; ++i) { + if (litId.clause->literals[i] == first_lit) { + replacement_lit = first_lit; + break; + } else if (litId.clause->literals[i] == second_lit) { + replacement_lit = second_lit; + break; + } + } + CADICAL_assert (replacement_lit); + + litId.current_lit = replacement_lit; + } else if (litId.current_lit == removed_lit) + litId.current_lit = -g->rhs[0]; + else if (litId.current_lit == -removed_lit) + litId.current_lit = g->rhs[0]; + LOG (litId.clause, "%d ->", litId.current_lit); + // TODO we need a replacement index + CADICAL_assert (std::find (begin (g->rhs), end (g->rhs), litId.current_lit) != + end (g->rhs)); + CADICAL_assert (std::find (begin (*litId.clause), end (*litId.clause), + litId.current_lit) != end (*litId.clause)); + } + return false; +} + +void Closure::merge_ite_gate_same_then_else_lrat ( + std::vector &clauses, + std::vector &reasons_implication, + std::vector &reasons_back) { + CADICAL_assert (clauses.size () == 4); + produce_rewritten_clause_lrat_and_clean (clauses); + CADICAL_assert (clauses.size () == 4); + auto then_imp = clauses[0]; + auto neg_then_imp = clauses[1]; + auto else_imp = clauses[2]; + auto neg_else_imp = clauses[3]; + reasons_implication.push_back (then_imp.clause->id); + reasons_implication.push_back (else_imp.clause->id); + reasons_back.push_back (neg_then_imp.clause->id); + reasons_back.push_back (neg_else_imp.clause->id); +} + +void Closure::simplify_ite_gate_then_else_set ( + Gate *g, std::vector &reasons_implication, + std::vector &reasons_back, size_t idx1, size_t idx2) { + CADICAL_assert (idx1 < g->pos_lhs_ids.size ()); + CADICAL_assert (idx2 < g->pos_lhs_ids.size ()); + Clause *c = g->pos_lhs_ids[idx1].clause; + Clause *d = g->pos_lhs_ids[idx2].clause; + push_id_and_rewriting_lrat_unit (c, Rewrite (), reasons_back, true, + Rewrite (), g->lhs); + push_id_and_rewriting_lrat_unit (d, Rewrite (), reasons_implication, true, + Rewrite (), g->lhs); + LOG (reasons_back, "LRAT"); + LOG (reasons_implication, "LRAT"); + // c = produce_rewritten_clause_lrat (c, g->lhs, false); + // CADICAL_assert (c); + // d = produce_rewritten_clause_lrat (d, g->lhs, false); + // CADICAL_assert (d); + // const int cond = g->rhs[0]; + // CADICAL_assert (internal->val (cond)); + // reasons_implication.push_back(internal->unit_id (internal->val (cond) > + // 0 ? cond : -cond)); reasons_implication.push_back(c->id); + // reasons_back.push_back(internal->unit_id (internal->val (cond) > 0 ? + // cond : -cond)); reasons_back.push_back(d->id); +} + +void Closure::simplify_ite_gate_condition_set ( + Gate *g, std::vector &reasons_lrat, + std::vector &reasons_back_lrat, size_t idx1, size_t idx2) { + CADICAL_assert (internal->lrat); + Clause *c = g->pos_lhs_ids[idx1].clause; + Clause *d = g->pos_lhs_ids[idx2].clause; +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + const int cond = g->rhs[0]; + CADICAL_assert (internal->val (cond)); + LOG ("cond = %d", cond); +#endif +#ifdef LOGGING + for (auto litid : g->pos_lhs_ids) + LOG (litid.clause, "clause in gate:"); +#endif + push_id_and_rewriting_lrat_unit (c, Rewrite (), reasons_lrat, true, + Rewrite (), -g->lhs); + push_id_and_rewriting_lrat_unit (d, Rewrite (), reasons_back_lrat, true, + Rewrite (), g->lhs); +} + +void Closure::simplify_ite_gate (Gate *g) { + if (skip_ite_gate (g)) + return; + LOG (g, "simplifying"); + CADICAL_assert (g->arity () == 3); + bool garbage = true; + int lhs = g->lhs; + auto &rhs = g->rhs; + const int cond = rhs[0]; + const int then_lit = rhs[1]; + const int else_lit = rhs[2]; + const signed char v_cond = internal->val (cond); + const signed char v_else = internal->val (else_lit); + const signed char v_then = internal->val (then_lit); + std::vector reasons_lrat, reasons_back_lrat; + if (v_cond && v_else && v_then) { // propagation has set all value anyway + LOG (g, "all values are set"); + CADICAL_assert (internal->val (g->lhs)); + garbage = true; + } else if (v_cond > 0) { + if (internal->lrat) + simplify_ite_gate_condition_set (g, reasons_lrat, reasons_back_lrat, + 0, 1); + if (merge_literals_lrat (lhs, then_lit, reasons_lrat, + reasons_back_lrat)) { + ++internal->stats.congruence.unary_ites; + ++internal->stats.congruence.unaries; + } + } else if (v_cond < 0) { + if (internal->lrat) + simplify_ite_gate_condition_set (g, reasons_lrat, reasons_back_lrat, + 2, 3); + if (merge_literals_lrat (lhs, else_lit, reasons_lrat, + reasons_back_lrat)) { + ++internal->stats.congruence.unary_ites; + ++internal->stats.congruence.unaries; + } + } else { + LOG ("then %d: %d; else %d: %d", then_lit, v_then, else_lit, v_else); + std::vector extra_reasons, extra_reasons_back; + CADICAL_assert (v_then || v_else); + if (v_then > 0 && v_else > 0) { + simplify_ite_gate_produce_unit_lrat (g, lhs, 1, 3); + learn_congruence_unit (lhs); + } else if (v_then < 0 && v_else < 0) { + simplify_ite_gate_produce_unit_lrat (g, -lhs, 0, 2); + learn_congruence_unit (-lhs); + } else if (v_then > 0 && v_else < 0) { + if (internal->lrat) + simplify_ite_gate_then_else_set (g, extra_reasons, + extra_reasons_back, 1, 2); + if (merge_literals_lrat (lhs, cond, extra_reasons, + extra_reasons_back)) { + ++internal->stats.congruence.unary_ites; + ++internal->stats.congruence.unaries; + } + } else if (v_then < 0 && v_else > 0) { + if (internal->lrat) + simplify_ite_gate_then_else_set (g, extra_reasons, + extra_reasons_back, 0, 3); + if (merge_literals_lrat (lhs, -cond, extra_reasons_back, + extra_reasons)) { + ++internal->stats.congruence.unary_ites; + ++internal->stats.congruence.unaries; + } + } else { + CADICAL_assert (!!v_then + !!v_else == 1); + auto git = g->indexed ? table.find (g) : end (table); + CADICAL_assert (!g->indexed || git != end (table)); + if (v_then > 0) { + g->lhs = -lhs; + rhs[0] = -cond; + rhs[1] = -else_lit; + simplify_ite_gate_to_and (g, 1, 3, then_lit); + } else if (v_then < 0) { + rhs[0] = -cond; + rhs[1] = else_lit; + simplify_ite_gate_to_and (g, 0, 2, -then_lit); + } else if (v_else > 0) { + g->lhs = -lhs; + rhs[0] = -then_lit; + rhs[1] = cond; + simplify_ite_gate_to_and (g, 3, 1, else_lit); + } else { + CADICAL_assert (v_else < 0); + rhs[0] = then_lit; + rhs[1] = cond; + simplify_ite_gate_to_and (g, 2, 0, -else_lit); + } + if (internal->unsat) + return; + if (internal->vlit (rhs[0]) > internal->vlit (rhs[1])) + std::swap (rhs[0], rhs[1]); + g->shrunken = true; + g->tag = Gate_Type::And_Gate; + rhs.resize (2); + CADICAL_assert (is_sorted (begin (rhs), end (rhs), + sort_literals_by_var_smaller (internal))); + g->hash = hash_lits (nonces, rhs); + check_and_gate_implied (g); + Gate *h = find_and_lits (rhs); + if (h) { + CADICAL_assert (garbage); + std::vector reasons_lrat, reasons_lrat_back; + if (internal->lrat) + merge_and_gate_lrat_produce_lrat (g, h, reasons_lrat, + reasons_lrat_back, false); + if (merge_literals_lrat (g->lhs, h->lhs, reasons_lrat, + reasons_lrat_back)) { + ++internal->stats.congruence.ites; + } + } else { + remove_gate (git); + index_gate (g); + garbage = false; + g->hash = hash_lits (nonces, g->rhs); + for (auto lit : rhs) + if (lit != cond && lit != then_lit && lit != else_lit) { + connect_goccs (g, lit); + } + + if (rhs[0] == -g->lhs || rhs[1] == -g->lhs) + simplify_and_gate ( + g); // TODO Kissat does not do that, but it has also no + // checks to verify that it cannot happen... + } + } + } + if (garbage && !internal->unsat) + mark_garbage (g); + ++internal->stats.congruence.simplified; + ++internal->stats.congruence.simplified_ites; +} + +void Closure::add_ite_matching_proof_chain ( + Gate *g, Gate *h, int lhs1, int lhs2, std::vector &reasons1, + std::vector &reasons2) { + check_ite_lrat_reasons (g); + check_ite_lrat_reasons (h, false); + CADICAL_assert (g->lhs == lhs1); + CADICAL_assert (h->lhs == lhs2); + if (lhs1 == lhs2) + return; + if (!internal->proof) + return; + LOG (g, "starting ITE matching proof chain"); + LOG (h, "starting ITE matching proof chain with"); + CADICAL_assert (unsimplified.empty ()); + CADICAL_assert (chain.empty ()); + if (internal->lrat) + check_correct_ite_flags (g); + const auto &rhs = g->rhs; + const int8_t flags_g = g->degenerated_ite; + const int8_t flags_h = h->degenerated_ite; + const int cond = rhs[0]; + LRAT_ID g_then_id = 0, g_neg_then_id = 0, g_neg_else_id = 0; + LRAT_ID h_then_id = 0, h_neg_then_id = 0, h_else_id = 0; + LRAT_ID g_else_id = 0, h_neg_else_id = 0; + const bool degenerated_g_then = ite_flags_no_then_clauses (flags_g); + const bool degenerated_g_else = ite_flags_no_else_clauses (flags_g); + const bool degenerated_h_then = ite_flags_no_then_clauses (flags_h); + const bool degenerated_h_else = ite_flags_no_else_clauses (flags_h); + + const bool degenerated_g_cond = ite_flags_cond_lhs (flags_g); + const bool degenerated_h_cond = ite_flags_cond_lhs (flags_h); + CADICAL_assert (!(degenerated_g_cond && degenerated_h_cond)); + + const bool degenerated_g_not_cond = ite_flags_neg_cond_lhs (flags_g); + const bool degenerated_h_not_cond = ite_flags_neg_cond_lhs (flags_h); + CADICAL_assert (!(degenerated_g_not_cond && degenerated_h_not_cond)); + + if (internal->lrat) { + // the code can produce tautologies in the case that: + // a = (c ? a : e) + // b = (c ? a : e) + // (no clauses with (then) index a/-a) + // but also for + // a = (a ? t : e) + // b = (a ? t : e) + // (no clauses with (then) index -a and (else) index e) + // and same for + // a = (c ? t : a) + // b = (c ? t : a) + // (no clauses with (then) index a and (else) index -e) + LOG (g, "get ids from"); + CADICAL_assert (g->pos_lhs_ids.size () == 4); + auto &g_then_clause = g->pos_lhs_ids[0].clause; + g_then_clause = + g_then_clause ? produce_rewritten_clause_lrat (g_then_clause, g->lhs, false) : nullptr; + if (g_then_clause) + g_then_id = g_then_clause->id; + + auto &g_neg_then_clause = g->pos_lhs_ids[1].clause; + g_neg_then_clause = + g_neg_then_clause ? produce_rewritten_clause_lrat (g_neg_then_clause, g->lhs, false) : nullptr; + if (g_neg_then_clause) + g_neg_then_id = g_neg_then_clause->id; + + auto &g_else_clause = g->pos_lhs_ids[2].clause; + g_else_clause = + g_else_clause ? produce_rewritten_clause_lrat (g_else_clause, g->lhs, false) : g_else_clause; + if (g_else_clause) + g_else_id = g_else_clause->id; + + auto &g_neg_else_clause = g->pos_lhs_ids[3].clause; + g_neg_else_clause = + g_neg_else_clause ? produce_rewritten_clause_lrat (g_neg_else_clause, g->lhs, false) : nullptr; + if (g_neg_else_clause) + g_neg_else_id = g_neg_else_clause->id; + + LOG (h, "now clauses from"); + CADICAL_assert (h->pos_lhs_ids.size () == 4); + auto &h_then_clause = h->pos_lhs_ids[0].clause; + h_then_clause = + h_then_clause ? produce_rewritten_clause_lrat (h_then_clause, h->lhs, false) : nullptr; + if (h_then_clause) + h_then_id = h_then_clause->id; + + auto &h_neg_then_clause = h->pos_lhs_ids[1].clause; + h_neg_then_clause = + h_neg_then_clause ? produce_rewritten_clause_lrat (h_neg_then_clause, h->lhs, false) : nullptr; + if (h_neg_then_clause) + h_neg_then_id = h_neg_then_clause->id; + + auto &h_else_clause = h->pos_lhs_ids[2].clause; + h_else_clause = + h_else_clause ? produce_rewritten_clause_lrat (h_else_clause, h->lhs, false) : nullptr; + if (h_else_clause) + h_else_id = h_else_clause->id; + + auto &h_neg_else_clause = h->pos_lhs_ids[3].clause; + h_neg_else_clause = + h_neg_else_clause ? produce_rewritten_clause_lrat (h_neg_else_clause, h->lhs, false) : nullptr; + if (h_neg_else_clause) + h_neg_else_id = h_neg_else_clause->id; + + } + + if (degenerated_g_cond) { + LOG ("special case: cond = lhs, g degenerated"); + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + LRAT_ID id1 = 0; + if (internal->lrat) { + lrat_chain.push_back (g_then_id); + lrat_chain.push_back (h_neg_then_id); + } + id1 = simplify_and_add_to_proof_chain (unsimplified); + + unsimplified.clear (); + unsimplified.push_back (lhs1); + unsimplified.push_back (-lhs2); + LRAT_ID id2 = 0; + if (internal->lrat) { + lrat_chain.push_back (g_neg_else_id); + lrat_chain.push_back (h_else_id); + } + id2 = simplify_and_add_to_proof_chain (unsimplified); + + CADICAL_assert (!internal->lrat || id1); + CADICAL_assert (!internal->lrat || id2); + reasons1.push_back (id1); + reasons2.push_back (id2); + unsimplified.clear (); + return; + } + + if (degenerated_h_cond) { + LOG ("special case: cond = lhs, h degenerated"); + unsimplified.push_back (lhs1); // potentially lhs1 == lhs2 + unsimplified.push_back (-lhs2); + LRAT_ID id1 = 0; + if (internal->lrat) { + lrat_chain.push_back (h_then_id); + lrat_chain.push_back (g_neg_then_id); + } + id1 = simplify_and_add_to_proof_chain (unsimplified); + + unsimplified.clear (); + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + LRAT_ID id2 = 0; + if (internal->lrat) { + lrat_chain.push_back (h_neg_else_id); + lrat_chain.push_back (g_else_id); + } + id2 = simplify_and_add_to_proof_chain (unsimplified); + + CADICAL_assert (!internal->lrat || id1); + CADICAL_assert (!internal->lrat || id2); + reasons2.push_back (id1); + reasons1.push_back (id2); + unsimplified.clear (); + return; + } + + if (degenerated_g_not_cond) { + LOG ("special case: cond = -lhs, g degenerated"); + unsimplified.push_back (lhs1); + unsimplified.push_back (-lhs2); + LRAT_ID id1 = 0; + if (internal->lrat) { + CADICAL_assert (g_neg_then_id && h_then_id && g_else_id && h_neg_else_id); + lrat_chain.push_back (g_neg_then_id); + lrat_chain.push_back (h_then_id); + } + id1 = simplify_and_add_to_proof_chain (unsimplified); + + unsimplified.clear (); + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + LRAT_ID id2 = -1; + + if (internal->lrat) { + lrat_chain.push_back (g_else_id); + lrat_chain.push_back (h_neg_else_id); + } + id2 = simplify_and_add_to_proof_chain (unsimplified); + CADICAL_assert (!internal->lrat || id1); + CADICAL_assert (!internal->lrat || id2); + reasons2.push_back (id1); + reasons1.push_back (id2); + unsimplified.clear (); + return; + } + + if (degenerated_h_not_cond) { + LOG ("special case: cond = -lhs, h degenerated"); + unsimplified.push_back (lhs1); + unsimplified.push_back (-lhs2); + LRAT_ID id1 = 0; + if (internal->lrat) { + CADICAL_assert (g_neg_then_id && h_then_id && g_else_id && h_neg_else_id); + lrat_chain.push_back (h_neg_then_id); + lrat_chain.push_back (g_then_id); + } + id1 = simplify_and_add_to_proof_chain (unsimplified); + + unsimplified.clear (); + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + LRAT_ID id2 = -1; + + if (internal->lrat) { + lrat_chain.push_back (h_else_id); + lrat_chain.push_back (g_neg_else_id); + } + id2 = simplify_and_add_to_proof_chain (unsimplified); + CADICAL_assert (!internal->lrat || id1); + CADICAL_assert (!internal->lrat || id2); + reasons2.push_back (id1); + reasons1.push_back (id2); + unsimplified.clear (); + return; + } + + LOG ("normal path"); + CADICAL_assert (!internal->lrat || degenerated_g_then || + (g_then_id && g_neg_then_id)); + CADICAL_assert (!internal->lrat || degenerated_g_else || + (g_else_id && g_neg_else_id)); + CADICAL_assert (!internal->lrat || degenerated_h_then || + (h_then_id && h_neg_then_id)); + CADICAL_assert (!internal->lrat || degenerated_h_else || + (h_else_id && h_neg_else_id)); + CADICAL_assert (!internal->lrat || g_then_id || h_neg_then_id); + CADICAL_assert (!internal->lrat || g_neg_then_id || h_then_id); + unsimplified.push_back (-lhs1); + unsimplified.push_back (lhs2); + unsimplified.push_back (-cond); + LRAT_ID id1 = 0; + if (degenerated_g_then || degenerated_h_then) { + id1 = degenerated_g_then ? h_neg_then_id : g_then_id; + } else { + if (internal->lrat) { + lrat_chain.push_back (g_then_id); + lrat_chain.push_back (h_neg_then_id); + } + id1 = simplify_and_add_to_proof_chain (unsimplified); + } + unsimplified.pop_back (); + unsimplified.push_back (cond); + + LRAT_ID id2 = 0; + if (degenerated_g_else || degenerated_h_else) { + id2 = degenerated_g_else ? h_neg_else_id : g_else_id; + } else { + if (internal->lrat) { + lrat_chain.push_back (h_neg_else_id); + lrat_chain.push_back (g_else_id); + } + id2 = simplify_and_add_to_proof_chain (unsimplified); + } + unsimplified.pop_back (); + + unsimplified.clear (); + unsimplified.push_back (lhs1); + unsimplified.push_back (-lhs2); + unsimplified.push_back (-cond); + CADICAL_assert (lrat_chain.empty ()); + + LRAT_ID id3 = 0; + if (degenerated_g_then || degenerated_h_then) { + id3 = degenerated_g_then ? h_then_id : g_neg_then_id; + } else { + if (internal->lrat) { + // lrat_chain.push_back (g_then_id); + // lrat_chain.push_back (h_neg_then_id); + lrat_chain.push_back (g_neg_then_id); + lrat_chain.push_back (h_then_id); + } + id3 = simplify_and_add_to_proof_chain (unsimplified); + } + unsimplified.pop_back (); + unsimplified.push_back (cond); + CADICAL_assert (lrat_chain.empty ()); + + LRAT_ID id4 = 0; + if (degenerated_g_else || degenerated_h_else) { + id4 = degenerated_g_else ? h_else_id : g_neg_else_id; + } else { + if (internal->lrat) { + lrat_chain.push_back (g_neg_else_id); + lrat_chain.push_back (h_else_id); + } + id4 = simplify_and_add_to_proof_chain (unsimplified); + } + unsimplified.pop_back (); + + if (internal->lrat) { + CADICAL_assert (!internal->lrat || (id1 && id2 && id3 && id4)); + reasons1.push_back (id1), reasons1.push_back (id2); + reasons2.push_back (id3), reasons2.push_back (id4); + } + + unsimplified.clear (); + + LOG ("finished ITE matching proof chain"); +} + +Gate *Closure::new_ite_gate (int lhs, int cond, int then_lit, int else_lit, + std::vector &&clauses) { + CADICAL_assert (chain.empty ()); + if (else_lit == -then_lit) { + if (then_lit < 0) + LOG ("skipping ternary XOR %d := %d ^ %d", lhs, cond, -then_lit); + else + LOG ("skipping ternary XOR %d := %d ^ %d", -lhs, cond, then_lit); + return nullptr; + } + if (else_lit == then_lit) { + LOG ("found trivial ITE gate %d := %d ? %d : %d", (lhs), (cond), + (then_lit), (else_lit)); + std::vector reasons_implication, reasons_back; + if (internal->lrat) { + merge_ite_gate_same_then_else_lrat (clauses, reasons_implication, + reasons_back); + } + if (merge_literals_lrat (lhs, then_lit, reasons_implication, + reasons_back)) + ++internal->stats.congruence.trivial_ite; + return 0; + } + + rhs.clear (); + rhs.push_back (cond); + rhs.push_back (then_lit); + rhs.push_back (else_lit); + LOG ("ITE gate %d = %d ? %d : %d", lhs, cond, then_lit, else_lit); + + bool negate_lhs = false; + Gate *g = new Gate; + g->lhs = lhs; + g->tag = Gate_Type::ITE_Gate; + g->rhs = {rhs}; + g->pos_lhs_ids = clauses; +#ifdef LOGGING + g->id = -1; +#endif + Gate *h = find_ite_gate (g, negate_lhs); + if (negate_lhs) + lhs = -lhs; + g->lhs = lhs; + check_ite_gate_implied (g); + check_ite_lrat_reasons ( + g, false); // due to merges done before during AND gate detection! + + if (h) { + check_ite_gate_implied (h); + std::vector extra_reasons_lit, extra_reasons_ulit; + add_ite_matching_proof_chain (h, g, h->lhs, lhs, extra_reasons_lit, + extra_reasons_ulit); + if (merge_literals_lrat (h, g, h->lhs, lhs, extra_reasons_lit, + extra_reasons_ulit)) { + ++internal->stats.congruence.ites; + LOG ("found merged literals"); + } + delete_proof_chain (); + delete g; + return h; + } else { + // do not sort clauses here obviously! + // sort (begin (g->rhs), end (g->rhs)); + g->garbage = false; + g->indexed = true; + g->shrunken = false; + g->hash = hash_lits (nonces, g->rhs); + table.insert (g); + ++internal->stats.congruence.gates; +#ifdef LOGGING + g->id = fresh_id++; +#endif + LOG (g, "creating new"); + if (internal->lrat) + update_ite_flags (g); + check_ite_gate_implied (g); + for (auto lit : g->rhs) { + connect_goccs (g, lit); + } + } + check_ite_lrat_reasons (g); + return g; +} + +void check_ite_lits_normalized (const std::vector &lits) { + CADICAL_assert (lits[0] > 0); + CADICAL_assert (lits[1] > 0); + CADICAL_assert (lits[0] != lits[1]); + CADICAL_assert (lits[0] != lits[2]); + CADICAL_assert (lits[1] != lits[2]); + CADICAL_assert (lits[0] != -lits[1]); + CADICAL_assert (lits[0] != -lits[2]); + CADICAL_assert (lits[1] != -lits[2]); +#ifdef CADICAL_NDEBUG + (void) lits; +#endif +} + +void Closure::check_ite_implied (int lhs, int cond, int then_lit, + int else_lit) { + if (internal->lrat) + return; + check_ternary (cond, -else_lit, lhs); + check_ternary (cond, else_lit, -lhs); + check_ternary (-cond, -then_lit, lhs); + check_ternary (-cond, then_lit, -lhs); +} + +void Closure::check_contained_module_rewriting (Clause *c, int lit, + bool normalized, + int except) { +#ifndef CADICAL_NDEBUG + if (lit == except) // happens for degenerated cases + except = 0; + const int normalize_lit = + (lit == except ? except : find_eager_representative (lit)); + CADICAL_assert (!normalized || lit == -except || normalize_lit == lit); + bool found = false; + LOG (c, "looking for (normalized) %d in ", lit); + for (auto other : *c) { + const int normalize_other = + (other == except ? except : find_eager_representative (other)); + LOG ("%d -> %d ", other, normalize_other); + CADICAL_assert (!normalized || other == -except || normalize_other == other); + if (normalize_other == normalize_lit) { + found = true; + break; + } + } + CADICAL_assert (found); +#else + (void) c, (void) lit, (void) normalized, (void) except; +#endif +} + +void Closure::check_ite_lrat_reasons (Gate *g, bool normalized) { +#ifndef CADICAL_NDEBUG + CADICAL_assert (g->tag == Gate_Type::ITE_Gate); + if (!internal->lrat) + return; + CADICAL_assert (g->rhs.size () == 3); + CADICAL_assert (is_tautological_ite_gate (g) || g->pos_lhs_ids.size () == 4); + CADICAL_assert (g->neg_lhs_ids.empty ()); + CADICAL_assert (g->pos_lhs_ids.size () == 4); +#else + (void) g, (void) normalized; +#endif +} + +void Closure::check_ite_gate_implied (Gate *g) { + CADICAL_assert (g->tag == Gate_Type::ITE_Gate); + if (internal->lrat) + return; + check_ite_implied (g->lhs, g->rhs[0], g->rhs[1], g->rhs[2]); +} + +void Closure::init_ite_gate_extraction ( + std::vector &candidates) { + std::vector ternary; + glargecounts.resize (2 * internal->vsize, 0); + for (auto c : internal->clauses) { + if (c->garbage) + continue; + if (c->redundant) + continue; + if (c->size < 3) + continue; + unsigned size = 0; + + CADICAL_assert (!c->garbage); + for (auto lit : *c) { + const signed char v = internal->val (lit); + if (v < 0) + continue; + if (v > 0) { + LOG (c, "deleting as satisfied due to %d", lit); + internal->mark_garbage (c); + goto CONTINUE_COUNTING_NEXT_CLAUSE; + } + if (size == 3) + goto CONTINUE_COUNTING_NEXT_CLAUSE; + size++; + } + if (size < 3) + continue; + CADICAL_assert (size == 3); + ternary.push_back (c); + LOG (c, "counting original ITE gate base"); + for (auto lit : *c) { + if (!internal->val (lit)) + ++largecount (lit); + } + CONTINUE_COUNTING_NEXT_CLAUSE:; + } + + for (auto c : ternary) { + CADICAL_assert (!c->garbage); + CADICAL_assert (!c->redundant); + unsigned positive = 0, negative = 0, twice = 0; + for (auto lit : *c) { + if (internal->val (lit)) + continue; + const int count_not_lit = largecount (-lit); + if (!count_not_lit) + goto CONTINUE_WITH_NEXT_TERNARY_CLAUSE; + const unsigned count_lit = largecount (lit); + CADICAL_assert (count_lit); + if (count_lit > 1 && count_not_lit > 1) + ++twice; + if (lit < 0) + ++negative; + else + ++positive; + } + if (twice < 2) + goto CONTINUE_WITH_NEXT_TERNARY_CLAUSE; + CADICAL_assert (c->size != 2); + for (auto lit : *c) + internal->occs (lit).push_back (c); + if (positive && negative) + candidates.push_back (c); + CONTINUE_WITH_NEXT_TERNARY_CLAUSE:; + } + + ternary.clear (); +} + +void Closure::reset_ite_gate_extraction () { + condbin[0].clear (); + condbin[1].clear (); + condeq[0].clear (); + condeq[1].clear (); + glargecounts.clear (); + internal->clear_occs (); +} + +void Closure::copy_conditional_equivalences (int lit, + lit_implications &condbin) { + CADICAL_assert (condbin.empty ()); + for (auto c : internal->occs (lit)) { + CADICAL_assert (c->size != 2); + int first = 0, second = 0; + for (auto other : *c) { + if (internal->val (other)) + continue; + if (other == lit) + continue; + if (!first) + first = other; + else { + CADICAL_assert (!second); + second = other; + } + } + CADICAL_assert (first), CADICAL_assert (second); + lit_implication p (first, second, c); + + if (internal->vlit (first) < internal->vlit (second)) { + CADICAL_assert (p.first == first); + CADICAL_assert (p.second == second); + } else { + CADICAL_assert (internal->vlit (second) < internal->vlit (first)); + p.swap (); + CADICAL_assert (p.first == second); + CADICAL_assert (p.second == first); + } + LOG (c, "literal %d condition binary clause %d %d", lit, first, second); + condbin.push_back (p); + } +} + +bool less_litpair (lit_equivalence p, lit_equivalence q) { + const int a = p.first; + const int b = q.first; + if (a < b) + return true; + if (b > a) + return false; + const int c = p.second; + const int d = q.second; + return (c < d); +} +struct litpair_rank { + CaDiCaL::Internal *internal; + litpair_rank (Internal *i) : internal (i) {} + typedef uint64_t Type; + Type operator() (const lit_implication &a) const { + uint64_t lita = internal->vlit (a.first); + uint64_t litb = internal->vlit (a.second); + return (lita << 32) + litb; + } +}; + +struct litpair_smaller { + CaDiCaL::Internal *internal; + litpair_smaller (Internal *i) : internal (i) {} + bool operator() (const lit_implication &a, + const lit_implication &b) const { + const auto s = litpair_rank (internal) (a); + const auto t = litpair_rank (internal) (b); + return s < t; + } +}; + +lit_implications::const_iterator +Closure::find_lit_implication_second_literal ( + int lit, lit_implications::const_iterator begin, + lit_implications::const_iterator end) { + LOG ("searching for %d in", lit); + for (auto it = begin; it != end; ++it) + LOG ("%d [%d]", it->first, it->second); + lit_implications::const_iterator found = std::lower_bound ( + begin, end, lit_implication{lit, lit}, + [] (const lit_implication &a, const lit_implication &b) { + return a.second < b.second; + }); +#ifndef CADICAL_NDEBUG + auto found2 = std::binary_search ( + begin, end, lit_implication{lit, lit}, + [] (const lit_implication &a, const lit_implication &b) { + return a.second < b.second; + }); +#endif + + if (found < end && found->second == lit) { + CADICAL_assert (found2 == (found < end)); + return found; + } + return end; +} + +void Closure::search_condeq (int lit, int pos_lit, + lit_implications::const_iterator pos_begin, + lit_implications::const_iterator pos_end, + int neg_lit, + lit_implications::const_iterator neg_begin, + lit_implications::const_iterator neg_end, + lit_equivalences &condeq) { + CADICAL_assert (neg_lit == -pos_lit); + CADICAL_assert (pos_begin < pos_end); + CADICAL_assert (neg_begin < neg_end); + CADICAL_assert (pos_begin->first == pos_lit); + CADICAL_assert (neg_begin->first == neg_lit); + CADICAL_assert (pos_end <= neg_begin || neg_end <= pos_begin); + for (lit_implications::const_iterator p = pos_begin; p != pos_end; p++) { + const int other = p->second; + const int not_other = -other; + const lit_implications::const_iterator other_clause = + find_lit_implication_second_literal (not_other, neg_begin, neg_end); + CADICAL_assert (std::find (begin (*p->clause), end (*p->clause), lit) != + end (*p->clause)); + if (other_clause != neg_end) { + CADICAL_assert (std::find (begin (*other_clause->clause), + end (*other_clause->clause), + neg_lit) != end (*other_clause->clause)); + CADICAL_assert (std::find (begin (*p->clause), end (*p->clause), other) != + end (*p->clause)); + lit_equivalence equivalence (neg_lit, other_clause->clause, other, + p->clause); + if (pos_lit > 0) { + equivalence.negate_both (); + } + if (internal->lrat) + equivalence.check_invariant (); + LOG ("found conditional %d equivalence %d = %d", lit, + equivalence.first, equivalence.second); + CADICAL_assert (equivalence.first > 0); + CADICAL_assert (internal->vlit (equivalence.first) < + internal->vlit (equivalence.second)); + check_ternary (lit, neg_lit, -other); + check_ternary (lit, -neg_lit, other); + condeq.push_back (equivalence); + if (equivalence.second < 0) { + lit_equivalence inverse_equivalence = + equivalence.swap ().negate_both (); + condeq.push_back (inverse_equivalence); + if (internal->lrat) + inverse_equivalence.check_invariant (); + } else { + lit_equivalence inverse_equivalence = equivalence.swap (); + condeq.push_back (inverse_equivalence); + if (internal->lrat) + inverse_equivalence.check_invariant (); + } + } + } +#ifndef LOGGING + (void) lit; +#endif +} + +void Closure::extract_condeq_pairs (int lit, lit_implications &condbin, + lit_equivalences &condeq) { + const lit_implications::const_iterator begin = condbin.cbegin (); + const lit_implications::const_iterator end = condbin.cend (); + lit_implications::const_iterator pos_begin = begin; + int next_lit = 0; + +#ifdef LOGGING + for (const auto &pair : condbin) + LOG ("unsorted conditional %d equivalence %d = %d", lit, pair.first, + pair.second); +#endif + LOG ("searching for first positive literal for lit %d", lit); + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->first; + LOG ("checking %d", next_lit); + if (next_lit > 0) + break; + pos_begin++; + } + + for (;;) { + CADICAL_assert (pos_begin != end); + CADICAL_assert (next_lit == pos_begin->first); + CADICAL_assert (next_lit > 0); + const int pos_lit = next_lit; + lit_implications::const_iterator pos_end = pos_begin + 1; + LOG ("searching for first other literal after finding lit %d", + next_lit); + for (;;) { + if (pos_end == end) + return; + next_lit = pos_end->first; + if (next_lit != pos_lit) + break; + pos_end++; + } + CADICAL_assert (pos_end != end); + CADICAL_assert (next_lit == pos_end->first); + const int neg_lit = -pos_lit; + if (next_lit != neg_lit) { + if (next_lit < 0) { + pos_begin = pos_end + 1; + LOG ("next_lit %d < 0", next_lit); + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->first; + if (next_lit > 0) + break; + pos_begin++; + } + } else + pos_begin = pos_end; + continue; + } + const lit_implications::const_iterator neg_begin = pos_end; + lit_implications::const_iterator neg_end = neg_begin + 1; + while (neg_end != end) { + next_lit = neg_end->first; + if (next_lit != neg_lit) + break; + neg_end++; + } +#ifdef LOGGING + for (lit_implications::const_iterator p = pos_begin; p != pos_end; p++) + LOG ("conditional %d binary clause %d %d with positive %d", (lit), + (p->first), (p->second), (pos_lit)); + for (lit_implications::const_iterator p = neg_begin; p != neg_end; p++) + LOG ("conditional %d binary clause %d %d with negative %d", (lit), + (p->first), (p->second), (neg_lit)); +#endif + const size_t pos_size = pos_end - pos_begin; + const size_t neg_size = neg_end - neg_begin; + if (pos_size <= neg_size) { + LOG ("searching negation of %zu conditional binary clauses " + "with positive %d in %zu conditional binary clauses with %d", + pos_size, (pos_lit), neg_size, (neg_lit)); + search_condeq (lit, pos_lit, pos_begin, pos_end, neg_lit, neg_begin, + neg_end, condeq); + } else { + LOG ("searching negation of %zu conditional binary clauses " + "with negative %d in %zu conditional binary clauses with %d", + neg_size, (neg_lit), pos_size, (pos_lit)); + search_condeq (lit, neg_lit, neg_begin, neg_end, pos_lit, pos_begin, + pos_end, condeq); + } + if (neg_end == end) + return; + CADICAL_assert (next_lit == neg_end->first); + if (next_lit < 0) { + pos_begin = neg_end + 1; + for (;;) { + if (pos_begin == end) + return; + next_lit = pos_begin->first; + if (next_lit > 0) + break; + pos_begin++; + } + } else + pos_begin = neg_end; + } +} + +void Closure::find_conditional_equivalences (int lit, + lit_implications &condbin, + lit_equivalences &condeq) { + CADICAL_assert (condbin.empty ()); + CADICAL_assert (condeq.empty ()); + CADICAL_assert (internal->occs (lit).size () > 1); + copy_conditional_equivalences (lit, condbin); + MSORT (internal->opts.radixsortlim, begin (condbin), end (condbin), + litpair_rank (this->internal), litpair_smaller (this->internal)); + + extract_condeq_pairs (lit, condbin, condeq); + MSORT (internal->opts.radixsortlim, begin (condbin), end (condbin), + litpair_rank (this->internal), litpair_smaller (this->internal)); + +#ifdef LOGGING + for (auto pair : condeq) + LOG ("sorted conditional %d equivalence %d = %d", lit, pair.first, + pair.second); + LOG ("found %zu conditional %d equivalences", condeq.size (), lit); + +#endif +} + +void Closure::merge_condeq (int cond, lit_equivalences &condeq, + lit_equivalences ¬_condeq) { + LOG ("merging cond for literal %d", cond); + auto q = begin (not_condeq); + const auto end_not_condeq = end (not_condeq); + for (auto p : condeq) { + const int lhs = p.first; + const int then_lit = p.second; + if (internal->lrat) + p.check_invariant (); + CADICAL_assert (lhs > 0); + while (q != end_not_condeq && q->first < lhs) + ++q; + while (q != end_not_condeq && q->first == lhs) { + LOG ("looking when %d at p= %d %d", cond, p.first, p.second); + LOG ("looking when %d at %d %d", -cond, q->first, q->second); + lit_equivalence not_cond_pair = *q++; + const int else_lit = not_cond_pair.second; + std::vector clauses; + if (internal->lrat) { + // The then/else literal is the second of the pair, hence the swap + // of the reasons + CADICAL_assert (p.first_clause && p.second_clause); + CADICAL_assert (not_cond_pair.first_clause && not_cond_pair.second_clause); + LOG (p.second_clause, "pairing %d", then_lit); + LOG (p.first_clause, "pairing %d", -then_lit); + LOG (not_cond_pair.second_clause, "pairing %d", else_lit); + LOG (not_cond_pair.first_clause, "pairing %d", -else_lit); + clauses.push_back (LitClausePair (then_lit, p.second_clause)); + clauses.push_back (LitClausePair (-then_lit, p.first_clause)); + clauses.push_back ( + LitClausePair (else_lit, not_cond_pair.second_clause)); + clauses.push_back ( + LitClausePair (-else_lit, not_cond_pair.first_clause)); + if (internal->lrat) + not_cond_pair.check_invariant (); + } + new_ite_gate (lhs, cond, then_lit, else_lit, std::move (clauses)); + if (internal->unsat) + return; + } + } +} + +void Closure::extract_ite_gates_of_literal (int lit) { + LOG ("search for ITE for literal %d ", lit); + find_conditional_equivalences (lit, condbin[0], condeq[0]); + if (!condeq[0].empty ()) { + find_conditional_equivalences (-lit, condbin[1], condeq[1]); + if (!condeq[1].empty ()) { + if (lit < 0) + merge_condeq (-lit, condeq[0], condeq[1]); + else + merge_condeq (lit, condeq[1], condeq[0]); + } + } + + condbin[0].clear (); + condbin[1].clear (); + condeq[0].clear (); + condeq[1].clear (); +} + +void Closure::extract_ite_gates_of_variable (int idx) { + const int lit = idx; + const int not_lit = -idx; + + auto lit_watches = internal->occs (lit); + auto not_lit_watches = internal->occs (not_lit); + const size_t size_lit_watches = lit_watches.size (); + const size_t size_not_lit_watches = not_lit_watches.size (); + if (size_lit_watches <= size_not_lit_watches) { + if (size_lit_watches > 1) + extract_ite_gates_of_literal (lit); + } else { + if (size_not_lit_watches > 1) + extract_ite_gates_of_literal (not_lit); + } +} + +void Closure::extract_ite_gates () { + CADICAL_assert (!full_watching); + if (!internal->opts.congruenceite) + return; + START (extractites); + std::vector candidates; + + init_ite_gate_extraction (candidates); + + for (auto idx : internal->vars) { + if (internal->flags (idx).active ()) { + extract_ite_gates_of_variable (idx); + if (internal->unsat) + break; + } + } + // Kissat has an alternative version MERGE_CONDITIONAL_EQUIVALENCES + reset_ite_gate_extraction (); + STOP (extractites); +} + +/*------------------------------------------------------------------------*/ +void Closure::extract_gates () { + START (extract); + extract_and_gates (); + CADICAL_assert (internal->unsat || lrat_chain.empty ()); + CADICAL_assert (internal->unsat || chain.empty ()); + if (internal->unsat || internal->terminated_asynchronously ()) { + STOP (extract); + return; + } + extract_xor_gates (); + CADICAL_assert (internal->unsat || lrat_chain.empty ()); + CADICAL_assert (internal->unsat || chain.empty ()); + + if (internal->unsat || internal->terminated_asynchronously ()) { + STOP (extract); + return; + } + extract_ite_gates (); + STOP (extract); +} + +/*------------------------------------------------------------------------*/ +// top level function to extract gate +bool Internal::extract_gates () { + if (unsat) + return false; + if (!opts.congruence) + return false; + if (level) + backtrack (); + if (!propagate ()) { + learn_empty_clause (); + return false; + } + if (congruence_delay.bumpreasons.limit) { + LOG ("delaying congruence %" PRId64 " more times", + congruence_delay.bumpreasons.limit); + congruence_delay.bumpreasons.limit--; + return false; + } + + // to remove false literals from clauses + // It makes the technique stronger as long clauses + // can become binary / ternary + // garbage_collection (); + + const int64_t old = stats.congruence.congruent; + const int old_merged = stats.congruence.congruent; + + // congruencebinary is already doing it (and more actually) + if (!internal->opts.congruencebinaries) { + const bool dedup = opts.deduplicate; + opts.deduplicate = true; + mark_duplicated_binary_clauses_as_garbage (); + opts.deduplicate = dedup; + } + ++stats.congruence.rounds; + clear_watches (); + // connect_binary_watches (); + + START_SIMPLIFIER (congruence, CONGRUENCE); + Closure closure (this); + + closure.init_closure (); + CADICAL_assert (unsat || closure.chain.empty ()); + CADICAL_assert (unsat || lrat_chain.empty ()); + closure.extract_binaries (); + CADICAL_assert (unsat || closure.chain.empty ()); + CADICAL_assert (unsat || lrat_chain.empty ()); + closure.extract_gates (); + CADICAL_assert (unsat || closure.chain.empty ()); + CADICAL_assert (unsat || lrat_chain.empty ()); + internal->clear_watches (); + internal->connect_watches (); + closure.reset_extraction (); + + if (!unsat) { + closure.find_units (); + CADICAL_assert (unsat || closure.chain.empty ()); + CADICAL_assert (unsat || lrat_chain.empty ()); + if (!internal->unsat) { + closure.find_equivalences (); + CADICAL_assert (unsat || closure.chain.empty ()); + CADICAL_assert (unsat || lrat_chain.empty ()); + + if (!unsat) { + const int propagated = closure.propagate_units_and_equivalences (); + CADICAL_assert (unsat || closure.chain.empty ()); + if (!unsat && propagated) + closure.forward_subsume_matching_clauses (); + } + } + } + + closure.reset_closure (); + internal->clear_watches (); + internal->connect_watches (); + if (!internal->unsat) { + propagated2 = propagated = 0; + propagate (); + } + CADICAL_assert (closure.new_unwatched_binary_clauses.empty ()); + internal->reset_occs (); + internal->reset_noccs (); + CADICAL_assert (!internal->occurring ()); + CADICAL_assert (lrat_chain.empty ()); + + const int64_t new_merged = stats.congruence.congruent; + +#ifndef CADICAL_QUIET + phase ("congruence-phase", stats.congruence.rounds, "merged %ld literals", + new_merged - old_merged); +#endif + if (!unsat && !internal->propagate ()) + unsat = true; + + STOP_SIMPLIFIER (congruence, CONGRUENCE); + report ('c', !opts.reportall && !(stats.congruence.congruent - old)); +#ifndef CADICAL_NDEBUG + size_t watched = 0; + for (auto v : vars) { + for (auto sgn = -1; sgn <= 1; sgn += 2) { + const int lit = v * sgn; + for (auto w : watches (lit)) { + if (w.binary ()) + CADICAL_assert (!w.clause->garbage); + if (w.clause->garbage) + continue; + ++watched; + LOG (w.clause, "watched"); + } + } + } + LOG ("and now the clauses:"); + size_t nb_clauses = 0; + for (auto c : clauses) { + if (c->garbage) + continue; + LOG (c, "watched"); + ++nb_clauses; + } + CADICAL_assert (watched == nb_clauses * 2); +#endif + CADICAL_assert (!internal->occurring ()); + + if (new_merged == old_merged) { + congruence_delay.bumpreasons.interval++; + } else { + congruence_delay.bumpreasons.interval /= 2; + } + + LOG ("delay congruence internal %" PRId64, + congruence_delay.bumpreasons.interval); + congruence_delay.bumpreasons.limit = + congruence_delay.bumpreasons.interval; + + return new_merged != old_merged; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_constrain.cpp b/src/sat/cadical/cadical_constrain.cpp new file mode 100644 index 000000000..47f14e3d9 --- /dev/null +++ b/src/sat/cadical/cadical_constrain.cpp @@ -0,0 +1,70 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::constrain (int lit) { + if (lit) + constraint.push_back (lit); + else { + if (level) + backtrack (); + LOG (constraint, "shrinking constraint"); + bool satisfied_constraint = false; + const vector::const_iterator end = constraint.end (); + vector::iterator i = constraint.begin (); + for (vector::const_iterator j = i; j != end; j++) { + int tmp = marked (*j); + if (tmp > 0) { + LOG ("removing duplicated literal %d from constraint", *j); + } else if (tmp < 0) { + LOG ("tautological since both %d and %d occur in constraint", -*j, + *j); + satisfied_constraint = true; + break; + } else { + tmp = val (*j); + if (tmp < 0) { + LOG ("removing falsified literal %d from constraint clause", *j); + } else if (tmp > 0) { + LOG ("satisfied constraint with literal %d", *j); + satisfied_constraint = true; + break; + } else { + *i++ = *j; + mark (*j); + } + } + } + constraint.resize (i - constraint.begin ()); + for (const auto &lit : constraint) + unmark (lit); + if (satisfied_constraint) + constraint.clear (); + else if (constraint.empty ()) { + unsat_constraint = true; + if (!conflict_id) + marked_failed = false; // allow to trigger failing () + } else + for (const auto lit : constraint) + freeze (lit); + } +} + +bool Internal::failed_constraint () { return unsat_constraint; } + +void Internal::reset_constraint () { + for (auto lit : constraint) + melt (lit); + LOG ("cleared %zd constraint literals", constraint.size ()); + constraint.clear (); + unsat_constraint = false; + marked_failed = true; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_contract.cpp b/src/sat/cadical/cadical_contract.cpp new file mode 100644 index 000000000..7485ff4aa --- /dev/null +++ b/src/sat/cadical/cadical_contract.cpp @@ -0,0 +1,33 @@ +#include "global.h" + +#ifndef CADICAL_NCONTRACTS + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void fatal_message_start (); + +// See comments in 'contract.hpp'. Ugly hack we keep for now. + +void require_solver_pointer_to_be_non_zero (const void *ptr, + const char *function_name, + const char *file_name) { + if (ptr) + return; + fatal_message_start (); + fprintf (stderr, + "invalid API usage of '%s' in '%s': " + "solver 'this' pointer zero (not initialized)\n", + function_name, file_name); + fflush (stderr); + abort (); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#endif diff --git a/src/sat/cadical/cadical_cover.cpp b/src/sat/cadical/cadical_cover.cpp new file mode 100644 index 000000000..0d3253e98 --- /dev/null +++ b/src/sat/cadical/cadical_cover.cpp @@ -0,0 +1,710 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Covered clause elimination (CCE) is described in our short LPAR-10 paper +// and later in more detail in our JAIR'15 article. Actually implement +// the asymmetric version which adds asymmetric literals too but still call +// it 'CCE' in the following (and not 'ACCE'). This implementation provides +// a simplified and cleaner version of the one implemented before in +// Lingeling. We still follow quite closely the original description in the +// literature, which is based on asymmetric literal addition (ALA) and +// covered literal addition (CLA). Both can be seen as kind of propagation, +// where the literals in the original and then extended clause are assigned +// to false, and the literals on the trail (actually we use our own 'added' +// stack for that) make up the extended clause. The ALA steps can be +// implemented by simple propagation (copied from 'propagate.cpp') using +// watches, while the CLA steps need full occurrence lists to determine the +// resolution candidate clauses. The CCE is successful if a conflict is +// found during ALA steps or if during a CLA step all resolution candidates +// of a literal on the trail are satisfied (the extended clause is blocked). + +struct Coveror { + std::vector added; // acts as trail + std::vector extend; // extension stack for witness + std::vector covered; // clause literals or added through CLA + std::vector intersection; // of literals in resolution candidates + + size_t alas, clas; // actual number of ALAs and CLAs + + struct { + size_t added, covered; + } next; // propagate next ... + + Coveror () : alas (0), clas (0) {} +}; + +/*------------------------------------------------------------------------*/ + +// Push on the extension stack a clause made up of the given literal, the +// original clause (initially copied to 'covered') and all the added covered +// literals so far. The given literal will act as blocking literal for that +// clause, if CCE is successful. Only in this case, this private extension +// stack is copied to the actual extension stack of the solver. Note, that +// even though all 'added' clauses correspond to the extended clause, we +// only need to save the original and added covered literals. + +inline void Internal::cover_push_extension (int lit, Coveror &coveror) { + coveror.extend.push_back (0); + coveror.extend.push_back (lit); // blocking literal comes first + bool found = false; + for (const auto &other : coveror.covered) + if (lit == other) + CADICAL_assert (!found), found = true; + else + coveror.extend.push_back (other); + CADICAL_assert (found); + (void) found; +} + +// Successful covered literal addition (CLA) step. + +inline void Internal::covered_literal_addition (int lit, Coveror &coveror) { + require_mode (COVER); + CADICAL_assert (level == 1); + cover_push_extension (lit, coveror); + for (const auto &other : coveror.intersection) { + LOG ("covered literal addition %d", other); + CADICAL_assert (!vals[other]), CADICAL_assert (!vals[-other]); + set_val (other, -1); + coveror.covered.push_back (other); + coveror.added.push_back (other); + coveror.clas++; + } + coveror.next.covered = 0; +} + +// Successful asymmetric literal addition (ALA) step. + +inline void Internal::asymmetric_literal_addition (int lit, + Coveror &coveror) { + require_mode (COVER); + CADICAL_assert (level == 1); + LOG ("initial asymmetric literal addition %d", lit); + CADICAL_assert (!vals[lit]), CADICAL_assert (!vals[-lit]); + set_val (lit, -1); + coveror.added.push_back (lit); + coveror.alas++; + coveror.next.covered = 0; +} + +/*------------------------------------------------------------------------*/ + +// In essence copied and adapted from 'propagate' in 'propagate.cpp'. Since +// this function is also a hot-spot here in 'cover' we specialize it in the +// same spirit as 'probe_propagate' and 'vivify_propagate'. Please refer to +// the detailed comments for 'propagate' in 'propagate.cpp' for details. + +bool Internal::cover_propagate_asymmetric (int lit, Clause *ignore, + Coveror &coveror) { + require_mode (COVER); + stats.propagations.cover++; + CADICAL_assert (val (lit) < 0); + bool subsumed = false; + LOG ("asymmetric literal propagation of %d", lit); + Watches &ws = watches (lit); + const const_watch_iterator eow = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i = j; + while (!subsumed && i != eow) { + const Watch w = *j++ = *i++; + if (w.clause == ignore) + continue; // costly but necessary here ... + const signed char b = val (w.blit); + if (b > 0) + continue; + if (w.clause->garbage) + j--; + else if (w.binary ()) { + if (b < 0) { + LOG (w.clause, "found subsuming"); + subsumed = true; + } else + asymmetric_literal_addition (-w.blit, coveror); + } else { + literal_iterator lits = w.clause->begin (); + const int other = lits[0] ^ lits[1] ^ lit; + lits[0] = other, lits[1] = lit; + const signed char u = val (other); + if (u > 0) + j[-1].blit = other; + else { + const int size = w.clause->size; + const const_literal_iterator end = lits + size; + const literal_iterator middle = lits + w.clause->pos; + literal_iterator k = middle; + signed char v = -1; + int r = 0; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + w.clause->pos = k - lits; + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + if (v > 0) + j[-1].blit = r; + else if (!v) { + LOG (w.clause, "unwatch %d in", lit); + lits[1] = r; + *k = lit; + watch_literal (r, lit, w.clause); + j--; + } else if (!u) { + CADICAL_assert (v < 0); + asymmetric_literal_addition (-other, coveror); + } else { + CADICAL_assert (u < 0), CADICAL_assert (v < 0); + LOG (w.clause, "found subsuming"); + subsumed = true; + break; + } + } + } + } + if (j != i) { + while (i != eow) + *j++ = *i++; + ws.resize (j - ws.begin ()); + } + return subsumed; +} + +// Covered literal addition (which needs full occurrence lists). The +// function returns 'true' if the extended clause is blocked on 'lit.' + +bool Internal::cover_propagate_covered (int lit, Coveror &coveror) { + require_mode (COVER); + + CADICAL_assert (val (lit) < 0); + if (frozen (lit)) { + LOG ("no covered propagation on frozen literal %d", lit); + return false; + } + + stats.propagations.cover++; + + LOG ("covered propagation of %d", lit); + CADICAL_assert (coveror.intersection.empty ()); + + Occs &os = occs (-lit); + const auto end = os.end (); + bool first = true; + + // Compute the intersection of the literals in all the clauses with + // '-lit'. If all these clauses are double satisfied then we know that + // the extended clauses (in 'added') is blocked. All literals in the + // intersection can be added as covered literal. As soon the intersection + // becomes empty (during traversal of clauses with '-lit') we abort. + + for (auto i = os.begin (); i != end; i++) { + + Clause *c = *i; + if (c->garbage) + continue; + + // First check whether clause is 'blocked', i.e., is double satisfied. + + bool blocked = false; + for (const auto &other : *c) { + if (other == -lit) + continue; + const signed char tmp = val (other); + if (tmp < 0) + continue; + if (tmp > 0) { + blocked = true; + break; + } + } + if (blocked) { // ... if 'c' is double satisfied. + LOG (c, "blocked"); + continue; // with next clause with '-lit'. + } + + if (first) { + + // Copy and mark literals of first clause. + + for (const auto &other : *c) { + if (other == -lit) + continue; + const signed char tmp = val (other); + if (tmp < 0) + continue; + CADICAL_assert (!tmp); + coveror.intersection.push_back (other); + mark (other); + } + + first = false; + + } else { + + // Unmark all literals in current clause. + + for (const auto &other : *c) { + if (other == -lit) + continue; + signed char tmp = val (other); + if (tmp < 0) + continue; + CADICAL_assert (!tmp); + tmp = marked (other); + if (tmp > 0) + unmark (other); + } + + // Then remove from intersection all marked literals. + + const auto end = coveror.intersection.end (); + auto j = coveror.intersection.begin (); + for (auto k = j; k != end; k++) { + const int other = *j++ = *k; + const int tmp = marked (other); + CADICAL_assert (tmp >= 0); + if (tmp) + j--, unmark (other); // remove marked and unmark it + else + mark (other); // keep unmarked and mark it + } + const size_t new_size = j - coveror.intersection.begin (); + coveror.intersection.resize (new_size); + + if (!coveror.intersection.empty ()) + continue; + + // No covered literal addition candidates in the intersection left! + // Move this clause triggering early abort to the beginning. + // This is a common move to front strategy to minimize effort. + + auto begin = os.begin (); + while (i != begin) { + auto prev = i - 1; + *i = *prev; + i = prev; + } + *begin = c; + + break; // early abort ... + } + } + + bool res = false; + if (first) { + LOG ("all resolution candidates with %d blocked", -lit); + CADICAL_assert (coveror.intersection.empty ()); + cover_push_extension (lit, coveror); + res = true; + } else if (coveror.intersection.empty ()) { + LOG ("empty intersection of resolution candidate literals"); + } else { + LOG (coveror.intersection, + "non-empty intersection of resolution candidate literals"); + covered_literal_addition (lit, coveror); + unmark (coveror.intersection); + coveror.intersection.clear (); + coveror.next.covered = 0; // Restart covering. + } + + unmark (coveror.intersection); + coveror.intersection.clear (); + + return res; +} + +/*------------------------------------------------------------------------*/ + +bool Internal::cover_clause (Clause *c, Coveror &coveror) { + + require_mode (COVER); + CADICAL_assert (!c->garbage); + + LOG (c, "trying covered clauses elimination on"); + bool satisfied = false; + for (const auto &lit : *c) + if (val (lit) > 0) + satisfied = true; + + if (satisfied) { + LOG (c, "clause already satisfied"); + mark_garbage (c); + return false; + } + + CADICAL_assert (coveror.added.empty ()); + CADICAL_assert (coveror.extend.empty ()); + CADICAL_assert (coveror.covered.empty ()); + + CADICAL_assert (!level); + level = 1; + LOG ("assuming literals of candidate clause"); + for (const auto &lit : *c) { + if (val (lit)) + continue; + asymmetric_literal_addition (lit, coveror); + coveror.covered.push_back (lit); + } + + bool tautological = false; + + coveror.next.added = coveror.next.covered = 0; + + while (!tautological) { + if (coveror.next.added < coveror.added.size ()) { + const int lit = coveror.added[coveror.next.added++]; + tautological = cover_propagate_asymmetric (lit, c, coveror); + } else if (coveror.next.covered < coveror.covered.size ()) { + const int lit = coveror.covered[coveror.next.covered++]; + tautological = cover_propagate_covered (lit, coveror); + } else + break; + } + + if (tautological) { + if (coveror.extend.empty ()) { + stats.cover.asymmetric++; + stats.cover.total++; + LOG (c, "asymmetric tautological"); + } else { + stats.cover.blocked++; + stats.cover.total++; + // Only copy extension stack if successful. + int prev = INT_MIN; + bool already_pushed = false; + int64_t last_id = 0; + LOG (c, "covered tautological"); + CADICAL_assert (clause.empty ()); + LOG (coveror.extend, "extension = "); + for (const auto &other : coveror.extend) { + if (!prev) { + // are we finishing a clause? + if (already_pushed) { + // add missing literals that are not needed for covering + // but avoid RAT proofs + for (auto i = 0, j = 0; i < c->size; ++i, ++j) { + const int lit = c->literals[i]; + if (j >= (int) coveror.covered.size () || + c->literals[i] != coveror.covered[j]) { + --j; + LOG ("adding lit %d not needed for ATA", lit); + clause.push_back (lit); + external->push_clause_literal_on_extension_stack (lit); + } + } + } + if (proof && already_pushed) { + if (lrat) + lrat_chain.push_back (c->id); + LOG ("LEARNING clause with id %" PRId64, last_id); + proof->add_derived_clause (last_id, false, clause, lrat_chain); + proof->weaken_plus (last_id, clause); + lrat_chain.clear (); + } + last_id = ++clause_id; + external->push_zero_on_extension_stack (); + external->push_witness_literal_on_extension_stack (other); + external->push_zero_on_extension_stack (); + external->push_id_on_extension_stack (last_id); + external->push_zero_on_extension_stack (); + clause.clear (); + already_pushed = true; + } + if (other) { + external->push_clause_literal_on_extension_stack (other); + clause.push_back (other); + LOG (clause, "current clause is"); + } + prev = other; + } + + if (proof) { + // add missing literals that are not needed for covering + // but avoid RAT proofs + for (auto i = 0, j = 0; i < c->size; ++i, ++j) { + const int lit = c->literals[i]; + if (j >= (int) coveror.covered.size () || + c->literals[i] != coveror.covered[j]) { + --j; + LOG ("adding lit %d not needed for ATA", lit); + clause.push_back (lit); + external->push_clause_literal_on_extension_stack (lit); + } + } + if (lrat) + lrat_chain.push_back (c->id); + proof->add_derived_clause (last_id, false, clause, lrat_chain); + proof->weaken_plus (last_id, clause); + lrat_chain.clear (); + } + clause.clear (); + + mark_garbage (c); + } + } + + // Backtrack and 'unassign' all literals. + + CADICAL_assert (level == 1); + for (const auto &lit : coveror.added) + set_val (lit, 0); + level = 0; + + coveror.covered.clear (); + coveror.extend.clear (); + coveror.added.clear (); + + return tautological; +} + +/*------------------------------------------------------------------------*/ + +// Not yet tried and larger clauses are tried first. + +struct clause_covered_or_smaller { + bool operator() (const Clause *a, const Clause *b) { + if (a->covered && !b->covered) + return true; + if (!a->covered && b->covered) + return false; + return a->size < b->size; + } +}; + +int64_t Internal::cover_round () { + + if (unsat) + return 0; + + init_watches (); + connect_watches (true); // irredundant watches only is enough + + int64_t delta = stats.propagations.search; + delta *= 1e-3 * opts.covereffort; + if (delta < opts.covermineff) + delta = opts.covermineff; + if (delta > opts.covermaxeff) + delta = opts.covermaxeff; + delta = max (delta, ((int64_t) 2) * active ()); + + PHASE ("cover", stats.cover.count, + "covered clause elimination limit of %" PRId64 " propagations", + delta); + + int64_t limit = stats.propagations.cover + delta; + + init_occs (); + + vector schedule; + Coveror coveror; + + // First connect all clauses and find all not yet tried clauses. + // +#ifndef CADICAL_QUIET + int64_t untried = 0; +#endif + // + for (auto c : clauses) { + CADICAL_assert (!c->frozen); + if (c->garbage) + continue; + if (c->redundant) + continue; + bool satisfied = false, allfrozen = true; + for (const auto &lit : *c) + if (val (lit) > 0) { + satisfied = true; + break; + } else if (allfrozen && !frozen (lit)) + allfrozen = false; + if (satisfied) { + mark_garbage (c); + continue; + } + if (allfrozen) { + c->frozen = true; + continue; + } + for (const auto &lit : *c) + occs (lit).push_back (c); + if (c->size < opts.coverminclslim) + continue; + if (c->size > opts.covermaxclslim) + continue; + if (c->covered) + continue; + schedule.push_back (c); +#ifndef CADICAL_QUIET + untried++; +#endif + } + + if (schedule.empty ()) { + + PHASE ("cover", stats.cover.count, "no previously untried clause left"); + + for (auto c : clauses) { + if (c->garbage) + continue; + if (c->redundant) + continue; + if (c->frozen) { + c->frozen = false; + continue; + } + if (c->size < opts.coverminclslim) + continue; + if (c->size > opts.covermaxclslim) + continue; + CADICAL_assert (c->covered); + c->covered = false; + schedule.push_back (c); + } + } else { // Mix of tried and not tried clauses .... + + for (auto c : clauses) { + if (c->garbage) + continue; + if (c->redundant) + continue; + if (c->frozen) { + c->frozen = false; + continue; + } + if (c->size < opts.coverminclslim) + continue; + if (c->size > opts.covermaxclslim) + continue; + if (!c->covered) + continue; + schedule.push_back (c); + } + } + + stable_sort (schedule.begin (), schedule.end (), + clause_covered_or_smaller ()); + +#ifndef CADICAL_QUIET + const size_t scheduled = schedule.size (); + PHASE ("cover", stats.cover.count, + "scheduled %zd clauses %.0f%% with %" PRId64 " untried %.0f%%", + scheduled, percent (scheduled, stats.current.irredundant), untried, + percent (untried, scheduled)); +#endif + + // Heuristically it should be beneficial to intersect with smaller clauses + // first, since then the chances are higher that the intersection of + // resolution candidates becomes emptier earlier. + + for (auto lit : lits) { + if (!active (lit)) + continue; + Occs &os = occs (lit); + stable_sort (os.begin (), os.end (), clause_smaller_size ()); + } + + // This is the main loop of trying to do CCE of candidate clauses. + // + int64_t covered = 0; + // + while (!terminated_asynchronously () && !schedule.empty () && + stats.propagations.cover < limit) { + Clause *c = schedule.back (); + schedule.pop_back (); + c->covered = true; + if (cover_clause (c, coveror)) + covered++; + } + +#ifndef CADICAL_QUIET + const size_t remain = schedule.size (); + const size_t tried = scheduled - remain; + PHASE ("cover", stats.cover.count, + "eliminated %" PRId64 " covered clauses out of %zd tried %.0f%%", + covered, tried, percent (covered, tried)); + if (remain) + PHASE ("cover", stats.cover.count, + "remaining %zu clauses %.0f%% untried", remain, + percent (remain, scheduled)); + else + PHASE ("cover", stats.cover.count, "all scheduled clauses tried"); +#endif + reset_occs (); + reset_watches (); + + return covered; +} + +/*------------------------------------------------------------------------*/ + +bool Internal::cover () { + + if (!opts.cover) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + if (!stats.current.irredundant) + return false; + + // TODO: Our current algorithm for producing the necessary clauses on the + // reconstruction stack for extending the witness requires a covered + // literal addition step which (empirically) conflicts with flushing + // during restoring clauses (see 'regr00{48,51}.trace') even though + // flushing during restore is disabled by default (as is covered clause + // elimination). The consequence of combining these two options + // ('opts.cover' and 'opts.restoreflush') can thus produce incorrect + // witness reconstruction and thus invalid witnesses. This is quite + // infrequent (one out of half billion mobical test cases) but as the two + // regression traces show, does happen. Thus we disable the combination. + // + if (opts.restoreflush) + return false; + + START_SIMPLIFIER (cover, COVER); + + stats.cover.count++; + + // During variable elimination unit clauses can be generated which need to + // be propagated properly over redundant clauses too. Since variable + // elimination avoids to have occurrence lists and watches at the same + // time this propagation is delayed until the end of variable elimination. + // Since we want to interleave CCE with it, we have to propagate here. + // Otherwise this triggers inconsistencies. + // + if (propagated < trail.size ()) { + init_watches (); + connect_watches (); // need to propagated over all clauses! + LOG ("elimination produced %zd units", + (size_t) (trail.size () - propagated)); + if (!propagate ()) { + LOG ("propagating units before covered clause elimination " + "results in empty clause"); + learn_empty_clause (); + CADICAL_assert (unsat); + } + reset_watches (); + } + CADICAL_assert (unsat || propagated == trail.size ()); + + int64_t covered = cover_round (); + + STOP_SIMPLIFIER (cover, COVER); + report ('c', !opts.reportall && !covered); + + return covered; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_decide.cpp b/src/sat/cadical/cadical_decide.cpp new file mode 100644 index 000000000..0874cffd7 --- /dev/null +++ b/src/sat/cadical/cadical_decide.cpp @@ -0,0 +1,258 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// This function determines the next decision variable on the queue, without +// actually removing it from the decision queue, e.g., calling it multiple +// times without any assignment will return the same result. This is of +// course used below in 'decide' but also in 'reuse_trail' to determine the +// largest decision level to backtrack to during 'restart' without changing +// the assigned variables (if 'opts.restartreusetrail' is non-zero). + +int Internal::next_decision_variable_on_queue () { + int64_t searched = 0; + int res = queue.unassigned; + while (val (res)) + res = link (res).prev, searched++; + if (searched) { + stats.searched += searched; + update_queue_unassigned (res); + } + LOG ("next queue decision variable %d bumped %" PRId64 "", res, + bumped (res)); + return res; +} + +// This function determines the best decision with respect to score. +// +int Internal::next_decision_variable_with_best_score () { + int res = 0; + for (;;) { + res = scores.front (); + if (!val (res)) + break; + (void) scores.pop_front (); + } + LOG ("next decision variable %d with score %g", res, score (res)); + return res; +} + +int Internal::next_decision_variable () { + if (use_scores ()) + return next_decision_variable_with_best_score (); + else + return next_decision_variable_on_queue (); +} + +/*------------------------------------------------------------------------*/ + +// Implements phase saving as well using a target phase during +// stabilization unless decision phase is forced to the initial value +// of a phase is forced through the 'phase' option. + +int Internal::decide_phase (int idx, bool target) { + const int initial_phase = opts.phase ? 1 : -1; + int phase = 0; + if (force_saved_phase) + phase = phases.saved[idx]; + if (!phase) + phase = phases.forced[idx]; // swapped with opts.forcephase case! + if (!phase && opts.forcephase) + phase = initial_phase; + if (!phase && target) + phase = phases.target[idx]; + if (!phase) + phase = phases.saved[idx]; + + // The following should not be necessary and in some version we had even + // a hard 'COVER' CADICAL_assertion here to check for this. Unfortunately it + // triggered for some users and we could not get to the root cause of + // 'phase' still not being set here. The logic for phase and target + // saving is pretty complex, particularly in combination with local + // search, and to avoid running in such an issue in the future again, we + // now use this 'defensive' code here, even though such defensive code is + // considered bad programming practice. + // + if (!phase) + phase = initial_phase; + + return phase * idx; +} + +// The likely phase of an variable used in 'collect' for optimizing +// co-location of clauses likely accessed together during search. + +int Internal::likely_phase (int idx) { return decide_phase (idx, false); } + +/*------------------------------------------------------------------------*/ + +// adds new level to control and trail +// +void Internal::new_trail_level (int lit) { + level++; + control.push_back (Level (lit, trail.size ())); +} + +/*------------------------------------------------------------------------*/ + +bool Internal::satisfied () { + if ((size_t) level < assumptions.size () + (!!constraint.size ())) + return false; + if (num_assigned < (size_t) max_var) + return false; + CADICAL_assert (num_assigned == (size_t) max_var); + if (propagated < trail.size ()) + return false; + size_t assigned = num_assigned; + return (assigned == (size_t) max_var); +} + +bool Internal::better_decision (int lit, int other) { + int lit_idx = abs (lit); + int other_idx = abs (other); + if (stable) + return stab[lit_idx] > stab[other_idx]; + else + return btab[lit_idx] > btab[other_idx]; +} + +// Search for the next decision and assign it to the saved phase. Requires +// that not all variables are assigned. + +int Internal::decide () { + CADICAL_assert (!satisfied ()); + START (decide); + int res = 0; + if ((size_t) level < assumptions.size ()) { + const int lit = assumptions[level]; + CADICAL_assert (assumed (lit)); + const signed char tmp = val (lit); + if (tmp < 0) { + LOG ("assumption %d falsified", lit); + res = 20; + } else if (tmp > 0) { + LOG ("assumption %d already satisfied", lit); + new_trail_level (0); + LOG ("added pseudo decision level"); + notify_decision (); + } else { + LOG ("deciding assumption %d", lit); + search_assume_decision (lit); + } + } else if ((size_t) level == assumptions.size () && constraint.size ()) { + + int satisfied_lit = 0; // The literal satisfying the constrain. + int unassigned_lit = 0; // Highest score unassigned literal. + int previous_lit = 0; // Move satisfied literals to the front. + + const size_t size_constraint = constraint.size (); + +#ifndef CADICAL_NDEBUG + unsigned sum = 0; + for (auto lit : constraint) + sum += lit; +#endif + for (size_t i = 0; i != size_constraint; i++) { + + // Get literal and move 'constraint[i] = constraint[i-1]'. + + int lit = constraint[i]; + constraint[i] = previous_lit; + previous_lit = lit; + + const signed char tmp = val (lit); + if (tmp < 0) { + LOG ("constraint literal %d falsified", lit); + continue; + } + + if (tmp > 0) { + LOG ("constraint literal %d satisfied", lit); + satisfied_lit = lit; + break; + } + + CADICAL_assert (!tmp); + LOG ("constraint literal %d unassigned", lit); + + if (!unassigned_lit || better_decision (lit, unassigned_lit)) + unassigned_lit = lit; + } + + if (satisfied_lit) { + + constraint[0] = satisfied_lit; // Move satisfied to the front. + + LOG ("literal %d satisfies constraint and " + "is implied by assumptions", + satisfied_lit); + + new_trail_level (0); + LOG ("added pseudo decision level for constraint"); + notify_decision (); + + } else { + + // Just move all the literals back. If we found an unsatisfied + // literal then it will be satisfied (most likely) at the next + // decision and moved then to the first position. + + if (size_constraint) { + + for (size_t i = 0; i + 1 != size_constraint; i++) + constraint[i] = constraint[i + 1]; + + constraint[size_constraint - 1] = previous_lit; + } + + if (unassigned_lit) { + + LOG ("deciding %d to satisfy constraint", unassigned_lit); + search_assume_decision (unassigned_lit); + + } else { + + LOG ("failing constraint"); + unsat_constraint = true; + res = 20; + } + } + +#ifndef CADICAL_NDEBUG + for (auto lit : constraint) + sum -= lit; + CADICAL_assert (!sum); // Checksum of literal should not change! +#endif + + } else { + + int decision = ask_decision (); + if ((size_t) level < assumptions.size () || + ((size_t) level == assumptions.size () && constraint.size ())) { + // Forced backtrack below pseudo decision levels. + // So one of the two branches above will handle it. + STOP (decide); + res = decide (); // STARTS and STOPS profiling + START (decide); + } else { + stats.decisions++; + if (!decision) { + int idx = next_decision_variable (); + const bool target = (opts.target > 1 || (stable && opts.target)); + decision = decide_phase (idx, target); + } + search_assume_decision (decision); + } + } + if (res) + marked_failed = false; + STOP (decide); + return res; +} +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_decompose.cpp b/src/sat/cadical/cadical_decompose.cpp new file mode 100644 index 000000000..9081321b4 --- /dev/null +++ b/src/sat/cadical/cadical_decompose.cpp @@ -0,0 +1,739 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::decompose_analyze_binary_chain (DFS *dfs, int from) { + if (!lrat) + return; + LOG ("binary chain starting at %d", from); + DFS &from_dfs = dfs[vlit (from)]; + Clause *reason = from_dfs.parent; + if (!reason) + return; + CADICAL_assert (reason->size == 2); + mini_chain.push_back (reason->id); + int other = reason->literals[0]; + other = other == from ? -reason->literals[1] : -other; + Flags &f = flags (other); + if (f.seen) + return; + f.seen = true; + analyzed.push_back (other); + decompose_analyze_binary_chain (dfs, other); +} + +vector Internal::decompose_analyze_binary_clauses (DFS *dfs, + int from) { + vector result; + LOG ("binary chain starting at %d", from); + DFS &from_dfs = dfs[vlit (from)]; + Clause *reason = from_dfs.parent; + while (reason) { + result.push_back (reason); + CADICAL_assert (reason->size == 2); + int other = reason->literals[0]; + other = other == from ? -reason->literals[1] : -other; + Flags &f = flags (other); + if (f.seen) + break; + f.seen = true; + analyzed.push_back (other); + from = other; + DFS &from_dfs = dfs[vlit (from)]; + reason = from_dfs.parent; + } + return result; +} + +void Internal::decompose_conflicting_scc_lrat (DFS *dfs, vector &scc) { + if (!lrat) + return; + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (mini_chain.empty ()); + for (auto &lit : scc) { + Flags &f = flags (lit); + if (f.seen) + return; + f.seen = true; + analyzed.push_back (lit); + decompose_analyze_binary_chain (dfs, lit); + for (auto p = mini_chain.rbegin (); p != mini_chain.rend (); p++) { + lrat_chain.push_back (*p); + } + mini_chain.clear (); + } + clear_analyzed_literals (); +} + +void Internal::build_lrat_for_clause ( + const vector> &dfs_chains, bool invert) { + CADICAL_assert (lrat); + LOG ("building chain for not subsumed clause"); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (sign_marked.empty ()); + // build chain for each replaced literal + for (const auto lit : clause) { + auto other = lit; + if (val (other) > 0) { + if (marked_decomposed (other)) + continue; + mark_decomposed (other); + int64_t id = unit_id (other); + lrat_chain.push_back (id); + continue; + } + CADICAL_assert (mini_chain.empty ()); + for (auto p : dfs_chains[vlit (other)]) { + if (marked_decomposed (other)) + continue; + mark_decomposed (other); + int implied = p->literals[0]; + implied = implied == other ? -p->literals[1] : -implied; + LOG ("ADDED %d -> %d (%" PRId64 ")", implied, other, p->id); + other = implied; + mini_chain.push_back (p->id); + if (val (implied) <= 0) + continue; + if (marked_decomposed (implied)) + break; + mark_decomposed (implied); + int64_t id = unit_id (implied); + mini_chain.push_back (id); + break; + } + if (invert) + for (auto p = mini_chain.rbegin (); p != mini_chain.rend (); p++) + lrat_chain.push_back (*p); + else + for (auto p = mini_chain.begin (); p != mini_chain.end (); p++) + lrat_chain.push_back (*p); + mini_chain.clear (); + } + clear_sign_marked_literals (); + LOG (lrat_chain, "lrat_chain:"); +} + +void Internal::clear_sign_marked_literals () { + LOG ("clearing %zd marked literals", sign_marked.size ()); + for (const auto &lit : sign_marked) { + // CADICAL_assert (marked_signed (lit)); violated on purpose in factor + unmark_decomposed (lit); + } + sign_marked.clear (); +} + +// This performs one round of Tarjan's algorithm, e.g., equivalent literal +// detection and substitution, on the whole formula. We might want to +// repeat it since its application might produce new binary clauses or +// units. Such units might even result in an empty clause. + +bool Internal::decompose_round () { + + if (!opts.decompose) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + + CADICAL_assert (!level); + + START_SIMPLIFIER (decompose, DECOMP); + + stats.decompositions++; + + const size_t size_dfs = 2 * (1 + (size_t) max_var); + DFS *dfs = new DFS[size_dfs]; + DeferDeleteArray dfs_delete (dfs); + int *reprs = new int[size_dfs]; + DeferDeleteArray reprs_delete (reprs); + clear_n (reprs, size_dfs); + vector> dfs_chains; + dfs_chains.resize (size_dfs); + if (lrat) { + for (size_t i = 0; i > size_dfs; i++) { + vector empty; + dfs_chains[i] = empty; + } + } + + int substituted = 0; +#ifndef CADICAL_QUIET + int non_trivial_sccs = 0; + int before = active (); +#endif + unsigned dfs_idx = 0; + + vector work; // depth first search working stack + vector scc; // collects members of one SCC + + // The binary implication graph might have disconnected components and + // thus we have in general to start several depth first searches. + + for (auto root_idx : vars) { + if (unsat) + break; + if (!active (root_idx)) + continue; + for (int root_sign = -1; !unsat && root_sign <= 1; root_sign += 2) { + int root = root_sign * root_idx; + if (dfs[vlit (root)].min == TRAVERSED) + continue; // skip traversed + LOG ("new dfs search starting at root %d", root); + CADICAL_assert (work.empty ()); + CADICAL_assert (scc.empty ()); + work.push_back (root); + while (!unsat && !work.empty ()) { + int parent = work.back (); + DFS &parent_dfs = dfs[vlit (parent)]; + if (parent_dfs.min == TRAVERSED) { // skip traversed + CADICAL_assert (reprs[vlit (parent)]); + work.pop_back (); + } else { + CADICAL_assert (!reprs[vlit (parent)]); + + // Go over all implied literals, thus need to iterate over all + // binary watched clauses with the negation of 'parent'. + + Watches &ws = watches (-parent); + + // Two cases: Either the node has never been visited before, i.e., + // it's depth first search index is zero, then perform the + // 'pre-fix' work before visiting it's children. Otherwise all + // it's children and nodes reachable from those children have been + // visited and their minimum reachable depth first search index + // has been computed. This second case is the 'post-fix' work. + + if (parent_dfs.idx) { // post-fix + + work.pop_back (); // 'parent' done + + // Get the minimum reachable depth first search index reachable + // from the children of 'parent'. + + unsigned new_min = parent_dfs.min; + + for (const auto &w : ws) { + if (!w.binary ()) + continue; + const int child = w.blit; + if (!active (child)) + continue; + DFS &child_dfs = dfs[vlit (child)]; + if (new_min > child_dfs.min) + new_min = child_dfs.min; + } + + LOG ("post-fix work dfs search %d index %u reaches minimum %u", + parent, parent_dfs.idx, new_min); + + if (parent_dfs.idx == new_min) { // entry to SCC + + // All nodes on the 'scc' stack after and including 'parent' + // are in the same SCC. Their representative is computed as + // the smallest literal (index-wise) in the SCC. If the SCC + // contains both a literal and its negation, then the formula + // becomes unsatisfiable. + + if (lrat) { + CADICAL_assert (analyzed.empty ()); + int other, first = 0; + bool conflicting = false; + size_t j = scc.size (); + do { + CADICAL_assert (j > 0); + other = scc[--j]; + if (!first || vlit (other) < vlit (first)) + first = other; + Flags &f = flags (other); + if (other == -parent) { + conflicting = true; // conflicting scc + } + if (f.seen) { + continue; // also conflicting scc + } + f.seen = true; + analyzed.push_back (other); + } while (other != parent); + + CADICAL_assert (!conflicting || first > 0); + vector to_justify; + if (conflicting) { + LOG ("conflicting scc simulating up at %d", parent); + to_justify.push_back (-parent); + } else + to_justify.push_back (first); + while (!to_justify.empty ()) { + const int next = to_justify.back (); + to_justify.pop_back (); + Watches &next_ws = watches (-next); + for (const auto &w : next_ws) { + if (!w.binary ()) + continue; + const int child = w.blit; + if (!active (child)) + continue; + if (!flags (child).seen) + continue; + DFS &child_dfs = dfs[vlit (child)]; + if (child_dfs.parent) + continue; + child_dfs.parent = w.clause; + to_justify.push_back (child); + } + } + + clear_analyzed_literals (); + } + + int other, repr = parent; +#ifndef CADICAL_QUIET + int size = 0; +#endif + CADICAL_assert (!scc.empty ()); + size_t j = scc.size (); + do { + CADICAL_assert (j > 0); + other = scc[--j]; + if (other == -parent) { + LOG ("both %d and %d in one SCC", parent, -parent); + if (lrat) { + Flags &f = flags (-parent); + f.seen = true; + analyzed.push_back (-parent); + decompose_analyze_binary_chain (dfs, parent); + for (auto p : mini_chain) + lrat_chain.push_back (p); + mini_chain.clear (); + } + assign_unit (parent); +#ifndef CADICAL_NDEBUG + bool ok = +#endif + propagate (); + CADICAL_assert (!ok); + learn_empty_clause (); + lrat_chain.clear (); + } else { + if (abs (other) < abs (repr)) + repr = other; +#ifndef CADICAL_QUIET + size++; +#endif + } + } while (!unsat && other != parent); + + if (unsat) + break; +#ifndef CADICAL_QUIET + LOG ("SCC of representative %d of size %d", repr, size); +#endif + do { + CADICAL_assert (!scc.empty ()); + other = scc.back (); + scc.pop_back (); + dfs[vlit (other)].min = TRAVERSED; + if (frozen (other)) { + reprs[vlit (other)] = other; + continue; + } + reprs[vlit (other)] = repr; + if (other == repr) + continue; + substituted++; + LOG ("literal %d in SCC of %d", other, repr); + if (!lrat) + continue; + CADICAL_assert (mini_chain.empty ()); + Flags &f = flags (repr); + f.seen = true; + analyzed.push_back (repr); + // no need to reverse dfs_chain because this is handled by + // build_lrat_for_clause. + dfs_chains[vlit (other)] = + decompose_analyze_binary_clauses (dfs, other); + clear_analyzed_literals (); + } while (other != parent); + +#ifndef CADICAL_QUIET + if (size > 1) + non_trivial_sccs++; +#endif + + } else { + + // Current node 'parent' is in a non-trivial SCC but is not + // the entry point of the SCC in this depth first search, so + // keep it on the SCC stack until the entry point is reached. + + parent_dfs.min = new_min; + } + + } else { // pre-fix + + dfs_idx++; + CADICAL_assert (dfs_idx < TRAVERSED); + parent_dfs.idx = parent_dfs.min = dfs_idx; + scc.push_back (parent); + + LOG ("pre-fix work dfs search %d index %u", parent, dfs_idx); + + // Now traverse all the children in the binary implication + // graph but keep 'parent' on the stack for 'post-fix' work. + + for (const auto &w : ws) { + if (!w.binary ()) + continue; + const int child = w.blit; + if (!active (child)) + continue; + DFS &child_dfs = dfs[vlit (child)]; + if (child_dfs.idx) + continue; + work.push_back (child); + } + } + } + } + } + } + + erase_vector (work); + erase_vector (scc); + // delete [] dfs; need to postpone until after changing clauses... + + // Only keep the representatives 'repr' mapping. + + PHASE ("decompose", stats.decompositions, + "%d non-trivial sccs, %d substituted %.2f%%", non_trivial_sccs, + substituted, percent (substituted, before)); + + bool new_unit = false, new_binary_clause = false; + + // Finally, mark substituted literals as such and push the equivalences of + // the substituted literals to their representative on the extension + // stack to fix an assignment during 'extend'. + // It is also necessary to do so for proper IDRUP/LIDRUP/Resolution proofs + + vector decompose_ids; + const size_t size = 2 * (1 + (size_t) max_var); + decompose_ids.resize (size); + + for (auto idx : vars) { + if (!substituted) + break; + if (unsat) + break; + if (!active (idx)) + continue; + int other = reprs[vlit (idx)]; + if (other == idx) + continue; + CADICAL_assert (!flags (other).eliminated ()); + CADICAL_assert (!flags (other).substituted ()); + + LOG ("marking equivalence of %d and %d", idx, other); + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + clause.push_back (other); + clause.push_back (-idx); + if (lrat) { + build_lrat_for_clause (dfs_chains); + CADICAL_assert (!lrat_chain.empty ()); + } + + const int64_t id1 = ++clause_id; + if (proof) { + proof->add_derived_clause (id1, false, clause, lrat_chain); + proof->weaken_minus (id1, clause); + } + external->push_binary_clause_on_extension_stack (id1, -idx, other); + + decompose_ids[vlit (-idx)] = id1; + + lrat_chain.clear (); + clause.clear (); + + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + clause.push_back (idx); + clause.push_back (-other); + if (lrat) { + build_lrat_for_clause (dfs_chains); + CADICAL_assert (!lrat_chain.empty ()); + } + const int64_t id2 = ++clause_id; + if (proof) { + proof->add_derived_clause (id2, false, clause, lrat_chain); + proof->weaken_minus (id2, clause); + } + external->push_binary_clause_on_extension_stack (id2, idx, -other); + decompose_ids[vlit (idx)] = id2; + + clause.clear (); + lrat_chain.clear (); + } + + vector postponed_garbage; + + // Now go over all clauses and find clause which contain literals that + // should be substituted by their representative. + + size_t clauses_size = clauses.size (); +#ifndef CADICAL_QUIET + size_t garbage = 0, replaced = 0; +#endif + for (size_t i = 0; substituted && !unsat && i < clauses_size; i++) { + Clause *c = clauses[i]; + if (c->garbage) + continue; + int j, size = c->size; + for (j = 0; j < size; j++) { + const int lit = c->literals[j]; + if (reprs[vlit (lit)] != lit) + break; + } + + if (j == size) + continue; + +#ifndef CADICAL_QUIET + replaced++; +#endif + LOG (c, "first substituted literal %d in", substituted); + + // Now copy the result to 'clause'. Substitute literals if they have a + // different representative. Skip duplicates and false literals. If a + // literal occurs in both phases or is assigned to true the clause is + // satisfied and can be marked as garbage. + + CADICAL_assert (clause.empty ()); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (analyzed.empty ()); + bool satisfied = false; + + for (int k = 0; !satisfied && k < size; k++) { + const int lit = c->literals[k]; + signed char tmp = val (lit); + if (tmp > 0) + satisfied = true; + else if (tmp < 0) { + if (!lrat) + continue; + Flags &f = flags (lit); + if (f.seen) + continue; + f.seen = true; + analyzed.push_back (lit); + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + continue; + } else { + const int other = reprs[vlit (lit)]; + tmp = val (other); + if (tmp < 0) { + if (!lrat) + continue; + Flags &f = flags (other); + if (!f.seen) { + f.seen = true; + analyzed.push_back (other); + int64_t id = unit_id (-other); + lrat_chain.push_back (id); + } + if (other == lit) + continue; + int64_t id = decompose_ids[vlit (-lit)]; + CADICAL_assert (id); + lrat_chain.push_back (id); + continue; + } else if (tmp > 0) + satisfied = true; + else { + tmp = marked (other); + if (tmp < 0) + satisfied = true; + else if (!tmp) { + mark (other); + clause.push_back (other); + } + if (other == lit) + continue; + if (!lrat) + continue; + int64_t id = decompose_ids[vlit (-lit)]; + CADICAL_assert (id); + lrat_chain.push_back (id); + } + } + } + if (lrat) + lrat_chain.push_back (c->id); + clear_analyzed_literals (); + LOG (lrat_chain, "lrat_chain:"); + if (satisfied) { + LOG (c, "satisfied after substitution (postponed)"); + postponed_garbage.push_back (c); +#ifndef CADICAL_QUIET + garbage++; +#endif + } else if (!clause.size ()) { + LOG ("learned empty clause during decompose"); + learn_empty_clause (); + } else if (clause.size () == 1) { + LOG (c, "unit %d after substitution", clause[0]); + assign_unit (clause[0]); + mark_garbage (c); + new_unit = true; +#ifndef CADICAL_QUIET + garbage++; +#endif + } else if (c->literals[0] != clause[0] || c->literals[1] != clause[1]) { + LOG ("need new clause since at least one watched literal changed"); + if (clause.size () == 2) + new_binary_clause = true; + size_t d_clause_idx = clauses.size (); + Clause *d = new_clause_as (c); + CADICAL_assert (clauses[d_clause_idx] == d); + clauses[d_clause_idx] = c; + clauses[i] = d; + mark_garbage (c); +#ifndef CADICAL_QUIET + garbage++; +#endif + } else { + LOG ("simply shrinking clause since watches did not change"); + CADICAL_assert (c->size > 2); + if (!c->redundant) + mark_removed (c); + if (proof) { + proof->add_derived_clause (++clause_id, c->redundant, clause, + lrat_chain); + proof->delete_clause (c); + c->id = clause_id; + } + size_t l; + int *literals = c->literals; + for (l = 2; l < clause.size (); l++) + literals[l] = clause[l]; + int flushed = c->size - (int) l; + if (flushed) { + if (l == 2) + new_binary_clause = true; + LOG ("flushed %d literals", flushed); + (void) shrink_clause (c, l); + } else if (likely_to_be_kept_clause (c)) + mark_added (c); + // we have shrunken c->size to l so even though there is an CADICAL_assertion + // for c->size > 2 at the beginning of this else block, the new size + // can be 2 now. + if (c->size == 2) { // cheaper to update only new binary clauses + CADICAL_assert (new_binary_clause); + update_watch_size (watches (c->literals[0]), c->literals[1], c); + update_watch_size (watches (c->literals[1]), c->literals[0], c); + } + LOG (c, "substituted"); + } + while (!clause.empty ()) { + int lit = clause.back (); + clause.pop_back (); + CADICAL_assert (marked (lit) > 0); + unmark (lit); + } + lrat_chain.clear (); + } + + if (proof) { + for (auto idx : vars) { + if (!substituted) + break; + if (!active (idx)) + continue; + const int64_t id1 = decompose_ids[vlit (-idx)]; + if (!id1) + continue; + int other = reprs[vlit (idx)]; + CADICAL_assert (other != idx); + CADICAL_assert (!flags (other).eliminated ()); + CADICAL_assert (!flags (other).substituted ()); + + clause.push_back (other); + clause.push_back (-idx); + proof->delete_clause (id1, false, clause); + clause.clear (); + + clause.push_back (idx); + clause.push_back (-other); + const int64_t id2 = decompose_ids[vlit (idx)]; + proof->delete_clause (id2, false, clause); + clause.clear (); + } + } + + if (!unsat && !postponed_garbage.empty ()) { + LOG ("now marking %zd postponed garbage clauses", + postponed_garbage.size ()); + for (const auto &c : postponed_garbage) + mark_garbage (c); + } + erase_vector (postponed_garbage); + + PHASE ("decompose", stats.decompositions, + "%zd clauses replaced %.2f%% producing %zd garbage clauses %.2f%%", + replaced, percent (replaced, clauses_size), garbage, + percent (garbage, replaced)); + + erase_vector (scc); + + // Propagate found units. + + if (!unsat && propagated < trail.size () && !propagate ()) { + LOG ("empty clause after propagating units from substitution"); + learn_empty_clause (); + } + + for (auto idx : vars) { + if (!substituted) + break; + if (unsat) + break; + if (!active (idx)) + continue; + int other = reprs[vlit (idx)]; + if (other == idx) + continue; + CADICAL_assert (!flags (other).eliminated ()); + CADICAL_assert (!flags (other).substituted ()); + if (!flags (other).fixed ()) + mark_substituted (idx); + } + + reprs_delete.free (); + dfs_delete.free (); + erase_vector (dfs_chains); + + if (substituted) + flush_all_occs_and_watches (); // particularly the 'blit's + + bool success = + unsat || (substituted > 0 && (new_unit || new_binary_clause)); + report ('d', !opts.reportall && !success); + + STOP_SIMPLIFIER (decompose, DECOMP); + + return success; +} + +void Internal::decompose () { + for (int round = 1; round <= opts.decomposerounds; round++) + if (!decompose_round ()) + break; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_deduplicate.cpp b/src/sat/cadical/cadical_deduplicate.cpp new file mode 100644 index 000000000..49d26a571 --- /dev/null +++ b/src/sat/cadical/cadical_deduplicate.cpp @@ -0,0 +1,176 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Equivalent literal substitution in 'decompose' and shrinking in 'subsume' +// or 'vivify' might produce duplicated binary clauses. They can not be +// found in 'subsume' nor 'vivify' since we explicitly do not consider +// binary clauses as candidates to be shrunken or subsumed. They are +// detected here by a simple scan of watch lists and then marked as garbage. +// This is actually also quite fast. + +// Further it might also be possible that two binary clauses can be resolved +// to produce a unit (we call it 'hyper unary resolution'). For example +// resolving the binary clauses '1 -2' and '1 2' produces the unit '1'. +// This could be found by probing in 'probe' unless '-1' also occurs in a +// binary clause (add the clause '-1 2' to those two clauses) in which case +// '1' as well as '2' both occur positively as well as negatively and none +// of them nor their negation is considered as probe + +void Internal::mark_duplicated_binary_clauses_as_garbage () { + + if (!opts.deduplicate) + return; + if (unsat) + return; + if (terminated_asynchronously ()) + return; + + START_SIMPLIFIER (deduplicate, DEDUP); + stats.deduplications++; + + CADICAL_assert (!level); + CADICAL_assert (watching ()); + + vector stack; // To save marked literals and unmark them later. + + int64_t subsumed = 0; + int64_t units = 0; + + for (auto idx : vars) { + + if (unsat) + break; + if (!active (idx)) + continue; + int unit = 0; + + for (int sign = -1; !unit && sign <= 1; sign += 2) { + + const int lit = sign * idx; // Consider all literals. + + CADICAL_assert (stack.empty ()); + Watches &ws = watches (lit); + + // We are removing references to garbage clause. Thus no 'auto'. + + const const_watch_iterator end = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i; + + for (i = j; !unit && i != end; i++) { + Watch w = *j++ = *i; + if (!w.binary ()) + continue; + int other = w.blit; + const int tmp = marked (other); + Clause *c = w.clause; + + if (tmp > 0) { // Found duplicated binary clause. + + if (c->garbage) { + j--; + continue; + } + LOG (c, "found duplicated"); + + // The previous identical clause 'd' might be redundant and if the + // second clause 'c' is not (so irredundant), then we have to keep + // 'c' instead of 'd', thus we search for it and replace it. + + if (!c->redundant) { + watch_iterator k; + for (k = ws.begin ();; k++) { + CADICAL_assert (k != i); + if (!k->binary ()) + continue; + if (k->blit != other) + continue; + Clause *d = k->clause; + if (d->garbage) + continue; + c = d; + break; + } + *k = w; + } + + LOG (c, "mark garbage duplicated"); + stats.subsumed++; + stats.deduplicated++; + subsumed++; + mark_garbage (c); + j--; + + } else if (tmp < 0) { // Hyper unary resolution. + + LOG ("found %d %d and %d %d which produces unit %d", lit, -other, + lit, other, lit); + unit = lit; + if (lrat) { + // taken from fradical + CADICAL_assert (lrat_chain.empty ()); + lrat_chain.push_back (c->id); + // We've forgotten where the other binary clause is, so go find + // it again + for (watch_iterator k = ws.begin ();; k++) { + CADICAL_assert (k != i); + if (!k->binary ()) + continue; + if (k->blit != -other) + continue; + lrat_chain.push_back (k->clause->id); + break; + } + } + j = ws.begin (); // Flush 'ws'. + units++; + + } else { + if (c->garbage) + continue; + mark (other); + stack.push_back (other); + } + } + + if (j == ws.begin ()) + erase_vector (ws); + else if (j != end) + ws.resize (j - ws.begin ()); // Shrink watchers. + + for (const auto &other : stack) + unmark (other); + + stack.clear (); + } + + // Propagation potentially messes up the watches and thus we can not + // propagate the unit immediately after finding it. Instead we break + // out of both loops and assign and propagate the unit here. + + if (unit) { + + stats.failed++; + stats.hyperunary++; + assign_unit (unit); + // lrat_chain.clear (); done in search_assign + + if (!propagate ()) { + LOG ("empty clause after propagating unit"); + learn_empty_clause (); + } + } + } + STOP_SIMPLIFIER (deduplicate, DEDUP); + + report ('2', !opts.reportall && !(subsumed + units)); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_definition.cpp b/src/sat/cadical/cadical_definition.cpp new file mode 100644 index 000000000..7c8fe4fbb --- /dev/null +++ b/src/sat/cadical/cadical_definition.cpp @@ -0,0 +1,289 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +#define INVALID_LIT UINT_MAX + +// functions below are passed to cadical_kitten +// +struct definition_extractor { + Eliminator *eliminator; + Internal *internal; + vector clauses[2]; + int lit; + vector> implicants; + int unit; +}; + +extern "C" { + +// used to extract definitions from cadical_kitten +// +static void traverse_definition_core (void *state, unsigned id) { + definition_extractor *extractor = (definition_extractor *) state; + Clause *clause; + const vector &clauses0 = extractor->clauses[0]; + const vector &clauses1 = extractor->clauses[1]; + Eliminator *eliminator = extractor->eliminator; + const size_t size_clauses0 = clauses0.size (); + const size_t size_clauses1 = clauses1.size (); + CADICAL_assert (size_clauses0 <= UINT_MAX); + unsigned sign; + CADICAL_assert (id < size_clauses0 + size_clauses1); + if (id < size_clauses0) { + clause = clauses0[id]; + sign = 1; + } else { + unsigned tmp = id - size_clauses0; +#ifndef CADICAL_NDEBUG + CADICAL_assert (size_clauses1 <= UINT_MAX); + CADICAL_assert (tmp < size_clauses1); +#endif + clause = clauses1[tmp]; + sign = 2; + } + (void) size_clauses1; + clause->gate = true; + eliminator->gates.push_back (clause); +#ifdef LOGGING + Internal *internal = extractor->internal; + LOG (clause, "extracted gate"); +#endif + eliminator->definition_unit |= sign; +} + +// extracts relevant learned clauses from kissat for drat proofs +// +static void traverse_one_sided_core_lemma (void *state, bool learned, + size_t size, + const unsigned *lits) { + if (!learned) + return; + definition_extractor *extractor = (definition_extractor *) state; + Eliminator *eliminator = extractor->eliminator; + Internal *internal = extractor->internal; + Proof *proof = internal->proof; + const int unit = extractor->unit; + vector &proof_clauses = eliminator->proof_clauses; + if (size) { + proof_clause pc; + pc.id = ++(internal->clause_id); + pc.literals.push_back (unit); + const unsigned *end = lits + size; + for (const unsigned *p = lits; p != end; p++) + pc.literals.push_back (internal->citten2lit (*p)); // conversion + proof_clauses.push_back (pc); + CADICAL_assert (proof); + proof->add_derived_clause (pc.id, true, pc.literals, pc.chain); + } else { + internal->assign_unit (unit); + for (const auto &pc : proof_clauses) { + proof->delete_clause (pc.id, true, pc.literals); + } + proof_clauses.clear (); + } +} + +// extract lrat proofs for relevant clauses +// +static void traverse_one_sided_core_lemma_with_lrat ( + void *state, unsigned cid, unsigned id, bool learned, size_t size, + const unsigned *lits, size_t chain_size, const unsigned *chain) { + definition_extractor *extractor = (definition_extractor *) state; + Eliminator *eliminator = extractor->eliminator; + Internal *internal = extractor->internal; + Proof *proof = internal->proof; + const int unit = extractor->unit; + const vector &clauses0 = extractor->clauses[0]; + const vector &clauses1 = extractor->clauses[1]; + vector &proof_clauses = eliminator->proof_clauses; + if (!learned) { // remember clauses for mapping to cadical_kitten internal + CADICAL_assert (size); + CADICAL_assert (!chain_size); + proof_clause pc; + pc.cid = cid; + pc.learned = false; + const size_t size_clauses0 = clauses0.size (); + CADICAL_assert (size_clauses0 <= UINT_MAX); + if (id < size_clauses0) { + pc.id = clauses0[id]->id; + } else { + unsigned tmp = id - size_clauses0; +#ifndef CADICAL_NDEBUG + const size_t size_clauses1 = clauses1.size (); + CADICAL_assert (size_clauses1 <= UINT_MAX); + CADICAL_assert (tmp < size_clauses1); +#endif + pc.id = clauses1[tmp]->id; + } + proof_clauses.push_back (pc); + } else { // actually add to proof + CADICAL_assert (chain_size); + if (size) { + proof_clause pc; + pc.id = ++(internal->clause_id); + pc.cid = cid; + pc.learned = true; + pc.literals.push_back (unit); + const unsigned *end = lits + size; + for (const unsigned *p = lits; p != end; p++) + pc.literals.push_back (internal->citten2lit (*p)); // conversion + for (const unsigned *p = chain + chain_size; p != chain; p--) { + int64_t id = 0; + for (const auto &cpc : proof_clauses) { + if (cpc.cid == *(p - 1)) { + id = cpc.id; + break; + } + } + CADICAL_assert (id); + pc.chain.push_back (id); + } + proof_clauses.push_back (pc); + CADICAL_assert (proof); + proof->add_derived_clause (pc.id, true, pc.literals, pc.chain); + } else { // learn unit finish proof + CADICAL_assert (internal->lrat_chain.empty ()); + for (const unsigned *p = chain + chain_size; p != chain; p--) { + int64_t id = 0; + for (const auto &cpc : proof_clauses) { + if (cpc.cid == *(p - 1)) { + id = cpc.id; + break; + } + } + CADICAL_assert (id); + internal->lrat_chain.push_back (id); + } + internal->assign_unit (unit); + CADICAL_assert (internal->lrat_chain.empty ()); + for (const auto &pc : proof_clauses) { + if (pc.learned) + proof->delete_clause (pc.id, true, pc.literals); + } + proof_clauses.clear (); + } + } +} + +} // end extern C + +// Code ported from kissat. Kitten (and kissat) use unsigned representation +// for literals whereas CaDiCaL uses signed representation. Conversion is +// necessary for communication using lit2citten and citten2lit. +// This code is called in elim and cadical_kitten is initialized beforehand. +// To avoid confusion all cadical interal definitions with cadical_kitten are called +// citten. +// +void Internal::find_definition (Eliminator &eliminator, int lit) { + if (!opts.elimdef) + return; + if (unsat) + return; + if (val (lit)) + return; + if (!eliminator.gates.empty ()) + return; + CADICAL_assert (!val (lit)); + CADICAL_assert (!level); + CADICAL_assert (citten); + const int not_lit = -lit; + definition_extractor extractor; + extractor.lit = lit; + extractor.clauses[0] = occs (lit); + extractor.clauses[1] = occs (not_lit); + extractor.eliminator = &eliminator; + extractor.internal = internal; + citten_clear_track_log_terminate (); + unsigned exported = 0; + for (unsigned sign = 0; sign < 2; sign++) { + const unsigned except = sign ? lit2citten (not_lit) : lit2citten (lit); + for (auto c : extractor.clauses[sign]) { + // to avoid copying the literals of c in their unsigned + // representation we instead implement the translation in cadical_kitten + if (!c->garbage) { + LOG (c, "adding to cadical_kitten"); + citten_clause_with_id_and_exception (citten, exported, c->size, + c->literals, except); + } + exported++; + } + } + stats.definitions_checked++; + const size_t limit = opts.elimdefticks; + cadical_kitten_set_ticks_limit (citten, limit); + int status = cadical_kitten_solve (citten); + if (!exported) + goto ABORT; + if (status == 20) { + LOG ("sub-solver result UNSAT shows definition exists"); + uint64_t learned; + unsigned reduced = cadical_kitten_compute_clausal_core (citten, &learned); + LOG ("1st sub-solver core of size %u original clauses out of %u", + reduced, exported); + for (int i = 2; i <= opts.elimdefcores; i++) { + cadical_kitten_shrink_to_clausal_core (citten); + cadical_kitten_shuffle_clauses (citten); + cadical_kitten_set_ticks_limit (citten, 10 * limit); + int tmp = cadical_kitten_solve (citten); + CADICAL_assert (!tmp || tmp == 20); + if (!tmp) { + LOG ("aborting core extraction"); + goto ABORT; + } +#ifndef CADICAL_NDEBUG + unsigned previous = reduced; +#endif + reduced = cadical_kitten_compute_clausal_core (citten, &learned); + LOG ("%d sub-solver core of size %u original clauses out of %u", i, + reduced, exported); + CADICAL_assert (reduced <= previous); +#if not defined(LOGGING) && defined(CADICAL_NDEBUG) + (void) reduced; +#endif + } + stats.definitions_extracted++; + eliminator.gatetype = DEF; + eliminator.definition_unit = 0; + cadical_kitten_traverse_core_ids (citten, &extractor, traverse_definition_core); + CADICAL_assert (eliminator.definition_unit); + int unit = 0; + if (eliminator.definition_unit == 2) { + unit = not_lit; + } else if (eliminator.definition_unit == 1) + unit = lit; + + if (unit) { + stats.definition_units++; + VERBOSE (2, "one sided core " + "definition extraction yields " + "failed literal"); + if (proof) { + if (lrat) { + extractor.unit = unit; + cadical_kitten_trace_core (citten, &extractor, + traverse_one_sided_core_lemma_with_lrat); + } else { + extractor.unit = unit; + cadical_kitten_traverse_core_clauses (citten, &extractor, + traverse_one_sided_core_lemma); + } + } else + assign_unit (unit); + elim_propagate (eliminator, unit); + } + } else { + ABORT: + LOG ("sub-solver failed to show that definition exists"); + } + stats.definition_ticks += cadical_kitten_current_ticks (citten); + return; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_drattracer.cpp b/src/sat/cadical/cadical_drattracer.cpp new file mode 100644 index 000000000..eb73b4c95 --- /dev/null +++ b/src/sat/cadical/cadical_drattracer.cpp @@ -0,0 +1,159 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +DratTracer::DratTracer (Internal *i, File *f, bool b) + : internal (i), file (f), binary (b) +#ifndef CADICAL_QUIET + , + added (0), deleted (0) +#endif +{ + (void) internal; +} + +void DratTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("DRAT TRACER connected to internal"); +} + +DratTracer::~DratTracer () { + LOG ("DRAT TRACER delete"); + delete file; +} + +/*------------------------------------------------------------------------*/ + +inline void DratTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void DratTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned idx = abs (lit); + CADICAL_assert (idx < (1u << 31)); + unsigned x = 2u * idx + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void DratTracer::drat_add_clause (const vector &clause) { + if (binary) + file->put ('a'); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} +void DratTracer::drat_delete_clause (const vector &clause) { + if (binary) + file->put ('d'); + else + file->put ("d "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +/*------------------------------------------------------------------------*/ + +void DratTracer::add_derived_clause (int64_t, bool, + const vector &clause, + const vector &) { + if (file->closed ()) + return; + LOG ("DRAT TRACER tracing addition of derived clause"); + drat_add_clause (clause); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void DratTracer::delete_clause (int64_t, bool, const vector &clause) { + if (file->closed ()) + return; + LOG ("DRAT TRACER tracing deletion of clause"); + drat_delete_clause (clause); +#ifndef CADICAL_QUIET + deleted++; +#endif +} + +/*------------------------------------------------------------------------*/ + +bool DratTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void DratTracer::print_statistics () { + uint64_t bytes = file->bytes (); + uint64_t total = added + deleted; + MSG ("DRAT %" PRId64 " added clauses %.2f%%", added, + percent (added, total)); + MSG ("DRAT %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("DRAT %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void DratTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("DRAT proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void DratTracer::flush (bool print) { + CADICAL_assert (!closed ()); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("DRAT proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_elim.cpp b/src/sat/cadical/cadical_elim.cpp new file mode 100644 index 000000000..951dfee74 --- /dev/null +++ b/src/sat/cadical/cadical_elim.cpp @@ -0,0 +1,1178 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Implements a variant of bounded variable elimination as originally +// described in our SAT'05 paper introducing 'SATeLite'. This is an +// inprocessing version, i.e., it is interleaved with search and triggers +// subsumption and strengthening, blocked and covered clause elimination +// during elimination rounds. It focuses only those variables which +// occurred in removed irredundant clauses since the last time an +// elimination round was run. By bounding the maximum resolvent size we can +// run each elimination round until completion. See the code of 'elim' for +// how elimination rounds are interleaved with blocked clause elimination +// and subsumption (which in turn also calls vivification and transitive +// reduction of the binary implication graph). + +/*------------------------------------------------------------------------*/ + +inline double Internal::compute_elim_score (unsigned lit) { + CADICAL_assert (1 <= lit), CADICAL_assert (lit <= (unsigned) max_var); + const unsigned uidx = 2 * lit; + const double pos = internal->ntab[uidx]; + const double neg = internal->ntab[uidx + 1]; + if (!pos) + return -neg; + if (!neg) + return -pos; + double sum = 0, prod = 0; + if (opts.elimsum) + sum = opts.elimsum * (pos + neg); + if (opts.elimprod) + prod = opts.elimprod * (pos * neg); + return prod + sum; +} + +/*------------------------------------------------------------------------*/ + +bool elim_more::operator() (unsigned a, unsigned b) { + const auto s = internal->compute_elim_score (a); + const auto t = internal->compute_elim_score (b); + if (s > t) + return true; + if (s < t) + return false; + return a > b; +} + +/*------------------------------------------------------------------------*/ + +// Note that the new fast subsumption algorithm implemented in 'subsume' +// does not distinguish between irredundant and redundant clauses and is +// also run during search to strengthen and remove 'sticky' redundant +// clauses but also irredundant ones. So beside learned units during search +// or as consequence of other preprocessors, these subsumption rounds during +// search can remove (irredundant) clauses (and literals), which in turn +// might make new bounded variable elimination possible. This is tested +// in the 'bool ineliminating ()' guard. + +bool Internal::ineliminating () { + + if (!opts.elim) + return false; + if (!preprocessing && !opts.inprocessing) + return false; + if (preprocessing) + CADICAL_assert (lim.preprocessing); + + // Respect (increasing) conflict limit. + // + if (lim.elim >= stats.conflicts) + return false; + + // Wait until there are new units or new removed variables + // (in removed or shrunken irredundant clauses and thus marked). + // + if (last.elim.fixed < stats.all.fixed) + return true; + if (last.elim.marked < stats.mark.elim) + return true; + + // VERBOSE (3, "elim not scheduled due to fixpoint"); + return false; +} + +/*------------------------------------------------------------------------*/ + +// Update the global elimination schedule after adding or removing a clause. + +void Internal::elim_update_added_clause (Eliminator &eliminator, + Clause *c) { + CADICAL_assert (!c->redundant); + ElimSchedule &schedule = eliminator.schedule; + for (const auto &lit : *c) { + if (!active (lit)) + continue; + occs (lit).push_back (c); + if (frozen (lit)) + continue; + noccs (lit)++; + const int idx = abs (lit); + if (schedule.contains (idx)) + schedule.update (idx); + } +} + +void Internal::elim_update_removed_lit (Eliminator &eliminator, int lit) { + if (!active (lit)) + return; + if (frozen (lit)) + return; + int64_t &score = noccs (lit); + CADICAL_assert (score > 0); + score--; + const int idx = abs (lit); + ElimSchedule &schedule = eliminator.schedule; + if (schedule.contains (idx)) + schedule.update (idx); + else { + LOG ("rescheduling %d for elimination after removing clause", idx); + schedule.push_back (idx); + } +} + +void Internal::elim_update_removed_clause (Eliminator &eliminator, + Clause *c, int except) { + CADICAL_assert (!c->redundant); + for (const auto &lit : *c) { + if (lit == except) + continue; + CADICAL_assert (lit != -except); + elim_update_removed_lit (eliminator, lit); + } +} + +/*------------------------------------------------------------------------*/ + +// Since we do not have watches we have to do our own unit propagation +// during elimination as soon we find a unit clause. This finds new units +// and also marks clauses satisfied by those units as garbage immediately. + +void Internal::elim_propagate (Eliminator &eliminator, int root) { + CADICAL_assert (val (root) > 0); + vector work; + size_t i = 0; + work.push_back (root); + while (i < work.size ()) { + int lit = work[i++]; + LOG ("elimination propagation of %d", lit); + CADICAL_assert (val (lit) > 0); + const Occs &ns = occs (-lit); + for (const auto &c : ns) { + if (c->garbage) + continue; + int unit = 0, satisfied = 0; + for (const auto &other : *c) { + const signed char tmp = val (other); + if (tmp < 0) + continue; + if (tmp > 0) { + satisfied = other; + break; + } + if (unit) + unit = INT_MIN; + else + unit = other; + } + if (satisfied) { + LOG (c, "elimination propagation of %d finds %d satisfied", lit, + satisfied); + elim_update_removed_clause (eliminator, c, satisfied); + mark_garbage (c); + } else if (!unit) { + LOG ("empty clause during elimination propagation of %d", lit); + // need to set conflict = c for lrat + conflict = c; + learn_empty_clause (); + conflict = 0; + break; + } else if (unit != INT_MIN) { + LOG ("new unit %d during elimination propagation of %d", unit, lit); + build_chain_for_units (unit, c, 0); + assign_unit (unit); + work.push_back (unit); + } + } + if (unsat) + break; + const Occs &ps = occs (lit); + for (const auto &c : ps) { + if (c->garbage) + continue; + LOG (c, "elimination propagation of %d produces satisfied", lit); + elim_update_removed_clause (eliminator, c, lit); + mark_garbage (c); + } + } +} + +/*------------------------------------------------------------------------*/ + +// On-the-fly self-subsuming resolution during variable elimination is due +// to HyoJung Han, Fabio Somenzi, SAT'09. Basically while resolving two +// clauses we test the resolvent to be smaller than one of the antecedents. +// If this is the case the pivot can be removed from the antecedent +// on-the-fly and the resolution can be skipped during elimination. + +void Internal::elim_on_the_fly_self_subsumption (Eliminator &eliminator, + Clause *c, int pivot) { + LOG (c, "pivot %d on-the-fly self-subsuming resolution", pivot); + stats.elimotfstr++; + stats.strengthened++; + CADICAL_assert (clause.empty ()); + for (const auto &lit : *c) { + if (lit == pivot) + continue; + const signed char tmp = val (lit); + CADICAL_assert (tmp <= 0); + if (tmp < 0) + continue; + clause.push_back (lit); + } + Clause *r = new_resolved_irredundant_clause (); + elim_update_added_clause (eliminator, r); + clause.clear (); + lrat_chain.clear (); + elim_update_removed_clause (eliminator, c, pivot); + mark_garbage (c); +} + +/*------------------------------------------------------------------------*/ + +// Resolve two clauses on the pivot literal 'pivot', which is assumed to +// occur in opposite phases in 'c' and 'd'. The actual resolvent is stored +// in the temporary global 'clause' if it is not redundant. It is +// considered redundant if one of the clauses is already marked as garbage +// it is root level satisfied, the resolvent is empty, a unit, or produces a +// self-subsuming resolution, which results in the pivot to be removed from +// at least one of the antecedents. + +// Note that current root level assignments are taken into account, i.e., by +// removing root level falsified literals. The function returns true if the +// resolvent is not redundant and for instance has to be taken into account +// during bounded variable elimination. + +// Detected units are immediately assigned and in case the last argument is +// true also propagated eagerly in a elimination specific propagation +// routine, which not only finds units but also updates the schedule. + +// When this function is called during computation of the number of +// non-trivial (or non-satisfied) resolvents we can eagerly propagate units. +// But during actually adding the resolvents this results in problems as we +// found one rare test case '../test/trace/reg0056.trace' (out of billions), +// where the pivot itself was assigned during such a propagation while +// adding resolvents and lead to pushing a clause to the reconstruction +// stack that later flipped the value of the pivot (while all other literals +// in that clause were unit implied too). Not pushing the pivot clauses to +// the reconstruction stack produced a wrong model too. Our fix is to only +// eagerly propagate during computation of the number of resolvents and +// otherwise delay propagation until the end of elimination (which is less +// precise regarding scheduling but very rarely happens). + +bool Internal::resolve_clauses (Eliminator &eliminator, Clause *c, + int pivot, Clause *d, + const bool propagate_eagerly) { + + CADICAL_assert (!c->redundant); + CADICAL_assert (!d->redundant); + + stats.elimres++; + + if (c->garbage || d->garbage) + return false; + if (c->size > d->size) { + pivot = -pivot; + swap (c, d); + } + + CADICAL_assert (!level); + CADICAL_assert (clause.empty ()); + + int satisfied = 0; // Contains this satisfying literal. + int tautological = 0; // Clashing literal if tautological. + + int s = 0; // Actual literals from 'c'. + int t = 0; // Actual literals from 'd'. + + // First determine whether the first antecedent is satisfied, add its + // literals to 'clause' and mark them (except for 'pivot'). + // + for (const auto &lit : *c) { + if (lit == pivot) { + s++; + continue; + } + CADICAL_assert (lit != -pivot); + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = lit; + break; + } else if (tmp < 0) { + if (!lrat) + continue; + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + continue; + } else + mark (lit), clause.push_back (lit), s++; + } + if (satisfied) { + LOG (c, "satisfied by %d antecedent", satisfied); + elim_update_removed_clause (eliminator, c, satisfied); + mark_garbage (c); + clause.clear (); + lrat_chain.clear (); + clear_analyzed_literals (); + unmark (c); + return false; + } + + // Then determine whether the second antecedent is satisfied, add its + // literal to 'clause' and check whether a clashing literal is found, such + // that the resolvent would be tautological. + // + for (const auto &lit : *d) { + if (lit == -pivot) { + t++; + continue; + } + CADICAL_assert (lit != pivot); + signed char tmp = val (lit); + if (tmp > 0) { + satisfied = lit; + break; + } else if (tmp < 0) { + if (!lrat) + continue; + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + continue; + } else if ((tmp = marked (lit)) < 0) { + tautological = lit; + break; + } else if (!tmp) + clause.push_back (lit), t++; + else + CADICAL_assert (tmp > 0), t++; + } + + clear_analyzed_literals (); + unmark (c); + const int64_t size = clause.size (); + + if (lrat) { + lrat_chain.push_back (d->id); + lrat_chain.push_back (c->id); + } + + if (satisfied) { + LOG (d, "satisfied by %d antecedent", satisfied); + elim_update_removed_clause (eliminator, d, satisfied); + mark_garbage (d); + clause.clear (); + lrat_chain.clear (); + return false; + } + + LOG (c, "first antecedent"); + LOG (d, "second antecedent"); + + if (tautological) { + clause.clear (); + LOG ("resolvent tautological on %d", tautological); + lrat_chain.clear (); + return false; + } + + if (!size) { + clause.clear (); + LOG ("empty resolvent"); + learn_empty_clause (); // already clears lrat_chain. + return false; + } + + if (size == 1) { + int unit = clause[0]; + LOG ("unit resolvent %d", unit); + clause.clear (); + assign_unit (unit); // already clears lrat_chain. + if (propagate_eagerly) + elim_propagate (eliminator, unit); + return false; + } + + LOG (clause, "resolvent"); + CADICAL_assert (!lrat || !lrat_chain.empty ()); + + // Double self-subsuming resolution. The clauses 'c' and 'd' are + // identical except for the pivot which occurs in different phase. The + // resolvent subsumes both antecedents. + + if (s > size && t > size) { + CADICAL_assert (s == size + 1); + CADICAL_assert (t == size + 1); + clause.clear (); + // LRAT is c + d (+ eventual units) + elim_on_the_fly_self_subsumption (eliminator, c, pivot); + LOG (d, "double pivot %d on-the-fly self-subsuming resolution", -pivot); + stats.elimotfsub++; + stats.subsumed++; + elim_update_removed_clause (eliminator, d, -pivot); + mark_garbage (d); + return false; + } + + // Single self-subsuming resolution: The pivot can be removed from 'c', + // which is implemented by adding a clause which is the same as 'c' but + // with 'pivot' removed and then marking 'c' as garbage. + + if (s > size) { + CADICAL_assert (s == size + 1); + clause.clear (); + // LRAT is c + d (+ eventual units) + elim_on_the_fly_self_subsumption (eliminator, c, pivot); + return false; + } + + // Same single self-subsuming resolution situation, but only for 'd'. + + if (t > size) { + CADICAL_assert (t == size + 1); + clause.clear (); + // LRAT is c + d (+ eventual units) -> same. + elim_on_the_fly_self_subsumption (eliminator, d, -pivot); + return false; + } + if (propagate_eagerly) + lrat_chain.clear (); + return true; +} + +/*------------------------------------------------------------------------*/ + +// Check whether the number of non-tautological resolvents on 'pivot' is +// smaller or equal to the number of clauses with 'pivot' or '-pivot'. This +// is the main criteria of bounded variable elimination. As a side effect +// it flushes garbage clauses with that variable, sorts its occurrence lists +// (smallest clauses first) and also negates pivot if it has more positive +// than negative occurrences. + +bool Internal::elim_resolvents_are_bounded (Eliminator &eliminator, + int pivot) { + const bool substitute = !eliminator.gates.empty (); + const bool resolve_gates = eliminator.definition_unit; + if (substitute) + LOG ("trying to substitute %d", pivot); + + stats.elimtried++; + + CADICAL_assert (!unsat); + CADICAL_assert (active (pivot)); + + const Occs &ps = occs (pivot); + const Occs &ns = occs (-pivot); + const int64_t pos = ps.size (); + const int64_t neg = ns.size (); + if (!pos || !neg) + return lim.elimbound >= 0; + const int64_t bound = pos + neg + lim.elimbound; + + LOG ("checking number resolvents on %d bounded by " + "%" PRId64 " = %" PRId64 " + %" PRId64 " + %" PRId64, + pivot, bound, pos, neg, lim.elimbound); + + // Try all resolutions between a positive occurrence (outer loop) of + // 'pivot' and a negative occurrence of 'pivot' (inner loop) as long the + // bound on non-tautological resolvents is not hit and the size of the + // generated resolvents does not exceed the resolvent clause size limit. + + int64_t resolvents = 0; // Non-tautological resolvents. + + for (const auto &c : ps) { + CADICAL_assert (!c->redundant); + if (c->garbage) + continue; + for (const auto &d : ns) { + CADICAL_assert (!d->redundant); + if (d->garbage) + continue; + if (!resolve_gates && substitute && c->gate == d->gate) + continue; + stats.elimrestried++; + if (resolve_clauses (eliminator, c, pivot, d, true)) { + resolvents++; + int size = clause.size (); + clause.clear (); + LOG ("now at least %" PRId64 + " non-tautological resolvents on pivot %d", + resolvents, pivot); + if (size > opts.elimclslim) { + LOG ("resolvent size %d too big after %" PRId64 + " resolvents on %d", + size, resolvents, pivot); + return false; + } + if (resolvents > bound) { + LOG ("too many non-tautological resolvents on %d", pivot); + return false; + } + } else if (unsat) + return false; + else if (val (pivot)) + return false; + } + } + + LOG ("need %" PRId64 " <= %" PRId64 " non-tautological resolvents", + resolvents, bound); + + return true; +} + +/*------------------------------------------------------------------------*/ +// Add all resolvents on 'pivot' and connect them. + +inline void Internal::elim_add_resolvents (Eliminator &eliminator, + int pivot) { + + const bool substitute = !eliminator.gates.empty (); + const bool resolve_gates = eliminator.definition_unit; + if (substitute) { + LOG ("substituting pivot %d by resolving with %zd gate clauses", pivot, + eliminator.gates.size ()); + stats.elimsubst++; + } + switch (eliminator.gatetype) { + case EQUI: + stats.eliminated_equi++; + break; + case AND: + stats.eliminated_and++; + break; + case ITE: + stats.eliminated_ite++; + break; + case XOR: + stats.eliminated_xor++; + break; + case DEF: + stats.eliminated_def++; + break; + default: + CADICAL_assert (eliminator.gatetype == NO); + } + + LOG ("adding all resolvents on %d", pivot); + + CADICAL_assert (!val (pivot)); + CADICAL_assert (!flags (pivot).eliminated ()); + + const Occs &ps = occs (pivot); + const Occs &ns = occs (-pivot); +#ifdef LOGGING + int64_t resolvents = 0; +#endif + for (auto &c : ps) { + if (unsat) + break; + if (c->garbage) + continue; + for (auto &d : ns) { + if (unsat) + break; + if (d->garbage) + continue; + if (!resolve_gates && substitute && c->gate == d->gate) + continue; + if (!resolve_clauses (eliminator, c, pivot, d, false)) + continue; + CADICAL_assert (!lrat || !lrat_chain.empty ()); + Clause *r = new_resolved_irredundant_clause (); + elim_update_added_clause (eliminator, r); + eliminator.enqueue (r); + lrat_chain.clear (); + clause.clear (); +#ifdef LOGGING + resolvents++; +#endif + } + } + + LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot); +} + +/*------------------------------------------------------------------------*/ + +// Remove clauses with 'pivot' and '-pivot' by marking them as garbage and +// push them on the extension stack. + +void Internal::mark_eliminated_clauses_as_garbage ( + Eliminator &eliminator, int pivot, bool &deleted_binary_clause) { + CADICAL_assert (!unsat); + + LOG ("marking irredundant clauses with %d as garbage", pivot); + + const int64_t substitute = eliminator.gates.size (); + if (substitute) + LOG ("pushing %" PRId64 " gate clauses on extension stack", substitute); +#ifndef CADICAL_NDEBUG + int64_t pushed = 0; +#endif + Occs &ps = occs (pivot); + for (const auto &c : ps) { + if (c->garbage) + continue; + CADICAL_assert (!c->redundant); + if (!substitute || c->gate) { + if (proof) + proof->weaken_minus (c); + if (c->size == 2) + deleted_binary_clause = true; + external->push_clause_on_extension_stack (c, pivot); +#ifndef CADICAL_NDEBUG + pushed++; +#endif + } + mark_garbage (c); + elim_update_removed_clause (eliminator, c, pivot); + } + erase_occs (ps); + + LOG ("marking irredundant clauses with %d as garbage", -pivot); + + Occs &ns = occs (-pivot); + for (const auto &d : ns) { + if (d->garbage) + continue; + CADICAL_assert (!d->redundant); + if (!substitute || d->gate) { + if (proof) + proof->weaken_minus (d); + if (d->size == 2) + deleted_binary_clause = true; + external->push_clause_on_extension_stack (d, -pivot); +#ifndef CADICAL_NDEBUG + pushed++; +#endif + } + mark_garbage (d); + elim_update_removed_clause (eliminator, d, -pivot); + } + erase_occs (ns); + + if (substitute) + CADICAL_assert (pushed <= substitute); + + // Unfortunately, we can not use the trick by Niklas Soerensson anymore, + // which avoids saving all clauses on the extension stack. This would + // break our new incremental 'restore' logic. +} + +/*------------------------------------------------------------------------*/ + +// Try to eliminate 'pivot' by bounded variable elimination. +void Internal::try_to_eliminate_variable (Eliminator &eliminator, int pivot, + bool &deleted_binary_clause) { + + if (!active (pivot)) + return; + CADICAL_assert (!frozen (pivot)); + + // First flush garbage clauses. + // + int64_t pos = flush_occs (pivot); + int64_t neg = flush_occs (-pivot); + + if (pos > neg) { + pivot = -pivot; + swap (pos, neg); + } + LOG ("pivot %d occurs positively %" PRId64 + " times and negatively %" PRId64 " times", + pivot, pos, neg); + CADICAL_assert (!eliminator.schedule.contains (abs (pivot))); + CADICAL_assert (pos <= neg); + + if (pos && neg > opts.elimocclim) { + LOG ("too many occurrences thus not eliminated %d", pivot); + CADICAL_assert (!eliminator.schedule.contains (abs (pivot))); + return; + } + + LOG ("trying to eliminate %d", pivot); + CADICAL_assert (!flags (pivot).eliminated ()); + + // Sort occurrence lists, such that shorter clauses come first. + Occs &ps = occs (pivot); + stable_sort (ps.begin (), ps.end (), clause_smaller_size ()); + Occs &ns = occs (-pivot); + stable_sort (ns.begin (), ns.end (), clause_smaller_size ()); + + if (pos) + find_gate_clauses (eliminator, pivot); + + if (!unsat && !val (pivot)) { + if (elim_resolvents_are_bounded (eliminator, pivot)) { + LOG ("number of resolvents on %d are bounded", pivot); + elim_add_resolvents (eliminator, pivot); + if (!unsat) + mark_eliminated_clauses_as_garbage (eliminator, pivot, + deleted_binary_clause); + if (active (pivot)) + mark_eliminated (pivot); + } else { + LOG ("too many resolvents on %d so not eliminated", pivot); + } + } + + unmark_gate_clauses (eliminator); + elim_backward_clauses (eliminator); +} + +/*------------------------------------------------------------------------*/ + +void Internal:: + mark_redundant_clauses_with_eliminated_variables_as_garbage () { + for (const auto &c : clauses) { + if (c->garbage || !c->redundant) + continue; + bool clean = true; + for (const auto &lit : *c) { + Flags &f = flags (lit); + if (f.eliminated ()) { + clean = false; + break; + } + if (f.pure ()) { + clean = false; + break; + } + } + if (!clean) + mark_garbage (c); + } +} + +/*------------------------------------------------------------------------*/ + +// This function performs one round of bounded variable elimination and +// returns the number of eliminated variables. The additional result +// 'completed' is true if this elimination round ran to completion (all +// variables have been tried). Otherwise it was asynchronously terminated +// or the resolution limit was hit. + +int Internal::elim_round (bool &completed, bool &deleted_binary_clause) { + + CADICAL_assert (opts.elim); + CADICAL_assert (!unsat); + + START_SIMPLIFIER (elim, ELIM); + stats.elimrounds++; + + int64_t marked_before = last.elim.marked; + last.elim.marked = stats.mark.elim; + CADICAL_assert (!level); + + int64_t resolution_limit; + + if (opts.elimlimited) { + int64_t delta = stats.propagations.search; + delta *= 1e-3 * opts.elimeffort; + if (delta < opts.elimmineff) + delta = opts.elimmineff; + if (delta > opts.elimmaxeff) + delta = opts.elimmaxeff; + delta = max (delta, (int64_t) 2l * active ()); + + PHASE ("elim-round", stats.elimrounds, + "limit of %" PRId64 " resolutions", delta); + + resolution_limit = stats.elimres + delta; + } else { + PHASE ("elim-round", stats.elimrounds, "resolutions unlimited"); + resolution_limit = LONG_MAX; + } + + init_noccs (); + + // First compute the number of occurrences of each literal and at the same + // time mark satisfied clauses and update 'elim' flags of variables in + // clauses with root level assigned literals (both false and true). + // + for (const auto &c : clauses) { + if (c->garbage || c->redundant) + continue; + bool satisfied = false, falsified = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) + satisfied = true; + else if (tmp < 0) + falsified = true; + else + CADICAL_assert (active (lit)); + } + if (satisfied) + mark_garbage (c); // forces more precise counts + else { + for (const auto &lit : *c) { + if (!active (lit)) + continue; + if (falsified) + mark_elim (lit); // simulate unit propagation + noccs (lit)++; + } + } + } + + init_occs (); + + Eliminator eliminator (this); + ElimSchedule &schedule = eliminator.schedule; + CADICAL_assert (schedule.empty ()); + + // Now find elimination candidates which occurred in clauses removed since + // the last time we ran bounded variable elimination, which in turned + // triggered their 'elim' bit to be set. + // + for (auto idx : vars) { + if (!active (idx)) + continue; + if (frozen (idx)) + continue; + if (!flags (idx).elim) + continue; + LOG ("scheduling %d for elimination initially", idx); + schedule.push_back (idx); + } + + schedule.shrink (); + +#ifndef CADICAL_QUIET + int64_t scheduled = schedule.size (); +#endif + + PHASE ("elim-round", stats.elimrounds, + "scheduled %" PRId64 " variables %.0f%% for elimination", + scheduled, percent (scheduled, active ())); + + // Connect irredundant clauses. + // + for (const auto &c : clauses) + if (!c->garbage && !c->redundant) + for (const auto &lit : *c) + if (active (lit)) + occs (lit).push_back (c); + +#ifndef CADICAL_QUIET + const int64_t old_resolutions = stats.elimres; +#endif + const int old_eliminated = stats.all.eliminated; + const int old_fixed = stats.all.fixed; + + // Limit on garbage literals during variable elimination. If the limit is + // hit a garbage collection is performed. + // + const int64_t garbage_limit = (2 * stats.irrlits / 3) + (1 << 20); + + // Main loops tries to eliminate variables according to the schedule. The + // schedule is updated dynamically and variables are potentially + // rescheduled to be tried again if they occur in a removed clause. + // +#ifndef CADICAL_QUIET + int64_t tried = 0; +#endif + while (!unsat && !terminated_asynchronously () && + stats.elimres <= resolution_limit && !schedule.empty ()) { + int idx = schedule.front (); + schedule.pop_front (); + flags (idx).elim = false; + try_to_eliminate_variable (eliminator, idx, deleted_binary_clause); +#ifndef CADICAL_QUIET + tried++; +#endif + if (stats.garbage.literals <= garbage_limit) + continue; + mark_redundant_clauses_with_eliminated_variables_as_garbage (); + garbage_collection (); + } + + // If the schedule is empty all variables have been tried (even + // rescheduled ones). Otherwise asynchronous termination happened or we + // ran into the resolution limit (or derived unsatisfiability). + // + completed = !schedule.size (); + + if (!completed) + last.elim.marked = marked_before; + + PHASE ("elim-round", stats.elimrounds, + "tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)", + tried, percent (tried, scheduled), schedule.size ()); + + schedule.erase (); + + // Collect potential literal clause instantiation pairs, which needs full + // occurrence lists and thus we have it here before resetting them. + // + Instantiator instantiator; + if (!unsat && !terminated_asynchronously () && opts.instantiate) + collect_instantiation_candidates (instantiator); + + reset_occs (); + reset_noccs (); + + // Mark all redundant clauses with eliminated variables as garbage. + // + if (!unsat) + mark_redundant_clauses_with_eliminated_variables_as_garbage (); + + int eliminated = stats.all.eliminated - old_eliminated; +#ifndef CADICAL_QUIET + int64_t resolutions = stats.elimres - old_resolutions; + PHASE ("elim-round", stats.elimrounds, + "eliminated %d variables %.0f%% in %" PRId64 " resolutions", + eliminated, percent (eliminated, scheduled), resolutions); +#endif + + last.elim.subsumephases = stats.subsumephases; + const int units = stats.all.fixed - old_fixed; + report ('e', !opts.reportall && !(eliminated + units)); + STOP_SIMPLIFIER (elim, ELIM); + + if (!unsat && !terminated_asynchronously () && + instantiator) // Do we have candidate pairs? + instantiate (instantiator); + + return eliminated; // non-zero if successful +} + +/*------------------------------------------------------------------------*/ + +// Increase elimination bound (additional clauses allowed during variable +// elimination), which is triggered if elimination with last bound completed +// (including no new subsumptions). This was pioneered by GlueMiniSAT in +// the SAT Race 2015 and then picked up Chanseok Oh in his COMinisatPS +// solver, which in turn is used in the Maple series of SAT solvers. +// The bound is no increased if the maximum bound is reached. + +void Internal::increase_elimination_bound () { + + if (lim.elimbound >= opts.elimboundmax) + return; + + if (lim.elimbound < 0) + lim.elimbound = 0; + else if (!lim.elimbound) + lim.elimbound = 1; + else + lim.elimbound *= 2; + + if (lim.elimbound > opts.elimboundmax) + lim.elimbound = opts.elimboundmax; + + PHASE ("elim-phase", stats.elimphases, + "new elimination bound %" PRId64 "", lim.elimbound); + + // Now reschedule all active variables for elimination again. + // +#ifdef LOGGING + int count = 0; +#endif + for (auto idx : vars) { + if (!active (idx)) + continue; + if (flags (idx).elim) + continue; + mark_elim (idx); +#ifdef LOGGING + count++; +#endif + } + LOG ("marked %d variables as elimination candidates", count); + + report ('^'); +} + +void Internal::init_citten () { + if (!opts.elimdef) + return; + CADICAL_assert (!citten); + citten = cadical_kitten_init (); +} + +void Internal::reset_citten () { + if (citten) { + cadical_kitten_release (citten); + citten = 0; + } +} + +/*------------------------------------------------------------------------*/ + +void Internal::elim (bool update_limits) { + + if (unsat) + return; + if (level) + backtrack (); + if (!propagate ()) { + learn_empty_clause (); + return; + } + + stats.elimphases++; + PHASE ("elim-phase", stats.elimphases, + "starting at most %d elimination rounds", opts.elimrounds); + + if (external_prop) { + CADICAL_assert (!level); + private_steps = true; + } + +#ifndef CADICAL_QUIET + int old_active_variables = active (); + int old_eliminated = stats.all.eliminated; +#endif + + // Make sure there was a complete subsumption phase since last + // elimination including vivification etc. + // + if (last.elim.subsumephases == stats.subsumephases) + subsume (); + + reset_watches (); // saves lots of memory + + init_citten (); + + // Alternate one round of bounded variable elimination ('elim_round') and + // subsumption ('subsume_round'), blocked ('block') and covered clause + // elimination ('cover') until nothing changes, or the round limit is hit. + // The loop also aborts early if no variable could be eliminated, the + // empty clause is resolved, it is asynchronously terminated or a + // resolution limit is hit. + + // This variable determines whether the whole loop of this bounded + // variable elimination phase ('elim') ran until completion. This + // potentially triggers an incremental increase of the elimination bound. + // + bool phase_complete = false, deleted_binary_clause = false; + + int round = 1; +#ifndef CADICAL_QUIET + int eliminated = 0; +#endif + + bool round_complete = false; + while (!unsat && !phase_complete && !terminated_asynchronously ()) { +#ifndef CADICAL_QUIET + int eliminated = +#endif + elim_round (round_complete, deleted_binary_clause); + + if (!round_complete) { + PHASE ("elim-phase", stats.elimphases, "last round %d incomplete %s", + round, eliminated ? "but successful" : "and unsuccessful"); + CADICAL_assert (!phase_complete); + break; + } + + if (round++ >= opts.elimrounds) { + PHASE ("elim-phase", stats.elimphases, "round limit %d hit (%s)", + round - 1, + eliminated ? "though last round successful" + : "last round unsuccessful anyhow"); + CADICAL_assert (!phase_complete); + break; + } + + // Prioritize 'subsumption' over blocked and covered clause elimination. + + if (subsume_round ()) + continue; + if (block ()) + continue; + if (cover ()) + continue; + + // Was not able to generate new variable elimination candidates after + // variable elimination round, neither through subsumption, nor blocked, + // nor covered clause elimination. + // + PHASE ("elim-phase", stats.elimphases, + "no new variable elimination candidates"); + + CADICAL_assert (round_complete); + phase_complete = true; + } + + if (phase_complete) { + stats.elimcompleted++; + PHASE ("elim-phase", stats.elimphases, + "fully completed elimination %" PRId64 + " at elimination bound %" PRId64 "", + stats.elimcompleted, lim.elimbound); + } else { + PHASE ("elim-phase", stats.elimphases, + "incomplete elimination %" PRId64 + " at elimination bound %" PRId64 "", + stats.elimcompleted + 1, lim.elimbound); + } + + reset_citten (); + if (deleted_binary_clause) + delete_garbage_clauses (); + init_watches (); + connect_watches (); + + if (unsat) + LOG ("elimination derived empty clause"); + else if (propagated < trail.size ()) { + LOG ("elimination produced %zd units", + (size_t) (trail.size () - propagated)); + if (!propagate ()) { + LOG ("propagating units after elimination results in empty clause"); + learn_empty_clause (); + } + } + + // If we ran variable elimination until completion we increase the + // variable elimination bound and reschedule elimination of all variables. + // + if (phase_complete) + increase_elimination_bound (); + +#ifndef CADICAL_QUIET + eliminated = stats.all.eliminated - old_eliminated; + PHASE ("elim-phase", stats.elimphases, "eliminated %d variables %.2f%%", + eliminated, percent (eliminated, old_active_variables)); +#endif + + if (external_prop) { + CADICAL_assert (!level); + private_steps = false; + } + + if (!update_limits) + return; + + int64_t delta = scale (opts.elimint * (stats.elimphases + 1)); + lim.elim = stats.conflicts + delta; + + PHASE ("elim-phase", stats.elimphases, + "new limit at %" PRId64 " conflicts after %" PRId64 " conflicts", + lim.elim, delta); + + last.elim.fixed = stats.all.fixed; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_elimfast.cpp b/src/sat/cadical/cadical_elimfast.cpp new file mode 100644 index 000000000..90dacf8c0 --- /dev/null +++ b/src/sat/cadical/cadical_elimfast.cpp @@ -0,0 +1,576 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Implements a variant of elimination with a much lower limit to be run as +// preprocessing. See elim for comments + +/*------------------------------------------------------------------------*/ + +// Flush garbage clause, check fast elimination limits and return number of +// remaining occurrences (or 'fastelimbound + 1' if some limit was hit). + +int64_t Internal::flush_elimfast_occs (int lit) { + const int64_t occslim = opts.fastelimbound; + const int64_t clslim = opts.fastelimocclim; + const int64_t failed = occslim + 1; + Occs &os = occs (lit); + const const_occs_iterator end = os.end (); + occs_iterator j = os.begin (), i = j; + int64_t res = 0; + while (i != end) { + Clause *c = *i++; + if (c->collect ()) + continue; + *j++ = c; + if (c->size > clslim) { + res = failed; + break; + } + if (++res > occslim) { + CADICAL_assert (opts.fastelimbound < 0 || res == failed); + break; + } + } + if (i != j) { + while (i != end) + *j++ = *i++; + os.resize (j - os.begin ()); + shrink_occs (os); + } + return res; +} + +/*------------------------------------------------------------------------*/ + +// Check whether the number of non-tautological resolvents on 'pivot' is +// smaller or equal to the number of clauses with 'pivot' or '-pivot'. This +// is the main criteria of bounded variable elimination. As a side effect +// it flushes garbage clauses with that variable, sorts its occurrence lists +// (smallest clauses first) and also negates pivot if it has more positive +// than negative occurrences. + +bool Internal::elimfast_resolvents_are_bounded (Eliminator &eliminator, + int pivot) { + CADICAL_assert (eliminator.gates.empty ()); + CADICAL_assert (!eliminator.definition_unit); + + stats.elimtried++; + + CADICAL_assert (!unsat); + CADICAL_assert (active (pivot)); + + const Occs &ps = occs (pivot); + const Occs &ns = occs (-pivot); + + int64_t pos = ps.size (); + int64_t neg = ns.size (); + + int64_t bound = opts.fastelimbound; + + if (!pos || !neg) + return bound >= 0; + + const int64_t sum = pos + neg; + const int64_t product = pos * neg; + if (bound > sum) + bound = sum; + + LOG ("checking number resolvents on %d bounded by " + "%" PRId64 " = %" PRId64 " + %" PRId64 " + %d", + pivot, bound, pos, neg, opts.fastelimbound); + + if (product <= bound) { + LOG ("fast elimination occurrence limits sufficiently small enough"); + return true; + } + + // Try all resolutions between a positive occurrence (outer loop) of + // 'pivot' and a negative occurrence of 'pivot' (inner loop) as long the + // bound on non-tautological resolvents is not hit and the size of the + // generated resolvents does not exceed the resolvent clause size limit. + + int64_t resolvents = 0; // Non-tautological resolvents. + + for (const auto &c : ps) { + CADICAL_assert (!c->redundant); + if (c->garbage) + continue; + for (const auto &d : ns) { + CADICAL_assert (!d->redundant); + if (d->garbage) + continue; + if (resolve_clauses (eliminator, c, pivot, d, true)) { + resolvents++; + int size = clause.size (); + clause.clear (); + LOG ("now at least %" PRId64 + " non-tautological resolvents on pivot %d", + resolvents, pivot); + if (size > opts.fastelimclslim) { + LOG ("resolvent size %d too big after %" PRId64 + " resolvents on %d", + size, resolvents, pivot); + return false; + } + if (resolvents > bound) { + LOG ("too many non-tautological resolvents on %d", pivot); + return false; + } + } else if (unsat) + return false; + else if (val (pivot)) + return false; + } + } + + LOG ("need %" PRId64 " <= %" PRId64 " non-tautological resolvents", + resolvents, bound); + + return true; +} +/*------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------*/ +// Add all resolvents on 'pivot' and connect them. + +inline void Internal::elimfast_add_resolvents (Eliminator &eliminator, + int pivot) { + + CADICAL_assert (eliminator.gates.empty ()); + CADICAL_assert (!eliminator.definition_unit); + + LOG ("adding all resolvents on %d", pivot); + + CADICAL_assert (!val (pivot)); + CADICAL_assert (!flags (pivot).eliminated ()); + + const Occs &ps = occs (pivot); + const Occs &ns = occs (-pivot); +#ifdef LOGGING + int64_t resolvents = 0; +#endif + for (auto &c : ps) { + if (unsat) + break; + if (c->garbage) + continue; + for (auto &d : ns) { + if (unsat) + break; + if (d->garbage) + continue; + if (!resolve_clauses (eliminator, c, pivot, d, false)) + continue; + CADICAL_assert (!lrat || !lrat_chain.empty ()); + Clause *r = new_resolved_irredundant_clause (); + elim_update_added_clause (eliminator, r); + eliminator.enqueue (r); + lrat_chain.clear (); + clause.clear (); +#ifdef LOGGING + resolvents++; +#endif + } + } + + LOG ("added %" PRId64 " resolvents to eliminate %d", resolvents, pivot); +} + +/*------------------------------------------------------------------------*/ + +// Try to eliminate 'pivot' by bounded variable elimination. +void Internal::try_to_fasteliminate_variable (Eliminator &eliminator, + int pivot, + bool &deleted_binary_clause) { + + if (!active (pivot)) + return; + CADICAL_assert (!frozen (pivot)); + + // First flush garbage clauses and check limits. + + int64_t bound = opts.fastelimbound; + + int64_t pos = flush_elimfast_occs (pivot); + if (pos > bound) { + LOG ("too many occurrences thus not eliminated %d", pivot); + CADICAL_assert (!eliminator.schedule.contains (abs (pivot))); + return; + } + + int64_t neg = flush_elimfast_occs (-pivot); + if (neg > bound) { + LOG ("too many occurrences thus not eliminated %d", -pivot); + CADICAL_assert (!eliminator.schedule.contains (abs (pivot))); + return; + } + + const int64_t product = pos * neg; + const int64_t sum = pos + neg; + if (bound > sum) + bound = sum; + + if (pos > neg) { + pivot = -pivot; + swap (pos, neg); + } + + LOG ("pivot %d occurs positively %" PRId64 + " times and negatively %" PRId64 " times", + pivot, pos, neg); + + CADICAL_assert (!eliminator.schedule.contains (abs (pivot))); + CADICAL_assert (pos <= neg); + + LOG ("trying to eliminate %d", pivot); + CADICAL_assert (!flags (pivot).eliminated ()); + + // Sort occurrence lists, such that shorter clauses come first. + Occs &ps = occs (pivot); + stable_sort (ps.begin (), ps.end (), clause_smaller_size ()); + Occs &ns = occs (-pivot); + stable_sort (ns.begin (), ns.end (), clause_smaller_size ()); + + if (!unsat && !val (pivot)) { + if (product <= bound || + elimfast_resolvents_are_bounded (eliminator, pivot)) { + LOG ("number of resolvents on %d are bounded", pivot); + elimfast_add_resolvents (eliminator, pivot); + if (!unsat) + mark_eliminated_clauses_as_garbage (eliminator, pivot, + deleted_binary_clause); + if (active (pivot)) + mark_eliminated (pivot); + } else { + LOG ("too many resolvents on %d so not eliminated", pivot); + } + } + + unmark_gate_clauses (eliminator); + elim_backward_clauses (eliminator); +} + +/*------------------------------------------------------------------------*/ + +// This function performs one round of bounded variable elimination and +// returns the number of eliminated variables. The additional result +// 'completed' is true if this elimination round ran to completion (all +// variables have been tried). Otherwise it was asynchronously terminated +// or the resolution limit was hit. + +int Internal::elimfast_round (bool &completed, + bool &deleted_binary_clause) { + + CADICAL_assert (opts.fastelim); + CADICAL_assert (!unsat); + + START_SIMPLIFIER (fastelim, ELIM); + + stats.elimfastrounds++; + + CADICAL_assert (!level); + + int64_t resolution_limit; + + if (opts.elimlimited) { + int64_t delta = stats.propagations.search; + delta *= 1e-3 * opts.elimeffort; + if (delta < opts.elimmineff) + delta = opts.elimmineff; + if (delta > opts.elimmaxeff) + delta = opts.elimmaxeff; + delta = max (delta, (int64_t) 2l * active ()); + + PHASE ("fastelim-round", stats.elimfastrounds, + "limit of %" PRId64 " resolutions", delta); + + resolution_limit = stats.elimres + delta; + } else { + PHASE ("fastelim-round", stats.elimfastrounds, "resolutions unlimited"); + resolution_limit = LONG_MAX; + } + + init_noccs (); + + // First compute the number of occurrences of each literal and at the same + // time mark satisfied clauses and update 'elim' flags of variables in + // clauses with root level assigned literals (both false and true). + // + for (const auto &c : clauses) { + if (c->garbage || c->redundant) + continue; + bool satisfied = false, falsified = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) + satisfied = true; + else if (tmp < 0) + falsified = true; + else + CADICAL_assert (active (lit)); + } + if (satisfied) + mark_garbage (c); // forces more precise counts + else { + for (const auto &lit : *c) { + if (!active (lit)) + continue; + if (falsified) + mark_elim (lit); // simulate unit propagation + noccs (lit)++; + } + } + } + + init_occs (); + + Eliminator eliminator (this); + ElimSchedule &schedule = eliminator.schedule; + CADICAL_assert (schedule.empty ()); + + // Now find elimination candidates which occurred in clauses removed since + // the last time we ran bounded variable elimination, which in turned + // triggered their 'elim' bit to be set. + // + for (auto idx : vars) { + if (!active (idx)) + continue; + if (frozen (idx)) + continue; + if (!flags (idx).elim) + continue; + LOG ("scheduling %d for elimination initially", idx); + schedule.push_back (idx); + } + + schedule.shrink (); + +#ifndef CADICAL_QUIET + int64_t scheduled = schedule.size (); +#endif + + PHASE ("fastelim-round", stats.elimfastrounds, + "scheduled %" PRId64 " variables %.0f%% for elimination", + scheduled, percent (scheduled, active ())); + + // Connect irredundant clauses. + // + for (const auto &c : clauses) + if (!c->garbage && !c->redundant) + for (const auto &lit : *c) + if (active (lit)) + occs (lit).push_back (c); + +#ifndef CADICAL_QUIET + const int64_t old_resolutions = stats.elimres; +#endif + const int old_eliminated = stats.all.eliminated; + const int old_fixed = stats.all.fixed; + + // Limit on garbage literals during variable elimination. If the limit is + // hit a garbage collection is performed. + // + const int64_t garbage_limit = (2 * stats.irrlits / 3) + (1 << 20); + + // Main loops tries to eliminate variables according to the schedule. The + // schedule is updated dynamically and variables are potentially + // rescheduled to be tried again if they occur in a removed clause. + // +#ifndef CADICAL_QUIET + int64_t tried = 0; +#endif + while (!unsat && !terminated_asynchronously () && + stats.elimres <= resolution_limit && !schedule.empty ()) { + int idx = schedule.front (); + schedule.pop_front (); + flags (idx).elim = false; + try_to_fasteliminate_variable (eliminator, idx, deleted_binary_clause); +#ifndef CADICAL_QUIET + tried++; +#endif + if (stats.garbage.literals <= garbage_limit) + continue; + mark_redundant_clauses_with_eliminated_variables_as_garbage (); + garbage_collection (); + } + + // If the schedule is empty all variables have been tried (even + // rescheduled ones). Otherwise asynchronous termination happened or we + // ran into the resolution limit (or derived unsatisfiability). + // + completed = !schedule.size (); + + PHASE ("fastelim-round", stats.elimfastrounds, + "tried to eliminate %" PRId64 " variables %.0f%% (%zd remain)", + tried, percent (tried, scheduled), schedule.size ()); + + schedule.erase (); + + reset_occs (); + reset_noccs (); + + // Mark all redundant clauses with eliminated variables as garbage. + // + if (!unsat) + mark_redundant_clauses_with_eliminated_variables_as_garbage (); + + int eliminated = stats.all.eliminated - old_eliminated; + stats.all.fasteliminated += eliminated; +#ifndef CADICAL_QUIET + int64_t resolutions = stats.elimres - old_resolutions; + PHASE ("fastelim-round", stats.elimfastrounds, + "eliminated %d variables %.0f%% in %" PRId64 " resolutions", + eliminated, percent (eliminated, scheduled), resolutions); +#endif + + const int units = stats.all.fixed - old_fixed; + report ('e', !opts.reportall && !(eliminated + units)); + STOP_SIMPLIFIER (fastelim, ELIM); + + return eliminated; // non-zero if successful +} + +/*------------------------------------------------------------------------*/ + +void Internal::elimfast () { + + if (unsat) + return; + if (level) + backtrack (); + if (!propagate ()) { + learn_empty_clause (); + return; + } + + stats.elimfastphases++; + PHASE ("fastelim-phase", stats.elimfastphases, + "starting at most %d elimination rounds", opts.fastelimrounds); + + if (external_prop) { + CADICAL_assert (!level); + private_steps = true; + } + +#ifndef CADICAL_QUIET + int old_active_variables = active (); + int old_eliminated = stats.all.eliminated; +#endif + + reset_watches (); // saves lots of memory + + // Alternate one round of bounded variable elimination ('elim_round') and + // subsumption ('subsume_round'), blocked ('block') and covered clause + // elimination ('cover') until nothing changes, or the round limit is hit. + // The loop also aborts early if no variable could be eliminated, the + // empty clause is resolved, it is asynchronously terminated or a + // resolution limit is hit. + + // This variable determines whether the whole loop of this bounded + // variable elimination phase ('elim') ran until completion. This + // potentially triggers an incremental increase of the elimination bound. + // + bool phase_complete = false, deleted_binary_clause = false; + + int round = 1; +#ifndef CADICAL_QUIET + int eliminated = 0; +#endif + + bool round_complete = false; + while (!unsat && !phase_complete && !terminated_asynchronously ()) { +#ifndef CADICAL_QUIET + int eliminated = +#endif + elimfast_round (round_complete, deleted_binary_clause); + + if (!round_complete) { + PHASE ("fastelim-phase", stats.elimphases, + "last round %d incomplete %s", round, + eliminated ? "but successful" : "and unsuccessful"); + CADICAL_assert (!phase_complete); + break; + } + + if (round++ >= opts.fastelimrounds) { + PHASE ("fastelim-phase", stats.elimphases, "round limit %d hit (%s)", + round - 1, + eliminated ? "though last round successful" + : "last round unsuccessful anyhow"); + CADICAL_assert (!phase_complete); + break; + } + + // Prioritize 'subsumption' over blocked and covered clause elimination. + + if (subsume_round ()) + continue; + + // Was not able to generate new variable elimination candidates after + // variable elimination round, neither through subsumption, nor blocked, + // nor covered clause elimination. + // + PHASE ("fastelim-phase", stats.elimphases, + "no new variable elimination candidates"); + + CADICAL_assert (round_complete); + phase_complete = true; + } + + for (auto idx : vars) { + if (active (idx)) + flags (idx).elim = true; + } + + if (phase_complete) { + stats.elimcompleted++; + PHASE ("fastelim-phase", stats.elimphases, + "fully completed elimination %" PRId64 + " at elimination bound %" PRId64 "", + stats.elimcompleted, lim.elimbound); + } else { + PHASE ("fastelim-phase", stats.elimphases, + "incomplete elimination %" PRId64 + " at elimination bound %" PRId64 "", + stats.elimcompleted + 1, lim.elimbound); + } + + if (deleted_binary_clause) + delete_garbage_clauses (); + init_watches (); + connect_watches (); + + if (unsat) + LOG ("elimination derived empty clause"); + else if (propagated < trail.size ()) { + LOG ("elimination produced %zd units", + (size_t) (trail.size () - propagated)); + if (!propagate ()) { + LOG ("propagating units after elimination results in empty clause"); + learn_empty_clause (); + } + } + +#ifndef CADICAL_QUIET + eliminated = stats.all.eliminated - old_eliminated; + PHASE ("fastelim-phase", stats.elimphases, + "eliminated %d variables %.2f%%", eliminated, + percent (eliminated, old_active_variables)); +#endif + + if (external_prop) { + CADICAL_assert (!level); + private_steps = false; + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_ema.cpp b/src/sat/cadical/cadical_ema.cpp new file mode 100644 index 000000000..e8faf5d76 --- /dev/null +++ b/src/sat/cadical/cadical_ema.cpp @@ -0,0 +1,101 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Updating an exponential moving average is placed here since we want to +// log both updates and phases of initialization, thus need 'LOG'. +// +// We now use initialization bias correction as in the ADAM method +// [KingmaBa-ICLR'15] instead of our ad-hoc initialization method used +// before. Our old variant used exponentially decreasing alphas: +// +// 1, +// 1/2, 1/2, +// 1/4, 1/4, 1/4, 1/4 +// 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, 1/8, +// ... +// 2^-n, ..., 2^-n 'n' times +// alpha, alpha, ... now 'alpha' forever. +// +// where 2^-n is the smallest negative power of two above 'alpha' +// +// This old method is better than the initializations described in our +// [BiereFroehlich-POS'15] paper and actually faster than the ADAM method, +// but less precise. We consider this old method obsolete now but it +// could still be useful for implementations relying on integers instead +// of floating points because it only needs shifts and integer arithmetic. +// +// Our new method for unbiased initialization of the exponential averages +// works as follows. First the biased moving average is computed as usual. +// Note that (as already before) we use the simpler equation +// +// new_biased = old_biased + alpha * (y - old_biased); +// +// which in principle (and thus easy to remember) can be implemented as +// +// biased += alpha * (y - biased); +// +// The original formulation in the ADAM paper (with 'alpha = 1 - beta') is +// +// new_biased = beta * old_biased + (1 - beta) * y +// +// To show that these are equivalent (modulo floating point issues) +// consider the following equivalent expressions: +// +// old_biased + alpha * (y - old_biased) +// old_biased + alpha * y - alpha * old_biased +// (1 - alpha) * old_biased + alpha * y +// beta * old_biased + (1 - beta) * y +// +// The real new idea taken from the ADAM paper is however to fix the biased +// moving average with a correction term '1.0 / (1.0 - pow (beta, updated))' +// by multiplication to obtain an unbiased moving average (called simply +// 'value' in our 'code'). In order to avoid computing 'pow' every time, we +// use 'exp' which is multiplied in every update with 'beta'. + +void EMA::update (Internal *internal, double y, const char *name) { +#ifdef LOGGING + updated++; + const double old_value = value; +#endif + const double old_biased = biased; + const double delta = y - old_biased; + const double scaled_delta = alpha * delta; + const double new_biased = old_biased + scaled_delta; + LOG ("update %" PRIu64 " of biased %s EMA %g with %g (delta %g) " + "yields %g (scaled delta %g)", + updated, name, old_biased, y, delta, new_biased, scaled_delta); + biased = new_biased; + const double old_exp = exp; + double new_exp, div, new_value; + if (old_exp) { + new_exp = old_exp * beta; + CADICAL_assert (new_exp < 1); + exp = new_exp; + div = 1 - new_exp; + CADICAL_assert (div > 0); + new_value = new_biased / div; + } else { + new_value = new_biased; +#ifdef LOGGING + new_exp = 0; + div = 1; +#endif + } + value = new_value; + LOG ("update %" PRIu64 " of corrected %s EMA %g with %g (delta %g) " + "yields %g (exponent %g, divisor %g)", + updated, name, old_value, y, delta, new_value, new_exp, div); +#ifndef LOGGING + (void) internal; + (void) name; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_extend.cpp b/src/sat/cadical/cadical_extend.cpp new file mode 100644 index 000000000..c549e90fa --- /dev/null +++ b/src/sat/cadical/cadical_extend.cpp @@ -0,0 +1,287 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void External::push_zero_on_extension_stack () { + extension.push_back (0); + LOG ("pushing 0 on extension stack"); +} + +void External::push_id_on_extension_stack (int64_t id) { + const uint32_t higher_bits = static_cast (id << 32); + const uint32_t lower_bits = (id & (((int64_t) 1 << 32) - 1)); + extension.push_back (higher_bits); + extension.push_back (lower_bits); + LOG ("pushing id %" PRIu64 " = %d + %d", id, higher_bits, lower_bits); +} + +void External::push_clause_literal_on_extension_stack (int ilit) { + CADICAL_assert (ilit); + const int elit = internal->externalize (ilit); + CADICAL_assert (elit); + extension.push_back (elit); + LOG ("pushing clause literal %d on extension stack (internal %d)", elit, + ilit); +} + +void External::push_witness_literal_on_extension_stack (int ilit) { + CADICAL_assert (ilit); + const int elit = internal->externalize (ilit); + CADICAL_assert (elit); + extension.push_back (elit); + LOG ("pushing witness literal %d on extension stack (internal %d)", elit, + ilit); + if (marked (witness, elit)) + return; + LOG ("marking witness %d", elit); + mark (witness, elit); +} + +// The extension stack allows to reconstruct a satisfying assignment for the +// original formula after removing eliminated clauses. This was pioneered +// by Niklas Soerensson in MiniSAT and for instance is described in our +// inprocessing paper, published at IJCAR'12. This first function adds a +// clause to this stack. First the blocking or eliminated literal is added, +// and then the rest of the clause. + +void External::push_clause_on_extension_stack (Clause *c) { + internal->stats.weakened++; + internal->stats.weakenedlen += c->size; + push_zero_on_extension_stack (); + push_id_on_extension_stack (c->id); + push_zero_on_extension_stack (); + for (const auto &lit : *c) + push_clause_literal_on_extension_stack (lit); +} + +void External::push_clause_on_extension_stack (Clause *c, int pivot) { + push_zero_on_extension_stack (); + push_witness_literal_on_extension_stack (pivot); + push_clause_on_extension_stack (c); +} + +void External::push_binary_clause_on_extension_stack (int64_t id, int pivot, + int other) { + internal->stats.weakened++; + internal->stats.weakenedlen += 2; + push_zero_on_extension_stack (); + push_witness_literal_on_extension_stack (pivot); + push_zero_on_extension_stack (); + push_id_on_extension_stack (id); + push_zero_on_extension_stack (); + push_clause_literal_on_extension_stack (pivot); + push_clause_literal_on_extension_stack (other); +} + +/*------------------------------------------------------------------------*/ + +void External::push_external_clause_and_witness_on_extension_stack ( + const vector &c, const vector &w, int64_t id) { + CADICAL_assert (id); + extension.push_back (0); + for (const auto &elit : w) { + CADICAL_assert (elit != INT_MIN); + init (abs (elit)); + extension.push_back (elit); + mark (witness, elit); + } + extension.push_back (0); + const uint32_t higher_bits = static_cast (id << 32); + const uint32_t lower_bits = (id & (((int64_t) 1 << 32) - 1)); + extension.push_back (higher_bits); + extension.push_back (lower_bits); + extension.push_back (0); + for (const auto &elit : c) { + CADICAL_assert (elit != INT_MIN); + init (abs (elit)); + extension.push_back (elit); + } +} + +/*------------------------------------------------------------------------*/ + +// This is the actual extension process. It goes backward over the clauses +// on the extension stack and flips the assignment of one of the blocking +// literals in the conditional autarky stored before the clause. In the +// original algorithm for witness construction for variable elimination and +// blocked clause removal the conditional autarky consists of a single +// literal from the removed clause, while in general the autarky witness can +// contain an arbitrary set of literals. We are using the more general +// witness reconstruction here which for instance would also work for +// super-blocked or set-blocked clauses. + +void External::extend () { + + CADICAL_assert (!extended); + START (extend); + internal->stats.extensions++; + + PHASE ("extend", internal->stats.extensions, + "mapping internal %d assignments to %d assignments", + internal->max_var, max_var); + +#ifndef CADICAL_QUIET + int64_t updated = 0; +#endif + for (unsigned i = 1; i <= (unsigned) max_var; i++) { + const int ilit = e2i[i]; + if (!ilit) + continue; + if (i >= vals.size ()) + vals.resize (i + 1, false); + vals[i] = (internal->val (ilit) > 0); +#ifndef CADICAL_QUIET + updated++; +#endif + } + PHASE ("extend", internal->stats.extensions, + "updated %" PRId64 " external assignments", updated); + PHASE ("extend", internal->stats.extensions, + "extending through extension stack of size %zd", + extension.size ()); + const auto begin = extension.begin (); + auto i = extension.end (); +#ifndef CADICAL_QUIET + int64_t flipped = 0; +#endif + while (i != begin) { + bool satisfied = false; + int lit; + CADICAL_assert (i != begin); + while ((lit = *--i)) { + if (satisfied) + continue; + if (ival (lit) == lit) + satisfied = true; + CADICAL_assert (i != begin); + } + CADICAL_assert (i != begin); + LOG ("id=%" PRId64, ((int64_t) *i << 32) + *(i - 1)); + CADICAL_assert (*i || *(i - 1)); + --i; + CADICAL_assert (i != begin); + --i; + CADICAL_assert (i != begin); + CADICAL_assert (!*i); + --i; + CADICAL_assert (i != begin); + if (satisfied) + while (*--i) + CADICAL_assert (i != begin); + else { + while ((lit = *--i)) { + const int tmp = ival (lit); // not 'signed char'!!! + if (tmp != lit) { + LOG ("flipping blocking literal %d", lit); + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + size_t idx = abs (lit); + if (idx >= vals.size ()) + vals.resize (idx + 1, false); + vals[idx] = !vals[idx]; + internal->stats.extended++; +#ifndef CADICAL_QUIET + flipped++; +#endif + } + CADICAL_assert (i != begin); + } + } + } + PHASE ("extend", internal->stats.extensions, + "flipped %" PRId64 " literals during extension", flipped); + extended = true; + LOG ("extended"); + STOP (extend); +} + +/*------------------------------------------------------------------------*/ + +bool External::traverse_witnesses_backward (WitnessIterator &it) { + if (internal->unsat) + return true; + vector clause, witness; + const auto begin = extension.begin (); + auto i = extension.end (); + while (i != begin) { + int lit; + while ((lit = *--i)) + clause.push_back (lit); + CADICAL_assert (!lit); + --i; + const int64_t id = + ((int64_t) * (i - 1) << 32) + static_cast (*i); + CADICAL_assert (id); + i -= 2; + CADICAL_assert (!*i); + CADICAL_assert (i != begin); + while ((lit = *--i)) + witness.push_back (lit); + reverse (clause.begin (), clause.end ()); + reverse (witness.begin (), witness.end ()); + LOG (clause, "traversing clause"); + if (!it.witness (clause, witness, id)) + return false; + clause.clear (); + witness.clear (); + } + return true; +} + +bool External::traverse_witnesses_forward (WitnessIterator &it) { + if (internal->unsat) + return true; + vector clause, witness; + const auto end = extension.end (); + auto i = extension.begin (); + if (i != end) { + int lit = *i++; + do { + CADICAL_assert (!lit), (void) lit; + while ((lit = *i++)) + witness.push_back (lit); + CADICAL_assert (!lit); + CADICAL_assert (i != end); + CADICAL_assert (!*i); + const int64_t id = + ((int64_t) *i << 32) + static_cast (*(i + 1)); + CADICAL_assert (id > 0); + i += 3; + CADICAL_assert (*i); + CADICAL_assert (i != end); + while (i != end && (lit = *i++)) + clause.push_back (lit); + if (!it.witness (clause, witness, id)) + return false; + clause.clear (); + witness.clear (); + } while (i != end); + } + return true; +} + +/*------------------------------------------------------------------------*/ + +void External::conclude_sat () { + if (!internal->proof || concluded) + return; + concluded = true; + if (!extended) + extend (); + vector model; + for (int idx = 1; idx <= max_var; idx++) { + if (ervars[idx]) + continue; + const int lit = ival (idx); + model.push_back (lit); + } + internal->proof->conclude_sat (model); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_external.cpp b/src/sat/cadical/cadical_external.cpp new file mode 100644 index 000000000..67d1f918d --- /dev/null +++ b/src/sat/cadical/cadical_external.cpp @@ -0,0 +1,1028 @@ +#include "global.h" + +#include "internal.hpp" +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +External::External (Internal *i) + : internal (i), max_var (0), vsize (0), extended (false), + concluded (false), terminator (0), learner (0), fixed_listener (0), + propagator (0), solution (0), vars (max_var) { + CADICAL_assert (internal); + CADICAL_assert (!internal->external); + internal->external = this; +} + +External::~External () { + if (solution) + delete[] solution; +} + +void External::enlarge (int new_max_var) { + + CADICAL_assert (!extended); + + size_t new_vsize = vsize ? 2 * vsize : 1 + (size_t) new_max_var; + while (new_vsize <= (size_t) new_max_var) + new_vsize *= 2; + LOG ("enlarge external size from %zd to new size %zd", vsize, new_vsize); + vsize = new_vsize; +} + +void External::init (int new_max_var, bool extension) { + CADICAL_assert (!extended); + if (new_max_var <= max_var) + return; + int new_vars = new_max_var - max_var; + int old_internal_max_var = internal->max_var; + int new_internal_max_var = old_internal_max_var + new_vars; + internal->init_vars (new_internal_max_var); + if ((size_t) new_max_var >= vsize) + enlarge (new_max_var); + LOG ("initialized %d external variables", new_vars); + if (!max_var) { + CADICAL_assert (e2i.empty ()); + e2i.push_back (0); + ext_units.push_back (0); + ext_units.push_back (0); + ext_flags.push_back (0); + ervars.push_back (0); + CADICAL_assert (internal->i2e.empty ()); + internal->i2e.push_back (0); + } else { + CADICAL_assert (e2i.size () == (size_t) max_var + 1); + CADICAL_assert (internal->i2e.size () == (size_t) old_internal_max_var + 1); + } + unsigned iidx = old_internal_max_var + 1, eidx; + for (eidx = max_var + 1u; eidx <= (unsigned) new_max_var; + eidx++, iidx++) { + LOG ("mapping external %u to internal %u", eidx, iidx); + CADICAL_assert (e2i.size () == eidx); + e2i.push_back (iidx); + ext_units.push_back (0); + ext_units.push_back (0); + ext_flags.push_back (0); + ervars.push_back (0); + internal->i2e.push_back (eidx); + CADICAL_assert (internal->i2e[iidx] == (int) eidx); + CADICAL_assert (e2i[eidx] == (int) iidx); + } + if (extension) + internal->stats.variables_extension += new_vars; + else + internal->stats.variables_original += new_vars; + if (new_max_var >= (int64_t) is_observed.size ()) + is_observed.resize (1 + (size_t) new_max_var, false); + if (internal->opts.checkfrozen) + if (new_max_var >= (int64_t) moltentab.size ()) + moltentab.resize (1 + (size_t) new_max_var, false); + CADICAL_assert (iidx == (size_t) new_internal_max_var + 1); + CADICAL_assert (eidx == (size_t) new_max_var + 1); + CADICAL_assert (ext_units.size () == (size_t) new_max_var * 2 + 2); + max_var = new_max_var; +} + +/*------------------------------------------------------------------------*/ + +void External::reset_assumptions () { + assumptions.clear (); + internal->reset_assumptions (); +} + +void External::reset_concluded () { + concluded = false; + internal->reset_concluded (); +} + +void External::reset_constraint () { + constraint.clear (); + internal->reset_constraint (); +} + +void External::reset_extended () { + if (!extended) + return; + LOG ("reset extended"); + extended = false; +} + +void External::reset_limits () { internal->reset_limits (); } + +/*------------------------------------------------------------------------*/ + +// when extension is true, elit should be a fresh variable and +// we can set a flag that it is an extension variable. +// This is then used in the API contracts, that extension variables are +// never part of the input +int External::internalize (int elit, bool extension) { + int ilit; + if (elit) { + CADICAL_assert (elit != INT_MIN); + const int eidx = abs (elit); + if (extension && eidx <= max_var) + FATAL ("can not add a definition for an already used variable %d", + eidx); + if (eidx > max_var) { + init (eidx, extension); + } + if (extension) { + CADICAL_assert (ervars.size () > (size_t) eidx); + ervars[eidx] = true; + } + ilit = e2i[eidx]; + if (elit < 0) + ilit = -ilit; + if (!ilit) { + CADICAL_assert (internal->max_var < INT_MAX); + ilit = internal->max_var + 1u; + internal->init_vars (ilit); + e2i[eidx] = ilit; + LOG ("mapping external %d to internal %d", eidx, ilit); + e2i[eidx] = ilit; + internal->i2e.push_back (eidx); + CADICAL_assert (internal->i2e[ilit] == eidx); + CADICAL_assert (e2i[eidx] == ilit); + if (elit < 0) + ilit = -ilit; + } + if (internal->opts.checkfrozen) { + CADICAL_assert (eidx < (int64_t) moltentab.size ()); + if (moltentab[eidx]) + FATAL ("can not reuse molten literal %d", eidx); + } + Flags &f = internal->flags (ilit); + if (f.status == Flags::UNUSED) + internal->mark_active (ilit); + else if (f.status != Flags::ACTIVE && f.status != Flags::FIXED) + internal->reactivate (ilit); + if (!marked (tainted, elit) && marked (witness, -elit)) { + CADICAL_assert (!internal->opts.checkfrozen); + LOG ("marking tainted %d", elit); + mark (tainted, elit); + } + } else + ilit = 0; + return ilit; +} + +void External::add (int elit) { + CADICAL_assert (elit != INT_MIN); + reset_extended (); + + bool forgettable = false; + + if (internal->opts.check && + (internal->opts.checkwitness || internal->opts.checkfailed)) { + + forgettable = + internal->from_propagator && internal->ext_clause_forgettable; + + // Forgettable clauses (coming from the external propagator) are not + // saved into the external 'original' stack. They are stored separately + // in external 'forgettable_original', from where they are deleted when + // the corresponding clause is deleted (actually deleted, not just + // marked as garbage). + if (!forgettable) + original.push_back (elit); + } + + const int ilit = internalize (elit); + CADICAL_assert (!elit == !ilit); + + // The external literals of the new clause must be saved for later + // when the proof is printed during add_original_lit (0) + if (elit && (internal->proof || forgettable)) { + eclause.push_back (elit); + if (internal->lrat) { + // actually find unit of -elit (flips elit < 0) + unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit); + CADICAL_assert ((size_t) eidx < ext_units.size ()); + const int64_t id = ext_units[eidx]; + bool added = ext_flags[abs (elit)]; + if (id && !added) { + ext_flags[abs (elit)] = true; + internal->lrat_chain.push_back (id); + } + } + } + + if (!elit && internal->proof && internal->lrat) { + for (const auto &elit : eclause) { + ext_flags[abs (elit)] = false; + } + } + + if (elit) + LOG ("adding external %d as internal %d", elit, ilit); + internal->add_original_lit (ilit); + + // Clean-up saved external literals once proof line is printed + if (!elit && (internal->proof || forgettable)) + eclause.clear (); +} + +void External::assume (int elit) { + CADICAL_assert (elit); + reset_extended (); + if (internal->proof) + internal->proof->add_assumption (elit); + assumptions.push_back (elit); + const int ilit = internalize (elit); + CADICAL_assert (ilit); + LOG ("assuming external %d as internal %d", elit, ilit); + internal->assume (ilit); +} + +bool External::flip (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + CADICAL_assert (!propagator); + + int eidx = abs (elit); + if (eidx > max_var) + return false; + if (marked (witness, elit)) + return false; + int ilit = e2i[eidx]; + if (!ilit) + return false; + bool res = internal->flip (ilit); + if (res && extended) + reset_extended (); + return res; +} + +bool External::flippable (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + CADICAL_assert (!propagator); + + int eidx = abs (elit); + if (eidx > max_var) + return false; + if (marked (witness, elit)) + return false; + int ilit = e2i[eidx]; + if (!ilit) + return false; + return internal->flippable (ilit); +} + +bool External::failed (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return 0; + int ilit = e2i[eidx]; + if (!ilit) + return 0; + if (elit < 0) + ilit = -ilit; + return internal->failed (ilit); +} + +void External::constrain (int elit) { + if (constraint.size () && !constraint.back ()) { + LOG (constraint, "replacing previous constraint"); + reset_constraint (); + } + CADICAL_assert (elit != INT_MIN); + reset_extended (); + const int ilit = internalize (elit); + CADICAL_assert (!elit == !ilit); + if (elit) + LOG ("adding external %d as internal %d to constraint", elit, ilit); + else if (!elit && internal->proof) { + internal->proof->add_constraint (constraint); + } + constraint.push_back (elit); + internal->constrain (ilit); +} + +bool External::failed_constraint () { + return internal->failed_constraint (); +} + +void External::phase (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + const int ilit = internalize (elit); + internal->phase (ilit); +} + +void External::unphase (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) { + UNUSED: + LOG ("resetting forced phase of unused external %d ignored", elit); + return; + } + int ilit = e2i[eidx]; + if (!ilit) + goto UNUSED; + if (elit < 0) + ilit = -ilit; + internal->unphase (ilit); +} + +/*------------------------------------------------------------------------*/ + +// External propagation related functions +// +// Note that when an already assigned variable is added as observed, the +// solver will backtrack to undo this assignment. +// +void External::add_observed_var (int elit) { + if (!propagator) { + LOG ("No connected propagator that could observe the variable, " + "observed flag is not set."); + return; + } + + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + reset_extended (); // tainting! + + int eidx = abs (elit); + if (eidx <= max_var && + (marked (witness, elit) || marked (witness, -elit))) { + LOG ("Error, only clean variables are allowed to become observed."); + CADICAL_assert (false); + + // TODO: here needs to come the taint and restore of the newly + // observed variable. Restore_clauses must be called before continue. + // LOG ("marking tainted %d", elit); + // mark (tainted, elit); + // mark (tainted, -elit); + // restore_clauses ... + } + + if (eidx >= (int64_t) is_observed.size ()) + is_observed.resize (1 + (size_t) eidx, false); + + if (is_observed[eidx]) + return; + + LOG ("marking %d as externally watched", eidx); + + // Will do the necessary internalization + freeze (elit); + is_observed[eidx] = true; + + int ilit = internalize (elit); + // internal add-observed-var backtracks to a lower decision level to + // unassign the variable in case it was already assigned previously (but + // not on the current level) + internal->add_observed_var (ilit); + + if (propagator->is_lazy) + return; + + // In case this variable was already assigned (e.g. via unit clause) and + // got compacted to map to another (not observed) variable, it can not be + // unnasigned so it must be notified explicitly now. (-> Can lead to + // repeated fixed assignment notifications, in case it was unobserved and + // observed again. But a repeated notification is less error-prone than + // never notifying an assignment.) + const int tmp = fixed (elit); + if (!tmp) + return; + int unit = tmp < 0 ? -elit : elit; + + LOG ("notify propagator about fixed assignment upon observe for %d", + unit); + + // internal add-observed-var had to backtrack to root-level already + CADICAL_assert (!internal->level); + + std::vector assigned = {unit}; + propagator->notify_assignment (assigned); +} + +void External::remove_observed_var (int elit) { + if (!propagator) { + LOG ("No connected propagator that could have watched the variable"); + return; + } + int eidx = abs (elit); + + if (eidx > max_var) + return; + + if (is_observed[eidx]) { + // Follow opposite order of add_observed_var, first remove internal + // is_observed + int ilit = e2i[eidx]; // internalize (elit); + internal->remove_observed_var (ilit); + + is_observed[eidx] = false; + melt (elit); + LOG ("unmarking %d as externally watched", eidx); + } +} + +void External::reset_observed_vars () { + // Shouldn't be called if there is no connected propagator + CADICAL_assert (propagator); + reset_extended (); + + internal->notified = 0; + LOG ("reset notified counter to 0"); + + if (!is_observed.size ()) + return; + + CADICAL_assert (!max_var || (size_t) max_var + 1 == is_observed.size ()); + + for (auto elit : vars) { + int eidx = abs (elit); + CADICAL_assert (eidx <= max_var); + if (is_observed[eidx]) { + int ilit = internalize (elit); + internal->remove_observed_var (ilit); + LOG ("unmarking %d as externally watched", eidx); + is_observed[eidx] = false; + melt (elit); + } + } +} + +bool External::observed (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return false; + if (eidx >= (int) is_observed.size ()) + return false; + + return is_observed[eidx]; +} + +bool External::is_witness (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return false; + return (marked (witness, elit) || marked (witness, -elit)); +} + +bool External::is_decision (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return false; + + int ilit = internalize (elit); + return internal->is_decision (ilit); +} + +void External::force_backtrack (size_t new_level) { + if (!propagator) { + LOG ("No connected propagator that could force backtracking"); + return; + } + LOG ("force backtrack to level %zd", new_level); + internal->force_backtrack (new_level); +} + +/*------------------------------------------------------------------------*/ + +int External::propagate_assumptions () { + int res = internal->propagate_assumptions (); + if (res == 10 && !extended) + extend (); // Call solution reconstruction + check_solve_result (res); + reset_limits (); + return res; +} + +void External::implied (std::vector &trailed) { + std::vector ilit_implicants; + internal->implied (ilit_implicants); + + // Those implied literals must be filtered out that are witnesses + // on the reconstruction stack -> no inplace externalize is possible. + // (Internal does not see these marks, so no earlier filter is + // possible.) + + trailed.clear(); + + for (const auto &ilit : ilit_implicants) { + CADICAL_assert (ilit); + const int elit = internal->externalize (ilit); + const int eidx = abs (elit); + const bool is_extension_var = ervars[eidx]; + if (!marked (tainted, elit) && !is_extension_var) { + trailed.push_back (elit); + } + } +} + +void External::conclude_unknown () { + if (!internal->proof || concluded) + return; + concluded = true; + + vector trail; + implied (trail); + internal->proof->conclude_unknown (trail); +} + +/*------------------------------------------------------------------------*/ + +// Internal checker if 'solve' claims the formula to be satisfiable. + +void External::check_satisfiable () { + LOG ("checking satisfiable"); + if (!extended) + extend (); + if (internal->opts.checkwitness) + check_assignment (&External::ival); + if (internal->opts.checkassumptions && !assumptions.empty ()) + check_assumptions_satisfied (); + if (internal->opts.checkconstraint && !constraint.empty ()) + check_constraint_satisfied (); +} + +// Internal checker if 'solve' claims formula to be unsatisfiable. + +void External::check_unsatisfiable () { + LOG ("checking unsatisfiable"); + if (!internal->opts.checkfailed) + return; + if (!assumptions.empty () || !constraint.empty ()) + check_failing (); +} + +// Check result of 'solve' to be correct. + +void External::check_solve_result (int res) { + if (!internal->opts.check) + return; + if (res == 10) + check_satisfiable (); + if (res == 20) + check_unsatisfiable (); +} + +// Prepare checking that completely molten literals are not used as argument +// of 'add' or 'assume', which is invalid under freezing semantics. This +// case would be caught by our 'restore' implementation so is only needed +// for checking the deprecated 'freeze' semantics. + +void External::update_molten_literals () { + if (!internal->opts.checkfrozen) + return; + CADICAL_assert ((size_t) max_var + 1 == moltentab.size ()); +#ifdef LOGGING + int registered = 0, molten = 0; +#endif + for (auto lit : vars) { + if (moltentab[lit]) { + LOG ("skipping already molten literal %d", lit); +#ifdef LOGGING + molten++; +#endif + } else if (frozen (lit)) + LOG ("skipping currently frozen literal %d", lit); + else { + LOG ("new molten literal %d", lit); + moltentab[lit] = true; +#ifdef LOGGING + registered++; + molten++; +#endif + } + } + LOG ("registered %d new molten literals", registered); + LOG ("reached in total %d molten literals", molten); +} + +int External::solve (bool preprocess_only) { + reset_extended (); + update_molten_literals (); + int res = internal->solve (preprocess_only); + check_solve_result (res); + reset_limits (); + return res; +} + +void External::terminate () { internal->terminate (); } + +int External::lookahead () { + reset_extended (); + update_molten_literals (); + int ilit = internal->lookahead (); + const int elit = + (ilit && ilit != INT_MIN) ? internal->externalize (ilit) : 0; + LOG ("lookahead internal %d external %d", ilit, elit); + return elit; +} + +CaDiCaL::CubesWithStatus External::generate_cubes (int depth, + int min_depth = 0) { + reset_extended (); + update_molten_literals (); + reset_limits (); + auto cubes = internal->generate_cubes (depth, min_depth); + auto externalize = [this] (int ilit) { + const int elit = ilit ? internal->externalize (ilit) : 0; + MSG ("lookahead internal %d external %d", ilit, elit); + return elit; + }; + auto externalize_map = [this, externalize] (std::vector cube) { + (void) this; + MSG ("Cube : "); + std::for_each (begin (cube), end (cube), externalize); + }; + std::for_each (begin (cubes.cubes), end (cubes.cubes), externalize_map); + + return cubes; +} + +/*------------------------------------------------------------------------*/ + +void External::freeze (int elit) { + reset_extended (); + int ilit = internalize (elit); + unsigned eidx = vidx (elit); + if (eidx >= frozentab.size ()) + frozentab.resize (eidx + 1, 0); + unsigned &ref = frozentab[eidx]; + if (ref < UINT_MAX) { + ref++; + LOG ("external variable %d frozen once and now frozen %u times", eidx, + ref); + } else + LOG ("external variable %d frozen but remains frozen forever", eidx); + internal->freeze (ilit); +} + +void External::melt (int elit) { + reset_extended (); + int ilit = internalize (elit); + unsigned eidx = vidx (elit); + CADICAL_assert (eidx < frozentab.size ()); + unsigned &ref = frozentab[eidx]; + CADICAL_assert (ref > 0); + if (ref < UINT_MAX) { + if (!--ref) { + if (observed (elit)) { + ref++; + LOG ("external variable %d is observed, can not be completely " + "molten", + eidx); + } else + LOG ("external variable %d melted once and now completely melted", + eidx); + } else + LOG ("external variable %d melted once but remains frozen %u times", + eidx, ref); + } else + LOG ("external variable %d melted but remains frozen forever", eidx); + internal->melt (ilit); +} + +/*------------------------------------------------------------------------*/ + +void External::check_assignment (int (External::*a) (int) const) { + + // First check all assigned and consistent. + // + for (auto idx : vars) { + if (!(this->*a) (idx)) + FATAL ("unassigned variable: %d", idx); + int value_idx = (this->*a) (idx); + int value_neg_idx = (this->*a) (-idx); + if (value_idx == idx) + CADICAL_assert (value_neg_idx == idx); + else { + CADICAL_assert (value_idx == -idx); + CADICAL_assert (value_neg_idx == -idx); + } + if (value_idx != value_neg_idx) + FATAL ("inconsistently assigned literals %d and %d", idx, -idx); + } + + // Then check that all (saved) original clauses are satisfied. + // + bool satisfied = false; + const auto end = original.end (); + auto start = original.begin (), i = start; +#ifndef CADICAL_QUIET + int64_t count = 0; +#endif + for (; i != end; i++) { + int lit = *i; + if (!lit) { + if (!satisfied) { + fatal_message_start (); + fputs ("unsatisfied clause:\n", stderr); + for (auto j = start; j != i; j++) + fprintf (stderr, "%d ", *j); + fputc ('0', stderr); + fatal_message_end (); + } + satisfied = false; + start = i + 1; +#ifndef CADICAL_QUIET + count++; +#endif + } else if (!satisfied && (this->*a) (lit) == lit) + satisfied = true; + } + + bool presence_flag; + // Check those forgettable external clauses that are still present, but + // only if the external propagator is still connected (otherwise solution + // reconstruction is allowed to touch the previously observed variables so + // there is no guarantee that the final model will satisfy these clauses.) + for (const auto &forgettables : forgettable_original) { + if (!propagator) + break; + presence_flag = true; + satisfied = false; +#ifndef CADICAL_QUIET + count++; +#endif + std::vector literals; + for (const auto lit : forgettables.second) { + if (presence_flag) { + // First integer is a Boolean flag, not a literal + if (!lit) { + // Deleted clauses can be ignored, they count as satisfied + satisfied = true; + break; + } + presence_flag = false; + continue; + } + + if ((this->*a) (lit) == lit) { + satisfied = true; + break; + } + } + + if (!satisfied) { + fatal_message_start (); + fputs ("unsatisfied external forgettable clause:\n", stderr); + for (size_t j = 1; j < forgettables.second.size (); j++) + fprintf (stderr, "%d ", forgettables.second[j]); + fputc ('0', stderr); + fatal_message_end (); + } + } +#ifndef CADICAL_QUIET + VERBOSE (1, "satisfying assignment checked on %" PRId64 " clauses", + count); +#endif +} + +/*------------------------------------------------------------------------*/ + +void External::check_assumptions_satisfied () { + for (const auto &lit : assumptions) { + // Not 'signed char' !!!! + const int tmp = ival (lit); + if (tmp != lit) + FATAL ("assumption %d falsified", lit); + if (!tmp) + FATAL ("assumption %d unassigned", lit); + } + VERBOSE (1, "checked that %zd assumptions are satisfied", + assumptions.size ()); +} + +void External::check_constraint_satisfied () { + for (const auto lit : constraint) { + if (ival (lit) == lit) { + VERBOSE (1, "checked that constraint is satisfied"); + return; + } + } + FATAL ("constraint not satisfied"); +} + +void External::check_failing () { + Solver *checker = new Solver (); + DeferDeletePtr delete_checker (checker); + checker->prefix ("checker "); +#ifdef LOGGING + if (internal->opts.log) + checker->set ("log", true); +#endif + + for (const auto lit : assumptions) { + if (!failed (lit)) + continue; + LOG ("checking failed literal %d in core", lit); + checker->add (lit); + checker->add (0); + } + if (failed_constraint ()) { + LOG (constraint, "checking failed constraint"); + for (const auto lit : constraint) + checker->add (lit); + } else if (constraint.size ()) + LOG (constraint, "constraint satisfied and ignored"); + + // Add original clauses as last step, failing () and failed_constraint () + // might add more external clauses (due to lazy explanation) + for (const auto lit : original) + checker->add (lit); + + // Add every forgettable external clauses + for (const auto &forgettables : forgettable_original) { + bool presence_flag = true; + for (const auto lit : forgettables.second) { + if (presence_flag) { + // First integer is a Boolean flag, not a literal, ignore it here + presence_flag = false; + continue; + } + checker->add (lit); + } + checker->add (0); + } + + int res = checker->solve (); + if (res != 20) + FATAL ("failed assumptions do not form a core"); + delete_checker.free (); + VERBOSE (1, "checked that %zd failing assumptions form a core", + assumptions.size ()); +} + +/*------------------------------------------------------------------------*/ + +// Traversal of unit clauses is implemented here. + +// In principle we want to traverse the clauses of the simplified formula +// only, particularly eliminated variables should be completely removed. +// This poses the question what to do with unit clauses. Should they be +// considered part of the simplified formula or of the witness to construct +// solutions for the original formula? Ideally they should be considered +// to be part of the witness only, i.e., as they have been simplified away. + +// Therefore we distinguish frozen and non-frozen units during clause +// traversal. Frozen units are treated as unit clauses while non-frozen +// units are treated as if they were already eliminated and put on the +// extension stack as witness clauses. + +// Furthermore, eliminating units during 'compact' could be interpreted as +// variable elimination, i.e., just add the resolvents (remove falsified +// literals), then drop the clauses with the unit, and push the unit on the +// extension stack. This is of course only OK if the user did not freeze +// that variable (even implicitly during assumptions). + +// Thanks go to Fahiem Bacchus for asking why there is a necessity to +// distinguish these two cases (frozen and non-frozen units). The answer is +// that it is not strictly necessary, and this distinction could be avoided +// by always treating units as remaining unit clauses, thus only using the +// first of the two following functions and dropping the 'if (!frozen (idx)) +// continue;' check in it. This has however the down-side that those units +// are still in the simplified formula and only as units. I would not +// consider such a formula as really being 'simplified'. On the other hand +// if the user explicitly freezes a literal, then it should continue to be +// in the simplified formula during traversal. So also only using the +// second function is not ideal. + +// There is however a catch where this solution breaks down (in the sense of +// producing less optimal results - that is keeping units in the formula +// which better would be witness clauses). The problem is with compact +// preprocessing which removes eliminated but also fixed internal variables. +// One internal unit (fixed) variable is kept and all the other external +// literals which became unit are mapped to that internal literal (negated +// or positively). Compact is called non-deterministically from the point +// of the user and thus there is no control on when this happens. If +// compact happens those external units are mapped to a single internal +// literal now and then all share the same 'frozen' counter. So if the +// user freezes one of them all in essence get frozen, which in turn then +// makes a difference in terms of traversing such a unit as unit clause or +// as unit witness. + +bool External::traverse_all_frozen_units_as_clauses (ClauseIterator &it) { + if (internal->unsat) + return true; + + vector clause; + + for (auto idx : vars) { + if (!frozen (idx)) + continue; + const int tmp = fixed (idx); + if (!tmp) + continue; + int unit = tmp < 0 ? -idx : idx; + clause.push_back (unit); + if (!it.clause (clause)) + return false; + clause.clear (); + } + + return true; +} + +bool External::traverse_all_non_frozen_units_as_witnesses ( + WitnessIterator &it) { + if (internal->unsat) + return true; + + vector clause_and_witness; + for (auto idx : vars) { + if (frozen (idx)) + continue; + const int tmp = fixed (idx); + if (!tmp) + continue; + int unit = tmp < 0 ? -idx : idx; + const int ilit = e2i[idx] * (tmp < 0 ? -1 : 1); + // heurstically add + max_var to the id to avoid reusing ids + const int64_t id = internal->lrat ? internal->unit_id (ilit) : 1; + CADICAL_assert (id); + clause_and_witness.push_back (unit); + if (!it.witness (clause_and_witness, clause_and_witness, id + max_var)) + return false; + clause_and_witness.clear (); + } + + return true; +} + +/*------------------------------------------------------------------------*/ + +void External::copy_flags (External &other) const { + const vector &this_ftab = internal->ftab; + vector &other_ftab = other.internal->ftab; + const unsigned limit = min (max_var, other.max_var); + for (unsigned eidx = 1; eidx <= limit; eidx++) { + const int this_ilit = e2i[eidx]; + if (!this_ilit) + continue; + const int other_ilit = other.e2i[eidx]; + if (!other_ilit) + continue; + if (!internal->active (this_ilit)) + continue; + if (!other.internal->active (other_ilit)) + continue; + CADICAL_assert (this_ilit != INT_MIN); + CADICAL_assert (other_ilit != INT_MIN); + const Flags &this_flags = this_ftab[abs (this_ilit)]; + Flags &other_flags = other_ftab[abs (other_ilit)]; + this_flags.copy (other_flags); + } +} + +/*------------------------------------------------------------------------*/ + +void External::export_learned_empty_clause () { + CADICAL_assert (learner); + if (learner->learning (0)) { + LOG ("exporting learned empty clause"); + learner->learn (0); + } else + LOG ("not exporting learned empty clause"); +} + +void External::export_learned_unit_clause (int ilit) { + CADICAL_assert (learner); + if (learner->learning (1)) { + LOG ("exporting learned unit clause"); + const int elit = internal->externalize (ilit); + CADICAL_assert (elit); + learner->learn (elit); + learner->learn (0); + } else + LOG ("not exporting learned unit clause"); +} + +void External::export_learned_large_clause (const vector &clause) { + CADICAL_assert (learner); + size_t size = clause.size (); + CADICAL_assert (size <= (unsigned) INT_MAX); + if (learner->learning ((int) size)) { + LOG ("exporting learned clause of size %zu", size); + for (auto ilit : clause) { + const int elit = internal->externalize (ilit); + CADICAL_assert (elit); + learner->learn (elit); + } + learner->learn (0); + } else + LOG ("not exporting learned clause of size %zu", size); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_external_propagate.cpp b/src/sat/cadical/cadical_external_propagate.cpp new file mode 100644 index 000000000..215170c8b --- /dev/null +++ b/src/sat/cadical/cadical_external_propagate.cpp @@ -0,0 +1,1232 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*----------------------------------------------------------------------------*/ +// +// Mark a variable as an observed one. It can be a new variable. It is +// assumed to be clean (not eliminated by previous simplifications). +// +void Internal::add_observed_var (int ilit) { + int idx = vidx (ilit); + if (idx >= (int64_t) relevanttab.size ()) + relevanttab.resize (1 + (size_t) idx, 0); + unsigned &ref = relevanttab[idx]; + if (ref < UINT_MAX) { + ref++; + LOG ("variable %d is observed %u times", idx, ref); + } else + LOG ("variable %d remains observed forever", idx); + // TODO: instead of actually backtracking, it would be enough to notify + // backtrack and re-play again every levels' notification to the + // propagator + if (val (ilit) && level && !fixed (ilit)) { + // The variable is already assigned, but we can not send a notification + // about it because it happened on an earlier decision level. + // To not break the stack-like view of the trail, we simply backtrack to + // undo this unnotifiable assignment. + const int assignment_level = var (ilit).level; + backtrack (assignment_level - 1); + } else if (level && fixed (ilit)) { + backtrack (0); + } +} + +/*----------------------------------------------------------------------------*/ +// +// Removing an observed variable should happen only once it is ensured +// that there is no unexplained propagation in the implication +// graph involving this variable. +// +void Internal::remove_observed_var (int ilit) { + if (!fixed (ilit) && level) { + backtrack (); + } + + CADICAL_assert (fixed (ilit) || !level); + + const int idx = vidx (ilit); + CADICAL_assert ((size_t) idx < relevanttab.size ()); + unsigned &ref = relevanttab[idx]; + CADICAL_assert (fixed (ilit) || ref > 0); + if (fixed (ilit)) + ref = 0; + else if (ref < UINT_MAX) { + if (!--ref) { + LOG ("variable %d is not observed anymore", idx); + } else + LOG ("variable %d is unobserved once but remains observed %u times", + ilit, ref); + } else + LOG ("variable %d remains observed forever", idx); +} + +/*----------------------------------------------------------------------------*/ +// +// Supposed to be used only by mobical. +// +bool Internal::observed (int ilit) const { + CADICAL_assert ((size_t) vidx (ilit) < relevanttab.size ()); + return relevanttab[vidx (ilit)] > 0; +} + +/*----------------------------------------------------------------------------*/ +// +// Check for unexplained propagations upon disconnecting external propagator +// +void Internal::set_tainted_literal () { + if (!opts.ilb) { + return; + } + for (auto idx : vars) { + if (!val (idx)) + continue; + if (var (idx).reason != external_reason) + continue; + if (!tainted_literal) { + tainted_literal = idx; + continue; + } + CADICAL_assert (val (tainted_literal)); + if (var (idx).level < var (tainted_literal).level) { + tainted_literal = idx; + } + } +} + +void Internal::renotify_trail_after_ilb () { + if (!external_prop || external_prop_is_lazy || !trail.size () || + !opts.ilb) { + return; + } + LOG ("notify external propagator about new assignments (after ilb)"); +#ifndef CADICAL_NDEBUG + LOG ("(decision level: %d, trail size: %zd, notified %zd)", level, + trail.size (), notified); +#endif + renotify_full_trail (); +} + +void Internal::renotify_trail_after_local_search () { + if (!external_prop || external_prop_is_lazy || !trail.size ()) { + return; + } + LOG ("notify external propagator about new assignments (after local " + "search)"); +#ifndef CADICAL_NDEBUG + LOG ("(decision level: %d, trail size: %zd, notified %zd)", level, + trail.size (), notified); +#endif + renotify_full_trail (); +} + +// It repeats ALL assignments of the trail, so the already notified +// root-level assignments will be notified multiple times. + +void Internal::renotify_full_trail () { + const size_t end_of_trail = trail.size (); + if (level) { + notified = 0; // TODO: save the last notified root-level position + // somewhere and use it here + notify_backtrack (0); + } + std::vector assigned; + + int prev_max_level = 0; + int current_level = 0; + int propagator_level = 0; + + while (notified < end_of_trail) { + int ilit = trail[notified++]; + // In theory, 0 ilit can happen due to pseudo-decision levels + if (!ilit) + current_level = prev_max_level + 1; + else + current_level = var (ilit).level; + + if (current_level > propagator_level) { + if (assigned.size ()) + external->propagator->notify_assignment (assigned); + while (current_level > propagator_level) { + external->propagator->notify_new_decision_level (); + propagator_level++; + } + assigned.clear (); + } + // Current level can be smaller than prev_max_level due to chrono + if (current_level > prev_max_level) + prev_max_level = current_level; + + if (!observed (ilit)) + continue; + + int elit = externalize (ilit); // TODO: double-check tainting + CADICAL_assert (elit); + // Fixed variables might get mapped (during compact) to another + // non-observed but fixed variable. + // This happens on root level, so notification about their assignment is + // already done. + CADICAL_assert (external->observed (elit) || fixed (ilit)); + if (!external->ervars[abs (elit)]) + assigned.push_back (elit); + } + if (assigned.size ()) + external->propagator->notify_assignment (assigned); + assigned.clear (); + + // In case there are some left over empty levels on the top of the trail, + // the external propagtor must be notified about them so the levels are + // synced + while (level > propagator_level) { + external->propagator->notify_new_decision_level (); + propagator_level++; + } + + return; +} + +/*----------------------------------------------------------------------------*/ +// +// Check if the variable is assigned by decision. +// +bool Internal::is_decision (int ilit) { + if (!level || fixed (ilit) || !val (ilit)) + return false; + + const int idx = vidx (ilit); + Var &v = var (idx); +#ifndef CADICAL_NDEBUG + LOG (v.reason, + "is_decision: i%d (current level: %d, is_fixed: %d, v.level: %d, " + "is_external_reason: %d, v.reason: )", + ilit, level, fixed (ilit), v.level, v.reason == external_reason); +#endif + if (!v.level || v.reason) + return false; + CADICAL_assert (!v.reason); + return true; +} + +void Internal::force_backtrack (size_t new_level) { + if (!forced_backt_allowed || level <= 0 || new_level >= (size_t) level) + return; + +#ifndef CADICAL_NDEBUG + LOG ("external propagator forces backtrack to decision level" + "%zd (from level %d)", + new_level, level); +#endif + backtrack (new_level); +} + +/*----------------------------------------------------------------------------*/ +// +// Call external propagator to check if there is a literal to be propagated. +// The reason of the propagation is not necessarily asked at that point. +// +// In case the externally propagated literal is already falsified, the +// reason is asked and conflict analysis starts. In case the externally +// propagated literal is already satisfied, nothing happens. +// +bool Internal::external_propagate () { + if (level) + require_mode (SEARCH); + CADICAL_assert (!unsat); + + size_t before = num_assigned; + bool cb_repropagate_needed = true; + while (cb_repropagate_needed && !conflict && external_prop && + !external_prop_is_lazy && !private_steps) { +#ifndef CADICAL_NDEBUG + LOG ("external propagation starts (decision level: %d, trail size: " + "%zd, notified %zd)", + level, trail.size (), notified); +#endif + cb_repropagate_needed = false; + // external->reset_extended (); //TODO for inprocessing + + notify_assignments (); + + int elit = external->propagator->cb_propagate (); + stats.ext_prop.ext_cb++; + stats.ext_prop.eprop_call++; + while (elit) { + CADICAL_assert (external->is_observed[abs (elit)]); + int ilit = external->e2i[abs (elit)]; + if (elit < 0) + ilit = -ilit; + int tmp = val (ilit); +#ifndef CADICAL_NDEBUG + CADICAL_assert (fixed (ilit) || observed (ilit)); + LOG ("External propagation of e%d (i%d val: %d)", elit, ilit, tmp); +#endif + if (!tmp) { + // variable is not assigned, it can be propagated + if (!level) { + Clause *res = learn_external_reason_clause (ilit, elit); +#ifndef LOGGING + LOG (res, "reason clause of external propagation of %d:", elit); +#endif + (void) res; + } else + search_assign_external (ilit); + stats.ext_prop.eprop_prop++; + + if (unsat || conflict) + break; + propagate (); + if (unsat || conflict) + break; + notify_assignments (); + } else if (tmp < 0) { + LOG ("External propgation of %d is falsified under current trail", + ilit); + stats.ext_prop.eprop_conf++; + int level_before = level; + size_t assigned = num_assigned; + Clause *res = learn_external_reason_clause (ilit, elit); +#ifndef LOGGING + LOG (res, "reason clause of external propagation of %d:", elit); +#endif + (void) res; + bool trail_changed = + (num_assigned != assigned || level != level_before || + propagated < trail.size ()); + + if (unsat || conflict) + break; + + if (trail_changed) { + propagate (); + if (unsat || conflict) + break; + notify_assignments (); + } + } // else (tmp > 0) -> the case of a satisfied literal is ignored + elit = external->propagator->cb_propagate (); + stats.ext_prop.ext_cb++; + stats.ext_prop.eprop_call++; + } + +#ifndef CADICAL_NDEBUG + LOG ("External propagation ends (decision level: %d, trail size: %zd, " + "notified %zd)", + level, trail.size (), notified); +#endif + if (!unsat && !conflict) { + int level_before = level; + size_t assigned = num_assigned; + bool has_external_clause = ask_external_clause (); + // New observed variable might have triggered a backtrack during this + // ask_external_clause call, so we need to propagate before continuing + stats.ext_prop.ext_cb++; + stats.ext_prop.elearn_call++; + + bool trail_changed = + (num_assigned != assigned || level != level_before || + propagated < trail.size ()); + if (trail_changed) { + propagate (); // unsat or conflict will be caught later + if (!unsat || !conflict) + notify_assignments (); + } + +#ifndef CADICAL_NDEBUG + if (has_external_clause) + LOG ("New external clauses are to be added."); + else + LOG ("No external clauses to be added."); +#endif + + while (has_external_clause) { + level_before = level; + assigned = num_assigned; + + add_external_clause (0); + trail_changed = + (num_assigned != assigned || level != level_before || + propagated < trail.size ()); + cb_repropagate_needed = true; + + if (unsat || conflict) { + cb_repropagate_needed = false; + break; + } + + if (trail_changed) { + propagate (); + if (unsat || conflict) { + cb_repropagate_needed = false; + break; + } + + notify_assignments (); + } + has_external_clause = ask_external_clause (); + stats.ext_prop.ext_cb++; + stats.ext_prop.elearn_call++; + } + } +#ifndef CADICAL_NDEBUG + LOG ("External clause addition ends on decision level %d at trail " + "size " + "%zd (notified %zd)", + level, trail.size (), notified); +#endif + } + if (before < num_assigned) + did_external_prop = true; + return !conflict; +} + +/*----------------------------------------------------------------------------*/ +// +// Helper function, calls 'cb_has_external_clause', while maintains the +// related redundancy type of the clause. +// + +bool Internal::ask_external_clause () { + ext_clause_forgettable = false; + bool res = + external->propagator->cb_has_external_clause (ext_clause_forgettable); + + return res; +} +/*----------------------------------------------------------------------------*/ +// +// Literals of the externally learned clause must be reordered based on the +// assignment levels of the literals. +// +void Internal::move_literals_to_watch () { + if (clause.size () < 2) + return; + if (!level) + return; + + for (int i = 0; i < 2; i++) { + int highest_position = i; + int highest_literal = clause[i]; + + int highest_level = var (highest_literal).level; + int highest_value = val (highest_literal); + + for (size_t j = i + 1; j < clause.size (); j++) { + const int other = clause[j]; + const int other_level = var (other).level; + const int other_value = val (other); + + if (other_value < 0) { + if (highest_value >= 0) + continue; + if (other_level <= highest_level) + continue; + } else if (other_value > 0) { + if (highest_value > 0 && other_level >= highest_level) + continue; + } else { + if (highest_value >= 0) + continue; + } + + highest_position = j; + highest_literal = other; + highest_level = other_level; + highest_value = other_value; + } +#ifndef CADICAL_NDEBUG + LOG ("highest position: %d highest level: %d highest value: %d", + highest_position, highest_level, highest_value); +#endif + + if (highest_position == i) + continue; + if (highest_position > i) { + std::swap (clause[i], clause[highest_position]); + } + } +} + +/*----------------------------------------------------------------------------*/ +// +// Reads out from the external propagator the lemma/proapgation reason +// clause literal by literal. In case propagated_elit is 0, it is about an +// external clause via 'cb_add_external_clause_lit'. Otherwise, it is about +// learning the reason of 'propagated_elit' via 'cb_add_reason_clause_lit'. +// The learned clause is simplified by the current root-level assignment +// (i.e. root-level falsified literals are removed, root satisfied clauses +// are skipped). Duplicate literals are removed, tauotologies are detected +// and skipped. It always adds the original (un-simplified) external clause +// to the proof as an input clause and +// the simplified version of it (except exceptions below) as a derived +// clause. +// +// In case the external clause, after simplifications, is satisfied, no +// clause is constructed, and the function returns 0. In case the external +// clause, after simplifications, is empty, no clause is constructed, unsat +// is set true and the function returns 0. In case the external clause, +// after simplifications, is unit, no clause is constructed, +// 'Internal::clause' has the unit literal (without 0) and the function +// returns 0. +// +// In every other cases a new clause is constructed and the pointer is in +// newest_clause +// +void Internal::add_external_clause (int propagated_elit, + bool no_backtrack) { + CADICAL_assert (original.empty ()); + int elit = 0; + + if (propagated_elit) { + // Propagation reason clauses are by default assumed to be forgettable + // irredundant. In case they would be unforgettably important, the + // propagator can add them as an explicit unforgettable external clause + // or set 'are_reasons_forgettable' to false. + ext_clause_forgettable = external->propagator->are_reasons_forgettable; +#ifndef CADICAL_NDEBUG + LOG ("add external reason of propagated lit: %d", propagated_elit); +#endif + elit = external->propagator->cb_add_reason_clause_lit (propagated_elit); + } else + elit = external->propagator->cb_add_external_clause_lit (); + + // we need to be build a new LRAT chain if we are already in the middle of + // the analysis (like during failed assumptions) + LOG (lrat_chain, "lrat chain before"); + std::vector lrat_chain_ext = std::move (lrat_chain); + lrat_chain.clear (); + clause.clear (); + + // Read out the external lemma into original and simplify it into clause + CADICAL_assert (clause.empty ()); + CADICAL_assert (original.empty ()); + + CADICAL_assert (!force_no_backtrack); + CADICAL_assert (!from_propagator); + force_no_backtrack = no_backtrack; + from_propagator = true; + while (elit) { + CADICAL_assert (external->is_observed[abs (elit)]); + external->add (elit); + if (propagated_elit) + elit = + external->propagator->cb_add_reason_clause_lit (propagated_elit); + else + elit = external->propagator->cb_add_external_clause_lit (); + } + external->add (elit); + CADICAL_assert (original.empty ()); + CADICAL_assert (clause.empty ()); + force_no_backtrack = false; + from_propagator = false; + lrat_chain = std::move (lrat_chain_ext); + LOG (lrat_chain, "lrat chain after"); +} + +/*----------------------------------------------------------------------------*/ +// +// Recursively calls 'learn_external_reason_clause' to explain every +// backward reachable externally propagated literal starting from 'ilit'. +// +void Internal::explain_reason (int ilit, Clause *reason, int &open) { + if (!opts.exteagerreasons) + return; +#ifndef CADICAL_NDEBUG + LOG (reason, "explain_reason of %d (open: %d)", ilit, open); +#endif + CADICAL_assert (reason); + CADICAL_assert (reason != external_reason); + for (const auto &other : *reason) { + if (other == ilit) + continue; + Flags &f = flags (other); + if (f.seen) + continue; + Var &v = var (other); + if (!v.level) + continue; + CADICAL_assert (val (other) < 0); + CADICAL_assert (v.level <= level); + if (v.reason == external_reason) { + v.reason = learn_external_reason_clause (-other, 0, true); + } + if (v.level && v.reason) { + f.seen = true; + open++; + } + } +} + +/*----------------------------------------------------------------------------*/ +// +// In case external propagation was used, the reason clauses of the relevant +// propagations must be learned lazily before/during conflict analysis. +// While conflict analysis needs to analyze only the current level, lazy +// clause learning must check every clause on every level that is backward +// reachable from the conflicting clause to guarantee that the assignment +// levels of the variables are accurate. So this explanation round is +// separated from the conflict analysis, thereby guranteeing that the flags +// and datastructures can be properly used later. +// +// This function must be called before the conflict analysis, in order to +// guarantee that every relevant reason clause is indeed learned already and +// to be sure that the levels of assignments are set correctly. +// +// Later TODO: experiment with bounded explanation (explain only those +// literals that are directly used during conflict analysis + +// minimizing/shrinking). The assignment levels are then only +// over-approximated. +// +void Internal::explain_external_propagations () { + CADICAL_assert (conflict); + CADICAL_assert (clause.empty ()); + + Clause *reason = conflict; + std::vector seen_lits; + int open = 0; // Seen but not explained literal + + explain_reason (0, reason, open); // marks conflict clause lits as seen + int i = trail.size (); // Start at end-of-trail + while (i > 0) { + const int lit = trail[--i]; + if (!flags (lit).seen) + continue; + seen_lits.push_back (lit); + Var &v = var (lit); + if (!v.level) + continue; + if (v.reason) { + open--; + explain_reason (lit, v.reason, open); + } + if (!open) + break; + } + CADICAL_assert (!open); + + if (!opts.exteagerrecalc) { + for (auto lit : seen_lits) { + Flags &f = flags (lit); + f.seen = false; + } +#ifndef CADICAL_NDEBUG + for (auto idx : vars) { + CADICAL_assert (!flags (idx).seen); + } +#endif + } + + // Traverse now in the opposite direction (from lower to higher levels) + // and calculate the actual assignment level for the seen assignments. + for (auto it = seen_lits.rbegin (); it != seen_lits.rend (); ++it) { + const int lit = *it; + Flags &f = flags (lit); + Var &v = var (lit); + if (v.reason) { + int real_level = 0; + for (const auto &other : *v.reason) { + if (other == lit) + continue; + int tmp = var (other).level; + if (tmp > real_level) + real_level = tmp; + } + if (v.level && !real_level) { + build_chain_for_units (lit, v.reason, 1); + learn_unit_clause (lit); + lrat_chain.clear (); + v.reason = 0; + } + CADICAL_assert (v.level >= real_level); + if (v.level > real_level) { + v.level = real_level; + } + } + f.seen = false; + } + +#if 0 // has been fuzzed extensively + for (auto idx : vars) { + CADICAL_assert (!flags (idx).seen); + } +#endif +} + +/*----------------------------------------------------------------------------*/ +// +// Learns the reason clause of the propagation of ilit from the +// external propagator via 'add_external_clause'. +// In case of falsified propagation steps, if the propagated literal is +// already fixed to the opposite value, externalize will not necessarily +// give back the original elit (but an equivalent one). To avoid that, in +// falsified propagation cases the propagated elit is added as a second +// argument. +// +Clause *Internal::learn_external_reason_clause (int ilit, + int falsified_elit, + bool no_backtrack) { + CADICAL_assert (external->propagator); + // we cannot modify clause during analysis + auto clause_tmp = std::move (clause); + + CADICAL_assert (clause.empty ()); + CADICAL_assert (original.empty ()); + + stats.ext_prop.eprop_expl++; + + int elit = 0; + if (!falsified_elit) { + CADICAL_assert (!fixed (ilit)); + elit = externalize (ilit); + } else + elit = falsified_elit; + + LOG ("ilit: %d, elit: %d", ilit, elit); + add_external_clause (elit, no_backtrack); + +#ifndef CADICAL_NDEBUG + if (!falsified_elit && newest_clause) { + // Check if external propagation is correct wrt to the topological order + // defined by the trail. In case it is a falsified external propagation + // step, the order does not matter, the reason simply supposed to be a + // falsified clause. + const int propagated_ilit = ilit; + for (auto const reason_ilit : *newest_clause) { + CADICAL_assert (var (reason_ilit).trail <= var (propagated_ilit).trail); + } + } +#endif + + clause = std::move (clause_tmp); + return newest_clause; +} + +/*----------------------------------------------------------------------------*/ +// +// Helper function to be able to call learn_external_reason_clause when the +// internal clause is already used in the caller side (for example during +// proof checking). These calls are assumed to be without a falsified elit. +// Dont use it in general instead of learn_external_reason_clause because it +// does not support the corner cases where a literal remains in clause. +// +Clause *Internal::wrapped_learn_external_reason_clause (int ilit) { + Clause *res; + std::vector chain_tmp{std::move (lrat_chain)}; + lrat_chain.clear (); + if (clause.empty ()) { + res = learn_external_reason_clause (ilit, 0, true); + } else { + std::vector clause_tmp{std::move (clause)}; + clause.clear (); + res = learn_external_reason_clause (ilit, 0, true); + // The learn_external_reason clause can leave a literal in clause when + // there is a falsified elit arg. Here it is not allowed to + // happen. + CADICAL_assert (clause.empty ()); + + clause = std::move (clause_tmp); + clause_tmp.clear (); + } + CADICAL_assert (lrat_chain.empty ()); + lrat_chain = std::move (chain_tmp); + chain_tmp.clear (); + return res; +} + +/*----------------------------------------------------------------------------*/ +// +// Checks if the new clause forces backtracking, new assignments or conflict +// analysis +// +void Internal::handle_external_clause (Clause *res) { + if (from_propagator) + stats.ext_prop.elearned++; + // at level 0 we have to do nothing... + if (!level) + return; + if (!res) { + if (from_propagator) + stats.ext_prop.elearn_prop++; + // new unit clause. For now just backtrack. + CADICAL_assert (!force_no_backtrack); + CADICAL_assert (level); + // if (!opts.chrono) { + backtrack (); + // } + return; + } + if (from_propagator) + stats.ext_prop.elearned++; + CADICAL_assert (res->size >= 2); + const int pos0 = res->literals[0]; + const int pos1 = res->literals[1]; + if (force_no_backtrack) { + CADICAL_assert (val (pos1) < 0); + CADICAL_assert (val (pos0) >= 0); + return; + // TODO: maybe fix levels + } + const int l1 = var (pos1).level; + if (val (pos0) < 0) { // conflicting or propagating clause + CADICAL_assert (0 < l1 && l1 <= var (pos0).level); + if (!opts.chrono) { + backtrack (l1); + } + if (val (pos0) < 0) { + conflict = res; + if (!from_propagator) { + // its better to backtrack instead of analyze + backtrack (l1 - 1); + conflict = 0; + CADICAL_assert (!val (pos0) && !val (pos1)); + } + } else { + search_assign_driving (pos0, res); + } + if (from_propagator) + stats.ext_prop.elearn_conf++; + return; + } + if (val (pos1) < 0 && !val (pos0)) { // propagating clause + if (!opts.chrono) { + backtrack (l1); + } + search_assign_driving (pos0, res); + if (from_propagator) + stats.ext_prop.elearn_conf++; + return; + } +} + +/*----------------------------------------------------------------------------*/ +// +// Asks the external propagator if the current solution is OK +// by calling 'cb_check_found_model (model)'. +// +// The checked model is built up after everything is restored +// from the reconstruction stack and every variable is reactivated +// and so it is not just simply the trail (i.e. it can be expensive). +// +// If the external propagator approves the model, the function +// returns true. +// +// If the propagator does not approve the model, the solver asks +// the propagator to add an external clause. +// This external clause, however, does NOT have to be falsified by +// the current model. The possible cases and reactions are described +// below in the function. The possible states after that function: +// - A solution was found and accepted by the external propagator +// - A conflicting clause was learned from the external propagator +// - The empty clause was learned due to something new learned from +// the external propagator. +// +// In case only new variables were introduced, but no new clauses were +// added, the function will return without a conflict to the outer CDCL +// loop, where the new (not yet satisfied) variables are recognized and +// the search continues. +bool Internal::external_check_solution () { + if (!external_prop) + return true; + + bool trail_changed = true; + bool added_new_clauses = false; + while (trail_changed || added_new_clauses) { + notify_assignments (); + if (!satisfied ()) + break; + trail_changed = false; // to be on the safe side + added_new_clauses = false; + LOG ("Final check by external propagator is invoked."); + stats.ext_prop.echeck_call++; + external->reset_extended (); + external->extend (); + + std::vector etrail; + + // Here the variables must be filtered by external->is_observed, + // because fixed variables are internally not necessarily observed + // anymore. + for (int idx = 1; idx <= external->max_var; idx++) { + if (!external->is_observed[idx]) + continue; + const int lit = external->ival (idx); + etrail.push_back (lit); +#ifndef CADICAL_NDEBUG +#ifdef LOGGING + bool p = external->vals[idx]; + LOG ("evals[%d]: %d ival(%d): %d", idx, p, idx, lit); +#endif +#endif + } + + bool is_consistent = + external->propagator->cb_check_found_model (etrail); + stats.ext_prop.ext_cb++; + if (is_consistent) { + LOG ("Found solution is approved by external propagator."); + return true; + } + + bool has_external_clause = ask_external_clause (); + + stats.ext_prop.ext_cb++; + stats.ext_prop.elearn_call++; + + if (has_external_clause) + LOG ( + "Found solution triggered new clauses from external propagator."); + + while (has_external_clause) { + int level_before = level; + size_t assigned = num_assigned; + add_external_clause (0); + bool trail_changed = + (num_assigned != assigned || level != level_before || + propagated < trail.size ()); + added_new_clauses = true; + // + // There are many possible scenarios here: + // - Learned conflicting clause: return to CDCL loop (conflict true) + // - Learned conflicting unit clause that after backtrack+BCP leads to + // a new complete solution: force the outer loop to check the new + // model (trail_changed is true, but (conflict & unsat) is false) + // - Learned empty clause: return to CDCL loop (unsat true) + // - Learned a non-conflicting unit clause: + // Though it does not invalidate the current solution, the solver + // will backtrack to the root level and will repropagate it. The + // search will start again (saved phases hopefully make it quick), + // but it is needed in order to guarantee that every fixed variable + // is properly handled+notified (important for incremental use + // cases). + // - Otherwise: the solution is considered approved and the CDCL-loop + // can return with res = 10. + // + if (unsat || conflict || trail_changed) + break; + has_external_clause = ask_external_clause (); + stats.ext_prop.ext_cb++; + stats.ext_prop.elearn_call++; + } + LOG ("No more external clause to add."); + if (unsat || conflict) + break; + } + + if (!unsat && conflict) { + const int conflict_level = var (conflict->literals[0]).level; + if (conflict_level != level) { + backtrack (conflict_level); + } + } + + return !conflict; +} + +/*----------------------------------------------------------------------------*/ +// +// Notify the external propagator that an observed variable got assigned. +// +void Internal::notify_assignments () { + if (!external_prop || external_prop_is_lazy || private_steps) + return; + + const size_t end_of_trail = trail.size (); + + if (notified >= end_of_trail) + return; + + LOG ("notify external propagator about new assignments"); + std::vector assigned; + + while (notified < end_of_trail) { + int ilit = trail[notified++]; + if (!observed (ilit)) + continue; + + int elit = externalize (ilit); // TODO: double-check tainting + CADICAL_assert (elit); + if (external->ervars[abs (elit)]) + continue; + // Fixed variables might get mapped (during compact) to another + // non-observed but fixed variable. + // This happens on root level, so notification about their assignment is + // already done. + CADICAL_assert (external->observed (elit) || + (fixed (ilit) && !external->ervars[abs (elit)])); + assigned.push_back (elit); + } + + external->propagator->notify_assignment (assigned); + return; +} + +/*----------------------------------------------------------------------------*/ + +void Internal::connect_propagator () { + if (level) + backtrack (); +} + +/*----------------------------------------------------------------------------*/ +// +// Notify the external propagator that a new decision level is started. +// +void Internal::notify_decision () { + if (!external_prop || external_prop_is_lazy || private_steps) + return; + external->propagator->notify_new_decision_level (); +} + +/*----------------------------------------------------------------------------*/ +// +// Notify the external propagator that backtrack to new_level. +// +void Internal::notify_backtrack (size_t new_level) { + if (!external_prop || external_prop_is_lazy || private_steps) + return; + external->propagator->notify_backtrack (new_level); +} + +/*----------------------------------------------------------------------------*/ +// +// Ask the external propagator if there is a suggested literal as next +// decision. +// +int Internal::ask_decision () { + if (!external_prop || external_prop_is_lazy || private_steps) + return 0; + + CADICAL_assert (!unsat); + CADICAL_assert (!conflict); + notify_assignments (); + int level_before = level; + forced_backt_allowed = true; + int elit = external->propagator->cb_decide (); + forced_backt_allowed = false; + stats.ext_prop.ext_cb++; + + if (level_before != level) { + + propagate (); + CADICAL_assert (!unsat); + CADICAL_assert (!conflict); + notify_assignments (); + + // In case the external propagator forced to backtrack below the + // pseduo decision levels, we must go back to the CDCL loop instead of + // making a decision. + if ((size_t) level < assumptions.size () || + ((size_t) level == assumptions.size () && constraint.size ())) { + return 0; + } + } + + if (!elit) + return 0; + LOG ("external propagator proposes decision: %d", elit); + CADICAL_assert (external->is_observed[abs (elit)]); + if (!external->is_observed[abs (elit)]) + return 0; + + int ilit = external->e2i[abs (elit)]; + if (elit < 0) + ilit = -ilit; + + CADICAL_assert (fixed (ilit) || observed (ilit)); + + LOG ("Asking external propagator for decision returned: %d (internal: " + "%d, fixed: %d, val: %d)", + elit, ilit, fixed (ilit), val (ilit)); + + if (fixed (ilit) || val (ilit)) { + LOG ("Proposed decision variable is already assigned, falling back to " + "internal decision."); + return 0; + } + + return ilit; +} + +/*----------------------------------------------------------------------------*/ +// +// Check if the clause is a forgettable clause coming from the external +// propagator. +// +bool Internal::is_external_forgettable (int64_t id) { + CADICAL_assert (opts.check); + return (external->forgettable_original.find (id) != + external->forgettable_original.end ()); +} + +/*----------------------------------------------------------------------------*/ +// +// When an external forgettable clause is deleted, it is marked in the +// 'forgettable_original' hash, so that the internal model checking can +// ignore it. +// +void Internal::mark_garbage_external_forgettable (int64_t id) { + CADICAL_assert (opts.check); + CADICAL_assert (is_external_forgettable (id)); + + LOG (external->forgettable_original[id], + "forgettable external lemma is deleted:"); + // Mark as removed by flipping the first flag to false. + external->forgettable_original[id][0] = 0; +} + +/*----------------------------------------------------------------------------*/ +// +// Check that the literals in the clause are properly ordered. Used only +// internally for debug purposes. +// +void Internal::check_watched_literal_invariants () { +#ifndef CADICAL_NDEBUG + int v0 = 0; + int v1 = 0; + + if (val (clause[0]) > 0) + v0 = 1; + else if (val (clause[0]) < 0) + v0 = -1; + + if (val (clause[1]) > 0) + v1 = 1; + else if (val (clause[1]) < 0) + v1 = -1; + CADICAL_assert (v0 >= v1); +#endif + if (val (clause[0]) > 0) { + if (val (clause[1]) > 0) { // Case 1: Both literals are satisfied + // They are ordered by lower to higher decision level + CADICAL_assert (var (clause[0]).level <= var (clause[1]).level); + + // Every other literal of the clause is either + // - satisfied at higher level + // - unassigned + // - falsified + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) <= 0 || + (var (clause[1]).level <= var (clause[i]).level)); + + } else if (val (clause[1]) == + 0) { // Case 2: First satisfied, next unassigned + + // Every other literal of the clause is either + // - unassigned + // - falsified + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) <= 0); + + } else { // Case 3: First satisfied, next falsified -> could have been a + // reason of a previous propagation + // Every other literal of the clause is falsified but at a lower + // decision level + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) < 0 && + (var (clause[1]).level >= var (clause[i]).level)); + } + } else if (val (clause[0]) == 0) { + if (val (clause[1]) == 0) { // Case 4: Both literals are unassigned + + // Every other literal of the clause is either + // - unassigned + // - falsified + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) <= 0); + + } else { // Case 5: First unassigned, next falsified -> PROPAGATE + // Every other literal of the clause is falsified but at a lower + // decision level + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) < 0 && + (var (clause[1]).level >= var (clause[i]).level)); + } + } else { + CADICAL_assert (val (clause[0]) < 0 && + val (clause[1]) < 0); // Case 6: Both literals are falsified + + // They are ordered by higher to lower decision level + CADICAL_assert (var (clause[0]).level >= var (clause[1]).level); + + // Every other literal of the clause is falsified, but at a lower level + for (size_t i = 2; i < clause.size (); i++) + CADICAL_assert (val (clause[i]) < 0 && + (var (clause[1]).level >= var (clause[i]).level)); + } +} + +#ifndef CADICAL_NDEBUG + +/*----------------------------------------------------------------------------*/ +// +// An expensive function that can be used for deep-debug trail-related +// issues in mobical. Do not use it unless it is really unavoidable. +// +// eq_class contains all the merged external literals that are currently +// compacted to the internal literal of trail[0] and return true. +// +// In case trail[0] does not exists or is not on the root level, the +// function returns false (indicating that there was no merger literal +// found). +// +bool Internal::get_merged_literals (std::vector &eq_class) { + eq_class.clear (); + + if (!trail.size ()) + return false; + + int ilit = trail[0]; + size_t lit_level = var (ilit).level; + + if (!lit_level) { + // Collect all the variables that are merged and mapped to that ilit + size_t e2i_size = external->e2i.size (); + int ivar = abs (ilit); + for (size_t i = 0; i < e2i_size; i++) { + int other = abs (external->e2i[i]); + if (other == ivar) { + if (external->e2i[i] == ilit) + eq_class.push_back (i); + else + eq_class.push_back (-1 * i); + } + } + + return true; + } + + return false; +} + +/*----------------------------------------------------------------------------*/ +// +// Collect all external variables that are FIXED internally. Again an +// expensive function that should be called only for debugging in mobical. +// +// Do not use it unless it is really unavoidable. +// +void Internal::get_all_fixed_literals (std::vector &fixed_lits) { + fixed_lits.clear (); + if (!trail.size ()) + return; + + int e2i_size = external->e2i.size (); + int ilit; + for (int eidx = 1; eidx < e2i_size; eidx++) { + ilit = external->e2i[eidx]; + if (ilit && !external->ervars[eidx]) { + Flags &f = flags (ilit); + if (f.status == Flags::FIXED) { + fixed_lits.push_back (vals[abs (ilit)] * eidx); + } + } + } +} +#endif + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_factor.cpp b/src/sat/cadical/cadical_factor.cpp new file mode 100644 index 000000000..3fee40685 --- /dev/null +++ b/src/sat/cadical/cadical_factor.cpp @@ -0,0 +1,927 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +#define FACTORS 1 +#define QUOTIENT 2 +#define NOUNTED 4 + +inline bool factor_occs_size::operator() (unsigned a, unsigned b) { + size_t s = internal->occs (internal->u2i (a)).size (); + size_t t = internal->occs (internal->u2i (b)).size (); + if (s > t) + return true; + if (s < t) + return false; + return a > b; +} + +// do full occurence list as in elim.cpp but filter out useless clauses +void Internal::factor_mode () { + reset_watches (); + + CADICAL_assert (!watching ()); + init_occs (); + + const int size_limit = opts.factorsize; + + vector bincount, largecount; + const unsigned max_lit = 2 * (max_var + 1); + enlarge_zero (bincount, max_lit); + if (size_limit > 2) + enlarge_zero (largecount, max_lit); + + vector candidates; + int64_t &ticks = stats.ticks.factor; + ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *)); + + // push binary clauses on the occurrence stack. + for (const auto &c : clauses) { + ticks++; + if (c->garbage) + continue; + if (c->redundant && c->size > 2) + continue; + if (c->size > size_limit) + continue; + if (c->size == 2) { + const int lit = c->literals[0]; + const int other = c->literals[1]; + bincount[vlit (lit)]++; + bincount[vlit (other)]++; + occs (lit).push_back (c); + occs (other).push_back (c); + continue; + } + candidates.push_back (c); + for (const auto &lit : *c) { + largecount[vlit (lit)]++; + } + } + if (size_limit == 2) + return; + + // iterate counts of larger clauses rounds often + const unsigned rounds = opts.factorcandrounds; + unsigned candidates_before = 0; + for (unsigned round = 1; round <= rounds; round++) { + LOG ("factor round %d", round); + if (candidates.size () == candidates_before) + break; + ticks += 1 + cache_lines (candidates.size (), sizeof (Clause *)); + candidates_before = candidates.size (); + vector newlargecount; + enlarge_zero (newlargecount, max_lit); + const auto begin = candidates.begin (); + auto p = candidates.begin (); + auto q = p; + const auto end = candidates.end (); + while (p != end) { + Clause *c = *q++ = *p++; + ticks++; + for (const auto &lit : *c) { + const auto idx = vlit (lit); + if (bincount[idx] + largecount[idx] < 2) { + q--; + goto CONTINUE_WITH_NEXT_CLAUSE; + } + } + for (const auto &lit : *c) { + const auto idx = vlit (lit); + newlargecount[idx]++; + } + CONTINUE_WITH_NEXT_CLAUSE: + continue; + } + candidates.resize (q - begin); + largecount.swap (newlargecount); + } + + // finally push remaining clause on the occurrence stack + for (const auto &c : candidates) + for (const auto &lit : *c) + occs (lit).push_back (c); +} + +// go back to two watch scheme +void Internal::reset_factor_mode () { + reset_occs (); + init_watches (); + connect_watches (); +} + +Factoring::Factoring (Internal *i, int64_t l) + : internal (i), limit (l), schedule (i) { + const unsigned max_var = internal->max_var; + const unsigned max_lit = 2 * (max_var + 1); + initial = max_var; + bound = internal->lim.elimbound; + enlarge_zero (count, max_lit); + quotients.first = quotients.last = 0; +} + +Factoring::~Factoring () { + CADICAL_assert (counted.empty ()); + CADICAL_assert (nounted.empty ()); + CADICAL_assert (flauses.empty ()); + internal->release_quotients (*this); + schedule.erase (); // actually not necessary +} + +double Internal::tied_next_factor_score (int lit) { + double res = occs (lit).size (); + LOG ("watches score %g of %d", res, lit); + return res; +} + +// the marks in cadical have 6 bits for marking but work on idx +// to mark everything (FACTORS, QUOTIENT, NOUNTED) we shift the bits +// depending on the sign of factor (+ bitmask) +// i.e. if factor is positive, we apply a bitmask to only get +// the first three bits (& 7u) +// otherwise we leftshift by 3 (>> 3) to get the bits 4,5,6 +// use markfact, unmarkfact, getfact for this purpose. +// +Quotient *Internal::new_quotient (Factoring &factoring, int factor) { + CADICAL_assert (!getfact (factor, FACTORS)); + markfact (factor, FACTORS); + Quotient *res = new Quotient (factor); + res->next = 0; + res->matched = 0; + Quotient *last = factoring.quotients.last; + res->bid = 0; + if (last) { + CADICAL_assert (factoring.quotients.first); + CADICAL_assert (!last->next); + last->next = res; + res->id = last->id + 1; + } else { + CADICAL_assert (!factoring.quotients.first); + factoring.quotients.first = res; + res->id = 0; + } + factoring.quotients.last = res; + res->prev = last; + LOG ("new quotient[%zu] with factor %d", res->id, factor); + return res; +} + +void Internal::release_quotients (Factoring &factoring) { + for (Quotient *q = factoring.quotients.first, *next; q; q = next) { + next = q->next; + int factor = q->factor; + CADICAL_assert (getfact (factor, FACTORS)); + unmarkfact (factor, FACTORS); + delete q; + } + factoring.quotients.first = factoring.quotients.last = 0; +} + +size_t Internal::first_factor (Factoring &factoring, int factor) { + CADICAL_assert (!factoring.quotients.first); + Quotient *quotient = new_quotient (factoring, factor); + vector &qlauses = quotient->qlauses; + int64_t ticks = 0; + for (const auto &c : occs (factor)) { + qlauses.push_back (c); + ticks++; + } + size_t res = qlauses.size (); + LOG ("quotient[0] factor %d size %zu", factor, res); + // This invariant can of course be broken by previous factorings + // CADICAL_assert (res > 1); + stats.ticks.factor += ticks; + return res; +} + +void Internal::clear_nounted (vector &nounted) { + for (const auto &lit : nounted) { + CADICAL_assert (getfact (lit, NOUNTED)); + unmarkfact (lit, NOUNTED); + } + nounted.clear (); +} + +void Internal::clear_flauses (vector &flauses) { + for (auto c : flauses) { + CADICAL_assert (c->swept); + c->swept = false; + } + flauses.clear (); +} + +Quotient *Internal::best_quotient (Factoring &factoring, + size_t *best_reduction_ptr) { + size_t factors = 1, best_reduction = 0; + Quotient *best = 0; + for (Quotient *q = factoring.quotients.first; q; q = q->next) { + size_t quotients = q->qlauses.size (); + size_t before_factorization = quotients * factors; + size_t after_factorization = quotients + factors; + if (before_factorization == after_factorization) + LOG ("quotient[%zu] factors %zu clauses into %zu thus no change", + factors - 1, before_factorization, after_factorization); + else if (before_factorization < after_factorization) + LOG ("quotient[%zu] factors %zu clauses into %zu thus %zu more", + factors - 1, before_factorization, after_factorization, + after_factorization - before_factorization); + else { + size_t delta = before_factorization - after_factorization; + LOG ("quotient[%zu] factors %zu clauses into %zu thus %zu less", + factors - 1, before_factorization, after_factorization, delta); + if (!best || best_reduction < delta) { + best_reduction = delta; + best = q; + } + } + factors++; + } + if (!best) { + LOG ("no decreasing quotient found"); + return 0; + } + LOG ("best decreasing quotient[%zu] with reduction %zu", best->id, + best_reduction); + *best_reduction_ptr = best_reduction; + return best; +} + +int Internal::next_factor (Factoring &factoring, unsigned *next_count_ptr) { + Quotient *last_quotient = factoring.quotients.last; + CADICAL_assert (last_quotient); + vector &last_clauses = last_quotient->qlauses; + vector &count = factoring.count; + vector &counted = factoring.counted; + vector &flauses = factoring.flauses; + CADICAL_assert (counted.empty ()); + CADICAL_assert (flauses.empty ()); + const int initial = factoring.initial; + int64_t ticks = 1 + cache_lines (last_clauses.size (), sizeof (Clause *)); + for (auto c : last_clauses) { + CADICAL_assert (!c->swept); + int min_lit = 0; + unsigned factors = 0; + size_t min_size = 0; + ticks++; + for (const auto &other : *c) { + if (getfact (other, FACTORS)) { + if (factors++) + break; + } else { + CADICAL_assert (!getfact (other, QUOTIENT)); + markfact (other, QUOTIENT); + const size_t other_size = occs (other).size (); + if (!min_lit || other_size < min_size) { + min_lit = other; + min_size = other_size; + } + } + } + CADICAL_assert (factors); + if (factors == 1) { + CADICAL_assert (min_lit); + const int c_size = c->size; + vector &nounted = factoring.nounted; + CADICAL_assert (nounted.empty ()); + ticks += 1 + cache_lines (occs (min_lit).size (), sizeof (Clause *)); + for (auto d : occs (min_lit)) { + if (c == d) + continue; + ticks++; + if (d->swept) + continue; + if (d->size != c_size) + continue; + int next = 0; + for (const auto &other : *d) { + if (getfact (other, QUOTIENT)) + continue; + if (getfact (other, FACTORS)) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + if (getfact (other, NOUNTED)) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + if (next) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + next = other; + } + CADICAL_assert (next); + if (abs (next) > abs (initial)) + continue; + if (!active (next)) + continue; + CADICAL_assert (!getfact (next, FACTORS)); + CADICAL_assert (!getfact (next, NOUNTED)); + markfact (next, NOUNTED); + nounted.push_back (next); + d->swept = true; + flauses.push_back (d); + if (!count[vlit (next)]) + counted.push_back (next); + count[vlit (next)]++; + CONTINUE_WITH_NEXT_MIN_WATCH:; + } + clear_nounted (nounted); + } + for (const auto &other : *c) + if (getfact (other, QUOTIENT)) + unmarkfact (other, QUOTIENT); + stats.ticks.factor += ticks; + ticks = 0; + if (stats.ticks.factor > factoring.limit) + break; + } + clear_flauses (flauses); + unsigned next_count = 0; + int next = 0; + if (stats.ticks.factor <= factoring.limit) { + unsigned ties = 0; + for (const auto &lit : counted) { + const unsigned lit_count = count[vlit (lit)]; + if (lit_count < next_count) + continue; + if (lit_count == next_count) { + CADICAL_assert (lit_count); + ties++; + } else { + CADICAL_assert (lit_count > next_count); + next_count = lit_count; + next = lit; + ties = 1; + } + } + if (next_count < 2) { + LOG ("next factor count %u smaller than 2", next_count); + next = 0; + } else if (ties > 1) { + LOG ("found %u tied next factor candidate literals with count %u", + ties, next_count); + double next_score = -1; + for (const auto &lit : counted) { + const unsigned lit_count = count[vlit (lit)]; + if (lit_count != next_count) + continue; + double lit_score = tied_next_factor_score (lit); + CADICAL_assert (lit_score >= 0); + LOG ("score %g of next factor candidate %d", lit_score, lit); + if (lit_score <= next_score) + continue; + next_score = lit_score; + next = lit; + } + CADICAL_assert (next_score >= 0); + CADICAL_assert (next); + LOG ("best score %g of next factor %d", next_score, next); + } else { + CADICAL_assert (ties == 1); + LOG ("single next factor %d with count %u", next, next_count); + } + } + for (const auto &lit : counted) + count[vlit (lit)] = 0; + counted.clear (); + CADICAL_assert (!next || next_count > 1); + *next_count_ptr = next_count; + return next; +} + +void Internal::factorize_next (Factoring &factoring, int next, + unsigned expected_next_count) { + Quotient *last_quotient = factoring.quotients.last; + Quotient *next_quotient = new_quotient (factoring, next); + + CADICAL_assert (last_quotient); + vector &last_clauses = last_quotient->qlauses; + vector &next_clauses = next_quotient->qlauses; + vector &matches = next_quotient->matches; + vector &flauses = factoring.flauses; + CADICAL_assert (flauses.empty ()); + + int64_t ticks = 1 + cache_lines (last_clauses.size (), sizeof (Clause *)); + + size_t i = 0; + + for (auto c : last_clauses) { + CADICAL_assert (!c->swept); + int min_lit = 0; + unsigned factors = 0; + size_t min_size = 0; + ticks++; + for (const auto &other : *c) { + if (getfact (other, FACTORS)) { + if (factors++) + break; + } else { + CADICAL_assert (!getfact (other, QUOTIENT)); + markfact (other, QUOTIENT); + const size_t other_size = occs (other).size (); + if (!min_lit || other_size < min_size) { + min_lit = other; + min_size = other_size; + } + } + } + CADICAL_assert (factors); + if (factors == 1) { + CADICAL_assert (min_lit); + const int c_size = c->size; + ticks += 1 + cache_lines (occs (min_lit).size (), sizeof (Clause *)); + for (auto d : occs (min_lit)) { + if (c == d) + continue; + ticks++; + if (d->swept) + continue; + if (d->size != c_size) + continue; + for (const auto &other : *d) { + if (getfact (other, QUOTIENT)) + continue; + if (other != next) + goto CONTINUE_WITH_NEXT_MIN_WATCH; + } + LOG (c, "matched"); + LOG (d, "keeping"); + + next_clauses.push_back (d); + matches.push_back (i); + flauses.push_back (d); + d->swept = true; + break; + + CONTINUE_WITH_NEXT_MIN_WATCH:; + } + } + for (const auto &other : *c) + if (getfact (other, QUOTIENT)) + unmarkfact (other, QUOTIENT); + i++; + } + clear_flauses (flauses); + stats.ticks.factor += ticks; + + CADICAL_assert (expected_next_count <= next_clauses.size ()); + (void) expected_next_count; +} + +// We only need to enlarge factoring.count as everything else is +// initialized in internal +void Internal::resize_factoring (Factoring &factoring, int lit) { + CADICAL_assert (lit > 0); + size_t new_var_size = lit + 1; + size_t new_lit_size = 2 * new_var_size; + enlarge_zero (factoring.count, new_lit_size); +} + +void Internal::flush_unmatched_clauses (Quotient *q) { + Quotient *prev = q->prev; + vector &q_matches = q->matches, &prev_matches = prev->matches; + vector &q_clauses = q->qlauses, &prev_clauses = prev->qlauses; + const size_t n = q_clauses.size (); + CADICAL_assert (n == q_matches.size ()); + bool prev_is_first = !prev->id; + size_t i = 0; + while (i < q_matches.size ()) { + size_t j = q_matches[i]; + q_matches[i] = i; + CADICAL_assert (i <= j); + if (!prev_is_first) { + size_t matches = prev_matches[j]; + prev_matches[i] = matches; + } + Clause *c = prev_clauses[j]; + prev_clauses[i] = c; + i++; + } + LOG ("flushing %zu clauses of quotient[%zu]", prev_clauses.size () - n, + prev->id); + if (!prev_is_first) + prev_matches.resize (n); + prev_clauses.resize (n); +} + +// special case when we have two quotients with negated factors. +// in this case, factoring does not make sense, and instead we +// can resolve the clauses of the two quotients. +// this subsumes all clauses in all quotients. +void Internal::add_self_subsuming_factor (Quotient *q, Quotient *p) { + const int factor = q->factor; + const int not_factor = p->factor; + CADICAL_assert (-factor == not_factor); + LOG ( + "adding self subsuming factor because blocked clause is a tautology"); + for (auto c : q->qlauses) { + for (const auto &lit : *c) { + if (lit == factor) + continue; + clause.push_back (lit); + } + if (lrat) { + for (auto d : p->qlauses) { + bool match = true; + for (const auto &lit : *d) { + if (lit == not_factor) + continue; + if (std::find (clause.begin (), clause.end (), lit) == + clause.end ()) { + match = false; + break; + } + } + if (match) { + lrat_chain.push_back (d->id); + break; + } + } + lrat_chain.push_back (c->id); + CADICAL_assert (lrat_chain.size () == 2); + } + if (clause.size () > 1) { + new_factor_clause (); + } else { + const int unit = clause[0]; + const signed char tmp = val (unit); + if (!tmp) + assign_unit (unit); + else if (tmp < 0) { + if (lrat) { + int64_t id = unit_id (-unit); + lrat_chain.push_back (id); + std::reverse (lrat_chain.begin (), lrat_chain.end ()); + } + learn_empty_clause (); + clause.clear (); + lrat_chain.clear (); + break; + } + } + clause.clear (); + lrat_chain.clear (); + } +} + +bool Internal::self_subsuming_factor (Quotient *q) { + Quotient *x = 0, *y = 0; + bool found = false; + for (Quotient *p = q; p; p = p->prev) { + const int factor = p->factor; + Flags &f = flags (factor); + if (f.seen) { + CADICAL_assert (std::find (analyzed.begin (), analyzed.end (), -factor) != + analyzed.end ()); + found = true; + x = p; + for (Quotient *r = q; r; r = r->prev) { + if (r->factor != -factor) + continue; + y = r; + break; + } + break; + } + analyzed.push_back (factor); + f.seen = true; + } + CADICAL_assert (!found || (x && y)); + clear_analyzed_literals (); + if (found) { + add_self_subsuming_factor (x, y); + return true; + } + return false; +} + +// this is a pure binary clauses containing fresh and one other literal +// it is added for all applicable quotients. +void Internal::add_factored_divider (Quotient *q, int fresh) { + const int factor = q->factor; + LOG ("factored %d divider %d", factor, fresh); + clause.push_back (factor); + clause.push_back (fresh); + new_factor_clause (); + clause.clear (); + if (lrat) + mini_chain.push_back (-clause_id); +} + +// this clause is blocked on fresh, i.e., it contains all literals from +// the binaries above, but negated. This is only added to the proof, to +// make checking easier. +void Internal::blocked_clause (Quotient *q, int not_fresh) { + if (!proof) + return; + int64_t new_id = ++clause_id; + q->bid = new_id; + CADICAL_assert (clause.empty ()); + for (Quotient *p = q; p; p = p->prev) + clause.push_back (-p->factor); + clause.push_back (not_fresh); + CADICAL_assert (!lrat || mini_chain.size ()); + proof->add_derived_clause (new_id, true, clause, mini_chain); + mini_chain.clear (); + clause.clear (); +} + +// this is the other side of the factored clauses. To derive these, +// one can resolved the blocked clause on all matching clauses of +// one type +void Internal::add_factored_quotient (Quotient *q, int not_fresh) { + LOG ("adding factored quotient[%zu] clauses", q->id); + const int factor = q->factor; + CADICAL_assert (lrat_chain.empty ()); + auto qlauses = q->qlauses; + for (unsigned idx = 0; idx < qlauses.size (); idx++) { + const auto c = qlauses[idx]; + CADICAL_assert (clause.empty ()); + for (const auto &other : *c) { + if (other == factor) { + continue; + } + clause.push_back (other); + } + if (lrat) { + CADICAL_assert (proof); + CADICAL_assert (q->bid); + unsigned idxtoo = idx; + for (Quotient *p = q; p; p = p->prev) { + lrat_chain.push_back (p->qlauses[idxtoo]->id); + if (p->prev) + idxtoo = p->matches[idx]; + } + lrat_chain.push_back (q->bid); + } + clause.push_back (not_fresh); + new_factor_clause (); + clause.clear (); + lrat_chain.clear (); + } + if (proof) { + for (Quotient *p = q; p; p = p->prev) { + clause.push_back (-p->factor); + } + clause.push_back (not_fresh); + proof->delete_clause (q->bid, true, clause); + clause.clear (); + } +} + +// remove deleted clauses once factored. +void Internal::eagerly_remove_from_occurences (Clause *c) { + for (const auto &lit : *c) { + auto &occ = occs (lit); + auto p = occ.begin (); + auto q = occ.begin (); + auto begin = occ.begin (); + auto end = occ.end (); + while (p != end) { + *q = *p++; + if (*q != c) + q++; + } + CADICAL_assert (q + 1 == p); + occ.resize (q - begin); + } +} + +// delete the factored clauses +void Internal::delete_unfactored (Quotient *q) { + LOG ("deleting unfactored quotient[%zu] clauses", q->id); + for (auto c : q->qlauses) { + eagerly_remove_from_occurences (c); + mark_garbage (c); + stats.literals_unfactored += c->size; + stats.clauses_unfactored++; + } +} + +// update the priority queue for scheduling +void Internal::update_factored (Factoring &factoring, Quotient *q) { + const int factor = q->factor; + update_factor_candidate (factoring, factor); + update_factor_candidate (factoring, -factor); + for (auto c : q->qlauses) { + LOG (c, "deleting unfactored"); + for (const auto &lit : *c) + if (lit != factor) + update_factor_candidate (factoring, lit); + } +} + +bool Internal::apply_factoring (Factoring &factoring, Quotient *q) { + for (Quotient *p = q; p->prev; p = p->prev) + flush_unmatched_clauses (p); + if (self_subsuming_factor (q)) { + for (Quotient *p = q; p; p = p->prev) + delete_unfactored (p); + for (Quotient *p = q; p; p = p->prev) + update_factored (factoring, p); + return true; + } + const int fresh = get_new_extension_variable (); + if (!fresh) + return false; + stats.factored++; + factoring.fresh.push_back (fresh); + for (Quotient *p = q; p; p = p->prev) + add_factored_divider (p, fresh); + const int not_fresh = -fresh; + blocked_clause (q, not_fresh); + add_factored_quotient (q, not_fresh); + for (Quotient *p = q; p; p = p->prev) + delete_unfactored (p); + for (Quotient *p = q; p; p = p->prev) + update_factored (factoring, p); + CADICAL_assert (fresh > 0); + resize_factoring (factoring, fresh); + return true; +} + +void Internal::update_factor_candidate (Factoring &factoring, int lit) { + FactorSchedule &schedule = factoring.schedule; + const size_t size = occs (lit).size (); + const unsigned idx = vlit (lit); + if (schedule.contains (idx)) + schedule.update (idx); + else if (size > 1) { + schedule.push_back (idx); + } +} + +void Internal::schedule_factorization (Factoring &factoring) { + for (const auto &idx : vars) { + if (active (idx)) { + Flags &f = flags (idx); + const int lit = idx; + const int not_lit = -lit; + if (f.factor & 1) + update_factor_candidate (factoring, lit); + if (f.factor & 2) + update_factor_candidate (factoring, not_lit); + } + } +#ifndef CADICAL_QUIET + size_t size_cands = factoring.schedule.size (); + VERBOSE (2, "scheduled %zu factorization candidate literals %.0f %%", + size_cands, percent (size_cands, max_var)); +#endif +} + +bool Internal::run_factorization (int64_t limit) { + Factoring factoring = Factoring (this, limit); + schedule_factorization (factoring); + bool done = false; +#ifndef CADICAL_QUIET + unsigned factored = 0; +#endif + int64_t *ticks = &stats.ticks.factor; + VERBOSE (3, "factorization limit of %" PRIu64 " ticks", limit - *ticks); + + while (!unsat && !done && !factoring.schedule.empty ()) { + const unsigned ufirst = factoring.schedule.pop_front (); + LOG ("next factor candidate %d", ufirst); + const int first = u2i (ufirst); + const int first_idx = vidx (first); + if (!active (first_idx)) + continue; + if (!occs (first).size ()) { + factoring.schedule.clear (); + break; + } + if (*ticks > limit) { + VERBOSE (2, "factorization ticks limit hit"); + break; + } + if (terminated_asynchronously ()) + break; + Flags &f = flags (first_idx); + const unsigned bit = 1u << (first < 0); + if (!(f.factor & bit)) + continue; + f.factor &= ~bit; + const size_t first_count = first_factor (factoring, first); + if (first_count > 1) { + for (;;) { + unsigned next_count; + const int next = next_factor (factoring, &next_count); + if (next == 0) + break; + CADICAL_assert (next_count > 1); + if (next_count < 2) + break; + factorize_next (factoring, next, next_count); + } + size_t reduction; + Quotient *q = best_quotient (factoring, &reduction); + if (q && (int) reduction > factoring.bound) { + if (apply_factoring (factoring, q)) { +#ifndef CADICAL_QUIET + factored++; +#endif + } else + done = true; + } + } + release_quotients (factoring); + } + + // since we cannot remove elements from the heap we check wether the + // first element in the heap has occurences + bool completed = factoring.schedule.empty (); + if (!completed) { + const unsigned idx = factoring.schedule.front (); + completed = occs (u2i (idx)).empty (); + } + // kissat initializes scores for new variables at this point, however + // this is actually done already during resize of internal +#ifndef CADICAL_QUIET + report ('f', !factored); +#endif + return completed; +} + +int Internal::get_new_extension_variable () { + const int current_max_external = external->max_var; + const int new_external = current_max_external + 1; + const int new_internal = external->internalize (new_external, true); + // one sideeffect of internalize is enlarging the internal datastructures + // which can initialize the watches (wtab) + if (watching ()) + reset_watches (); + // it does not enlarge otab, however, so we do this manually + init_occs (); + CADICAL_assert (vlit (new_internal)); + return new_internal; +} + +bool Internal::factor () { + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + if (!opts.factor) + return false; + // The following CADICAL_assertion fails if there are *only* user propagator + // clauses (which are redundant). + // CADICAL_assert (stats.mark.factor || clauses.empty ()); + if (last.factor.marked >= stats.mark.factor) { + VERBOSE (3, + "factorization skipped as no literals have been" + "marked to be added (%" PRIu64 " < %" PRIu64 ")", + last.factor.marked, stats.mark.factor); + return false; + } + CADICAL_assert (!level); + + SET_EFFORT_LIMIT (limit, factor, stats.factor); + if (!stats.factor) + limit += opts.factoriniticks * 1e6; + + START_SIMPLIFIER (factor, FACTOR); + stats.factor++; + +#ifndef CADICAL_QUIET + struct { + int64_t variables, clauses, ticks; + } before, after, delta; + before.variables = stats.variables_extension + stats.variables_original; + before.ticks = stats.ticks.factor; + before.clauses = stats.current.irredundant; +#endif + + factor_mode (); + bool completed = run_factorization (limit); + reset_factor_mode (); + + propagated = 0; + if (!unsat && !propagate ()) { + learn_empty_clause (); + } + +#ifndef CADICAL_QUIET + after.variables = stats.variables_extension + stats.variables_original; + after.clauses = stats.current.irredundant; + after.ticks = stats.ticks.factor; + delta.variables = after.variables - before.variables; + delta.clauses = before.clauses - after.clauses; + delta.ticks = after.ticks - before.ticks; + VERBOSE (2, "used %f million factorization ticks", delta.ticks * 1e-6); + phase ("factorization", stats.factor, + "introduced %" PRId64 " extension variables %.0f%%", + delta.variables, percent (delta.variables, before.variables)); + phase ("factorization", stats.factor, + "removed %" PRId64 " irredundant clauses %.0f%%", delta.clauses, + percent (delta.clauses, before.clauses)); +#endif + + if (completed) + last.factor.marked = stats.mark.factor; + STOP_SIMPLIFIER (factor, FACTOR); + return true; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_file.cpp b/src/sat/cadical/cadical_file.cpp new file mode 100644 index 000000000..98f19f752 --- /dev/null +++ b/src/sat/cadical/cadical_file.cpp @@ -0,0 +1,527 @@ +#include "global.h" + +#include "internal.hpp" + +/*------------------------------------------------------------------------*/ + +// Some more low-level 'C' headers. + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#ifdef WIN32 + +#include +#include +#include + +#define access _access +#define popen _popen +#define pclose _pclose +#define R_OK 4 +#define W_OK 2 +#define S_IFIFO _S_IFIFO +#define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) + +#else + +extern "C" { +#include +#include +} + +#endif + +#if defined(__APPLE__) || defined(__MACH__) + +extern "C" { +#include +#include +} + +#include + +#endif + +ABC_NAMESPACE_IMPL_START + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Private constructor. + +File::File (Internal *i, bool w, int c, int p, FILE *f, const char *n) + : internal (i), +#if !defined(CADICAL_QUIET) || !defined(CADICAL_NDEBUG) + writing (w), +#endif + close_file (c), child_pid (p), file (f), _name (strdup (n)), + _lineno (1), _bytes (0) { + (void) w; + CADICAL_assert (f), CADICAL_assert (n); +} + +/*------------------------------------------------------------------------*/ + +bool File::exists (const char *path) { + struct stat buf; + if (stat (path, &buf)) + return false; + if (access (path, R_OK)) + return false; + return true; +} + +bool File::writable (const char *path) { + int res; + if (!path) + res = 1; + else if (!strcmp (path, "/dev/null")) + res = 0; + else { + if (!*path) + res = 2; + else { + struct stat buf; + const char *p = strrchr (path, '/'); + if (!p) { + if (stat (path, &buf)) + res = ((errno == ENOENT) ? 0 : -2); + else if (S_ISDIR (buf.st_mode)) + res = 3; + else + res = (access (path, W_OK) ? 4 : 0); + } else if (!p[1]) + res = 5; + else { + size_t len = p - path; + char *dirname = new char[len + 1]; + strncpy (dirname, path, len); + dirname[len] = 0; + if (stat (dirname, &buf)) + res = 6; + else if (!S_ISDIR (buf.st_mode)) + res = 7; + else if (access (dirname, W_OK)) + res = 8; + else if (stat (path, &buf)) + res = (errno == ENOENT) ? 0 : -3; + else + res = access (path, W_OK) ? 9 : 0; + delete[] dirname; + } + } + } + return !res; +} + +bool File::piping () { + struct stat stat; + int fd = fileno (file); + if (fstat (fd, &stat)) + return true; + return S_ISFIFO (stat.st_mode); +} + +// These are signatures for supported compressed file types. In 2018 the +// SAT Competition was running on StarExec and used internally 'bzip2' +// compressed files, but gave them uncompressed to the solver using exactly +// the same path (with '.bz2' suffix). Then 'CaDiCaL' tried to read that +// actually uncompressed file through 'bzip2', which of course failed. Now +// we double check and fall back to reading the file as is, if the signature +// does not match after issuing a warning. + +static int xzsig[] = {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00, 0x00, EOF}; +static int bz2sig[] = {0x42, 0x5A, 0x68, EOF}; +static int gzsig[] = {0x1F, 0x8B, EOF}; +static int sig7z[] = {0x37, 0x7A, 0xBC, 0xAF, 0x27, 0x1C, EOF}; +static int lzmasig[] = {0x5D, EOF}; + +bool File::match (Internal *internal, const char *path, const int *sig) { + CADICAL_assert (path); + FILE *tmp = fopen (path, "r"); + if (!tmp) { + WARNING ("failed to open '%s' to check signature", path); + return false; + } + bool res = true; + for (const int *p = sig; res && (*p != EOF); p++) + res = (cadical_getc_unlocked (tmp) == *p); + fclose (tmp); + if (!res) + WARNING ("file type signature check for '%s' failed", path); + return res; +} + +size_t File::size (const char *path) { + struct stat buf; + if (stat (path, &buf)) + return 0; + return (size_t) buf.st_size; +} + +// Check that 'prg' is in the 'PATH' and thus can be found if executed +// through 'popen' or 'exec'. + +char *File::find_program (const char *prg) { + size_t prglen = strlen (prg); + const char *c = getenv ("PATH"); + if (!c) + return 0; + size_t len = strlen (c); + char *e = new char[len + 1]; + strcpy (e, c); + char *res = 0; + for (char *p = e, *q; !res && p < e + len; p = q) { + for (q = p; *q && *q != ':'; q++) + ; + *q++ = 0; + size_t pathlen = (q - p) + prglen; + char *path = new char[pathlen + 1]; + snprintf (path, pathlen + 1, "%s/%s", p, prg); + CADICAL_assert (strlen (path) == pathlen); + if (exists (path)) + res = path; + else + delete[] path; + } + delete[] e; + return res; +} + +/*------------------------------------------------------------------------*/ + +FILE *File::open_file (Internal *internal, const char *path, + const char *mode) { + (void) internal; + return fopen (path, mode); +} + +FILE *File::read_file (Internal *internal, const char *path) { + MSG ("opening file to read '%s'", path); + return open_file (internal, path, "r"); +} + +FILE *File::write_file (Internal *internal, const char *path) { + MSG ("opening file to write '%s'", path); + return open_file (internal, path, "wb"); +} + +/*------------------------------------------------------------------------*/ + +void File::split_str (const char *command, std::vector &argv) { + const char *c = command; + while (*c && *c == ' ') + c++; + while (*c) { + const char *p = c; + while (*p && *p != ' ') + p++; + const size_t bytes = p - c; + char *arg = new char[bytes + 1]; + (void) strncpy (arg, c, bytes); + arg[bytes] = 0; + argv.push_back (arg); + while (*p && *p == ' ') + p++; + c = p; + } +} + +void File::delete_str_vector (std::vector &argv) { + for (auto str : argv) + delete[] str; +} + +FILE *File::open_pipe (Internal *internal, const char *fmt, + const char *path, const char *mode) { +#ifdef CADICAL_QUIET + (void) internal; +#endif + size_t prglen = 0; + while (fmt[prglen] && fmt[prglen] != ' ') + prglen++; + char *prg = new char[prglen + 1]; + strncpy (prg, fmt, prglen); + prg[prglen] = 0; + char *found = find_program (prg); + if (found) + MSG ("found '%s' in path for '%s'", found, prg); + if (!found) + MSG ("did not find '%s' in path", prg); + delete[] prg; + if (!found) + return 0; + delete[] found; + size_t cmd_size = strlen (fmt) + strlen (path); + char *cmd = new char[cmd_size]; + snprintf (cmd, cmd_size, fmt, path); + FILE *res = popen (cmd, mode); + delete[] cmd; + return res; +} + +FILE *File::read_pipe (Internal *internal, const char *fmt, const int *sig, + const char *path) { + if (!File::exists (path)) { + LOG ("file '%s' does not exist", path); + return 0; + } + LOG ("file '%s' exists", path); + if (sig && !File::match (internal, path, sig)) + return 0; + LOG ("file '%s' matches signature for '%s'", path, fmt); + MSG ("opening pipe to read '%s'", path); + return open_pipe (internal, fmt, path, "r"); +} + +#ifndef _WIN32 + +#if defined(__APPLE__) || defined(__MACH__) +static std::mutex compressed_file_writing_mutex; +#endif + +FILE *File::write_pipe (Internal *internal, const char *command, + const char *path, int &child_pid) { + CADICAL_assert (command[0] && command[0] != ' '); + MSG ("writing through command '%s' to '%s'", command, path); +#ifdef CADICAL_QUIET + (void) internal; +#endif + std::vector args; + split_str (command, args); + CADICAL_assert (!args.empty ()); + args.push_back (0); + char **argv = args.data (); + char *absolute_command_path = find_program (argv[0]); + int pipe_fds[2], out; + FILE *res = 0; +#if defined(__APPLE__) || defined(__MACH__) + compressed_file_writing_mutex.lock (); +#endif + if (!absolute_command_path) + MSG ("could not find '%s' in 'PATH' environment variable", argv[0]); + else if (::pipe (pipe_fds) < 0) + MSG ("could not generate pipe to '%s' command", command); + else if ((out = ::open (path, O_CREAT | O_TRUNC | O_WRONLY, 0644)) < 0) + MSG ("could not open '%s' for writing", path); + else if ((child_pid = ::fork ()) < 0) { + MSG ("could not fork process to execute '%s' command", command); + ::close (out); + } else if (child_pid) { + ::close (pipe_fds[0]); + res = ::fdopen (pipe_fds[1], "wb"); + } else { + + // Connect stdin and stdout in child + + ::dup2 (pipe_fds[0], 0); + ::dup2 (out, 1); + + // Make sure to close all non-required fds to not cause hangs. + // This is handled now by closefrom and remains for documentation + // purposes: + // + // ::close (pipe_fds[0]); + // ::close (pipe_fds[1]); + // ::close (out); + + // Surpress '7z' verbose output on 'stderr'. + + if (command[0] == '7') { + ::close (2); + } + + // Before the fork another thread could have created more fds. These + // fds are cloned into the child process. As this inhibits pipes to + // be closed by the parent process we have to close all of the + // erroneously cloned fds here. + +#ifndef CADICAL_NCLOSEFROM + ::closefrom (3); +#else + // Simplistic replacement on Unix without 'closefrom'. + for (int fd = 3; fd != FD_SETSIZE; fd++) + ::close (fd); +#endif + execv (absolute_command_path, argv); + _exit (1); + } + if (absolute_command_path) + delete[] absolute_command_path; + delete_str_vector (args); +#ifdef CADICAL_QUIET + (void) internal; +#endif +#if defined(__APPLE__) || defined(__MACH__) + if (!res) + compressed_file_writing_mutex.unlock (); +#endif + return res; +} + +#endif + +/*------------------------------------------------------------------------*/ + +File *File::read (Internal *internal, FILE *f, const char *n) { + return new File (internal, false, 0, 0, f, n); +} + +File *File::write (Internal *internal, FILE *f, const char *n) { + return new File (internal, true, 0, 0, f, n); +} + +File *File::read (Internal *internal, const char *path) { + FILE *file; + int close_input = 2; + if (has_suffix (path, ".xz")) { + file = read_pipe (internal, "xz -c -d %s", xzsig, path); + if (!file) + goto READ_FILE; + } else if (has_suffix (path, ".lzma")) { + file = read_pipe (internal, "lzma -c -d %s", lzmasig, path); + if (!file) + goto READ_FILE; + } else if (has_suffix (path, ".bz2")) { + file = read_pipe (internal, "bzip2 -c -d %s", bz2sig, path); + if (!file) + goto READ_FILE; + } else if (has_suffix (path, ".gz")) { + file = read_pipe (internal, "gzip -c -d %s", gzsig, path); + if (!file) + goto READ_FILE; + } else if (has_suffix (path, ".7z")) { + file = read_pipe (internal, "7z x -so %s 2>/dev/null", sig7z, path); + if (!file) + goto READ_FILE; + } else { + READ_FILE: + file = read_file (internal, path); + close_input = 1; + } + + if (!file) + return 0; + + return new File (internal, false, close_input, 0, file, path); +} + +File *File::write (Internal *internal, const char *path) { + FILE *file; + int close_output = 3, child_pid = 0; +#ifndef _WIN32 + if (has_suffix (path, ".xz")) + file = write_pipe (internal, "xz -c", path, child_pid); + else if (has_suffix (path, ".bz2")) + file = write_pipe (internal, "bzip2 -c", path, child_pid); + else if (has_suffix (path, ".gz")) + file = write_pipe (internal, "gzip -c", path, child_pid); + else if (has_suffix (path, ".7z")) + file = write_pipe (internal, "7z a -an -txz -si -so", path, child_pid); + else +#endif + file = write_file (internal, path), close_output = 1; + + if (!file) + return 0; + + return new File (internal, true, close_output, child_pid, file, path); +} + +void File::close (bool print) { + CADICAL_assert (file); +#ifndef CADICAL_QUIET + if (internal->opts.quiet) + print = false; + else if (internal->opts.verbose > 0) + print = true; +#endif + if (close_file == 0) { + if (print) + MSG ("disconnecting from '%s'", name ()); + } + if (close_file == 1) { + if (print) + MSG ("closing file '%s'", name ()); + fclose (file); + } + if (close_file == 2) { + if (print) + MSG ("closing input pipe to read '%s'", name ()); + pclose (file); + } +#ifndef _WIN32 + if (close_file == 3) { + if (print) + MSG ("closing output pipe to write '%s'", name ()); + fclose (file); + waitpid (child_pid, 0, 0); +#if defined(__APPLE__) || defined(__MACH__) + compressed_file_writing_mutex.unlock (); +#endif + } +#endif + file = 0; // mark as closed + + // TODO what about error checking for 'fclose', 'pclose' or 'waitpid'? + +#ifndef CADICAL_QUIET + if (print) { + if (writing) { + uint64_t written_bytes = bytes (); + double written_mb = written_bytes / (double) (1 << 20); + MSG ("after writing %" PRIu64 " bytes %.1f MB", written_bytes, + written_mb); + if (close_file == 3) { + size_t actual_bytes = size (name ()); + if (actual_bytes) { + double actual_mb = actual_bytes / (double) (1 << 20); + MSG ("deflated to %zd bytes %.1f MB", actual_bytes, actual_mb); + MSG ("factor %.2f (%.2f%% compression)", + relative (written_bytes, actual_bytes), + percent (actual_bytes, written_bytes)); + } else + MSG ("but could not determine actual size of written file"); + } + } else { + uint64_t read_bytes = bytes (); + double read_mb = read_bytes / (double) (1 << 20); + MSG ("after reading %" PRIu64 " bytes %.1f MB", read_bytes, read_mb); + if (close_file == 2) { + size_t actual_bytes = size (name ()); + double actual_mb = actual_bytes / (double) (1 << 20); + MSG ("inflated from %zd bytes %.1f MB", actual_bytes, actual_mb); + MSG ("factor %.2f (%.2f%% compression)", + relative (read_bytes, actual_bytes), + percent (actual_bytes, read_bytes)); + } + } + } +#endif +} + +void File::flush () { + CADICAL_assert (file); + fflush (file); +} + +File::~File () { + if (file) + close (); + free (_name); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_flags.cpp b/src/sat/cadical/cadical_flags.cpp new file mode 100644 index 000000000..5c06a33b4 --- /dev/null +++ b/src/sat/cadical/cadical_flags.cpp @@ -0,0 +1,141 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::mark_fixed (int lit) { + if (external->fixed_listener) { + int elit = externalize (lit); + CADICAL_assert (elit); + const int eidx = abs (elit); + if (!external->ervars[eidx]) + external->fixed_listener->notify_fixed_assignment (elit); + } + Flags &f = flags (lit); + CADICAL_assert (f.status == Flags::ACTIVE); + f.status = Flags::FIXED; + LOG ("fixed %d", abs (lit)); + stats.all.fixed++; + stats.now.fixed++; + stats.inactive++; + CADICAL_assert (stats.active); + stats.active--; + CADICAL_assert (!active (lit)); + CADICAL_assert (f.fixed ()); + + if (external_prop && private_steps) { + // If pre/inprocessing found a fixed assignment, we want the propagator + // to know about it. + // But at that point it is not guaranteed to be already on the trail, so + // notification will happen only later. + CADICAL_assert (!level); + } +} + +void Internal::mark_eliminated (int lit) { + Flags &f = flags (lit); + CADICAL_assert (f.status == Flags::ACTIVE); + f.status = Flags::ELIMINATED; + LOG ("eliminated %d", abs (lit)); + stats.all.eliminated++; + stats.now.eliminated++; + stats.inactive++; + CADICAL_assert (stats.active); + stats.active--; + CADICAL_assert (!active (lit)); + CADICAL_assert (f.eliminated ()); +} + +void Internal::mark_pure (int lit) { + Flags &f = flags (lit); + CADICAL_assert (f.status == Flags::ACTIVE); + f.status = Flags::PURE; + LOG ("pure %d", abs (lit)); + stats.all.pure++; + stats.now.pure++; + stats.inactive++; + CADICAL_assert (stats.active); + stats.active--; + CADICAL_assert (!active (lit)); + CADICAL_assert (f.pure ()); +} + +void Internal::mark_substituted (int lit) { + Flags &f = flags (lit); + CADICAL_assert (f.status == Flags::ACTIVE); + f.status = Flags::SUBSTITUTED; + LOG ("substituted %d", abs (lit)); + stats.all.substituted++; + stats.now.substituted++; + stats.inactive++; + CADICAL_assert (stats.active); + stats.active--; + CADICAL_assert (!active (lit)); + CADICAL_assert (f.substituted ()); +} + +void Internal::mark_active (int lit) { + Flags &f = flags (lit); + CADICAL_assert (f.status == Flags::UNUSED); + f.status = Flags::ACTIVE; + LOG ("activate %d previously unused", abs (lit)); + CADICAL_assert (stats.inactive); + stats.inactive--; + CADICAL_assert (stats.unused); + stats.unused--; + stats.active++; + CADICAL_assert (active (lit)); +} + +void Internal::reactivate (int lit) { + CADICAL_assert (!active (lit)); + Flags &f = flags (lit); + CADICAL_assert (f.status != Flags::FIXED); + CADICAL_assert (f.status != Flags::UNUSED); +#ifdef LOGGING + const char *msg = 0; +#endif + switch (f.status) { + default: + case Flags::ELIMINATED: + CADICAL_assert (f.status == Flags::ELIMINATED); + CADICAL_assert (stats.now.eliminated > 0); + stats.now.eliminated--; +#ifdef LOGGING + msg = "eliminated"; +#endif + break; + case Flags::SUBSTITUTED: +#ifdef LOGGING + msg = "substituted"; +#endif + CADICAL_assert (stats.now.substituted > 0); + stats.now.substituted--; + break; + case Flags::PURE: +#ifdef LOGGING + msg = "pure literal"; +#endif + CADICAL_assert (stats.now.pure > 0); + stats.now.pure--; + break; + } +#ifdef LOGGING + CADICAL_assert (msg); + LOG ("reactivate previously %s %d", msg, abs (lit)); +#endif + f.status = Flags::ACTIVE; + f.sweep = false; + CADICAL_assert (active (lit)); + stats.reactivated++; + CADICAL_assert (stats.inactive > 0); + stats.inactive--; + stats.active++; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_flip.cpp b/src/sat/cadical/cadical_flip.cpp new file mode 100644 index 000000000..a89e3abb7 --- /dev/null +++ b/src/sat/cadical/cadical_flip.cpp @@ -0,0 +1,275 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +bool Internal::flip (int lit) { + + // Do not try to flip inactive literals except for unused variables. + + if (!active (lit) && !flags (lit).unused ()) + return false; + + /* + if (flags (lit).unused ()) { + CADICAL_assert (lit <= max_var); + mark_active (lit); + set_val (lit, 1); + return true; + } + */ + + // TODO: Unused case is not handled yet. + // if (flags (lit).unused ()) return false; + + // Need to reestablish proper watching invariants as if there are no + // blocking literals as flipping in principle does not work with them. + + if (propergated < trail.size ()) + propergate (); + + LOG ("trying to flip %d", lit); + + const int idx = vidx (lit); + const signed char original_value = vals[idx]; + CADICAL_assert (original_value); + lit = original_value < 0 ? -idx : idx; + CADICAL_assert (val (lit) > 0); + + // Here we go over all the clauses in which 'lit' is watched by 'lit' and + // check whether assigning 'lit' to false would break watching invariants + // or even make the clause false. We also try to find replacement + // watches in case this fixes the watching invariant. This code is very + // similar to propagation of a literal in 'Internal::propagate'. + + bool res = true; + + Watches &ws = watches (lit); + const const_watch_iterator eow = ws.end (); + watch_iterator bow = ws.begin (); + + // We first go over binary watches/clauses first as this is cheaper and + // has higher chance of failure and we can not use blocking literals. + + for (const_watch_iterator i = bow; i != eow; i++) { + const Watch w = *i; + if (!w.binary ()) + continue; + const signed char b = val (w.blit); + if (b > 0) + continue; + CADICAL_assert (b < 0); + res = false; + break; + } + + if (res) { + const_watch_iterator i = bow; + watch_iterator j = bow; + + while (i != eow) { + + const Watch w = *j++ = *i++; + + if (w.binary ()) + continue; + + if (w.clause->garbage) { + j--; + continue; + } + + literal_iterator lits = w.clause->begin (); + + const int other = lits[0] ^ lits[1] ^ lit; + const signed char u = val (other); + if (u > 0) + continue; + + const int size = w.clause->size; + const literal_iterator middle = lits + w.clause->pos; + const const_literal_iterator end = lits + size; + literal_iterator k = middle; + + int r = 0; + signed char v = -1; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + + if (v < 0) { + res = false; + break; + } + + CADICAL_assert (v > 0); + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + w.clause->pos = k - lits; + lits[0] = other, lits[1] = r, *k = lit; + watch_literal (r, lit, w.clause); + j--; + } + + if (j != i) { + + while (i != eow) + *j++ = *i++; + + ws.resize (j - ws.begin ()); + } + } +#ifdef LOGGING + if (res) + LOG ("literal %d can be flipped", lit); + else + LOG ("literal %d can not be flipped", lit); +#endif + + if (res) { + + const int idx = vidx (lit); + const signed char original_value = vals[idx]; + CADICAL_assert (original_value); + lit = original_value < 0 ? -idx : idx; + CADICAL_assert (val (lit) > 0); + + LOG ("flipping value of %d = 1 to %d = -1", lit, lit); + + set_val (idx, -original_value); + CADICAL_assert (val (-lit) > 0); + CADICAL_assert (val (lit) < 0); + + Var &v = var (idx); + CADICAL_assert (trail[v.trail] == lit); + trail[v.trail] = -lit; + if (opts.ilb) { + if (!tainted_literal) + tainted_literal = lit; + else { + CADICAL_assert (val (tainted_literal)); + if (v.level < var (tainted_literal).level) { + tainted_literal = lit; + } + } + } + } else + LOG ("flipping value of %d failed", lit); + + return res; +} + +bool Internal::flippable (int lit) { + + // Can not check inactive literals except for unused variables. + + if (!active (lit) && !flags (lit).unused ()) + return false; + + /* + if (flags (lit).unused ()) { + CADICAL_assert (lit <= max_var); + mark_active (lit); + return true; + } + */ + // TODO: Unused case is not handled yet + // if (flags (lit).unused ()) return false; + + // Need to reestablish proper watching invariants as if there are no + // blocking literals as flipping in principle does not work with them. + + if (propergated < trail.size ()) + propergate (); + + LOG ("checking whether %d is flippable", lit); + + const int idx = vidx (lit); + const signed char original_value = vals[idx]; + CADICAL_assert (original_value); + lit = original_value < 0 ? -idx : idx; + CADICAL_assert (val (lit) > 0); + + // Here we go over all the clauses in which 'lit' is watched by 'lit' and + // check whether assigning 'lit' to false would break watching invariants + // or even make the clause false. In contrast to 'flip' we do not try to + // find replacement literals but do use blocking literals'. Therefore we + // also do not split the traversal code into two parts. + + bool res = true; + + Watches &ws = watches (lit); + const const_watch_iterator eow = ws.end (); + for (watch_iterator i = ws.begin (); i != eow; i++) { + + const Watch w = *i; + const signed char b = val (w.blit); + if (b > 0) + continue; + CADICAL_assert (b < 0); + + if (w.binary ()) { + res = false; + break; + } + + if (w.clause->garbage) + continue; + + literal_iterator lits = w.clause->begin (); + + const int other = lits[0] ^ lits[1] ^ lit; + const signed char u = val (other); + if (u > 0) { + i->blit = other; + continue; + } + + const int size = w.clause->size; + const literal_iterator middle = lits + w.clause->pos; + const const_literal_iterator end = lits + size; + literal_iterator k = middle; + + int r = 0; + signed char v = -1; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + + if (v < 0) { + res = false; + break; + } + + CADICAL_assert (v > 0); + CADICAL_assert (lits + 2 <= k); + CADICAL_assert (k <= w.clause->end ()); + w.clause->pos = k - lits; + i->blit = r; + } + +#ifdef LOGGING + if (res) + LOG ("literal %d can be flipped", lit); + else + LOG ("literal %d can not be flipped", lit); +#endif + + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_format.cpp b/src/sat/cadical/cadical_format.cpp new file mode 100644 index 000000000..942d9d74a --- /dev/null +++ b/src/sat/cadical/cadical_format.cpp @@ -0,0 +1,95 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Format::enlarge () { + char *old = buffer; + buffer = new char[size = size ? 2 * size : 1]; + memcpy (buffer, old, count); + delete[] old; +} + +inline void Format::push_char (char ch) { + if (size == count) + enlarge (); + buffer[count++] = ch; +} + +void Format::push_string (const char *s) { + char ch; + while ((ch = *s++)) + push_char (ch); +} + +void Format::push_int (int d) { + char tmp[16]; + snprintf (tmp, sizeof tmp, "%d", d); + push_string (tmp); +} + +void Format::push_uint64 (uint64_t u) { + char tmp[16]; + snprintf (tmp, sizeof tmp, "%" PRIu64, u); + push_string (tmp); +} + +static bool match_format (const char *&str, const char *pattern) { + CADICAL_assert (pattern); + const char *p = str; + const char *q = pattern; + while (*q) + if (*q++ != *p++) + return false; + str = p; + return true; +} + +const char *Format::add (const char *fmt, va_list &ap) { + const char *p = fmt; + char ch; + while ((ch = *p++)) { + if (ch != '%') + push_char (ch); + else if (*p == 'c') + push_char (va_arg (ap, int)), p++; + else if (*p == 'd') + push_int (va_arg (ap, int)), p++; + else if (*p == 's') + push_string (va_arg (ap, const char *)), p++; + else if (match_format (p, PRIu64)) + push_uint64 (va_arg (ap, uint64_t)); + else { + push_char ('%'); + push_char (*p); + break; + } // unsupported + } + push_char (0); + count--; // thus automatic append in subsequent calls. + return buffer; +} + +const char *Format::init (const char *fmt, ...) { + count = 0; + va_list ap; + va_start (ap, fmt); + const char *res = add (fmt, ap); + va_end (ap); + return res; +} + +const char *Format::append (const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + const char *res = add (fmt, ap); + va_end (ap); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_frattracer.cpp b/src/sat/cadical/cadical_frattracer.cpp new file mode 100644 index 000000000..d35a18271 --- /dev/null +++ b/src/sat/cadical/cadical_frattracer.cpp @@ -0,0 +1,283 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +FratTracer::FratTracer (Internal *i, File *f, bool b, bool a) + : internal (i), file (f), binary (b), with_antecedents (a) +#ifndef CADICAL_QUIET + , + added (0), deleted (0), finalized (0), original (0) +#endif +{ + (void) internal; +} + +void FratTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("FRAT TRACER connected to internal"); +} + +FratTracer::~FratTracer () { + LOG ("FRAT TRACER delete"); + delete file; +} + +/*------------------------------------------------------------------------*/ + +inline void FratTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void FratTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned x = 2 * abs (lit) + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +inline void FratTracer::put_binary_id (int64_t id, bool can_be_negative) { + CADICAL_assert (binary); + CADICAL_assert (file); + uint64_t x = abs (id); + if (can_be_negative) { + x = 2 * x + (id < 0); + } + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void FratTracer::frat_add_original_clause (int64_t id, + const vector &clause) { + if (binary) + file->put ('o'); + else + file->put ("o "); + if (binary) + put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +void FratTracer::frat_add_derived_clause (int64_t id, + const vector &clause) { + if (binary) + file->put ('a'); + else + file->put ("a "); + if (binary) + put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +void FratTracer::frat_add_derived_clause (int64_t id, + const vector &clause, + const vector &chain) { + if (binary) + file->put ('a'); + else + file->put ("a "); + if (binary) + put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (), file->put ('l'); + else + file->put ("0 l "); + for (const auto &c : chain) + if (binary) + put_binary_id (c, true); // LRAT can have negative ids + else + file->put (c), file->put (' '); // in proof chain, so they get + if (binary) + put_binary_zero (); // since cadical has no rat-steps + else + file->put ("0\n"); // this is just 2c here +} + +void FratTracer::frat_delete_clause (int64_t id, + const vector &clause) { + if (binary) + file->put ('d'); + else + file->put ("d "); + if (binary) + put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +void FratTracer::frat_finalize_clause (int64_t id, + const vector &clause) { + if (binary) + file->put ('f'); + else + file->put ("f "); + if (binary) + put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +/*------------------------------------------------------------------------*/ + +void FratTracer::add_original_clause (int64_t id, bool, + const vector &clause, bool) { + if (file->closed ()) + return; + LOG ("FRAT TRACER tracing addition of original clause"); + frat_add_original_clause (id, clause); +} + +void FratTracer::add_derived_clause (int64_t id, bool, + const vector &clause, + const vector &chain) { + if (file->closed ()) + return; + LOG ("FRAT TRACER tracing addition of derived clause"); + if (with_antecedents) + frat_add_derived_clause (id, clause, chain); + else + frat_add_derived_clause (id, clause); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void FratTracer::delete_clause (int64_t id, bool, + const vector &clause) { + if (file->closed ()) + return; + LOG ("FRAT TRACER tracing deletion of clause"); + frat_delete_clause (id, clause); +#ifndef CADICAL_QUIET + deleted++; +#endif +} + +void FratTracer::finalize_clause (int64_t id, const vector &clause) { + if (file->closed ()) + return; + LOG ("FRAT TRACER tracing finalization of clause"); + frat_finalize_clause (id, clause); +} + +/*------------------------------------------------------------------------*/ + +bool FratTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void FratTracer::print_statistics () { + uint64_t bytes = file->bytes (); + uint64_t total = original + added + deleted + finalized; + MSG ("FRAT %" PRId64 " original clauses %.2f%%", original, + percent (original, total)); + MSG ("FRAT %" PRId64 " added clauses %.2f%%", added, + percent (added, total)); + MSG ("FRAT %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("FRAT %" PRId64 " finalized clauses %.2f%%", finalized, + percent (finalized, total)); + MSG ("FRAT %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void FratTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("FRAT proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void FratTracer::flush (bool print) { + CADICAL_assert (!closed ()); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("FRAT proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_gates.cpp b/src/sat/cadical/cadical_gates.cpp new file mode 100644 index 000000000..06a73d1eb --- /dev/null +++ b/src/sat/cadical/cadical_gates.cpp @@ -0,0 +1,772 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// As in our original SATeLite published at SAT'05 we are trying to find +// gates in order to restrict the number of resolutions that need to be +// tried. If there is such a gate, we only need to consider resolvents +// among gate and one non-gate clauses. Resolvents between definitions will +// be tautological anyhow and resolvents among non-gates can actually be +// shown to be redundant too. + +/*------------------------------------------------------------------------*/ + +// The next function returns a non-zero if the clause 'c', which is assumed +// to contain the literal 'first', after removing falsified literals is a +// binary clause. Then the actual second literal is returned. + +int Internal::second_literal_in_binary_clause (Eliminator &eliminator, + Clause *c, int first) { + CADICAL_assert (!c->garbage); + int second = 0; + for (const auto &lit : *c) { + if (lit == first) + continue; + const signed char tmp = val (lit); + if (tmp < 0) + continue; + if (tmp > 0) { + mark_garbage (c); + elim_update_removed_clause (eliminator, c); + return 0; + } + if (second) { + second = INT_MIN; + break; + } + second = lit; + } + if (!second) + return 0; + if (second == INT_MIN) + return 0; + CADICAL_assert (active (second)); +#ifdef LOGGING + if (c->size == 2) + LOG (c, "found binary"); + else + LOG (c, "found actual binary %d %d", first, second); +#endif + return second; +} + +/*------------------------------------------------------------------------*/ + +// need a copy from above that does not care about garbage + +int Internal::second_literal_in_binary_clause_lrat (Clause *c, int first) { + if (c->garbage) + return 0; + int second = 0; + for (const auto &lit : *c) { + if (lit == first) + continue; + const signed char tmp = val (lit); + if (tmp < 0) + continue; + if (tmp > 0) + return 0; + if (!tmp) { + if (second) { + second = INT_MIN; + break; + } + second = lit; + } + } + if (!second) + return 0; + if (second == INT_MIN) + return 0; + return second; +} + +// I needed to find the second clause for hyper unary resolution to build +// LRAT this is not efficient but I could not find a better way then just +// finding the corresponding clause in all possible clauses +// +Clause *Internal::find_binary_clause (int first, int second) { + int best = first; + int other = second; + if (occs (first).size () > occs (second).size ()) { + best = second; + other = first; + } + for (auto c : occs (best)) + if (second_literal_in_binary_clause_lrat (c, best) == other) + return c; + return 0; +} + +/*------------------------------------------------------------------------*/ + +// Mark all other literals in binary clauses with 'first'. During this +// marking we might also detect hyper unary resolvents producing a unit. +// If such a unit is found we propagate it and return immediately. + +void Internal::mark_binary_literals (Eliminator &eliminator, int first) { + + if (unsat) + return; + if (val (first)) + return; + if (!eliminator.gates.empty ()) + return; + + CADICAL_assert (!marked (first)); + CADICAL_assert (eliminator.marked.empty ()); + + const Occs &os = occs (first); + for (const auto &c : os) { + if (c->garbage) + continue; + const int second = + second_literal_in_binary_clause (eliminator, c, first); + if (!second) + continue; + const int tmp = marked (second); + if (tmp < 0) { + // had a bug where units could occur multiple times here + // solved with flags + LOG ("found binary resolved unit %d", first); + if (lrat) { + Clause *d = find_binary_clause (first, -second); + CADICAL_assert (d); + for (auto &lit : *d) { + if (lit == first || lit == -second) + continue; + CADICAL_assert (val (lit) < 0); + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + // LOG ("gates added id %" PRId64, id); + } + for (auto &lit : *c) { + if (lit == first || lit == second) + continue; + CADICAL_assert (val (lit) < 0); + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + // LOG ("gates added id %" PRId64, id); + } + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + // LOG ("gates added id %" PRId64, c->id); + // LOG ("gates added id %" PRId64, d->id); + clear_analyzed_literals (); + } + assign_unit (first); + elim_propagate (eliminator, first); + return; + } + if (tmp > 0) { + LOG (c, "duplicated actual binary clause"); + elim_update_removed_clause (eliminator, c); + mark_garbage (c); + continue; + } + eliminator.marked.push_back (second); + mark (second); + LOG ("marked second literal %d in binary clause %d %d", second, first, + second); + } +} + +// Unmark all literals saved on the 'marked' stack. + +void Internal::unmark_binary_literals (Eliminator &eliminator) { + LOG ("unmarking %zd literals", eliminator.marked.size ()); + for (const auto &lit : eliminator.marked) + unmark (lit); + eliminator.marked.clear (); +} + +/*------------------------------------------------------------------------*/ + +// Find equivalence for 'pivot'. Requires that all other literals in binary +// clauses with 'pivot' are marked (through 'mark_binary_literals'); + +void Internal::find_equivalence (Eliminator &eliminator, int pivot) { + + if (!opts.elimequivs) + return; + + CADICAL_assert (opts.elimsubst); + + if (unsat) + return; + if (val (pivot)) + return; + if (!eliminator.gates.empty ()) + return; + + mark_binary_literals (eliminator, pivot); + if (unsat || val (pivot)) + goto DONE; + + for (const auto &c : occs (-pivot)) { + + if (c->garbage) + continue; + + const int second = + second_literal_in_binary_clause (eliminator, c, -pivot); + if (!second) + continue; + const int tmp = marked (second); + if (tmp > 0) { + LOG ("found binary resolved unit %d", second); + // did not find a bug where units could occur multiple times here + // still solved potential issues with flags + if (lrat) { + Clause *d = find_binary_clause (pivot, second); + CADICAL_assert (d); + for (auto &lit : *d) { + if (lit == pivot || lit == second) + continue; + CADICAL_assert (val (lit) < 0); + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + // LOG ("gates added id %" PRId64, id); + } + for (auto &lit : *c) { + if (lit == -pivot || lit == second) + continue; + CADICAL_assert (val (lit) < 0); + Flags &f = flags (lit); + if (f.seen) + continue; + analyzed.push_back (lit); + f.seen = true; + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + // LOG ("gates added id %" PRId64, id); + } + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + clear_analyzed_literals (); + // LOG ("gates added id %" PRId64, c->id); + // LOG ("gates added id %" PRId64, d->id); + } + assign_unit (second); + elim_propagate (eliminator, second); + if (val (pivot)) + break; + if (unsat) + break; + } + if (tmp >= 0) + continue; + + LOG ("found equivalence %d = %d", pivot, -second); + stats.elimequivs++; + stats.elimgates++; + + LOG (c, "first gate clause"); + CADICAL_assert (!c->gate); + c->gate = true; + eliminator.gates.push_back (c); + + Clause *d = 0; + const Occs &ps = occs (pivot); + for (const auto &e : ps) { + if (e->garbage) + continue; + const int other = + second_literal_in_binary_clause (eliminator, e, pivot); + if (other == -second) { + d = e; + break; + } + } + CADICAL_assert (d); + + LOG (d, "second gate clause"); + CADICAL_assert (!d->gate); + d->gate = true; + eliminator.gates.push_back (d); + eliminator.gatetype = EQUI; + + break; + } + +DONE: + unmark_binary_literals (eliminator); +} + +/*------------------------------------------------------------------------*/ + +// Find and gates for 'pivot' with a long clause, in which the pivot occurs +// positively. Requires that all other literals in binary clauses with +// 'pivot' are marked (through 'mark_binary_literals'); + +void Internal::find_and_gate (Eliminator &eliminator, int pivot) { + + if (!opts.elimands) + return; + + CADICAL_assert (opts.elimsubst); + + if (unsat) + return; + if (val (pivot)) + return; + if (!eliminator.gates.empty ()) + return; + + mark_binary_literals (eliminator, pivot); + if (unsat || val (pivot)) + goto DONE; + + for (const auto &c : occs (-pivot)) { + + if (c->garbage) + continue; + if (c->size < 3) + continue; + + bool all_literals_marked = true; + unsigned arity = 0; + int satisfied = 0; + + for (const auto &lit : *c) { + if (lit == -pivot) + continue; + CADICAL_assert (lit != pivot); + signed char tmp = val (lit); + if (tmp < 0) + continue; + if (tmp > 0) { + satisfied = lit; + break; + } + tmp = marked (lit); + if (tmp < 0) { + arity++; + continue; + } + all_literals_marked = false; + break; + } + + if (!all_literals_marked) + continue; + + if (satisfied) { + LOG (c, "satisfied by %d candidate base clause", satisfied); + mark_garbage (c); + continue; + } + +#ifdef LOGGING + if (opts.log) { + Logger::print_log_prefix (this); + tout.magenta (); + printf ("found arity %u AND gate %d = ", arity, -pivot); + bool first = true; + for (const auto &lit : *c) { + if (lit == -pivot) + continue; + CADICAL_assert (lit != pivot); + if (!first) + fputs (" & ", stdout); + printf ("%d", -lit); + first = false; + } + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); + } +#endif + stats.elimands++; + stats.elimgates++; + eliminator.gatetype = AND; + + (void) arity; + CADICAL_assert (!c->gate); + c->gate = true; + eliminator.gates.push_back (c); + for (const auto &lit : *c) { + if (lit == -pivot) + continue; + CADICAL_assert (lit != pivot); + signed char tmp = val (lit); + if (tmp < 0) + continue; + CADICAL_assert (!tmp); + CADICAL_assert (marked (lit) < 0); + marks[vidx (lit)] *= 2; + } + + unsigned count = 0; + for (const auto &d : occs (pivot)) { + if (d->garbage) + continue; + const int other = + second_literal_in_binary_clause (eliminator, d, pivot); + if (!other) + continue; + const int tmp = marked (other); + if (tmp != 2) + continue; + LOG (d, "AND gate binary side clause"); + CADICAL_assert (!d->gate); + d->gate = true; + eliminator.gates.push_back (d); + count++; + } + CADICAL_assert (count >= arity); + (void) count; + + break; + } + +DONE: + unmark_binary_literals (eliminator); +} + +/*------------------------------------------------------------------------*/ + +// Find and extract ternary clauses. + +bool Internal::get_ternary_clause (Clause *d, int &a, int &b, int &c) { + if (d->garbage) + return false; + if (d->size < 3) + return false; + int found = 0; + a = b = c = 0; + for (const auto &lit : *d) { + if (val (lit)) + continue; + if (++found == 1) + a = lit; + else if (found == 2) + b = lit; + else if (found == 3) + c = lit; + else + return false; + } + return found == 3; +} + +// This function checks whether 'd' exists as ternary clause. + +bool Internal::match_ternary_clause (Clause *d, int a, int b, int c) { + if (d->garbage) + return false; + int found = 0; + for (const auto &lit : *d) { + if (val (lit)) + continue; + if (a != lit && b != lit && c != lit) + return false; + found++; + } + return found == 3; +} + +Clause *Internal::find_ternary_clause (int a, int b, int c) { + if (occs (b).size () > occs (c).size ()) + swap (b, c); + if (occs (a).size () > occs (b).size ()) + swap (a, b); + for (auto d : occs (a)) + if (match_ternary_clause (d, a, b, c)) + return d; + return 0; +} + +/*------------------------------------------------------------------------*/ + +// Find if-then-else gate. + +void Internal::find_if_then_else (Eliminator &eliminator, int pivot) { + + if (!opts.elimites) + return; + + CADICAL_assert (opts.elimsubst); + + if (unsat) + return; + if (val (pivot)) + return; + if (!eliminator.gates.empty ()) + return; + + const Occs &os = occs (pivot); + const auto end = os.end (); + for (auto i = os.begin (); i != end; i++) { + Clause *di = *i; + int ai, bi, ci; + if (!get_ternary_clause (di, ai, bi, ci)) + continue; + if (bi == pivot) + swap (ai, bi); + if (ci == pivot) + swap (ai, ci); + CADICAL_assert (ai == pivot); + for (auto j = i + 1; j != end; j++) { + Clause *dj = *j; + int aj, bj, cj; + if (!get_ternary_clause (dj, aj, bj, cj)) + continue; + if (bj == pivot) + swap (aj, bj); + if (cj == pivot) + swap (aj, cj); + CADICAL_assert (aj == pivot); + if (abs (bi) == abs (cj)) + swap (bj, cj); + if (abs (ci) == abs (cj)) + continue; + if (bi != -bj) + continue; + Clause *d1 = find_ternary_clause (-pivot, bi, -ci); + if (!d1) + continue; + Clause *d2 = find_ternary_clause (-pivot, bj, -cj); + if (!d2) + continue; + LOG (di, "1st if-then-else"); + LOG (dj, "2nd if-then-else"); + LOG (d1, "3rd if-then-else"); + LOG (d2, "4th if-then-else"); + LOG ("found ITE gate %d == (%d ? %d : %d)", pivot, -bi, -ci, -cj); + CADICAL_assert (!di->gate); + CADICAL_assert (!dj->gate); + CADICAL_assert (!d1->gate); + CADICAL_assert (!d2->gate); + di->gate = true; + dj->gate = true; + d1->gate = true; + d2->gate = true; + eliminator.gates.push_back (di); + eliminator.gates.push_back (dj); + eliminator.gates.push_back (d1); + eliminator.gates.push_back (d2); + stats.elimgates++; + stats.elimites++; + eliminator.gatetype = ITE; + return; + } + } +} + +/*------------------------------------------------------------------------*/ + +// Find and extract clause. + +bool Internal::get_clause (Clause *c, vector &l) { + if (c->garbage) + return false; + l.clear (); + for (const auto &lit : *c) { + if (val (lit) < 0) + continue; + if (val (lit) > 0) { + l.clear (); + return false; + } + l.push_back (lit); + } + return true; +} + +// Check whether 'c' contains only the literals in 'l'. + +bool Internal::is_clause (Clause *c, const vector &lits) { + if (c->garbage) + return false; + int size = lits.size (); + if (c->size < size) + return false; + int found = 0; + for (const auto &lit : *c) { + if (val (lit) < 0) + continue; + if (val (lit) > 0) + return false; + const auto it = find (lits.begin (), lits.end (), lit); + if (it == lits.end ()) + return false; + if (++found > size) + return false; + } + return found == size; +} + +Clause *Internal::find_clause (const vector &lits) { + int best = 0; + size_t len = 0; + for (const auto &lit : lits) { + size_t l = occs (lit).size (); + if (best && l >= len) + continue; + len = l, best = lit; + } + for (auto c : occs (best)) + if (is_clause (c, lits)) + return c; + return 0; +} + +void Internal::find_xor_gate (Eliminator &eliminator, int pivot) { + + if (!opts.elimxors) + return; + + CADICAL_assert (opts.elimsubst); + + if (unsat) + return; + if (val (pivot)) + return; + if (!eliminator.gates.empty ()) + return; + + vector lits; + + for (auto d : occs (pivot)) { + + if (!get_clause (d, lits)) + continue; + + const int size = lits.size (); // clause size + const int arity = size - 1; // arity of XOR + + if (size < 3) + continue; + if (arity > opts.elimxorlim) + continue; + + CADICAL_assert (eliminator.gates.empty ()); + + unsigned needed = (1u << arity) - 1; // additional clauses + unsigned signs = 0; // literals to negate + + do { + const unsigned prev = signs; + while (parity (++signs)) + ; + for (int j = 0; j < size; j++) { + const unsigned bit = 1u << j; + int lit = lits[j]; + if ((prev & bit) != (signs & bit)) + lits[j] = lit = -lit; + } + Clause *e = find_clause (lits); + if (!e) + break; + eliminator.gates.push_back (e); + } while (--needed); + + if (needed) { + eliminator.gates.clear (); + continue; + } + + eliminator.gates.push_back (d); + CADICAL_assert (eliminator.gates.size () == (1u << arity)); + +#ifdef LOGGING + if (opts.log) { + Logger::print_log_prefix (this); + tout.magenta (); + printf ("found arity %u XOR gate %d = ", arity, -pivot); + bool first = true; + for (const auto &lit : *d) { + if (lit == pivot) + continue; + CADICAL_assert (lit != -pivot); + if (!first) + fputs (" ^ ", stdout); + printf ("%d", lit); + first = false; + } + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); + } +#endif + stats.elimgates++; + stats.elimxors++; + const auto end = eliminator.gates.end (); + auto j = eliminator.gates.begin (); + for (auto i = j; i != end; i++) { + Clause *e = *i; + if (e->gate) + continue; + e->gate = true; + LOG (e, "contributing"); + *j++ = e; + } + eliminator.gates.resize (j - eliminator.gates.begin ()); + eliminator.gatetype = XOR; + break; + } +} + +/*------------------------------------------------------------------------*/ + +// Find a gate for 'pivot'. If such a gate is found, the gate clauses are +// marked and pushed on the stack of gates. Further hyper unary resolution +// might detect units, which are propagated. This might assign the pivot or +// even produce the empty clause. + +void Internal::find_gate_clauses (Eliminator &eliminator, int pivot) { + if (!opts.elimsubst) + return; + + if (unsat) + return; + if (val (pivot)) + return; + + CADICAL_assert (eliminator.gates.empty ()); + + find_equivalence (eliminator, pivot); + find_and_gate (eliminator, pivot); + find_and_gate (eliminator, -pivot); + find_if_then_else (eliminator, pivot); + find_xor_gate (eliminator, pivot); + find_definition (eliminator, pivot); +} + +void Internal::unmark_gate_clauses (Eliminator &eliminator) { + LOG ("unmarking %zd gate clauses", eliminator.gates.size ()); + for (const auto &c : eliminator.gates) { + CADICAL_assert (c->gate); + c->gate = false; + } + eliminator.gates.clear (); + eliminator.definition_unit = 0; +} + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_idruptracer.cpp b/src/sat/cadical/cadical_idruptracer.cpp new file mode 100644 index 000000000..ec6467163 --- /dev/null +++ b/src/sat/cadical/cadical_idruptracer.cpp @@ -0,0 +1,572 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +IdrupTracer::IdrupTracer (Internal *i, File *f, bool b) + : internal (i), file (f), binary (b), num_clauses (0), size_clauses (0), + clauses (0), last_hash (0), last_id (0), last_clause (0) +#ifndef CADICAL_QUIET + , + added (0), deleted (0) +#endif +{ + (void) internal; + + // Initialize random number table for hash function. + // + Random random (42); + for (unsigned n = 0; n < num_nonces; n++) { + uint64_t nonce = random.next (); + if (!(nonce & 1)) + nonce++; + CADICAL_assert (nonce), CADICAL_assert (nonce & 1); + nonces[n] = nonce; + } +#ifndef CADICAL_NDEBUG + binary = b; +#else + (void) b; +#endif + piping = file->piping (); +} + +void IdrupTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("IDRUP TRACER connected to internal"); +} + +IdrupTracer::~IdrupTracer () { + LOG ("IDRUP TRACER delete"); + delete file; + for (size_t i = 0; i < size_clauses; i++) + for (IdrupClause *c = clauses[i], *next; c; c = next) + next = c->next, delete_clause (c); + delete[] clauses; +} + +/*------------------------------------------------------------------------*/ + +void IdrupTracer::enlarge_clauses () { + CADICAL_assert (num_clauses == size_clauses); + const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1; + LOG ("IDRUP Tracer enlarging clauses of tracer from %" PRIu64 + " to %" PRIu64, + (uint64_t) size_clauses, (uint64_t) new_size_clauses); + IdrupClause **new_clauses; + new_clauses = new IdrupClause *[new_size_clauses]; + clear_n (new_clauses, new_size_clauses); + for (uint64_t i = 0; i < size_clauses; i++) { + for (IdrupClause *c = clauses[i], *next; c; c = next) { + next = c->next; + const uint64_t h = reduce_hash (c->hash, new_size_clauses); + c->next = new_clauses[h]; + new_clauses[h] = c; + } + } + delete[] clauses; + clauses = new_clauses; + size_clauses = new_size_clauses; +} + +IdrupClause *IdrupTracer::new_clause () { + const size_t size = imported_clause.size (); + CADICAL_assert (size <= UINT_MAX); + const int off = size ? -1 : 0; + const size_t bytes = sizeof (IdrupClause) + (size - off) * sizeof (int); + IdrupClause *res = (IdrupClause *) new char[bytes]; + res->next = 0; + res->hash = last_hash; + res->id = last_id; + res->size = size; + int *literals = res->literals, *p = literals; + for (const auto &lit : imported_clause) { + *p++ = lit; + } + last_clause = res; + num_clauses++; + return res; +} + +void IdrupTracer::delete_clause (IdrupClause *c) { + CADICAL_assert (c); + num_clauses--; + delete[] (char *) c; +} + +uint64_t IdrupTracer::reduce_hash (uint64_t hash, uint64_t size) { + CADICAL_assert (size > 0); + unsigned shift = 32; + uint64_t res = hash; + while ((((uint64_t) 1) << shift) > size) { + res ^= res >> shift; + shift >>= 1; + } + res &= size - 1; + CADICAL_assert (res < size); + return res; +} + +uint64_t IdrupTracer::compute_hash (const int64_t id) { + CADICAL_assert (id > 0); + unsigned j = id % num_nonces; + uint64_t tmp = nonces[j] * (uint64_t) id; + return last_hash = tmp; +} + +bool IdrupTracer::find_and_delete (const int64_t id) { + if (!num_clauses) + return false; + IdrupClause **res = 0, *c; + const uint64_t hash = compute_hash (id); + const uint64_t h = reduce_hash (hash, size_clauses); + for (res = clauses + h; (c = *res); res = &c->next) { + if (c->hash == hash && c->id == id) { + break; + } + if (!c->next) + return false; + } + if (!c) + return false; + CADICAL_assert (c && res); + *res = c->next; + int *begin = c->literals; + for (size_t i = 0; i < c->size; i++) { + imported_clause.push_back (begin[i]); + } + delete_clause (c); + return true; +} + +void IdrupTracer::insert () { + if (num_clauses == size_clauses) + enlarge_clauses (); + const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses); + IdrupClause *c = new_clause (); + c->next = clauses[h]; + clauses[h] = c; +} + +/*------------------------------------------------------------------------*/ + +inline void IdrupTracer::flush_if_piping () { + if (piping) + file->flush (); +} + +inline void IdrupTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void IdrupTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned x = 2 * abs (lit) + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +inline void IdrupTracer::put_binary_id (int64_t id, bool can_be_negative) { + CADICAL_assert (binary); + CADICAL_assert (file); + uint64_t x = abs (id); + if (can_be_negative) { + x = 2 * x + (id < 0); + } + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void IdrupTracer::idrup_add_restored_clause (const vector &clause) { + if (binary) + file->put ('r'); + else + file->put ("r "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + // flush_if_piping (); +} + +void IdrupTracer::idrup_add_derived_clause (const vector &clause) { + if (binary) + file->put ('l'); + else + file->put ("l "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + // flush_if_piping (); +} + +void IdrupTracer::idrup_add_original_clause (const vector &clause) { + if (binary) + file->put ('i'); + else + file->put ("i "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + // flush_if_piping (); +} + +void IdrupTracer::idrup_delete_clause (int64_t id, + const vector &clause) { + if (find_and_delete (id)) { + CADICAL_assert (imported_clause.empty ()); + if (binary) + file->put ('w'); + else + file->put ("w "); +#ifndef CADICAL_QUIET + weakened++; +#endif + } else { + if (binary) + file->put ('d'); + else + file->put ("d "); +#ifndef CADICAL_QUIET + deleted++; +#endif + } + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + // flush_if_piping (); +} + +void IdrupTracer::idrup_conclude_and_delete ( + const vector &conclusion) { + uint64_t size = conclusion.size (); + if (size > 1) { + if (binary) { + file->put ('U'); + put_binary_id (size); + } else { + file->put ("U "); + file->put (size), file->put ("\n"); + } + } + for (auto &id : conclusion) { + if (binary) + file->put ('u'); + else + file->put ("u "); + (void) find_and_delete (id); + for (const auto &external_lit : imported_clause) { + // flip sign... + const auto not_elit = -external_lit; + if (binary) + put_binary_lit (not_elit); + else + file->put (not_elit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + imported_clause.clear (); + } + flush_if_piping (); +} + +void IdrupTracer::idrup_report_status (int status) { + if (binary) + file->put ('s'); + else + file->put ("s "); + if (status == SATISFIABLE) + file->put ("SATISFIABLE"); + else if (status == UNSATISFIABLE) + file->put ("UNSATISFIABLE"); + else + file->put ("UNKNOWN"); + if (!binary) + file->put ("\n"); + flush_if_piping (); +} + +void IdrupTracer::idrup_conclude_sat (const vector &model) { + if (binary) + file->put ('m'); + else + file->put ("m "); + for (auto &lit : model) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +void IdrupTracer::idrup_conclude_unknown (const vector &trail) { + if (binary) + file->put ('e'); + else + file->put ("e "); + for (auto &lit : trail) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +void IdrupTracer::idrup_solve_query () { + if (binary) + file->put ('q'); + else + file->put ("q "); + for (auto &lit : assumptions) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +/*------------------------------------------------------------------------*/ + +void IdrupTracer::add_derived_clause (int64_t, bool, + const vector &clause, + const vector &) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (clause, "IDRUP TRACER tracing addition of derived clause"); + idrup_add_derived_clause (clause); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void IdrupTracer::add_assumption_clause (int64_t id, + const vector &clause, + const vector &) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (clause, "IDRUP TRACER tracing addition of assumption clause"); + for (auto &lit : clause) + imported_clause.push_back (lit); + last_id = id; + insert (); + imported_clause.clear (); +} + +void IdrupTracer::delete_clause (int64_t id, bool, + const vector &clause) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG ("IDRUP TRACER tracing deletion of clause[%" PRId64 "]", id); + idrup_delete_clause (id, clause); +} + +void IdrupTracer::weaken_minus (int64_t id, const vector &) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG ("IDRUP TRACER tracing weaken minus of clause[%" PRId64 "]", id); + last_id = id; + insert (); +#ifndef CADICAL_QUIET + weakened++; +#endif +} + +void IdrupTracer::conclude_unsat (ConclusionType, + const vector &conclusion) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (conclusion, "IDRUP TRACER tracing conclusion of clause(s)"); + idrup_conclude_and_delete (conclusion); +} + +void IdrupTracer::add_original_clause (int64_t id, bool, + const vector &clause, + bool restored) { + if (file->closed ()) + return; + if (!restored) { + LOG (clause, "IDRUP TRACER tracing addition of original clause"); +#ifndef CADICAL_QUIET + original++; +#endif + return idrup_add_original_clause (clause); + } + CADICAL_assert (restored); + if (find_and_delete (id)) { + LOG (clause, + "IDRUP TRACER the clause was not yet weakened, so no restore"); + return; + } + LOG (clause, "IDRUP TRACER tracing addition of restored clause"); + idrup_add_restored_clause (clause); +#ifndef CADICAL_QUIET + restore++; +#endif +} + +void IdrupTracer::report_status (int status, int64_t) { + if (file->closed ()) + return; + LOG ("IDRUP TRACER tracing report of status %d", status); + idrup_report_status (status); +} + +void IdrupTracer::conclude_sat (const vector &model) { + if (file->closed ()) + return; + LOG (model, "IDRUP TRACER tracing conclusion of model"); + idrup_conclude_sat (model); +} + +void IdrupTracer::conclude_unknown (const vector &trail) { + if (file->closed ()) + return; + LOG (trail, "IDRUP TRACER tracing conclusion of unknown state"); + idrup_conclude_unknown (trail); +} + +void IdrupTracer::solve_query () { + if (file->closed ()) + return; + LOG (assumptions, "IDRUP TRACER tracing solve query with assumptions"); + idrup_solve_query (); +#ifndef CADICAL_QUIET + solved++; +#endif +} + +void IdrupTracer::add_assumption (int lit) { + LOG ("IDRUP TRACER tracing addition of assumption %d", lit); + assumptions.push_back (lit); +} + +void IdrupTracer::reset_assumptions () { + LOG (assumptions, "IDRUP TRACER tracing reset of assumptions"); + assumptions.clear (); +} + +/*------------------------------------------------------------------------*/ + +bool IdrupTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void IdrupTracer::print_statistics () { + // TODO complete this. + uint64_t bytes = file->bytes (); + uint64_t total = added + deleted + weakened + restore + original; + MSG ("LIDRUP %" PRId64 " original clauses %.2f%%", original, + percent (original, total)); + MSG ("LIDRUP %" PRId64 " learned clauses %.2f%%", added, + percent (added, total)); + MSG ("LIDRUP %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("LIDRUP %" PRId64 " weakened clauses %.2f%%", weakened, + percent (weakened, total)); + MSG ("LIDRUP %" PRId64 " restored clauses %.2f%%", restore, + percent (restore, total)); + MSG ("LIDRUP %" PRId64 " queries %.2f", solved, relative (solved, total)); + MSG ("IDRUP %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void IdrupTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("IDRUP proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void IdrupTracer::flush (bool print) { + CADICAL_assert (!closed ()); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("IDRUP proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_instantiate.cpp b/src/sat/cadical/cadical_instantiate.cpp new file mode 100644 index 000000000..2c34015b2 --- /dev/null +++ b/src/sat/cadical/cadical_instantiate.cpp @@ -0,0 +1,371 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This provides an implementation of variable instantiation, a technique +// for removing literals with few occurrence (see also 'instantiate.hpp'). + +/*------------------------------------------------------------------------*/ + +// Triggered at the end of a variable elimination round ('elim_round'). + +void Internal::collect_instantiation_candidates ( + Instantiator &instantiator) { + CADICAL_assert (occurring ()); + for (auto idx : vars) { + if (frozen (idx)) + continue; + if (!active (idx)) + continue; + if (flags (idx).elim) + continue; // BVE attempt pending + for (int sign = -1; sign <= 1; sign += 2) { + const int lit = sign * idx; + if (noccs (lit) > opts.instantiateocclim) + continue; + Occs &os = occs (lit); + for (const auto &c : os) { + if (c->garbage) + continue; + if (opts.instantiateonce && c->instantiated) + continue; + if (c->size < opts.instantiateclslim) + continue; + bool satisfied = false; + int unassigned = 0; + for (const auto &other : *c) { + const signed char tmp = val (other); + if (tmp > 0) + satisfied = true; + if (!tmp) + unassigned++; + } + if (satisfied) + continue; + if (unassigned < 3) + continue; // avoid learning units + size_t negoccs = occs (-lit).size (); + LOG (c, + "instantiation candidate literal %d " + "with %zu negative occurrences in", + lit, negoccs); + instantiator.candidate (lit, c, c->size, negoccs); + } + } + } +} + +/*------------------------------------------------------------------------*/ + +// Specialized propagation and assignment routines for instantiation. + +inline void Internal::inst_assign (int lit) { + LOG ("instantiate assign %d", lit); + CADICAL_assert (!val (lit)); + CADICAL_assert ((int) num_assigned < max_var); + num_assigned++; + set_val (lit, 1); + trail.push_back (lit); +} + +// Conflict analysis is only needed to do valid resolution proofs. +// We remember propagated clauses in order of assignment (in inst_chain) +// which allows us to do a variant of conflict analysis if the instantiation +// attempt succeeds. +// +bool Internal::inst_propagate () { // Adapted from 'propagate'. + START (propagate); + int64_t before = propagated; + bool ok = true; + while (ok && propagated != trail.size ()) { + const int lit = -trail[propagated++]; + LOG ("instantiate propagating %d", -lit); + Watches &ws = watches (lit); + const const_watch_iterator eow = ws.end (); + const_watch_iterator i = ws.begin (); + watch_iterator j = ws.begin (); + while (i != eow) { + const Watch w = *j++ = *i++; + const signed char b = val (w.blit); + if (b > 0) + continue; + if (w.binary ()) { + if (b < 0) { + ok = false; + LOG (w.clause, "conflict"); + if (lrat) { + inst_chain.push_back (w.clause); + } + break; + } else { + if (lrat) { + inst_chain.push_back (w.clause); + } + inst_assign (w.blit); + } + } else { + literal_iterator lits = w.clause->begin (); + const int other = lits[0] ^ lits[1] ^ lit; + lits[0] = other, lits[1] = lit; + const signed char u = val (other); + if (u > 0) + j[-1].blit = other; + else { + const int size = w.clause->size; + const const_literal_iterator end = lits + size; + const literal_iterator middle = lits + w.clause->pos; + literal_iterator k = middle; + signed char v = -1; + int r = 0; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + w.clause->pos = k - lits; + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + if (v > 0) { + j[-1].blit = r; + } else if (!v) { + LOG (w.clause, "unwatch %d in", r); + lits[1] = r; + *k = lit; + watch_literal (r, lit, w.clause); + j--; + } else if (!u) { + CADICAL_assert (v < 0); + if (lrat) { + inst_chain.push_back (w.clause); + } + inst_assign (other); + } else { + CADICAL_assert (u < 0); + CADICAL_assert (v < 0); + if (lrat) { + inst_chain.push_back (w.clause); + } + LOG (w.clause, "conflict"); + ok = false; + break; + } + } + } + } + if (j != i) { + while (i != eow) + *j++ = *i++; + ws.resize (j - ws.begin ()); + } + } + int64_t delta = propagated - before; + stats.propagations.instantiate += delta; + STOP (propagate); + return ok; +} + +/*------------------------------------------------------------------------*/ + +// This is the instantiation attempt. + +bool Internal::instantiate_candidate (int lit, Clause *c) { + stats.instried++; + if (c->garbage) + return false; + CADICAL_assert (!level); + bool found = false, satisfied = false, inactive = false; + int unassigned = 0; + for (const auto &other : *c) { + if (other == lit) + found = true; + const signed char tmp = val (other); + if (tmp > 0) { + satisfied = true; + break; + } + if (!tmp && !active (other)) { + inactive = true; + break; + } + if (!tmp) + unassigned++; + } + if (!found) + return false; + if (inactive) + return false; + if (satisfied) + return false; + if (unassigned < 3) + return false; + size_t before = trail.size (); + CADICAL_assert (propagated == before); + CADICAL_assert (active (lit)); + CADICAL_assert (inst_chain.empty ()); + LOG (c, "trying to instantiate %d in", lit); + CADICAL_assert (!c->garbage); + c->instantiated = true; + CADICAL_assert (lrat_chain.empty ()); + level++; + inst_assign (lit); // Assume 'lit' to true. + for (const auto &other : *c) { + if (other == lit) + continue; + const signed char tmp = val (other); + if (tmp) { + CADICAL_assert (tmp < 0); + continue; + } + inst_assign (-other); // Assume other to false. + } + bool ok = inst_propagate (); // Propagate. + CADICAL_assert (lrat_chain.empty ()); // chain will be built here + if (ok) { + inst_chain.clear (); + } else if (lrat) { // analyze conflict for lrat + CADICAL_assert (inst_chain.size ()); + Clause *reason = inst_chain.back (); + inst_chain.pop_back (); + lrat_chain.push_back (reason->id); + for (const auto &other : *reason) { + Flags &f = flags (other); + CADICAL_assert (!f.seen); + f.seen = true; + analyzed.push_back (other); + } + } + while (trail.size () > before) { // Backtrack. + const int other = trail.back (); + LOG ("instantiate unassign %d", other); + trail.pop_back (); + CADICAL_assert (val (other) > 0); + num_assigned--; + set_val (other, 0); + // this is a variant of conflict analysis which is only needed for lrat + if (!ok && inst_chain.size () && lrat) { + Flags &f = flags (other); + if (f.seen) { + Clause *reason = inst_chain.back (); + lrat_chain.push_back (reason->id); + for (const auto &other : *reason) { + Flags &f = flags (other); + if (f.seen) + continue; + f.seen = true; + analyzed.push_back (other); + } + f.seen = false; + } + inst_chain.pop_back (); + } + } + CADICAL_assert (inst_chain.empty ()); + // post processing step for lrat + if (!ok && lrat) { + if (flags (lit).seen) + lrat_chain.push_back (c->id); + for (const auto &other : *c) { + Flags &f = flags (other); + f.seen = false; + } + for (int other : analyzed) { + Flags &f = flags (other); + if (!f.seen) { + f.seen = true; + continue; + } + int64_t id = unit_id (-other); + lrat_chain.push_back (id); + } + clear_analyzed_literals (); + reverse (lrat_chain.begin (), lrat_chain.end ()); + } + CADICAL_assert (analyzed.empty ()); + propagated = before; + CADICAL_assert (level == 1); + level = 0; + if (ok) { + CADICAL_assert (lrat_chain.empty ()); + LOG ("instantiation failed"); + return false; + } + unwatch_clause (c); + LOG (lrat_chain, "instantiate proof chain"); + strengthen_clause (c, lit); + watch_clause (c); + lrat_chain.clear (); + CADICAL_assert (c->size > 1); + LOG ("instantiation succeeded"); + stats.instantiated++; + return true; +} + +/*------------------------------------------------------------------------*/ + +// Try to instantiate all candidates collected before through the +// 'collect_instantiation_candidates' routine. + +void Internal::instantiate (Instantiator &instantiator) { + CADICAL_assert (opts.instantiate); + START (instantiate); + stats.instrounds++; +#ifndef CADICAL_QUIET + const int64_t candidates = instantiator.candidates.size (); + int64_t tried = 0; +#endif + int64_t instantiated = 0; + init_watches (); + connect_watches (); + if (propagated < trail.size ()) { + if (!propagate ()) { + LOG ("propagation after connecting watches failed"); + learn_empty_clause (); + CADICAL_assert (unsat); + } + } + PHASE ("instantiate", stats.instrounds, + "attempting to instantiate %" PRId64 + " candidate literal clause pairs", + candidates); + while (!unsat && !terminated_asynchronously () && + !instantiator.candidates.empty ()) { + Instantiator::Candidate cand = instantiator.candidates.back (); + instantiator.candidates.pop_back (); +#ifndef CADICAL_QUIET + tried++; +#endif + if (!active (cand.lit)) + continue; + LOG (cand.clause, + "trying to instantiate %d with " + "%zd negative occurrences in", + cand.lit, cand.negoccs); + if (!instantiate_candidate (cand.lit, cand.clause)) + continue; + instantiated++; + VERBOSE (2, + "instantiation %" PRId64 " (%.1f%%) succeeded " + "(%.1f%%) with %zd negative occurrences in size %d clause", + tried, percent (tried, candidates), + percent (instantiated, tried), cand.negoccs, cand.size); + } + PHASE ("instantiate", stats.instrounds, + "instantiated %" PRId64 " candidate successfully " + "out of %" PRId64 " tried %.1f%%", + instantiated, tried, percent (instantiated, tried)); + report ('I', !instantiated); + reset_watches (); + STOP (instantiate); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_internal.cpp b/src/sat/cadical/cadical_internal.cpp new file mode 100644 index 000000000..abe18c59c --- /dev/null +++ b/src/sat/cadical/cadical_internal.cpp @@ -0,0 +1,1196 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ +static Clause external_reason_clause; + +Internal::Internal () + : mode (SEARCH), unsat (false), iterating (false), + localsearching (false), lookingahead (false), preprocessing (false), + protected_reasons (false), force_saved_phase (false), + searching_lucky_phases (false), stable (false), reported (false), + external_prop (false), did_external_prop (false), + external_prop_is_lazy (true), forced_backt_allowed (false), + + private_steps (false), rephased (0), vsize (0), max_var (0), + clause_id (0), original_id (0), reserved_ids (0), conflict_id (0), + saved_decisions (0), concluded (false), lrat (false), frat (false), + level (0), vals (0), score_inc (1.0), scores (this), conflict (0), + ignore (0), external_reason (&external_reason_clause), + newest_clause (0), force_no_backtrack (false), + from_propagator (false), ext_clause_forgettable (false), + tainted_literal (0), notified (0), probe_reason (0), propagated (0), + propagated2 (0), propergated (0), best_assigned (0), + target_assigned (0), no_conflict_until (0), unsat_constraint (false), + marked_failed (true), sweep_incomplete (false), citten (0), + num_assigned (0), proof (0), opts (this), +#ifndef CADICAL_QUIET + profiles (this), force_phase_messages (false), +#endif + arena (this), prefix ("c "), internal (this), external (0), + termination_forced (false), vars (this->max_var), + lits (this->max_var) { + control.push_back (Level (0, 0)); + + // The 'dummy_binary' is used in 'try_to_subsume_clause' to fake a real + // clause, which then can be used to subsume or strengthen the given + // clause in one routine for both binary and non binary clauses. This + // fake binary clause is always kept non-redundant (and not-moved etc.) + // due to the following 'memset'. Only literals will be changed. + + // In a previous version we used local automatic allocated 'Clause' on the + // stack, which became incompatible with several compilers (see the + // discussion on flexible array member in 'Clause.cpp'). + + size_t bytes = Clause::bytes (2); + dummy_binary = (Clause *) new char[bytes]; + memset (dummy_binary, 0, bytes); + dummy_binary->size = 2; +} + +Internal::~Internal () { + // If a memory exception ocurred a profile might still be active. +#ifndef CADICAL_QUIET +#define PROFILE(NAME, LEVEL) \ + if (PROFILE_ACTIVE (NAME)) \ + STOP (NAME); + PROFILES +#undef PROFILE +#endif + delete[] (char *) dummy_binary; + for (const auto &c : clauses) + delete_clause (c); + if (proof) + delete proof; + for (auto &tracer : tracers) + delete tracer; + for (auto &filetracer : file_tracers) + delete filetracer; + for (auto &stattracer : stat_tracers) + delete stattracer; + if (vals) { + vals -= vsize; + delete[] vals; + } +} + +/*------------------------------------------------------------------------*/ + +// Values in 'vals' can be accessed in the range '[-max_var,max_var]' that +// is directly by a literal. This is crucial for performance. By shifting +// the start of 'vals' appropriately, we achieve that negative offsets from +// the start of 'vals' can be used. We also need to set both values at +// 'lit' and '-lit' during assignments. In MiniSAT integer literals are +// encoded, using the least significant bit as negation. This avoids taking +// the 'abs ()' (as in our solution) and thus also avoids a branch in the +// hot-spot of the solver (clause traversal in propagation). That solution +// requires another (branch less) negation of the values though and +// debugging is harder since literals occur only encoded in clauses. +// The main draw-back of our solution is that we have to shift the memory +// and access it through negative indices, which looks less clean (but still +// as far I can tell is properly defined C / C++). You might get a warning +// by static analyzers though. Clang with '--analyze' thought that this +// idiom would generate a memory leak thus we use the following dummy. + +static signed char *ignore_clang_analyze_memory_leak_warning; + +void Internal::enlarge_vals (size_t new_vsize) { + signed char *new_vals; + const size_t bytes = 2u * new_vsize; + new_vals = new signed char[bytes]; // g++-4.8 does not like ... { 0 }; + memset (new_vals, 0, bytes); + ignore_clang_analyze_memory_leak_warning = new_vals; + new_vals += new_vsize; + + if (vals) { + memcpy (new_vals - max_var, vals - max_var, 2u * max_var + 1u); + vals -= vsize; + delete[] vals; + } else + CADICAL_assert (!vsize); + vals = new_vals; +} + +/*------------------------------------------------------------------------*/ + +void Internal::enlarge (int new_max_var) { + // New variables can be created that can invoke enlarge anytime (via calls + // during ipasir-up call-backs), thus assuming (!level) is not correct + size_t new_vsize = vsize ? 2 * vsize : 1 + (size_t) new_max_var; + while (new_vsize <= (size_t) new_max_var) + new_vsize *= 2; + LOG ("enlarge internal size from %zd to new size %zd", vsize, new_vsize); + // Ordered in the size of allocated memory (larger block first). + if (lrat || frat) + enlarge_zero (unit_clauses_idx, 2 * new_vsize); + enlarge_only (wtab, 2 * new_vsize); + enlarge_only (vtab, new_vsize); + enlarge_zero (parents, new_vsize); + enlarge_only (links, new_vsize); + enlarge_zero (btab, new_vsize); + enlarge_zero (gtab, new_vsize); + enlarge_zero (stab, new_vsize); + enlarge_init (ptab, 2 * new_vsize, -1); + enlarge_only (ftab, new_vsize); + enlarge_vals (new_vsize); + vsize = new_vsize; + if (external) + enlarge_zero (relevanttab, new_vsize); + const signed char val = opts.phase ? 1 : -1; + enlarge_init (phases.saved, new_vsize, val); + enlarge_zero (phases.forced, new_vsize); + enlarge_zero (phases.target, new_vsize); + enlarge_zero (phases.best, new_vsize); + enlarge_zero (phases.prev, new_vsize); + enlarge_zero (phases.min, new_vsize); + enlarge_zero (marks, new_vsize); +} + +void Internal::init_vars (int new_max_var) { + if (new_max_var <= max_var) + return; + // New variables can be created that can invoke enlarge anytime (via calls + // during ipasir-up call-backs), thus assuming (!level) is not correct + LOG ("initializing %d internal variables from %d to %d", + new_max_var - max_var, max_var + 1, new_max_var); + if ((size_t) new_max_var >= vsize) + enlarge (new_max_var); +#ifndef CADICAL_NDEBUG + for (int64_t i = -new_max_var; i < -max_var; i++) + CADICAL_assert (!vals[i]); + for (unsigned i = max_var + 1; i <= (unsigned) new_max_var; i++) + CADICAL_assert (!vals[i]), CADICAL_assert (!btab[i]), CADICAL_assert (!gtab[i]); + for (uint64_t i = 2 * ((uint64_t) max_var + 1); + i <= 2 * (uint64_t) new_max_var + 1; i++) + CADICAL_assert (ptab[i] == -1); +#endif + CADICAL_assert (!btab[0]); + int old_max_var = max_var; + max_var = new_max_var; + init_queue (old_max_var, new_max_var); + init_scores (old_max_var, new_max_var); + int initialized = new_max_var - old_max_var; + stats.vars += initialized; + stats.unused += initialized; + stats.inactive += initialized; + LOG ("finished initializing %d internal variables", initialized); +} + +void Internal::add_original_lit (int lit) { + CADICAL_assert (abs (lit) <= max_var); + if (lit) { + original.push_back (lit); + } else { + const int64_t id = + original_id < reserved_ids ? ++original_id : ++clause_id; + if (proof) { + // Use the external form of the clause for printing in proof + // Externalize(internalized literal) != external literal + CADICAL_assert (!original.size () || !external->eclause.empty ()); + proof->add_external_original_clause (id, false, external->eclause); + } + if (internal->opts.check && + (internal->opts.checkwitness || internal->opts.checkfailed)) { + bool forgettable = from_propagator && ext_clause_forgettable; + if (forgettable && opts.check) { + CADICAL_assert (!original.size () || !external->eclause.empty ()); + + // First integer is the presence-flag (even if the clause is empty) + external->forgettable_original[id] = {1}; + + for (auto const &elit : external->eclause) + external->forgettable_original[id].push_back (elit); + + LOG (external->eclause, + "clause added to external forgettable map:"); + } + } + + add_new_original_clause (id); + original.clear (); + } +} + +void Internal::finish_added_clause_with_id (int64_t id, bool restore) { + if (proof) { + // Use the external form of the clause for printing in proof + // Externalize(internalized literal) != external literal + CADICAL_assert (!original.size () || !external->eclause.empty ()); + proof->add_external_original_clause (id, false, external->eclause, + restore); + } + add_new_original_clause (id); + original.clear (); +} + +/*------------------------------------------------------------------------*/ + +void Internal::reserve_ids (int number) { + // return; + LOG ("reserving %d ids", number); + CADICAL_assert (number >= 0); + CADICAL_assert (!clause_id && !reserved_ids && !original_id); + clause_id = reserved_ids = number; + if (proof) + proof->begin_proof (reserved_ids); +} + +/*------------------------------------------------------------------------*/ + +#ifdef PROFILE_MODE + +// Separating these makes it easier to profile stable and unstable search. + +bool Internal::propagate_wrapper () { + if (stable) + return propagate_stable (); + else + return propagate_unstable (); +} + +void Internal::analyze_wrapper () { + if (stable) + analyze_stable (); + else + analyze_unstable (); +} + +int Internal::decide_wrapper () { + if (stable) + return decide_stable (); + else + return decide_unstable (); +} + +#endif + +/*------------------------------------------------------------------------*/ + +// This is the main CDCL loop with interleaved inprocessing. + +int Internal::cdcl_loop_with_inprocessing () { + + int res = 0; + + START (search); + + if (stable) { + START (stable); + report ('['); + } else { + START (unstable); + report ('{'); + } + + while (!res) { + if (unsat) + res = 20; + else if (unsat_constraint) + res = 20; + else if (!propagate_wrapper ()) + analyze_wrapper (); // propagate and analyze + else if (iterating) + iterate (); // report learned unit + else if (!external_propagate () || unsat) { // external propagation + if (unsat) + continue; + else + analyze (); + } else if (satisfied ()) { // found model + if (!external_check_solution () || unsat) { + if (unsat) + continue; + else + analyze (); + } else if (satisfied ()) + res = 10; + } else if (search_limits_hit ()) + break; // decision or conflict limit + else if (terminated_asynchronously ()) // externally terminated + break; + else if (restarting ()) + restart (); // restart by backtracking + else if (rephasing ()) + rephase (); // reset variable phases + else if (reducing ()) + reduce (); // collect useless clauses + else if (inprobing ()) + inprobe (); // schedule of inprocessing + else if (ineliminating ()) + elim (); // variable elimination + else if (compacting ()) + compact (); // collect variables + else if (conditioning ()) + condition (); // globally blocked clauses + else + res = decide (); // next decision + } + + if (stable) { + STOP (stable); + report (']'); + } else { + STOP (unstable); + report ('}'); + } + + STOP (search); + + return res; +} + +int Internal::propagate_assumptions () { + if (proof) + proof->solve_query (); + if (opts.ilb) { + if (opts.ilbassumptions) + sort_and_reuse_assumptions (); + stats.ilbtriggers++; + stats.ilbsuccess += (level > 0); + stats.levelsreused += level; + if (level) { + CADICAL_assert (control.size () > 1); + stats.literalsreused += num_assigned - control[1].trail; + } + } + init_search_limits (); + init_report_limits (); + + int res = already_solved (); // root-level propagation is done here + + int last_assumption_level = assumptions.size (); + if (constraint.size ()) + last_assumption_level++; + + if (!res) { + restore_clauses (); + while (!res) { + if (unsat) + res = 20; + else if (unsat_constraint) + res = 20; + else if (!propagate ()) { + // let analyze run to get failed assumptions + analyze (); + } else if (!external_propagate () || unsat) { // external propagation + if (unsat) + continue; + else + analyze (); + } else if (satisfied ()) { // found model + if (!external_check_solution () || unsat) { + if (unsat) + continue; + else + analyze (); + } else if (satisfied ()) + res = 10; + } else if (search_limits_hit ()) + break; // decision or conflict limit + else if (terminated_asynchronously ()) // externally terminated + break; + else { + if (level >= last_assumption_level) + break; + res = decide (); + } + } + } + + if (unsat || unsat_constraint) + res = 20; + + if (!res && satisfied ()) + res = 10; + + finalize (res); + reset_solving (); + report_solving (res); + + return res; +} + +void Internal::implied (std::vector &entrailed) { + int last_assumption_level = assumptions.size (); + if (constraint.size ()) + last_assumption_level++; + + size_t trail_limit = trail.size(); + if (level > last_assumption_level) + trail_limit = control[last_assumption_level + 1].trail; + + for (size_t i = 0; i < trail_limit; i++) + entrailed.push_back (trail[i]); +} + +/*------------------------------------------------------------------------*/ + +// Most of the limits are only initialized in the first 'solve' call and +// increased as in a stand-alone non-incremental SAT call except for those +// explicitly marked as being reset below. + +void Internal::init_report_limits () { + reported = false; + lim.report = 0; + lim.recompute_tier = 5000; +} + +void Internal::init_preprocessing_limits () { + + const bool incremental = lim.initialized; + if (incremental) + LOG ("reinitializing preprocessing limits incrementally"); + else + LOG ("initializing preprocessing limits and increments"); + + const char *mode = 0; + + /*----------------------------------------------------------------------*/ + + if (incremental) + mode = "keeping"; + else { + last.elim.marked = -1; + lim.elim = stats.conflicts + scale (opts.elimint); + mode = "initial"; + } + (void) mode; + LOG ("%s elim limit %" PRId64 " after %" PRId64 " conflicts", mode, + lim.elim, lim.elim - stats.conflicts); + + // Initialize and reset elimination bounds in any case. + + lim.elimbound = opts.elimboundmin; + LOG ("elimination bound %" PRId64 "", lim.elimbound); + + /*----------------------------------------------------------------------*/ + + if (!incremental) { + + last.ternary.marked = -1; // TODO this should not be necessary... + + lim.compact = stats.conflicts + opts.compactint; + LOG ("initial compact limit %" PRId64 " increment %" PRId64 "", + lim.compact, lim.compact - stats.conflicts); + } + + /*----------------------------------------------------------------------*/ + + if (incremental) + mode = "keeping"; + else { + double delta = log10 (stats.added.irredundant); + delta = delta * delta; + lim.inprobe = stats.conflicts + opts.inprobeint * delta; + mode = "initial"; + } + (void) mode; + LOG ("%s probe limit %" PRId64 " after %" PRId64 " conflicts", mode, + lim.inprobe, lim.inprobe - stats.conflicts); + + /*----------------------------------------------------------------------*/ + + if (incremental) + mode = "keeping"; + else { + lim.condition = stats.conflicts + opts.conditionint; + mode = "initial"; + } + LOG ("%s condition limit %" PRId64 " increment %" PRId64, mode, + lim.condition, lim.condition - stats.conflicts); + + /*----------------------------------------------------------------------*/ + + // Initial preprocessing rounds. + + if (inc.preprocessing <= 0) { + lim.preprocessing = 0; + LOG ("no preprocessing"); + } else { + lim.preprocessing = inc.preprocessing; + LOG ("limiting to %" PRId64 " preprocessing rounds", lim.preprocessing); + } +} + +void Internal::init_search_limits () { + + const bool incremental = lim.initialized; + if (incremental) + LOG ("reinitializing search limits incrementally"); + else + LOG ("initializing search limits and increments"); + + const char *mode = 0; + + /*----------------------------------------------------------------------*/ + + if (incremental) + mode = "keeping"; + else { + last.reduce.conflicts = -1; + lim.reduce = stats.conflicts + opts.reduceinit; + mode = "initial"; + } + (void) mode; + LOG ("%s reduce limit %" PRId64 " after %" PRId64 " conflicts", mode, + lim.reduce, lim.reduce - stats.conflicts); + + /*----------------------------------------------------------------------*/ + + if (incremental) + mode = "keeping"; + else { + lim.flush = opts.flushint; + inc.flush = opts.flushint; + mode = "initial"; + } + (void) mode; + LOG ("%s flush limit %" PRId64 " interval %" PRId64 "", mode, lim.flush, + inc.flush); + + /*----------------------------------------------------------------------*/ + + // Initialize or reset 'rephase' limits in any case. + + lim.rephase = stats.conflicts + opts.rephaseint; + lim.rephased[0] = lim.rephased[1] = 0; + LOG ("new rephase limit %" PRId64 " after %" PRId64 " conflicts", + lim.rephase, lim.rephase - stats.conflicts); + + /*----------------------------------------------------------------------*/ + + // Initialize or reset 'restart' limits in any case. + + lim.restart = stats.conflicts + opts.restartint; + LOG ("new restart limit %" PRId64 " increment %" PRId64 "", lim.restart, + lim.restart - stats.conflicts); + + /*----------------------------------------------------------------------*/ + + if (!incremental) { + stable = opts.stabilize && opts.stabilizeonly; + if (stable) + LOG ("starting in always forced stable phase"); + else + LOG ("starting in default non-stable phase"); + init_averages (); + } else if (opts.stabilize && opts.stabilizeonly) { + LOG ("keeping always forced stable phase"); + CADICAL_assert (stable); + } else if (stable) { + LOG ("switching back to default non-stable phase"); + stable = false; + swap_averages (); + } else + LOG ("keeping non-stable phase"); + + if (!incremental) { + inc.stabilize = 0; + lim.stabilize = stats.conflicts + opts.stabilizeinit; + LOG ("initial stabilize limit %" PRId64 " after %d conflicts", + lim.stabilize, (int) opts.stabilizeinit); + } + + if (opts.stabilize && opts.reluctant) { + LOG ("new restart reluctant doubling sequence period %d", + opts.reluctant); + reluctant.enable (opts.reluctant, opts.reluctantmax); + } else + reluctant.disable (); + + /*----------------------------------------------------------------------*/ + + // Conflict and decision limits. + + if (inc.conflicts < 0) { + lim.conflicts = -1; + LOG ("no limit on conflicts"); + } else { + lim.conflicts = stats.conflicts + inc.conflicts; + LOG ("conflict limit after %" PRId64 " conflicts at %" PRId64 + " conflicts", + inc.conflicts, lim.conflicts); + } + + if (inc.decisions < 0) { + lim.decisions = -1; + LOG ("no limit on decisions"); + } else { + lim.decisions = stats.decisions + inc.decisions; + LOG ("conflict limit after %" PRId64 " decisions at %" PRId64 + " decisions", + inc.decisions, lim.decisions); + } + + /*----------------------------------------------------------------------*/ + + // Initial preprocessing rounds. + + if (inc.localsearch <= 0) { + lim.localsearch = 0; + LOG ("no local search"); + } else { + lim.localsearch = inc.localsearch; + LOG ("limiting to %" PRId64 " local search rounds", lim.localsearch); + } + + /*----------------------------------------------------------------------*/ + + lim.initialized = true; +} + +/*------------------------------------------------------------------------*/ + +bool Internal::preprocess_round (int round) { + (void) round; + if (unsat) + return false; + if (!max_var) + return false; + START (preprocess); + struct { + int64_t vars, clauses; + } before, after; + before.vars = active (); + before.clauses = stats.current.irredundant; + stats.preprocessings++; + CADICAL_assert (!preprocessing); + preprocessing = true; + PHASE ("preprocessing", stats.preprocessings, + "starting round %d with %" PRId64 " variables and %" PRId64 + " clauses", + round, before.vars, before.clauses); + int old_elimbound = lim.elimbound; + if (opts.inprobing) + inprobe (false); + if (opts.elim) + elim (false); + if (opts.condition) + condition (false); + + after.vars = active (); + after.clauses = stats.current.irredundant; + CADICAL_assert (preprocessing); + preprocessing = false; + PHASE ("preprocessing", stats.preprocessings, + "finished round %d with %" PRId64 " variables and %" PRId64 + " clauses", + round, after.vars, after.clauses); + STOP (preprocess); + report ('P'); + if (unsat) + return false; + if (after.vars < before.vars) + return true; + if (old_elimbound < lim.elimbound) + return true; + return false; +} + +// for now counts as one of the preprocessing rounds TODO: change this? +void Internal::preprocess_quickly () { + if (unsat) + return; + if (!max_var) + return; + if (!opts.preprocesslight) + return; + START (preprocess); + struct { + int64_t vars, clauses; + } before, after; + before.vars = active (); + before.clauses = stats.current.irredundant; + // stats.preprocessings++; + CADICAL_assert (!preprocessing); + preprocessing = true; + PHASE ("preprocessing", stats.preprocessings, + "starting with %" PRId64 " variables and %" PRId64 " clauses", + before.vars, before.clauses); + + if (extract_gates ()) + decompose (); + + if (sweep ()) + decompose (); + + if (opts.factor) + factor (); + + if (opts.fastelim) + elimfast (); + // if (opts.condition) + // condition (false); + after.vars = active (); + after.clauses = stats.current.irredundant; + CADICAL_assert (preprocessing); + preprocessing = false; + PHASE ("preprocessing", stats.preprocessings, + "finished with %" PRId64 " variables and %" PRId64 " clauses", + after.vars, after.clauses); + STOP (preprocess); + report ('P'); +} + +int Internal::preprocess () { + preprocess_quickly (); + for (int i = 0; i < lim.preprocessing; i++) + if (!preprocess_round (i)) + break; + if (unsat) + return 20; + return 0; +} + +/*------------------------------------------------------------------------*/ + +int Internal::try_to_satisfy_formula_by_saved_phases () { + LOG ("satisfying formula by saved phases"); + CADICAL_assert (!level); + CADICAL_assert (!force_saved_phase); + CADICAL_assert (propagated == trail.size ()); + force_saved_phase = true; + if (external_prop) { + CADICAL_assert (!level); + LOG ("external notifications are turned off during preprocessing."); + private_steps = true; + } + int res = 0; + while (!res) { + if (satisfied ()) { + LOG ("formula indeed satisfied by saved phases"); + res = 10; + } else if (decide ()) { + LOG ("inconsistent assumptions with redundant clauses and phases"); + res = 20; + } else if (!propagate ()) { + LOG ("saved phases do not satisfy redundant clauses"); + CADICAL_assert (level > 0); + backtrack (); + conflict = 0; // ignore conflict + CADICAL_assert (!res); + break; + } + } + CADICAL_assert (force_saved_phase); + force_saved_phase = false; + if (external_prop) { + private_steps = false; + LOG ("external notifications are turned back on."); + if (!level) + notify_assignments (); // In case fixed assignments were found. + else { + renotify_trail_after_local_search (); + } + } + return res; +} + +/*------------------------------------------------------------------------*/ + +void Internal::produce_failed_assumptions () { + LOG ("producing failed assumptions"); + CADICAL_assert (!level); + CADICAL_assert (!assumptions.empty ()); + while (!unsat) { + CADICAL_assert (!satisfied ()); + notify_assignments (); + if (decide ()) + break; + while (!unsat && !propagate ()) + analyze (); + } + notify_assignments (); + if (unsat) + LOG ("formula is actually unsatisfiable unconditionally"); + else + LOG ("assumptions indeed failing"); +} + +/*------------------------------------------------------------------------*/ + +int Internal::local_search_round (int round) { + + CADICAL_assert (round > 0); + + if (unsat) + return false; + if (!max_var) + return false; + + START_OUTER_WALK (); + CADICAL_assert (!localsearching); + localsearching = true; + + // Determine propagation limit quadratically scaled with rounds. + // + int64_t limit = opts.walkmineff; + limit *= round; + if (LONG_MAX / round > limit) + limit *= round; + else + limit = LONG_MAX; + + int res = walk_round (limit, true); + + CADICAL_assert (localsearching); + localsearching = false; + STOP_OUTER_WALK (); + + report ('L'); + + return res; +} + +int Internal::local_search () { + + if (unsat) + return 0; + if (!max_var) + return 0; + if (!opts.walk) + return 0; + if (constraint.size ()) + return 0; + + int res = 0; + + for (int i = 1; !res && i <= lim.localsearch; i++) + res = local_search_round (i); + + if (res == 10) { + LOG ("local search determined formula to be satisfiable"); + CADICAL_assert (!stats.walk.minimum); + res = try_to_satisfy_formula_by_saved_phases (); + } else if (res == 20) { + LOG ("local search determined assumptions to be inconsistent"); + CADICAL_assert (!assumptions.empty ()); + produce_failed_assumptions (); + } + + return res; +} + +/*------------------------------------------------------------------------*/ + +// if preprocess_only is false and opts.ilb is true we do not preprocess +// such that we do not have to backtrack to level 0. +// +int Internal::solve (bool preprocess_only) { + CADICAL_assert (clause.empty ()); + START (solve); + if (proof) + proof->solve_query (); + if (opts.ilb) { + if (opts.ilbassumptions) + sort_and_reuse_assumptions (); + stats.ilbtriggers++; + stats.ilbsuccess += (level > 0); + stats.levelsreused += level; + if (level) { + CADICAL_assert (control.size () > 1); + stats.literalsreused += num_assigned - control[1].trail; + } + if (external->propagator) + renotify_trail_after_ilb (); + } + if (preprocess_only) + LOG ("internal solving in preprocessing only mode"); + else + LOG ("internal solving in full mode"); + init_report_limits (); + int res = already_solved (); + if (!res && preprocess_only && level) + backtrack (); + if (!res) + res = restore_clauses (); + if (!res || (res == 10 && external_prop)) { + init_preprocessing_limits (); + if (!preprocess_only) + init_search_limits (); + } + if (!preprocess_only) { + if (!res && !level) + res = local_search (); + } + if (!res && !level) + res = preprocess (); + if (!preprocess_only) { + if (!res && !level) + res = local_search (); + if (!res && !level) + res = lucky_phases (); + if (!res || (res == 10 && external_prop)) { + if (res == 10 && external_prop && level) + backtrack (); + res = cdcl_loop_with_inprocessing (); + } + } + finalize (res); + reset_solving (); + report_solving (res); + STOP (solve); + return res; +} + +int Internal::already_solved () { + int res = 0; + if (unsat || unsat_constraint) { + LOG ("already inconsistent"); + res = 20; + } else { + if (level && !opts.ilb) + backtrack (); + if (!level && !propagate ()) { + LOG ("root level propagation produces conflict"); + learn_empty_clause (); + res = 20; + } + if (max_var == 0 && res == 0) + res = 10; + } + return res; +} +void Internal::report_solving (int res) { + if (res == 10) + report ('1'); + else if (res == 20) + report ('0'); + else + report ('?'); +} + +void Internal::reset_solving () { + if (termination_forced) { + + // TODO this leads potentially to a data race if the external + // user is calling 'terminate' twice within one 'solve' call. + // A proper solution would be to guard / protect setting the + // 'termination_forced' flag and only allow it during solving and + // ignore it otherwise thus also the second time it is called during a + // 'solve' call. We could move resetting it also the start of + // 'solve'. + // + termination_forced = false; + + LOG ("reset forced termination"); + } +} + +int Internal::restore_clauses () { + int res = 0; + if (opts.restoreall <= 1 && external->tainted.empty ()) { + LOG ("no tainted literals and nothing to restore"); + report ('*'); + } else { + report ('+'); + // remove_garbage_binaries (); + external->restore_clauses (); + internal->report ('r'); + if (!unsat && !level && !propagate ()) { + LOG ("root level propagation after restore produces conflict"); + learn_empty_clause (); + res = 20; + } + } + return res; +} + +int Internal::lookahead () { + CADICAL_assert (clause.empty ()); + START (lookahead); + CADICAL_assert (!lookingahead); + lookingahead = true; + if (external_prop) { + if (level) { + // Combining lookahead with external propagator is limited + // Note that lookahead_probing (); would also force backtrack anyway + backtrack (); + } + LOG ("external notifications are turned off during preprocessing."); + private_steps = true; + } + int tmp = already_solved (); + if (!tmp) + tmp = restore_clauses (); + int res = 0; + if (!tmp) + res = lookahead_probing (); + if (res == INT_MIN) + res = 0; + reset_solving (); + report_solving (tmp); + CADICAL_assert (lookingahead); + lookingahead = false; + STOP (lookahead); + if (external_prop) { + private_steps = false; + LOG ("external notifications are turned back on."); + notify_assignments (); // In case fixed assignments were found. + } + return res; +} + +/*------------------------------------------------------------------------*/ + +void Internal::finalize (int res) { + if (!proof) + return; + LOG ("finalizing"); + // finalize external units + if (frat) { + for (const auto &evar : external->vars) { + CADICAL_assert (evar > 0); + const auto eidx = 2 * evar; + int sign = 1; + int64_t id = external->ext_units[eidx]; + if (!id) { + sign = -1; + id = external->ext_units[eidx + 1]; + } + if (id) { + proof->finalize_external_unit (id, evar * sign); + } + } + // finalize internal units + for (const auto &lit : lits) { + const auto elit = externalize (lit); + if (elit) { + const unsigned eidx = (elit < 0) + 2u * (unsigned) abs (elit); + const int64_t id = external->ext_units[eidx]; + if (id) { + CADICAL_assert (unit_clauses (vlit (lit)) == id); + continue; + } + } + const int64_t id = unit_clauses (vlit (lit)); + if (!id) + continue; + proof->finalize_unit (id, lit); + } + // See the discussion in 'propagate' on why garbage binary clauses stick + // around. + for (const auto &c : clauses) + if (!c->garbage || (c->size == 2 && !c->flushed)) + proof->finalize_clause (c); + + // finalize conflict and proof + if (conflict_id) { + proof->finalize_clause (conflict_id, {}); + } + } + proof->report_status (res, conflict_id); + if (res == 10) + external->conclude_sat (); + else if (res == 20) + conclude_unsat (); + else if (!res) + external->conclude_unknown (); +} + +/*------------------------------------------------------------------------*/ + +void Internal::print_statistics () { + stats.print (this); + for (auto &st : stat_tracers) + st->print_stats (); +} + +/*------------------------------------------------------------------------*/ + +// Only useful for debugging purposes. + +void Internal::dump (Clause *c) { + for (const auto &lit : *c) + printf ("%d ", lit); + printf ("0\n"); +} + +void Internal::dump () { + int64_t m = assumptions.size (); + for (auto idx : vars) + if (fixed (idx)) + m++; + for (const auto &c : clauses) + if (!c->garbage) + m++; + printf ("p cnf %d %" PRId64 "\n", max_var, m); + for (auto idx : vars) { + const int tmp = fixed (idx); + if (tmp) + printf ("%d 0\n", tmp < 0 ? -idx : idx); + } + for (const auto &c : clauses) + if (!c->garbage) + dump (c); + for (const auto &lit : assumptions) + printf ("%d 0\n", lit); + fflush (stdout); +} + +/*------------------------------------------------------------------------*/ + +bool Internal::traverse_constraint (ClauseIterator &it) { + if (constraint.empty () && !unsat_constraint) + return true; + + vector eclause; + if (unsat) + return it.clause (eclause); + + LOG (constraint, "traversing constraint"); + bool satisfied = false; + for (auto ilit : constraint) { + const int tmp = fixed (ilit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + const int elit = externalize (ilit); + eclause.push_back (elit); + } + if (!satisfied && !it.clause (eclause)) + return false; + + return true; +} +/*------------------------------------------------------------------------*/ + +bool Internal::traverse_clauses (ClauseIterator &it) { + vector eclause; + if (unsat) + return it.clause (eclause); + for (const auto &c : clauses) { + if (c->garbage) + continue; + if (c->redundant) + continue; + bool satisfied = false; + for (const auto &ilit : *c) { + const int tmp = fixed (ilit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + const int elit = externalize (ilit); + eclause.push_back (elit); + } + if (!satisfied && !it.clause (eclause)) + return false; + eclause.clear (); + } + return true; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_ipasir.cpp b/src/sat/cadical/cadical_ipasir.cpp new file mode 100644 index 000000000..cb88deb4b --- /dev/null +++ b/src/sat/cadical/cadical_ipasir.cpp @@ -0,0 +1,46 @@ +#include "global.h" + +#include "ipasir.h" +#include "ccadical.h" + +ABC_NAMESPACE_IMPL_START + +const char *ipasir_signature () { return ccadical_signature (); } + +void *ipasir_init () { return ccadical_init (); } + +void ipasir_release (void *solver) { + ccadical_release ((CCaDiCaL *) solver); +} + +void ipasir_add (void *solver, int lit) { + ccadical_add ((CCaDiCaL *) solver, lit); +} + +void ipasir_assume (void *solver, int lit) { + ccadical_assume ((CCaDiCaL *) solver, lit); +} + +int ipasir_solve (void *solver) { + return ccadical_solve ((CCaDiCaL *) solver); +} + +int ipasir_val (void *solver, int lit) { + return ccadical_val ((CCaDiCaL *) solver, lit); +} + +int ipasir_failed (void *solver, int lit) { + return ccadical_failed ((CCaDiCaL *) solver, lit); +} + +void ipasir_set_terminate (void *solver, void *state, + int (*terminate) (void *state)) { + ccadical_set_terminate ((CCaDiCaL *) solver, state, terminate); +} + +void ipasir_set_learn (void *solver, void *state, int max_length, + void (*learn) (void *state, int *clause)) { + ccadical_set_learn ((CCaDiCaL *) solver, state, max_length, learn); +} + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_kitten.c b/src/sat/cadical/cadical_kitten.c new file mode 100644 index 000000000..da3738980 --- /dev/null +++ b/src/sat/cadical/cadical_kitten.c @@ -0,0 +1,2609 @@ +#include "global.h" + +#include "kitten.h" +#include "random.h" +#include "stack.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ABC_NAMESPACE_IMPL_START + +typedef signed char value; + +static void die (const char *fmt, ...) { + fputs ("cadical_kitten: error: ", stderr); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); + exit (1); +} + +static inline void *cadical_kitten_calloc (size_t n, size_t size) { + void *res = calloc (n, size); + if (n && size && !res) + die ("out of memory allocating '%zu * %zu' bytes", n, size); + return res; +} + +#define CALLOC(T, P, N) \ + do { \ + (P) = (T*)cadical_kitten_calloc (N, sizeof *(P)); \ + } while (0) +#define DEALLOC(P, N) free (P) + +#undef ENLARGE_STACK + +#define ENLARGE_STACK(S) \ + do { \ + CADICAL_assert (FULL_STACK (S)); \ + const size_t SIZE = SIZE_STACK (S); \ + const size_t OLD_CAPACITY = CAPACITY_STACK (S); \ + const size_t NEW_CAPACITY = OLD_CAPACITY ? 2 * OLD_CAPACITY : 1; \ + const size_t BYTES = NEW_CAPACITY * sizeof *(S).begin; \ + (S).begin = (unsigned*)realloc ((S).begin, BYTES); \ + if (!(S).begin) \ + die ("out of memory reallocating '%zu' bytes", BYTES); \ + (S).allocated = (S).begin + NEW_CAPACITY; \ + (S).end = (S).begin + SIZE; \ + } while (0) + +// Beside allocators above also use stand alone statistics counters. + +#define INC(NAME) \ + do { \ + statistics *statistics = &cadical_kitten->statistics; \ + CADICAL_assert (statistics->NAME < UINT64_MAX); \ + statistics->NAME++; \ + } while (0) + +#define ADD(NAME, DELTA) \ + do { \ + statistics *statistics = &cadical_kitten->statistics; \ + CADICAL_assert (statistics->NAME <= UINT64_MAX - (DELTA)); \ + statistics->NAME += (DELTA); \ + } while (0) + +#define KITTEN_TICKS (cadical_kitten->statistics.cadical_kitten_ticks) + +#define INVALID UINT_MAX +#define MAX_VARS ((1u << 31) - 1) + +#define CORE_FLAG (1u) +#define LEARNED_FLAG (2u) + +// clang-format off + +typedef struct kar kar; +typedef struct kink kink; +typedef struct klause klause; +typedef STACK (unsigned) klauses; +typedef unsigneds katches; + +// clang-format on + +struct kar { + unsigned level; + unsigned reason; +}; + +struct kink { + unsigned next; + unsigned prev; + uint64_t stamp; +}; + +struct klause { + unsigned aux; + unsigned size; + unsigned flags; + unsigned lits[1]; +}; + +typedef struct statistics statistics; + +struct statistics { + uint64_t learned; + uint64_t original; + uint64_t cadical_kitten_flip; + uint64_t cadical_kitten_flipped; + uint64_t cadical_kitten_sat; + uint64_t cadical_kitten_solved; + uint64_t cadical_kitten_conflicts; + uint64_t cadical_kitten_decisions; + uint64_t cadical_kitten_propagations; + uint64_t cadical_kitten_ticks; + uint64_t cadical_kitten_unknown; + uint64_t cadical_kitten_unsat; +}; + +typedef struct kimits kimits; + +struct kimits { + uint64_t ticks; +}; + +struct cadical_kitten { + // First zero initialized field in 'clear_cadical_kitten' is 'status'. + // + int status; + +#if defined(LOGGING) + bool logging; +#endif + bool antecedents; + bool learned; + + unsigned level; + unsigned propagated; + unsigned unassigned; + unsigned inconsistent; + unsigned failing; + + uint64_t generator; + + size_t lits; + size_t evars; + + size_t end_original_ref; + + struct { + unsigned first, last; + uint64_t stamp; + unsigned search; + } queue; + + // The 'size' field below is the first not zero reinitialized field + // by 'memset' in 'clear_cadical_kitten' (after 'kissat'). + + size_t size; + size_t esize; + + kar *vars; + kink *links; + value *marks; + value *values; + bool *failed; + unsigned char *phases; + unsigned *import; + katches *watches; + + unsigneds analyzed; + unsigneds assumptions; + unsigneds core; + unsigneds rcore; + unsigneds eclause; + unsigneds export_; + unsigneds klause; + unsigneds klauses; + unsigneds resolved; + unsigneds trail; + unsigneds units; + unsigneds prime[2]; + + kimits limits; + int (*terminator) (void *); + void *terminator_data; + unsigneds clause; + uint64_t initialized; + statistics statistics; +}; + +/*------------------------------------------------------------------------*/ + +static inline bool is_core_klause (klause *c) { + return c->flags & CORE_FLAG; +} + +static inline bool is_learned_klause (klause *c) { + return c->flags & LEARNED_FLAG; +} + +static inline void set_core_klause (klause *c) { c->flags |= CORE_FLAG; } + +static inline void unset_core_klause (klause *c) { c->flags &= ~CORE_FLAG; } + +static inline klause *dereference_klause (cadical_kitten *cadical_kitten, unsigned ref) { + unsigned *res = BEGIN_STACK (cadical_kitten->klauses) + ref; + CADICAL_assert (res < END_STACK (cadical_kitten->klauses)); + return (klause *) res; +} + +static inline unsigned reference_klause (cadical_kitten *cadical_kitten, const klause *c) { + const unsigned *const begin = BEGIN_STACK (cadical_kitten->klauses); + const unsigned *p = (const unsigned *) c; + CADICAL_assert (begin <= p); + CADICAL_assert (p < END_STACK (cadical_kitten->klauses)); + const unsigned res = p - begin; + return res; +} + +/*------------------------------------------------------------------------*/ + +#define KATCHES(KIT) (cadical_kitten->watches[CADICAL_assert ((KIT) < cadical_kitten->lits), (KIT)]) + +#define all_klauses(C) \ + klause *C = begin_klauses (cadical_kitten), *end_##C = end_klauses (cadical_kitten); \ + (C) != end_##C; \ + (C) = next_klause (cadical_kitten, C) + +#define all_original_klauses(C) \ + klause *C = begin_klauses (cadical_kitten), \ + *end_##C = end_original_klauses (cadical_kitten); \ + (C) != end_##C; \ + (C) = next_klause (cadical_kitten, C) + +#define all_learned_klauses(C) \ + klause *C = begin_learned_klauses (cadical_kitten), \ + *end_##C = end_klauses (cadical_kitten); \ + (C) != end_##C; \ + (C) = next_klause (cadical_kitten, C) + +#define all_kits(KIT) \ + size_t KIT = 0, KIT_##END = cadical_kitten->lits; \ + KIT != KIT_##END; \ + KIT++ + +#define BEGIN_KLAUSE(C) (C)->lits + +#define END_KLAUSE(C) (BEGIN_KLAUSE (C) + (C)->size) + +#define all_literals_in_klause(KIT, C) \ + unsigned KIT, *KIT##_PTR = BEGIN_KLAUSE (C), \ + *KIT##_END = END_KLAUSE (C); \ + KIT##_PTR != KIT##_END && ((KIT = *KIT##_PTR), true); \ + ++KIT##_PTR + +#define all_antecedents(REF, C) \ + unsigned REF, *REF##_PTR = antecedents (C), \ + *REF##_END = REF##_PTR + (C)->aux; \ + REF##_PTR != REF##_END && ((REF = *REF##_PTR), true); \ + ++REF##_PTR + +#ifdef LOGGING + +#define logging (cadical_kitten->logging) + +static void log_basic (cadical_kitten *, const char *, ...) + __attribute__ ((format (printf, 2, 3))); + +static void log_basic (cadical_kitten *cadical_kitten, const char *fmt, ...) { + CADICAL_assert (logging); + printf ("c KITTEN %u ", cadical_kitten->level); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + fflush (stdout); +} + +static void log_reference (cadical_kitten *, unsigned, const char *, ...) + __attribute__ ((format (printf, 3, 4))); + +static void log_reference (cadical_kitten *cadical_kitten, unsigned ref, const char *fmt, + ...) { + klause *c = dereference_klause (cadical_kitten, ref); + CADICAL_assert (logging); + printf ("c KITTEN %u ", cadical_kitten->level); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (is_learned_klause (c)) { + fputs (" learned", stdout); + if (c->aux) + printf ("[%u]", c->aux); + } else { + fputs (" original", stdout); + if (c->aux != INVALID) + printf ("[%u]", c->aux); + } + printf (" size %u clause[%u]", c->size, ref); + value *values = cadical_kitten->values; + kar *vars = cadical_kitten->vars; + for (all_literals_in_klause (lit, c)) { + printf (" %u", lit); + const value value = values[lit]; + if (value) + printf ("@%u=%d", vars[lit / 2].level, (int) value); + } + fputc ('\n', stdout); + fflush (stdout); +} + +static void log_literals (cadical_kitten *, unsigned *, unsigned, const char *, ...) + __attribute__ ((format (printf, 4, 5))); + +static void log_literals (cadical_kitten *cadical_kitten, unsigned *lits, unsigned size, + const char *fmt, ...) { + CADICAL_assert (logging); + printf ("c KITTEN %u ", cadical_kitten->level); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + value *values = cadical_kitten->values; + kar *vars = cadical_kitten->vars; + for (unsigned i = 0; i < size; i++) { + const unsigned lit = lits[i]; + printf (" %u", lit); + const value value = values[lit]; + if (value) + printf ("@%u=%d", vars[lit / 2].level, (int) value); + } + fputc ('\n', stdout); + fflush (stdout); +} + +#define LOG(...) \ + do { \ + if (logging) \ + log_basic (cadical_kitten, __VA_ARGS__); \ + } while (0) + +#define ROG(...) \ + do { \ + if (logging) \ + log_reference (cadical_kitten, __VA_ARGS__); \ + } while (0) + +#define LOGLITS(...) \ + do { \ + if (logging) \ + log_literals (cadical_kitten, __VA_ARGS__); \ + } while (0) + +#else + +#define LOG(...) \ + do { \ + } while (0) +#define ROG(...) \ + do { \ + } while (0) +#define LOGLITS(...) \ + do { \ + } while (0) + +#endif + +static void check_queue (cadical_kitten *cadical_kitten) { +#ifdef CHECK_KITTEN + const unsigned vars = cadical_kitten->lits / 2; + unsigned found = 0, prev = INVALID; + kink *links = cadical_kitten->links; + uint64_t stamp = 0; + for (unsigned idx = cadical_kitten->queue.first, next; idx != INVALID; + idx = next) { + kink *link = links + idx; + CADICAL_assert (link->prev == prev); + CADICAL_assert (!found || stamp < link->stamp); + CADICAL_assert (link->stamp < cadical_kitten->queue.stamp); + stamp = link->stamp; + next = link->next; + prev = idx; + found++; + } + CADICAL_assert (found == vars); + unsigned next = INVALID; + found = 0; + for (unsigned idx = cadical_kitten->queue.last, prev; idx != INVALID; + idx = prev) { + kink *link = links + idx; + CADICAL_assert (link->next == next); + prev = link->prev; + next = idx; + found++; + } + CADICAL_assert (found == vars); + value *values = cadical_kitten->values; + bool first = true; + for (unsigned idx = cadical_kitten->queue.search, next; idx != INVALID; + idx = next) { + kink *link = links + idx; + next = link->next; + const unsigned lit = 2 * idx; + CADICAL_assert (first || values[lit]); + first = false; + } +#else + (void) cadical_kitten; +#endif +} + +static void update_search (cadical_kitten *cadical_kitten, unsigned idx) { + if (cadical_kitten->queue.search == idx) + return; + cadical_kitten->queue.search = idx; + LOG ("search updated to %u stamped %" PRIu64, idx, + cadical_kitten->links[idx].stamp); +} + +static void enqueue (cadical_kitten *cadical_kitten, unsigned idx) { + LOG ("enqueue %u", idx); + kink *links = cadical_kitten->links; + kink *l = links + idx; + const unsigned last = cadical_kitten->queue.last; + if (last == INVALID) + cadical_kitten->queue.first = idx; + else + links[last].next = idx; + l->prev = last; + l->next = INVALID; + cadical_kitten->queue.last = idx; + l->stamp = cadical_kitten->queue.stamp++; + LOG ("stamp %" PRIu64, l->stamp); +} + +static void dequeue (cadical_kitten *cadical_kitten, unsigned idx) { + LOG ("dequeue %u", idx); + kink *links = cadical_kitten->links; + kink *l = links + idx; + const unsigned prev = l->prev; + const unsigned next = l->next; + if (prev == INVALID) + cadical_kitten->queue.first = next; + else + links[prev].next = next; + if (next == INVALID) + cadical_kitten->queue.last = prev; + else + links[next].prev = prev; +} + +static void init_queue (cadical_kitten *cadical_kitten, size_t old_vars, size_t new_vars) { + for (size_t idx = old_vars; idx < new_vars; idx++) { + CADICAL_assert (!cadical_kitten->values[2 * idx]); + CADICAL_assert (cadical_kitten->unassigned < UINT_MAX); + cadical_kitten->unassigned++; + enqueue (cadical_kitten, idx); + } + LOG ("initialized decision queue from %zu to %zu", old_vars, new_vars); + update_search (cadical_kitten, cadical_kitten->queue.last); + check_queue (cadical_kitten); +} + +static void initialize_cadical_kitten (cadical_kitten *cadical_kitten) { + cadical_kitten->queue.first = INVALID; + cadical_kitten->queue.last = INVALID; + cadical_kitten->inconsistent = INVALID; + cadical_kitten->failing = INVALID; + cadical_kitten->queue.search = INVALID; + cadical_kitten->terminator = 0; + cadical_kitten->terminator_data = 0; + cadical_kitten->limits.ticks = UINT64_MAX; + cadical_kitten->generator = cadical_kitten->initialized++; +} + +static void clear_cadical_kitten (cadical_kitten *cadical_kitten) { + size_t bytes = (char *) &cadical_kitten->size - (char *) &cadical_kitten->status; + memset (&cadical_kitten->status, 0, bytes); + memset (&cadical_kitten->statistics, 0, sizeof (statistics)); + initialize_cadical_kitten (cadical_kitten); +} + +#define RESIZE1(T, P) \ + do { \ + void *OLD_PTR = (P); \ + CALLOC (T, (P), new_size / 2); \ + const size_t BYTES = old_vars * sizeof *(P); \ + memcpy ((P), OLD_PTR, BYTES); \ + void *NEW_PTR = (P); \ + (P) = (T*)OLD_PTR; \ + DEALLOC ((P), old_size / 2); \ + (P) = (T*)NEW_PTR; \ + } while (0) + +#define RESIZE2(T, P) \ + do { \ + void *OLD_PTR = (P); \ + CALLOC (T, (P), new_size); \ + const size_t BYTES = old_lits * sizeof *(P); \ + memcpy ((P), OLD_PTR, BYTES); \ + void *NEW_PTR = (P); \ + (P) = (T*)OLD_PTR; \ + DEALLOC ((P), old_size); \ + (P) = (T*)NEW_PTR; \ + } while (0) + +static void enlarge_internal (cadical_kitten *cadical_kitten, size_t lit) { + const size_t new_lits = (lit | 1) + 1; + const size_t old_lits = cadical_kitten->lits; + CADICAL_assert (old_lits <= lit); + CADICAL_assert (old_lits < new_lits); + CADICAL_assert ((lit ^ 1) < new_lits); + CADICAL_assert (lit < new_lits); + const size_t old_size = cadical_kitten->size; + const unsigned new_vars = new_lits / 2; + const unsigned old_vars = old_lits / 2; + if (old_size < new_lits) { + size_t new_size = old_size ? 2 * old_size : 2; + while (new_size <= lit) + new_size *= 2; + LOG ("internal literals resized to %zu from %zu (requested %zu)", + new_size, old_size, new_lits); + + RESIZE1 (value, cadical_kitten->marks); + RESIZE1 (unsigned char, cadical_kitten->phases); + RESIZE2 (value, cadical_kitten->values); + RESIZE2 (bool, cadical_kitten->failed); + RESIZE1 (kar, cadical_kitten->vars); + RESIZE1 (kink, cadical_kitten->links); + RESIZE2 (katches, cadical_kitten->watches); + + cadical_kitten->size = new_size; + } + cadical_kitten->lits = new_lits; + init_queue (cadical_kitten, old_vars, new_vars); + LOG ("internal literals activated until %zu literals", new_lits); + return; +} + +static const char *status_to_string (int status) { + switch (status) { + case 10: + return "formula satisfied"; + case 11: + return "formula satisfied and prime implicant computed"; + case 20: + return "formula inconsistent"; + case 21: + return "formula inconsistent and core computed"; + default: + CADICAL_assert (!status); + return "formula unsolved"; + } +} + +static void invalid_api_usage (const char *fun, const char *fmt, ...) { + fprintf (stderr, "cadical_kitten: fatal error: invalid API usage in '%s': ", fun); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); + fflush (stderr); + abort (); +} + +#define INVALID_API_USAGE(...) invalid_api_usage (__func__, __VA_ARGS__) + +#define REQUIRE_INITIALIZED() \ + do { \ + if (!cadical_kitten) \ + INVALID_API_USAGE ("solver argument zero"); \ + } while (0) + +#define REQUIRE_STATUS(EXPECTED) \ + do { \ + REQUIRE_INITIALIZED (); \ + if (cadical_kitten->status != (EXPECTED)) \ + INVALID_API_USAGE ("invalid status '%s' (expected '%s')", \ + status_to_string (cadical_kitten->status), \ + status_to_string (EXPECTED)); \ + } while (0) + +#define UPDATE_STATUS(STATUS) \ + do { \ + if (cadical_kitten->status != (STATUS)) \ + LOG ("updating status from '%s' to '%s'", \ + status_to_string (cadical_kitten->status), status_to_string (STATUS)); \ + else \ + LOG ("keeping status at '%s'", status_to_string (STATUS)); \ + cadical_kitten->status = (STATUS); \ + } while (0) + +cadical_kitten *cadical_kitten_init (void) { + cadical_kitten *cadical_kitten; + CALLOC (struct cadical_kitten, cadical_kitten, 1); + initialize_cadical_kitten (cadical_kitten); + return cadical_kitten; +} + +#ifdef LOGGING +void cadical_kitten_set_logging (cadical_kitten *cadical_kitten) { logging = true; } +#endif + +void cadical_kitten_track_antecedents (cadical_kitten *cadical_kitten) { + REQUIRE_STATUS (0); + + if (cadical_kitten->learned) + INVALID_API_USAGE ("can not start tracking antecedents after learning"); + + LOG ("enabling antecedents tracking"); + cadical_kitten->antecedents = true; +} + +void cadical_kitten_randomize_phases (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + + LOG ("randomizing phases"); + + unsigned char *phases = cadical_kitten->phases; + const unsigned vars = cadical_kitten->size / 2; + + uint64_t random = kissat_next_random64 (&cadical_kitten->generator); + + unsigned i = 0; + const unsigned rest = vars & ~63u; + + while (i != rest) { + uint64_t *p = (uint64_t *) (phases + i); + p[0] = (random >> 0) & 0x0101010101010101; + p[1] = (random >> 1) & 0x0101010101010101; + p[2] = (random >> 2) & 0x0101010101010101; + p[3] = (random >> 3) & 0x0101010101010101; + p[4] = (random >> 4) & 0x0101010101010101; + p[5] = (random >> 5) & 0x0101010101010101; + p[6] = (random >> 6) & 0x0101010101010101; + p[7] = (random >> 7) & 0x0101010101010101; + random = kissat_next_random64 (&cadical_kitten->generator); + i += 64; + } + + unsigned shift = 0; + while (i != vars) + phases[i++] = (random >> shift++) & 1; +} + +void cadical_kitten_flip_phases (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + + LOG ("flipping phases"); + + unsigned char *phases = cadical_kitten->phases; + const unsigned vars = cadical_kitten->size / 2; + + unsigned i = 0; + const unsigned rest = vars & ~7u; + + while (i != rest) { + uint64_t *p = (uint64_t *) (phases + i); + *p ^= 0x0101010101010101; + i += 8; + } + + while (i != vars) + phases[i++] ^= 1; +} + +void cadical_kitten_no_ticks_limit (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + LOG ("forcing no ticks limit"); + cadical_kitten->limits.ticks = UINT64_MAX; +} + +uint64_t cadical_kitten_current_ticks (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + const uint64_t current = KITTEN_TICKS; + return current; +} + +void cadical_kitten_set_ticks_limit (cadical_kitten *cadical_kitten, uint64_t delta) { + REQUIRE_INITIALIZED (); + const uint64_t current = KITTEN_TICKS; + uint64_t limit; + if (UINT64_MAX - delta <= current) { + LOG ("forcing unlimited ticks limit"); + limit = UINT64_MAX; + } else { + limit = current + delta; + LOG ("new limit of %" PRIu64 " ticks after %" PRIu64, limit, delta); + } + + cadical_kitten->limits.ticks = limit; +} + +void cadical_kitten_no_terminator (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + LOG ("removing terminator"); + cadical_kitten->terminator = 0; + cadical_kitten->terminator_data = 0; +} + +void cadical_kitten_set_terminator (cadical_kitten *cadical_kitten, void *data, + int (*terminator) (void *)) { + REQUIRE_INITIALIZED (); + LOG ("setting terminator"); + cadical_kitten->terminator = terminator; + cadical_kitten->terminator_data = data; +} + +static void shuffle_unsigned_array (cadical_kitten *cadical_kitten, size_t size, + unsigned *a) { + for (size_t i = 0; i < size; i++) { + const size_t j = kissat_pick_random (&cadical_kitten->generator, 0, i); + if (j == i) + continue; + const unsigned first = a[i]; + const unsigned second = a[j]; + a[i] = second; + a[j] = first; + } +} + +static void shuffle_unsigned_stack (cadical_kitten *cadical_kitten, unsigneds *stack) { + const size_t size = SIZE_STACK (*stack); + unsigned *a = BEGIN_STACK (*stack); + shuffle_unsigned_array (cadical_kitten, size, a); +} + +static void shuffle_katches (cadical_kitten *cadical_kitten) { + LOG ("shuffling watch lists"); + for (size_t lit = 0; lit < cadical_kitten->lits; lit++) + shuffle_unsigned_stack (cadical_kitten, &KATCHES (lit)); +} + +static void shuffle_queue (cadical_kitten *cadical_kitten) { + LOG ("shuffling variable decision order"); + + const unsigned vars = cadical_kitten->lits / 2; + for (unsigned i = 0; i < vars; i++) { + const unsigned idx = kissat_pick_random (&cadical_kitten->generator, 0, vars); + dequeue (cadical_kitten, idx); + enqueue (cadical_kitten, idx); + } + update_search (cadical_kitten, cadical_kitten->queue.last); +} + +static void shuffle_units (cadical_kitten *cadical_kitten) { + LOG ("shuffling units"); + shuffle_unsigned_stack (cadical_kitten, &cadical_kitten->units); +} + +void cadical_kitten_shuffle_clauses (cadical_kitten *cadical_kitten) { + REQUIRE_STATUS (0); + shuffle_queue (cadical_kitten); + shuffle_katches (cadical_kitten); + shuffle_units (cadical_kitten); +} + +static inline unsigned *antecedents (klause *c) { + CADICAL_assert (is_learned_klause (c)); + return c->lits + c->size; +} + +static inline void watch_klause (cadical_kitten *cadical_kitten, unsigned lit, + unsigned ref) { + ROG (ref, "watching %u in", lit); + katches *watches = &KATCHES (lit); + PUSH_STACK (*watches, ref); +} + +static inline void connect_new_klause (cadical_kitten *cadical_kitten, unsigned ref) { + ROG (ref, "new"); + + klause *c = dereference_klause (cadical_kitten, ref); + + if (!c->size) { + if (cadical_kitten->inconsistent == INVALID) { + ROG (ref, "registering inconsistent empty"); + cadical_kitten->inconsistent = ref; + } else + ROG (ref, "ignoring inconsistent empty"); + } else if (c->size == 1) { + ROG (ref, "watching unit"); + PUSH_STACK (cadical_kitten->units, ref); + } else { + watch_klause (cadical_kitten, c->lits[0], ref); + watch_klause (cadical_kitten, c->lits[1], ref); + } +} + +static unsigned new_reference (cadical_kitten *cadical_kitten) { + size_t ref = SIZE_STACK (cadical_kitten->klauses); + if (ref >= INVALID) { + die ("maximum number of literals exhausted"); + } + const unsigned res = (unsigned) ref; + CADICAL_assert (res != INVALID); + INC (cadical_kitten_ticks); + return res; +} + +static void new_original_klause (cadical_kitten *cadical_kitten, unsigned id) { + unsigned res = new_reference (cadical_kitten); + unsigned size = SIZE_STACK (cadical_kitten->klause); + unsigneds *klauses = &cadical_kitten->klauses; + PUSH_STACK (*klauses, id); + PUSH_STACK (*klauses, size); + PUSH_STACK (*klauses, 0); + for (all_stack (unsigned, lit, cadical_kitten->klause)) + PUSH_STACK (*klauses, lit); + connect_new_klause (cadical_kitten, res); + cadical_kitten->end_original_ref = SIZE_STACK (*klauses); + cadical_kitten->statistics.original++; +} + +static void enlarge_external (cadical_kitten *cadical_kitten, size_t eidx) { + const size_t old_size = cadical_kitten->esize; + const unsigned old_evars = cadical_kitten->evars; + CADICAL_assert (old_evars <= eidx); + const unsigned new_evars = eidx + 1; + if (old_size <= eidx) { + size_t new_size = old_size ? 2 * old_size : 1; + while (new_size <= eidx) + new_size *= 2; + LOG ("external resizing to %zu variables from %zu (requested %u)", + new_size, old_size, new_evars); + unsigned *old_import = cadical_kitten->import; + CALLOC (unsigned, cadical_kitten->import, new_size); + const size_t bytes = old_evars * sizeof *cadical_kitten->import; + memcpy (cadical_kitten->import, old_import, bytes); + DEALLOC (old_import, old_size); + cadical_kitten->esize = new_size; + } + cadical_kitten->evars = new_evars; + LOG ("external variables enlarged to %u", new_evars); +} + +static unsigned import_literal (cadical_kitten *cadical_kitten, unsigned elit) { + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + enlarge_external (cadical_kitten, eidx); + + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) { + iidx = SIZE_STACK (cadical_kitten->export_); + PUSH_STACK (cadical_kitten->export_, eidx); + cadical_kitten->import[eidx] = iidx + 1; + } else + iidx--; + unsigned ilit = 2 * iidx + (elit & 1); + LOG ("imported external literal %u as internal literal %u", elit, ilit); + if (ilit >= cadical_kitten->lits) + enlarge_internal (cadical_kitten, ilit); + return ilit; +} + +static unsigned export_literal (cadical_kitten *cadical_kitten, unsigned ilit) { + const unsigned iidx = ilit / 2; + CADICAL_assert (iidx < SIZE_STACK (cadical_kitten->export_)); + const unsigned eidx = PEEK_STACK (cadical_kitten->export_, iidx); + const unsigned elit = 2 * eidx + (ilit & 1); + return elit; +} + +unsigned cadical_new_learned_klause (cadical_kitten *cadical_kitten) { + unsigned res = new_reference (cadical_kitten); + unsigneds *klauses = &cadical_kitten->klauses; + const size_t size = SIZE_STACK (cadical_kitten->klause); + CADICAL_assert (size <= UINT_MAX); + const size_t aux = + cadical_kitten->antecedents ? SIZE_STACK (cadical_kitten->resolved) : 0; + CADICAL_assert (aux <= UINT_MAX); + PUSH_STACK (*klauses, (unsigned) aux); + PUSH_STACK (*klauses, (unsigned) size); + PUSH_STACK (*klauses, LEARNED_FLAG); + for (all_stack (unsigned, lit, cadical_kitten->klause)) + PUSH_STACK (*klauses, lit); + if (aux) + for (all_stack (unsigned, ref, cadical_kitten->resolved)) + PUSH_STACK (*klauses, ref); + connect_new_klause (cadical_kitten, res); + cadical_kitten->learned = true; + cadical_kitten->statistics.learned++; + return res; +} + +void cadical_kitten_clear (cadical_kitten *cadical_kitten) { + LOG ("clear cadical_kitten of size %zu", cadical_kitten->size); + + CADICAL_assert (EMPTY_STACK (cadical_kitten->analyzed)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->eclause)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->resolved)); + + CLEAR_STACK (cadical_kitten->assumptions); + CLEAR_STACK (cadical_kitten->core); + CLEAR_STACK (cadical_kitten->klause); + CLEAR_STACK (cadical_kitten->klauses); + CLEAR_STACK (cadical_kitten->trail); + CLEAR_STACK (cadical_kitten->units); + CLEAR_STACK (cadical_kitten->clause); + CLEAR_STACK (cadical_kitten->prime[0]); + CLEAR_STACK (cadical_kitten->prime[1]); + + for (all_kits (kit)) + CLEAR_STACK (KATCHES (kit)); + + while (!EMPTY_STACK (cadical_kitten->export_)) + cadical_kitten->import[POP_STACK (cadical_kitten->export_)] = 0; + + const size_t lits = cadical_kitten->size; + const unsigned vars = lits / 2; + +#ifndef CADICAL_NDEBUG + for (unsigned i = 0; i < vars; i++) + CADICAL_assert (!cadical_kitten->marks[i]); +#endif + + memset (cadical_kitten->phases, 0, vars); + memset (cadical_kitten->values, 0, lits); + memset (cadical_kitten->failed, 0, lits); + memset (cadical_kitten->vars, 0, vars); + + clear_cadical_kitten (cadical_kitten); +} + +void cadical_kitten_release (cadical_kitten *cadical_kitten) { + RELEASE_STACK (cadical_kitten->analyzed); + RELEASE_STACK (cadical_kitten->assumptions); + RELEASE_STACK (cadical_kitten->core); + RELEASE_STACK (cadical_kitten->eclause); + RELEASE_STACK (cadical_kitten->export_); + RELEASE_STACK (cadical_kitten->klause); + RELEASE_STACK (cadical_kitten->klauses); + RELEASE_STACK (cadical_kitten->resolved); + RELEASE_STACK (cadical_kitten->trail); + RELEASE_STACK (cadical_kitten->units); + RELEASE_STACK (cadical_kitten->clause); + RELEASE_STACK (cadical_kitten->prime[0]); + RELEASE_STACK (cadical_kitten->prime[1]); + + for (size_t lit = 0; lit < cadical_kitten->size; lit++) + RELEASE_STACK (cadical_kitten->watches[lit]); + + const size_t lits = cadical_kitten->size; + const unsigned vars = lits / 2; + DEALLOC (cadical_kitten->marks, vars); + DEALLOC (cadical_kitten->phases, vars); + DEALLOC (cadical_kitten->values, lits); + DEALLOC (cadical_kitten->failed, lits); + DEALLOC (cadical_kitten->vars, vars); + DEALLOC (cadical_kitten->links, vars); + DEALLOC (cadical_kitten->watches, lits); + DEALLOC (cadical_kitten->import, cadical_kitten->esize); + (void) vars; + + free (cadical_kitten); +} + +static inline void move_to_front (cadical_kitten *cadical_kitten, unsigned idx) { + if (idx == cadical_kitten->queue.last) + return; + LOG ("move to front variable %u", idx); + dequeue (cadical_kitten, idx); + enqueue (cadical_kitten, idx); + CADICAL_assert (cadical_kitten->values[2 * idx]); +} + +static inline void assign (cadical_kitten *cadical_kitten, unsigned lit, unsigned reason) { +#ifdef LOGGING + if (reason == INVALID) + LOG ("assign %u as decision", lit); + else + ROG (reason, "assign %u reason", lit); +#endif + value *values = cadical_kitten->values; + const unsigned not_lit = lit ^ 1; + CADICAL_assert (!values[lit]); + CADICAL_assert (!values[not_lit]); + values[lit] = 1; + values[not_lit] = -1; + const unsigned idx = lit / 2; + const unsigned sign = lit & 1; + cadical_kitten->phases[idx] = sign; + PUSH_STACK (cadical_kitten->trail, lit); + kar *v = cadical_kitten->vars + idx; + v->level = cadical_kitten->level; + if (!v->level) { + CADICAL_assert (reason != INVALID); + klause *c = dereference_klause (cadical_kitten, reason); + if (c->size > 1) { + if (cadical_kitten->antecedents) { + PUSH_STACK (cadical_kitten->resolved, reason); + for (all_literals_in_klause (other, c)) + if (other != lit) { + const unsigned other_idx = other / 2; + const unsigned other_ref = cadical_kitten->vars[other_idx].reason; + CADICAL_assert (other_ref != INVALID); + PUSH_STACK (cadical_kitten->resolved, other_ref); + } + } + PUSH_STACK (cadical_kitten->klause, lit); + reason = cadical_new_learned_klause (cadical_kitten); + CLEAR_STACK (cadical_kitten->resolved); + CLEAR_STACK (cadical_kitten->klause); + } + } + v->reason = reason; + CADICAL_assert (cadical_kitten->unassigned); + cadical_kitten->unassigned--; +} + +static inline unsigned propagate_literal (cadical_kitten *cadical_kitten, unsigned lit) { + LOG ("propagating %u", lit); + value *values = cadical_kitten->values; + CADICAL_assert (values[lit] > 0); + const unsigned not_lit = lit ^ 1; + katches *watches = cadical_kitten->watches + not_lit; + unsigned conflict = INVALID; + unsigned *q = BEGIN_STACK (*watches); + const unsigned *const end_watches = END_STACK (*watches); + unsigned const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + while (p != end_watches) { + const unsigned ref = *q++ = *p++; + klause *c = dereference_klause (cadical_kitten, ref); + CADICAL_assert (c->size > 1); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ not_lit; + const value other_value = values[other]; + ticks++; + if (other_value > 0) + continue; + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + replacement_value = values[replacement]; + if (replacement_value >= 0) + break; + } + if (replacement_value >= 0) { + CADICAL_assert (replacement != INVALID); + ROG (ref, "unwatching %u in", not_lit); + lits[0] = other; + lits[1] = replacement; + *r = not_lit; + watch_klause (cadical_kitten, replacement, ref); + q--; + } else if (other_value < 0) { + ROG (ref, "conflict"); + INC (cadical_kitten_conflicts); + conflict = ref; + break; + } else { + CADICAL_assert (!other_value); + assign (cadical_kitten, other, ref); + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (cadical_kitten_ticks, ticks); + return conflict; +} + +static inline unsigned propagate (cadical_kitten *cadical_kitten) { + CADICAL_assert (cadical_kitten->inconsistent == INVALID); + unsigned propagated = 0; + unsigned conflict = INVALID; + while (conflict == INVALID && + cadical_kitten->propagated < SIZE_STACK (cadical_kitten->trail)) { + if (cadical_kitten->terminator && + cadical_kitten->terminator (cadical_kitten->terminator_data)) { + break; + } + const unsigned lit = PEEK_STACK (cadical_kitten->trail, cadical_kitten->propagated); + conflict = propagate_literal (cadical_kitten, lit); + cadical_kitten->propagated++; + propagated++; + } + ADD (cadical_kitten_propagations, propagated); + return conflict; +} + +static void bump (cadical_kitten *cadical_kitten) { + value *marks = cadical_kitten->marks; + for (all_stack (unsigned, idx, cadical_kitten->analyzed)) { + marks[idx] = 0; + move_to_front (cadical_kitten, idx); + } + check_queue (cadical_kitten); +} + +static inline void unassign (cadical_kitten *cadical_kitten, value *values, unsigned lit) { + const unsigned not_lit = lit ^ 1; + CADICAL_assert (values[lit]); + CADICAL_assert (values[not_lit]); + const unsigned idx = lit / 2; +#ifdef LOGGING + kar *var = cadical_kitten->vars + idx; + cadical_kitten->level = var->level; + LOG ("unassign %u", lit); +#endif + values[lit] = values[not_lit] = 0; + CADICAL_assert (cadical_kitten->unassigned < cadical_kitten->lits / 2); + cadical_kitten->unassigned++; + kink *links = cadical_kitten->links; + kink *link = links + idx; + if (link->stamp > links[cadical_kitten->queue.search].stamp) + update_search (cadical_kitten, idx); +} + +static void backtrack (cadical_kitten *cadical_kitten, unsigned jump) { + check_queue (cadical_kitten); + CADICAL_assert (jump < cadical_kitten->level); + LOG ("back%s to level %u", + (cadical_kitten->level == jump + 1 ? "tracking" : "jumping"), jump); + kar *vars = cadical_kitten->vars; + value *values = cadical_kitten->values; + unsigneds *trail = &cadical_kitten->trail; + while (!EMPTY_STACK (*trail)) { + const unsigned lit = TOP_STACK (*trail); + const unsigned idx = lit / 2; + const unsigned level = vars[idx].level; + if (level == jump) + break; + (void) POP_STACK (*trail); + unassign (cadical_kitten, values, lit); + } + cadical_kitten->propagated = SIZE_STACK (*trail); + cadical_kitten->level = jump; + check_queue (cadical_kitten); +} + +void cadical_completely_backtrack_to_root_level (cadical_kitten *cadical_kitten) { + check_queue (cadical_kitten); + LOG ("completely backtracking to level 0"); + value *values = cadical_kitten->values; + unsigneds *trail = &cadical_kitten->trail; + unsigneds *units = &cadical_kitten->units; +#ifndef CADICAL_NDEBUG + kar *vars = cadical_kitten->vars; +#endif + for (all_stack (unsigned, lit, *trail)) { + CADICAL_assert (vars[lit / 2].level); + unassign (cadical_kitten, values, lit); + } + CLEAR_STACK (*trail); + for (all_stack (unsigned, ref, *units)) { + klause *c = dereference_klause (cadical_kitten, ref); + CADICAL_assert (c->size == 1); + const unsigned unit = c->lits[0]; + const value value = values[unit]; + if (value <= 0) + continue; + unassign (cadical_kitten, values, unit); + } + cadical_kitten->propagated = 0; + cadical_kitten->level = 0; + check_queue (cadical_kitten); +} + +static void analyze (cadical_kitten *cadical_kitten, unsigned conflict) { + CADICAL_assert (cadical_kitten->level); + CADICAL_assert (cadical_kitten->inconsistent == INVALID); + CADICAL_assert (EMPTY_STACK (cadical_kitten->analyzed)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->resolved)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + PUSH_STACK (cadical_kitten->klause, INVALID); + unsigned reason = conflict; + value *marks = cadical_kitten->marks; + const kar *const vars = cadical_kitten->vars; + const unsigned level = cadical_kitten->level; + unsigned const *p = END_STACK (cadical_kitten->trail); + unsigned open = 0, jump = 0, size = 1, uip; + for (;;) { + CADICAL_assert (reason != INVALID); + klause *c = dereference_klause (cadical_kitten, reason); + CADICAL_assert (c); + ROG (reason, "analyzing"); + PUSH_STACK (cadical_kitten->resolved, reason); + for (all_literals_in_klause (lit, c)) { + const unsigned idx = lit / 2; + if (marks[idx]) + continue; + CADICAL_assert (cadical_kitten->values[lit] < 0); + LOG ("analyzed %u", lit); + marks[idx] = true; + PUSH_STACK (cadical_kitten->analyzed, idx); + const kar *const v = vars + idx; + const unsigned tmp = v->level; + if (tmp < level) { + if (tmp > jump) { + jump = tmp; + if (size > 1) { + const unsigned other = PEEK_STACK (cadical_kitten->klause, 1); + POKE_STACK (cadical_kitten->klause, 1, lit); + lit = other; + } + } + PUSH_STACK (cadical_kitten->klause, lit); + size++; + } else + open++; + } + unsigned idx; + do { + CADICAL_assert (BEGIN_STACK (cadical_kitten->trail) < p); + uip = *--p; + } while (!marks[idx = uip / 2]); + CADICAL_assert (open); + if (!--open) + break; + reason = vars[idx].reason; + } + const unsigned not_uip = uip ^ 1; + LOG ("first UIP %u jump level %u size %u", not_uip, jump, size); + POKE_STACK (cadical_kitten->klause, 0, not_uip); + bump (cadical_kitten); + CLEAR_STACK (cadical_kitten->analyzed); + const unsigned learned_ref = cadical_new_learned_klause (cadical_kitten); + CLEAR_STACK (cadical_kitten->resolved); + CLEAR_STACK (cadical_kitten->klause); + backtrack (cadical_kitten, jump); + assign (cadical_kitten, not_uip, learned_ref); +} + +static void failing (cadical_kitten *cadical_kitten) { + CADICAL_assert (cadical_kitten->inconsistent == INVALID); + CADICAL_assert (!EMPTY_STACK (cadical_kitten->assumptions)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->analyzed)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->resolved)); + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + LOG ("analyzing failing assumptions"); + const value *const values = cadical_kitten->values; + const kar *const vars = cadical_kitten->vars; + unsigned failed_clashing = INVALID; + unsigned first_failed = INVALID; + unsigned failed_unit = INVALID; + for (all_stack (unsigned, lit, cadical_kitten->assumptions)) { + if (values[lit] >= 0) + continue; + if (first_failed == INVALID) + first_failed = lit; + const unsigned failed_idx = lit / 2; + const kar *const failed_var = vars + failed_idx; + if (!failed_var->level) { + failed_unit = lit; + break; + } + if (failed_clashing == INVALID && failed_var->reason == INVALID) + failed_clashing = lit; + } + unsigned failed; + if (failed_unit != INVALID) + failed = failed_unit; + else if (failed_clashing != INVALID) + failed = failed_clashing; + else + failed = first_failed; + CADICAL_assert (failed != INVALID); + const unsigned failed_idx = failed / 2; + const kar *const failed_var = vars + failed_idx; + const unsigned failed_reason = failed_var->reason; + LOG ("first failed assumption %u", failed); + cadical_kitten->failed[failed] = true; + + if (failed_unit != INVALID) { + CADICAL_assert (dereference_klause (cadical_kitten, failed_reason)->size == 1); + LOG ("root-level falsified assumption %u", failed); + cadical_kitten->failing = failed_reason; + ROG (cadical_kitten->failing, "failing reason"); + return; + } + + const unsigned not_failed = failed ^ 1; + if (failed_clashing != INVALID) { + LOG ("clashing with negated assumption %u", not_failed); + cadical_kitten->failed[not_failed] = true; + CADICAL_assert (cadical_kitten->failing == INVALID); + return; + } + + value *marks = cadical_kitten->marks; + CADICAL_assert (!marks[failed_idx]); + marks[failed_idx] = true; + PUSH_STACK (cadical_kitten->analyzed, failed_idx); + PUSH_STACK (cadical_kitten->klause, not_failed); + + unsigneds work; + INIT_STACK (work); + + LOGLITS (BEGIN_STACK (cadical_kitten->trail), SIZE_STACK (cadical_kitten->trail), + "trail"); + + CADICAL_assert (SIZE_STACK (cadical_kitten->trail)); + unsigned const *p = END_STACK (cadical_kitten->trail); + unsigned open = 1; + for (;;) { + if (!open) + break; + open--; + unsigned idx, uip; + do { + CADICAL_assert (BEGIN_STACK (cadical_kitten->trail) < p); + uip = *--p; + } while (!marks[idx = uip / 2]); + + const kar *var = vars + idx; + const unsigned reason = var->reason; + if (reason == INVALID) { + unsigned lit = 2 * idx; + if (values[lit] < 0) + lit ^= 1; + LOG ("failed assumption %u", lit); + CADICAL_assert (!cadical_kitten->failed[lit]); + cadical_kitten->failed[lit] = true; + const unsigned not_lit = lit ^ 1; + PUSH_STACK (cadical_kitten->klause, not_lit); + } else { + ROG (reason, "analyzing"); + PUSH_STACK (cadical_kitten->resolved, reason); + klause *c = dereference_klause (cadical_kitten, reason); + for (all_literals_in_klause (other, c)) { + const unsigned other_idx = other / 2; + if (marks[other_idx]) + continue; + CADICAL_assert (other_idx != idx); + marks[other_idx] = true; + CADICAL_assert (values[other]); + if (vars[other_idx].level) + open++; + else + PUSH_STACK (work, other_idx); + PUSH_STACK (cadical_kitten->analyzed, other_idx); + + LOG ("analyzing final literal %u", other ^ 1); + } + } + } + for (size_t next = 0; next < SIZE_STACK (work); next++) { + const unsigned idx = PEEK_STACK (work, next); + const kar *var = vars + idx; + const unsigned reason = var->reason; + if (reason == INVALID) { + unsigned lit = 2 * idx; + if (values[lit] < 0) + lit ^= 1; + LOG ("failed assumption %u", lit); + CADICAL_assert (!cadical_kitten->failed[lit]); + cadical_kitten->failed[lit] = true; + const unsigned not_lit = lit ^ 1; + PUSH_STACK (cadical_kitten->klause, not_lit); + } else { + ROG (reason, "analyzing unit"); + PUSH_STACK (cadical_kitten->resolved, reason); + } + } + + // this is bfs not dfs so it does not work for lrat :/ + /* + for (size_t next = 0; next < SIZE_STACK (cadical_kitten->analyzed); next++) { + const unsigned idx = PEEK_STACK (cadical_kitten->analyzed, next); + CADICAL_assert (marks[idx]); + const kar *var = vars + idx; + const unsigned reason = var->reason; + if (reason == INVALID) { + unsigned lit = 2 * idx; + if (values[lit] < 0) + lit ^= 1; + LOG ("failed assumption %u", lit); + CADICAL_assert (!cadical_kitten->failed[lit]); + cadical_kitten->failed[lit] = true; + const unsigned not_lit = lit ^ 1; + PUSH_STACK (cadical_kitten->klause, not_lit); + } else { + ROG (reason, "analyzing"); + PUSH_STACK (cadical_kitten->resolved, reason); + klause *c = dereference_klause (cadical_kitten, reason); + for (all_literals_in_klause (other, c)) { + const unsigned other_idx = other / 2; + if (other_idx == idx) + continue; + if (marks[other_idx]) + continue; + marks[other_idx] = true; + PUSH_STACK (cadical_kitten->analyzed, other_idx); + LOG ("analyzing final literal %u", other ^ 1); + } + } + } + */ + + for (all_stack (unsigned, idx, cadical_kitten->analyzed)) + CADICAL_assert (marks[idx]), marks[idx] = 0; + CLEAR_STACK (cadical_kitten->analyzed); + + RELEASE_STACK (work); + + const size_t resolved = SIZE_STACK (cadical_kitten->resolved); + CADICAL_assert (resolved); + + if (resolved == 1) { + cadical_kitten->failing = PEEK_STACK (cadical_kitten->resolved, 0); + ROG (cadical_kitten->failing, "reusing as core"); + } else { + cadical_kitten->failing = cadical_new_learned_klause (cadical_kitten); + ROG (cadical_kitten->failing, "new core"); + } + + CLEAR_STACK (cadical_kitten->resolved); + CLEAR_STACK (cadical_kitten->klause); +} + +static void flush_trail (cadical_kitten *cadical_kitten) { + unsigneds *trail = &cadical_kitten->trail; + LOG ("flushing %zu root-level literals from trail", SIZE_STACK (*trail)); + CADICAL_assert (!cadical_kitten->level); + cadical_kitten->propagated = 0; + CLEAR_STACK (*trail); +} + +static int decide (cadical_kitten *cadical_kitten) { + if (!cadical_kitten->level && !EMPTY_STACK (cadical_kitten->trail)) + flush_trail (cadical_kitten); + + const value *const values = cadical_kitten->values; + unsigned decision = INVALID; + const size_t assumptions = SIZE_STACK (cadical_kitten->assumptions); + while (cadical_kitten->level < assumptions) { + unsigned assumption = PEEK_STACK (cadical_kitten->assumptions, cadical_kitten->level); + value value = values[assumption]; + if (value < 0) { + LOG ("found failing assumption %u", assumption); + failing (cadical_kitten); + return 20; + } else if (value > 0) { + + cadical_kitten->level++; + LOG ("pseudo decision level %u for already satisfied assumption %u", + cadical_kitten->level, assumption); + } else { + decision = assumption; + LOG ("using assumption %u as decision", decision); + break; + } + } + + if (!cadical_kitten->unassigned) + return 10; + + if (KITTEN_TICKS >= cadical_kitten->limits.ticks) { + LOG ("ticks limit %" PRIu64 " hit after %" PRIu64 " ticks", + cadical_kitten->limits.ticks, KITTEN_TICKS); + return -1; + } + + if (cadical_kitten->terminator && cadical_kitten->terminator (cadical_kitten->terminator_data)) { + LOG ("terminator requested termination"); + return -1; + } + + if (decision == INVALID) { + unsigned idx = cadical_kitten->queue.search; + const kink *const links = cadical_kitten->links; + for (;;) { + CADICAL_assert (idx != INVALID); + if (!values[2 * idx]) + break; + idx = links[idx].prev; + } + update_search (cadical_kitten, idx); + const unsigned phase = cadical_kitten->phases[idx]; + decision = 2 * idx + phase; + LOG ("decision %u variable %u phase %u", decision, idx, phase); + } + INC (cadical_kitten_decisions); + cadical_kitten->level++; + assign (cadical_kitten, decision, INVALID); + return 0; +} + +static void inconsistent (cadical_kitten *cadical_kitten, unsigned ref) { + CADICAL_assert (ref != INVALID); + CADICAL_assert (cadical_kitten->inconsistent == INVALID); + + if (!cadical_kitten->antecedents) { + cadical_kitten->inconsistent = ref; + ROG (ref, "registering inconsistent virtually empty"); + return; + } + + unsigneds *analyzed = &cadical_kitten->analyzed; + unsigneds *resolved = &cadical_kitten->resolved; + + CADICAL_assert (EMPTY_STACK (*analyzed)); + CADICAL_assert (EMPTY_STACK (*resolved)); + + value *marks = cadical_kitten->marks; + const kar *const vars = cadical_kitten->vars; + unsigned next = 0; + + for (;;) { + CADICAL_assert (ref != INVALID); + klause *c = dereference_klause (cadical_kitten, ref); + CADICAL_assert (c); + ROG (ref, "analyzing inconsistent"); + PUSH_STACK (*resolved, ref); + for (all_literals_in_klause (lit, c)) { + const unsigned idx = lit / 2; + CADICAL_assert (!vars[idx].level); + if (marks[idx]) + continue; + CADICAL_assert (cadical_kitten->values[lit] < 0); + LOG ("analyzed %u", lit); + marks[idx] = true; + PUSH_STACK (cadical_kitten->analyzed, idx); + } + if (next == SIZE_STACK (cadical_kitten->analyzed)) + break; + const unsigned idx = PEEK_STACK (cadical_kitten->analyzed, next); + next++; + const kar *const v = vars + idx; + CADICAL_assert (!v->level); + ref = v->reason; + } + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + ref = cadical_new_learned_klause (cadical_kitten); + ROG (ref, "registering final inconsistent empty"); + cadical_kitten->inconsistent = ref; + + for (all_stack (unsigned, idx, *analyzed)) + marks[idx] = 0; + + CLEAR_STACK (*analyzed); + CLEAR_STACK (*resolved); +} + +static int propagate_units (cadical_kitten *cadical_kitten) { + if (cadical_kitten->inconsistent != INVALID) + return 20; + + if (EMPTY_STACK (cadical_kitten->units)) { + LOG ("no root level unit clauses"); + return 0; + } + + LOG ("propagating %zu root level unit clauses", + SIZE_STACK (cadical_kitten->units)); + + const value *const values = cadical_kitten->values; + + for (size_t next = 0; next < SIZE_STACK (cadical_kitten->units); next++) { + const unsigned ref = PEEK_STACK (cadical_kitten->units, next); + CADICAL_assert (ref != INVALID); + klause *c = dereference_klause (cadical_kitten, ref); + CADICAL_assert (c->size == 1); + ROG (ref, "propagating unit"); + const unsigned unit = c->lits[0]; + const value value = values[unit]; + if (value > 0) + continue; + if (value < 0) { + inconsistent (cadical_kitten, ref); + return 20; + } + assign (cadical_kitten, unit, ref); + } + const unsigned conflict = propagate (cadical_kitten); + if (conflict == INVALID) + return 0; + inconsistent (cadical_kitten, conflict); + return 20; +} + +/*------------------------------------------------------------------------*/ + +static klause *begin_klauses (cadical_kitten *cadical_kitten) { + return (klause *) BEGIN_STACK (cadical_kitten->klauses); +} + +static klause *end_original_klauses (cadical_kitten *cadical_kitten) { + return (klause *) (BEGIN_STACK (cadical_kitten->klauses) + + cadical_kitten->end_original_ref); +} + +static klause *end_klauses (cadical_kitten *cadical_kitten) { + return (klause *) END_STACK (cadical_kitten->klauses); +} + +static klause *next_klause (cadical_kitten *cadical_kitten, klause *c) { + CADICAL_assert (begin_klauses (cadical_kitten) <= c); + CADICAL_assert (c < end_klauses (cadical_kitten)); + unsigned *res = c->lits + c->size; + if (cadical_kitten->antecedents && is_learned_klause (c)) + res += c->aux; + return (klause *) res; +} + +/*------------------------------------------------------------------------*/ + +static void reset_core (cadical_kitten *cadical_kitten) { + LOG ("resetting core clauses"); + size_t reset = 0; + for (all_klauses (c)) + if (is_core_klause (c)) + unset_core_klause (c), reset++; + LOG ("reset %zu core clauses", reset); + CLEAR_STACK (cadical_kitten->core); +} + +static void reset_assumptions (cadical_kitten *cadical_kitten) { + LOG ("reset %zu assumptions", SIZE_STACK (cadical_kitten->assumptions)); + while (!EMPTY_STACK (cadical_kitten->assumptions)) { + const unsigned assumption = POP_STACK (cadical_kitten->assumptions); + cadical_kitten->failed[assumption] = false; + } +#ifndef CADICAL_NDEBUG + for (size_t i = 0; i < cadical_kitten->size; i++) + CADICAL_assert (!cadical_kitten->failed[i]); +#endif + CLEAR_STACK (cadical_kitten->assumptions); + if (cadical_kitten->failing != INVALID) { + ROG (cadical_kitten->failing, "reset failed assumption reason"); + cadical_kitten->failing = INVALID; + } +} + +static void reset_incremental (cadical_kitten *cadical_kitten) { + // if (cadical_kitten->level) + cadical_completely_backtrack_to_root_level (cadical_kitten); + if (!EMPTY_STACK (cadical_kitten->assumptions)) + reset_assumptions (cadical_kitten); + else + CADICAL_assert (cadical_kitten->failing == INVALID); + if (cadical_kitten->status == 21) + reset_core (cadical_kitten); + UPDATE_STATUS (0); +} + +/*------------------------------------------------------------------------*/ + +static bool flip_literal (cadical_kitten *cadical_kitten, unsigned lit) { + INC (cadical_kitten_flip); + signed char *values = cadical_kitten->values; + if (values[lit] < 0) + lit ^= 1; + LOG ("trying to flip value of satisfied literal %u", lit); + CADICAL_assert (values[lit] > 0); + katches *watches = cadical_kitten->watches + lit; + unsigned *q = BEGIN_STACK (*watches); + const unsigned *const end_watches = END_STACK (*watches); + unsigned const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + bool res = true; + while (p != end_watches) { + const unsigned ref = *q++ = *p++; + klause *c = dereference_klause (cadical_kitten, ref); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ lit; + const value other_value = values[other]; + ticks++; + if (other_value > 0) + continue; + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + CADICAL_assert (replacement != lit); + replacement_value = values[replacement]; + CADICAL_assert (replacement_value); + if (replacement_value > 0) + break; + } + if (replacement_value > 0) { + CADICAL_assert (replacement != INVALID); + ROG (ref, "unwatching %u in", lit); + lits[0] = other; + lits[1] = replacement; + *r = lit; + watch_klause (cadical_kitten, replacement, ref); + q--; + } else { + CADICAL_assert (replacement_value < 0); + ROG (ref, "single satisfied"); + res = false; + break; + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (cadical_kitten_ticks, ticks); + if (res) { + LOG ("flipping value of %u", lit); + values[lit] = -1; + const unsigned not_lit = lit ^ 1; + values[not_lit] = 1; + INC (cadical_kitten_flipped); + } else + LOG ("failed to flip value of %u", lit); + return res; +} + +/*------------------------------------------------------------------------*/ + +// this cadical specific clause addition avoids copying clauses multiple +// times just to convert literals to unsigned representation. +// +static unsigned int2u (int lit) { + CADICAL_assert (lit != 0); + int idx = abs (lit) - 1; + return (lit < 0) + 2u * (unsigned) idx; +} + +void cadical_kitten_assume (cadical_kitten *cadical_kitten, unsigned elit) { + REQUIRE_INITIALIZED (); + if (cadical_kitten->status) + reset_incremental (cadical_kitten); + const unsigned ilit = import_literal (cadical_kitten, elit); + LOG ("registering assumption %u", ilit); + PUSH_STACK (cadical_kitten->assumptions, ilit); +} + +void cadical_kitten_assume_signed (cadical_kitten *cadical_kitten, int elit) { + unsigned kelit = int2u (elit); + cadical_kitten_assume (cadical_kitten, kelit); +} + +void cadical_kitten_clause_with_id_and_exception (cadical_kitten *cadical_kitten, unsigned id, + size_t size, + const unsigned *elits, + unsigned except) { + REQUIRE_INITIALIZED (); + if (cadical_kitten->status) + reset_incremental (cadical_kitten); + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + const unsigned *const end = elits + size; + for (const unsigned *p = elits; p != end; p++) { + const unsigned elit = *p; + if (elit == except) + continue; + const unsigned ilit = import_literal (cadical_kitten, elit); + CADICAL_assert (ilit < cadical_kitten->lits); + const unsigned iidx = ilit / 2; + if (cadical_kitten->marks[iidx]) + INVALID_API_USAGE ("variable '%u' of literal '%u' occurs twice", + elit / 2, elit); + cadical_kitten->marks[iidx] = true; + PUSH_STACK (cadical_kitten->klause, ilit); + } + for (unsigned *p = cadical_kitten->klause.begin; p != cadical_kitten->klause.end; p++) + cadical_kitten->marks[*p / 2] = false; + new_original_klause (cadical_kitten, id); + CLEAR_STACK (cadical_kitten->klause); +} + +void citten_clause_with_id_and_exception (cadical_kitten *cadical_kitten, unsigned id, + size_t size, const int *elits, + unsigned except) { + REQUIRE_INITIALIZED (); + if (cadical_kitten->status) + reset_incremental (cadical_kitten); + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + const int *const end = elits + size; + for (const int *p = elits; p != end; p++) { + const unsigned elit = int2u (*p); // this is the conversion + if (elit == except) + continue; + const unsigned ilit = import_literal (cadical_kitten, elit); + CADICAL_assert (ilit < cadical_kitten->lits); + const unsigned iidx = ilit / 2; + if (cadical_kitten->marks[iidx]) + INVALID_API_USAGE ("variable '%u' of literal '%u' occurs twice", + elit / 2, elit); + cadical_kitten->marks[iidx] = true; + PUSH_STACK (cadical_kitten->klause, ilit); + } + for (unsigned *p = cadical_kitten->klause.begin; p != cadical_kitten->klause.end; p++) + cadical_kitten->marks[*p / 2] = false; + new_original_klause (cadical_kitten, id); + CLEAR_STACK (cadical_kitten->klause); +} + +void citten_clause_with_id_and_equivalence (cadical_kitten *cadical_kitten, unsigned id, + size_t size, const int *elits, + unsigned lit, unsigned other) { + REQUIRE_INITIALIZED (); + if (cadical_kitten->status) + reset_incremental (cadical_kitten); + CADICAL_assert (EMPTY_STACK (cadical_kitten->klause)); + bool sat = false; + const int *const end = elits + size; + for (const int *p = elits; p != end; p++) { + const unsigned elit = int2u (*p); // this is the conversion + if (elit == (lit ^ 1u) || elit == (other ^ 1u)) + continue; + if (elit == lit || elit == other) { + sat = true; + break; + } + const unsigned ilit = import_literal (cadical_kitten, elit); + CADICAL_assert (ilit < cadical_kitten->lits); + const unsigned iidx = ilit / 2; + if (cadical_kitten->marks[iidx]) + INVALID_API_USAGE ("variable '%u' of literal '%u' occurs twice", + elit / 2, elit); + cadical_kitten->marks[iidx] = true; + PUSH_STACK (cadical_kitten->klause, ilit); + } + for (unsigned *p = cadical_kitten->klause.begin; p != cadical_kitten->klause.end; p++) + cadical_kitten->marks[*p / 2] = false; + if (!sat) + new_original_klause (cadical_kitten, id); + CLEAR_STACK (cadical_kitten->klause); +} + +void cadical_kitten_clause (cadical_kitten *cadical_kitten, size_t size, unsigned *elits) { + cadical_kitten_clause_with_id_and_exception (cadical_kitten, INVALID, size, elits, + INVALID); +} + +void citten_clause_with_id (cadical_kitten *cadical_kitten, unsigned id, size_t size, + int *elits) { + citten_clause_with_id_and_exception (cadical_kitten, id, size, elits, INVALID); +} + +void cadical_kitten_unit (cadical_kitten *cadical_kitten, unsigned lit) { + cadical_kitten_clause (cadical_kitten, 1, &lit); +} + +void cadical_kitten_binary (cadical_kitten *cadical_kitten, unsigned a, unsigned b) { + unsigned clause[2] = {a, b}; + cadical_kitten_clause (cadical_kitten, 2, clause); +} + +int cadical_kitten_solve (cadical_kitten *cadical_kitten) { + REQUIRE_INITIALIZED (); + if (cadical_kitten->status) + reset_incremental (cadical_kitten); + else // if (cadical_kitten->level) + cadical_completely_backtrack_to_root_level (cadical_kitten); + + LOG ("starting solving under %zu assumptions", + SIZE_STACK (cadical_kitten->assumptions)); + + INC (cadical_kitten_solved); + + int res = propagate_units (cadical_kitten); + while (!res) { + const unsigned conflict = propagate (cadical_kitten); + if (cadical_kitten->terminator && + cadical_kitten->terminator (cadical_kitten->terminator_data)) { + LOG ("terminator requested termination"); + res = -1; + break; + } + if (conflict != INVALID) { + if (cadical_kitten->level) + analyze (cadical_kitten, conflict); + else { + inconsistent (cadical_kitten, conflict); + res = 20; + } + } else + res = decide (cadical_kitten); + } + + if (res < 0) + res = 0; + + if (!res && !EMPTY_STACK (cadical_kitten->assumptions)) + reset_assumptions (cadical_kitten); + + UPDATE_STATUS (res); + + if (res == 10) + INC (cadical_kitten_sat); + else if (res == 20) + INC (cadical_kitten_unsat); + else + INC (cadical_kitten_unknown); + + LOG ("finished solving with result %d", res); + + return res; +} + +int cadical_kitten_status (cadical_kitten *cadical_kitten) { return cadical_kitten->status; } + +unsigned cadical_kitten_compute_clausal_core (cadical_kitten *cadical_kitten, + uint64_t *learned_ptr) { + REQUIRE_STATUS (20); + + if (!cadical_kitten->antecedents) + INVALID_API_USAGE ("antecedents not tracked"); + + LOG ("computing clausal core"); + + unsigneds *resolved = &cadical_kitten->resolved; + CADICAL_assert (EMPTY_STACK (*resolved)); + + unsigned original = 0; + uint64_t learned = 0; + + unsigned reason_ref = cadical_kitten->inconsistent; + + if (reason_ref == INVALID) { + CADICAL_assert (!EMPTY_STACK (cadical_kitten->assumptions)); + reason_ref = cadical_kitten->failing; + if (reason_ref == INVALID) { + LOG ("assumptions mutually inconsistent"); + //goto DONE; + if (learned_ptr) + *learned_ptr = learned; + + LOG ("clausal core of %u original clauses", original); + LOG ("clausal core of %" PRIu64 " learned clauses", learned); + cadical_kitten->statistics.original = original; + cadical_kitten->statistics.learned = 0; + UPDATE_STATUS (21); + + return original; + } + } + + PUSH_STACK (*resolved, reason_ref); + unsigneds *core = &cadical_kitten->core; + CADICAL_assert (EMPTY_STACK (*core)); + + while (!EMPTY_STACK (*resolved)) { + const unsigned c_ref = POP_STACK (*resolved); + if (c_ref == INVALID) { + const unsigned d_ref = POP_STACK (*resolved); + ROG (d_ref, "core[%zu]", SIZE_STACK (*core)); + PUSH_STACK (*core, d_ref); + klause *d = dereference_klause (cadical_kitten, d_ref); + CADICAL_assert (!is_core_klause (d)); + set_core_klause (d); + if (is_learned_klause (d)) + learned++; + else + original++; + } else { + klause *c = dereference_klause (cadical_kitten, c_ref); + if (is_core_klause (c)) + continue; + PUSH_STACK (*resolved, c_ref); + PUSH_STACK (*resolved, INVALID); + ROG (c_ref, "analyzing antecedent core"); + if (!is_learned_klause (c)) + continue; + for (all_antecedents (d_ref, c)) { + klause *d = dereference_klause (cadical_kitten, d_ref); + if (!is_core_klause (d)) + PUSH_STACK (*resolved, d_ref); + } + } + } + + //DONE: + + if (learned_ptr) + *learned_ptr = learned; + + LOG ("clausal core of %u original clauses", original); + LOG ("clausal core of %" PRIu64 " learned clauses", learned); + cadical_kitten->statistics.original = original; + cadical_kitten->statistics.learned = 0; + UPDATE_STATUS (21); + + return original; +} + +void cadical_kitten_traverse_core_ids (cadical_kitten *cadical_kitten, void *state, + void (*traverse) (void *, unsigned)) { + REQUIRE_STATUS (21); + + LOG ("traversing core of original clauses"); + + unsigned traversed = 0; + + for (all_original_klauses (c)) { + // only happens for 'true' incremental calls, i.e. if add happens after + // solve + if (is_learned_klause (c)) + continue; + if (!is_core_klause (c)) + continue; + ROG (reference_klause (cadical_kitten, c), "traversing"); + traverse (state, c->aux); + traversed++; + } + + LOG ("traversed %u original core clauses", traversed); + (void) traversed; + + CADICAL_assert (cadical_kitten->status == 21); +} + +void cadical_kitten_traverse_core_clauses (cadical_kitten *cadical_kitten, void *state, + void (*traverse) (void *, bool, size_t, + const unsigned *)) { + REQUIRE_STATUS (21); + + LOG ("traversing clausal core"); + + unsigned traversed = 0; + + for (all_stack (unsigned, c_ref, cadical_kitten->core)) { + klause *c = dereference_klause (cadical_kitten, c_ref); + CADICAL_assert (is_core_klause (c)); + const bool learned = is_learned_klause (c); + unsigneds *eclause = &cadical_kitten->eclause; + CADICAL_assert (EMPTY_STACK (*eclause)); + for (all_literals_in_klause (ilit, c)) { + const unsigned elit = export_literal (cadical_kitten, ilit); + PUSH_STACK (*eclause, elit); + } + const size_t size = SIZE_STACK (*eclause); + const unsigned *elits = eclause->begin; + ROG (reference_klause (cadical_kitten, c), "traversing"); + traverse (state, learned, size, elits); + CLEAR_STACK (*eclause); + traversed++; + } + + LOG ("traversed %u core clauses", traversed); + (void) traversed; + + CADICAL_assert (cadical_kitten->status == 21); +} + +void cadical_kitten_traverse_core_clauses_with_id ( + cadical_kitten *cadical_kitten, void *state, + void (*traverse) (void *state, unsigned, bool learned, size_t, + const unsigned *)) { + REQUIRE_STATUS (21); + + LOG ("traversing clausal core"); + + unsigned traversed = 0; + + for (all_stack (unsigned, c_ref, cadical_kitten->core)) { + klause *c = dereference_klause (cadical_kitten, c_ref); + CADICAL_assert (is_core_klause (c)); + const bool learned = is_learned_klause (c); + unsigneds *eclause = &cadical_kitten->eclause; + CADICAL_assert (EMPTY_STACK (*eclause)); + for (all_literals_in_klause (ilit, c)) { + const unsigned elit = export_literal (cadical_kitten, ilit); + PUSH_STACK (*eclause, elit); + } + const size_t size = SIZE_STACK (*eclause); + const unsigned *elits = eclause->begin; + ROG (reference_klause (cadical_kitten, c), "traversing"); + unsigned ctag = learned ? 0 : c->aux; + traverse (state, ctag, learned, size, elits); + CLEAR_STACK (*eclause); + traversed++; + } + + LOG ("traversed %u core clauses", traversed); + (void) traversed; + + CADICAL_assert (cadical_kitten->status == 21); +} + +void cadical_kitten_trace_core (cadical_kitten *cadical_kitten, void *state, + void (*trace) (void *, unsigned, unsigned, bool, + size_t, const unsigned *, size_t, + const unsigned *)) { + REQUIRE_STATUS (21); + + LOG ("tracing clausal core"); + + unsigned traced = 0; + + for (all_stack (unsigned, c_ref, cadical_kitten->core)) { + klause *c = dereference_klause (cadical_kitten, c_ref); + CADICAL_assert (is_core_klause (c)); + const bool learned = is_learned_klause (c); + unsigneds *eclause = &cadical_kitten->eclause; + CADICAL_assert (EMPTY_STACK (*eclause)); + for (all_literals_in_klause (ilit, c)) { + const unsigned elit = export_literal (cadical_kitten, ilit); + PUSH_STACK (*eclause, elit); + } + const size_t size = SIZE_STACK (*eclause); + const unsigned *elits = eclause->begin; + + unsigneds *resolved = &cadical_kitten->resolved; + CADICAL_assert (EMPTY_STACK (*resolved)); + if (learned) { + for (all_antecedents (ref, c)) { + PUSH_STACK (*resolved, ref); + } + } + const size_t rsize = SIZE_STACK (*resolved); + const unsigned *rids = resolved->begin; + + unsigned cid = reference_klause (cadical_kitten, c); + unsigned ctag = learned ? 0 : c->aux; + ROG (cid, "tracing"); + trace (state, cid, ctag, learned, size, elits, rsize, rids); + CLEAR_STACK (*eclause); + CLEAR_STACK (*resolved); + traced++; + } + + LOG ("traced %u core clauses", traced); + (void) traced; + + CADICAL_assert (cadical_kitten->status == 21); +} + +void cadical_kitten_shrink_to_clausal_core (cadical_kitten *cadical_kitten) { + REQUIRE_STATUS (21); + + LOG ("shrinking formula to core of original clauses"); + + CLEAR_STACK (cadical_kitten->trail); + + cadical_kitten->unassigned = cadical_kitten->lits / 2; + cadical_kitten->propagated = 0; + cadical_kitten->level = 0; + + update_search (cadical_kitten, cadical_kitten->queue.last); + + memset (cadical_kitten->values, 0, cadical_kitten->lits); + + for (all_kits (lit)) + CLEAR_STACK (KATCHES (lit)); + + CADICAL_assert (cadical_kitten->inconsistent != INVALID); + klause *inconsistent = dereference_klause (cadical_kitten, cadical_kitten->inconsistent); + if (is_learned_klause (inconsistent) || inconsistent->size) { + ROG (cadical_kitten->inconsistent, "resetting inconsistent"); + cadical_kitten->inconsistent = INVALID; + } else + ROG (cadical_kitten->inconsistent, "keeping inconsistent"); + + CLEAR_STACK (cadical_kitten->units); + + klause *begin = begin_klauses (cadical_kitten), *q = begin; + klause const *const end = end_original_klauses (cadical_kitten); +#ifdef LOGGING + unsigned original = 0; +#endif + for (klause *c = begin, *next; c != end; c = next) { + next = next_klause (cadical_kitten, c); + // CADICAL_assert (!is_learned_klause (c)); not necessarily true + if (is_learned_klause (c)) + continue; + if (!is_core_klause (c)) + continue; + unset_core_klause (c); + const unsigned dst = (unsigned *) q - (unsigned *) begin; + const unsigned size = c->size; + if (!size) { + if (cadical_kitten->inconsistent != INVALID) + cadical_kitten->inconsistent = dst; + } else if (size == 1) { + PUSH_STACK (cadical_kitten->units, dst); + ROG (dst, "keeping"); + } else { + watch_klause (cadical_kitten, c->lits[0], dst); + watch_klause (cadical_kitten, c->lits[1], dst); + } + if (c == q) + q = next; + else { + const size_t bytes = (char *) next - (char *) c; + memmove (q, c, bytes); + q = (klause *) ((char *) q + bytes); + } +#ifdef LOGGING + original++; +#endif + } + SET_END_OF_STACK (cadical_kitten->klauses, (unsigned *) q); + cadical_kitten->end_original_ref = SIZE_STACK (cadical_kitten->klauses); + LOG ("end of original clauses at %zu", cadical_kitten->end_original_ref); + LOG ("%u original clauses left", original); + + CLEAR_STACK (cadical_kitten->core); + + UPDATE_STATUS (0); +} + +signed char cadical_kitten_signed_value (cadical_kitten *cadical_kitten, int selit) { + REQUIRE_STATUS (10); + const unsigned elit = int2u (selit); + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + return 0; + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) + return 0; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return cadical_kitten->values[ilit]; +} + +signed char cadical_kitten_value (cadical_kitten *cadical_kitten, unsigned elit) { + REQUIRE_STATUS (10); + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + return 0; + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) + return 0; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return cadical_kitten->values[ilit]; +} + +signed char cadical_kitten_fixed (cadical_kitten *cadical_kitten, unsigned elit) { + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + return 0; + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) + return 0; + iidx--; + const unsigned ilit = 2 * iidx + (elit & 1); + signed char res = cadical_kitten->values[ilit]; + if (!res) + return 0; + kar *v = cadical_kitten->vars + iidx; + if (v->level) + return 0; + return res; +} + +signed char cadical_kitten_fixed_signed (cadical_kitten *cadical_kitten, int elit) { + unsigned kelit = int2u (elit); + return cadical_kitten_fixed (cadical_kitten, kelit); +} + +bool cadical_kitten_flip_literal (cadical_kitten *cadical_kitten, unsigned elit) { + REQUIRE_STATUS (10); + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + return false; + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) + return false; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + if (cadical_kitten_fixed (cadical_kitten, elit)) + return false; + return flip_literal (cadical_kitten, ilit); +} + +bool cadical_kitten_flip_signed_literal (cadical_kitten *cadical_kitten, int elit) { + REQUIRE_STATUS (10); + unsigned kelit = int2u (elit); + return cadical_kitten_flip_literal (cadical_kitten, kelit); +} + +bool cadical_kitten_failed (cadical_kitten *cadical_kitten, unsigned elit) { + REQUIRE_STATUS (20); + const unsigned eidx = elit / 2; + if (eidx >= cadical_kitten->evars) + return false; + unsigned iidx = cadical_kitten->import[eidx]; + if (!iidx) + return false; + const unsigned ilit = 2 * (iidx - 1) + (elit & 1); + return cadical_kitten->failed[ilit]; +} + +// checks both watches for clauses with only one literal positively +// assigned. if such a clause is found, return false. Otherwise fix watch +// invariant and return true +static bool prime_propagate (cadical_kitten *cadical_kitten, const unsigned idx, + void *state, const bool ignoring, + bool (*ignore) (void *, unsigned)) { + unsigned lit = 2 * idx; + unsigned conflict = INVALID; + value *values = cadical_kitten->values; + for (int i = 0; i < 2; i++) { + if (conflict != INVALID) + break; + lit = lit ^ i; + const unsigned not_lit = lit ^ 1; + katches *watches = cadical_kitten->watches + not_lit; + unsigned *q = BEGIN_STACK (*watches); + const unsigned *const end_watches = END_STACK (*watches); + unsigned const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + while (p != end_watches) { + const unsigned ref = *q++ = *p++; + klause *c = dereference_klause (cadical_kitten, ref); + if (is_learned_klause (c) || ignore (state, c->aux) == ignoring) + continue; + CADICAL_assert (c->size > 1); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ not_lit; + const value other_value = values[other]; + ticks++; + if (other_value > 0) + continue; + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + replacement_value = values[replacement]; + if (replacement_value > 0) + break; + } + if (replacement_value > 0) { + CADICAL_assert (replacement != INVALID); + ROG (ref, "unwatching %u in", not_lit); + lits[0] = other; + lits[1] = replacement; + *r = not_lit; + watch_klause (cadical_kitten, replacement, ref); + q--; + } else { + ROG (ref, "idx %u forced prime by", idx); + conflict = ref; + break; + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (cadical_kitten_ticks, ticks); + } + return conflict == INVALID; +} + +void cadical_kitten_add_prime_implicant (cadical_kitten *cadical_kitten, void *state, int side, + void (*add_implicant) (void *, int, size_t, + const unsigned *)) { + REQUIRE_STATUS (11); + // might be possible in some edge cases + unsigneds *prime = &cadical_kitten->prime[side]; + unsigneds *prime2 = &cadical_kitten->prime[!side]; + CADICAL_assert (!EMPTY_STACK (*prime) || !EMPTY_STACK (*prime2)); + CLEAR_STACK (*prime2); + + for (all_stack (unsigned, lit, *prime)) { + const unsigned not_lit = lit ^ 1; + const unsigned elit = export_literal (cadical_kitten, not_lit); + PUSH_STACK (*prime2, elit); + } + + // adds a clause which will reset cadical_kitten status and backtrack + add_implicant (state, side, SIZE_STACK (*prime2), BEGIN_STACK (*prime2)); + CLEAR_STACK (*prime); + CLEAR_STACK (*prime2); +} + +// computes two prime implicants, only considering clauses based on ignore +// return -1 if no prime implicant has been computed, otherwise returns +// index of shorter implicant. +// TODO does not work if flip has been called beforehand +int cadical_kitten_compute_prime_implicant (cadical_kitten *cadical_kitten, void *state, + bool (*ignore) (void *, unsigned)) { + REQUIRE_STATUS (10); + + value *values = cadical_kitten->values; + kar *vars = cadical_kitten->vars; + unsigneds unassigned; + INIT_STACK (unassigned); + bool limit_hit = 0; + CADICAL_assert (EMPTY_STACK (cadical_kitten->prime[0]) && EMPTY_STACK (cadical_kitten->prime[1])); + for (int i = 0; i < 2; i++) { + const bool ignoring = i; + for (all_stack (unsigned, lit, cadical_kitten->trail)) { + if (KITTEN_TICKS >= cadical_kitten->limits.ticks) { + LOG ("ticks limit %" PRIu64 " hit after %" PRIu64 " ticks", + cadical_kitten->limits.ticks, KITTEN_TICKS); + limit_hit = 1; + break; + } + CADICAL_assert (values[lit] > 0); + const unsigned idx = lit / 2; + const unsigned ref = vars[idx].reason; + CADICAL_assert (vars[idx].level); + klause *c = 0; + if (ref != INVALID) + c = dereference_klause (cadical_kitten, ref); + if (ref == INVALID || is_learned_klause (c) || + ignore (state, c->aux) == ignoring) { + LOG ("non-prime candidate var %d", idx); + if (prime_propagate (cadical_kitten, idx, state, ignoring, ignore)) { + values[lit] = 0; + values[lit ^ 1] = 0; + PUSH_STACK (unassigned, lit); + } else + CADICAL_assert (values[lit] > 0); + } + } + unsigneds *prime = &cadical_kitten->prime[i]; + // push on prime implicant stack. + for (all_kits (lit)) { + if (values[lit] > 0) + PUSH_STACK (*prime, lit); + } + // reassign all literals on + for (all_stack (unsigned, lit, unassigned)) { + CADICAL_assert (!values[lit]); + values[lit] = 1; + values[lit ^ 1] = -1; + } + CLEAR_STACK (unassigned); + } + RELEASE_STACK (unassigned); + + if (limit_hit) { + CLEAR_STACK (cadical_kitten->prime[0]); + CLEAR_STACK (cadical_kitten->prime[1]); + return -1; + } + // the only case when one of the prime implicants is allowed to be empty + // is if ignore returns always true or always false. + CADICAL_assert (!EMPTY_STACK (cadical_kitten->prime[0]) || + !EMPTY_STACK (cadical_kitten->prime[1])); + UPDATE_STATUS (11); + + int res = SIZE_STACK (cadical_kitten->prime[0]) > SIZE_STACK (cadical_kitten->prime[1]); + return res; +} + +static bool contains_blit (cadical_kitten *cadical_kitten, klause *c, const unsigned blit) { + for (all_literals_in_klause (lit, c)) { + if (lit == blit) + return true; + } + return false; +} + +static bool prime_propagate_blit (cadical_kitten *cadical_kitten, const unsigned idx, + const unsigned blit) { + unsigned lit = 2 * idx; + unsigned conflict = INVALID; + value *values = cadical_kitten->values; + LOG ("prime propagating idx %u for blit %u", idx, blit); + for (int i = 0; i < 2; i++) { + if (conflict != INVALID) + break; + lit = lit ^ i; + if (lit == blit) + continue; + const unsigned not_lit = lit ^ 1; + katches *watches = cadical_kitten->watches + not_lit; + unsigned *q = BEGIN_STACK (*watches); + const unsigned *const end_watches = END_STACK (*watches); + unsigned const *p = q; + uint64_t ticks = (((char *) end_watches - (char *) q) >> 7) + 1; + while (p != end_watches) { + const unsigned ref = *q++ = *p++; + klause *c = dereference_klause (cadical_kitten, ref); + if (is_learned_klause (c)) + continue; + ROG (ref, "checking with blit %u", blit); + CADICAL_assert (c->size > 1); + unsigned *lits = c->lits; + const unsigned other = lits[0] ^ lits[1] ^ not_lit; + const value other_value = values[other]; + ticks++; + bool use = other == blit || not_lit == blit; + if (other_value > 0) + continue; + value replacement_value = -1; + unsigned replacement = INVALID; + const unsigned *const end_lits = lits + c->size; + unsigned *r; + for (r = lits + 2; r != end_lits; r++) { + replacement = *r; + replacement_value = values[replacement]; + use = use || replacement == blit; + if (replacement_value > 0) + break; + } + if (replacement_value > 0) { + CADICAL_assert (replacement != INVALID); + ROG (ref, "unwatching %u in", not_lit); + lits[0] = other; + lits[1] = replacement; + *r = not_lit; + watch_klause (cadical_kitten, replacement, ref); + q--; + } else if (!use) { + continue; + } else { + ROG (ref, "idx %u forced prime by", idx); + conflict = ref; + break; + } + } + while (p != end_watches) + *q++ = *p++; + SET_END_OF_STACK (*watches, q); + ADD (cadical_kitten_ticks, ticks); + } + return conflict == INVALID; +} + +static int compute_prime_implicant_for (cadical_kitten *cadical_kitten, unsigned blit) { + value *values = cadical_kitten->values; + kar *vars = cadical_kitten->vars; + unsigneds unassigned; + INIT_STACK (unassigned); + bool limit_hit = false; + CADICAL_assert (EMPTY_STACK (cadical_kitten->prime[0]) && EMPTY_STACK (cadical_kitten->prime[1])); + for (int i = 0; i < 2; i++) { + const unsigned block = blit ^ i; + const bool ignoring = i; + if (prime_propagate_blit (cadical_kitten, block / 2, block)) { + value tmp = values[blit]; + CADICAL_assert (tmp); + values[blit] = 0; + values[blit ^ 1] = 0; + PUSH_STACK (unassigned, tmp > 0 ? blit : blit ^ 1); + PUSH_STACK (cadical_kitten->prime[i], block); // will be negated! + } else + CADICAL_assert (false); + for (all_stack (unsigned, lit, cadical_kitten->trail)) { + if (KITTEN_TICKS >= cadical_kitten->limits.ticks) { + LOG ("ticks limit %" PRIu64 " hit after %" PRIu64 " ticks", + cadical_kitten->limits.ticks, KITTEN_TICKS); + limit_hit = true; + break; + } + if (!values[lit]) + continue; + CADICAL_assert (values[lit]); // not true when flipping is involved + const unsigned idx = lit / 2; + const unsigned ref = vars[idx].reason; + CADICAL_assert (vars[idx].level); + LOG ("non-prime candidate var %d", idx); + if (prime_propagate_blit (cadical_kitten, idx, block)) { + value tmp = values[lit]; + CADICAL_assert (tmp); + values[lit] = 0; + values[lit ^ 1] = 0; + PUSH_STACK (unassigned, tmp > 0 ? lit : lit ^ 1); + } + } + unsigneds *prime = &cadical_kitten->prime[i]; + // push on prime implicant stack. + for (all_kits (lit)) { + if (values[lit] > 0) + PUSH_STACK (*prime, lit); + } + // reassign all literals on + for (all_stack (unsigned, lit, unassigned)) { + CADICAL_assert (!values[lit]); + values[lit] = 1; + values[lit ^ 1] = -1; + } + CLEAR_STACK (unassigned); + } + RELEASE_STACK (unassigned); + + if (limit_hit) { + CLEAR_STACK (cadical_kitten->prime[0]); + CLEAR_STACK (cadical_kitten->prime[1]); + return -1; + } + // the only case when one of the prime implicants is allowed to be empty + // is if ignore returns always true or always false. + CADICAL_assert (!EMPTY_STACK (cadical_kitten->prime[0]) || + !EMPTY_STACK (cadical_kitten->prime[1])); + LOGLITS (BEGIN_STACK (cadical_kitten->prime[0]), SIZE_STACK (cadical_kitten->prime[0]), + "first implicant %u", blit); + LOGLITS (BEGIN_STACK (cadical_kitten->prime[1]), SIZE_STACK (cadical_kitten->prime[1]), + "second implicant %u", blit ^ 1); + UPDATE_STATUS (11); + + int res = SIZE_STACK (cadical_kitten->prime[0]) > SIZE_STACK (cadical_kitten->prime[1]); + return res; +} + +int cadical_kitten_flip_and_implicant_for_signed_literal (cadical_kitten *cadical_kitten, + int elit) { + REQUIRE_STATUS (10); + unsigned kelit = int2u (elit); + if (!cadical_kitten_flip_literal (cadical_kitten, kelit)) { + return -2; + } + const unsigned eidx = kelit / 2; + unsigned iidx = cadical_kitten->import[eidx]; + CADICAL_assert (iidx); + const unsigned ilit = 2 * (iidx - 1) + (kelit & 1); + return compute_prime_implicant_for (cadical_kitten, ilit); +} + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_lidruptracer.cpp b/src/sat/cadical/cadical_lidruptracer.cpp new file mode 100644 index 000000000..db07e36e6 --- /dev/null +++ b/src/sat/cadical/cadical_lidruptracer.cpp @@ -0,0 +1,662 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +LidrupTracer::LidrupTracer (Internal *i, File *f, bool b) + : internal (i), file (f), binary (b), num_clauses (0), size_clauses (0), + clauses (0), last_hash (0), last_id (0), last_clause (0) +#ifndef CADICAL_QUIET + , + added (0), deleted (0) +#endif +{ + (void) internal; + + // Initialize random number table for hash function. + // + Random random (42); + for (unsigned n = 0; n < num_nonces; n++) { + uint64_t nonce = random.next (); + if (!(nonce & 1)) + nonce++; + CADICAL_assert (nonce), CADICAL_assert (nonce & 1); + nonces[n] = nonce; + } +#ifndef CADICAL_NDEBUG + binary = b; +#else + (void) b; +#endif + piping = file->piping (); +} + +void LidrupTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("LIDRUP TRACER connected to internal"); +} + +LidrupTracer::~LidrupTracer () { + LOG ("LIDRUP TRACER delete"); + delete file; + for (size_t i = 0; i < size_clauses; i++) + for (LidrupClause *c = clauses[i], *next; c; c = next) + next = c->next, delete_clause (c); + delete[] clauses; +} + +/*------------------------------------------------------------------------*/ + +void LidrupTracer::enlarge_clauses () { + CADICAL_assert (num_clauses == size_clauses); + const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1; + LOG ("LIDRUP Tracer enlarging clauses of tracer from %" PRIu64 + " to %" PRIu64, + (uint64_t) size_clauses, (uint64_t) new_size_clauses); + LidrupClause **new_clauses; + new_clauses = new LidrupClause *[new_size_clauses]; + clear_n (new_clauses, new_size_clauses); + for (uint64_t i = 0; i < size_clauses; i++) { + for (LidrupClause *c = clauses[i], *next; c; c = next) { + next = c->next; + const uint64_t h = reduce_hash (c->hash, new_size_clauses); + c->next = new_clauses[h]; + new_clauses[h] = c; + } + } + delete[] clauses; + clauses = new_clauses; + size_clauses = new_size_clauses; +} + +LidrupClause *LidrupTracer::new_clause () { + LidrupClause *res = new LidrupClause; + res->next = 0; + res->hash = last_hash; + res->id = last_id; + for (const auto &id : imported_chain) { + res->chain.push_back (id); + } + for (const auto &lit : imported_clause) { + res->literals.push_back (lit); + } + last_clause = res; + num_clauses++; + return res; +} + +void LidrupTracer::delete_clause (LidrupClause *c) { + CADICAL_assert (c); + num_clauses--; + delete c; +} + +uint64_t LidrupTracer::reduce_hash (uint64_t hash, uint64_t size) { + CADICAL_assert (size > 0); + unsigned shift = 32; + uint64_t res = hash; + while ((((uint64_t) 1) << shift) > size) { + res ^= res >> shift; + shift >>= 1; + } + res &= size - 1; + CADICAL_assert (res < size); + return res; +} + +uint64_t LidrupTracer::compute_hash (const int64_t id) { + CADICAL_assert (id > 0); + unsigned j = id % num_nonces; + uint64_t tmp = nonces[j] * (uint64_t) id; + return last_hash = tmp; +} + +bool LidrupTracer::find_and_delete (const int64_t id) { + if (!num_clauses) + return false; + LidrupClause **res = 0, *c; + const uint64_t hash = compute_hash (id); + const uint64_t h = reduce_hash (hash, size_clauses); + for (res = clauses + h; (c = *res); res = &c->next) { + if (c->hash == hash && c->id == id) { + break; + } + if (!c->next) + return false; + } + if (!c) + return false; + CADICAL_assert (c && res); + *res = c->next; + for (auto &lit : c->literals) { + imported_clause.push_back (lit); + } + for (auto &cid : c->chain) { + imported_chain.push_back (cid); + } + delete_clause (c); + return true; +} + +void LidrupTracer::insert () { + if (num_clauses == size_clauses) + enlarge_clauses (); + const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses); + LidrupClause *c = new_clause (); + c->next = clauses[h]; + clauses[h] = c; +} + +/*------------------------------------------------------------------------*/ + +inline void LidrupTracer::flush_if_piping () { + if (piping) + file->flush (); +} + +inline void LidrupTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void LidrupTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned x = 2 * abs (lit) + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +inline void LidrupTracer::put_binary_id (int64_t id, bool can_be_negative) { + CADICAL_assert (binary); + CADICAL_assert (file); + uint64_t x = abs (id); + if (can_be_negative) { + x = 2 * x + (id < 0); + } + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void LidrupTracer::lidrup_add_restored_clause (int64_t id) { + if (!batch_weaken.empty () || !batch_delete.empty ()) + lidrup_batch_weaken_restore_and_delete (); + batch_restore.push_back (id); +} + +void LidrupTracer::lidrup_add_derived_clause ( + int64_t id, const vector &clause, const vector &chain) { + lidrup_batch_weaken_restore_and_delete (); + if (binary) { + file->put ('l'); + put_binary_id (id); + } else { + file->put ("l "); + file->put (id); + file->put (' '); + } + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0 "); + for (const auto &cid : chain) + if (binary) + put_binary_id (cid); + else + file->put (cid), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +void LidrupTracer::lidrup_add_original_clause (int64_t id, + const vector &clause) { + lidrup_batch_weaken_restore_and_delete (); + if (binary) { + file->put ('i'); + put_binary_id (id); + } else { + file->put ("i "); + file->put (id); + file->put (' '); + } + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +} + +void LidrupTracer::lidrup_batch_weaken_restore_and_delete () { + CADICAL_assert (batch_weaken.empty () || batch_delete.empty ()); + if (!batch_weaken.empty ()) { + if (binary) { + file->put ('w'); + } else { + file->put ("w "); + } + for (const auto &id : batch_weaken) { + if (binary) + put_binary_id (id); + else + file->put (id), file->put (' '); + } + batch_weaken.clear (); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +#ifndef CADICAL_QUIET + batched++; +#endif + } + if (!batch_delete.empty ()) { + if (binary) { + file->put ('d'); + } else { + file->put ("d "); + } + for (const auto &id : batch_delete) { + if (binary) + put_binary_id (id); + else + file->put (id), file->put (' '); + } + batch_delete.clear (); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +#ifndef CADICAL_QUIET + batched++; +#endif + } + if (!batch_restore.empty ()) { + if (binary) { + file->put ('r'); + } else { + file->put ("r "); + } + for (const auto &id : batch_restore) { + if (binary) + put_binary_id (id); + else + file->put (id), file->put (' '); + } + batch_restore.clear (); + if (binary) + put_binary_zero (); + else + file->put ("0\n"); +#ifndef CADICAL_QUIET + batched++; +#endif + } +} + +void LidrupTracer::lidrup_conclude_and_delete ( + const vector &conclusion) { + lidrup_batch_weaken_restore_and_delete (); + int64_t size = conclusion.size (); + if (size > 1) { + if (binary) { + file->put ('U'); + put_binary_id (size); + } else { + file->put ("U "); + file->put (size), file->put ("\n"); + } + } + for (auto &id : conclusion) { + if (binary) + file->put ('u'); + else + file->put ("u "); + if (!find_and_delete (id)) { + CADICAL_assert (imported_clause.empty ()); + CADICAL_assert (conclusion.size () == 1); + if (binary) { + put_binary_zero (); + put_binary_id (id); + put_binary_zero (); + } else { + file->put ("0 "); + file->put (id); + file->put (" 0\n"); + } + } else { + for (const auto &external_lit : imported_clause) { + // flip sign... + const auto not_elit = -external_lit; + if (binary) + put_binary_lit (not_elit); + else + file->put (not_elit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0 "); + for (const auto &cid : imported_chain) { + if (binary) + put_binary_id (cid); + else + file->put (cid), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + imported_clause.clear (); + imported_chain.clear (); + } + } + flush_if_piping (); +} + +void LidrupTracer::lidrup_report_status (int status) { + lidrup_batch_weaken_restore_and_delete (); + if (binary) + file->put ('s'); + else + file->put ("s "); + if (status == SATISFIABLE) + file->put ("SATISFIABLE"); + else if (status == UNSATISFIABLE) + file->put ("UNSATISFIABLE"); + else + file->put ("UNKNOWN"); + if (!binary) + file->put ("\n"); + flush_if_piping (); +} + +void LidrupTracer::lidrup_conclude_sat (const vector &model) { + lidrup_batch_weaken_restore_and_delete (); + if (binary) + file->put ('m'); + else + file->put ("m "); + for (auto &lit : model) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +void LidrupTracer::lidrup_conclude_unknown (const vector &trail) { + lidrup_batch_weaken_restore_and_delete (); + if (binary) + file->put ('e'); + else + file->put ("e "); + for (auto &lit : trail) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +void LidrupTracer::lidrup_solve_query () { + lidrup_batch_weaken_restore_and_delete (); + if (binary) + file->put ('q'); + else + file->put ("q "); + for (auto &lit : assumptions) { + if (binary) + put_binary_lit (lit); + else + file->put (lit), file->put (' '); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + flush_if_piping (); +} + +/*------------------------------------------------------------------------*/ + +void LidrupTracer::add_derived_clause (int64_t id, bool, + const vector &clause, + const vector &chain) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (clause, "LIDRUP TRACER tracing addition of derived clause"); + lidrup_add_derived_clause (id, clause, chain); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void LidrupTracer::add_assumption_clause (int64_t id, + const vector &clause, + const vector &chain) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (clause, + "LIDRUP TRACER tracing addition of assumption clause[%" PRId64 "]", + id); + for (auto &lit : clause) + imported_clause.push_back (lit); + for (auto &cid : chain) + imported_chain.push_back (cid); + last_id = id; + insert (); + imported_clause.clear (); + imported_chain.clear (); +} + +void LidrupTracer::delete_clause (int64_t id, bool, const vector &) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG ("LIDRUP TRACER tracing deletion of clause[%" PRId64 "]", id); + if (find_and_delete (id)) { + CADICAL_assert (imported_clause.empty ()); + if (!batch_delete.empty () || !batch_restore.empty ()) + lidrup_batch_weaken_restore_and_delete (); + batch_weaken.push_back (id); +#ifndef CADICAL_QUIET + weakened++; +#endif + } else { + if (!batch_weaken.empty () || !batch_restore.empty ()) + lidrup_batch_weaken_restore_and_delete (); + batch_delete.push_back (id); +#ifndef CADICAL_QUIET + deleted++; +#endif + } +} + +void LidrupTracer::weaken_minus (int64_t id, const vector &) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG ("LIDRUP TRACER tracing weaken minus of clause[%" PRId64 "]", id); + last_id = id; + insert (); +} + +void LidrupTracer::conclude_unsat (ConclusionType, + const vector &conclusion) { + if (file->closed ()) + return; + CADICAL_assert (imported_clause.empty ()); + LOG (conclusion, "LIDRUP TRACER tracing conclusion of clause(s)"); + lidrup_conclude_and_delete (conclusion); +} + +void LidrupTracer::add_original_clause (int64_t id, bool, + const vector &clause, + bool restored) { + if (file->closed ()) + return; + if (!restored) { + LOG (clause, "LIDRUP TRACER tracing addition of original clause"); +#ifndef CADICAL_QUIET + original++; +#endif + return lidrup_add_original_clause (id, clause); + } + CADICAL_assert (restored); + if (find_and_delete (id)) { + LOG (clause, + "LIDRUP TRACER the clause was not yet weakened, so no restore"); + return; + } + LOG (clause, "LIDRUP TRACER tracing addition of restored clause"); + lidrup_add_restored_clause (id); +#ifndef CADICAL_QUIET + restore++; +#endif +} + +void LidrupTracer::report_status (int status, int64_t) { + if (file->closed ()) + return; + LOG ("LIDRUP TRACER tracing report of status %d", status); + lidrup_report_status (status); +} + +void LidrupTracer::conclude_sat (const vector &model) { + if (file->closed ()) + return; + LOG (model, "LIDRUP TRACER tracing conclusion of model"); + lidrup_conclude_sat (model); +} + +void LidrupTracer::conclude_unknown (const vector &entrailed) { + if (file->closed ()) + return; + LOG (entrailed, "LIDRUP TRACER tracing conclusion of UNK"); + lidrup_conclude_unknown (entrailed); +} + +void LidrupTracer::solve_query () { + if (file->closed ()) + return; + LOG (assumptions, "LIDRUP TRACER tracing solve query with assumptions"); + lidrup_solve_query (); +#ifndef CADICAL_QUIET + solved++; +#endif +} + +void LidrupTracer::add_assumption (int lit) { + LOG ("LIDRUP TRACER tracing addition of assumption %d", lit); + assumptions.push_back (lit); +} + +void LidrupTracer::reset_assumptions () { + LOG (assumptions, "LIDRUP TRACER tracing reset of assumptions"); + assumptions.clear (); +} + +/*------------------------------------------------------------------------*/ + +bool LidrupTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void LidrupTracer::print_statistics () { + // TODO complete this. + uint64_t bytes = file->bytes (); + uint64_t total = added + deleted + weakened + restore + original; + MSG ("LIDRUP %" PRId64 " original clauses %.2f%%", original, + percent (original, total)); + MSG ("LIDRUP %" PRId64 " learned clauses %.2f%%", added, + percent (added, total)); + MSG ("LIDRUP %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("LIDRUP %" PRId64 " weakened clauses %.2f%%", weakened, + percent (weakened, total)); + MSG ("LIDRUP %" PRId64 " restored clauses %.2f%%", restore, + percent (restore, total)); + MSG ("LIDRUP %" PRId64 " batches of deletions, weaken and restores %.2f", + batched, relative (batched, deleted + restore + weakened)); + MSG ("LIDRUP %" PRId64 " queries %.2f", solved, relative (solved, total)); + MSG ("LIDRUP %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void LidrupTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("LIDRUP proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void LidrupTracer::flush (bool print) { + CADICAL_assert (!closed ()); + lidrup_batch_weaken_restore_and_delete (); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("LIDRUP proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_limit.cpp b/src/sat/cadical/cadical_limit.cpp new file mode 100644 index 000000000..69417a473 --- /dev/null +++ b/src/sat/cadical/cadical_limit.cpp @@ -0,0 +1,135 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +Limit::Limit () { memset (this, 0, sizeof *this); } + +/*------------------------------------------------------------------------*/ + +double Internal::scale (double v) const { + const double ratio = clause_variable_ratio (); + const double factor = (ratio <= 2) ? 1.0 : log (ratio) / log (2); + double res = factor * v; + if (res < 1) + res = 1; + return res; +} + +/*------------------------------------------------------------------------*/ + +Last::Last () { memset (this, 0, sizeof *this); } + +/*------------------------------------------------------------------------*/ + +Inc::Inc () { + memset (this, 0, sizeof *this); + decisions = conflicts = -1; // unlimited +} + +void Internal::limit_terminate (int l) { + if (l <= 0 && !lim.terminate.forced) { + LOG ("keeping unbounded terminate limit"); + } else if (l <= 0) { + LOG ("reset terminate limit to be unbounded"); + lim.terminate.forced = 0; + } else { + lim.terminate.forced = l; + LOG ("new terminate limit of %d calls", l); + } +} + +void Internal::limit_conflicts (int l) { + if (l < 0 && inc.conflicts < 0) { + LOG ("keeping unbounded conflict limit"); + } else if (l < 0) { + LOG ("reset conflict limit to be unbounded"); + inc.conflicts = -1; + } else { + inc.conflicts = l; + LOG ("new conflict limit of %d conflicts", l); + } +} + +void Internal::limit_decisions (int l) { + if (l < 0 && inc.decisions < 0) { + LOG ("keeping unbounded decision limit"); + } else if (l < 0) { + LOG ("reset decision limit to be unbounded"); + inc.decisions = -1; + } else { + inc.decisions = l; + LOG ("new decision limit of %d decisions", l); + } +} + +void Internal::limit_preprocessing (int l) { + if (l < 0) { + LOG ("ignoring invalid preprocessing limit %d", l); + } else if (!l) { + LOG ("reset preprocessing limit to no preprocessing"); + inc.preprocessing = 0; + } else { + inc.preprocessing = l; + LOG ("new preprocessing limit of %d preprocessing rounds", l); + } +} + +void Internal::limit_local_search (int l) { + if (l < 0) { + LOG ("ignoring invalid local search limit %d", l); + } else if (!l) { + LOG ("reset local search limit to no local search"); + inc.localsearch = 0; + } else { + inc.localsearch = l; + LOG ("new local search limit of %d local search rounds", l); + } +} + +bool Internal::is_valid_limit (const char *name) { + if (!strcmp (name, "terminate")) + return true; + if (!strcmp (name, "conflicts")) + return true; + if (!strcmp (name, "decisions")) + return true; + if (!strcmp (name, "preprocessing")) + return true; + if (!strcmp (name, "localsearch")) + return true; + return false; +} + +bool Internal::limit (const char *name, int l) { + bool res = true; + if (!strcmp (name, "terminate")) + limit_terminate (l); + else if (!strcmp (name, "conflicts")) + limit_conflicts (l); + else if (!strcmp (name, "decisions")) + limit_decisions (l); + else if (!strcmp (name, "preprocessing")) + limit_preprocessing (l); + else if (!strcmp (name, "localsearch")) + limit_local_search (l); + else + res = false; + return res; +} + +void Internal::reset_limits () { + LOG ("reset limits"); + limit_terminate (0); + limit_conflicts (-1); + limit_decisions (-1); + limit_preprocessing (0); + limit_local_search (0); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_logging.cpp b/src/sat/cadical/cadical_logging.cpp new file mode 100644 index 000000000..589bf097e --- /dev/null +++ b/src/sat/cadical/cadical_logging.cpp @@ -0,0 +1,221 @@ +#include "global.h" + +#ifdef LOGGING + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Logger::print_log_prefix (Internal *internal) { + internal->print_prefix (); + tout.magenta (); + fputs ("LOG ", stdout); + tout.magenta (true); + printf ("%d ", internal->level); + tout.normal (); +} + +void Logger::log_empty_line (Internal *internal) { + internal->print_prefix (); + tout.magenta (); + const int len = internal->prefix.size (), max = 78 - len; + for (int i = 0; i < max; i++) + fputc ('-', stdout); + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +void Logger::log (Internal *internal, const char *fmt, ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +// It is hard to factor out the common part between the two clause loggers, +// since they are also used in slightly different contexts. Our attempt to +// do so were not more readable than the current version. See the header +// for an explanation of the difference between the following two functions. + +void Logger::log (Internal *internal, const Clause *c, const char *fmt, + ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (c) { + if (c->redundant) + printf (" glue %d redundant", c->glue); + else + printf (" irredundant"); + printf (" size %d clause[%" PRId64 "]", c->size, c->id); + if (c->moved) + printf (" ... (moved)"); + else { + if (internal->opts.logsort) { + vector s; + for (const auto &lit : *c) + s.push_back (lit); + sort (s.begin (), s.end (), clause_lit_less_than ()); + for (const auto &lit : s) + printf (" %d", lit); + } else { + for (const auto &lit : *c) { + printf (" %s", loglit (internal, lit).c_str ()); + } + } + } + } else if (internal->level) + printf (" decision"); + else + printf (" unit"); + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +void Logger::log (Internal *internal, const Gate *g, const char *fmt, ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (g) { + printf ("%s%s%s gate[%" PRIu64 "] (arity: %ld) %s := %s", + g->degenerated_and_pos ? " deg+" : "", + g->degenerated_and_neg ? " deg-" : "", + g->garbage ? " garbage" : "", g->id, g->arity (), + loglit (internal, g->lhs).c_str (), + string_of_gate (g->tag).c_str ()); + for (const auto &lit : g->rhs) { + printf (" %s", loglit (internal, lit).c_str ()); + } + } else + printf (" null gate"); + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +// Same as above, but for the global clause 'c' (which is not a reason). + +void Logger::log (Internal *internal, const vector &c, const char *fmt, + ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (internal->opts.logsort) { + vector s; + for (const auto &lit : c) + s.push_back (lit); + sort (s.begin (), s.end (), clause_lit_less_than ()); + for (const auto &lit : s) + printf (" %d", lit); + } else { + for (const auto &lit : c) + printf (" %d", lit); + } + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +// Now for 'restore_clause' to avoid copying (without logging). + +void Logger::log (Internal *internal, + const vector::const_iterator &begin, + const vector::const_iterator &end, const char *fmt, + ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + if (internal->opts.logsort) { + vector s; + for (auto p = begin; p != end; p++) + s.push_back (*p); + sort (s.begin (), s.end (), clause_lit_less_than ()); + for (const auto &lit : s) + printf (" %d", lit); + } else { + for (auto p = begin; p != end; p++) + printf (" %d", *p); + } + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +// for LRAT proof chains + +void Logger::log (Internal *internal, const vector &c, + const char *fmt, ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + for (const auto &id : c) + printf (" %" PRId64, id); + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +// for LRAT proof clauses + +void Logger::log (Internal *internal, const int *literals, + const unsigned size, const char *fmt, ...) { + print_log_prefix (internal); + tout.magenta (); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + for (unsigned i = 0; i < size; i++) { + const int lit = literals[i]; + printf (" %d", lit); + } + fputc ('\n', stdout); + tout.normal (); + fflush (stdout); +} + +string Logger::loglit (Internal *internal, int lit) { + std::string v = std::to_string (lit); + if (lit && -internal->max_var <= lit && internal->max_var >= lit) { + const int va = internal->val (lit); + if (va) { + v = v + "@" + std::to_string (internal->var (lit).level); + if (!internal->var (lit).reason) + v = v + "+"; + } + if (va > 0) + v += "=1"; + else if (va < 0) + v += "=-1"; + } + return v; +} +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#endif diff --git a/src/sat/cadical/cadical_lookahead.cpp b/src/sat/cadical/cadical_lookahead.cpp new file mode 100644 index 000000000..de84858cc --- /dev/null +++ b/src/sat/cadical/cadical_lookahead.cpp @@ -0,0 +1,526 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +struct literal_occ { + int lit; + int count; + bool operator< (const literal_occ &locc) const { + return (count > locc.count) || (count == locc.count && lit < locc.lit); + } + literal_occ operator++ () { + ++count; + return *this; + } +}; + +std::vector Internal::lookahead_populate_locc () { + std::vector loccs ((std::size_t) max_var + 1); + for (std::size_t lit = 0; lit < loccs.size (); ++lit) { + loccs[lit].lit = lit; + } + for (const auto &c : clauses) + if (!c->redundant) + for (const auto &lit : *c) + if (active (lit)) + ++loccs[std::abs (lit)]; + std::sort (begin (loccs), end (loccs)); + std::vector locc_map; + locc_map.reserve (max_var); + for (const auto &locc : loccs) + locc_map.push_back (locc.lit); + return locc_map; +} + +int Internal::lookahead_locc (const std::vector &loccs) { + for (auto lit : loccs) + if (active (abs (lit)) && !assumed (lit) && !assumed (-lit) && + !val (lit)) + return lit; + return 0; +} + +// This calculates the literal that appears the most often reusing the +// available datastructures and iterating over the clause set. This is too +// slow to be called iteratively. A faster (but inexact) version is +// lookahead_populate_loc and lookahead_loc. +int Internal::most_occurring_literal () { + init_noccs (); + for (const auto &c : clauses) + if (!c->redundant) + for (const auto &lit : *c) + if (active (lit)) + noccs (lit)++; + int64_t max_noccs = 0; + int res = 0; + + if (unsat) + return INT_MIN; + + propagate (); + for (int idx = 1; idx <= max_var; idx++) { + if (!active (idx) || assumed (idx) || assumed (-idx) || val (idx)) + continue; + for (int sign = -1; sign <= 1; sign += 2) { + const int lit = sign * idx; + if (!active (lit)) + continue; + int64_t tmp = noccs (lit); + if (tmp <= max_noccs) + continue; + max_noccs = tmp; + res = lit; + } + } + MSG ("maximum occurrence %" PRId64 " of literal %d", max_noccs, res); + reset_noccs (); + return res; +} + +// We probe on literals first, which occur more often negated and thus we +// sort the 'probes' stack in such a way that literals which occur negated +// less frequently come first. Probes are taken from the back of the stack. + +struct probe_negated_noccs_rank { + Internal *internal; + probe_negated_noccs_rank (Internal *i) : internal (i) {} + typedef size_t Type; + Type operator() (int a) const { return internal->noccs (-a); } +}; + +// Follow the ideas in 'generate_probes' but flush non root probes and +// reorder remaining probes. + +void Internal::lookahead_flush_probes () { + + CADICAL_assert (!probes.empty ()); + + init_noccs (); + for (const auto &c : clauses) { + int a, b; + if (!is_binary_clause (c, a, b)) + continue; + noccs (a)++; + noccs (b)++; + } + + const auto eop = probes.end (); + auto j = probes.begin (); + for (auto i = j; i != eop; i++) { + int lit = *i; + if (!active (lit)) + continue; + const bool have_pos_bin_occs = noccs (lit) > 0; + const bool have_neg_bin_occs = noccs (-lit) > 0; + if (have_pos_bin_occs == have_neg_bin_occs) + continue; + if (have_pos_bin_occs) + lit = -lit; + CADICAL_assert (!noccs (lit)), CADICAL_assert (noccs (-lit) > 0); + if (propfixed (lit) >= stats.all.fixed) + continue; + MSG ("keeping probe %d negated occs %" PRId64 "", lit, noccs (-lit)); + *j++ = lit; + } + size_t remain = j - probes.begin (); +#ifndef CADICAL_QUIET + size_t flushed = probes.size () - remain; +#endif + probes.resize (remain); + + rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this)); + + reset_noccs (); + shrink_vector (probes); + + PHASE ("probe-round", stats.probingrounds, + "flushed %zd literals %.0f%% remaining %zd", flushed, + percent (flushed, remain + flushed), remain); +} + +void Internal::lookahead_generate_probes () { + + CADICAL_assert (probes.empty ()); + + // First determine all the literals which occur in binary clauses. It is + // way faster to go over the clauses once, instead of walking the watch + // lists for each literal. + // + init_noccs (); + for (const auto &c : clauses) { + int a, b; + if (!is_binary_clause (c, a, b)) + continue; + noccs (a)++; + noccs (b)++; + } + + for (int idx = 1; idx <= max_var; idx++) { + + // Then focus on roots of the binary implication graph, which are + // literals occurring negatively in a binary clause, but not positively. + // If neither 'idx' nor '-idx' is a root it makes less sense to probe + // this variable. + + // This argument requires that equivalent literal substitution through + // 'decompose' is performed, because otherwise there might be 'cyclic + // roots' which are not tried, i.e., -1 2 0, 1 -2 0, 1 2 3 0, 1 2 -3 0. + + const bool have_pos_bin_occs = noccs (idx) > 0; + const bool have_neg_bin_occs = noccs (-idx) > 0; + + // if (have_pos_bin_occs == have_neg_bin_occs) continue; + + if (have_pos_bin_occs) { + int probe = -idx; + + // See the discussion where 'propfixed' is used below. + // + if (propfixed (probe) >= stats.all.fixed) + continue; + + MSG ("scheduling probe %d negated occs %" PRId64 "", probe, + noccs (-probe)); + probes.push_back (probe); + } + + if (have_neg_bin_occs) { + int probe = idx; + + // See the discussion where 'propfixed' is used below. + // + if (propfixed (probe) >= stats.all.fixed) + continue; + + MSG ("scheduling probe %d negated occs %" PRId64 "", probe, + noccs (-probe)); + probes.push_back (probe); + } + } + + rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this)); + + reset_noccs (); + shrink_vector (probes); + + PHASE ("probe-round", stats.probingrounds, + "scheduled %zd literals %.0f%%", probes.size (), + percent (probes.size (), 2 * max_var)); +} + +int Internal::lookahead_next_probe () { + + int generated = 0; + + for (;;) { + + if (probes.empty ()) { + if (generated++) + return 0; + lookahead_generate_probes (); + } + + while (!probes.empty ()) { + + int probe = probes.back (); + probes.pop_back (); + + // Eliminated or assigned. + // + if (!active (probe) || assumed (probe) || assumed (-probe)) + continue; + + // There is now new unit since the last time we propagated this probe, + // thus we propagated it before without obtaining a conflict and + // nothing changed since then. Thus there is no need to propagate it + // again. This observation was independently made by Partik Simons + // et.al. in the context of implementing 'smodels' (see for instance + // Alg. 4 in his JAIR article from 2002) and it has also been + // contributed to the thesis work of Yacine Boufkhad. + // + if (propfixed (probe) >= stats.all.fixed) + continue; + + return probe; + } + } +} + +bool non_tautological_cube (std::vector cube) { + std::sort (begin (cube), end (cube), clause_lit_less_than ()); + + for (size_t i = 0, j = 1; j < cube.size (); ++i, ++j) + if (cube[i] == cube[j]) + return false; + else if (cube[i] == -cube[j]) + return false; + else if (cube[i] == 0) + return false; + + return true; +} + +bool Internal::terminating_asked () { + + if (external->terminator && external->terminator->terminate ()) { + MSG ("connected terminator forces termination"); + return true; + } + + if (termination_forced) { + MSG ("termination forced"); + return true; + } + return false; +} + +// We run probing on all literals with some differences: +// +// * no limit on the number of propagations. We rely on terminating to +// stop() +// * we run only one round +// +// The run can be expensive, so we actually first run the cheaper +// occurrence version and only then run lookahead. +// +int Internal::lookahead_probing () { + + if (!active ()) + return 0; + + MSG ("lookahead-probe-round %" PRId64 + " without propagations limit and %zu assumptions", + stats.probingrounds, assumptions.size ()); + + termination_forced = false; + +#ifndef CADICAL_QUIET + int old_failed = stats.failed; + int64_t old_probed = stats.probed; +#endif + int64_t old_hbrs = stats.hbrs; + + if (unsat) + return INT_MIN; + if (level) + backtrack (); + if (!propagate ()) { + MSG ("empty clause before probing"); + learn_empty_clause (); + return INT_MIN; + } + + if (terminating_asked ()) + return most_occurring_literal (); + + decompose (); + + if (ternary ()) // If we derived a binary clause + decompose (); // then start another round of ELS. + + // Remove duplicated binary clauses and perform in essence hyper unary + // resolution, i.e., derive the unit '2' from '1 2' and '-1 2'. + // + mark_duplicated_binary_clauses_as_garbage (); + + lim.conflicts = -1; + + if (!probes.empty ()) + lookahead_flush_probes (); + + // We reset 'propfixed' since there was at least another conflict thus + // a new learned clause, which might produce new propagations (and hyper + // binary resolvents). During 'generate_probes' we keep the old value. + // + for (int idx = 1; idx <= max_var; idx++) + propfixed (idx) = propfixed (-idx) = -1; + + CADICAL_assert (unsat || propagated == trail.size ()); + propagated = propagated2 = trail.size (); + + int probe; + int res = most_occurring_literal (); + int max_hbrs = -1; + + set_mode (PROBE); + + MSG ("unsat = %d, terminating_asked () = %d ", unsat, + terminating_asked ()); + init_probehbr_lrat (); + while (!unsat && !terminating_asked () && + (probe = lookahead_next_probe ())) { + stats.probed++; + int hbrs; + + probe_assign_decision (probe); + if (probe_propagate ()) + hbrs = trail.size (), backtrack (); + else + hbrs = 0, failed_literal (probe); + clean_probehbr_lrat (); + if (max_hbrs < hbrs || + (max_hbrs == hbrs && + internal->bumped (probe) > internal->bumped (res))) { + res = probe; + max_hbrs = hbrs; + } + } + + reset_mode (PROBE); + + if (unsat) { + MSG ("probing derived empty clause"); + res = INT_MIN; + } else if (propagated < trail.size ()) { + MSG ("probing produced %zd units", + (size_t) (trail.size () - propagated)); + if (!propagate ()) { + MSG ("propagating units after probing results in empty clause"); + learn_empty_clause (); + res = INT_MIN; + } else + sort_watches (); + } + +#ifndef CADICAL_QUIET + int failed = stats.failed - old_failed; + int64_t probed = stats.probed - old_probed; +#endif + int64_t hbrs = stats.hbrs - old_hbrs; + + MSG ("lookahead-probe-round %" PRId64 " probed %" PRId64 + " and found %d failed literals", + stats.probingrounds, probed, failed); + + if (hbrs) + PHASE ("lookahead-probe-round", stats.probingrounds, + "found %" PRId64 " hyper binary resolvents", hbrs); + + MSG ("lookahead literal %d with %d\n", res, max_hbrs); + + return res; +} + +CubesWithStatus Internal::generate_cubes (int depth, int min_depth) { + if (!active () || depth == 0) { + CubesWithStatus cubes; + cubes.status = 0; + cubes.cubes.push_back (std::vector ()); + return cubes; + } + + lookingahead = true; + START (lookahead); + MSG ("Generating cubes of depth %i", depth); + + // presimplify required due to assumptions + + termination_forced = false; + int res = already_solved (); + if (res == 0) + res = restore_clauses (); + if (unsat) + res = 10; + if (res != 0) + res = solve (true); + if (res != 0) { + MSG ("Solved during preprocessing"); + CubesWithStatus cubes; + cubes.status = res; + lookingahead = false; + STOP (lookahead); + return cubes; + } + + reset_limits (); + MSG ("generate cubes with %zu assumptions\n", assumptions.size ()); + + CADICAL_assert (ntab.empty ()); + std::vector current_assumptions{assumptions}; + std::vector> cubes{{assumptions}}; + auto loccs{lookahead_populate_locc ()}; + LOG ("loccs populated\n"); + CADICAL_assert (ntab.empty ()); + + for (int i = 0; i < depth; ++i) { + LOG ("Probing at depth %i, currently %zu have been generated", i, + cubes.size ()); + std::vector> cubes2{std::move (cubes)}; + cubes.clear (); + + for (size_t j = 0; j < cubes2.size (); ++j) { + CADICAL_assert (ntab.empty ()); + CADICAL_assert (!unsat); + reset_assumptions (); + for (auto lit : cubes2[j]) + assume (lit); + restore_clauses (); + propagate (); + // preprocess_round(0); //uncomment maybe + + if (unsat) { + LOG ("current cube is unsat; skipping"); + unsat = false; + continue; + } + + int res = terminating_asked () ? lookahead_locc (loccs) + : lookahead_probing (); + if (unsat) { + LOG ("current cube is unsat; skipping"); + unsat = false; + continue; + } + + if (res == 0) { + LOG ("no lit to split %i", res); + cubes.push_back (cubes2[j]); + continue; + } + + CADICAL_assert (res != 0); + LOG ("splitting on lit %i", res); + std::vector cube1{cubes2[j]}; + cube1.push_back (res); + std::vector cube2{std::move (cubes2[j])}; + cube2.push_back (-res); + cubes.push_back (cube1); + cubes.push_back (cube2); + } + + if (terminating_asked () && i >= min_depth) + break; + } + + CADICAL_assert (std::for_each ( + std::begin (cubes), std::end (cubes), + [] (std::vector cube) { return non_tautological_cube (cube); })); + reset_assumptions (); + + for (auto lit : current_assumptions) + assume (lit); + + STOP (lookahead); + lookingahead = false; + + if (unsat) { + LOG ("Solved during preprocessing"); + CubesWithStatus cubes; + cubes.status = 20; + return cubes; + } + + CubesWithStatus rcubes; + rcubes.status = 0; + rcubes.cubes = cubes; + + return rcubes; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_lratchecker.cpp b/src/sat/cadical/cadical_lratchecker.cpp new file mode 100644 index 000000000..5f342a370 --- /dev/null +++ b/src/sat/cadical/cadical_lratchecker.cpp @@ -0,0 +1,835 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +inline unsigned LratChecker::l2u (int lit) { + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + unsigned res = 2 * (abs (lit) - 1); + if (lit < 0) + res++; + return res; +} + +signed char &LratChecker::mark (int lit) { + const unsigned u = l2u (lit); + CADICAL_assert (u < marks.size ()); + return marks[u]; +} + +signed char &LratChecker::checked_lit (int lit) { + const unsigned u = l2u (lit); + CADICAL_assert (u < checked_lits.size ()); + return checked_lits[u]; +} + +/*------------------------------------------------------------------------*/ + +LratCheckerClause *LratChecker::new_clause () { + const size_t size = imported_clause.size (); + CADICAL_assert (size <= UINT_MAX); + const int off = size ? 1 : 0; + const size_t bytes = + sizeof (LratCheckerClause) + (size - off) * sizeof (int); + LratCheckerClause *res = (LratCheckerClause *) new char[bytes]; + res->garbage = false; + res->next = 0; + res->hash = last_hash; + res->id = last_id; + res->size = size; + res->used = false; + res->tautological = false; + int *literals = res->literals, *p = literals; +#ifndef CADICAL_NDEBUG + for (auto &b : checked_lits) + CADICAL_assert (!b); // = false; +#endif + for (const auto &lit : imported_clause) { + *p++ = lit; + checked_lit (-lit) = true; + if (checked_lit (lit)) { + LOG (imported_clause, "LRAT CHECKER clause tautological"); + res->tautological = true; + } + } + for (const auto &lit : imported_clause) + checked_lit (-lit) = false; + num_clauses++; + + return res; +} + +void LratChecker::delete_clause (LratCheckerClause *c) { + CADICAL_assert (c); + if (!c->garbage) { + CADICAL_assert (num_clauses); + num_clauses--; + } else { + CADICAL_assert (num_garbage); + num_garbage--; + } + delete[] (char *) c; +} + +void LratChecker::enlarge_clauses () { + CADICAL_assert (num_clauses == size_clauses); + const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1; + LOG ("LRAT CHECKER enlarging clauses of checker from %" PRIu64 + " to %" PRIu64, + (uint64_t) size_clauses, (uint64_t) new_size_clauses); + LratCheckerClause **new_clauses; + new_clauses = new LratCheckerClause *[new_size_clauses]; + clear_n (new_clauses, new_size_clauses); + for (uint64_t i = 0; i < size_clauses; i++) { + for (LratCheckerClause *c = clauses[i], *next; c; c = next) { + next = c->next; + const uint64_t h = reduce_hash (c->hash, new_size_clauses); + c->next = new_clauses[h]; + new_clauses[h] = c; + } + } + delete[] clauses; + clauses = new_clauses; + size_clauses = new_size_clauses; +} + +// Probably not necessary since we have no watches. +// +void LratChecker::collect_garbage_clauses () { + + stats.collections++; + + LOG ("LRAT CHECKER collecting %" PRIu64 " garbage clauses %.0f%%", + num_garbage, percent (num_garbage, num_clauses)); + + for (LratCheckerClause *c = garbage, *next; c; c = next) + next = c->next, delete_clause (c); + + CADICAL_assert (!num_garbage); + garbage = 0; +} + +/*------------------------------------------------------------------------*/ + +LratChecker::LratChecker (Internal *i) + : internal (i), size_vars (0), concluded (false), num_clauses (0), + num_finalized (0), num_garbage (0), size_clauses (0), clauses (0), + garbage (0), last_hash (0), last_id (0), current_id (0) { + + // Initialize random number table for hash function. + // + Random random (42); + for (unsigned n = 0; n < num_nonces; n++) { + uint64_t nonce = random.next (); + if (!(nonce & 1)) + nonce++; + CADICAL_assert (nonce), CADICAL_assert (nonce & 1); + nonces[n] = nonce; + } + + memset (&stats, 0, sizeof (stats)); // Initialize statistics. +} + +void LratChecker::connect_internal (Internal *i) { + internal = i; + LOG ("connected to internal"); +} + +LratChecker::~LratChecker () { + LOG ("LRAT CHECKER delete"); + for (size_t i = 0; i < size_clauses; i++) + for (LratCheckerClause *c = clauses[i], *next; c; c = next) + next = c->next, delete_clause (c); + for (LratCheckerClause *c = garbage, *next; c; c = next) + next = c->next, delete_clause (c); + delete[] clauses; +} + +/*------------------------------------------------------------------------*/ + +void LratChecker::enlarge_vars (int64_t idx) { + + CADICAL_assert (0 < idx), CADICAL_assert (idx <= INT_MAX); + + int64_t new_size_vars = size_vars ? 2 * size_vars : 2; + while (idx >= new_size_vars) + new_size_vars *= 2; + LOG ("LRAT CHECKER enlarging variables of checker from %" PRId64 + " to %" PRId64 "", + size_vars, new_size_vars); + + marks.resize (2 * new_size_vars); + checked_lits.resize (2 * new_size_vars); + + CADICAL_assert (idx < new_size_vars); + size_vars = new_size_vars; +} + +inline void LratChecker::import_literal (int lit) { + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + int idx = abs (lit); + if (idx >= size_vars) + enlarge_vars (idx); + imported_clause.push_back (lit); +} + +void LratChecker::import_clause (const vector &c) { + for (const auto &lit : c) + import_literal (lit); +} + +/*------------------------------------------------------------------------*/ + +uint64_t LratChecker::reduce_hash (uint64_t hash, uint64_t size) { + CADICAL_assert (size > 0); + unsigned shift = 32; + uint64_t res = hash; + while ((((uint64_t) 1) << shift) > size) { + res ^= res >> shift; + shift >>= 1; + } + res &= size - 1; + CADICAL_assert (res < size); + return res; +} + +uint64_t LratChecker::compute_hash (const int64_t id) { + CADICAL_assert (id > 0); + unsigned j = id % num_nonces; + uint64_t tmp = nonces[j] * (uint64_t) id; + return last_hash = tmp; +} + +LratCheckerClause **LratChecker::find (const int64_t id) { + stats.searches++; + LratCheckerClause **res, *c; + const uint64_t hash = compute_hash (id); + const uint64_t h = reduce_hash (hash, size_clauses); + for (res = clauses + h; (c = *res); res = &c->next) { + if (c->hash == hash && c->id == id) { + break; + } + stats.collisions++; + } + return res; +} + +void LratChecker::insert () { + stats.insertions++; + if (num_clauses == size_clauses) + enlarge_clauses (); + const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses); + LratCheckerClause *c = new_clause (); + c->next = clauses[h]; + clauses[h] = c; +} + +/*------------------------------------------------------------------------*/ + +// "strict" resolution check instead of rup check +bool LratChecker::check_resolution (vector proof_chain) { + if (proof_chain.empty ()) { + LOG ("LRAT CHECKER resolution check skipped clause is tautological"); + return true; + } + // LOG (imported_clause, "LRAT CHECKER checking clause with resolution"); +#ifndef CADICAL_NDEBUG + for (auto &b : checked_lits) + CADICAL_assert (!b); // = false; +#endif + if (!proof_chain.size () || proof_chain.back () < 0) + return false; + LratCheckerClause *c = *find (proof_chain.back ()); + CADICAL_assert (c); + for (int *i = c->literals; i < c->literals + c->size; i++) { + int lit = *i; + checked_lit (lit) = true; + CADICAL_assert (!checked_lit (-lit)); + } + for (auto p = proof_chain.end () - 2; p >= proof_chain.begin (); p--) { + auto &id = *p; + c = *find (id); + CADICAL_assert (c); // since this is checked in check already + for (int *i = c->literals; i < c->literals + c->size; i++) { + int lit = *i; + if (!checked_lit (-lit)) + checked_lit (lit) = true; + else + checked_lit (-lit) = false; + } + } + for (const auto &lit : imported_clause) { + if (checked_lit (-lit)) { + LOG ("LRAT CHECKER resolution failed, resolved literal %d in learned " + "clause", + lit); + for (auto &b : checked_lits) + b = false; // clearing checking bits + return false; + } + if (!checked_lit (lit)) { + // learned clause is subsumed by resolvents + checked_lit (lit) = true; + } + checked_lit (-lit) = true; + } + bool failed = false; + for (int64_t lit = 1; lit < size_vars; lit++) { + bool ok = checked_lit (lit) && checked_lit (-lit); + ok = ok || (!checked_lit (lit) && !checked_lit (-lit)); + checked_lit (lit) = checked_lit (-lit) = false; + if (!ok && !failed) { + LOG ("LRAT CHECKER resolution failed, learned clause does not match " + "on " + "variable %" PRId64, + lit); + failed = true; + } + } + + return !failed; +} + +/*------------------------------------------------------------------------*/ + +bool LratChecker::check (vector proof_chain) { + LOG (imported_clause, "LRAT CHECKER checking clause"); + stats.checks++; +#ifndef CADICAL_NDEBUG + for (auto &b : checked_lits) + CADICAL_assert (!b); // = false; +#endif + bool taut = false; + for (const auto &lit : imported_clause) { // tautological clauses + checked_lit (-lit) = true; + if (checked_lit (lit)) { + LOG (imported_clause, "LRAT CHECKER clause tautological"); + CADICAL_assert (!proof_chain.size ()); // would be unnecessary hence a bug + taut = true; + } + } + // we assume that we can have RUP and ER clauses. One side of the ER + // clauses are pure, i.e. without any chain, the long clause is blocked, + // so the chain consists only of negative ids. Therefore these checks are + // enough to distiguish between RUP and ER + if (taut || !proof_chain.size () || proof_chain.back () < 0) { + for (const auto &lit : imported_clause) { // tautological clauses + checked_lit (-lit) = false; + } + return taut; + } + + vector used_clauses; + bool checking = false; + for (auto &id : proof_chain) { + LratCheckerClause *c = *find (id); + if (!c) { + LOG ("LRAT CHECKER LRAT failed. Did not find clause with id %" PRIu64, + id); + break; + } + if (c->tautological) { + LOG ("LRAT CHECKER LRAT failed. Clause with id %" PRId64 + " is tautological", + id); + break; + } + used_clauses.push_back (c); + if (c->used) { + LOG ("LRAT CHECKER LRAT failed. Id %" PRId64 + " was used multiple times", + id); + break; + } else + c->used = true; + int unit = 0; + for (int *i = c->literals; i < c->literals + c->size; i++) { + int lit = *i; + if (checked_lit (-lit)) + continue; + if (unit && unit != lit) { + unit = INT_MIN; // multiple unfalsified literals + break; + } + unit = lit; // potential unit + } + if (unit == INT_MIN) { + LOG ("LRAT CHECKER check failed, found non unit clause %" PRId64, id); + break; + } + if (!unit) { + LOG ("LRAT CHECKER check succeded, clause falsified %" PRId64, id); + checking = true; + break; + } + // LOG ("LRAT CHECKER found unit clause %" PRIu64 ", assign %d", id, + // unit); + checked_lit (unit) = true; + } + for (auto &lc : used_clauses) { + lc->used = false; + } + for (auto &b : checked_lits) + b = false; + if (!checking) { + LOG ("LRAT CHECKER failed, no conflict found"); + return false; // check failed because no empty clause was found + } + return true; +} + +bool LratChecker::check_blocked (vector proof_chain) { + for (const auto &lit : imported_clause) { + checked_lit (-lit) = true; + mark (-lit) = true; + } + for (size_t i = 0; i < size_clauses; i++) { + for (LratCheckerClause *c = clauses[i], *next; c; c = next) { + next = c->next; + if (c->garbage) + continue; + // if c is part of the proof chain its id occurs negatively there. + if (std::find (proof_chain.begin (), proof_chain.end (), -c->id) != + proof_chain.end ()) { + // clause needs to be blocked + unsigned count = 0; + vector candidates; + for (unsigned i = 0; i < c->size; i++) { + const int lit = c->literals[i]; + if (checked_lit (lit)) { + count++; + } + if (mark (lit)) { + candidates.push_back (lit); + } + } + if (count < 2) { + // check failed + for (const auto &lit : imported_clause) { + checked_lit (-lit) = false; + mark (-lit) = false; + } + return false; + } else { + // all literals outside of candidates are not valid RAT candidates + for (auto &lit : imported_clause) { + if (mark (-lit) && + std::find (candidates.begin (), candidates.end (), -lit) == + candidates.end ()) { + mark (-lit) = false; + } + } + } + } else { + // any literal contained in the clause is not a valid RAT candidate + for (unsigned i = 0; i < c->size; i++) { + const int lit = c->literals[i]; + if (checked_lit (lit)) { + mark (lit) = false; + } + } + } + } + } + bool success = false; + for (const auto &lit : imported_clause) { + if (mark (-lit)) + success = true; + checked_lit (-lit) = mark (-lit) = false; + } + return success; +} + +/*------------------------------------------------------------------------*/ + +void LratChecker::add_original_clause (int64_t id, bool, + const vector &c, bool restore) { + START (checking); + LOG (c, "LRAT CHECKER addition of original clause[%" PRId64 "]", id); + if (restore) + restore_clause (id, c); + stats.added++; + stats.original++; + import_clause (c); + last_id = id; + if (!restore && id == 1 + current_id) + current_id = id; + + if (size_clauses && !restore) { + LratCheckerClause **p = find (id), *d = *p; + if (d) { + fatal_message_start (); + fputs ("different clause with id ", stderr); + fprintf (stderr, "%" PRId64, id); + fputs (" already present\n", stderr); + fatal_message_end (); + } + } + CADICAL_assert (id); + insert (); + imported_clause.clear (); + STOP (checking); +} + +void LratChecker::add_derived_clause (int64_t id, bool, + const vector &c, + const vector &proof_chain) { + START (checking); + LOG (c, "LRAT CHECKER addition of derived clause[%" PRId64 "]", id); + stats.added++; + stats.derived++; + import_clause (c); + last_id = id; + CADICAL_assert (id == current_id + 1); + current_id = id; + if (size_clauses) { + LratCheckerClause **p = find (id), *d = *p; + if (d) { + fatal_message_start (); + fputs ("different clause with id ", stderr); + fprintf (stderr, "%" PRId64, id); + fputs (" already present\n", stderr); + fatal_message_end (); + } + } + CADICAL_assert (id); + bool failed = true; + if (check (proof_chain) && check_resolution (proof_chain)) { + failed = false; + } else if (check_blocked (proof_chain)) { + failed = false; + } + if (failed) { + LOG (proof_chain, "LRAT CHECKER check failed with chain"); +#ifdef LOGGING + for (const auto &pid : proof_chain) { + const int64_t aid = abs (pid); + LratCheckerClause **p = find (aid), *d = *p; + LOG (d->literals, d->size, "clause[%" PRId64 "]", pid); + } +#endif + fatal_message_start (); + fputs ("failed to check derived clause:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } else + insert (); + imported_clause.clear (); + STOP (checking); +} + +void LratChecker::add_assumption_clause (int64_t id, const vector &c, + const vector &chain) { + for (auto &lit : c) { + if (std::find (assumptions.begin (), assumptions.end (), -lit) != + assumptions.end ()) + continue; + if (std::find (constraint.begin (), constraint.end (), -lit) != + constraint.end ()) + continue; + fatal_message_start (); + fputs ("clause contains non assumptions or constraint literals\n", + stderr); + fatal_message_end (); + } + add_derived_clause (id, true, c, chain); + delete_clause (id, true, c); + assumption_clauses.push_back (id); +} + +void LratChecker::add_assumption (int a) { assumptions.push_back (a); } + +void LratChecker::add_constraint (const vector &c) { + constraint.clear (); + for (auto &lit : c) { + CADICAL_assert (lit); + if (std::find (constraint.begin (), constraint.end (), lit) != + constraint.end ()) + continue; + constraint.push_back (lit); + } +} + +void LratChecker::reset_assumptions () { + assumption_clauses.clear (); + assumptions.clear (); + concluded = false; + // constraint.clear (); +} + +void LratChecker::conclude_unsat (ConclusionType conclusion, + const vector &ids) { + if (concluded) { + fatal_message_start (); + fputs ("already concluded\n", stderr); + fatal_message_end (); + } + concluded = true; + if (conclusion == CONFLICT) { + LratCheckerClause **p = find (ids.back ()), *d = *p; + if (!d || d->size) { + fatal_message_start (); + fputs ("empty clause not in proof\n", stderr); + fatal_message_end (); + } + return; + } else if (conclusion == ASSUMPTIONS) { + if (ids.size () != 1 || assumption_clauses.size () != 1) { + fatal_message_start (); + fputs ("expected exactly one assumption clause\n", stderr); + fatal_message_end (); + } + if (ids.back () != assumption_clauses.back ()) { + fatal_message_start (); + fputs ("conclusion is not an assumption clause\n", stderr); + fatal_message_end (); + } + return; + } else { + CADICAL_assert (conclusion == CONSTRAINT); + if (constraint.size () != ids.size ()) { + fatal_message_start (); + fputs ("not complete conclusion given for constraint\n", stderr); + fputs ("The constraint contains the literals: ", stderr); + for (auto c : constraint) { + fprintf (stderr, "%d ", c); + } + + fputs ("\nThe ids are: ", stderr); + for (auto c : ids) { + fprintf (stderr, "%" PRId64 " ", c); + } + fatal_message_end (); + } + for (auto &id : ids) { + if (std::find (assumption_clauses.begin (), assumption_clauses.end (), + id) != assumption_clauses.end ()) + continue; + fatal_message_start (); + fputs ("assumption clause for constraint missing\n", stderr); + fatal_message_end (); + } + } +} + +/*------------------------------------------------------------------------*/ + +void LratChecker::delete_clause (int64_t id, bool, const vector &c) { + START (checking); + LOG (c, "LRAT CHECKER checking deletion of clause[%" PRId64 "]", id); + stats.deleted++; + import_clause (c); + last_id = id; + LratCheckerClause **p = find (id), *d = *p; + if (d) { + for (const auto &lit : imported_clause) + mark (lit) = true; + const int *dp = d->literals; + for (unsigned i = 0; i < d->size; i++) { + int lit = *(dp + i); + if (!mark (lit)) { // should never happen since ids + fatal_message_start (); // are unique. + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + } + for (const auto &lit : imported_clause) + mark (lit) = false; + + // Remove from hash table, mark as garbage, connect to garbage list. + num_garbage++; + CADICAL_assert (num_clauses); + num_clauses--; + *p = d->next; + d->next = garbage; + garbage = d; + d->garbage = true; + + // If there are enough garbage clauses collect them. + // TODO: probably can just delete clause directly without + // specific garbage collection phase. + if (num_garbage > 0.5 * max ((size_t) size_clauses, (size_t) size_vars)) + collect_garbage_clauses (); + } else { + fatal_message_start (); + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + imported_clause.clear (); + STOP (checking); +} + +/*------------------------------------------------------------------------*/ + +void LratChecker::weaken_minus (int64_t id, const vector &c) { + LOG (c, "LRAT CHECKER saving clause[%" PRId64 "] to restore later", id); + import_clause (c); + + CADICAL_assert (id <= current_id); + last_id = id; + LratCheckerClause **p = find (id), *d = *p; + if (d) { + for (const auto &lit : imported_clause) + mark (lit) = true; + const int *dp = d->literals; + for (unsigned i = 0; i < d->size; i++) { + int lit = *(dp + i); + if (!mark (lit)) { // should never happen since ids + fatal_message_start (); // are unique. + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + } + for (const auto &lit : imported_clause) + mark (lit) = false; + } else { + fatal_message_start (); + fputs ("weakened clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + imported_clause.clear (); + + vector e = c; + sort (begin (e), end (e)); + clauses_to_reconstruct[id] = e; +} + +void LratChecker::restore_clause (int64_t id, const vector &c) { + LOG (c, "LRAT CHECKER check of restoration of clause[%" PRId64 "]", id); + if (clauses_to_reconstruct.find (id) == end (clauses_to_reconstruct)) { + fatal_message_start (); + fputs ("restoring clauses not deleted previously:\n", stderr); + for (const auto &lit : c) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + vector e = c; + sort (begin (e), end (e)); + const vector &d = clauses_to_reconstruct.find (id)->second; + bool eq = true; + if (c.size () != d.size ()) { + eq = false; + } + + for (std::vector::size_type i = 0; i < e.size () && eq; ++i) { + eq = (e[i] == d[i]); + } + + if (!eq) { + fatal_message_start (); + fputs ("restoring clause that is different than the one imported:\n", + stderr); + for (const auto &lit : c) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fputs ("vs:\n", stderr); + for (const auto &lit : d) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + + clauses_to_reconstruct.erase (id); +} + +void LratChecker::finalize_clause (int64_t id, const vector &c) { + START (checking); + LOG (c, "LRAT CHECKER checking finalize of clause[%" PRId64 "]", id); + stats.finalized++; + num_finalized++; + import_clause (c); + CADICAL_assert (id <= current_id); + last_id = id; + LratCheckerClause **p = find (id), *d = *p; + if (d) { + for (const auto &lit : imported_clause) + mark (lit) = true; + const int *dp = d->literals; + for (unsigned i = 0; i < d->size; i++) { + int lit = *(dp + i); + if (!mark (lit)) { // should never happen since ids + fatal_message_start (); // are unique. + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + } + for (const auto &lit : imported_clause) + mark (lit) = false; + + } else { + fatal_message_start (); + fputs ("deleted clause not in proof:\n", stderr); + for (const auto &lit : imported_clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); + } + imported_clause.clear (); + STOP (checking); +} + +// check if all clauses have been deleted +void LratChecker::report_status (int, int64_t) { + START (checking); + if (num_finalized == num_clauses) { + num_finalized = 0; + LOG ("LRAT CHECKER successful finalize check, all clauses have been " + "deleted"); + } else { + fatal_message_start (); + fputs ("finalize check failed ", stderr); + fprintf (stderr, "%" PRIu64, num_clauses); + fputs (" are not finalized", stderr); + fatal_message_end (); + } + STOP (checking); +} + +/*------------------------------------------------------------------------*/ + +void LratChecker::dump () { + int max_var = 0; + for (uint64_t i = 0; i < size_clauses; i++) + for (LratCheckerClause *c = clauses[i]; c; c = c->next) + for (unsigned i = 0; i < c->size; i++) + if (abs (c->literals[i]) > max_var) + max_var = abs (c->literals[i]); + printf ("p cnf %d %" PRIu64 "\n", max_var, num_clauses); + for (uint64_t i = 0; i < size_clauses; i++) + for (LratCheckerClause *c = clauses[i]; c; c = c->next) { + for (unsigned i = 0; i < c->size; i++) + printf ("%d ", c->literals[i]); + printf ("0\n"); + } +} + +void LratChecker::begin_proof (int64_t id) { current_id = id; } + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_lrattracer.cpp b/src/sat/cadical/cadical_lrattracer.cpp new file mode 100644 index 000000000..4da728e18 --- /dev/null +++ b/src/sat/cadical/cadical_lrattracer.cpp @@ -0,0 +1,206 @@ +#include "global.h" + +#include "internal.hpp" + +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +LratTracer::LratTracer (Internal *i, File *f, bool b) + : internal (i), file (f), binary (b) +#ifndef CADICAL_QUIET + , + added (0), deleted (0) +#endif + , + latest_id (0) { + (void) internal; +} + +void LratTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("LRAT TRACER connected to internal"); +} + +LratTracer::~LratTracer () { + LOG ("LRAT TRACER delete"); + delete file; +} + +/*------------------------------------------------------------------------*/ + +inline void LratTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void LratTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned idx = abs (lit); + CADICAL_assert (idx < (1u << 31)); + unsigned x = 2 * idx + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +inline void LratTracer::put_binary_id (int64_t id) { + CADICAL_assert (binary); + CADICAL_assert (file); + uint64_t x = abs (id); + x = 2 * x + (id < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void LratTracer::lrat_add_clause (int64_t id, const vector &clause, + const vector &chain) { + if (delete_ids.size ()) { + if (!binary) + file->put (latest_id), file->put (" "); + if (binary) + file->put ('d'); + else + file->put ("d "); + for (auto &did : delete_ids) { + if (binary) + put_binary_id (did); + else + file->put (did), file->put (" "); + } + if (binary) + put_binary_zero (); + else + file->put ("0\n"); + delete_ids.clear (); + } + latest_id = id; + + if (binary) + file->put ('a'), put_binary_id (id); + else + file->put (id), file->put (" "); + for (const auto &external_lit : clause) + if (binary) + put_binary_lit (external_lit); + else + file->put (external_lit), file->put (' '); + if (binary) + put_binary_zero (); + else + file->put ("0 "); + for (const auto &c : chain) + if (binary) + put_binary_id (c); + else + file->put (c), file->put (' '); // in proof chain, so they get + if (binary) + put_binary_zero (); // since cadical has no rat-steps + else + file->put ("0\n"); // this is just 2c here +} + +void LratTracer::lrat_delete_clause (int64_t id) { + delete_ids.push_back (id); // pushing off deletion for later +} + +/*------------------------------------------------------------------------*/ + +void LratTracer::add_derived_clause (int64_t id, bool, + const vector &clause, + const vector &chain) { + if (file->closed ()) + return; + LOG ("LRAT TRACER tracing addition of derived clause"); + lrat_add_clause (id, clause, chain); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void LratTracer::delete_clause (int64_t id, bool, const vector &) { + if (file->closed ()) + return; + LOG ("LRAT TRACER tracing deletion of clause"); + lrat_delete_clause (id); +#ifndef CADICAL_QUIET + deleted++; +#endif +} + +void LratTracer::begin_proof (int64_t id) { + if (file->closed ()) + return; + LOG ("LRAT TRACER tracing begin of proof"); + latest_id = id; +} + +/*------------------------------------------------------------------------*/ + +bool LratTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void LratTracer::print_statistics () { + uint64_t bytes = file->bytes (); + uint64_t total = added + deleted; + MSG ("LRAT %" PRId64 " added clauses %.2f%%", added, + percent (added, total)); + MSG ("LRAT %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("LRAT %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void LratTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("LRAT proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void LratTracer::flush (bool print) { + CADICAL_assert (!closed ()); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("LRAT proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_lucky.cpp b/src/sat/cadical/cadical_lucky.cpp new file mode 100644 index 000000000..d67782db4 --- /dev/null +++ b/src/sat/cadical/cadical_lucky.cpp @@ -0,0 +1,440 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// It turns out that even in the competition there are formulas which are +// easy to satisfy by either setting all variables to the same truth value +// or by assigning variables to the same value and propagating it. In the +// latter situation this can be done either in the order of all variables +// (forward or backward) or in the order of all clauses. These lucky +// assignments can be tested initially in a kind of pre-solving step. + +// This function factors out clean up code common among the 'lucky' +// functions for backtracking and resetting a potential conflict. One could +// also use exceptions here, but there are two different reasons for +// aborting early. The first kind of aborting is due to asynchronous +// termination and the second kind due to a situation in which it is clear +// that a particular function will not be successful (for instance a +// completely negative clause is found). The latter situation returns zero +// and will just abort the particular lucky function, while the former will +// abort all (by returning '-1'). + +int Internal::unlucky (int res) { + if (level > 0) + backtrack (); + if (conflict) + conflict = 0; + return res; +} + +int Internal::trivially_false_satisfiable () { + LOG ("checking that all clauses contain a negative literal"); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (const auto &c : clauses) { + if (terminated_asynchronously (100)) + return unlucky (-1); + if (c->garbage) + continue; + if (c->redundant) + continue; + bool satisfied = false, found_negative_literal = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + if (lit > 0) + continue; + found_negative_literal = true; + break; + } + if (satisfied || found_negative_literal) + continue; + LOG (c, "found purely positively"); + return unlucky (0); + } + VERBOSE (1, "all clauses contain a negative literal"); + for (auto idx : vars) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + search_assume_decision (-idx); + if (propagate ()) + continue; + CADICAL_assert (level > 0); + LOG ("propagation failed including redundant clauses"); + return unlucky (0); + } + stats.lucky.constant.zero++; + return 10; +} + +int Internal::trivially_true_satisfiable () { + LOG ("checking that all clauses contain a positive literal"); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (const auto &c : clauses) { + if (terminated_asynchronously (100)) + return unlucky (-1); + if (c->garbage) + continue; + if (c->redundant) + continue; + bool satisfied = false, found_positive_literal = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + if (lit < 0) + continue; + found_positive_literal = true; + break; + } + if (satisfied || found_positive_literal) + continue; + LOG (c, "found purely negatively"); + return unlucky (0); + } + VERBOSE (1, "all clauses contain a positive literal"); + for (auto idx : vars) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + search_assume_decision (idx); + if (propagate ()) + continue; + CADICAL_assert (level > 0); + LOG ("propagation failed including redundant clauses"); + return unlucky (0); + } + stats.lucky.constant.one++; + return 10; +} + +/*------------------------------------------------------------------------*/ +inline bool Internal::lucky_propagate_discrepency (int dec) { + search_assume_decision (dec); + bool no_conflict = propagate (); + if (no_conflict) + return false; + if (level > 1) { + backtrack (level - 1); + search_assume_decision (-dec); + no_conflict = propagate (); + if (no_conflict) + return false; + return true; + } else { + analyze (); + CADICAL_assert (!level); + no_conflict = propagate (); + if (!no_conflict) { + analyze (); + LOG ("lucky inconsistency backward assigning to true"); + return true; + } + } + return false; +} + +int Internal::forward_false_satisfiable () { + LOG ("checking increasing variable index false assignment"); + CADICAL_assert (!unsat); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (auto idx : vars) { + START: + if (terminated_asynchronously (100)) + return unlucky (-1); + if (val (idx)) + continue; + if (lucky_propagate_discrepency (-idx)) { + if (unsat) + return 20; + else + return unlucky (0); + } else + goto START; + } + VERBOSE (1, "forward assuming variables false satisfies formula"); + CADICAL_assert (satisfied ()); + stats.lucky.forward.zero++; + return 10; +} + +int Internal::forward_true_satisfiable () { + LOG ("checking increasing variable index true assignment"); + CADICAL_assert (!unsat); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (auto idx : vars) { + START: + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + if (lucky_propagate_discrepency (idx)) { + if (unsat) + return 20; + else + return unlucky (0); + } else + goto START; + } + VERBOSE (1, "forward assuming variables true satisfies formula"); + CADICAL_assert (satisfied ()); + stats.lucky.forward.one++; + return 10; +} + +/*------------------------------------------------------------------------*/ + +int Internal::backward_false_satisfiable () { + LOG ("checking decreasing variable index false assignment"); + CADICAL_assert (!unsat); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (int idx = max_var; idx > 0; idx--) { + START: + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + if (lucky_propagate_discrepency (-idx)) { + if (unsat) + return 20; + else + return unlucky (0); + } else + goto START; + } + VERBOSE (1, "backward assuming variables false satisfies formula"); + CADICAL_assert (satisfied ()); + stats.lucky.backward.zero++; + return 10; +} + +int Internal::backward_true_satisfiable () { + LOG ("checking decreasing variable index true assignment"); + CADICAL_assert (!unsat); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (int idx = max_var; idx > 0; idx--) { + START: + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + if (lucky_propagate_discrepency (idx)) { + if (unsat) + return 20; + else + return unlucky (0); + } else + goto START; + } + VERBOSE (1, "backward assuming variables true satisfies formula"); + CADICAL_assert (satisfied ()); + stats.lucky.backward.one++; + return 10; +} + +/*------------------------------------------------------------------------*/ + +// The following two functions test if the formula is a satisfiable horn +// formula. Actually the test is slightly more general. It goes over all +// clauses and assigns the first positive literal to true and propagates. +// Already satisfied clauses are of course skipped. A reverse function +// is not implemented yet. + +int Internal::positive_horn_satisfiable () { + LOG ("checking that all clauses are positive horn satisfiable"); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (const auto &c : clauses) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (c->garbage) + continue; + if (c->redundant) + continue; + int positive_literal = 0; + bool satisfied = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + if (lit < 0) + continue; + positive_literal = lit; + break; + } + if (satisfied) + continue; + if (!positive_literal) { + LOG (c, "no positive unassigned literal in"); + return unlucky (0); + } + CADICAL_assert (positive_literal > 0); + LOG (c, "found positive literal %d in", positive_literal); + search_assume_decision (positive_literal); + if (propagate ()) + continue; + LOG ("propagation of positive literal %d leads to conflict", + positive_literal); + return unlucky (0); + } + for (auto idx : vars) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + search_assume_decision (-idx); + if (propagate ()) + continue; + LOG ("propagation of remaining literal %d leads to conflict", -idx); + return unlucky (0); + } + VERBOSE (1, "clauses are positive horn satisfied"); + CADICAL_assert (!conflict); + CADICAL_assert (satisfied ()); + stats.lucky.horn.positive++; + return 10; +} + +int Internal::negative_horn_satisfiable () { + LOG ("checking that all clauses are negative horn satisfiable"); + CADICAL_assert (!level); + CADICAL_assert (assumptions.empty ()); + for (const auto &c : clauses) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (c->garbage) + continue; + if (c->redundant) + continue; + int negative_literal = 0; + bool satisfied = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + satisfied = true; + break; + } + if (tmp < 0) + continue; + if (lit > 0) + continue; + negative_literal = lit; + break; + } + if (satisfied) + continue; + if (!negative_literal) { + if (level > 0) + backtrack (); + LOG (c, "no negative unassigned literal in"); + return unlucky (0); + } + CADICAL_assert (negative_literal < 0); + LOG (c, "found negative literal %d in", negative_literal); + search_assume_decision (negative_literal); + if (propagate ()) + continue; + LOG ("propagation of negative literal %d leads to conflict", + negative_literal); + return unlucky (0); + } + for (auto idx : vars) { + if (terminated_asynchronously (10)) + return unlucky (-1); + if (val (idx)) + continue; + search_assume_decision (idx); + if (propagate ()) + continue; + LOG ("propagation of remaining literal %d leads to conflict", idx); + return unlucky (0); + } + VERBOSE (1, "clauses are negative horn satisfied"); + CADICAL_assert (!conflict); + CADICAL_assert (satisfied ()); + stats.lucky.horn.negative++; + return 10; +} + +/*------------------------------------------------------------------------*/ + +int Internal::lucky_phases () { + CADICAL_assert (!level); + require_mode (SEARCH); + if (!opts.lucky) + return 0; + + // TODO: Some of the lucky assignments can also be found if there are + // assumptions, but this is not completely implemented nor tested yet. + // Nothing done for constraint either. + // External propagator assumes a CDCL loop, so lucky is not tried here. + if (!assumptions.empty () || !constraint.empty () || external_prop) + return 0; + + START (search); + START (lucky); + CADICAL_assert (!searching_lucky_phases); + searching_lucky_phases = true; + stats.lucky.tried++; + const int64_t active_before = stats.active; + int res = trivially_false_satisfiable (); + if (!res) + res = trivially_true_satisfiable (); + if (!res) + res = forward_true_satisfiable (); + if (!res) + res = forward_false_satisfiable (); + if (!res) + res = backward_false_satisfiable (); + if (!res) + res = backward_true_satisfiable (); + if (!res) + res = positive_horn_satisfiable (); + if (!res) + res = negative_horn_satisfiable (); + if (res < 0) + CADICAL_assert (termination_forced), res = 0; + if (res == 10) + stats.lucky.succeeded++; + report ('l', !res); + CADICAL_assert (searching_lucky_phases); + + const int64_t units = active_before - stats.active; + + if (!res && units) + LOG ("lucky %zd units", units); + searching_lucky_phases = false; + STOP (lucky); + STOP (search); + + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_message.cpp b/src/sat/cadical/cadical_message.cpp new file mode 100644 index 000000000..42d8c0e48 --- /dev/null +++ b/src/sat/cadical/cadical_message.cpp @@ -0,0 +1,218 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ +#ifndef CADICAL_QUIET +/*------------------------------------------------------------------------*/ + +void Internal::print_prefix () { fputs (prefix.c_str (), stdout); } + +void Internal::vmessage (const char *fmt, va_list &ap) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet) + return; + print_prefix (); + vprintf (fmt, ap); + fputc ('\n', stdout); + fflush (stdout); +} + +void Internal::message (const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + vmessage (fmt, ap); + va_end (ap); +} + +void Internal::message () { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet) + return; + print_prefix (); + fputc ('\n', stdout); + fflush (stdout); +} + +/*------------------------------------------------------------------------*/ + +void Internal::vverbose (int level, const char *fmt, va_list &ap) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet || level > opts.verbose) + return; + print_prefix (); + vprintf (fmt, ap); + fputc ('\n', stdout); + fflush (stdout); +} + +void Internal::verbose (int level, const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + vverbose (level, fmt, ap); + va_end (ap); +} + +void Internal::verbose (int level) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet || level > opts.verbose) + return; + print_prefix (); + fputc ('\n', stdout); + fflush (stdout); +} + +/*------------------------------------------------------------------------*/ + +void Internal::section (const char *title) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet) + return; + if (stats.sections++) + MSG (); + print_prefix (); + tout.blue (); + fputs ("--- [ ", stdout); + tout.blue (true); + fputs (title, stdout); + tout.blue (); + fputs (" ] ", stdout); + for (int i = strlen (title) + strlen (prefix.c_str ()) + 9; i < 78; i++) + fputc ('-', stdout); + tout.normal (); + fputc ('\n', stdout); + MSG (); +} + +/*------------------------------------------------------------------------*/ + +void Internal::phase (const char *phase, const char *fmt, ...) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet || (!force_phase_messages && opts.verbose < 2)) + return; + print_prefix (); + printf ("[%s] ", phase); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + fflush (stdout); +} + +void Internal::phase (const char *phase, int64_t count, const char *fmt, + ...) { +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet || (!force_phase_messages && opts.verbose < 2)) + return; + print_prefix (); + printf ("[%s-%" PRId64 "] ", phase, count); + va_list ap; + va_start (ap, fmt); + vprintf (fmt, ap); + va_end (ap); + fputc ('\n', stdout); + fflush (stdout); +} + +/*------------------------------------------------------------------------*/ +#endif // ifndef CADICAL_QUIET +/*------------------------------------------------------------------------*/ + +void Internal::warning (const char *fmt, ...) { + fflush (stdout); + terr.bold (); + fputs ("cadical: ", stderr); + terr.red (1); + fputs ("warning:", stderr); + terr.normal (); + fputc (' ', stderr); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fputc ('\n', stderr); + fflush (stderr); +} + +/*------------------------------------------------------------------------*/ + +void Internal::error_message_start () { + fflush (stdout); + terr.bold (); + fputs ("cadical: ", stderr); + terr.red (1); + fputs ("error:", stderr); + terr.normal (); + fputc (' ', stderr); +} + +void Internal::error_message_end () { + fputc ('\n', stderr); + fflush (stderr); + // TODO add possibility to use call back instead. + exit (1); +} + +void Internal::verror (const char *fmt, va_list &ap) { + error_message_start (); + vfprintf (stderr, fmt, ap); + error_message_end (); +} + +void Internal::error (const char *fmt, ...) { + va_list ap; + va_start (ap, fmt); + verror (fmt, ap); + va_end (ap); // unreachable +} + +/*------------------------------------------------------------------------*/ + +void fatal_message_start () { + fflush (stdout); + terr.bold (); + fputs ("cadical: ", stderr); + terr.red (1); + fputs ("fatal error:", stderr); + terr.normal (); + fputc (' ', stderr); +} + +void fatal_message_end () { + fputc ('\n', stderr); + fflush (stderr); + abort (); +} + +void fatal (const char *fmt, ...) { + fatal_message_start (); + va_list ap; + va_start (ap, fmt); + vfprintf (stderr, fmt, ap); + va_end (ap); + fatal_message_end (); + abort (); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_minimize.cpp b/src/sat/cadical/cadical_minimize.cpp new file mode 100644 index 000000000..97af5fbe6 --- /dev/null +++ b/src/sat/cadical/cadical_minimize.cpp @@ -0,0 +1,230 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Functions for learned clause minimization. We only have the recursive +// version, which actually really is implemented recursively. We also +// played with a derecursified version, which however was more complex and +// slower. The trick to keep potential stack exhausting recursion under +// guards is to explicitly limit the recursion depth. + +// Instead of signatures as in the original implementation in MiniSAT and +// our corresponding paper, we use the 'poison' idea of Allen Van Gelder to +// mark unsuccessful removal attempts, then Donald Knuth's idea to abort +// minimization if only one literal was seen on the level and a new idea of +// also aborting if the earliest seen literal was assigned afterwards. + +bool Internal::minimize_literal (int lit, int depth) { + LOG ("attempt to minimize lit %d at depth %d", lit, depth); + CADICAL_assert (val (lit) > 0); + Flags &f = flags (lit); + Var &v = var (lit); + if (!v.level || f.removable || f.keep) + return true; + if (!v.reason || f.poison || v.level == level) + return false; + const Level &l = control[v.level]; + if (!depth && l.seen.count < 2) + return false; // Don Knuth's idea + if (v.trail <= l.seen.trail) + return false; // new early abort + if (depth > opts.minimizedepth) + return false; + bool res = true; + CADICAL_assert (v.reason); + if (opts.minimizeticks) + stats.ticks.search[stable]++; + if (v.reason == external_reason) { + CADICAL_assert (!opts.exteagerreasons); + v.reason = learn_external_reason_clause (lit, 0, true); + if (!v.reason) { + CADICAL_assert (!v.level); + return true; + } + } + CADICAL_assert (v.reason != external_reason); + const const_literal_iterator end = v.reason->end (); + const_literal_iterator i; + for (i = v.reason->begin (); res && i != end; i++) { + const int other = *i; + if (other == lit) + continue; + res = minimize_literal (-other, depth + 1); + } + if (res) + f.removable = true; + else + f.poison = true; + minimized.push_back (lit); + if (!depth) { + LOG ("minimizing %d %s", lit, res ? "succeeded" : "failed"); + } + return res; +} + +// Sorting the clause before minimization with respect to the trail order +// (literals with smaller trail height first) is necessary but natural and +// might help to minimize the required recursion depth too. + +struct minimize_trail_positive_rank { + Internal *internal; + minimize_trail_positive_rank (Internal *s) : internal (s) {} + typedef unsigned Type; + Type operator() (const int &a) const { + CADICAL_assert (internal->val (a)); + return (unsigned) internal->var (a).trail; + } +}; + +struct minimize_trail_smaller { + Internal *internal; + minimize_trail_smaller (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return internal->var (a).trail < internal->var (b).trail; + } +}; + +struct minimize_trail_level_positive_rank { + Internal *internal; + minimize_trail_level_positive_rank (Internal *s) : internal (s) {} + typedef uint64_t Type; + Type operator() (const int &a) const { + CADICAL_assert (internal->val (a)); + Var &v = internal->var (a); + uint64_t res = v.level; + res <<= 32; + res |= v.trail; + return res; + } +}; + +struct minimize_trail_level_smaller { + Internal *internal; + minimize_trail_level_smaller (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return minimize_trail_level_positive_rank (internal) (a) < + minimize_trail_level_positive_rank (internal) (b); + } +}; + +void Internal::minimize_clause () { + START (minimize); + LOG (clause, "minimizing first UIP clause"); + + external->check_learned_clause (); // check 1st UIP learned clause first + minimize_sort_clause (); + + CADICAL_assert (minimized.empty ()); + CADICAL_assert (minimize_chain.empty ()); + const auto end = clause.end (); + auto j = clause.begin (), i = j; + std::vector stack; + for (; i != end; i++) { + if (minimize_literal (-*i)) { + if (lrat) { + CADICAL_assert (mini_chain.empty ()); + calculate_minimize_chain (-*i, stack); + for (auto p : mini_chain) { + minimize_chain.push_back (p); + } + mini_chain.clear (); + } + stats.minimized++; + } else + flags (*j++ = *i).keep = true; + } + LOG ("minimized %zd literals", (size_t) (clause.end () - j)); + if (j != end) + clause.resize (j - clause.begin ()); + clear_minimized_literals (); + for (auto p = minimize_chain.rbegin (); p != minimize_chain.rend (); + p++) { + lrat_chain.push_back (*p); + } + minimize_chain.clear (); + STOP (minimize); +} + +// go backwards in reason graph and add ids +// mini_chain is in correct order so we have to add it to minimize_chain +// and then reverse when we put it on lrat_chain +// +// We have to use the non-recursive as we cannot limit the depth like the +// minimize version. Unlike the minimize version, we have to keep literals +// on the stack in order to push its reason later. +void Internal::calculate_minimize_chain (int lit, std::vector &stack) { + CADICAL_assert (stack.empty ()); + stack.push_back (vidx (lit)); + + while (!stack.empty ()) { + const int idx = stack.back (); + CADICAL_assert (idx); + stack.pop_back (); + if (idx < 0) { + Var &v = var (idx); + mini_chain.push_back (v.reason->id); + continue; + } + CADICAL_assert (idx); + Flags &f = flags (idx); + Var &v = var (idx); + if (f.keep || f.added || f.poison) { + continue; + } + if (!v.level) { + if (f.seen) + continue; + f.seen = true; + unit_analyzed.push_back (idx); + const int lit = val (idx) > 0 ? idx : -idx; + int64_t id = unit_id (lit); + unit_chain.push_back (id); + continue; + } + f.added = true; + CADICAL_assert (v.reason && f.removable); + const const_literal_iterator end = v.reason->end (); + const_literal_iterator i; + LOG (v.reason, "LRAT chain for lit %d at depth %zd by going over", lit, + stack.size ()); + stack.push_back (-idx); + for (i = v.reason->begin (); i != end; i++) { + const int other = *i; + if (other == idx) + continue; + stack.push_back (vidx (other)); + } + } + CADICAL_assert (stack.empty ()); +} + +// Sort the literals in reverse assignment order (thus trail order) to +// establish the base case of the recursive minimization algorithm in the +// positive case (where a literal with 'keep' true is hit). +// +void Internal::minimize_sort_clause () { + MSORT (opts.radixsortlim, clause.begin (), clause.end (), + minimize_trail_positive_rank (this), + minimize_trail_smaller (this)); +} + +void Internal::clear_minimized_literals () { + LOG ("clearing %zd minimized literals", minimized.size ()); + for (const auto &lit : minimized) { + Flags &f = flags (lit); + f.poison = f.removable = f.shrinkable = f.added = false; + } + for (const auto &lit : clause) + CADICAL_assert (!flags (lit).shrinkable), flags (lit).keep = + flags (lit).shrinkable = + flags (lit).added = false; + minimized.clear (); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_occs.cpp b/src/sat/cadical/cadical_occs.cpp new file mode 100644 index 000000000..f79d7a690 --- /dev/null +++ b/src/sat/cadical/cadical_occs.cpp @@ -0,0 +1,58 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Occurrence lists. + +void Internal::init_occs () { + if (otab.size () < 2 * vsize) + otab.resize (2 * vsize, Occs ()); + LOG ("initialized occurrence lists"); +} + +void Internal::reset_occs () { + CADICAL_assert (occurring ()); + erase_vector (otab); + LOG ("reset occurrence lists"); +} + +void Internal::clear_occs () { + CADICAL_assert (occurring ()); + for (auto &occ : otab) + occ.clear (); + LOG ("clear occurrence lists"); +} + +/*------------------------------------------------------------------------*/ + +// One-sided occurrence counter (each literal has its own counter). + +void Internal::init_noccs () { + CADICAL_assert (ntab.empty ()); + if (ntab.size () < 2 * vsize) + ntab.resize (2 * vsize, 0); + LOG ("initialized two-sided occurrence counters"); +} + +void Internal::clear_noccs () { + CADICAL_assert (!ntab.empty ()); + for (auto &nt : ntab) + nt = 0; + LOG ("clear two-sided occurrence counters"); +} + +void Internal::reset_noccs () { + CADICAL_assert (!max_var || !ntab.empty ()); + erase_vector (ntab); + LOG ("reset two-sided occurrence counters"); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_options.cpp b/src/sat/cadical/cadical_options.cpp new file mode 100644 index 000000000..88eba53ab --- /dev/null +++ b/src/sat/cadical/cadical_options.cpp @@ -0,0 +1,365 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// By default, e.g., for library usage, the 'opts.report' value is zero +// ('false') but can be set to '1' by the stand alone solver. Using here +// a static default value avoids that the stand alone solver reports that +// '--report=1' is different from the default in 'print ()' below. +// +int Options::reportdefault; + +/*------------------------------------------------------------------------*/ + +// The order of initializations of static objects is undefined and thus we +// can not assume that this table is already initialized if a solver and +// thus the constructor of 'Options' is called. Therefore we just have to +// reinitialize this table in every call to 'Options::Options'. This does +// not produce a data race even for parallel initialization since the +// same values are written by all threads under the assumption that the +// 'reportdefault' is set before any solver is initialized. We do have to +// perform this static initialization though, since 'has' is static and does +// not require that the 'Options' constructor was called. + +Option Options::table[] = { +#define OPTION(N, V, L, H, O, P, R, D) \ + {#N, (int) V, (int) L, (int) H, (int) O, (bool) P, D}, + OPTIONS +#undef OPTION +}; + +/*------------------------------------------------------------------------*/ + +// Binary search in 'table', which requires option names to be sorted, which +// in turned is checked at start-up in 'Options::Options'. + +Option *Options::has (const char *name) { + size_t l = 0, r = number_of_options; + while (l < r) { + size_t m = l + (r - l) / 2; + Option *res = &table[m]; + int tmp = strcmp (name, res->name); + if (!tmp) + return res; + if (tmp < 0) + r = m; + if (tmp > 0) + l = m + 1; + } + return 0; +} + +/*------------------------------------------------------------------------*/ + +bool Options::parse_long_option (const char *arg, string &name, int &val) { + if (arg[0] != '-' || arg[1] != '-') + return false; + const bool has_no_prefix = + (arg[2] == 'n' && arg[3] == 'o' && arg[4] == '-'); + const size_t offset = has_no_prefix ? 5 : 2; + name = arg + offset; + const size_t pos = name.find_first_of ('='); + if (pos != string::npos) + name[pos] = 0; + if (!Options::has (name.c_str ())) + return false; + if (pos == string::npos) + val = !has_no_prefix; + else { + const char *val_str = name.c_str () + pos + 1; + if (!parse_int_str (val_str, val)) + return false; + } + return true; +} + +/*------------------------------------------------------------------------*/ + +void Options::initialize_from_environment (int &val, const char *name, + const int L, const int H) { + char key[80], *q; + const char *p; + CADICAL_assert (strlen (name) + strlen ("CADICAL_") + 1 < sizeof (key)); + for (p = "CADICAL_", q = key; *p; p++) + *q++ = *p; + for (p = name; *p; p++) + *q++ = toupper (*p); + CADICAL_assert (q < key + sizeof (key)); + *q = 0; + const char *val_str = getenv (key); + if (!val_str) + return; + if (!parse_int_str (val_str, val)) + return; + if (val < L) + val = L; + if (val > H) + val = H; +} + +// Initialize all the options to their default value 'V'. + +Options::Options (Internal *s) : internal (s) { + CADICAL_assert (number_of_options == sizeof Options::table / sizeof (Option)); + + // First initialize them according to defaults in 'options.hpp'. + // + const char *prev = ""; + size_t i = 0; +#define OPTION(N, V, L, H, O, P, R, D) \ + do { \ + if ((L) > (V)) \ + FATAL ("'" #N "' default '" #V "' " \ + "lower minimum '" #L "' in 'options.hpp'"); \ + if ((H) < (V)) \ + FATAL ("'" #N "' default '" #V "' " \ + "larger maximum '" #H "' in 'options.hpp'"); \ + if (strcmp (prev, #N) > 0) \ + FATAL ("'%s' ordered before '" #N "' in 'options.hpp'", prev); \ + N = (int) (V); \ + CADICAL_assert (&val (i) == &N); \ + /* The order of initializing static data is undefined and thus */ \ + /* it might be the case that the 'table' is not initialized yet. */ \ + /* Thus this construction just reinitializes the table too even */ \ + /* though it might not be necessary. */ \ + CADICAL_assert (!table[i].name || !strcmp (table[i].name, #N)); \ + table[i] = {#N, (int) (V), (int) (L), (int) (H), \ + (int) (O), (bool) (P), D}; \ + prev = #N; \ + i++; \ + } while (0); + OPTIONS +#undef OPTION + + // Check consistency in debugging mode. + // +#ifndef CADICAL_NDEBUG + CADICAL_assert (i == number_of_options); + CADICAL_assert (!has ("aaaaa")); + CADICAL_assert (!has ("non-existing-option")); + CADICAL_assert (!has ("zzzzz")); +#endif + + // Now overwrite default options with environment values. + // +#define OPTION(N, V, L, H, O, P, R, D) \ + initialize_from_environment (N, #N, L, H); + OPTIONS +#undef OPTION +} + +/*------------------------------------------------------------------------*/ + +void Options::set (Option *o, int new_val) { + CADICAL_assert (o); + int &val = o->val (this), old_val = val; + if (old_val == new_val) { + LOG ("keeping value '%d' of option '%s'", old_val, o->name); + return; + } + if (new_val < o->lo) { + LOG ("bounding '%d' to lower limit '%d' for option '%s'", new_val, + o->lo, o->name); + new_val = o->lo; + } + if (new_val > o->hi) { + LOG ("bounding '%d' to upper limit '%d' for option '%s'", new_val, + o->hi, o->name); + new_val = o->hi; + } + val = new_val; + LOG ("set option 'set (\"%s\", %d)' from '%d'", o->name, new_val, + old_val); +} + +// Explicit option value setting. + +bool Options::set (const char *name, int val) { + Option *o = has (name); + if (!o) + return false; + set (o, val); + return true; +} + +int Options::get (const char *name) { + Option *o = has (name); + return o ? o->val (this) : 0; +} + +/*------------------------------------------------------------------------*/ + +void Options::print () { + unsigned different = 0; +#ifdef CADICAL_QUIET + const bool verbose = false; +#endif + char buffer[256]; + // We prefer the macro iteration here since '[VLH]' might be '1e9' etc. +#define OPTION(N, V, L, H, O, P, R, D) \ + if (N != (V)) \ + different++; \ + if (verbose || N != (V)) { \ + if ((L) == 0 && (H) == 1) { \ + snprintf (buffer, sizeof buffer, "--" #N "=%s", \ + (N ? "true" : "false")); \ + MSG (" %s%-30s%s (%s default %s'%s'%s)", \ + ((N == (V)) ? "" : tout.bright_yellow_code ()), buffer, \ + ((N == (V)) ? "" : tout.normal_code ()), \ + ((N == (V)) ? "same as" : "different from"), \ + ((N == (V)) ? tout.green_code () : tout.yellow_code ()), \ + (bool) (V) ? "true" : "false", tout.normal_code ()); \ + } else { \ + snprintf (buffer, sizeof buffer, "--" #N "=%d", N); \ + MSG (" %s%-30s%s (%s default %s'" #V "'%s)", \ + ((N == (V)) ? "" : tout.bright_yellow_code ()), buffer, \ + ((N == (V)) ? "" : tout.normal_code ()), \ + ((N == (V)) ? "same as" : "different from"), \ + ((N == (V)) ? tout.green_code () : tout.yellow_code ()), \ + tout.normal_code ()); \ + } \ + } + OPTIONS +#undef OPTION + if (!different) + MSG ("all options are set to their default value"); +} + +/*------------------------------------------------------------------------*/ + +void Options::usage () { + // We prefer the macro iteration here since '[VLH]' might be '1e9' etc. +#define OPTION(N, V, L, H, O, P, R, D) \ + if ((L) == 0 && (H) == 1) \ + printf (" %-26s " D " [%s]\n", "--" #N "=bool", \ + (bool) (V) ? "true" : "false"); \ + else \ + printf (" %-26s " D " [" #V "]\n", "--" #N "=" #L ".." #H); + OPTIONS +#undef OPTION +} + +/*------------------------------------------------------------------------*/ + +void Options::optimize (int val) { + + if (val < 0) { + LOG ("ignoring negative optimization mode '%d'", val); + return; + } + + const int max_val = 31; + if (val > max_val) { + LOG ("optimization argument '%d' reduced to '%d'", val, max_val); + val = max_val; + } + + int64_t factor2 = 1; + for (int i = 0; i < val && factor2 <= INT_MAX; i++) + factor2 *= 2; + + int64_t factor10 = 1; + for (int i = 0; i < val && factor10 <= INT_MAX; i++) + factor10 *= 10; + + unsigned increased = 0; +#define OPTION(N, V, L, H, O, P, R, D) \ + do { \ + if (!(O)) \ + break; \ + const int64_t factor1 = ((O) == 1 ? factor2 : factor10); \ + int64_t new_val = factor1 * (int64_t) (V); \ + if (new_val > (H)) \ + new_val = (H); \ + if (new_val == (int) (V)) \ + break; \ + LOG ("optimization mode '%d' for '%s' " \ + "gives '%" PRId64 "' instead of '%d", \ + val, #N, new_val, (int) (V)); \ + CADICAL_assert (new_val <= INT_MAX); \ + N = (int) new_val; \ + increased++; \ + } while (0); + OPTIONS +#undef OPTION + if (increased) + MSG ("optimization mode '-O%d' increased %u limits", val, increased); +} + +/*------------------------------------------------------------------------*/ + +void Options::disable_preprocessing () { + size_t count = 0; +#define OPTION(N, V, L, H, O, P, R, D) \ + do { \ + if (!(P)) \ + break; \ + if (!(N)) \ + break; \ + LOG ("plain mode disables '%s'", #N); \ + CADICAL_assert ((L) == 0); \ + CADICAL_assert ((H) == 1); \ + count++; \ + N = 0; \ + } while (0); + OPTIONS +#undef OPTION + LOG ("forced plain mode disabled %zd preprocessing options", count); +#ifndef LOGGING + (void) count; +#endif +} + +bool Options::is_preprocessing_option (const char *name) { + Option *o = has (name); + return o ? o->preprocessing : false; +} + +/*------------------------------------------------------------------------*/ + +void Options::reset_default_values () { + size_t count = 0; +#define OPTION(N, V, L, H, O, P, R, D) \ + do { \ + if (!(R)) \ + break; \ + if (N == (V)) \ + break; \ + LOG ("resetting option '%s' to default %s", #N, #V); \ + count++; \ + N = (int) (V); \ + } while (0); + OPTIONS +#undef OPTION + LOG ("reset %zd options to their default values", count); +#ifndef LOGGING + (void) count; +#endif +} + +/*------------------------------------------------------------------------*/ + +void Options::copy (Options &other) const { +#ifdef LOGGING + Internal *internal = other.internal; +#endif +#define OPTION(N, V, L, H, O, P, R, D) \ + if ((N) == (int) (V)) \ + LOG ("keeping non default option '--%s=%s'", #N, #V); \ + else if ((N) != (int) (V)) { \ + LOG ("overwriting default option by '--%s=%d'", #N, N); \ + other.N = N; \ + } + OPTIONS +#undef OPTION +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_parse.cpp b/src/sat/cadical/cadical_parse.cpp new file mode 100644 index 000000000..0668aca7d --- /dev/null +++ b/src/sat/cadical/cadical_parse.cpp @@ -0,0 +1,442 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Parse error. + +#define PER(...) \ + do { \ + internal->error_message.init ( \ + "%s:%" PRIu64 ": parse error: ", file->name (), \ + (uint64_t) file->lineno ()); \ + return internal->error_message.append (__VA_ARGS__); \ + } while (0) + +/*------------------------------------------------------------------------*/ + +// Parsing utilities. + +inline int Parser::parse_char () { return file->get (); } + +// Return an non zero error string if a parse error occurred. + +inline const char *Parser::parse_string (const char *str, char prev) { + for (const char *p = str; *p; p++) + if (parse_char () == *p) + prev = *p; + else if (*p == ' ') + PER ("expected space after '%c'", prev); + else + PER ("expected '%c' after '%c'", *p, prev); + return 0; +} + +inline const char *Parser::parse_positive_int (int &ch, int &res, + const char *name) { + CADICAL_assert (isdigit (ch)); + res = ch - '0'; + while (isdigit (ch = parse_char ())) { + int digit = ch - '0'; + if (INT_MAX / 10 < res || INT_MAX - digit < 10 * res) + PER ("too large '%s' in header", name); + res = 10 * res + digit; + } + return 0; +} + +static const char *cube_token = "unexpected 'a' in CNF"; + +inline const char *Parser::parse_lit (int &ch, int &lit, int &vars, + int strict) { + if (ch == 'a') + return cube_token; + int sign = 0; + if (ch == '-') { + if (!isdigit (ch = parse_char ())) + PER ("expected digit after '-'"); + sign = -1; + } else if (!isdigit (ch)) + PER ("expected digit or '-'"); + else + sign = 1; + lit = ch - '0'; + while (isdigit (ch = parse_char ())) { + int digit = ch - '0'; + if (INT_MAX / 10 < lit || INT_MAX - digit < 10 * lit) + PER ("literal too large"); + lit = 10 * lit + digit; + } + if (ch == '\r') + ch = parse_char (); + if (ch != 'c' && ch != ' ' && ch != '\t' && ch != '\n' && ch != EOF) + PER ("expected white space after '%d'", sign * lit); + if (lit > vars) { + if (strict != FORCED) + PER ("literal %d exceeds maximum variable %d", sign * lit, vars); + else + vars = lit; + } + lit *= sign; + return 0; +} + +/*------------------------------------------------------------------------*/ + +// Parsing CNF in DIMACS format. + +const char *Parser::parse_dimacs_non_profiled (int &vars, int strict) { + +#ifndef CADICAL_QUIET + double start = internal->time (); +#endif + + bool found_inccnf_header = false; + int ch, clauses = 0; + vars = 0; + + // First read comments before header with possibly embedded options. + // + for (;;) { + ch = parse_char (); + if (strict != STRICT) + if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r') + continue; + if (ch != 'c') + break; + string buf; + while ((ch = parse_char ()) != '\n') + if (ch == EOF) + PER ("unexpected end-of-file in header comment"); + else if (ch != '\r') + buf.push_back (ch); + const char *o; + for (o = buf.c_str (); *o && *o != '-'; o++) + ; + if (!*o) + continue; + PHASE ("parse-dimacs", "found option '%s'", o); + if (*o) + solver->set_long_option (o); + } + + if (ch != 'p') + PER ("expected 'c' or 'p'"); + + ch = parse_char (); + if (strict == STRICT) { + if (ch != ' ') + PER ("expected space after 'p'"); + ch = parse_char (); + } else if (ch != ' ' && ch != '\t') + PER ("expected white space after 'p'"); + else { + do + ch = parse_char (); + while (ch == ' ' || ch == '\t'); + } + + // Now read 'p cnf ' header of DIMACS file + // or 'p inccnf' of incremental 'INCCNF' file. + // + if (ch == 'c') { + CADICAL_assert (!found_inccnf_header); + if (strict == STRICT) { + const char *err = parse_string ("nf ", 'c'); + if (err) + return err; + ch = parse_char (); + if (!isdigit (ch)) + PER ("expected digit after 'p cnf '"); + err = parse_positive_int (ch, vars, ""); + if (err) + return err; + if (ch != ' ') + PER ("expected ' ' after 'p cnf %d'", vars); + if (!isdigit (ch = parse_char ())) + PER ("expected digit after 'p cnf %d '", vars); + err = parse_positive_int (ch, clauses, ""); + if (err) + return err; + if (ch != '\n') + PER ("expected new-line after 'p cnf %d %d'", vars, clauses); + } else { + if (parse_char () != 'n') + PER ("expected 'n' after 'p c'"); + if (parse_char () != 'f') + PER ("expected 'f' after 'p cn'"); + ch = parse_char (); + if (!isspace (ch)) + PER ("expected space after 'p cnf'"); + do + ch = parse_char (); + while (isspace (ch)); + if (!isdigit (ch)) + PER ("expected digit after 'p cnf '"); + const char *err = parse_positive_int (ch, vars, ""); + if (err) + return err; + if (!isspace (ch)) + PER ("expected space after 'p cnf %d'", vars); + do + ch = parse_char (); + while (isspace (ch)); + if (!isdigit (ch)) + PER ("expected digit after 'p cnf %d '", vars); + err = parse_positive_int (ch, clauses, ""); + if (err) + return err; + while (ch != '\n') { + if (ch != '\r' && !isspace (ch)) + PER ("expected new-line after 'p cnf %d %d'", vars, clauses); + ch = parse_char (); + } + } + + MSG ("found %s'p cnf %d %d'%s header", tout.green_code (), vars, + clauses, tout.normal_code ()); + + if (strict != FORCED) + solver->reserve (vars); + internal->reserve_ids (clauses); + } else if (!parse_inccnf_too) + PER ("expected 'c' after 'p '"); + else if (ch == 'i') { + found_inccnf_header = true; + const char *err = parse_string ("nccnf", 'i'); + if (err) + return err; + ch = parse_char (); + if (strict == STRICT) { + if (ch != '\n') + PER ("expected new-line after 'p inccnf'"); + } else { + while (ch != '\n') { + if (ch != '\r' && !isspace (ch)) + PER ("expected new-line after 'p inccnf'"); + ch = parse_char (); + } + } + + MSG ("found %s'p inccnf'%s header", tout.green_code (), + tout.normal_code ()); + + strict = FORCED; + } else + PER ("expected 'c' or 'i' after 'p '"); + + if (parse_inccnf_too) + *parse_inccnf_too = false; + + // Now read body of DIMACS part. + // + int lit = 0, parsed = 0; + while ((ch = parse_char ()) != EOF) { + if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r') + continue; + if (ch == 'c') { + while ((ch = parse_char ()) != '\n' && ch != EOF) + ; + if (ch == EOF) + break; + continue; + } + if (ch == 'a' && found_inccnf_header) + break; + const char *err = parse_lit (ch, lit, vars, strict); + if (err) + return err; + if (ch == 'c') { + while ((ch = parse_char ()) != '\n') + if (ch == EOF) + PER ("unexpected end-of-file in comment"); + } + solver->add (lit); + if (!found_inccnf_header && !lit && parsed++ >= clauses && + strict != FORCED) + PER ("too many clauses"); + } + + if (lit) + PER ("last clause without terminating '0'"); + + if (!found_inccnf_header && parsed < clauses && strict != FORCED) + PER ("clause missing"); + +#ifndef CADICAL_QUIET + double end = internal->time (); + MSG ("parsed %d clauses in %.2f seconds %s time", parsed, end - start, + internal->opts.realtime ? "real" : "process"); +#endif + +#ifndef CADICAL_QUIET + start = end; + size_t num_cubes = 0; +#endif + if (ch == 'a') { + CADICAL_assert (parse_inccnf_too); + CADICAL_assert (found_inccnf_header); + if (!*parse_inccnf_too) + *parse_inccnf_too = true; + for (;;) { + ch = parse_char (); + if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r') + continue; + if (ch == 'c') { + while ((ch = parse_char ()) != '\n' && ch != EOF) + ; + if (ch == EOF) + break; + continue; + } + const char *err = parse_lit (ch, lit, vars, strict); + if (err == cube_token) + PER ("two 'a' in a row"); + else if (err) + return err; + if (ch == 'c') { + while ((ch = parse_char ()) != '\n') + if (ch == EOF) + PER ("unexpected end-of-file in comment"); + } + if (cubes) + cubes->push_back (lit); + if (!lit) { +#ifndef CADICAL_QUIET + num_cubes++; +#endif + for (;;) { + ch = parse_char (); + if (ch == ' ' || ch == '\n' || ch == '\t' || ch == '\r') + continue; + if (ch == 'c') { + while ((ch = parse_char ()) != '\n' && ch != EOF) + ; + if (ch == EOF) + break; + } + if (ch == EOF) + break; + if (ch != 'a') + PER ("expected 'a' or end-of-file after zero"); + lit = INT_MIN; + break; + } + if (ch == EOF) + break; + } + } + if (lit) + PER ("last cube without terminating '0'"); + } +#ifndef CADICAL_QUIET + if (found_inccnf_header) { + double end = internal->time (); + MSG ("parsed %zd cubes in %.2f seconds %s time", num_cubes, end - start, + internal->opts.realtime ? "real" : "process"); + } +#endif + + return 0; +} + +/*------------------------------------------------------------------------*/ + +// Parsing solution in competition output format. + +const char *Parser::parse_solution_non_profiled () { + external->solution = new signed char[external->max_var + 1u]; + external->solution_size = external->max_var; + clear_n (external->solution, external->max_var + 1u); + int ch; + for (;;) { + ch = parse_char (); + if (ch == EOF) + PER ("missing 's' line"); + else if (ch == 'c') { + while ((ch = parse_char ()) != '\n') + if (ch == EOF) + PER ("unexpected end-of-file in comment"); + } else if (ch == 's') + break; + else + PER ("expected 'c' or 's'"); + } + const char *err = parse_string (" SATISFIABLE", 's'); + if (err) + return err; + if ((ch = parse_char ()) == '\r') + ch = parse_char (); + if (ch != '\n') + PER ("expected new-line after 's SATISFIABLE'"); +#ifndef CADICAL_QUIET + int count = 0; +#endif + for (;;) { + ch = parse_char (); + if (ch != 'v') + PER ("expected 'v' at start-of-line"); + if ((ch = parse_char ()) != ' ') + PER ("expected ' ' after 'v'"); + int lit = 0; + ch = parse_char (); + do { + if (ch == ' ' || ch == '\t') { + ch = parse_char (); + continue; + } + err = parse_lit (ch, lit, external->max_var, false); + if (err) + return err; + if (ch == 'c') + PER ("unexpected comment"); + if (!lit) + break; + if (external->solution[abs (lit)]) + PER ("variable %d occurs twice", abs (lit)); + LOG ("solution %d", lit); + external->solution[abs (lit)] = sign (lit); +#ifndef CADICAL_QUIET + count++; +#endif + if (ch == '\r') + ch = parse_char (); + } while (ch != '\n'); + if (!lit) + break; + } + MSG ("parsed %d values %.2f%%", count, + percent (count, external->max_var)); + return 0; +} + +/*------------------------------------------------------------------------*/ + +// Wrappers to profile parsing and at the same time use the convenient +// implicit 'return' in PER in the non-profiled versions. + +const char *Parser::parse_dimacs (int &vars, int strict) { + CADICAL_assert (strict == FORCED || strict == RELAXED || strict == STRICT); + START (parse); + const char *err = parse_dimacs_non_profiled (vars, strict); + STOP (parse); + return err; +} + +const char *Parser::parse_solution () { + START (parse); + const char *err = parse_solution_non_profiled (); + STOP (parse); + return err; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_phases.cpp b/src/sat/cadical/cadical_phases.cpp new file mode 100644 index 000000000..f7e4eaffd --- /dev/null +++ b/src/sat/cadical/cadical_phases.cpp @@ -0,0 +1,50 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::copy_phases (vector &dst) { + START (copy); + for (auto i : vars) + dst[i] = phases.saved[i]; + STOP (copy); +} + +void Internal::clear_phases (vector &dst) { + START (copy); + for (auto i : vars) + dst[i] = 0; + STOP (copy); +} + +void Internal::phase (int lit) { + const int idx = vidx (lit); + signed char old_forced_phase = phases.forced[idx]; + signed char new_forced_phase = sign (lit); + if (old_forced_phase == new_forced_phase) { + LOG ("forced phase remains at %d", old_forced_phase * idx); + return; + } + if (old_forced_phase) + LOG ("overwriting old forced phase %d", old_forced_phase * idx); + LOG ("new forced phase %d", new_forced_phase * idx); + phases.forced[idx] = new_forced_phase; +} + +void Internal::unphase (int lit) { + const int idx = vidx (lit); + signed char old_forced_phase = phases.forced[idx]; + if (!old_forced_phase) { + LOG ("forced phase of %d already reset", lit); + return; + } + LOG ("clearing old forced phase %d", old_forced_phase * idx); + phases.forced[idx] = 0; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_probe.cpp b/src/sat/cadical/cadical_probe.cpp new file mode 100644 index 000000000..19ff0961f --- /dev/null +++ b/src/sat/cadical/cadical_probe.cpp @@ -0,0 +1,993 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Failed literal probing uses its own propagation and assignment +// functions. It further provides on-the-fly generation of hyper binary +// resolvents but only probes on roots of the binary implication graph. The +// search for failed literals is limited, but untried roots are kept until +// the next time 'probe' is called. Left over probes from the last attempt +// and new probes are tried until the limit is hit or all are tried. + +/*------------------------------------------------------------------------*/ + +bool Internal::inprobing () { + if (!opts.inprobing) + return false; + if (!preprocessing && !opts.inprocessing) + return false; + if (preprocessing) + CADICAL_assert (lim.preprocessing); + if (stats.inprobingphases && last.inprobe.reductions == stats.reductions) + return false; + return lim.inprobe <= stats.conflicts; +} + +/*------------------------------------------------------------------------*/ + +inline int Internal::get_parent_reason_literal (int lit) { + const int idx = vidx (lit); + int res = parents[idx]; + if (lit < 0) + res = -res; + return res; +} + +inline void Internal::set_parent_reason_literal (int lit, int reason) { + const int idx = vidx (lit); + if (lit < 0) + reason = -reason; + parents[idx] = reason; +} + +/*-----------------------------------------------------------------------*/ + +// for opts.probehbr=false we need to do a lot of extra work to remember the +// correct lrat_chains... This solution is also memory intensive I think +// all corresponding functions are guarded to only work with the right +// options so they can be called without checking for options +// +// call locally after failed_literal or backtracking +// +void Internal::clean_probehbr_lrat () { + if (!lrat || opts.probehbr) + return; + for (auto &field : probehbr_chains) { + for (auto &chain : field) { + chain.clear (); + } + } +} + +// call globally before a probe round (or a lookahead round) +// +void Internal::init_probehbr_lrat () { + if (!lrat || opts.probehbr) + return; + const size_t size = 2 * (1 + (size_t) max_var); + probehbr_chains.resize (size); + for (size_t i = 0; i < size; i++) { + probehbr_chains[i].resize (size); + // commented because not needed... should be empty already + /* + for (size_t j = 0; j < size; j++) { + vector empty; + probehbr_chains[i][j] = empty; + } + */ + } +} + +// sets lrat_chain to the stored chain in probehbr_chains. +// this leads to conflict with unit reason uip +// +void Internal::get_probehbr_lrat (int lit, int uip) { + if (!lrat || opts.probehbr) + return; + CADICAL_assert (lit); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (val (uip) < 0); + lrat_chain = probehbr_chains[vlit (lit)][vlit (uip)]; + int64_t id = unit_id (-uip); + lrat_chain.push_back (id); +} + +// sets the corresponding probehbr_chain to what is currently stored in +// lrat_chain. also clears lrat_chain. +// +void Internal::set_probehbr_lrat (int lit, int uip) { + if (!lrat || opts.probehbr) + return; + CADICAL_assert (lit); + CADICAL_assert (lrat_chain.size ()); + CADICAL_assert (probehbr_chains[vlit (lit)][vlit (uip)].empty ()); + probehbr_chains[vlit (lit)][vlit (uip)] = lrat_chain; + lrat_chain.clear (); +} + +// compute lrat_chain for the part of the tree from lit to dom +// use mini_chain because it needs to be reversed +// +void Internal::probe_dominator_lrat (int dom, Clause *reason) { + if (!lrat || !dom) + return; + LOG (reason, "probe dominator LRAT for %d from", dom); + for (const auto lit : *reason) { + if (val (lit) >= 0) + continue; + const auto other = -lit; + if (other == dom) + continue; + Flags &f = flags (other); + if (f.seen) + continue; + f.seen = true; + analyzed.push_back (other); + Var u = var (other); + if (u.level) { + if (!u.reason) { + LOG ("this may be a problem %d", other); + continue; + } + probe_dominator_lrat (dom, u.reason); + continue; + } + int64_t id = unit_id (other); + lrat_chain.push_back (id); + } + lrat_chain.push_back (reason->id); +} + +/*------------------------------------------------------------------------*/ + +// On-the-fly (dynamic) hyper binary resolution on decision level one can +// make use of the fact that the implication graph is actually a tree. + +// Compute a dominator of two literals in the binary implication tree. + +int Internal::probe_dominator (int a, int b) { + require_mode (PROBE); + int l = a, k = b; + Var *u = &var (l), *v = &var (k); + CADICAL_assert (val (l) > 0), CADICAL_assert (val (k) > 0); + CADICAL_assert (u->level == 1), CADICAL_assert (v->level == 1); + while (l != k) { + if (u->trail > v->trail) + swap (l, k), swap (u, v); + if (!get_parent_reason_literal (l)) + return l; + int parent = get_parent_reason_literal (k); + CADICAL_assert (parent), CADICAL_assert (val (parent) > 0); + v = &var (k = parent); + CADICAL_assert (v->level == 1); + } + LOG ("dominator %d of %d and %d", l, a, b); + CADICAL_assert (val (l) > 0); + return l; +} + +// The idea of dynamic on-the-fly hyper-binary resolution came up in the +// PrecoSAT solver, where it originally was used on all decision levels. + +// It turned out, that most of the hyper-binary resolvents were generated +// during probing on decision level one anyhow. Thus this version is +// specialized to decision level one, where actually all long (non-binary) +// forcing clauses can be resolved to become binary. So if we find a clause +// which would force a new assignment at decision level one during probing +// we resolve it (the 'reason' argument) to obtain a hyper binary resolvent. +// It consists of the still unassigned literal (the new unit) and the +// negation of the unique closest dominator of the negation of all (false) +// literals in the clause (which has to exist on decision level one). + +// There are two special cases which should be mentioned: +// +// (A) The reason is already a binary clause in a certain sense, since all +// its unwatched literals are root level fixed to false. In this +// situation it would be better to shrink the clause immediately instead +// of adding a new clause consisting only of the watched literals. +// However, this would happen during the next garbage collection anyhow. +// +// (B) The resolvent subsumes the original reason clause. This is +// equivalent to the property that the negated dominator is contained in +// the original reason. Again one could in principle shrink the clause. +// +// Note that (A) is actually subsumed by (B). The possible optimization to +// shrink the clause on-the-fly is difficult (need to update 'blit' and +// 'binary' of the other watch at least) and also not really that important. +// For (B) we simply add the new binary resolvent and mark the old subsumed +// clause as garbage instead. And since in the situation of (A) the +// shrinking will be performed at the next garbage collection anyhow, we +// do not change clauses in (A). + +// The hyper binary resolvent clause is redundant unless it subsumes the +// original reason and that one is irredundant. + +// If the option 'opts.probehbr' is 'false', we actually do not add the new +// hyper binary resolvent, but simply pretend we would have added it and +// still return the dominator as new reason / parent for the new unit. + +// Finally note that adding clauses changes the watches of the propagated +// literal and thus we can not use standard iterators during probing but +// need to fall back to indices. One watch for the hyper binary resolvent +// clause is added at the end of the currently propagated watches, but its +// watch is a binary watch and will be skipped during propagating long +// clauses anyhow. + +inline int Internal::hyper_binary_resolve (Clause *reason) { + require_mode (PROBE); + CADICAL_assert (level == 1); + CADICAL_assert (reason->size > 2); + const const_literal_iterator end = reason->end (); + const int *lits = reason->literals; + const_literal_iterator k; +#ifndef CADICAL_NDEBUG + // First literal unassigned, all others false. + CADICAL_assert (!val (lits[0])); + for (k = lits + 1; k != end; k++) + CADICAL_assert (val (*k) < 0); + CADICAL_assert (var (lits[1]).level == 1); +#endif + LOG (reason, "hyper binary resolving"); + stats.hbrs++; + stats.hbrsizes += reason->size; + const int lit = lits[1]; + int dom = -lit, non_root_level_literals = 0; + for (k = lits + 2; k != end; k++) { + const int other = -*k; + CADICAL_assert (val (other) > 0); + if (!var (other).level) + continue; + dom = probe_dominator (dom, other); + non_root_level_literals++; + } + probe_reason = reason; + if (non_root_level_literals && opts.probehbr) { // !(A) + bool contained = false; + for (k = lits + 1; !contained && k != end; k++) + contained = (*k == -dom); + const bool red = !contained || reason->redundant; + if (red) + stats.hbreds++; + LOG ("new %s hyper binary resolvent %d %d", + (red ? "redundant" : "irredundant"), -dom, lits[0]); + CADICAL_assert (clause.empty ()); + clause.push_back (-dom); + clause.push_back (lits[0]); + probe_dominator_lrat (dom, reason); + if (lrat) + clear_analyzed_literals (); + Clause *c = new_hyper_binary_resolved_clause (red, 2); + probe_reason = c; + if (red) + c->hyper = true; + clause.clear (); + lrat_chain.clear (); + if (contained) { + stats.hbrsubs++; + LOG (reason, "subsumed original"); + mark_garbage (reason); + } + } else if (non_root_level_literals && lrat) { + // still calculate LRAT and remember for later + CADICAL_assert (!opts.probehbr); + probe_dominator_lrat (dom, reason); + clear_analyzed_literals (); + set_probehbr_lrat (dom, lits[0]); + } + return dom; +} + +/*------------------------------------------------------------------------*/ + +// The following functions 'probe_assign' and 'probe_propagate' are used for +// propagating during failed literal probing in simplification mode, as +// replacement of the generic propagation routine 'propagate' and +// 'search_assign'. + +// The code is mostly copied from 'propagate.cpp' and specialized. We only +// comment on the differences. More explanations are in 'propagate.cpp'. + +inline void Internal::probe_assign (int lit, int parent) { + require_mode (PROBE); + int idx = vidx (lit); + CADICAL_assert (!val (idx)); + CADICAL_assert (!flags (idx).eliminated () || !parent); + CADICAL_assert (!parent || val (parent) > 0); + Var &v = var (idx); + v.level = level; + v.trail = (int) trail.size (); + CADICAL_assert ((int) num_assigned < max_var); + num_assigned++; + v.reason = level ? probe_reason : 0; + probe_reason = 0; + set_parent_reason_literal (lit, parent); + if (!level) + learn_unit_clause (lit); + else + CADICAL_assert (level == 1); + const signed char tmp = sign (lit); + set_val (idx, tmp); + CADICAL_assert (val (lit) > 0); + CADICAL_assert (val (-lit) < 0); + trail.push_back (lit); + + // Do not save the current phase during inprocessing but remember the + // number of units on the trail of the last time this literal was + // assigned. This allows us to avoid some redundant failed literal + // probing attempts. Search for 'propfixed' in 'probe.cpp' for details. + // + if (level) + propfixed (lit) = stats.all.fixed; + + if (parent) + LOG ("probe assign %d parent %d", lit, parent); + else if (level) + LOG ("probe assign %d probe", lit); + else + LOG ("probe assign %d negated failed literal UIP", lit); +} + +void Internal::probe_assign_decision (int lit) { + require_mode (PROBE); + CADICAL_assert (!level); + CADICAL_assert (propagated == trail.size ()); + level++; + control.push_back (Level (lit, trail.size ())); + probe_assign (lit, 0); +} + +void Internal::probe_assign_unit (int lit) { + require_mode (PROBE); + CADICAL_assert (!level); + CADICAL_assert (active (lit)); + probe_assign (lit, 0); +} + +/*------------------------------------------------------------------------*/ + +// same as in propagate but inlined here +// +inline void Internal::probe_lrat_for_units (int lit) { + if (!lrat) + return; + if (level) + return; // not decision level 0 + LOG ("building chain for units"); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (probe_reason); + for (auto &reason_lit : *probe_reason) { + if (lit == reason_lit) + continue; + CADICAL_assert (val (reason_lit)); + if (!val (reason_lit)) + continue; + const int signed_reason_lit = val (reason_lit) * reason_lit; + int64_t id = unit_id (signed_reason_lit); + lrat_chain.push_back (id); + } + lrat_chain.push_back (probe_reason->id); +} + +/*------------------------------------------------------------------------*/ + +// This is essentially the same as 'propagate' except that we prioritize and +// always propagate binary clauses first (see our CPAIOR'13 paper on tree +// based look ahead), then immediately stop at a conflict and of course use +// 'probe_assign' instead of 'search_assign'. The binary propagation part +// is factored out too. If a new unit on decision level one is found we +// perform hyper binary resolution and thus actually build an implication +// tree instead of a DAG. Statistics counters are also different. + +inline void Internal::probe_propagate2 () { + require_mode (PROBE); + int64_t &ticks = stats.ticks.probe; + while (propagated2 != trail.size ()) { + const int lit = -trail[propagated2++]; + LOG ("probe propagating %d over binary clauses", -lit); + Watches &ws = watches (lit); + ticks += 1 + cache_lines (ws.size (), sizeof (const_watch_iterator *)); + for (const auto &w : ws) { + if (!w.binary ()) + continue; + const signed char b = val (w.blit); + if (b > 0) + continue; + ticks++; + if (b < 0) + conflict = w.clause; // but continue + else { + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (!probe_reason); + probe_reason = w.clause; + probe_lrat_for_units (w.blit); + probe_assign (w.blit, -lit); + lrat_chain.clear (); + } + } + } +} + +bool Internal::probe_propagate () { + require_mode (PROBE); + CADICAL_assert (!unsat); + START (propagate); + int64_t before = propagated2 = propagated; + int64_t &ticks = stats.ticks.probe; + while (!conflict) { + if (propagated2 != trail.size ()) + probe_propagate2 (); + else if (propagated != trail.size ()) { + const int lit = -trail[propagated++]; + LOG ("probe propagating %d over large clauses", -lit); + Watches &ws = watches (lit); + ticks += 1 + cache_lines (ws.size (), + sizeof (sizeof (const_watch_iterator *))); + size_t i = 0, j = 0; + while (i != ws.size ()) { + const Watch w = ws[j++] = ws[i++]; + if (w.binary ()) + continue; + const signed char b = val (w.blit); + if (b > 0) + continue; + ticks++; + if (w.clause->garbage) + continue; + const literal_iterator lits = w.clause->begin (); + const int other = lits[0] ^ lits[1] ^ lit; + // lits[0] = other, lits[1] = lit; + const signed char u = val (other); + if (u > 0) + ws[j - 1].blit = other; + else { + const int size = w.clause->size; + const const_literal_iterator end = lits + size; + const literal_iterator middle = lits + w.clause->pos; + literal_iterator k = middle; + int r = 0; + signed char v = -1; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + w.clause->pos = k - lits; + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + if (v > 0) + ws[j - 1].blit = r; + else if (!v) { + ticks++; + LOG (w.clause, "unwatch %d in", r); + *k = lit; + lits[0] = other; + lits[1] = r; + watch_literal (r, lit, w.clause); + j--; + } else if (!u) { + ticks++; + if (level == 1) { + lits[0] = other, lits[1] = lit; + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (!probe_reason); + int dom = hyper_binary_resolve (w.clause); + probe_assign (other, dom); + } else { + ticks++; + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (!probe_reason); + probe_reason = w.clause; + probe_lrat_for_units (other); + probe_assign_unit (other); + lrat_chain.clear (); + } + probe_propagate2 (); + } else + conflict = w.clause; + } + } + if (j != i) { + while (i != ws.size ()) + ws[j++] = ws[i++]; + ws.resize (j); + } + } else + break; + } + int64_t delta = propagated2 - before; + stats.propagations.probe += delta; + if (conflict) + LOG (conflict, "conflict"); + STOP (propagate); + return !conflict; +} + +/*------------------------------------------------------------------------*/ + +// This a specialized instance of 'analyze'. + +void Internal::failed_literal (int failed) { + + LOG ("analyzing failed literal probe %d", failed); + stats.failed++; + stats.probefailed++; + + CADICAL_assert (!unsat); + CADICAL_assert (conflict); + CADICAL_assert (level == 1); + CADICAL_assert (analyzed.empty ()); + CADICAL_assert (lrat_chain.empty ()); + + START (analyze); + + LOG (conflict, "analyzing failed literal conflict"); + + int uip = 0; + for (const auto &lit : *conflict) { + const int other = -lit; + if (!var (other).level) { + CADICAL_assert (val (other) > 0); + continue; + } + uip = uip ? probe_dominator (uip, other) : other; + } + probe_dominator_lrat (uip, conflict); + if (lrat) + clear_analyzed_literals (); + + LOG ("found probing UIP %d", uip); + CADICAL_assert (uip); + + vector work; + + int parent = uip; + while (parent != failed) { + const int next = get_parent_reason_literal (parent); + parent = next; + CADICAL_assert (parent); + work.push_back (parent); + } + + backtrack (); + conflict = 0; + + CADICAL_assert (!val (uip)); + probe_assign_unit (-uip); + lrat_chain.clear (); + + if (!probe_propagate ()) + learn_empty_clause (); + + size_t j = 0; + while (!unsat && j < work.size ()) { + // CADICAL_assert (!opts.probehbr); CADICAL_assertion fails ... + const int parent = work[j++]; + const signed char tmp = val (parent); + if (tmp > 0) { + CADICAL_assert (!opts.probehbr); // ... CADICAL_assertion should hold here + get_probehbr_lrat (parent, uip); + LOG ("clashing failed parent %d", parent); + learn_empty_clause (); + } else if (tmp == 0) { + CADICAL_assert (!opts.probehbr); // ... and here + LOG ("found unassigned failed parent %d", parent); + get_probehbr_lrat (parent, uip); // this is computed during + probe_assign_unit (-parent); // propagation and can include + lrat_chain.clear (); // multiple chains where only one + if (!probe_propagate ()) + learn_empty_clause (); // is needed! + } + uip = parent; + } + work.clear (); + erase_vector (work); + + STOP (analyze); + + CADICAL_assert (unsat || val (failed) < 0); +} + +/*------------------------------------------------------------------------*/ + +bool Internal::is_binary_clause (Clause *c, int &a, int &b) { + CADICAL_assert (!level); + if (c->garbage) + return false; + int first = 0, second = 0; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) + return false; + if (tmp < 0) + continue; + if (second) + return false; + if (first) + second = lit; + else + first = lit; + } + if (!second) + return false; + a = first, b = second; + return true; +} + +// We probe on literals first, which occur more often negated and thus we +// sort the 'probes' stack in such a way that literals which occur negated +// less frequently come first. Probes are taken from the back of the stack. + +struct probe_negated_noccs_rank { + Internal *internal; + probe_negated_noccs_rank (Internal *i) : internal (i) {} + typedef size_t Type; + Type operator() (int a) const { return internal->noccs (-a); } +}; + +// Fill the 'probes' schedule. + +void Internal::generate_probes () { + + CADICAL_assert (probes.empty ()); + + int64_t &ticks = stats.ticks.probe; + + // First determine all the literals which occur in binary clauses. It is + // way faster to go over the clauses once, instead of walking the watch + // lists for each literal. + // + init_noccs (); + ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *)); + for (const auto &c : clauses) { + int a, b; + ticks++; + if (!is_binary_clause (c, a, b)) + continue; + noccs (a)++; + noccs (b)++; + } + + for (auto idx : vars) { + + // Then focus on roots of the binary implication graph, which are + // literals occurring negatively in a binary clause, but not positively. + // If neither 'idx' nor '-idx' is a root it makes less sense to probe + // this variable. + + // This argument requires that equivalent literal substitution through + // 'decompose' is performed, because otherwise there might be 'cyclic + // roots' which are not tried, i.e., -1 2 0, 1 -2 0, 1 2 3 0, 1 2 -3 0. + + ticks += 2; + + const bool have_pos_bin_occs = noccs (idx) > 0; + const bool have_neg_bin_occs = noccs (-idx) > 0; + + if (have_pos_bin_occs == have_neg_bin_occs) + continue; + + int probe = have_neg_bin_occs ? idx : -idx; + + // See the discussion where 'propfixed' is used below. + // + if (propfixed (probe) >= stats.all.fixed) + continue; + + LOG ("scheduling probe %d negated occs %" PRId64 "", probe, + noccs (-probe)); + probes.push_back (probe); + } + + rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this)); + + reset_noccs (); + shrink_vector (probes); + + PHASE ("probe-round", stats.probingrounds, + "scheduled %zd literals %.0f%%", probes.size (), + percent (probes.size (), 2u * max_var)); +} + +// Follow the ideas in 'generate_probes' but flush non root probes and +// reorder remaining probes. + +void Internal::flush_probes () { + + CADICAL_assert (!probes.empty ()); + int64_t &ticks = stats.ticks.probe; + + init_noccs (); + ticks += 1 + cache_lines (clauses.size (), sizeof (Clause *)); + for (const auto &c : clauses) { + int a, b; + ticks++; + if (!is_binary_clause (c, a, b)) + continue; + noccs (a)++; + noccs (b)++; + } + + const auto eop = probes.end (); + auto j = probes.begin (); + for (auto i = j; i != eop; i++) { + int lit = *i; + if (!active (lit)) + continue; + ticks += 2; + const bool have_pos_bin_occs = noccs (lit) > 0; + const bool have_neg_bin_occs = noccs (-lit) > 0; + if (have_pos_bin_occs == have_neg_bin_occs) + continue; + if (have_pos_bin_occs) + lit = -lit; + CADICAL_assert (!noccs (lit)), CADICAL_assert (noccs (-lit) > 0); + if (propfixed (lit) >= stats.all.fixed) + continue; + LOG ("keeping probe %d negated occs %" PRId64 "", lit, noccs (-lit)); + *j++ = lit; + } + size_t remain = j - probes.begin (); +#ifndef CADICAL_QUIET + size_t flushed = probes.size () - remain; +#endif + probes.resize (remain); + + rsort (probes.begin (), probes.end (), probe_negated_noccs_rank (this)); + + reset_noccs (); + shrink_vector (probes); + + PHASE ("probe-round", stats.probingrounds, + "flushed %zd literals %.0f%% remaining %zd", flushed, + percent (flushed, remain + flushed), remain); +} + +int Internal::next_probe () { + + int generated = 0; + + for (;;) { + + if (probes.empty ()) { + if (generated++) + return 0; + generate_probes (); + } + + while (!probes.empty ()) { + + int probe = probes.back (); + probes.pop_back (); + + // Eliminated or assigned. + // + if (!active (probe)) + continue; + + // There is now new unit since the last time we propagated this probe, + // thus we propagated it before without obtaining a conflict and + // nothing changed since then. Thus there is no need to propagate it + // again. This observation was independently made by Partik Simons + // et.al. in the context of implementing 'smodels' (see for instance + // Alg. 4 in his JAIR article from 2002) and it has also been + // contributed to the thesis work of Yacine Boufkhad. + // + if (propfixed (probe) >= stats.all.fixed) + continue; + + return probe; + } + } +} + +bool Internal::probe () { + + if (!opts.probe) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + + SET_EFFORT_LIMIT (limit, probe, true); + + START_SIMPLIFIER (probe, PROBE); + stats.probingrounds++; + + // Probing is limited in terms of non-probing propagations + // 'stats.propagations'. We allow a certain percentage 'opts.probeeffort' + // (say %5) of probing propagations in each probing with a lower bound of + // 'opts.probmineff'. + // + + PHASE ("probe-round", stats.probingrounds, + "probing limit of %" PRId64 " propagations ", limit); + + int old_failed = stats.failed; +#ifndef CADICAL_QUIET + int64_t old_probed = stats.probed; +#endif + int64_t old_hbrs = stats.hbrs; + + if (!probes.empty ()) + flush_probes (); + + // We reset 'propfixed' since there was at least another conflict thus + // a new learned clause, which might produce new propagations (and hyper + // binary resolvents). During 'generate_probes' we keep the old value. + // + for (auto idx : vars) + propfixed (idx) = propfixed (-idx) = -1; + + CADICAL_assert (unsat || propagated == trail.size ()); + propagated = propagated2 = trail.size (); + + int probe; + init_probehbr_lrat (); + while (!unsat && !terminated_asynchronously () && + stats.ticks.probe < limit && (probe = next_probe ())) { + stats.probed++; + LOG ("probing %d", probe); + probe_assign_decision (probe); + if (probe_propagate ()) + backtrack (); + else + failed_literal (probe); + clean_probehbr_lrat (); + } + + if (unsat) + LOG ("probing derived empty clause"); + else if (propagated < trail.size ()) { + LOG ("probing produced %zd units", + (size_t) (trail.size () - propagated)); + if (!propagate ()) { + LOG ("propagating units after probing results in empty clause"); + learn_empty_clause (); + } else + sort_watches (); + } + + int failed = stats.failed - old_failed; +#ifndef CADICAL_QUIET + int64_t probed = stats.probed - old_probed; +#endif + int64_t hbrs = stats.hbrs - old_hbrs; + + PHASE ("probe-round", stats.probingrounds, + "probed %" PRId64 " and found %d failed literals", probed, failed); + + if (hbrs) + PHASE ("probe-round", stats.probingrounds, + "found %" PRId64 " hyper binary resolvents", hbrs); + + STOP_SIMPLIFIER (probe, PROBE); + + report ('p', !opts.reportall && !(unsat + failed + hbrs)); + + return !unsat && failed; +} + +/*------------------------------------------------------------------------*/ + +// This schedules a number of inprocessing techniques. +// These range from very cheap and beneficial (decompose) to +// more expensive and sometimes less beneficial. We want to limit +// expensive techniques to some fraction of total time or search time. +// this is done using 'ticks'. +// Generally, there are options for each of the techniques to set the +// efficiency, i.e., the fraction of ticks they are allowed as budget. +// Whenever e.g. vivify is called, the budget is calculated from the +// search ticks that have passed since the last vivify round and this +// efficiency. +// We want to be able to run inprocessing frequently, without it dominating +// runtimes. This entire inprocessing scheme is scheduled after a certain +// amount of conflicts were found, the gap between two inprocessing rounds +// increasing by a constant number each time. In effect, the number of +// inprocessing rounds is allways the square root of the number of conflicts +// with some constant factor. +// This factor can also be with the option 'inprobeint' +// Some of the techniques are not run always, for different reasons. +// 'factor' or BVA depends on certain structures of the irredundant clauses +// and as such will only be run when new irredundant clauses are derived or +// it was not able to finish with the entire search space. +// 'sweeping' is especially usefull on certain classes of formulas, and uses +// a increasing or decreasing delay that depends on how usefull it was. +// In cases where it is less usefull, we obviously want to reset the budged, +// even if the routine was delayed. +// Additionally 'vivify', 'sweep' and 'factor' can also have a big initial +// overhead in setting up the datastructures. This has to be accounted for +// with the 'ticks', however, since inprocessing is done frequently, this +// overhead is too expensive to pay. So instead, we accumulate the budget +// of 'ticks' and delay the technique until it passes a certain threshhold, +// which depends on the the cost of initialization. Note that in the case of +// sweeping, we have two different delays, one which resets the budged, and +// one which passes it to the next round. In this case the former takes +// precendent, until we would run sweeping once, at which point the focus +// switches to the latter delay until the budget is big enough, such that +// sweeping can be run. Then we switch back to the other delay. + +void CaDiCaL::Internal::inprobe (bool update_limits) { + + if (unsat) + return; + if (level) + backtrack (); + if (!propagate ()) { + learn_empty_clause (); + return; + } + + stats.inprobingphases++; + if (external_prop) { + CADICAL_assert (!level); + private_steps = true; + } + const int before = active (); + const int before_extended = stats.variables_extension; + + // schedule of inprobing techniques. + // + { + mark_duplicated_binary_clauses_as_garbage (); + decompose (); + if (ternary ()) + decompose (); // If we derived a binary clause + if (probe ()) + decompose (); + + if (extract_gates ()) + decompose (); + if (sweep ()) // full occurrence list + decompose (); // ... and (ELS) afterwards. + (void) vivify (); // resets watches + transred (); // builds big. + factor (); // resets watches, partial occurrence list + } + + if (external_prop) { + CADICAL_assert (!level); + private_steps = false; + } + + if (!update_limits) + return; + + const int after = active (); + const int after_extended = stats.variables_extension; + const int diff_extended = after_extended - before_extended; + CADICAL_assert (diff_extended >= 0); + const int removed = before - after + diff_extended; + CADICAL_assert (removed >= 0); + + if (removed) { + stats.inprobesuccess++; + PHASE ("probe-phase", stats.inprobingphases, + "successfully removed %d active variables %.0f%%", removed, + percent (removed, before)); + } else + PHASE ("probe-phase", stats.inprobingphases, + "could not remove any active variable"); + + const int64_t delta = + 25 * opts.inprobeint * log10 (stats.inprobingphases + 9); + lim.inprobe = stats.conflicts + delta; + + PHASE ("probe-phase", stats.inprobingphases, + "new limit at %" PRId64 " conflicts after %" PRId64 " conflicts", + lim.inprobe, delta); + + last.inprobe.reductions = stats.reductions; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_profile.cpp b/src/sat/cadical/cadical_profile.cpp new file mode 100644 index 000000000..a95c97661 --- /dev/null +++ b/src/sat/cadical/cadical_profile.cpp @@ -0,0 +1,113 @@ +#include "global.h" + +#ifndef CADICAL_QUIET + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Initialize all profile counters with constant name and profiling level. + +Profiles::Profiles (Internal *s) + : internal (s) +#define PROFILE(NAME, LEVEL) , NAME (#NAME, LEVEL) + PROFILES +#undef PROFILE +{ +} + +void Internal::start_profiling (Profile &profile, double s) { + CADICAL_assert (profile.level <= opts.profile); + CADICAL_assert (!profile.active); + profile.started = s; + profile.active = true; +} + +void Internal::stop_profiling (Profile &profile, double s) { + CADICAL_assert (profile.level <= opts.profile); + CADICAL_assert (profile.active); + profile.value += s - profile.started; + profile.active = false; +} + +double Internal::update_profiles () { + double now = time (); +#define PROFILE(NAME, LEVEL) \ + do { \ + Profile &profile = profiles.NAME; \ + if (profile.active) { \ + CADICAL_assert (profile.level <= opts.profile); \ + profile.value += now - profile.started; \ + profile.started = now; \ + } \ + } while (0); + PROFILES +#undef PROFILE + return now; +} + +double Internal::solve_time () { + (void) update_profiles (); + return profiles.solve.value; +} + +#define PRT(S, T) \ + MSG ("%s" S "%s", tout.magenta_code (), T, tout.normal_code ()) + +void Internal::print_profile () { + double now = update_profiles (); + const char *time_type = opts.realtime ? "real" : "process"; + SECTION ("run-time profiling"); + PRT ("%s time taken by individual solving procedures", time_type); + PRT ("(percentage relative to %s time for solving)", time_type); + LINE (); + const size_t size = sizeof profiles / sizeof (Profile); + struct Profile *profs[size]; + size_t n = 0; +#define PROFILE(NAME, LEVEL) \ + do { \ + if (LEVEL > opts.profile) \ + break; \ + Profile *p = &profiles.NAME; \ + if (p == &profiles.solve) \ + break; \ + if (!profiles.NAME.value && p != &profiles.parse && \ + p != &profiles.search && p != &profiles.simplify) \ + break; \ + profs[n++] = p; \ + } while (0); + PROFILES +#undef PROFILE + + CADICAL_assert (n <= size); + + // Explicit bubble sort to avoid heap allocation since 'print_profile' + // is also called during catching a signal after out of heap memory. + // This only makes sense if 'profs' is allocated on the stack, and + // not the heap, which should be the case. + + double solve = profiles.solve.value; + + for (size_t i = 0; i < n; i++) { + for (size_t j = i + 1; j < n; j++) + if (profs[j]->value > profs[i]->value) + swap (profs[i], profs[j]); + MSG ("%12.2f %7.2f%% %s", profs[i]->value, + percent (profs[i]->value, solve), profs[i]->name); + } + + MSG (" ================================="); + MSG ("%12.2f %7.2f%% solve", solve, percent (solve, now)); + + LINE (); + PRT ("last line shows %s time for solving", time_type); + PRT ("(percentage relative to total %s time)", time_type); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#endif // ifndef CADICAL_QUIET diff --git a/src/sat/cadical/cadical_proof.cpp b/src/sat/cadical/cadical_proof.cpp new file mode 100644 index 000000000..13c9a1a5d --- /dev/null +++ b/src/sat/cadical/cadical_proof.cpp @@ -0,0 +1,663 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +using namespace std; + +/*------------------------------------------------------------------------*/ + +// Enable proof logging and checking by allocating a 'Proof' object. + +void Internal::new_proof_on_demand () { + if (!proof) { + LOG ("connecting proof to internal solver"); + proof = new Proof (this); + } +} + +void Internal::resize_unit_clauses_idx () { + size_t new_vsize = vsize ? 2 * vsize : 1 + (size_t) max_var; + unit_clauses_idx.resize (2 * new_vsize, 0); +} + +void Internal::force_lrat () { + if (lrat) + return; + CADICAL_assert (proof); + lrat = true; +} + +void Internal::connect_proof_tracer (Tracer *tracer, bool antecedents, + bool finalize_clauses) { + new_proof_on_demand (); + if (antecedents) + force_lrat (); + if (finalize_clauses) + frat = true; + resize_unit_clauses_idx (); + proof->connect (tracer); + tracers.push_back (tracer); +} + +void Internal::connect_proof_tracer (InternalTracer *tracer, + bool antecedents, + bool finalize_clauses) { + new_proof_on_demand (); + if (antecedents) + force_lrat (); + if (finalize_clauses) + frat = true; + resize_unit_clauses_idx (); + tracer->connect_internal (this); + proof->connect (tracer); + tracers.push_back (tracer); +} + +void Internal::connect_proof_tracer (StatTracer *tracer, bool antecedents, + bool finalize_clauses) { + new_proof_on_demand (); + if (antecedents) + force_lrat (); + if (finalize_clauses) + frat = true; + resize_unit_clauses_idx (); + tracer->connect_internal (this); + proof->connect (tracer); + stat_tracers.push_back (tracer); +} + +void Internal::connect_proof_tracer (FileTracer *tracer, bool antecedents, + bool finalize_clauses) { + new_proof_on_demand (); + if (antecedents) + force_lrat (); + if (finalize_clauses) + frat = true; + resize_unit_clauses_idx (); + tracer->connect_internal (this); + proof->connect (tracer); + file_tracers.push_back (tracer); +} + +bool Internal::disconnect_proof_tracer (Tracer *tracer) { + auto it = std::find (tracers.begin (), tracers.end (), tracer); + if (it != tracers.end ()) { + tracers.erase (it); + CADICAL_assert (proof); + proof->disconnect (tracer); + return true; + } + return false; +} + +bool Internal::disconnect_proof_tracer (StatTracer *tracer) { + auto it = std::find (stat_tracers.begin (), stat_tracers.end (), tracer); + if (it != stat_tracers.end ()) { + stat_tracers.erase (it); + CADICAL_assert (proof); + proof->disconnect (tracer); + return true; + } + return false; +} + +bool Internal::disconnect_proof_tracer (FileTracer *tracer) { + auto it = std::find (file_tracers.begin (), file_tracers.end (), tracer); + if (it != file_tracers.end ()) { + file_tracers.erase (it); + CADICAL_assert (proof); + proof->disconnect (tracer); + return true; + } + return false; +} + +void Proof::disconnect (Tracer *t) { + tracers.erase (std::remove (tracers.begin (), tracers.end (), t), + tracers.end ()); +} + +// Enable proof tracing. + +void Internal::trace (File *file) { + if (opts.veripb) { + LOG ("PROOF connecting VeriPB tracer"); + bool antecedents = opts.veripb == 1 || opts.veripb == 2; + bool deletions = opts.veripb == 2 || opts.veripb == 4; + FileTracer *ft = + new VeripbTracer (this, file, opts.binary, antecedents, deletions); + connect_proof_tracer (ft, antecedents); + } else if (opts.frat) { + LOG ("PROOF connecting FRAT tracer"); + bool antecedents = opts.frat == 1; + resize_unit_clauses_idx (); + FileTracer *ft = + new FratTracer (this, file, opts.binary, opts.frat == 1); + connect_proof_tracer (ft, antecedents, true); + } else if (opts.lrat) { + LOG ("PROOF connecting LRAT tracer"); + FileTracer *ft = new LratTracer (this, file, opts.binary); + connect_proof_tracer (ft, true); + } else if (opts.idrup) { + LOG ("PROOF connecting IDRUP tracer"); + FileTracer *ft = new IdrupTracer (this, file, opts.binary); + connect_proof_tracer (ft, true); + } else if (opts.lidrup) { + LOG ("PROOF connecting LIDRUP tracer"); + FileTracer *ft = new LidrupTracer (this, file, opts.binary); + connect_proof_tracer (ft, true); + } else { + LOG ("PROOF connecting DRAT tracer"); + FileTracer *ft = new DratTracer (this, file, opts.binary); + connect_proof_tracer (ft, false); + } +} + +// Enable proof checking. + +void Internal::check () { + new_proof_on_demand (); + if (opts.checkproof > 1) { + StatTracer *lratchecker = new LratChecker (this); + DeferDeletePtr delete_lratchecker ( + (LratChecker *) lratchecker); + LOG ("PROOF connecting LRAT proof checker"); + force_lrat (); + frat = true; + resize_unit_clauses_idx (); + proof->connect (lratchecker); + stat_tracers.push_back (lratchecker); + delete_lratchecker.release (); + } + if (opts.checkproof == 1 || opts.checkproof == 3) { + StatTracer *checker = new Checker (this); + DeferDeletePtr delete_checker ((Checker *) checker); + LOG ("PROOF connecting proof checker"); + proof->connect (checker); + stat_tracers.push_back (checker); + delete_checker.release (); + } +} + +// We want to close a proof trace and stop checking as soon we are done. + +void Internal::close_trace (bool print) { + for (auto &tracer : file_tracers) + tracer->close (print); +} + +// We can flush a proof trace file before actually closing it. + +void Internal::flush_trace (bool print) { + for (auto &tracer : file_tracers) + tracer->flush (print); +} + +/*------------------------------------------------------------------------*/ + +Proof::Proof (Internal *s) : internal (s) { LOG ("PROOF new"); } + +Proof::~Proof () { LOG ("PROOF delete"); } + +/*------------------------------------------------------------------------*/ + +inline void Proof::add_literal (int internal_lit) { + const int external_lit = internal->externalize (internal_lit); + clause.push_back (external_lit); +} + +inline void Proof::add_literals (Clause *c) { + for (auto const &lit : *c) + add_literal (lit); +} + +inline void Proof::add_literals (const vector &c) { + for (auto const &lit : c) + add_literal (lit); +} + +/*------------------------------------------------------------------------*/ + +void Proof::add_original_clause (int64_t id, bool r, const vector &c) { + LOG (c, "PROOF adding original internal clause"); + add_literals (c); + clause_id = id; + redundant = r; + add_original_clause (); +} + +void Proof::add_external_original_clause (int64_t id, bool r, + const vector &c, + bool restore) { + // literals of c are already external + CADICAL_assert (clause.empty ()); + for (auto const &lit : c) + clause.push_back (lit); + clause_id = id; + redundant = r; + add_original_clause (restore); +} + +void Proof::delete_external_original_clause (int64_t id, bool r, + const vector &c) { + // literals of c are already external + CADICAL_assert (clause.empty ()); + for (auto const &lit : c) + clause.push_back (lit); + clause_id = id; + redundant = r; + delete_clause (); +} + +void Proof::add_derived_empty_clause (int64_t id, + const vector &chain) { + LOG ("PROOF adding empty clause"); + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = id; + redundant = false; + add_derived_clause (); +} + +void Proof::add_derived_unit_clause (int64_t id, int internal_unit, + const vector &chain) { + LOG ("PROOF adding unit clause %d", internal_unit); + CADICAL_assert (proof_chain.empty ()); + CADICAL_assert (clause.empty ()); + add_literal (internal_unit); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = id; + redundant = false; + add_derived_clause (); +} + +/*------------------------------------------------------------------------*/ + +void Proof::add_derived_clause (Clause *c, const vector &chain) { + LOG (c, "PROOF adding to proof derived"); + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + add_literals (c); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = c->id; + redundant = c->redundant; + add_derived_clause (); +} + +void Proof::add_derived_clause (int64_t id, bool r, const vector &c, + const vector &chain) { + LOG (c, "PROOF adding derived clause"); + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + for (const auto &lit : c) + add_literal (lit); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = id; + redundant = r; + add_derived_clause (); +} + +void Proof::add_assumption_clause (int64_t id, const vector &c, + const vector &chain) { + // literals of c are already external + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + for (const auto &lit : c) + clause.push_back (lit); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = id; + add_assumption_clause (); +} + +void Proof::add_assumption (int a) { + // a is already external + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + clause.push_back (a); + add_assumption (); +} + +void Proof::add_constraint (const vector &c) { + // literals of c are already external + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + for (const auto &lit : c) + clause.push_back (lit); + add_constraint (); +} + +void Proof::add_assumption_clause (int64_t id, int lit, + const vector &chain) { + CADICAL_assert (clause.empty ()); + CADICAL_assert (proof_chain.empty ()); + clause.push_back (lit); + for (const auto &cid : chain) + proof_chain.push_back (cid); + clause_id = id; + add_assumption_clause (); +} + +void Proof::delete_clause (Clause *c) { + LOG (c, "PROOF deleting from proof"); + clause.clear (); // Can be non-empty if an allocation fails during adding. + add_literals (c); + clause_id = c->id; + redundant = c->redundant; + delete_clause (); // Increments 'statistics.deleted'. +} + +void Proof::delete_clause (int64_t id, bool r, const vector &c) { + LOG (c, "PROOF deleting from proof"); + CADICAL_assert (clause.empty ()); + add_literals (c); + clause_id = id; + redundant = r; + delete_clause (); // Increments 'statistics.deleted'. +} + +void Proof::weaken_minus (Clause *c) { + LOG (c, "PROOF weaken minus of"); + CADICAL_assert (clause.empty ()); + add_literals (c); + clause_id = c->id; + weaken_minus (); +} + +void Proof::weaken_minus (int64_t id, const vector &c) { + LOG (c, "PROOF deleting from proof"); + CADICAL_assert (clause.empty ()); + add_literals (c); + clause_id = id; + weaken_minus (); +} + +void Proof::weaken_plus (Clause *c) { + weaken_minus (c); + delete_clause (c); // Increments 'statistics.deleted'. +} + +void Proof::weaken_plus (int64_t id, const vector &c) { + weaken_minus (id, c); + delete_clause (id, false, c); // Increments 'statistics.deleted'. +} + +void Proof::delete_unit_clause (int64_t id, const int lit) { + LOG ("PROOF deleting unit from proof %d", lit); + CADICAL_assert (clause.empty ()); + add_literal (lit); + clause_id = id; + redundant = false; + delete_clause (); +} + +void Proof::finalize_clause (Clause *c) { + LOG (c, "PROOF finalizing clause"); + CADICAL_assert (clause.empty ()); + add_literals (c); + clause_id = c->id; + finalize_clause (); +} + +void Proof::finalize_clause (int64_t id, const vector &c) { + LOG (c, "PROOF finalizing clause"); + CADICAL_assert (clause.empty ()); + for (const auto &lit : c) + add_literal (lit); + clause_id = id; + finalize_clause (); +} + +void Proof::finalize_unit (int64_t id, int lit) { + LOG ("PROOF finalizing clause %d", lit); + CADICAL_assert (clause.empty ()); + add_literal (lit); + clause_id = id; + finalize_clause (); +} + +void Proof::finalize_external_unit (int64_t id, int lit) { + LOG ("PROOF finalizing clause %d", lit); + CADICAL_assert (clause.empty ()); + clause.push_back (lit); + clause_id = id; + finalize_clause (); +} + +/*------------------------------------------------------------------------*/ + +// During garbage collection clauses are shrunken by removing falsified +// literals. To avoid copying the clause, we provide a specialized tracing +// function here, which traces the required 'add' and 'remove' operations. + +void Proof::flush_clause (Clause *c) { + LOG (c, "PROOF flushing falsified literals in"); + CADICAL_assert (clause.empty ()); + const bool antecedents = (internal->lrat || internal->frat); + for (int i = 0; i < c->size; i++) { + int internal_lit = c->literals[i]; + if (internal->fixed (internal_lit) < 0) { + if (antecedents) { + int64_t id = internal->unit_id (-internal_lit); + proof_chain.push_back (id); + } + continue; + } + add_literal (internal_lit); + } + proof_chain.push_back (c->id); + redundant = c->redundant; + int64_t id = ++internal->clause_id; + clause_id = id; + add_derived_clause (); + delete_clause (c); + c->id = id; +} + +// While strengthening clauses, e.g., through self-subsuming resolutions, +// during subsumption checking, we have a similar situation, except that we +// have to remove exactly one literal. Again the following function allows +// to avoid copying the clause and instead provides tracing of the required +// 'add' and 'remove' operations. + +void Proof::strengthen_clause (Clause *c, int remove, + const vector &chain) { + LOG (c, "PROOF strengthen by removing %d in", remove); + CADICAL_assert (clause.empty ()); + for (int i = 0; i < c->size; i++) { + int internal_lit = c->literals[i]; + if (internal_lit == remove) + continue; + add_literal (internal_lit); + } + int64_t id = ++internal->clause_id; + clause_id = id; + redundant = c->redundant; + for (const auto &cid : chain) + proof_chain.push_back (cid); + add_derived_clause (); + delete_clause (c); + c->id = id; +} + +void Proof::otfs_strengthen_clause (Clause *c, const std::vector &old, + const vector &chain) { + LOG (c, "PROOF otfs strengthen"); + CADICAL_assert (clause.empty ()); + for (int i = 0; i < c->size; i++) { + int internal_lit = c->literals[i]; + add_literal (internal_lit); + } + int64_t id = ++internal->clause_id; + clause_id = id; + redundant = c->redundant; + for (const auto &cid : chain) + proof_chain.push_back (cid); + add_derived_clause (); + delete_clause (c->id, c->redundant, old); + c->id = id; +} + +void Proof::strengthen (int64_t id) { + clause_id = id; + strengthen (); +} + +/*------------------------------------------------------------------------*/ + +void Proof::add_original_clause (bool restore) { + LOG (clause, "PROOF adding original external clause"); + CADICAL_assert (clause_id); + + for (auto &tracer : tracers) { + tracer->add_original_clause (clause_id, false, clause, restore); + } + clause.clear (); + clause_id = 0; +} + +void Proof::add_derived_clause () { + LOG (clause, "PROOF adding derived external clause (redundant: %d)", + redundant); + CADICAL_assert (clause_id); + for (auto &tracer : tracers) { + tracer->add_derived_clause (clause_id, redundant, clause, proof_chain); + } + proof_chain.clear (); + clause.clear (); + clause_id = 0; +} + +void Proof::delete_clause () { + LOG (clause, "PROOF deleting external clause"); + for (auto &tracer : tracers) { + tracer->delete_clause (clause_id, redundant, clause); + } + clause.clear (); + clause_id = 0; +} + +void Proof::demote_clause () { + LOG (clause, "PROOF demoting external clause"); + CADICAL_assert (!redundant); + for (auto &tracer : tracers) { + tracer->demote_clause (clause_id, clause); + } + clause.clear (); + clause_id = 0; +} + +void Proof::weaken_minus () { + LOG (clause, "PROOF marking as clause to restore"); + for (auto &tracer : tracers) { + tracer->weaken_minus (clause_id, clause); + } + clause.clear (); + clause_id = 0; +} + +void Proof::strengthen () { + LOG ("PROOF strengthen clause with id %" PRId64, clause_id); + for (auto &tracer : tracers) { + tracer->strengthen (clause_id); + } + clause_id = 0; +} + +void Proof::finalize_clause () { + for (auto &tracer : tracers) { + tracer->finalize_clause (clause_id, clause); + } + clause.clear (); + clause_id = 0; +} + +void Proof::add_assumption_clause () { + LOG (clause, "PROOF adding assumption clause"); + for (auto &tracer : tracers) { + tracer->add_assumption_clause (clause_id, clause, proof_chain); + } + proof_chain.clear (); + clause.clear (); + clause_id = 0; +} + +void Proof::add_assumption () { + LOG (clause, "PROOF adding assumption"); + CADICAL_assert (clause.size () == 1); + for (auto &tracer : tracers) { + tracer->add_assumption (clause.back ()); + } + clause.clear (); +} + +void Proof::add_constraint () { + LOG (clause, "PROOF adding constraint"); + for (auto &tracer : tracers) { + tracer->add_constraint (clause); + } + clause.clear (); +} + +void Proof::reset_assumptions () { + LOG ("PROOF reset assumptions"); + for (auto &tracer : tracers) { + tracer->reset_assumptions (); + } +} + +void Proof::report_status (int status, int64_t id) { + LOG ("PROOF reporting status %d", status); + for (auto &tracer : tracers) { + tracer->report_status (status, id); + } +} + +void Proof::begin_proof (int64_t id) { + LOG (clause, "PROOF begin proof"); + for (auto &tracer : tracers) { + tracer->begin_proof (id); + } +} + +void Proof::solve_query () { + LOG (clause, "PROOF solve query"); + for (auto &tracer : tracers) { + tracer->solve_query (); + } +} + +void Proof::conclude_unsat (ConclusionType con, + const vector &conclusion) { + LOG (clause, "PROOF conclude unsat"); + for (auto &tracer : tracers) { + tracer->conclude_unsat (con, conclusion); + } +} + +void Proof::conclude_sat (const vector &model) { + LOG (clause, "PROOF conclude sat"); + for (auto &tracer : tracers) { + tracer->conclude_sat (model); + } +} + +void Proof::conclude_unknown (const vector &trail) { + LOG (clause, "PROOF conclude unknown"); + for (auto &tracer : tracers) { + tracer->conclude_unknown (trail); + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_propagate.cpp b/src/sat/cadical/cadical_propagate.cpp new file mode 100644 index 000000000..a4f21ee6a --- /dev/null +++ b/src/sat/cadical/cadical_propagate.cpp @@ -0,0 +1,586 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// We are using the address of 'decision_reason' as pseudo reason for +// decisions to distinguish assignment decisions from other assignments. +// Before we added chronological backtracking all learned units were +// assigned at decision level zero ('Solver.level == 0') and we just used a +// zero pointer as reason. After allowing chronological backtracking units +// were also assigned at higher decision level (but with assignment level +// zero), and it was not possible anymore to just distinguish the case +// 'unit' versus 'decision' by just looking at the current level. Both had +// a zero pointer as reason. Now only units have a zero reason and +// decisions need to use the pseudo reason 'decision_reason'. + +// External propagation steps use the pseudo reason 'external_reason'. +// The corresponding actual reason clauses are learned only when they are +// relevant in conflict analysis or in root-level fixing steps. + +static Clause decision_reason_clause; +static Clause *decision_reason = &decision_reason_clause; + +// If chronological backtracking is used the actual assignment level might +// be lower than the current decision level. In this case the assignment +// level is defined as the maximum level of the literals in the reason +// clause except the literal for which the clause is a reason. This +// function determines this assignment level. For non-chronological +// backtracking as in classical CDCL this function always returns the +// current decision level, the concept of assignment level does not make +// sense, and accordingly this function can be skipped. + +// In case of external propagation, it is implicitly assumed that the +// assignment level is the level of the literal (since the reason clause, +// i.e., the set of other literals, is unknown). + +inline int Internal::assignment_level (int lit, Clause *reason) { + + CADICAL_assert (opts.chrono || external_prop); + if (!reason || reason == external_reason) + return level; + + int res = 0; + + for (const auto &other : *reason) { + if (other == lit) + continue; + CADICAL_assert (val (other)); + int tmp = var (other).level; + if (tmp > res) + res = tmp; + } + + return res; +} + +// calculate lrat_chain +// +void Internal::build_chain_for_units (int lit, Clause *reason, + bool forced) { + if (!lrat) + return; + if (opts.chrono && assignment_level (lit, reason) && !forced) + return; + else if (!opts.chrono && level && !forced) + return; // not decision level 0 + CADICAL_assert (lrat_chain.empty ()); + for (auto &reason_lit : *reason) { + if (lit == reason_lit) + continue; + CADICAL_assert (val (reason_lit)); + if (!val (reason_lit)) + continue; + const int signed_reason_lit = val (reason_lit) * reason_lit; + int64_t id = unit_id (signed_reason_lit); + lrat_chain.push_back (id); + } + lrat_chain.push_back (reason->id); +} + +// same code as above but reason is assumed to be conflict and lit is not +// needed +// +void Internal::build_chain_for_empty () { + if (!lrat || !lrat_chain.empty ()) + return; + CADICAL_assert (!level); + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (conflict); + LOG (conflict, "lrat for global empty clause with conflict"); + for (auto &lit : *conflict) { + CADICAL_assert (val (lit) < 0); + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + } + lrat_chain.push_back (conflict->id); +} + +/*------------------------------------------------------------------------*/ + +inline void Internal::search_assign (int lit, Clause *reason) { + + if (level) + require_mode (SEARCH); + + const int idx = vidx (lit); + const bool from_external = reason == external_reason; + CADICAL_assert (!val (idx)); + CADICAL_assert (!flags (idx).eliminated () || reason == decision_reason || + reason == external_reason); + Var &v = var (idx); + int lit_level; + CADICAL_assert (!lrat || level || reason == external_reason || + reason == decision_reason || !lrat_chain.empty ()); + // The following cases are explained in the two comments above before + // 'decision_reason' and 'assignment_level'. + // + // External decision reason means that the propagation was done by + // an external propagation and the reason clause not known (yet). + // In that case it is assumed that the propagation is NOT out of + // order (i.e. lit_level = level), because due to lazy explanation, + // we can not calculate the real assignment level. + // The function assignment_level () will also assign the current level + // to literals with external reason. + if (!reason) + lit_level = 0; // unit + else if (reason == decision_reason) + lit_level = level, reason = 0; + else if (opts.chrono) + lit_level = assignment_level (lit, reason); + else + lit_level = level; + if (!lit_level) + reason = 0; + + v.level = lit_level; + v.trail = trail.size (); + v.reason = reason; + CADICAL_assert ((int) num_assigned < max_var); + CADICAL_assert (num_assigned == trail.size ()); + num_assigned++; + if (!lit_level && !from_external) + learn_unit_clause (lit); // increases 'stats.fixed' + CADICAL_assert (lit_level || !from_external); + const signed char tmp = sign (lit); + set_val (idx, tmp); + CADICAL_assert (val (lit) > 0); // Just a bit paranoid but useful. + CADICAL_assert (val (-lit) < 0); // Ditto. + if (!searching_lucky_phases) + phases.saved[idx] = tmp; // phase saving during search + trail.push_back (lit); +#ifdef LOGGING + if (!lit_level) + LOG ("root-level unit assign %d @ 0", lit); + else + LOG (reason, "search assign %d @ %d", lit, lit_level); +#endif + + if (watching ()) { + const Watches &ws = watches (-lit); + if (!ws.empty ()) { + const Watch &w = ws[0]; +#ifndef WIN32 + __builtin_prefetch (&w, 0, 1); +#endif + } + } + lrat_chain.clear (); +} + +/*------------------------------------------------------------------------*/ + +// External versions of 'search_assign' which are not inlined. They either +// are used to assign unit clauses on the root-level, in 'decide' to assign +// a decision or in 'analyze' to assign the literal 'driven' by a learned +// clause. This happens far less frequently than the 'search_assign' above, +// which is called directly in 'propagate' below and thus is inlined. + +void Internal::assign_unit (int lit) { + CADICAL_assert (!level); + search_assign (lit, 0); +} + +// Just assume the given literal as decision (increase decision level and +// assign it). This is used below in 'decide'. + +void Internal::search_assume_decision (int lit) { + require_mode (SEARCH); + CADICAL_assert (propagated == trail.size ()); + new_trail_level (lit); + notify_decision (); + LOG ("search decide %d", lit); + search_assign (lit, decision_reason); +} + +void Internal::search_assign_driving (int lit, Clause *c) { + require_mode (SEARCH); + search_assign (lit, c); + notify_assignments (); +} + +void Internal::search_assign_external (int lit) { + require_mode (SEARCH); + search_assign (lit, external_reason); + notify_assignments (); +} + +/*------------------------------------------------------------------------*/ + +// The 'propagate' function is usually the hot-spot of a CDCL SAT solver. +// The 'trail' stack saves assigned variables and is used here as BFS queue +// for checking clauses with the negation of assigned variables for being in +// conflict or whether they produce additional assignments. + +// This version of 'propagate' uses lazy watches and keeps two watched +// literals at the beginning of the clause. We also use 'blocking literals' +// to reduce the number of times clauses have to be visited (2008 JSAT paper +// by Chu, Harwood and Stuckey). The watches know if a watched clause is +// binary, in which case it never has to be visited. If a binary clause is +// falsified we continue propagating. + +// Finally, for long clauses we save the position of the last watch +// replacement in 'pos', which in turn reduces certain quadratic accumulated +// propagation costs (2013 JAIR article by Ian Gent) at the expense of four +// more bytes for each clause. + +bool Internal::propagate () { + + if (level) + require_mode (SEARCH); + CADICAL_assert (!unsat); + LOG ("starting propagate"); + START (propagate); + + // Updating statistics counter in the propagation loops is costly so we + // delay until propagation ran to completion. + // + int64_t before = propagated; + int64_t ticks = 0; + + while (!conflict && propagated != trail.size ()) { + + const int lit = -trail[propagated++]; + LOG ("propagating %d", -lit); + Watches &ws = watches (lit); + + const const_watch_iterator eow = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i = j; + ticks += 1 + cache_lines (ws.size (), sizeof *i); + + while (i != eow) { + + const Watch w = *j++ = *i++; + const signed char b = val (w.blit); + LOG (w.clause, "checking"); + + if (b > 0) + continue; // blocking literal satisfied + + if (w.binary ()) { + + // CADICAL_assert (w.clause->redundant || !w.clause->garbage); + + // In principle we can ignore garbage binary clauses too, but that + // would require to dereference the clause pointer all the time with + // + // if (w.clause->garbage) { j--; continue; } // (*) + // + // This is too costly. It is however necessary to produce correct + // proof traces if binary clauses are traced to be deleted ('d ...' + // line) immediately as soon they are marked as garbage. Actually + // finding instances where this happens is pretty difficult (six + // parallel fuzzing jobs in parallel took an hour), but it does + // occur. Our strategy to avoid generating incorrect proofs now is + // to delay tracing the deletion of binary clauses marked as garbage + // until they are really deleted from memory. For large clauses + // this is not necessary since we have to access the clause anyhow. + // + // Thanks go to Mathias Fleury, who wanted me to explain why the + // line '(*)' above was in the code. Removing it actually really + // improved running times and thus I tried to find concrete + // instances where this happens (which I found), and then + // implemented the described fix. + + // Binary clauses are treated separately since they do not require + // to access the clause at all (only during conflict analysis, and + // there also only to simplify the code). + + if (b < 0) + conflict = w.clause; // but continue ... + else { + build_chain_for_units (w.blit, w.clause, 0); + search_assign (w.blit, w.clause); + // lrat_chain.clear (); done in search_assign + ticks++; + } + + } else { + CADICAL_assert (w.clause->size > 2); + + if (conflict) + break; // Stop if there was a binary conflict already. + + // The cache line with the clause data is forced to be loaded here + // and thus this first memory access below is the real hot-spot of + // the solver. Note, that this check is positive very rarely and + // thus branch prediction should be almost perfect here. + + ticks++; + + if (w.clause->garbage) { + j--; + continue; + } + + literal_iterator lits = w.clause->begin (); + + // Simplify code by forcing 'lit' to be the second literal in the + // clause. This goes back to MiniSAT. We use a branch-less version + // for conditionally swapping the first two literals, since it + // turned out to be substantially faster than this one + // + // if (lits[0] == lit) swap (lits[0], lits[1]); + // + // which achieves the same effect, but needs a branch. + // + const int other = lits[0] ^ lits[1] ^ lit; + const signed char u = val (other); // value of the other watch + + if (u > 0) + j[-1].blit = other; // satisfied, just replace blit + else { + + // This follows Ian Gent's (JAIR'13) idea of saving the position + // of the last watch replacement. In essence it needs two copies + // of the default search for a watch replacement (in essence the + // code in the 'if (v < 0) { ... }' block below), one starting at + // the saved position until the end of the clause and then if that + // one failed to find a replacement another one starting at the + // first non-watched literal until the saved position. + + const int size = w.clause->size; + const literal_iterator middle = lits + w.clause->pos; + const const_literal_iterator end = lits + size; + literal_iterator k = middle; + + // Find replacement watch 'r' at position 'k' with value 'v'. + + int r = 0; + signed char v = -1; + + while (k != end && (v = val (r = *k)) < 0) + k++; + + if (v < 0) { // need second search starting at the head? + + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + + w.clause->pos = k - lits; // always save position + + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + + if (v > 0) { + + // Replacement satisfied, so just replace 'blit'. + + j[-1].blit = r; + + } else if (!v) { + + // Found new unassigned replacement literal to be watched. + + LOG (w.clause, "unwatch %d in", lit); + + lits[0] = other; + lits[1] = r; + *k = lit; + + watch_literal (r, lit, w.clause); + + j--; // Drop this watch from the watch list of 'lit'. + + ticks++; + + } else if (!u) { + + CADICAL_assert (v < 0); + + // The other watch is unassigned ('!u') and all other literals + // assigned to false (still 'v < 0'), thus we found a unit. + // + build_chain_for_units (other, w.clause, 0); + search_assign (other, w.clause); + // lrat_chain.clear (); done in search_assign + ticks++; + + // Similar code is in the implementation of the SAT'18 paper on + // chronological backtracking but in our experience, this code + // first does not really seem to be necessary for correctness, + // and further does not improve running time either. + // + if (opts.chrono > 1) { + + const int other_level = var (other).level; + + if (other_level > var (lit).level) { + + // The assignment level of the new unit 'other' is larger + // than the assignment level of 'lit'. Thus we should find + // another literal in the clause at that higher assignment + // level and watch that instead of 'lit'. + + CADICAL_assert (size > 2); + + int pos, s = 0; + + for (pos = 2; pos < size; pos++) + if (var (s = lits[pos]).level == other_level) + break; + + CADICAL_assert (s); + CADICAL_assert (pos < size); + + LOG (w.clause, "unwatch %d in", lit); + lits[pos] = lit; + lits[0] = other; + lits[1] = s; + watch_literal (s, lit, w.clause); + + j--; // Drop this watch from the watch list of 'lit'. + } + } + } else { + + CADICAL_assert (u < 0); + CADICAL_assert (v < 0); + + // The other watch is assigned false ('u < 0') and all other + // literals as well (still 'v < 0'), thus we found a conflict. + + conflict = w.clause; + break; + } + } + } + } + + if (j != i) { + + while (i != eow) + *j++ = *i++; + + ws.resize (j - ws.begin ()); + } + } + + if (searching_lucky_phases) { + + if (conflict) + LOG (conflict, "ignoring lucky conflict"); + + } else { + + // Avoid updating stats eagerly in the hot-spot of the solver. + // + stats.propagations.search += propagated - before; + stats.ticks.search[stable] += ticks; + + if (!conflict) + no_conflict_until = propagated; + else { + + if (stable) + stats.stabconflicts++; + stats.conflicts++; + + LOG (conflict, "conflict"); + + // The trail before the current decision level was conflict free. + // + no_conflict_until = control[level].trail; + } + } + + STOP (propagate); + + return !conflict; +} + +/*------------------------------------------------------------------------*/ + +void Internal::propergate () { + + CADICAL_assert (!conflict); + CADICAL_assert (propagated == trail.size ()); + + while (propergated != trail.size ()) { + + const int lit = -trail[propergated++]; + LOG ("propergating %d", -lit); + Watches &ws = watches (lit); + + const const_watch_iterator eow = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i = j; + + while (i != eow) { + + const Watch w = *j++ = *i++; + + if (w.binary ()) { + CADICAL_assert (val (w.blit) > 0); + continue; + } + if (w.clause->garbage) { + j--; + continue; + } + + literal_iterator lits = w.clause->begin (); + + const int other = lits[0] ^ lits[1] ^ lit; + const signed char u = val (other); + + // TODO: check if u == 0 can happen. + if (u > 0) + continue; + CADICAL_assert (u < 0); + + const int size = w.clause->size; + const literal_iterator middle = lits + w.clause->pos; + const const_literal_iterator end = lits + size; + literal_iterator k = middle; + + int r = 0; + signed char v = -1; + + while (k != end && (v = val (r = *k)) < 0) + k++; + + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + w.clause->pos = k - lits; + + CADICAL_assert (v > 0); + + LOG (w.clause, "unwatch %d in", lit); + + lits[0] = other; + lits[1] = r; + *k = lit; + + watch_literal (r, lit, w.clause); + + j--; + } + + if (j != i) { + + while (i != eow) + *j++ = *i++; + + ws.resize (j - ws.begin ()); + } + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_queue.cpp b/src/sat/cadical/cadical_queue.cpp new file mode 100644 index 000000000..c8b5b8030 --- /dev/null +++ b/src/sat/cadical/cadical_queue.cpp @@ -0,0 +1,96 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Slightly different than 'bump_variable' since the variable is not +// enqueued at all. + +inline void Internal::init_enqueue (int idx) { + Link &l = links[idx]; + if (opts.reverse) { + l.prev = 0; + if (queue.first) { + CADICAL_assert (!links[queue.first].prev); + links[queue.first].prev = idx; + btab[idx] = btab[queue.first] - 1; + } else { + CADICAL_assert (!queue.last); + queue.last = idx; + btab[idx] = 0; + } + CADICAL_assert (btab[idx] <= stats.bumped); + l.next = queue.first; + queue.first = idx; + if (!queue.unassigned) + update_queue_unassigned (queue.last); + } else { + l.next = 0; + if (queue.last) { + CADICAL_assert (!links[queue.last].next); + links[queue.last].next = idx; + } else { + CADICAL_assert (!queue.first); + queue.first = idx; + } + btab[idx] = ++stats.bumped; + l.prev = queue.last; + queue.last = idx; + update_queue_unassigned (queue.last); + } +} + +// Initialize VMTF queue from current 'old_max_var + 1' to 'new_max_var'. +// This incorporates an initial variable order. We currently simply assume +// that variables with smaller index are more important. This is the same +// as in MiniSAT (implicitly) and also matches the 'scores' initialization. +// +void Internal::init_queue (int old_max_var, int new_max_var) { + LOG ("initializing VMTF queue from %d to %d", old_max_var + 1, + new_max_var); + CADICAL_assert (old_max_var < new_max_var); + // New variables can be created that can invoke enlarge anytime (eg via + // calls during ipasir-up call-backs), thus assuming (!level) is not + // correct + for (int idx = old_max_var; idx < new_max_var; idx++) + init_enqueue (idx + 1); +} + +// Shuffle the VMTF queue. + +void Internal::shuffle_queue () { + if (!opts.shuffle) + return; + if (!opts.shufflequeue) + return; + stats.shuffled++; + LOG ("shuffling queue"); + vector shuffle; + if (opts.shufflerandom) { + for (int idx = max_var; idx; idx--) + shuffle.push_back (idx); + Random random (opts.seed); // global seed + random += stats.shuffled; // different every time + for (int i = 0; i <= max_var - 2; i++) { + const int j = random.pick_int (i, max_var - 1); + swap (shuffle[i], shuffle[j]); + } + } else { + for (int idx = queue.last; idx; idx = links[idx].prev) + shuffle.push_back (idx); + } + queue.first = queue.last = 0; + for (const int idx : shuffle) + queue.enqueue (links, idx); + int64_t bumped = queue.bumped; + for (int idx = queue.last; idx; idx = links[idx].prev) + btab[idx] = bumped--; + queue.unassigned = queue.last; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_random.cpp b/src/sat/cadical/cadical_random.cpp new file mode 100644 index 000000000..85d022e8f --- /dev/null +++ b/src/sat/cadical/cadical_random.cpp @@ -0,0 +1,233 @@ +#include "global.h" + +#include "internal.hpp" + +/*------------------------------------------------------------------------*/ + +// Our random number generator is seeded by default (i.e., in the default +// constructor) with random seeds, which should be unique across machines, +// processes and time. This makes this code below rather operating system +// dependent. We also use in essence defensive programming, overlaying +// several methods to get randomness since in the past we were bitten a +// couple of times (and got the same seeds). Having several methods makes +// it also simpler to port randomly initializing seeds to different +// operating systems (even though currently it is only tested on Linux). +// This functionality is only used in the 'Mobical' model based tester at +// this point, since the main solver explicitly sets a random seed ('0' by +// default in 'options.hpp') and also currently only uses this seed in the +// local search procedure explicitly without using the default constructor. +// It is crucial for 'Mobical' to make sure that concurrent runs are really +// independent. + +/*------------------------------------------------------------------------*/ + +// Uncomment the following definition to force printing the computed hash +// values for individual machine and process properties. This is only needed +// for testing, porting and debugging different ports of this seeding and +// hashing functions (uncomment and run 'mobical' for instance). + +/* +#define DO_PRINT_HASH +*/ + +#ifdef DO_PRINT_HASH +#define PRINT_HASH(H) \ + do { \ + printf ("c PRINT_HASH %32s () = %020" PRIu64 "\n", __func__, H); \ + fflush (stdout); \ + } while (0) +#else +#define PRINT_HASH(...) \ + do { \ + } while (0) +#endif + +/*------------------------------------------------------------------------*/ + +// This is Linux specific but if '/var/lib/dbus/machine-id' does not exist +// does not have any effect. TODO: add a similar machine identity hashing +// function for other operating systems (Windows and macOS). + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +static uint64_t hash_machine_identifier () { + FILE *file = fopen ("/var/lib/dbus/machine-id", "r"); + uint64_t res = 0; + if (file) { + char buffer[128]; + memset (buffer, 0, sizeof buffer); + size_t bytes = fread (buffer, 1, sizeof buffer - 1, file); + CADICAL_assert (bytes); + fclose (file); + if (bytes && bytes < sizeof buffer) { + buffer[bytes] = 0; + res = hash_string (buffer); + } + } + PRINT_HASH (res); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +/*------------------------------------------------------------------------*/ + +// On our Linux cluster where we used an NFS mounted root disk the +// 'machine-id' above (even on a locally mounted '/var' disk on each node) +// was copied from '/etc/machine-id' which was shared among all nodes +// (before figuring this out and fixing it). Thus the main idea of getting +// different hash values through this machine identifier machines did not +// work. As an additional measure to increase the possibility to get +// different seeds we are now also using network addresses (explicitly). + +#ifndef WIN32 + +extern "C" { +#include +#include +#include +#include +#include +#include +} + +#endif + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +static uint64_t hash_network_addresses () { + uint64_t res = 0; + + // We still need to properly port this to Windows, but since accessing the + // IP address is only required for better randomization during testing + // (running 'mobical' on a cluster for instance) it is not crucial unless + // you really need to run 'mobical' on a Windows cluster where each node + // has identical IP addresses. + +#ifndef WIN32 + struct ifaddrs *addrs; + if (!getifaddrs (&addrs)) { + for (struct ifaddrs *addr = addrs; addr; addr = addr->ifa_next) { + if (!addr->ifa_addr) + continue; + const int family = addr->ifa_addr->sa_family; + if (family == AF_INET || family == AF_INET6) { + const int size = (family == AF_INET) ? sizeof (struct sockaddr_in) + : sizeof (struct sockaddr_in6); + char buffer[128]; + if (!getnameinfo (addr->ifa_addr, size, buffer, sizeof buffer, 0, 0, + NI_NUMERICHOST)) { + uint64_t tmp = hash_string (buffer); +#ifdef DO_PRINT_HASH + printf ("c PRINT_HASH %35s = %020" PRIu64 "\n", buffer, tmp); + fflush (stdout); +#endif + res ^= tmp; + res *= 10000000000000000051ul; + } + } + } + freeifaddrs (addrs); + } +#endif + + PRINT_HASH (res); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +/*------------------------------------------------------------------------*/ + +// Hash the current wall-clock time in seconds. + +extern "C" { +#include +} + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +static uint64_t hash_time () { + uint64_t res = ::time (0); + PRINT_HASH (res); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +/*------------------------------------------------------------------------*/ + +// Hash the process identified. + +extern "C" { +#include +#ifdef WIN32 +#include +#else +#include +#endif +} + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +static uint64_t hash_process () { + uint64_t res = getpid (); + PRINT_HASH (res); + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +/*------------------------------------------------------------------------*/ + +// Hash the current number of clock cycles. + +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +static uint64_t hash_clock_cycles () { + uint64_t res = std::clock (); + PRINT_HASH (res); + return res; +} + +} // namespace CaDiCaL + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +Random::Random () : state (1) { + add (hash_machine_identifier ()); + add (hash_network_addresses ()); + add (hash_clock_cycles ()); + add (hash_process ()); + add (hash_time ()); +#ifdef DO_PRINT_HASH + printf ("c PRINT_HASH %32s = %020" PRIu64 "\n", "combined", state); + fflush (stdout); +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_reap.cpp b/src/sat/cadical/cadical_reap.cpp new file mode 100644 index 000000000..4625c44db --- /dev/null +++ b/src/sat/cadical/cadical_reap.cpp @@ -0,0 +1,142 @@ +#include "global.h" + +#include "reap.hpp" +#include +#include +#include + +#ifdef _MSC_VER +#include +static inline int __builtin_clz(unsigned x) { + unsigned long r; + _BitScanReverse(&r, x); + return (int)(r ^ 31); +} +#endif + +ABC_NAMESPACE_IMPL_START + +void Reap::init () { + for (auto &bucket : buckets) + bucket = {0}; + CADICAL_assert (!num_elements); + CADICAL_assert (!last_deleted); + min_bucket = 32; + CADICAL_assert (!max_bucket); +} + +void Reap::release () { + num_elements = 0; + last_deleted = 0; + min_bucket = 32; + max_bucket = 0; +} + +Reap::Reap () { + num_elements = 0; + last_deleted = 0; + min_bucket = 32; + max_bucket = 0; +} + +static inline unsigned leading_zeroes_of_unsigned (unsigned x) { + return x ? __builtin_clz (x) : sizeof (unsigned) * 8; +} + +void Reap::push (unsigned e) { + CADICAL_assert (last_deleted <= e); + const unsigned diff = e ^ last_deleted; + const unsigned bucket = 32 - leading_zeroes_of_unsigned (diff); + buckets[bucket].push_back (e); + if (min_bucket > bucket) + min_bucket = bucket; + if (max_bucket < bucket) + max_bucket = bucket; + CADICAL_assert (num_elements != UINT_MAX); + num_elements++; +} + +unsigned Reap::pop () { + CADICAL_assert (num_elements > 0); + unsigned i = min_bucket; + for (;;) { + CADICAL_assert (i < 33); + CADICAL_assert (i <= max_bucket); + std::vector &s = buckets[i]; + if (s.empty ()) { + min_bucket = ++i; + continue; + } + unsigned res; + if (i) { + res = UINT_MAX; + const auto begin = std::begin (s); + const auto end = std::end (s); + auto q = std::begin (s); + CADICAL_assert (begin < end); + for (auto p = begin; p != end; ++p) { + const unsigned tmp = *p; + if (tmp >= res) + continue; + res = tmp; + q = p; + } + + for (auto p = begin; p != end; ++p) { + if (p == q) + continue; + const unsigned other = *p; + const unsigned diff = other ^ res; + CADICAL_assert (sizeof (unsigned) == 4); + const unsigned j = 32 - leading_zeroes_of_unsigned (diff); + CADICAL_assert (j < i); + buckets[j].push_back (other); + if (min_bucket > j) + min_bucket = j; + } + + s.clear (); + + if (i && max_bucket == i) { +#ifndef CADICAL_NDEBUG + for (unsigned j = i + 1; j < 33; j++) + CADICAL_assert (buckets[j].empty ()); +#endif + if (s.empty ()) + max_bucket = i - 1; + } + } else { + res = last_deleted; + CADICAL_assert (!buckets[0].empty ()); + CADICAL_assert (buckets[0].at (0) == res); + buckets[0].pop_back (); + } + + if (min_bucket == i) { +#ifndef CADICAL_NDEBUG + for (unsigned j = 0; j < i; j++) + CADICAL_assert (buckets[j].empty ()); +#endif + if (s.empty ()) + min_bucket = std::min ((int) (i + 1), 32); + } + + --num_elements; + CADICAL_assert (last_deleted <= res); + last_deleted = res; + + return res; + } +} + +void Reap::clear () { + CADICAL_assert (max_bucket <= 32); + for (unsigned i = 0; i < 33; i++) + buckets[i].clear (); + num_elements = 0; + last_deleted = 0; + min_bucket = 32; + max_bucket = 0; +} + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_reduce.cpp b/src/sat/cadical/cadical_reduce.cpp new file mode 100644 index 000000000..cc6f35228 --- /dev/null +++ b/src/sat/cadical/cadical_reduce.cpp @@ -0,0 +1,284 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Once in a while we reduce, e.g., we remove learned clauses which are +// supposed to be less useful in the future. This is done in increasing +// intervals, which has the effect of allowing more and more learned clause +// to be kept for a longer period. The number of learned clauses kept +// in memory corresponds to an upper bound on the 'space' of a resolution +// proof needed to refute a formula in proof complexity sense. + +bool Internal::reducing () { + if (!opts.reduce) + return false; + if (!stats.current.redundant) + return false; + return stats.conflicts >= lim.reduce; +} + +/*------------------------------------------------------------------------*/ + +// Even less regularly we are flushing all redundant clauses. + +bool Internal::flushing () { + if (!opts.flush) + return false; + return stats.conflicts >= lim.flush; +} + +/*------------------------------------------------------------------------*/ + +void Internal::mark_clauses_to_be_flushed () { + const int tier1limit = tier1[false]; + const int tier2limit = max (tier1limit, tier2[false]); + for (const auto &c : clauses) { + if (!c->redundant) + continue; // keep irredundant + if (c->garbage) + continue; // already marked as garbage + if (c->reason) + continue; // need to keep reasons + const unsigned used = c->used; + if (used) + c->used--; + if (c->glue < tier1limit && used) + continue; + if (c->glue < tier2limit && used >= max_used - 1) + continue; + mark_garbage (c); // flush unused clauses + if (c->hyper) + stats.flush.hyper++; + else + stats.flush.learned++; + } + // No change to 'lim.kept{size,glue}'. +} + +/*------------------------------------------------------------------------*/ + +// Clauses of larger glue or larger size are considered less useful. +// +// We also follow the observations made by the Glucose team in their +// IJCAI'09 paper and keep all low glue clauses limited by +// 'options.keepglue' (typically '2'). +// +// In earlier versions we pre-computed a 64-bit sort key per clause and +// wrapped a pointer to the clause and the 64-bit sort key into a separate +// data structure for sorting. This was probably faster but awkward and +// so we moved back to a simpler scheme which also uses 'stable_sort' +// instead of 'rsort' below. Sorting here is not a hot-spot anyhow. + +struct reduce_less_useful { + bool operator() (const Clause *c, const Clause *d) const { + if (c->glue > d->glue) + return true; + if (c->glue < d->glue) + return false; + return c->size > d->size; + } +}; + +// This function implements the important reduction policy. It determines +// which redundant clauses are considered not useful and thus will be +// collected in a subsequent garbage collection phase. + +void Internal::mark_useless_redundant_clauses_as_garbage () { + + // We use a separate stack for sorting candidates for removal. This uses + // (slightly) more memory but has the advantage to keep the relative order + // in 'clauses' intact, which actually due to using stable sorting goes + // into the candidate selection (more recently learned clauses are kept if + // they otherwise have the same glue and size). + + vector stack; + const int tier1limit = tier1[false]; + const int tier2limit = max (tier1limit, tier2[false]); + + stack.reserve (stats.current.redundant); + + for (const auto &c : clauses) { + if (!c->redundant) + continue; // Keep irredundant. + if (c->garbage) + continue; // Skip already marked. + if (c->reason) + continue; // Need to keep reasons. + const unsigned used = c->used; + if (used) + c->used--; + if (c->glue <= tier1limit && used) + continue; + if (c->glue <= tier2limit && used >= max_used - 1) + continue; + if (c->hyper) { // Hyper binary and ternary resolvents + CADICAL_assert (c->size <= 3); // are only kept for one reduce round + if (!used) + mark_garbage (c); // unless + continue; // used recently. + } + stack.push_back (c); + } + + stable_sort (stack.begin (), stack.end (), reduce_less_useful ()); + size_t target = 1e-2 * opts.reducetarget * stack.size (); + + // This is defensive code, which I usually consider a bug, but here I am + // just not sure that using floating points in the line above is precise + // in all situations and instead of figuring that out, I just use this. + // + if (target > stack.size ()) + target = stack.size (); + + PHASE ("reduce", stats.reductions, "reducing %zd clauses %.0f%%", target, + percent (target, stats.current.redundant)); + + auto i = stack.begin (); + const auto t = i + target; + while (i != t) { + Clause *c = *i++; + LOG (c, "marking useless to be collected"); + mark_garbage (c); + stats.reduced++; + } + + lim.keptsize = lim.keptglue = 0; + + const auto end = stack.end (); + for (i = t; i != end; i++) { + Clause *c = *i; + LOG (c, "keeping"); + if (c->size > lim.keptsize) + lim.keptsize = c->size; + if (c->glue > lim.keptglue) + lim.keptglue = c->glue; + } + + erase_vector (stack); + + PHASE ("reduce", stats.reductions, "maximum kept size %d glue %d", + lim.keptsize, lim.keptglue); +} + +/*------------------------------------------------------------------------*/ + +// If chronological backtracking produces out-of-order assigned units, then +// it is necessary to completely propagate them at the root level in order +// to derive all implied units. Otherwise the blocking literals in +// 'flush_watches' are messed up and CADICAL_assertion 'FW1' fails. + +bool Internal::propagate_out_of_order_units () { + if (!level) + return true; + int oou = 0; + for (size_t i = control[1].trail; !oou && i < trail.size (); i++) { + const int lit = trail[i]; + CADICAL_assert (val (lit) > 0); + if (var (lit).level) + continue; + LOG ("found out-of-order assigned unit %d", oou); + oou = lit; + } + if (!oou) + return true; + CADICAL_assert (opts.chrono || external_prop); + backtrack (0); + if (propagate ()) + return true; + learn_empty_clause (); + return false; +} + +/*------------------------------------------------------------------------*/ + +// reduction is scheduled with reduceint, reducetarget and reduceopt. +// with reduceopt=1 the number of learnt clauses scale with +// sqrt of conflicts times reduceint +// the scaling is the same as with reduceopt=0 (the classical default) +// however, the constants are different. To avoid this (and get roughly the +// same behaviour with reduceopt=0 and reduceopt=1) we need to scale the +// interval, namely (reduceint^2/2) +// Lastly, reduceopt=2 just replaces sqrt conflicts with log conflicts. +// The learnt clauses should not be bigger than +// 1/reducetarget * reduceint * function (conflicts) +// for function being log if reduceint=2 an sqrt otherwise. +// This is however only the theoretical target and second chance for +// tier2 clauses and very long lifespan of tier1 clauses (through used flag) +// make this behave differently. +// reduceinit shifts the curve to the right, increasing the number of +// clauses in the solver. This impact will decrease over time. + +void Internal::reduce () { + START (reduce); + + stats.reductions++; + report ('.', 1); + + bool flush = flushing (); + if (flush) + stats.flush.count++; + + if (!propagate_out_of_order_units ()) + goto DONE; + + mark_satisfied_clauses_as_garbage (); + protect_reasons (); + if (flush) + mark_clauses_to_be_flushed (); + else + mark_useless_redundant_clauses_as_garbage (); + garbage_collection (); + + { + int64_t delta = opts.reduceint; + double factor = stats.reductions + 1; + if (opts.reduceopt == + 0) // adjust delta such this is the same as reduceopt=1 + delta = delta * delta / 2; + else if (opts.reduceopt == 1) { + // this is the same as reduceopt=0 if reduceint = sqrt (reduceint) = + // 17 + factor = sqrt ((double) stats.conflicts); + } else if (opts.reduceopt == 2) + // log scaling instead + factor = log ((double) stats.conflicts); + if (factor < 1) + factor = 1; + delta = delta * factor; + if (irredundant () > 1e5) { + delta *= log (irredundant () / 1e4) / log (10); + } + if (delta < 1) + delta = 1; + lim.reduce = stats.conflicts + delta; + PHASE ("reduce", stats.reductions, + "new reduce limit %" PRId64 " after %" PRId64 " conflicts", + lim.reduce, delta); + } + + if (flush) { + PHASE ("flush", stats.flush.count, "new flush increment %" PRId64 "", + inc.flush); + inc.flush *= opts.flushfactor; + lim.flush = stats.conflicts + inc.flush; + PHASE ("flush", stats.flush.count, "new flush limit %" PRId64 "", + lim.flush); + } + + last.reduce.conflicts = stats.conflicts; + +DONE: + + report (flush ? 'f' : '-'); + STOP (reduce); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_rephase.cpp b/src/sat/cadical/cadical_rephase.cpp new file mode 100644 index 000000000..3b2e62526 --- /dev/null +++ b/src/sat/cadical/cadical_rephase.cpp @@ -0,0 +1,342 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// We experimented with resetting and reinitializing the saved phase with +// many solvers. Actually RSAT had already such a scheme. Our newest +// version seems to be quite beneficial for satisfiable instances. In an +// arithmetic increasing interval in the number of conflicts we either use +// the original phase (set by the option 'phase'), its inverted value, flip +// the current phase, pick random phases, then pick the best since the last +// time a best phase was picked and finally also use local search to find +// phases which minimize the number of falsified clauses. During +// stabilization (see 'stabilizing' in 'restart.cpp' when 'stable' is true) +// we execute a different rephasing schedule. The same applies if local +// search is disabled. + +/*------------------------------------------------------------------------*/ + +bool Internal::rephasing () { + if (!opts.rephase) + return false; + if (opts.forcephase) + return false; + return stats.conflicts > lim.rephase; +} + +/*------------------------------------------------------------------------*/ + +// Pick the original default phase. + +char Internal::rephase_original () { + stats.rephased.original++; + signed char val = opts.phase ? 1 : -1; // original = initial + PHASE ("rephase", stats.rephased.total, "switching to original phase %d", + val); + for (auto idx : vars) + phases.saved[idx] = val; + return 'O'; +} + +// Pick the inverted original default phase. + +char Internal::rephase_inverted () { + stats.rephased.inverted++; + signed char val = opts.phase ? -1 : 1; // original = -initial + PHASE ("rephase", stats.rephased.total, + "switching to inverted original phase %d", val); + for (auto idx : vars) + phases.saved[idx] = val; + return 'I'; +} + +// Flip the current phase. + +char Internal::rephase_flipping () { + stats.rephased.flipped++; + PHASE ("rephase", stats.rephased.total, + "flipping all phases individually"); + for (auto idx : vars) + phases.saved[idx] *= -1; + return 'F'; +} + +// Complete random picking of phases. + +char Internal::rephase_random () { + stats.rephased.random++; + PHASE ("rephase", stats.rephased.total, "resetting all phases randomly"); + Random random (opts.seed); // global seed + random += stats.rephased.random; // different every time + for (auto idx : vars) + phases.saved[idx] = random.generate_bool () ? -1 : 1; + return '#'; +} + +// Best phases are those saved at the largest trail height without conflict. +// See code and comments in 'update_target_and_best' in 'backtrack.cpp' + +char Internal::rephase_best () { + stats.rephased.best++; + PHASE ("rephase", stats.rephased.total, + "overwriting saved phases by best phases"); + signed char val; + for (auto idx : vars) + if ((val = phases.best[idx])) + phases.saved[idx] = val; + return 'B'; +} + +// Trigger local search 'walk' in 'walk.cpp'. + +char Internal::rephase_walk () { + stats.rephased.walk++; + PHASE ("rephase", stats.rephased.total, + "starting local search to improve current phase"); + walk (); + return 'W'; +} + +/*------------------------------------------------------------------------*/ + +void Internal::rephase () { + + stats.rephased.total++; + PHASE ("rephase", stats.rephased.total, + "reached rephase limit %" PRId64 " after %" PRId64 " conflicts", + lim.rephase, stats.conflicts); + + // Report current 'target' and 'best' and then set 'rephased' below, which + // will trigger reporting the new 'target' and 'best' after updating it in + // the next 'update_target_and_best' called from the next 'backtrack'. + // + report ('~', 1); + + backtrack (); + clear_phases (phases.target); + target_assigned = 0; + + size_t count = lim.rephased[stable]++; + bool single; + char type; + + if (opts.stabilize && opts.stabilizeonly) + single = true; + else + single = !opts.stabilize; + + if (single && !opts.walk) { + // (inverted,best,flipping,best,random,best,original,best)^\omega + switch (count % 8) { + case 0: + type = rephase_inverted (); + break; + case 1: + type = rephase_best (); + break; + case 2: + type = rephase_flipping (); + break; + case 3: + type = rephase_best (); + break; + case 4: + type = rephase_random (); + break; + case 5: + type = rephase_best (); + break; + case 6: + type = rephase_original (); + break; + case 7: + type = rephase_best (); + break; + default: + type = 0; + break; + } + } else if (single && opts.walk) { + // (inverted,best,walk, + // flipping,best,walk, + // random,best,walk, + // original,best,walk)^\omega + switch (count % 12) { + case 0: + type = rephase_inverted (); + break; + case 1: + type = rephase_best (); + break; + case 2: + type = rephase_walk (); + break; + case 3: + type = rephase_flipping (); + break; + case 4: + type = rephase_best (); + break; + case 5: + type = rephase_walk (); + break; + case 6: + type = rephase_random (); + break; + case 7: + type = rephase_best (); + break; + case 8: + type = rephase_walk (); + break; + case 9: + type = rephase_original (); + break; + case 10: + type = rephase_best (); + break; + case 11: + type = rephase_walk (); + break; + default: + type = 0; + break; + } + } else if (stable && !opts.walk) { + // original,inverted,(best,original,best,inverted)^\omega + if (!count) + type = rephase_original (); + else if (count == 1) + type = rephase_inverted (); + else + switch ((count - 2) % 4) { + case 0: + type = rephase_best (); + break; + case 1: + type = rephase_original (); + break; + case 2: + type = rephase_best (); + break; + case 3: + type = rephase_inverted (); + break; + default: + type = 0; + break; + } + } else if (stable && opts.walk) { + // original,inverted,(best,walk,original,best,walk,inverted)^\omega + if (!count) + type = rephase_original (); + else if (count == 1) + type = rephase_inverted (); + else + switch ((count - 2) % 6) { + case 0: + type = rephase_best (); + break; + case 1: + type = rephase_walk (); + break; + case 2: + type = rephase_original (); + break; + case 3: + type = rephase_best (); + break; + case 4: + type = rephase_walk (); + break; + case 5: + type = rephase_inverted (); + break; + default: + type = 0; + break; + } + } else if (!stable && (!opts.walk || !opts.walknonstable)) { + // flipping,(random,best,flipping,best)^\omega + if (!count) + type = rephase_flipping (); + else + switch ((count - 1) % 4) { + case 0: + type = rephase_random (); + break; + case 1: + type = rephase_best (); + break; + case 2: + type = rephase_flipping (); + break; + case 3: + type = rephase_best (); + break; + default: + type = 0; + break; + } + } else { + CADICAL_assert (!stable && opts.walk && opts.walknonstable); + // flipping,(random,best,walk,flipping,best,walk)^\omega + if (!count) + type = rephase_flipping (); + else + switch ((count - 1) % 6) { + case 0: + type = rephase_random (); + break; + case 1: + type = rephase_best (); + break; + case 2: + type = rephase_walk (); + break; + case 3: + type = rephase_flipping (); + break; + case 4: + type = rephase_best (); + break; + case 5: + type = rephase_walk (); + break; + default: + type = 0; + break; + } + } + CADICAL_assert (type); + + int64_t delta = opts.rephaseint * (stats.rephased.total + 1); + lim.rephase = stats.conflicts + delta; + + PHASE ("rephase", stats.rephased.total, + "new rephase limit %" PRId64 " after %" PRId64 " conflicts", + lim.rephase, delta); + + // This will trigger to report the effect of this new set of phases at the + // 'backtrack' (actually 'update_target_and_best') after the next + // conflict, as well as resetting 'best_assigned' then to allow to compute + // a new "best" assignment at that point. + // + last.rephase.conflicts = stats.conflicts; + rephased = type; + + if (stable) + shuffle_scores (); + else + shuffle_queue (); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_report.cpp b/src/sat/cadical/cadical_report.cpp new file mode 100644 index 000000000..18271d17e --- /dev/null +++ b/src/sat/cadical/cadical_report.cpp @@ -0,0 +1,302 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +#ifndef CADICAL_QUIET + +/*------------------------------------------------------------------------*/ + +// Provide nicely formatted progress report messages while running through +// the 'report' function below. The code is so complex, because it should +// be easy to add and remove reporting of certain statistics, while still +// providing a nicely looking format with automatically aligned headers. + +/*------------------------------------------------------------------------*\ + +The 'reports' are shown as 'c ...' with '' as follows: + +i propagated learned unit clause +O backtracked after phases reset to original phase +F backtracked after flipping phases +# backtracked after randomly setting phases +B backtracked after resetting to best phases +W backtracked after local search improved phases +b blocked clause elimination +G before garbage collection +C after garbage collection +/ compacted internal literals and remapped external to internal +c covered clause elimination +d decomposed binary implication graph and substituted equivalent literals +2 removed duplicated binary clauses +e bounded variable elimination round +^ variable elimination bound increased +I variable instantiation +[ start of stable search phase +] end of stable search phase +{ start of unstable search phase +} end of unstable search phase +P preprocessing round (capital 'P') +L local search round +* start of solving without the need to restore clauses ++ start of solving before restoring clauses +r start of solving after restoring clauses +1 end of solving returns satisfiable +0 end of solving returns unsatisfiable +? end of solving due to interrupt +l lucky phase solving +p failed literal probing round (lower case 'p') +. before reducing redundant clauses +f flushed redundant clauses +- reduced redundant clauses +~ start of resetting phases +R restart +s subsumed clause removal round +3 ternary resolution round +t transition reduction of binary implication graph +u vivified tier1 clauses +v vivified tier2 clauses +x vivified tier3 clauses +w vivified irredundant clauses + +The order of the list follows the occurrences of 'report' in the source +files, i.e., obtained from "grep 'report (' *.cpp". Note that some of the +reports are only printed for higher verbosity level (for instance 'R'). + +\*------------------------------------------------------------------------*/ + +struct Report { + + const char *header; + char buffer[32]; + int pos; + + Report (const char *h, int precision, int min, double value); + Report () {} + + void print_header (char *line); +}; + +/*------------------------------------------------------------------------*/ + +void Report::print_header (char *line) { + int len = strlen (header); + for (int i = -1, j = pos - (len + 1) / 2 - 3; i < len; i++, j++) + line[j] = i < 0 ? ' ' : header[i]; +} + +Report::Report (const char *h, int precision, int min, double value) + : header (h) { + char fmt[32]; + if (precision < 0) + snprintf (fmt, sizeof fmt, "%%.%df", -precision - 1); + else + snprintf (fmt, sizeof fmt, "%%.%df", precision); + snprintf (buffer, sizeof buffer, fmt, value); + const int width = strlen (buffer); + if (precision < 0) + strcat (buffer, "%"); + if (width >= min) + return; + if (precision < 0) + snprintf (fmt, sizeof fmt, "%%%d.%df%%%%", min, -precision - 1); + else + snprintf (fmt, sizeof fmt, "%%%d.%df", min, precision); + snprintf (buffer, sizeof buffer, fmt, value); +} + +/*------------------------------------------------------------------------*/ + +// The following statistics are printed in columns, whenever 'report' is +// called. For instance 'reduce' with prefix '-' will call it. The other +// more interesting report is due to learning a unit, called iteration, with +// prefix 'i'. To add another statistics column, add a corresponding line +// here. If you want to report something else add 'report (..)' functions. + +#define TIME opts.reportsolve ? solve_time () : time () + +#define MB (current_resident_set_size () / (double) (1l << 20)) + +#define REMAINING (percent (active (), stats.variables_original)) + +#define TRAIL (percent (averages.current.trail.slow, max_var)) + +#define TARGET (percent (target_assigned, max_var)) + +#define BEST (percent (best_assigned, max_var)) + +#define REPORTS \ + /* HEADER, PRECISION, MIN, VALUE */ \ + REPORT ("seconds", 2, 5, TIME) \ + REPORT ("MB", 0, 2, MB) \ + REPORT ("level", 0, 2, averages.current.level) \ + REPORT ("reductions", 0, 1, stats.reductions) \ + REPORT ("restarts", 0, 3, stats.restarts) \ + REPORT ("rate", 0, 2, averages.current.decisions) \ + REPORT ("conflicts", 0, 4, stats.conflicts) \ + REPORT ("redundant", 0, 4, stats.current.redundant) \ + REPORT ("trail", -1, 2, TRAIL) \ + REPORT ("glue", 0, 1, averages.current.glue.slow) \ + REPORT ("irredundant", 0, 4, stats.current.irredundant) \ + REPORT ("variables", 0, 3, active ()) \ + REPORT ("remaining", -1, 2, REMAINING) + +// Note, keep an empty line before this line (because of '\')! + +#if 0 // ADDITIONAL STATISTICS TO REPORT + +REPORT("best", -1, 2, BEST) \ +REPORT("target", -1, 2, TARGET) \ +REPORT("maxvar", 0, 2, external->max_var) + +#endif + +static const int num_reports = // as compile time constant +#define REPORT(HEAD, PREC, MIN, EXPR) 1 + + REPORTS +#undef REPORT + 0; + +/*------------------------------------------------------------------------*/ + +void Internal::report (char type, int verbose) { + if (!opts.report) + return; +#ifdef LOGGING + if (!opts.log) +#endif + if (opts.quiet || (verbose > opts.verbose)) + return; + if (!reported) { + CADICAL_assert (!lim.report); + reported = true; + MSG ("%stime measured in %s time %s%s", tout.magenta_code (), + internal->opts.realtime ? "real" : "process", + internal->opts.reportsolve ? "in solving" : "since initialization", + tout.normal_code ()); + } + Report reports[num_reports]; + int n = 0; +#define REPORT(HEAD, PREC, MIN, EXPR) \ + CADICAL_assert (n < num_reports); \ + reports[n++] = Report (HEAD, PREC, MIN, (double) (EXPR)); + REPORTS +#undef REPORT + if (!lim.report) { + print_prefix (); + fputc ('\n', stdout); + int pos = 4; + for (int i = 0; i < n; i++) { + int len = strlen (reports[i].buffer); + reports[i].pos = pos + (len + 1) / 2; + pos += len + 1; + } + const int max_line = pos + 20, nrows = 3; + char *line = new char[max_line]; + for (int start = 0; start < nrows; start++) { + int i; + for (i = 0; i < max_line; i++) + line[i] = ' '; + for (i = start; i < n; i += nrows) + reports[i].print_header (line); + for (i = max_line - 1; line[i - 1] == ' '; i--) + ; + line[i] = 0; + print_prefix (); + tout.yellow (); + fputs (line, stdout); + tout.normal (); + fputc ('\n', stdout); + } + print_prefix (); + fputc ('\n', stdout); + delete[] line; + lim.report = 19; + } else + lim.report--; + print_prefix (); + switch (type) { + case '[': + case ']': + tout.magenta (true); + break; + case 's': + case 'b': + case 'c': + tout.green (false); + break; + case 'e': + tout.green (true); + break; + case 'p': + case '2': + case '3': + case 'u': + case 'v': + case 'w': + case 'x': + case 'f': + case '=': + tout.blue (false); + break; + case 't': + tout.cyan (false); + break; + case 'd': + tout.blue (true); + break; + case 'z': + case '!': + tout.cyan (true); + break; + case '-': + tout.normal (); + break; + case '/': + tout.yellow (true); + break; + case 'a': + case 'n': + tout.red (false); + break; + case '0': + case '1': + case '?': + case 'i': + tout.bold (); + break; + case 'L': + case 'P': + tout.bold (); + tout.underline (); + break; + default: + break; + } + fputc (type, stdout); + if (stable || type == ']') + tout.magenta (); + else if (type != 'L' && type != 'P') + tout.normal (); + for (int i = 0; i < n; i++) { + fputc (' ', stdout); + fputs (reports[i].buffer, stdout); + } + if (stable || type == 'L' || type == 'P' || type == ']') + tout.normal (); + fputc ('\n', stdout); + fflush (stdout); +} + +#else // ifndef CADICAL_QUIET + +void Internal::report (char, int) {} + +#endif + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_resources.cpp b/src/sat/cadical/cadical_resources.cpp new file mode 100644 index 000000000..9a2b421e5 --- /dev/null +++ b/src/sat/cadical/cadical_resources.cpp @@ -0,0 +1,166 @@ +#include "global.h" + +#include "internal.hpp" + +/*------------------------------------------------------------------------*/ + +// This is operating system specific code. We mostly develop on Linux and +// there it should be fine and mostly works out-of-the-box on MacOS too but +// Windows needs special treatment (as probably other operating systems +// too). + +extern "C" { + +#ifdef WIN32 + +#ifndef __WIN32_WINNT +#define __WIN32_WINNT 0x0600 +#endif + +// Clang-format would reorder the includes which breaks the Windows code +// as it expects 'windows.h' to be included first. So disable it here. + +// clang-format off + +#include +#include + +// clang-format on + +#else + +#include +#include +#include +#include + +#endif + +#include +} + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +#ifdef WIN32 + +double absolute_real_time () { + FILETIME f; + GetSystemTimeAsFileTime (&f); + ULARGE_INTEGER t; + t.LowPart = f.dwLowDateTime; + t.HighPart = f.dwHighDateTime; + double res = (__int64) t.QuadPart; + res *= 1e-7; + return res; +} + +double absolute_process_time () { + double res = 0; + FILETIME fc, fe, fu, fs; + if (GetProcessTimes (GetCurrentProcess (), &fc, &fe, &fu, &fs)) { + ULARGE_INTEGER u, s; + u.LowPart = fu.dwLowDateTime; + u.HighPart = fu.dwHighDateTime; + s.LowPart = fs.dwLowDateTime; + s.HighPart = fs.dwHighDateTime; + res = (__int64) u.QuadPart + (__int64) s.QuadPart; + res *= 1e-7; + } + return res; +} + +#else + +double absolute_real_time () { + struct timeval tv; + if (gettimeofday (&tv, 0)) + return 0; + return 1e-6 * tv.tv_usec + tv.tv_sec; +} + +// We use 'getrusage' for 'process_time' and 'maximum_resident_set_size' +// which is pretty standard on Unix but probably not available on Windows +// etc. For different variants of Unix not all fields are meaningful. + +double absolute_process_time () { + double res; + struct rusage u; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + res = u.ru_utime.tv_sec + 1e-6 * u.ru_utime.tv_usec; // user time + res += u.ru_stime.tv_sec + 1e-6 * u.ru_stime.tv_usec; // + system time + return res; +} + +#endif + +double Internal::real_time () const { + return absolute_real_time () - stats.time.real; +} + +double Internal::process_time () const { + return absolute_process_time () - stats.time.process; +} + +/*------------------------------------------------------------------------*/ + +#ifdef WIN32 + +uint64_t current_resident_set_size () { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo (GetCurrentProcess (), &pmc, sizeof (pmc))) { + return pmc.WorkingSetSize; + } else + return 0; +} + +uint64_t maximum_resident_set_size () { + PROCESS_MEMORY_COUNTERS pmc; + if (GetProcessMemoryInfo (GetCurrentProcess (), &pmc, sizeof (pmc))) { + return pmc.PeakWorkingSetSize; + } else + return 0; +} + +#else + +// This seems to work on Linux (man page says since Linux 2.6.32). + +uint64_t maximum_resident_set_size () { + struct rusage u; + if (getrusage (RUSAGE_SELF, &u)) + return 0; + return ((uint64_t) u.ru_maxrss) << 10; +} + +// Unfortunately 'getrusage' on Linux does not support current resident set +// size (the field 'ru_ixrss' is there but according to the man page +// 'unused'). Thus we fall back to use the '/proc' file system instead. So +// this is not portable at all and needs to be replaced on other systems +// The code would still compile though (assuming 'sysconf' and +// '_SC_PAGESIZE' are available). + +uint64_t current_resident_set_size () { + char path[64]; + snprintf (path, sizeof path, "/proc/%" PRId64 "/statm", + (int64_t) getpid ()); + FILE *file = fopen (path, "r"); + if (!file) + return 0; + uint64_t dummy, rss; + int scanned = fscanf (file, "%" PRIu64 " %" PRIu64 "", &dummy, &rss); + fclose (file); + return scanned == 2 ? rss * sysconf (_SC_PAGESIZE) : 0; +} + +#endif + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_restart.cpp b/src/sat/cadical/cadical_restart.cpp new file mode 100644 index 000000000..1c6dc44c8 --- /dev/null +++ b/src/sat/cadical/cadical_restart.cpp @@ -0,0 +1,165 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// As observed by Chanseok Oh and implemented in MapleSAT solvers too, +// various mostly satisfiable instances benefit from long quiet phases +// with less or almost no restarts. We implement this idea by prohibiting +// the Glucose style restart scheme in a geometric fashion, which is very +// similar to how originally restarts were scheduled in MiniSAT and earlier +// solvers. We start with say 1e3 = 1000 (opts.stabilizeinit) conflicts of +// Glucose restarts. Then in a "stabilizing" phase we disable these +// until 1e4 = 2000 conflicts (if 'opts.stabilizefactor' is '200' percent) +// have passed. After that we switch back to regular Glucose style restarts +// until again 2 times more conflicts than the previous limit are reached. +// Actually, in the latest version we still restarts during stabilization +// but only in a reluctant doubling scheme with a rather high interval. + +bool Internal::stabilizing () { + if (!opts.stabilize) + return false; + if (stable && opts.stabilizeonly) + return true; + if (!inc.stabilize) { + CADICAL_assert (!stable); + if (stats.conflicts <= lim.stabilize) + return false; + } else if (stats.ticks.search[stable] <= lim.stabilize) + return stable; + report (stable ? ']' : '}'); + if (stable) + STOP (stable); + else + STOP (unstable); + const int64_t delta_conflicts = + stats.conflicts - last.stabilize.conflicts; + const int64_t delta_ticks = + stats.ticks.search[stable] - last.stabilize.ticks; + const char *current_mode = stable ? "stable" : "unstable"; + const char *next_mode = stable ? "unstable" : "stable"; + PHASE ("stabilizing", stats.stabphases, + "reached %s stabilization limit %" PRId64 " after %" PRId64 + " conflicts and %" PRId64 " ticks at %" PRId64 + " conflicts and %" PRId64 " ticks", + current_mode, lim.stabilize, delta_conflicts, delta_ticks, + stats.conflicts, stats.ticks.search[stable]); + if (!inc.stabilize) + inc.stabilize = delta_ticks; + if (!inc.stabilize) // rare occurence in incremental calls requiring no + // ticks + inc.stabilize = 1; + + stable = !stable; // Switch!!!!! + + int64_t next_delta_ticks = inc.stabilize; + int64_t stabphases = stats.stabphases + 1; + next_delta_ticks *= stabphases * stabphases; + + lim.stabilize = stats.ticks.search[stable] + next_delta_ticks; + if (lim.stabilize <= stats.ticks.search[stable]) + lim.stabilize = stats.ticks.search[stable] + 1; + + if (stable) + stats.stabphases++; + + swap_averages (); + PHASE ("stabilizing", stats.stabphases, + "next %s stabilization limit %" PRId64 + " at ticks interval %" PRId64, + next_mode, lim.stabilize, next_delta_ticks); + report (stable ? '[' : '{'); + if (stable) + START (stable); + else + START (unstable); + return stable; +} + +// Restarts are scheduled by a variant of the Glucose scheme as presented +// in our POS'15 paper using exponential moving averages. There is a slow +// moving average of the average recent glucose level of learned clauses +// as well as a fast moving average of those glues. If the end of a base +// restart conflict interval has passed and the fast moving average is +// above a certain margin over the slow moving average then we restart. + +bool Internal::restarting () { + if (!opts.restart) + return false; + if ((size_t) level < assumptions.size () + 2) + return false; + if (stabilizing ()) + return reluctant; + if (stats.conflicts <= lim.restart) + return false; + double f = averages.current.glue.fast; + double margin = (100.0 + opts.restartmargin) / 100.0; + double s = averages.current.glue.slow, l = margin * s; + LOG ("EMA glue slow %.2f fast %.2f limit %.2f", s, f, l); + return l <= f; +} + +// This is Marijn's reuse trail idea. Instead of always backtracking to +// the top we figure out which decisions will be made again anyhow and +// only backtrack to the level of the last such decision or to the top if +// no such decision exists top (in which case we do not reuse any level). + +int Internal::reuse_trail () { + const int trivial_decisions = + assumptions.size () + // Plus 1 if the constraint is satisfied via implications of + // assumptions and a pseudo-decision level was introduced. + + !control[assumptions.size () + 1].decision; + if (!opts.restartreusetrail) + return trivial_decisions; + int next_decision = next_decision_variable (); + CADICAL_assert (1 <= next_decision); + int res = trivial_decisions; + if (use_scores ()) { + while (res < level) { + int decision = control[res + 1].decision; + if (decision && score_smaller (this) (abs (decision), next_decision)) + break; + res++; + } + } else { + int64_t limit = bumped (next_decision); + while (res < level) { + int decision = control[res + 1].decision; + if (decision && bumped (decision) < limit) + break; + res++; + } + } + int reused = res - trivial_decisions; + if (reused > 0) { + stats.reused++; + stats.reusedlevels += reused; + if (stable) + stats.reusedstable++; + } + return res; +} + +void Internal::restart () { + START (restart); + stats.restarts++; + stats.restartlevels += level; + if (stable) + stats.restartstable++; + LOG ("restart %" PRId64 "", stats.restarts); + backtrack (reuse_trail ()); + + lim.restart = stats.conflicts + opts.restartint; + LOG ("new restart limit at %" PRId64 " conflicts", lim.restart); + + report ('R', 2); + STOP (restart); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_restore.cpp b/src/sat/cadical/cadical_restore.cpp new file mode 100644 index 000000000..f9551fb5e --- /dev/null +++ b/src/sat/cadical/cadical_restore.cpp @@ -0,0 +1,273 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// In incremental solving after a first call to 'solve' has finished and +// before calling the internal 'solve' again incrementally we have to +// restore clauses which have the negation of a literal as a witness literal +// on the extension stack, which was added as original literal in a new +// clause or in an assumption. This procedure has to be applied +// recursively, i.e., the literals of restored clauses are treated in the +// same way as literals of a new original clause. +// +// To figure out whether literals are such witnesses we have a 'witness' +// bit for each external literal, which is set in 'block', 'elim', and +// 'decompose' if a clause is pushed on the extension stack. The witness +// bits are recomputed after restoring clauses. +// +// We further mark in the external solver newly internalized external +// literals in 'add' and 'assume' since the last call to 'solve' as tainted +// if they occur negated as a witness literal on the extension stack. Then +// we go through the extension stack and restore all clauses which have a +// tainted literal (and its negation a marked as witness). +// +// Since the API contract disallows to call 'val' and 'failed' in an +// 'UNKNOWN' state. We do not have to internalize literals there. +// +// In order to have tainted literals accepted by the internal solver they +// have to be active and thus we might need to 'reactivate' them before +// restoring clauses if they are inactive. In case they have completely +// been eliminated and removed from the internal solver in 'compact', then +// we just use a new internal variable. This is performed in 'internalize' +// during marking external literals as tainted. +// +// To check that this approach is correct the external solver can maintain a +// stack of original clauses and current assumptions both in terms of +// external literals. Whenever 'solve' determines that the current +// incremental call is satisfiable we check that the (extended) witness does +// satisfy the saved original clauses, as well as all the assumptions. To +// enable these checks set 'opts.check' as well as 'opts.checkwitness' and +// 'opts.checkassumptions' all to 'true'. The model based tester actually +// prefers to enable the 'opts.check' option and the other two are 'true' by +// default anyhow. +// +// See our SAT'19 paper [FazekasBiereScholl-SAT'19] for more details. + +/*------------------------------------------------------------------------*/ + +void External::restore_clause (const vector::const_iterator &begin, + const vector::const_iterator &end, + const int64_t id) { + LOG (begin, end, "restoring external clause[%" PRId64 "]", id); + CADICAL_assert (eclause.empty ()); + CADICAL_assert (id); + for (auto p = begin; p != end; p++) { + eclause.push_back (*p); + if (internal->proof && internal->lrat) { + const auto &elit = *p; + unsigned eidx = (elit > 0) + 2u * (unsigned) abs (elit); + CADICAL_assert ((size_t) eidx < ext_units.size ()); + const int64_t id = ext_units[eidx]; + bool added = ext_flags[abs (elit)]; + if (id && !added) { + ext_flags[abs (elit)] = true; + internal->lrat_chain.push_back (id); + } + } + int ilit = internalize (*p); + internal->add_original_lit (ilit), internal->stats.restoredlits++; + } + if (internal->proof && internal->lrat) { + for (const auto &elit : eclause) { + ext_flags[abs (elit)] = false; + } + } + internal->finish_added_clause_with_id (id, true); + eclause.clear (); + internal->stats.restored++; +} + +/*------------------------------------------------------------------------*/ + +void External::restore_clauses () { + + CADICAL_assert (internal->opts.restoreall == 2 || !tainted.empty ()); + + START (restore); + internal->stats.restorations++; + + struct { + int64_t weakened, satisfied, restored, removed; + } clauses; + memset (&clauses, 0, sizeof clauses); + + if (internal->opts.restoreall && tainted.empty ()) + PHASE ("restore", internal->stats.restorations, + "forced to restore all clauses"); + +#ifndef CADICAL_QUIET + { + unsigned numtainted = 0; + for (const auto b : tainted) + if (b) + numtainted++; + + PHASE ("restore", internal->stats.restorations, + "starting with %u tainted literals %.0f%%", numtainted, + percent (numtainted, 2u * max_var)); + } +#endif + + auto end_of_extension = extension.end (); + auto p = extension.begin (), q = p; + + // Go over all witness labelled clauses on the extension stack, restore + // those necessary, remove restored and flush satisfied clauses. + // + while (p != end_of_extension) { + + clauses.weakened++; + + CADICAL_assert (!*p); + const auto saved = q; // Save old start. + *q++ = *p++; // Copy zero '0'. + + // Copy witness part and try to find a tainted witness literal in it. + // + int tlit = 0; // Negation tainted. + int elit; + // + CADICAL_assert (p != end_of_extension); + // + while ((elit = *q++ = *p++)) { + + if (marked (tainted, -elit)) { + tlit = elit; + LOG ("negation of witness literal %d tainted", tlit); + } + + CADICAL_assert (p != end_of_extension); + } + + // now copy the id of the clause + const int64_t id = ((int64_t) (*p) << 32) + (int64_t) * (p + 1); + LOG ("id is %" PRId64, id); + *q++ = *p++; + *q++ = *p++; + CADICAL_assert (id); + CADICAL_assert (!*p); + *q++ = *p++; + + // Now find 'end_of_clause' (clause starts at 'p') and at the same time + // figure out whether the clause is actually root level satisfied. + // + int satisfied = 0; + auto end_of_clause = p; + while (end_of_clause != end_of_extension && (elit = *end_of_clause)) { + if (!satisfied && fixed (elit) > 0) + satisfied = elit; + end_of_clause++; + } + CADICAL_assert (id); + + // Do not apply our 'FLUSH' rule to remove satisfied (implied) clauses + // if the corresponding option is set simply by resetting 'satisfied'. + // + if (satisfied && !internal->opts.restoreflush) { + LOG (p, end_of_clause, "forced to not remove %d satisfied", + satisfied); + satisfied = 0; + } + + if (satisfied || tlit || internal->opts.restoreall) { + + if (satisfied) { + LOG (p, end_of_clause, + "flushing implied clause satisfied by %d from extension stack", + satisfied); + clauses.satisfied++; + } else { + restore_clause (p, end_of_clause, id); // Might taint literals. + clauses.restored++; + } + + clauses.removed++; + p = end_of_clause; + q = saved; + + } else { + + LOG (p, end_of_clause, "keeping clause on extension stack"); + + while (p != end_of_clause) // Copy clause too. + *q++ = *p++; + } + } + + extension.resize (q - extension.begin ()); + shrink_vector (extension); + +#ifndef CADICAL_QUIET + if (clauses.satisfied) + PHASE ("restore", internal->stats.restorations, + "removed %" PRId64 " satisfied %.0f%% of %" PRId64 + " weakened clauses", + clauses.satisfied, percent (clauses.satisfied, clauses.weakened), + clauses.weakened); + else + PHASE ("restore", internal->stats.restorations, + "no satisfied clause removed out of %" PRId64 + " weakened clauses", + clauses.weakened); + + if (clauses.restored) + PHASE ("restore", internal->stats.restorations, + "restored %" PRId64 " clauses %.0f%% out of %" PRId64 + " weakened clauses", + clauses.restored, percent (clauses.restored, clauses.weakened), + clauses.weakened); + else + PHASE ("restore", internal->stats.restorations, + "no clause restored out of %" PRId64 " weakened clauses", + clauses.weakened); + { + unsigned numtainted = 0; + for (const auto &b : tainted) + if (b) + numtainted++; + + PHASE ("restore", internal->stats.restorations, + "finishing with %u tainted literals %.0f%%", numtainted, + percent (numtainted, 2u * max_var)); + } + +#endif + LOG ("extension stack clean"); + tainted.clear (); + + // Finally recompute the witness bits. + // + witness.clear (); + const auto begin_of_extension = extension.begin (); + p = extension.end (); + while (p != begin_of_extension) { + while (*--p) + CADICAL_assert (p != begin_of_extension); + int elit; + CADICAL_assert (p != begin_of_extension); + --p; + CADICAL_assert (p != begin_of_extension); + CADICAL_assert (*p || *(p - 1)); + --p; + CADICAL_assert (p != begin_of_extension); + CADICAL_assert (!*p); + --p; + CADICAL_assert (p != begin_of_extension); + while ((elit = *--p)) { + mark (witness, elit); + CADICAL_assert (p != begin_of_extension); + } + } + + STOP (restore); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_score.cpp b/src/sat/cadical/cadical_score.cpp new file mode 100644 index 000000000..420357929 --- /dev/null +++ b/src/sat/cadical/cadical_score.cpp @@ -0,0 +1,57 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// This initializes variables on the binary 'scores' heap also with +// smallest variable index first (thus picked first) and larger indices at +// the end. +// +void Internal::init_scores (int old_max_var, int new_max_var) { + LOG ("initializing EVSIDS scores from %d to %d", old_max_var + 1, + new_max_var); + for (int i = old_max_var; i < new_max_var; i++) + scores.push_back (i + 1); +} + +// Shuffle the EVSIDS heap. + +void Internal::shuffle_scores () { + if (!opts.shuffle) + return; + if (!opts.shufflescores) + return; + CADICAL_assert (!level); + stats.shuffled++; + LOG ("shuffling scores"); + vector shuffle; + if (opts.shufflerandom) { + scores.erase (); + for (int idx = max_var; idx; idx--) + shuffle.push_back (idx); + Random random (opts.seed); // global seed + random += stats.shuffled; // different every time + for (int i = 0; i <= max_var - 2; i++) { + const int j = random.pick_int (i, max_var - 1); + swap (shuffle[i], shuffle[j]); + } + } else { + while (!scores.empty ()) { + int idx = scores.front (); + (void) scores.pop_front (); + shuffle.push_back (idx); + } + } + score_inc = 0; + for (const auto &idx : shuffle) { + stab[idx] = score_inc++; + scores.push_back (idx); + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_shrink.cpp b/src/sat/cadical/cadical_shrink.cpp new file mode 100644 index 000000000..201c220e7 --- /dev/null +++ b/src/sat/cadical/cadical_shrink.cpp @@ -0,0 +1,513 @@ +#include "global.h" + +#include "internal.hpp" +#include "reap.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::reset_shrinkable () { +#ifdef LOGGING + size_t reset = 0; +#endif + for (const auto &lit : shrinkable) { + LOG ("resetting lit %i", lit); + Flags &f = flags (lit); + CADICAL_assert (f.shrinkable); + f.shrinkable = false; +#ifdef LOGGING + ++reset; +#endif + } + LOG ("resetting %zu shrinkable variables", reset); +} + +void Internal::mark_shrinkable_as_removable ( + int blevel, std::vector::size_type minimized_start) { +#ifdef LOGGING + size_t marked = 0, reset = 0; +#endif +#ifndef CADICAL_NDEBUG + unsigned kept = 0, minireset = 0; + for (; minimized_start < minimized.size (); ++minimized_start) { + const int lit = minimized[minimized_start]; + Flags &f = flags (lit); + const Var &v = var (lit); + if (v.level == blevel) { + CADICAL_assert (!f.poison); + ++minireset; + } else + ++kept; + } + (void) kept; + (void) minireset; +#else + (void) blevel; + (void) minimized_start; +#endif + + for (const int lit : shrinkable) { + Flags &f = flags (lit); + CADICAL_assert (f.shrinkable); + CADICAL_assert (!f.poison); + f.shrinkable = false; +#ifdef LOGGING + ++reset; +#endif + if (f.removable) + continue; + f.removable = true; + minimized.push_back (lit); +#ifdef LOGGING + ++marked; +#endif + } + LOG ("resetting %zu shrinkable variables", reset); + LOG ("marked %zu removable variables", marked); +} + +int inline Internal::shrink_literal (int lit, int blevel, + unsigned max_trail) { + CADICAL_assert (val (lit) < 0); + + Flags &f = flags (lit); + Var &v = var (lit); + CADICAL_assert (v.level <= blevel); + + if (!v.level) { + LOG ("skipping root level assigned %d", (lit)); + return 0; + } + + if (v.reason == external_reason) { + CADICAL_assert (!opts.exteagerreasons); + v.reason = learn_external_reason_clause (-lit, 0, true); + if (!v.reason) { + CADICAL_assert (!v.level); + return 0; + } + } + CADICAL_assert (v.reason != external_reason); + if (f.shrinkable) { + LOG ("skipping already shrinkable literal %d", (lit)); + return 0; + } + + if (v.level < blevel) { + if (f.removable) { + LOG ("skipping removable thus shrinkable %d", (lit)); + return 0; + } + const bool always_minimize_on_lower_blevel = (opts.shrink > 2); + if (always_minimize_on_lower_blevel && minimize_literal (-lit, 1)) { + LOG ("minimized thus shrinkable %d", (lit)); + return 0; + } + LOG ("literal %d on lower blevel %u < %u not removable/shrinkable", + (lit), v.level, blevel); + return -1; + } + + LOG ("marking %d as shrinkable", lit); + f.shrinkable = true; + f.poison = false; + shrinkable.push_back (lit); + if (opts.shrinkreap) { + CADICAL_assert (max_trail < trail.size ()); + const unsigned dist = max_trail - v.trail; + reap.push (dist); + } + return 1; +} + +unsigned Internal::shrunken_block_uip ( + int uip, int blevel, std::vector::reverse_iterator &rbegin_block, + std::vector::reverse_iterator &rend_block, + std::vector::size_type minimized_start, const int uip0) { + CADICAL_assert (clause[0] == uip0); + + LOG ("UIP on level %u, uip: %i (replacing by %i)", blevel, uip, uip0); + CADICAL_assert (rend_block > rbegin_block); + CADICAL_assert (rend_block < clause.rend ()); + unsigned block_shrunken = 0; + *rbegin_block = -uip; + Var &v = var (-uip); + Level &l = control[v.level]; + l.seen.trail = v.trail; + l.seen.count = 1; + + Flags &f = flags (-uip); + if (!f.seen) { + analyzed.push_back (-uip); + f.seen = true; + } + + flags (-uip).keep = true; + for (auto p = rbegin_block + 1; p != rend_block; ++p) { + const int lit = *p; + if (lit == -uip0) + continue; + *p = uip0; + // if (lit == -uip) continue; + ++block_shrunken; + CADICAL_assert (clause[0] == uip0); + } + mark_shrinkable_as_removable (blevel, minimized_start); + CADICAL_assert (clause[0] == uip0); + return block_shrunken; +} + +void inline Internal::shrunken_block_no_uip ( + const std::vector::reverse_iterator &rbegin_block, + const std::vector::reverse_iterator &rend_block, + unsigned &block_minimized, const int uip0) { + STOP (shrink); + START (minimize); + CADICAL_assert (rend_block > rbegin_block); + LOG ("no UIP found, now minimizing"); + for (auto p = rbegin_block; p != rend_block; ++p) { + CADICAL_assert (p != clause.rend () - 1); + const int lit = *p; + if (opts.minimize && minimize_literal (-lit)) { + CADICAL_assert (!flags (lit).keep); + ++block_minimized; + *p = uip0; + } else { + flags (lit).keep = true; + CADICAL_assert (flags (lit).keep); + } + } + STOP (minimize); + START (shrink); +} + +void Internal::push_literals_of_block ( + const std::vector::reverse_iterator &rbegin_block, + const std::vector::reverse_iterator &rend_block, int blevel, + unsigned max_trail) { + CADICAL_assert (rbegin_block < rend_block); + for (auto p = rbegin_block; p != rend_block; ++p) { + CADICAL_assert (p != clause.rend () - 1); + CADICAL_assert (!flags (*p).keep); + const int lit = *p; + LOG ("pushing lit %i of blevel %i", lit, var (lit).level); +#ifndef CADICAL_NDEBUG + int tmp = +#endif + shrink_literal (lit, blevel, max_trail); + CADICAL_assert (tmp > 0); + } +} + +unsigned inline Internal::shrink_next (int blevel, unsigned &open, + unsigned &max_trail) { + const auto &t = &trail; + if (opts.shrinkreap) { + CADICAL_assert (!reap.empty ()); + const unsigned dist = reap.pop (); + --open; + CADICAL_assert (dist <= max_trail); + const unsigned pos = max_trail - dist; + CADICAL_assert (pos < t->size ()); + const int uip = (*t)[pos]; + CADICAL_assert (val (uip) > 0); + LOG ("trying to shrink literal %d at trail[%u] and level %d", uip, pos, + blevel); + return uip; + } else { + int uip; +#ifndef CADICAL_NDEBUG + unsigned init_max_trail = max_trail; +#endif + do { + CADICAL_assert (max_trail <= init_max_trail); + uip = (*t)[max_trail--]; + } while (!flags (uip).shrinkable); + --open; + LOG ("open is now %d, uip = %d, level %d", open, uip, blevel); + return uip; + } + (void) blevel; +} + +unsigned inline Internal::shrink_along_reason (int uip, int blevel, + bool resolve_large_clauses, + bool &failed_ptr, + unsigned max_trail) { + LOG ("shrinking along the reason of lit %i", uip); + unsigned open = 0; +#ifndef CADICAL_NDEBUG + const Flags &f = flags (uip); +#endif + const Var &v = var (uip); + + CADICAL_assert (f.shrinkable); + CADICAL_assert (v.level == blevel); + CADICAL_assert (v.reason); + + if (opts.minimizeticks) + stats.ticks.search[stable]++; + + if (resolve_large_clauses || v.reason->size == 2) { + const Clause &c = *v.reason; + LOG (v.reason, "resolving with reason"); + for (int lit : c) { + if (lit == uip) + continue; + CADICAL_assert (val (lit) < 0); + int tmp = shrink_literal (lit, blevel, max_trail); + if (tmp < 0) { + failed_ptr = true; + break; + } + if (tmp > 0) { + ++open; + } + } + } else { + failed_ptr = true; + } + return open; +} + +unsigned +Internal::shrink_block (std::vector::reverse_iterator &rbegin_lits, + std::vector::reverse_iterator &rend_block, + int blevel, unsigned &open, + unsigned &block_minimized, const int uip0, + unsigned max_trail) { + CADICAL_assert (shrinkable.empty ()); + CADICAL_assert (blevel <= this->level); + CADICAL_assert (open < clause.size ()); + CADICAL_assert (rbegin_lits >= clause.rbegin ()); + CADICAL_assert (rend_block < clause.rend ()); + CADICAL_assert (rbegin_lits < rend_block); + CADICAL_assert (opts.shrink); + +#ifdef LOGGING + + LOG ("trying to shrink %u literals on level %u", open, blevel); + + const auto &t = &trail; + + LOG ("maximum trail position %zd on level %u", t->size (), blevel); + if (opts.shrinkreap) + LOG ("shrinking up to %u", max_trail); +#endif + + const bool resolve_large_clauses = (opts.shrink > 1); + bool failed = false; + unsigned block_shrunken = 0; + std::vector::size_type minimized_start = minimized.size (); + int uip = uip0; + unsigned max_trail2 = max_trail; + + if (!failed) { + push_literals_of_block (rbegin_lits, rend_block, blevel, max_trail); + CADICAL_assert (!opts.shrinkreap || reap.size () == open); + + CADICAL_assert (open > 0); + while (!failed) { + CADICAL_assert (!opts.shrinkreap || reap.size () == open); + uip = shrink_next (blevel, open, max_trail); + if (open == 0) { + break; + } + open += shrink_along_reason (uip, blevel, resolve_large_clauses, + failed, max_trail2); + CADICAL_assert (open >= 1); + } + + if (!failed) + LOG ("shrinking found UIP %i on level %i (open: %d)", uip, blevel, + open); + else + LOG ("shrinking failed on level %i", blevel); + } + + if (failed) + reset_shrinkable (), shrunken_block_no_uip (rbegin_lits, rend_block, + block_minimized, uip0); + else + block_shrunken = shrunken_block_uip (uip, blevel, rbegin_lits, + rend_block, minimized_start, uip0); + + if (opts.shrinkreap) + reap.clear (); + shrinkable.clear (); + return block_shrunken; +} + +// Smaller level and trail. Comparing literals on their level is necessary +// for chronological backtracking, since trail order might in this case not +// respect level order. + +struct shrink_trail_negative_rank { + Internal *internal; + shrink_trail_negative_rank (Internal *s) : internal (s) {} + typedef uint64_t Type; + Type operator() (int a) { + Var &v = internal->var (a); + uint64_t res = v.level; + res <<= 32; + res |= v.trail; + return ~res; + } +}; + +struct shrink_trail_larger { + Internal *internal; + shrink_trail_larger (Internal *s) : internal (s) {} + bool operator() (const int &a, const int &b) const { + return shrink_trail_negative_rank (internal) (a) < + shrink_trail_negative_rank (internal) (b); + } +}; + +// Finds the beginning of the block (rend_block, non-included) ending at +// rend_block (included). Then tries to shrinks and minimizes literals the +// block +std::vector::reverse_iterator Internal::minimize_and_shrink_block ( + std::vector::reverse_iterator &rbegin_block, + unsigned &total_shrunken, unsigned &total_minimized, const int uip0) + +{ + LOG ("shrinking block"); + CADICAL_assert (rbegin_block < clause.rend () - 1); + int blevel; + unsigned open = 0; + unsigned max_trail; + + // find begining of block; + std::vector::reverse_iterator rend_block; + { + CADICAL_assert (rbegin_block <= clause.rend ()); + const int lit = *rbegin_block; + const int idx = vidx (lit); + blevel = vtab[idx].level; + max_trail = vtab[idx].trail; + LOG ("Block at level %i (first lit: %i)", blevel, lit); + + rend_block = rbegin_block; + bool finished; + do { + CADICAL_assert (rend_block < clause.rend () - 1); + const int lit = *(++rend_block); + const int idx = vidx (lit); + finished = (blevel != vtab[idx].level); + if (!finished && (unsigned) vtab[idx].trail > max_trail) + max_trail = vtab[idx].trail; + ++open; + LOG ( + "testing if lit %i is on the same level (of lit: %i, global: %i)", + lit, vtab[idx].level, blevel); + + } while (!finished); + } + CADICAL_assert (open > 0); + CADICAL_assert (open < clause.size ()); + CADICAL_assert (rbegin_block < clause.rend ()); + CADICAL_assert (rend_block < clause.rend ()); + + unsigned block_shrunken = 0, block_minimized = 0; + if (open < 2) { + flags (*rbegin_block).keep = true; + minimized.push_back (*rbegin_block); + } else + block_shrunken = shrink_block (rbegin_block, rend_block, blevel, open, + block_minimized, uip0, max_trail); + + LOG ("shrunken %u literals on level %u (including %u minimized)", + block_shrunken, blevel, block_minimized); + + total_shrunken += block_shrunken; + total_minimized += block_minimized; + + return rend_block; +} + +void Internal::shrink_and_minimize_clause () { + CADICAL_assert (opts.minimize || opts.shrink > 0); + LOG (clause, "shrink first UIP clause"); + + START (shrink); + external->check_learned_clause (); // check 1st UIP learned clause first + MSORT (opts.radixsortlim, clause.begin (), clause.end (), + shrink_trail_negative_rank (this), shrink_trail_larger (this)); + unsigned total_shrunken = 0; + unsigned total_minimized = 0; + + LOG (clause, "shrink first UIP clause (CADICAL_asserting lit: %i)", clause[0]); + + auto rend_lits = clause.rend () - 1; + auto rend_block = clause.rbegin (); + const int uip0 = clause[0]; + + // for direct LRAT we remember how the clause used to look + vector old_clause_lrat; + CADICAL_assert (minimize_chain.empty ()); + if (lrat) + for (auto &i : clause) + old_clause_lrat.push_back (i); + + while (rend_block != rend_lits) { + rend_block = minimize_and_shrink_block (rend_block, total_shrunken, + total_minimized, uip0); + } + + LOG (clause, + "post shrink pass (with uips, not removed) first UIP clause"); + LOG (old_clause_lrat, "(used for lratdirect) before shrink: clause"); +#if defined(LOGGING) || !defined(CADICAL_NDEBUG) + const unsigned old_size = clause.size (); +#endif + std::vector stack; + { + std::vector::size_type i = 1; + for (std::vector::size_type j = 1; j < clause.size (); ++j) { + CADICAL_assert (i <= j); + clause[i] = clause[j]; + if (lrat) { + CADICAL_assert (j < old_clause_lrat.size ()); + CADICAL_assert (mini_chain.empty ()); + if (clause[j] != old_clause_lrat[j]) { + calculate_minimize_chain (-old_clause_lrat[j], stack); + for (auto p : mini_chain) { + minimize_chain.push_back (p); + } + mini_chain.clear (); + } + } + if (clause[j] == uip0) { + continue; + } + CADICAL_assert (flags (clause[i]).keep); + ++i; + LOG ("keeping literal %i", clause[j]); + } + clause.resize (i); + } + CADICAL_assert (old_size == + (unsigned) clause.size () + total_shrunken + total_minimized); + LOG (clause, "after shrinking first UIP clause"); + LOG ("clause shrunken by %zd literals (including %u minimized)", + old_size - clause.size (), total_minimized); + + stats.shrunken += total_shrunken; + stats.minishrunken += total_minimized; + STOP (shrink); + + START (minimize); + clear_minimized_literals (); + for (auto p = minimize_chain.rbegin (); p != minimize_chain.rend (); + p++) { + lrat_chain.push_back (*p); + } + minimize_chain.clear (); + STOP (minimize); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_signal.cpp b/src/sat/cadical/cadical_signal.cpp new file mode 100644 index 000000000..099277f03 --- /dev/null +++ b/src/sat/cadical/cadical_signal.cpp @@ -0,0 +1,144 @@ +#include "global.h" + +#include "signal.hpp" +#include "cadical.hpp" +#include "resources.hpp" + +/*------------------------------------------------------------------------*/ + +#include +#include + +/*------------------------------------------------------------------------*/ + +#ifndef WIN32 +extern "C" { +#include +} +#endif + +ABC_NAMESPACE_IMPL_START + +/*------------------------------------------------------------------------*/ + +// Signal handlers for printing statistics even if solver is interrupted. + +namespace CaDiCaL { + +static volatile bool caught_signal = false; +static Handler *signal_handler; + +#ifndef WIN32 + +static volatile bool caught_alarm = false; +static volatile bool alarm_set = false; +static int alarm_time = -1; + +void Handler::catch_alarm () { catch_signal (SIGALRM); } + +#endif + +#define SIGNALS \ + SIGNAL (SIGABRT) \ + SIGNAL (SIGINT) \ + SIGNAL (SIGSEGV) \ + SIGNAL (SIGTERM) + +#define SIGNAL(SIG) static void (*SIG##_handler) (int); +SIGNALS +#undef SIGNAL + +#ifndef WIN32 + +static void (*SIGALRM_handler) (int); + +void Signal::reset_alarm () { + if (!alarm_set) + return; + (void) signal (SIGALRM, SIGALRM_handler); + SIGALRM_handler = 0; + caught_alarm = false; + alarm_set = false; + alarm_time = -1; +} + +#endif + +void Signal::reset () { + signal_handler = 0; +#define SIGNAL(SIG) \ + (void) signal (SIG, SIG##_handler); \ + SIG##_handler = 0; + SIGNALS +#undef SIGNAL +#ifndef WIN32 + reset_alarm (); +#endif + caught_signal = false; +} + +const char *Signal::name (int sig) { +#define SIGNAL(SIG) \ + if (sig == SIG) \ + return #SIG; + SIGNALS +#undef SIGNAL +#ifndef WIN32 + if (sig == SIGALRM) + return "SIGALRM"; +#endif + return "UNKNOWN"; +} + +// TODO printing is not reentrant and might lead to deadlock if the signal +// is raised during another print attempt (and locked IO is used). To avoid +// this we have to either run our own low-level printing routine here or in +// 'Message' or just dump those statistics somewhere else were we have +// exclusive access to. All these solutions are painful and not elegant. + +static void catch_signal (int sig) { +#ifndef WIN32 + if (sig == SIGALRM && absolute_real_time () >= alarm_time) { + if (!caught_alarm) { + caught_alarm = true; + if (signal_handler) + signal_handler->catch_alarm (); + } + Signal::reset_alarm (); + } else +#endif + { + if (!caught_signal) { + caught_signal = true; + if (signal_handler) + signal_handler->catch_signal (sig); + } + Signal::reset (); + ::raise (sig); + } +} + +void Signal::set (Handler *h) { + signal_handler = h; +#define SIGNAL(SIG) SIG##_handler = signal (SIG, catch_signal); + SIGNALS +#undef SIGNAL +} + +#ifndef WIN32 + +void Signal::alarm (int seconds) { + CADICAL_assert (seconds >= 0); + CADICAL_assert (!alarm_set); + CADICAL_assert (alarm_time < 0); + SIGALRM_handler = signal (SIGALRM, catch_signal); + alarm_set = true; + alarm_time = absolute_real_time () + seconds; + ::alarm (seconds); +} + +#endif + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_solution.cpp b/src/sat/cadical/cadical_solution.cpp new file mode 100644 index 000000000..1d7514e42 --- /dev/null +++ b/src/sat/cadical/cadical_solution.cpp @@ -0,0 +1,56 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Sam Buss suggested to debug the case where a solver incorrectly claims +// the formula to be unsatisfiable by checking every learned clause to be +// satisfied by a satisfying assignment. Thus the first inconsistent +// learned clause will be immediately flagged without the need to generate +// proof traces and perform forward proof checking. The incorrectly derived +// clause will raise an abort signal and thus allows to debug the issue with +// a symbolic debugger immediately. + +void External::check_solution_on_learned_clause () { + CADICAL_assert (solution); + for (const auto &lit : internal->clause) + if (sol (internal->externalize (lit)) == lit) + return; + fatal_message_start (); + fputs ("learned clause unsatisfied by solution:\n", stderr); + for (const auto &lit : internal->clause) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); +} + +void External::check_solution_on_shrunken_clause (Clause *c) { + CADICAL_assert (solution); + for (const auto &lit : *c) + if (sol (internal->externalize (lit)) == lit) + return; + fatal_message_start (); + for (const auto &lit : *c) + fprintf (stderr, "%d ", lit); + fputc ('0', stderr); + fatal_message_end (); +} + +void External::check_no_solution_after_learning_empty_clause () { + CADICAL_assert (solution); + FATAL ("learned empty clause but got solution"); +} + +void External::check_solution_on_learned_unit_clause (int unit) { + CADICAL_assert (solution); + if (sol (internal->externalize (unit)) == unit) + return; + FATAL ("learned unit %d contradicts solution", unit); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_solver.cpp b/src/sat/cadical/cadical_solver.cpp new file mode 100644 index 000000000..df051f4de --- /dev/null +++ b/src/sat/cadical/cadical_solver.cpp @@ -0,0 +1,1764 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// See corresponding header file 'cadical.hpp' (!) for more information. +// +// Again, to avoid confusion, note that, 'cadical.hpp' is the header file of +// this file 'solver.cpp', since we want to call the application and main +// file 'cadical.cpp', while at the same time using 'cadical.hpp' as the +// main header file of the library (and not 'solver.hpp'). + +/*------------------------------------------------------------------------*/ +#ifdef LOGGING + +// Needs to be kept in sync with the color schemes used in 'logging.cpp'. +// +#define api_code blue_code // API call color +#define log_code magenta_code // standard/default logging color +#define emph_code bright_magenta_code // emphasized logging color + +#endif +/*------------------------------------------------------------------------*/ + +// Log state transitions. + +#define STATE(S) \ + do { \ + CADICAL_assert (is_power_of_two (S)); \ + if (_state == S) \ + break; \ + _state = S; \ + LOG ("API enters state %s" #S "%s", tout.emph_code (), \ + tout.normal_code ()); \ + } while (0) + +void Solver::transition_to_steady_state () { + if (state () == CONFIGURING) { + LOG ("API leaves state %sCONFIGURING%s", tout.emph_code (), + tout.normal_code ()); + if (internal->opts.check && internal->opts.checkproof) { + internal->check (); + } + } else if (state () == SATISFIED) { + LOG ("API leaves state %sSATISFIED%s", tout.emph_code (), + tout.normal_code ()); + external->reset_assumptions (); + external->reset_concluded (); + external->reset_constraint (); + } else if (state () == UNSATISFIED) { + LOG ("API leaves state %sUNSATISFIED%s", tout.emph_code (), + tout.normal_code ()); + external->reset_assumptions (); + external->reset_concluded (); + external->reset_constraint (); + } else if (state() == INCONCLUSIVE) { + external->reset_assumptions (); + external->reset_concluded (); + external->reset_constraint (); + } + if (state () != STEADY) + STATE (STEADY); +} + +/*------------------------------------------------------------------------*/ +#ifdef LOGGING +/*------------------------------------------------------------------------*/ + +// The following logging code is useful for debugging mostly (or trying to +// understand what the solver is actually doing). It needs to be enabled +// during configuration using the '-l' option for './configure', which +// forces 'LOGGING' to be defined during compilation. This includes all the +// logging code, which then still needs to enabled during run-time with the +// '-l' or 'log' option. + +static void log_api_call (Internal *internal, const char *name, + const char *suffix) { + Logger::log (internal, "API call %s'%s ()'%s %s", tout.api_code (), name, + tout.log_code (), suffix); +} + +static void log_api_call (Internal *internal, const char *name, int arg, + const char *suffix) { + Logger::log (internal, "API call %s'%s (%d)'%s %s", tout.api_code (), + name, arg, tout.log_code (), suffix); +} + +static void log_api_call (Internal *internal, const char *name, + const char *arg, const char *suffix) { + Logger::log (internal, "API call %s'%s (\"%s\")'%s %s", tout.api_code (), + name, arg, tout.log_code (), suffix); +} + +static void log_api_call (Internal *internal, const char *name, + const char *a1, int a2, const char *s) { + Logger::log (internal, "API call %s'%s (\"%s\", %d)'%s %s", + tout.api_code (), name, a1, a2, tout.log_code (), s); +} + +/*------------------------------------------------------------------------*/ + +// We factored out API call begin/end logging and use overloaded functions. + +static void log_api_call_begin (Internal *internal, const char *name) { + Logger::log_empty_line (internal); + log_api_call (internal, name, "started"); +} + +static void log_api_call_begin (Internal *internal, const char *name, + int arg) { + Logger::log_empty_line (internal); + log_api_call (internal, name, arg, "started"); +} + +static void log_api_call_begin (Internal *internal, const char *name, + const char *arg) { + Logger::log_empty_line (internal); + log_api_call (internal, name, arg, "started"); +} + +static void log_api_call_begin (Internal *internal, const char *name, + const char *arg1, int arg2) { + Logger::log_empty_line (internal); + log_api_call (internal, name, arg1, arg2, "started"); +} + +/*------------------------------------------------------------------------*/ + +static void log_api_call_end (Internal *internal, const char *name) { + log_api_call (internal, name, "succeeded"); +} + +static void log_api_call_end (Internal *internal, const char *name, + int lit) { + log_api_call (internal, name, lit, "succeeded"); +} + +static void log_api_call_end (Internal *internal, const char *name, + const char *arg) { + Logger::log_empty_line (internal); + log_api_call (internal, name, arg, "succeeded"); +} + +static void log_api_call_end (Internal *internal, const char *name, + const char *arg, bool res) { + log_api_call (internal, name, arg, res ? "succeeded" : "failed"); +} + +static void log_api_call_end (Internal *internal, const char *name, + const char *arg, int val, bool res) { + log_api_call (internal, name, arg, val, res ? "succeeded" : "failed"); +} + +static void log_api_call_returns (Internal *internal, const char *name, + bool res) { + log_api_call (internal, name, res ? "returns 'true'" : "returns 'false'"); +} + +static void log_api_call_returns (Internal *internal, const char *name, + int res) { + char fmt[32]; + snprintf (fmt, sizeof fmt, "returns '%d'", res); + log_api_call (internal, name, fmt); +} + +static void log_api_call_returns (Internal *internal, const char *name, + int64_t res) { + char fmt[32]; + snprintf (fmt, sizeof fmt, "returns '%" PRId64 "'", res); + log_api_call (internal, name, fmt); +} + +static void log_api_call_returns (Internal *internal, const char *name, + int lit, int res) { + char fmt[32]; + snprintf (fmt, sizeof fmt, "returns '%d'", res); + log_api_call (internal, name, lit, fmt); +} + +static void log_api_call_returns (Internal *internal, const char *name, + const char *arg, bool res) { + log_api_call (internal, name, arg, + res ? "returns 'true'" : "returns 'false'"); +} + +static void log_api_call_returns (Internal *internal, const char *name, + int lit, bool res) { + log_api_call (internal, name, lit, + res ? "returns 'true'" : "returns 'false'"); +} + +static void log_api_call_returns (Internal *internal, const char *name, + const char *arg, const char *res) { + Logger::log (internal, "API call %s'%s (\"%s\")'%s returns '%s'", + tout.api_code (), name, arg, tout.log_code (), + res ? res : ""); +} + +static void log_api_call_returns (Internal *internal, const char *name, + const char *arg1, int arg2, + const char *res) { + Logger::log (internal, "API call %s'%s (\"%s\", %d)'%s returns '%s'", + tout.api_code (), name, arg1, arg2, tout.log_code (), + res ? res : ""); +} + +/*------------------------------------------------------------------------*/ + +#define LOG_API_CALL_BEGIN(...) \ + do { \ + if (!internal->opts.log) \ + break; \ + log_api_call_begin (internal, __VA_ARGS__); \ + } while (0) + +#define LOG_API_CALL_END(...) \ + do { \ + if (!internal->opts.log) \ + break; \ + log_api_call_end (internal, __VA_ARGS__); \ + } while (0) + +#define LOG_API_CALL_RETURNS(...) \ + do { \ + if (!internal->opts.log) \ + break; \ + log_api_call_returns (internal, __VA_ARGS__); \ + } while (0) + +/*------------------------------------------------------------------------*/ +#else // end of 'then' part of 'ifdef LOGGING' +/*------------------------------------------------------------------------*/ + +#define LOG_API_CALL_BEGIN(...) \ + do { \ + } while (0) +#define LOG_API_CALL_END(...) \ + do { \ + } while (0) +#define LOG_API_CALL_RETURNS(...) \ + do { \ + } while (0) + +/*------------------------------------------------------------------------*/ +#endif // end of 'else' part of 'ifdef LOGGING' +/*------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------*/ +#ifndef CADICAL_NTRACING +/*------------------------------------------------------------------------*/ + +#define TRACE(...) \ + do { \ + /*if ((this == 0)) break; */ /* gcc-12 produces warning */ \ + if ((internal == 0)) \ + break; \ + LOG_API_CALL_BEGIN (__VA_ARGS__); \ + if (!trace_api_file) \ + break; \ + trace_api_call (__VA_ARGS__); \ + } while (0) + +void Solver::trace_api_call (const char *s0) const { + CADICAL_assert (trace_api_file); + LOG ("TRACE %s", s0); + fprintf (trace_api_file, "%s\n", s0); + fflush (trace_api_file); +} + +void Solver::trace_api_call (const char *s0, int i1) const { + CADICAL_assert (trace_api_file); + LOG ("TRACE %s %d", s0, i1); + fprintf (trace_api_file, "%s %d\n", s0, i1); + fflush (trace_api_file); +} + +void Solver::trace_api_call (const char *s0, const char *s1) const { + CADICAL_assert (trace_api_file); + LOG ("TRACE %s %s", s0, s1); + fprintf (trace_api_file, "%s %s\n", s0, s1); + fflush (trace_api_file); +} + +void Solver::trace_api_call (const char *s0, const char *s1, int i2) const { + CADICAL_assert (trace_api_file); + LOG ("TRACE %s %s %d", s0, s1, i2); + fprintf (trace_api_file, "%s %s %d\n", s0, s1, i2); + fflush (trace_api_file); +} + +/*------------------------------------------------------------------------*/ + +// The global 'tracing_api_calls_through_environment_variable_method' flag +// is used to ensure that only one solver traces to a file. Otherwise the +// method to use an environment variable to point to the trace file is +// bogus, since those different solver instances would all write to the same +// file producing garbage. A more sophisticated solution would use a +// different mechanism to tell the solver to which file to trace to, but in +// our experience it is quite convenient to get traces out of applications +// which use the solver as library by just setting an environment variable +// without requiring to change any application code. +// +static bool tracing_api_calls_through_environment_variable_method; + +/*------------------------------------------------------------------------*/ +#else // CADICAL_NTRACING +/*------------------------------------------------------------------------*/ + +#define TRACE(...) \ + do { \ + } while (0) + +/*------------------------------------------------------------------------*/ +#endif +/*------------------------------------------------------------------------*/ + +static bool tracing_nb_lidrup_env_var_method = false; + +Solver::Solver () { + +#ifndef CADICAL_NTRACING + const char *path = getenv ("CADICAL_API_TRACE"); + if (!path) + path = getenv ("CADICALAPITRACE"); + if (path) { + if (tracing_api_calls_through_environment_variable_method) + FATAL ("can not trace API calls of two solver instances " + "using environment variable 'CADICAL_API_TRACE'"); + if (!(trace_api_file = fopen (path, "w"))) + FATAL ("failed to open file '%s' to trace API calls " + "using environment variable 'CADICAL_API_TRACE'", + path); + close_trace_api_file = true; + tracing_api_calls_through_environment_variable_method = true; + } else { + tracing_api_calls_through_environment_variable_method = false; + close_trace_api_file = false; + trace_api_file = 0; + } +#endif + + adding_clause = false; + adding_constraint = false; + _state = INITIALIZING; + internal = new Internal (); + DeferDeletePtr delete_internal (internal); + TRACE ("init"); + external = new External (internal); + DeferDeletePtr delete_external (external); + STATE (CONFIGURING); +#ifndef CADICAL_NTRACING + if (tracing_api_calls_through_environment_variable_method) + message ("tracing API calls to '%s'", path); +#endif + + const char *lidrup_path = getenv ("CADICAL_LIDRUP_TRACE"); + if (!lidrup_path) + lidrup_path = getenv ("CADICALLIDRUPTRACE"); + if (lidrup_path) { + + // if (tracing_nb_lidrup_env_var_method) + // FATAL ("can not trace LIDRUP of two solver instances " + // "using environment variable 'CADICAL_LIDRUP_TRACE'"); + // Here we use the solver interface to setup non-binary IDRUP tracing to + // the defined file. Options set by the user can and will overwrite + // these settings if neeed be. + set ("lidrup", 1); + set ("binary", 0); + trace_proof (lidrup_path); + tracing_nb_lidrup_env_var_method = true; + } else { + tracing_nb_lidrup_env_var_method = false; + } + + delete_internal.release (); + delete_external.release (); +} + +Solver::~Solver () { + + TRACE ("reset"); + REQUIRE_VALID_OR_SOLVING_STATE (); + STATE (DELETING); + + tracing_nb_lidrup_env_var_method = false; + +#ifdef LOGGING + // + // After deleting 'internal' logging does not work anymore. + // + bool logging = internal->opts.log; + int level = internal->level; + string prefix = internal->prefix; +#endif + + delete internal; + delete external; + +#ifndef CADICAL_NTRACING + if (close_trace_api_file) { + close_trace_api_file = false; + CADICAL_assert (trace_api_file); + CADICAL_assert (tracing_api_calls_through_environment_variable_method); + fclose (trace_api_file); + tracing_api_calls_through_environment_variable_method = false; + } +#endif + +#ifdef LOGGING + // + // Need to log success of this API call manually. + // + if (logging) { + printf ("%s%sLOG %s%d%s API call %s'reset ()'%s succeeded%s\n", + prefix.c_str (), tout.log_code (), tout.emph_code (), level, + tout.log_code (), tout.api_code (), tout.log_code (), + tout.normal_code ()); + fflush (stdout); + } +#endif +} + +/*------------------------------------------------------------------------*/ + +int Solver::vars () { + TRACE ("vars"); + REQUIRE_VALID_OR_SOLVING_STATE (); + int res = external->max_var; + LOG_API_CALL_RETURNS ("vars", res); + return res; +} + +void Solver::reserve (int min_max_var) { + TRACE ("reserve", min_max_var); + REQUIRE_VALID_STATE (); + transition_to_steady_state (); + external->reset_extended (); + external->init (min_max_var); + LOG_API_CALL_END ("reserve", min_max_var); +} + +int Solver::reserve_difference (int number_of_vars) { + TRACE ("reserve_difference", number_of_vars); + REQUIRE_VALID_STATE (); + transition_to_steady_state (); + external->reset_extended (); + int new_max_var = external->max_var + number_of_vars; + external->init (new_max_var); + LOG_API_CALL_END ("reserve_difference", number_of_vars); + return new_max_var; +} + +/*------------------------------------------------------------------------*/ +#ifndef CADICAL_NTRACING + +void Solver::trace_api_calls (FILE *file) { + LOG_API_CALL_BEGIN ("trace_api_calls"); + REQUIRE_VALID_STATE (); + REQUIRE (file != 0, "invalid zero file argument"); + REQUIRE (!tracing_api_calls_through_environment_variable_method, + "already tracing API calls " + "using environment variable 'CADICAL_API_TRACE'"); + REQUIRE (!trace_api_file, "called twice"); + trace_api_file = file; + LOG_API_CALL_END ("trace_api_calls"); + trace_api_call ("init"); +} + +#endif +/*------------------------------------------------------------------------*/ + +bool Solver::is_valid_option (const char *name) { + return Options::has (name); +} + +bool Solver::is_preprocessing_option (const char *name) { + return Options::is_preprocessing_option (name); +} + +bool Solver::is_valid_long_option (const char *arg) { + string name; + int tmp; + return Options::parse_long_option (arg, name, tmp); +} + +int Solver::get (const char *arg) { + REQUIRE_VALID_OR_SOLVING_STATE (); + return internal->opts.get (arg); +} + +bool Solver::set (const char *arg, int val) { + TRACE ("set", arg, val); + REQUIRE_VALID_STATE (); + if (strcmp (arg, "log") && strcmp (arg, "quiet") && + strcmp (arg, "report") && strcmp (arg, "verbose")) { + REQUIRE ( + state () == CONFIGURING, + "can only set option 'set (\"%s\", %d)' right after initialization", + arg, val); + } + bool res = internal->opts.set (arg, val); + LOG_API_CALL_END ("set", arg, val, res); + + return res; +} + +bool Solver::set_long_option (const char *arg) { + LOG_API_CALL_BEGIN ("set", arg); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only set option '%s' right after initialization", arg); + bool res; + if (arg[0] != '-' || arg[1] != '-') + res = false; + else { + int val; + string name; + res = Options::parse_long_option (arg, name, val); + if (res) + set (name.c_str (), val); + } + LOG_API_CALL_END ("set", arg, res); + return res; +} + +void Solver::optimize (int arg) { + LOG_API_CALL_BEGIN ("optimize", arg); + REQUIRE_VALID_STATE (); + internal->opts.optimize (arg); + LOG_API_CALL_END ("optimize", arg); +} + +bool Solver::limit (const char *arg, int val) { + TRACE ("limit", arg, val); + REQUIRE_VALID_STATE (); + bool res = internal->limit (arg, val); + LOG_API_CALL_END ("limit", arg, val, res); + return res; +} + +bool Solver::is_valid_limit (const char *arg) { + return Internal::is_valid_limit (arg); +} + +void Solver::prefix (const char *str) { + LOG_API_CALL_BEGIN ("prefix", str); + REQUIRE_VALID_OR_SOLVING_STATE (); + internal->prefix = str; + LOG_API_CALL_END ("prefix", str); +} + +bool Solver::is_valid_configuration (const char *name) { + return Config::has (name); +} + +bool Solver::configure (const char *name) { + TRACE ("configure", name); + LOG_API_CALL_BEGIN ("configure", name); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only set configuration '%s' right after initialization", + name); + bool res = Config::set (internal->opts, name); + LOG_API_CALL_END ("configure", name, res); + return res; +} + +/*===== IPASIR BEGIN =====================================================*/ + +void Solver::add (int lit) { + TRACE ("add", lit); + REQUIRE_VALID_STATE (); + if (lit) + REQUIRE_VALID_LIT (lit); + transition_to_steady_state (); + external->add (lit); + adding_clause = lit; + if (adding_clause) + STATE (ADDING); + else if (!adding_constraint) + STATE (STEADY); + LOG_API_CALL_END ("add", lit); +} + +void Solver::clause (int a) { + REQUIRE_VALID_LIT (a); + add (a), add (0); +} + +void Solver::clause (int a, int b) { + REQUIRE_VALID_LIT (a); + REQUIRE_VALID_LIT (b); + add (a), add (b), add (0); +} + +void Solver::clause (int a, int b, int c) { + REQUIRE_VALID_LIT (a); + REQUIRE_VALID_LIT (b); + REQUIRE_VALID_LIT (c); + add (a), add (b), add (c), add (0); +} + +void Solver::clause (int a, int b, int c, int d) { + REQUIRE_VALID_LIT (a); + REQUIRE_VALID_LIT (b); + REQUIRE_VALID_LIT (c); + REQUIRE_VALID_LIT (d); + add (a), add (b), add (c), add (d), add (0); +} + +void Solver::clause (int a, int b, int c, int d, int e) { + REQUIRE_VALID_LIT (a); + REQUIRE_VALID_LIT (b); + REQUIRE_VALID_LIT (c); + REQUIRE_VALID_LIT (d); + REQUIRE_VALID_LIT (e); + add (a), add (b), add (c), add (d), add (e), add (0); +} + +void Solver::clause (const int *lits, size_t size) { + REQUIRE (!size || lits, + "first argument 'lits' zero while second argument 'size' not"); + const int *end = lits + size; + for (const int *p = lits; p != end; p++) { + const int lit = *p; + REQUIRE_VALID_LIT (lit); + add (lit); + } + add (0); +} + +void Solver::clause (const std::vector &lits) { + for (auto lit : lits) { + REQUIRE_VALID_LIT (lit); + add (lit); + } + add (0); +} + +bool Solver::inconsistent () { return internal->unsat; } + +void Solver::constrain (int lit) { + TRACE ("constrain", lit); + REQUIRE_VALID_STATE (); + if (lit) + REQUIRE_VALID_LIT (lit); + transition_to_steady_state (); + external->constrain (lit); + adding_constraint = lit; + if (adding_constraint) + STATE (ADDING); + else if (!adding_clause) + STATE (STEADY); + LOG_API_CALL_END ("constrain", lit); +} + +void Solver::assume (int lit) { + TRACE ("assume", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + transition_to_steady_state (); + external->assume (lit); + LOG_API_CALL_END ("assume", lit); +} + +int Solver::lookahead () { + TRACE ("lookahead"); + REQUIRE_VALID_OR_SOLVING_STATE (); + int lit = external->lookahead (); + TRACE ("lookahead"); + return lit; +} + +Solver::CubesWithStatus Solver::generate_cubes (int depth, int min_depth) { + TRACE ("lookahead_cubes"); + REQUIRE_VALID_OR_SOLVING_STATE (); + auto cubes = external->generate_cubes (depth, min_depth); + TRACE ("lookahead_cubes"); + + CubesWithStatus cubes2; + cubes2.status = cubes.status; + cubes2.cubes = cubes.cubes; + return cubes2; +} + +void Solver::reset_assumptions () { + TRACE ("reset_assumptions"); + REQUIRE_VALID_STATE (); + transition_to_steady_state (); + external->reset_assumptions (); + external->reset_concluded (); + LOG_API_CALL_END ("reset_assumptions"); +} + +void Solver::reset_constraint () { + TRACE ("reset_constraint"); + REQUIRE_VALID_STATE (); + transition_to_steady_state (); + external->reset_constraint (); + external->reset_concluded (); + LOG_API_CALL_END ("reset_constraint"); +} + +/*------------------------------------------------------------------------*/ + +int Solver::propagate () { + TRACE ("propagate_assumptions"); + REQUIRE_VALID_STATE (); + transition_to_steady_state (); + const int res = external->propagate_assumptions (); + if (tracing_nb_lidrup_env_var_method) + flush_proof_trace (true); + LOG_API_CALL_RETURNS ("propagate_assumptions", res); + if (res == 10) + STATE (SATISFIED); + else if (res == 20) + STATE (UNSATISFIED); + else + STATE (INCONCLUSIVE); + return res; +} + +void Solver::implied (std::vector &entrailed) { + TRACE ("implied"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == INCONCLUSIVE, + "can only get implied literals only in unknown state"); + external->conclude_unknown (); + external->implied (entrailed); + if (tracing_nb_lidrup_env_var_method) + flush_proof_trace (true); + LOG_API_CALL_RETURNS ("implied", (int) entrailed.size ()); +} + +/*------------------------------------------------------------------------*/ + +int Solver::call_external_solve_and_check_results (bool preprocess_only) { + transition_to_steady_state (); + CADICAL_assert (state () & READY); + STATE (SOLVING); + const int res = external->solve (preprocess_only); + if (res == 10) + STATE (SATISFIED); + else if (res == 20) + STATE (UNSATISFIED); + else + STATE (INCONCLUSIVE); +#if 0 // EXPENSIVE ALTERNATIVE ASSUMPTION CHECKING + // This checks that the set of failed assumptions form a core using the + // external 'copy (...)' function to copy the solver, which can be trusted + // less, since it involves copying the extension stack too. The + // 'External::check_assumptions_failing' is a better alternative and can + // be enabled by options too. We keep this code though to have an + // alternative failed assumption checking available for debugging. + // + if (res == 20 && !external->assumptions.empty ()) { + Solver checker; + // checking restored clauses does not work (because the clauses are not added) + checker.set("checkproof", 1); + checker.set("lrat", 0); + checker.prefix ("checker "); + copy (checker); + checker.set("log", 1); + for (const auto & lit : external->assumptions) + if (failed (lit)) + checker.add (lit), checker.add (0); + if (checker.solve () != 20) + FATAL ("copying assumption checker failed"); + } +#endif + if (!res) { + external->reset_assumptions (); + external->reset_constraint (); + external->reset_concluded (); + } + return res; +} + +int Solver::solve () { + TRACE ("solve"); + REQUIRE_READY_STATE (); + const int res = call_external_solve_and_check_results (false); + LOG_API_CALL_RETURNS ("solve", res); + if (tracing_nb_lidrup_env_var_method) + flush_proof_trace (true); + return res; +} + +int Solver::simplify (int rounds) { + TRACE ("simplify", rounds); + REQUIRE_READY_STATE (); + REQUIRE (rounds >= 0, "negative number of simplification rounds '%d'", + rounds); + internal->limit ("preprocessing", rounds); + const int res = call_external_solve_and_check_results (true); + LOG_API_CALL_RETURNS ("simplify", rounds, res); + return res; +} + +/*------------------------------------------------------------------------*/ + +int Solver::val (int lit) { + TRACE ("val", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + REQUIRE (state () == SATISFIED, "can only get value in satisfied state"); + if (!external->extended) + external->extend (); + external->conclude_sat (); + int res = external->ival (lit); + LOG_API_CALL_RETURNS ("val", lit, res); + CADICAL_assert (state () == SATISFIED); + CADICAL_assert (res == lit || res == -lit); + return res; +} + +bool Solver::flip (int lit) { + TRACE ("flip", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + REQUIRE (state () == SATISFIED, "can only flip value in satisfied state"); + REQUIRE (!external->propagator, + "can only flip when no external propagator is present"); + bool res = external->flip (lit); + LOG_API_CALL_RETURNS ("flip", lit, res); + CADICAL_assert (state () == SATISFIED); + return res; +} + +bool Solver::flippable (int lit) { + TRACE ("flippable", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + REQUIRE (state () == SATISFIED, "can only flip value in satisfied state"); + REQUIRE (!external->propagator, + "can only flip when no external propagator is present"); + bool res = external->flippable (lit); + LOG_API_CALL_RETURNS ("flippable", lit, res); + CADICAL_assert (state () == SATISFIED); + return res; +} + +bool Solver::failed (int lit) { + TRACE ("failed", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + REQUIRE (state () == UNSATISFIED, + "can only get failed assumptions in unsatisfied state"); + bool res = external->failed (lit); + LOG_API_CALL_RETURNS ("failed", lit, res); + CADICAL_assert (state () == UNSATISFIED); + return res; +} + +bool Solver::constraint_failed () { + TRACE ("constraint_failed"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == UNSATISFIED, + "can only determine if constraint failed in unsatisfied state"); + bool res = external->failed_constraint (); + LOG_API_CALL_RETURNS ("constraint_failed", res); + CADICAL_assert (state () == UNSATISFIED); + return res; +} + +int Solver::fixed (int lit) const { + TRACE ("fixed", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + int res = external->fixed (lit); + LOG_API_CALL_RETURNS ("fixed", lit, res); + return res; +} + +void Solver::phase (int lit) { + TRACE ("phase", lit); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (lit); + external->phase (lit); + LOG_API_CALL_END ("phase", lit); +} + +void Solver::unphase (int lit) { + TRACE ("unphase", lit); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (lit); + external->unphase (lit); + LOG_API_CALL_END ("unphase", lit); +} + +/*------------------------------------------------------------------------*/ + +void Solver::terminate () { + LOG_API_CALL_BEGIN ("terminate"); + REQUIRE_VALID_OR_SOLVING_STATE (); + external->terminate (); + LOG_API_CALL_END ("terminate"); +} + +void Solver::connect_terminator (Terminator *terminator) { + LOG_API_CALL_BEGIN ("connect_terminator"); + REQUIRE_VALID_STATE (); + REQUIRE (terminator, "can not connect zero terminator"); +#ifdef LOGGING + if (external->terminator) + LOG ("connecting new terminator (disconnecting previous one)"); + else + LOG ("connecting new terminator (no previous one)"); +#endif + external->terminator = terminator; + LOG_API_CALL_END ("connect_terminator"); +} + +void Solver::disconnect_terminator () { + LOG_API_CALL_BEGIN ("disconnect_terminator"); + REQUIRE_VALID_STATE (); +#ifdef LOGGING + if (external->terminator) + LOG ("disconnecting previous terminator"); + else + LOG ("ignoring to disconnect terminator (no previous one)"); +#endif + external->terminator = 0; + LOG_API_CALL_END ("disconnect_terminator"); +} + +/*------------------------------------------------------------------------*/ + +void Solver::connect_learner (Learner *learner) { + LOG_API_CALL_BEGIN ("connect_learner"); + REQUIRE_VALID_STATE (); + REQUIRE (learner, "can not connect zero learner"); +#ifdef LOGGING + if (external->learner) + LOG ("connecting new learner (disconnecting previous one)"); + else + LOG ("connecting new learner (no previous one)"); +#endif + external->learner = learner; + LOG_API_CALL_END ("connect_learner"); +} + +void Solver::disconnect_learner () { + LOG_API_CALL_BEGIN ("disconnect_learner"); + REQUIRE_VALID_STATE (); +#ifdef LOGGING + if (external->learner) + LOG ("disconnecting previous learner"); + else + LOG ("ignoring to disconnect learner (no previous one)"); +#endif + external->learner = 0; + LOG_API_CALL_END ("disconnect_learner"); +} + +/*===== IPASIR END =======================================================*/ + +void Solver::connect_fixed_listener ( + FixedAssignmentListener *fixed_listener) { + LOG_API_CALL_BEGIN ("connect_fixed_listener"); + REQUIRE_VALID_STATE (); + REQUIRE (fixed_listener, "can not connect zero fixed listener"); + +#ifdef LOGGING + if (external->fixed_listener) + LOG ("connecting new listener of fixed assignments (disconnecting " + "previous one)"); + else + LOG ("connecting new listener of fixed assigments (no previous one)"); +#endif + if (external->fixed_listener) + disconnect_fixed_listener (); + external->fixed_listener = fixed_listener; + // Listeners are treated as real-time listeners, thus previously found + // fixed assignments are not sent out (would be rather expensive to + // recover it retrospect, see external_propagate.cpp/get_fixed_literals () + // function). + LOG_API_CALL_END ("connect_fixed_listener"); +} + +void Solver::disconnect_fixed_listener () { + LOG_API_CALL_BEGIN ("disconnect_fixed_listener"); + REQUIRE_VALID_STATE (); +#ifdef LOGGING + if (external->fixed_listener) + LOG ("disconnecting previous listener of fixed assignments"); + else + LOG ("ignoring to disconnect listener of fixed assignments (no " + "previous one)"); +#endif + external->fixed_listener = 0; + LOG_API_CALL_END ("disconnect_fixed_listener"); +} + +/*===== IPASIR-UP BEGIN ==================================================*/ + +void Solver::connect_external_propagator (ExternalPropagator *propagator) { + LOG_API_CALL_BEGIN ("connect_external_propagator"); + REQUIRE_VALID_STATE (); + REQUIRE (propagator, "can not connect zero propagator"); +#ifdef LOGGING + if (external->propagator) + LOG ("connecting new external propagator (disconnecting previous one)"); + else + LOG ("connecting new external propagator (no previous one)"); +#endif + if (external->propagator) + disconnect_external_propagator (); + + external->propagator = propagator; + internal->connect_propagator (); + internal->external_prop = true; + internal->external_prop_is_lazy = propagator->is_lazy; + LOG_API_CALL_END ("connect_external_propagator"); +} + +void Solver::disconnect_external_propagator () { + LOG_API_CALL_BEGIN ("disconnect_external_propagator"); + REQUIRE_VALID_STATE (); + +#ifdef LOGGING + if (external->propagator) + LOG ("disconnecting previous external propagator"); + else + LOG ("ignoring to disconnect external propagator (no previous one)"); +#endif + if (external->propagator) + external->reset_observed_vars (); + + external->propagator = 0; + internal->set_tainted_literal (); + internal->external_prop = false; + internal->external_prop_is_lazy = true; + LOG_API_CALL_END ("disconnect_external_propagator"); +} + +void Solver::add_observed_var (int idx) { + TRACE ("observe", idx); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (idx); + external->add_observed_var (idx); + LOG_API_CALL_END ("observe", idx); +} + +void Solver::remove_observed_var (int idx) { + TRACE ("unobserve", idx); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (idx); + external->remove_observed_var (idx); + LOG_API_CALL_END ("unobserve", idx); +} + +void Solver::reset_observed_vars () { + TRACE ("reset_observed_vars"); + REQUIRE_VALID_OR_SOLVING_STATE (); + external->reset_observed_vars (); + LOG_API_CALL_END ("reset_observed_vars"); +} + +/*===== IPASIR-UP END ====================================================*/ + +int Solver::active () const { + TRACE ("active"); + REQUIRE_VALID_STATE (); + int res = internal->active (); + LOG_API_CALL_RETURNS ("active", res); + return res; +} + +int64_t Solver::redundant () const { + TRACE ("redundant"); + REQUIRE_VALID_STATE (); + int64_t res = internal->redundant (); + LOG_API_CALL_RETURNS ("redundant", res); + return res; +} + +int64_t Solver::irredundant () const { + TRACE ("irredundant"); + REQUIRE_VALID_STATE (); + int64_t res = internal->irredundant (); + LOG_API_CALL_RETURNS ("irredundant", res); + return res; +} + +/*------------------------------------------------------------------------*/ + +void Solver::freeze (int lit) { + TRACE ("freeze", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + external->freeze (lit); + LOG_API_CALL_END ("freeze", lit); +} + +void Solver::melt (int lit) { + TRACE ("melt", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + REQUIRE (external->frozen (lit), + "can not melt completely melted literal '%d'", lit); + external->melt (lit); + LOG_API_CALL_END ("melt", lit); +} + +bool Solver::frozen (int lit) const { + TRACE ("frozen", lit); + REQUIRE_VALID_STATE (); + REQUIRE_VALID_LIT (lit); + bool res = external->frozen (lit); + LOG_API_CALL_RETURNS ("frozen", lit, res); + return res; +} + +/*------------------------------------------------------------------------*/ + +bool Solver::trace_proof (FILE *external_file, const char *name) { + TRACE ("trace_proof", name); + REQUIRE_VALID_STATE (); + REQUIRE ( + state () == CONFIGURING, + "can only start proof tracing to '%s' right after initialization", + name); + File *internal_file = File::write (internal, external_file, name); + CADICAL_assert (internal_file); + internal->trace (internal_file); + LOG_API_CALL_RETURNS ("trace_proof", name, true); + return true; +} + +bool Solver::trace_proof (const char *path) { + TRACE ("trace_proof", path); + REQUIRE_VALID_STATE (); + REQUIRE ( + state () == CONFIGURING, + "can only start proof tracing to '%s' right after initialization", + path); + File *internal_file = File::write (internal, path); + bool res = (internal_file != 0); + internal->trace (internal_file); + LOG_API_CALL_RETURNS ("trace_proof", path, res); + return res; +} + +void Solver::flush_proof_trace (bool print_statistics_unless_quiet) { + TRACE ("flush_proof_trace"); + REQUIRE_VALID_STATE (); + REQUIRE (!internal->file_tracers.empty (), "proof is not traced"); + REQUIRE (!internal->file_tracers.back ()->closed (), + "proof trace already closed"); + internal->flush_trace (print_statistics_unless_quiet); + LOG_API_CALL_END ("flush_proof_trace"); +} + +void Solver::close_proof_trace (bool print_statistics_unless_quiet) { + TRACE ("close_proof_trace"); + REQUIRE_VALID_STATE (); + REQUIRE (!internal->file_tracers.empty (), "proof is not traced"); + REQUIRE (!internal->file_tracers.back ()->closed (), + "proof trace already closed"); + internal->close_trace (print_statistics_unless_quiet); + LOG_API_CALL_END ("close_proof_trace"); +} + +/*------------------------------------------------------------------------*/ + +void Solver::connect_proof_tracer (Tracer *tracer, bool antecedents, + bool finalize_clauses) { + LOG_API_CALL_BEGIN ("connect proof tracer"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only start proof tracing to right after initialization"); + REQUIRE (tracer, "can not connect zero tracer"); + internal->connect_proof_tracer (tracer, antecedents, finalize_clauses); + LOG_API_CALL_END ("connect proof tracer"); +} + +void Solver::connect_proof_tracer (InternalTracer *tracer, bool antecedents, + bool finalize_clauses) { + LOG_API_CALL_BEGIN ("connect proof tracer"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only start proof tracing to right after initialization"); + REQUIRE (tracer, "can not connect zero tracer"); + internal->connect_proof_tracer (tracer, antecedents, finalize_clauses); + LOG_API_CALL_END ("connect proof tracer"); +} + +void Solver::connect_proof_tracer (StatTracer *tracer, bool antecedents, + bool finalize_clauses) { + LOG_API_CALL_BEGIN ("connect proof tracer with stats"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only start proof tracing to right after initialization"); + REQUIRE (tracer, "can not connect zero tracer"); + internal->connect_proof_tracer (tracer, antecedents, finalize_clauses); + LOG_API_CALL_END ("connect proof tracer with stats"); +} + +void Solver::connect_proof_tracer (FileTracer *tracer, bool antecedents, + bool finalize_clauses) { + LOG_API_CALL_BEGIN ("connect proof tracer with file"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only start proof tracing right after initialization"); + REQUIRE (tracer, "can not connect zero tracer"); + internal->connect_proof_tracer (tracer, antecedents, finalize_clauses); + LOG_API_CALL_END ("connect proof tracer with file"); +} + +bool Solver::disconnect_proof_tracer (Tracer *tracer) { + LOG_API_CALL_BEGIN ("disconnect proof tracer"); + REQUIRE_VALID_STATE (); + REQUIRE (tracer, "can not disconnect zero tracer"); + bool res = internal->disconnect_proof_tracer (tracer); + LOG_API_CALL_RETURNS ("connect proof tracer", res); + return res; +} + +bool Solver::disconnect_proof_tracer (StatTracer *tracer) { + LOG_API_CALL_BEGIN ("disconnect proof tracer"); + REQUIRE_VALID_STATE (); + REQUIRE (tracer, "can not disconnect zero tracer"); + bool res = internal->disconnect_proof_tracer (tracer); + LOG_API_CALL_RETURNS ("disconnect proof tracer", res); + return res; +} + +bool Solver::disconnect_proof_tracer (FileTracer *tracer) { + LOG_API_CALL_BEGIN ("disconnect proof tracer"); + REQUIRE_VALID_STATE (); + REQUIRE (tracer, "can not disconnect zero tracer"); + bool res = internal->disconnect_proof_tracer (tracer); + LOG_API_CALL_RETURNS ("disconnect proof tracer", res); + return res; +} + +/*------------------------------------------------------------------------*/ + +void Solver::conclude () { + TRACE ("conclude"); + REQUIRE_VALID_STATE (); + REQUIRE (state () == UNSATISFIED || state () == SATISFIED || + state () == INCONCLUSIVE, + "can only conclude in satisfied, unsatisfied or inconclusive state"); + if (state () == UNSATISFIED) + internal->conclude_unsat (); + else if (state () == SATISFIED) + external->conclude_sat (); + else if (state () == INCONCLUSIVE) + external->conclude_unknown (); + CADICAL_assert (state () == UNSATISFIED || state () == SATISFIED || + state () == INCONCLUSIVE); + LOG_API_CALL_END ("conclude"); +} + +/*------------------------------------------------------------------------*/ + +void Solver::build (FILE *file, const char *prefix) { + + CADICAL_assert (file == stdout || file == stderr); + + Terminal *terminal; + + if (file == stdout) + terminal = &tout; + else if (file == stderr) + terminal = &terr; + else + terminal = 0; + + const char *v = CaDiCaL::version (); + const char *i = identifier (); + const char *c = compiler (); + const char *b = date (); + const char *f = flags (); + + CADICAL_assert (v); + + fputs (prefix, file); + if (terminal) + terminal->magenta (); + fputs ("Version ", file); + if (terminal) + terminal->normal (); + fputs (v, file); + if (i) { + if (terminal) + terminal->magenta (); + fputc (' ', file); + fputs (i, file); + if (terminal) + terminal->normal (); + } + fputc ('\n', file); + + if (c) { + fputs (prefix, file); + if (terminal) + terminal->magenta (); + fputs (c, file); + if (f) { + fputc (' ', file); + fputs (f, file); + } + if (terminal) + terminal->normal (); + fputc ('\n', file); + } + + if (b) { + fputs (prefix, file); + if (terminal) + terminal->magenta (); + fputs (b, file); + if (terminal) + terminal->normal (); + fputc ('\n', file); + } + + fflush (file); +} + +const char *Solver::version () { return CaDiCaL::version (); } + +const char *Solver::signature () { return CaDiCaL::signature (); } + +void Solver::options () { + REQUIRE_VALID_STATE (); + internal->opts.print (); +} + +void Solver::usage () { Options::usage (); } + +void Solver::configurations () { Config::usage (); } + +void Solver::statistics () { + if (state () == DELETING) + return; + TRACE ("stats"); + REQUIRE_VALID_OR_SOLVING_STATE (); + internal->print_statistics (); + LOG_API_CALL_END ("stats"); +} + +void Solver::resources () { + if (state () == DELETING) + return; + TRACE ("resources"); + REQUIRE_VALID_OR_SOLVING_STATE (); + internal->print_resource_usage (); + LOG_API_CALL_END ("resources"); +} + +/*------------------------------------------------------------------------*/ + +const char *Solver::read_dimacs (File *file, int &vars, int strict, + bool *incremental, vector *cubes) { + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only read DIMACS file right after initialization"); + Parser *parser = new Parser (this, file, incremental, cubes); + const char *err = parser->parse_dimacs (vars, strict); + delete parser; + return err; +} + +const char *Solver::read_dimacs (FILE *external_file, const char *name, + int &vars, int strict) { + LOG_API_CALL_BEGIN ("read_dimacs", name); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only read DIMACS file right after initialization"); + File *file = File::read (internal, external_file, name); + CADICAL_assert (file); + const char *err = read_dimacs (file, vars, strict); + delete file; + LOG_API_CALL_RETURNS ("read_dimacs", name, err); + return err; +} + +const char *Solver::read_dimacs (const char *path, int &vars, int strict) { + LOG_API_CALL_BEGIN ("read_dimacs", path); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only read DIMACS file right after initialization"); + File *file = File::read (internal, path); + if (!file) + return internal->error_message.init ("failed to read DIMACS file '%s'", + path); + const char *err = read_dimacs (file, vars, strict); + delete file; + LOG_API_CALL_RETURNS ("read_dimacs", path, err); + return err; +} + +const char *Solver::read_dimacs (FILE *external_file, const char *name, + int &vars, int strict, bool &incremental, + vector &cubes) { + LOG_API_CALL_BEGIN ("read_dimacs", name); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only read DIMACS file right after initialization"); + File *file = File::read (internal, external_file, name); + CADICAL_assert (file); + const char *err = read_dimacs (file, vars, strict, &incremental, &cubes); + delete file; + LOG_API_CALL_RETURNS ("read_dimacs", name, err); + return err; +} + +const char *Solver::read_dimacs (const char *path, int &vars, int strict, + bool &incremental, vector &cubes) { + LOG_API_CALL_BEGIN ("read_dimacs", path); + REQUIRE_VALID_STATE (); + REQUIRE (state () == CONFIGURING, + "can only read DIMACS file right after initialization"); + File *file = File::read (internal, path); + if (!file) + return internal->error_message.init ("failed to read DIMACS file '%s'", + path); + const char *err = read_dimacs (file, vars, strict, &incremental, &cubes); + delete file; + LOG_API_CALL_RETURNS ("read_dimacs", path, err); + return err; +} + +const char *Solver::read_solution (const char *path) { + LOG_API_CALL_BEGIN ("solution", path); + REQUIRE_VALID_STATE (); + File *file = File::read (internal, path); + if (!file) + return internal->error_message.init ( + "failed to read solution file '%s'", path); + Parser *parser = new Parser (this, file, 0, 0); + const char *err = parser->parse_solution (); + delete parser; + delete file; + if (!err) + external->check_assignment (&External::sol); + LOG_API_CALL_RETURNS ("read_solution", path, err); + return err; +} + +/*------------------------------------------------------------------------*/ + +void Solver::dump_cnf () { + TRACE ("dump"); + REQUIRE_INITIALIZED (); + internal->dump (); + LOG_API_CALL_END ("dump"); +} + +/*------------------------------------------------------------------------*/ + +ExternalPropagator *Solver::get_propagator () { + return external->propagator; +} + +bool Solver::observed (int lit) { + TRACE ("observed", lit); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (lit); + bool res = external->observed (lit); + LOG_API_CALL_RETURNS ("observed", lit, res); + return res; +} + +bool Solver::is_witness (int lit) { + TRACE ("is_witness", lit); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (lit); + bool res = external->is_witness (lit); + LOG_API_CALL_RETURNS ("is_witness", lit, res); + return res; +} + +bool Solver::is_decision (int lit) { + TRACE ("is_decision", lit); + REQUIRE_VALID_OR_SOLVING_STATE (); + REQUIRE_VALID_LIT (lit); + bool res = external->is_decision (lit); + LOG_API_CALL_RETURNS ("is_decision", lit, res); + return res; +} + +void Solver::force_backtrack (size_t new_level) { + TRACE ("force_backtrack", new_level); + REQUIRE_VALID_OR_SOLVING_STATE (); + external->force_backtrack (new_level); +} + +/*------------------------------------------------------------------------*/ + +bool Solver::traverse_clauses (ClauseIterator &it) const { + LOG_API_CALL_BEGIN ("traverse_clauses"); + REQUIRE_VALID_STATE (); + bool res = external->traverse_all_frozen_units_as_clauses (it) && + internal->traverse_clauses (it) && + internal->traverse_constraint (it); + LOG_API_CALL_RETURNS ("traverse_clauses", res); + return res; +} + +bool Solver::traverse_witnesses_backward (WitnessIterator &it) const { + LOG_API_CALL_BEGIN ("traverse_witnesses_backward"); + REQUIRE_VALID_STATE (); + bool res = external->traverse_all_non_frozen_units_as_witnesses (it) && + external->traverse_witnesses_backward (it); + LOG_API_CALL_RETURNS ("traverse_witnesses_backward", res); + return res; +} + +bool Solver::traverse_witnesses_forward (WitnessIterator &it) const { + LOG_API_CALL_BEGIN ("traverse_witnesses_forward"); + REQUIRE_VALID_STATE (); + bool res = external->traverse_witnesses_forward (it) && + external->traverse_all_non_frozen_units_as_witnesses (it); + LOG_API_CALL_RETURNS ("traverse_witnesses_forward", res); + return res; +} + +/*------------------------------------------------------------------------*/ + +class ClauseCounter : public ClauseIterator { +public: + int vars; + int64_t clauses; + ClauseCounter () : vars (0), clauses (0) {} + bool clause (const vector &c) { + for (const auto &lit : c) { + CADICAL_assert (lit != INT_MIN); + int idx = abs (lit); + if (idx > vars) + vars = idx; + } + clauses++; + return true; + } +}; + +class ClauseWriter : public ClauseIterator { + File *file; + +public: + ClauseWriter (File *f) : file (f) {} + bool clause (const vector &c) { + for (const auto &lit : c) { + if (!file->put (lit)) + return false; + if (!file->put (' ')) + return false; + } + return file->put ("0\n"); + } +}; + +const char *Solver::write_dimacs (const char *path, int min_max_var) { + LOG_API_CALL_BEGIN ("write_dimacs", path, min_max_var); + REQUIRE_VALID_STATE (); +#ifndef CADICAL_QUIET + const double start = internal->time (); +#endif + internal->restore_clauses (); + ClauseCounter counter; + (void) traverse_clauses (counter); + LOG ("found maximal variable %d and %" PRId64 " clauses", counter.vars, + counter.clauses); + File *file = File::write (internal, path); + const char *res = 0; + if (file) { + int actual_max_vars = max (min_max_var, counter.vars); + MSG ("writing %s'p cnf %d %" PRId64 "'%s header", tout.green_code (), + actual_max_vars, counter.clauses, tout.normal_code ()); + file->put ("p cnf "); + file->put (actual_max_vars); + file->put (' '); + file->put (counter.clauses); + file->put ('\n'); + ClauseWriter writer (file); + if (!traverse_clauses (writer)) + res = internal->error_message.init ( + "writing to DIMACS file '%s' failed", path); + delete file; + } else + res = internal->error_message.init ( + "failed to open DIMACS file '%s' for writing", path); +#ifndef CADICAL_QUIET + if (!res) { + const double end = internal->time (); + MSG ("wrote %" PRId64 " clauses in %.2f seconds %s time", + counter.clauses, end - start, + internal->opts.realtime ? "real" : "process"); + } +#endif + LOG_API_CALL_RETURNS ("write_dimacs", path, min_max_var, res); + return res; +} + +/*------------------------------------------------------------------------*/ + +struct WitnessWriter : public WitnessIterator { + File *file; + int64_t witnesses; + WitnessWriter (File *f) : file (f), witnesses (0) {} + bool write (const vector &a) { + for (const auto &lit : a) { + if (!file->put (lit)) + return false; + if (!file->put (' ')) + return false; + } + return file->put ('0'); + } + bool witness (const vector &c, const vector &w, int64_t) { + if (!write (c)) + return false; + if (!file->put (' ')) + return false; + if (!write (w)) + return false; + if (!file->put ('\n')) + return false; + witnesses++; + return true; + } +}; + +const char *Solver::write_extension (const char *path) { + LOG_API_CALL_BEGIN ("write_extension", path); + REQUIRE_VALID_STATE (); + const char *res = 0; +#ifndef CADICAL_QUIET + const double start = internal->time (); +#endif + File *file = File::write (internal, path); + WitnessWriter writer (file); + if (file) { + if (!traverse_witnesses_backward (writer)) + res = internal->error_message.init ( + "writing to DIMACS file '%s' failed", path); + delete file; + } else + res = internal->error_message.init ( + "failed to open extension file '%s' for writing", path); +#ifndef CADICAL_QUIET + if (!res) { + const double end = internal->time (); + MSG ("wrote %" PRId64 " witnesses in %.2f seconds %s time", + writer.witnesses, end - start, + internal->opts.realtime ? "real" : "process"); + } +#endif + LOG_API_CALL_RETURNS ("write_extension", path, res); + return res; +} + +/*------------------------------------------------------------------------*/ + +struct ClauseCopier : public ClauseIterator { + Solver &dst; + +public: + ClauseCopier (Solver &d) : dst (d) {} + bool clause (const vector &c) { + for (const auto &lit : c) + dst.add (lit); + dst.add (0); + return true; + } +}; + +struct WitnessCopier : public WitnessIterator { + External *dst; + +public: + WitnessCopier (External *d) : dst (d) {} + bool witness (const vector &c, const vector &w, int64_t id) { + dst->push_external_clause_and_witness_on_extension_stack (c, w, id); + return true; + } +}; + +void Solver::copy (Solver &other) const { + REQUIRE_READY_STATE (); + REQUIRE (other.state () & CONFIGURING, "target solver already modified"); + internal->opts.copy (other.internal->opts); + ClauseCopier clause_copier (other); + traverse_clauses (clause_copier); + WitnessCopier witness_copier (other.external); + traverse_witnesses_forward (witness_copier); + external->copy_flags (*other.external); +} + +/*------------------------------------------------------------------------*/ + +void Solver::section (const char *title) { + if (state () == DELETING) + return; +#ifdef CADICAL_QUIET + (void) title; +#endif + REQUIRE_INITIALIZED (); + SECTION (title); +} + +void Solver::message (const char *fmt, ...) { + if (state () == DELETING) + return; +#ifdef CADICAL_QUIET + (void) fmt; +#else + REQUIRE_INITIALIZED (); + va_list ap; + va_start (ap, fmt); + internal->vmessage (fmt, ap); + va_end (ap); +#endif +} + +void Solver::message () { + if (state () == DELETING) + return; + REQUIRE_INITIALIZED (); +#ifndef CADICAL_QUIET + internal->message (); +#endif +} + +void Solver::verbose (int level, const char *fmt, ...) { + if (state () == DELETING) + return; + REQUIRE_VALID_OR_SOLVING_STATE (); +#ifdef CADICAL_QUIET + (void) level; + (void) fmt; +#else + va_list ap; + va_start (ap, fmt); + internal->vverbose (level, fmt, ap); + va_end (ap); +#endif +} + +void Solver::error (const char *fmt, ...) { + if (state () == DELETING) + return; + REQUIRE_INITIALIZED (); + va_list ap; + va_start (ap, fmt); + internal->verror (fmt, ap); + va_end (ap); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_stable.cpp b/src/sat/cadical/cadical_stable.cpp new file mode 100644 index 000000000..f177139ac --- /dev/null +++ b/src/sat/cadical/cadical_stable.cpp @@ -0,0 +1,39 @@ +#include "global.h" + +#ifdef PROFILE_MODE + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +bool Internal::propagate_stable () { + CADICAL_assert (stable); + START (propstable); + bool res = propagate (); + STOP (propstable); + return res; +} + +void Internal::analyze_stable () { + CADICAL_assert (stable); + START (analyzestable); + analyze (); + STOP (analyzestable); +} + +int Internal::decide_stable () { + CADICAL_assert (stable); + return decide (); +} + +}; // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#else +ABC_NAMESPACE_IMPL_START +int stable_if_not_profile_mode_dummy; +ABC_NAMESPACE_IMPL_END +#endif diff --git a/src/sat/cadical/cadical_stats.cpp b/src/sat/cadical/cadical_stats.cpp new file mode 100644 index 000000000..8b07745d6 --- /dev/null +++ b/src/sat/cadical/cadical_stats.cpp @@ -0,0 +1,826 @@ +// vim: set tw=300: set VIM text width to 300 characters for this file. +#include "global.h" + + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +Stats::Stats () { + time.real = absolute_real_time (); + time.process = absolute_process_time (); + walk.minimum = LONG_MAX; + used.resize (2); + used[0].resize (127); + used[1].resize (127); +} + +/*------------------------------------------------------------------------*/ + +#define PRT(FMT, ...) \ + do { \ + if (FMT[0] == ' ' && !all) \ + break; \ + MSG (FMT, __VA_ARGS__); \ + } while (0) + +/*------------------------------------------------------------------------*/ + +void Stats::print (Internal *internal) { + +#ifdef CADICAL_QUIET + (void) internal; +#else + + Stats &stats = internal->stats; + + int all = internal->opts.verbose > 0 || internal->opts.stats; +#ifdef LOGGING + if (internal->opts.log) + all = true; +#endif // ifdef LOGGING + + if (internal->opts.profile) + internal->print_profile (); + + double t = internal->solve_time (); + + int64_t propagations = 0; + propagations += stats.propagations.cover; + propagations += stats.propagations.probe; + propagations += stats.propagations.search; + propagations += stats.propagations.transred; + propagations += stats.propagations.vivify; + propagations += stats.propagations.walk; + + int64_t vivified = stats.vivifysubs + stats.vivifystrs; + int64_t searchticks = stats.ticks.search[0] + stats.ticks.search[1]; + int64_t inprobeticks = stats.ticks.vivify + stats.ticks.probe + + stats.ticks.factor + stats.ticks.ternary + + stats.ticks.sweep; + int64_t totalticks = searchticks + inprobeticks; + + size_t extendbytes = internal->external->extension.size (); + extendbytes *= sizeof (int); + + SECTION ("statistics"); + + if (all || stats.blocked) { + PRT ("blocked: %15" PRId64 + " %10.2f %% of irredundant clauses", + stats.blocked, percent (stats.blocked, stats.added.irredundant)); + PRT (" blockings: %15" PRId64 " %10.2f internal", + stats.blockings, relative (stats.conflicts, stats.blockings)); + PRT (" candidates: %15" PRId64 " %10.2f per blocking ", + stats.blockcands, relative (stats.blockcands, stats.blockings)); + PRT (" blockres: %15" PRId64 " %10.2f per candidate", + stats.blockres, relative (stats.blockres, stats.blockcands)); + PRT (" pure: %15" PRId64 " %10.2f %% of all variables", + stats.all.pure, percent (stats.all.pure, stats.vars)); + PRT (" pureclauses: %15" PRId64 " %10.2f per pure literal", + stats.blockpured, relative (stats.blockpured, stats.all.pure)); + } + if (all || stats.chrono) + PRT ("chronological: %15" PRId64 " %10.2f %% of conflicts", + stats.chrono, percent (stats.chrono, stats.conflicts)); + if (all) + PRT ("compacts: %15" PRId64 " %10.2f interval", + stats.compacts, relative (stats.conflicts, stats.compacts)); + if (all || stats.conflicts) { + PRT ("conflicts: %15" PRId64 " %10.2f per second", + stats.conflicts, relative (stats.conflicts, t)); + PRT (" backtracked: %15" PRId64 " %10.2f %% of conflicts", + stats.backtracks, percent (stats.backtracks, stats.conflicts)); + } + if (all || stats.conditioned) { + PRT ("conditioned: %15" PRId64 + " %10.2f %% of irredundant clauses", + stats.conditioned, + percent (stats.conditioned, stats.added.irredundant)); + PRT (" conditionings: %15" PRId64 " %10.2f interval", + stats.conditionings, + relative (stats.conflicts, stats.conditionings)); + PRT (" condcands: %15" PRId64 " %10.2f candidate clauses", + stats.condcands, relative (stats.condcands, stats.conditionings)); + PRT (" condassinit: %17.1f %9.2f %% initial assigned", + relative (stats.condassinit, stats.conditionings), + percent (stats.condassinit, stats.condassvars)); + PRT (" condcondinit: %17.1f %9.2f %% initial condition", + relative (stats.condcondinit, stats.conditionings), + percent (stats.condcondinit, stats.condassinit)); + PRT (" condautinit: %17.1f %9.2f %% initial autarky", + relative (stats.condautinit, stats.conditionings), + percent (stats.condautinit, stats.condassinit)); + PRT (" condassrem: %17.1f %9.2f %% final assigned", + relative (stats.condassrem, stats.conditioned), + percent (stats.condassrem, stats.condassirem)); + PRT (" condcondrem: %19.3f %7.2f %% final conditional", + relative (stats.condcondrem, stats.conditioned), + percent (stats.condcondrem, stats.condassrem)); + PRT (" condautrem: %19.3f %7.2f %% final autarky", + relative (stats.condautrem, stats.conditioned), + percent (stats.condautrem, stats.condassrem)); + PRT (" condprops: %15" PRId64 " %10.2f per candidate", + stats.condprops, relative (stats.condprops, stats.condcands)); + } + if (all || stats.cover.total) { + PRT ("covered: %15" PRId64 + " %10.2f %% of irredundant clauses", + stats.cover.total, + percent (stats.cover.total, stats.added.irredundant)); + PRT (" coverings: %15" PRId64 " %10.2f interval", + stats.cover.count, relative (stats.conflicts, stats.cover.count)); + PRT (" asymmetric: %15" PRId64 " %10.2f %% of covered clauses", + stats.cover.asymmetric, + percent (stats.cover.asymmetric, stats.cover.total)); + PRT (" blocked: %15" PRId64 " %10.2f %% of covered clauses", + stats.cover.blocked, + percent (stats.cover.blocked, stats.cover.total)); + } + if (all || stats.decisions) { + PRT ("decisions: %15" PRId64 " %10.2f per second", + stats.decisions, relative (stats.decisions, t)); + PRT (" searched: %15" PRId64 " %10.2f per decision", + stats.searched, relative (stats.searched, stats.decisions)); + } + if (all || stats.all.eliminated) { + PRT ("eliminated: %15" PRId64 " %10.2f %% of all variables", + stats.all.eliminated, percent (stats.all.eliminated, stats.vars)); + PRT (" fastelim: %15" PRId64 " %10.2f %% of eliminated", + stats.all.fasteliminated, + percent (stats.all.fasteliminated, stats.all.eliminated)); + PRT (" elimphases: %15" PRId64 " %10.2f interval", + stats.elimphases, relative (stats.conflicts, stats.elimphases)); + PRT (" elimrounds: %15" PRId64 " %10.2f per phase", + stats.elimrounds, relative (stats.elimrounds, stats.elimphases)); + PRT (" elimtried: %15" PRId64 " %10.2f %% eliminated", + stats.elimtried, percent (stats.all.eliminated, stats.elimtried)); + PRT (" elimgates: %15" PRId64 " %10.2f %% gates per tried", + stats.elimgates, percent (stats.elimgates, stats.elimtried)); + PRT (" elimequivs: %15" PRId64 " %10.2f %% equivalence gates", + stats.elimequivs, percent (stats.elimequivs, stats.elimgates)); + PRT (" elimands: %15" PRId64 " %10.2f %% and gates", + stats.elimands, percent (stats.elimands, stats.elimgates)); + PRT (" elimites: %15" PRId64 " %10.2f %% if-then-else gates", + stats.elimites, percent (stats.elimites, stats.elimgates)); + PRT (" elimxors: %15" PRId64 " %10.2f %% xor gates", + stats.elimxors, percent (stats.elimxors, stats.elimgates)); + PRT (" elimdefs: %15" PRId64 " %10.2f %% definitions", + stats.definitions_extracted, + percent (stats.definitions_extracted, stats.elimgates)); + PRT (" elimsubst: %15" PRId64 " %10.2f %% substituted", + stats.elimsubst, percent (stats.elimsubst, stats.all.eliminated)); + PRT (" elimsubstequi: %15" PRId64 " %10.2f %% equivalence gates", + stats.eliminated_equi, + percent (stats.eliminated_equi, stats.elimsubst)); + PRT (" elimsubstands: %15" PRId64 " %10.2f %% and gates", + stats.eliminated_and, + percent (stats.eliminated_and, stats.elimsubst)); + PRT (" elimsubstites: %15" PRId64 " %10.2f %% if-then-else gates", + stats.eliminated_ite, + percent (stats.eliminated_ite, stats.elimsubst)); + PRT (" elimsubstxors: %15" PRId64 " %10.2f %% xor gates", + stats.eliminated_xor, + percent (stats.eliminated_xor, stats.elimsubst)); + PRT (" elimsubstdefs: %15" PRId64 " %10.2f %% definitions", + stats.eliminated_def, + percent (stats.eliminated_def, stats.elimsubst)); + PRT (" elimres: %15" PRId64 " %10.2f per eliminated", + stats.elimres, relative (stats.elimres, stats.all.eliminated)); + PRT (" elimrestried: %15" PRId64 " %10.2f %% per resolution", + stats.elimrestried, percent (stats.elimrestried, stats.elimres)); + PRT (" def checked: %15" PRId64 " %10.2f per phase", + stats.definitions_checked, + relative (stats.definitions_checked, stats.elimphases)); + PRT (" def extracted: %15" PRId64 " %10.2f %% per checked", + stats.definitions_extracted, + percent (stats.definitions_extracted, stats.definitions_checked)); + PRT (" def units: %15" PRId64 " %10.2f %% per checked", + stats.definition_units, + percent (stats.definition_units, stats.definitions_checked)); + } + if (all || stats.ext_prop.ext_cb) { + PRT ("ext.prop. calls: %15" PRId64 " %10.2f %% of queries", + stats.ext_prop.eprop_call, + percent (stats.ext_prop.eprop_call, stats.ext_prop.ext_cb)); + PRT (" propagating: %15" PRId64 " %10.2f %% per eprop-call", + stats.ext_prop.eprop_prop, + percent (stats.ext_prop.eprop_prop, stats.ext_prop.eprop_call)); + PRT (" explained: %15" PRId64 " %10.2f %% per eprop-call", + stats.ext_prop.eprop_expl, + percent (stats.ext_prop.eprop_expl, stats.ext_prop.eprop_call)); + PRT (" falsified: %15" PRId64 " %10.2f %% per eprop-call", + stats.ext_prop.eprop_conf, + percent (stats.ext_prop.eprop_conf, stats.ext_prop.eprop_call)); + PRT ("ext.clause calls:%15" PRId64 " %10.2f %% of queries", + stats.ext_prop.elearn_call, + percent (stats.ext_prop.elearn_call, stats.ext_prop.ext_cb)); + PRT (" learned: %15" PRId64 " %10.2f %% per called", + stats.ext_prop.elearned, + percent (stats.ext_prop.elearned, stats.ext_prop.elearn_call)); + PRT (" conflicting: %15" PRId64 " %10.2f %% per learned", + stats.ext_prop.elearn_conf, + percent (stats.ext_prop.elearn_conf, stats.ext_prop.elearned)); + PRT (" propagating: %15" PRId64 " %10.2f %% per learned", + stats.ext_prop.elearn_prop, + percent (stats.ext_prop.elearn_prop, stats.ext_prop.elearned)); + PRT ("ext.final check: %15" PRId64 " %10.2f %% of queries", + stats.ext_prop.echeck_call, + percent (stats.ext_prop.echeck_call, stats.ext_prop.ext_cb)); + } + if (all || stats.factored) { + PRT ("factored: %15" PRId64 " %10.2f %% of variables", + stats.factored, percent (stats.factored, internal->max_var)); + PRT (" factor: %15" PRId64 " %10.2f conflict interval", + stats.factor, relative (stats.conflicts, stats.factor)); + PRT (" cls factored: %15" PRId64 " %10.2f per factored", + stats.factor_added, relative (stats.factor_added, factored)); + PRT (" lits factored: %15" PRId64 " %10.2f per factored", + stats.literals_factored, + relative (stats.literals_factored, factored)); + PRT (" cls unfactored:%15" PRId64 " %10.2f per factored", + stats.clauses_unfactored, + relative (stats.clauses_unfactored, factored)); + PRT (" lits unfactored:%14" PRId64 " %10.2f per factored", + stats.literals_unfactored, + relative (stats.literals_unfactored, factored)); + } + if (all || stats.all.fixed) { + PRT ("fixed: %15" PRId64 " %10.2f %% of all variables", + stats.all.fixed, percent (stats.all.fixed, stats.vars)); + PRT (" failed: %15" PRId64 " %10.2f %% of all variables", + stats.failed, percent (stats.failed, stats.vars)); + PRT (" probefailed: %15" PRId64 " %10.2f %% per failed", + stats.probefailed, percent (stats.probefailed, stats.failed)); + PRT (" transredunits: %15" PRId64 " %10.2f %% per failed", + stats.transredunits, percent (stats.transredunits, stats.failed)); + PRT (" inprobephases: %15" PRId64 " %10.2f interval", + stats.inprobingphases, + relative (stats.conflicts, stats.inprobingphases)); + PRT (" inprobesuccess:%15" PRId64 " %10.2f %% phases", + stats.inprobesuccess, + percent (stats.inprobesuccess, stats.inprobingphases)); + PRT (" probingrounds: %15" PRId64 " %10.2f per phase", + stats.probingrounds, + relative (stats.probingrounds, stats.inprobingphases)); + PRT (" probed: %15" PRId64 " %10.2f per failed", + stats.probed, relative (stats.probed, stats.failed)); + PRT (" hbrs: %15" PRId64 " %10.2f per probed", + stats.hbrs, relative (stats.hbrs, stats.probed)); + PRT (" hbrsizes: %15" PRId64 " %10.2f per hbr", + stats.hbrsizes, relative (stats.hbrsizes, stats.hbrs)); + PRT (" hbreds: %15" PRId64 " %10.2f %% per hbr", + stats.hbreds, percent (stats.hbreds, stats.hbrs)); + PRT (" hbrsubs: %15" PRId64 " %10.2f %% per hbr", + stats.hbrsubs, percent (stats.hbrsubs, stats.hbrs)); + } + PRT (" units: %15" PRId64 " %10.2f interval", stats.units, + relative (stats.conflicts, stats.units)); + PRT (" binaries: %15" PRId64 " %10.2f interval", + stats.binaries, relative (stats.conflicts, stats.binaries)); + if (all || stats.flush.learned) { + PRT ("flushed: %15" PRId64 " %10.2f %% per conflict", + stats.flush.learned, + percent (stats.flush.learned, stats.conflicts)); + PRT (" hyper: %15" PRId64 " %10.2f %% per conflict", + stats.flush.hyper, relative (stats.flush.hyper, stats.conflicts)); + PRT (" flushings: %15" PRId64 " %10.2f interval", + stats.flush.count, relative (stats.conflicts, stats.flush.count)); + } + if (all || stats.instantiated) { + PRT ("instantiated: %15" PRId64 " %10.2f %% of tried", + stats.instantiated, percent (stats.instantiated, stats.instried)); + PRT (" instrounds: %15" PRId64 " %10.2f %% of elimrounds", + stats.instrounds, percent (stats.instrounds, stats.elimrounds)); + } + if (all || stats.conflicts) { + PRT ("learned: %15" PRId64 " %10.2f %% per conflict", + stats.learned.clauses, + percent (stats.learned.clauses, stats.conflicts)); + PRT ("@ bumped: %15" PRId64 " %10.2f per learned", + stats.bumped, relative (stats.bumped, stats.learned.clauses)); + PRT (" recomputed: %15" PRId64 " %10.2f %% per learned", + stats.recomputed, + percent (stats.recomputed, stats.learned.clauses)); + PRT (" promoted1: %15" PRId64 " %10.2f %% per learned", + stats.promoted1, percent (stats.promoted1, stats.learned.clauses)); + PRT (" promoted2: %15" PRId64 " %10.2f %% per learned", + stats.promoted2, percent (stats.promoted2, stats.learned.clauses)); + PRT (" improvedglue: %15" PRId64 " %10.2f %% per learned", + stats.improvedglue, + percent (stats.improvedglue, stats.learned.clauses)); + } + if (all || stats.lucky.succeeded) { + PRT ("lucky: %15" PRId64 " %10.2f %% of tried", + stats.lucky.succeeded, + percent (stats.lucky.succeeded, stats.lucky.tried)); + PRT (" constantzero %15" PRId64 " %10.2f %% of tried", + stats.lucky.constant.zero, + percent (stats.lucky.constant.zero, stats.lucky.tried)); + PRT (" constantone %15" PRId64 " %10.2f %% of tried", + stats.lucky.constant.one, + percent (stats.lucky.constant.one, stats.lucky.tried)); + PRT (" backwardone %15" PRId64 " %10.2f %% of tried", + stats.lucky.backward.one, + percent (stats.lucky.backward.one, stats.lucky.tried)); + PRT (" backwardzero %15" PRId64 " %10.2f %% of tried", + stats.lucky.backward.zero, + percent (stats.lucky.backward.zero, stats.lucky.tried)); + PRT (" forwardone %15" PRId64 " %10.2f %% of tried", + stats.lucky.forward.one, + percent (stats.lucky.forward.one, stats.lucky.tried)); + PRT (" forwardzero %15" PRId64 " %10.2f %% of tried", + stats.lucky.forward.zero, + percent (stats.lucky.forward.zero, stats.lucky.tried)); + PRT (" positivehorn %15" PRId64 " %10.2f %% of tried", + stats.lucky.horn.positive, + percent (stats.lucky.horn.positive, stats.lucky.tried)); + PRT (" negativehorn %15" PRId64 " %10.2f %% of tried", + stats.lucky.horn.negative, + percent (stats.lucky.horn.negative, stats.lucky.tried)); + } + PRT (" extendbytes: %15zd %10.2f bytes and MB", extendbytes, + extendbytes / (double) (1l << 20)); + if (all || stats.learned.clauses) + PRT ("learned_lits: %15" PRId64 " %10.2f %% learned literals", + stats.learned.literals, + percent (stats.learned.literals, stats.learned.literals)); + PRT ("minimized: %15" PRId64 " %10.2f %% learned literals", + stats.minimized, percent (stats.minimized, stats.learned.literals)); + PRT ("shrunken: %15" PRId64 " %10.2f %% learned literals", + stats.shrunken, percent (stats.shrunken, stats.learned.literals)); + PRT ("minishrunken: %15" PRId64 " %10.2f %% learned literals", + stats.minishrunken, + percent (stats.minishrunken, stats.learned.literals)); + + if (all || stats.conflicts) { + PRT ("otfs: %15" PRId64 " %10.2f %% of conflict", + stats.otfs.subsumed + stats.otfs.strengthened, + percent (stats.otfs.subsumed + stats.otfs.strengthened, + stats.conflicts)); + PRT (" subsumed %15" PRId64 " %10.2f %% of conflict", + stats.otfs.subsumed, + percent (stats.otfs.subsumed, stats.conflicts)); + PRT (" strengthened %15" PRId64 " %10.2f %% of conflict", + stats.otfs.strengthened, + percent (stats.otfs.strengthened, stats.conflicts)); + } + + PRT ("propagations: %15" PRId64 " %10.2f M per second", + propagations, relative (propagations / 1e6, t)); + PRT (" coverprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.cover, + percent (stats.propagations.cover, propagations)); + PRT (" probeprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.probe, + percent (stats.propagations.probe, propagations)); + PRT (" searchprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.search, + percent (stats.propagations.search, propagations)); + PRT (" transredprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.transred, + percent (stats.propagations.transred, propagations)); + PRT (" vivifyprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.vivify, + percent (stats.propagations.vivify, propagations)); + PRT (" walkprops: %15" PRId64 " %10.2f %% of propagations", + stats.propagations.walk, + percent (stats.propagations.walk, propagations)); + if (all || stats.reactivated) { + PRT ("reactivated: %15" PRId64 " %10.2f %% of all variables", + stats.reactivated, percent (stats.reactivated, stats.vars)); + } + if (all || stats.reduced) { + PRT ("reduced: %15" PRId64 " %10.2f %% per conflict", + stats.reduced, percent (stats.reduced, stats.conflicts)); + PRT (" reductions: %15" PRId64 " %10.2f interval", + stats.reductions, relative (stats.conflicts, stats.reductions)); + PRT (" sqrt scheme: %15" PRId64 " %10.2f %% reductions", + stats.reduced_sqrt, + relative (stats.reduced_sqrt, stats.reductions)); + PRT (" prct scheme: %15" PRId64 " %10.2f %% reductions", + stats.reduced_prct, + relative (stats.reduced_prct, stats.reductions)); + PRT (" collections: %15" PRId64 " %10.2f interval", + stats.collections, relative (stats.conflicts, stats.collections)); + } + if (all || stats.rephased.total) { + PRT ("rephased: %15" PRId64 " %10.2f interval", + stats.rephased.total, + relative (stats.conflicts, stats.rephased.total)); + PRT (" rephasedbest: %15" PRId64 " %10.2f %% rephased best", + stats.rephased.best, + percent (stats.rephased.best, stats.rephased.total)); + PRT (" rephasedflip: %15" PRId64 " %10.2f %% rephased flipping", + stats.rephased.flipped, + percent (stats.rephased.flipped, stats.rephased.total)); + PRT (" rephasedinv: %15" PRId64 " %10.2f %% rephased inverted", + stats.rephased.inverted, + percent (stats.rephased.inverted, stats.rephased.total)); + PRT (" rephasedorig: %15" PRId64 " %10.2f %% rephased original", + stats.rephased.original, + percent (stats.rephased.original, stats.rephased.total)); + PRT (" rephasedrand: %15" PRId64 " %10.2f %% rephased random", + stats.rephased.random, + percent (stats.rephased.random, stats.rephased.total)); + PRT (" rephasedwalk: %15" PRId64 " %10.2f %% rephased walk", + stats.rephased.walk, + percent (stats.rephased.walk, stats.rephased.total)); + } + if (all) + PRT ("rescored: %15" PRId64 " %10.2f interval", + stats.rescored, relative (stats.conflicts, stats.rescored)); + if (all || stats.restarts) { + PRT ("restarts: %15" PRId64 " %10.2f interval", + stats.restarts, relative (stats.conflicts, stats.restarts)); + PRT (" reused: %15" PRId64 " %10.2f %% per restart", + stats.reused, percent (stats.reused, stats.restarts)); + PRT (" reusedlevels: %15" PRId64 " %10.2f %% per restart levels", + stats.reusedlevels, + percent (stats.reusedlevels, stats.restartlevels)); + } + if (all || stats.restored) { + PRT ("restored: %15" PRId64 " %10.2f %% per weakened", + stats.restored, percent (stats.restored, stats.weakened)); + PRT (" restorations: %15" PRId64 " %10.2f %% per extension", + stats.restorations, + percent (stats.restorations, stats.extensions)); + PRT (" literals: %15" PRId64 " %10.2f per restored clause", + stats.restoredlits, relative (stats.restoredlits, stats.restored)); + } + if (all || stats.stabphases) { + PRT ("stabilizing: %15" PRId64 " %10.2f %% of conflicts", + stats.stabphases, percent (stats.stabconflicts, stats.conflicts)); + PRT (" restartstab: %15" PRId64 " %10.2f %% of all restarts", + stats.restartstable, + percent (stats.restartstable, stats.restarts)); + PRT (" reusedstab: %15" PRId64 " %10.2f %% per stable restarts", + stats.reusedstable, + percent (stats.reusedstable, stats.restartstable)); + } + if (all || stats.all.substituted) { + PRT ("substituted: %15" PRId64 " %10.2f %% of all variables", + stats.all.substituted, + percent (stats.all.substituted, stats.vars)); + PRT (" decompositions:%15" PRId64 " %10.2f per phase", + stats.decompositions, + relative (stats.decompositions, stats.inprobingphases)); + } + if (all || stats.sweep_equivalences) { + PRT ("sweep equivs: %15" PRId64 " %10.2f %% of swept variables", + stats.sweep_equivalences, + percent (stats.sweep_equivalences, stats.sweep_variables)); + PRT (" sweepings: %15" PRId64 " %10.2f vars per sweeping", + stats.sweep, relative (stats.sweep_variables, stats.sweep)); + PRT (" swept vars: %15" PRId64 " %10.2f %% of all variables", + stats.sweep_variables, + percent (stats.sweep_variables, stats.vars)); + PRT (" sweep units: %15" PRId64 " %10.2f %% of all variables", + stats.sweep_units, percent (stats.sweep_units, stats.vars)); + PRT (" solved: %15" PRId64 " %10.2f per swept variable", + stats.sweep_solved, + relative (stats.sweep_solved, stats.sweep_variables)); + PRT (" sat: %15" PRId64 " %10.2f %% solved", + stats.sweep_sat, percent (stats.sweep_sat, stats.sweep_solved)); + PRT (" unsat: %15" PRId64 " %10.2f %% solved", + stats.sweep_unsat, + percent (stats.sweep_unsat, stats.sweep_solved)); + PRT (" backbone solved:%14" PRId64 " %10.2f %% solved", + stats.sweep_solved_backbone, + percent (stats.sweep_solved_backbone, stats.sweep_solved)); + PRT (" sat: %15" PRId64 " %10.2f %% backbone solved", + stats.sweep_sat_backbone, + percent (stats.sweep_sat_backbone, stats.sweep_solved_backbone)); + PRT (" unsat: %15" PRId64 " %10.2f %% backbone solved", + stats.sweep_unsat_backbone, + percent (stats.sweep_unsat_backbone, stats.sweep_solved_backbone)); + PRT (" unknown: %15" PRId64 " %10.2f %% backbone solved", + stats.sweep_unknown_backbone, + percent (stats.sweep_unknown_backbone, + stats.sweep_solved_backbone)); + PRT (" fixed: %15" PRId64 " %10.2f per swept variable", + stats.sweep_fixed_backbone, + relative (stats.sweep_fixed_backbone, stats.sweep_variables)); + PRT (" flip: %15" PRId64 " %10.2f per swept variable", + stats.sweep_flip_backbone, + relative (stats.sweep_flip_backbone, stats.sweep_variables)); + PRT (" flipped: %15" PRId64 " %10.2f %% of backbone flip", + stats.sweep_flipped_backbone, + percent (stats.sweep_flipped_backbone, stats.sweep_flip_backbone)); + PRT (" equiv solved: %15" PRId64 " %10.2f %% solved", + stats.sweep_solved_equivalences, + percent (stats.sweep_solved_equivalences, stats.sweep_solved)); + PRT (" sat: %15" PRId64 " %10.2f %% equiv solved", + stats.sweep_sat_equivalences, + percent (stats.sweep_sat_equivalences, + stats.sweep_solved_equivalences)); + PRT (" unsat: %15" PRId64 " %10.2f %% equiv solved", + stats.sweep_unsat_equivalences, + percent (stats.sweep_unsat_equivalences, + stats.sweep_solved_equivalences)); + PRT (" unknown: %15" PRId64 " %10.2f %% equiv solved", + stats.sweep_unknown_equivalences, + percent (stats.sweep_unknown_equivalences, + stats.sweep_solved_equivalences)); + PRT (" flip: %15" PRId64 " %10.2f per swept variable", + stats.sweep_flip_equivalences, + relative (stats.sweep_flip_equivalences, stats.sweep_variables)); + PRT (" flipped: %15" PRId64 " %10.2f %% of equiv flip", + stats.sweep_flipped_equivalences, + percent (stats.sweep_flipped_equivalences, + stats.sweep_flip_equivalences)); + PRT (" depth: %15" PRId64 " %10.2f per swept variable", + stats.sweep_depth, + relative (stats.sweep_depth, stats.sweep_variables)); + PRT (" environment: %15" PRId64 " %10.2f per swept variable", + stats.sweep_environment, + relative (stats.sweep_environment, stats.sweep_variables)); + PRT (" clauses: %15" PRId64 " %10.2f per swept variable", + stats.sweep_clauses, + relative (stats.sweep_clauses, stats.sweep_variables)); + PRT (" completed: %15" PRId64 " %10.2f sweeps to complete", + stats.sweep_completed, + relative (stats.sweep, stats.sweep_completed)); + } + if (all || stats.subsumed) { + PRT ("subsumed: %15" PRId64 " %10.2f %% of all clauses", + stats.subsumed, percent (stats.subsumed, stats.added.total)); + PRT (" subsumephases: %15" PRId64 " %10.2f interval", + stats.subsumephases, + relative (stats.conflicts, stats.subsumephases)); + PRT (" subsumerounds: %15" PRId64 " %10.2f per phase", + stats.subsumerounds, + relative (stats.subsumerounds, stats.subsumephases)); + PRT (" deduplicated: %15" PRId64 " %10.2f %% per subsumed", + stats.deduplicated, percent (stats.deduplicated, stats.subsumed)); + PRT (" transreds: %15" PRId64 " %10.2f interval", + stats.transreds, relative (stats.conflicts, stats.transreds)); + PRT (" transitive: %15" PRId64 " %10.2f %% per subsumed", + stats.transitive, percent (stats.transitive, stats.subsumed)); + PRT (" subirr: %15" PRId64 " %10.2f %% of subsumed", + stats.subirr, percent (stats.subirr, stats.subsumed)); + PRT (" subred: %15" PRId64 " %10.2f %% of subsumed", + stats.subred, percent (stats.subred, stats.subsumed)); + PRT (" subtried: %15" PRId64 " %10.2f tried per subsumed", + stats.subtried, relative (stats.subtried, stats.subsumed)); + PRT (" subchecks: %15" PRId64 " %10.2f per tried", + stats.subchecks, relative (stats.subchecks, stats.subtried)); + PRT (" subchecks2: %15" PRId64 " %10.2f %% per subcheck", + stats.subchecks2, percent (stats.subchecks2, stats.subchecks)); + PRT (" elimotfsub: %15" PRId64 " %10.2f %% of subsumed", + stats.elimotfsub, percent (stats.elimotfsub, stats.subsumed)); + PRT (" elimbwsub: %15" PRId64 " %10.2f %% of subsumed", + stats.elimbwsub, percent (stats.elimbwsub, stats.subsumed)); + PRT (" eagersub: %15" PRId64 " %10.2f %% of subsumed", + stats.eagersub, percent (stats.eagersub, stats.subsumed)); + PRT (" eagertried: %15" PRId64 " %10.2f tried per eagersub", + stats.eagertried, relative (stats.eagertried, stats.eagersub)); + } + if (all || stats.strengthened) { + PRT ("strengthened: %15" PRId64 " %10.2f %% of all clauses", + stats.strengthened, + percent (stats.strengthened, stats.added.total)); + PRT (" elimotfstr: %15" PRId64 " %10.2f %% of strengthened", + stats.elimotfstr, percent (stats.elimotfstr, stats.strengthened)); + PRT (" elimbwstr: %15" PRId64 " %10.2f %% of strengthened", + stats.elimbwstr, percent (stats.elimbwstr, stats.strengthened)); + } + if (all || stats.htrs) { + PRT ("ternary: %15" PRId64 " %10.2f %% of resolved", + stats.htrs, percent (stats.htrs, stats.ternres)); + PRT (" phases: %15" PRId64 " %10.2f interval", + stats.ternary, relative (stats.conflicts, stats.ternary)); + PRT (" htr3: %15" PRId64 + " %10.2f %% ternary hyper ternres", + stats.htrs3, percent (stats.htrs3, stats.htrs)); + PRT (" htr2: %15" PRId64 " %10.2f %% binary hyper ternres", + stats.htrs2, percent (stats.htrs2, stats.htrs)); + } + PRT ("ticks: %15" PRId64 " %10.2f propagation", totalticks, + relative (totalticks, stats.propagations.search)); + PRT (" searchticks: %15" PRId64 " %10.2f %% totalticks", + searchticks, percent (searchticks, totalticks)); + PRT (" stableticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.search[1], percent (stats.ticks.search[1], searchticks)); + PRT (" unstableticks:%15" PRId64 " %10.2f %% searchticks", + stats.ticks.search[0], percent (stats.ticks.search[0], searchticks)); + PRT (" inprobeticks: %15" PRId64 " %10.2f %% totalticks", + inprobeticks, percent (inprobeticks, totalticks)); + PRT (" factorticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.factor, percent (stats.ticks.factor, searchticks)); + PRT (" probeticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.probe, percent (stats.ticks.probe, searchticks)); + PRT (" sweepticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.sweep, percent (stats.ticks.sweep, searchticks)); + PRT (" ternaryticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.ternary, percent (stats.ticks.ternary, searchticks)); + PRT (" vivifyticks: %15" PRId64 " %10.2f %% searchticks", + stats.ticks.vivify, percent (stats.ticks.vivify, searchticks)); + if (all) { + PRT ("tier recomputed: %15" PRId64 " %10.2f interval", + stats.tierecomputed, + relative (stats.conflicts, stats.tierecomputed)); + } + if (all || stats.ilbtriggers) { + PRT ("trail reuses: %15" PRId64 " %10.2f %% of incremental calls", + stats.ilbsuccess, percent (stats.ilbsuccess, stats.ilbtriggers)); + PRT (" levels: %15" PRId64 " %10.2f per reuse", + stats.levelsreused, + relative (stats.levelsreused, stats.ilbsuccess)); + PRT (" literals: %15" PRId64 " %10.2f per reuse", + stats.literalsreused, + relative (stats.literalsreused, stats.ilbsuccess)); + PRT (" assumptions: %15" PRId64 " %10.2f per reuse", + stats.assumptionsreused, + relative (stats.assumptionsreused, stats.ilbsuccess)); + } + if (all || vivified) { + PRT ("vivified: %15" PRId64 " %10.2f %% of all clauses", + vivified, percent (vivified, stats.added.total)); + PRT (" vivifications: %15" PRId64 " %10.2f interval", + stats.vivifications, + relative (stats.conflicts, stats.vivifications)); + PRT (" vivifychecks: %15" PRId64 " %10.2f %% per conflict", + stats.vivifychecks, percent (stats.vivifychecks, stats.conflicts)); + PRT (" vivifysched: %15" PRId64 " %10.2f %% checks per scheduled", + stats.vivifysched, + percent (stats.vivifychecks, stats.vivifysched)); + PRT (" vivifyunits: %15" PRId64 " %10.2f %% per vivify check", + stats.vivifyunits, + percent (stats.vivifyunits, stats.vivifychecks)); + PRT (" vivifyinst: %15" PRId64 " %10.2f %% per vivify check", + stats.vivifyinst, percent (stats.vivifyinst, stats.vivifychecks)); + PRT (" vivifysubs: %15" PRId64 " %10.2f %% per subsumed", + stats.vivifysubs, percent (stats.vivifysubs, stats.subsumed)); + PRT (" vivifysubred: %15" PRId64 " %10.2f %% per subs", + stats.vivifysubred, + percent (stats.vivifysubred, stats.vivifysubs)); + PRT (" vivifysubirr: %15" PRId64 " %10.2f %% per subs", + stats.vivifysubirr, + percent (stats.vivifysubirr, stats.vivifysubs)); + PRT (" vivifystrs: %15" PRId64 " %10.2f %% per strengthened", + stats.vivifystrs, percent (stats.vivifystrs, stats.strengthened)); + PRT (" vivifystrirr: %15" PRId64 " %10.2f %% per vivifystrs", + stats.vivifystrirr, + percent (stats.vivifystrirr, stats.vivifystrs)); + PRT (" vivifystred1: %15" PRId64 " %10.2f %% per vivifystrs", + stats.vivifystred1, + percent (stats.vivifystred1, stats.vivifystrs)); + PRT (" vivifystred2: %15" PRId64 " %10.2f %% per viviyfstrs", + stats.vivifystred2, + percent (stats.vivifystred2, stats.vivifystrs)); + PRT (" vivifystred3: %15" PRId64 " %10.2f %% per vivifystrs", + stats.vivifystred3, + percent (stats.vivifystred3, stats.vivifystrs)); + PRT (" vivifydemote: %15" PRId64 " %10.2f %% per vivifystrs", + stats.vivifydemote, + percent (stats.vivifydemote, stats.vivifystrs)); + PRT (" vivifydecs: %15" PRId64 " %10.2f per checks", + stats.vivifydecs, relative (stats.vivifydecs, stats.vivifychecks)); + PRT (" vivifyreused: %15" PRId64 " %10.2f %% per decision", + stats.vivifyreused, + percent (stats.vivifyreused, stats.vivifydecs)); + } + if (all || stats.walk.count) { + PRT ("walked: %15" PRId64 " %10.2f interval", + stats.walk.count, relative (stats.conflicts, stats.walk.count)); +#ifndef CADICAL_QUIET + if (internal->profiles.walk.value > 0) + PRT (" flips: %15" PRId64 " %10.2f M per second", + stats.walk.flips, + relative (1e-6 * stats.walk.flips, + internal->profiles.walk.value)); + else +#endif + PRT (" flips: %15" PRId64 " %10.2f per walk", + stats.walk.flips, relative (stats.walk.flips, stats.walk.count)); + if (stats.walk.minimum < LONG_MAX) + PRT (" minimum: %15" PRId64 " %10.2f %% clauses", + stats.walk.minimum, + percent (stats.walk.minimum, stats.added.irredundant)); + PRT (" broken: %15" PRId64 " %10.2f per flip", + stats.walk.broken, relative (stats.walk.broken, stats.walk.flips)); + } + if (all || stats.weakened) { + PRT ("weakened: %15" PRId64 " %10.2f average size", + stats.weakened, relative (stats.weakenedlen, stats.weakened)); + PRT (" extensions: %15" PRId64 " %10.2f interval", + stats.extensions, relative (stats.conflicts, stats.extensions)); + PRT (" flipped: %15" PRId64 " %10.2f per weakened", + stats.extended, relative (stats.extended, stats.weakened)); + } + + if (all || stats.congruence.gates) { + PRT ("congruence: %15" PRId64 " %10.2f interval", + stats.congruence.rounds, + relative (stats.conflicts, stats.congruence.rounds)); + PRT (" units: %15" PRId64 " %10.2f per congruent", + stats.congruence.units, + relative (stats.congruence.units, stats.congruence.congruent)); + PRT (" cong-and: %15" PRId64 " %10.2f per found gates", + stats.congruence.ands, + relative (stats.congruence.ands, stats.congruence.gates)); + PRT (" cong-ite: %15" PRId64 " %10.2f per found gates", + stats.congruence.ites, + relative (stats.congruence.ites, stats.congruence.gates)); + PRT (" cong-xor: %15" PRId64 " %10.2f per found gates", + stats.congruence.xors, + relative (stats.congruence.xors, stats.congruence.gates)); + PRT (" congruent: %15" PRId64 " %10.2f per round", + stats.congruence.congruent, + relative (stats.congruence.rounds, stats.congruence.congruent)); + PRT (" unaries: %15" PRId64 " %10.2f per round", + stats.congruence.unaries, + relative (stats.congruence.rounds, stats.congruence.unaries)); + PRT (" rewri.-ands: %15" PRId64 " %10.2f per round", + stats.congruence.rewritten_ands, + relative (stats.congruence.rounds, + stats.congruence.rewritten_ands)); + PRT (" subsumed: %15" PRId64 " %10.2f per round", + stats.congruence.subsumed, + relative (stats.congruence.rounds, stats.congruence.subsumed)); + } + + LINE (); + MSG ("%sseconds are measured in %s time for solving%s", + tout.magenta_code (), internal->opts.realtime ? "real" : "process", + tout.normal_code ()); + +#endif // ifndef CADICAL_QUIET +} + +void Internal::print_resource_usage () { +#ifndef CADICAL_QUIET + SECTION ("resources"); + uint64_t m = maximum_resident_set_size (); + MSG ("total process time since initialization: %12.2f seconds", + internal->process_time ()); + MSG ("total real time since initialization: %12.2f seconds", + internal->real_time ()); + MSG ("maximum resident set size of process: %12.2f MB", + m / (double) (1l << 20)); +#endif +} + +/*------------------------------------------------------------------------*/ + +void Checker::print_stats () { + + if (!stats.added && !stats.deleted) + return; + + SECTION ("checker statistics"); + + MSG ("checks: %15" PRId64 "", stats.checks); + MSG ("assumptions: %15" PRId64 " %10.2f per check", + stats.assumptions, relative (stats.assumptions, stats.checks)); + MSG ("propagations: %15" PRId64 " %10.2f per check", + stats.propagations, relative (stats.propagations, stats.checks)); + MSG ("original: %15" PRId64 " %10.2f %% of all clauses", + stats.original, percent (stats.original, stats.added)); + MSG ("derived: %15" PRId64 " %10.2f %% of all clauses", + stats.derived, percent (stats.derived, stats.added)); + MSG ("deleted: %15" PRId64 " %10.2f %% of all clauses", + stats.deleted, percent (stats.deleted, stats.added)); + MSG ("insertions: %15" PRId64 " %10.2f %% of all clauses", + stats.insertions, percent (stats.insertions, stats.added)); + MSG ("collections: %15" PRId64 " %10.2f deleted per collection", + stats.collections, relative (stats.collections, stats.deleted)); + MSG ("collisions: %15" PRId64 " %10.2f per search", + stats.collisions, relative (stats.collisions, stats.searches)); + MSG ("searches: %15" PRId64 "", stats.searches); + MSG ("units: %15" PRId64 "", stats.units); +} + +void LratChecker::print_stats () { + + if (!stats.added && !stats.deleted) + return; + + SECTION ("lrat checker statistics"); + + MSG ("checks: %15" PRId64 "", stats.checks); + MSG ("insertions: %15" PRId64 " %10.2f %% of all clauses", + stats.insertions, percent (stats.insertions, stats.added)); + MSG ("original: %15" PRId64 " %10.2f %% of all clauses", + stats.original, percent (stats.original, stats.added)); + MSG ("derived: %15" PRId64 " %10.2f %% of all clauses", + stats.derived, percent (stats.derived, stats.added)); + MSG ("deleted: %15" PRId64 " %10.2f %% of all clauses", + stats.deleted, percent (stats.deleted, stats.added)); + MSG ("finalized: %15" PRId64 " %10.2f %% of all clauses", + stats.finalized, percent (stats.finalized, stats.added)); + MSG ("collections: %15" PRId64 " %10.2f deleted per collection", + stats.collections, relative (stats.collections, stats.deleted)); + MSG ("collisions: %15" PRId64 " %10.2f per search", + stats.collisions, relative (stats.collisions, stats.searches)); + MSG ("searches: %15" PRId64 "", stats.searches); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_subsume.cpp b/src/sat/cadical/cadical_subsume.cpp new file mode 100644 index 000000000..b75040ca8 --- /dev/null +++ b/src/sat/cadical/cadical_subsume.cpp @@ -0,0 +1,652 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This file implements a global forward subsumption algorithm, which is run +// frequently during search. It works both on original (irredundant) +// clauses and on 'sticky' learned clauses which are likely to be kept. +// This is abstracted away in the 'likely_to_be_kept_clause' function, which +// implicitly relies on 'opts.reducetier1glue' (glucose level of clauses +// which are not reduced) as well as dynamically determined size and glucose +// level ('lim.keptglue' and 'lim.keptsize') of clauses kept in 'reduce'. +// +// Note, that 'forward' means that the clause from which the subsumption +// check is started is checked for being subsumed by other (smaller or equal +// size) clauses. Since 'vivification' is an extended version of subsume, +// more powerful, but also slower, we schedule 'vivify' right after +// 'subsume', which in contrast to 'subsume' is not run until to completion. +// +// This implementation is inspired by Bayardo's SDM'11 analysis of our +// subsumption algorithm in our SATeLite preprocessor in the context of +// finding extremal sets in data mining and his suggested improvements. + +// Our original subsumption algorithm in 'Quantor' and 'SATeLite' (and in +// MiniSAT and descendants) is based on backward subsumption. It uses the +// observation that only the occurrence list of one literal of a clause has +// to be traversed in order to find all potential clauses which are subsumed +// by the candidate. Thus the literal with the smallest number of +// occurrences is used. However, that scheme requires to connect all +// literals of surviving clauses, while forward algorithms only need to +// connect one literal. On the other hand forward checking requires to +// traverse the occurrence lists of all literals of the candidate clause to +// find subsuming clauses. During connecting the single literal (similar to +// the one-watch scheme by Lintao Zhang) one can connect a literal with a +// minimal number of occurrence so far, which implicitly also reduces future +// occurrence list traversal time. + +// Also the actual subsumption check is cheaper since during backward +// checking the short subsuming candidate clause is marked and all the +// literals in the larger subsume candidate clauses have to be traversed, +// while for our forward approach the long subsumed candidate clause is only +// marked once, while the literals of the shorter subsuming clauses have to +// be checked. We also use a fixed special more cache friendly data +// structure for binary clauses, to avoid traversing them directly. + +// In our forward scheme it is still possible to skip occurrence lists of +// literals which were not added since the last subsumption round, since +// only those can contain subsuming candidates. Actually, clauses which +// contain at least one literal, which was not added since the last +// subsumption round do not have to be connected at all, even though they +// might still be subsumed them self. + +// Bayardo suggests to sort the literals in clauses and perform some kind of +// partial merge-sort, while we mark literals, but do sort literals during +// connecting a clause w.r.t. the number of occurrences, in order to find +// literals which do not occur in the subsumed candidate fast with high +// probability (less occurring literals have a higher chance). + +// This is the actual subsumption and strengthening check. We assume that +// all the literals of the candidate clause to be subsumed or strengthened +// are marked, so we only have to check that all the literals of the +// argument clause 'subsuming', which is checked for subsuming the candidate +// clause 'subsumed', has all its literals marked (in the correct phase). +// If exactly one is in the opposite phase we can still strengthen the +// candidate clause by this single literal which occurs in opposite phase. +// +// The result is INT_MIN if all literals are marked and thus the candidate +// clause can be subsumed. It is zero if neither subsumption nor +// strengthening is possible. Otherwise the candidate clause can be +// strengthened and as a result the negation of the literal which can be +// removed is returned. + +inline int Internal::subsume_check (Clause *subsuming, Clause *subsumed) { +#ifdef CADICAL_NDEBUG + (void) subsumed; +#endif + // Only use 'subsumed' for these following CADICAL_assertion checks. Otherwise we + // only require that 'subsumed' has all its literals marked. + // + CADICAL_assert (!subsumed->garbage); + CADICAL_assert (!subsuming->garbage); + CADICAL_assert (subsuming != subsumed); + CADICAL_assert (subsuming->size <= subsumed->size); + + stats.subchecks++; + if (subsuming->size == 2) + stats.subchecks2++; + + int flipped = 0, prev = 0; + bool failed = false; + const auto eoc = subsuming->end (); + for (auto i = subsuming->begin (); !failed && i != eoc; i++) { + int lit = *i; + *i = prev; + prev = lit; + const int tmp = marked (lit); + if (!tmp) + failed = true; + else if (tmp > 0) + continue; + else if (flipped) + failed = true; + else + flipped = lit; + } + CADICAL_assert (prev); + CADICAL_assert (!subsuming->literals[0]); + subsuming->literals[0] = prev; + if (failed) + return 0; + + if (!flipped) + return INT_MIN; // subsumed!! + else if (!opts.subsumestr) + return 0; + else + return flipped; // strengthen!! +} + +/*------------------------------------------------------------------------*/ + +// Candidate clause 'subsumed' is subsumed by 'subsuming'. + +inline void Internal::subsume_clause (Clause *subsuming, Clause *subsumed) { + stats.subsumed++; + CADICAL_assert (subsuming->size <= subsumed->size); + LOG (subsumed, "subsumed"); + if (subsumed->redundant) + stats.subred++; + else + stats.subirr++; + if (subsumed->redundant || !subsuming->redundant) { + mark_garbage (subsumed); + return; + } + LOG ("turning redundant subsuming clause into irredundant clause"); + subsuming->redundant = false; + if (proof) + proof->strengthen (subsuming->id); + mark_garbage (subsumed); + stats.current.irredundant++; + stats.added.irredundant++; + stats.irrlits += subsuming->size; + CADICAL_assert (stats.current.redundant > 0); + stats.current.redundant--; + CADICAL_assert (stats.added.redundant > 0); + stats.added.redundant--; + // ... and keep 'stats.added.total'. +} + +/*------------------------------------------------------------------------*/ + +// Candidate clause 'c' is strengthened by removing 'lit'. + +void Internal::strengthen_clause (Clause *c, int lit) { + if (opts.check && is_external_forgettable (c->id)) + mark_garbage_external_forgettable (c->id); + stats.strengthened++; + CADICAL_assert (c->size > 2); + LOG (c, "removing %d in", lit); + if (proof) { + LOG (lrat_chain, "strengthening clause with chain"); + proof->strengthen_clause (c, lit, lrat_chain); + } + if (!c->redundant) + mark_removed (lit); + auto new_end = remove (c->begin (), c->end (), lit); + CADICAL_assert (new_end + 1 == c->end ()), (void) new_end; + (void) shrink_clause (c, c->size - 1); + // bump_clause2 (c); + LOG (c, "strengthened"); + external->check_shrunken_clause (c); +} + +/*------------------------------------------------------------------------*/ + +// Find clauses connected in the occurrence lists 'occs' which subsume the +// candidate clause 'c' given as first argument. If this is the case the +// clause is subsumed and the result is positive. If the clause was +// strengthened the result is negative. Otherwise the candidate clause +// can not be subsumed nor strengthened and zero is returned. + +inline int Internal::try_to_subsume_clause (Clause *c, + vector &shrunken) { + + stats.subtried++; + CADICAL_assert (!level); + LOG (c, "trying to subsume"); + + mark (c); // signed! + + Clause *d = 0; + int flipped = 0; + + for (const auto &lit : *c) { + + // Only clauses which have a variable which has recently been added are + // checked for being subsumed. The idea is that all these newly added + // clauses are candidates for subsuming the clause. Then we also only + // need to check occurrences of these variables. The occurrence lists + // of other literal do not have to be checked. + // + if (!flags (lit).subsume) + continue; + + for (int sign = -1; !d && sign <= 1; sign += 2) { + + // First we check against all binary clauses. The other literals of + // all binary clauses of 'sign*lit' are stored in one consecutive + // array, which is way faster than storing clause pointers and + // dereferencing them. Since this binary clause array is also not + // shrunken, we also can bail out earlier if subsumption or + // strengthening is determined. + + // In both cases the (self-)subsuming clause is stored in 'd', which + // makes it nonzero and forces aborting both the outer and inner loop. + // If the binary clause can strengthen the candidate clause 'c' + // (through self-subsuming resolution), then 'filled' is set to the + // literal which can be removed in 'c', otherwise to 'INT_MIN' which + // is a non-valid literal. + + for (const auto &bin : bins (sign * lit)) { + const auto &other = bin.lit; + const int tmp = marked (other); + if (!tmp) + continue; + if (tmp < 0 && sign < 0) + continue; + if (tmp < 0) { + if (sign < 0) + continue; // tautological resolvent + dummy_binary->literals[0] = lit; + dummy_binary->literals[1] = other; + flipped = other; + } else { + dummy_binary->literals[0] = sign * lit; + dummy_binary->literals[1] = other; + flipped = (sign < 0) ? -lit : INT_MIN; + } + + // This dummy binary clauses is initialized in 'Internal::Internal' + // and only changes it literals in the lines above. By using such + // a faked binary clause we can simply reuse 'subsume_clause' as + // well as the code around 'strengthen_clause' uniform for both real + // clauses and this special case for binary clauses + + dummy_binary->id = bin.id; + d = dummy_binary; + + break; + } + + if (d) + break; + + // In this second loop we check for larger than binary clauses to + // subsume or strengthen the candidate clause. This is more costly, + // and needs a call to 'subsume_check'. Otherwise the same contract + // as above for communicating 'subsumption' or 'strengthening' to the + // code after the loop is used. + // + const Occs &os = occs (sign * lit); + for (const auto &e : os) { + CADICAL_assert (!e->garbage); // sanity check + if (e->garbage) + continue; // defensive: not needed + flipped = subsume_check (e, c); + if (!flipped) + continue; + d = e; // leave also outer loop + break; + } + } + + if (d) + break; + } + + unmark (c); + + if (flipped == INT_MIN) { + LOG (d, "subsuming"); + subsume_clause (d, c); + return 1; + } + + if (flipped) { + LOG (d, "strengthening"); + if (lrat) { + CADICAL_assert (lrat_chain.empty ()); + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + } + if (d->used > c->used) + c->used = d->used; + strengthen_clause (c, -flipped); + lrat_chain.clear (); + CADICAL_assert (likely_to_be_kept_clause (c)); + shrunken.push_back (c); + return -1; + } + + return 0; +} + + +struct subsume_less_noccs { + Internal *internal; + subsume_less_noccs (Internal *i) : internal (i) {} + bool operator() (int a, int b) { + const signed char u = internal->val (a), v = internal->val (b); + if (!u && v) + return true; + if (u && !v) + return false; + const int64_t m = internal->noccs (a), n = internal->noccs (b); + if (m < n) + return true; + if (m > n) + return false; + return abs (a) < abs (b); + } +}; + +/*------------------------------------------------------------------------*/ + +// Usually called from 'subsume' below if 'subsuming' triggered it. Then +// the idea is to subsume both redundant and irredundant clauses. It is also +// called in the elimination loop in 'elim' in which case we focus on +// irredundant clauses only to help bounded variable elimination. The +// function returns true of an irredundant clause was removed or +// strengthened, which might then in the second usage scenario trigger new +// variable eliminations. + +bool Internal::subsume_round () { + + if (!opts.subsume) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + if (!stats.current.redundant && !stats.current.irredundant) + return false; + + START_SIMPLIFIER (subsume, SUBSUME); + stats.subsumerounds++; + + int64_t check_limit; + if (opts.subsumelimited) { + int64_t delta = stats.propagations.search; + delta *= 1e-3 * opts.subsumeeffort; + if (delta < opts.subsumemineff) + delta = opts.subsumemineff; + if (delta > opts.subsumemaxeff) + delta = opts.subsumemaxeff; + delta = max (delta, (int64_t) 2l * active ()); + + PHASE ("subsume-round", stats.subsumerounds, + "limit of %" PRId64 " subsumption checks", delta); + + check_limit = stats.subchecks + delta; + } else { + PHASE ("subsume-round", stats.subsumerounds, + "unlimited subsumption checks"); + check_limit = LONG_MAX; + } + + int old_marked_candidate_variables_for_elimination = stats.mark.elim; + + CADICAL_assert (!level); + + // Allocate schedule and occurrence lists. + // + vector schedule; + init_noccs (); + + // Determine candidate clauses and sort them by size. + // + int64_t left_over_from_last_subsumption_round = 0; + + for (auto c : clauses) { + + if (c->garbage) + continue; + if (c->size > opts.subsumeclslim) + continue; + if (!likely_to_be_kept_clause (c)) + continue; + + bool fixed = false; + int subsume = 0; + for (const auto &lit : *c) + if (val (lit)) + fixed = true; + else if (flags (lit).subsume) + subsume++; + + // If the clause contains a root level assigned (fixed) literal we will + // not work on it. This simplifies the code substantially since we do + // not have to care about assignments at all. Strengthening becomes + // much simpler too. + // + if (fixed) { + LOG (c, "skipping (fixed literal)"); + continue; + } + + // Further, if less than two variables in the clause were added since + // the last subsumption round, the clause is ignored too. + // + if (subsume < 2) { + LOG (c, "skipping (only %d added literals)", subsume); + continue; + } + + if (c->subsume) + left_over_from_last_subsumption_round++; + schedule.push_back (ClauseSize (c->size, c)); + for (const auto &lit : *c) + noccs (lit)++; + } + shrink_vector (schedule); + + // Smaller clauses are checked and connected first. + // + rsort (schedule.begin (), schedule.end (), smaller_clause_size_rank ()); + + if (!left_over_from_last_subsumption_round) + for (auto cs : schedule) + if (cs.clause->size > 2) + cs.clause->subsume = true; + +#ifndef CADICAL_QUIET + int64_t scheduled = schedule.size (); + int64_t total = stats.current.irredundant + stats.current.redundant; + PHASE ("subsume-round", stats.subsumerounds, + "scheduled %" PRId64 " clauses %.0f%% out of %" PRId64 " clauses", + scheduled, percent (scheduled, total), total); +#endif + + // Now go over the scheduled clauses in the order of increasing size and + // try to forward subsume and strengthen them. Forward subsumption tries + // to find smaller or same size clauses which subsume or might strengthen + // the candidate. After the candidate has been processed connect one + // of its literals (with smallest number of occurrences at this point) in + // a one-watched scheme. + + int64_t subsumed = 0, strengthened = 0, checked = 0; + + vector shrunken; + init_occs (); + init_bins (); + + for (const auto &s : schedule) { + + if (terminated_asynchronously ()) + break; + if (stats.subchecks >= check_limit) + break; + + Clause *c = s.clause; + CADICAL_assert (!c->garbage); + + checked++; + + // First try to subsume or strengthen this candidate clause. For binary + // clauses this could be done much faster by hashing and is costly due + // to a usually large number of binary clauses. There is further the + // issue, that strengthening binary clauses (through double + // self-subsuming resolution) would produce units, which needs much more + // care. In the same (lazy) spirit we also ignore clauses with fixed + // literals (false or true). + // + if (c->size > 2 && c->subsume) { + c->subsume = false; + const int tmp = try_to_subsume_clause (c, shrunken); + if (tmp > 0) { + subsumed++; + continue; + } + if (tmp < 0) + strengthened++; + } + + // If not subsumed connect smallest occurring literal, where occurring + // means the number of times it was used to connect (as a one-watch) a + // previous smaller or equal sized clause. This minimizes the length of + // the occurrence lists traversed during 'try_to_subsume_clause'. Also + // note that this number is usually way smaller than the number of + // occurrences computed before and stored in 'noccs'. + // + int minlit = 0; + int64_t minoccs = 0; + size_t minsize = 0; + bool subsume = true; + bool binary = (c->size == 2 && !c->redundant); + + for (const auto &lit : *c) { + + if (!flags (lit).subsume) + subsume = false; + const size_t size = binary ? bins (lit).size () : occs (lit).size (); + if (minlit && minsize <= size) + continue; + const int64_t tmp = noccs (lit); + if (minlit && minsize == size && tmp <= minoccs) + continue; + minlit = lit, minsize = size, minoccs = tmp; + } + + // If there is a variable in a clause different from is not 'subsume' + // (has been added since the last subsumption round), then this clause + // can not serve to strengthen or subsume another clause, since all + // shrunken or added clauses mark all their variables as 'subsume'. + // + if (!subsume) + continue; + + if (!binary) { + + // If smallest occurring literal occurs too often do not connect. + // + if (minsize > (size_t) opts.subsumeocclim) + continue; + + LOG (c, + "watching %d with %zd current and total %" PRId64 " occurrences", + minlit, minsize, minoccs); + + occs (minlit).push_back (c); + + // This sorting should give faster failures for assumption checks + // since the less occurring variables are put first in a clause and + // thus will make it more likely to be found as witness for a clause + // not to be subsuming. One could in principle (see also the + // discussion on 'subsumption' in our 'Splatz' solver) replace marking + // by a kind of merge sort, as also suggested by Bayardo. It would + // avoid 'marked' calls and thus might be slightly faster but could + // not take benefit of this sorting optimization. + // + sort (c->begin (), c->end (), subsume_less_noccs (this)); + + } else { + + // If smallest occurring literal occurs too often do not connect. + // + if (minsize > (size_t) opts.subsumebinlim) + continue; + + LOG (c, + "watching %d with %zd current binary and total %" PRId64 + " occurrences", + minlit, minsize, minoccs); + + const int minlit_pos = (c->literals[1] == minlit); + const int other = c->literals[!minlit_pos]; + bins (minlit).push_back (Bin{other, c->id}); + } + } + + PHASE ("subsume-round", stats.subsumerounds, + "subsumed %" PRId64 " and strengthened %" PRId64 " out of %" PRId64 + " clauses %.0f%%", + subsumed, strengthened, scheduled, + percent (subsumed + strengthened, scheduled)); + + const int64_t remain = schedule.size () - checked; + const bool completed = !remain; + + if (completed) + PHASE ("subsume-round", stats.subsumerounds, + "checked all %" PRId64 " scheduled clauses", checked); + else + PHASE ("subsume-round", stats.subsumerounds, + "checked %" PRId64 " clauses %.0f%% of scheduled (%" PRId64 + " remain)", + checked, percent (checked, scheduled), remain); + + // Release occurrence lists and schedule. + // + erase_vector (schedule); + reset_noccs (); + reset_occs (); + reset_bins (); + + // Reset all old 'added' flags and mark variables in shrunken + // clauses as 'added' for the next subsumption round. + // + if (completed) + reset_subsume_bits (); + + for (const auto &c : shrunken) + mark_added (c); + erase_vector (shrunken); + + report ('s', !opts.reportall && !(subsumed + strengthened)); + + STOP_SIMPLIFIER (subsume, SUBSUME); + + return old_marked_candidate_variables_for_elimination < stats.mark.elim; +} + +/*------------------------------------------------------------------------*/ + +void Internal::subsume () { + + if (!stats.current.redundant && !stats.current.irredundant) + return; + + if (unsat) + return; + + backtrack (); + if (!propagate ()) { + learn_empty_clause (); + return; + } + + stats.subsumephases++; + + if (external_prop) { + CADICAL_assert (!level); + private_steps = true; + } + + if (opts.subsume) { + reset_watches (); + subsume_round (); + init_watches (); + connect_watches (); + if (!unsat && !propagate ()) { + LOG ("propagation after subsume rounds results in inconsistency"); + learn_empty_clause (); + } + } + + transred (); + if (external_prop) { + CADICAL_assert (!level); + private_steps = false; + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_sweep.cpp b/src/sat/cadical/cadical_sweep.cpp new file mode 100644 index 000000000..4dca2b4bc --- /dev/null +++ b/src/sat/cadical/cadical_sweep.cpp @@ -0,0 +1,1959 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +Sweeper::Sweeper (Internal *i) + : internal (i), random (internal->opts.seed) { + random += internal->stats.sweep; // different seed every time + internal->init_sweeper (*this); +} + +Sweeper::~Sweeper () { + // this is already called actively + // internal->release_sweeper (this); + return; +} + +#define INVALID64 INT64_MAX +#define INVALID UINT_MAX + +int Internal::sweep_solve () { + START (sweepsolve); + cadical_kitten_randomize_phases (citten); + stats.sweep_solved++; + int res = cadical_kitten_solve (citten); + if (res == 10) + stats.sweep_sat++; + if (res == 20) + stats.sweep_unsat++; + STOP (sweepsolve); + return res; +} + +bool Internal::sweep_flip (int lit) { + START (sweepflip); + bool res = cadical_kitten_flip_signed_literal (citten, lit); + STOP (sweepflip); + return res; +} + +int Internal::sweep_flip_and_implicant (int lit) { + START (sweepimplicant); + int res = cadical_kitten_flip_and_implicant_for_signed_literal (citten, lit); + STOP (sweepimplicant); + return res; +} + +void Internal::sweep_set_cadical_kitten_ticks_limit (Sweeper &sweeper) { + uint64_t remaining = 0; + const uint64_t current = sweeper.current_ticks; + if (current < sweeper.limit.ticks) + remaining = sweeper.limit.ticks - current; + LOG ("'cadical_kitten_ticks' remaining %" PRIu64, remaining); + cadical_kitten_set_ticks_limit (citten, remaining); +} + +void Internal::sweep_update_noccs (Clause *c) { + if (c->redundant) + return; + for (const auto &lit : *c) { + noccs (lit)--; + } +} + +bool Internal::can_sweep_clause (Clause *c) { + if (c->garbage) + return false; + if (!c->redundant) + return true; + return c->size == 2; // && !c->hyper; // could ignore hyper +} + +// essentially do full occurence list as in elim.cpp +void Internal::sweep_dense_mode_and_watch_irredundant () { + reset_watches (); + + init_noccs (); + + // mark satisfied irredundant clauses as garbage + for (const auto &c : clauses) { + if (!can_sweep_clause (c)) + continue; + bool satisfied = false; + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp <= 0) + continue; + if (tmp > 0) { + satisfied = true; + break; + } + } + if (satisfied) + mark_garbage (c); // forces more precise counts + else { + for (const auto &lit : *c) + noccs (lit)++; + } + } + + init_occs (); + + // Connect irredundant clauses. + // + for (const auto &c : clauses) { + if (!c->garbage) { + for (const auto &lit : *c) + if (active (lit)) + occs (lit).push_back (c); + } else if (c->size == 2) { + if (!c->flushed) { + if (proof) { + c->flushed = true; + proof->delete_clause (c); + } + } + } + } +} + +// go back to two watch scheme +void Internal::sweep_sparse_mode () { + reset_occs (); + reset_noccs (); + init_watches (); + connect_watches (); +} + +// propagate without watches but full occurence list +void Internal::sweep_dense_propagate (Sweeper &sweeper) { + vector &work = sweeper.propagate; + size_t i = 0; + uint64_t &ticks = sweeper.current_ticks; + while (i < work.size ()) { + int lit = work[i++]; + LOG ("sweeping propagation of %d", lit); + CADICAL_assert (val (lit) > 0); + ticks += 1 + cache_lines (occs (-lit).size (), sizeof (Clause *)); + const Occs &ns = occs (-lit); + for (const auto &c : ns) { + ticks++; + if (!can_sweep_clause (c)) + continue; + int unit = 0, satisfied = 0; + for (const auto &other : *c) { + const signed char tmp = val (other); + if (tmp < 0) + continue; + if (tmp > 0) { + satisfied = other; + break; + } + if (unit) + unit = INT_MIN; + else + unit = other; + } + if (satisfied) { + LOG (c, "sweeping propagation of %d finds %d satisfied", lit, + satisfied); + mark_garbage (c); + sweep_update_noccs (c); + } else if (!unit) { + LOG ("empty clause during sweeping propagation of %d", lit); + // need to set conflict = c for lrat + conflict = c; + learn_empty_clause (); + conflict = 0; + break; + } else if (unit != INT_MIN) { + LOG ("new unit %d during sweeping propagation of %d", unit, lit); + build_chain_for_units (unit, c, 0); + assign_unit (unit); + work.push_back (unit); + ticks++; + } + } + if (unsat) + break; + + // not necessary but should help + ticks += 1 + cache_lines (occs (lit).size (), sizeof (Clause *)); + const Occs &ps = occs (lit); + for (const auto &c : ps) { + ticks++; + if (c->garbage) + continue; + // if (c->redundant) // TODO I assume it does not hurt to mark + // everything here continue; + LOG (c, "sweeping propagation of %d produces satisfied", lit); + mark_garbage (c); + sweep_update_noccs (c); + } + } + work.clear (); +} + +bool Internal::cadical_kitten_ticks_limit_hit (Sweeper &sweeper, const char *when) { + const uint64_t current = + cadical_kitten_current_ticks (citten) + sweeper.current_ticks; + if (current >= sweeper.limit.ticks) { + LOG ("'cadical_kitten_ticks' limit of %" PRIu64 " ticks hit after %" PRIu64 + " ticks during %s", + sweeper.limit.ticks, current, when); + return true; + } +#ifndef LOGGING + (void) when; +#endif + return false; +} + +void Internal::init_sweeper (Sweeper &sweeper) { + sweeper.encoded = 0; + enlarge_zero (sweeper.depths, max_var + 1); + sweeper.reprs = new int[2 * max_var + 1]; + sweeper.reprs += max_var; + enlarge_zero (sweeper.prev, max_var + 1); + enlarge_zero (sweeper.next, max_var + 1); + for (const auto &lit : lits) + sweeper.reprs[lit] = lit; + sweeper.first = sweeper.last = 0; + sweeper.current_ticks = + 2 * + clauses + .size (); // initialize with the cost of building full occ list. + sweeper.current_ticks += + 2 + 2 * cache_lines (clauses.size (), sizeof (Clause *)); + CADICAL_assert (!citten); + citten = cadical_kitten_init (); + citten_clear_track_log_terminate (); + + sweep_dense_mode_and_watch_irredundant (); // full occurence list + + if (lrat) { + sweeper.prev_units.push_back (0); + for (const auto &idx : vars) { + sweeper.prev_units.push_back (val (idx) != 0); + } + } + + unsigned completed = stats.sweep_completed; + const unsigned max_completed = 32; + if (completed > max_completed) + completed = max_completed; + + uint64_t vars_limit = opts.sweepvars; + vars_limit <<= completed; + const unsigned max_vars_limit = opts.sweepmaxvars; + if (vars_limit > max_vars_limit) + vars_limit = max_vars_limit; + sweeper.limit.vars = vars_limit; + VERBOSE (3, "sweeper variable limit %u", sweeper.limit.vars); + + uint64_t depth_limit = stats.sweep_completed; + depth_limit += opts.sweepdepth; + const unsigned max_depth = opts.sweepmaxdepth; + if (depth_limit > max_depth) + depth_limit = max_depth; + sweeper.limit.depth = depth_limit; + VERBOSE (3, "sweeper depth limit %u", sweeper.limit.depth); + + uint64_t clause_limit = opts.sweepclauses; + clause_limit <<= completed; + const unsigned max_clause_limit = opts.sweepmaxclauses; + if (clause_limit > max_clause_limit) + clause_limit = max_clause_limit; + sweeper.limit.clauses = clause_limit; + VERBOSE (3, "sweeper clause limit %u", sweeper.limit.clauses); +} + +void Internal::release_sweeper (Sweeper &sweeper) { + + sweeper.reprs -= max_var; + delete[] sweeper.reprs; + + erase_vector (sweeper.depths); + erase_vector (sweeper.prev); + erase_vector (sweeper.next); + erase_vector (sweeper.vars); + erase_vector (sweeper.clause); + erase_vector (sweeper.backbone); + erase_vector (sweeper.partition); + erase_vector (sweeper.prev_units); + for (unsigned i = 0; i < 2; i++) + erase_vector (sweeper.core[i]); + + cadical_kitten_release (citten); + citten = 0; + stats.ticks.sweep += sweeper.current_ticks; + sweep_sparse_mode (); + return; +} + +void Internal::clear_sweeper (Sweeper &sweeper) { + LOG ("clearing sweeping environment"); + sweeper.current_ticks += cadical_kitten_current_ticks (citten); + + citten_clear_track_log_terminate (); + for (auto &idx : sweeper.vars) { + CADICAL_assert (sweeper.depths[idx]); + sweeper.depths[idx] = 0; + } + sweeper.vars.clear (); + for (auto c : sweeper.clauses) { + CADICAL_assert (c->swept); + c->swept = false; + } + sweeper.clauses.clear (); + sweeper.backbone.clear (); + sweeper.partition.clear (); + sweeper.encoded = 0; + sweep_set_cadical_kitten_ticks_limit (sweeper); +} + +int Internal::sweep_repr (Sweeper &sweeper, int lit) { + int res; + { + int prev = lit; + while ((res = sweeper.reprs[prev]) != prev) + prev = res; + } + if (res == lit) + return res; + LOG ("sweeping repr[%d] = %d", lit, res); + { + const int not_res = -res; + int next, prev = lit; + while ((next = sweeper.reprs[prev]) != res) { + const int not_prev = -prev; + sweeper.reprs[not_prev] = not_res; + sweeper.reprs[prev] = res; + prev = next; + } + CADICAL_assert (sweeper.reprs[-prev] == not_res); + } + return res; +} + +void Internal::add_literal_to_environment (Sweeper &sweeper, unsigned depth, + int lit) { + const int repr = sweep_repr (sweeper, lit); + if (repr != lit) + return; + const int idx = abs (lit); + if (sweeper.depths[idx]) + return; + CADICAL_assert (depth < UINT_MAX); + sweeper.depths[idx] = depth + 1; + CADICAL_assert (idx); + sweeper.vars.push_back (idx); + LOG ("sweeping[%u] adding literal %d", depth, lit); +} + +void Internal::sweep_add_clause (Sweeper &sweeper, unsigned depth) { + // TODO: CADICAL_assertion fails, check if this an issue or can be avoided + // CADICAL_assert (sweeper.clause.size () > 1); + for (const auto &lit : sweeper.clause) + add_literal_to_environment (sweeper, depth, lit); + citten_clause_with_id (citten, sweeper.clauses.size (), + sweeper.clause.size (), sweeper.clause.data ()); + sweeper.clause.clear (); + if (opts.sweepcountbinary || sweeper.clause.size () > 2) + sweeper.encoded++; +} + +void Internal::sweep_clause (Sweeper &sweeper, unsigned depth, Clause *c) { + if (c->swept) + return; + CADICAL_assert (can_sweep_clause (c)); + LOG (c, "sweeping[%u]", depth); + CADICAL_assert (sweeper.clause.empty ()); + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp > 0) { + mark_garbage (c); + sweep_update_noccs (c); + sweeper.clause.clear (); + return; + } + if (tmp < 0) { + if (lrat) + sweeper.prev_units[abs (lit)] = true; + continue; + } + sweeper.clause.push_back (lit); + } + c->swept = true; + sweep_add_clause (sweeper, depth); + sweeper.clauses.push_back (c); +} + +extern "C" { +static void save_core_clause (void *state, unsigned id, bool learned, + size_t size, const unsigned *lits) { + Sweeper *sweeper = (Sweeper *) state; + Internal *internal = sweeper->internal; + if (internal->unsat) + return; + vector &core = sweeper->core[sweeper->save]; + sweep_proof_clause pc; + if (learned) { + pc.sweep_id = INVALID; // necessary + pc.cad_id = INVALID64; // delay giving ids + } else { + pc.sweep_id = id; // necessary + CADICAL_assert (id < sweeper->clauses.size ()); + pc.cad_id = sweeper->clauses[id]->id; + } + pc.kit_id = 0; + pc.learned = learned; + const unsigned *end = lits + size; + for (const unsigned *p = lits; p != end; p++) { + pc.literals.push_back (internal->citten2lit (*p)); // conversion + } +#ifdef LOGGING + LOG (pc.literals, "traced %s", + pc.learned == true ? "learned" : "original"); +#endif + core.push_back (pc); +} + +static void save_core_clause_with_lrat (void *state, unsigned cid, + unsigned id, bool learned, + size_t size, const unsigned *lits, + size_t chain_size, + const unsigned *chain) { + Sweeper *sweeper = (Sweeper *) state; + Internal *internal = sweeper->internal; + if (internal->unsat) + return; + vector &core = sweeper->core[sweeper->save]; + vector &clauses = sweeper->clauses; + sweep_proof_clause pc; + pc.kit_id = cid; + pc.learned = learned; + pc.sweep_id = INVALID; + pc.cad_id = INVALID64; + if (!learned) { + CADICAL_assert (size); + CADICAL_assert (!chain_size); + CADICAL_assert (id < clauses.size ()); + pc.sweep_id = id; + CADICAL_assert (id < clauses.size ()); + pc.cad_id = clauses[id]->id; + for (const auto &lit : *clauses[id]) { + pc.literals.push_back (lit); + } + } else { + CADICAL_assert (chain_size); + pc.sweep_id = INVALID; + pc.cad_id = INVALID64; // delay giving ids + const unsigned *end = lits + size; + for (const unsigned *p = lits; p != end; p++) { + pc.literals.push_back (internal->citten2lit (*p)); // conversion + } + for (const unsigned *p = chain + chain_size; p != chain; p--) { + pc.chain.push_back (*(p - 1)); + } + } +#ifdef LOGGING + if (learned) + LOG (pc.literals, "traced %s", + pc.learned == true ? "learned" : "original"); + else { + CADICAL_assert (id < clauses.size ()); + LOG (clauses[id], "traced"); + } +#endif + core.push_back (pc); +} + +static int citten_terminate (void *data) { + return ((Internal *) data)->terminated_asynchronously (); +} + +} // end extern C + +void Internal::citten_clear_track_log_terminate () { + CADICAL_assert (citten); + cadical_kitten_clear (citten); + cadical_kitten_track_antecedents (citten); + if (external->terminator) + cadical_kitten_set_terminator (citten, internal, citten_terminate); +#ifdef LOGGING + if (opts.log) + cadical_kitten_set_logging (citten); +#endif +} + +void Internal::add_core (Sweeper &sweeper, unsigned core_idx) { + if (unsat) + return; + LOG ("check and add extracted core[%u] lemmas to proof", core_idx); + CADICAL_assert (core_idx == 0 || core_idx == 1); + vector &core = sweeper.core[core_idx]; + + CADICAL_assert (!lrat || proof); + + unsigned unsat_size = 0; + for (auto &pc : core) { + unsat_size++; + + if (!pc.learned) { + LOG (pc.literals, + "not adding already present core[%u] cadical_kitten[%u] clause", + core_idx, pc.kit_id); + continue; + } + + LOG (pc.literals, "adding extracted core[%u] cadical_kitten[%u] lemma", + core_idx, pc.kit_id); + + const unsigned new_size = pc.literals.size (); + + if (lrat) { + CADICAL_assert (pc.cad_id == INVALID64); + for (auto &cid : pc.chain) { + int64_t id = 0; + for (const auto &cpc : core) { + if (cpc.kit_id == cid) { + if (cpc.learned) + id = cpc.cad_id; + else { + id = cpc.cad_id; + CADICAL_assert (cpc.cad_id == sweeper.clauses[cpc.sweep_id]->id); + CADICAL_assert (!sweeper.clauses[cpc.sweep_id]->garbage); + // avoid duplicate ids of units with seen flags + for (const auto &lit : cpc.literals) { + if (val (lit) >= 0) + continue; + if (flags (lit).seen) + continue; + const int idx = abs (lit); + if (sweeper.prev_units[idx]) { + int64_t uid = unit_id (-lit); + lrat_chain.push_back (uid); + analyzed.push_back (lit); + flags (lit).seen = true; + } + } + } + break; + } + } + CADICAL_assert (id); + if (id != INVALID64) + lrat_chain.push_back (id); + } + clear_analyzed_literals (); + } + + if (!new_size) { + LOG ("sweeping produced empty clause"); + learn_empty_clause (); + core.resize (unsat_size); + return; + } + + if (new_size == 1) { + int unit = pc.literals[0]; + if (val (unit) > 0) { + LOG ("already assigned sweeping unit %d", unit); + lrat_chain.clear (); + } else if (val (unit) < 0) { + LOG ("falsified sweeping unit %d leads to empty clause", unit); + if (lrat) { + int64_t id = unit_id (-unit); + lrat_chain.push_back (id); + } + learn_empty_clause (); + core.resize (unsat_size); + return; + } else { + LOG ("sweeping produced unit %d", unit); + assign_unit (unit); + stats.sweep_units++; + sweeper.propagate.push_back (unit); + } + if (proof && lrat) + pc.cad_id = unit_id (unit); + continue; + } + + CADICAL_assert (new_size > 1); + CADICAL_assert (pc.learned); + + if (proof) { + pc.cad_id = ++clause_id; + proof->add_derived_clause (pc.cad_id, true, pc.literals, lrat_chain); + lrat_chain.clear (); + } + } +} + +void Internal::save_core (Sweeper &sweeper, unsigned core) { + LOG ("saving extracted core[%u] lemmas", core); + CADICAL_assert (core == 0 || core == 1); + CADICAL_assert (sweeper.core[core].empty ()); + sweeper.save = core; + cadical_kitten_compute_clausal_core (citten, 0); + if (lrat) + cadical_kitten_trace_core (citten, &sweeper, save_core_clause_with_lrat); + else + cadical_kitten_traverse_core_clauses_with_id (citten, &sweeper, + save_core_clause); +} + +void Internal::clear_core (Sweeper &sweeper, unsigned core_idx) { + CADICAL_assert (core_idx == 0 || core_idx == 1); + LOG ("clearing core[%u] lemmas", core_idx); + vector &core = sweeper.core[core_idx]; + if (proof) { + LOG ("deleting sub-solver core clauses"); + for (auto &pc : core) { + if (pc.learned && pc.literals.size () > 1) + proof->delete_clause (pc.cad_id, true, pc.literals); + } + } + core.clear (); +} + +void Internal::save_add_clear_core (Sweeper &sweeper) { + save_core (sweeper, 0); + add_core (sweeper, 0); + clear_core (sweeper, 0); +} + +void Internal::init_backbone_and_partition (Sweeper &sweeper) { + LOG ("initializing backbone and equivalent literals candidates"); + sweeper.backbone.clear (); + sweeper.partition.clear (); + for (const auto &idx : sweeper.vars) { + if (!active (idx)) + continue; + CADICAL_assert (idx > 0); + const int lit = idx; + const int not_lit = -lit; + const signed char tmp = cadical_kitten_signed_value (citten, lit); + const int candidate = (tmp < 0) ? not_lit : lit; + LOG ("sweeping candidate %d", candidate); + sweeper.backbone.push_back (candidate); + sweeper.partition.push_back (candidate); + } + sweeper.partition.push_back (0); + + LOG (sweeper.backbone, "initialized backbone candidates"); + LOG (sweeper.partition, "initialized equivalence candidates"); +} + +void Internal::sweep_empty_clause (Sweeper &sweeper) { + CADICAL_assert (!unsat); + save_add_clear_core (sweeper); + CADICAL_assert (unsat); +} + +void Internal::sweep_refine_partition (Sweeper &sweeper) { + LOG ("refining partition"); + vector &old_partition = sweeper.partition; + vector new_partition; + auto old_begin = old_partition.begin (); + const auto old_end = old_partition.end (); +#ifdef LOGGING + unsigned old_classes = 0; + unsigned new_classes = 0; +#endif + for (auto p = old_begin, q = p; p != old_end; p = q + 1) { + unsigned assigned_true = 0; + int other; + for (q = p; (other = *q) != 0; q++) { + if (sweep_repr (sweeper, other) != other) + continue; + if (val (other)) + continue; + signed char value = cadical_kitten_signed_value (citten, other); + if (!value) + LOG ("dropping sub-solver unassigned %d", other); + else if (value > 0) { + new_partition.push_back (other); + assigned_true++; + } + } +#ifdef LOGGING + LOG ("refining class %u of size %zu", old_classes, (size_t) (q - p)); + old_classes++; +#endif + if (assigned_true == 0) + LOG ("no positive literal in class"); + else if (assigned_true == 1) { +#ifdef LOGGING + other = +#else + (void) +#endif + new_partition.back (); + new_partition.pop_back (); + LOG ("dropping singleton class %d", other); + } else { + LOG ("%u positive literal in class", assigned_true); + new_partition.push_back (0); +#ifdef LOGGING + new_classes++; +#endif + } + + unsigned assigned_false = 0; + for (q = p; (other = *q) != 0; q++) { + if (sweep_repr (sweeper, other) != other) + continue; + if (val (other)) + continue; + signed char value = cadical_kitten_signed_value (citten, other); + if (value < 0) { + new_partition.push_back (other); + assigned_false++; + } + } + + if (assigned_false == 0) + LOG ("no negative literal in class"); + else if (assigned_false == 1) { +#ifdef LOGGING + other = +#else + (void) +#endif + new_partition.back (); + new_partition.pop_back (); + LOG ("dropping singleton class %d", other); + } else { + LOG ("%u negative literal in class", assigned_false); + new_partition.push_back (0); +#ifdef LOGGING + new_classes++; +#endif + } + } + old_partition.swap (new_partition); + LOG ("refined %u classes into %u", old_classes, new_classes); +} + +void Internal::sweep_refine_backbone (Sweeper &sweeper) { + LOG ("refining backbone candidates"); + const auto end = sweeper.backbone.end (); + auto q = sweeper.backbone.begin (); + for (auto p = q; p != end; p++) { + const int lit = *p; + if (val (lit)) + continue; + signed char value = cadical_kitten_signed_value (citten, lit); + if (!value) + LOG ("dropping sub-solver unassigned %d", lit); + else if (value > 0) + *q++ = lit; + } + sweeper.backbone.resize (q - sweeper.backbone.begin ()); +} + +void Internal::sweep_refine (Sweeper &sweeper) { + CADICAL_assert (cadical_kitten_status (citten) == 10); + if (sweeper.backbone.empty ()) + LOG ("no need to refine empty backbone candidates"); + else + sweep_refine_backbone (sweeper); + if (sweeper.partition.empty ()) + LOG ("no need to refine empty partition candidates"); + else + sweep_refine_partition (sweeper); +} + +void Internal::flip_backbone_literals (Sweeper &sweeper) { + const unsigned max_rounds = opts.sweepfliprounds; + if (!max_rounds) + return; + CADICAL_assert (sweeper.backbone.size ()); + if (cadical_kitten_status (citten) != 10) + return; +#ifdef LOGGING + unsigned total_flipped = 0; +#endif + unsigned flipped, round = 0; + do { + round++; + flipped = 0; + bool refine = false; + auto begin = sweeper.backbone.begin (), q = begin, p = q; + const auto end = sweeper.backbone.end (); + bool limit_hit = false; + while (p != end) { + const int lit = *p++; + stats.sweep_flip_backbone++; + if (limit_hit || terminated_asynchronously ()) { + break; + } else if (sweep_flip (lit)) { + LOG ("flipping backbone candidate %d succeeded", lit); +#ifdef LOGGING + total_flipped++; +#endif + stats.sweep_flipped_backbone++; + flipped++; + } else { + LOG ("flipping backbone candidate %d failed", lit); + *q++ = lit; + } + } + while (p != end) + *q++ = *p++; + sweeper.backbone.resize (q - sweeper.backbone.begin ()); + LOG ("flipped %u backbone candidates in round %u", flipped, round); + + if (limit_hit) + break; + if (terminated_asynchronously ()) + break; + if (cadical_kitten_ticks_limit_hit (sweeper, "backbone flipping")) + break; + if (refine) + sweep_refine (sweeper); + } while (flipped && round < max_rounds); + LOG ("flipped %u backbone candidates in total in %u rounds", + total_flipped, round); +} + +bool Internal::sweep_extract_fixed (Sweeper &sweeper, int lit) { + const int not_lit = -lit; + stats.sweep_solved_backbone++; + cadical_kitten_assume_signed (citten, not_lit); + int res = sweep_solve (); + if (!res) { + stats.sweep_unknown_backbone++; + return false; + } + CADICAL_assert (res == 20); + LOG ("sweep unit %d", lit); + save_add_clear_core (sweeper); + stats.sweep_unsat_backbone++; + return true; +} + +bool Internal::sweep_backbone_candidate (Sweeper &sweeper, int lit) { + LOG ("trying backbone candidate %d", lit); + signed char value = cadical_kitten_fixed_signed (citten, lit); + if (value) { + stats.sweep_fixed_backbone++; + CADICAL_assert (value > 0); + if (val (lit) <= 0) { + return sweep_extract_fixed (sweeper, lit); + } else + LOG ("literal %d already fixed", lit); + return false; + } + + int res = cadical_kitten_status (citten); + if (res != 10) { + LOG ("not flipping due to status %d != 10", res); + } + stats.sweep_flip_backbone++; + if (res == 10 && sweep_flip (lit)) { + stats.sweep_flipped_backbone++; + LOG ("flipping %d succeeded", lit); + // LOGBACKBONE ("refined backbone candidates"); + return false; + } + + LOG ("flipping %d failed", lit); + const int not_lit = -lit; + stats.sweep_solved_backbone++; + cadical_kitten_assume_signed (citten, not_lit); + res = sweep_solve (); + if (res == 10) { + LOG ("sweeping backbone candidate %d failed", lit); + sweep_refine (sweeper); + stats.sweep_sat_backbone++; + return false; + } + + if (res == 20) { + LOG ("sweep unit %d", lit); + save_add_clear_core (sweeper); + CADICAL_assert (val (lit)); + stats.sweep_unsat_backbone++; + return true; + } + + stats.sweep_unknown_backbone++; + + LOG ("sweeping backbone candidate %d failed", lit); + return false; +} + +// at this point the binary (lit or other) is already present +// in the proof via 'add_core'. +// We just copy it as an irredundant clause, call weaken minus +// and push it on the extension stack. +// +int64_t Internal::add_sweep_binary (sweep_proof_clause pc, int lit, + int other) { + CADICAL_assert (!unsat); + if (unsat) + return 0; // sanity check, should be fuzzed + + CADICAL_assert (!val (lit) && !val (other)); + if (val (lit) || val (other)) + return 0; + + if (lrat) { + for (const auto &plit : pc.literals) { + if (val (plit)) { + int64_t id = unit_id (-plit); + lrat_chain.push_back (id); + } + } + lrat_chain.push_back (pc.cad_id); + } + clause.push_back (lit); + clause.push_back (other); + const int64_t id = ++clause_id; + if (proof) { + proof->add_derived_clause (id, false, clause, lrat_chain); + proof->weaken_minus (id, clause); + } + external->push_binary_clause_on_extension_stack (id, lit, other); + clause.clear (); + lrat_chain.clear (); + return id; +} + +void Internal::delete_sweep_binary (const sweep_binary &sb) { + if (unsat) + return; + if (!proof) + return; + vector bin; + bin.push_back (sb.lit); + bin.push_back (sb.other); + proof->delete_clause (sb.id, false, bin); +} + +bool Internal::scheduled_variable (Sweeper &sweeper, int idx) { + return sweeper.prev[idx] != 0 || sweeper.first == idx; +} + +void Internal::schedule_inner (Sweeper &sweeper, int idx) { + CADICAL_assert (idx); + if (!active (idx)) + return; + const int next = sweeper.next[idx]; + if (next != 0) { + LOG ("rescheduling inner %d as last", idx); + const unsigned prev = sweeper.prev[idx]; + CADICAL_assert (sweeper.prev[next] == idx); + sweeper.prev[next] = prev; + if (prev == 0) { + CADICAL_assert (sweeper.first == idx); + sweeper.first = next; + } else { + CADICAL_assert (sweeper.next[prev] == idx); + sweeper.next[prev] = next; + } + const unsigned last = sweeper.last; + if (last == 0) { + CADICAL_assert (sweeper.first == 0); + sweeper.first = idx; + } else { + CADICAL_assert (sweeper.next[last] == 0); + sweeper.next[last] = idx; + } + sweeper.prev[idx] = last; + sweeper.next[idx] = 0; + sweeper.last = idx; + } else if (sweeper.last != idx) { + LOG ("scheduling inner %d as last", idx); + const unsigned last = sweeper.last; + if (last == 0) { + CADICAL_assert (sweeper.first == 0); + sweeper.first = idx; + } else { + CADICAL_assert (sweeper.next[last] == 0); + sweeper.next[last] = idx; + } + CADICAL_assert (sweeper.next[idx] == 0); + sweeper.prev[idx] = last; + sweeper.last = idx; + } else + LOG ("keeping inner %d scheduled as last", idx); +} + +void Internal::schedule_outer (Sweeper &sweeper, int idx) { + CADICAL_assert (!scheduled_variable (sweeper, idx)); + CADICAL_assert (active (idx)); + const int first = sweeper.first; + if (first == 0) { + CADICAL_assert (sweeper.last == 0); + sweeper.last = idx; + } else { + CADICAL_assert (sweeper.prev[first] == 0); + sweeper.prev[first] = idx; + } + CADICAL_assert (sweeper.prev[idx] == 0); + sweeper.next[idx] = first; + sweeper.first = idx; + LOG ("scheduling outer %d as first", idx); +} + +int Internal::next_scheduled (Sweeper &sweeper) { + int res = sweeper.last; + if (res == 0) { + LOG ("no more scheduled variables left"); + return 0; + } + CADICAL_assert (res > 0); + LOG ("dequeuing next scheduled %d", res); + const unsigned prev = sweeper.prev[res]; + CADICAL_assert (sweeper.next[res] == 0); + sweeper.prev[res] = 0; + if (prev == 0) { + CADICAL_assert (sweeper.first == res); + sweeper.first = 0; + } else { + CADICAL_assert (sweeper.next[prev] == res); + sweeper.next[prev] = 0; + } + sweeper.last = prev; + return res; +} + +void Internal::sweep_substitute_lrat (Clause *c, int64_t id) { + if (!lrat) + return; + for (const auto &lit : *c) { + CADICAL_assert (val (lit) <= 0); + if (val (lit) < 0) { + int64_t id = unit_id (-lit); + lrat_chain.push_back (id); + } + } + lrat_chain.push_back (id); + lrat_chain.push_back (c->id); +} + +#define all_scheduled(IDX) \ + int IDX = sweeper.first, NEXT_##IDX; \ + IDX != 0 && (NEXT_##IDX = sweeper.next[IDX], true); \ + IDX = NEXT_##IDX + +// Substitute equivalences in clauses (see +// 'sweep_substitute_new_equivalences' for explanation) +void Internal::substitute_connected_clauses (Sweeper &sweeper, int lit, + int repr, int64_t id) { + if (unsat) + return; + if (val (lit)) + return; + if (val (repr)) + return; + LOG ("substituting %d with %d in all irredundant clauses", lit, repr); + + CADICAL_assert (lit != repr); + CADICAL_assert (lit != -repr); + + CADICAL_assert (active (lit)); + CADICAL_assert (active (repr)); + + uint64_t &ticks = sweeper.current_ticks; + + { + ticks += 1 + cache_lines (occs (lit).size (), sizeof (Clause *)); + Occs &ns = occs (lit); + auto const begin = ns.begin (); + const auto end = ns.end (); + auto q = begin; + auto p = q; + while (p != end) { + Clause *c = *q++ = *p++; + ticks++; + if (c->garbage) + continue; + CADICAL_assert (clause.empty ()); + bool satisfied = false; + bool repr_already_watched = false; + const int not_repr = -repr; +#ifndef CADICAL_NDEBUG + bool found = false; +#endif + for (const auto &other : *c) { + if (other == lit) { +#ifndef CADICAL_NDEBUG + CADICAL_assert (!found); + found = true; +#endif + clause.push_back (repr); + continue; + } + CADICAL_assert (other != -lit); + if (other == repr) { + CADICAL_assert (!repr_already_watched); + repr_already_watched = true; + continue; + } + if (other == not_repr) { + satisfied = true; + break; + } + const signed char tmp = val (other); + if (tmp < 0) + continue; + if (tmp > 0) { + satisfied = true; + break; + } + clause.push_back (other); + } + if (satisfied) { + clause.clear (); + mark_garbage (c); + sweep_update_noccs (c); + continue; + } + CADICAL_assert (found); + const unsigned new_size = clause.size (); + sweep_substitute_lrat (c, id); + if (new_size == 0) { + LOG (c, "substituted empty clause"); + CADICAL_assert (!unsat); + learn_empty_clause (); + break; + } + ticks++; + if (new_size == 1) { + LOG (c, "reduces to unit"); + const int unit = clause[0]; + clause.clear (); + assign_unit (unit); + sweeper.propagate.push_back (unit); + mark_garbage (c); + sweep_update_noccs (c); + stats.sweep_units++; + break; + } + CADICAL_assert (c->size >= 2); + if (!c->redundant) + mark_removed (c); + uint64_t new_id = ++clause_id; + if (proof) { + proof->add_derived_clause (new_id, c->redundant, clause, + lrat_chain); + proof->delete_clause (c); + } + c->id = new_id; + lrat_chain.clear (); + size_t l; + int *literals = c->literals; + for (l = 0; l < clause.size (); l++) + literals[l] = clause[l]; + int flushed = c->size - (int) l; + if (flushed) { + LOG ("flushed %d literals", flushed); + (void) shrink_clause (c, l); + } else if (likely_to_be_kept_clause (c)) + mark_added (c); + LOG (c, "substituted"); + if (!repr_already_watched) { + occs (repr).push_back (c); + noccs (repr)++; + } + clause.clear (); + q--; + } + while (p != end) + *q++ = *p++; + ns.resize (q - ns.begin ()); + } +} + +// In contrast to kissat we substitute the equivalences explicitely after +// every successful round of sweeping. This is necessary in order to extract +// valid LRAT proofs for subsequent rounds of sweeping. +void Internal::sweep_substitute_new_equivalences (Sweeper &sweeper) { + if (unsat) + return; + + unsigned count = 0; + CADICAL_assert (lrat_chain.empty ()); + + for (const auto &sb : sweeper.binaries) { + count++; + const auto lit = sb.lit; + const auto other = sb.other; + if (abs (lit) < abs (other)) { + substitute_connected_clauses (sweeper, -other, lit, sb.id); + } else { + substitute_connected_clauses (sweeper, -lit, other, sb.id); + } + CADICAL_assert (lrat_chain.empty ()); + if (val (lit) < 0) { + if (lrat) { + const int64_t lid = unit_id (-lit); + lrat_chain.push_back (lid); + } + if (!val (other)) { + if (lrat) + lrat_chain.push_back (sb.id); + assign_unit (other); + } else if (val (other) < 0) { + if (lrat) { + const int64_t oid = unit_id (-other); + lrat_chain.push_back (oid); + lrat_chain.push_back (sb.id); + } + learn_empty_clause (); + return; + } + } else if (val (other) < 0) { + if (!val (lit)) { + if (lrat) { + const int64_t oid = unit_id (-other); + lrat_chain.push_back (oid); + lrat_chain.push_back (sb.id); + } + assign_unit (lit); + } else + CADICAL_assert (val (lit) > 0); + } + lrat_chain.clear (); + delete_sweep_binary (sb); + if (count == 2) { + if (!val (lit) && !val (other)) { + const auto idx = abs (lit) < abs (other) ? abs (other) : abs (lit); + if (!flags (idx).fixed ()) + mark_substituted (idx); + } + count = 0; + } + CADICAL_assert (lrat_chain.empty ()); + } + sweeper.binaries.clear (); +} + +void Internal::sweep_remove (Sweeper &sweeper, int lit) { + CADICAL_assert (sweeper.reprs[lit] != lit); + vector &partition = sweeper.partition; + const auto begin_partition = partition.begin (); + auto p = begin_partition; + const auto end_partition = partition.end (); + for (; *p != lit; p++) + CADICAL_assert (p + 1 != end_partition); + auto begin_class = p; + while (begin_class != begin_partition && begin_class[-1] != 0) + begin_class--; + auto end_class = p; + while (*end_class != 0) + end_class++; + const unsigned size = end_class - begin_class; + LOG ("removing non-representative %d from equivalence class of size %u", + lit, size); + CADICAL_assert (size > 1); + auto q = begin_class; + if (size == 2) { + LOG ("completely squashing equivalence class of %d", lit); + for (auto r = end_class + 1; r != end_partition; r++) + *q++ = *r; + } else { + for (auto r = begin_class; r != end_partition; r++) + if (r != p) + *q++ = *r; + } + partition.resize (q - partition.begin ()); +} + +void Internal::flip_partition_literals (Sweeper &sweeper) { + const unsigned max_rounds = opts.sweepfliprounds; + if (!max_rounds) + return; + CADICAL_assert (sweeper.partition.size ()); + if (cadical_kitten_status (citten) != 10) + return; +#ifdef LOGGING + unsigned total_flipped = 0; +#endif + unsigned flipped, round = 0; + do { + round++; + flipped = 0; + bool refine = false; + bool limit_hit = false; + auto begin = sweeper.partition.begin (), dst = begin, src = dst; + const auto end = sweeper.partition.end (); + while (src != end) { + auto end_src = src; + while (CADICAL_assert (end_src != end), *end_src != 0) + end_src++; + unsigned size = end_src - src; + CADICAL_assert (size > 1); + auto q = dst; + for (auto p = src; p != end_src; p++) { + const int lit = *p; + if (limit_hit) { + *q++ = lit; + continue; + } else if (cadical_kitten_ticks_limit_hit (sweeper, "partition flipping")) { + *q++ = lit; + limit_hit = true; + continue; + } else if (sweep_flip (lit)) { + LOG ("flipping equivalence candidate %d succeeded", lit); +#ifdef LOGGING + total_flipped++; +#endif + flipped++; + if (--size < 2) + break; + } else { + LOG ("flipping equivalence candidate %d failed", lit); + *q++ = lit; + } + stats.sweep_flip_equivalences++; + } + if (size > 1) { + *q++ = 0; + dst = q; + } + src = end_src + 1; + } + stats.sweep_flipped_equivalences += flipped; + sweeper.partition.resize (dst - sweeper.partition.begin ()); + LOG ("flipped %u equivalence candidates in round %u", flipped, round); + if (terminated_asynchronously ()) + break; + if (cadical_kitten_ticks_limit_hit (sweeper, "partition flipping")) + break; + if (refine) + sweep_refine (sweeper); + } while (flipped && round < max_rounds); + LOG ("flipped %u equivalence candidates in total in %u rounds", + total_flipped, round); +} + +bool Internal::sweep_equivalence_candidates (Sweeper &sweeper, int lit, + int other) { + LOG ("trying equivalence candidates %d = %d", lit, other); + const auto begin = sweeper.partition.begin (); + auto const end = sweeper.partition.end (); + CADICAL_assert (begin + 3 <= end); + CADICAL_assert (end[-3] == lit); + CADICAL_assert (end[-2] == other); + const int third = (end - begin == 3) ? 0 : end[-4]; + int res = cadical_kitten_status (citten); + if (res == 10) { + stats.sweep_flip_equivalences++; + if (sweep_flip (lit)) { + stats.sweep_flipped_equivalences++; + LOG ("flipping %d succeeded", lit); + if (third == 0) { + LOG ("squashing equivalence class of %d", lit); + sweeper.partition.resize (sweeper.partition.size () - 3); + } else { + LOG ("removing %d from equivalence class of %d", lit, other); + end[-3] = other; + end[-2] = 0; + sweeper.partition.resize (sweeper.partition.size () - 1); + } + return false; + } + stats.sweep_flip_equivalences++; + if (sweep_flip (other)) { + stats.sweep_flipped_equivalences++; + LOG ("flipping %d succeeded", other); + if (third == 0) { + LOG ("squashing equivalence class of %d", lit); + sweeper.partition.resize (sweeper.partition.size () - 3); + } else { + LOG ("removing %d from equivalence class of %d", other, lit); + end[-2] = 0; + sweeper.partition.resize (sweeper.partition.size () - 1); + } + return false; + } + } + // frozen variables are not allowed to be eliminated. + // It might still be beneficial to learn the binaries, if they + // really are equivalent, but we avoid the issue by not trying + // for equivalence at all if the non-representative is frozen. + // i.e., the higher absolute value + if (abs (lit) > abs (other) && frozen (lit)) { + if (third == 0) { + LOG ("squashing equivalence class of %d", lit); + sweeper.partition.resize (sweeper.partition.size () - 3); + } else { + LOG ("removing %d from equivalence class of %d", lit, other); + end[-3] = other; + end[-2] = 0; + sweeper.partition.resize (sweeper.partition.size () - 1); + } + return false; + } else if (abs (other) > abs (lit) && frozen (other)) { + if (third == 0) { + LOG ("squashing equivalence class of %d", lit); + sweeper.partition.resize (sweeper.partition.size () - 3); + } else { + LOG ("removing %d from equivalence class of %d", lit, other); + end[-2] = 0; + sweeper.partition.resize (sweeper.partition.size () - 1); + } + return false; + } + + const int not_other = -other; + const int not_lit = -lit; + LOG ("flipping %d and %d both failed", lit, other); + cadical_kitten_assume_signed (citten, not_lit); + cadical_kitten_assume_signed (citten, other); + stats.sweep_solved_equivalences++; + res = sweep_solve (); + if (res == 10) { + stats.sweep_sat_equivalences++; + LOG ("first sweeping implication %d -> %d failed", other, lit); + sweep_refine (sweeper); + } else if (!res) { + stats.sweep_unknown_equivalences++; + LOG ("first sweeping implication %d -> %d hit ticks limit", other, lit); + } + + if (res != 20) + return false; + + stats.sweep_unsat_equivalences++; + LOG ("first sweeping implication %d -> %d succeeded", other, lit); + + save_core (sweeper, 0); + + cadical_kitten_assume_signed (citten, lit); + cadical_kitten_assume_signed (citten, not_other); + res = sweep_solve (); + stats.sweep_solved_equivalences++; + if (res == 10) { + stats.sweep_sat_equivalences++; + LOG ("second sweeping implication %d <- %d failed", other, lit); + sweep_refine (sweeper); + } else if (!res) { + stats.sweep_unknown_equivalences++; + LOG ("second sweeping implication %d <- %d hit ticks limit", other, + lit); + } + + if (res != 20) { + sweeper.core[0].clear (); + return false; + } + + CADICAL_assert (res == 20); + + stats.sweep_unsat_equivalences++; + LOG ("second sweeping implication %d <- %d succeeded too", other, lit); + + save_core (sweeper, 1); + + LOG ("sweep equivalence %d = %d", lit, other); + + // If cadical_kitten behaves as expected, the two binaries of the equivalence + // should be stored at sweeper.core[i].back () for i in {0, 1}. + // We pick the smaller absolute valued literal as representative and + // store the equivalence . + add_core (sweeper, 0); + add_core (sweeper, 1); + if (!val (lit) && !val (other)) { + CADICAL_assert (sweeper.core[0].size ()); + CADICAL_assert (sweeper.core[1].size ()); + stats.sweep_equivalences++; + sweep_binary bin1; + sweep_binary bin2; + if (abs (lit) > abs (other)) { + bin1.lit = lit; + bin1.other = not_other; + bin2.lit = not_lit; + bin2.other = other; + bin1.id = add_sweep_binary (sweeper.core[0].back (), lit, not_other); + bin2.id = add_sweep_binary (sweeper.core[1].back (), not_lit, other); + } else { + bin1.lit = not_other; + bin1.other = lit; + bin2.lit = other; + bin2.other = not_lit; + bin1.id = add_sweep_binary (sweeper.core[0].back (), not_other, lit); + bin2.id = add_sweep_binary (sweeper.core[1].back (), other, not_lit); + } + if (bin1.id && bin2.id) { + sweeper.binaries.push_back (bin1); + sweeper.binaries.push_back (bin2); + } + } + + int repr; + if (abs (lit) < abs (other)) { + repr = sweeper.reprs[other] = lit; + sweeper.reprs[not_other] = not_lit; + sweep_remove (sweeper, other); + } else { + repr = sweeper.reprs[lit] = other; + sweeper.reprs[not_lit] = not_other; + sweep_remove (sweeper, lit); + } + clear_core (sweeper, 0); + clear_core (sweeper, 1); + + const int repr_idx = abs (repr); + schedule_inner (sweeper, repr_idx); + + return true; +} + +const char *Internal::sweep_variable (Sweeper &sweeper, int idx) { + CADICAL_assert (!unsat); + if (!active (idx)) + return "inactive variable"; + const int start = idx; + if (sweeper.reprs[start] != start) + return "non-representative variable"; + CADICAL_assert (sweeper.vars.empty ()); + CADICAL_assert (sweeper.clauses.empty ()); + CADICAL_assert (sweeper.backbone.empty ()); + CADICAL_assert (sweeper.partition.empty ()); + CADICAL_assert (!sweeper.encoded); + + stats.sweep_variables++; + + LOG ("sweeping %d", idx); + CADICAL_assert (!val (start)); + LOG ("starting sweeping[0]"); + add_literal_to_environment (sweeper, 0, start); + LOG ("finished sweeping[0]"); + LOG ("starting sweeping[1]"); + + bool limit_reached = false; + size_t expand = 0, next = 1; + bool success = false; + unsigned depth = 1; + + uint64_t &ticks = sweeper.current_ticks; + while (!limit_reached) { + if (sweeper.encoded >= sweeper.limit.clauses) { + LOG ("environment clause limit reached"); + limit_reached = true; + break; + } + if (expand == next) { + LOG ("finished sweeping[%u]", depth); + if (depth >= sweeper.limit.depth) { + LOG ("environment depth limit reached"); + break; + } + next = sweeper.vars.size (); + if (expand == next) { + LOG ("completely copied all clauses"); + break; + } + depth++; + LOG ("starting sweeping[%u]", depth); + } + const unsigned choices = next - expand; + if (opts.sweeprand && choices > 1) { + const unsigned swaps = sweeper.random.pick_int (0, choices - 1); + if (swaps) { + CADICAL_assert (expand + swaps < sweeper.vars.size ()); + swap (sweeper.vars[expand], sweeper.vars[expand + swaps]); + } + } + const int idx = sweeper.vars[expand]; + LOG ("traversing and adding clauses of %d", idx); + for (unsigned sign = 0; sign < 2; sign++) { + const int lit = sign ? -idx : idx; + ticks += 1 + cache_lines (occs (lit).size (), sizeof (Clause *)); + Occs &ns = occs (lit); + for (auto c : ns) { + ticks++; + if (!can_sweep_clause (c)) + continue; + sweep_clause (sweeper, depth, c); + if (sweeper.vars.size () >= sweeper.limit.vars) { + LOG ("environment variable limit reached"); + limit_reached = true; + break; + } + } + if (limit_reached) + break; + } + expand++; + } + stats.sweep_depth += depth; + stats.sweep_clauses += sweeper.encoded; + stats.sweep_environment += sweeper.vars.size (); + VERBOSE (3, + "sweeping variable %d environment of " + "%zu variables %u clauses depth %u", + externalize (idx), sweeper.vars.size (), sweeper.encoded, depth); + + int res; + if (sweeper.vars.size () == 1) { + LOG ("not sweeping literal %d with environment size 1", idx); + goto DONE; + } + res = sweep_solve (); + LOG ("sub-solver returns '%d'", res); + if (res == 10) { + init_backbone_and_partition (sweeper); +#ifndef CADICAL_QUIET + uint64_t units = stats.sweep_units; + uint64_t solved = stats.sweep_solved; +#endif + START (sweepbackbone); + while (sweeper.backbone.size ()) { + if (unsat || terminated_asynchronously () || + cadical_kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + STOP_SWEEP_BACKBONE: + STOP (sweepbackbone); + goto DONE; + } + flip_backbone_literals (sweeper); + if (terminated_asynchronously () || + cadical_kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + goto STOP_SWEEP_BACKBONE; + } + if (sweeper.backbone.empty ()) + break; + const int lit = sweeper.backbone.back (); + sweeper.backbone.pop_back (); + if (!active (lit)) + continue; + if (sweep_backbone_candidate (sweeper, lit)) + success = true; + } + STOP (sweepbackbone); +#ifndef CADICAL_QUIET + units = stats.sweep_units - units; + solved = stats.sweep_solved - solved; +#endif + VERBOSE (3, + "complete swept variable %d backbone with %" PRIu64 + " units in %" PRIu64 " solver calls", + externalize (idx), units, solved); + CADICAL_assert (sweeper.backbone.empty ()); +#ifndef CADICAL_QUIET + uint64_t equivalences = stats.sweep_equivalences; + solved = stats.sweep_solved; +#endif + START (sweepequivalences); + while (sweeper.partition.size ()) { + if (unsat || terminated_asynchronously () || + cadical_kitten_ticks_limit_hit (sweeper, "partition refinement")) { + limit_reached = true; + STOP_SWEEP_EQUIVALENCES: + STOP (sweepequivalences); + goto DONE; + } + flip_partition_literals (sweeper); + if (terminated_asynchronously () || + cadical_kitten_ticks_limit_hit (sweeper, "backbone refinement")) { + limit_reached = true; + goto STOP_SWEEP_EQUIVALENCES; + } + if (sweeper.partition.empty ()) + break; + if (sweeper.partition.size () > 2) { + const auto end = sweeper.partition.end (); + CADICAL_assert (end[-1] == 0); + int lit = end[-3]; + int other = end[-2]; + if (sweep_equivalence_candidates (sweeper, lit, other)) + success = true; + } else + sweeper.partition.clear (); + } + STOP (sweepequivalences); +#ifndef CADICAL_QUIET + equivalences = stats.sweep_equivalences - equivalences; + solved = stats.sweep_solved - solved; + if (equivalences) + VERBOSE (3, + "complete swept variable %d partition with %" PRIu64 + " equivalences in %" PRIu64 " solver calls", + externalize (idx), equivalences, solved); +#endif + } else if (res == 20) + sweep_empty_clause (sweeper); + +DONE: + clear_sweeper (sweeper); + + if (!unsat) + sweep_substitute_new_equivalences (sweeper); + if (!unsat) + sweep_dense_propagate (sweeper); + + if (success && limit_reached) + return "successfully despite reaching limit"; + if (!success && !limit_reached) + return "unsuccessfully without reaching limit"; + else if (success && !limit_reached) + return "successfully without reaching limit"; + CADICAL_assert (!success && limit_reached); + return "unsuccessfully and reached limit"; +} + +struct sweep_candidate { + unsigned rank; + int idx; +}; + +struct rank_sweep_candidate { + bool operator() (sweep_candidate a, sweep_candidate b) const { + CADICAL_assert (a.rank && b.rank); + CADICAL_assert (a.idx > 0 && b.idx > 0); + if (a.rank < b.rank) + return true; + if (b.rank < a.rank) + return false; + return a.idx < b.idx; + } +}; + +bool Internal::scheduable_variable (Sweeper &sweeper, int idx, + size_t *occ_ptr) { + const int lit = idx; + const size_t pos = noccs (lit); + if (!pos) + return false; + const unsigned max_occurrences = sweeper.limit.clauses; + if (pos > max_occurrences) + return false; + const int not_lit = -lit; + const size_t neg = noccs (not_lit); + if (!neg) + return false; + if (neg > max_occurrences) + return false; + *occ_ptr = pos + neg; + return true; +} + +unsigned Internal::schedule_all_other_not_scheduled_yet (Sweeper &sweeper) { + vector fresh; + for (const auto &idx : vars) { + Flags &f = flags (idx); + if (!f.active ()) + continue; + if (sweep_incomplete && !f.sweep) + continue; + if (scheduled_variable (sweeper, idx)) + continue; + size_t occ; + if (!scheduable_variable (sweeper, idx, &occ)) { + f.sweep = false; + continue; + } + sweep_candidate cand; + cand.rank = occ; + cand.idx = idx; + fresh.push_back (cand); + } + const size_t size = fresh.size (); + CADICAL_assert (size <= UINT_MAX); + sort (fresh.begin (), fresh.end (), rank_sweep_candidate ()); + for (auto &cand : fresh) + schedule_outer (sweeper, cand.idx); + return size; +} + +unsigned Internal::reschedule_previously_remaining (Sweeper &sweeper) { + unsigned rescheduled = 0; + for (const auto &idx : sweep_schedule) { + Flags &f = flags (idx); + if (!f.active ()) + continue; + if (scheduled_variable (sweeper, idx)) + continue; + size_t occ; + if (!scheduable_variable (sweeper, idx, &occ)) { + f.sweep = false; + continue; + } + schedule_inner (sweeper, idx); + rescheduled++; + } + sweep_schedule.clear (); + return rescheduled; +} + +unsigned Internal::incomplete_variables () { + unsigned res = 0; + for (const auto &idx : vars) { + Flags &f = flags (idx); + if (!f.active ()) + continue; + if (f.sweep) + res++; + } + return res; +} + +void Internal::mark_incomplete (Sweeper &sweeper) { + unsigned marked = 0; + for (all_scheduled (idx)) { + if (!flags (idx).sweep) { + flags (idx).sweep = true; + marked++; + } + } + sweep_incomplete = true; +#ifndef CADICAL_QUIET + VERBOSE (2, "marked %u scheduled sweeping variables as incomplete", + marked); +#else + (void) marked; +#endif +} + +unsigned Internal::schedule_sweeping (Sweeper &sweeper) { + const unsigned rescheduled = reschedule_previously_remaining (sweeper); + const unsigned fresh = schedule_all_other_not_scheduled_yet (sweeper); + const unsigned scheduled = fresh + rescheduled; + const unsigned incomplete = incomplete_variables (); +#ifndef CADICAL_QUIET + PHASE ("sweep", stats.sweep, + "scheduled %u variables %.0f%% " + "(%u rescheduled %.0f%%, %u incomplete %.0f%%)", + scheduled, percent (scheduled, active ()), rescheduled, + percent (rescheduled, scheduled), incomplete, + percent (incomplete, scheduled)); +#endif + if (incomplete) + CADICAL_assert (sweep_incomplete); + else { + if (sweep_incomplete) + stats.sweep_completed++; + mark_incomplete (sweeper); + } + return scheduled; +} + +void Internal::unschedule_sweeping (Sweeper &sweeper, unsigned swept, + unsigned scheduled) { +#ifdef CADICAL_QUIET + (void) scheduled, (void) swept; +#endif + CADICAL_assert (sweep_schedule.empty ()); + CADICAL_assert (sweep_incomplete); + for (all_scheduled (idx)) + if (active (idx)) { + sweep_schedule.push_back (idx); + LOG ("untried scheduled %d", idx); + } +#ifndef CADICAL_QUIET + const unsigned retained = sweep_schedule.size (); +#endif + VERBOSE (3, "retained %u variables %.0f%% to be swept next time", + retained, percent (retained, active ())); + const unsigned incomplete = incomplete_variables (); + if (incomplete) + VERBOSE (3, "need to sweep %u more variables %.0f%% for completion", + incomplete, percent (incomplete, active ())); + else { + VERBOSE (3, "no more variables needed to complete sweep"); + sweep_incomplete = false; + stats.sweep_completed++; + } + PHASE ("sweep", stats.sweep, "swept %u variables (%u remain %.0f%%)", + swept, incomplete, percent (incomplete, scheduled)); +} + +bool Internal::sweep () { + if (!opts.sweep) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + if (delaying_sweep.bumpreasons.delay ()) { // TODO need to fix Delay + last.sweep.ticks = stats.ticks.search[0] + stats.ticks.search[1]; + return false; + } + delaying_sweep.bumpreasons.bypass_delay (); + SET_EFFORT_LIMIT (tickslimit, sweep, !opts.sweepcomplete); + delaying_sweep.bumpreasons.unbypass_delay (); + + CADICAL_assert (!level); + START_SIMPLIFIER (sweep, SWEEP); + stats.sweep++; + uint64_t equivalences = stats.sweep_equivalences; + uint64_t units = stats.sweep_units; + Sweeper sweeper = Sweeper (this); + if (opts.sweepcomplete) + sweeper.limit.ticks = INT64_MAX; + else + sweeper.limit.ticks = tickslimit - stats.ticks.sweep; + sweep_set_cadical_kitten_ticks_limit (sweeper); + const unsigned scheduled = schedule_sweeping (sweeper); + uint64_t swept = 0, limit = 10; + for (;;) { + if (unsat) + break; + if (terminated_asynchronously ()) + break; + if (cadical_kitten_ticks_limit_hit (sweeper, "sweeping loop")) + break; + int idx = next_scheduled (sweeper); + if (idx == 0) + break; + flags (idx).sweep = false; +#ifndef CADICAL_QUIET + const char *res = +#endif + sweep_variable (sweeper, idx); + VERBOSE (2, "swept[%" PRIu64 "] external variable %d %s", swept, + externalize (idx), res); + if (++swept == limit) { + VERBOSE (2, + "found %" PRIu64 " equivalences and %" PRIu64 + " units after sweeping %" PRIu64 " variables ", + stats.sweep_equivalences - equivalences, + stats.sweep_units - units, swept); + limit *= 10; + } + } + VERBOSE (2, "swept %" PRIu64 " variables", swept); + equivalences = stats.sweep_equivalences - equivalences, + units = stats.sweep_units - units; + PHASE ("sweep", stats.sweep, + "found %" PRIu64 " equivalences and %" PRIu64 " units", + equivalences, units); + unschedule_sweeping (sweeper, swept, scheduled); + release_sweeper (sweeper); + + if (!unsat) { + propagated = 0; + if (!propagate ()) { + learn_empty_clause (); + } + } + + uint64_t eliminated = equivalences + units; + report ('=', !eliminated); + + if (relative (eliminated, swept) < 0.001) { + delaying_sweep.bumpreasons.bump_delay (); + } else { + delaying_sweep.bumpreasons.reduce_delay (); + } + STOP_SIMPLIFIER (sweep, SWEEP); + return eliminated; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_terminal.cpp b/src/sat/cadical/cadical_terminal.cpp new file mode 100644 index 000000000..c8aeea2ab --- /dev/null +++ b/src/sat/cadical/cadical_terminal.cpp @@ -0,0 +1,49 @@ +#include "global.h" + +#include "internal.hpp" + +#ifdef WIN32 +#include +#define isatty _isatty +#endif + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +Terminal::Terminal (FILE *f) : file (f), reset_on_exit (false) { + CADICAL_assert (file); + int fd = fileno (f); + CADICAL_assert (fd == 1 || fd == 2); + use_colors = connected = isatty (fd); +} + +void Terminal::force_colors () { use_colors = connected = true; } +void Terminal::force_no_colors () { use_colors = false; } +void Terminal::force_reset_on_exit () { reset_on_exit = true; } + +void Terminal::reset () { + if (!connected) + return; + erase_until_end_of_line (); + cursor (true); + normal (); + fflush (file); +} + +void Terminal::disable () { + reset (); + connected = use_colors = false; +} + +Terminal::~Terminal () { + if (reset_on_exit) + reset (); +} + +Terminal tout (stdout); +Terminal terr (stderr); + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_ternary.cpp b/src/sat/cadical/cadical_ternary.cpp new file mode 100644 index 000000000..9f99e2279 --- /dev/null +++ b/src/sat/cadical/cadical_ternary.cpp @@ -0,0 +1,456 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This procedure can be used to produce all possible hyper ternary +// resolvents from ternary clauses. In contrast to hyper binary resolution +// we would only try to produce ternary clauses from ternary clauses, i.e., +// do not consider quaternary clauses as antecedents. Of course if a binary +// clause is generated we keep it too. In any case we have to make sure +// though that we do not add clauses which are already in the formula or are +// subsumed by a binary clause in the formula. This procedure simulates +// structural hashing for multiplexer (if-then-else) and binary XOR gates in +// combination with equivalent literal substitution ('decompose') if run +// until to completion (which in the current implementation is too costly +// though and would need to be interleaved more eagerly with equivalent +// literal substitution). For more information see our CPAIOR'13 paper. + +/*------------------------------------------------------------------------*/ + +// Check whether a binary clause consisting of the permutation of the given +// literals already exists. + +bool Internal::ternary_find_binary_clause (int a, int b) { + CADICAL_assert (occurring ()); + CADICAL_assert (active (a)); + CADICAL_assert (active (b)); + size_t s = occs (a).size (); + size_t t = occs (b).size (); + int lit = s < t ? a : b; + if (opts.ternaryocclim < (int) occs (lit).size ()) + return true; + for (const auto &c : occs (lit)) { + if (c->size != 2) + continue; + const int *lits = c->literals; + if (lits[0] == a && lits[1] == b) + return true; + if (lits[0] == b && lits[1] == a) + return true; + } + return false; +} + +/*------------------------------------------------------------------------*/ + +// Check whether a ternary clause consisting of the permutation of the given +// literals already exists or is subsumed by an existing binary clause. + +bool Internal::ternary_find_ternary_clause (int a, int b, int c) { + CADICAL_assert (occurring ()); + CADICAL_assert (active (a)); + CADICAL_assert (active (b)); + CADICAL_assert (active (c)); + size_t r = occs (a).size (); + size_t s = occs (b).size (); + size_t t = occs (c).size (); + int lit; + if (r < s) + lit = (t < r) ? c : a; + else + lit = (t < s) ? c : b; + if (opts.ternaryocclim < (int) occs (lit).size ()) + return true; + for (const auto &d : occs (lit)) { + const int *lits = d->literals; + if (d->size == 2) { + if (lits[0] == a && lits[1] == b) + return true; + if (lits[0] == b && lits[1] == a) + return true; + if (lits[0] == a && lits[1] == c) + return true; + if (lits[0] == c && lits[1] == a) + return true; + if (lits[0] == b && lits[1] == c) + return true; + if (lits[0] == c && lits[1] == b) + return true; + } else { + CADICAL_assert (d->size == 3); + if (lits[0] == a && lits[1] == b && lits[2] == c) + return true; + if (lits[0] == a && lits[1] == c && lits[2] == b) + return true; + if (lits[0] == b && lits[1] == a && lits[2] == c) + return true; + if (lits[0] == b && lits[1] == c && lits[2] == a) + return true; + if (lits[0] == c && lits[1] == a && lits[2] == b) + return true; + if (lits[0] == c && lits[1] == b && lits[2] == a) + return true; + } + } + return false; +} + +/*------------------------------------------------------------------------*/ + +// Try to resolve the two ternary clauses on the given pivot (assumed to +// occur positively in the first clause, negatively in the second). If the +// resolvent has four literals, is tautological, already exists or in the +// case of a ternary resolvent is subsumed by an existing binary clause then +// 'false' is returned. The global 'clause' contains the resolvent and +// needs to be cleared in any case. + +bool Internal::hyper_ternary_resolve (Clause *c, int pivot, Clause *d) { + LOG ("hyper binary resolving on pivot %d", pivot); + LOG (c, "1st antecedent"); + LOG (d, "2nd antecedent"); + stats.ternres++; + CADICAL_assert (c->size == 3); + CADICAL_assert (d->size == 3); + CADICAL_assert (clause.empty ()); + for (const auto &lit : *c) + if (lit != pivot) + clause.push_back (lit); + for (const auto &lit : *d) { + if (lit == -pivot) + continue; + if (lit == clause[0]) + continue; + if (lit == -clause[0]) + return false; + if (lit == clause[1]) + continue; + if (lit == -clause[1]) + return false; + clause.push_back (lit); + } + size_t size = clause.size (); + if (size > 3) + return false; + if (size == 2 && ternary_find_binary_clause (clause[0], clause[1])) + return false; + if (size == 3 && + ternary_find_ternary_clause (clause[0], clause[1], clause[2])) + return false; + return true; +} + +/*------------------------------------------------------------------------*/ + +// Produce all ternary resolvents on literal 'pivot' and increment the +// 'steps' counter by the number of clauses containing 'pivot' which are +// used during this process. The reason for choosing this metric to measure +// the effort spent in 'ternary' is that it should be similar to one +// propagation step during search. + +void Internal::ternary_lit (int pivot, int64_t &steps, int64_t &htrs) { + LOG ("starting hyper ternary resolutions on pivot %d", pivot); + steps -= 1 + cache_lines (occs (pivot).size (), sizeof (Clause *)); + for (const auto &c : occs (pivot)) { + if (steps < 0) + break; + if (htrs < 0) + break; + if (c->garbage) + continue; + if (c->size != 3) { + CADICAL_assert (c->size == 2); + continue; + } + if (--steps < 0) + break; + bool assigned = false; + for (const auto &lit : *c) + if (val (lit)) { + assigned = true; + break; + } + if (assigned) + continue; + steps -= 1 + cache_lines (occs (-pivot).size (), sizeof (Clause *)); + for (const auto &d : occs (-pivot)) { + if (htrs < 0) + break; + if (--steps < 0) + break; + if (d->garbage) + continue; + if (d->size != 3) { + CADICAL_assert (d->size == 2); + continue; + } + for (const auto &lit : *d) + if (val (lit)) { + assigned = true; + break; + } + if (assigned) + continue; + CADICAL_assert (clause.empty ()); + htrs--; + if (hyper_ternary_resolve (c, pivot, d)) { + size_t size = clause.size (); + bool red = (size == 3 || (c->redundant && d->redundant)); + if (lrat) { + CADICAL_assert (lrat_chain.empty ()); + lrat_chain.push_back (c->id); + lrat_chain.push_back (d->id); + } + Clause *r = new_hyper_ternary_resolved_clause (red); + if (red) + r->hyper = true; + lrat_chain.clear (); + clause.clear (); + LOG (r, "hyper ternary resolved"); + stats.htrs++; + for (const auto &lit : *r) + occs (lit).push_back (r); + if (size == 2) { + LOG ("hyper ternary resolvent subsumes both antecedents"); + mark_garbage (c); + mark_garbage (d); + stats.htrs2++; + break; + } else { + CADICAL_assert (r->size == 3); + stats.htrs3++; + } + } else { + LOG (clause, "ignoring size %zd resolvent", clause.size ()); + clause.clear (); + } + } + } +} + +/*------------------------------------------------------------------------*/ + +// Same as 'ternary_lit' but pick the phase of the variable based on the +// number of positive and negative occurrence. + +void Internal::ternary_idx (int idx, int64_t &steps, int64_t &htrs) { + CADICAL_assert (0 < idx); + CADICAL_assert (idx <= max_var); + steps -= 3; + if (!active (idx)) + return; + if (!flags (idx).ternary) + return; + int pos = occs (idx).size (); + int neg = occs (-idx).size (); + if (pos <= opts.ternaryocclim && neg <= opts.ternaryocclim) { + LOG ("index %d has %zd positive and %zd negative occurrences", idx, + occs (idx).size (), occs (-idx).size ()); + int pivot = (neg < pos ? -idx : idx); + ternary_lit (pivot, steps, htrs); + } + flags (idx).ternary = false; +} + +/*------------------------------------------------------------------------*/ + +// One round of ternary resolution over all variables. As in 'block' and +// 'elim' we maintain a persistent global flag 'ternary' for each variable, +// which records, whether a ternary clause containing it was added. Then we +// can focus on those variables for which we have not tried ternary +// resolution before and nothing changed for them since then. This works +// across multiple calls to 'ternary' as well as 'ternary_round' since this +// 'ternary' variable flag is updated during adding (ternary) resolvents. +// This function goes over each variable just once. + +bool Internal::ternary_round (int64_t &steps_limit, int64_t &htrs_limit) { + + CADICAL_assert (!unsat); + +#ifndef CADICAL_QUIET + int64_t bincon = 0; + int64_t terncon = 0; +#endif + + init_occs (); + + steps_limit -= 1 + cache_lines (clauses.size (), sizeof (Clause *)); + for (const auto &c : clauses) { + steps_limit--; + if (c->garbage) + continue; + if (c->size > 3) + continue; + bool assigned = false, marked = false; + for (const auto &lit : *c) { + if (val (lit)) { + assigned = true; + break; + } + if (flags (lit).ternary) + marked = true; + } + if (assigned) + continue; + if (c->size == 2) { +#ifndef CADICAL_QUIET + bincon++; +#endif + } else { + CADICAL_assert (c->size == 3); + if (!marked) + continue; +#ifndef CADICAL_QUIET + terncon++; +#endif + } + + for (const auto &lit : *c) + occs (lit).push_back (c); + } + + PHASE ("ternary", stats.ternary, + "connected %" PRId64 " ternary %.0f%% " + "and %" PRId64 " binary clauses %.0f%%", + terncon, percent (terncon, clauses.size ()), bincon, + percent (bincon, clauses.size ())); + + // Try ternary resolution on all variables once. + // + for (auto idx : vars) { + if (terminated_asynchronously ()) + break; + if (steps_limit < 0) + break; + if (htrs_limit < 0) + break; + ternary_idx (idx, steps_limit, htrs_limit); + } + + // Gather some statistics for the verbose messages below and also + // determine whether new variables have been marked and it would make + // sense to run another round of ternary resolution over those variables. + // + int remain = 0; + for (auto idx : vars) { + if (!active (idx)) + continue; + if (!flags (idx).ternary) + continue; + remain++; + } + if (remain) + PHASE ("ternary", stats.ternary, "%d variables remain %.0f%%", remain, + percent (remain, max_var)); + else + PHASE ("ternary", stats.ternary, "completed hyper ternary resolution"); + + reset_occs (); + CADICAL_assert (!unsat); + + return remain; // Are there variables that should be tried again? +} + +/*------------------------------------------------------------------------*/ + +bool Internal::ternary () { + + if (!opts.ternary) + return false; + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + + // No new ternary clauses added since last time? + // + if (last.ternary.marked == stats.mark.ternary) + return false; + + SET_EFFORT_LIMIT (limit, ternary, true); + + START_SIMPLIFIER (ternary, TERNARY); + stats.ternary++; + + CADICAL_assert (!level); + + CADICAL_assert (!unsat); + if (watching ()) + reset_watches (); + + // The number of clauses derived through ternary resolution can grow + // substantially, particularly for random formulas. Thus we limit the + // number of added clauses too (actually the number of 'htrs'). + // + int64_t htrs_limit = stats.current.redundant + stats.current.irredundant; + htrs_limit *= opts.ternarymaxadd; + htrs_limit /= 100; + + // approximation of ternary ticks. + // TODO: count with ternary.ticks directly. + int64_t steps_limit = stats.ticks.ternary - limit; + stats.ticks.ternary = limit; + + // With 'stats.ternary' we actually count the number of calls to + // 'ternary_round' and not the number of calls to 'ternary'. But before + // the first round we want to show the limit on the number of steps and + // thus we increase counter for the first round here and skip increasing + // it in the loop below. + // + PHASE ("ternary", stats.ternary, + "will run a maximum of %d rounds " + "limited to %" PRId64 " steps and %" PRId64 " clauses", + opts.ternaryrounds, steps_limit, htrs_limit); + + bool resolved_binary_clause = false; + bool completed = false; + + for (int round = 0; + !terminated_asynchronously () && round < opts.ternaryrounds; + round++) { + if (htrs_limit < 0) + break; + if (steps_limit < 0) + break; + if (round) + stats.ternary++; + int old_htrs2 = stats.htrs2; + int old_htrs3 = stats.htrs3; + completed = ternary_round (steps_limit, htrs_limit); + int delta_htrs2 = stats.htrs2 - old_htrs2; + int delta_htrs3 = stats.htrs3 - old_htrs3; + PHASE ("ternary", stats.ternary, + "derived %d ternary and %d binary resolvents", delta_htrs3, + delta_htrs2); + report ('3', !opts.reportall && !(delta_htrs2 + delta_htrs2)); + if (delta_htrs2) + resolved_binary_clause = true; + if (!delta_htrs3) + break; + } + + CADICAL_assert (!occurring ()); + CADICAL_assert (!unsat); + init_watches (); + connect_watches (); + if (!propagate ()) { + LOG ("propagation after connecting watches results in inconsistency"); + learn_empty_clause (); + } + + if (completed) + last.ternary.marked = stats.mark.ternary; + + STOP_SIMPLIFIER (ternary, TERNARY); + + return resolved_binary_clause; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_tier.cpp b/src/sat/cadical/cadical_tier.cpp new file mode 100644 index 000000000..92faf507f --- /dev/null +++ b/src/sat/cadical/cadical_tier.cpp @@ -0,0 +1,59 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::recompute_tier () { + if (!opts.recomputetier) + return; + + ++stats.tierecomputed; + const int64_t delta = + stats.tierecomputed >= 16 ? 1u << 16 : (1u << stats.tierecomputed); + lim.recompute_tier = stats.conflicts + delta; + LOG ("rescheduling in %zd at %zd (conflicts at %zd)", delta, + lim.recompute_tier, stats.conflicts); +#ifndef CADICAL_NDEBUG + uint64_t total_used = 0; + for (auto u : stats.used[stable]) + total_used += u; + CADICAL_assert (total_used == stats.bump_used[stable]); +#endif + + if (!stats.bump_used[stable]) { + tier1[stable] = opts.reducetier1glue; + tier2[stable] = opts.reducetier2glue; + LOG ("tier1 limit = %d", tier1[stable]); + LOG ("tier2 limit = %d", tier2[stable]); + return; + } else { + uint64_t accumulated_tier1_limit = + stats.bump_used[stable] * opts.tier1limit / 100; + uint64_t accumulated_tier2_limit = + stats.bump_used[stable] * opts.tier2limit / 100; + uint64_t accumulated_used = 0; + for (size_t glue = 0; glue < stats.used[stable].size (); ++glue) { + const uint64_t u = stats.used[stable][glue]; + accumulated_used += u; + if (accumulated_used <= accumulated_tier1_limit) { + tier1[stable] = glue; + } + if (accumulated_used >= accumulated_tier2_limit) { + tier2[stable] = glue; + break; + } + } + } + + LOG ("tier1 limit = %d in %s mode", tier1[stable], + stable ? "stable" : "focused"); + LOG ("tier2 limit = %d in %s mode", tier2[stable], + stable ? "stable" : "focused"); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_transred.cpp b/src/sat/cadical/cadical_transred.cpp new file mode 100644 index 000000000..82a961dee --- /dev/null +++ b/src/sat/cadical/cadical_transred.cpp @@ -0,0 +1,259 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +// Implement transitive reduction in the binary implication graph. This is +// important for hyper binary resolution, which has the risk to produce too +// many hyper binary resolvents otherwise. This algorithm only works on +// binary clauses and is usually pretty fast. It will also find some failed +// literals (in the binary implication graph). + +void Internal::transred () { + if (!opts.transred) + return; + if (unsat) + return; + if (terminated_asynchronously ()) + return; + if (!stats.current.redundant && !stats.current.irredundant) + return; + + CADICAL_assert (opts.transred); + CADICAL_assert (!level); + + START_SIMPLIFIER (transred, TRANSRED); + stats.transreds++; + + // Transitive reduction can not be run to completion for larger formulas + // with many binary clauses. We bound it in the same way as 'probe_core'. + // + int64_t limit = stats.propagations.search; + limit -= last.transred.propagations; + limit *= 1e-3 * opts.transredeffort; + if (limit < opts.transredmineff) + limit = opts.transredmineff; + if (limit > opts.transredmaxeff) + limit = opts.transredmaxeff; + + PHASE ("transred", stats.transreds, + "transitive reduction limit of %" PRId64 " propagations", limit); + + const auto end = clauses.end (); + auto i = clauses.begin (); + + // Find first clause not checked for being transitive yet. + // + for (; i != end; i++) { + Clause *c = *i; + if (c->garbage) + continue; + if (c->size != 2) + continue; + if (c->redundant && c->hyper) + continue; + if (!c->transred) + break; + } + + // If all candidate clauses have been checked reschedule all. + // + if (i == end) { + + PHASE ("transred", stats.transreds, + "rescheduling all clauses since no clauses to check left"); + for (i = clauses.begin (); i != end; i++) { + Clause *c = *i; + if (c->transred) + c->transred = false; + } + i = clauses.begin (); + } + + // Move watches of binary clauses to the front. Thus we can stop iterating + // watches as soon a long clause is found during watch traversal. + // + sort_watches (); + + // This working stack plays the same role as the 'trail' during standard + // propagation. + // + vector work; + + int64_t propagations = 0, units = 0, removed = 0; + + while (!unsat && i != end && !terminated_asynchronously () && + propagations < limit) { + Clause *c = *i++; + + // A clause is a candidate for being transitive if it is binary, and not + // the result of hyper binary resolution. The reason for excluding + // those, is that they come in large numbers, most of them are reduced + // away anyhow and further are non-transitive at the point they are + // added (see the code in 'hyper_binary_resolve' in 'prope.cpp' and + // also check out our CPAIOR paper on tree-based look ahead). + // + if (c->garbage) + continue; + if (c->size != 2) + continue; + if (c->redundant && c->hyper) + continue; + if (c->transred) + continue; // checked before? + c->transred = true; // marked as checked + + LOG (c, "checking transitive reduction of"); + + // Find a different path from 'src' to 'dst' in the binary implication + // graph, not using 'c'. Since this is the same as checking whether + // there is a path from '-dst' to '-src', we can do the reverse search + // if the number of watches of '-dst' is larger than those of 'src'. + // + int src = -c->literals[0]; + int dst = c->literals[1]; + if (val (src) || val (dst)) + continue; + if (watches (-src).size () < watches (dst).size ()) { + int tmp = dst; + dst = -src; + src = -tmp; + } + + LOG ("searching path from %d to %d", src, dst); + + // If the candidate clause is irredundant then we can not use redundant + // binary clauses in the implication graph. See our inprocessing rules + // paper, why this restriction is required. + // + const bool irredundant = !c->redundant; + + CADICAL_assert (work.empty ()); + mark (src); + work.push_back (src); + LOG ("transred assign %d", src); + + bool transitive = false; // found path from 'src' to 'dst'? + bool failed = false; // 'src' failed literal? + + size_t j = 0; // 'propagated' in BFS + + CADICAL_assert (lrat_chain.empty ()); + CADICAL_assert (mini_chain.empty ()); + vector parents; + + while (!transitive && !failed && j < work.size ()) { + const int lit = work[j++]; + CADICAL_assert (marked (lit) > 0); + LOG ("transred propagating %d", lit); + propagations++; + const Watches &ws = watches (-lit); + const const_watch_iterator eow = ws.end (); + const_watch_iterator k; + for (k = ws.begin (); !transitive && !failed && k != eow; k++) { + const Watch &w = *k; + if (!w.binary ()) + break; // since we sorted watches above + Clause *d = w.clause; + if (d == c) + continue; + if (irredundant && d->redundant) + continue; + if (d->garbage) + continue; + const int other = w.blit; + if (other == dst) + transitive = true; // 'dst' reached + else { + const int tmp = marked (other); + if (tmp > 0) + continue; + else if (tmp < 0) { + if (lrat) { + parents.push_back (lit); + mini_chain.push_back (d->id); + work.push_back (other); + } + LOG ("found both %d and %d reachable", -other, other); + failed = true; + } else { + if (lrat) { + parents.push_back (lit); + mini_chain.push_back (d->id); + } + mark (other); + work.push_back (other); + LOG ("transred assign %d", other); + } + } + } + } + + int failed_lit = work.back (); + int next_pos = 0; + int next_neg = 0; + + // Unassign all assigned literals (same as '[bp]acktrack'). + // + while (!work.empty ()) { + const int lit = work.back (); + work.pop_back (); + if (lrat && failed && !work.empty ()) { + CADICAL_assert (!parents.empty () && !mini_chain.empty ()); + LOG ("transred LRAT current lit %d next pos %d next neg %d", lit, + next_pos, next_neg); + if (lit == failed_lit || lit == next_pos) { + lrat_chain.push_back (mini_chain.back ()); + next_pos = parents.back (); + } else if (lit == -failed_lit || lit == next_neg) { + lrat_chain.push_back (mini_chain.back ()); + next_neg = parents.back (); + } + parents.pop_back (); + mini_chain.pop_back (); + } + unmark (lit); + } + mini_chain.clear (); + CADICAL_assert (mini_chain.empty ()); + if (lrat && failed) { + reverse (lrat_chain.begin (), lrat_chain.end ()); + } + + if (transitive) { + removed++; + stats.transitive++; + LOG (c, "transitive redundant"); + mark_garbage (c); + } else if (failed) { + units++; + LOG ("found failed literal %d during transitive reduction", src); + stats.failed++; + stats.transredunits++; + assign_unit (-src); + if (!propagate ()) { + VERBOSE (1, "propagating new unit results in conflict"); + learn_empty_clause (); + } + } + lrat_chain.clear (); + } + + last.transred.propagations = stats.propagations.search; + stats.propagations.transred += propagations; + erase_vector (work); + + PHASE ("transred", stats.transreds, + "removed %" PRId64 " transitive clauses, found %" PRId64 " units", + removed, units); + + STOP_SIMPLIFIER (transred, TRANSRED); + report ('t', !opts.reportall && !(removed + units)); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_unstable.cpp b/src/sat/cadical/cadical_unstable.cpp new file mode 100644 index 000000000..c3162d70a --- /dev/null +++ b/src/sat/cadical/cadical_unstable.cpp @@ -0,0 +1,38 @@ +#include "global.h" + +#ifdef PROFILE_MODE +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +bool Internal::propagate_unstable () { + CADICAL_assert (!stable); + START (propunstable); + bool res = propagate (); + STOP (propunstable); + return res; +} + +void Internal::analyze_unstable () { + CADICAL_assert (!stable); + START (analyzeunstable); + analyze (); + STOP (analyzeunstable); +} + +int Internal::decide_unstable () { + CADICAL_assert (!stable); + return decide (); +} + +}; // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END + +#else +ABC_NAMESPACE_IMPL_START +int unstable_if_no_profile_mode; +ABC_NAMESPACE_IMPL_END +#endif diff --git a/src/sat/cadical/cadical_util.cpp b/src/sat/cadical/cadical_util.cpp new file mode 100644 index 000000000..fd9b5b29d --- /dev/null +++ b/src/sat/cadical/cadical_util.cpp @@ -0,0 +1,135 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +bool parse_int_str (const char *val_str, int &val) { + if (!strcmp (val_str, "true")) + val = 1; + else if (!strcmp (val_str, "false")) + val = 0; + else { + const char *p = val_str; + int sign; + + if (*p == '-') + sign = -1, p++; + else + sign = 1; + + int ch; + if (!isdigit ((ch = *p++))) + return false; + + const int64_t bound = -(int64_t) INT_MIN; + int64_t mantissa = ch - '0'; + + while (isdigit (ch = *p++)) { + if (bound / 10 < mantissa) + mantissa = bound; + else + mantissa *= 10; + const int digit = ch - '0'; + if (bound - digit < mantissa) + mantissa = bound; + else + mantissa += digit; + } + + int exponent = 0; + if (ch == 'e') { + while (isdigit ((ch = *p++))) + exponent = exponent ? 10 : ch - '0'; + if (ch) + return false; + } else if (ch) + return false; + + CADICAL_assert (exponent <= 10); + int64_t val64 = mantissa; + for (int i = 0; i < exponent; i++) + val64 *= 10; + + if (sign < 0) { + val64 = -val64; + if (val64 < INT_MIN) + val64 = INT_MIN; + } else { + if (val64 > INT_MAX) + val64 = INT_MAX; + } + + CADICAL_assert (INT_MIN <= val64); + CADICAL_assert (val64 <= INT_MAX); + + val = val64; + } + return true; +} + +/*------------------------------------------------------------------------*/ + +bool has_suffix (const char *str, const char *suffix) { + size_t k = strlen (str), l = strlen (suffix); + return k > l && !strcmp (str + k - l, suffix); +} + +bool has_prefix (const char *str, const char *prefix) { + for (const char *p = str, *q = prefix; *q; q++, p++) + if (*q != *p) + return false; + return true; +} + +/*------------------------------------------------------------------------*/ + +bool is_color_option (const char *arg) { + return !strcmp (arg, "--color") || !strcmp (arg, "--colors") || + !strcmp (arg, "--colour") || !strcmp (arg, "--colours") || + !strcmp (arg, "--color=1") || !strcmp (arg, "--colors=1") || + !strcmp (arg, "--colour=1") || !strcmp (arg, "--colours=1") || + !strcmp (arg, "--color=true") || !strcmp (arg, "--colors=true") || + !strcmp (arg, "--colour=true") || !strcmp (arg, "--colours=true"); +} + +bool is_no_color_option (const char *arg) { + return !strcmp (arg, "--no-color") || !strcmp (arg, "--no-colors") || + !strcmp (arg, "--no-colour") || !strcmp (arg, "--no-colours") || + !strcmp (arg, "--color=0") || !strcmp (arg, "--colors=0") || + !strcmp (arg, "--colour=0") || !strcmp (arg, "--colours=0") || + !strcmp (arg, "--color=false") || + !strcmp (arg, "--colors=false") || + !strcmp (arg, "--colour=false") || + !strcmp (arg, "--colours=false"); +} + +/*------------------------------------------------------------------------*/ + +static uint64_t primes[] = { + 1111111111111111111lu, 2222222222222222249lu, 3333333333333333347lu, + 4444444444444444537lu, 5555555555555555621lu, 6666666666666666677lu, + 7777777777777777793lu, 8888888888888888923lu, 9999999999999999961lu, +}; + +uint64_t hash_string (const char *str) { + const unsigned size = sizeof primes / sizeof *primes; + uint64_t res = 0; + unsigned char ch; + unsigned i = 0; + for (const char *p = str; (ch = *p); p++) { + res += ch; + res *= primes[i++]; + if (i == size) + i = 0; + } + return res; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_var.cpp b/src/sat/cadical/cadical_var.cpp new file mode 100644 index 000000000..e2348da7b --- /dev/null +++ b/src/sat/cadical/cadical_var.cpp @@ -0,0 +1,45 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::reset_subsume_bits () { + LOG ("marking all variables as not subsume"); + for (auto idx : vars) + flags (idx).subsume = false; +} + +void Internal::check_var_stats () { +#ifndef CADICAL_NDEBUG + int64_t fixed = 0, eliminated = 0, substituted = 0, pure = 0, unused = 0; + for (auto idx : vars) { + Flags &f = flags (idx); + if (f.active ()) + continue; + if (f.fixed ()) + fixed++; + if (f.eliminated ()) + eliminated++; + if (f.substituted ()) + substituted++; + if (f.unused ()) + unused++; + if (f.pure ()) + pure++; + } + CADICAL_assert (stats.now.fixed == fixed); + CADICAL_assert (stats.now.eliminated == eliminated); + CADICAL_assert (stats.now.substituted == substituted); + CADICAL_assert (stats.now.pure == pure); + int64_t inactive = unused + fixed + eliminated + substituted + pure; + CADICAL_assert (stats.inactive == inactive); + CADICAL_assert (max_var == stats.active + stats.inactive); +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_veripbtracer.cpp b/src/sat/cadical/cadical_veripbtracer.cpp new file mode 100644 index 000000000..b1f7c95a7 --- /dev/null +++ b/src/sat/cadical/cadical_veripbtracer.cpp @@ -0,0 +1,400 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +VeripbTracer::VeripbTracer (Internal *i, File *f, bool b, bool a, bool c) + : internal (i), file (f), with_antecedents (a), checked_deletions (c), + num_clauses (0), size_clauses (0), clauses (0), last_hash (0), + last_id (0), last_clause (0) +#ifndef CADICAL_QUIET + , + added (0), deleted (0) +#endif +{ + (void) internal; + + // Initialize random number table for hash function. + // + Random random (42); + for (unsigned n = 0; n < num_nonces; n++) { + uint64_t nonce = random.next (); + if (!(nonce & 1)) + nonce++; + CADICAL_assert (nonce), CADICAL_assert (nonce & 1); + nonces[n] = nonce; + } +#ifndef CADICAL_NDEBUG + binary = b; +#else + (void) b; +#endif +} + +void VeripbTracer::connect_internal (Internal *i) { + internal = i; + file->connect_internal (internal); + LOG ("VERIPB TRACER connected to internal"); +} + +VeripbTracer::~VeripbTracer () { + LOG ("VERIPB TRACER delete"); + delete file; + for (size_t i = 0; i < size_clauses; i++) + for (HashId *c = clauses[i], *next; c; c = next) + next = c->next, delete_clause (c); + delete[] clauses; +} + +/*------------------------------------------------------------------------*/ + +void VeripbTracer::enlarge_clauses () { + CADICAL_assert (num_clauses == size_clauses); + const uint64_t new_size_clauses = size_clauses ? 2 * size_clauses : 1; + LOG ("VeriPB Tracer enlarging clauses from %" PRIu64 " to %" PRIu64, + (uint64_t) size_clauses, (uint64_t) new_size_clauses); + HashId **new_clauses; + new_clauses = new HashId *[new_size_clauses]; + clear_n (new_clauses, new_size_clauses); + for (uint64_t i = 0; i < size_clauses; i++) { + for (HashId *c = clauses[i], *next; c; c = next) { + next = c->next; + const uint64_t h = reduce_hash (c->hash, new_size_clauses); + c->next = new_clauses[h]; + new_clauses[h] = c; + } + } + delete[] clauses; + clauses = new_clauses; + size_clauses = new_size_clauses; +} + +HashId *VeripbTracer::new_clause () { + HashId *res = new HashId (); + res->next = 0; + res->hash = last_hash; + res->id = last_id; + last_clause = res; + num_clauses++; + return res; +} + +void VeripbTracer::delete_clause (HashId *c) { + CADICAL_assert (c); + num_clauses--; + delete c; +} + +uint64_t VeripbTracer::reduce_hash (uint64_t hash, uint64_t size) { + CADICAL_assert (size > 0); + unsigned shift = 32; + uint64_t res = hash; + while ((((uint64_t) 1) << shift) > size) { + res ^= res >> shift; + shift >>= 1; + } + res &= size - 1; + CADICAL_assert (res < size); + return res; +} + +uint64_t VeripbTracer::compute_hash (const int64_t id) { + CADICAL_assert (id > 0); + unsigned j = id % num_nonces; // Dont know if this is a good + uint64_t tmp = nonces[j] * (uint64_t) id; // hash funktion or even better + return last_hash = tmp; // than just using id. +} + +bool VeripbTracer::find_and_delete (const int64_t id) { + if (!num_clauses) + return false; + /* + if (last_clause && last_clause->id == id) { + const uint64_t h = reduce_hash (last_clause->hash, size_clauses); + clauses[h] = last_clause->next; + delete last_clause; + return true; + } + */ + HashId **res = 0, *c; + const uint64_t hash = compute_hash (id); + const uint64_t h = reduce_hash (hash, size_clauses); + for (res = clauses + h; (c = *res); res = &c->next) { + if (c->hash == hash && c->id == id) { + break; + } + if (!c->next) + return false; + } + if (!c) + return false; + CADICAL_assert (c && res); + *res = c->next; + delete_clause (c); + return true; +} + +void VeripbTracer::insert () { + if (num_clauses == size_clauses) + enlarge_clauses (); + const uint64_t h = reduce_hash (compute_hash (last_id), size_clauses); + HashId *c = new_clause (); + c->next = clauses[h]; + clauses[h] = c; +} + +/*------------------------------------------------------------------------*/ + +inline void VeripbTracer::put_binary_zero () { + CADICAL_assert (binary); + CADICAL_assert (file); + file->put ((unsigned char) 0); +} + +inline void VeripbTracer::put_binary_lit (int lit) { + CADICAL_assert (binary); + CADICAL_assert (file); + CADICAL_assert (lit != INT_MIN); + unsigned x = 2 * abs (lit) + (lit < 0); + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +inline void VeripbTracer::put_binary_id (int64_t id, bool can_be_negative) { + CADICAL_assert (binary); + CADICAL_assert (file); + uint64_t x = abs (id); + if (can_be_negative) { + x = 2 * x + (id < 0); + } + unsigned char ch; + while (x & ~0x7f) { + ch = (x & 0x7f) | 0x80; + file->put (ch); + x >>= 7; + } + ch = x; + file->put (ch); +} + +/*------------------------------------------------------------------------*/ + +void VeripbTracer::veripb_add_derived_clause ( + int64_t id, bool redundant, const vector &clause, + const vector &chain) { + file->put ("pol "); + bool first = true; + for (auto p = chain.rbegin (); p != chain.rend (); p++) { + auto cid = *p; + if (first) { + first = false; + file->put (cid); + } else { + file->put (' '); + file->put (cid); + file->put (" + s"); + } + } + file->put ("\n"); + file->put ("e "); + for (const auto &external_lit : clause) { + file->put ("1 "); + if (external_lit < 0) + file->put ('~'); + file->put ('x'); + file->put (abs (external_lit)); + file->put (' '); + } + file->put (">= 1 ; "); + file->put (id); + file->put (" ;\n"); + if (!redundant && checked_deletions) { + file->put ("core id "); + file->put (id); + file->put ("\n"); + } +} + +void VeripbTracer::veripb_add_derived_clause (int64_t id, bool redundant, + const vector &clause) { + file->put ("rup "); + for (const auto &external_lit : clause) { + file->put ("1 "); + if (external_lit < 0) + file->put ('~'); + file->put ('x'); + file->put (abs (external_lit)); + file->put (' '); + } + file->put (">= 1 ;\n"); + if (!redundant && checked_deletions) { + file->put ("core id "); + file->put (id); + file->put ("\n"); + } +} + +void VeripbTracer::veripb_begin_proof (int64_t reserved_ids) { + file->put ("pseudo-Boolean proof version 2.0\n"); + file->put ("f "); + file->put (reserved_ids); + file->put ("\n"); +} + +void VeripbTracer::veripb_delete_clause (int64_t id, bool redundant) { + if (!redundant && checked_deletions && find_and_delete (id)) + return; + if (redundant || !checked_deletions) + file->put ("del id "); + else { + file->put ("delc "); + } + file->put (id); + file->put ("\n"); +} + +void VeripbTracer::veripb_report_status (bool unsat, int64_t conflict_id) { + file->put ("output NONE\n"); + if (unsat) { + file->put ("conclusion UNSAT : "); + file->put (conflict_id); + file->put (" \n"); + } else + file->put ("conclusion NONE\n"); + file->put ("end pseudo-Boolean proof\n"); +} + +void VeripbTracer::veripb_strengthen (int64_t id) { + if (!checked_deletions) + return; + file->put ("core id "); + file->put (id); + file->put ("\n"); +} + +/*------------------------------------------------------------------------*/ + +void VeripbTracer::begin_proof (int64_t id) { + if (file->closed ()) + return; + LOG ("VERIPB TRACER tracing start of proof with %" PRId64 + "original clauses", + id); + veripb_begin_proof (id); +} + +void VeripbTracer::add_derived_clause (int64_t id, bool redundant, + const vector &clause, + const vector &chain) { + if (file->closed ()) + return; + LOG ("VERIPB TRACER tracing addition of derived clause[%" PRId64 "]", id); + if (with_antecedents) + veripb_add_derived_clause (id, redundant, clause, chain); + else + veripb_add_derived_clause (id, redundant, clause); +#ifndef CADICAL_QUIET + added++; +#endif +} + +void VeripbTracer::delete_clause (int64_t id, bool redundant, + const vector &) { + if (file->closed ()) + return; + LOG ("VERIPB TRACER tracing deletion of clause[%" PRId64 "]", id); + veripb_delete_clause (id, redundant); +#ifndef CADICAL_QUIET + deleted++; +#endif +} + +void VeripbTracer::report_status (int status, int64_t conflict_id) { + if (file->closed ()) + return; +#ifdef LOGGING + if (conflict_id) + LOG ("VERIPB TRACER tracing finalization of proof with empty " + "clause[%" PRId64 "]", + conflict_id); +#endif + veripb_report_status (status == UNSATISFIABLE, conflict_id); +} + +void VeripbTracer::weaken_minus (int64_t id, const vector &) { + if (!checked_deletions) + return; + if (file->closed ()) + return; + LOG ("VERIPB TRACER tracing weaken minus of clause[%" PRId64 "]", id); + last_id = id; + insert (); +} + +void VeripbTracer::strengthen (int64_t id) { + if (file->closed ()) + return; + LOG ("VERIPB TRACER tracing strengthen of clause[%" PRId64 "]", id); + veripb_strengthen (id); +} + +/*------------------------------------------------------------------------*/ + +bool VeripbTracer::closed () { return file->closed (); } + +#ifndef CADICAL_QUIET + +void VeripbTracer::print_statistics () { + // TODO complete + uint64_t bytes = file->bytes (); + uint64_t total = added + deleted; + MSG ("VeriPB %" PRId64 " added clauses %.2f%%", added, + percent (added, total)); + MSG ("VeriPB %" PRId64 " deleted clauses %.2f%%", deleted, + percent (deleted, total)); + MSG ("VeriPB %" PRId64 " bytes (%.2f MB)", bytes, + bytes / (double) (1 << 20)); +} + +#endif + +void VeripbTracer::close (bool print) { + CADICAL_assert (!closed ()); + file->close (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("VeriPB proof file '%s' closed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +void VeripbTracer::flush (bool print) { + CADICAL_assert (!closed ()); + file->flush (); +#ifndef CADICAL_QUIET + if (print) { + MSG ("VeriPB proof file '%s' flushed", file->name ()); + print_statistics (); + } +#else + (void) print; +#endif +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_version.cpp b/src/sat/cadical/cadical_version.cpp new file mode 100644 index 000000000..a0cceef63 --- /dev/null +++ b/src/sat/cadical/cadical_version.cpp @@ -0,0 +1,113 @@ +#include "global.h" + +/*------------------------------------------------------------------------*/ + +// To simplify the build process without 'make', you can disable the +// generation of 'build.hpp' through '../scripts/make-build-header.sh' by +// defining '-DCADICAL_NBUILD'. Then we try to guess part of the configuration. + +#ifndef CADICAL_NBUILD +#if __GNUC__ > 4 +#if __has_include() +#include "build.hpp" +#endif // __has_include +#else +#include "build.hpp" +#endif // __GNUC > 4 +#endif // CADICAL_NBUILD + +/*------------------------------------------------------------------------*/ + +// We prefer short 3 character version numbers made of digits and lower case +// letters only, which gives 36^3 = 46656 different versions. The following +// macro is used for the non-standard build process and only set from +// the file '../VERSION' with '../scripts/update-version.sh'. The standard +// build process relies on 'VERSION' to be defined in 'build.hpp'. + +#ifdef CADICAL_NBUILD +#ifndef VERSION +#define VERSION "2.2.0-rc1" +#endif // VERSION +#endif // CADICAL_NBUILD + + /*------------------------------------------------------------------------*/ + + // The copyright of the code is here. + + static const char *COPYRIGHT = "Copyright (c) 2016-2024"; +static const char *AUTHORS = + "A. Biere, M. Fleury, N. Froleyks, K. Fazekas, F. Pollitt, T. Faller"; +static const char *AFFILIATIONS = + "JKU Linz, University of Freiburg, TU Wien"; + +/*------------------------------------------------------------------------*/ + +// Again if we do not have 'CADICAL_NBUILD' or if something during configuration is +// broken we still want to be able to compile the solver. In this case we +// then try our best to figure out 'COMPILER' and 'DATE', but for +// 'IDENTIFIER' and 'FLAGS' we can only use the '0' string, which marks +// those as unknown. + +#ifndef COMPILER +#ifdef __clang__ +#ifdef __VERSION__ +#define COMPILER "clang++-" __VERSION__ +#else +#define COMPILER "clang++" +#endif +#elif defined(__GNUC__) +#ifdef __VERSION__ +#define COMPILER "g++-" __VERSION__ +#else +#define COMPILER "g++" +#endif +#else +#define COMPILER 0 +#endif +#endif + +// GIT SHA2 identifier. +// +#ifndef IDENTIFIER +#define IDENTIFIER 0 +#endif +#ifdef SHORTID +#define SHORTIDSTR "-" SHORTID +#else +#define SHORTIDSTR "" +#define SHORTID 0 +#endif + +// Compilation flags. +// +#ifndef FLAGS +#define FLAGS 0 +#endif + +// Build Time and operating system. +// +#ifndef DATE +#define DATE __DATE__ " " __TIME__ +#endif + +/*------------------------------------------------------------------------*/ + +#include "version.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +const char *version () { return VERSION; } +const char *copyright () { return COPYRIGHT; } +const char *authors () { return AUTHORS; } +const char *affiliations () { return AFFILIATIONS; } +const char *signature () { return "cadical-" VERSION SHORTIDSTR; } +const char *identifier () { return IDENTIFIER; } +const char *compiler () { return COMPILER; } +const char *date () { return DATE; } +const char *flags () { return FLAGS; } + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_vivify.cpp b/src/sat/cadical/cadical_vivify.cpp new file mode 100644 index 000000000..8d99c2932 --- /dev/null +++ b/src/sat/cadical/cadical_vivify.cpp @@ -0,0 +1,1962 @@ +#include "global.h" + +#include "vivify.hpp" +#include "internal.hpp" +#include "util.hpp" +#include +#include +#include + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Vivification is a special case of asymmetric tautology elimination (ATE) +// and asymmetric literal elimination (ALE). It strengthens and removes +// clauses proven redundant through unit propagation. +// +// The original algorithm is due to a paper by Piette, Hamadi and Sais +// published at ECAI'08. We have an inprocessing version, e.g., it does not +// necessarily run-to-completion. Our version also performs conflict +// analysis and uses a new heuristic for selecting clauses to vivify. + +// Our idea is to focus on clauses with many occurrences of its literals in +// other clauses first. This both complements nicely our implementation of +// subsume, which is bounded, e.g., subsumption attempts are skipped for +// very long clauses with literals with many occurrences and also is +// stronger in the sense that it enables to remove more clauses due to unit +// propagation (AT checks). + +// While first focusing on irredundant clause we then added a separate phase +// upfront which focuses on strengthening also redundant clauses in spirit +// of the ideas presented in the IJCAI'17 paper by M. Luo, C.-M. Li, F. +// Xiao, F. Manya, and Z. Lu. + +// There is another very similar approach called 'distilliation' published +// by Han and Somenzi in DAC'07, which reorganizes the CNF in a trie data +// structure to reuse decisions and propagations along the trie. We used +// that as an inspiration but instead of building a trie we simple sort +// clauses and literals in such a way that we get the same effect. If a new +// clause is 'distilled' or 'vivified' we first check how many of the +// decisions (which are only lazily undone) can be reused for that clause. +// Reusing can be improved by picking a global literal order and sorting the +// literals in all clauses with respect to that order. We favor literals +// with more occurrences first. Then we sort clauses lexicographically with +// respect to that literal order. + +/*------------------------------------------------------------------------*/ + +// Candidate clause 'subsumed' is subsumed by 'subsuming'. + +inline void Internal::vivify_subsume_clause (Clause *subsuming, + Clause *subsumed) { + stats.subsumed++; + stats.vivifysubs++; +#ifndef CADICAL_NDEBUG + CADICAL_assert (subsuming); + CADICAL_assert (subsumed); + CADICAL_assert (subsuming != subsumed); + CADICAL_assert (!subsumed->garbage); + // size after removeing units; + int real_size_subsuming = 0, real_size_subsumed = 0; + for (auto lit : *subsuming) { + if (!val (lit) || var (lit).level) + ++real_size_subsuming; + else + CADICAL_assert (val (lit) < 0); + } + for (auto lit : *subsumed) { + if (!val (lit) || var (lit).level) + ++real_size_subsumed; + else + CADICAL_assert (val (lit) < 0); + } + CADICAL_assert (real_size_subsuming <= real_size_subsumed); +#endif + LOG (subsumed, "subsumed"); + if (subsumed->redundant) { + stats.subred++; + ++stats.vivifysubred; + } else { + stats.subirr++; + ++stats.vivifysubirr; + } + if (subsuming->garbage) { + CADICAL_assert (subsuming->size == 2); + LOG (subsuming, + "binary subsuming clause was already deleted, so undeleting"); + subsuming->garbage = false; + subsuming->glue = 1; + ++stats.current.total; + if (subsuming->redundant) + stats.current.redundant++; + else + stats.current.irredundant++, stats.irrlits += subsuming->size; + } + if (subsumed->redundant || !subsuming->redundant) { + mark_garbage (subsumed); + return; + } + LOG ("turning redundant subsuming clause into irredundant clause"); + subsuming->redundant = false; + if (proof) + proof->strengthen (subsuming->id); + mark_garbage (subsumed); + mark_added (subsuming); + stats.current.irredundant++; + stats.added.irredundant++; + stats.irrlits += subsuming->size; + CADICAL_assert (stats.current.redundant > 0); + stats.current.redundant--; + CADICAL_assert (stats.added.redundant > 0); + stats.added.redundant--; + // ... and keep 'stats.added.total'. +} + +// demoting a clause (opposite is promote from subsume.cpp) + +inline void Internal::demote_clause (Clause *c) { + stats.subsumed++; + stats.vivifydemote++; + LOG (c, "demoting"); + CADICAL_assert (!c->redundant); + mark_removed (c); + c->redundant = true; + CADICAL_assert (stats.current.irredundant > 0); + stats.current.irredundant--; + CADICAL_assert (stats.added.irredundant > 0); + stats.added.irredundant--; + stats.irrlits -= c->size; + stats.current.redundant++; + stats.added.redundant++; + c->glue = c->size - 1; + // ... and keep 'stats.added.total'. +} + +/*------------------------------------------------------------------------*/ +// For vivification we have a separate dedicated propagation routine, which +// prefers to propagate binary clauses first. It also uses its own +// assignment procedure 'vivify_assign', which does not mess with phase +// saving during search nor the conflict and other statistics and further +// can be inlined separately here. The propagation routine needs to ignore +// (large) clauses which are currently vivified. + +inline void Internal::vivify_assign (int lit, Clause *reason) { + require_mode (VIVIFY); + const int idx = vidx (lit); + CADICAL_assert (!vals[idx]); + CADICAL_assert (!flags (idx).eliminated () || !reason); + Var &v = var (idx); + v.level = level; // required to reuse decisions + v.trail = (int) trail.size (); // used in 'vivify_better_watch' + CADICAL_assert ((int) num_assigned < max_var); + num_assigned++; + v.reason = level ? reason : 0; // for conflict analysis + if (!level) + learn_unit_clause (lit); + const signed char tmp = sign (lit); + vals[idx] = tmp; + vals[-idx] = -tmp; + CADICAL_assert (val (lit) > 0); + CADICAL_assert (val (-lit) < 0); + trail.push_back (lit); + LOG (reason, "vivify assign %d", lit); +} + +// Assume negated literals in candidate clause. + +void Internal::vivify_assume (int lit) { + require_mode (VIVIFY); + level++; + control.push_back (Level (lit, trail.size ())); + LOG ("vivify decide %d", lit); + CADICAL_assert (level > 0); + CADICAL_assert (propagated == trail.size ()); + vivify_assign (lit, 0); +} + +// Dedicated routine similar to 'propagate' in 'propagate.cpp' and +// 'probe_propagate' with 'probe_propagate2' in 'probe.cpp'. Please refer +// to that code for more explanation on how propagation is implemented. + +bool Internal::vivify_propagate (int64_t &ticks) { + require_mode (VIVIFY); + CADICAL_assert (!unsat); + START (propagate); + int64_t before = propagated2 = propagated; + for (;;) { + if (propagated2 != trail.size ()) { + const int lit = -trail[propagated2++]; + LOG ("vivify propagating %d over binary clauses", -lit); + Watches &ws = watches (lit); + ticks += + 1 + cache_lines (ws.size (), sizeof (const_watch_iterator *)); + for (const auto &w : ws) { + if (!w.binary ()) + continue; + const signed char b = val (w.blit); + if (b > 0) + continue; + if (b < 0) + conflict = w.clause; // but continue + else { + ticks++; + build_chain_for_units (w.blit, w.clause, 0); + vivify_assign (w.blit, w.clause); + lrat_chain.clear (); + } + } + } else if (!conflict && propagated != trail.size ()) { + const int lit = -trail[propagated++]; + LOG ("vivify propagating %d over large clauses", -lit); + Watches &ws = watches (lit); + const const_watch_iterator eow = ws.end (); + const_watch_iterator i = ws.begin (); + ticks += 1 + cache_lines (ws.size (), sizeof (*i)); + watch_iterator j = ws.begin (); + while (i != eow) { + const Watch w = *j++ = *i++; + if (w.binary ()) + continue; + if (val (w.blit) > 0) + continue; + ticks++; + if (w.clause->garbage) { + j--; + continue; + } + literal_iterator lits = w.clause->begin (); + const int other = lits[0] ^ lits[1] ^ lit; + const signed char u = val (other); + if (u > 0) + j[-1].blit = other; + else { + const int size = w.clause->size; + const const_literal_iterator end = lits + size; + const literal_iterator middle = lits + w.clause->pos; + literal_iterator k = middle; + signed char v = -1; + int r = 0; + while (k != end && (v = val (r = *k)) < 0) + k++; + if (v < 0) { + k = lits + 2; + CADICAL_assert (w.clause->pos <= size); + while (k != middle && (v = val (r = *k)) < 0) + k++; + } + w.clause->pos = k - lits; + CADICAL_assert (lits + 2 <= k), CADICAL_assert (k <= w.clause->end ()); + if (v > 0) + j[-1].blit = r; + else if (!v) { + LOG (w.clause, "unwatch %d in", r); + lits[0] = other; + lits[1] = r; + *k = lit; + ticks++; + watch_literal (r, lit, w.clause); + j--; + } else if (!u) { + if (w.clause == ignore) { + LOG ("ignoring propagation due to clause to vivify"); + continue; + } + ticks++; + CADICAL_assert (v < 0); + vivify_chain_for_units (other, w.clause); + vivify_assign (other, w.clause); + lrat_chain.clear (); + } else { + if (w.clause == ignore) { + LOG ("ignoring conflict due to clause to vivify"); + continue; + } + CADICAL_assert (u < 0); + CADICAL_assert (v < 0); + conflict = w.clause; + break; + } + } + } + if (j != i) { + while (i != eow) + *j++ = *i++; + ws.resize (j - ws.begin ()); + } + } else + break; + } + int64_t delta = propagated2 - before; + stats.propagations.vivify += delta; + if (conflict) + LOG (conflict, "conflict"); + STOP (propagate); + return !conflict; +} + +/*------------------------------------------------------------------------*/ + +// Check whether a literal occurs less often. In the implementation below +// (search for 'int64_t score = ...' or '@4') we actually compute a +// weighted occurrence count similar to the Jeroslow Wang heuristic. + +struct vivify_more_noccs { + + Internal *internal; + + vivify_more_noccs (Internal *i) : internal (i) {} + + bool operator() (int a, int b) { + int64_t n = internal->noccs (a); + int64_t m = internal->noccs (b); + if (n > m) + return true; // larger occurrences / score first + if (n < m) + return false; // smaller occurrences / score last + if (a == -b) + return a > 0; // positive literal first + return abs (a) < abs (b); // smaller index first + } +}; + +struct vivify_more_noccs_kissat { + + Internal *internal; + + vivify_more_noccs_kissat (Internal *i) : internal (i) {} + + bool operator() (int a, int b) { + unsigned t = internal->noccs (a); + unsigned s = internal->noccs (b); + return ((t - s) | ((b - a) & ~(s - t))) >> 31; + } +}; + +// Sort candidate clauses by the number of occurrences (actually by their +// score) of their literals, with clauses to be vivified first last. We +// assume that clauses are sorted w.r.t. more occurring (higher score) +// literals first (with respect to 'vivify_more_noccs'). +// +// For example if there are the following (long irredundant) clauses +// +// 1 -3 -4 (A) +// -1 -2 3 4 (B) +// 2 -3 4 (C) +// +// then we have the following literal scores using Jeroslow Wang scores and +// normalizing it with 2^12 (which is the same as 1<<12): +// +// nocc ( 1) = 2^12 * (2^-3 ) = 512 3. +// nocc (-1) = 2^12 * (2^-4 ) = 256 6. +// nocc ( 2) = 2^12 * (2^-3 ) = 512 4. +// nocc (-2) = 2^12 * (2^-4 ) = 256 7. @1 +// nocc ( 3) = 2^12 * (2^-4 ) = 256 8. +// nocc (-3) = 2^12 * (2^-3 + 2^-3) = 1024 1. +// nocc ( 4) = 2^12 * (2^-3 + 2^-4) = 768 2. +// nocc (-4) = 2^12 * (2^-3 ) = 512 5. +// +// which gives the literal order (according to 'vivify_more_noccs') +// +// -3, 4, 1, 2, -4, -1, -2, 3 +// +// Then sorting the literals in each clause gives +// +// -3 1 -4 (A') +// 4 -1 -2 3 (B') @2 +// -3 4 2 (C') +// +// and finally sorting those clauses lexicographically w.r.t. scores is +// +// -3 4 2 (C') +// -3 1 -4 (A') @3 +// 4 -1 -2 3 (B') +// +// This order is defined by 'vivify_clause_later' which returns 'true' if +// the first clause should be vivified later than the second. + +struct vivify_clause_later { + + Internal *internal; + + vivify_clause_later (Internal *i) : internal (i) {} + + bool operator() (Clause *a, Clause *b) const { + + if (a == b) + return false; + + // First focus on clauses scheduled in the last vivify round but not + // checked yet since then. + // + if (!a->vivify && b->vivify) + return true; + if (a->vivify && !b->vivify) + return false; + + // Among redundant clauses (in redundant mode) prefer small glue. + // + if (a->redundant) { + CADICAL_assert (b->redundant); + if (a->glue > b->glue) + return true; + if (a->glue < b->glue) + return false; + } + + // Then prefer shorter size. + // + if (a->size > b->size) + return true; + if (a->size < b->size) + return false; + + // Now compare literals in the clauses lexicographically with respect to + // the literal order 'vivify_more_noccs' assuming literals are sorted + // decreasingly with respect to that order. + // + const auto eoa = a->end (), eob = b->end (); + auto j = b->begin (); + for (auto i = a->begin (); i != eoa && j != eob; i++, j++) + if (*i != *j) + return vivify_more_noccs (internal) (*j, *i); + + return j == eob; // Prefer shorter clauses to be vivified first. + } +}; + +/*------------------------------------------------------------------------*/ + +// Attempting on-the-fly subsumption during sorting when the last line is +// reached in 'vivify_clause_later' above turned out to be trouble some for +// identical clauses. This is the single point where 'vivify_clause_later' +// is not asymmetric and would require 'stable' sorting for determinism. It +// can also not be made 'complete' on-the-fly. Instead of on-the-fly +// subsumption we thus go over the sorted scheduled in a linear scan +// again and remove certain subsumed clauses (the subsuming clause is +// syntactically a prefix of the subsumed clause), which includes +// those troublesome syntactically identical clauses. + +struct vivify_flush_smaller { + + bool operator() (Clause *a, Clause *b) const { + + const auto eoa = a->end (), eob = b->end (); + auto i = a->begin (), j = b->begin (); + for (; i != eoa && j != eob; i++, j++) + if (*i != *j) + return *i < *j; + + return j == eob && i != eoa; + } +}; + +void Internal::flush_vivification_schedule (std::vector &schedule, + int64_t &ticks) { + ticks += 1 + 3 * cache_lines (schedule.size (), sizeof (Clause *)); + stable_sort (schedule.begin (), schedule.end (), vivify_flush_smaller ()); + + const auto end = schedule.end (); + auto j = schedule.begin (), i = j; + + Clause *prev = 0; + int64_t subsumed = 0; + for (; i != end; i++) { + ticks++; + Clause *c = *j++ = *i; + if (!prev || c->size < prev->size) { + prev = c; + continue; + } + const auto eop = prev->end (); + auto k = prev->begin (); + for (auto l = c->begin (); k != eop; k++, l++) + if (*k != *l) + break; + if (k == eop) { + LOG (c, "found subsumed"); + LOG (prev, "subsuming"); + CADICAL_assert (!c->garbage); + CADICAL_assert (!prev->garbage); + CADICAL_assert (c->redundant || !prev->redundant); + mark_garbage (c); + subsumed++; + j--; + } else + prev = c; + } + + if (subsumed) + PHASE ("vivify", stats.vivifications, + "flushed %" PRId64 " subsumed scheduled clauses", subsumed); + + stats.vivifysubs += subsumed; + + if (subsumed) { + schedule.resize (j - schedule.begin ()); + shrink_vector (schedule); + } else + CADICAL_assert (j == end); +} + +/*------------------------------------------------------------------------*/ + +// Depending on whether we try to vivify redundant or irredundant clauses, +// we schedule a clause to be vivified. For redundant clauses we initially +// only try to vivify them if they are likely to survive the next 'reduce' +// operation, but this left the last schedule empty most of the time. + +bool Internal::consider_to_vivify_clause (Clause *c) { + if (c->garbage) + return false; + if (opts.vivifyonce >= 1 && c->redundant && c->vivified) + return false; + if (opts.vivifyonce >= 2 && !c->redundant && c->vivified) + return false; + if (!c->redundant) + return true; + CADICAL_assert (c->redundant); + + // likely_to_be_kept_clause is too aggressive at removing tier-3 clauses + return true; +} + +/*------------------------------------------------------------------------*/ + +// In a strengthened clause the idea is to move non-false literals to the +// front, followed by false literals. Literals are further sorted by +// reverse assignment order. The goal is to use watches which require to +// backtrack as few as possible decision levels. + +struct vivify_better_watch { + + Internal *internal; + + vivify_better_watch (Internal *i) : internal (i) {} + + bool operator() (int a, int b) { + + const signed char av = internal->val (a), bv = internal->val (b); + + if (av >= 0 && bv < 0) + return true; + if (av < 0 && bv >= 0) + return false; + + return internal->var (a).trail > internal->var (b).trail; + } +}; + +// Common code to actually strengthen a candidate clause. The resulting +// strengthened clause is communicated through the global 'clause'. + +void Internal::vivify_strengthen (Clause *c) { + + CADICAL_assert (!clause.empty ()); + + if (clause.size () == 1) { + + backtrack_without_updating_phases (); + const int unit = clause[0]; + LOG (c, "vivification shrunken to unit %d", unit); + CADICAL_assert (!val (unit)); + assign_unit (unit); + // lrat_chain.clear (); done in search_assign + stats.vivifyunits++; + + bool ok = propagate (); + if (!ok) + learn_empty_clause (); + + } else { + + // See explanation before 'vivify_better_watch' above. + // + sort (clause.begin (), clause.end (), vivify_better_watch (this)); + + int new_level = level; + + const int lit0 = clause[0]; + signed char val0 = val (lit0); + if (val0 < 0) { + const int level0 = var (lit0).level; + LOG ("1st watch %d negative at level %d", lit0, level0); + new_level = level0 - 1; + } + + const int lit1 = clause[1]; + const signed char val1 = val (lit1); + if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) { + const int level1 = var (lit1).level; + LOG ("2nd watch %d negative at level %d", lit1, level1); + new_level = level1 - 1; + } + + CADICAL_assert (new_level >= 0); + if (new_level < level) + backtrack (new_level); + + CADICAL_assert (val (lit0) >= 0); + CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 && + var (lit0).level <= var (lit1).level)); + + Clause *d = new_clause_as (c); + LOG (c, "before vivification"); + LOG (d, "after vivification"); + (void) d; + } + clause.clear (); + mark_garbage (c); + lrat_chain.clear (); + ++stats.vivifystrs; +} + +void Internal::vivify_sort_watched (Clause *c) { + + sort (c->begin (), c->end (), vivify_better_watch (this)); + + int new_level = level; + + const int lit0 = c->literals[0]; + signed char val0 = val (lit0); + if (val0 < 0) { + const int level0 = var (lit0).level; + LOG ("1st watch %d negative at level %d", lit0, level0); + new_level = level0 - 1; + } + + const int lit1 = c->literals[1]; + const signed char val1 = val (lit1); + if (val1 < 0 && !(val0 > 0 && var (lit0).level <= var (lit1).level)) { + const int level1 = var (lit1).level; + LOG ("2nd watch %d negative at level %d", lit1, level1); + new_level = level1 - 1; + } + + CADICAL_assert (new_level >= 0); + if (new_level < level) + backtrack (new_level); + + CADICAL_assert (val (lit0) >= 0); + CADICAL_assert (val (lit1) >= 0 || (val (lit0) > 0 && val (lit1) < 0 && + var (lit0).level <= var (lit1).level)); +} +// Conflict analysis from 'start' which learns a decision only clause. +// +// We cannot use the stack-based implementation of Kissat, because we need +// to iterate over the conflict in topological ordering to produce a valid +// LRAT proof + +void Internal::vivify_analyze (Clause *start, bool &subsumes, + Clause **subsuming, + const Clause *const candidate, int implied, + bool &redundant) { + const auto &t = &trail; // normal trail, so next_trail is wrong + int i = t->size (); // Start at end-of-trail. + Clause *reason = start; + CADICAL_assert (reason); + CADICAL_assert (!trail.empty ()); + int uip = trail.back (); + bool mark_implied = (implied); + + while (i >= 0) { + if (reason) { + redundant = (redundant || reason->redundant); + subsumes = (start != reason && reason->size <= start->size); + LOG (reason, "resolving on %d with", uip); + for (auto other : *reason) { + const Var v = var (other); + Flags &f = flags (other); + if (!marked2 (other) && v.level) { + LOG ("not subsuming due to lit %d", other); + subsumes = false; + } + if (!val (other)) { + LOG ("skipping unset lit %d", other); + continue; + } + if (other == uip) { + continue; + } + if (!v.level) { + if (f.seen || !lrat || reason == start) + continue; + LOG ("unit reason for %d", other); + int64_t id = unit_id (-other); + LOG ("adding unit reason %zd for %d", id, other); + unit_chain.push_back (id); + f.seen = true; + analyzed.push_back (other); + continue; + } + if (mark_implied && other != implied) { + LOG ("skipping non-implied literal %d on current level", other); + continue; + } + + CADICAL_assert (val (other)); + if (f.seen) + continue; + LOG ("pushing lit %d", other); + analyzed.push_back (other); + f.seen = true; + } + if (start->redundant) { + const int new_glue = recompute_glue (start); + promote_clause (start, new_glue); + } + if (subsumes) { + CADICAL_assert (reason); + LOG (reason, "clause found subsuming"); + LOG (candidate, "clause found subsumed"); + *subsuming = reason; + return; + } + } else { + LOG ("vivify analyzed decision %d", uip); + clause.push_back (-uip); + } + mark_implied = false; + + uip = 0; + while (!uip && i > 0) { + CADICAL_assert (i > 0); + const int lit = (*t)[--i]; + if (!var (lit).level) + continue; + if (flags (lit).seen) + uip = lit; + } + if (!uip) + break; + LOG ("uip is %d", uip); + Var &w = var (uip); + reason = w.reason; + if (lrat && reason) + lrat_chain.push_back (reason->id); + } + (void) candidate; +} + +void Internal::vivify_deduce (Clause *candidate, Clause *conflict, + int implied, Clause **subsuming, + bool &redundant) { + CADICAL_assert (lrat_chain.empty ()); + bool subsumes; + Clause *reason; + + CADICAL_assert (clause.empty ()); + if (implied) { + reason = candidate; + mark2 (candidate); + const int not_implied = -implied; + CADICAL_assert (var (not_implied).level); + Flags &f = flags (not_implied); + f.seen = true; + LOG ("pushing implied lit %d", not_implied); + analyzed.push_back (not_implied); + clause.push_back (implied); + } else { + reason = (conflict ? conflict : candidate); + CADICAL_assert (reason); + CADICAL_assert (!reason->garbage); + mark2 (candidate); + subsumes = (candidate != reason); + redundant = reason->redundant; + LOG (reason, "resolving with"); + if (lrat) + lrat_chain.push_back (reason->id); + for (auto lit : *reason) { + const Var &v = var (lit); + Flags &f = flags (lit); + CADICAL_assert (val (lit) < 0); + if (!v.level) { + if (!lrat) + continue; + LOG ("adding unit %d", lit); + if (!f.seen) { + // nevertheless we can use var (l) as if l was still assigned + // because var is updated lazily + int64_t id = unit_id (-lit); + LOG ("adding unit reason %zd for %d", id, lit); + unit_chain.push_back (id); + } + f.seen = true; + analyzed.push_back (lit); + continue; + } + CADICAL_assert (v.level); + if (!marked2 (lit)) { + LOG ("lit %d is not marked", lit); + subsumes = false; + } + LOG ("analyzing lit %d", lit); + LOG ("pushing lit %d", lit); + analyzed.push_back (lit); + f.seen = true; + } + if (reason != candidate && reason->redundant) { + const int new_glue = recompute_glue (reason); + promote_clause (reason, new_glue); + } + if (subsumes) { + CADICAL_assert (candidate != reason); +#ifndef CADICAL_NDEBUG + int nonfalse_reason = 0; + for (auto lit : *reason) + if (!fixed (lit)) + ++nonfalse_reason; + + int nonfalse_candidate = 0; + for (auto lit : *candidate) + if (!fixed (lit)) + ++nonfalse_candidate; + + CADICAL_assert (nonfalse_reason <= nonfalse_candidate); +#endif + LOG (candidate, "vivify subsumed 0"); + LOG (reason, "vivify subsuming 0"); + *subsuming = reason; + unmark (candidate); + if (lrat) + lrat_chain.clear (); + return; + } + } + + vivify_analyze (reason, subsumes, subsuming, candidate, implied, + redundant); + unmark (candidate); + if (subsumes) { + CADICAL_assert (*subsuming); + LOG (candidate, "vivify subsumed"); + LOG (*subsuming, "vivify subsuming"); + if (lrat) + lrat_chain.clear (); + } +} +/*------------------------------------------------------------------------*/ + +bool Internal::vivify_shrinkable (const std::vector &sorted, + Clause *conflict) { + + unsigned count_implied = 0; + for (auto lit : sorted) { + const signed char value = val (lit); + if (!value) { + LOG ("vivification unassigned %d", lit); + return true; + } + if (value > 0) { + LOG ("vivification implied satisfied %d", lit); + if (conflict) + return true; + if (count_implied++) { + LOG ("at least one implied literal with conflict thus shrinking"); + return true; + } + } else { + CADICAL_assert (value < 0); + const Var &v = var (lit); + const Flags &f = flags (lit); + if (!v.level) + continue; + if (!f.seen) { + LOG ("vivification non-analyzed %d", lit); + return true; + } + if (v.reason) { + LOG ("vivification implied falsified %d", lit); + return true; + } + } + } + return false; +} +/*------------------------------------------------------------------------*/ + +inline void Internal::vivify_increment_stats (const Vivifier &vivifier) { + switch (vivifier.tier) { + case Vivify_Mode::TIER1: + ++stats.vivifystred1; + break; + case Vivify_Mode::TIER2: + ++stats.vivifystred2; + break; + case Vivify_Mode::TIER3: + ++stats.vivifystred3; + break; + default: + CADICAL_assert (vivifier.tier == Vivify_Mode::IRREDUNDANT); + ++stats.vivifystrirr; + break; + } +} +/*------------------------------------------------------------------------*/ +// instantiate last literal (see the description of the hack track 2023), +// fix the watches and +// backtrack two level back +bool Internal::vivify_instantiate ( + const std::vector &sorted, Clause *c, + std::vector> &lrat_stack, + int64_t &ticks) { + LOG ("now trying instantiation"); + conflict = nullptr; + const int lit = sorted.back (); + LOG ("vivify instantiation"); + CADICAL_assert (!var (lit).reason); + CADICAL_assert (var (lit).level); + CADICAL_assert (val (lit)); + backtrack (level - 1); + CADICAL_assert (val (lit) == 0); + stats.vivifydecs++; + vivify_assume (lit); + bool ok = vivify_propagate (ticks); + if (!ok) { + LOG (c, "instantiate success with literal %d in", lit); + stats.vivifyinst++; + // strengthen clause + if (lrat) { + clear_analyzed_literals (); + CADICAL_assert (lrat_chain.empty ()); + vivify_build_lrat (0, c, lrat_stack); + vivify_build_lrat (0, conflict, lrat_stack); + clear_analyzed_literals (); + } + int remove = lit; + conflict = nullptr; + unwatch_clause (c); + backtrack_without_updating_phases (level - 2); + strengthen_clause (c, remove); + vivify_sort_watched (c); + watch_clause (c); + CADICAL_assert (!conflict); + return true; + } else { + LOG ("vivify instantiation failed"); + return false; + } +} + +/*------------------------------------------------------------------------*/ + +// Main function: try to vivify this candidate clause in the given mode. + +bool Internal::vivify_clause (Vivifier &vivifier, Clause *c) { + + CADICAL_assert (c->size > 2); // see (NO-BINARY) below + CADICAL_assert (analyzed.empty ()); + + c->vivify = false; // mark as checked / tried + c->vivified = true; // and globally remember + + CADICAL_assert (!c->garbage); + + auto &lrat_stack = vivifier.lrat_stack; + auto &ticks = vivifier.ticks; + ticks++; + + // First check whether the candidate clause is already satisfied and at + // the same time copy its non fixed literals to 'sorted'. The literals + // in the candidate clause might not be sorted anymore due to replacing + // watches during propagation, even though we sorted them initially + // while pushing the clause onto the schedule and sorting the schedule. + // + auto &sorted = vivifier.sorted; + sorted.clear (); + + for (const auto &lit : *c) { + const int tmp = fixed (lit); + if (tmp > 0) { + LOG (c, "satisfied by propagated unit %d", lit); + mark_garbage (c); + return false; + } else if (!tmp) + sorted.push_back (lit); + } + + CADICAL_assert (sorted.size () > 1); + if (sorted.size () == 2) { + LOG ("skipping actual binary"); + return false; + } + + sort (sorted.begin (), sorted.end (), vivify_more_noccs_kissat (this)); + + // The actual vivification checking is performed here, by assuming the + // negation of each of the remaining literals of the clause in turn and + // propagating it. If a conflict occurs or another literal in the + // clause becomes assigned during propagation, we can stop. + // + LOG (c, "vivification checking"); + stats.vivifychecks++; + + // If the decision 'level' is non-zero, then we can reuse decisions for + // the previous candidate, and avoid re-propagating them. In preliminary + // experiments this saved between 30%-50% decisions (and thus + // propagations), which in turn lets us also vivify more clauses within + // the same propagation bounds, or terminate earlier if vivify runs to + // completion. + // + if (level) { +#ifdef LOGGING + int orig_level = level; +#endif + // First check whether this clause is actually a reason for forcing + // one of its literals to true and then backtrack one level before + // that happened. Otherwise this clause might be incorrectly + // considered to be redundant or if this situation is checked then + // redundancy by other clauses using this forced literal becomes + // impossible. + // + int forced = 0; + + // This search could be avoided if we would eagerly set the 'reason' + // boolean flag of clauses, which however we do not want to do for + // binary clauses (during propagation) and thus would still require + // a version of 'protect_reason' for binary clauses during 'reduce' + // (well binary clauses are not collected during 'reduce', but again + // this exception from the exception is pretty complex and thus a + // simply search here is probably easier to understand). + + for (const auto &lit : *c) { + const signed char tmp = val (lit); + if (tmp < 0) + continue; + if (tmp > 0 && var (lit).reason == c) + forced = lit; + break; + } + if (forced) { + LOG ("clause is reason forcing %d", forced); + CADICAL_assert (var (forced).level); + backtrack_without_updating_phases (var (forced).level - 1); + } + + // As long the (remaining) literals of the sorted clause match + // decisions on the trail we just reuse them. + // + if (level) { + + int l = 1; // This is the decision level we want to reuse. + + for (const auto &lit : sorted) { + CADICAL_assert (!fixed (lit)); + const int decision = control[l].decision; + if (-lit == decision) { + LOG ("reusing decision %d at decision level %d", decision, l); + stats.vivifyreused++; + if (++l > level) + break; + } else { + LOG ("literal %d does not match decision %d at decision level %d", + lit, decision, l); + backtrack_without_updating_phases (l - 1); + break; + } + } + } + + LOG ("reused %d decision levels from %d", level, orig_level); + } + + LOG (sorted, "sorted size %zd probing schedule", sorted.size ()); + + // Make sure to ignore this clause during propagation. This is not that + // easy for binary clauses (NO-BINARY), e.g., ignoring binary clauses, + // without changing 'propagate'. Actually, we do not want to remove binary + // clauses which are subsumed. Those are hyper binary resolvents and + // should be kept as learned clauses instead, unless they are transitive + // in the binary implication graph, which in turn is detected during + // transitive reduction in 'transred'. + // + ignore = c; + + int subsume = 0; // determined to be redundant / subsumed + + // If the candidate is redundant, i.e., we are in redundant mode, the + // clause is subsumed (in one of the two cases below where 'subsume' is + // assigned) and further all reasons involved are only binary clauses, + // then this redundant clause is what we once called a hidden tautology, + // and even for redundant clauses it makes sense to remove the candidate. + // It does not add anything to propagation power of the formula. This is + // the same argument as removing transitive clauses in the binary + // implication graph during transitive reduction. + // + + // Go over the literals in the candidate clause in sorted order. + // + for (const auto &lit : sorted) { + + // Exit loop as soon a literal is positively implied (case '@5' below) + // or propagation of the negation of a literal fails ('@6'). + // + if (subsume) + break; + + // We keep on assigning literals, even though we know already that we + // can remove one (was negatively implied), since we either might run + // into the 'subsume' case above or more false literals become implied. + // In any case this might result in stronger vivified clauses. As a + // consequence continue with this loop even if 'remove' is non-zero. + + const signed char tmp = val (lit); + + if (tmp) { // literal already assigned + + const Var &v = var (lit); + CADICAL_assert (v.level); + if (!v.reason) { + LOG ("skipping decision %d", lit); + continue; + } + + if (tmp < 0) { + CADICAL_assert (v.level); + LOG ("literal %d is already false and can be removed", lit); + continue; + } + + CADICAL_assert (tmp > 0); + LOG ("subsumed since literal %d already true", lit); + subsume = lit; // will be able to subsume candidate '@5' + break; + } + + CADICAL_assert (!tmp); + + stats.vivifydecs++; + vivify_assume (-lit); + LOG ("negated decision %d score %" PRId64 "", lit, noccs (lit)); + + if (!vivify_propagate (ticks)) { + break; // hot-spot + } + } + + if (subsume) { + int better_subsume_trail = var (subsume).trail; + for (auto lit : sorted) { + if (val (lit) <= 0) + continue; + const Var v = var (lit); + if (v.trail < better_subsume_trail) { + LOG ("improving subsume from %d at %d to %d at %d", subsume, + better_subsume_trail, lit, v.trail); + better_subsume_trail = v.trail; + subsume = lit; + } + } + } + + Clause *subsuming = nullptr; + bool redundant = false; + const int level_after_assumptions = level; + CADICAL_assert (level_after_assumptions); + vivify_deduce (c, conflict, subsume, &subsuming, redundant); + + bool res; + + // reverse lrat_chain. We could probably work with reversed iterators + // (views) to be more efficient but we would have to distinguish in proof + // + if (lrat) { + for (auto id : unit_chain) + lrat_chain.push_back (id); + unit_chain.clear (); + reverse (lrat_chain.begin (), lrat_chain.end ()); + } + + if (subsuming) { + CADICAL_assert (c != subsuming); + LOG (c, "deleting subsumed clause"); + if (c->redundant && subsuming->redundant && c->glue < subsuming->glue) { + promote_clause (c, c->glue); + } + vivify_subsume_clause (subsuming, c); + res = false; + // stats.vivifysubs++; // already done in vivify_subsume_clause + } else if (vivify_shrinkable (sorted, conflict)) { + vivify_increment_stats (vivifier); + LOG ("vivify succeeded, learning new clause"); + clear_analyzed_literals (); + LOG (lrat_chain, "lrat"); + LOG (clause, "learning clause"); + conflict = nullptr; // TODO dup from below + vivify_strengthen (c); + res = true; + } else if (subsume && c->redundant) { + LOG (c, "vivification implied"); + mark_garbage (c); + ++stats.vivifyimplied; + res = true; + } else if ((conflict || subsume) && !c->redundant && !redundant) { + LOG ("demote clause from irredundant to redundant"); + if (opts.vivifydemote) { + demote_clause (c); + const int new_glue = recompute_glue (c); + promote_clause (c, new_glue); + res = false; + } else { + mark_garbage (c); + ++stats.vivifyimplied; + res = true; + } + } else if (subsume) { + LOG (c, "no vivification instantiation with implied literal %d", + subsume); + CADICAL_assert (!c->redundant); + CADICAL_assert (redundant); + res = false; + ++stats.vivifyimplied; + } else { + CADICAL_assert (level > 2); + CADICAL_assert ((size_t) level == sorted.size ()); + LOG (c, "vivification failed on"); + lrat_chain.clear (); + CADICAL_assert (!subsume); + if (!subsume && opts.vivifyinst) { + res = vivify_instantiate (sorted, c, lrat_stack, ticks); + CADICAL_assert (!conflict); + } else { + LOG ("cannot apply instantiation"); + res = false; + } + } + + if (conflict && level == level_after_assumptions) { + LOG ("forcing backtracking at least one level after conflict"); + backtrack_without_updating_phases (level - 1); + } + + clause.clear (); + clear_analyzed_literals (); // TODO why needed? + lrat_chain.clear (); + conflict = nullptr; + return res; +} + +// when we can strengthen clause c we have to build lrat. +// uses f.seen so do not forget to clear flags afterwards. +// this can happen in three cases. (1), (2) are only sound in redundant mode +// (1) literal l in c is positively implied. in this case we call the +// function with (l, l.reason). This justifies the reduction because the new +// clause c' will include l and all decisions so l.reason is a conflict +// assuming -c' (2) conflict during vivify propagation. function is called +// with (0, conflict) similar to (1) but more direct. (3) some literals in c +// are negatively implied and can therefore be removed. in this case we call +// the function with (0, c). originally we justified each literal in c on +// its own but this is not actually necessary. +// + +// Non-recursive version, as some bugs have been found. DFS over the +// reasons with preordering (aka we explore the entire reason before +// exploring deeper) +void Internal::vivify_build_lrat ( + int lit, Clause *reason, + std::vector> &stack) { + CADICAL_assert (stack.empty ()); + stack.push_back ({lit, reason, false}); + while (!stack.empty ()) { + int lit; + Clause *reason; + bool finished; + std::tie (lit, reason, finished) = stack.back (); + LOG ("VIVIFY LRAT justifying %d", lit); + stack.pop_back (); + if (lit && flags (lit).seen) { + LOG ("skipping already justified"); + continue; + } + if (finished) { + lrat_chain.push_back (reason->id); + if (lit && reason) { + Flags &f = flags (lit); + f.seen = true; + analyzed.push_back (lit); // CADICAL_assert (val (other) < 0); + CADICAL_assert (flags (lit).seen); + } + continue; + } else + stack.push_back ({lit, reason, true}); + for (const auto &other : *reason) { + if (other == lit) + continue; + Var &v = var (other); + Flags &f = flags (other); + if (f.seen) + continue; + if (!v.level) { + const int64_t id = unit_id (-other); + lrat_chain.push_back (id); + f.seen = true; + analyzed.push_back (other); + continue; + } + if (v.reason) { // recursive justification + LOG ("VIVIFY LRAT pushing %d", other); + stack.push_back ({other, v.reason, false}); + } + } + } + stack.clear (); +} + +// calculate lrat_chain +// +inline void Internal::vivify_chain_for_units (int lit, Clause *reason) { + if (!lrat) + return; + // LOG ("building chain for units"); bad line for debugging + // equivalence if (opts.chrono && assignment_level (lit, reason)) return; + if (level) + return; // not decision level 0 + CADICAL_assert (lrat_chain.empty ()); + for (auto &reason_lit : *reason) { + if (lit == reason_lit) + continue; + CADICAL_assert (val (reason_lit)); + const int signed_reason_lit = val (reason_lit) * reason_lit; + int64_t id = unit_id (signed_reason_lit); + lrat_chain.push_back (id); + } + lrat_chain.push_back (reason->id); +} + +vivify_ref create_ref (Internal *internal, Clause *c) { + LOG (c, "creating vivify_refs of clause"); + vivify_ref ref; + ref.clause = c; + ref.size = c->size; + for (int i = 0; i < COUNTREF_COUNTS; ++i) + ref.count[i] = 0; + ref.vivify = c->vivify; + int lits[COUNTREF_COUNTS] = {0}; + for (int i = 0; i != std::min (COUNTREF_COUNTS, c->size); ++i) { + int best = 0; + unsigned best_count = 0; + for (auto lit : *c) { + LOG ("to find best number of occurrences for literal %d, looking at " + "literal %d", + i, lit); + for (int j = 0; j != i; ++j) { + LOG ("comparing %d with literal %d", lit, lits[j]); + if (lits[j] == lit) + goto CONTINUE_WITH_NEXT_LITERAL; + } + { + const int64_t lit_count = internal->noccs (lit); + CADICAL_assert (lit_count); + LOG ("checking literal %d with %zd occurrences", lit, lit_count); + if (lit_count <= best_count) + continue; + best_count = lit_count; + best = lit; + } + CONTINUE_WITH_NEXT_LITERAL:; + } + CADICAL_assert (best); + CADICAL_assert (best_count); + CADICAL_assert (best_count < UINT32_MAX); + ref.count[i] = + ((uint64_t) best_count << 32) + (uint64_t) internal->vlit (best); + LOG ("final count at position %d is %d - %d: %lu", i, best, best_count, + ref.count[i]); + lits[i] = best; + } + return ref; +} +/*------------------------------------------------------------------------*/ +inline void +Internal::vivify_prioritize_leftovers ([[maybe_unused]] char tag, + size_t prioritized, + std::vector &schedule) { + if (prioritized) { + PHASE ("vivify", stats.vivifications, + "[phase %c] leftovers of %" PRId64 " clause", tag, prioritized); + } else { + PHASE ("vivify", stats.vivifications, + "[phase %c] prioritizing all clause", tag); + for (auto c : schedule) + c->vivify = true; + } + const size_t max = opts.vivifyschedmax; + if (schedule.size () > max) { + if (prioritized) { + std::partition (begin (schedule), end (schedule), + [] (Clause *c) { return c->vivify; }); + } + schedule.resize (max); + } + // let's try to save a bit of memory + shrink_vector (schedule); +} + +void Internal::vivify_initialize (Vivifier &vivifier, int64_t &ticks) { + + const int tier1 = vivifier.tier1_limit; + const int tier2 = vivifier.tier2_limit; + // Count the number of occurrences of literals in all clauses, + // particularly binary clauses, which are usually responsible + // for most of the propagations. + // + init_noccs (); + + // Disconnect all watches since we sort literals within clauses. + // + CADICAL_assert (watching ()); +#if 0 + clear_watches (); +#endif + + size_t prioritized_irred = 0, prioritized_tier1 = 0, + prioritized_tier2 = 0, prioritized_tier3 = 0; + for (const auto &c : clauses) { + ++ticks; + if (c->size == 2) + continue; // see also (NO-BINARY) above + if (!consider_to_vivify_clause (c)) + continue; + + // This computes an approximation of the Jeroslow Wang heuristic + // score + // + // nocc (L) = sum 2^(12-|C|) + // L in C in F + // + // but we cap the size at 12, that is all clauses of size 12 and + // larger contribute '1' to the score, which allows us to use 'long' + // numbers. See the example above (search for '@1'). + // + const int shift = 12 - c->size; + const int64_t score = shift < 1 ? 1 : (1l << shift); // @4 + for (const auto lit : *c) { + noccs (lit) += score; + } + LOG (c, "putting clause in candidates"); + if (!c->redundant) + vivifier.schedule_irred.push_back (c), + prioritized_irred += (c->vivify); + else if (c->glue <= tier1) + vivifier.schedule_tier1.push_back (c), + prioritized_tier1 += (c->vivify); + else if (c->glue <= tier2) + vivifier.schedule_tier2.push_back (c), + prioritized_tier2 += (c->vivify); + else + vivifier.schedule_tier3.push_back (c), + prioritized_tier3 += (c->vivify); + ++ticks; + } + + vivify_prioritize_leftovers ('x', prioritized_irred, + vivifier.schedule_irred); + vivify_prioritize_leftovers ('u', prioritized_tier1, + vivifier.schedule_tier1); + vivify_prioritize_leftovers ('v', prioritized_tier2, + vivifier.schedule_tier2); + vivify_prioritize_leftovers ('w', prioritized_tier3, + vivifier.schedule_tier3); + + if (opts.vivifyflush) { + clear_watches (); + for (auto &sched : vivifier.schedules) { + for (const auto &c : sched) { + // Literals in scheduled clauses are sorted with their highest score + // literals first (as explained above in the example at '@2'). This + // is also needed in the prefix subsumption checking below. We do an + // approximation below that is done only in the vivify_ref structure + // below. + // + sort (c->begin (), c->end (), vivify_more_noccs (this)); + } + // Flush clauses subsumed by another clause with the same prefix, + // which also includes flushing syntactically identical clauses. + // + flush_vivification_schedule (sched, ticks); + } + connect_watches (); // watch all relevant clauses + } +#if 0 + connect_watches (); // watch all relevant clauses + vivify_propagate (ticks); +#endif + vivify_propagate (ticks); +} + +inline std::vector ¤t_refs_schedule (Vivifier &vivifier) { + switch (vivifier.tier) { + case Vivify_Mode::TIER1: + return vivifier.refs_schedule_tier1; + break; + case Vivify_Mode::TIER2: + return vivifier.refs_schedule_tier2; + break; + case Vivify_Mode::TIER3: + return vivifier.refs_schedule_tier3; + break; + default: + return vivifier.refs_schedule_irred; + break; + } +#ifdef WIN32 + __assume(false); +#else + __builtin_unreachable (); +#endif +} + +inline std::vector ¤t_schedule (Vivifier &vivifier) { + switch (vivifier.tier) { + case Vivify_Mode::TIER1: + return vivifier.schedule_tier1; + break; + case Vivify_Mode::TIER2: + return vivifier.schedule_tier2; + break; + case Vivify_Mode::TIER3: + return vivifier.schedule_tier3; + break; + default: + return vivifier.schedule_irred; + break; + } +#ifdef WIN32 + __assume(false); +#else + __builtin_unreachable (); +#endif +} + +struct vivify_refcount_rank { + int offset; + vivify_refcount_rank (int j) : offset (j) { + CADICAL_assert (offset < COUNTREF_COUNTS); + } + typedef uint64_t Type; + Type operator() (const vivify_ref &a) const { return a.count[offset]; } +}; + +struct vivify_refcount_smaller { + int offset; + vivify_refcount_smaller (int j) : offset (j) { + CADICAL_assert (offset < COUNTREF_COUNTS); + } + bool operator() (const vivify_ref &a, const vivify_ref &b) const { + const auto s = vivify_refcount_rank (offset) (a); + const auto t = vivify_refcount_rank (offset) (b); + return s < t; + } +}; + +struct vivify_inversesize_rank { + vivify_inversesize_rank () {} + typedef uint64_t Type; + Type operator() (const vivify_ref &a) const { return ~a.size; } +}; + +struct vivify_inversesize_smaller { + vivify_inversesize_smaller () {} + bool operator() (const vivify_ref &a, const vivify_ref &b) const { + const auto s = vivify_inversesize_rank () (a); + const auto t = vivify_inversesize_rank () (b); + return s < t; + } +}; + +/*------------------------------------------------------------------------*/ +// There are two modes of vivification, one using all clauses and one +// focusing on irredundant clauses only. The latter variant working on +// irredundant clauses only can also remove irredundant asymmetric +// tautologies (clauses subsumed through unit propagation), which in +// redundant mode is incorrect (due to propagating over redundant clauses). + +void Internal::vivify_round (Vivifier &vivifier, int64_t ticks_limit) { + + if (unsat) + return; + if (terminated_asynchronously ()) + return; + + PHASE ("vivify", stats.vivifications, + "starting %c vivification round ticks limit %" PRId64 "", + vivifier.tag, ticks_limit); + + PHASE ("vivify", stats.vivifications, + "starting %c vivification round ticks limit %" PRId64 "", + vivifier.tag, ticks_limit); + + CADICAL_assert (watching ()); + + auto &refs_schedule = current_refs_schedule (vivifier); + auto &schedule = current_schedule (vivifier); + + int64_t ticks = 1 + schedule.size (); + + // Sort candidates, with first to be tried candidate clause last, i.e., + // many occurrences and high score literals) as in the example explained + // above (search for '@3'). + // + if (vivifier.tier != Vivify_Mode::IRREDUNDANT || + irredundant () / 10 < redundant ()) { + // Literals in scheduled clauses are sorted with their highest score + // literals first (as explained above in the example at '@2'). This is + // also needed in the prefix subsumption checking below. We do an + // approximation below that is done only in the vivify_ref structure + // below. + // + + // first build the schedule with vivifier_refs + auto end_schedule = end (schedule); + refs_schedule.resize (schedule.size ()); + std::transform (begin (schedule), end_schedule, begin (refs_schedule), + [&] (Clause *c) { return create_ref (this, c); }); + // now sort by size + MSORT (opts.radixsortlim, refs_schedule.begin (), refs_schedule.end (), + vivify_inversesize_rank (), vivify_inversesize_smaller ()); + // now (stable) sort by number of occurrences + for (int i = 0; i < COUNTREF_COUNTS; ++i) { + const int offset = COUNTREF_COUNTS - 1 - i; + MSORT (opts.radixsortlim, refs_schedule.begin (), + refs_schedule.end (), vivify_refcount_rank (offset), + vivify_refcount_smaller (offset)); + } + // force left-overs at the end + std::stable_partition (begin (refs_schedule), end (refs_schedule), + [] (vivify_ref c) { return !c.vivify; }); + std::transform (begin (refs_schedule), end (refs_schedule), + begin (schedule), + [] (vivify_ref c) { return c.clause; }); + erase_vector (refs_schedule); + LOG ("clause after sorting final:"); + } else { + // skip sorting but still put clauses with the vivify tag at the end to + // be done first Kissat does this implicitely by going twice over all + // clauses + std::stable_partition (begin (schedule), end (schedule), + [] (Clause *c) { return !c->vivify; }); + } + + // Remember old values of counters to summarize after each round with + // verbose messages what happened in that round. + // + int64_t checked = stats.vivifychecks; + int64_t subsumed = stats.vivifysubs; + int64_t strengthened = stats.vivifystrs; + int64_t units = stats.vivifyunits; + + int64_t scheduled = schedule.size (); + stats.vivifysched += scheduled; + + PHASE ("vivify", stats.vivifications, + "scheduled %" PRId64 " clauses to be vivified %.0f%%", scheduled, + percent (scheduled, stats.current.irredundant)); + + // Limit the number of propagations during vivification as in 'probe'. + // + const int64_t limit = ticks_limit - stats.ticks.vivify; + CADICAL_assert (limit >= 0); + + // the clauses might still contain set literals, so propagation since the + // beginning + propagated2 = propagated = 0; + + if (!unsat && !propagate ()) { + LOG ("propagation after connecting watches in inconsistency"); + learn_empty_clause (); + } + + vivifier.ticks = ticks; + int retry = 0; + while (!unsat && !terminated_asynchronously () && !schedule.empty () && + vivifier.ticks < limit) { + Clause *c = schedule.back (); // Next candidate. + schedule.pop_back (); + if (vivify_clause (vivifier, c) && !c->garbage && c->size > 2 && + retry < opts.vivifyretry) { + ++retry; + schedule.push_back (c); + } else + retry = 0; + } + + if (level) + backtrack_without_updating_phases (); + + if (!unsat) { + int64_t still_need_to_be_vivified = schedule.size (); +#if 0 + // in the current round we have new_clauses_to_vivify @ leftovers from previous round There are + // now two possibilities: (i) we consider all clauses as leftovers, or (ii) only the leftovers + // from previous round are considered leftovers. + // + // CaDiCaL had the first version before. If + // commented out we go to the second version. + for (auto c : schedule) + c->vivify = true; +#elif 1 + // if we have gone through all the leftovers, the current clauses are + // leftovers for the next round + if (!schedule.empty () && !schedule.front ()->vivify && + schedule.back ()->vivify) + for (auto c : schedule) + c->vivify = true; +#else + // do nothing like in kissat and use the candidates for next time. +#endif + // Preference clauses scheduled but not vivified yet next time. + // + if (still_need_to_be_vivified) + PHASE ("vivify", stats.vivifications, + "still need to vivify %" PRId64 " clauses %.02f%% of %" PRId64 + " scheduled", + still_need_to_be_vivified, + percent (still_need_to_be_vivified, scheduled), scheduled); + else { + PHASE ("vivify", stats.vivifications, + "no previously not yet vivified clause left"); + } + + erase_vector (schedule); // Reclaim memory early. + } + + if (!unsat) { + + // Since redundant clause were disconnected during propagating vivified + // units in redundant mode, and further irredundant clauses are + // arbitrarily sorted, we have to propagate all literals again after + // connecting the first two literals in the clauses, in order to + // reestablish the watching invariant. + // + propagated2 = propagated = 0; + + if (!propagate ()) { + LOG ("propagating vivified units leads to conflict"); + learn_empty_clause (); + } + } + + checked = stats.vivifychecks - checked; + subsumed = stats.vivifysubs - subsumed; + strengthened = stats.vivifystrs - strengthened; + units = stats.vivifyunits - units; + + PHASE ("vivify", stats.vivifications, + "checked %" PRId64 " clauses %.02f%% of %" PRId64 + " scheduled using %" PRIu64 " ticks", + checked, percent (checked, scheduled), scheduled, vivifier.ticks); + if (units) + PHASE ("vivify", stats.vivifications, + "found %" PRId64 " units %.02f%% of %" PRId64 " checked", units, + percent (units, checked), checked); + if (subsumed) + PHASE ("vivify", stats.vivifications, + "subsumed %" PRId64 " clauses %.02f%% of %" PRId64 " checked", + subsumed, percent (subsumed, checked), checked); + if (strengthened) + PHASE ("vivify", stats.vivifications, + "strengthened %" PRId64 " clauses %.02f%% of %" PRId64 + " checked", + strengthened, percent (strengthened, checked), checked); + + stats.subsumed += subsumed; + stats.strengthened += strengthened; + stats.ticks.vivify += vivifier.ticks; + + bool unsuccessful = !(subsumed + strengthened + units); + report (vivifier.tag, unsuccessful); +} + +void set_vivifier_mode (Vivifier &vivifier, Vivify_Mode tier) { + vivifier.tier = tier; + switch (tier) { + case Vivify_Mode::TIER1: + vivifier.tag = 'u'; + break; + case Vivify_Mode::TIER2: + vivifier.tag = 'v'; + break; + case Vivify_Mode::TIER3: + vivifier.tag = 'w'; + break; + default: + CADICAL_assert (tier == Vivify_Mode::IRREDUNDANT); + vivifier.tag = 'x'; + break; + } +} +/*------------------------------------------------------------------------*/ + +void Internal::compute_tier_limits (Vivifier &vivifier) { + if (!opts.vivifycalctier) { + vivifier.tier1_limit = 2; + vivifier.tier2_limit = 6; + return; + } + vivifier.tier1_limit = tier1[false]; + vivifier.tier2_limit = tier2[false]; +} + +/*------------------------------------------------------------------------*/ + +bool Internal::vivify () { + + if (unsat) + return false; + if (terminated_asynchronously ()) + return false; + if (!opts.vivify) + return false; + if (!stats.current.irredundant) + return false; + if (level) + backtrack (); + CADICAL_assert (opts.vivify); + CADICAL_assert (!level); + + SET_EFFORT_LIMIT (totallimit, vivify, true); + + private_steps = true; + + START_SIMPLIFIER (vivify, VIVIFY); + stats.vivifications++; + + // the effort is normalized by dividing by sumeffort below, hence no need + // to multiply by 1e-3 (also making the precision better) + double tier1effort = !opts.vivifytier1 ? 0 : (double) opts.vivifytier1eff; + double tier2effort = !opts.vivifytier2 ? 0 : (double) opts.vivifytier2eff; + double tier3effort = !opts.vivifytier3 ? 0 : (double) opts.vivifytier3eff; + double irreffort = + delaying_vivify_irredundant.bumpreasons.delay () || !opts.vivifyirred + ? 0 + : (double) opts.vivifyirredeff; + double sumeffort = tier1effort + tier2effort + tier3effort + irreffort; + if (!stats.current.redundant) + tier1effort = tier2effort = tier3effort = 0; + if (!sumeffort) + sumeffort = irreffort = 1; + int64_t total = totallimit - stats.ticks.vivify; + + PHASE ("vivify", stats.vivifications, + "vivification limit of %" PRId64 " ticks", total); + Vivifier vivifier (Vivify_Mode::TIER1); + compute_tier_limits (vivifier); + + if (vivifier.tier1_limit == vivifier.tier2_limit) { + tier1effort += tier2effort; + tier2effort = 0; + LOG ("vivification tier1 matches tier2 " + "thus using tier2 budget for tier1"); + } + int64_t init_ticks = 0; + + // Refill the schedule every time. Unchecked clauses are 'saved' by + // setting their 'vivify' bit, such that they can be tried next time. + // + // TODO: count against ticks.vivify directly instead of this unholy + // shifting. + vivify_initialize (vivifier, init_ticks); + stats.ticks.vivify += init_ticks; + int64_t limit = stats.ticks.vivify; + const double shared_effort = (double) init_ticks / 4.0; + if (opts.vivifytier1) { + set_vivifier_mode (vivifier, Vivify_Mode::TIER1); + if (limit < stats.ticks.vivify) + limit = stats.ticks.vivify; + const double effort = (total * tier1effort) / sumeffort; + CADICAL_assert (std::numeric_limits::max () - (int64_t) effort >= + limit); + limit += effort; + if (limit - shared_effort > stats.ticks.vivify) { + limit -= shared_effort; + CADICAL_assert (limit >= 0); + vivify_round (vivifier, limit); + } else { + LOG ("building the schedule already used our entire ticks budget for " + "tier1"); + } + } + + if (!unsat && tier2effort) { + erase_vector ( + vivifier.schedule_tier1); // save memory (well, not really as we + // already reached the peak memory) + if (limit < stats.ticks.vivify) + limit = stats.ticks.vivify; + const double effort = (total * tier2effort) / sumeffort; + CADICAL_assert (std::numeric_limits::max () - (int64_t) effort >= + limit); + limit += effort; + if (limit - shared_effort > stats.ticks.vivify) { + limit -= shared_effort; + CADICAL_assert (limit >= 0); + set_vivifier_mode (vivifier, Vivify_Mode::TIER2); + vivify_round (vivifier, limit); + } else { + LOG ("building the schedule already used our entire ticks budget for " + "tier2"); + } + } + + if (!unsat && tier3effort) { + erase_vector (vivifier.schedule_tier2); + if (limit < stats.ticks.vivify) + limit = stats.ticks.vivify; + const double effort = (total * tier3effort) / sumeffort; + CADICAL_assert (std::numeric_limits::max () - (int64_t) effort >= + limit); + limit += effort; + if (limit - shared_effort > stats.ticks.vivify) { + limit -= shared_effort; + CADICAL_assert (limit >= 0); + set_vivifier_mode (vivifier, Vivify_Mode::TIER3); + vivify_round (vivifier, limit); + } else { + LOG ("building the schedule already used our entire ticks budget for " + "tier3"); + } + } + + if (!unsat && irreffort) { + erase_vector (vivifier.schedule_tier3); + if (limit < stats.ticks.vivify) + limit = stats.ticks.vivify; + const double effort = (total * irreffort) / sumeffort; + CADICAL_assert (std::numeric_limits::max () - (int64_t) effort >= + limit); + limit += effort; + if (limit - shared_effort > stats.ticks.vivify) { + limit -= shared_effort; + CADICAL_assert (limit >= 0); + set_vivifier_mode (vivifier, Vivify_Mode::IRREDUNDANT); + const int old = stats.vivifystrirr; + const int old_tried = stats.vivifychecks; + vivify_round (vivifier, limit); + if (stats.vivifychecks - old_tried == 0 || + (float) (stats.vivifystrirr - old) / + (float) (stats.vivifychecks - old_tried) < + 0.01) { + delaying_vivify_irredundant.bumpreasons.bump_delay (); + } else { + delaying_vivify_irredundant.bumpreasons.reduce_delay (); + } + } else { + delaying_vivify_irredundant.bumpreasons.bump_delay (); + LOG ("building the schedule already used our entire ticks budget for " + "irredundant"); + } + } + + reset_noccs (); + STOP_SIMPLIFIER (vivify, VIVIFY); + + private_steps = false; + + return true; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_walk.cpp b/src/sat/cadical/cadical_walk.cpp new file mode 100644 index 000000000..13b1bf9f4 --- /dev/null +++ b/src/sat/cadical/cadical_walk.cpp @@ -0,0 +1,710 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// Random walk local search based on 'ProbSAT' ideas. + +struct Walker { + + Internal *internal; + + Random random; // local random number generator + int64_t propagations; // number of propagations + int64_t limit; // limit on number of propagations + vector broken; // currently unsatisfied clauses + double epsilon; // smallest considered score + vector table; // break value to score table + vector scores; // scores of candidate literals + + double score (unsigned); // compute score from break count + + Walker (Internal *, double size, int64_t limit); +}; + +// These are in essence the CB values from Adrian Balint's thesis. They +// denote the inverse 'cb' of the base 'b' of the (probability) weight +// 'b^-i' for picking a literal with the break value 'i' (first column is +// the 'size', second the 'CB' value). + +static double cbvals[][2] = { + {0.0, 2.00}, {3.0, 2.50}, {4.0, 2.85}, {5.0, 3.70}, + {6.0, 5.10}, {7.0, 7.40}, // Adrian has '5.4', but '7.4' looks better. +}; + +static const int ncbvals = sizeof cbvals / sizeof cbvals[0]; + +// We interpolate the CB values for uniform random SAT formula to the non +// integer situation of average clause size by piecewise linear functions. +// +// y2 - y1 +// ------- * (x - x1) + y1 +// x2 - x1 +// +// where 'x' is the average size of clauses and 'y' the CB value. + +inline static double fitcbval (double size) { + int i = 0; + while (i + 2 < ncbvals && + (cbvals[i][0] > size || cbvals[i + 1][0] < size)) + i++; + const double x2 = cbvals[i + 1][0], x1 = cbvals[i][0]; + const double y2 = cbvals[i + 1][1], y1 = cbvals[i][1]; + const double dx = x2 - x1, dy = y2 - y1; + CADICAL_assert (dx); + const double res = dy * (size - x1) / dx + y1; + CADICAL_assert (res > 0); + return res; +} + +// Initialize the data structures for one local search round. + +Walker::Walker (Internal *i, double size, int64_t l) + : internal (i), random (internal->opts.seed), // global random seed + propagations (0), limit (l) { + random += internal->stats.walk.count; // different seed every time + + // This is the magic constant in ProbSAT (also called 'CB'), which we pick + // according to the average size every second invocation and otherwise + // just the default '2.0', which turns into the base '0.5'. + // + const bool use_size_based_cb = (internal->stats.walk.count & 1); + const double cb = use_size_based_cb ? fitcbval (size) : 2.0; + CADICAL_assert (cb); + const double base = 1 / cb; // scores are 'base^0,base^1,base^2,... + + double next = 1; + for (epsilon = next; next; next = epsilon * base) + table.push_back (epsilon = next); + + PHASE ("walk", internal->stats.walk.count, + "CB %.2f with inverse %.2f as base and table size %zd", cb, base, + table.size ()); +} + +// The scores are tabulated for faster computation (to avoid 'pow'). + +inline double Walker::score (unsigned i) { + const double res = (i < table.size () ? table[i] : epsilon); + LOG ("break %u mapped to score %g", i, res); + return res; +} + +/*------------------------------------------------------------------------*/ + +Clause *Internal::walk_pick_clause (Walker &walker) { + require_mode (WALK); + CADICAL_assert (!walker.broken.empty ()); + int64_t size = walker.broken.size (); + if (size > INT_MAX) + size = INT_MAX; + int pos = walker.random.pick_int (0, size - 1); + Clause *res = walker.broken[pos]; + LOG (res, "picking random position %d", pos); + return res; +} + +/*------------------------------------------------------------------------*/ + +// Compute the number of clauses which would be become unsatisfied if 'lit' +// is flipped and set to false. This is called the 'break-count' of 'lit'. + +unsigned Internal::walk_break_value (int lit) { + + require_mode (WALK); + CADICAL_assert (val (lit) > 0); + + unsigned res = 0; // The computed break-count of 'lit'. + + for (auto &w : watches (lit)) { + CADICAL_assert (w.blit != lit); + if (val (w.blit) > 0) + continue; + if (w.binary ()) { + res++; + continue; + } + + Clause *c = w.clause; + CADICAL_assert (lit == c->literals[0]); + + // Now try to find a second satisfied literal starting at 'literals[1]' + // shifting all the traversed literals to right by one position in order + // to move such a second satisfying literal to 'literals[1]'. This move + // to front strategy improves the chances to find the second satisfying + // literal earlier in subsequent break-count computations. + // + auto begin = c->begin () + 1; + const auto end = c->end (); + auto i = begin; + int prev = 0; + while (i != end) { + const int other = *i; + *i++ = prev; + prev = other; + if (val (other) < 0) + continue; + + // Found 'other' as second satisfying literal. + + w.blit = other; // Update 'blit' + *begin = other; // and move to front. + + break; + } + + if (i != end) + continue; // Double satisfied! + + // Otherwise restore literals (undo shift to the right). + // + while (i != begin) { + const int other = *--i; + *i = prev; + prev = other; + } + + res++; // Literal 'lit' single satisfies clause 'c'. + } + + return res; +} + +/*------------------------------------------------------------------------*/ + +// Given an unsatisfied clause 'c', in which we want to flip a literal, we +// first determine the exponential score based on the break-count of its +// literals and then sample the literals based on these scores. The CB +// value is smaller than one and thus the score is exponentially decreasing +// with the break-count increasing. The sampling works as in 'ProbSAT' and +// 'YalSAT' by summing up the scores and then picking a random limit in the +// range of zero to the sum, then summing up the scores again and picking +// the first literal which reaches the limit. Note, that during incremental +// SAT solving we can not flip assumed variables. Those are assigned at +// decision level one, while the other variables are assigned at two. + +int Internal::walk_pick_lit (Walker &walker, Clause *c) { + LOG ("picking literal by break-count"); + CADICAL_assert (walker.scores.empty ()); + double sum = 0; + int64_t propagations = 0; + for (const auto lit : *c) { + CADICAL_assert (active (lit)); + if (var (lit).level == 1) { + LOG ("skipping assumption %d for scoring", -lit); + continue; + } + CADICAL_assert (active (lit)); + propagations++; + unsigned tmp = walk_break_value (-lit); + double score = walker.score (tmp); + LOG ("literal %d break-count %u score %g", lit, tmp, score); + walker.scores.push_back (score); + sum += score; + } + LOG ("scored %zd literals", walker.scores.size ()); + CADICAL_assert (!walker.scores.empty ()); + walker.propagations += propagations; + stats.propagations.walk += propagations; + CADICAL_assert (walker.scores.size () <= (size_t) c->size); + const double lim = sum * walker.random.generate_double (); + LOG ("score sum %g limit %g", sum, lim); + const auto end = c->end (); + auto i = c->begin (); + auto j = walker.scores.begin (); + int res; + for (;;) { + CADICAL_assert (i != end); + res = *i++; + if (var (res).level > 1) + break; + LOG ("skipping assumption %d without score", -res); + } + sum = *j++; + while (sum <= lim && i != end) { + res = *i++; + if (var (res).level == 1) { + LOG ("skipping assumption %d without score", -res); + continue; + } + sum += *j++; + } + walker.scores.clear (); + LOG ("picking literal %d by break-count", res); + return res; +} + +/*------------------------------------------------------------------------*/ + +void Internal::walk_flip_lit (Walker &walker, int lit) { + + require_mode (WALK); + LOG ("flipping assign %d", lit); + CADICAL_assert (val (lit) < 0); + + // First flip the literal value. + // + const int tmp = sign (lit); + const int idx = abs (lit); + set_val (idx, tmp); + CADICAL_assert (val (lit) > 0); + + // Then remove 'c' and all other now satisfied (made) clauses. + { + // Simply go over all unsatisfied (broken) clauses. + + LOG ("trying to make %zd broken clauses", walker.broken.size ()); + + // We need to measure (and bound) the memory accesses during traversing + // broken clauses in terms of 'propagations'. This is tricky since we + // are not actually propagating literals. Instead we use the clause + // variable 'ratio' as an approximation to the number of clauses used + // during propagating a literal. Note that we use a one-watch scheme. + // Accordingly the number of broken clauses traversed divided by that + // ratio is an approximation of the number of propagation this would + // correspond to (in terms of memory access). To eagerly update these + // statistics we simply increment the propagation counter after every + // 'ratio' traversed clause. These propagations are particularly + // expensive if the number of broken clauses is large which usually + // happens initially. + // + const double ratio = clause_variable_ratio (); + const auto eou = walker.broken.end (); + auto j = walker.broken.begin (), i = j; +#ifdef LOGGING + int64_t made = 0; +#endif + int64_t count = 0; + + while (i != eou) { + + Clause *d = *j++ = *i++; + + int *literals = d->literals, prev = 0; + + // Find 'lit' in 'd'. + // + const int size = d->size; + for (int i = 0; i < size; i++) { + const int other = literals[i]; + CADICAL_assert (active (other)); + literals[i] = prev; + prev = other; + if (other == lit) + break; + CADICAL_assert (val (other) < 0); + } + + // If 'lit' is in 'd' then move it to the front to watch it. + // + if (prev == lit) { + literals[0] = lit; + LOG (d, "made"); + watch_literal (literals[0], literals[1], d); +#ifdef LOGGING + made++; +#endif + j--; + + } else { // Otherwise the clause is not satisfied, undo shift. + + for (int i = size - 1; i >= 0; i--) { + int other = literals[i]; + literals[i] = prev; + prev = other; + } + } + + if (count--) + continue; + + // Update these counters eagerly. Otherwise if we delay the update + // until all clauses are traversed, interrupting the solver has a high + // chance of giving bogus statistics on the number of 'propagations' + // in 'walk', if it is interrupted in this loop. + + count = ratio; // Starting counting down again. + walker.propagations++; + stats.propagations.walk++; + } + LOG ("made %" PRId64 " clauses by flipping %d", made, lit); + walker.broken.resize (j - walker.broken.begin ()); + } + + // Finally add all new unsatisfied (broken) clauses. + { + walker.propagations++; // This really corresponds now to one + stats.propagations.walk++; // propagation (in a one-watch scheme). + +#ifdef LOGGING + int64_t broken = 0; +#endif + Watches &ws = watches (-lit); + + LOG ("trying to break %zd watched clauses", ws.size ()); + + for (const auto &w : ws) { + Clause *d = w.clause; + LOG (d, "unwatch %d in", -lit); + int *literals = d->literals, replacement = 0, prev = -lit; + CADICAL_assert (literals[0] == -lit); + const int size = d->size; + for (int i = 1; i < size; i++) { + const int other = literals[i]; + CADICAL_assert (active (other)); + literals[i] = prev; // shift all to right + prev = other; + const signed char tmp = val (other); + if (tmp < 0) + continue; + replacement = other; // satisfying literal + break; + } + if (replacement) { + literals[1] = -lit; + literals[0] = replacement; + CADICAL_assert (-lit != replacement); + watch_literal (replacement, -lit, d); + } else { + for (int i = size - 1; i > 0; i--) { // undo shift + const int other = literals[i]; + literals[i] = prev; + prev = other; + } + CADICAL_assert (literals[0] == -lit); + LOG (d, "broken"); + walker.broken.push_back (d); +#ifdef LOGGING + broken++; +#endif + } + } + LOG ("broken %" PRId64 " clauses by flipping %d", broken, lit); + ws.clear (); + } +} + +/*------------------------------------------------------------------------*/ + +// Check whether to save the current phases as new global minimum. + +inline void Internal::walk_save_minimum (Walker &walker) { + int64_t broken = walker.broken.size (); + if (broken >= stats.walk.minimum) + return; + VERBOSE (3, "new global minimum %" PRId64 "", broken); + stats.walk.minimum = broken; + for (auto i : vars) { + const signed char tmp = vals[i]; + if (tmp) + phases.min[i] = phases.saved[i] = tmp; + } +} + +/*------------------------------------------------------------------------*/ + +int Internal::walk_round (int64_t limit, bool prev) { + + backtrack (); + if (propagated < trail.size () && !propagate ()) { + LOG ("empty clause after root level propagation"); + learn_empty_clause (); + return 20; + } + + stats.walk.count++; + + clear_watches (); + + // Remove all fixed variables first (assigned at decision level zero). + // + if (last.collect.fixed < stats.all.fixed) + garbage_collection (); + +#ifndef CADICAL_QUIET + // We want to see more messages during initial local search. + // + if (localsearching) { + CADICAL_assert (!force_phase_messages); + force_phase_messages = true; + } +#endif + + PHASE ("walk", stats.walk.count, + "random walk limit of %" PRId64 " propagations", limit); + + // First compute the average clause size for picking the CB constant. + // + double size = 0; + int64_t n = 0; + for (const auto c : clauses) { + if (c->garbage) + continue; + if (c->redundant) { + if (!opts.walkredundant) + continue; + if (!likely_to_be_kept_clause (c)) + continue; + } + size += c->size; + n++; + } + double average_size = relative (size, n); + + PHASE ("walk", stats.walk.count, + "%" PRId64 " clauses average size %.2f over %d variables", n, + average_size, active ()); + + // Instantiate data structures for this local search round. + // + Walker walker (internal, average_size, limit); + + bool failed = false; // Inconsistent assumptions? + + level = 1; // Assumed variables assigned at level 1. + + if (assumptions.empty ()) { + LOG ("no assumptions so assigning all variables to decision phase"); + } else { + LOG ("assigning assumptions to their forced phase first"); + for (const auto lit : assumptions) { + signed char tmp = val (lit); + if (tmp > 0) + continue; + if (tmp < 0) { + LOG ("inconsistent assumption %d", lit); + failed = true; + break; + } + if (!active (lit)) + continue; + tmp = sign (lit); + const int idx = abs (lit); + LOG ("initial assign %d to assumption phase", tmp < 0 ? -idx : idx); + set_val (idx, tmp); + CADICAL_assert (level == 1); + var (idx).level = 1; + } + if (!failed) + LOG ("now assigning remaining variables to their decision phase"); + } + + level = 2; // All other non assumed variables assigned at level 2. + + if (!failed) { + + for (auto idx : vars) { + if (!active (idx)) { + LOG ("skipping inactive variable %d", idx); + continue; + } + if (vals[idx]) { + CADICAL_assert (var (idx).level == 1); + LOG ("skipping assumed variable %d", idx); + continue; + } + int tmp = 0; + if (prev) + tmp = phases.prev[idx]; + if (!tmp) + tmp = sign (decide_phase (idx, true)); + CADICAL_assert (tmp == 1 || tmp == -1); + set_val (idx, tmp); + CADICAL_assert (level == 2); + var (idx).level = 2; + LOG ("initial assign %d to decision phase", tmp < 0 ? -idx : idx); + } + + LOG ("watching satisfied and registering broken clauses"); +#ifdef LOGGING + int64_t watched = 0; +#endif + for (const auto c : clauses) { + + if (c->garbage) + continue; + if (c->redundant) { + if (!opts.walkredundant) + continue; + if (!likely_to_be_kept_clause (c)) + continue; + } + + bool satisfiable = false; // contains not only assumptions + int satisfied = 0; // clause satisfied? + + int *lits = c->literals; + const int size = c->size; + + // Move to front satisfied literals and determine whether there + // is at least one (non-assumed) literal that can be flipped. + // + for (int i = 0; satisfied < 2 && i < size; i++) { + const int lit = lits[i]; + CADICAL_assert (active (lit)); // Due to garbage collection. + if (val (lit) > 0) { + swap (lits[satisfied], lits[i]); + if (!satisfied++) + LOG ("first satisfying literal %d", lit); + } else if (!satisfiable && var (lit).level > 1) { + LOG ("non-assumption potentially satisfying literal %d", lit); + satisfiable = true; + } + } + + if (!satisfied && !satisfiable) { + LOG (c, "due to assumptions unsatisfiable"); + LOG ("stopping local search since assumptions falsify a clause"); + failed = true; + break; + } + + if (satisfied) { + watch_literal (lits[0], lits[1], c); +#ifdef LOGGING + watched++; +#endif + } else { + CADICAL_assert (satisfiable); // at least one non-assumed variable ... + LOG (c, "broken"); + walker.broken.push_back (c); + } + } +#ifdef LOGGING + if (!failed) { + int64_t broken = walker.broken.size (); + int64_t total = watched + broken; + LOG ("watching %" PRId64 " clauses %.0f%% " + "out of %" PRId64 " (watched and broken)", + watched, percent (watched, total), total); + } +#endif + } + + int64_t old_global_minimum = stats.walk.minimum; + + int res; // Tells caller to continue with local search. + + if (!failed) { + + int64_t broken = walker.broken.size (); + + PHASE ("walk", stats.walk.count, + "starting with %" PRId64 " unsatisfied clauses " + "(%.0f%% out of %" PRId64 ")", + broken, percent (broken, stats.current.irredundant), + stats.current.irredundant); + + walk_save_minimum (walker); + + int64_t minimum = broken; +#ifndef CADICAL_QUIET + int64_t flips = 0; +#endif + while (!terminated_asynchronously () && !walker.broken.empty () && + walker.propagations < walker.limit) { +#ifndef CADICAL_QUIET + flips++; +#endif + stats.walk.flips++; + stats.walk.broken += broken; + Clause *c = walk_pick_clause (walker); + const int lit = walk_pick_lit (walker, c); + walk_flip_lit (walker, lit); + broken = walker.broken.size (); + LOG ("now have %" PRId64 " broken clauses in total", broken); + if (broken >= minimum) + continue; + minimum = broken; + VERBOSE (3, "new phase minimum %" PRId64 " after %" PRId64 " flips", + minimum, flips); + walk_save_minimum (walker); + } + + if (minimum < old_global_minimum) + PHASE ("walk", stats.walk.count, + "%snew global minimum %" PRId64 "%s in %" PRId64 " flips and " + "%" PRId64 " propagations", + tout.bright_yellow_code (), minimum, tout.normal_code (), + flips, walker.propagations); + else + PHASE ("walk", stats.walk.count, + "best phase minimum %" PRId64 " in %" PRId64 " flips and " + "%" PRId64 " propagations", + minimum, flips, walker.propagations); + + if (opts.profile >= 2) { + PHASE ("walk", stats.walk.count, + "%.2f million propagations per second", + relative (1e-6 * walker.propagations, + time () - profiles.walk.started)); + + PHASE ("walk", stats.walk.count, "%.2f thousand flips per second", + relative (1e-3 * flips, time () - profiles.walk.started)); + + } else { + PHASE ("walk", stats.walk.count, "%.2f million propagations", + 1e-6 * walker.propagations); + + PHASE ("walk", stats.walk.count, "%.2f thousand flips", 1e-3 * flips); + } + + if (minimum > 0) { + LOG ("minimum %" PRId64 " non-zero thus potentially continue", + minimum); + res = 0; + } else { + LOG ("minimum is zero thus stop local search"); + res = 10; + } + + } else { + + res = 20; + + PHASE ("walk", stats.walk.count, + "aborted due to inconsistent assumptions"); + } + + copy_phases (phases.prev); + + for (auto idx : vars) + if (active (idx)) + set_val (idx, 0); + + CADICAL_assert (level == 2); + level = 0; + + clear_watches (); + connect_watches (); + +#ifndef CADICAL_QUIET + if (localsearching) { + CADICAL_assert (force_phase_messages); + force_phase_messages = false; + } +#endif + + return res; +} + +void Internal::walk () { + START_INNER_WALK (); + int64_t limit = stats.propagations.search; + limit *= 1e-3 * opts.walkeffort; + if (limit < opts.walkmineff) + limit = opts.walkmineff; + if (limit > opts.walkmaxeff) + limit = opts.walkmaxeff; + (void) walk_round (limit, false); + STOP_INNER_WALK (); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/cadical_watch.cpp b/src/sat/cadical/cadical_watch.cpp new file mode 100644 index 000000000..84c51ef2c --- /dev/null +++ b/src/sat/cadical/cadical_watch.cpp @@ -0,0 +1,132 @@ +#include "global.h" + +#include "internal.hpp" + +ABC_NAMESPACE_IMPL_START + +namespace CaDiCaL { + +void Internal::init_watches () { + CADICAL_assert (wtab.empty ()); + if (wtab.size () < 2 * vsize) + wtab.resize (2 * vsize, Watches ()); + LOG ("initialized watcher tables"); +} + +void Internal::clear_watches () { + for (auto lit : lits) + watches (lit).clear (); +} + +void Internal::reset_watches () { + CADICAL_assert (!wtab.empty ()); + erase_vector (wtab); + LOG ("reset watcher tables"); +} + +// This can be quite costly since lots of memory is accessed in a rather +// random fashion, and thus we optionally profile it. + +void Internal::connect_watches (bool irredundant_only) { + START (connect); + CADICAL_assert (watching ()); + + LOG ("watching all %sclauses", irredundant_only ? "irredundant " : ""); + + // First connect binary clauses. + // + for (const auto &c : clauses) { + if (irredundant_only && c->redundant) + continue; + if (c->garbage || c->size > 2) + continue; + watch_clause (c); + } + + // Then connect non-binary clauses. + // + for (const auto &c : clauses) { + if (irredundant_only && c->redundant) + continue; + if (c->garbage || c->size == 2) + continue; + watch_clause (c); + if (!level) { + const int lit0 = c->literals[0]; + const int lit1 = c->literals[1]; + const signed char tmp0 = val (lit0); + const signed char tmp1 = val (lit1); + if (tmp0 > 0) + continue; + if (tmp1 > 0) + continue; + if (tmp0 < 0) { + const size_t pos0 = var (lit0).trail; + if (pos0 < propagated) { + propagated = pos0; + LOG ("literal %d resets propagated to %zd", lit0, pos0); + } + } + if (tmp1 < 0) { + const size_t pos1 = var (lit1).trail; + if (pos1 < propagated) { + propagated = pos1; + LOG ("literal %d resets propagated to %zd", lit1, pos1); + } + } + } + } + + STOP (connect); +} + +// This can be quite costly since lots of memory is accessed in a rather +// random fashion, and thus we optionally profile it. + +void Internal::connect_binary_watches () { + START (connect); + CADICAL_assert (watching ()); + + LOG ("watching binary clauses"); + + // First connect binary clauses. + // + for (const auto &c : clauses) { + if (c->garbage || c->size > 2) + continue; + watch_clause (c); + } + + STOP (connect); +} + +void Internal::sort_watches () { + CADICAL_assert (watching ()); + LOG ("sorting watches"); + Watches saved; + for (auto lit : lits) { + Watches &ws = watches (lit); + + const const_watch_iterator end = ws.end (); + watch_iterator j = ws.begin (); + const_watch_iterator i; + + CADICAL_assert (saved.empty ()); + + for (i = j; i != end; i++) { + const Watch w = *i; + if (w.binary ()) + *j++ = w; + else + saved.push_back (w); + } + + std::copy (saved.cbegin (), saved.cend (), j); + + saved.clear (); + } +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_IMPL_END diff --git a/src/sat/cadical/ccadical.h b/src/sat/cadical/ccadical.h new file mode 100644 index 000000000..ec5292a79 --- /dev/null +++ b/src/sat/cadical/ccadical.h @@ -0,0 +1,74 @@ +#ifndef _ccadical_h_INCLUDED +#define _ccadical_h_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ +ABC_NAMESPACE_HEADER_START +/*------------------------------------------------------------------------*/ + +#include +#include + +// C wrapper for CaDiCaL's C++ API following IPASIR. + +typedef struct CCaDiCaL CCaDiCaL; + +const char *ccadical_signature (void); +CCaDiCaL *ccadical_init (void); +void ccadical_release (CCaDiCaL *); + +void ccadical_add (CCaDiCaL *, int lit); +void ccadical_assume (CCaDiCaL *, int lit); +int ccadical_solve (CCaDiCaL *); +int ccadical_val (CCaDiCaL *, int lit); +int ccadical_failed (CCaDiCaL *, int lit); + +void ccadical_set_terminate (CCaDiCaL *, void *state, + int (*terminate) (void *state)); + +void ccadical_set_learn (CCaDiCaL *, void *state, int max_length, + void (*learn) (void *state, int *clause)); + +/*------------------------------------------------------------------------*/ + +// Non-IPASIR conformant 'C' functions. + +void ccadical_constrain (CCaDiCaL *, int lit); +int ccadical_constraint_failed (CCaDiCaL *); +void ccadical_set_option (CCaDiCaL *, const char *name, int val); +void ccadical_limit (CCaDiCaL *, const char *name, int limit); +int ccadical_get_option (CCaDiCaL *, const char *name); +void ccadical_print_statistics (CCaDiCaL *); +int64_t ccadical_active (CCaDiCaL *); +int64_t ccadical_irredundant (CCaDiCaL *); +int ccadical_fixed (CCaDiCaL *, int lit); +int ccadical_trace_proof (CCaDiCaL *, FILE *, const char *); +void ccadical_close_proof (CCaDiCaL *); +void ccadical_conclude (CCaDiCaL *); +void ccadical_terminate (CCaDiCaL *); +void ccadical_freeze (CCaDiCaL *, int lit); +int ccadical_frozen (CCaDiCaL *, int lit); +void ccadical_melt (CCaDiCaL *, int lit); +int ccadical_simplify (CCaDiCaL *); +int ccadical_vars (CCaDiCaL *); +int ccadical_reserve_difference (CCaDiCaL *, int number_of_vars); + +// Extra + +void ccadical_reserve(CCaDiCaL *, int min_max_var); +int ccadical_is_inconsistent(CCaDiCaL *); + +/*------------------------------------------------------------------------*/ + +// Support legacy names used before moving to more IPASIR conforming names. + +#define ccadical_reset ccadical_release +#define ccadical_sat ccadical_solve +#define ccadical_deref ccadical_val + +/*------------------------------------------------------------------------*/ +ABC_NAMESPACE_HEADER_END +/*------------------------------------------------------------------------*/ + +#endif diff --git a/src/sat/cadical/checker.hpp b/src/sat/cadical/checker.hpp new file mode 100644 index 000000000..8c351e326 --- /dev/null +++ b/src/sat/cadical/checker.hpp @@ -0,0 +1,178 @@ +#ifndef _checker_hpp_INCLUDED +#define _checker_hpp_INCLUDED + +#include "global.h" + +#include "tracer.hpp" // Alphabetically after 'checker'. + +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This checker implements an online forward DRUP proof checker enabled by +// 'opts.checkproof' (requires 'opts.check' also to be enabled). This is +// useful for model basted testing (and delta-debugging), where we can not +// rely on an external proof checker such as 'drat-trim'. We also do not +// have yet a flow for offline incremental proof checking, while this +// checker here can also be used in an incremental setting. +// +// In essence the checker implements is a simple propagation online SAT +// solver with an additional hash table to find clauses fast for +// 'delete_clause'. It requires its own data structure for clauses +// ('CheckerClause') and watches ('CheckerWatch'). +// +// In our experiments the checker slows down overall SAT solving time by a +// factor of 3, which we contribute to its slightly less efficient +// implementation. + +/*------------------------------------------------------------------------*/ + +struct CheckerClause { + CheckerClause *next; // collision chain link for hash table + uint64_t hash; // previously computed full 64-bit hash + unsigned size; // zero if this is a garbage clause + int literals[2]; // otherwise 'literals' of length 'size' +}; + +struct CheckerWatch { + int blit; + unsigned size; + CheckerClause *clause; + CheckerWatch () {} + CheckerWatch (int b, CheckerClause *c) + : blit (b), size (c->size), clause (c) {} +}; + +typedef vector CheckerWatcher; + +/*------------------------------------------------------------------------*/ + +class Checker : public StatTracer { + + Internal *internal; + + // Capacity of variable values. + // + int64_t size_vars; + + // For the assignment we want to have an as fast access as possible and + // thus we use an array which can also be indexed by negative literals and + // is actually valid in the range [-size_vars+1, ..., size_vars-1]. + // + signed char *vals; + + // The 'watchers' and 'marks' data structures are not that time critical + // and thus we access them by first mapping a literal to 'unsigned'. + // + static unsigned l2u (int lit); + vector watchers; // watchers of literals + vector marks; // mark bits of literals + + signed char &mark (int lit); + CheckerWatcher &watcher (int lit); + + bool inconsistent; // found or added empty clause + + uint64_t num_clauses; // number of clauses in hash table + uint64_t num_garbage; // number of garbage clauses + uint64_t size_clauses; // size of clause hash table + CheckerClause **clauses; // hash table of clauses + CheckerClause *garbage; // linked list of garbage clauses + + vector unsimplified; // original clause for reporting + vector simplified; // clause for sorting + + vector trail; // for propagation + + unsigned next_to_propagate; // next to propagate on trail + + void enlarge_vars (int64_t idx); + void import_literal (int lit); + void import_clause (const vector &); + bool tautological (); + + static const unsigned num_nonces = 4; + + uint64_t nonces[num_nonces]; // random numbers for hashing + uint64_t last_hash; // last computed hash value of clause + int64_t last_id; + uint64_t compute_hash (); // compute and save hash value of clause + + // Reduce hash value to the actual size. + // + static uint64_t reduce_hash (uint64_t hash, uint64_t size); + + void enlarge_clauses (); // enlarge hash table for clauses + void insert (); // insert clause in hash table + CheckerClause **find (); // find clause position in hash table + + void add_clause (const char *type); + + void collect_garbage_clauses (); + + CheckerClause *new_clause (); + void delete_clause (CheckerClause *); + + signed char val (int lit); // returns '-1', '0' or '1' + + bool clause_satisfied (CheckerClause *); + + void assign (int lit); // assign a literal to true + void assume (int lit); // assume a literal + bool propagate (); // propagate and check for conflicts + void backtrack (unsigned); // prepare for next clause + bool check (); // check simplified clause is implied + bool check_blocked (); // check if clause is blocked + + struct { + + int64_t added; // number of added clauses + int64_t original; // number of added original clauses + int64_t derived; // number of added derived clauses + + int64_t deleted; // number of deleted clauses + + int64_t assumptions; // number of assumed literals + int64_t propagations; // number of propagated literals + + int64_t insertions; // number of clauses added to hash table + int64_t collisions; // number of hash collisions in 'find' + int64_t searches; // number of searched clauses in 'find' + + int64_t checks; // number of implication checks + + int64_t collections; // garbage collections + int64_t units; + + } stats; + +public: + Checker (Internal *); + virtual ~Checker (); + + void connect_internal (Internal *i) override; + + void add_original_clause (int64_t, bool, const vector &, + bool = false) override; + void add_derived_clause (int64_t, bool, const vector &, + const vector &) override; + void delete_clause (int64_t, bool, const vector &) override; + + void finalize_clause (int64_t, const vector &) override {} // skip + void report_status (int, int64_t) override {} // skip + void begin_proof (int64_t) override {} // skip + void add_assumption_clause (int64_t, const vector &, + const vector &) override; + void print_stats () override; + void dump (); // for debugging purposes only +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/clause.hpp b/src/sat/cadical/clause.hpp new file mode 100644 index 000000000..e5d0290f6 --- /dev/null +++ b/src/sat/cadical/clause.hpp @@ -0,0 +1,194 @@ +#ifndef _clause_hpp_INCLUDED +#define _clause_hpp_INCLUDED + +#include "global.h" + +#include "util.hpp" +#include +#include +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +typedef int *literal_iterator; +typedef const int *const_literal_iterator; + +/*------------------------------------------------------------------------*/ + +// The 'Clause' data structure is very important. There are usually many +// clauses and accessing them is a hot-spot. Thus we use common +// optimizations to reduce memory and improve cache usage, even though this +// induces some complexity in understanding the code. +// +// The most important optimization is to 'embed' the actual literals in the +// clause. This requires a variadic size structure and thus strictly is not +// 'C' conform, but supported by all compilers we used. The alternative is +// to store the actual literals somewhere else, which not only needs more +// memory but more importantly also requires another memory access and thus +// is very costly. + +struct Clause { + union { + int64_t id; // Used to create LRAT-style proofs + Clause *copy; // Only valid if 'moved', then that's where to. + // + // The 'copy' field is only valid for 'moved' clauses in the moving + // garbage collector 'copy_non_garbage_clauses' for keeping clauses + // compactly in a contiguous memory arena. Otherwise, so almost all of + // the time, 'id' is valid. See 'collect.cpp' for details. + }; + bool conditioned : 1; // Tried for globally blocked clause elimination. + bool covered : 1; // Already considered for covered clause elimination. + bool enqueued : 1; // Enqueued on backward queue. + bool frozen : 1; // Temporarily frozen (in covered clause elimination). + bool garbage : 1; // can be garbage collected unless it is a 'reason' + bool gate : 1; // Clause part of a gate (function definition). + bool hyper : 1; // redundant hyper binary or ternary resolved + bool instantiated : 1; // tried to instantiate + bool moved : 1; // moved during garbage collector ('copy' valid) + bool reason : 1; // reason / antecedent clause can not be collected + bool redundant : 1; // aka 'learned' so not 'irredundant' (original) + bool transred : 1; // already checked for transitive reduction + bool subsume : 1; // not checked in last subsumption round + bool swept : 1; // clause used to sweep equivalences + bool flushed : 1; // garbage in proof deleted binaries + unsigned used : 8; // resolved in conflict analysis since last 'reduce' + bool vivified : 1; // clause already vivified + bool vivify : 1; // clause scheduled to be vivified + + // The glucose level ('LBD' or short 'glue') is a heuristic value for the + // expected usefulness of a learned clause, where smaller glue is consider + // more useful. During learning the 'glue' is determined as the number of + // decisions in the learned clause. Thus the glue of a clause is a strict + // upper limit on the smallest number of decisions needed to make it + // propagate. For instance a binary clause will propagate if one of its + // literals is set to false. Similarly a learned clause with glue 1 can + // propagate after one decision, one with glue 2 after 2 decisions etc. + // In some sense the glue is an abstraction of the size of the clause. + // + // See the IJCAI'09 paper by Audemard & Simon for more details. We + // switched back and forth between keeping the glue stored in a clause and + // using it only initially to determine whether it is kept, that is + // survives clause reduction. The latter strategy is not bad but also + // does not allow to use glue values for instance in 'reduce'. + // + // More recently we also update the glue and promote clauses to lower + // level tiers during conflict analysis. The idea of using three tiers is + // also due to Chanseok Oh and thus used in all recent 'Maple...' solvers. + // Tier one are the always kept clauses with low glue at most + // 'opts.reducetier1glue' (default '2'). The second tier contains all + // clauses with glue larger than 'opts.reducetier1glue' but smaller or + // equal than 'opts.reducetier2glue' (default '6'). The third tier + // consists of clauses with glue larger than 'opts.reducetier2glue'. + // + // Clauses in tier one are not deleted in 'reduce'. Clauses in tier + // two require to be unused in two consecutive 'reduce' intervals before + // being collected while for clauses in tier three not being used since + // the last 'reduce' call makes them deletion candidates. Clauses derived + // by hyper binary or ternary resolution (even though small and thus with + // low glue) are always removed if they remain unused during one interval. + // See 'mark_useless_redundant_clauses_as_garbage' in 'reduce.cpp' and + // 'bump_clause' in 'analyze.cpp'. + // + int glue; + + int size; // Actual size of 'literals' (at least 2). + int pos; // Position of last watch replacement [Gent'13]. + + // This 'flexible array member' is of variadic 'size' (and actually + // shrunken if strengthened) and keeps the literals close to the header of + // the clause to avoid another pointer dereference, which would be costly. + + // In earlier versions we used 'literals[2]' to fake it (in order to + // support older Microsoft compilers even though this feature is in C99) + // and at the same time being able to overlay the first two literals with + // the 'copy' field above, as having a flexible array member inside a + // union is not allowed. Now compilers start to figure out that those + // literals can be accessed with indices larger than 1 and produce + // warnings. After having the 'id' field mandatory we now overlay that + // one with the copy field. + + // However, it turns out that even though flexible array members are in + // C99 they are not in C11++, and therefore pedantic compilation with + // '--pedantic' fails completely. Therefore we still support as + // alternative faked flexible array members, which unfortunately need + // then again more care when accessing the literals outside the faked + // virtual sizes and the compiler can somehow figure that out, because + // that would in turn produce a warning. + +#ifndef NFLEXIBLE + int literals[]; +#else + int literals[2]; +#endif + + // Supports simple range based for loops over clauses. + + literal_iterator begin () { return literals; } + literal_iterator end () { return literals + size; } + + const_literal_iterator begin () const { return literals; } + const_literal_iterator end () const { return literals + size; } + + static size_t bytes (int size) { + + // Memory sanitizer insists that clauses put into consecutive memory in + // the arena are still 8 byte aligned. We could also allocate 8 byte + // aligned memory there. However, assuming the real memory foot print + // of a clause is 8 bytes anyhow, we just allocate 8 byte aligned memory + // all the time (even if allocated outside of the arena). + // + CADICAL_assert (size > 1); + const size_t header_bytes = sizeof (Clause); + const size_t actual_literal_bytes = size * sizeof (int); + size_t combined_bytes = header_bytes + actual_literal_bytes; +#ifdef NFLEXIBLE + const size_t faked_literals_bytes = sizeof ((Clause *) 0)->literals; + combined_bytes -= faked_literals_bytes; +#endif + size_t aligned_bytes = align (combined_bytes, 8); + return aligned_bytes; + } + + size_t bytes () const { return bytes (size); } + + // Check whether this clause is ready to be collected and deleted. The + // 'reason' flag is only there to protect reason clauses in 'reduce', + // which does not backtrack to the root level. If garbage collection is + // triggered from a preprocessor, which backtracks to the root level, then + // 'reason' is false for sure. We want to use the same garbage collection + // code though for both situations and thus hide here this variance. + // + bool collect () const { return !reason && garbage; } +}; + +struct clause_smaller_size { + bool operator() (const Clause *a, const Clause *b) { + return a->size < b->size; + } +}; + +/*------------------------------------------------------------------------*/ + +// Place literals over the same variable close to each other. This would +// allow eager removal of identical literals and detection of tautological +// clauses but is only currently used for better logging (see also +// 'opts.logsort' in 'logging.cpp'). + +struct clause_lit_less_than { + bool operator() (int a, int b) const { + using namespace std; + int s = abs (a), t = abs (b); + return s < t || (s == t && a < b); + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/config.hpp b/src/sat/cadical/config.hpp new file mode 100644 index 000000000..d49b05c84 --- /dev/null +++ b/src/sat/cadical/config.hpp @@ -0,0 +1,26 @@ +#ifndef _config_hpp_INCLUDED +#define _config_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class Options; + +struct Config { + + static bool has (const char *); + static bool set (Options &, const char *); + static void usage (); + + static const char **begin (); + static const char **end (); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/congruence.hpp b/src/sat/cadical/congruence.hpp new file mode 100644 index 000000000..e0acf9b00 --- /dev/null +++ b/src/sat/cadical/congruence.hpp @@ -0,0 +1,720 @@ +#ifndef _congruenc_hpp_INCLUDED +#define _congruenc_hpp_INCLUDED + +#include "global.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "clause.hpp" +#include "inttypes.hpp" +#include "util.hpp" +#include "watch.hpp" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +typedef int64_t LRAT_ID; + +// This implements the algorithm algorithm from SAT 2024. +// +// The idea is to: +// 0. handle binary clauses +// 1. detect gates and merge gates with same inputs ('lazy') +// 2. eagerly replace the equivalent literals and merge gates with same +// inputs +// 3. forward subsume +// +// In step 0 the normalization is fully lazy but we do not care about a +// normal form. Therefore we actually eagerly merge literals. +// +// In step 2 there is a subtility: we only replace with the equivalence +// chain as far as we propagated so far. This is the eager part. For LRAT we +// produce the equivalence up to the point we have propagated, no the full +// chain. This is important for merging literals. To merge literals we use +// union-find but we only compress paths when rewriting the literal, not +// before. The compression was not considered important in Kissat, but we do +// it aggressively as a mirror of the equivalences we have generated. +// +// We have two structures for merging: +// - the lazy ones contains alls merges, with functions like +// find_representative +// +// - the eager version that gets the merges one by one, with functions +// like find_eager_representatives +// +// The two structures are nicely separated and we only working on one of +// them except for: +// +// 1. When propagating one equivalence, we first important the +// equivalence from the lazy to the eager version, producing the full +// chain. +// +// 2. When merging the literals, we merge the literals given by the lazy +// structure, then we merge their representative in the eager version, +// updating only the lazy structure. We do not update the eager version. +// +// An important point: We cannot use internal->lrat_chain and +// internal->clause because in most places we can interrupt the +// transformation to learn a new clause representing an equivalence. +// However, we can only have 2 layers so we use this->lrat_chain and +// internal->lrat_chain when we really produce the proof. +struct Internal; + +#define LD_MAX_ARITY 26 +#define MAX_ARITY ((1 << LD_MAX_ARITY) - 1) + +enum class Gate_Type { And_Gate, XOr_Gate, ITE_Gate }; + +// Wrapper when we are looking for implication in if-then-else gates +struct lit_implication { + int first; + int second; + Clause *clause; + lit_implication (int f, int s, Clause *_id) + : first (f), second (s), clause (_id) {} + lit_implication (int f, int s) : first (f), second (s), clause (0) {} + lit_implication () : first (0), second (0), clause (nullptr) {} + void swap () { std::swap (first, second); } +}; + +// Wrapper when we are looking for equivalence for if-then-else-gate. They +// are produced by merging implication +struct lit_equivalence { + int first; + int second; + Clause *first_clause; + Clause *second_clause; + void check_invariant () { + CADICAL_assert (second_clause); + CADICAL_assert (first_clause); + CADICAL_assert (std::find (begin (*first_clause), end (*first_clause), first) != + end (*first_clause)); + CADICAL_assert (std::find (begin (*second_clause), end (*second_clause), + second) != end (*second_clause)); + CADICAL_assert (std::find (begin (*first_clause), end (*first_clause), + -second) != end (*first_clause)); + CADICAL_assert (std::find (begin (*second_clause), end (*second_clause), + -first) != end (*second_clause)); + } + lit_equivalence (int f, Clause *f_id, int s, Clause *s_id) + : first (f), second (s), first_clause (f_id), second_clause (s_id) {} + lit_equivalence (int f, int s) + : first (f), second (s), first_clause (nullptr), + second_clause (nullptr) {} + lit_equivalence () + : first (0), second (0), first_clause (nullptr), + second_clause (nullptr) {} + lit_equivalence swap () { + std::swap (first, second); + std::swap (first_clause, second_clause); + return *this; + } + lit_equivalence negate_both () { + first = -first; + second = -second; + std::swap (first_clause, second_clause); + return *this; + } +}; + +typedef std::vector lit_implications; +typedef std::vector lit_equivalences; + +std::string string_of_gate (Gate_Type t); + +struct LitClausePair { + int current_lit; // current literal from the gate + Clause *clause; + LitClausePair (int lit, Clause *cl) : current_lit (lit), clause (cl) {} + LitClausePair () : current_lit (0), clause (nullptr) {} +}; +struct LitIdPair { + int lit; // current literal from the gate + LRAT_ID id; + LitIdPair (int l, LRAT_ID i) : lit (l), id (i) {} + LitIdPair () : lit (0), id (0) {} +}; + +/*------------------------------------------------------------------------*/ + +// Sorting the scheduled clauses is way faster if we compute and save the +// clause size in the schedule to avoid pointer access to clauses during +// sorting. This slightly increases the schedule size though. + +struct ClauseSize { + size_t size; + Clause *clause; + ClauseSize (int s, Clause *c) : size (s), clause (c) {} + ClauseSize (Clause *c): size (c->size), clause (c) {} + ClauseSize () {} +}; + +struct smaller_clause_size_rank { + typedef size_t Type; + Type operator() (const ClauseSize &a) { return a.size; } +}; + +/*------------------------------------------------------------------------*/ +// There are many special cases for ITE gates and we have to keep track of +// them as it is a gate property (rewriting might not make it obvious +// anymore). +// a = (a ? t : e) results in no -t and no +e gate (a --> a = t == (-a v -a v t) & (-a v a v -t)) +// a = (-a ? t : e) results in no +t and no -e gate +// a = (c ? a : e) results in no t gate (none of them) +// a = (c ? t : a) results in no e gate (none of them) + +enum Special_ITE_GATE { + NORMAL = 0, + NO_PLUS_THEN = (1 << 0), + NO_NEG_THEN = (1 << 1), + NO_THEN = NO_PLUS_THEN + NO_NEG_THEN, + NO_PLUS_ELSE = (1 << 2), + NO_NEG_ELSE = (1 << 3), + NO_ELSE = NO_PLUS_ELSE + NO_NEG_ELSE, + COND_LHS = NO_NEG_THEN + NO_PLUS_ELSE, + UCOND_LHS = NO_PLUS_THEN + NO_NEG_ELSE, +}; + +inline bool ite_flags_no_then_clauses (int8_t flag) { + return (flag & NO_THEN) == NO_THEN; +} + +inline bool ite_flags_no_else_clauses (int8_t flag) { + return (flag & NO_ELSE) == NO_ELSE; +} + +inline bool ite_flags_neg_cond_lhs (int8_t flag) { + return (flag & UCOND_LHS) == UCOND_LHS; +} + +inline bool ite_flags_cond_lhs (int8_t flag) { + return (flag & COND_LHS) == COND_LHS; +} + +/*------------------------------------------------------------------------*/ + +// The core structure of this algorithm: the gate. It is composed of a +// left-hand side and an array of right-hand side. +// +// There are a few tags to help remembering the status of the gate (like +// deleted) +// +// To keep track of the proof we use two extra arrays: +// - `neg_lhs_ids' contains the long clause for AND gates. Otherwise, it is +// empty. TODO: change to std::option as it contains at most one element +// - `pos_lhs_ids' contains all the remaining gates. +// +// We keep the reasons with an index. This index depends on the gates: + +// - AND-Gates and ITE-Gates: the index is the literal from the RHS +// +// - XOR-Gates: if you order the clauses by the order of the literals, +// each literal is either positive (bit '1') or negative (bit '0'). This +// gives a number that we can use. +// +// TODO Florian: I do not think that you have to changed anything, look at +// the 'Look at this first' in the CPP file. +// +// Important for the proofs: the LHS is not updated. +// +// TODO: we currently use a vector for the rhs, but we could also use FMA +// and inline the structure to avoid any indirection. +// +// One warning for degenerated gate: it is a monotone property on the +// defining clauses, but not on the LHS/RHS as the LHS is not rewritten: +// take 4 = AND 3 4 (degenerated with only the clause -4 3) with a rewriting +// 4 -> 1 (unchanged clause) and later 1 -> 3 (unchanged clause) but you do +// not know anymore from the gate that it is degenerated +struct Gate { +#ifdef LOGGING + uint64_t id; +#endif + int lhs; + Gate_Type tag; + bool garbage : 1; + bool indexed : 1; + bool marked : 1; + bool shrunken : 1; + size_t hash; // TODO remove this field (the C++ implementation is caching + // it anyway) + vector pos_lhs_ids; + vector neg_lhs_ids; + bool degenerated_and_neg = false; // LRAT only relevant for AND Gates, neg lhs in RHS + bool degenerated_and_pos = false; // LRAT only relevant for AND Gates, pos lhs in RHS + int8_t degenerated_ite = Special_ITE_GATE::NORMAL; + vector rhs; + + size_t arity () const { return rhs.size (); } + + bool operator== (Gate const &lhs) { + return tag == lhs.tag && hash == lhs.hash && rhs == lhs.rhs; + } +}; + +typedef vector GOccs; + +struct GateEqualTo { + bool operator() (const Gate *const lhs, const Gate *const rhs) const { + return lhs->rhs == rhs->rhs && lhs->tag == rhs->tag; + } +}; + +struct CompactBinary { + Clause *clause; + LRAT_ID id; + int lit1, lit2; + CompactBinary (Clause *c, LRAT_ID i, int l1, int l2) + : clause (c), id (i), lit1 (l1), lit2 (l2) {} + CompactBinary () : clause (nullptr), id (0), lit1 (0), lit2 (0) {} +}; + +struct Hash { + Hash (std::array &ncs) : nonces (ncs) {} + std::array &nonces; + size_t operator() (const Gate *const g) const; +}; + +struct Rewrite { + int src, dst; + LRAT_ID id1; + LRAT_ID id2; + + Rewrite (int _src, int _dst, LRAT_ID _id1, LRAT_ID _id2) + : src (_src), dst (_dst), id1 (_id1), id2 (_id2) {} + Rewrite () : src (0), dst (0), id1 (0), id2 (0) {} +}; + +struct Closure { + + Closure (Internal *i); + + Internal *const internal; + vector extra_clauses; + vector binaries; + std::vector> offsetsize; + bool full_watching = false; + std::array nonces; + typedef unordered_set GatesTable; + + vector scheduled; + vector marks; + vector mu1_ids, mu2_ids, + mu4_ids; // remember the ids and the literal. 2 and 4 are + // only used for lrat proofs, but we need 1 to + // promote binary clauses to irredundant + + vector lits; // result of definitions + vector rhs; // stack for storing RHS + vector unsimplified; // stack for storing unsimplified version (XOR, + // ITEs) for DRAT proof + vector chain; // store clauses to be able to delete them properly + vector clause; // storing partial clauses + vector + glargecounts; // count for large clauses to complement internal->noccs + vector gnew_largecounts; // count for large clauses to + // complement internal->noccs + GatesTable table; + std::array condbin; + std::array condeq; + + std::vector new_unwatched_binary_clauses; + // LRAT proofs + vector resolvent_analyzed; + mutable vector lrat_chain; // storing LRAT chain + +#ifdef LOGGING + uint64_t fresh_id; +#endif + + uint64_t &new_largecounts (int lit); + uint64_t &largecounts (int lit); + + void unmark_all (); + vector representant; // union-find + vector eager_representant; // union-find + vector representant_id; // lrat version of union-find + vector eager_representant_id; // lrat version of union-find + int &representative (int lit); + int representative (int lit) const; + LRAT_ID &representative_id (int lit); + LRAT_ID representative_id (int lit) const; + int &eager_representative (int lit); + int eager_representative (int lit) const; + LRAT_ID &eager_representative_id (int lit); + LRAT_ID eager_representative_id (int lit) const; + std::vector lazy_propagated_idx; + char &lazy_propagated (int lit); + + int find_lrat_representative_with_marks (int lit); + // representative in the union-find structure in the lazy equivalences + int find_representative (int lit); + // find the representative and produce the binary clause representing the + // normalization from the literal to the result. + int find_representative_and_compress (int, bool update_eager = true); + // find the lazy representative for the `lit' and `-lit' + void find_representative_and_compress_both (int); + // find the eager representative + int find_eager_representative (int); + + // compreses the path from lit to the representative with a new clause if + // needed. Save internal->lrat_chain to avoid any issue. + int find_eager_representative_and_compress (int); + // Import the path from the literal and its negation to the representative + // in the lazy graph to the eager part, producing the binary clauses. + void import_lazy_and_find_eager_representative_and_compress_both ( + int); // generates clauses for -lit and lit + + // returns the ID of the LRAT clause for the normalization from the + // literal lit to its argument, assuming that the representative was + // already compressed. + LRAT_ID find_representative_lrat (int lit); + // returns the ID of the LRAT clause for the eager normalization from the + // literal lit to its argument assuming that the representative was + // already compressed. + LRAT_ID find_eager_representative_lrat (int lit); + + // Writes the LRAT chain required for the eager normalization to + // `lrat_chain`. + void produce_eager_representative_lrat (int lit); + // Writes the LRAT chain required for the lazy normalization to + // `lrat_chain`. + void produce_representative_lrat (int lit); + + // learns a binary clause if not unit + Clause *maybe_add_binary_clause (int a, int b); + // add binary clause + Clause *add_binary_clause (int a, int b); + // add tmp clause + Clause *add_tmp_binary_clause (int a, int b); + // add clause taking core of tmp or full + Clause *learn_binary_tmp_or_full_clause (int a, int b); + + // promotes a clause from redundant to irredundant. We do this for all + // clauses involved in gates to make sure that we produce correct result. + void promote_clause (Clause *); + + // Merge functions. We actually need different several versions for LRAT + // in order to simplify the proof production. + // + // When merging binary clauses, we can simply produce the LRAT chain by + // (1) using the two binary clauses and (2) the reason clause from the + // literals to the representatives. + // + // The same approach does not work for merging gates because the + // representative might be also a representative of another literal + // (because of eager rewriting), requiring to resolve more than once on + // the same literal. An example of this are the two gates 4=-2&7 and + // 6=-2&1, the rewriting 7=1 and the equivalence 4=1. The simple road of + // merging 6 and 4 (requires resolving away 1) + adding the rewrite 4 to 1 + // (requires adding 1) does not work. + // + // Therefore, we actually go for the more regular road and produce two + // equivalence: the merge from the LHS, followed by the actual equivalence + // (by combining it with the rewrite). In DRAT this is less important + // because the checker finds a chain and is less restricted than our LRAT + // chain. + bool merge_literals_equivalence (int lit, int other, Clause *c1, + Clause *c2); + bool merge_literals_lrat (Gate *g, Gate *h, int lit, int other, + const std::vector & = {}, + const std::vector & = {}); + bool merge_literals_lrat (int lit, int other, + const std::vector & = {}, + const std::vector & = {}); + + // proof production + vector lrat_chain_and_gate; + void push_lrat_id (const Clause *const c, int lit); + void push_lrat_unit (int lit); + + // pushes the clause with the reasons to rewrite clause + // unless: + // - the rewriting is not necessary (resolvent_marked == 1) + // - it is overwritten by one of the arguments + void push_id_and_rewriting_lrat_unit (Clause *c, Rewrite rewrite1, + std::vector &chain, + bool = true, + Rewrite rewrite2 = Rewrite (), + int execept_lhs = 0, + int except_lhs2 = 0); + void push_id_and_rewriting_lrat_full (Clause *c, Rewrite rewrite1, + std::vector &chain, + bool = true, + Rewrite rewrite2 = Rewrite (), + int execept_lhs = 0, + int except_lhs2 = 0); + // TODO: does nothing except pushing on the stack, remove! + void push_id_on_chain (std::vector &chain, Clause *c); + // TODO: does nothing except pushing on the stack, remove! + void push_id_on_chain (std::vector &chain, + const std::vector &c); + // TODO: does nothing except pushing on the stack, remove! + void push_id_on_chain (std::vector &chain, Rewrite rewrite, int); + void update_and_gate_build_lrat_chain ( + Gate *g, Gate *h, std::vector &extra_reasons_lit, + std::vector &extra_reasons_ulit, bool remove_units = true); + void update_and_gate_unit_build_lrat_chain ( + Gate *g, int src, LRAT_ID id1, LRAT_ID id2, int dst, + std::vector &extra_reasons_lit, + std::vector &extra_reasons_ulit); + // occs + vector gtab; + GOccs &goccs (int lit); + void connect_goccs (Gate *g, int lit); + vector garbage; + void mark_garbage (Gate *); + // remove the gate from the table + bool remove_gate (Gate *); + bool remove_gate (GatesTable::iterator git); + void index_gate (Gate *); + + // second counter for size, complements noccs + uint64_t &largecount (int lit); + + // simplification + bool skip_and_gate (Gate *g); + bool skip_xor_gate (Gate *g); + void update_and_gate (Gate *g, GatesTable::iterator, int src, int dst, + LRAT_ID id1, LRAT_ID id2, int falsified = 0, + int clashing = 0); + void update_xor_gate (Gate *g, GatesTable::iterator); + void shrink_and_gate (Gate *g, int falsified = 0, int clashing = 0); + bool simplify_gate (Gate *g); + void simplify_and_gate (Gate *g); + void simplify_ite_gate (Gate *g); + Clause *simplify_xor_clause (int lhs, Clause *); + void simplify_xor_gate (Gate *g); + bool simplify_gates (int lit); + void simplify_and_sort_xor_lrat_clauses (const vector &, + vector &, int, + int except2 = 0, bool flip = 0); + void simplify_unit_xor_lrat_clauses (const vector &, int); + + // rewriting + bool rewriting_lhs (Gate *g, int dst); + bool rewrite_gates (int dst, int src, LRAT_ID id1, LRAT_ID id2); + bool rewrite_gate (Gate *g, int dst, int src, LRAT_ID id1, LRAT_ID id2); + void rewrite_xor_gate (Gate *g, int dst, int src); + void rewrite_and_gate (Gate *g, int dst, int src, LRAT_ID id1, + LRAT_ID id2); + void rewrite_ite_gate (Gate *g, int dst, int src); + + size_t units; // next trail position to propagate + bool propagate_unit (int lit); + bool propagate_units (); + size_t propagate_units_and_equivalences (); + bool propagate_equivalence (int lit); + + // gates + void init_closure (); + void reset_closure (); + void reset_extraction (); + void reset_and_gate_extraction (); + void extract_and_gates (Closure &); + void extract_gates (); + void extract_and_gates_with_base_clause (Clause *c); + void init_and_gate_extraction (); + Gate *find_first_and_gate (Clause *base_clause, int lhs); + Gate *find_remaining_and_gate (Clause *base_clause, int lhs); + void extract_and_gates (); + + Gate *find_and_lits (const vector &rhs, Gate *except = nullptr); + // rhs is sorted, so passing by copy + Gate *find_gate_lits (const vector &rhs, Gate_Type typ, + Gate *except = nullptr); + Gate *find_xor_lits (const vector &rhs); + // not const to normalize negations, also fixes the order of the LRAT + Gate *find_ite_gate (Gate *, bool &); + Gate *find_xor_gate (Gate *); + + void reset_xor_gate_extraction (); + void init_xor_gate_extraction (std::vector &candidates); + LRAT_ID check_and_add_to_proof_chain (vector &clause); + void add_xor_matching_proof_chain (Gate *g, int lhs1, + const vector &, + int lhs2, vector &, + vector &); + void add_xor_shrinking_proof_chain (Gate *g, int src); + void extract_xor_gates (); + void extract_xor_gates_with_base_clause (Clause *c); + Clause *find_large_xor_side_clause (std::vector &lits); + + void merge_condeq (int cond, lit_equivalences &condeq, + lit_equivalences ¬_condeq); + void find_conditional_equivalences (int lit, lit_implications &condbin, + lit_equivalences &condeq); + void copy_conditional_equivalences (int lit, lit_implications &condbin); + void check_ite_implied (int lhs, int cond, int then_lit, int else_lit); + void check_ite_gate_implied (Gate *g); + void check_and_gate_implied (Gate *g); + void check_ite_lrat_reasons (Gate *g, bool = false); + void check_contained_module_rewriting (Clause *c, int lit, bool, + int except); + void delete_proof_chain (); + + // ite gate extraction + void extract_ite_gates_of_literal (int); + void extract_ite_gates_of_variable (int idx); + void extract_condeq_pairs (int lit, lit_implications &condbin, + lit_equivalences &condeq); + void init_ite_gate_extraction (std::vector &candidates); + lit_implications::const_iterator find_lit_implication_second_literal ( + int lit, lit_implications::const_iterator begin, + lit_implications::const_iterator end); + void search_condeq (int lit, int pos_lit, + lit_implications::const_iterator pos_begin, + lit_implications::const_iterator pos_end, int neg_lit, + lit_implications::const_iterator neg_begin, + lit_implications::const_iterator neg_end, + lit_equivalences &condeq); + void reset_ite_gate_extraction (); + void extract_ite_gates (); + + void forward_subsume_matching_clauses (); + + void extract_congruence (); + + void add_ite_matching_proof_chain (Gate *g, Gate *h, int lhs1, int lhs2, + std::vector &reasons1, + std::vector &reasons2); + void add_ite_turned_and_binary_clauses (Gate *g); + Gate *new_and_gate (Clause *, int); + Gate *new_ite_gate (int lhs, int cond, int then_lit, int else_lit, + std::vector &&clauses); + Gate *new_xor_gate (const vector &, int); + // check + void check_xor_gate_implied (Gate const *const); + void check_ternary (int a, int b, int c); + void check_binary_implied (int a, int b); + void check_implied (); + + // learn units. You can delay units if you want to learn several at once before + // propagation. Otherwise, propagate! If you need propagation even if nothing is set, use the + // second parameter. + // + // The function can also learn the empty clause if the unit is already set. Do not add the unit in + // the chain! + bool learn_congruence_unit (int unit, bool = false, bool = false); + bool fully_propagate (); + void learn_congruence_unit_falsifies_lrat_chain (Gate *g, int src, + int dst, + int clashing, + int falsified, int unit); + void learn_congruence_unit_when_lhs_set (Gate *g, int src, LRAT_ID id1, + LRAT_ID id2, int dst); + + void find_units (); + void find_equivalences (); + void subsume_clause (Clause *subsuming, Clause *subsumed); + bool find_subsuming_clause (Clause *c); + void produce_rewritten_clause_lrat_and_clean (vector &, + int execept_lhs = 0, + bool = true); + // rewrite the clause using eager rewriting and rew1 and rew2, except for + // 2 literals Usage: + // - the except are used to ignore LHS of gates that have not and should + // not be rewritten. + // - TODO: except_lhs2 should never be used actually + // - the Rewrite are for additional rewrite to allow for lazy rewrites + // to be taken into account without being added to the eager rewriting + // (yet) + Clause *produce_rewritten_clause_lrat (Clause *c, int execept_lhs = 0, + bool remove_units = true, bool = true); + void produce_rewritten_clause_lrat (vector &, + int execept_lhs = 0, + bool = true); + void compute_rewritten_clause_lrat_simple (Clause *c, int except); + // variant where we update the indices after removing the tautologies and + // remove the tautological clauses + void produce_rewritten_clause_lrat_and_clean ( + std::vector &litIds, int except_lhs, + size_t &old_position1, size_t &old_position2, + bool remove_units = true); + // binary extraction and ternary strengthening + void extract_binaries (); + bool find_binary (int, int) const; + + Clause *new_tmp_clause (std::vector &clause); + Clause *maybe_promote_tmp_binary_clause (Clause *); + void check_not_tmp_binary_clause (Clause *c); + Clause *new_clause (); + // + void sort_literals_by_var (vector &rhs); + void sort_literals_by_var_except (vector &rhs, int, int except2 = 0); + + // schedule + queue schedule; + void schedule_literal (int lit); + void add_clause_to_chain (std::vector, LRAT_ID); + // proof. If delete_id is non-zero, then delete the clause instead of + // learning it + LRAT_ID simplify_and_add_to_proof_chain (vector &unsimplified, + LRAT_ID delete_id = 0); + + // we define our own wrapper as cadical has otherwise a non-compatible + // marking system + signed char &marked (int lit); + void set_mu1_reason (int lit, Clause *c); + void set_mu2_reason (int lit, Clause *c); + void set_mu4_reason (int lit, Clause *c); + LitClausePair marked_mu1 (int lit); + LitClausePair marked_mu2 (int lit); + LitClausePair marked_mu4 (int lit); + + // XOR + uint32_t number_from_xor_reason_reversed (const std::vector &rhs); + uint32_t number_from_xor_reason (const std::vector &rhs, int, + int except2 = 0, bool flip = 0); + void gate_sort_lrat_reasons (std::vector &, int, + int except2 = 0, bool flip = 0); + void gate_sort_lrat_reasons (LitClausePair &, int, int except2 = 0, + bool flip = 0); + + bool rewrite_ite_gate_to_and (Gate *g, int dst, int src, size_t c, + size_t d, int cond_lit_to_learn_if_degenerated); + void produce_ite_merge_then_else_reasons ( + Gate *g, int dst, int src, std::vector &reasons_implication, + std::vector &reasons_back); + void produce_ite_merge_lhs_then_else_reasons ( + Gate *g, std::vector &reasons_implication, + std::vector &reasons_back, + std::vector &reasons_unit, bool, bool &); + void rewrite_ite_gate_update_lrat_reasons (Gate *g, int src, int dst); + void simplify_ite_gate_produce_unit_lrat (Gate *g, int lit, size_t idx1, + size_t idx2); + void merge_and_gate_lrat_produce_lrat ( + Gate *g, Gate *h, std::vector &reasons_lrat, + std::vector &reasons_lrat_back, bool remove_units = true); + // first index is a binary clause after unit propagation and the second + // has length 3 + bool simplify_ite_gate_to_and (Gate *g, size_t idx1, size_t idx2, + int removed); + void + merge_ite_gate_same_then_else_lrat (std::vector &clauses, + std::vector &reasons_implication, + std::vector &reasons_back); + void simplify_ite_gate_then_else_set ( + Gate *g, std::vector &reasons_implication, + std::vector &reasons_back, size_t idx1, size_t idx2); + + void simplify_ite_gate_condition_set ( + Gate *g, std::vector &reasons_lrat, + std::vector &reasons_back_lrat, size_t idx1, size_t idx2); + bool normalize_ite_lits_gate (Gate *rhs); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/contract.hpp b/src/sat/cadical/contract.hpp new file mode 100644 index 000000000..4a3ba066a --- /dev/null +++ b/src/sat/cadical/contract.hpp @@ -0,0 +1,142 @@ +#ifndef _contract_hpp_INCLUDED +#define _contract_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +/*------------------------------------------------------------------------*/ +#ifndef CADICAL_NCONTRACTS +/*------------------------------------------------------------------------*/ + +// If the user violates API contracts while calling functions declared in +// 'cadical.hpp' and implemented in 'solver.cpp' then an error is reported. +// Currently we also force aborting the program. In the future it might be +// better to allow the user to provide a call back function, which then can +// for instance throw a C++ exception or execute a 'longjmp' in 'C' etc. + +#define CONTRACT_VIOLATED(...) \ + do { \ + fatal_message_start (); \ + fprintf (stderr, \ + "invalid API usage of '%s' in '%s': ", __PRETTY_FUNCTION__, \ + __FILE__); \ + fprintf (stderr, __VA_ARGS__); \ + fputc ('\n', stderr); \ + fflush (stderr); \ + abort (); \ + } while (0) + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +// It would be much easier to just write 'REQUIRE (this, "not initialized")' +// which however produces warnings due to the '-Wnonnull' check. Note, that +// 'this' is always assumed to be non zero in modern C++. Much worse, if we +// use instead 'this != 0' or something similar like 'this != nullptr' then +// optimization silently removes this check ('gcc-7.4.0' at least) even +// though of course a zero pointer might be used as 'this' if the user did +// not initialize it. The only solution I found is to disable optimization +// for this check. It does not seem to be necessary for 'clang++' though +// ('clang++-6.0.0' at least). The alternative is to not check that the +// user forgot to initialize the solver pointer, but as long this works we +// keep this ugly hack. It also forces the function not to be inlined. +// The actual code I is in 'contract.cpp'. +// +void require_solver_pointer_to_be_non_zero (const void *ptr, + const char *function_name, + const char *file_name); +#define REQUIRE_NON_ZERO_THIS() \ + do { \ + require_solver_pointer_to_be_non_zero (this, __PRETTY_FUNCTION__, \ + __FILE__); \ + } while (0) + +} // namespace CaDiCaL + +/*------------------------------------------------------------------------*/ + +// These are common shortcuts for 'Solver' API contracts (requirements). + +#define REQUIRE(COND, ...) \ + do { \ + if ((COND)) \ + break; \ + CONTRACT_VIOLATED (__VA_ARGS__); \ + } while (0) + +#define REQUIRE_INITIALIZED() \ + do { \ + REQUIRE_NON_ZERO_THIS (); \ + REQUIRE (external, "external solver not initialized"); \ + REQUIRE (internal, "internal solver not initialized"); \ + } while (0) + +#define REQUIRE_VALID_STATE() \ + do { \ + REQUIRE_INITIALIZED (); \ + REQUIRE (this->state () & VALID, "solver in invalid state"); \ + } while (0) + +#define REQUIRE_READY_STATE() \ + do { \ + REQUIRE_VALID_STATE (); \ + REQUIRE (state () != ADDING, \ + "clause incomplete (terminating zero not added)"); \ + } while (0) + +#define REQUIRE_VALID_OR_SOLVING_STATE() \ + do { \ + REQUIRE_INITIALIZED (); \ + REQUIRE (this->state () & (VALID | SOLVING), \ + "solver neither in valid nor solving state"); \ + } while (0) + +#define REQUIRE_VALID_LIT(LIT) \ + do { \ + REQUIRE ((int) (LIT) && ((int) (LIT)) != INT_MIN, \ + "invalid literal '%d'", (int) (LIT)); \ + REQUIRE (external->is_valid_input ((int) (LIT)), \ + "extension variable %d defined by the solver", (int) (LIT)); \ + } while (0) + +#define REQUIRE_STEADY_STATE() \ + do { \ + REQUIRE_INITIALIZED (); \ + REQUIRE (this->state () & STEADY, "solver is not in steady state"); \ + } while (0) + +/*------------------------------------------------------------------------*/ +#else // CADICAL_NCONTRACTS +/*------------------------------------------------------------------------*/ + +#define REQUIRE(...) \ + do { \ + } while (0) +#define REQUIRE_INITIALIZED() \ + do { \ + } while (0) +#define REQUIRE_VALID_STATE() \ + do { \ + } while (0) +#define REQUIRE_READY_STATE() \ + do { \ + } while (0) +#define REQUIRE_VALID_OR_SOLVING_STATE() \ + do { \ + } while (0) +#define REQUIRE_VALID_LIT(...) \ + do { \ + } while (0) +#define REQUIRE_STEADY_STATE() \ + do { \ + } while (0) + +/*------------------------------------------------------------------------*/ +#endif +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/cover.hpp b/src/sat/cadical/cover.hpp new file mode 100644 index 000000000..47e6f1b82 --- /dev/null +++ b/src/sat/cadical/cover.hpp @@ -0,0 +1,36 @@ +#ifndef _cover_hpp_INCLUDED +#define _cover_hpp_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ + +// This header only provides the 'COVER' macro for testing. It is unrelated +// to 'cover.cpp' which implements covered clause elimination (CCE), but we +// wanted to use the name base name in both cases. More explanation on CCE +// is provided in 'cover.cpp'. + +/*------------------------------------------------------------------------*/ + +// Coverage goal, used similar to 'CADICAL_assert' (but with flipped condition) and +// also included even if 'CADICAL_NDEBUG' is defined (in optimizing compilation). +// +// This should in essence not be used in production code. +// +// There seems to be no problem overloading the name 'COVER' of this macro +// with the constant 'COVER' of 'Internal::Mode' (surprisingly). + +#define COVER(COND) \ + do { \ + if (!(COND)) \ + break; \ + fprintf (stderr, \ + "%scadical%s: %s:%d: %s: Coverage goal %s`%s'%s reached.\n", \ + terr.bold_code (), terr.normal_code (), __FUNCTION__, \ + __LINE__, __FILE__, terr.green_code (), #COND, \ + terr.normal_code ()); \ + fflush (stderr); \ + abort (); \ + } while (0) + +#endif diff --git a/src/sat/cadical/decompose.hpp b/src/sat/cadical/decompose.hpp new file mode 100644 index 000000000..9c93e8448 --- /dev/null +++ b/src/sat/cadical/decompose.hpp @@ -0,0 +1,29 @@ +#ifndef _decompose_hpp_INCLUDED +#define _decompose_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// This implements Tarjan's algorithm for decomposing the binary implication +// graph intro strongly connected components (SCCs). Literals in one SCC +// are equivalent and we replace them all by the literal with the smallest +// index in the SCC. These variables are marked 'substituted' and will be +// removed from all clauses. Their value will be fixed during 'extend'. + +#define TRAVERSED UINT_MAX // mark completely traversed + +struct DFS { + unsigned idx; // depth first search index + unsigned min; // minimum reachable index + Clause *parent; // for lrat + DFS () : idx (0), min (0), parent (0) {} +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/delay.hpp b/src/sat/cadical/delay.hpp new file mode 100644 index 000000000..9ee9a9234 --- /dev/null +++ b/src/sat/cadical/delay.hpp @@ -0,0 +1,44 @@ +#ifndef _delay_hpp_INCLUDED +#define _delay_hpp_INCLUDED + +#include "global.h" + +#include +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { +struct Delay { + unsigned count; + unsigned current; + + Delay () : count (0), current (0) {} + + bool delay () { + if (count) { + --count; + return true; + } else { + return false; + } + } + + void bump_delay () { + current += current < std::numeric_limits::max (); + count = current; + } + + void reduce_delay () { + if (!current) + return; + current /= 2; + count = current; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/drattracer.hpp b/src/sat/cadical/drattracer.hpp new file mode 100644 index 000000000..8c2c023d5 --- /dev/null +++ b/src/sat/cadical/drattracer.hpp @@ -0,0 +1,59 @@ +#ifndef _drattracer_h_INCLUDED +#define _drattracer_h_INCLUDED + +#include "global.h" + +#include "tracer.hpp" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class DratTracer : public FileTracer { + + Internal *internal; + File *file; + bool binary; +#ifndef CADICAL_QUIET + int64_t added, deleted; +#endif + void put_binary_zero (); + void put_binary_lit (int external_lit); + + // support DRAT + void drat_add_clause (const vector &); + void drat_delete_clause (const vector &); + +public: + // own and delete 'file' + DratTracer (Internal *, File *file, bool binary); + ~DratTracer (); + + void connect_internal (Internal *i) override; + void begin_proof (int64_t) override {} // skip + + void add_original_clause (int64_t, bool, const vector &, + bool = false) override {} // skip + + void add_derived_clause (int64_t, bool, const vector &, + const vector &) override; + + void delete_clause (int64_t, bool, const vector &) override; + + void finalize_clause (int64_t, const vector &) override {} // skip + + void report_status (int, int64_t) override {} // skip + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/elim.hpp b/src/sat/cadical/elim.hpp new file mode 100644 index 000000000..73e1bf547 --- /dev/null +++ b/src/sat/cadical/elim.hpp @@ -0,0 +1,59 @@ +#ifndef _elim_hpp_INCLUDED +#define _elim_hpp_INCLUDED + +#include "global.h" + +#include "heap.hpp" // Alphabetically after 'elim.hpp'. + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct elim_more { + Internal *internal; + elim_more (Internal *i) : internal (i) {} + bool operator() (unsigned a, unsigned b); +}; + +typedef heap ElimSchedule; + +struct proof_clause { + int64_t id; + vector literals; + // for lrat + unsigned cid; // cadical_kitten id + bool learned; + vector chain; +}; + +enum GateType { NO = 0, EQUI = 1, AND = 2, ITE = 3, XOR = 4, DEF = 5 }; + +struct Eliminator { + + Internal *internal; + ElimSchedule schedule; + + Eliminator (Internal *i) + : internal (i), schedule (elim_more (i)), definition_unit (0), + gatetype (NO) {} + ~Eliminator (); + + queue backward; + + Clause *dequeue (); + void enqueue (Clause *); + + vector gates; + unsigned definition_unit; + vector proof_clauses; + vector marked; + GateType gatetype; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/ema.hpp b/src/sat/cadical/ema.hpp new file mode 100644 index 000000000..51eb5f8c1 --- /dev/null +++ b/src/sat/cadical/ema.hpp @@ -0,0 +1,74 @@ +#ifndef _ema_hpp_INCLUDED +#define _ema_hpp_INCLUDED + +#include "global.h" + +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +// This is a more complex generic exponential moving average class to +// support more robust initialization (see comments in the 'update' +// implementation). + +struct EMA { + +#ifdef LOGGING + uint64_t updated; +#endif + double value; // unbiased (corrected) moving average + double biased; // biased initialized moving average + double alpha; // input scaling with 'alpha = 1 - beta' + double beta; // decay of 'biased' with 'beta = 1 - alpha' + double exp; // 'exp = pow (beta, updated)' + + EMA () + : +#ifdef LOGGING + updated (0), +#endif + value (0), biased (0), alpha (0), beta (0), exp (0) { + } + + EMA (double a) + : +#ifdef LOGGING + updated (0), +#endif + value (0), biased (0), alpha (a), beta (1 - a), exp (!!beta) { + CADICAL_assert (beta >= 0); + } + + operator double () const { return value; } + void update (Internal *, double y, const char *name); +}; + +} // namespace CaDiCaL + +/*------------------------------------------------------------------------*/ + +// Compact average update and initialization macros for better logging. + +#define UPDATE_AVERAGE(A, Y) \ + do { \ + A.update (internal, (Y), #A); \ + } while (0) + +#define INIT_EMA(E, WINDOW) \ + do { \ + CADICAL_assert ((WINDOW) >= 1); \ + double ALPHA = 1.0 / (double) (WINDOW); \ + E = EMA (ALPHA); \ + LOG ("init " #E " EMA target alpha %g window %d", ALPHA, \ + (int) WINDOW); \ + } while (0) + +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/external.hpp b/src/sat/cadical/external.hpp new file mode 100644 index 000000000..7618fb936 --- /dev/null +++ b/src/sat/cadical/external.hpp @@ -0,0 +1,467 @@ +#ifndef _external_hpp_INCLUDED +#define _external_hpp_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ + +#include "range.hpp" +#include +#include + +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +/*------------------------------------------------------------------------*/ + +// The CaDiCaL code is split into three layers: +// +// Solver: facade object providing the actual API of the solver +// External: communication layer between 'Solver' and 'Internal' +// Internal: the actual solver code +// +// Note, that 'Solver' is defined in 'cadical.hpp' and 'solver.cpp', while +// 'External' and 'Internal' in '{external,internal}.{hpp,cpp}'. +// +// Also note, that any user should access the library only through the +// 'Solver' API. For the library internal 'Parser' code we make an +// exception and allow access to both 'External' and 'Internal'. The former +// to enforce the same external to internal mapping of variables and the +// latter for profiling and messages. The same applies to 'App'. +// +// The 'External' class provided here stores the information needed to map +// external variable indices to internal variables (actually literals). +// This is helpful for shrinking the working size of the internal solver +// after many variables become inactive. It will also help to provide +// support for extended resolution in the future, since it allows to +// introduce only internally visible variables (even though we do not know +// how to support generating incremental proofs in this situation yet). +// +// External literals are usually called 'elit' and internal 'ilit'. + +/*------------------------------------------------------------------------*/ + +struct Clause; +struct Internal; +struct CubesWithStatus; + +/*------------------------------------------------------------------------*/ + +/*------------------------------------------------------------------------*/ + +struct External { + + /*==== start of state ==================================================*/ + + Internal *internal; // The actual internal solver. + + int max_var; // External maximum variable index. + size_t vsize; // Allocated external size. + + vector vals; // Current external (extended) assignment. + vector e2i; // External 'idx' to internal 'lit'. + + vector assumptions; // External assumptions. + vector constraint; // External constraint. Terminated by zero. + + vector + ext_units; // External units. Needed to compute LRAT for eclause + vector ext_flags; // to avoid duplicate units + vector eclause; // External version of original input clause. + // The extension stack for reconstructing complete satisfying assignments + // (models) of the original external formula is kept in this external + // solver object. It keeps track of blocked clauses and clauses containing + // eliminated variable. These irredundant clauses are stored in terms of + // external literals on the 'extension' stack after mapping the + // internal literals given as arguments with 'externalize'. + + bool extended; // Have been extended. + bool concluded; + vector extension; // Solution reconstruction extension stack. + + vector witness; // Literal witness on extension stack. + vector tainted; // Literal tainted in adding literals. + + vector ervars; // Variables added through Extended Resolution. + + vector frozentab; // Reference counts for frozen variables. + + // Regularly checked terminator if non-zero. The terminator is set from + // 'Solver::set (Terminator *)' and checked by 'Internal::terminating ()'. + + Terminator *terminator; + + // If there is a learner export learned clauses. + + Learner *learner; + + void export_learned_empty_clause (); + void export_learned_unit_clause (int ilit); + void export_learned_large_clause (const vector &); + + // If there is a listener for fixed assignments. + + FixedAssignmentListener *fixed_listener; + + // If there is an external propagator. + + ExternalPropagator *propagator; + + vector is_observed; // Quick flag for each external variable + + // Saved 'forgettable' original clauses coming from the external + // propagator. The value of the map starts with a Boolean flag indicating + // if the clause is still present or got already deleted, and then + // followed by the literals of the clause. + unordered_map> forgettable_original; + + void add_observed_var (int elit); + void remove_observed_var (int elit); + void reset_observed_vars (); + + bool observed (int elit); + bool is_witness (int elit); + bool is_decision (int elit); + + void force_backtrack (size_t new_level); + + //----------------------------------------------------------------------// + + signed char *solution; // Given solution checking for debugging. + int solution_size; // Given solution checking for debugging. + vector original; // Saved original formula for checking. + + // If 'opts.checkfrozen' is set make sure that only literals are added + // which were never completely molten before. These molten literals are + // marked at the beginning of the 'solve' call. Note that variables + // larger than 'max_var' are not molten and can thus always be used in the + // future. Only needed to check and debug old style freeze semantics. + // + vector moltentab; + + //----------------------------------------------------------------------// + + const Range vars; // Provides safe variable iterations. + + /*==== end of state ====================================================*/ + + // These two just factor out common sanity (CADICAL_assertion) checking code. + + inline int vidx (int elit) const { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int res = abs (elit); + CADICAL_assert (res <= max_var); + return res; + } + + inline int vlit (int elit) const { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + CADICAL_assert (abs (elit) <= max_var); + return elit; + } + + inline bool is_valid_input (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + return eidx > max_var || !ervars[eidx]; + } + + /*----------------------------------------------------------------------*/ + + // The following five functions push individual literals or clauses on the + // extension stack. They all take internal literals as argument, and map + // them back to external literals first, before pushing them on the stack. + + void push_zero_on_extension_stack (); + + // Our general version of extension stacks always pushes a set of witness + // literals (for variable elimination the literal of the eliminated + // literal and for blocked clauses the blocking literal) followed by all + // the clause literals starting with and separated by zero. + // + void push_clause_literal_on_extension_stack (int ilit); + void push_witness_literal_on_extension_stack (int ilit); + + void push_clause_on_extension_stack (Clause *); + void push_clause_on_extension_stack (Clause *, int witness); + void push_binary_clause_on_extension_stack (int64_t id, int witness, + int other); + + // The main 'extend' function which extends an internal assignment to an + // external assignment using the extension stack (and sets 'extended'). + // + void extend (); + void conclude_sat (); + + /*----------------------------------------------------------------------*/ + + // Marking external literals. + + unsigned elit2ulit (int elit) const { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + const int idx = abs (elit) - 1; + CADICAL_assert (idx <= max_var); + return 2u * idx + (elit < 0); + } + + bool marked (const vector &map, int elit) const { + const unsigned ulit = elit2ulit (elit); + return ulit < map.size () ? map[ulit] : false; + } + + void mark (vector &map, int elit) { + const unsigned ulit = elit2ulit (elit); + if (ulit >= map.size ()) + map.resize (ulit + 1, false); + map[ulit] = true; + } + + void unmark (vector &map, int elit) { + const unsigned ulit = elit2ulit (elit); + if (ulit < map.size ()) + map[ulit] = false; + } + + /*----------------------------------------------------------------------*/ + + void push_external_clause_and_witness_on_extension_stack ( + const vector &clause, const vector &witness, int64_t id); + + void push_id_on_extension_stack (int64_t id); + + // Restore a clause, which was pushed on the extension stack. + void restore_clause (const vector::const_iterator &begin, + const vector::const_iterator &end, + const int64_t id); + + void restore_clauses (); + + /*----------------------------------------------------------------------*/ + + // Explicitly freeze and melt literals (instead of just freezing + // internally and implicitly assumed literals). Passes on freezing and + // melting to the internal solver, which has separate frozen counters. + + void freeze (int elit); + void melt (int elit); + + bool frozen (int elit) { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return false; + if (eidx >= (int) frozentab.size ()) + return false; + return frozentab[eidx] > 0; + } + + /*----------------------------------------------------------------------*/ + + External (Internal *); + ~External (); + + void enlarge (int new_max_var); // Enlarge allocated 'vsize'. + void init (int new_max_var, + bool extension = false); // Initialize up-to 'new_max_var'. + + int internalize ( + int, + bool extension = false); // Translate external to internal literal. + + /*----------------------------------------------------------------------*/ + + // According to the CaDiCaL API contract (as well as IPASIR) we have to + // forget about the previous assumptions after a 'solve' call. This + // should however be delayed until we transition out of an 'UNSATISFIED' + // state, i.e., after no more 'failed' calls are expected. Note that + // 'failed' requires to know the failing assumptions, and the 'failed' + // status of those should cleared before at start of the next 'solve'. + // As a consequence 'reset_assumptions' is only called from + // 'transition_to_unknown_state' in API calls in 'solver.cpp'. + + void reset_assumptions (); + + // Similarly to 'failed', 'conclude' needs to know about failing + // assumptions and therefore needs to be reset when leaving the + // 'UNSATISFIED' state. + // + void reset_concluded (); + + // Similarly a valid external assignment obtained through 'extend' has to + // be reset at each point it risks to become invalid. This is done + // in the external layer in 'external.cpp' functions.. + + void reset_extended (); + + // Finally, the semantics of incremental solving also require that limits + // are only valid for the next 'solve' call. Since the limits can not + // really be queried, handling them is less complex and they are just + // reset immediately at the end of 'External::solve'. + + void reset_limits (); + + /*----------------------------------------------------------------------*/ + + // Proxies to IPASIR functions. + + void add (int elit); + void assume (int elit); + int solve (bool preprocess_only); + + // We call it 'ival' as abbreviation for 'val' with 'int' return type to + // avoid bugs due to using 'signed char tmp = val (lit)', which might turn + // a negative value into a positive one (happened in 'extend'). + // + inline int ival (int elit) const { + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + bool val = false; + if (eidx <= max_var && (size_t) eidx < vals.size ()) + val = vals[eidx]; + if (elit < 0) + val = !val; + return val ? elit : -elit; + } + + bool flip (int elit); + bool flippable (int elit); + + bool failed (int elit); + + void terminate (); + + // Other important non IPASIR functions. + + /*----------------------------------------------------------------------*/ + + // Add literal to external constraint. + // + void constrain (int elit); + + // Returns true if 'solve' returned 20 because of the constraint. + // + bool failed_constraint (); + + // Deletes the current constraint clause. Called on + // 'transition_to_unknown_state' and if a new constraint is added. Can be + // called directly using the API. + // + void reset_constraint (); + + /*----------------------------------------------------------------------*/ + + int propagate_assumptions (); + void implied (std::vector &entrailed); + void conclude_unknown (); + + /*----------------------------------------------------------------------*/ + int lookahead (); + CaDiCaL::CubesWithStatus generate_cubes (int, int); + + int fixed (int elit) const; // Implemented in 'internal.hpp'. + + /*----------------------------------------------------------------------*/ + + void phase (int elit); + void unphase (int elit); + + /*----------------------------------------------------------------------*/ + + // Traversal functions for the witness stack and units. The explanation + // in 'external.cpp' for why we have to distinguish these cases. + + bool traverse_all_frozen_units_as_clauses (ClauseIterator &); + bool traverse_all_non_frozen_units_as_witnesses (WitnessIterator &); + bool traverse_witnesses_backward (WitnessIterator &); + bool traverse_witnesses_forward (WitnessIterator &); + + /*----------------------------------------------------------------------*/ + + // Copy flags for determining preprocessing state. + + void copy_flags (External &other) const; + + /*----------------------------------------------------------------------*/ + + // Check solver behaves as expected during testing and debugging. + + void check_assumptions_satisfied (); + void check_constraint_satisfied (); + void check_failing (); + + void check_solution_on_learned_clause (); + void check_solution_on_shrunken_clause (Clause *); + void check_solution_on_learned_unit_clause (int unit); + void check_no_solution_after_learning_empty_clause (); + + void check_learned_empty_clause () { + if (solution) + check_no_solution_after_learning_empty_clause (); + } + + void check_learned_unit_clause (int unit) { + if (solution) + check_solution_on_learned_unit_clause (unit); + } + + void check_learned_clause () { + if (solution) + check_solution_on_learned_clause (); + } + + void check_shrunken_clause (Clause *c) { + if (solution) + check_solution_on_shrunken_clause (c); + } + + void check_assignment (int (External::*assignment) (int) const); + + void check_satisfiable (); + void check_unsatisfiable (); + + void check_solve_result (int res); + + void update_molten_literals (); + + /*----------------------------------------------------------------------*/ + + // For debugging and testing only. See 'solution.hpp' for more details. + // TODO: if elit > solution_size, elit is an extension variable. For now + // the clause will count as satisfied regardless. For the future one + // should check that actually there is one consistent extension for the + // solution that satisfies the clauses with this extension variable (by + // setting it to a value once a clause is learned which is not satisfied + // already). + // + inline int sol (int elit) const { + CADICAL_assert (solution); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return 0; + else if (eidx > solution_size) + return elit; + signed char value = solution[eidx]; + if (!value) + return 0; + if (elit < 0) + value = -value; + return value > 0 ? elit : -elit; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/factor.hpp b/src/sat/cadical/factor.hpp new file mode 100644 index 000000000..3aa5ea6c6 --- /dev/null +++ b/src/sat/cadical/factor.hpp @@ -0,0 +1,60 @@ +#ifndef _factor_hpp_INCLUDED +#define _factor_hpp_INCLUDED + +#include "global.h" + +#include "clause.hpp" +#include "heap.hpp" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct factor_occs_size { + Internal *internal; + factor_occs_size (Internal *i) : internal (i) {} + bool operator() (unsigned a, unsigned b); +}; + +struct Quotient { + Quotient (int f) : factor (f) {} + ~Quotient () {} + int factor; + size_t id; + int64_t bid; // for LRAT + Quotient *prev, *next; + vector qlauses; + vector matches; + size_t matched; +}; + +typedef heap FactorSchedule; + +struct Factoring { + Factoring (Internal *, int64_t); + ~Factoring (); + + // These are initialized by the constructor + Internal *internal; + int64_t limit; + FactorSchedule schedule; + + int initial; + int bound; + vector count; + vector fresh; + vector counted; + vector nounted; + vector flauses; + struct { + Quotient *first, *last; + } quotients; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/file.hpp b/src/sat/cadical/file.hpp new file mode 100644 index 000000000..860e9613a --- /dev/null +++ b/src/sat/cadical/file.hpp @@ -0,0 +1,221 @@ +#ifndef _file_hpp_INCLUDED +#define _file_hpp_INCLUDED + +#include "global.h" + +#include +#include +#include +#include +#include + +#ifndef CADICAL_NDEBUG +#include +#endif + +/*------------------------------------------------------------------------*/ +#ifdef WIN32 +#define cadical_putc_unlocked putc +#define cadical_getc_unlocked getc +#else +#ifndef NUNLOCKED +#define cadical_putc_unlocked putc_unlocked +#define cadical_getc_unlocked getc_unlocked +#else +#define cadical_putc_unlocked putc +#define cadical_getc_unlocked getc +#endif +#endif +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Wraps a 'C' file 'FILE' with name and supports zipped reading and writing +// through 'popen' using external helper tools. Reading has line numbers. +// Compression and decompression relies on external utilities, e.g., 'gzip', +// 'bzip2', 'xz', and '7z', which should be in the 'PATH'. + +struct Internal; + +class File { + + Internal *internal; +#if !defined(CADICAL_QUIET) || !defined(CADICAL_NDEBUG) + bool writing; +#endif + + int close_file; // need to close file (1=fclose, 2=pclose, 3=pipe) + int child_pid; + FILE *file; + char *_name; + uint64_t _lineno; + uint64_t _bytes; + + File (Internal *, bool, int, int, FILE *, const char *); + + static FILE *open_file (Internal *, const char *path, const char *mode); + static FILE *read_file (Internal *, const char *path); + static FILE *write_file (Internal *, const char *path); + + static void split_str (const char *, std::vector &); + static void delete_str_vector (std::vector &); + + static FILE *open_pipe (Internal *, const char *fmt, const char *path, + const char *mode); + static FILE *read_pipe (Internal *, const char *fmt, const int *sig, + const char *path); +#ifndef WIN32 + static FILE *write_pipe (Internal *, const char *fmt, const char *path, + int &child_pid); +#endif + +public: + static char *find_program (const char *prg); // search in 'PATH' + static bool exists (const char *path); // file exists? + static bool writable (const char *path); // can write to that file? + static size_t size (const char *path); // file size in bytes + + bool piping (); // Is opened file a pipe? + + // Does the file match the file type signature. + // + static bool match (Internal *, const char *path, const int *sig); + + // Read from existing file. Assume given name. + // + static File *read (Internal *, FILE *f, const char *name); + + // Open file from path name for reading (possibly through opening a pipe + // to a decompression utility, based on the suffix). + // + static File *read (Internal *, const char *path); + + // Same for writing as for reading above. + // + static File *write (Internal *, FILE *, const char *name); + static File *write (Internal *, const char *path); + + ~File (); + + // Using the 'unlocked' versions here is way faster but + // not thread safe if the same file is used by different + // threads, which on the other hand currently is impossible. + + int get () { + CADICAL_assert (!writing); + int res = cadical_getc_unlocked (file); + if (res == '\n') + _lineno++; + if (res != EOF) + _bytes++; + return res; + } + + bool put (char ch) { + CADICAL_assert (writing); + if (cadical_putc_unlocked (ch, file) == EOF) + return false; + _bytes++; + return true; + } + + bool endl () { return put ('\n'); } + + bool put (unsigned char ch) { + CADICAL_assert (writing); + if (cadical_putc_unlocked (ch, file) == EOF) + return false; + _bytes++; + return true; + } + + bool put (const char *s) { + for (const char *p = s; *p; p++) + if (!put (*p)) + return false; + return true; + } + + bool put (int lit) { + CADICAL_assert (writing); + if (!lit) + return put ('0'); + else if (lit == -2147483648) { + CADICAL_assert (lit == INT_MIN); + return put ("-2147483648"); + } else { + char buffer[11]; + int i = sizeof buffer; + buffer[--i] = 0; + CADICAL_assert (lit != INT_MIN); + unsigned idx = abs (lit); + while (idx) { + CADICAL_assert (i > 0); + buffer[--i] = '0' + idx % 10; + idx /= 10; + } + if (lit < 0 && !put ('-')) + return false; + return put (buffer + i); + } + } + + bool put (int64_t l) { + CADICAL_assert (writing); + if (!l) + return put ('0'); + else if (l == INT64_MIN) { + CADICAL_assert (sizeof l == 8); + return put ("-9223372036854775808"); + } else { + char buffer[21]; + int i = sizeof buffer; + buffer[--i] = 0; + CADICAL_assert (l != INT64_MIN); + uint64_t k = l < 0 ? -l : l; + while (k) { + CADICAL_assert (i > 0); + buffer[--i] = '0' + k % 10; + k /= 10; + } + if (l < 0 && !put ('-')) + return false; + return put (buffer + i); + } + } + + bool put (uint64_t l) { + CADICAL_assert (writing); + if (!l) + return put ('0'); + else { + char buffer[22]; + int i = sizeof buffer; + buffer[--i] = 0; + while (l) { + CADICAL_assert (i > 0); + buffer[--i] = '0' + l % 10; + l /= 10; + } + return put (buffer + i); + } + } + + const char *name () const { return _name; } + uint64_t lineno () const { return _lineno; } + uint64_t bytes () const { return _bytes; } + + void connect_internal (Internal *i) { internal = i; } + bool closed () { return !file; } + + void close (bool print = false); + void flush (); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/flags.hpp b/src/sat/cadical/flags.hpp new file mode 100644 index 000000000..995438c7a --- /dev/null +++ b/src/sat/cadical/flags.hpp @@ -0,0 +1,91 @@ +#ifndef _flags_hpp_INCLUDED +#define _flags_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Flags { // Variable flags. + + // The first set of flags is related to 'analyze' and 'minimize'. + // + bool seen : 1; // seen in generating first UIP clause in 'analyze' + bool keep : 1; // keep in learned clause in 'minimize' + bool poison : 1; // can not be removed in 'minimize' + bool removable : 1; // can be removed in 'minimize' + bool shrinkable : 1; // can be removed in 'shrink' + bool added : 1; // has already been added to lrat_chain (in 'minimize') + + // These three variable flags are used to schedule clauses in subsumption + // ('subsume'), variables in bounded variable elimination ('elim') and in + // hyper ternary resolution ('ternary'). + // + bool elim : 1; // removed since last 'elim' round (*) + bool subsume : 1; // added since last 'subsume' round (*) + bool ternary : 1; // added in ternary clause since last 'ternary' (*) + bool sweep : 1; + bool blockable : 1; + + unsigned char + marked_signed : 2; // generate correct LRAT chains in decompose + unsigned char factor : 2; + + // These literal flags are used by blocked clause elimination ('block'). + // + unsigned char block : 2; // removed since last 'block' round (*) + unsigned char skip : 2; // skip this literal as blocking literal + + // Bits for handling assumptions. + // + unsigned char assumed : 2; + unsigned char failed : 2; // 0 if not part of failure + // 1 if positive lit is in failure + // 2 if negated lit is in failure + + enum { + UNUSED = 0, + ACTIVE = 1, + FIXED = 2, + ELIMINATED = 3, + SUBSTITUTED = 4, + PURE = 5 + }; + + unsigned char status : 3; + + // Initialized explicitly in 'Internal::init' through this function. + // + Flags () { + seen = keep = poison = removable = shrinkable = added = sweep = false; + subsume = elim = ternary = true; + block = 3u; + skip = assumed = failed = marked_signed = factor = 0; + status = UNUSED; + } + + bool unused () const { return status == UNUSED; } + bool active () const { return status == ACTIVE; } + bool fixed () const { return status == FIXED; } + bool eliminated () const { return status == ELIMINATED; } + bool substituted () const { return status == SUBSTITUTED; } + bool pure () const { return status == PURE; } + + // The flags marked with '(*)' are copied during 'External::copy_flags', + // which in essence means they are reset in the copy if they were clear. + // This avoids the effort of fruitless preprocessing the copy. + + void copy (Flags &dst) const { + dst.elim = elim; + dst.subsume = subsume; + dst.ternary = ternary; + dst.block = block; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/format.hpp b/src/sat/cadical/format.hpp new file mode 100644 index 000000000..cf229e760 --- /dev/null +++ b/src/sat/cadical/format.hpp @@ -0,0 +1,42 @@ +#ifndef _format_hpp_INCLUDED +#define _format_hpp_INCLUDED + +#include "global.h" + +#include +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// This class provides a 'printf' style formatting utility. +// Only '%c', '%d', '%s' are supported at this point. +// It is used to capture and save an error message. + +class Format { + char *buffer; + int64_t count, size; + void enlarge (); + void push_char (char); + void push_string (const char *); + void push_int (int); + void push_uint64 (uint64_t); + const char *add (const char *fmt, va_list &); + +public: + Format () : buffer (0), count (0), size (0) {} + ~Format () { + if (buffer) + delete[] buffer; + } + const char *init (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); + const char *append (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); + operator const char * () const { return count ? buffer : 0; } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/frattracer.hpp b/src/sat/cadical/frattracer.hpp new file mode 100644 index 000000000..cadfa3721 --- /dev/null +++ b/src/sat/cadical/frattracer.hpp @@ -0,0 +1,68 @@ +#ifndef _frattracer_h_INCLUDED +#define _frattracer_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class FratTracer : public FileTracer { + + Internal *internal; + File *file; + bool binary; + bool with_antecedents; + +#ifndef CADICAL_QUIET + int64_t added, deleted; + int64_t finalized, original; +#endif + + vector delete_ids; + + void put_binary_zero (); + void put_binary_lit (int external_lit); + void put_binary_id (int64_t id, bool = false); + + // support FRAT + void frat_add_original_clause (int64_t, const vector &); + void frat_add_derived_clause (int64_t, const vector &); + void frat_add_derived_clause (int64_t, const vector &, + const vector &); + void frat_delete_clause (int64_t, const vector &); + void frat_finalize_clause (int64_t, const vector &); + +public: + // own and delete 'file' + FratTracer (Internal *, File *file, bool binary, bool antecedents); + ~FratTracer (); + + void connect_internal (Internal *i) override; + void begin_proof (int64_t) override {} // skip + + void add_original_clause (int64_t, bool, const vector &, + bool = false) override; + + void add_derived_clause (int64_t, bool, const vector &, + const vector &) override; + + void delete_clause (int64_t, bool, const vector &) override; + + void finalize_clause (int64_t, const vector &) override; + + void report_status (int, int64_t) override {} // skip + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/global.h b/src/sat/cadical/global.h new file mode 100644 index 000000000..916246cab --- /dev/null +++ b/src/sat/cadical/global.h @@ -0,0 +1,21 @@ +#ifndef ABC_SAT_CADICAL_GLOBAL_HPP_ +#define ABC_SAT_CADICAL_GLOBAL_HPP_ + +// comment out next line to enable cadical debug mode +#define CADICAL_NDEBUG + +#define CADICAL_NBUILD +#define CADICAL_QUIET +#define CADICAL_NCONTRACTS +#define CADICAL_NTRACING +#define CADICAL_NCLOSEFROM + +#ifdef CADICAL_NDEBUG +#define CADICAL_assert(ignore) ((void)0) +#else +#define CADICAL_assert(cond) assert(cond) +#endif + +#include "misc/util/abc_global.h" + +#endif diff --git a/src/sat/cadical/heap.hpp b/src/sat/cadical/heap.hpp new file mode 100644 index 000000000..c42941413 --- /dev/null +++ b/src/sat/cadical/heap.hpp @@ -0,0 +1,218 @@ +#ifndef _heap_hpp_INCLUDED +#define _heap_hpp_INCLUDED + +#include "global.h" + +#include "util.hpp" // Alphabetically after 'heap.hpp'. + +#include + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +// This is a priority queue with updates for unsigned integers implemented +// as binary heap. We need to map integer elements added (through +// 'push_back') to positions on the binary heap in 'array'. This map is +// stored in the 'pos' array. This approach is really wasteful (at least in +// terms of memory) if only few and a sparse set of integers is added. So +// it should not be used in this situation. A generic priority queue would +// implement the mapping externally provided by another template parameter. +// Since we use 'UINT_MAX' as 'not contained' flag, we can only have +// 'UINT_MAX - 1' elements in the heap. + +const unsigned invalid_heap_position = UINT_MAX; + +template class heap { + + vector array; // actual binary heap + vector pos; // positions of elements in array + C less; // less-than for elements + + // Map an element to its position entry in the 'pos' map. + // + unsigned &index (unsigned e) { + if (e >= pos.size ()) + pos.resize (1 + (size_t) e, invalid_heap_position); + unsigned &res = pos[e]; + CADICAL_assert (res == invalid_heap_position || (size_t) res < array.size ()); + return res; + } + + bool has_parent (unsigned e) { return index (e) > 0; } + bool has_left (unsigned e) { + return (size_t) 2 * index (e) + 1 < size (); + } + bool has_right (unsigned e) { + return (size_t) 2 * index (e) + 2 < size (); + } + + unsigned parent (unsigned e) { + CADICAL_assert (has_parent (e)); + return array[(index (e) - 1) / 2]; + } + + unsigned left (unsigned e) { + CADICAL_assert (has_left (e)); + return array[2 * index (e) + 1]; + } + + unsigned right (unsigned e) { + CADICAL_assert (has_right (e)); + return array[2 * index (e) + 2]; + } + + // Exchange elements 'a' and 'b' in 'array' and fix their positions. + // + void exchange (unsigned a, unsigned b) { + unsigned &i = index (a), &j = index (b); + swap (array[i], array[j]); + swap (i, j); + } + + // Bubble up an element as far as necessary. + // + void up (unsigned e) { + unsigned p; + while (has_parent (e) && less ((p = parent (e)), e)) + exchange (p, e); + } + + // Bubble down an element as far as necessary. + // + void down (unsigned e) { + while (has_left (e)) { + unsigned c = left (e); + if (has_right (e)) { + unsigned r = right (e); + if (less (c, r)) + c = r; + } + if (!less (e, c)) + break; + exchange (e, c); + } + } + + // Very expensive checker for the main 'heap' invariant. Can be enabled + // to find violations of antisymmetry in the client implementation of + // 'less' and as well of course bugs in this heap implementation. It + // should be enabled during testing applications of the heap. + // + void check () { +#if 0 // EXPENSIVE HEAP CHECKING IF ENABLED +#warning "expensive checking in heap enabled" + CADICAL_assert (array.size () <= invalid_heap_position); + for (size_t i = 0; i < array.size (); i++) { + size_t l = 2*i + 1, r = 2*i + 2; + if (l < array.size ()) CADICAL_assert (!less (array[i], array[l])); + if (r < array.size ()) CADICAL_assert (!less (array[i], array[r])); + CADICAL_assert (array[i] >= 0); + { + CADICAL_assert ((size_t) array[i] < pos.size ()); + CADICAL_assert (i == (size_t) pos[array[i]]); + } + } + for (size_t i = 0; i < pos.size (); i++) { + if (pos[i] == invalid_heap_position) continue; + CADICAL_assert (pos[i] < array.size ()); + CADICAL_assert (array[pos[i]] == (unsigned) i); + } +#endif + } + +public: + heap (const C &c) : less (c) {} + + // Number of elements in the heap. + // + size_t size () const { return array.size (); } + + // Check if no more elements are in the heap. + // + bool empty () const { return array.empty (); } + + // Check whether 'e' is already in the heap. + // + bool contains (unsigned e) const { + if ((size_t) e >= pos.size ()) + return false; + return pos[e] != invalid_heap_position; + } + + // Add a new (not contained) element 'e' to the heap. + // + void push_back (unsigned e) { + CADICAL_assert (!contains (e)); + size_t i = array.size (); + CADICAL_assert (i < (size_t) invalid_heap_position); + array.push_back (e); + index (e) = (unsigned) i; + up (e); + down (e); + check (); + } + + // Returns the maximum element in the heap. + // + unsigned front () const { + CADICAL_assert (!empty ()); + return array[0]; + } + + // Removes the maximum element in the heap. + // + unsigned pop_front () { + CADICAL_assert (!empty ()); + unsigned res = array[0], last = array.back (); + if (size () > 1) + exchange (res, last); + index (res) = invalid_heap_position; + array.pop_back (); + if (size () > 1) + down (last); + check (); + return res; + } + + // Notify the heap, that evaluation of 'less' has changed for 'e'. + // + void update (unsigned e) { + CADICAL_assert (contains (e)); + up (e); + down (e); + check (); + } + + void clear () { + array.clear (); + pos.clear (); + } + + void erase () { + erase_vector (array); + erase_vector (pos); + } + + void shrink () { + shrink_vector (array); + shrink_vector (pos); + } + + // Standard iterators 'inherited' from 'vector'. + // + typedef typename vector::iterator iterator; + typedef typename vector::const_iterator const_iterator; + iterator begin () { return array.begin (); } + iterator end () { return array.end (); } + const_iterator begin () const { return array.begin (); } + const_iterator end () const { return array.end (); } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/idruptracer.hpp b/src/sat/cadical/idruptracer.hpp new file mode 100644 index 000000000..4ea478705 --- /dev/null +++ b/src/sat/cadical/idruptracer.hpp @@ -0,0 +1,116 @@ +#ifndef _idruptracer_h_INCLUDED +#define _idruptracer_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +class FileTracer; + +namespace CaDiCaL { + +struct IdrupClause { + IdrupClause *next; // collision chain link for hash table + uint64_t hash; // previously computed full 64-bit hash + int64_t id; // id of clause + unsigned size; + int literals[1]; +}; + +class IdrupTracer : public FileTracer { + + Internal *internal; + File *file; + bool binary; + bool piping; // The 'file' is a pipe and needs eagerly flushing. + + // hash table for conclusion + // + uint64_t num_clauses; // number of clauses in hash table + uint64_t size_clauses; // size of clause hash table + IdrupClause **clauses; // hash table of clauses + vector imported_clause; + vector assumptions; + + static const unsigned num_nonces = 4; + + uint64_t nonces[num_nonces]; // random numbers for hashing + uint64_t last_hash; // last computed hash value of clause + int64_t last_id; // id of the last added clause + IdrupClause *last_clause; + uint64_t compute_hash (int64_t); // compute and save hash value of clause + + IdrupClause *new_clause (); + void delete_clause (IdrupClause *); + + static uint64_t reduce_hash (uint64_t hash, uint64_t size); + + void enlarge_clauses (); // enlarge hash table for clauses + void insert (); // insert clause in hash table + bool + find_and_delete (const int64_t); // find clause position in hash table + +#ifndef CADICAL_QUIET + int64_t added, deleted, weakened, restore, original, solved; +#endif + + void flush_if_piping (); + + void put_binary_zero (); + void put_binary_lit (int external_lit); + void put_binary_id (int64_t id, bool = false); + + void idrup_add_derived_clause (const vector &clause); + void idrup_delete_clause (int64_t id, const vector &clause); + void idrup_add_restored_clause (const vector &clause); + void idrup_add_original_clause (const vector &clause); + void idrup_conclude_and_delete (const vector &conclusion); + void idrup_report_status (int status); + void idrup_conclude_sat (const vector &model); + void idrup_conclude_unknown (const vector &trail); + void idrup_solve_query (); + +public: + IdrupTracer (Internal *, File *file, bool); + ~IdrupTracer (); + + // proof section: + void add_derived_clause (int64_t, bool, const vector &, + const vector &) override; + void add_assumption_clause (int64_t, const vector &, + const vector &) override; + void weaken_minus (int64_t, const vector &) override; + void delete_clause (int64_t, bool, const vector &) override; + void add_original_clause (int64_t, bool, const vector &, + bool = false) override; + void report_status (int, int64_t) override; + void conclude_sat (const vector &) override; + void conclude_unsat (ConclusionType, const vector &) override; + void conclude_unknown (const vector &) override; + + void solve_query () override; + void add_assumption (int) override; + void reset_assumptions () override; + + // skip + void begin_proof (int64_t) override {} + void finalize_clause (int64_t, const vector &) override {} + void strengthen (int64_t) override {} + void add_constraint (const vector &) override {} + + // logging and file io + void connect_internal (Internal *i) override; + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/instantiate.hpp b/src/sat/cadical/instantiate.hpp new file mode 100644 index 000000000..f6c8767dd --- /dev/null +++ b/src/sat/cadical/instantiate.hpp @@ -0,0 +1,51 @@ +#ifndef _instantiate_hpp_INCLUDED +#define _instantiate_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// We are trying to remove literals in clauses, which occur in few clauses +// and further restrict this removal to variables for which variable +// elimination failed. Thus if for instance we succeed in removing the +// single occurrence of a literal, pure literal elimination can +// eliminate the corresponding variable in the next variable elimination +// round. The set of such literal clause candidate pairs is collected at +// the end of a variable elimination round and tried before returning. The +// name of this technique is inspired by 'variable instantiation' as +// described in [AnderssonBjesseCookHanna-DAC'02] and apparently +// successfully used in the 'Oepir' SAT solver. + +struct Clause; +struct Internal; + +class Instantiator { + + friend struct Internal; + + struct Candidate { + int lit; + int size; + size_t negoccs; + Clause *clause; + Candidate (int l, Clause *c, int s, size_t n) + : lit (l), size (s), negoccs (n), clause (c) {} + }; + + vector candidates; + +public: + void candidate (int l, Clause *c, int s, size_t n) { + candidates.push_back (Candidate (l, c, s, n)); + } + + operator bool () const { return !candidates.empty (); } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/internal.hpp b/src/sat/cadical/internal.hpp new file mode 100644 index 000000000..d86479a52 --- /dev/null +++ b/src/sat/cadical/internal.hpp @@ -0,0 +1,1866 @@ +#ifndef _internal_hpp_INCLUDED +#define _internal_hpp_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ + +// Wrapped build specific headers which should go first. + +#include "inttypes.hpp" + +/*------------------------------------------------------------------------*/ + +// Common 'C' headers. + +#include +#include +#include +#include +#include +#include +#include +#include + +// Less common 'C' header. + +#ifndef WIN32 +extern "C" { +#include +} +#endif + +/*------------------------------------------------------------------------*/ + +// Common 'C++' headers. + +#include +#include +#include +#include +#include + +/*------------------------------------------------------------------------*/ + +// All internal headers are included here. This gives a nice overview on +// what is needed altogether. The 'Internal' class needs almost all the +// headers anyhow (since the idea is to avoid pointer references as much as +// possible). Most implementation files need to see the definition of the +// 'Internal' too. Thus there is no real advantage in trying to reduce the +// number of header files included here. The other benefit of having all +// header files here is that '.cpp' files then only need to include this. + +#include "arena.hpp" +#include "averages.hpp" +#include "bins.hpp" +#include "block.hpp" +#include "cadical.hpp" +#include "checker.hpp" +#include "clause.hpp" +#include "config.hpp" +#include "congruence.hpp" +#include "contract.hpp" +#include "cover.hpp" +#include "decompose.hpp" +#include "drattracer.hpp" +#include "elim.hpp" +#include "ema.hpp" +#include "external.hpp" +#include "factor.hpp" +#include "file.hpp" +#include "flags.hpp" +#include "format.hpp" +#include "frattracer.hpp" +#include "heap.hpp" +#include "idruptracer.hpp" +#include "instantiate.hpp" +#include "internal.hpp" +#include "level.hpp" +#include "lidruptracer.hpp" +#include "limit.hpp" +#include "logging.hpp" +#include "lratchecker.hpp" +#include "lrattracer.hpp" +#include "message.hpp" +#include "occs.hpp" +#include "options.hpp" +#include "parse.hpp" +#include "phases.hpp" +#include "profile.hpp" +#include "proof.hpp" +#include "queue.hpp" +#include "radix.hpp" +#include "random.hpp" +#include "range.hpp" +#include "reap.hpp" +#include "reluctant.hpp" +#include "resources.hpp" +#include "score.hpp" +#include "stats.hpp" +#include "sweep.hpp" +#include "terminal.hpp" +#include "tracer.hpp" +#include "util.hpp" +#include "var.hpp" +#include "veripbtracer.hpp" +#include "version.hpp" +#include "vivify.hpp" +#include "watch.hpp" + +// c headers +//extern "C" { +#include "kitten.h" +//} +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +struct Coveror; +struct External; +struct Walker; +class Tracer; +class FileTracer; +class StatTracer; + +struct CubesWithStatus { + int status = 0; + std::vector> cubes; +}; + +/*------------------------------------------------------------------------*/ + +struct Internal { + + /*----------------------------------------------------------------------*/ + + // The actual internal state of the solver is set and maintained in this + // section. This is currently only used for debugging and testing. + + enum Mode { + BLOCK = (1 << 0), + CONDITION = (1 << 1), + CONGRUENCE = (1 << 2), + COVER = (1 << 3), + DECOMP = (1 << 4), + DEDUP = (1 << 5), + ELIM = (1 << 6), + FACTOR = (1 << 7), + LUCKY = (1 << 8), + PROBE = (1 << 9), + SEARCH = (1 << 10), + SIMPLIFY = (1 << 11), + SUBSUME = (1 << 12), + SWEEP = (1 << 13), + TERNARY = (1 << 14), + TRANSRED = (1 << 15), + VIVIFY = (1 << 16), + WALK = (1 << 17), + }; + + bool in_mode (Mode m) const { return (mode & m) != 0; } + void set_mode (Mode m) { + CADICAL_assert (!(mode & m)); + mode |= m; + } + void reset_mode (Mode m) { + CADICAL_assert (mode & m); + mode &= ~m; + } + void require_mode (Mode m) const { CADICAL_assert (mode & m), (void) m; } + + /*----------------------------------------------------------------------*/ + + int mode; // current internal state + int tier1[2] = { + 2, 2}; // tier1 limit for 0=focused, 1=stable; aka tier1[stable] + int tier2[2] = { + 6, 6}; // tier2 limit for 0=focused, 1=stable; aka tier1[stable] + bool unsat; // empty clause found or learned + bool iterating; // report learned unit ('i' line) + bool localsearching; // true during local search + bool lookingahead; // true during look ahead + bool preprocessing; // true during preprocessing + bool protected_reasons; // referenced reasons are protected + bool force_saved_phase; // force saved phase in decision + bool searching_lucky_phases; // during 'lucky_phases' + bool stable; // true during stabilization phase + bool reported; // reported in this solving call + bool external_prop; // true if an external propagator is connected + bool did_external_prop; // true if ext. propagation happened + bool external_prop_is_lazy; // true if the external propagator is lazy + bool forced_backt_allowed; // external propagator can force backtracking + bool private_steps; // no notification of ext. prop during these steps + char rephased; // last type of resetting phases + Reluctant reluctant; // restart counter in stable mode + size_t vsize; // actually allocated variable data size + int max_var; // internal maximum variable index + int64_t clause_id; // last used id for clauses + int64_t original_id; // ids for original clauses to produce LRAT + int64_t reserved_ids; // number of reserved ids for original clauses + int64_t conflict_id; // store conflict id for finalize (frat) + int64_t saved_decisions; // to compute decision rate average + bool concluded; // keeps track of conclude + vector conclusion; // store ids of conclusion clauses + vector + unit_clauses_idx; // keep track of unit_clauses (LRAT/FRAT) + vector lrat_chain; // create LRAT in solver: option lratdirect + vector mini_chain; // used to create LRAT in minimize + vector minimize_chain; // used to create LRAT in minimize + vector unit_chain; // used to avoid duplicate units + vector inst_chain; // for LRAT in instantiate + vector>> + probehbr_chains; // only used if opts.probehbr=false + bool lrat; // generate LRAT internally + bool frat; // finalize non-deleted clauses in proof + int level; // decision level ('control.size () - 1') + Phases phases; // saved, target and best phases + signed char *vals; // assignment [-max_var,max_var] + vector marks; // signed marks [1,max_var] + vector frozentab; // frozen counters [1,max_var] + vector i2e; // maps internal 'idx' to external 'lit' + vector relevanttab; // Reference counts for observed variables. + Queue queue; // variable move to front decision queue + Links links; // table of links for decision queue + double score_inc; // current score increment + ScoreSchedule scores; // score based decision priority queue + vector stab; // table of variable scores [1,max_var] + vector vtab; // variable table [1,max_var] + vector parents; // parent literals during probing + vector ftab; // variable and literal flags + vector btab; // enqueue time stamps for queue + vector gtab; // time stamp table to recompute glue + vector otab; // table of occurrences for all literals + vector rtab; // table of redundant occurrences + vector ptab; // table for caching probing attempts + vector ntab; // number of one-sided occurrences table + vector big; // binary implication graph + vector wtab; // table of watches for all literals + Clause *conflict; // set in 'propagation', reset in 'analyze' + Clause *ignore; // ignored during 'vivify_propagate' + Clause *dummy_binary; // Dummy binary clause for subsumption + Clause *external_reason; // used as reason at external propagations + Clause *newest_clause; // used in external_propagate + bool force_no_backtrack; // for new clauses with external propagator + bool from_propagator; // differentiate new clauses... + bool ext_clause_forgettable; // Is new clause from propagator forgettable + int tainted_literal; // used for ILB + size_t notified; // next trail position to notify external prop + Clause *probe_reason; // set during probing + size_t propagated; // next trail position to propagate + size_t propagated2; // next binary trail position to propagate + size_t propergated; // propagated without blocking literals + size_t best_assigned; // best maximum assigned ever + size_t target_assigned; // maximum assigned without conflict + size_t no_conflict_until; // largest trail prefix without conflict + vector trail; // currently assigned literals + vector clause; // simplified in parsing & learning + vector assumptions; // assumed literals + vector constraint; // literals of the constraint + bool unsat_constraint; // constraint used for unsatisfiability? + bool marked_failed; // are the failed assumptions marked? + vector original; // original added literals + vector levels; // decision levels in learned clause + vector analyzed; // analyzed literals in 'analyze' + vector unit_analyzed; // to avoid duplicate units in lrat_chain + vector sign_marked; // literals skipped in 'decompose' + vector minimized; // removable or poison in 'minimize' + vector shrinkable; // removable or poison in 'shrink' + Reap reap; // radix heap for shrink + + vector sweep_schedule; // remember sweep varibles to reschedule + bool sweep_incomplete; // sweep + + cadical_kitten *citten; + + size_t num_assigned; // check for satisfied + + vector probes; // remaining scheduled probes + vector control; // 'level + 1 == control.size ()' + vector clauses; // ordered collection of all clauses + Averages averages; // glue, size, jump moving averages + Delay delay[2]; // Delay certain functions + Delay congruence_delay; // Delay congruence if not successful recently + Limit lim; // limits for various phases + Last last; // statistics at last occurrence + Inc inc; // increments on limits + + Delay delaying_vivify_irredundant; + Delay delaying_sweep; + + Proof *proof; // abstraction layer between solver and tracers + vector + tracers; // proof tracing objects (ie interpolant calculator) + vector + file_tracers; // file proof tracers (ie DRAT, LRAT...) + vector stat_tracers; // checkers + + Options opts; // run-time options + Stats stats; // statistics +#ifndef CADICAL_QUIET + Profiles profiles; // time profiles for various functions + bool force_phase_messages; // force 'phase (...)' messages +#endif + Arena arena; // memory arena for moving garbage collector + Format error_message; // provide persistent error message + string prefix; // verbose messages prefix + + Internal *internal; // proxy to 'this' in macros + External *external; // proxy to 'external' buddy in 'Solver' + + const unsigned max_used = 255; // must fix into the header of the clause! + /*----------------------------------------------------------------------*/ + + // Asynchronous termination flag written by 'terminate' and read by + // 'terminated_asynchronously' (the latter at the end of this header). + // + volatile bool termination_forced; + + /*----------------------------------------------------------------------*/ + + const Range vars; // Provides safe variable iteration. + const Sange lits; // Provides safe literal iteration. + + /*----------------------------------------------------------------------*/ + + Internal (); + ~Internal (); + + /*----------------------------------------------------------------------*/ + + // Internal delegates and helpers for corresponding functions in + // 'External' and 'Solver'. The 'init_vars' function initializes + // variables up to and including the requested variable index. + // + void init_vars (int new_max_var); + + void init_enqueue (int idx); + void init_queue (int old_max_var, int new_max_var); + + void init_scores (int old_max_var, int new_max_var); + + void add_original_lit (int lit); + + // only able to restore irredundant clause + void finish_added_clause_with_id (int64_t id, bool restore = false); + + // Reserve ids for original clauses to produce lrat + void reserve_ids (int number); + + // Enlarge tables. + // + void enlarge_vals (size_t new_vsize); + void enlarge (int new_max_var); + + // A variable is 'active' if it is not eliminated nor fixed. + // + bool active (int lit) { return flags (lit).active (); } + + int active () const { + int res = stats.active; +#ifndef CADICAL_NDEBUG + int tmp = max_var; + tmp -= stats.unused; + tmp -= stats.now.fixed; + tmp -= stats.now.eliminated; + tmp -= stats.now.substituted; + tmp -= stats.now.pure; + CADICAL_assert (tmp >= 0); + CADICAL_assert (tmp == res); +#endif + return res; + } + + void reactivate (int lit); // During 'restore'. + + // Currently remaining active redundant and irredundant clauses. + + int64_t redundant () const { return stats.current.redundant; } + + int64_t irredundant () const { return stats.current.irredundant; } + + double clause_variable_ratio () const { + return relative (irredundant (), active ()); + } + + // Scale values relative to clause variable ratio. + // + double scale (double v) const; + + // Unsigned literals (abs) with checks. + // + int vidx (int lit) const { + int idx; + CADICAL_assert (lit); + CADICAL_assert (lit != INT_MIN); + idx = abs (lit); + CADICAL_assert (idx <= max_var); + return idx; + } + + // Unsigned version with LSB denoting sign. This is used in indexing + // arrays by literals. The idea is to keep the elements in such an array + // for both the positive and negated version of a literal close together. + // + unsigned vlit (int lit) const { + return (lit < 0) + 2u * (unsigned) vidx (lit); + } + + int u2i (unsigned u) { + CADICAL_assert (u > 1); + int res = u / 2; + CADICAL_assert (res <= max_var); + if (u & 1) + res = -res; + return res; + } + + int citten2lit (unsigned ulit) { + int res = (ulit / 2) + 1; + CADICAL_assert (res <= max_var); + if (ulit & 1) + res = -res; + return res; + } + + unsigned lit2citten (int lit) { + int idx = vidx (lit) - 1; + return (lit < 0) + 2u * (unsigned) idx; + } + + int64_t unit_id (int lit) const { + CADICAL_assert (lrat || frat); + CADICAL_assert (val (lit) > 0); + const unsigned uidx = vlit (lit); + int64_t id = unit_clauses_idx[uidx]; + CADICAL_assert (id); + return id; + } + + inline int64_t &unit_clauses (int uidx) { + CADICAL_assert (lrat || frat); + CADICAL_assert (uidx > 0); + CADICAL_assert ((size_t) uidx < unit_clauses_idx.size ()); + return unit_clauses_idx[uidx]; + } + + // Helper functions to access variable and literal data. + // + Var &var (int lit) { return vtab[vidx (lit)]; } + Link &link (int lit) { return links[vidx (lit)]; } + Flags &flags (int lit) { return ftab[vidx (lit)]; } + int64_t &bumped (int lit) { return btab[vidx (lit)]; } + int &propfixed (int lit) { return ptab[vlit (lit)]; } + double &score (int lit) { return stab[vidx (lit)]; } + + const Flags &flags (int lit) const { return ftab[vidx (lit)]; } + + bool occurring () const { return !otab.empty (); } + bool watching () const { return !wtab.empty (); } + + Bins &bins (int lit) { return big[vlit (lit)]; } + Occs &occs (int lit) { return otab[vlit (lit)]; } + int64_t &noccs (int lit) { return ntab[vlit (lit)]; } + Watches &watches (int lit) { return wtab[vlit (lit)]; } + + // Variable bumping through exponential VSIDS (EVSIDS) as in MiniSAT. + // + bool use_scores () const { return opts.score && stable; } + void bump_variable_score (int lit); + void bump_variable_score_inc (); + void rescale_variable_scores (); + + // Marking variables with a sign (positive or negative). + // + signed char marked (int lit) const { + signed char res = marks[vidx (lit)]; + if (lit < 0) + res = -res; + return res; + } + void mark (int lit) { + CADICAL_assert (!marked (lit)); + marks[vidx (lit)] = sign (lit); + CADICAL_assert (marked (lit) > 0); + CADICAL_assert (marked (-lit) < 0); + } + void unmark (int lit) { + marks[vidx (lit)] = 0; + CADICAL_assert (!marked (lit)); + } + + // Use only bits 6 and 7 to store the sign or zero. The remaining + // bits can be used as additional flags. + // + signed char marked67 (int lit) const { + signed char res = marks[vidx (lit)] >> 6; + if (lit < 0) + res = -res; + return res; + } + void mark67 (int lit) { + signed char &m = marks[vidx (lit)]; + const signed char mask = 0x3f; +#ifndef CADICAL_NDEBUG + const signed char bits = m & mask; +#endif + m = (m & mask) | (sign (lit) << 6); + CADICAL_assert (marked (lit) > 0); + CADICAL_assert (marked (-lit) < 0); + CADICAL_assert ((m & mask) == bits); + CADICAL_assert (marked67 (lit) > 0); + CADICAL_assert (marked67 (-lit) < 0); + } + void unmark67 (int lit) { + signed char &m = marks[vidx (lit)]; + const signed char mask = 0x3f; +#ifndef CADICAL_NDEBUG + const signed bits = m & mask; +#endif + m &= mask; + CADICAL_assert ((m & mask) == bits); + } + + void unmark (vector &lits) { + for (const auto &lit : lits) + unmark (lit); + } + + // The other 6 bits of the 'marks' bytes can be used as additional + // (unsigned) marking bits. Currently we only use the least significant + // bit in 'condition' to mark variables in the conditional part. + // + bool getbit (int lit, int bit) const { + CADICAL_assert (0 <= bit), CADICAL_assert (bit < 6); + return marks[vidx (lit)] & (1 << bit); + } + void setbit (int lit, int bit) { + CADICAL_assert (0 <= bit), CADICAL_assert (bit < 6); + CADICAL_assert (!getbit (lit, bit)); + marks[vidx (lit)] |= (1 << bit); + CADICAL_assert (getbit (lit, bit)); + } + void unsetbit (int lit, int bit) { + CADICAL_assert (0 <= bit), CADICAL_assert (bit < 6); + CADICAL_assert (getbit (lit, bit)); + marks[vidx (lit)] &= ~(1 << bit); + CADICAL_assert (!getbit (lit, bit)); + } + + // Marking individual literals. + // + bool marked2 (int lit) const { + unsigned res = marks[vidx (lit)]; + CADICAL_assert (res <= 3); + unsigned bit = bign (lit); + return (res & bit) != 0; + } + void mark2 (int lit) { + marks[vidx (lit)] |= bign (lit); + CADICAL_assert (marked2 (lit)); + } + + // marks bits 1,2,3 and 4,5,6 depending on fact and sign of lit + // + bool getfact (int lit, int fact) const { + CADICAL_assert (fact == 1 || fact == 2 || fact == 4); + int res = marks[vidx (lit)]; + if (lit < 0) { + res >>= 3; + } else { + res &= 7; + } + // CADICAL_assert (!res || res == 1 || res == 2 || res == 4); + return res & fact; + } + + void markfact (int lit, int fact) { + CADICAL_assert (fact == 1 || fact == 2 || fact == 4); + CADICAL_assert (!getfact (lit, fact)); +#ifndef CADICAL_NDEBUG + int before = getfact (-lit, fact); +#endif + int res = marks[vidx (lit)]; + if (lit < 0) { + res |= fact << 3; + } else { + res |= fact; + } + marks[vidx (lit)] = res; + CADICAL_assert (getfact (lit, fact)); +#ifndef CADICAL_NDEBUG + CADICAL_assert (getfact (-lit, fact) == before); +#endif + } + + void unmarkfact (int lit, int fact) { + CADICAL_assert (fact == 1 || fact == 2 || fact == 4); + CADICAL_assert (getfact (lit, fact)); + int res = marks[vidx (lit)]; + if (lit < 0) { + res &= ~(fact << 3); + } else { + res &= ~fact; + } + marks[vidx (lit)] = res; + CADICAL_assert (!getfact (lit, fact)); + } + + // Marking and unmarking of all literals in a clause. + // + void mark_clause (); // mark 'this->clause' + void mark (Clause *); + void mark2 (Clause *); + void unmark_clause (); // unmark 'this->clause' + void unmark (Clause *); + + // Watch literal 'lit' in clause with blocking literal 'blit'. + // Inlined here, since it occurs in the tight inner loop of 'propagate'. + // + inline void watch_literal (int lit, int blit, Clause *c) { + CADICAL_assert (lit != blit); + Watches &ws = watches (lit); + ws.push_back (Watch (blit, c)); + LOG (c, "watch %d blit %d in", lit, blit); + } + + // Add two watches to a clause. This is used initially during allocation + // of a clause and during connecting back all watches after preprocessing. + // + inline void watch_clause (Clause *c) { + const int l0 = c->literals[0]; + const int l1 = c->literals[1]; + watch_literal (l0, l1, c); + watch_literal (l1, l0, c); + } + + inline void unwatch_clause (Clause *c) { + const int l0 = c->literals[0]; + const int l1 = c->literals[1]; + remove_watch (watches (l0), c); + remove_watch (watches (l1), c); + } + + // Update queue to point to last potentially still unassigned variable. + // All variables after 'queue.unassigned' in bump order are assumed to be + // assigned. Then update the 'queue.bumped' field and log it. This is + // inlined here since it occurs in several inner loops. + // + inline void update_queue_unassigned (int idx) { + CADICAL_assert (0 < idx); + CADICAL_assert (idx <= max_var); + queue.unassigned = idx; + queue.bumped = btab[idx]; + LOG ("queue unassigned now %d bumped %" PRId64 "", idx, btab[idx]); + } + + void bump_queue (int idx); + + // Mark (active) variables as eliminated, substituted, pure or fixed, + // which turns them into inactive variables. + // + void mark_eliminated (int); + void mark_substituted (int); + void mark_active (int); + void mark_fixed (int); + void mark_pure (int); + + // Managing clauses in 'clause.cpp'. Without explicit 'Clause' argument + // these functions work on the global temporary 'clause'. + // + Clause *new_clause (bool red, int glue = 0); + void promote_clause (Clause *, int new_glue); + void promote_clause_glue_only (Clause *, int new_glue); + size_t shrink_clause (Clause *, int new_size); + void minimize_sort_clause (); + void shrink_and_minimize_clause (); + void reset_shrinkable (); + void mark_shrinkable_as_removable (int, std::vector::size_type); + int shrink_literal (int, int, unsigned); + unsigned shrunken_block_uip (int, int, + std::vector::reverse_iterator &, + std::vector::reverse_iterator &, + std::vector::size_type, const int); + void shrunken_block_no_uip (const std::vector::reverse_iterator &, + const std::vector::reverse_iterator &, + unsigned &, const int); + void push_literals_of_block (const std::vector::reverse_iterator &, + const std::vector::reverse_iterator &, + int, unsigned); + unsigned shrink_next (int, unsigned &, unsigned &); + std::vector::reverse_iterator + minimize_and_shrink_block (std::vector::reverse_iterator &, + unsigned int &, unsigned int &, const int); + unsigned shrink_block (std::vector::reverse_iterator &, + std::vector::reverse_iterator &, int, + unsigned &, unsigned &, const int, unsigned); + unsigned shrink_along_reason (int, int, bool, bool &, unsigned); + + void deallocate_clause (Clause *); + void delete_clause (Clause *); + void mark_garbage (Clause *); + void assign_original_unit (int64_t, int); + void add_new_original_clause (int64_t); + Clause *new_learned_redundant_clause (int glue); + Clause *new_hyper_binary_resolved_clause (bool red, int glue); + Clause *new_clause_as (const Clause *orig); + Clause *new_resolved_irredundant_clause (); + + // Forward reasoning through propagation in 'propagate.cpp'. + // + int assignment_level (int lit, Clause *); + void build_chain_for_units (int lit, Clause *reason, bool forced); + void build_chain_for_empty (); + void search_assign (int lit, Clause *); + void search_assign_driving (int lit, Clause *reason); + void search_assign_external (int lit); + void search_assume_decision (int decision); + void assign_unit (int lit); + int64_t cache_lines (size_t bytes) { return (bytes + 127) / 128; } + int64_t cache_lines (size_t n, size_t bytes) { + return cache_lines (n * bytes); + } + bool propagate (); + +#ifdef PROFILE_MODE + bool propagate_wrapper (); + bool propagate_unstable (); + bool propagate_stable (); + void analyze_wrapper (); + void analyze_unstable (); + void analyze_stable (); + int decide_wrapper (); + int decide_stable (); + int decide_unstable (); +#else +#define propagate_wrapper propagate +#define analyze_wrapper analyze +#define decide_wrapper decide +#endif + + void propergate (); // Repropagate without blocking literals. + + // Undo and restart in 'backtrack.cpp'. + // + void unassign (int lit); + void update_target_and_best (); + void backtrack (int target_level = 0); + void backtrack_without_updating_phases (int target_level = 0); + + // Minimized learned clauses in 'minimize.cpp'. + // + bool minimize_literal (int lit, int depth = 0); + void minimize_clause (); + void calculate_minimize_chain (int lit, std::vector &stack); + + // Learning from conflicts in 'analyze.cc'. + // + void learn_empty_clause (); + void learn_unit_clause (int lit); + + void bump_variable (int lit); + void bump_variables (); + int recompute_glue (Clause *); + void bump_clause (Clause *); + void bump_clause2 (Clause *); + void clear_unit_analyzed_literals (); + void clear_analyzed_literals (); + void clear_analyzed_levels (); + void clear_minimized_literals (); + bool bump_also_reason_literal (int lit); + void bump_also_reason_literals (int lit, int depth_limit, + size_t size_limit); + void bump_also_all_reason_literals (); + void analyze_literal (int lit, int &open, int &resolvent_size, + int &antecedent_size); + void analyze_reason (int lit, Clause *, int &open, int &resolvent_size, + int &antecedent_size); + Clause *new_driving_clause (const int glue, int &jump); + int find_conflict_level (int &forced); + int determine_actual_backtrack_level (int jump); + void otfs_strengthen_clause (Clause *, int, int, + const std::vector &); + void otfs_subsume_clause (Clause *subsuming, Clause *subsumed); + int otfs_find_backtrack_level (int &forced); + Clause *on_the_fly_strengthen (Clause *conflict, int lit); + void update_decision_rate_average (); + void analyze (); + void iterate (); // report learned unit clause + + // Learning from external propagator in 'external_propagate.cpp' + // + bool external_propagate (); + bool external_check_solution (); + void add_external_clause (int propagated_lit = 0, + bool no_backtrack = false); + Clause *learn_external_reason_clause (int lit, int falsified_elit = 0, + bool no_backtrack = false); + Clause *wrapped_learn_external_reason_clause (int lit); + void explain_external_propagations (); + void explain_reason (int lit, Clause *, int &open); + void move_literals_to_watch (); + void handle_external_clause (Clause *); + void notify_assignments (); + void notify_decision (); + void notify_backtrack (size_t new_level); + void force_backtrack (size_t new_level); + int ask_decision (); + bool ask_external_clause (); + void add_observed_var (int ilit); + void remove_observed_var (int ilit); + bool observed (int ilit) const; + bool is_decision (int ilit); + void check_watched_literal_invariants (); + void set_tainted_literal (); + void renotify_trail_after_ilb (); + void renotify_trail_after_local_search (); + void renotify_full_trail (); + void connect_propagator (); + void mark_garbage_external_forgettable (int64_t id); + bool is_external_forgettable (int64_t id); +#ifndef CADICAL_NDEBUG + bool get_merged_literals (std::vector &); + void get_all_fixed_literals (std::vector &); +#endif + + void recompute_tier (); + // Use last learned clause to subsume some more. + // + void eagerly_subsume_recently_learned_clauses (Clause *); + + // Restarting policy in 'restart.cc'. + // + bool stabilizing (); + bool restarting (); + int reuse_trail (); + void restart (); + + // Functions to set and reset certain 'phases'. + // + void clear_phases (vector &); // reset argument to zero + void copy_phases (vector &); // copy 'saved' to argument + + // Resetting the saved phased in 'rephase.cpp'. + // + bool rephasing (); + char rephase_best (); + char rephase_flipping (); + char rephase_inverted (); + char rephase_original (); + char rephase_random (); + char rephase_walk (); + void shuffle_scores (); + void shuffle_queue (); + void rephase (); + + // Lucky feasible case checking. + // + int unlucky (int res); + bool lucky_propagate_discrepency (int); + int trivially_false_satisfiable (); + int trivially_true_satisfiable (); + int forward_false_satisfiable (); + int forward_true_satisfiable (); + int backward_false_satisfiable (); + int backward_true_satisfiable (); + int positive_horn_satisfiable (); + int negative_horn_satisfiable (); + + // Asynchronous terminating check. + // + bool terminated_asynchronously (int factor = 1); + + bool search_limits_hit (); + + void terminate () { + LOG ("forcing asynchronous termination"); + termination_forced = true; + } + + // Reducing means determining useless clauses with 'reduce' in + // 'reduce.cpp' as well as root level satisfied clause and then removing + // those which are not used as reason anymore with garbage collection. + // + bool flushing (); + bool reducing (); + void protect_reasons (); + void mark_clauses_to_be_flushed (); + void mark_useless_redundant_clauses_as_garbage (); + bool propagate_out_of_order_units (); + void unprotect_reasons (); + void reduce (); + + // Garbage collection in 'collect.cpp' called from 'reduce' and during + // inprocessing and preprocessing. + // + int clause_contains_fixed_literal (Clause *); + void remove_falsified_literals (Clause *); + void mark_satisfied_clauses_as_garbage (); + void copy_clause (Clause *); + void flush_watches (int lit, Watches &); + size_t flush_occs (int lit); + void flush_all_occs_and_watches (); + void update_reason_references (); + void copy_non_garbage_clauses (); + void delete_garbage_clauses (); + void check_clause_stats (); + void check_var_stats (); + bool arenaing (); + void garbage_collection (); + + // only remove binary clauses from the watches + void remove_garbage_binaries (); + + // Set-up occurrence list counters and containers. + // + void init_occs (); + void init_bins (); + void init_noccs (); + void clear_noccs (); + void clear_occs (); + void reset_occs (); + void reset_bins (); + void reset_noccs (); + + // Operators on watches. + // + void init_watches (); + void connect_watches (bool irredundant_only = false); + void connect_binary_watches (); + void sort_watches (); + void clear_watches (); + void reset_watches (); + + // Regular forward subsumption checking in 'subsume.cpp'. + // + void strengthen_clause (Clause *, int); + void subsume_clause (Clause *subsuming, Clause *subsumed); + int subsume_check (Clause *subsuming, Clause *subsumed); + int try_to_subsume_clause (Clause *, vector &shrunken); + void reset_subsume_bits (); + bool subsume_round (); + void subsume (); + + // Covered clause elimination of large clauses. + // + void covered_literal_addition (int lit, Coveror &); + void asymmetric_literal_addition (int lit, Coveror &); + void cover_push_extension (int lit, Coveror &); + bool cover_propagate_asymmetric (int lit, Clause *ignore, Coveror &); + bool cover_propagate_covered (int lit, Coveror &); + bool cover_clause (Clause *c, Coveror &); + int64_t cover_round (); + bool cover (); + + // Strengthening through vivification in 'vivify.cpp'. + // + void demote_clause (Clause *); + void flush_vivification_schedule (std::vector &, int64_t &); + void vivify_increment_stats (const Vivifier &vivifier); + void vivify_subsume_clause (Clause *subsuming, Clause *subsumed); + void compute_tier_limits (Vivifier &); + void vivify_initialize (Vivifier &vivifier, int64_t &ticks); + inline void vivify_prioritize_leftovers (char, size_t prioritized, + std::vector &schedule); + bool consider_to_vivify_clause (Clause *candidate); + void vivify_sort_watched (Clause *c); + bool vivify_instantiate ( + const std::vector &, Clause *, + std::vector> &lrat_stack, + int64_t &ticks); + void vivify_analyze_redundant (Vivifier &, Clause *start, bool &); + void vivify_build_lrat (int, Clause *, + std::vector> &); + void vivify_chain_for_units (int lit, Clause *reason); + void vivify_strengthen (Clause *candidate); + void vivify_assign (int lit, Clause *); + void vivify_assume (int lit); + bool vivify_propagate (int64_t &); + void vivify_deduce (Clause *candidate, Clause *conflct, int implied, + Clause **, bool &); + bool vivify_clause (Vivifier &, Clause *candidate); + void vivify_analyze (Clause *start, bool &, Clause **, + const Clause *const, int implied, bool &); + bool vivify_shrinkable (const std::vector &sorted, Clause *c); + void vivify_round (Vivifier &, int64_t delta); + bool vivify (); + + // Compacting (shrinking internal variable tables) in 'compact.cpp' + // + bool compacting (); + void compact (); + + // Transitive reduction of binary implication graph in 'transred.cpp' + // + void transred (); + + // We monitor the maximum size and glue of clauses during 'reduce' and + // thus can predict if a redundant extended clause is likely to be kept in + // the next 'reduce' phase. These clauses are target of subsumption and + // vivification checks, in addition to irredundant clauses. Their + // variables are also marked as being 'added'. + // + bool likely_to_be_kept_clause (Clause *c) { + if (!c->redundant) + return true; + if (c->glue <= tier2[false]) + return true; + if (c->glue > lim.keptglue) + return false; + if (c->size > lim.keptsize) + return false; + return true; + } + + // We mark variables in added or shrunken clauses as 'subsume' candidates + // if the clause is likely to be kept in the next 'reduce' phase (see last + // function above). This gives a persistent (across consecutive + // interleaved search and inprocessing phases) set of variables which have + // to be reconsidered in subsumption checks, i.e., only clauses with + // 'subsume' marked variables are checked to be forward subsumed. + // A similar technique is used to reduce the effort in hyper ternary + // resolution to focus on variables in new ternary clauses. + // + void mark_subsume (int lit) { + Flags &f = flags (lit); + if (f.subsume) + return; + LOG ("marking %d as subsuming literal candidate", abs (lit)); + stats.mark.subsume++; + f.subsume = true; + } + void mark_ternary (int lit) { + Flags &f = flags (lit); + if (f.ternary) + return; + LOG ("marking %d as ternary resolution literal candidate", abs (lit)); + stats.mark.ternary++; + f.ternary = true; + } + void mark_factor (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + if (f.factor & bit) + return; + LOG ("marking %d as factor literal candidate", lit); + stats.mark.factor++; + f.factor |= bit; + } + void mark_added (int lit, int size, bool redundant); + void mark_added (Clause *); + + bool marked_subsume (int lit) const { return flags (lit).subsume; } + + // If irredundant clauses are removed or literals in clauses are removed, + // then variables in such clauses should be reconsidered to be eliminated + // through bounded variable elimination. In contrast to 'subsume' the + // 'elim' flag is restricted to 'irredundant' clauses only. For blocked + // clause elimination it is better to have a more precise signed version, + // which allows to independently mark positive and negative literals. + // + void mark_elim (int lit) { + Flags &f = flags (lit); + if (f.elim) + return; + LOG ("marking %d as elimination literal candidate", lit); + stats.mark.elim++; + f.elim = true; + } + void mark_block (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + if (f.block & bit) + return; + LOG ("marking %d as blocking literal candidate", lit); + stats.mark.block++; + f.block |= bit; + } + void mark_removed (int lit) { + mark_elim (lit); + mark_block (-lit); + } + void mark_removed (Clause *, int except = 0); + + // The following two functions are only used for testing & debugging. + + bool marked_block (int lit) const { + const Flags &f = flags (lit); + const unsigned bit = bign (lit); + return (f.block & bit) != 0; + } + void unmark_block (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + f.block &= ~bit; + } + + // During scheduling literals for blocked clause elimination we skip those + // literals which occur negated in a too large clause. + // + void mark_skip (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + if (f.skip & bit) + return; + LOG ("marking %d to be skipped as blocking literal", lit); + f.skip |= bit; + } + bool marked_skip (int lit) { + const Flags &f = flags (lit); + const unsigned bit = bign (lit); + return (f.skip & bit) != 0; + } + + // During decompose ignore literals where we already built LRAT chains + // + void mark_decomposed (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + CADICAL_assert ((f.marked_signed & bit) == 0); + sign_marked.push_back (lit); + f.marked_signed |= bit; + } + void unmark_decomposed (int lit) { + Flags &f = flags (lit); + const unsigned bit = bign (lit); + f.marked_signed &= ~bit; + } + bool marked_decomposed (int lit) { + const Flags &f = flags (lit); + const unsigned bit = bign (lit); + return (f.marked_signed & bit) != 0; + } + void clear_sign_marked_literals (); + + // Blocked Clause elimination in 'block.cpp'. + // + bool is_blocked_clause (Clause *c, int pivot); + void block_schedule (Blocker &); + size_t block_candidates (Blocker &, int lit); + Clause *block_impossible (Blocker &, int lit); + void block_literal_with_at_least_two_negative_occs (Blocker &, int lit); + void block_literal_with_one_negative_occ (Blocker &, int lit); + void block_pure_literal (Blocker &, int lit); + void block_reschedule_clause (Blocker &, int lit, Clause *); + void block_reschedule (Blocker &, int lit); + void block_literal (Blocker &, int lit); + bool block (); + + // Find gates in 'gates.cpp' for bounded variable substitution. + // + int second_literal_in_binary_clause_lrat (Clause *, int first); + int second_literal_in_binary_clause (Eliminator &, Clause *, int first); + void mark_binary_literals (Eliminator &, int pivot); + void find_and_gate (Eliminator &, int pivot); + void find_equivalence (Eliminator &, int pivot); + + bool get_ternary_clause (Clause *, int &, int &, int &); + bool match_ternary_clause (Clause *, int, int, int); + Clause *find_ternary_clause (int, int, int); + + bool get_clause (Clause *, vector &); + bool is_clause (Clause *, const vector &); + Clause *find_clause (const vector &); + void find_xor_gate (Eliminator &, int pivot); + + void find_if_then_else (Eliminator &, int pivot); + + Clause *find_binary_clause (int, int); + void find_gate_clauses (Eliminator &, int pivot); + void unmark_gate_clauses (Eliminator &); + + // mine definitions for cadical_kitten in 'definition.cpp' + // + void find_definition (Eliminator &, int); + void init_citten (); + void reset_citten (); + void citten_clear_track_log_terminate (); + + // Bounded variable elimination in 'elim.cpp'. + // + bool ineliminating (); + double compute_elim_score (unsigned lit); + void mark_redundant_clauses_with_eliminated_variables_as_garbage (); + void unmark_binary_literals (Eliminator &); + bool resolve_clauses (Eliminator &, Clause *, int pivot, Clause *, bool); + void mark_eliminated_clauses_as_garbage (Eliminator &, int pivot, bool &); + bool elim_resolvents_are_bounded (Eliminator &, int pivot); + void elim_update_removed_lit (Eliminator &, int lit); + void elim_update_removed_clause (Eliminator &, Clause *, int except = 0); + void elim_update_added_clause (Eliminator &, Clause *); + void elim_add_resolvents (Eliminator &, int pivot); + void elim_backward_clause (Eliminator &, Clause *); + void elim_backward_clauses (Eliminator &); + void elim_propagate (Eliminator &, int unit); + void elim_on_the_fly_self_subsumption (Eliminator &, Clause *, int); + void try_to_eliminate_variable (Eliminator &, int pivot, bool &); + void increase_elimination_bound (); + int elim_round (bool &completed, bool &); + void elim (bool update_limits = true); + + int64_t flush_elimfast_occs (int lit); + void elimfast_add_resolvents (Eliminator &, int pivot); + bool elimfast_resolvents_are_bounded (Eliminator &, int pivot); + void try_to_fasteliminate_variable (Eliminator &, int pivot, bool &); + int elimfast_round (bool &completed, bool &); + void elimfast (); + + // sweeping in 'sweep.cpp' + int sweep_solve (); + void sweep_set_cadical_kitten_ticks_limit (Sweeper &sweeper); + bool cadical_kitten_ticks_limit_hit (Sweeper &sweeper, const char *when); + void init_sweeper (Sweeper &sweeper); + void release_sweeper (Sweeper &sweeper); + void clear_sweeper (Sweeper &sweeper); + int sweep_repr (Sweeper &sweeper, int lit); + void add_literal_to_environment (Sweeper &sweeper, unsigned depth, int); + void sweep_clause (Sweeper &sweeper, unsigned depth, Clause *); + void sweep_add_clause (Sweeper &sweeper, unsigned depth); + void add_core (Sweeper &sweeper, unsigned core_idx); + void save_core (Sweeper &sweeper, unsigned core); + void clear_core (Sweeper &sweeper, unsigned core_idx); + void save_add_clear_core (Sweeper &sweeper); + void init_backbone_and_partition (Sweeper &sweeper); + void sweep_empty_clause (Sweeper &sweeper); + void sweep_refine_partition (Sweeper &sweeper); + void sweep_refine_backbone (Sweeper &sweeper); + void sweep_refine (Sweeper &sweeper); + void flip_backbone_literals (struct Sweeper &sweeper); + bool sweep_backbone_candidate (Sweeper &sweeper, int lit); + int64_t add_sweep_binary (sweep_proof_clause, int lit, int other); + bool scheduled_variable (Sweeper &sweeper, int idx); + void schedule_inner (Sweeper &sweeper, int idx); + void schedule_outer (Sweeper &sweeper, int idx); + int next_scheduled (Sweeper &sweeper); + void substitute_connected_clauses (Sweeper &sweeper, int lit, int other, + int64_t id); + void sweep_remove (Sweeper &sweeper, int lit); + void flip_partition_literals (struct Sweeper &sweeper); + const char *sweep_variable (Sweeper &sweeper, int idx); + bool scheduable_variable (Sweeper &sweeper, int idx, size_t *occ_ptr); + unsigned schedule_all_other_not_scheduled_yet (Sweeper &sweeper); + bool sweep_equivalence_candidates (Sweeper &sweeper, int lit, int other); + unsigned reschedule_previously_remaining (Sweeper &sweeper); + unsigned incomplete_variables (); + void mark_incomplete (Sweeper &sweeper); + unsigned schedule_sweeping (Sweeper &sweeper); + void unschedule_sweeping (Sweeper &sweeper, unsigned swept, + unsigned scheduled); + bool sweep (); + void sweep_dense_propagate (Sweeper &sweeper); + void sweep_sparse_mode (); + void sweep_dense_mode_and_watch_irredundant (); + void sweep_substitute_lrat (Clause *c, int64_t id); + void sweep_substitute_new_equivalences (Sweeper &sweeper); + void sweep_update_noccs (Clause *c); + void delete_sweep_binary (const sweep_binary &sb); + bool can_sweep_clause (Clause *c); + bool sweep_flip (int); + int sweep_flip_and_implicant (int); + bool sweep_extract_fixed (Sweeper &sweeper, int lit); + + // factor + void factor_mode (); + void reset_factor_mode (); + double tied_next_factor_score (int); + Quotient *new_quotient (Factoring &, int); + void release_quotients (Factoring &); + size_t first_factor (Factoring &, int); + void clear_nounted (vector &); + void clear_flauses (vector &); + Quotient *best_quotient (Factoring &, size_t *); + int next_factor (Factoring &, unsigned *); + void factorize_next (Factoring &, int, unsigned); + void resize_factoring (Factoring &factoring, int lit); + void flush_unmatched_clauses (Quotient *); + void add_self_subsuming_factor (Quotient *, Quotient *); + bool self_subsuming_factor (Quotient *); + void add_factored_divider (Quotient *, int); + void blocked_clause (Quotient *q, int); + void add_factored_quotient (Quotient *, int not_fresh); + void eagerly_remove_from_occurences (Clause *c); + void delete_unfactored (Quotient *q); + void update_factored (Factoring &factoring, Quotient *q); + bool apply_factoring (Factoring &factoring, Quotient *q); + void update_factor_candidate (Factoring &, int); + void schedule_factorization (Factoring &); + bool run_factorization (int64_t limit); + bool factor (); + int get_new_extension_variable (); + Clause *new_factor_clause (); + + // instantiate + // + void inst_assign (int lit); + bool inst_propagate (); + void collect_instantiation_candidates (Instantiator &); + bool instantiate_candidate (int lit, Clause *); + void instantiate (Instantiator &); + + void new_trail_level (int lit); + + // Hyper ternary resolution. + // + bool ternary_find_binary_clause (int, int); + bool ternary_find_ternary_clause (int, int, int); + Clause *new_hyper_ternary_resolved_clause (bool red); + Clause *new_hyper_ternary_resolved_clause_and_watch (bool red, bool); + bool hyper_ternary_resolve (Clause *, int, Clause *); + void ternary_lit (int pivot, int64_t &steps, int64_t &htrs); + void ternary_idx (int idx, int64_t &steps, int64_t &htrs); + bool ternary_round (int64_t &steps, int64_t &htrs); + bool ternary (); + + // Probing in 'probe.cpp'. + // + bool inprobing (); + void failed_literal (int lit); + void probe_lrat_for_units (int lit); + void probe_assign_unit (int lit); + void probe_assign_decision (int lit); + void probe_assign (int lit, int parent); + void mark_duplicated_binary_clauses_as_garbage (); + int get_parent_reason_literal (int lit); + void set_parent_reason_literal (int lit, int reason); + void clean_probehbr_lrat (); + void init_probehbr_lrat (); + void get_probehbr_lrat (int lit, int uip); + void set_probehbr_lrat (int lit, int uip); + void probe_post_dominator_lrat (vector &, int, int); + void probe_dominator_lrat (int dom, Clause *reason); + int probe_dominator (int a, int b); + int hyper_binary_resolve (Clause *); + void probe_propagate2 (); + bool probe_propagate (); + bool is_binary_clause (Clause *c, int &, int &); + void generate_probes (); + void flush_probes (); + int next_probe (); + bool probe (); + void inprobe (bool update_limits = true); + + // ProbSAT/WalkSAT implementation called initially or from 'rephase'. + // + void walk_save_minimum (Walker &); + Clause *walk_pick_clause (Walker &); + unsigned walk_break_value (int lit); + int walk_pick_lit (Walker &, Clause *); + void walk_flip_lit (Walker &, int lit); + int walk_round (int64_t limit, bool prev); + void walk (); + + // Detect strongly connected components in the binary implication graph + // (BIG) and equivalent literal substitution (ELS) in 'decompose.cpp'. + // + void decompose_conflicting_scc_lrat (DFS *dfs, vector &); + void build_lrat_for_clause (const vector> &dfs_chains, + bool invert = false); + vector decompose_analyze_binary_clauses (DFS *dfs, int from); + void decompose_analyze_binary_chain (DFS *dfs, int); + bool decompose_round (); + void decompose (); + + void reset_limits (); // Reset after 'solve' call. + + // Try flipping a literal while not falsifying a model. + + bool flip (int lit); + bool flippable (int lit); + + // Assumption handling. + // + void assume_analyze_literal (int lit); + void assume_analyze_reason (int lit, Clause *reason); + void assume (int); // New assumption literal. + bool failed (int lit); // Literal failed assumption? + void reset_assumptions (); // Reset after 'solve' call. + void sort_and_reuse_assumptions (); // reorder the assumptions in order to + // reuse parts of the trail + void failing (); // Prepare failed assumptions. + + bool assumed (int lit) { // Marked as assumption. + Flags &f = flags (lit); + const unsigned bit = bign (lit); + return (f.assumed & bit) != 0; + } + + // Add temporary clause as constraint. + // + void constrain (int); // Add literal to constraint. + bool + failed_constraint (); // Was constraint used to proof unsatisfiablity? + void reset_constraint (); // Reset after 'solve' call. + + // Propagate the current set of assumptions and return the + // non-witness assigned literals + int propagate_assumptions (); + void implied (std::vector &entrailed); + + // Forcing decision variables to a certain phase. + // + void phase (int lit); + void unphase (int lit); + + // Globally blocked clause elimination. + // + bool is_autarky_literal (int lit) const; + bool is_conditional_literal (int lit) const; + void mark_as_conditional_literal (int lit); + void unmark_as_conditional_literal (int lit); + // + bool is_in_candidate_clause (int lit) const; + void mark_in_candidate_clause (int lit); + void unmark_in_candidate_clause (int lit); + // + void condition_assign (int lit); + void condition_unassign (int lit); + // + bool conditioning (); + long condition_round (long unassigned_literal_propagation_limit); + void condition (bool update_limits = true); + + // Part on picking the next decision in 'decide.cpp'. + // + bool satisfied (); + int next_decision_variable_on_queue (); + int next_decision_variable_with_best_score (); + int next_decision_variable (); + int decide_phase (int idx, bool target); + int likely_phase (int idx); + bool better_decision (int lit, int other); + int decide (); // 0=decision, 20=failed + + // Internal functions to enable explicit search limits. + // + void limit_terminate (int); + void limit_decisions (int); // Force decision limit. + void limit_conflicts (int); // Force conflict limit. + void limit_preprocessing (int); // Enable 'n' preprocessing rounds. + void limit_local_search (int); // Enable 'n' local search rounds. + + // External versions can access limits by 'name'. + // + static bool is_valid_limit (const char *name); + bool limit (const char *name, int); // 'true' if 'name' valid + + // Set all the CDCL search limits and increments for scheduling + // inprocessing, restarts, clause database reductions, etc. + // + void init_report_limits (); + void init_preprocessing_limits (); + void init_search_limits (); + + // The computed averages are local to the 'stable' and 'unstable' phase. + // Their main use is to be reported in 'report', except for the 'glue' + // averages, which are used to schedule (prohibit actually) restarts + // during 'unstable' phases ('stable' phases use reluctant doubling). + // + void init_averages (); + void swap_averages (); + + int try_to_satisfy_formula_by_saved_phases (); + void produce_failed_assumptions (); + + // Main solve & search functions in 'internal.cpp'. + // + // We have three pre-solving techniques. These consist of preprocessing, + // local search and searching for lucky phases, which in full solving + // mode except for the last are usually optional and then followed by + // the main CDCL search loop with inprocessing. If only preprocessing + // is requested from 'External::simplify' only preprocessing is called + // though. This is all orchestrated by the 'solve' function. + // + int already_solved (); + int restore_clauses (); + bool preprocess_round (int round); + void preprocess_quickly (); + int preprocess (); + int local_search_round (int round); + int local_search (); + int lucky_phases (); + int cdcl_loop_with_inprocessing (); + void reset_solving (); + int solve (bool preprocess_only = false); + void finalize (int); + + // + int lookahead (); + CubesWithStatus generate_cubes (int, int); + int most_occurring_literal (); + int lookahead_probing (); + int lookahead_next_probe (); + void lookahead_flush_probes (); + void lookahead_generate_probes (); + std::vector lookahead_populate_locc (); + int lookahead_locc (const std::vector &); + + bool terminating_asked (); + +#ifndef CADICAL_QUIET + // Built in profiling in 'profile.cpp' (see also 'profile.hpp'). + // + void start_profiling (Profile &p, double); + void stop_profiling (Profile &p, double); + + double update_profiles (); // Returns 'time ()'. + void print_profile (); +#endif + + // Get the value of an internal literal: -1=false, 0=unassigned, 1=true. + // We use a redundant table for both negative and positive literals. This + // allows a branch-less check for the value of literal and is considered + // substantially faster than negating the result if the argument is + // negative. We also avoid taking the absolute value. + // + signed char val (int lit) const { + CADICAL_assert (-max_var <= lit); + CADICAL_assert (lit); + CADICAL_assert (lit <= max_var); + return vals[lit]; + } + + // As suggested by Matt Ginsberg it might be useful to factor-out a common + // setter function for setting and resetting the value of a literal. + // + void set_val (int lit, signed char val) { + CADICAL_assert (-1 <= val); + CADICAL_assert (val <= 1); + CADICAL_assert (-max_var <= lit); + CADICAL_assert (lit); + CADICAL_assert (lit <= max_var); + vals[lit] = val; + vals[-lit] = -val; + } + + // As 'val' but restricted to the root-level value of a literal. + // It is not that time critical and also needs to check the decision level + // of the variable anyhow. + // + int fixed (int lit) { + CADICAL_assert (-max_var <= lit); + CADICAL_assert (lit); + CADICAL_assert (lit <= max_var); + const int idx = vidx (lit); + int res = vals[idx]; + if (res && vtab[idx].level) + res = 0; + if (lit < 0) + res = -res; + return res; + } + + // Map back an internal literal to an external. + // + int externalize (int lit) { + CADICAL_assert (lit != INT_MIN); + const int idx = abs (lit); + CADICAL_assert (idx); + CADICAL_assert (idx <= max_var); + int res = i2e[idx]; + if (lit < 0) + res = -res; + return res; + } + + // Explicit freezing and melting of variables. + // + void freeze (int lit) { + int idx = vidx (lit); + if ((size_t) idx >= frozentab.size ()) { + size_t new_vsize = vsize ? 2 * vsize : 1 + (size_t) max_var; + while (new_vsize <= (size_t) max_var) + new_vsize *= 2; + frozentab.resize (new_vsize); + } + unsigned &ref = frozentab[idx]; + if (ref < UINT_MAX) { + ref++; + LOG ("variable %d frozen %u times", idx, ref); + } else + LOG ("variable %d remains frozen forever", idx); + } + void melt (int lit) { + int idx = vidx (lit); + unsigned &ref = frozentab[idx]; + if (ref < UINT_MAX) { + if (!--ref) { + if (relevanttab[idx]) { + LOG ("variable %d is observed, can not be completely molten", + idx); + ref++; + } else + LOG ("variable %d completely molten", idx); + } else + LOG ("variable %d melted once but remains frozen %u times", lit, + ref); + } else + LOG ("variable %d remains frozen forever", idx); + } + bool frozen (int lit) { + return (size_t) vidx (lit) < frozentab.size () && + frozentab[vidx (lit)] > 0; + } + + // Congruence closure + bool extract_gates (); + + // Parsing functions in 'parse.cpp'. + // + const char *parse_dimacs (FILE *); + const char *parse_dimacs (const char *); + const char *parse_solution (const char *); + + // Enable and disable proof logging and checking. + // + void new_proof_on_demand (); + void force_lrat (); // sets lrat=true + void resize_unit_clauses_idx (); // resizes unit_clauses_idx + void close_trace (bool stats = false); // Stop proof tracing. + void flush_trace (bool stats = false); // Flush proof trace file. + void trace (File *); // Start write proof file. + void check (); // Enable online proof checking. + + void connect_proof_tracer (Tracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (InternalTracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (StatTracer *tracer, bool antecedents, + bool finalize_clauses = false); + void connect_proof_tracer (FileTracer *tracer, bool antecedents, + bool finalize_clauses = false); + bool disconnect_proof_tracer (Tracer *tracer); + bool disconnect_proof_tracer (StatTracer *tracer); + bool disconnect_proof_tracer (FileTracer *tracer); + void conclude_unsat (); + void reset_concluded (); + + // Dump to '' as DIMACS for debugging. + // + void dump (Clause *); + void dump (); + + // Export and traverse all irredundant (non-unit) clauses. + // + bool traverse_clauses (ClauseIterator &); + + // Export and traverse all irredundant (non-unit) clauses. + // + bool traverse_constraint (ClauseIterator &); + + /*----------------------------------------------------------------------*/ + + double solve_time (); // accumulated time spent in 'solve ()' + + double process_time () const; // since solver was initialized + double real_time () const; // since solver was initialized + + double time () { return opts.realtime ? real_time () : process_time (); } + + // Regularly reports what is going on in 'report.cpp'. + // + void report (char type, int verbose_level = 0); + void report_solving (int); + + void print_statistics (); + void print_resource_usage (); + + /*----------------------------------------------------------------------*/ + +#ifndef CADICAL_QUIET + + void print_prefix (); + + // Non-verbose messages and warnings, i.e., always printed unless 'quiet' + // is set, which disables messages at run-time, or even 'CADICAL_QUIET' is defined + // through the configuration option './configure --quiet', which disables + // such messages completely at compile-time. + // + void vmessage (const char *, va_list &); + void message (const char *, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); + void message (); // empty line + + // Verbose messages with explicit verbose 'level' controlled by + // 'opts.verbose' (verbose level '0' gives the same as 'message'). + // + void vverbose (int level, const char *fmt, va_list &); + void verbose (int level, const char *fmt, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + void verbose (int level); + + // This is for printing section headers in the form + // + // c ---- [ ] --------------------- + // + // nicely aligned (and of course is ignored if 'quiet' is set). + // + void section (const char *title); + + // Print verbose message about phases if 'opts.verbose > 1' (but not if + // 'quiet' is set). Note that setting 'log' or '-l' forces all verbose + // output (and also ignores 'quiet' set to true'). The 'phase' argument + // is used to print a 'phase' prefix for the message as follows: + // + // c [<phase>] ... + // + void phase (const char *phase, const char *, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + + // Same as the last 'phase' above except that the prefix gets a count: + // + // c [<phase>-<count>] ... + // + void phase (const char *phase, int64_t count, const char *, ...) + CADICAL_ATTRIBUTE_FORMAT (4, 5); +#endif + + // Print error messages which are really always printed (even if 'quiet' + // is set). This leads to exit the current process with exit status '1'. + // + // TODO add possibility to use a call back instead of calling exit. + // + void error_message_end (); + void verror (const char *, va_list &); + void error (const char *, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); + void error_message_start (); + + // Warning messages. + // + void warning (const char *, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); +}; + +// Fatal internal error which leads to abort. +// +void fatal_message_start (); +void fatal_message_end (); +void fatal (const char *, ...) CADICAL_ATTRIBUTE_FORMAT (1, 2); + +/*------------------------------------------------------------------------*/ + +// Has to be put here, i.e., not into 'score.hpp', since we need the +// definition of 'Internal::score' above (after '#include "score.hpp"'). + +inline bool score_smaller::operator() (unsigned a, unsigned b) { + + // Avoid computing twice 'abs' in 'score ()'. + // + CADICAL_assert (1 <= a); + CADICAL_assert (a <= (unsigned) internal->max_var); + CADICAL_assert (1 <= b); + CADICAL_assert (b <= (unsigned) internal->max_var); + double s = internal->stab[a]; + double t = internal->stab[b]; + + if (s < t) + return true; + if (s > t) + return false; + + return a > b; +} + +/*------------------------------------------------------------------------*/ + +// Implemented here for keeping it all inline (requires Internal::fixed). + +inline int External::fixed (int elit) const { + CADICAL_assert (elit); + CADICAL_assert (elit != INT_MIN); + int eidx = abs (elit); + if (eidx > max_var) + return 0; + int ilit = e2i[eidx]; + if (!ilit) + return 0; + if (elit < 0) + ilit = -ilit; + return internal->fixed (ilit); +} + +/*------------------------------------------------------------------------*/ + +// We want to have termination checks inlined, particularly the first +// function which appears in preprocessor loops. Even though this first +// 'termination_forced' is set asynchronously, this should not lead to a +// data race issue (it also has been declared 'volatile'). + +inline bool Internal::terminated_asynchronously (int factor) { + // First way of asynchronous termination is through 'terminate' which sets + // the 'termination_forced' flag directly. The second way is through a + // call back to a 'terminator' if it is non-zero, which however is costly. + // + if (termination_forced) { + LOG ("termination asynchronously forced"); + return true; + } + + // This is only for testing and debugging asynchronous termination calls. + // In production code this could be removed but then should not be costly + // and keeping it will allow to test correctness of asynchronous + // termination on the production platform too. After this triggers we + // have to set the 'termination_forced' flag, such that subsequent calls + // to this function do not check this again. + // + if (lim.terminate.forced) { + CADICAL_assert (lim.terminate.forced > 0); + if (lim.terminate.forced-- == 1) { + LOG ("internally forcing termination"); + termination_forced = true; + return true; + } + LOG ("decremented internal forced termination limit to %d", + lim.terminate.forced); + } + + // The second way of asynchronous termination is through registering and + // calling an external 'Terminator' object. This is of course more costly + // than just checking a (volatile though) boolean flag, particularly in + // tight loops. To avoid this cost we only call the terminator in + // intervals of 'opts.terminateint', which in addition can be scaled up by + // the argument 'factor'. If the terminator returns 'true' we set the + // 'termination_forced' flag to 'true' in order to remember the + // termination status and to avoid the terminator again. Setting this + // flag leads to the first test above to succeed in subsequent calls. + // + if (external->terminator && !lim.terminate.check--) { + CADICAL_assert (factor > 0); + CADICAL_assert (INT_MAX / factor > opts.terminateint); + lim.terminate.check = factor * opts.terminateint; + if (external->terminator->terminate ()) { + termination_forced = true; // Cache it. + LOG ("connected terminator forces termination"); + return true; + } + } + + return false; +} + +/*------------------------------------------------------------------------*/ + +inline bool Internal::search_limits_hit () { + CADICAL_assert (!preprocessing); + CADICAL_assert (!localsearching); + + if (lim.conflicts >= 0 && stats.conflicts >= lim.conflicts) { + LOG ("conflict limit %" PRId64 " reached", lim.conflicts); + return true; + } + + if (lim.decisions >= 0 && stats.decisions >= lim.decisions) { + LOG ("decision limit %" PRId64 " reached", lim.decisions); + return true; + } + + return false; +} + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/inttypes.hpp b/src/sat/cadical/inttypes.hpp new file mode 100644 index 000000000..2f7cb74a4 --- /dev/null +++ b/src/sat/cadical/inttypes.hpp @@ -0,0 +1,30 @@ +#ifndef _inttypes_h_INCLUDED +#define _inttypes_h_INCLUDED + +#include "global.h" + +// This is an essence a wrapper around '<cinttypes>' respectively +// 'inttypes.h' in order to please the 'MinGW' cross-compiler (we are using +// 'i686-w64-mingw32-gcc') to produce correct 'printf' style formatting for +// 64-bit numbers as this does not work out-of-the-box (which is also very +// annoying). This also produces lots of warnings (through '-Wformat' and +// the corresponding 'attribute' declaration for 'printf' style functions). +// Again 'MinGW' is not fully standard compliant here and we have to cover +// up for that manually. + +// We repeat the code on making this work which is also contained in +// 'cadical.hpp' as we do not want to require users of the library to +// include another header file (like this one) beside 'cadical.hpp'. + +#ifndef PRINTF_FORMAT +#ifdef __MINGW32__ +#define __USE_MINGW_ANSI_STDIO 1 +#define PRINTF_FORMAT __MINGW_PRINTF_FORMAT +#else +#define PRINTF_FORMAT printf +#endif +#endif + +#include <cinttypes> + +#endif diff --git a/src/sat/cadical/ipasir.h b/src/sat/cadical/ipasir.h new file mode 100644 index 000000000..ccb610130 --- /dev/null +++ b/src/sat/cadical/ipasir.h @@ -0,0 +1,35 @@ +#ifndef _ipasir_h_INCLUDED +#define _ipasir_h_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ +ABC_NAMESPACE_HEADER_START +/*------------------------------------------------------------------------*/ + +// Here are the declarations for the actual IPASIR functions, which is the +// generic incremental reentrant SAT solver API used for instance in the SAT +// competition. The other 'C' API in 'ccadical.h' is (more) type safe and +// has additional functions only supported by the CaDiCaL library. Please +// also refer to our SAT Race 2015 article in the Journal of AI from 2016. + +const char *ipasir_signature (void); +void *ipasir_init (void); +void ipasir_release (void *solver); +void ipasir_add (void *solver, int lit); +void ipasir_assume (void *solver, int lit); +int ipasir_solve (void *solver); +int ipasir_val (void *solver, int lit); +int ipasir_failed (void *solver, int lit); + +void ipasir_set_terminate (void *solver, void *state, + int (*terminate) (void *state)); + +void ipasir_set_learn (void *solver, void *state, int max_length, + void (*learn) (void *state, int *clause)); + +/*------------------------------------------------------------------------*/ +ABC_NAMESPACE_HEADER_END +/*------------------------------------------------------------------------*/ + +#endif diff --git a/src/sat/cadical/kitten.h b/src/sat/cadical/kitten.h new file mode 100644 index 000000000..c70d47b0d --- /dev/null +++ b/src/sat/cadical/kitten.h @@ -0,0 +1,95 @@ +#ifndef _cadical_kitten_h_INCLUDED +#define _cadical_kitten_h_INCLUDED + +#include "global.h" + +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> + +ABC_NAMESPACE_HEADER_START + +typedef struct cadical_kitten cadical_kitten; + +cadical_kitten *cadical_kitten_init (void); +void cadical_kitten_clear (cadical_kitten *); +void cadical_kitten_release (cadical_kitten *); + +#ifdef LOGGING +void cadical_kitten_set_logging (cadical_kitten *cadical_kitten); +#endif + +void cadical_kitten_track_antecedents (cadical_kitten *); + +void cadical_kitten_shuffle_clauses (cadical_kitten *); +void cadical_kitten_flip_phases (cadical_kitten *); +void cadical_kitten_randomize_phases (cadical_kitten *); + +void cadical_kitten_assume (cadical_kitten *, unsigned lit); +void cadical_kitten_assume_signed (cadical_kitten *, int lit); + +void cadical_kitten_clause (cadical_kitten *, size_t size, unsigned *); +void citten_clause_with_id (cadical_kitten *, unsigned id, size_t size, int *); +void cadical_kitten_unit (cadical_kitten *, unsigned); +void cadical_kitten_binary (cadical_kitten *, unsigned, unsigned); + +void cadical_kitten_clause_with_id_and_exception (cadical_kitten *, unsigned id, + size_t size, const unsigned *, + unsigned except); + +void citten_clause_with_id_and_exception (cadical_kitten *, unsigned id, + size_t size, const int *, + unsigned except); +void citten_clause_with_id_and_equivalence (cadical_kitten *, unsigned id, + size_t size, const int *, + unsigned, unsigned); +void cadical_kitten_no_ticks_limit (cadical_kitten *); +void cadical_kitten_set_ticks_limit (cadical_kitten *, uint64_t); +uint64_t cadical_kitten_current_ticks (cadical_kitten *); + +void cadical_kitten_no_terminator (cadical_kitten *); +void cadical_kitten_set_terminator (cadical_kitten *, void *, int (*) (void *)); + +int cadical_kitten_solve (cadical_kitten *); +int cadical_kitten_status (cadical_kitten *); + +signed char cadical_kitten_value (cadical_kitten *, unsigned); +signed char cadical_kitten_signed_value (cadical_kitten *, int); // converts second argument +signed char cadical_kitten_fixed (cadical_kitten *, unsigned); +signed char cadical_kitten_fixed_signed (cadical_kitten *, int); // converts +bool cadical_kitten_failed (cadical_kitten *, unsigned); +bool cadical_kitten_flip_literal (cadical_kitten *, unsigned); +bool cadical_kitten_flip_signed_literal (cadical_kitten *, int); + +unsigned cadical_kitten_compute_clausal_core (cadical_kitten *, uint64_t *learned); +void cadical_kitten_shrink_to_clausal_core (cadical_kitten *); + +void cadical_kitten_traverse_core_ids (cadical_kitten *, void *state, + void (*traverse) (void *state, unsigned id)); + +void cadical_kitten_traverse_core_clauses (cadical_kitten *, void *state, + void (*traverse) (void *state, + bool learned, size_t, + const unsigned *)); +void cadical_kitten_traverse_core_clauses_with_id ( + cadical_kitten *, void *state, + void (*traverse) (void *state, unsigned, bool learned, size_t, + const unsigned *)); +void cadical_kitten_trace_core (cadical_kitten *, void *state, + void (*trace) (void *, unsigned, unsigned, bool, + size_t, const unsigned *, size_t, + const unsigned *)); + +int cadical_kitten_compute_prime_implicant (cadical_kitten *cadical_kitten, void *state, + bool (*ignore) (void *, unsigned)); + +void cadical_kitten_add_prime_implicant (cadical_kitten *cadical_kitten, void *state, int side, + void (*add_implicant) (void *, int, size_t, + const unsigned *)); + +int cadical_kitten_flip_and_implicant_for_signed_literal (cadical_kitten *cadical_kitten, int elit); + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/sat/cadical/level.hpp b/src/sat/cadical/level.hpp new file mode 100644 index 000000000..bbfe774ff --- /dev/null +++ b/src/sat/cadical/level.hpp @@ -0,0 +1,39 @@ +#ifndef _level_hpp_INCLUDED +#define _level_hpp_INCLUDED + +#include "global.h" + +#include <climits> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// For each new decision we increase the decision level and push a 'Level' +// on the 'control' stack. The information gathered here is used in +// 'reuse_trail' and for early aborts in clause minimization. + +struct Level { + + int decision; // decision literal of this level + int trail; // trail start of this level + + struct { + int count; // how many variables seen during 'analyze' + int trail; // smallest trail position seen on this level + } seen; + + void reset () { + seen.count = 0; + seen.trail = INT_MAX; + } + + Level (int d, int t) : decision (d), trail (t) { reset (); } + Level () {} +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/lidruptracer.hpp b/src/sat/cadical/lidruptracer.hpp new file mode 100644 index 000000000..02cd33fcd --- /dev/null +++ b/src/sat/cadical/lidruptracer.hpp @@ -0,0 +1,123 @@ +#ifndef _lidruptracer_h_INCLUDED +#define _lidruptracer_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +class FileTracer; + +namespace CaDiCaL { + +struct LidrupClause { + LidrupClause *next; // collision chain link for hash table + uint64_t hash; // previously computed full 64-bit hash + int64_t id; // id of clause + std::vector<int64_t> chain; + std::vector<int> literals; +}; + +class LidrupTracer : public FileTracer { + + Internal *internal; + File *file; + bool binary; + bool piping; // The 'file' is a pipe and needs eagerly flushing. + + // hash table for conclusion + // + uint64_t num_clauses; // number of clauses in hash table + uint64_t size_clauses; // size of clause hash table + LidrupClause **clauses; // hash table of clauses + vector<int> imported_clause; + vector<int> assumptions; + vector<int64_t> imported_chain; + vector<int64_t> batch_weaken; + vector<int64_t> batch_delete; + vector<int64_t> batch_restore; + + static const unsigned num_nonces = 4; + + uint64_t nonces[num_nonces]; // random numbers for hashing + uint64_t last_hash; // last computed hash value of clause + int64_t last_id; // id of the last added clause + LidrupClause *last_clause; + uint64_t compute_hash (int64_t); // compute and save hash value of clause + + LidrupClause *new_clause (); + void delete_clause (LidrupClause *); + + static uint64_t reduce_hash (uint64_t hash, uint64_t size); + + void enlarge_clauses (); // enlarge hash table for clauses + void insert (); // insert clause in hash table + bool + find_and_delete (const int64_t); // find clause position in hash table + +#ifndef CADICAL_QUIET + int64_t added, deleted, weakened, restore, original, solved, batched; +#endif + + void flush_if_piping (); + + void put_binary_zero (); + void put_binary_lit (int external_lit); + void put_binary_id (int64_t id, bool = true); + + void lidrup_add_derived_clause (int64_t id, const vector<int> &clause, + const vector<int64_t> &chain); + void lidrup_delete_clause (int64_t id); //, const vector<int> &clause); + void + lidrup_add_restored_clause (int64_t id); //, const vector<int> &clause); + void lidrup_add_original_clause (int64_t id, const vector<int> &clause); + void lidrup_conclude_and_delete (const vector<int64_t> &conclusion); + void lidrup_report_status (int status); + void lidrup_conclude_sat (const vector<int> &model); + void lidrup_conclude_unknown (const vector<int> &trail); + void lidrup_solve_query (); + void lidrup_batch_weaken_restore_and_delete (); + +public: + LidrupTracer (Internal *, File *file, bool); + ~LidrupTracer (); + + // proof section: + void add_derived_clause (int64_t, bool, const vector<int> &, + const vector<int64_t> &) override; + void add_assumption_clause (int64_t, const vector<int> &, + const vector<int64_t> &) override; + void weaken_minus (int64_t, const vector<int> &) override; + void delete_clause (int64_t, bool, const vector<int> &) override; + void add_original_clause (int64_t, bool, const vector<int> &, + bool = false) override; + void report_status (int, int64_t) override; + void conclude_sat (const vector<int> &) override; + void conclude_unsat (ConclusionType, const vector<int64_t> &) override; + void conclude_unknown (const vector<int> &) override; + + void solve_query () override; + void add_assumption (int) override; + void reset_assumptions () override; + + // skip + void begin_proof (int64_t) override {} + void finalize_clause (int64_t, const vector<int> &) override {} + void strengthen (int64_t) override {} + void add_constraint (const vector<int> &) override {} + + // logging and file io + void connect_internal (Internal *i) override; + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/limit.hpp b/src/sat/cadical/limit.hpp new file mode 100644 index 000000000..0f6923214 --- /dev/null +++ b/src/sat/cadical/limit.hpp @@ -0,0 +1,162 @@ +#ifndef _limit_hpp_INCLUDED +#define _limit_hpp_INCLUDED + +#include "global.h" + +#include <cstdint> +#include <limits> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct Limit { + + bool initialized; + + int64_t conflicts; // conflict limit if non-negative + int64_t decisions; // decision limit if non-negative + int64_t preprocessing; // limit on preprocessing rounds + int64_t localsearch; // limit on local search rounds + + int64_t compact; // conflict limit for next 'compact' + int64_t condition; // conflict limit for next 'condition' + int64_t elim; // conflict limit for next 'elim' + int64_t flush; // conflict limit for next 'flush' + int64_t inprobe; // conflict limit for next 'inprobe' + int64_t reduce; // conflict limit for next 'reduce' + int64_t rephase; // conflict limit for next 'rephase' + int64_t report; // report limit for header + int64_t restart; // conflict limit for next 'restart' + int64_t stabilize; // conflict/ticks limit for next 'stabilize' + + int keptsize; // maximum kept size in 'reduce' + int keptglue; // maximum kept glue in 'reduce' + int64_t recompute_tier; // conflict limit for next tier recomputation + + // How often rephased during (1) or out (0) of stabilization. + // + int64_t rephased[2]; + + // Current elimination bound per eliminated variable. + // + int64_t elimbound; + + struct { + int check; // countdown to next terminator call + int forced; // forced termination for testing + } terminate; + + Limit (); +}; + +struct Delay { + struct { + int64_t interval = 0, limit = 0; + bool bypass = 0; + + bool delay () { + if (bypass) + return true; + if (limit) { + --limit; + return true; + } else { + return false; + } + } + + void bump_delay () { + interval += interval < INT64_MAX; + limit = interval; + } + + void reduce_delay () { + if (!interval) + return; + interval /= 2; + limit = interval; + } + + void bypass_delay () { bypass = 1; } + void unbypass_delay () { bypass = 0; } + } bumpreasons; +}; + +struct Last { + struct { + int64_t propagations; + } transred; + struct { + int64_t ticks; + } sweep, vivify, probe; + struct { + int64_t fixed, subsumephases, marked; + } elim; + struct { + int64_t reductions; + } inprobe; + struct { + int64_t conflicts; + } reduce, rephase; + struct { + int64_t ticks; + int64_t marked; + } ternary; + struct { + int64_t fixed; + } collect; + struct { + int64_t marked, ticks; + } factor; + struct { + int64_t conflicts; + int64_t ticks; + } stabilize; + Last (); +}; + +struct Inc { + int64_t flush; // flushing interval in terms of conflicts + int64_t stabilize; // base ticks limit after first mode switch + int64_t conflicts; // next conflict limit if non-negative + int64_t decisions; // next decision limit if non-negative + int64_t preprocessing; // next preprocessing limit if non-negative + int64_t localsearch; // next local search limit if non-negative + Inc (); +}; + +#define SET_EFFORT_LIMIT(LIMIT, NAME, THRESHHOLD) \ + int64_t LIMIT; \ + do { \ + const int64_t OLD_LIMIT = stats.ticks.NAME; \ + const int64_t TICKS = stats.ticks.search[0] + stats.ticks.search[1]; \ + const int64_t LAST = last.NAME.ticks; \ + int64_t REFERENCE = TICKS - LAST; \ + if (!REFERENCE || !stats.conflicts) { \ + VERBOSE (2, "last %" PRId64 " current %" PRId64 " delta %" PRId64, \ + LAST, TICKS, REFERENCE); \ + REFERENCE = opts.preprocessinit; \ + } \ + const double EFFORT = (double) opts.NAME##effort * 1e-3; \ + const int64_t DELTA = EFFORT * REFERENCE; \ + const int64_t THRESH = opts.NAME##thresh * clauses.size (); \ + if (THRESHHOLD && DELTA < THRESH) { \ + VERBOSE (2, \ + "delaying %s with ticklimit %" PRId64 \ + " and threshhold %" PRId64, \ + #NAME, DELTA, THRESH); \ + return false; \ + } \ + last.NAME.ticks = TICKS; \ + const int64_t NEW_LIMIT = OLD_LIMIT + DELTA; \ + LIMIT = NEW_LIMIT; \ + } while (0) + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/logging.hpp b/src/sat/cadical/logging.hpp new file mode 100644 index 000000000..edbd60f8c --- /dev/null +++ b/src/sat/cadical/logging.hpp @@ -0,0 +1,104 @@ +#ifndef _logging_hpp_INCLUDED +#define _logging_hpp_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ +#ifdef LOGGING +/*------------------------------------------------------------------------*/ + +#include <cstdint> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// For debugging purposes and to help understanding what the solver is doing +// there is a logging facility which is compiled in by './configure -l'. It +// still has to be enabled at run-time though (again using the '-l' option +// in the stand-alone solver). It produces quite a bit of information. + +using namespace std; + +struct Clause; +struct Gate; +struct Internal; + +struct Logger { + + static void print_log_prefix (Internal *); + + // Simple logging of a C-style format string. + // + static void log (Internal *, const char *fmt, ...) + CADICAL_ATTRIBUTE_FORMAT (2, 3); + + // Prints the format string (with its argument) and then the clause. The + // clause can also be a zero pointer and then is interpreted as a decision + // (current decision level > 0) or unit clause (zero decision level) and + // printed accordingly. + // + static void log (Internal *, const Clause *, const char *fmt, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + + // Same as before, except that this is meant for the global 'clause' stack + // used for new clauses (and not for reasons). + // + static void log (Internal *, const vector<int> &, const char *fmt, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + + // Another variant, to avoid copying (without logging). + // + static void log (Internal *, const vector<int>::const_iterator &begin, + const vector<int>::const_iterator &end, const char *fmt, + ...) CADICAL_ATTRIBUTE_FORMAT (4, 5); + + // used for logging LRAT proof chains + // + static void log (Internal *, const vector<int64_t> &, const char *fmt, + ...) CADICAL_ATTRIBUTE_FORMAT (3, 4); + + static void log (Internal *, const int *, const unsigned, const char *fmt, + ...) CADICAL_ATTRIBUTE_FORMAT (4, 5); + + static void log_empty_line (Internal *); + + static void log (Internal *, const Gate *, const char *fmt, ...) + CADICAL_ATTRIBUTE_FORMAT (3, 4); + + static string loglit (Internal *, int lit); +}; + +} // namespace CaDiCaL + +/*------------------------------------------------------------------------*/ + +// Make sure that 'logging' code is really not included (second case of the +// '#ifdef') if logging code is not included. + +#define LOG(...) \ + do { \ + if (!internal->opts.log) \ + break; \ + Logger::log (internal, __VA_ARGS__); \ + } while (0) + +#define LOGLIT(lit) Logger::loglit (internal, lit).c_str () + +ABC_NAMESPACE_CXX_HEADER_END + +/*------------------------------------------------------------------------*/ +#else // end of 'then' part of 'ifdef LOGGING' +/*------------------------------------------------------------------------*/ + +#define LOG(...) \ + do { \ + } while (0) + +#define LOGLIT(...) + +/*------------------------------------------------------------------------*/ +#endif // end of 'else' part of 'ifdef LOGGING' +/*------------------------------------------------------------------------*/ +#endif diff --git a/src/sat/cadical/lratchecker.hpp b/src/sat/cadical/lratchecker.hpp new file mode 100644 index 000000000..0e791e9f4 --- /dev/null +++ b/src/sat/cadical/lratchecker.hpp @@ -0,0 +1,170 @@ +#ifndef _lratchecker_hpp_INCLUDED +#define _lratchecker_hpp_INCLUDED + +#include "global.h" + +/*------------------------------------------------------------------------*/ +#include <unordered_map> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +// This checker implements an LRUP checker. +// It requires LRAT-style proof chains for each learned clause +// +// Most of the infrastructure is taken from checker, but without the +// propagation + +/*------------------------------------------------------------------------*/ + +struct LratCheckerClause { + LratCheckerClause *next; // collision chain link for hash table + uint64_t hash; // previously computed full 64-bit hash + int64_t id; // id of clause + bool garbage; // for garbage clauses + unsigned size; + bool used; + bool tautological; + int literals[1]; // 'literals' of length 'size' +}; + +/*------------------------------------------------------------------------*/ + +class LratChecker : public StatTracer { + + Internal *internal; + + // Capacity of variable values. + // + int64_t size_vars; + + // The 'watchers' and 'marks' data structures are not that time critical + // and thus we access them by first mapping a literal to 'unsigned'. + // + static unsigned l2u (int lit); + + signed char &checked_lit (int lit); + signed char &mark (int lit); + + vector<signed char> checked_lits; + vector<signed char> marks; // mark bits of literals + unordered_map<int64_t, vector<int>> clauses_to_reconstruct; + vector<int> assumptions; + vector<int> constraint; + bool concluded; + + uint64_t num_clauses; // number of clauses in hash table + uint64_t num_finalized; + uint64_t num_garbage; // number of garbage clauses + uint64_t size_clauses; // size of clause hash table + LratCheckerClause **clauses; // hash table of clauses + LratCheckerClause *garbage; // linked list of garbage clauses + + vector<int> imported_clause; // original clause for reporting + vector<int64_t> assumption_clauses; + + void enlarge_vars (int64_t idx); + void import_literal (int lit); + void import_clause (const vector<int> &); + + static const unsigned num_nonces = 4; + + uint64_t nonces[num_nonces]; // random numbers for hashing + uint64_t last_hash; // last computed hash value of clause + int64_t last_id; // id of the last added/deleted clause + int64_t current_id; // id of the last added clause + uint64_t compute_hash (int64_t); // compute and save hash value of clause + + // Reduce hash value to the actual size. + // + static uint64_t reduce_hash (uint64_t hash, uint64_t size); + + void enlarge_clauses (); // enlarge hash table for clauses + void insert (); // insert clause in hash table + LratCheckerClause ** + find (const int64_t); // find clause position in hash table + + void add_clause (const char *type); + + void collect_garbage_clauses (); + + LratCheckerClause *new_clause (); + void delete_clause (LratCheckerClause *); + + bool check (vector<int64_t>); // check RUP + bool check_resolution (vector<int64_t>); // check resolution + bool check_blocked (vector<int64_t>); // check ER + + struct { + + int64_t added; // number of added clauses + int64_t original; // number of added original clauses + int64_t derived; // number of added derived clauses + + int64_t deleted; // number of deleted clauses + int64_t finalized; // number of finalized clauses + + int64_t insertions; // number of clauses added to hash table + int64_t collisions; // number of hash collisions in 'find' + int64_t searches; // number of searched clauses in 'find' + + int64_t checks; // number of implication checks + + int64_t collections; // garbage collections + + } stats; + +public: + LratChecker (Internal *); + virtual ~LratChecker (); + + void connect_internal (Internal *i) override; + void begin_proof (int64_t) override; + + void add_original_clause (int64_t, bool, const vector<int> &, + bool restore) override; + void restore_clause (int64_t, const vector<int> &); + + // check the proof chain for the new clause and add it to the checker + void add_derived_clause (int64_t, bool, const vector<int> &, + const vector<int64_t> &) override; + + // check if the clause is present and delete it from the checker + void delete_clause (int64_t, bool, const vector<int> &) override; + // check if the clause is present and delete it from the checker + void weaken_minus (int64_t, const vector<int> &) override; + + // check if the clause is present and delete it from the checker + void finalize_clause (int64_t, const vector<int> &) override; + + // check the proof chain of the assumption clause and delete it + // immediately also check that they contain only assumptions and + // constraints + void add_assumption_clause (int64_t, const vector<int> &, + const vector<int64_t> &) override; + + // mark lit as assumption + void add_assumption (int) override; + + // mark lits as constraint + void add_constraint (const vector<int> &) override; + + void reset_assumptions () override; + + // check if all clauses have been deleted + void report_status (int, int64_t) override; + + void conclude_unsat (ConclusionType, const vector<int64_t> &) override; + + void print_stats () override; + void dump (); // for debugging purposes only +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/lrattracer.hpp b/src/sat/cadical/lrattracer.hpp new file mode 100644 index 000000000..9d8e92b6a --- /dev/null +++ b/src/sat/cadical/lrattracer.hpp @@ -0,0 +1,63 @@ +#ifndef _lrattracer_h_INCLUDED +#define _lrattracer_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class LratTracer : public FileTracer { + + Internal *internal; + File *file; + bool binary; + +#ifndef CADICAL_QUIET + int64_t added, deleted; +#endif + int64_t latest_id; + vector<int64_t> delete_ids; + + void put_binary_zero (); + void put_binary_lit (int external_lit); + void put_binary_id (int64_t id); + + // support LRAT + void lrat_add_clause (int64_t, const vector<int> &, + const vector<int64_t> &); + void lrat_delete_clause (int64_t); + +public: + // own and delete 'file' + LratTracer (Internal *, File *file, bool binary); + ~LratTracer (); + + void connect_internal (Internal *i) override; + void begin_proof (int64_t) override; + + void add_original_clause (int64_t, bool, const vector<int> &, + bool = false) override {} // skip + + void add_derived_clause (int64_t, bool, const vector<int> &, + const vector<int64_t> &) override; + + void delete_clause (int64_t, bool, const vector<int> &) override; + + void finalize_clause (int64_t, const vector<int> &) override {} // skip + + void report_status (int, int64_t) override {} // skip + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/message.hpp b/src/sat/cadical/message.hpp new file mode 100644 index 000000000..e0209fed8 --- /dev/null +++ b/src/sat/cadical/message.hpp @@ -0,0 +1,71 @@ +#ifndef _message_h_INCLUDED +#define _message_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +/*------------------------------------------------------------------------*/ + +// Macros for compact message code. + +#ifndef CADICAL_QUIET + +#define LINE() \ + do { \ + if (internal) \ + internal->message (); \ + } while (0) + +#define MSG(...) \ + do { \ + if (internal) \ + internal->message (__VA_ARGS__); \ + } while (0) + +#define PHASE(...) \ + do { \ + if (internal) \ + internal->phase (__VA_ARGS__); \ + } while (0) + +#define SECTION(...) \ + do { \ + if (internal) \ + internal->section (__VA_ARGS__); \ + } while (0) + +#define VERBOSE(...) \ + do { \ + if (internal) \ + internal->verbose (__VA_ARGS__); \ + } while (0) + +#else + +#define LINE() \ + do { \ + } while (0) +#define MSG(...) \ + do { \ + } while (0) +#define PHASE(...) \ + do { \ + } while (0) +#define SECTION(...) \ + do { \ + } while (0) +#define VERBOSE(...) \ + do { \ + } while (0) + +#endif + +#define FATAL fatal +#define WARNING(...) internal->warning (__VA_ARGS__) + +/*------------------------------------------------------------------------*/ + +ABC_NAMESPACE_CXX_HEADER_END + +#endif // ifndef _message_h_INCLUDED diff --git a/src/sat/cadical/module.make b/src/sat/cadical/module.make new file mode 100644 index 000000000..74f5d23cb --- /dev/null +++ b/src/sat/cadical/module.make @@ -0,0 +1,91 @@ +SRC +=src/sat/cadical/cadicalSolver.c \ +src/sat/cadical/cadicalTest.c \ +src/sat/cadical/cadical_analyze.cpp \ +src/sat/cadical/cadical_arena.cpp \ +src/sat/cadical/cadical_assume.cpp \ +src/sat/cadical/cadical_averages.cpp \ +src/sat/cadical/cadical_backtrack.cpp \ +src/sat/cadical/cadical_backward.cpp \ +src/sat/cadical/cadical_bins.cpp \ +src/sat/cadical/cadical_block.cpp \ +src/sat/cadical/cadical_ccadical.cpp \ +src/sat/cadical/cadical_checker.cpp \ +src/sat/cadical/cadical_clause.cpp \ +src/sat/cadical/cadical_collect.cpp \ +src/sat/cadical/cadical_compact.cpp \ +src/sat/cadical/cadical_condition.cpp \ +src/sat/cadical/cadical_config.cpp \ +src/sat/cadical/cadical_congruence.cpp \ +src/sat/cadical/cadical_constrain.cpp \ +src/sat/cadical/cadical_contract.cpp \ +src/sat/cadical/cadical_cover.cpp \ +src/sat/cadical/cadical_decide.cpp \ +src/sat/cadical/cadical_decompose.cpp \ +src/sat/cadical/cadical_deduplicate.cpp \ +src/sat/cadical/cadical_definition.cpp \ +src/sat/cadical/cadical_drattracer.cpp \ +src/sat/cadical/cadical_elim.cpp \ +src/sat/cadical/cadical_elimfast.cpp \ +src/sat/cadical/cadical_ema.cpp \ +src/sat/cadical/cadical_extend.cpp \ +src/sat/cadical/cadical_external.cpp \ +src/sat/cadical/cadical_external_propagate.cpp \ +src/sat/cadical/cadical_factor.cpp \ +src/sat/cadical/cadical_file.cpp \ +src/sat/cadical/cadical_flags.cpp \ +src/sat/cadical/cadical_flip.cpp \ +src/sat/cadical/cadical_format.cpp \ +src/sat/cadical/cadical_frattracer.cpp \ +src/sat/cadical/cadical_gates.cpp \ +src/sat/cadical/cadical_idruptracer.cpp \ +src/sat/cadical/cadical_instantiate.cpp \ +src/sat/cadical/cadical_internal.cpp \ +src/sat/cadical/cadical_ipasir.cpp \ +src/sat/cadical/cadical_lidruptracer.cpp \ +src/sat/cadical/cadical_limit.cpp \ +src/sat/cadical/cadical_logging.cpp \ +src/sat/cadical/cadical_lookahead.cpp \ +src/sat/cadical/cadical_lratchecker.cpp \ +src/sat/cadical/cadical_lrattracer.cpp \ +src/sat/cadical/cadical_lucky.cpp \ +src/sat/cadical/cadical_message.cpp \ +src/sat/cadical/cadical_minimize.cpp \ +src/sat/cadical/cadical_occs.cpp \ +src/sat/cadical/cadical_options.cpp \ +src/sat/cadical/cadical_parse.cpp \ +src/sat/cadical/cadical_phases.cpp \ +src/sat/cadical/cadical_probe.cpp \ +src/sat/cadical/cadical_profile.cpp \ +src/sat/cadical/cadical_proof.cpp \ +src/sat/cadical/cadical_propagate.cpp \ +src/sat/cadical/cadical_queue.cpp \ +src/sat/cadical/cadical_random.cpp \ +src/sat/cadical/cadical_reap.cpp \ +src/sat/cadical/cadical_reduce.cpp \ +src/sat/cadical/cadical_rephase.cpp \ +src/sat/cadical/cadical_report.cpp \ +src/sat/cadical/cadical_resources.cpp \ +src/sat/cadical/cadical_restart.cpp \ +src/sat/cadical/cadical_restore.cpp \ +src/sat/cadical/cadical_score.cpp \ +src/sat/cadical/cadical_shrink.cpp \ +src/sat/cadical/cadical_signal.cpp \ +src/sat/cadical/cadical_solution.cpp \ +src/sat/cadical/cadical_solver.cpp \ +src/sat/cadical/cadical_stable.cpp \ +src/sat/cadical/cadical_stats.cpp \ +src/sat/cadical/cadical_subsume.cpp \ +src/sat/cadical/cadical_sweep.cpp \ +src/sat/cadical/cadical_terminal.cpp \ +src/sat/cadical/cadical_ternary.cpp \ +src/sat/cadical/cadical_tier.cpp \ +src/sat/cadical/cadical_transred.cpp \ +src/sat/cadical/cadical_unstable.cpp \ +src/sat/cadical/cadical_util.cpp \ +src/sat/cadical/cadical_var.cpp \ +src/sat/cadical/cadical_veripbtracer.cpp \ +src/sat/cadical/cadical_version.cpp \ +src/sat/cadical/cadical_vivify.cpp \ +src/sat/cadical/cadical_walk.cpp \ +src/sat/cadical/cadical_watch.cpp \ +src/sat/cadical/cadical_kitten.c diff --git a/src/sat/cadical/occs.hpp b/src/sat/cadical/occs.hpp new file mode 100644 index 000000000..28a9246bc --- /dev/null +++ b/src/sat/cadical/occs.hpp @@ -0,0 +1,42 @@ +#ifndef _occs_h_INCLUDED +#define _occs_h_INCLUDED + +#include "global.h" + +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Full occurrence lists used in a one-watch scheme for all clauses in +// subsumption checking and for irredundant clauses in variable elimination. + +struct Clause; +using namespace std; + +typedef vector<Clause *> Occs; + +inline void shrink_occs (Occs &os) { shrink_vector (os); } +inline void erase_occs (Occs &os) { erase_vector (os); } + +inline void remove_occs (Occs &os, Clause *c) { + const auto end = os.end (); + auto i = os.begin (); + for (auto j = i; j != end; j++) { + const Clause *d = *i++ = *j; + if (c == d) + i--; + } + CADICAL_assert (i + 1 == end); + os.resize (i - os.begin ()); +} + +typedef Occs::iterator occs_iterator; +typedef Occs::const_iterator const_occs_iterator; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/options.hpp b/src/sat/cadical/options.hpp new file mode 100644 index 000000000..1527da2e1 --- /dev/null +++ b/src/sat/cadical/options.hpp @@ -0,0 +1,422 @@ +#ifndef _options_hpp_INCLUDED +#define _options_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +/*------------------------------------------------------------------------*/ + +// In order to add a new option, simply add a new line below. Make sure that +// options are sorted correctly (with '!}sort -k 2' in 'vi'). Otherwise +// initializing the options will trigger an internal error. For the model +// based tester 'mobical' the policy is that options which become redundant +// because another one is disabled (set to zero) should have the name of the +// latter as prefix. The 'O' column determines the options which are +// target to 'optimize' them ('-O[1-3]'). A zero value in the 'O' column +// means that this option is not optimized. A value of '1' results in +// optimizing its value exponentially with exponent base '2', and a value +// of '2' uses base '10'. The 'P' column determines simplification +// options (disabled with '--plain') and 'R' which values can be reset. + +// clang-format off + +#define OPTIONS \ +\ +/* NAME DEFAULT, LO, HI,O,P,R, USAGE */ \ +\ +OPTION( arena, 1, 0, 1,0,0,1, "allocate clauses in arena") \ +OPTION( arenacompact, 1, 0, 1,0,0,1, "keep clauses compact") \ +OPTION( arenasort, 1, 0, 1,0,0,1, "sort clauses in arena") \ +OPTION( arenatype, 3, 1, 3,0,0,1, "1=clause, 2=var, 3=queue") \ +OPTION( binary, 1, 0, 1,0,0,1, "use binary proof format") \ +OPTION( block, 0, 0, 1,0,1,1, "blocked clause elimination") \ +OPTION( blockmaxclslim, 1e5, 1,2e9,2,0,1, "maximum clause size") \ +OPTION( blockminclslim, 2, 2,2e9,0,0,1, "minimum clause size") \ +OPTION( blockocclim, 1e2, 1,2e9,2,0,1, "occurrence limit") \ +OPTION( bump, 1, 0, 1,0,0,1, "bump variables") \ +OPTION( bumpreason, 1, 0, 1,0,0,1, "bump reason literals too") \ +OPTION( bumpreasondepth, 1, 1, 3,0,0,1, "bump reason depth") \ +OPTION( bumpreasonlimit, 10, 1,2e9,0,0,1, "bump reason limit") \ +OPTION( bumpreasonrate, 100, 1,2e9,0,0,1, "bump reason decision rate") \ +OPTION( check, 0, 0, 1,0,0,0, "enable internal checking") \ +OPTION( checkassumptions, 1, 0, 1,0,0,0, "check assumptions satisfied") \ +OPTION( checkconstraint, 1, 0, 1,0,0,0, "check constraint satisfied") \ +OPTION( checkfailed, 1, 0, 1,0,0,0, "check failed literals form core") \ +OPTION( checkfrozen, 0, 0, 1,0,0,0, "check all frozen semantics") \ +OPTION( checkproof, 3, 0, 3,0,0,0, "1=drat, 2=lrat, 3=both") \ +OPTION( checkwitness, 1, 0, 1,0,0,0, "check witness internally") \ +OPTION( chrono, 1, 0, 2,0,0,1, "chronological backtracking") \ +OPTION( chronoalways, 0, 0, 1,0,0,1, "force always chronological") \ +OPTION( chronolevelim, 1e2, 0,2e9,0,0,1, "chronological level limit") \ +OPTION( chronoreusetrail, 1, 0, 1,0,0,1, "reuse trail chronologically") \ +OPTION( compact, 1, 0, 1,0,1,1, "compact internal variables") \ +OPTION( compactint, 2e3, 1,2e9,0,0,1, "compacting interval") \ +OPTION( compactlim, 1e2, 0,1e3,0,0,1, "inactive limit per mille") \ +OPTION( compactmin, 1e2, 1,2e9,0,0,1, "minimum inactive limit") \ +OPTION( condition, 0, 0, 1,0,1,1, "globally blocked clause elim") \ +OPTION( conditioneffort, 100, 1,1e5,0,0,1, "relative efficiency per mille") \ +OPTION( conditionint, 1e4, 1,2e9,0,0,1, "initial conflict interval") \ +OPTION( conditionmaxeff, 1e7, 0,2e9,1,0,1, "maximum condition efficiency") \ +OPTION( conditionmaxrat, 100, 1,2e9,1,0,1, "maximum clause variable ratio") \ +OPTION( conditionmineff, 0, 0,2e9,1,0,1, "minimum condition efficiency") \ +OPTION( congruence, 1, 0, 1,0,0,1, "congruence closure") \ +OPTION( congruenceand, 1, 0, 1,0,0,1, "extract AND gates") \ +OPTION( congruenceandarity,1e6,2,5e7,0,0,1, "AND gate arity limit") \ +OPTION( congruencebinaries,1, 0, 1,0,0,1, "extract binary and strengthen ternary clauses") \ +OPTION( congruenceite, 1, 0, 1,0,0,1, "extract ITE gates") \ +OPTION( congruencexor, 1, 0, 1,0,0,1, "extract XOR gates") \ +OPTION( congruencexorarity,4, 2, 31,0,0,1, "XOR gate arity limit") \ +OPTION( congruencexorcounts,1, 1,5e6,0,0,1, "XOR gate round") \ +OPTION( cover, 0, 0, 1,0,1,1, "covered clause elimination") \ +OPTION( covereffort, 4, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( covermaxclslim, 1e5, 1,2e9,2,0,1, "maximum clause size") \ +OPTION( covermaxeff, 1e8, 0,2e9,1,0,1, "maximum cover efficiency") \ +OPTION( coverminclslim, 2, 2,2e9,0,0,1, "minimum clause size") \ +OPTION( covermineff, 0, 0,2e9,1,0,1, "minimum cover efficiency") \ +OPTION( decompose, 1, 0, 1,0,1,1, "decompose BIG in SCCs and ELS") \ +OPTION( decomposerounds, 2, 1, 16,1,0,1, "number of decompose rounds") \ +OPTION( deduplicate, 1, 0, 1,0,1,1, "remove duplicated binaries") \ +OPTION( eagersubsume, 1, 0, 1,0,1,1, "subsume recently learned") \ +OPTION( eagersubsumelim, 20, 1,1e3,0,0,1, "limit on subsumed candidates") \ +OPTION( elim, 1, 0, 1,0,1,1, "bounded variable elimination") \ +OPTION( elimands, 1, 0, 1,0,0,1, "find AND gates") \ +OPTION( elimbackward, 1, 0, 1,0,0,1, "eager backward subsumption") \ +OPTION( elimboundmax, 16, -1,2e6,1,0,1, "maximum elimination bound") \ +OPTION( elimboundmin, 0, -1,2e6,0,0,1, "minimum elimination bound") \ +OPTION( elimclslim, 1e2, 2,2e9,2,0,1, "resolvent size limit") \ +OPTION( elimdef, 0, 0, 1,0,0,1, "mine definitions with cadical_kitten") \ +OPTION( elimdefcores, 1, 1,100,0,0,1, "number of unsat cores") \ +OPTION( elimdefticks, 2e5, 0,2e9,1,0,1, "cadical_kitten ticks limit") \ +OPTION( elimeffort, 1e3, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( elimequivs, 1, 0, 1,0,0,1, "find equivalence gates") \ +OPTION( elimint, 2e3, 1,2e9,0,0,1, "elimination interval") \ +OPTION( elimites, 1, 0, 1,0,0,1, "find if-then-else gates") \ +OPTION( elimlimited, 1, 0, 1,0,0,1, "limit resolutions") \ +OPTION( elimmaxeff, 2e9, 0,2e9,1,0,1, "maximum elimination efficiency") \ +OPTION( elimmineff, 1e7, 0,2e9,1,0,1, "minimum elimination efficiency") \ +OPTION( elimocclim, 1e2, 0,2e9,2,0,1, "occurrence limit") \ +OPTION( elimprod, 1, 0,1e4,0,0,1, "elim score product weight") \ +OPTION( elimrounds, 2, 1,512,1,0,1, "usual number of rounds") \ +OPTION( elimsubst, 1, 0, 1,0,0,1, "elimination by substitution") \ +OPTION( elimsum, 1, 0,1e4,0,0,1, "elimination score sum weight") \ +OPTION( elimxorlim, 5, 2, 27,1,0,1, "maximum XOR size") \ +OPTION( elimxors, 1, 0, 1,0,0,1, "find XOR gates") \ +OPTION( emadecisions, 1e5, 1,2e9,0,0,1, "window decision rate") \ +OPTION( emagluefast, 33, 1,2e9,0,0,1, "window fast glue") \ +OPTION( emaglueslow, 1e5, 1,2e9,0,0,1, "window slow glue") \ +OPTION( emajump, 1e5, 1,2e9,0,0,1, "window back-jump level") \ +OPTION( emalevel, 1e5, 1,2e9,0,0,1, "window back-track level") \ +OPTION( emasize, 1e5, 1,2e9,0,0,1, "window learned clause size") \ +OPTION( ematrailfast, 1e2, 1,2e9,0,0,1, "window fast trail") \ +OPTION( ematrailslow, 1e5, 1,2e9,0,0,1, "window slow trail") \ +OPTION( exteagerreasons, 1, 0, 1,0,0,1, "eagerly ask for all reasons (0: only when needed)") \ +OPTION( exteagerrecalc, 1, 0, 1,0,0,1, "after eagerly asking for reasons recalculate all levels (0: trust the external tool)") \ +OPTION( externallrat, 0, 0, 1,0,0,1, "external lrat") \ +OPTION( factor, 1, 0, 1,0,1,1, "bounded variable addition") \ +OPTION( factorcandrounds, 2, 0,2e9,0,0,1, "candidates reduction rounds") \ +OPTION( factoreffort, 50, 0,1e6,0,0,1, "relative effort per mille") \ +OPTION( factoriniticks, 300, 1,1e6,0,0,1, "initial effort in millions") \ +OPTION( factorsize, 5, 2,2e9,0,0,1, "clause size limit") \ +OPTION( factorthresh, 7, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \ +OPTION( fastelim, 1, 0, 1,0,1,1, "fast BVE during preprocessing") \ +OPTION( fastelimbound, 8, 1,1e3,1,0,1, "fast BVE bound during preprocessing") \ +OPTION( fastelimclslim, 1e2, 2,2e9,2,0,1, "fast BVE resolvent size limit") \ +OPTION( fastelimocclim, 100, 1,2e9,2,0,1, "fast BVE occurence limit during preprocessing") \ +OPTION( fastelimrounds, 4, 1,512,1,0,1, "number of fastelim rounds") \ +OPTION( flush, 0, 0, 1,0,1,1, "flush redundant clauses") \ +OPTION( flushfactor, 3, 1,1e3,0,0,1, "interval increase") \ +OPTION( flushint, 1e5, 1,2e9,0,0,1, "initial limit") \ +OPTION( forcephase, 0, 0, 1,0,0,1, "always use initial phase") \ +OPTION( frat, 0, 0, 2,0,0,1, "1=frat(lrat), 2=frat(drat)") \ +OPTION( idrup, 0, 0, 1,0,0,1, "incremental proof format") \ +OPTION( ilb, 0, 0, 1,0,0,1, "ILB (incremental lazy backtrack)") \ +OPTION( ilbassumptions, 0, 0, 1,0,0,1, "trail reuse for assumptions (ILB-like)") \ +OPTION( inprobeint, 100, 1,2e9,0,0,1, "inprobing interval" ) \ +OPTION( inprobing, 1, 0, 1,0,1,1, "enable probe inprocessing") \ +OPTION( inprocessing, 1, 0, 1,0,1,1, "enable general inprocessing") \ +OPTION( instantiate, 0, 0, 1,0,1,1, "variable instantiation") \ +OPTION( instantiateclslim, 3, 2,2e9,0,0,1, "minimum clause size") \ +OPTION( instantiateocclim, 1, 1,2e9,2,0,1, "maximum occurrence limit") \ +OPTION( instantiateonce, 1, 0, 1,0,0,1, "instantiate each clause once") \ +OPTION( lidrup, 0, 0, 1,0,0,1, "linear incremental proof format") \ +LOGOPT( log, 0, 0, 1,0,0,0, "enable logging") \ +LOGOPT( logsort, 0, 0, 1,0,0,0, "sort logged clauses") \ +OPTION( lrat, 0, 0, 1,0,0,1, "use LRAT proof format") \ +OPTION( lucky, 1, 0, 1,0,0,1, "search for lucky phases") \ +OPTION( minimize, 1, 0, 1,0,0,1, "minimize learned clauses") \ +OPTION( minimizedepth, 1e3, 0,1e3,0,0,1, "minimization depth") \ +OPTION( minimizeticks, 1, 0, 1,0,0,1, "increment ticks in minimization") \ +OPTION( otfs, 1, 0, 1,0,0,1, "on-the-fly self subsumption") \ +OPTION( phase, 1, 0, 1,0,0,1, "initial phase") \ +OPTION( preprocessinit, 2e6, 0,2e9,2,0,1, "initial preprocessing base limit" ) \ +OPTION( preprocesslight, 1, 0, 1,0,1,1, "lightweight preprocessing" ) \ +OPTION( probe, 1, 0, 1,0,1,1, "failed literal probing" ) \ +OPTION( probeeffort, 8, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( probehbr, 1, 0, 1,0,0,1, "learn hyper binary clauses") \ +OPTION( probethresh, 0, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \ +OPTION( profile, 2, 0, 4,0,0,0, "profiling level") \ +QUTOPT( quiet, 0, 0, 1,0,0,0, "disable all messages") \ +OPTION( radixsortlim, 32, 0,2e9,0,0,1, "radix sort limit") \ +OPTION( realtime, 0, 0, 1,0,0,0, "real instead of process time") \ +OPTION( recomputetier, 1, 0, 1,0,0,1, "recompute tiers") \ +OPTION( reduce, 1, 0, 1,0,0,1, "reduce useless clauses") \ +OPTION( reduceinit, 300, 1,1e6,0,0,1, "initial interval") \ +OPTION( reduceint, 25, 2,1e6,0,0,1, "reduce interval") \ +OPTION( reduceopt, 1, 0, 2,0,0,1, "0=prct,1=sqrt,2=max") \ +OPTION( reducetarget, 75, 10,1e2,0,0,1, "reduce fraction in percent") \ +OPTION( reducetier1glue, 2, 1,2e9,0,0,1, "glue of kept learned clauses") \ +OPTION( reducetier2glue, 6, 1,2e9,0,0,1, "glue of tier two clauses") \ +OPTION( reluctant, 1024, 0,2e9,0,0,1, "reluctant doubling period") \ +OPTION( reluctantmax,1048576, 0,2e9,0,0,1, "reluctant doubling period") \ +OPTION( rephase, 1, 0, 1,0,0,1, "enable resetting phase") \ +OPTION( rephaseint, 1e3, 1,2e9,0,0,1, "rephase interval") \ +OPTION( report,reportdefault, 0, 1,0,0,1, "enable reporting") \ +OPTION( reportall, 0, 0, 1,0,0,1, "report even if not successful") \ +OPTION( reportsolve, 0, 0, 1,0,0,1, "use solving not process time") \ +OPTION( restart, 1, 0, 1,0,0,1, "enable restarts") \ +OPTION( restartint, 2, 1,2e9,0,0,1, "restart interval") \ +OPTION( restartmargin, 10, 0,1e2,0,0,1, "slow fast margin in percent") \ +OPTION( restartreusetrail, 1, 0, 1,0,0,1, "enable trail reuse") \ +OPTION( restoreall, 0, 0, 2,0,0,1, "restore all clauses (2=really)") \ +OPTION( restoreflush, 0, 0, 1,0,0,1, "remove satisfied clauses") \ +OPTION( reverse, 0, 0, 1,0,0,1, "reverse variable ordering") \ +OPTION( score, 1, 0, 1,0,0,1, "use EVSIDS scores") \ +OPTION( scorefactor, 950,500,1e3,0,0,1, "score factor per mille") \ +OPTION( seed, 0, 0,2e9,0,0,1, "random seed") \ +OPTION( shrink, 3, 0, 3,0,0,1, "shrink conflict clause (1=only with binary, 2=minimize when pulling, 3=full)") \ +OPTION( shrinkreap, 1, 0, 1,0,0,1, "use a reap for shrinking") \ +OPTION( shuffle, 0, 0, 1,0,0,1, "shuffle variables") \ +OPTION( shufflequeue, 1, 0, 1,0,0,1, "shuffle variable queue") \ +OPTION( shufflerandom, 0, 0, 1,0,0,1, "not reverse but random") \ +OPTION( shufflescores, 1, 0, 1,0,0,1, "shuffle variable scores") \ +OPTION( stabilize, 1, 0, 1,0,0,1, "enable stabilizing phases") \ +OPTION( stabilizeinit, 1e3, 1,2e9,0,0,1, "stabilizing interval") \ +OPTION( stabilizeonly, 0, 0, 1,0,0,1, "only stabilizing phases") \ +OPTION( stats, 0, 0, 1,0,0,1, "print all statistics at the end of the run") \ +OPTION( subsume, 1, 0, 1,0,1,1, "enable clause subsumption") \ +OPTION( subsumebinlim, 1e4, 0,2e9,1,0,1, "watch list length limit") \ +OPTION( subsumeclslim, 1e2, 0,2e9,2,0,1, "clause length limit") \ +OPTION( subsumeeffort, 1e3, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( subsumelimited, 1, 0, 1,0,0,1, "limit subsumption checks") \ +OPTION( subsumemaxeff, 1e8, 0,2e9,1,0,1, "maximum subsuming efficiency") \ +OPTION( subsumemineff, 0, 0,2e9,1,0,1, "minimum subsuming efficiency") \ +OPTION( subsumeocclim, 1e2, 0,2e9,1,0,1, "watch list length limit") \ +OPTION( subsumestr, 1, 0, 1,0,0,1, "subsume strenghten") \ +OPTION( sweep, 1, 0, 1,0,1,1, "enable SAT sweeping") \ +OPTION( sweepclauses, 1024, 0,2e9,1,0,1, "environment clauses") \ +OPTION( sweepcomplete, 0, 0, 1,0,0,1, "run SAT sweeping to completion") \ +OPTION( sweepcountbinary, 1, 0, 1,0,0,1, "count binaries to environment") \ +OPTION( sweepdepth, 2, 0,2e9,1,0,1, "environment depth") \ +OPTION( sweepeffort, 1e2, 0,1e4,0,0,1, "relative effort in ticks per mille") \ +OPTION( sweepfliprounds, 1, 0,2e9,1,0,1, "flipping rounds") \ +OPTION( sweepmaxclauses, 3e5, 2,2e9,1,0,1, "maximum environment clauses") \ +OPTION( sweepmaxdepth, 3, 1,2e9,1,0,1, "maximum environment depth") \ +OPTION( sweepmaxvars, 8192, 2,2e9,1,0,1, "maximum environment variables") \ +OPTION( sweeprand, 0, 0, 1,0,0,1, "randomize sweeping environment") \ +OPTION( sweepthresh, 5, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \ +OPTION( sweepvars, 256, 0,2e9,1,0,1, "environment variables") \ +OPTION( target, 1, 0, 2,0,0,1, "target phases (1=stable only)") \ +OPTION( terminateint, 10, 0,1e4,0,0,1, "termination check interval") \ +OPTION( ternary, 1, 0, 1,0,1,1, "hyper ternary resolution") \ +OPTION( ternaryeffort, 8, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( ternarymaxadd, 1e3, 0,1e4,1,0,1, "max clauses added in percent") \ +OPTION( ternaryocclim, 1e2, 1,2e9,2,0,1, "ternary occurrence limit") \ +OPTION( ternaryrounds, 2, 1, 16,1,0,1, "maximum ternary rounds") \ +OPTION( ternarythresh, 6, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \ +OPTION( tier1limit, 50, 0,100,0,0,1, "limit of tier1 usage in percentage") \ +OPTION( tier2limit, 90, 0,100,0,0,1, "limit of tier2 usage in percentage") \ +OPTION( transred, 1, 0, 1,0,1,1, "transitive reduction of BIG") \ +OPTION( transredeffort, 1e2, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( transredmaxeff, 1e8, 0,2e9,1,0,1, "maximum efficiency") \ +OPTION( transredmineff, 0, 0,2e9,1,0,1, "minimum efficiency") \ +QUTOPT( verbose, 0, 0, 3,0,0,0, "more verbose messages") \ +OPTION( veripb, 0, 0, 4,0,0,1, "odd=checkdeletions, > 2=drat") \ +OPTION( vivify, 1, 0, 1,0,1,1, "vivification") \ +OPTION( vivifycalctier, 0, 0, 1,0,0,1, "recalculate tier limits") \ +OPTION( vivifydemote, 0, 0, 1,0,1,1, "demote irredundant or delete directly") \ +OPTION( vivifyeffort, 50, 0,1e5,1,0,1, "overall efficiency per mille") \ +OPTION( vivifyflush, 1, 0, 1,1,0,1, "flush subsumed before vivification rounds") \ +OPTION( vivifyinst, 1, 0, 1,0,0,1, "instantiate last literal when vivify") \ +OPTION( vivifyirred, 1, 0, 1,0,1,1, "vivification irred") \ +OPTION( vivifyirredeff, 3, 1,100,1,0,1, "irredundant efficiency per mille") \ +OPTION( vivifyonce, 0, 0, 2,0,0,1, "vivify once: 1=red, 2=red+irr") \ +OPTION( vivifyretry, 0, 0, 5,0,0,1, "re-vivify clause if vivify was successful") \ +OPTION( vivifyschedmax, 5e3, 10,2e9,0,0,1, "maximum schedule size") \ +OPTION( vivifythresh, 20, 0,100,1,0,1, "delay if ticks smaller thresh*clauses") \ +OPTION( vivifytier1, 1, 0, 1,0,1,1, "vivification tier1") \ +OPTION( vivifytier1eff, 4, 0,100,1,0,1, "relative tier1 effort") \ +OPTION( vivifytier2, 1, 0, 1,0,1,1, "vivification tier2") \ +OPTION( vivifytier2eff, 2, 1,100,1,0,1, "relative tier2 effort") \ +OPTION( vivifytier3, 1, 0, 1,0,1,1, "vivification tier3") \ +OPTION( vivifytier3eff, 1, 1,100,1,0,1, "relative tier3 effort") \ +OPTION( walk, 1, 0, 1,0,0,1, "enable random walks") \ +OPTION( walkeffort, 20, 1,1e5,1,0,1, "relative efficiency per mille") \ +OPTION( walkmaxeff, 1e7, 0,2e9,1,0,1, "maximum efficiency") \ +OPTION( walkmineff, 0, 0,1e7,1,0,1, "minimum efficiency") \ +OPTION( walknonstable, 1, 0, 1,0,0,1, "walk in non-stabilizing phase") \ +OPTION( walkredundant, 0, 0, 1,0,0,1, "walk redundant clauses too") \ + +// Note, keep an empty line right before this line because of the last '\'! +// Also keep those single spaces after 'OPTION(' for proper sorting. + +// clang-format on + +/*------------------------------------------------------------------------*/ + +// Some of the 'OPTION' macros above should only be included if certain +// compile time options are enabled. This has the effect, that for instance +// if 'LOGGING' is defined, and thus logging code is included, then also the +// 'log' option is defined. Otherwise the 'log' option is not included. + +#ifdef LOGGING +#define LOGOPT OPTION +#else +#define LOGOPT(...) /**/ +#endif + +#ifdef CADICAL_QUIET +#define QUTOPT(...) /**/ +#else +#define QUTOPT OPTION +#endif + +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +struct Internal; + +/*------------------------------------------------------------------------*/ + +class Options; + +struct Option { + const char *name; + int def, lo, hi; + int optimizable; + bool preprocessing; + const char *description; + int &val (Options *); +}; + +/*------------------------------------------------------------------------*/ + +// Produce a compile time constant for the number of options. + +static const size_t number_of_options = +#define OPTION(N, V, L, H, O, P, R, D) 1 + + OPTIONS +#undef OPTION + + 0; + +/*------------------------------------------------------------------------*/ + +class Options { + + Internal *internal; + + void set (Option *, int val); // Force to [lo,hi] interval. + + friend struct Option; + static Option table[]; + + static void initialize_from_environment (int &val, const char *name, + const int L, const int H); + + friend Config; + + void reset_default_values (); + void disable_preprocessing (); + +public: + // For library usage we disable reporting by default while for the stand + // alone SAT solver we enable it by default. This default value has to + // be set before the constructor of 'Options' is called (which in turn is + // called from the constructor of 'Solver'). If we would simply overwrite + // its initial value while initializing the stand alone solver, we will + // get that change of the default value (from 'false' to 'true') shown + // during calls to 'print ()', which is confusing to the user. + // + static int reportdefault; + + Options (Internal *); + + // Makes options directly accessible, e.g., for instance declares the + // member 'int restart' here. This will give fast access to option values + // internally in the solver and thus can also be used in tight loops. + // +private: + int __start_of_options__; // Used by 'val' below. +public: +#define OPTION(N, V, L, H, O, P, R, D) \ + int N; // Access option values by name. + OPTIONS +#undef OPTION + + // It would be more elegant to use an anonymous 'struct' of the actual + // option values overlayed with an 'int values[number_of_options]' array + // but that is not proper ISO C++ and produces a warning. Instead we use + // the following construction which relies on '__start_of_options__' and + // that the following options are really allocated directly after it. + // + inline int &val (size_t idx) { + CADICAL_assert (idx < number_of_options); + return (&__start_of_options__ + 1)[idx]; + } + + // With the following function we can get rather fast access to the option + // limits, the default value and the description. The code uses binary + // search over the sorted option 'table'. This static data is shared + // among different instances of the solver. The actual current option + // values are here in the 'Options' class. They can be accessed by the + // offset of the static options using 'Option::val' if you have an + // 'Option' or to have even faster access directly by the member function + // (the 'N' above, e.g., 'restart'). + // + static Option *has (const char *name); + + bool set (const char *name, int); // Explicit version. + int get (const char *name); // Get current value. + + void print (); // Print current values in command line form + static void usage (); // Print usage message for all options. + + void optimize (int val); // increase some limits (val=0..31) + + static bool is_preprocessing_option (const char *name); + + // Parse long option argument + // + // --<name> + // --<name>=<val> + // --no-<name> + // + // where '<val>' is as in 'parse_option_value'. If parsing succeeds, + // 'true' is returned and the string will be set to the name of the + // option. Additionally the parsed value is set (last argument). + // + static bool parse_long_option (const char *, string &, int &); + + // Iterating options. + + typedef Option *iterator; + typedef const Option *const_iterator; + + static iterator begin () { return table; } + static iterator end () { return table + number_of_options; } + + void copy (Options &other) const; // Copy 'this' into 'other'. +}; + +inline int &Option::val (Options *opts) { + CADICAL_assert (Options::table <= this && + this < Options::table + number_of_options); + return opts->val (this - Options::table); +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/parse.hpp b/src/sat/cadical/parse.hpp new file mode 100644 index 000000000..a37117f43 --- /dev/null +++ b/src/sat/cadical/parse.hpp @@ -0,0 +1,78 @@ +#ifndef _parse_hpp_INCLUDED +#define _parse_hpp_INCLUDED + +#include "global.h" + +#include <cassert> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Factors out common functions for parsing of DIMACS and solution files. + +class File; +struct External; +struct Internal; + +class Parser { + + Solver *solver; + Internal *internal; + External *external; + File *file; + + void perr (const char *fmt, ...) CADICAL_ATTRIBUTE_FORMAT (2, 3); + int parse_char (); + + enum { + FORCED = 0, // Force reading even if header is broken. + RELAXED = 1, // Relaxed white space treatment in header. + STRICT = 2, // Strict white space and header compliance. + }; + + const char *parse_string (const char *str, char prev); + const char *parse_positive_int (int &ch, int &res, const char *name); + const char *parse_lit (int &ch, int &lit, int &vars, int strict); + const char *parse_dimacs_non_profiled (int &vars, int strict); + const char *parse_solution_non_profiled (); + + bool *parse_inccnf_too; + vector<int> *cubes; + +public: + // Parse a DIMACS CNF or ICNF file. + // + // Return zero if successful. Otherwise parse error. + Parser (Solver *s, File *f, bool *i, vector<int> *c) + : solver (s), internal (s->internal), external (s->external), + file (f), parse_inccnf_too (i), cubes (c) {} + + // Parse a DIMACS file. Return zero if successful. Otherwise a parse + // error is return. The parsed clauses are added to the solver and the + // maximum variable index found is returned in the 'vars' argument. The + // 'strict' argument can be '0' in which case the numbers in the header + // can be arbitrary, e.g., 'p cnf 0 0' all the time, without producing a + // parse error. Only for this setting the parsed literals are not checked + // to overflow the maximum variable index of the header. The strictest + // form of parsing is enforced for the value '2' of 'strict', in which + // case the header can not have additional white space, while a value of + // '1' exactly relaxes this, e.g., 'p cnf \t 1 3 \r\n' becomes legal. + // + const char *parse_dimacs (int &vars, int strict); + + // Parse a solution file as used in the SAT competition, e.g., with + // comment lines 'c ...', a status line 's ...' and value lines 'v ...'. + // Returns zero if successful. Otherwise a string is returned describing + // the parse error. The parsed solution is saved in 'solution' and can be + // accessed with 'sol (int lit)'. We use it for checking learned clauses. + // + const char *parse_solution (); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/phases.hpp b/src/sat/cadical/phases.hpp new file mode 100644 index 000000000..98e54d439 --- /dev/null +++ b/src/sat/cadical/phases.hpp @@ -0,0 +1,24 @@ +#ifndef _phases_hpp_INCLUDED +#define _phases_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Phases { + + vector<signed char> best; // The current largest trail phase. + vector<signed char> forced; // Forced through 'phase'. + vector<signed char> min; // The current minimum unsatisfied phase. + vector<signed char> prev; // Previous during local search. + vector<signed char> saved; // The actual saved phase. + vector<signed char> target; // The current target phase. +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/profile.hpp b/src/sat/cadical/profile.hpp new file mode 100644 index 000000000..f3799389b --- /dev/null +++ b/src/sat/cadical/profile.hpp @@ -0,0 +1,280 @@ +#ifndef _profiles_h_INCLUDED +#define _profiles_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +/*------------------------------------------------------------------------*/ +#ifndef CADICAL_QUIET +/*------------------------------------------------------------------------*/ + +namespace CaDiCaL { + +struct Internal; + +/*------------------------------------------------------------------------*/ + +// The solver contains some built in profiling (even for optimized code). +// The idea is that even without using external tools it is possible to get +// an overview of where time is spent. This is enabled with the option +// 'profile', e.g., you might want to use '--profile=3', or even higher +// values for more detailed profiling information. Currently the default is +// '--profile=2', which should only induce a tiny profiling overhead. +// +// Profiling has a Heisenberg effect, since we rely on calling 'getrusage' +// instead of using profile counters and sampling. For functions which are +// executed many times, this overhead is substantial (say 10%-20%). For +// functions which are not executed many times there is in essence no +// overhead in measuring time spent in them. These get a smaller profiling +// level, which is the second argument in the 'PROFILE' macro below. Thus +// using '--profile=1' for instance should not add any penalty to the +// run-time, while '--profile=3' and higher levels slow down the solver. +// +// To profile say 'foo', just add another line 'PROFILE(foo,LEVEL)' and wrap +// the code to be profiled within a 'START (foo)' / 'STOP (foo)' block. + +/*------------------------------------------------------------------------*/ + +// Profile counters for functions which are not compiled in should be +// removed. This is achieved by adding a wrapper macro for them here. + +/*------------------------------------------------------------------------*/ + +#ifdef PROFILE_MODE +#define MROFILE PROFILE +#else +#define MROFILE(...) /**/ +#endif + +#define PROFILES \ + PROFILE (analyze, 3) \ + MROFILE (analyzestable, 4) \ + MROFILE (analyzeunstable, 4) \ + PROFILE (backward, 3) \ + PROFILE (block, 2) \ + PROFILE (bump, 4) \ + PROFILE (checking, 2) \ + PROFILE (cdcl, 1) \ + PROFILE (collect, 3) \ + PROFILE (compact, 3) \ + PROFILE (condition, 2) \ + PROFILE (congruence, 2) \ + PROFILE (congruencemerge, 4) \ + PROFILE (congruencematching, 4) \ + PROFILE (connect, 3) \ + PROFILE (copy, 4) \ + PROFILE (cover, 2) \ + PROFILE (decide, 3) \ + PROFILE (decompose, 3) \ + PROFILE (definition, 2) \ + PROFILE (elim, 2) \ + PROFILE (factor, 2) \ + PROFILE (fastelim, 2) \ + PROFILE (extend, 3) \ + PROFILE (extract, 3) \ + PROFILE (extractands, 4) \ + PROFILE (extractbinaries, 4) \ + PROFILE (extractites, 4) \ + PROFILE (extractxors, 4) \ + PROFILE (instantiate, 2) \ + PROFILE (lucky, 2) \ + PROFILE (lookahead, 2) \ + PROFILE (minimize, 4) \ + PROFILE (shrink, 4) \ + PROFILE (parse, 0) /* As 'opts.profile' might change in parsing*/ \ + PROFILE (probe, 2) \ + PROFILE (deduplicate, 3) \ + PROFILE (propagate, 4) \ + MROFILE (propstable, 4) \ + MROFILE (propunstable, 4) \ + PROFILE (reduce, 3) \ + PROFILE (restart, 3) \ + PROFILE (restore, 2) \ + PROFILE (search, 1) \ + PROFILE (solve, 0) \ + PROFILE (stable, 2) \ + PROFILE (sweep, 2) \ + PROFILE (sweepbackbone, 3) \ + PROFILE (sweepequivalences, 3) \ + PROFILE (sweepflip, 4) \ + PROFILE (sweepimplicant, 4) \ + PROFILE (sweepsolve, 4) \ + PROFILE (preprocess, 2) \ + PROFILE (simplify, 1) \ + PROFILE (subsume, 2) \ + PROFILE (ternary, 2) \ + PROFILE (transred, 3) \ + PROFILE (unstable, 2) \ + PROFILE (vivify, 2) \ + PROFILE (walk, 2) + +/*------------------------------------------------------------------------*/ + +// See 'START' and 'STOP' in 'macros.hpp' too. + +struct Profile { + + bool active; + double value; // accumulated time + double started; // started time if active + const char *name; // name of the profiled function (or 'phase') + const int level; // allows to cheaply test if profiling is enabled + + Profile (const char *n, int l) + : active (false), value (0), name (n), level (l) {} +}; + +struct Profiles { + Internal *internal; +#define PROFILE(NAME, LEVEL) Profile NAME; + PROFILES +#undef PROFILE + Profiles (Internal *); +}; + +} // namespace CaDiCaL + +#define NON_CADICAL_QUIET_PROFILE_CODE(CODE) CODE + +#else // !defined(CADICAL_QUIET) + +#define NON_CADICAL_QUIET_PROFILE_CODE(CODE) /**/ + +#endif + +/*------------------------------------------------------------------------*/ + +// Macros for Profiling support and checking and changing the mode. + +#define START(P) \ + do { \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + if (internal->profiles.P.level <= internal->opts.profile) \ + internal->start_profiling (internal->profiles.P, \ + internal->time ());) \ + } while (0) + +#define STOP(P) \ + do { \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + if (internal->profiles.P.level <= internal->opts.profile) \ + internal->stop_profiling (internal->profiles.P, \ + internal->time ());) \ + } while (0) + +#define PROFILE_ACTIVE(P) \ + ((internal->profiles.P.level <= internal->opts.profile) && \ + (internal->profiles.P.active)) + +/*------------------------------------------------------------------------*/ + +#define START_SIMPLIFIER(S, M) \ + do { \ + NON_CADICAL_QUIET_PROFILE_CODE (const double N = time (); \ + const int L = internal->opts.profile;) \ + if (!preprocessing && !lookingahead) { \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + if (stable && internal->profiles.stable.level <= L) \ + internal->stop_profiling (internal->profiles.stable, N); \ + if (!stable && internal->profiles.unstable.level <= L) \ + internal->stop_profiling (internal->profiles.unstable, N); \ + if (internal->profiles.search.level <= L) \ + internal->stop_profiling (internal->profiles.search, N);) \ + reset_mode (SEARCH); \ + } \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + if (internal->profiles.simplify.level <= L) \ + internal->start_profiling (internal->profiles.simplify, N); \ + if (internal->profiles.S.level <= L) \ + internal->start_profiling (internal->profiles.S, N);) \ + set_mode (SIMPLIFY); \ + set_mode (M); \ + } while (0) + +/*------------------------------------------------------------------------*/ + +#define STOP_SIMPLIFIER(S, M) \ + do { \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + const double N = time (); const int L = internal->opts.profile; \ + if (internal->profiles.S.level <= L) \ + internal->stop_profiling (internal->profiles.S, N); \ + if (internal->profiles.simplify.level <= L) \ + internal->stop_profiling (internal->profiles.simplify, N);) \ + reset_mode (M); \ + reset_mode (SIMPLIFY); \ + if (!preprocessing && !lookingahead) { \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + if (internal->profiles.search.level <= L) \ + internal->start_profiling (internal->profiles.search, N); \ + if (stable && internal->profiles.stable.level <= L) \ + internal->start_profiling (internal->profiles.stable, N); \ + if (!stable && internal->profiles.unstable.level <= L) \ + internal->start_profiling (internal->profiles.unstable, N);) \ + set_mode (SEARCH); \ + } \ + } while (0) + +/*------------------------------------------------------------------------*/ +// Used in 'walk' before calling 'walk_round' within the CDCL loop. + +#define START_INNER_WALK() \ + do { \ + require_mode (SEARCH); \ + CADICAL_assert (!preprocessing); \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + const double N = time (); const int L = internal->opts.profile; \ + if (stable && internal->profiles.stable.level <= L) \ + internal->stop_profiling (internal->profiles.stable, N); \ + if (!stable && internal->profiles.unstable.level <= L) \ + internal->stop_profiling (internal->profiles.unstable, N); \ + if (internal->profiles.walk.level <= L) \ + internal->start_profiling (internal->profiles.walk, N);) \ + set_mode (WALK); \ + } while (0) + +/*------------------------------------------------------------------------*/ +// Used in 'walk' after calling 'walk_round' within the CDCL loop. + +#define STOP_INNER_WALK() \ + do { \ + require_mode (SEARCH); \ + CADICAL_assert (!preprocessing); \ + reset_mode (WALK); \ + NON_CADICAL_QUIET_PROFILE_CODE ( \ + const double N = time (); const int L = internal->opts.profile; \ + if (internal->profiles.walk.level <= L) \ + internal->stop_profiling (internal->profiles.walk, N); \ + if (stable && internal->profiles.stable.level <= L) \ + internal->start_profiling (internal->profiles.stable, N); \ + if (!stable && internal->profiles.unstable.level <= L) \ + internal->start_profiling (internal->profiles.unstable, N); \ + internal->profiles.walk.started = (N);) \ + } while (0) + +/*------------------------------------------------------------------------*/ +// Used in 'local_search' before calling 'walk_round'. + +#define START_OUTER_WALK() \ + do { \ + require_mode (SEARCH); \ + CADICAL_assert (!preprocessing); \ + NON_CADICAL_QUIET_PROFILE_CODE (START (walk);) \ + set_mode (WALK); \ + } while (0) + +/*------------------------------------------------------------------------*/ +// Used in 'local_search' after calling 'walk_round'. + +#define STOP_OUTER_WALK() \ + do { \ + require_mode (SEARCH); \ + CADICAL_assert (!preprocessing); \ + reset_mode (WALK); \ + NON_CADICAL_QUIET_PROFILE_CODE (STOP (walk);) \ + } while (0) + +ABC_NAMESPACE_CXX_HEADER_END + +#endif // ifndef _profiles_h_INCLUDED diff --git a/src/sat/cadical/proof.hpp b/src/sat/cadical/proof.hpp new file mode 100644 index 000000000..69be6b080 --- /dev/null +++ b/src/sat/cadical/proof.hpp @@ -0,0 +1,120 @@ +#ifndef _proof_h_INCLUDED +#define _proof_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +/*------------------------------------------------------------------------*/ + +class File; +struct Clause; +struct Internal; +class Tracer; +class FileTracer; + +/*------------------------------------------------------------------------*/ + +// Provides proof checking and writing. + +class Proof { + + Internal *internal; + + vector<int> clause; // of external literals + vector<int64_t> proof_chain; // LRAT style proof chain of clause + int64_t clause_id; // id of added clause + bool redundant; + + // the 'tracers' + vector<Tracer *> tracers; // tracers (ie checker) + vector<FileTracer *> file_tracers; // file tracers (ie LRAT tracer) + + void add_literal (int internal_lit); // add to 'clause' + void add_literals (Clause *); // add to 'clause' + + void add_literals (const vector<int> &); // ditto + + void add_original_clause ( + bool restore = false); // notify observers of original clauses + void add_derived_clause (); + void add_assumption_clause (); + void delete_clause (); + void demote_clause (); + void weaken_minus (); + void strengthen (); + void finalize_clause (); + void add_assumption (); + void add_constraint (); + +public: + Proof (Internal *); + ~Proof (); + + void connect (Tracer *t) { tracers.push_back (t); } + void disconnect (Tracer *t); + // Add original clauses to the proof (for online proof checking). + // + void add_original_clause (int64_t, bool, const vector<int> &); + + void add_assumption_clause (int64_t, const vector<int> &, + const vector<int64_t> &); + void add_assumption_clause (int64_t, int, const vector<int64_t> &); + void add_assumption (int); + void add_constraint (const vector<int> &); + void reset_assumptions (); + + // Add/delete original clauses to/from the proof using their original + // external literals (from external->eclause) + // + void add_external_original_clause (int64_t, bool, const vector<int> &, + bool restore = false); + void delete_external_original_clause (int64_t, bool, const vector<int> &); + + // Add derived (such as learned) clauses to the proof. + // + void add_derived_empty_clause (int64_t, const vector<int64_t> &); + void add_derived_unit_clause (int64_t, int unit, const vector<int64_t> &); + void add_derived_clause (Clause *c, const vector<int64_t> &); + void add_derived_clause (int64_t, bool, const vector<int> &, + const vector<int64_t> &); + + // deletion of clauses. It comes in several variants, depending if the + // clause should be restored or not + void delete_clause (int64_t, bool, const vector<int> &); + void weaken_minus (int64_t, const vector<int> &); + void weaken_plus (int64_t, const vector<int> &); + void delete_unit_clause (int64_t id, const int lit); + void delete_clause (Clause *); + void weaken_minus (Clause *); + void weaken_plus (Clause *); + void strengthen (int64_t); + + void finalize_unit (int64_t, int); + void finalize_external_unit (int64_t, int); + void finalize_clause (int64_t, const vector<int> &c); + void finalize_clause (Clause *); + + void report_status (int, int64_t); + void begin_proof (int64_t); + void conclude_unsat (ConclusionType, const vector<int64_t> &); + void conclude_sat (const vector<int> &model); + void conclude_unknown (const vector<int> &trace); + void solve_query (); + // These two actually pretend to add and remove a clause. + // + void flush_clause (Clause *); // remove falsified literals + void strengthen_clause (Clause *, int, const vector<int64_t> &); + void otfs_strengthen_clause (Clause *, const vector<int> &, + const vector<int64_t> &); + + void flush (); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/queue.hpp b/src/sat/cadical/queue.hpp new file mode 100644 index 000000000..14e1f2da7 --- /dev/null +++ b/src/sat/cadical/queue.hpp @@ -0,0 +1,70 @@ +#ifndef _queue_hpp_INCLUDED +#define _queue_hpp_INCLUDED + +#include "global.h" + +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Links for double linked decision queue. + +struct Link { + + int prev, next; // variable indices + + // initialized explicitly in 'init_queue' +}; + +typedef std::vector<Link> Links; + +// Variable move to front (VMTF) decision queue ordered by 'bumped'. See +// our SAT'15 paper for an explanation on how this works. + +struct Queue { + + // We use integers instead of variable pointers. This is more compact and + // also avoids issues due to moving the variable table during 'resize'. + + int first, last; // anchors (head/tail) for doubly linked list + int unassigned; // all variables after this one are assigned + int64_t bumped; // see 'Internal.update_queue_unassigned' + + Queue () : first (0), last (0), unassigned (0), bumped (0) {} + + // We explicitly provide the mapping of integer indices to links to the + // following two (inlined) functions. This avoids including + // 'internal.hpp' and breaks a cyclic dependency, so we can keep their + // code here in this header file. Otherwise they are just ordinary doubly + // linked list 'dequeue' and 'enqueue' operations. + + inline void dequeue (Links &links, int idx) { + Link &l = links[idx]; + if (l.prev) + links[l.prev].next = l.next; + else + first = l.next; + if (l.next) + links[l.next].prev = l.prev; + else + last = l.prev; + } + + inline void enqueue (Links &links, int idx) { + Link &l = links[idx]; + if ((l.prev = last)) + links[last].next = idx; + else + first = idx; + last = idx; + l.next = 0; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/radix.hpp b/src/sat/cadical/radix.hpp new file mode 100644 index 000000000..4c4d7b574 --- /dev/null +++ b/src/sat/cadical/radix.hpp @@ -0,0 +1,186 @@ +#ifndef _radix_hpp_INCLUDED +#define _radix_hpp_INCLUDED + +#include "global.h" + +#include <cassert> +#include <cstring> +#include <iterator> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +// This provides an implementation of a generic radix sort algorithm. The +// reason for having it is that for certain benchmarks and certain parts of +// CaDiCaL where sorting is used, the standard sorting algorithm 'sort' +// turned out to be a hot-spot. Up to 30% of the total running time was for +// instance used for some benchmarks in sorting variables during bumping +// to make sure to bump them in 'enqueued' order. +// +// Further, in most cases, where we need to sort something, sorting is +// actually performed on positive numbers (such as the 'enqueued' time stamp +// during bumping), which allows to use radix sort or variants. At least +// starting with medium sized arrays to be sorted (say above 1000 elements, +// but see discussion on 'MSORT' below), radix sort can be way faster. +// +// Finally it is stable, which is actually preferred most of the time too. +// +// This template algorithm 'rsort' takes as first template parameter the +// iterator class similar to the standard 'sort' algorithm template, but +// then as second parameter a function class (similar to the second 'less +// than' parameter of 'sort') which can obtain a 'rank' from each element, +// on which they are compared. The 'rank' should be able to turn an element +// into a number. The type of these ranks is determined automatically but +// should be 'unsigned'. + +struct pointer_rank { + typedef size_t Type; + Type operator() (void *ptr) { return (size_t) ptr; } +}; + +template <class I, class Rank> void rsort (I first, I last, Rank rank) { + typedef typename iterator_traits<I>::value_type T; + typedef typename Rank::Type R; + + CADICAL_assert (first <= last); + const size_t n = last - first; + if (n <= 1) + return; + + const size_t l = 8; // Radix 8, thus byte-wise. + const size_t w = (1 << l); // So many buckets. + + const unsigned mask = w - 1; // Fast mod 'w'. + +// Uncomment the following define for large values of 'w' in order to keep +// the large bucket array 'count' on the heap instead of the stack. +// +// #define CADICAL_RADIX_BUCKETS_ON_THE_HEAP +// +#ifdef CADICAL_RADIX_BUCKETS_ON_THE_HEAP + size_t *count = new size_t[w]; // Put buckets on the heap. +#else + size_t count[w]; // Put buckets on the stack. +#endif + + I a = first, b = last, c = a; + bool initialized = false; + std::vector<T> v; + + R upper = 0, lower = ~upper; + R shifted = mask; + bool bounded = false; + + R masked_lower = 0, masked_upper = mask; + + for (size_t i = 0; i < 8 * sizeof (rank (*first)); + i += l, shifted <<= l) { + + if (bounded && (lower & shifted) == (upper & shifted)) + continue; + + memset (count + masked_lower, 0, + (masked_upper - masked_lower + 1) * sizeof *count); + + const I end = c + n; + bool sorted = true; + R last = 0; + + for (I p = c; p != end; p++) { + const auto r = rank (*p); + if (!bounded) + lower &= r, upper |= r; + const auto s = r >> i; + const auto m = s & mask; + if (sorted && last > m) + sorted = false; + else + last = m; + count[m]++; + } + + masked_lower = (lower >> i) & mask; + masked_upper = (upper >> i) & mask; + + if (!bounded) { + bounded = true; + if ((lower & shifted) == (upper & shifted)) + continue; + } + + if (sorted) + continue; + + size_t pos = 0; + for (R j = masked_lower; j <= masked_upper; j++) { + const size_t delta = count[j]; + count[j] = pos; + pos += delta; + } + + if (!initialized) { + CADICAL_assert (&*c == &*a); // MS VC++ + v.resize (n); + b = v.begin (); + initialized = true; + } + + I d = (&*c == &*a) ? b : a; // MS VC++ + + for (I p = c; p != end; p++) { + const auto r = rank (*p); + const auto s = r >> i; + const auto m = s & mask; + d[count[m]++] = *p; + } + c = d; + } + + if (c == b) { + for (size_t i = 0; i < n; i++) + a[i] = b[i]; + } + +#ifdef CADICAL_RADIX_BUCKETS_ON_THE_HEAP + delete[] count; +#endif + +#ifndef CADICAL_NDEBUG + for (I p = first; p + 1 != last; p++) + CADICAL_assert (rank (p[0]) <= rank (p[1])); +#endif +} + +// It turns out that for small number of elements (like '100') and in +// particular for large value ranges the standard sorting function is +// considerably faster than our radix sort (like 2.5x). This negative effect +// vanishes at around 800 elements (sorting integers) and thus we provide a +// function 'MSORT' which selects between standard sort and radix sort based +// on the number of elements. However we failed to put this into proper C++ +// style template code and thus have to use a macro instead. We also do not +// use it everywhere instead of 'rsort' since it requires a fourth +// parameter, which is awkward, particular in those situation where we +// expect large arrays to be sorted anyhow (such as during sorting the +// clauses in an arena or the probes during probing). The first argument +// is the limit up to which we use the standard sort. Above the limit we +// use radix sort. As usual we do not want to hard code it here (default +// is '800') in order to make fuzzing and delta debugging more effective. + +#define MSORT(LIMIT, FIRST, LAST, RANK, LESS) \ + do { \ + const size_t N = LAST - FIRST; \ + if (N <= (size_t) (LIMIT)) \ + sort (FIRST, LAST, LESS); \ + else \ + rsort (FIRST, LAST, RANK); \ + } while (0) + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/random.h b/src/sat/cadical/random.h new file mode 100644 index 000000000..62fe5ed19 --- /dev/null +++ b/src/sat/cadical/random.h @@ -0,0 +1,50 @@ +#ifndef _random_h_INCLUDED +#define _random_h_INCLUDED + +#include "global.h" + +#include <assert.h> +#include <stdbool.h> +#include <stdint.h> + +ABC_NAMESPACE_HEADER_START + +typedef uint64_t generator; + +static inline uint64_t kissat_next_random64 (generator *rng) { + *rng *= 6364136223846793005ul; + *rng += 1442695040888963407ul; + return *rng; +} + +static inline unsigned kissat_next_random32 (generator *rng) { + return kissat_next_random64 (rng) >> 32; +} + +static inline unsigned kissat_pick_random (generator *rng, unsigned l, + unsigned r) { + CADICAL_assert (l <= r); + if (l == r) + return l; + const unsigned delta = r - l; + const unsigned tmp = kissat_next_random32 (rng); + const double fraction = tmp / 4294967296.0; + CADICAL_assert (0 <= fraction), CADICAL_assert (fraction < 1); + const unsigned scaled = delta * fraction; + CADICAL_assert (scaled < delta); + const unsigned res = l + scaled; + CADICAL_assert (l <= res), CADICAL_assert (res < r); + return res; +} + +static inline bool kissat_pick_bool (generator *rng) { + return kissat_pick_random (rng, 0, 2); +} + +static inline double kissat_pick_double (generator *rng) { + return kissat_next_random32 (rng) / 4294967296.0; +} + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/sat/cadical/random.hpp b/src/sat/cadical/random.hpp new file mode 100644 index 000000000..a5c78d38a --- /dev/null +++ b/src/sat/cadical/random.hpp @@ -0,0 +1,104 @@ +#ifndef _random_hpp_INCLUDED +#define _random_hpp_INCLUDED + +#include "global.h" + +#include <cstdint> + +ABC_NAMESPACE_CXX_HEADER_START + +// Random number generator. + +namespace CaDiCaL { + +class Random { + + uint64_t state; + + void add (uint64_t a) { + if (!(state += a)) + state = 1; + next (); + } + +public: + // Without argument use a machine, process and time dependent seed. + // + Random (); + + Random (uint64_t seed) : state (seed) {} + void operator= (uint64_t seed) { state = seed; } + Random (const Random &other) : state (other.seed ()) {} + + void operator+= (uint64_t a) { add (a); } + uint64_t seed () const { return state; } + + uint64_t next () { + state *= 6364136223846793005ul; + state += 1442695040888963407ul; + CADICAL_assert (state); + return state; + } + + uint32_t generate () { + next (); + return state >> 32; + } + int generate_int () { return (int) generate (); } + bool generate_bool () { return generate () < 2147483648u; } + + // Generate 'double' value in the range '[0,1]' excluding '1'. + // + double generate_double () { return generate () / 4294967295.0; } + + // Generate 'int' value in the range '[l,r]'. + // + int pick_int (int l, int r) { + CADICAL_assert (l <= r); + const unsigned delta = 1 + r - (unsigned) l; + unsigned tmp = generate (), scaled; + if (delta) { + const double fraction = tmp / 4294967296.0; + scaled = delta * fraction; + } else + scaled = tmp; + const int res = scaled + l; + CADICAL_assert (l <= res); + CADICAL_assert (res <= r); + return res; + } + + int pick_log (int l, int r) { + CADICAL_assert (l <= r); + const unsigned delta = 1 + r - (unsigned) l; + int log_delta = delta ? 0 : 32; + while (log_delta < 32 && (1u << log_delta) < delta) + log_delta++; + const int log_res = pick_int (0, log_delta); + unsigned tmp = generate (); + if (log_res < 32) + tmp &= (1u << log_res) - 1; + if (delta) + tmp %= delta; + const int res = l + tmp; + CADICAL_assert (l <= res), CADICAL_assert (res <= r); + return res; + } + + // Generate 'double' value in the range '[l,r]'. + // + double pick_double (double l, double r) { + CADICAL_assert (l <= r); + double res = (r - l) * generate_double (); + res += l; + CADICAL_assert (l <= res); + CADICAL_assert (res <= r); + return res; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/range.hpp b/src/sat/cadical/range.hpp new file mode 100644 index 000000000..7049dab42 --- /dev/null +++ b/src/sat/cadical/range.hpp @@ -0,0 +1,105 @@ +#ifndef _range_hpp_INCLUDED +#define _range_hpp_INCLUDED + +#include "global.h" + +#include <cassert> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Clause; + +/*----------------------------------------------------------------------*/ + +// Used for compact and safe iteration over positive ranges of integers, +// particularly for iterating over all variable indices. +// +// Range vars (max_var); +// for (auto idx : vars) ... +// +// This iterates over '1, ..., max_var' and is safe for non-negative +// numbers, thus also for 'max_var == 0' or 'max_var == INT_MAX'. +// +// Note that +// +// for (int idx = 1; idx <= max_var; idx++) ... +// +// leads to an overflow if 'max_var == INT_MAX' and thus depending on what +// the compiler does ('int' overflow is undefined) might lead to any +// behaviour (infinite loop or worse array access way out of bounds). +// +// If we make 'idx' in this last 'for' loop an 'unsigned' then it is safe to +// use this idiom, but we would need to cast 'max_var' explicitly to 'int' +// in order to avoid a warning in the loop condition and actually everywhere +// where 'idx' is compared to a 'signed' expression. Worse for instance +// 'vals[-idx]' will lead to out of bounds access too. This is awkward and +// using the range iterator provided here is safer in general. +// +// Another issue is that the dereferencing operator '*' below is required to +// return a reference to the internal index of the iterator. Thus the 'idx' +// in the auto loop is actually of the same type as the internal state of +// the iterator. To keep it 'signed' and still avoid overflow issues we +// just have to make sure to use the proper increment (with two implicit +// casts, i.e., from 'int' to 'unsigned', then 'unsigned' addition and the +// result is cast back from 'unsigned' to 'int'). +// +// For simplicity we keep a reference to the actual maximum integer, e.g., +// 'max_var', which makes the idiom 'for (auto idx : vars) ...' possible. +// Further note that the referenced integer has to be non-negative before +// starting to iterate (it can be zero though), otherwise it breaks. + +class Range { + static unsigned inc (unsigned u) { return u + 1u; } + class iterator { + int idx; + + public: + iterator (int i) : idx (i) {} + void operator++ () { idx = inc (idx); } + const int &operator* () const { return idx; } + friend bool operator!= (const iterator &a, const iterator &b) { + return a.idx != b.idx; + } + }; + int &n; + +public: + iterator begin () const { return CADICAL_assert (n >= 0), iterator (inc (0)); } + iterator end () const { return CADICAL_assert (n >= 0), iterator (inc (n)); } + Range (int &m) : n (m) { CADICAL_assert (m >= 0); } +}; + +// Same, but iterating over literals '-1,1,-2,2,....,-max_var,max_var'. +// +// The only difference to 'Range' is the 'inc' function, but I am too lazy +// to figure out how to properly factor the code into a generic range +// template with 'inc' as only parameter. This gives at least clean code. + +class Sange { + static unsigned inc (unsigned u) { return ~u + (u >> 31); } + class iterator { + int lit; + + public: + iterator (int i) : lit (i) {} + void operator++ () { lit = inc (lit); } + const int &operator* () const { return lit; } + friend bool operator!= (const iterator &a, const iterator &b) { + return a.lit != b.lit; + } + }; + int &n; + +public: + iterator begin () const { return CADICAL_assert (n >= 0), iterator (inc (0)); } + iterator end () const { return CADICAL_assert (n >= 0), iterator (inc (n)); } + Sange (int &m) : n (m) { CADICAL_assert (m >= 0); } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/reap.hpp b/src/sat/cadical/reap.hpp new file mode 100644 index 000000000..1ea10e6e3 --- /dev/null +++ b/src/sat/cadical/reap.hpp @@ -0,0 +1,34 @@ +#ifndef _reap_h_INCLUDED +#define _reap_h_INCLUDED + +#include "global.h" + +#include <cstddef> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +class Reap { +public: + Reap (); + void init (); + void release (); + inline bool empty () { return !num_elements; } + + inline size_t size () { return num_elements; } + + void push (unsigned); + void clear (); + unsigned pop (); + +private: + size_t num_elements; + unsigned last_deleted; + unsigned min_bucket; + unsigned max_bucket; + std::vector<unsigned> buckets[33]; +}; + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/reluctant.hpp b/src/sat/cadical/reluctant.hpp new file mode 100644 index 000000000..495382b56 --- /dev/null +++ b/src/sat/cadical/reluctant.hpp @@ -0,0 +1,88 @@ +#ifndef _reluctant_hpp_INCLUDED +#define _reluctant_hpp_INCLUDED + +#include "global.h" + +#include <cassert> +#include <cstdint> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// This is Donald Knuth's version of the Luby restart sequence which he +// called 'reluctant doubling'. His bit-twiddling formulation in line (DK) +// requires to keep two words around which are updated every time the +// reluctant doubling sequence is advanced. The original version in the +// literature uses a complex recursive function which computes the length of +// the next inactive sub-sequence every time (but is state-less). +// +// In our code we incorporate a base interval 'period' and only after period +// many calls to 'tick' times the current sequence value we update the +// reluctant doubling sequence value. The 'tick' call is decoupled from +// the activation signal of the sequence (the 'bool ()' operator) through +// 'trigger'. It is also possible to set an upper limit to the length of an +// inactive sub-sequence. If that limit is reached the whole reluctant +// doubling sequence starts over with the initial values. + +class Reluctant { + + uint64_t u, v, limit; + uint64_t period, countdown; + bool trigger, limited; + +public: + Reluctant () : period (0), trigger (false) {} + + void enable (int p, int64_t l) { + CADICAL_assert (p > 0); + u = v = 1; + period = countdown = p; + trigger = false; + if (l <= 0) + limited = false; + else + limited = true, limit = l; + }; + + void disable () { period = 0, trigger = false; } + + // Increments the count until the 'period' is hit. Then it performs the + // actual increment of reluctant doubling. This gives the common 'Luby' + // sequence with the specified base interval period. As soon the limit is + // reached (countdown goes to zero) we remember this event and then + // disable updating the reluctant sequence until the signal is delivered. + + void tick () { + + if (!period) + return; // disabled + if (trigger) + return; // already triggered + if (--countdown) + return; // not there yet + + if ((u & -u) == v) + u = u + 1, v = 1; + else + v = 2 * v; // (DK) + + if (limited && v >= limit) + u = v = 1; + countdown = v * period; + trigger = true; + } + + operator bool () { + if (!trigger) + return false; + trigger = false; + return true; + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/resources.hpp b/src/sat/cadical/resources.hpp new file mode 100644 index 000000000..852668b32 --- /dev/null +++ b/src/sat/cadical/resources.hpp @@ -0,0 +1,22 @@ +#ifndef _resources_hpp_INCLUDED +#define _resources_hpp_INCLUDED + +#include "global.h" + +#include <cstdint> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +double absolute_real_time (); +double absolute_process_time (); + +uint64_t maximum_resident_set_size (); +uint64_t current_resident_set_size (); + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif // ifndef _resources_hpp_INCLUDED diff --git a/src/sat/cadical/score.hpp b/src/sat/cadical/score.hpp new file mode 100644 index 000000000..88196121e --- /dev/null +++ b/src/sat/cadical/score.hpp @@ -0,0 +1,22 @@ +#ifndef _score_hpp_INCLUDED +#define _score_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct score_smaller { + Internal *internal; + score_smaller (Internal *i) : internal (i) {} + bool operator() (unsigned a, unsigned b); +}; + +typedef heap<score_smaller> ScoreSchedule; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/signal.hpp b/src/sat/cadical/signal.hpp new file mode 100644 index 000000000..1dc2fb3b7 --- /dev/null +++ b/src/sat/cadical/signal.hpp @@ -0,0 +1,39 @@ +#ifndef _signal_hpp_INCLUDED +#define _signal_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Helper class for handling signals in applications. + +class Handler { +public: + Handler () {} + virtual ~Handler () {} + virtual void catch_signal (int sig) = 0; +#ifndef WIN32 + virtual void catch_alarm (); +#endif +}; + +class Signal { + +public: + static void set (Handler *); + static void reset (); +#ifndef WIN32 + static void alarm (int seconds); + static void reset_alarm (); +#endif + + static const char *name (int sig); +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/stack.h b/src/sat/cadical/stack.h new file mode 100644 index 000000000..89f270388 --- /dev/null +++ b/src/sat/cadical/stack.h @@ -0,0 +1,116 @@ +#ifndef _stack_h_INCLUDED +#define _stack_h_INCLUDED + +#include "global.h" + +#include <stdlib.h> + +ABC_NAMESPACE_HEADER_START + +#define STACK(TYPE) \ + struct { \ + TYPE *begin; \ + TYPE *end; \ + TYPE *allocated; \ + } + +#define FULL_STACK(S) ((S).end == (S).allocated) +#define EMPTY_STACK(S) ((S).begin == (S).end) +#define SIZE_STACK(S) ((size_t) ((S).end - (S).begin)) +#define CAPACITY_STACK(S) ((size_t) ((S).allocated - (S).begin)) + +#define INIT_STACK(S) \ + do { \ + (S).begin = (S).end = (S).allocated = 0; \ + } while (0) + +#define TOP_STACK(S) (END_STACK (S)[CADICAL_assert (!EMPTY_STACK (S)), -1]) + +#define PEEK_STACK(S, N) \ + (BEGIN_STACK (S)[CADICAL_assert ((N) < SIZE_STACK (S)), (N)]) + +#define POKE_STACK(S, N, E) \ + do { \ + PEEK_STACK (S, N) = (E); \ + } while (0) + +#define POP_STACK(S) (CADICAL_assert (!EMPTY_STACK (S)), *--(S).end) + +#define PUSH_STACK(S, E) \ + do { \ + if (FULL_STACK (S)) \ + ENLARGE_STACK (S); \ + *(S).end++ = (E); \ + } while (0) + +#define BEGIN_STACK(S) (S).begin + +#define END_STACK(S) (S).end + +#define CLEAR_STACK(S) \ + do { \ + (S).end = (S).begin; \ + } while (0) + +#define RESIZE_STACK(S, NEW_SIZE) \ + do { \ + const size_t TMP_NEW_SIZE = (NEW_SIZE); \ + CADICAL_assert (TMP_NEW_SIZE <= SIZE_STACK (S)); \ + (S).end = (S).begin + TMP_NEW_SIZE; \ + } while (0) + +#define SET_END_OF_STACK(S, P) \ + do { \ + CADICAL_assert (BEGIN_STACK (S) <= (P)); \ + CADICAL_assert ((P) <= END_STACK (S)); \ + if ((P) == END_STACK (S)) \ + break; \ + (S).end = (P); \ + } while (0) + +#define RELEASE_STACK(S) \ + do { \ + DEALLOC ((S).begin, CAPACITY_STACK (S)); \ + INIT_STACK (S); \ + } while (0) + +#define REMOVE_STACK(T, S, E) \ + do { \ + CADICAL_assert (!EMPTY_STACK (S)); \ + T *END_REMOVE_STACK = END_STACK (S); \ + T *P_REMOVE_STACK = BEGIN_STACK (S); \ + while (*P_REMOVE_STACK != (E)) { \ + P_REMOVE_STACK++; \ + CADICAL_assert (P_REMOVE_STACK != END_REMOVE_STACK); \ + } \ + P_REMOVE_STACK++; \ + while (P_REMOVE_STACK != END_REMOVE_STACK) { \ + P_REMOVE_STACK[-1] = *P_REMOVE_STACK; \ + P_REMOVE_STACK++; \ + } \ + (S).end--; \ + } while (0) + +#define all_stack(T, E, S) \ + T E, *E##_PTR = BEGIN_STACK (S), *const E##_END = END_STACK (S); \ + E##_PTR != E##_END && (E = *E##_PTR, true); \ + ++E##_PTR + +#define all_pointers(T, E, S) \ + T *E, *const *E##_PTR = BEGIN_STACK (S), \ + *const *const E##_END = END_STACK (S); \ + E##_PTR != E##_END && (E = *E##_PTR, true); \ + ++E##_PTR + +// clang-format off + +typedef STACK (char) chars; +typedef STACK (int) ints; +typedef STACK (size_t) sizes; +typedef STACK (unsigned) unsigneds; + +// clang-format on + +ABC_NAMESPACE_HEADER_END + +#endif diff --git a/src/sat/cadical/stats.hpp b/src/sat/cadical/stats.hpp new file mode 100644 index 000000000..4a6afa9c9 --- /dev/null +++ b/src/sat/cadical/stats.hpp @@ -0,0 +1,376 @@ +#ifndef _stats_hpp_INCLUDED +#define _stats_hpp_INCLUDED + +#include "global.h" + +#include <cstdint> +#include <cstdlib> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct Stats { + + Internal *internal; + + int64_t vars = 0; // internal initialized variables + + int64_t conflicts = 0; // generated conflicts in 'propagate' + int64_t decisions = 0; // number of decisions in 'decide' + + struct { + int64_t cover = 0; // propagated during covered clause elimination + int64_t instantiate = 0; // propagated during variable instantiation + int64_t probe = 0; // propagated during probing + int64_t search = 0; // propagated literals during search + int64_t transred = 0; // propagated during transitive reduction + int64_t vivify = 0; // propagated during vivification + int64_t walk = 0; // propagated during local search + } propagations; + + struct { + int64_t search[2] = {0}; + int64_t factor = 0; + int64_t probe = 0; + int64_t sweep = 0; + int64_t ternary = 0; + int64_t vivify = 0; + } ticks; + + struct { + int64_t ext_cb = 0; // number of times any external callback was called + int64_t eprop_call = 0; // number of times external_propagate was called + int64_t eprop_prop = 0; // number of times external propagate propagated + int64_t eprop_conf = + 0; // number of times ex-propagate was already falsified + int64_t eprop_expl = + 0; // number of times external propagate was explained + int64_t elearn_call = + 0; // number of times external clause learning was tried + int64_t elearned = + 0; // learned external clauses (incl. eprop explanations) + int64_t elearn_prop = + 0; // number of learned and propagating external clauses + int64_t elearn_conf = + 0; // number of learned and conflicting external clauses + int64_t echeck_call = 0; // number of checking found complete solutions + } ext_prop; + + int64_t condassinit = 0; // initial assigned literals + int64_t condassirem = 0; // initial assigned literals for blocked + int64_t condassrem = 0; // remaining assigned literals for blocked + int64_t condassvars = 0; // sum of active variables at initial assignment + int64_t condautinit = 0; // initial literals in autarky part + int64_t condautrem = 0; // remaining literals in autarky part for blocked + int64_t condcands = 0; // globally blocked candidate clauses + int64_t condcondinit = 0; // initial literals in conditional part + int64_t condcondrem = + 0; // remaining literals in conditional part for blocked + int64_t conditioned = 0; // globally blocked clauses eliminated + int64_t conditionings = 0; // globally blocked clause eliminations + int64_t condprops = 0; // propagated unassigned literals + + struct { + int64_t block = 0; // block marked literals + int64_t elim = 0; // elim marked variables + int64_t subsume = 0; // subsume marked variables + int64_t ternary = 0; // ternary marked variables + int64_t factor = 0; + } mark; + + struct { + int64_t total = 0; + int64_t redundant = 0; + int64_t irredundant = 0; + } current, added; // Clauses. + + struct { + double process = 0, real = 0; + } time; + + struct { + int64_t count = 0; // number of covered clause elimination rounds + int64_t asymmetric = 0; // number of asymmetric tautologies in CCE + int64_t blocked = 0; // number of blocked covered tautologies + int64_t total = 0; // total number of eliminated clauses + } cover; + + struct { + int64_t tried = 0; + int64_t succeeded = 0; + struct { + int64_t one = 0, zero = 0; + } constant, forward, backward; + struct { + int64_t positive = 0, negative = 0; + } horn; + } lucky; + + struct { + int64_t total = 0; // total number of happened rephases + int64_t best = 0; // how often reset to best phases + int64_t flipped = 0; // how often reset phases by flipping + int64_t inverted = 0; // how often reset to inverted phases + int64_t original = 0; // how often reset to original phases + int64_t random = 0; // how often randomly reset phases + int64_t walk = 0; // phases improved through random walked + } rephased; + + struct { + int64_t count = 0; + int64_t broken = 0; + int64_t flips = 0; + int64_t minimum = 0; + } walk; + + struct { + int64_t count = 0; // flushings of learned clauses counter + int64_t learned = 0; // flushed learned clauses + int64_t hyper = 0; // flushed hyper binary/ternary clauses + } flush; + + int64_t compacts = 0; // number of compactifications + int64_t shuffled = 0; // shuffled queues and scores + int64_t restarts = 0; // actual number of happened restarts + int64_t restartlevels = 0; // levels at restart + int64_t restartstable = 0; // actual number of happened restarts + int64_t stabphases = 0; // number of stabilization phases + int64_t stabconflicts = + 0; // number of search conflicts during stabilizing + int64_t rescored = 0; // number of times scores were rescored + int64_t reused = 0; // number of reused trails + int64_t reusedlevels = 0; // reused levels at restart + int64_t reusedstable = 0; // number of reused trails during stabilizing + int64_t sections = 0; // 'section' counter + int64_t chrono = 0; // chronological backtracks + int64_t backtracks = 0; // number of backtracks + int64_t improvedglue = 0; // improved glue during bumping + int64_t promoted1 = 0; // promoted clauses to tier one + int64_t promoted2 = 0; // promoted clauses to tier two + int64_t bumped = 0; // seen and bumped variables in 'analyze' + int64_t recomputed = 0; // recomputed glues 'recompute_glue' + int64_t searched = 0; // searched decisions in 'decide' + int64_t reductions = 0; // 'reduce' counter + int64_t reduced = 0; // number of reduced clauses + int64_t reduced_sqrt = 0; + int64_t reduced_prct = 0; + int64_t collected = 0; // number of collected bytes + int64_t collections = 0; // number of garbage collections + int64_t hbrs = 0; // hyper binary resolvents + int64_t hbrsizes = 0; // sum of hyper resolved base clauses + int64_t hbreds = 0; // redundant hyper binary resolvents + int64_t hbrsubs = 0; // subsuming hyper binary resolvents + int64_t instried = 0; // number of tried instantiations + int64_t instantiated = 0; // number of successful instantiations + int64_t instrounds = 0; // number of instantiation rounds + int64_t subsumed = 0; // number of subsumed clauses + int64_t deduplicated = 0; // number of removed duplicated binary clauses + int64_t deduplications = 0; // number of deduplication phases + int64_t strengthened = 0; // number of strengthened clauses + + int64_t eliminated_equi = + 0; // number of successful equivalence eliminations + int64_t eliminated_and = 0; // number of successful AND gate eliminations + int64_t eliminated_ite = 0; // number of successful ITE gate eliminations + int64_t eliminated_xor = 0; // number of successful XOR gate eliminations + int64_t eliminated_def = + 0; // number of successful definition eliminations + + int64_t definitions_checked = 0; + int64_t definitions_extracted = 0; + int64_t definition_units = 0; + int64_t definition_ticks = 0; + + int64_t factor = 0; + int64_t factored = 0; + int64_t factor_added = 0; + int64_t variables_extension = 0; + int64_t variables_original = 0; + int64_t literals_factored = 0; + int64_t clauses_unfactored = 0; + int64_t literals_unfactored = 0; + + int64_t elimotfstr = + 0; // number of on-the-fly strengthened during elimination + int64_t subirr = 0; // number of subsumed irredundant clauses + int64_t subred = 0; // number of subsumed redundant clauses + int64_t subtried = 0; // number of tried subsumptions + int64_t subchecks = 0; // number of pair-wise subsumption checks + int64_t subchecks2 = 0; // same but restricted to binary clauses + int64_t elimotfsub = + 0; // number of on-the-fly subsumed during elimination + int64_t subsumerounds = 0; // number of subsumption rounds + int64_t subsumephases = 0; // number of scheduled subsumption phases + int64_t eagertried = 0; // number of traversed eager subsumed candidates + int64_t eagersub = + 0; // number of eagerly subsumed recently learned clauses + int64_t elimres = 0; // number of resolved clauses in BVE + int64_t elimrestried = 0; // number of tried resolved clauses in BVE + int64_t elimfastrounds = 0; // number of elimination rounds + int64_t elimrounds = 0; // number of elimination rounds + int64_t elimphases = 0; // number of scheduled elimination phases + int64_t elimfastphases = 0; // number of scheduled elimination phases + int64_t elimcompleted = 0; // number complete elimination procedures + int64_t elimtried = 0; // number of variable elimination attempts + int64_t elimsubst = 0; // number of eliminations through substitutions + int64_t elimgates = 0; // number of gates found during elimination + int64_t elimequivs = 0; // number of equivalences found during elimination + int64_t elimands = 0; // number of AND gates found during elimination + int64_t elimites = 0; // number of ITE gates found during elimination + int64_t elimxors = 0; // number of XOR gates found during elimination + int64_t elimbwsub = 0; // number of eager backward subsumed clauses + int64_t elimbwstr = 0; // number of eager backward strengthened clauses + int64_t ternary = 0; // number of ternary resolution phases + int64_t ternres = 0; // number of ternary resolutions + int64_t htrs = 0; // number of hyper ternary resolvents + int64_t htrs2 = 0; // number of binary hyper ternary resolvents + int64_t htrs3 = 0; // number of ternary hyper ternary resolvents + int64_t decompositions = 0; // number of SCC + ELS + int64_t vivifications = 0; // number of vivifications + int64_t vivifychecks = 0; // checked clauses during vivification + int64_t vivifydecs = 0; // vivification decisions + int64_t vivifyreused = 0; // reused vivification decisions + int64_t vivifysched = 0; // scheduled clauses for vivification + int64_t vivifysubs = 0; // subsumed clauses during vivification + int64_t vivifysubred = 0; // subsumed clauses during vivification + int64_t vivifysubirr = 0; // subsumed clauses during vivification + int64_t vivifystrs = 0; // strengthened clauses during vivification + int64_t vivifystrirr = 0; // strengthened irredundant clause + int64_t vivifystred1 = 0; // strengthened redundant clause (1) + int64_t vivifystred2 = 0; // strengthened redundant clause (2) + int64_t vivifystred3 = 0; // strengthened redundant clause (3) + int64_t vivifyunits = 0; // units during vivification + int64_t vivifyimplied = 0; // implied during vivification + int64_t vivifyinst = 0; // instantiation during vivification + int64_t vivifydemote = 0; // demoting during vivification + int64_t transreds = 0; + int64_t transitive = 0; + struct { + int64_t literals = 0; + int64_t clauses = 0; + } learned; + int64_t minimized = 0; // minimized literals + int64_t shrunken = 0; // shrunken literals + int64_t minishrunken = 0; // shrunken during minimization literals + + int64_t irrlits = 0; // literals in irredundant clauses + struct { + int64_t bytes = 0; + int64_t clauses = 0; + int64_t literals = 0; + } garbage; + + int64_t sweep_units = 0; + int64_t sweep_flip_backbone = 0; + int64_t sweep_fixed_backbone = 0; + int64_t sweep_flipped_backbone = 0; + int64_t sweep_solved_backbone = 0; + int64_t sweep_sat_backbone = 0; + int64_t sweep_unsat_backbone = 0; + int64_t sweep_unknown_backbone = 0; + int64_t sweep_flip_equivalences = 0; + int64_t sweep_flipped_equivalences = 0; + int64_t sweep_sat_equivalences = 0; + int64_t sweep_unsat_equivalences = 0; + int64_t sweep_unknown_equivalences = 0; + int64_t sweep_solved_equivalences = 0; + int64_t sweep_equivalences = 0; + int64_t sweep_variables = 0; + int64_t sweep_completed = 0; + int64_t sweep_solved = 0; + int64_t sweep_sat = 0; + int64_t sweep_unsat = 0; + int64_t sweep_depth = 0; + int64_t sweep_environment = 0; + int64_t sweep_clauses = 0; + int64_t sweep = 0; + + int64_t units = 0; // learned unit clauses + int64_t binaries = 0; // learned binary clauses + int64_t inprobingphases = 0; // number of scheduled probing phases + int64_t probingrounds = 0; // number of probing rounds + int64_t inprobesuccess = 0; // number successful probing phases + int64_t probed = 0; // number of probed literals + int64_t failed = 0; // number of failed literals + int64_t hyperunary = 0; // hyper unary resolved unit clauses + int64_t probefailed = 0; // failed literals from probing + int64_t transredunits = 0; // units derived in transitive reduction + int64_t blockings = 0; // number of blocked clause eliminations + int64_t blocked = 0; // number of actually blocked clauses + int64_t blockres = 0; // number of resolutions during blocking + int64_t blockcands = 0; // number of clause / pivot pairs tried + int64_t blockpured = 0; // number of clauses blocked through pure literals + int64_t blockpurelits = 0; // number of pure literals + int64_t extensions = 0; // number of extended witnesses + int64_t extended = 0; // number of flipped literals during extension + int64_t weakened = 0; // number of clauses pushed to extension stack + int64_t weakenedlen = 0; // lengths of weakened clauses + int64_t restorations = 0; // number of restore calls + int64_t restored = 0; // number of restored clauses + int64_t reactivated = 0; // number of reactivated clauses + int64_t restoredlits = 0; // number of restored literals + + int64_t preprocessings = 0; + + int64_t ilbtriggers = 0; + int64_t ilbsuccess = 0; + int64_t levelsreused = 0; + int64_t literalsreused = 0; + int64_t assumptionsreused = 0; + int64_t tierecomputed = 0; // number of tier recomputation; + + struct { + int64_t fixed = 0; // number of top level assigned variables + int64_t eliminated = 0; // number of eliminated variables + int64_t fasteliminated = 0; // number of fast eliminated variables only + int64_t substituted = 0; // number of substituted variables + int64_t pure = 0; // number of pure literals + } all, now; + + struct { + int64_t strengthened = 0; // number of clauses strengthened during OTFS + int64_t subsumed = 0; // number of clauses subsumed by OTFS + } otfs; + + int64_t unused = 0; // number of unused variables + int64_t active = 0; // number of active variables + int64_t inactive = 0; // number of inactive variables + std::vector<uint64_t> bump_used = {0, 0}; + std::vector<std::vector<uint64_t>> used; // used clauses in focused mode + + struct { + int64_t gates = 0; + int64_t ands = 0; + int64_t ites = 0; + int64_t xors = 0; + int64_t units = 0; + int64_t congruent = 0; + int64_t rounds = 0; + int64_t unary_and = 0; + int64_t unaries = 0; + int64_t rewritten_ands = 0; + int64_t simplified = 0; + int64_t simplified_ands = 0; + int64_t simplified_xors = 0; + int64_t simplified_ites = 0; + int64_t subsumed = 0; + int64_t trivial_ite = 0; + int64_t unary_ites = 0; + } congruence; + + Stats (); + + void print (Internal *); +}; + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/sweep.hpp b/src/sat/cadical/sweep.hpp new file mode 100644 index 000000000..ad093e070 --- /dev/null +++ b/src/sat/cadical/sweep.hpp @@ -0,0 +1,67 @@ +#ifndef _sweep_hpp_INCLUDED +#define _sweep_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +struct sweep_proof_clause { + unsigned sweep_id; // index for sweeper.clauses + int64_t cad_id; // cadical id + unsigned kit_id; // cadical_kitten id + bool learned; + vector<int> literals; + vector<unsigned> chain; // lrat +}; + +struct sweep_blocked_clause { + int blit; + int64_t id; + vector<int> literals; +}; + +struct sweep_binary { + int lit; + int other; + int64_t id; +}; + +struct Sweeper { + Sweeper (Internal *internal); + ~Sweeper (); + Internal *internal; + Random random; + vector<unsigned> depths; + int *reprs; + vector<int> next, prev; + int first, last, blit; + unsigned encoded; + unsigned save; + vector<int> vars; + vector<Clause *> clauses; + vector<sweep_blocked_clause> blocked_clauses; + bool flush_blocked_clauses; + vector<int> blockable; + vector<int> clause; + vector<int> propagate; + vector<int> backbone; + vector<int> partition; + vector<bool> prev_units; + vector<sweep_binary> binaries; + vector<sweep_proof_clause> core[2]; + uint64_t current_ticks; + struct { + uint64_t ticks; + unsigned clauses, depth, vars; + } limit; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/terminal.hpp b/src/sat/cadical/terminal.hpp new file mode 100644 index 000000000..a99346b7c --- /dev/null +++ b/src/sat/cadical/terminal.hpp @@ -0,0 +1,104 @@ +#ifndef _terminal_hpp_INCLUDED +#define _terminal_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class Terminal { + + FILE *file; // 'stdout' or 'stderr' + bool connected; // Connected to terminal. + bool use_colors; // Use colors. + bool reset_on_exit; // Reset on exit. + + void escape () { + CADICAL_assert (connected); + fputs ("\033[", file); + } + + void color (int color, bool bright) { + if (!use_colors) + return; + CADICAL_assert (connected); + escape (); + fputc (bright ? '1' : '0', file); + fprintf (file, ";%dm", color); + fflush (file); + } + + void code (const char *str) { + if (!use_colors) + return; + if (!connected) + return; + escape (); + fputs (str, file); + fflush (file); + } + +public: + Terminal (FILE *file); + ~Terminal (); + + void disable (); // Assume disconnected in any case. + void force_colors (); + void force_no_colors (); + void force_reset_on_exit (); + + bool colors () { return use_colors; } + + operator bool () const { return connected; } + + void red (bool bright = false) { color (31, bright); } + void green (bool bright = false) { color (32, bright); } + void yellow (bool bright = false) { color (33, bright); } + void blue (bool bright = false) { color (34, bright); } + void magenta (bool bright = false) { color (35, bright); } + void black (bool bright = false) { color (90, bright); } + void cyan (bool bright = false) { color (96, bright); } + + void bold () { code ("1m"); } + void normal () { code ("0m"); } + void inverse () { code ("7m"); } + void underline () { code ("4m"); } + +#define MODIFY(CODE) (use_colors ? "\033[" CODE "m" : "") + + const char *bright_magenta_code () { return MODIFY ("1;35"); } + const char *magenta_code () { return MODIFY ("0;35"); } + const char *blue_code () { return MODIFY ("0;34"); } + const char *bright_blue_code () { return MODIFY ("1;34"); } + const char *yellow_code () { return MODIFY ("0;33"); } + const char *bright_yellow_code () { return MODIFY ("1;33"); } + const char *green_code () { return MODIFY ("0;32"); } + const char *red_code () { return MODIFY ("0;31"); } + const char *cyan_code () { return MODIFY ("0;96"); } + const char *bright_red_code () { return MODIFY ("1;31"); } + const char *normal_code () { return MODIFY ("0"); } + const char *bold_code () { return MODIFY ("1"); } + + void cursor (bool on) { code (on ? "?25h" : "?25l"); } + + void erase_until_end_of_line () { code ("K"); } + + void erase_line_if_connected_otherwise_new_line () { + if (connected) + code ("1G"); + else + fputc ('\n', file), fflush (file); + } + + void reset (); +}; + +extern Terminal tout; // Terminal of 'stdout' (file descriptor '1') +extern Terminal terr; // Terminal of 'stderr' (file descriptor '2') + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/testing.hpp b/src/sat/cadical/testing.hpp new file mode 100644 index 000000000..f90569e5c --- /dev/null +++ b/src/sat/cadical/testing.hpp @@ -0,0 +1,30 @@ +#ifndef _testing_hpp_INCLUDED +#define _testing_hpp_INCLUDED + +#include "global.h" + +// This class provides access to 'internal' which should only be used for +// internal testing and not for any other purpose as 'internal' is supposed +// to be hidden. + +#include "cadical.hpp" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +class Testing { + Solver *solver; + +public: + Testing (Solver &s) : solver (&s) {} + Testing (Solver *s) : solver (s) {} + Internal *internal () { return solver->internal; } + External *external () { return solver->external; } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/tracer.hpp b/src/sat/cadical/tracer.hpp new file mode 100644 index 000000000..d1a9f1137 --- /dev/null +++ b/src/sat/cadical/tracer.hpp @@ -0,0 +1,183 @@ +#ifndef _tracer_hpp_INCLUDED +#define _tracer_hpp_INCLUDED + +#include "global.h" + +#include <cstdint> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Internal; + +enum ConclusionType { CONFLICT = 1, ASSUMPTIONS = 2, CONSTRAINT = 4 }; + +// Proof tracer class to observer all possible proof events, +// such as added or deleted clauses. +// An implementation can decide on which events to act. +// +class Tracer { + +public: + Tracer () {} + virtual ~Tracer () {} + + /*------------------------------------------------------------------------*/ + /* */ + /* Basic Events */ + /* */ + /*------------------------------------------------------------------------*/ + + // Notify the tracer that a original clause has been added. + // Includes ID and whether the clause is redundant or irredundant + // Arguments: ID, redundant, clause, restored + // + virtual void add_original_clause (int64_t, bool, const std::vector<int> &, + bool = false) {} + + // Notify the observer that a new clause has been derived. + // Includes ID and whether the clause is redundant or irredundant + // If antecedents are derived they will be included here. + // Arguments: ID, redundant, clause, antecedents + // + virtual void add_derived_clause (int64_t, bool, const std::vector<int> &, + const std::vector<int64_t> &) {} + + // Notify the observer that a clause is deleted. + // Includes ID and redundant/irredundant + // Arguments: ID, redundant, clause + // + virtual void delete_clause (int64_t, bool, const std::vector<int> &) {} + + // Notify the observer that a clause is deleted. + // Includes ID and redundant/irredundant + // Arguments: ID, redundant, clause + // + virtual void demote_clause (uint64_t, const std::vector<int> &) {} + + // Notify the observer to remember that the clause might be restored later + // Arguments: ID, clause + // + virtual void weaken_minus (int64_t, const std::vector<int> &) {} + + // Notify the observer that a clause is strengthened + // Arguments: ID + // + virtual void strengthen (int64_t) {} + + // Notify the observer that the solve call ends with status StatusType + // If the status is UNSAT and an empty clause has been derived, the second + // argument will contain its id. + // Note that the empty clause is already added through add_derived_clause + // and finalized with finalize_clause + // Arguments: int, ID + // + virtual void report_status (int, int64_t) {} + + /*------------------------------------------------------------------------*/ + /* */ + /* Specifically non-incremental */ + /* */ + /*------------------------------------------------------------------------*/ + + // Notify the observer that a clause is finalized. + // Arguments: ID, clause + // + virtual void finalize_clause (int64_t, const std::vector<int> &) {} + + // Notify the observer that the proof begins with a set of reserved ids + // for original clauses. Given ID is the first derived clause ID. + // Arguments: ID + // + virtual void begin_proof (int64_t) {} + + /*------------------------------------------------------------------------*/ + /* */ + /* Specifically incremental */ + /* */ + /*------------------------------------------------------------------------*/ + + // Notify the observer that an assumption has been added + // Arguments: assumption_literal + // + virtual void solve_query () {} + + // Notify the observer that an assumption has been added + // Arguments: assumption_literal + // + virtual void add_assumption (int) {} + + // Notify the observer that a constraint has been added + // Arguments: constraint_clause + // + virtual void add_constraint (const std::vector<int> &) {} + + // Notify the observer that assumptions and constraints are reset + // + virtual void reset_assumptions () {} + + // Notify the observer that this clause could be derived, which + // is the negation of a core of failing assumptions/constraints. + // If antecedents are derived they will be included here. + // Arguments: ID, clause, antecedents + // + virtual void add_assumption_clause (int64_t, const std::vector<int> &, + const std::vector<int64_t> &) {} + + // Notify the observer that conclude unsat was requested. + // will give either the id of the empty clause, the id of a failing + // assumption clause or the ids of the failing constrain clauses + // Arguments: conclusion_type, clause_ids + // + virtual void conclude_unsat (ConclusionType, + const std::vector<int64_t> &) {} + + // Notify the observer that conclude sat was requested. + // will give the complete model as a vector. + // + virtual void conclude_sat (const std::vector<int> &) {} + + // Notify the observer that conclude unknown was requested. + // will give the current trail as a vector. + // + virtual void conclude_unknown (const std::vector<int> &) {} +}; + +/*--------------------------------------------------------------------------*/ + +// Following tracers for internal use. + +struct InternalTracer : public Tracer { +public: + InternalTracer () {} + virtual ~InternalTracer () {} + + virtual void connect_internal (Internal *) {} +}; + +class StatTracer : public InternalTracer { +public: + StatTracer () {} + virtual ~StatTracer () {} + + virtual void print_stats () {} +}; + +class FileTracer : public InternalTracer { + +public: + FileTracer () {} + virtual ~FileTracer () {} + + virtual bool closed () = 0; + virtual void close (bool print = false) = 0; + virtual void flush (bool print = false) = 0; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/util.hpp b/src/sat/cadical/util.hpp new file mode 100644 index 000000000..3882c6ede --- /dev/null +++ b/src/sat/cadical/util.hpp @@ -0,0 +1,173 @@ +#ifndef _util_hpp_INCLUDED +#define _util_hpp_INCLUDED + +#include "global.h" + +#include <cassert> +#include <cstdint> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +using namespace std; + +// Common simple utility functions independent from 'Internal'. + +/*------------------------------------------------------------------------*/ + +inline double relative (double a, double b) { return b ? a / b : 0; } +inline double percent (double a, double b) { return relative (100 * a, b); } +inline int sign (int lit) { return (lit > 0) - (lit < 0); } +inline unsigned bign (int lit) { return 1 + (lit < 0); } + +/*------------------------------------------------------------------------*/ + +bool has_suffix (const char *str, const char *suffix); +bool has_prefix (const char *str, const char *prefix); + +/*------------------------------------------------------------------------*/ + +// Parse integer string in the form of +// +// true +// false +// [-]<mantissa>[e<exponent>] +// +// and in the latter case '<val>' has to be within [-INT_MAX,INT_MAX]. +// +// The function returns true if parsing is successful and then also sets +// the second argument to the parsed value. + +bool parse_int_str (const char *str, int &); + +/*------------------------------------------------------------------------*/ + +inline bool is_power_of_two (unsigned n) { return n && !(n & (n - 1)); } + +inline bool contained (int64_t c, int64_t l, int64_t u) { + return l <= c && c <= u; +} + +inline bool aligned (size_t n, size_t alignment) { + CADICAL_assert (is_power_of_two (alignment)); + const size_t mask = alignment - 1; + return !(n & mask); +} + +inline size_t align (size_t n, size_t alignment) { + size_t res = n; + CADICAL_assert (is_power_of_two (alignment)); + const size_t mask = alignment - 1; + if (res & mask) + res = (res | mask) + 1; + CADICAL_assert (aligned (res, alignment)); + return res; +} + +/*------------------------------------------------------------------------*/ + +inline bool parity (unsigned a) { + CADICAL_assert (sizeof a == 4); + unsigned tmp = a; + tmp ^= (tmp >> 16); + tmp ^= (tmp >> 8); + tmp ^= (tmp >> 4); + tmp ^= (tmp >> 2); + tmp ^= (tmp >> 1); + return tmp & 1; +} + +/*------------------------------------------------------------------------*/ + +// The standard 'Effective STL' way (though not guaranteed) to clear a +// vector and reduce its capacity to zero, thus deallocating all its +// internal memory. This is quite important for keeping the actual +// allocated size of watched and occurrence lists small particularly during +// bounded variable elimination where many clauses are added and removed. + +template <class T> void erase_vector (std::vector<T> &v) { + if (v.capacity ()) { + std::vector<T> ().swap (v); + } + CADICAL_assert (!v.capacity ()); // not guaranteed though +} + +// The standard 'Effective STL' way (though not guaranteed) to shrink the +// capacity of a vector to its size thus kind of releasing all the internal +// excess memory not needed at the moment any more. + +template <class T> void shrink_vector (std::vector<T> &v) { + if (v.capacity () > v.size ()) { + std::vector<T> (v).swap (v); + } + CADICAL_assert (v.capacity () == v.size ()); // not guaranteed though +} + +template <class T> +static void enlarge_init (vector<T> &v, size_t N, const T &i) { + if (v.size () < N) + v.resize (N, i); +} + +template <class T> static void enlarge_only (vector<T> &v, size_t N) { + if (v.size () < N) + v.resize (N, T ()); +} + +template <class T> static void enlarge_zero (vector<T> &v, size_t N) { + enlarge_init (v, N, (const T &) 0); +} + +// Clean-up class for bad_alloc error safety. + +template <typename T> struct DeferDeleteArray { + T *data; + DeferDeleteArray (T *t) : data (t) {} + ~DeferDeleteArray () { delete[] data; } + void release () { data = nullptr; } + void free () { + delete[] data; + data = nullptr; + } +}; + +template <typename T> struct DeferDeletePtr { + T *data; + DeferDeletePtr (T *t) : data (t) {} + ~DeferDeletePtr () { delete data; } + void release () { data = nullptr; } + void free () { + delete data; + data = nullptr; + } +}; + +/*------------------------------------------------------------------------*/ + +template <class T> inline void clear_n (T *base, size_t n) { + memset (base, 0, sizeof (T) * n); +} + +/*------------------------------------------------------------------------*/ + +// These are options both to 'cadical' and 'mobical'. After wasting some +// on not remembering the spelling (British vs American), nor singular vs +// plural and then wanted to use '--color=false', and '--colours=0' too, I +// just factored this out into these two utility functions. + +bool is_color_option (const char *arg); // --color, --colour, ... +bool is_no_color_option (const char *arg); // --no-color, --no-colour, ... + +/*------------------------------------------------------------------------*/ + +uint64_t hash_string (const char *str); + +/*------------------------------------------------------------------------*/ + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/var.hpp b/src/sat/cadical/var.hpp new file mode 100644 index 000000000..8793afbc4 --- /dev/null +++ b/src/sat/cadical/var.hpp @@ -0,0 +1,28 @@ +#ifndef _var_hpp_INCLUDED +#define _var_hpp_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Clause; + +// This structure captures data associated with an assigned variable. + +struct Var { + + // Note that none of these members is valid unless the variable is + // assigned. During unassigning a variable we do not reset it. + + int level; // decision level + int trail; // trail height at assignment + Clause *reason; // implication graph edge during search +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/veripbtracer.hpp b/src/sat/cadical/veripbtracer.hpp new file mode 100644 index 000000000..b9c47362f --- /dev/null +++ b/src/sat/cadical/veripbtracer.hpp @@ -0,0 +1,108 @@ +#ifndef _veripbtracer_h_INCLUDED +#define _veripbtracer_h_INCLUDED + +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +class FileTracer; + +namespace CaDiCaL { + +struct HashId { + HashId *next; // collision chain link for hash table + uint64_t hash; // previously computed full 64-bit hash + int64_t id; // id of clause +}; + +class VeripbTracer : public FileTracer { + + Internal *internal; + File *file; +#ifndef CADICAL_NDEBUG + bool binary; +#endif + bool with_antecedents; + bool checked_deletions; + + // hash table for checked deletions + // + uint64_t num_clauses; // number of clauses in hash table + uint64_t size_clauses; // size of clause hash table + HashId **clauses; // hash table of clauses + + static const unsigned num_nonces = 4; + + uint64_t nonces[num_nonces]; // random numbers for hashing + uint64_t last_hash; // last computed hash value of clause + int64_t last_id; // id of the last added clause + HashId *last_clause; + uint64_t compute_hash (int64_t); // compute and save hash value of clause + + HashId *new_clause (); + void delete_clause (HashId *); + + // Reduce hash value to the actual size. + // + static uint64_t reduce_hash (uint64_t hash, uint64_t size); + + void enlarge_clauses (); // enlarge hash table for clauses + void insert (); // insert clause in hash table + bool + find_and_delete (const int64_t); // find clause position in hash table + +#ifndef CADICAL_QUIET + int64_t added, deleted; +#endif + vector<int64_t> delete_ids; + + void put_binary_zero (); + void put_binary_lit (int external_lit); + void put_binary_id (int64_t id, bool = false); + + // support veriPB + void veripb_add_derived_clause (int64_t, bool redundant, + const vector<int> &clause, + const vector<int64_t> &chain); + void veripb_add_derived_clause (int64_t, bool redundant, + const vector<int> &clause); + void veripb_begin_proof (int64_t reserved_ids); + void veripb_delete_clause (int64_t id, bool redundant); + void veripb_report_status (bool unsat, int64_t conflict_id); + void veripb_strengthen (int64_t); + +public: + // own and delete 'file' + VeripbTracer (Internal *, File *file, bool, bool, bool); + ~VeripbTracer (); + + void connect_internal (Internal *i) override; + void begin_proof (int64_t) override; + + void add_original_clause (int64_t, bool, const vector<int> &, + bool = false) override {} // skip + + void add_derived_clause (int64_t, bool, const vector<int> &, + const vector<int64_t> &) override; + + void delete_clause (int64_t, bool, const vector<int> &) override; + void finalize_clause (int64_t, const vector<int> &) override {} // skip + + void report_status (int, int64_t) override; + + void weaken_minus (int64_t, const vector<int> &) override; + void strengthen (int64_t) override; + +#ifndef CADICAL_QUIET + void print_statistics (); +#endif + bool closed () override; + void close (bool) override; + void flush (bool) override; +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/version.hpp b/src/sat/cadical/version.hpp new file mode 100644 index 000000000..d63aefa35 --- /dev/null +++ b/src/sat/cadical/version.hpp @@ -0,0 +1,19 @@ +#include "global.h" + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +const char *version (); +const char *copyright (); +const char *authors (); +const char *affiliations (); +const char *signature (); +const char *identifier (); +const char *compiler (); +const char *date (); +const char *flags (); + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END diff --git a/src/sat/cadical/vivify.hpp b/src/sat/cadical/vivify.hpp new file mode 100644 index 000000000..a5c6c0e10 --- /dev/null +++ b/src/sat/cadical/vivify.hpp @@ -0,0 +1,68 @@ +#ifndef _vivify_hpp_INCLUDED +#define _vivify_hpp_INCLUDED + +#include "global.h" + +#include "util.hpp" + +#include <array> +#include <cstdint> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +struct Clause; + +enum class Vivify_Mode { TIER1, TIER2, TIER3, IRREDUNDANT }; + +#define COUNTREF_COUNTS 2 + +struct vivify_ref { + bool vivify; + std::size_t size; + uint64_t count[COUNTREF_COUNTS]; + Clause *clause; +}; + +// In the vivifier structure, we put the schedules in an array in order to +// be able to iterate over them, but we provide the reference to them to +// make sure that you do need to remember the order. +struct Vivifier { + std::array<std::vector<vivify_ref>, 4> refs_schedules; + std::vector<vivify_ref> &refs_schedule_tier1, &refs_schedule_tier2, + &refs_schedule_tier3, &refs_schedule_irred; + std::array<std::vector<Clause *>, 4> schedules; + std::vector<Clause *> &schedule_tier1, &schedule_tier2, &schedule_tier3, + &schedule_irred; + std::vector<int> sorted; + Vivify_Mode tier; + char tag; + int tier1_limit; + int tier2_limit; + int64_t ticks; + std::vector<std::tuple<int, Clause *, bool>> lrat_stack; + Vivifier (Vivify_Mode mode_tier) + : refs_schedule_tier1 (refs_schedules[0]), + refs_schedule_tier2 (refs_schedules[1]), + refs_schedule_tier3 (refs_schedules[2]), + refs_schedule_irred (refs_schedules[3]), + schedule_tier1 (schedules[0]), schedule_tier2 (schedules[1]), + schedule_tier3 (schedules[2]), schedule_irred (schedules[3]), + tier (mode_tier) {} + + void erase () { + erase_vector (refs_schedule_tier1); + erase_vector (refs_schedule_tier2); + erase_vector (refs_schedule_tier3); + erase_vector (refs_schedule_irred); + erase_vector (sorted); + } +}; + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif diff --git a/src/sat/cadical/watch.hpp b/src/sat/cadical/watch.hpp new file mode 100644 index 000000000..1acab094b --- /dev/null +++ b/src/sat/cadical/watch.hpp @@ -0,0 +1,78 @@ +#ifndef _watch_hpp_INCLUDED +#define _watch_hpp_INCLUDED + +#include "global.h" + +#include <cassert> +#include <vector> + +ABC_NAMESPACE_CXX_HEADER_START + +namespace CaDiCaL { + +// Watch lists for CDCL search. The blocking literal (see also comments +// related to 'propagate') is a must and thus combining that with a 64 bit +// pointer will give a 16 byte (8 byte aligned) structure anyhow, which +// means the additional 4 bytes for the size come for free. As alternative +// one could use a 32-bit reference instead of the pointer which would +// however limit the number of clauses to '2^32 - 1'. One would also need +// to use at least one more bit (either taken away from the variable space +// or the clauses) to denote whether the watch is binary. + +// in fashion of Intel Sat 10.4230/LIPIcs.SAT.2022.8 we try to +// guarantee the following invariant: +// For both watches: +// if the watched literal is negatively assigned +// either it will be propagated in the future +// or the corresponding blocking literal is positively assigned +// and its level is smaller than the level of the watched literal +// + +struct Clause; + +struct Watch { + + Clause *clause; + int blit; + int size; + + Watch (int b, Clause *c) : clause (c), blit (b), size (c->size) {} + Watch () {} + + bool binary () const { return size == 2; } +}; + +typedef vector<Watch> Watches; // of one literal + +typedef Watches::iterator watch_iterator; +typedef Watches::const_iterator const_watch_iterator; + +inline void remove_watch (Watches &ws, Clause *clause) { + const auto end = ws.end (); + auto i = ws.begin (); + for (auto j = i; j != end; j++) { + const Watch &w = *i++ = *j; + if (w.clause == clause) + i--; + } + CADICAL_assert (i + 1 == end); + ws.resize (i - ws.begin ()); +} + +// search for the clause and updates the size marked in the watch lists +inline void update_watch_size (Watches &ws, int blit, Clause *conflict) { + bool found = false; + const int size = conflict->size; + for (Watch &w : ws) { + if (w.clause == conflict) + w.size = size, w.blit = blit, found = true; + CADICAL_assert (w.clause->garbage || w.size == 2 || w.clause->size != 2); + } + CADICAL_assert (found), (void) found; +} + +} // namespace CaDiCaL + +ABC_NAMESPACE_CXX_HEADER_END + +#endif