use std::marker::PhantomData;
use std::ops::Index;
use num_rational::BigRational;
use num_traits::One;
use rustc_hash::FxHashSet;
use serde::{Deserialize, Serialize};
use super::rational_string;
use super::space::{Original, Reduced, Space};
use super::{EquivFold, Literal, VarId};
use crate::error::VitriError;
fn dimacs_literals(i: usize) -> [i32; 2] {
let var = VarId(i as u32);
[Literal::pos(var).to_dimacs(), Literal::neg(var).to_dimacs()]
}
#[derive(Clone, Debug, Default)]
pub struct WeightTable {
w_pos: Vec<Option<BigRational>>,
w_neg: Vec<Option<BigRational>>,
}
impl WeightTable {
fn ensure(&mut self, var: usize) {
if self.w_pos.len() <= var {
self.w_pos.resize(var + 1, None);
self.w_neg.resize(var + 1, None);
}
}
pub(super) fn set(&mut self, lit: i32, w: BigRational) {
let var = VarId::from_dimacs(lit).idx();
self.ensure(var);
if lit > 0 {
self.w_pos[var] = Some(w);
} else {
self.w_neg[var] = Some(w);
}
}
pub fn from_dimacs_pairs(
pairs: impl IntoIterator<Item = (i32, BigRational)>,
num_vars: u32,
) -> Result<Self, VitriError> {
let mut table = Self::default();
for (lit, weight) in pairs {
let var = VarId::try_from_dimacs(lit).ok_or_else(|| {
VitriError::input(format!("weight literal {lit} names no DIMACS variable"))
})?;
if var.0 >= num_vars {
return Err(VitriError::input(format!(
"weight literal {lit} exceeds declared variable count {num_vars}"
)));
}
table.set(lit, weight);
}
Ok(table)
}
pub(super) fn validate_num_vars(&self, num_vars: u32) -> Result<(), VitriError> {
if self.w_pos.len().max(self.w_neg.len()) > num_vars as usize {
let literal = self
.to_literal_pairs()
.into_iter()
.find_map(|(lit, _)| (VarId::from_dimacs(lit).0 >= num_vars).then_some(lit))
.expect("a weight table longer than num_vars has a declared out-of-range row");
return Err(VitriError::input(format!(
"weight literal {literal} exceeds declared variable count {num_vars}"
)));
}
Ok(())
}
pub fn to_literal_pairs(&self) -> Vec<(i32, BigRational)> {
let mut out = Vec::new();
let n = self.w_pos.len().max(self.w_neg.len());
for v in 0..n {
let [pos, neg] = dimacs_literals(v);
if let Some(Some(w)) = self.w_pos.get(v) {
out.push((pos, w.clone()));
}
if let Some(Some(w)) = self.w_neg.get(v) {
out.push((neg, w.clone()));
}
}
out
}
pub fn resolve<S: Space>(&self, num_vars: usize) -> Weights<S> {
Weights(
(0..num_vars)
.map(|v| {
let wn = self
.w_neg
.get(v)
.and_then(|o| o.clone())
.unwrap_or_else(BigRational::one);
let wp = self
.w_pos
.get(v)
.and_then(|o| o.clone())
.unwrap_or_else(BigRational::one);
(wn, wp)
})
.collect(),
PhantomData,
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct LiteralWeight {
pub literal: i32,
pub weight: String,
}
pub struct Weights<S: Space>(Vec<(BigRational, BigRational)>, PhantomData<S>);
impl<S: Space> Weights<S> {
pub fn uniform(num_vars: usize) -> Self {
Weights(
vec![(BigRational::one(), BigRational::one()); num_vars],
PhantomData,
)
}
pub fn empty() -> Self {
Weights(Vec::new(), PhantomData)
}
pub fn from_dimacs_pairs(pairs: &[(i32, BigRational)], num_vars: usize) -> Self {
let mut w = Self::uniform(num_vars);
for (lit, val) in pairs {
let Some(idx) = VarId::try_from_dimacs(*lit)
.map(VarId::idx)
.filter(|idx| *idx < num_vars)
else {
continue;
};
if *lit > 0 {
w.0[idx].1 = val.clone();
} else {
w.0[idx].0 = val.clone();
}
}
w
}
pub(crate) fn from_carried(
entries: impl IntoIterator<Item = Option<(BigRational, BigRational)>>,
) -> Self {
Weights(
entries
.into_iter()
.map(|e| e.unwrap_or_else(|| (BigRational::one(), BigRational::one())))
.collect(),
PhantomData,
)
}
pub(crate) fn try_from_dimacs_lits(
num_vars: u32,
mut read: impl FnMut(i32) -> Option<BigRational>,
) -> Option<Self> {
let mut pairs = Vec::with_capacity(num_vars as usize);
for v in 0..num_vars {
let [pos, neg] = dimacs_literals(v as usize);
let wp = read(pos)?;
let wn = read(neg)?;
pairs.push((wn, wp));
}
Some(Weights(pairs, PhantomData))
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get(&self, var: VarId) -> Option<&(BigRational, BigRational)> {
self.0.get(var.idx())
}
pub fn as_pairs(&self) -> &[(BigRational, BigRational)] {
&self.0
}
pub fn unequal_vars(&self) -> FxHashSet<VarId> {
self.0
.iter()
.enumerate()
.filter(|(_, (wn, wp))| wn != wp)
.map(|(i, _)| VarId(i as u32))
.collect()
}
pub fn weighted_vars(&self) -> impl Iterator<Item = VarId> + '_ {
self.0
.iter()
.enumerate()
.filter(|(_, (wn, wp))| !wn.is_one() || !wp.is_one())
.map(|(i, _)| VarId(i as u32))
}
pub fn to_dimacs_pairs(&self) -> Vec<(i32, BigRational)> {
self.0
.iter()
.enumerate()
.flat_map(|(i, (wn, wp))| {
let [pos, neg] = dimacs_literals(i);
[(pos, wp.clone()), (neg, wn.clone())]
})
.collect()
}
pub fn to_record_rows(&self) -> Vec<LiteralWeight> {
self.0
.iter()
.enumerate()
.flat_map(|(i, (wn, wp))| {
let [pos, neg] = dimacs_literals(i);
[
LiteralWeight {
literal: pos,
weight: rational_string(wp),
},
LiteralWeight {
literal: neg,
weight: rational_string(wn),
},
]
})
.collect()
}
pub fn resize_neutral(&mut self, num_vars: usize) {
self.0
.resize(num_vars, (BigRational::one(), BigRational::one()));
}
pub(crate) fn fold_into(&mut self, pair: (BigRational, BigRational), surv: Literal) {
let (en, ep) = pair;
let s = surv.var.idx();
if surv.positive {
self.0[s].0 *= en;
self.0[s].1 *= ep;
} else {
self.0[s].0 *= ep;
self.0[s].1 *= en;
}
}
pub(crate) fn fold_eliminated(&mut self, folds: &[EquivFold]) {
for f in folds {
let pair = self.0[f.eliminated.idx()].clone();
self.fold_into(pair, f.survivor);
}
}
}
impl Weights<Original> {
pub(crate) fn assume_reduced_identity(self) -> Weights<Reduced> {
Weights(self.0, PhantomData)
}
}
impl<S: Space> Index<VarId> for Weights<S> {
type Output = (BigRational, BigRational);
fn index(&self, var: VarId) -> &Self::Output {
&self.0[var.idx()]
}
}
impl<S: Space> Clone for Weights<S> {
fn clone(&self) -> Self {
Weights(self.0.clone(), PhantomData)
}
}
impl<S: Space> std::fmt::Debug for Weights<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Weights").field(&self.0).finish()
}
}
impl<S: Space> PartialEq for Weights<S> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl<S: Space> Eq for Weights<S> {}