use crate::{
c2d_lexer::{parse_alt_space1_number1, split_numbers},
d4_lexer::parse_signed_alt_space1_number1,
};
use nom::{
IResult, Parser,
branch::alt,
bytes::complete::tag,
character::complete::char,
combinator::{map, recognize, value},
multi::many1,
sequence::{preceded, terminated},
};
use std::cmp::max;
use std::collections::{BTreeSet, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader, Write};
use std::path::Path;
use std::process::exit;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CNFToken {
Comment,
Clause { features: BTreeSet<i32> },
Header { features: usize, clauses: usize },
}
use crate::util::format_vec;
#[inline]
pub fn check_for_cnf_header(line: &str) -> IResult<&str, CNFToken> {
alt((lex_comment, lex_header, lex_clause)).parse(line)
}
fn lex_header(line: &str) -> IResult<&str, CNFToken> {
map(
preceded(tag("p cnf"), parse_alt_space1_number1),
|out: &str| {
let nums: Vec<usize> = split_numbers(out);
CNFToken::Header {
features: nums[0],
clauses: nums[1],
}
},
)
.parse(line)
}
fn lex_clause(line: &str) -> IResult<&str, CNFToken> {
map(
terminated(recognize(many1(parse_signed_alt_space1_number1)), tag("0")),
|out: &str| CNFToken::Clause {
features: split_numbers(out).into_iter().collect(),
},
)
.parse(line)
}
fn lex_comment(line: &str) -> IResult<&str, CNFToken> {
value(CNFToken::Comment, char('c')).parse(line)
}
pub fn add_clause_cnf(path: &Path, clause: &[i32]) {
let file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let mut manipulated_cnf = Vec::new();
for line in lines {
let line = line.expect("Unable to read line");
match check_for_cnf_header(line.as_str()).unwrap().1 {
CNFToken::Header {
features, clauses, ..
} => {
let max_feature_number = max(
features,
clause.iter().map(|f| f.unsigned_abs()).max().unwrap() as usize,
);
manipulated_cnf.push(format!("p cnf {max_feature_number} {}", clauses + 1))
}
CNFToken::Comment | CNFToken::Clause { .. } => manipulated_cnf.push(line),
}
}
manipulated_cnf.push(format!("{} 0", format_vec(clause.iter())));
let mut wfile = File::create(path).unwrap();
wfile
.write_all(manipulated_cnf.join("\n").as_bytes())
.unwrap();
}
pub fn remove_tail_clauses_cnf(path: &str, total_features: Option<usize>, amount: usize) {
let mut file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let mut manipulated_cnf = Vec::new();
for line in lines {
let line = line.expect("Unable to read line");
match check_for_cnf_header(line.as_str()).unwrap().1 {
CNFToken::Header { features, clauses } => manipulated_cnf.push(format!(
"p cnf {} {}",
total_features.unwrap_or(features),
clauses - amount
)),
CNFToken::Comment | CNFToken::Clause { .. } => manipulated_cnf.push(line),
}
}
manipulated_cnf.truncate(manipulated_cnf.len() - amount);
file = File::create(path).unwrap();
file.write_all(manipulated_cnf.join("\n").as_bytes())
.unwrap();
}
pub fn remove_clause_cnf(path: &Path, clause: &[i32], total_features: Option<usize>) {
let mut file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let mut manipulated_cnf = Vec::new();
for line in lines {
let line = line.expect("Unable to read line");
match check_for_cnf_header(line.as_str()).unwrap().1 {
CNFToken::Header { features, clauses } => manipulated_cnf.push(format!(
"p cnf {} {}",
total_features.unwrap_or(features),
clauses - 1
)),
CNFToken::Comment | CNFToken::Clause { .. } => {
if line != format!("{} 0", format_vec(clause.iter())) {
manipulated_cnf.push(line)
}
}
}
}
file = File::create(path).unwrap();
file.write_all(manipulated_cnf.join("\n").as_bytes())
.unwrap();
}
pub fn get_all_clauses_cnf(path: &Path) -> Vec<Vec<i32>> {
let file = File::open(path).unwrap();
let lines = BufReader::new(file).lines();
let mut clauses = Vec::new();
for line in lines {
let line = line.expect("Unable to read line");
if let CNFToken::Clause { .. } = check_for_cnf_header(line.as_str()).unwrap().1 {
let mut clause: Vec<i32> = line
.split(' ')
.filter(|split| !split.is_empty())
.map(|num| match num.parse::<i32>() {
Ok(v) => v,
Err(e) => {
eprintln!("Error: {e} while trying to parse num: {num}, in line: {line}");
exit(1)
}
})
.collect();
clause.remove(clause.len() - 1); clauses.push(clause);
}
}
clauses
}
pub fn simplify_clauses(mut clauses: Vec<Vec<i32>>) -> Vec<Vec<i32>> {
let mut clause_set: Vec<HashSet<i32>> = Vec::with_capacity(clauses.len());
for inner_vec in clauses.iter_mut() {
clause_set.push(inner_vec.drain(..).collect());
}
clause_set.retain(|clause| clause.iter().all(|elem| !clause.contains(&-elem)));
let mut decisions = HashSet::new();
clause_set.iter().for_each(|clause| {
if clause.len() == 1 {
decisions.insert(clause.clone().into_iter().collect::<Vec<_>>()[0]);
}
});
let mut simplified_clauses = Vec::with_capacity(clause_set.len());
for clause in clause_set.into_iter() {
simplified_clauses.push(clause.into_iter().collect());
}
(simplified_clauses, decisions) = apply_decisions(simplified_clauses, decisions);
decisions
.into_iter()
.for_each(|decision| simplified_clauses.push(vec![decision]));
simplified_clauses
}
pub fn apply_decisions(
mut relevant_clauses: Vec<Vec<i32>>,
mut accumlated_decisions: HashSet<i32>,
) -> (Vec<Vec<i32>>, HashSet<i32>) {
let mut decisions = accumlated_decisions.clone();
while !decisions.is_empty() {
let mut new_decisions = HashSet::new();
let mut reduced_clauses = Vec::new();
for mut clause in relevant_clauses {
if !clause.iter().any(|variable| decisions.contains(variable)) {
clause.retain(|variable| !decisions.contains(&-variable));
if !clause.is_empty() {
if clause.len() == 1 {
new_decisions.insert(clause[0]);
}
reduced_clauses.push(clause);
}
}
}
accumlated_decisions.extend(decisions);
relevant_clauses = reduced_clauses;
decisions = new_decisions;
}
(relevant_clauses, accumlated_decisions)
}
pub fn reduce_clause(clause: &[i32], decisions: &HashSet<i32>) -> Option<Vec<i32>> {
if clause.is_empty() {
return Some(vec![]);
}
let mut set_clause: HashSet<i32> = HashSet::new();
for elem in clause {
if set_clause.contains(&-elem) || decisions.contains(elem) {
return Some(vec![]);
} else if !decisions.contains(&-elem) {
set_clause.insert(*elem);
}
}
if set_clause.is_empty() {
return None;
}
Some(set_clause.into_iter().collect())
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn lex_cnf_lines() {
let comment = "c 1 N_100300__F_100332";
let header = "p cnf 2513 10275";
let clause = "-1628 1734 0";
assert_eq!(check_for_cnf_header(comment).unwrap().1, CNFToken::Comment);
assert_eq!(
check_for_cnf_header(header).unwrap().1,
CNFToken::Header {
features: 2513,
clauses: 10275
}
);
assert_eq!(
check_for_cnf_header(clause).unwrap().1,
CNFToken::Clause {
features: vec![-1628, 1734].into_iter().collect()
}
);
}
}