Skip to content

Commit

Permalink
Add reverse trailing capability
Browse files Browse the repository at this point in the history
  • Loading branch information
Dekker1 committed Jun 2, 2024
1 parent 479bfc6 commit 940cbd5
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 37 deletions.
5 changes: 4 additions & 1 deletion crates/huub/src/solver/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ impl IpasirPropagator for Engine {
// Process Boolean assignment
if persistent && self.state.decision_level() != 0 {
// Note that the assignment might already be known and trailed, but not previously marked as persistent
self.persistent.push(lit)
self.persistent.push(lit);
if self.state.trail.is_backtracking() {
return;
}
}
if self.state.trail.assign_sat(lit).is_some() {
return;
Expand Down
175 changes: 139 additions & 36 deletions crates/huub/src/solver/engine/trail.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use std::{

use index_vec::IndexVec;
use pindakaas::{Lit as RawLit, Var as RawVar};
use tracing::{debug, trace};

use crate::{actions::trailing::TrailingActions, IntVal};

Expand All @@ -16,25 +17,30 @@ index_vec::define_index_type! {
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Trail {
trail: Vec<u32>,
pos: usize,

Check warning on line 20 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L19-L20

Added lines #L19 - L20 were not covered by tests
prev_len: Vec<usize>,

int_value: IndexVec<TrailedInt, IntVal>,
sat_value: HashMap<RawVar, bool>,
sat_restore_value: HashMap<RawVar, bool>,

Check warning on line 25 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L23-L25

Added lines #L23 - L25 were not covered by tests
}

impl Default for Trail {
fn default() -> Self {
Self {
trail: Vec::new(),
pos: 0,
prev_len: Vec::new(),
int_value: IndexVec::new(),
sat_value: HashMap::new(),
sat_restore_value: HashMap::new(),
}
}
}

impl Trail {
fn push_trail(&mut self, event: TrailEvent) {
debug_assert_eq!(self.pos, self.trail.len());
match event {
TrailEvent::SatAssignment(r) => {
let r = i32::from(r);
Expand All @@ -48,43 +54,120 @@ impl Trail {
self.trail.push(i as u32)
}
}
self.pos = self.trail.len();
}
fn pop_trail(&mut self) -> Option<TrailEvent> {
if self.trail.is_empty() {
fn undo<const RESTORE: bool>(&mut self) -> Option<TrailEvent> {
debug_assert!(self.pos <= self.trail.len());
if self.pos == 0 {
return None;

Check warning on line 62 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L62

Added line #L62 was not covered by tests
}
let i = self.trail.pop().unwrap() as i32;
Some(if i.is_positive() {
// Find event before current position
let i = self.trail[self.pos - 1] as i32;
let event = if i.is_positive() {
self.pos -= 1;
// SAFETY: This is safe because RawVar uses the same representation as i32
TrailEvent::SatAssignment(unsafe { transmute(i) })
} else {
let i = -i as usize;
let high = self.trail.pop().unwrap() as u64;
let low = self.trail.pop().unwrap() as u64;
let high = self.trail[self.pos - 2] as u64;
let low = self.trail[self.pos - 3] as u64;
self.pos -= 3;
TrailEvent::IntAssignment(i.into(), ((high << 32) | low) as i64)
})
};

match event {
TrailEvent::SatAssignment(r) => {
let b = self.sat_value.remove(&r).unwrap();
if RESTORE {
let x = self.sat_restore_value.insert(r, b);
debug_assert!(x.is_none());
}
}
TrailEvent::IntAssignment(i, v) => {
if RESTORE {
self.trail.swap(self.pos, self.pos + 2);
}
self.int_value[i] = v;
}
}
Some(event)
}
fn undo_event(&mut self, event: TrailEvent) {

fn redo(&mut self) -> Option<TrailEvent> {
debug_assert!(self.pos <= self.trail.len());

if self.pos == self.trail.len() {
return None;

Check warning on line 100 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L100

Added line #L100 was not covered by tests
}
// Find event at current position
let i = self.trail[self.pos] as i32;
let event = if i.is_positive() {
self.pos += 1;
// SAFETY: This is safe because RawVar uses the same representation as i32
TrailEvent::SatAssignment(unsafe { transmute(i) })
} else {
let i = -i as usize;
let high = self.trail[self.pos + 1] as u64;
let low = self.trail[self.pos + 2] as u64;
self.pos += 3;
TrailEvent::IntAssignment(i.into(), ((high << 32) | low) as i64)
};

match event {
TrailEvent::SatAssignment(r) => {
let _ = self.sat_value.remove(&r);
let b = self.sat_restore_value.remove(&r).unwrap();
let x = self.sat_value.insert(r, b);
debug_assert!(x.is_none());
}
TrailEvent::IntAssignment(i, v) => {
self.trail.swap(self.pos - 1, self.pos - 3);
self.int_value[i] = v;
}
}
Some(event)
}

pub(crate) fn undo_until_found_lit(&mut self, lit: RawLit) {
let var = lit.var();
while let Some(event) = self.pop_trail() {
if !self.sat_value.contains_key(&var) {
if !self.sat_restore_value.contains_key(&var) {
panic!("literal is not present in the trail")

Check warning on line 134 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L133-L134

Added lines #L133 - L134 were not covered by tests
}
debug!(

Check warning on line 136 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L136

Added line #L136 was not covered by tests
lit = i32::from(lit),
"literal is already unset, restoring to point of setting literal"
);
while let Some(event) = self.redo() {
let found = matches!(event, TrailEvent::SatAssignment(r) if r == var);
if found {
trace!(

Check warning on line 143 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L140-L143

Added lines #L140 - L143 were not covered by tests
len = self.pos,
lit = i32::from(lit),
"reversed the trail to find literal"
);
return;
}
}
unreachable!("literal not on trail")

Check warning on line 151 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L151

Added line #L151 was not covered by tests
}
while let Some(event) = self.undo::<true>() {
let found = matches!(event, TrailEvent::SatAssignment(r) if r == var);
self.undo_event(event);
if found {
let e = self.redo();
debug_assert_eq!(e, Some(TrailEvent::SatAssignment(var)));
trace!(
len = self.pos,
lit = i32::from(lit),
"reversed the trail to find literal"
);
return;
}
}
panic!("literal {:?} was never assigned", lit)
unreachable!("literal not on trail")

Check warning on line 166 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L166

Added line #L166 was not covered by tests
}

pub(crate) fn is_backtracking(&self) -> bool {
self.pos != self.trail.len()
}

pub(crate) fn notify_new_decision_level(&mut self) {
Expand All @@ -98,11 +181,18 @@ impl Trail {
"backtracking to level {level} length {len}, but trail is already at length {}",
self.trail.len()

Check warning on line 182 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L182

Added line #L182 was not covered by tests
);
while self.trail.len() > len {
let event = self.pop_trail().unwrap();
self.undo_event(event);
if len <= self.pos {
while self.pos > len {
let _ = self.undo::<false>();
}
} else {
while self.pos < len {
let _ = self.redo();

Check warning on line 190 in crates/huub/src/solver/engine/trail.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/solver/engine/trail.rs#L189-L190

Added lines #L189 - L190 were not covered by tests
}
}
debug_assert_eq!(self.trail.len(), len);
debug_assert_eq!(self.pos, len);
self.trail.truncate(len);
self.sat_restore_value.clear();
}
pub(crate) fn decision_level(&self) -> usize {
self.prev_len.len()
Expand Down Expand Up @@ -158,38 +248,51 @@ mod tests {
use pindakaas::solver::{cadical::Cadical, NextVarRange};

use crate::{
solver::engine::trail::{Trail, TrailEvent, TrailedInt},
solver::engine::trail::{Trail, TrailEvent},
IntVal,
};

#[test]
fn test_trail_event() {
let mut slv = Cadical::default();
let mut trail = Trail::default();
let lits = slv.next_var_range(10).unwrap();
let int_events: [(u32, IntVal); 10] = [
(0, 0),
(1, 1),
(2, -1),
(i32::MAX as u32, IntVal::MAX),
(9474, IntVal::MIN),
(6966, 4084),
(4099, -9967),
(1977, 9076),
(2729, -4312),
(941, 1718),
];
let int_events: Vec<_> = [
0,
1,
-1,
IntVal::MAX,
IntVal::MIN,
4084,
-9967,
9076,
-4312,
1718,
]
.into_iter()
.map(|i| (trail.track_int(0), i))
.collect();

let mut trail = Trail::default();
for (l, (i, v)) in lits.clone().zip(int_events.iter()) {
trail.push_trail(TrailEvent::SatAssignment(l));
trail.push_trail(TrailEvent::IntAssignment(TrailedInt::from_raw(*i), *v));
let _ = trail.assign_sat(if usize::from(*i) % 2 == 0 {
l.into()
} else {
!l
});
trail.push_trail(TrailEvent::IntAssignment(*i, *v));
}
for (l, (i, v)) in lits.rev().zip(int_events.iter().rev()) {
assert_eq!(
trail.pop_trail().unwrap(),
TrailEvent::IntAssignment(TrailedInt::from_raw(*i), *v)
);
assert_eq!(trail.pop_trail().unwrap(), TrailEvent::SatAssignment(l))
let e = trail.undo::<true>().unwrap();
assert_eq!(e, TrailEvent::IntAssignment(*i, *v));
let r = trail.redo().unwrap();
assert_eq!(r, e);
let _ = trail.undo::<true>().unwrap();
let e = trail.undo::<true>().unwrap();
assert_eq!(e, TrailEvent::SatAssignment(l));
let r = trail.redo().unwrap();
assert_eq!(r, e);
let _ = trail.undo::<true>().unwrap();
}
}
}

0 comments on commit 940cbd5

Please sign in to comment.