Skip to content

Commit

Permalink
Use stable sort in DisjunctiveStrictEdgeFinding for mostly sorted vec…
Browse files Browse the repository at this point in the history
…tors
  • Loading branch information
Dekker1 committed Sep 6, 2024
1 parent 29ba087 commit 06b2372
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 87 deletions.
2 changes: 1 addition & 1 deletion crates/fzn-huub/tests/flatzinc_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ mod tests {
assert_first_solution!(warm_start_success);
assert_first_solution!(warm_start_fail);

assert_optimal!(jobshop_la04);
assert_optimal!(jobshop_la05);
assert_optimal!(jobshop_newspaper);

assert_search_order!(bool_indomain_max);
assert_search_order!(bool_indomain_min);
Expand Down
2 changes: 1 addition & 1 deletion crates/huub/src/model/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
all_different_int::AllDifferentIntValue,
array_int_minimum::ArrayIntMinimumBounds,
array_var_int_element::ArrayVarIntElementBounds,
disjunctive_stict::DisjunctiveStrictEdgeFinding,
disjunctive_strict::DisjunctiveStrictEdgeFinding,
int_abs::IntAbsBounds,
int_div::IntDivBounds,
int_lin_le::{IntLinearLessEqBounds, IntLinearLessEqImpBounds},
Expand Down
2 changes: 1 addition & 1 deletion crates/huub/src/propagator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ pub(crate) mod all_different_int;
pub(crate) mod array_int_minimum;
pub(crate) mod array_var_int_element;
pub(crate) mod conflict;
pub(crate) mod disjunctive_stict;
pub(crate) mod disjunctive_strict;
pub(crate) mod int_abs;
pub(crate) mod int_div;
pub(crate) mod int_event;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,29 @@ struct OmegaThetaTreeNode {
struct OmegaThetaTree {
nodes: Vec<OmegaThetaTreeNode>,
size: usize,
levaes_start_idx: usize,
leaves_start_idx: usize,

Check warning on line 37 in crates/huub/src/propagator/disjunctive_strict.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/disjunctive_strict.rs#L37

Added line #L37 was not covered by tests
}

#[inline]
fn left_child(i: usize) -> usize {
(i << 1) + 1
}
impl OmegaThetaTree {
#[inline]
/// Calculate the index of the left child of a node `i`
fn left_child(i: usize) -> usize {
(i << 1) + 1
}

#[inline]
fn right_child(i: usize) -> usize {
(i << 1) + 2
}
#[inline]
/// Calculate the index of the right child of a node `i`
fn right_child(i: usize) -> usize {
(i << 1) + 2
}

#[inline]
/// Calculate the index of the parent of a node `i`
fn parent(i: usize) -> usize {
debug_assert_ne!(i, 0);
(i - 1) >> 1
}

impl OmegaThetaTree {
pub(crate) fn new(tasks_no: usize) -> Self {
let tree_size = (1 << (33 - (tasks_no as i32 - 1).leading_zeros())) - 1;
OmegaThetaTree {
Expand All @@ -61,46 +70,46 @@ impl OmegaThetaTree {
tree_size
],
size: tree_size,
levaes_start_idx: tree_size / 2,
leaves_start_idx: tree_size / 2,
}
}

// task are sorted by earliest start time
pub(crate) fn fill(&mut self, sorted_tasks: &[usize], sorted_time: &[i32], durations: &[i64]) {
fn fill(&mut self, sorted_tasks: &[usize], sorted_time: &[i32], durations: &[i64]) {
let n = sorted_tasks.len();
// fill the leave nodes
for i in 0..n {
let idx = self.levaes_start_idx + i;
let idx = self.leaves_start_idx + i;
let ect = sorted_time[i] + durations[sorted_tasks[i]] as i32;
self.nodes[idx].total_durations = durations[sorted_tasks[i]] as i32;
self.nodes[idx].earliest_completion = ect;
self.nodes[idx].total_durations_gray = durations[sorted_tasks[i]] as i32;
self.nodes[idx].earliest_completion_gray = ect;
}

// udpate internal nodes in a botoom-up fashion
(0..self.levaes_start_idx).rev().for_each(|i| {
// update internal nodes in a bottom-up fashion
(0..self.leaves_start_idx).rev().for_each(|i| {
self.update_internal_node(i);
});
}

pub(crate) fn root(&self) -> &OmegaThetaTreeNode {
fn root(&self) -> &OmegaThetaTreeNode {
&self.nodes[0]
}

pub(crate) fn remove_task(&mut self, i: usize) {
assert!(self.levaes_start_idx + i < self.size);
let idx = self.levaes_start_idx + i;
fn remove_task(&mut self, i: usize) {
assert!(self.leaves_start_idx + i < self.size);
let idx = self.leaves_start_idx + i;
self.nodes[idx].total_durations = 0;
self.nodes[idx].earliest_completion = i32::MIN;
self.nodes[idx].total_durations_gray = 0;
self.nodes[idx].earliest_completion_gray = i32::MIN;
self.recursive_update(idx);
}

pub(crate) fn annotate_gray_task(&mut self, i: usize) {
assert!(self.levaes_start_idx + i < self.size);
let idx = self.levaes_start_idx + i;
fn annotate_gray_task(&mut self, i: usize) {
assert!(self.leaves_start_idx + i < self.size);
let idx = self.leaves_start_idx + i;
self.nodes[idx].total_durations = 0;
self.nodes[idx].earliest_completion = i32::MIN;
self.recursive_update(idx);
Expand All @@ -110,14 +119,14 @@ impl OmegaThetaTree {
if i == 0 {
return;
}
let parent = (i - 1) >> 1;
let parent = Self::parent(i);
self.update_internal_node(parent);
self.recursive_update(parent);
}

fn update_internal_node(&mut self, i: usize) {
let left_child = left_child(i);
let right_child = right_child(i);
let left_child = Self::left_child(i);
let right_child = Self::right_child(i);
let left_total_durations = self.nodes[left_child].total_durations;
let right_total_durations = self.nodes[right_child].total_durations;
let left_total_durations_gray = self.nodes[left_child].total_durations_gray;
Expand Down Expand Up @@ -146,81 +155,85 @@ impl OmegaThetaTree {
}

// Find the gray task responsible for pushing the earliest completion time
pub(crate) fn blocked_task(&self, earliest_completion_time: i32) -> usize {
fn blocked_task(&self, earliest_completion_time: i32) -> usize {
assert!(self.nodes[0].earliest_completion <= earliest_completion_time);
assert!(self.nodes[0].earliest_completion_gray >= earliest_completion_time);
let mut node_id = 0;
let mut earliest_completion_time = earliest_completion_time;
while node_id < self.levaes_start_idx {
if self.nodes[left_child(node_id)].total_durations_gray == 0 {
node_id = right_child(node_id);
} else if self.nodes[right_child(node_id)].total_durations_gray == 0 {
node_id = left_child(node_id);
} else if self.nodes[right_child(node_id)].earliest_completion_gray
while node_id < self.leaves_start_idx {
if self.nodes[Self::left_child(node_id)].total_durations_gray == 0 {
node_id = Self::right_child(node_id);
} else if self.nodes[Self::right_child(node_id)].total_durations_gray == 0 {
node_id = Self::left_child(node_id);
} else if self.nodes[Self::right_child(node_id)].earliest_completion_gray
>= earliest_completion_time
{
node_id = right_child(node_id);
} else if self.nodes[left_child(node_id)].earliest_completion
+ self.nodes[right_child(node_id)].total_durations_gray
node_id = Self::right_child(node_id);
} else if self.nodes[Self::left_child(node_id)].earliest_completion
+ self.nodes[Self::right_child(node_id)].total_durations_gray
>= earliest_completion_time
{
// The binding task is to the left, blocked task contributes only to the sum
earliest_completion_time -= self.nodes[left_child(node_id)].earliest_completion;
node_id = right_child(node_id);
while node_id < self.levaes_start_idx {
if self.nodes[left_child(node_id)].total_durations_gray
+ self.nodes[right_child(node_id)].total_durations
earliest_completion_time -=
self.nodes[Self::left_child(node_id)].earliest_completion;
node_id = Self::right_child(node_id);
while node_id < self.leaves_start_idx {
if self.nodes[Self::left_child(node_id)].total_durations_gray
+ self.nodes[Self::right_child(node_id)].total_durations
== earliest_completion_time
{
earliest_completion_time -=
self.nodes[right_child(node_id)].total_durations;
node_id = left_child(node_id);
} else if self.nodes[left_child(node_id)].total_durations
+ self.nodes[right_child(node_id)].total_durations_gray
self.nodes[Self::right_child(node_id)].total_durations;
node_id = Self::left_child(node_id);
} else if self.nodes[Self::left_child(node_id)].total_durations
+ self.nodes[Self::right_child(node_id)].total_durations_gray
>= earliest_completion_time
{
earliest_completion_time -= self.nodes[left_child(node_id)].total_durations;
node_id = right_child(node_id);
earliest_completion_time -=
self.nodes[Self::left_child(node_id)].total_durations;
node_id = Self::right_child(node_id);
} else {
unreachable!("unexpected case");
}
}
break;
} else {
earliest_completion_time -= self.nodes[right_child(node_id)].total_durations;
node_id = left_child(node_id);
earliest_completion_time -= self.nodes[Self::right_child(node_id)].total_durations;
node_id = Self::left_child(node_id);
}
}
node_id - self.levaes_start_idx
node_id - self.leaves_start_idx
}

// Finding the task responsible for pushing the earliest completion time beyond the time_bound
pub(crate) fn binding_task(&self, time_bound: i32, node_id: usize) -> usize {
fn binding_task(&self, time_bound: i32, node_id: usize) -> usize {
assert!(self.nodes[0].earliest_completion >= time_bound);
let mut node_id = node_id;
let mut earliest_completion_time = time_bound;
while node_id < self.levaes_start_idx {
if self.nodes[right_child(node_id)].earliest_completion >= earliest_completion_time {
node_id = right_child(node_id);
while node_id < self.leaves_start_idx {
if self.nodes[Self::right_child(node_id)].earliest_completion
>= earliest_completion_time
{
node_id = Self::right_child(node_id);
} else {
earliest_completion_time -= self.nodes[right_child(node_id)].total_durations;
node_id = left_child(node_id);
earliest_completion_time -= self.nodes[Self::right_child(node_id)].total_durations;
node_id = Self::left_child(node_id);
}
}
node_id - self.levaes_start_idx
node_id - self.leaves_start_idx
}

// Finding the task responsible for min{est_S, est_i} where
// - S is the set of tasks in the tree
// - task i is one of the gray task in the tree
pub(crate) fn gray_est_responsible_task(&self, earliest_completion_time: i32) -> usize {
fn gray_est_responsible_task(&self, earliest_completion_time: i32) -> usize {
assert!(self.nodes[0].earliest_completion <= earliest_completion_time);
assert!(self.nodes[0].earliest_completion_gray >= earliest_completion_time);
let mut node_id = 0;
let mut earliest_completion_time = earliest_completion_time;
while node_id < self.levaes_start_idx {
let left_child = left_child(node_id);
let right_child = right_child(node_id);
while node_id < self.leaves_start_idx {
let left_child = Self::left_child(node_id);
let right_child = Self::right_child(node_id);
if self.nodes[right_child].earliest_completion_gray >= earliest_completion_time {
node_id = right_child;
} else if self.nodes[left_child].earliest_completion
Expand All @@ -236,7 +249,7 @@ impl OmegaThetaTree {
node_id = left_child;
}
}
node_id - self.levaes_start_idx
node_id - self.leaves_start_idx
}
}

Expand All @@ -251,8 +264,6 @@ pub(crate) struct DisjunctiveStrictEdgeFinding {
// Parameters
start_times: Vec<IntView>,
durations: Vec<i64>,
// Action List
action_list: Vec<u32>,

// Internal state for propagation
tasks_sorted_earliest_start: Vec<usize>,
Expand Down Expand Up @@ -351,19 +362,18 @@ impl DisjunctiveStrictEdgeFinding {
.collect();

self.tasks_sorted_earliest_start
.sort_unstable_by_key(|&i| actions.get_int_lower_bound(self.start_times[i]));
.sort_by_key(|&i| earliest_start[i]);
self.tasks_sorted_lastest_completion
.sort_unstable_by_key(|&i| {
-(actions.get_int_upper_bound(self.start_times[i]) + self.durations[i])
});
let sorted_ealiest_start = self
.sort_by_key(|&i| -latest_completion[i]);

let sorted_earliest_start = self
.tasks_sorted_earliest_start
.iter()
.map(|&i| actions.get_int_lower_bound(self.start_times[i]) as i32)
.collect::<Vec<_>>();
.map(|&i| earliest_start[i] as i32)
.collect_vec();
self.ot_tree.fill(
&self.tasks_sorted_earliest_start,
&sorted_ealiest_start,
&sorted_earliest_start,
&self.durations,
);
for ii in 0..self.tasks_sorted_earliest_start.len() {
Expand All @@ -390,14 +400,20 @@ impl DisjunctiveStrictEdgeFinding {
let time_bound = self.latest_completion_time(lct_task, actions);
if earliest_completion_time > time_bound {
// resource overload detected, eagerly build the reason clause for conflict
let expl = self
.explain_earliest_completion_time(time_bound + 1)
.build_reason(actions);
// trace!("resource overload detected, conflict clause: {:?}", clause);
trace!("earliest completion time: {:?}", earliest_start);
trace!("latest completion time: {:?}", latest_completion);
actions.set_int_lower_bound(
self.start_times[lct_task],
earliest_completion_time as i64 - self.durations[lct_task],
self.explain_earliest_completion_time(time_bound + 1),
)?;
let err = actions
.set_int_lower_bound(
self.start_times[lct_task],
earliest_completion_time as i64 - self.durations[lct_task],
expl,
)
.unwrap_err();
return Err(err);
}
while self.ot_tree.root().earliest_completion_gray
> self.latest_completion_time(lct_task, actions)
Expand Down Expand Up @@ -448,15 +464,10 @@ where
E: ExplanationActions,
T: TrailingActions,
{
fn notify_event(&mut self, data: u32, _: &IntEvent, _: &mut T) -> bool {
self.action_list.push(data);
fn notify_event(&mut self, _: u32, _: &IntEvent, _: &mut T) -> bool {
true
}

fn notify_backtrack(&mut self, _new_level: usize) {
self.action_list.clear()
}

#[tracing::instrument(name = "disjunctive_bounds", level = "trace", skip(self, actions))]
fn propagate(&mut self, actions: &mut P) -> Result<(), Conflict> {
self.propagate_lower_bounds(actions)?;
Expand Down Expand Up @@ -536,7 +547,6 @@ impl Poster for DisjunctiveEdgeFindingPoster {
let prop = DisjunctiveStrictEdgeFinding {
start_times: self.start_times,
durations: self.durations,
action_list: Vec::new(),
tasks_sorted_earliest_start: (0..n).collect(),
tasks_sorted_lastest_completion: (0..n).collect(),
task_rankings_by_earliest_start: (0..n).collect(),
Expand Down Expand Up @@ -571,7 +581,7 @@ mod tests {
use tracing_test::traced_test;

use crate::{
propagator::disjunctive_stict::DisjunctiveStrictEdgeFinding,
propagator::disjunctive_strict::DisjunctiveStrictEdgeFinding,
solver::engine::int_var::{EncodingType, IntVar},
Solver,
};
Expand Down
6 changes: 6 additions & 0 deletions crates/huub/src/propagator/reason.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,9 @@ impl<A: ExplanationActions> ReasonBuilder<A> for Reason {
Ok(self)
}
}

impl<A: ExplanationActions> ReasonBuilder<A> for Result<Reason, bool> {
fn build_reason(self, _: &mut A) -> Result<Reason, bool> {
self
}
}

0 comments on commit 06b2372

Please sign in to comment.