use crate::hash::FxHashMap;
use crate::packed::PackedTask;
pub struct Selection {
pub chosen: Vec<(usize, Vec<u32>)>,
pub bound: f64,
pub capped: bool,
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
struct Atom {
var: usize,
fact: u32,
positive: bool,
}
struct Pref {
idx: usize,
weight: f64,
disjuncts: Vec<Vec<Atom>>,
}
const NODE_CAP: usize = 200_000;
pub fn select(
task: &PackedTask,
groups: &[Vec<u32>],
weights: &[f64],
dnf: &FxHashMap<usize, Vec<Vec<u32>>>,
banned: &crate::hash::FxHashSet<u32>,
) -> Option<Selection> {
let mut var_of: FxHashMap<u32, usize> = FxHashMap::default();
for (gi, g) in groups.iter().enumerate() {
for &f in g {
var_of.insert(f, gi);
}
}
let mut n_vars = groups.len();
let mut twin: FxHashMap<u32, u32> = FxHashMap::default();
{
let mut by_name: FxHashMap<&str, u32> = FxHashMap::default();
for (f, name) in task.fact_names.iter().enumerate() {
by_name.insert(name.as_str(), f as u32);
}
for (f, name) in task.fact_names.iter().enumerate() {
let up = name.to_ascii_uppercase();
if let Some(inner) = up.strip_prefix("(NOT ").and_then(|s| s.strip_suffix(')')) {
if let Some(&pf) = by_name.get(inner) {
twin.insert(f as u32, pf);
} else if let Some(&pf) = by_name.get(inner.to_ascii_lowercase().as_str()) {
twin.insert(f as u32, pf);
}
}
}
}
let fresh = |f: u32, var_of: &mut FxHashMap<u32, usize>, n_vars: &mut usize| -> usize {
*var_of.entry(f).or_insert_with(|| {
let v = *n_vars;
*n_vars += 1;
v
})
};
let mut prefs: Vec<Pref> = Vec::new();
let mut forced_violated = 0.0;
for (i, &weight) in weights.iter().enumerate() {
let Some(djs) = dnf.get(&i) else {
continue;
};
let mut disjuncts: Vec<Vec<Atom>> = Vec::new();
let mut trivially_true = false;
let mut any_banned = false;
for facts in djs {
if facts.is_empty() {
trivially_true = true;
break;
}
if facts
.iter()
.any(|f| banned.contains(f) && !twin.contains_key(f))
{
any_banned = true;
continue;
}
let mut req: Vec<Atom> = facts
.iter()
.map(|&f| match twin.get(&f) {
Some(&pf) => Atom {
var: fresh(pf, &mut var_of, &mut n_vars),
fact: pf,
positive: false,
},
None => Atom {
var: fresh(f, &mut var_of, &mut n_vars),
fact: f,
positive: true,
},
})
.collect();
req.sort_unstable();
req.dedup();
let ok = req.iter().all(|a| {
req.iter().all(|b| {
a == b
|| a.var != b.var
|| (a.positive != b.positive && a.fact != b.fact)
|| (!a.positive && !b.positive)
})
});
if ok {
let keep: Vec<bool> = req
.iter()
.map(|a| {
a.positive
|| !req
.iter()
.any(|b| b.positive && b.var == a.var && b.fact != a.fact)
})
.collect();
let mut it = keep.iter();
req.retain(|_| *it.next().unwrap());
disjuncts.push(req);
}
}
if trivially_true {
continue;
}
if disjuncts.is_empty() {
if any_banned {
forced_violated += weight; }
continue;
}
prefs.push(Pref {
idx: i,
weight,
disjuncts,
});
}
if prefs.len() < 2 {
return None;
}
let mut domain: Vec<Vec<u32>> = vec![Vec::new(); n_vars];
for p in &prefs {
for d in &p.disjuncts {
for a in d {
if a.positive && !domain[a.var].contains(&a.fact) {
domain[a.var].push(a.fact);
}
}
}
}
for d in &mut domain {
d.sort_unstable();
}
let mut touch_w: Vec<f64> = vec![0.0; n_vars];
for p in &prefs {
let mut seen: Vec<usize> = p
.disjuncts
.iter()
.flat_map(|d| d.iter().map(|a| a.var))
.collect();
seen.sort_unstable();
seen.dedup();
for v in seen {
touch_w[v] += p.weight;
}
}
let mut order: Vec<usize> = (0..n_vars).filter(|&v| !domain[v].is_empty()).collect();
order.sort_by(|&a, &b| {
touch_w[b]
.partial_cmp(&touch_w[a])
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.cmp(&b))
});
struct Dfs<'a> {
prefs: &'a [Pref],
domain: &'a [Vec<u32>],
order: &'a [usize],
assign: Vec<Option<u32>>,
best_cost: f64,
best_assign: Vec<Option<u32>>,
nodes: usize,
}
impl Dfs<'_> {
fn split(&self) -> (f64, f64) {
let mut forced = 0.0;
for p in self.prefs {
let dead = p.disjuncts.iter().all(|d| {
d.iter().any(|a| match self.assign[a.var] {
Some(x) if a.positive => x != a.fact + 1,
Some(x) => x == a.fact + 1,
None => false,
})
});
if dead {
forced += p.weight;
}
}
(forced, 0.0)
}
fn go(&mut self, depth: usize) {
self.nodes += 1;
if self.nodes > NODE_CAP {
return;
}
let (forced, _) = self.split();
if forced >= self.best_cost {
return; }
if depth == self.order.len() {
self.best_cost = forced;
self.best_assign = self.assign.clone();
return;
}
let v = self.order[depth];
let mut vals: Vec<(f64, u32)> = self.domain[v]
.iter()
.map(|&f| {
let w: f64 = self
.prefs
.iter()
.filter(|p| {
p.disjuncts
.iter()
.any(|d| d.iter().any(|a| a.positive && a.var == v && a.fact == f))
})
.map(|p| p.weight)
.sum();
(w, f)
})
.collect();
vals.sort_by(|a, b| {
b.0.partial_cmp(&a.0)
.unwrap_or(std::cmp::Ordering::Equal)
.then(a.1.cmp(&b.1))
});
for (_, f) in vals {
self.assign[v] = Some(f + 1);
self.go(depth + 1);
if self.nodes > NODE_CAP {
return;
}
}
self.assign[v] = Some(0); self.go(depth + 1);
self.assign[v] = None;
}
}
let mut dfs = Dfs {
prefs: &prefs,
domain: &domain,
order: &order,
assign: vec![None; n_vars],
best_cost: f64::INFINITY,
best_assign: vec![None; n_vars],
nodes: 0,
};
dfs.best_cost = prefs.iter().map(|p| p.weight).sum::<f64>() + 1e-9;
dfs.go(0);
let capped = dfs.nodes > NODE_CAP;
let assign = dfs.best_assign;
let mut chosen: Vec<(usize, Vec<u32>)> = Vec::new();
let mut bound = forced_violated;
for p in &prefs {
let sat = p.disjuncts.iter().find(|d| {
d.iter().all(|a| {
if a.positive {
matches!(assign[a.var], Some(x) if x == a.fact + 1)
} else {
!matches!(assign[a.var], Some(x) if x == a.fact + 1)
}
})
});
match sat {
Some(d) => chosen.push((
p.idx,
d.iter().filter(|a| a.positive).map(|a| a.fact).collect(),
)),
None => bound += p.weight,
}
}
if chosen.is_empty() {
return None;
}
Some(Selection {
chosen,
bound,
capped,
})
}