blob: 06f99f4fdea1ec084f322504a4d31cdf0bc4519d [file] [log] [blame] [edit]
//! Clause storage (unit and non-unit clauses).
use std::{convert::TryInto, mem::transmute};
use partial_ref::{partial, PartialRef};
use rustc_hash::FxHashMap as HashMap;
use smallvec::SmallVec;
use varisat_formula::{lit::LitIdx, Lit};
use varisat_internal_proof::ClauseHash;
use crate::{
context::{parts::*, Context},
processing::{process_step, CheckedProofStep},
sorted_lits::copy_canonical,
variables::{ensure_sampling_var, ensure_var},
CheckerError,
};
const INLINE_LITS: usize = 3;
/// Literals of a clause, either inline or an index into a buffer
pub struct ClauseLits {
length: LitIdx,
inline: [LitIdx; INLINE_LITS],
}
impl ClauseLits {
/// Create a new ClauseLits, storing them in the given buffer if necessary
fn new(lits: &[Lit], buffer: &mut Vec<Lit>) -> ClauseLits {
let mut inline = [0; INLINE_LITS];
let length = lits.len();
if length > INLINE_LITS {
inline[0] = buffer
.len()
.try_into()
.expect("exceeded maximal literal buffer size");
buffer.extend(lits);
} else {
let lits = unsafe {
// Lit is a repr(transparent) wrapper of LitIdx
#[allow(clippy::transmute_ptr_to_ptr)]
transmute::<&[Lit], &[LitIdx]>(lits)
};
inline[..length].copy_from_slice(lits);
}
ClauseLits {
length: length as LitIdx,
inline,
}
}
/// Returns the literals as a slice given a storage buffer
pub fn slice<'a, 'b, 'c>(&'a self, buffer: &'b [Lit]) -> &'c [Lit]
where
'a: 'c,
'b: 'c,
{
if self.length > INLINE_LITS as LitIdx {
&buffer[self.inline[0] as usize..][..self.length as usize]
} else {
unsafe {
// Lit is a repr(transparent) wrapper of LitIdx
#[allow(clippy::transmute_ptr_to_ptr)]
transmute::<&[LitIdx], &[Lit]>(&self.inline[..self.length as usize])
}
}
}
/// Literals stored in the literal buffer
fn buffer_used(&self) -> usize {
if self.length > INLINE_LITS as LitIdx {
self.length as usize
} else {
0
}
}
}
/// Literals and metadata for non-unit clauses.
pub struct Clause {
/// LRAT clause id.
pub id: u64,
/// How often the clause is present as irred., red. clause.
///
/// For checking the formula is a multiset of clauses. This is necessary as the generating
/// solver might not check for duplicated clauses.
ref_count: [u32; 2],
/// Clause's literals.
pub lits: ClauseLits,
}
/// Identifies the origin of a unit clause.
#[derive(Copy, Clone, Debug)]
pub enum UnitId {
Global(u64),
TracePos(usize),
InClause,
}
/// Known unit clauses and metadata.
#[derive(Copy, Clone, Debug)]
pub struct UnitClause {
pub id: UnitId,
pub value: bool,
}
/// Return type of [`store_clause`]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum StoreClauseResult {
New,
Duplicate,
NewlyIrredundant,
}
/// Return type of [`delete_clause`]
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum DeleteClauseResult {
Unchanged,
NewlyRedundant,
Removed,
}
/// Checker clause storage.
#[derive(Default)]
pub struct Clauses {
/// Next clause id to use.
pub next_clause_id: u64,
/// Literal storage for clauses,
pub literal_buffer: Vec<Lit>,
/// Number of literals in the buffer which are from deleted clauses.
garbage_size: usize,
/// Stores all known non-unit clauses indexed by their hash.
pub clauses: HashMap<ClauseHash, SmallVec<[Clause; 1]>>,
/// Stores known unit clauses and propagations during a clause check.
pub unit_clauses: Vec<Option<UnitClause>>,
/// This stores a conflict of input unit clauses.
///
/// Our representation for unit clauses doesn't support conflicting units so this is used as a
/// workaround.
pub unit_conflict: Option<[u64; 2]>,
}
impl Clauses {
/// Value of a literal if known from unit clauses.
pub fn lit_value(&self, lit: Lit) -> Option<(bool, UnitClause)> {
self.unit_clauses[lit.index()]
.map(|unit_clause| (unit_clause.value ^ lit.is_negative(), unit_clause))
}
}
/// Adds a clause to the checker.
pub fn add_clause<'a>(
mut ctx: partial!(Context<'a>, mut ClausesP, mut CheckerStateP, mut ProcessingP<'a>, mut TmpDataP, mut VariablesP, ClauseHasherP),
clause: &[Lit],
) -> Result<(), CheckerError> {
if ctx.part(CheckerStateP).unsat {
return Ok(());
}
let (tmp_data, mut ctx) = ctx.split_part_mut(TmpDataP);
if copy_canonical(&mut tmp_data.tmp, clause) {
let (clauses, mut ctx) = ctx.split_part_mut(ClausesP);
process_step(
ctx.borrow(),
&CheckedProofStep::TautologicalClause {
id: clauses.next_clause_id,
clause: &tmp_data.tmp,
},
)?;
clauses.next_clause_id += 1;
return Ok(());
}
for &lit in tmp_data.tmp.iter() {
ensure_sampling_var(ctx.borrow(), lit.var())?;
}
let (id, added) = store_clause(ctx.borrow(), &tmp_data.tmp, false);
let (clauses, mut ctx) = ctx.split_part_mut(ClausesP);
match added {
StoreClauseResult::New => {
process_step(
ctx.borrow(),
&CheckedProofStep::AddClause {
id,
clause: &tmp_data.tmp,
},
)?;
}
StoreClauseResult::NewlyIrredundant | StoreClauseResult::Duplicate => {
if let StoreClauseResult::NewlyIrredundant = added {
process_step(
ctx.borrow(),
&CheckedProofStep::MakeIrredundant {
id,
clause: &tmp_data.tmp,
},
)?;
}
process_step(
ctx.borrow(),
&CheckedProofStep::DuplicatedClause {
id: clauses.next_clause_id,
same_as_id: id,
clause: &tmp_data.tmp,
},
)?;
// This is a duplicated clause. We want to ensure that the clause ids match the input
// order so we skip a clause id.
clauses.next_clause_id += 1;
}
}
Ok(())
}
/// Adds a clause to the checker data structures.
///
/// `lits` must be sorted and free of duplicates.
///
/// Returns the id of the added clause and indicates whether the clause is new or changed from
/// redundant to irredundant.
pub fn store_clause(
mut ctx: partial!(
Context,
mut CheckerStateP,
mut ClausesP,
mut VariablesP,
ClauseHasherP
),
lits: &[Lit],
redundant: bool,
) -> (u64, StoreClauseResult) {
for &lit in lits.iter() {
ensure_var(ctx.borrow(), lit.var());
}
match lits[..] {
[] => {
let id = ctx.part(ClausesP).next_clause_id;
ctx.part_mut(ClausesP).next_clause_id += 1;
ctx.part_mut(CheckerStateP).unsat = true;
(id, StoreClauseResult::New)
}
[lit] => store_unit_clause(ctx.borrow(), lit),
_ => {
let hash = ctx.part(ClauseHasherP).clause_hash(&lits);
let (clauses, mut ctx) = ctx.split_part_mut(ClausesP);
let candidates = clauses.clauses.entry(hash).or_default();
for candidate in candidates.iter_mut() {
if candidate.lits.slice(&clauses.literal_buffer) == &lits[..] {
let result = if !redundant && candidate.ref_count[0] == 0 {
// first irredundant copy
StoreClauseResult::NewlyIrredundant
} else {
StoreClauseResult::Duplicate
};
let ref_count = &mut candidate.ref_count[redundant as usize];
*ref_count = ref_count.checked_add(1).expect("ref_count overflow");
return (candidate.id, result);
}
}
let id = clauses.next_clause_id;
let mut ref_count = [0, 0];
ref_count[redundant as usize] += 1;
candidates.push(Clause {
id,
ref_count,
lits: ClauseLits::new(&lits, &mut clauses.literal_buffer),
});
clauses.next_clause_id += 1;
for &lit in lits.iter() {
ctx.part_mut(VariablesP).lit_data[lit.code()].clause_count += 1;
}
(id, StoreClauseResult::New)
}
}
}
/// Adds a unit clause to the checker data structures.
///
/// Returns the id of the added clause and a boolean that is true if the clause wasn't already
/// present.
pub fn store_unit_clause(
mut ctx: partial!(Context, mut CheckerStateP, mut ClausesP),
lit: Lit,
) -> (u64, StoreClauseResult) {
match ctx.part(ClausesP).lit_value(lit) {
Some((
true,
UnitClause {
id: UnitId::Global(id),
..
},
)) => (id, StoreClauseResult::Duplicate),
Some((
false,
UnitClause {
id: UnitId::Global(conflicting_id),
..
},
)) => {
ctx.part_mut(CheckerStateP).unsat = true;
let id = ctx.part(ClausesP).next_clause_id;
ctx.part_mut(ClausesP).unit_conflict = Some([conflicting_id, id]);
ctx.part_mut(ClausesP).next_clause_id += 1;
(id, StoreClauseResult::New)
}
Some(_) => unreachable!(),
None => {
let id = ctx.part(ClausesP).next_clause_id;
ctx.part_mut(ClausesP).unit_clauses[lit.index()] = Some(UnitClause {
value: lit.is_positive(),
id: UnitId::Global(id),
});
ctx.part_mut(ClausesP).next_clause_id += 1;
(id, StoreClauseResult::New)
}
}
}
/// Delete a clause from the current formula.
///
/// `lits` must be sorted and free of duplicates.
///
/// Returns the id of the deleted clause and whether the ref_count (or irredundant ref_count)
/// became zero.
pub fn delete_clause(
mut ctx: partial!(
Context,
mut ClausesP,
mut VariablesP,
CheckerStateP,
ClauseHasherP
),
lits: &[Lit],
redundant: bool,
) -> Result<(u64, DeleteClauseResult), CheckerError> {
if lits.len() < 2 {
return Err(CheckerError::check_failed(
ctx.part(CheckerStateP).step,
format!("delete of unit or empty clause {:?}", lits),
));
}
let hash = ctx.part(ClauseHasherP).clause_hash(lits);
let clauses = ctx.part_mut(ClausesP);
let candidates = clauses.clauses.entry(hash).or_default();
let mut found = false;
let mut result = None;
let literal_buffer = &clauses.literal_buffer;
let garbage_size = &mut clauses.garbage_size;
candidates.retain(|candidate| {
if found || candidate.lits.slice(literal_buffer) != lits {
true
} else {
found = true;
let ref_count = &mut candidate.ref_count[redundant as usize];
if *ref_count == 0 {
true
} else {
*ref_count -= 1;
if candidate.ref_count == [0, 0] {
*garbage_size += candidate.lits.buffer_used();
result = Some((candidate.id, DeleteClauseResult::Removed));
false
} else {
if !redundant && candidate.ref_count[0] == 0 {
result = Some((candidate.id, DeleteClauseResult::NewlyRedundant));
} else {
result = Some((candidate.id, DeleteClauseResult::Unchanged));
}
true
}
}
}
});
if candidates.is_empty() {
clauses.clauses.remove(&hash);
}
if let Some((_, DeleteClauseResult::Removed)) = result {
for &lit in lits.iter() {
ctx.part_mut(VariablesP).lit_data[lit.code()].clause_count -= 1;
}
}
if let Some(result) = result {
collect_garbage(ctx.borrow());
return Ok(result);
}
let msg = match (found, redundant) {
(false, _) => format!("delete of unknown clause {:?}", lits),
(_, true) => format!("delete of redundant clause {:?} which is irredundant", lits),
(_, false) => format!("delete of irredundant clause {:?} which is redundant", lits),
};
Err(CheckerError::check_failed(
ctx.part(CheckerStateP).step,
msg,
))
}
/// Perform a garbage collection if required
fn collect_garbage(mut ctx: partial!(Context, mut ClausesP)) {
let clauses = ctx.part_mut(ClausesP);
if clauses.garbage_size * 2 <= clauses.literal_buffer.len() {
return;
}
let mut new_buffer = vec![];
new_buffer.reserve(clauses.literal_buffer.len());
for (_, candidates) in clauses.clauses.iter_mut() {
for clause in candidates.iter_mut() {
let new_lits =
ClauseLits::new(clause.lits.slice(&clauses.literal_buffer), &mut new_buffer);
clause.lits = new_lits;
}
}
clauses.literal_buffer = new_buffer;
clauses.garbage_size = 0;
}