1#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
2#![allow(clippy::similar_names)]
3use crate::sat::assignment::Assignment;
18use crate::sat::clause::Clause;
19use crate::sat::clause_management::ClauseManagementImpls::LbdActivityClauseManagement;
20use crate::sat::clause_storage::LiteralStorage;
21use crate::sat::cnf::Cnf;
22use crate::sat::literal::Literal;
23use crate::sat::propagation::Propagator;
24use crate::sat::trail::Trail;
25use clap::ValueEnum;
26use rustc_hash::{FxHashMap, FxHashSet};
27use std::fmt::{Debug, Display};
28
29const DECAY_FACTOR: f64 = 0.95;
31
32pub trait ClauseManagement: Clone + Debug + Default {
42 fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self;
49
50 fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>);
59
60 fn should_clean_db(&self) -> bool;
68
69 fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
82 &mut self,
83 cnf: &mut Cnf<L, S>,
84 trail: &mut Trail<L, S>,
85 propagator: &mut P,
86 assignment: &mut A,
87 );
88
89 fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
99 &mut self,
100 cnf: &mut Cnf<L, S>,
101 c_ref: usize,
102 );
103
104 fn num_removed(&self) -> usize;
106}
107
108#[derive(Debug, Clone, Default, PartialEq)]
123pub struct LbdClauseManagement<const N: usize> {
124 interval: usize,
126 conflicts_since_last_cleanup: usize,
128 num_removed: usize,
130
131 candidates: Vec<(usize, u32, f64)>,
133 indices_to_remove: FxHashSet<usize>,
135 new_learnt_clauses: Vec<Vec<i32>>,
137 old_to_new_idx_map: FxHashMap<usize, usize>,
139}
140
141impl<const N: usize> ClauseManagement for LbdClauseManagement<N> {
142 fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self {
147 let initial_capacity = clauses.len();
148 let candidates = Vec::with_capacity(initial_capacity);
149
150 let mut indices_to_remove = FxHashSet::default();
151 indices_to_remove.reserve(initial_capacity);
152
153 let new_learnt_clauses = Vec::with_capacity(initial_capacity);
154
155 let mut old_to_new_idx_map = FxHashMap::default();
156 old_to_new_idx_map.reserve(initial_capacity);
157
158 Self {
159 interval: N,
160 conflicts_since_last_cleanup: 0,
161 num_removed: 0,
162 candidates,
163 indices_to_remove,
164 new_learnt_clauses,
165 old_to_new_idx_map,
166 }
167 }
168
169 fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>) {
171 self.conflicts_since_last_cleanup = self.conflicts_since_last_cleanup.wrapping_add(1);
172 Self::decay_clause_activities(cnf);
173 }
174
175 fn should_clean_db(&self) -> bool {
177 self.conflicts_since_last_cleanup >= self.interval
178 }
179
180 fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
194 &mut self,
195 cnf: &mut Cnf<L, S>,
196 trail: &mut Trail<L, S>,
197 propagator: &mut P,
198 _assignment: &mut A,
199 ) {
200 let learnt_start_idx = cnf.non_learnt_idx;
201 if cnf.len() <= learnt_start_idx {
202 return;
203 }
204
205 self.candidates.clear();
206 let locked_clauses = trail.get_locked_clauses();
207
208 for idx in learnt_start_idx..cnf.len() {
209 if !locked_clauses.contains(&idx) {
210 let clause = &cnf[idx];
211 self.candidates.push((idx, clause.lbd, clause.activity()));
212 }
213 }
214
215 if self.candidates.is_empty() {
216 return;
217 }
218
219 let num_candidates = self.candidates.len();
220 let num_to_remove = num_candidates / 2;
221
222 if num_to_remove == 0 {
223 return;
224 }
225
226 let comparison = |a: &(usize, u32, f64), b: &(usize, u32, f64)| {
227 let (_, lbd_a, act_a) = a;
228 let (_, lbd_b, act_b) = b;
229
230 let keep_a_heuristic = *lbd_a <= 2;
231 let keep_b_heuristic = *lbd_b <= 2;
232
233 if keep_a_heuristic && !keep_b_heuristic {
234 return std::cmp::Ordering::Greater;
235 }
236 if !keep_a_heuristic && keep_b_heuristic {
237 return std::cmp::Ordering::Less;
238 }
239
240 match lbd_a.cmp(lbd_b) {
241 std::cmp::Ordering::Equal => act_a
243 .partial_cmp(act_b)
244 .unwrap_or(std::cmp::Ordering::Equal),
245 other_lbd_cmp => other_lbd_cmp.reverse(),
246 }
247 };
248
249 self.candidates
250 .select_nth_unstable_by(num_to_remove, comparison);
251
252 self.indices_to_remove.clear();
253 for (idx, _, _) in self.candidates.iter().take(num_to_remove) {
254 self.indices_to_remove.insert(*idx);
255 }
256
257 let mut new_learnt_clauses = Vec::with_capacity(num_to_remove);
258 self.old_to_new_idx_map.clear();
259
260 let mut current_new_idx = learnt_start_idx;
261 for old_idx in learnt_start_idx..cnf.len() {
262 if !self.indices_to_remove.contains(&old_idx) {
263 new_learnt_clauses.push(cnf[old_idx].clone());
264 self.old_to_new_idx_map.insert(old_idx, current_new_idx);
265 current_new_idx = current_new_idx.wrapping_add(1);
266 }
267 }
268
269 let new_learnt_count = self.new_learnt_clauses.len();
270 let new_total_len = learnt_start_idx.wrapping_add(new_learnt_count);
271
272 cnf.clauses.truncate(learnt_start_idx);
274 cnf.clauses.append(&mut new_learnt_clauses);
275
276 trail.remap_clause_indices(&self.old_to_new_idx_map);
277
278 propagator.cleanup_learnt(learnt_start_idx);
279 for new_idx in learnt_start_idx..new_total_len {
280 propagator.add_clause(&cnf[new_idx], new_idx);
282 }
283
284 self.conflicts_since_last_cleanup = 0;
285 self.num_removed = self.num_removed.wrapping_add(num_to_remove);
286 }
287
288 fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
292 &mut self,
293 cnf: &mut Cnf<L, S>,
294 c_ref: usize,
295 ) {
296 let clause = &mut cnf[c_ref];
297 clause.bump_activity(1.0);
298 }
299
300 fn num_removed(&self) -> usize {
302 self.num_removed
303 }
304}
305
306impl<const N: usize> LbdClauseManagement<N> {
307 fn decay_clause_activities<L: Literal, S: LiteralStorage<L>>(cnf: &mut Cnf<L, S>) {
310 for clause in &mut cnf.clauses[cnf.non_learnt_idx..] {
311 clause.decay_activity(DECAY_FACTOR);
312 }
313 }
314}
315
316#[derive(Debug, Clone, Default, PartialEq, Copy, Eq)]
325pub struct NoClauseManagement;
326
327impl ClauseManagement for NoClauseManagement {
328 fn new<L: Literal, S: LiteralStorage<L>>(_clauses: &[Clause<L, S>]) -> Self {
331 Self
332 }
333
334 fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, _cnf: &mut Cnf<L, S>) {}
336
337 fn should_clean_db(&self) -> bool {
339 false
340 }
341
342 fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
344 &mut self,
345 _cnf: &mut Cnf<L, S>,
346 _trail: &mut Trail<L, S>,
347 _propagator: &mut P,
348 _assignment: &mut A,
349 ) {
350 }
351
352 fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
354 &mut self,
355 _cnf: &mut Cnf<L, S>,
356 _c_ref: usize,
357 ) {
358 }
359
360 fn num_removed(&self) -> usize {
362 0
363 }
364}
365
366#[derive(Debug, Clone, PartialEq)]
368pub enum ClauseManagementImpls<const N: usize> {
369 NoClauseManagement(NoClauseManagement),
371 LbdActivityClauseManagement(LbdClauseManagement<N>),
373}
374
375impl<const N: usize> Default for ClauseManagementImpls<N> {
376 fn default() -> Self {
377 Self::NoClauseManagement(NoClauseManagement)
378 }
379}
380
381impl<const N: usize> ClauseManagement for ClauseManagementImpls<N> {
382 fn new<L: Literal, S: LiteralStorage<L>>(clauses: &[Clause<L, S>]) -> Self {
383 LbdActivityClauseManagement(LbdClauseManagement::new(clauses))
384 }
385
386 fn on_conflict<L: Literal, S: LiteralStorage<L>>(&mut self, cnf: &mut Cnf<L, S>) {
387 match self {
388 LbdActivityClauseManagement(m) => m.on_conflict(cnf),
389 Self::NoClauseManagement(m) => m.on_conflict(cnf),
390 }
391 }
392
393 fn should_clean_db(&self) -> bool {
394 match self {
395 LbdActivityClauseManagement(l) => l.should_clean_db(),
396 Self::NoClauseManagement(m) => m.should_clean_db(),
397 }
398 }
399
400 fn clean_clause_db<L: Literal, S: LiteralStorage<L>, P: Propagator<L, S, A>, A: Assignment>(
401 &mut self,
402 cnf: &mut Cnf<L, S>,
403 trail: &mut Trail<L, S>,
404 propagator: &mut P,
405 assignment: &mut A,
406 ) {
407 match self {
408 LbdActivityClauseManagement(m) => m.clean_clause_db(cnf, trail, propagator, assignment),
409 Self::NoClauseManagement(m) => m.clean_clause_db(cnf, trail, propagator, assignment),
410 }
411 }
412
413 fn bump_involved_clause_activities<L: Literal, S: LiteralStorage<L>>(
414 &mut self,
415 cnf: &mut Cnf<L, S>,
416 c_ref: usize,
417 ) {
418 match self {
419 LbdActivityClauseManagement(m) => m.bump_involved_clause_activities(cnf, c_ref),
420 Self::NoClauseManagement(m) => m.bump_involved_clause_activities(cnf, c_ref),
421 }
422 }
423
424 fn num_removed(&self) -> usize {
425 match self {
426 LbdActivityClauseManagement(m) => m.num_removed(),
427 Self::NoClauseManagement(m) => m.num_removed(),
428 }
429 }
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash, Default, ValueEnum)]
434pub enum ClauseManagementType {
435 NoClauseManagement,
437 #[default]
439 LbdActivityClauseManagement,
440}
441
442impl Display for ClauseManagementType {
443 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
444 match self {
445 Self::NoClauseManagement => write!(f, "No Clause Management"),
446 Self::LbdActivityClauseManagement => write!(f, "LBD and Activity Clause Management"),
447 }
448 }
449}
450
451impl ClauseManagementType {
452 #[allow(dead_code)]
454 pub fn to_impl<L: Literal, S: LiteralStorage<L>, const N: usize>(
455 self,
456 clauses: &[Clause<L, S>],
457 ) -> ClauseManagementImpls<N> {
458 match self {
459 Self::NoClauseManagement => {
460 ClauseManagementImpls::NoClauseManagement(NoClauseManagement::new(clauses))
461 }
462 Self::LbdActivityClauseManagement => {
463 LbdActivityClauseManagement(LbdClauseManagement::<N>::new(clauses))
464 }
465 }
466 }
467}