use std::collections::{BTreeMap, BTreeSet};
use rustsat::types::Lit;
#[derive(Clone)]
pub struct MusDict {
muses: BTreeMap<Lit, BTreeSet<MusContext>>,
}
impl Default for MusDict {
fn default() -> Self {
Self::new()
}
}
impl MusDict {
#[must_use]
pub fn new() -> Self {
MusDict {
muses: BTreeMap::new(),
}
}
pub fn add_mus(&mut self, lit: Lit, new_mus: BTreeSet<Lit>) {
if let Some(mus_list) = self.muses.get_mut(&lit) {
let len = if let Some(element) = mus_list.iter().next() {
element.mus_len()
} else {
usize::MAX
};
if new_mus.len() < len {
mus_list.clear();
mus_list.insert(MusContext::new(lit, new_mus));
} else if new_mus.len() == len {
mus_list.insert(MusContext::new(lit, new_mus));
}
} else {
let hs: BTreeSet<_> = std::iter::once(MusContext::new(lit, new_mus)).collect();
self.muses.insert(lit, hs);
}
}
#[must_use]
pub fn min_lit(&self, lit: Lit) -> Option<usize> {
if let Some(mus_list) = self.muses.get(&lit) {
mus_list.iter().next().map(MusContext::mus_len)
} else {
None
}
}
#[must_use]
pub fn muses(&self) -> &BTreeMap<Lit, BTreeSet<MusContext>> {
&self.muses
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.muses.is_empty()
}
#[must_use]
pub fn min(&self) -> Option<usize> {
self.muses
.values()
.flat_map(|sets| sets.iter().map(MusContext::mus_len))
.min()
}
#[must_use]
pub fn min_filtered(&self, valid_lits: &BTreeSet<Lit>) -> Option<usize> {
self.muses
.iter()
.filter(|(lit, _)| valid_lits.contains(lit))
.flat_map(|(_, sets)| sets.iter().map(MusContext::mus_len))
.min()
}
pub fn merge(&mut self, other: &MusDict) {
for (lit, mus_set) in &other.muses {
for mc in mus_set {
self.add_mus(*lit, mc.mus.clone());
}
}
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
pub struct MusContext {
pub lits: BTreeSet<Lit>,
pub mus: BTreeSet<Lit>,
}
impl MusContext {
#[must_use]
pub fn new(l: Lit, mus: BTreeSet<Lit>) -> Self {
Self {
lits: BTreeSet::from([l]),
mus,
}
}
#[must_use]
pub fn new_multi_lit(lits: BTreeSet<Lit>, mus: BTreeSet<Lit>) -> Self {
Self { lits, mus }
}
#[must_use]
pub fn new_with_more_lits(mut lits: BTreeSet<Lit>, mc: &Self) -> Self {
for l in &mc.lits {
lits.insert(*l);
}
Self {
lits,
mus: mc.mus.clone(),
}
}
#[must_use]
pub fn mus_len(&self) -> usize {
self.mus.len()
}
}
#[must_use]
pub fn merge_muscontexts(v: &[MusContext]) -> Vec<MusContext> {
let mut mus_map: BTreeMap<&BTreeSet<Lit>, BTreeSet<Lit>> = BTreeMap::new();
for mc in v {
mus_map.entry(&mc.mus).or_default().extend(&mc.lits);
}
mus_map
.into_iter()
.map(|(mus, lits)| MusContext::new_multi_lit(lits, mus.clone()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let mus_dict = MusDict::new();
assert!(mus_dict.muses().is_empty());
assert_eq!(mus_dict.min(), None);
assert!(mus_dict.is_empty());
}
#[test]
fn test_add_mus_existing_literal_smaller_length() -> anyhow::Result<()> {
let mut mus_dict = MusDict::new();
let lit = Lit::from_ipasir(1)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(4)?]);
mus_dict.add_mus(lit, mus1.clone());
mus_dict.add_mus(lit, mus2.clone());
assert_eq!(mus_dict.min(), Some(1));
assert!(!mus_dict.is_empty());
Ok(())
}
#[test]
fn test_add_mus_existing_literal_equal_length() -> anyhow::Result<()> {
let mut mus_dict = MusDict::new();
let lit = Lit::from_ipasir(1)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(4)?, Lit::from_ipasir(5)?]);
mus_dict.add_mus(lit, mus1.clone());
mus_dict.add_mus(lit, mus2.clone());
let bts: BTreeSet<_> = vec![MusContext::new(lit, mus1), MusContext::new(lit, mus2)]
.into_iter()
.collect();
assert_eq!(mus_dict.muses().get(&lit), Some(&bts));
assert_eq!(mus_dict.min(), Some(2));
assert!(!mus_dict.is_empty());
Ok(())
}
#[test]
fn test_min_lit_existing_literal() -> anyhow::Result<()> {
let mut mus_dict = MusDict::new();
let lit = Lit::from_ipasir(1)?;
let lit2 = Lit::from_ipasir(2)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(4)?, Lit::from_ipasir(5)?]);
mus_dict.add_mus(lit, mus1.clone());
mus_dict.add_mus(lit, mus2.clone());
assert_eq!(mus_dict.min_lit(lit), Some(2));
assert_eq!(mus_dict.min_lit(lit2), None);
Ok(())
}
#[test]
fn test_min_lit_non_existing_literal() -> anyhow::Result<()> {
let mus_dict = MusDict::new();
let lit = Lit::from_ipasir(1)?;
assert_eq!(mus_dict.min_lit(lit), None);
Ok(())
}
#[test]
fn test_add_mus_existing_literal_larger_length() -> anyhow::Result<()> {
let mut mus_dict = MusDict::new();
let lit = Lit::from_ipasir(1)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(4)?]);
mus_dict.add_mus(lit, mus2.clone());
mus_dict.add_mus(lit, mus1.clone());
let bts: BTreeSet<_> = std::iter::once(MusContext::new(lit, mus2)).collect();
assert_eq!(mus_dict.muses().get(&lit), Some(&bts));
assert_eq!(mus_dict.min(), Some(1));
assert!(!mus_dict.is_empty());
Ok(())
}
#[test]
fn test_add_mus_new_literal() -> anyhow::Result<()> {
let mut mus_dict = MusDict::new();
let lit1 = Lit::from_ipasir(1)?;
let lit2 = Lit::from_ipasir(2)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(3)?, Lit::from_ipasir(4)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(5)?, Lit::from_ipasir(6)?]);
mus_dict.add_mus(lit1, mus1.clone());
mus_dict.add_mus(lit2, mus2.clone());
let bts1: BTreeSet<_> = std::iter::once(MusContext::new(lit1, mus1)).collect();
let bts2: BTreeSet<_> = std::iter::once(MusContext::new(lit2, mus2)).collect();
assert_eq!(mus_dict.muses().get(&lit1), Some(&bts1));
assert_eq!(mus_dict.min(), Some(2));
assert_eq!(mus_dict.muses().get(&lit2), Some(&bts2));
assert_eq!(mus_dict.min(), Some(2));
assert!(!mus_dict.is_empty());
Ok(())
}
#[test]
fn test_merge_muscontexts_empty() {
let v: Vec<MusContext> = Vec::new();
let result = merge_muscontexts(&v);
assert_eq!(result.len(), 0);
}
#[test]
fn test_merge_muscontexts_single_entry() -> anyhow::Result<()> {
let lit = Lit::from_ipasir(1)?;
let mus = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mc = MusContext::new(lit, mus);
let v = vec![mc.clone()];
let result = merge_muscontexts(&v);
assert_eq!(result.len(), 1);
assert_eq!(result[0], mc);
Ok(())
}
#[test]
fn test_merge_muscontexts_identical_mus() -> anyhow::Result<()> {
let lit1 = Lit::from_ipasir(1)?;
let lit2 = Lit::from_ipasir(2)?;
let mus = BTreeSet::from([Lit::from_ipasir(3)?, Lit::from_ipasir(4)?]);
let mc1 = MusContext::new(lit1, mus.clone());
let mc2 = MusContext::new(lit2, mus);
let v = vec![mc1, mc2];
let result = merge_muscontexts(&v);
assert_eq!(result.len(), 1);
assert_eq!(result[0].mus_len(), 2);
assert_eq!(result[0].lits.len(), 2);
assert!(result[0].lits.contains(&lit1));
assert!(result[0].lits.contains(&lit2));
Ok(())
}
#[test]
fn test_merge_muscontexts_different_mus() -> anyhow::Result<()> {
let lit = Lit::from_ipasir(1)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(2)?, Lit::from_ipasir(3)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(4)?, Lit::from_ipasir(5)?]);
let mc1 = MusContext::new(lit, mus1);
let mc2 = MusContext::new(lit, mus2);
let v = vec![mc1, mc2];
let result = merge_muscontexts(&v);
assert_eq!(result.len(), 2);
assert!(result[0].mus.contains(&Lit::from_ipasir(2)?));
assert!(result[1].mus.contains(&Lit::from_ipasir(4)?));
Ok(())
}
#[test]
fn test_merge_muscontexts_complex_case() -> anyhow::Result<()> {
let lit1 = Lit::from_ipasir(1)?;
let lit2 = Lit::from_ipasir(2)?;
let lit3 = Lit::from_ipasir(3)?;
let mus1 = BTreeSet::from([Lit::from_ipasir(10)?, Lit::from_ipasir(11)?]);
let mus2 = BTreeSet::from([Lit::from_ipasir(20)?, Lit::from_ipasir(21)?]);
let mc1 = MusContext::new(lit1, mus1.clone());
let mc2 = MusContext::new(lit2, mus1);
let mc3 = MusContext::new(lit3, mus2);
let v = vec![mc1, mc2, mc3];
let result = merge_muscontexts(&v);
assert_eq!(result.len(), 2);
let merged_entry = result.iter().find(|mc| mc.lits.contains(&lit1)).unwrap();
assert_eq!(merged_entry.lits.len(), 2);
assert!(merged_entry.lits.contains(&lit1));
assert!(merged_entry.lits.contains(&lit2));
let single_entry = result.iter().find(|mc| mc.lits.contains(&lit3)).unwrap();
assert_eq!(single_entry.lits.len(), 1);
assert!(single_entry.lits.contains(&lit3));
Ok(())
}
}