#include "internal.hpp" 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 (); 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); 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) { assert (0 < src); assert (src <= internal->max_var); const int res = table[src]; 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) { assert (first_fixed); res = map_first_fixed; if (tmp != first_fixed_val) res = -res; } } else if ((src) < 0) res = -res; 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; assert (0 < dst); 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; assert (0 < dst); 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)); 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); assert (active () < max_var); stats.compacts++; assert (!level); assert (!unsat); assert (!conflict); assert (clause.empty ()); assert (levels.empty ()); assert (analyzed.empty ()); assert (minimized.empty ()); assert (control.size () == 1); 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 ()) { assert (!external->assumptions.empty ()); LOG ("temporarily reset internal assumptions"); reset_assumptions (); } const bool is_constraint = !constraint.empty (); if (is_constraint) { 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'. // for (auto eidx : external->vars) { int src = external->e2i[eidx]; if (!src) continue; 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; } // Map the literals in all clauses. // for (const auto & c : clauses) { assert (!c->garbage); for (auto & src : *c) { assert (!val (src)); int dst; dst = mapper.map_lit (src); 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; 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. /*======================================================================*/ mapper.map_flush_and_shrink_lits (trail); propagated = trail.size (); if (mapper.first_fixed) { assert (trail.size () == 1); var (mapper.first_fixed).trail = 0; // before mapping 'vtab' } else assert (trail.empty ()); if (!probes.empty ()) mapper.map_flush_and_shrink_lits (probes); /*======================================================================*/ // 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 = mapper.map_idx (src); if (!dst) continue; if (src == dst) continue; assert (dst < src); frozentab[dst] += frozentab[src]; frozentab[src] = 0; } frozentab.resize (mapper.new_vsize); shrink_vector (frozentab); /*----------------------------------------------------------------------*/ if (!external->assumptions.empty ()) { for (const auto & elit : external->assumptions) { assert (elit); assert (elit != INT_MIN); int eidx = abs (elit); assert (eidx <= external->max_var); int ilit = external->e2i[eidx]; 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; } // 'constrain' uses 'val', so this code has to be after remapping that if (is_constraint) { assert (!level); assert (!external->constraint.back ()); for (auto elit : external->constraint){ assert (elit != INT_MIN); int eidx = abs (elit); assert (eidx <= external->max_var); int ilit = external->e2i[eidx]; assert (!ilit == !elit); if (elit < 0) ilit = -ilit; LOG ("re adding lit extrenal %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 (!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; 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; INIT_EMA (averages.current.trail.fast, opts.ematrailfast); INIT_EMA (averages.current.trail.slow, opts.ematrailslow); /*----------------------------------------------------------------------*/ max_var = mapper.new_max_var; vsize = mapper.new_vsize; 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); } }