use super::*;
use smallvec::SmallVec;
fn frontier_relevance_is_uniform<'a, C: Constraint<'a>>(
constraints: &[C],
variable: VariableId,
frontier: &Frontier<'_>,
) -> bool {
const MAX_ROWS: usize = 64;
let rows = frontier.len();
if rows < 2 {
return true;
}
let stride = rows.div_ceil(MAX_ROWS);
constraints.iter().all(|c| {
let expected = c.estimate(variable, &frontier.row(0)).is_some();
(0..rows)
.step_by(stride)
.chain(std::iter::once(rows - 1))
.all(|i| c.estimate(variable, &frontier.row(i)).is_some() == expected)
})
}
pub struct IntersectionConstraint<C> {
constraints: Vec<C>,
}
impl<'a, C> IntersectionConstraint<C>
where
C: Constraint<'a> + 'a,
{
pub fn new(constraints: Vec<C>) -> Self {
IntersectionConstraint { constraints }
}
}
impl<'a, C> Constraint<'a> for IntersectionConstraint<C>
where
C: Constraint<'a> + 'a,
{
fn variables(&self) -> VariableSet {
self.constraints
.iter()
.fold(VariableSet::new_empty(), |vs, c| vs.union(c.variables()))
}
fn estimate(&self, variable: VariableId, binding: &Binding) -> Option<usize> {
self.constraints
.iter()
.filter_map(|c| c.estimate(variable, binding))
.min()
}
fn propose(
&self,
variable: VariableId,
frontier: &Frontier<'_>,
proposals: &mut ProposalBuffer,
) {
let rows = frontier.len();
if rows == 0 {
return;
}
let mut choice: Vec<usize> = Vec::with_capacity(rows);
let mut relevant_rows: SmallVec<[usize; 8]> =
SmallVec::from_elem(0, self.constraints.len());
let mut confirmers: SmallVec<[(usize, usize); 8]> = SmallVec::new();
for row in 0..rows {
let binding = frontier.row(row);
let mut best = usize::MAX;
let mut best_child = usize::MAX;
for (i, c) in self.constraints.iter().enumerate() {
if let Some(estimate) = c.estimate(variable, &binding) {
relevant_rows[i] += 1;
if row == 0 {
confirmers.push((estimate, i));
}
if best_child == usize::MAX || estimate < best {
best = estimate;
best_child = i;
}
}
}
choice.push(best_child);
}
assert!(
relevant_rows.iter().all(|&n| n == 0 || n == rows),
"Constraint::estimate returned Some for some rows of a frontier \
and None for others on variable {variable}. Whether the answer \
is Some or None must depend only on which variables are bound, \
not on their values (see Constraint::estimate). This propose \
ORs relevance across the batch, so a child that claims the \
variable on any row is then allowed to confirm every row."
);
if choice.iter().all(|&c| c == usize::MAX) {
return;
}
let single = choice.iter().all(|&c| c == choice[0]);
if single {
let base = proposals.len();
self.constraints[choice[0]].propose(variable, frontier, proposals);
let mut region = proposals.region(base);
if !confirmers.is_sorted_by_key(|&(estimate, _)| estimate) {
confirmers.sort_unstable_by_key(|&(estimate, _)| estimate);
}
for &(_, i) in confirmers.iter().filter(|&&(_, i)| i != choice[0]) {
self.constraints[i].confirm(variable, frontier, &mut region);
}
return;
}
let mut select: Vec<u32> = Vec::new();
let mut positions: Vec<u32> = Vec::new();
for proposer in 0..self.constraints.len() {
positions.clear();
positions.extend(
choice
.iter()
.enumerate()
.filter(|(_, &c)| c == proposer)
.map(|(row, _)| row as u32),
);
if positions.is_empty() {
continue;
}
select.clear();
frontier.compose(positions.iter().copied(), &mut select);
let sub = frontier.with_select(&select);
let base = proposals.len();
self.constraints[proposer].propose(variable, &sub, proposals);
let mut region = proposals.region(base);
if !confirmers.is_sorted_by_key(|&(estimate, _)| estimate) {
confirmers.sort_unstable_by_key(|&(estimate, _)| estimate);
}
for &(_, i) in confirmers.iter().filter(|&&(_, i)| i != proposer) {
self.constraints[i].confirm(variable, &sub, &mut region);
}
proposals.remap_region(base, &positions);
}
}
fn confirm(&self, variable: VariableId, frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
if frontier.is_empty() {
return;
}
let binding = frontier.row(0);
debug_assert!(
frontier_relevance_is_uniform(&self.constraints, variable, frontier),
"Constraint::estimate returned Some for some rows of a frontier \
and None for others on variable {variable}. Whether the answer \
is Some or None must depend only on which variables are bound, \
not on their values (see Constraint::estimate). Reading \
relevance off row 0 — as this does — otherwise changes the bag."
);
let mut relevant_constraints: SmallVec<[(usize, &C); 8]> = self
.constraints
.iter()
.filter_map(|c| Some((c.estimate(variable, &binding)?, c)))
.collect();
if !relevant_constraints.is_sorted_by_key(|(estimate, _)| *estimate) {
relevant_constraints.sort_unstable_by_key(|(estimate, _)| *estimate);
}
for (_, c) in relevant_constraints.iter() {
c.confirm(variable, frontier, cands);
}
}
fn satisfied(&self, binding: &Binding) -> bool {
self.constraints.iter().all(|c| c.satisfied(binding))
}
fn influence(&self, variable: VariableId) -> VariableSet {
self.constraints
.iter()
.fold(VariableSet::new_empty(), |acc, c| {
acc.union(c.influence(variable))
})
}
}
#[macro_export]
macro_rules! and {
($($c:expr),+ $(,)?) => (
::std::sync::Arc::new(
$crate::query::intersectionconstraint::IntersectionConstraint::new(vec![
$(Box::new($c)
as Box<dyn $crate::query::Constraint + Send + Sync>),+
])
)
)
}
pub use and;