use itertools::Itertools;
use tracing::trace;
use crate::{
Conjunction, IntVal,
actions::{
ConstructionActions, InitActions, IntDecisionActions, IntInspectionActions, IntPropCond,
PostingActions, PropagationActions, ReasoningContext, ReasoningEngine, Trailed,
TrailingActions,
},
constraints::{
Constraint, IntModelActions, IntSolverActions, Propagator, ReasonBuilder,
SimplificationStatus,
},
lower::{LoweringContext, LoweringError},
model,
solver::{IntLitMeaning, engine::Engine, queue::PriorityLevel},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Disjunctive {
pub(crate) propagator: DisjunctivePropagator<model::View<IntVal>>,
pub(crate) edge_finding_propagation: Option<bool>,
pub(crate) not_last_propagation: Option<bool>,
pub(crate) detectable_precedence_propagation: Option<bool>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum DisjunctivePropagationRule {
EdgeFinding,
NotLast,
Precedence,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct DisjunctivePropagator<I> {
start_times: Vec<I>,
durations: Vec<IntVal>,
ot_tree: OmegaThetaTree,
trailed_info: Vec<TaskInfo>,
edge_finding_enabled: bool,
not_last_enabled: bool,
detectable_precedence_enabled: bool,
tasks_sorted_by_earliest_start: Vec<usize>,
tasks_sorted_by_latest_start: Vec<usize>,
tasks_sorted_by_earliest_completion: Vec<usize>,
tasks_sorted_by_latest_completion: Vec<usize>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct OmegaThetaTree {
nodes: Vec<OmegaThetaTreeNode>,
leaves_start_idx: usize,
node_index_offset: Vec<usize>,
task_no: Vec<usize>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct OmegaThetaTreeNode {
total_durations: i64,
earliest_completion: i64,
total_durations_gray: i64,
earliest_completion_gray: i64,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
struct TaskInfo {
earliest_start: Trailed<IntVal>,
latest_completion: Trailed<IntVal>,
}
impl Disjunctive {
pub fn detectable_precedence_propagation_enabled(&self) -> bool {
self.detectable_precedence_propagation.unwrap_or(false)
}
pub fn edge_finding_propagation_enabled(&self) -> bool {
self.edge_finding_propagation.unwrap_or(true)
}
pub fn not_last_propagation_enabled(&self) -> bool {
self.not_last_propagation.unwrap_or(false)
}
}
impl<E> Constraint<E> for Disjunctive
where
E: ReasoningEngine,
model::View<IntVal>: IntModelActions<E>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
self.propagate(ctx)?;
if self
.propagator
.start_times
.iter()
.all(|v| v.val(ctx).is_some())
{
return Ok(SimplificationStatus::Subsumed);
}
Ok(SimplificationStatus::NoFixpoint)
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
let start_times = self
.propagator
.start_times
.iter()
.map(|&v| slv.solver_view(v))
.collect_vec();
let iter = start_times.iter().zip(self.propagator.durations.iter());
let horizon = iter.clone().map(|(v, d)| v.max(slv) + d).max().unwrap();
let symmetric_vars: Vec<_> = iter.map(|(v, d)| -*v + (horizon - d)).collect();
DisjunctivePropagator::post(
slv,
start_times,
self.propagator.durations.clone(),
self.edge_finding_propagation_enabled(),
self.not_last_propagation_enabled(),
self.detectable_precedence_propagation_enabled(),
);
DisjunctivePropagator::post(
slv,
symmetric_vars,
self.propagator.durations.clone(),
self.edge_finding_propagation_enabled(),
self.not_last_propagation_enabled(),
self.detectable_precedence_propagation_enabled(),
);
Ok(())
}
}
impl<E> Propagator<E> for Disjunctive
where
E: ReasoningEngine,
model::View<IntVal>: IntSolverActions<E>,
{
fn explain(
&mut self,
ctx: &mut E::ExplanationContext<'_>,
lit: E::Atom,
data: u64,
) -> Conjunction<E::Atom> {
self.propagator.explain(ctx, lit, data)
}
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
self.propagator.initialize(ctx);
}
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
self.propagator.propagate(ctx)
}
}
impl<I> DisjunctivePropagator<I> {
fn data_for_explanation(
&self,
task_no: usize,
propagation_rule: DisjunctivePropagationRule,
) -> u64 {
((propagation_rule as u64) << 62) + task_no as u64
}
fn earliest_completion_time<Ctx>(&self, ctx: &mut Ctx, i: usize) -> IntVal
where
Ctx: ReasoningContext + ?Sized,
I: IntInspectionActions<Ctx>,
{
self.earliest_start_time(ctx, i) + self.durations[i]
}
fn earliest_start_time<Ctx>(&self, ctx: &mut Ctx, i: usize) -> IntVal
where
Ctx: ReasoningContext + ?Sized,
I: IntInspectionActions<Ctx>,
{
self.start_times[i].min(ctx)
}
fn explain_edge_finding<E>(
&mut self,
ctx: &mut E::ExplanationContext<'_>,
task_no: usize,
earliest_start: i64,
latest_completion: i64,
) -> Conjunction<E::Atom>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let latest_completion_times = (0..self.start_times.len())
.map(|i| self.latest_completion_time(ctx, i))
.collect_vec();
let earliest_start_times = (0..self.start_times.len())
.map(|i| self.earliest_start_time(ctx, i))
.collect_vec();
trace!(
target: "disjunctive",
task_no,
left_cut_set =? (0..self.start_times.len())
.filter(|&j| {
j != task_no
&& earliest_start_times[j] >= earliest_start
&& latest_completion_times[j] <= latest_completion
})
.map(|j| {
(
j,
earliest_start_times[j],
latest_completion_times[j],
)
})
.collect_vec(),
window =? (earliest_start, latest_completion),
"explain edge finding"
);
let mut clause = Vec::new();
let (bv, _) =
self.start_times[task_no].lit_relaxed(ctx, IntLitMeaning::GreaterEq(earliest_start));
clause.push(bv);
let mut energy = latest_completion - earliest_start - self.durations[task_no];
for i in 0..self.start_times.len() {
if i != task_no
&& earliest_start_times[i] >= earliest_start
&& latest_completion_times[i] <= latest_completion
{
clause.push(self.start_times[i].min_lit(ctx));
let (bv, _) = self.start_times[i].lit_relaxed(
ctx,
IntLitMeaning::Less(latest_completion - self.durations[i] + 1),
);
clause.push(bv);
energy -= self.durations[i];
if energy < 0 {
break;
}
}
}
clause
}
fn explain_not_last<E>(
&mut self,
ctx: &mut E::ExplanationContext<'_>,
task_no: usize,
earliest_start: i64,
updated_lct_i: i64,
) -> Conjunction<E::Atom>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let nlset = (0..self.start_times.len())
.filter(|j| {
{
*j != task_no
&& self.latest_start_time(ctx, *j) <= updated_lct_i
&& self.earliest_start_time(ctx, *j) >= earliest_start
}
})
.collect_vec();
trace!(
target: "disjunctive",
task_no,
window =? (earliest_start, updated_lct_i),
nlset = ? nlset.iter().map(|&j| (j, self.durations[j], self.earliest_start_time(ctx,j, ), self.latest_start_time( ctx,j,))).collect_vec(),
"explain not last"
);
assert_ne!(nlset.len(), 0);
let mut clause = Vec::new();
clause.push(self.start_times[task_no].max_lit(ctx));
for j in nlset {
let (bv, _) =
self.start_times[j].lit_relaxed(ctx, IntLitMeaning::GreaterEq(earliest_start));
clause.push(bv);
let (bv, _) =
self.start_times[j].lit_relaxed(ctx, IntLitMeaning::Less(updated_lct_i + 1));
clause.push(bv);
}
clause
}
fn explain_overload_checking<Ctx>(&self, time_bound: i64) -> impl ReasonBuilder<Ctx> + '_
where
Ctx: ReasoningContext + ?Sized,
I: IntDecisionActions<Ctx>,
{
move |ctx: &mut Ctx| {
let binding_task = self.ot_tree.binding_task(time_bound, 0);
let earliest_start = self.start_times[binding_task].min(ctx);
let mut slack = time_bound - earliest_start;
let mut e_tasks = Vec::new();
trace!(
target: "disjunctive",
window =? (earliest_start, time_bound),
"explain resource overload"
);
for i in 0..self.tasks_sorted_by_earliest_start.len() {
let task_no = self.tasks_sorted_by_earliest_start[i];
if self.earliest_start_time(ctx, task_no) >= earliest_start
&& self.latest_completion_time(ctx, task_no) < time_bound
{
e_tasks.push(task_no);
slack -= self.durations[task_no];
if slack <= 0 {
break;
}
}
}
e_tasks
.iter()
.flat_map(|&i| {
let bv = self.start_times[i].lit(
ctx,
IntLitMeaning::Less((time_bound - slack) - self.durations[i]),
);
[self.start_times[i].min_lit(ctx), bv]
})
.collect_vec()
}
}
fn explain_precedence<E>(
&mut self,
ctx: &mut E::ExplanationContext<'_>,
task_no: usize,
earliest_start: i64,
latest_start: i64,
) -> Conjunction<E::Atom>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let precedence_set = (0..self.start_times.len())
.filter(|&j| {
j != task_no
&& self.earliest_start_time(ctx, j) >= earliest_start
&& self.latest_start_time(ctx, j) < latest_start
})
.collect_vec();
trace!(
target: "disjunctive",
task_no,
window =? (earliest_start, latest_start),
precedence_set = ?precedence_set,
"explain detectable precedence"
);
assert_ne!(precedence_set.len(), 0);
let task_i_est = self.earliest_start_time(ctx, task_no);
let mut clause = Vec::new();
let (bv, _) =
self.start_times[task_no].lit_relaxed(ctx, IntLitMeaning::GreaterEq(task_i_est));
clause.push(bv);
for j in precedence_set {
let v = self.start_times[j].clone();
let (bv, _) = v.lit_relaxed(ctx, IntLitMeaning::GreaterEq(earliest_start));
clause.push(bv);
let (bv, _) = v.lit_relaxed(
ctx,
IntLitMeaning::Less(task_i_est + self.durations[task_no]),
);
clause.push(bv);
}
clause
}
fn latest_completion_time<Ctx>(&self, ctx: &mut Ctx, i: usize) -> IntVal
where
Ctx: ReasoningContext + ?Sized,
I: IntInspectionActions<Ctx>,
{
self.latest_start_time(ctx, i) + self.durations[i]
}
fn latest_start_time<Ctx>(&self, ctx: &mut Ctx, i: usize) -> IntVal
where
Ctx: ReasoningContext + ?Sized,
I: IntInspectionActions<Ctx>,
{
self.start_times[i].max(ctx)
}
pub(crate) fn new<E>(
solver: &mut E,
start_times: Vec<I>,
durations: Vec<IntVal>,
edge_finding_enabled: bool,
not_last_enabled: bool,
detectable_precedence_enabled: bool,
) -> Self
where
E: ConstructionActions + ?Sized,
{
let n = start_times.len();
let trailed_info = (0..n)
.map(|_| TaskInfo {
earliest_start: solver.new_trailed(0),
latest_completion: solver.new_trailed(0),
})
.collect();
Self {
start_times,
durations,
ot_tree: OmegaThetaTree::new(n),
trailed_info,
edge_finding_enabled,
not_last_enabled,
detectable_precedence_enabled,
tasks_sorted_by_earliest_start: (0..n).collect_vec(),
tasks_sorted_by_latest_start: (0..n).collect_vec(),
tasks_sorted_by_earliest_completion: (0..n).collect_vec(),
tasks_sorted_by_latest_completion: (0..n).collect_vec(),
}
}
pub fn post<E>(
solver: &mut E,
start_times: Vec<I>,
durations: Vec<IntVal>,
edge_finding_enabled: bool,
not_last_enabled: bool,
detectable_precedence_enabled: bool,
) where
E: PostingActions + ?Sized,
I: IntSolverActions<Engine>,
{
let b = Box::new(Self::new(
solver,
start_times,
durations,
edge_finding_enabled,
not_last_enabled,
detectable_precedence_enabled,
));
solver.add_propagator(b);
}
fn propagate_detectable_precedence<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<bool, E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut propagated = false;
self.ot_tree.clear();
let mut updated_est = (0..self.start_times.len())
.map(|i| self.earliest_start_time(ctx, i))
.collect_vec();
let mut binding_tasks = vec![None; self.start_times.len()];
let latest_start_times = (0..self.start_times.len())
.map(|i| self.latest_start_time(ctx, i))
.collect_vec();
let earliest_completion_times = (0..self.start_times.len())
.map(|i| self.earliest_completion_time(ctx, i))
.collect_vec();
self.tasks_sorted_by_latest_start
.sort_by_key(|&i| latest_start_times[i]);
self.tasks_sorted_by_earliest_completion
.sort_by_key(|&i| earliest_completion_times[i]);
let mut lst_front_idx = 0;
for &ect_task in self.tasks_sorted_by_earliest_completion.iter() {
let ect = earliest_completion_times[ect_task];
while lst_front_idx < self.tasks_sorted_by_latest_start.len()
&& ect > latest_start_times[self.tasks_sorted_by_latest_start[lst_front_idx]]
{
let front_task = self.tasks_sorted_by_latest_start[lst_front_idx];
self.ot_tree.add_task(
front_task,
self.earliest_start_time(ctx, front_task),
self.durations[front_task],
);
trace!(
target: "disjunctive",
successor = ect_task,
predecessor = front_task,
"precedence detected",
);
lst_front_idx += 1;
}
let task_exists = self.ot_tree.remove_task(ect_task);
let tasks_in_tree_ect = self.ot_tree.root().earliest_completion;
if tasks_in_tree_ect > self.earliest_start_time(ctx, ect_task) {
binding_tasks[ect_task] = Some(self.ot_tree.binding_task(tasks_in_tree_ect, 0));
updated_est[ect_task] = updated_est[ect_task].max(tasks_in_tree_ect);
trace!(
target: "disjunctive",
ect_task,
updated_est = updated_est[ect_task],
tasks_in_tree =? (0..lst_front_idx)
.map(|i| self.tasks_sorted_by_latest_start[i])
.filter(|&task_no| task_no != ect_task)
.map(|task_no| {
(
task_no,
latest_start_times[task_no],
)
})
.collect_vec(),
tasks_in_tree_ect,
"propagate detected precedence"
);
}
if task_exists {
self.ot_tree.add_task(
ect_task,
self.earliest_start_time(ctx, ect_task),
self.durations[ect_task],
);
}
}
for (i, v) in self.start_times.iter().enumerate() {
if let Some(binding_task) = binding_tasks[i] {
let earliest_start_time = self.earliest_start_time(ctx, i);
let earliest_completion_time = self.earliest_completion_time(ctx, i);
if updated_est[i] > earliest_start_time {
let lb = self.start_times[binding_task].min(ctx);
ctx.set_trailed(self.trailed_info[i].earliest_start, lb);
ctx.set_trailed(
self.trailed_info[i].latest_completion,
earliest_completion_time,
);
let data = self.data_for_explanation(i, DisjunctivePropagationRule::Precedence);
v.tighten_min(ctx, updated_est[i], ctx.deferred_reason(data))?;
propagated = true;
}
}
}
trace!(target: "disjunctive", propagated, "detectable precedence propagation completed");
Ok(propagated)
}
fn propagate_edge_finding<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
check_overload: bool,
) -> Result<bool, E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut propagated = false;
let earliest_start: Vec<_> = self.start_times.iter().map(|v| v.min(ctx)).collect();
self.ot_tree
.fill(earliest_start.as_slice(), self.durations.as_slice());
let latest_completion_times: Vec<_> = (0..self.start_times.len())
.map(|i| self.latest_completion_time(ctx, i))
.collect();
self.tasks_sorted_by_latest_completion
.sort_by_key(|&i| -latest_completion_times[i]);
for (j, &lct_task) in self.tasks_sorted_by_latest_completion.iter().enumerate() {
let lct = self.latest_completion_time(ctx, lct_task);
let ect_in_tree = self.ot_tree.root().earliest_completion;
if check_overload {
if ect_in_tree > lct {
let expl = self.explain_overload_checking(lct + 1);
self.start_times[lct_task].tighten_min(
ctx,
ect_in_tree - self.durations[lct_task],
expl,
)?;
}
} else {
assert!(ect_in_tree <= lct);
}
while j > 0 && self.ot_tree.root().earliest_completion_gray > lct {
let ect_gray_in_tree = self.ot_tree.root().earliest_completion_gray;
let blocked_task = self.ot_tree.blocked_task(ect_gray_in_tree);
if self.start_times[blocked_task].min(ctx) < ect_in_tree {
let gray_est_task = self.ot_tree.blocking_task(ect_gray_in_tree);
let lb = self.start_times[gray_est_task].min(ctx);
ctx.set_trailed(self.trailed_info[blocked_task].earliest_start, lb);
ctx.set_trailed(
self.trailed_info[blocked_task].latest_completion,
ect_gray_in_tree - 1,
);
trace!(
target: "disjunctive",
ect_in_tree,
task = blocked_task,
window =? (lb, ect_gray_in_tree - 1),
"propagate edge finding"
);
let data = self.data_for_explanation(
blocked_task,
DisjunctivePropagationRule::EdgeFinding,
);
self.start_times[blocked_task].tighten_min(
ctx,
ect_in_tree,
ctx.deferred_reason(data),
)?;
propagated = true;
}
self.ot_tree.remove_task(blocked_task);
}
self.ot_tree.annotate_gray_task(lct_task);
}
trace!(target: "disjunctive", propagated, "edge finding propagation completed");
Ok(propagated)
}
fn propagate_not_last<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<bool, E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
let mut propagated = false;
self.ot_tree.clear();
let mut updated_lct = (0..self.start_times.len())
.map(|i| self.latest_completion_time(ctx, i))
.collect_vec();
let mut binding_tasks = vec![None; self.start_times.len()];
let latest_start_times = (0..self.start_times.len())
.map(|i| self.latest_start_time(ctx, i))
.collect_vec();
let latest_completion_times = (0..self.start_times.len())
.map(|i| self.latest_completion_time(ctx, i))
.collect_vec();
self.tasks_sorted_by_latest_start
.sort_by_key(|&i| latest_start_times[i]);
self.tasks_sorted_by_latest_completion
.sort_by_key(|&i| latest_completion_times[i]);
let mut lst_front_idx = 0;
for &lct_task in self.tasks_sorted_by_latest_completion.iter() {
let lct = latest_completion_times[lct_task];
while lst_front_idx < self.tasks_sorted_by_latest_start.len()
&& lct > latest_start_times[self.tasks_sorted_by_latest_start[lst_front_idx]]
{
let lst_task = self.tasks_sorted_by_latest_start[lst_front_idx];
self.ot_tree.add_task(
lst_task,
self.earliest_start_time(ctx, lst_task),
self.durations[lst_task],
);
lst_front_idx += 1;
}
let task_exists = self.ot_tree.remove_task(lct_task);
let tasks_in_tree_ect = self.ot_tree.root().earliest_completion;
if tasks_in_tree_ect > (lct - self.durations[lct_task]) {
binding_tasks[lct_task] = Some(self.ot_tree.binding_task(tasks_in_tree_ect, 0));
let front_lst =
latest_start_times[self.tasks_sorted_by_latest_start[lst_front_idx - 1]];
updated_lct[lct_task] = updated_lct[lct_task].min(front_lst);
trace!(
target: "disjunctive",
lct_task=? (lct_task, lct),
updated_lct = updated_lct[lct_task],
lst_front_idx,
tasks_in_tree =? (0..lst_front_idx)
.map(|i| self.tasks_sorted_by_latest_start[i])
.filter(|&task_no| task_no != lct_task)
.map(|task_no| {
(
task_no,
latest_start_times[task_no],
)
})
.collect_vec(),
tasks_in_tree_ect,
"propagate not last"
);
}
if task_exists {
self.ot_tree.add_task(
lct_task,
self.earliest_start_time(ctx, lct_task),
self.durations[lct_task],
);
}
}
for (i, v) in self.start_times.iter().enumerate() {
if let Some(binding_task) = binding_tasks[i]
&& updated_lct[i] < self.latest_completion_time(ctx, i)
{
let lb = self.earliest_start_time(ctx, binding_task);
trace!(
target: "disjunctive",
task = i,
window =? (lb, updated_lct[i]),
"not last propagation"
);
ctx.set_trailed(self.trailed_info[i].earliest_start, lb);
ctx.set_trailed(self.trailed_info[i].latest_completion, updated_lct[i]);
let data = self.data_for_explanation(i, DisjunctivePropagationRule::NotLast);
v.tighten_max(
ctx,
updated_lct[i] - self.durations[i],
ctx.deferred_reason(data),
)?;
propagated = true;
}
}
trace!(target: "disjunctive", propagated, "not last propagation completed");
Ok(propagated)
}
fn propagate_overload_checking<E>(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<(), E::Conflict>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
self.ot_tree.clear();
let tasks_sorted_by_lct = (0..self.start_times.len())
.map(|i| (i, self.latest_completion_time(ctx, i)))
.sorted_by_key(|(_, lct)| *lct)
.collect_vec();
for (i, lct_i) in tasks_sorted_by_lct.iter() {
let est_i = self.earliest_start_time(ctx, *i);
self.ot_tree.add_task(*i, est_i, self.durations[*i]);
let ect = self.ot_tree.root().earliest_completion;
if ect > *lct_i {
let binding_task = self
.ot_tree
.binding_task(self.ot_tree.root().earliest_completion, 0);
let earliest_start = self.start_times[binding_task].min(ctx);
let expl = self.explain_overload_checking(lct_i + 1);
trace!(
target: "disjunctive",
time_window =? (earliest_start, lct_i),
"resource overload"
);
self.start_times[*i].tighten_min(ctx, ect - self.durations[*i], expl)?;
}
}
Ok(())
}
fn propagation_rule_from_data(&self, data: u64) -> DisjunctivePropagationRule {
match data >> 62 {
0 => DisjunctivePropagationRule::EdgeFinding,
1 => DisjunctivePropagationRule::NotLast,
2 => DisjunctivePropagationRule::Precedence,
_ => unreachable!("invalid propagation rule in data"),
}
}
fn task_no_from_data(&self, data: u64) -> usize {
((data << 2) >> 2) as usize
}
}
impl<E, I> Propagator<E> for DisjunctivePropagator<I>
where
E: ReasoningEngine,
I: IntSolverActions<E>,
{
#[tracing::instrument(
name = "disjunctive",
target = "solver",
level = "trace",
skip(self, ctx)
)]
fn explain(
&mut self,
ctx: &mut E::ExplanationContext<'_>,
_: E::Atom,
data: u64,
) -> Conjunction<E::Atom> {
let task_no = self.task_no_from_data(data);
let earliest_start = ctx.trailed(self.trailed_info[task_no].earliest_start);
let latest_completion = ctx.trailed(self.trailed_info[task_no].latest_completion);
match self.propagation_rule_from_data(data) {
DisjunctivePropagationRule::EdgeFinding => {
self.explain_edge_finding(ctx, task_no, earliest_start, latest_completion)
}
DisjunctivePropagationRule::NotLast => {
self.explain_not_last(ctx, task_no, earliest_start, latest_completion)
}
DisjunctivePropagationRule::Precedence => {
self.explain_precedence(ctx, task_no, earliest_start, latest_completion)
}
}
}
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
ctx.set_priority(PriorityLevel::Low);
for v in &self.start_times {
v.enqueue_when(ctx, IntPropCond::Bounds);
}
}
#[tracing::instrument(
name = "disjunctive",
target = "solver",
level = "trace",
skip(self, ctx)
)]
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
let earliest_start: Vec<_> = self.start_times.iter().map(|v| v.min(ctx)).collect();
self.tasks_sorted_by_earliest_start
.sort_by_key(|&i| earliest_start[i]);
self.ot_tree
.initialize(self.tasks_sorted_by_earliest_start.as_slice());
if self.edge_finding_enabled {
if self.propagate_edge_finding(ctx, true)? {
return Ok(());
}
} else {
self.propagate_overload_checking(ctx)?;
}
if self.detectable_precedence_enabled && self.propagate_detectable_precedence(ctx)? {
return Ok(());
}
if self.not_last_enabled && self.propagate_not_last(ctx)? {
return Ok(());
}
Ok(())
}
}
impl OmegaThetaTree {
fn add_task(&mut self, task_no: usize, earliest_start_time: i64, duration: i64) {
assert!(task_no < self.task_no.len());
let idx = self.node_index(task_no);
self.nodes[idx].total_durations = duration;
self.nodes[idx].earliest_completion = earliest_start_time + duration;
self.nodes[idx].total_durations_gray = duration;
self.nodes[idx].earliest_completion_gray = earliest_start_time + duration;
self.recursive_update(idx);
}
fn annotate_gray_task(&mut self, task_no: usize) {
assert!(task_no < self.task_no.len());
let idx = self.node_index(task_no);
self.nodes[idx].total_durations = 0;
self.nodes[idx].earliest_completion = i64::MIN;
self.recursive_update(idx);
}
fn binding_task(&self, time_bound: i64, idx: usize) -> usize {
assert!(self.root().earliest_completion >= time_bound);
let mut node_id = idx;
let mut earliest_completion_time = time_bound;
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[Self::right_child(node_id)].total_durations;
node_id = Self::left_child(node_id);
}
}
self.task_no[node_id - self.leaves_start_idx]
}
fn blocked_task(&self, time_bound: i64) -> usize {
assert!(self.root().earliest_completion <= time_bound);
assert!(self.root().earliest_completion_gray >= time_bound);
let mut node_id = 0;
let mut earliest_completion_time = time_bound;
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 = 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
{
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[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[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[Self::right_child(node_id)].total_durations;
node_id = Self::left_child(node_id);
}
}
self.task_no[node_id - self.leaves_start_idx]
}
fn blocking_task(&self, time_bound: i64) -> usize {
assert!(self.root().earliest_completion <= time_bound);
assert!(self.root().earliest_completion_gray >= time_bound);
let mut node_id = 0;
let mut earliest_completion_time = time_bound;
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
+ self.nodes[right_child].total_durations_gray
>= earliest_completion_time
{
return self.binding_task(
earliest_completion_time - self.nodes[right_child].total_durations_gray,
left_child,
);
} else {
earliest_completion_time -= self.nodes[right_child].total_durations;
node_id = left_child;
}
}
self.task_no[node_id - self.leaves_start_idx]
}
fn clear(&mut self) {
for i in 0..self.nodes.len() {
self.nodes[i].total_durations = 0;
self.nodes[i].earliest_completion = i64::MIN;
self.nodes[i].total_durations_gray = 0;
self.nodes[i].earliest_completion_gray = i64::MIN;
}
}
fn fill(&mut self, earliest_start: &[i64], durations: &[i64]) {
assert_eq!(earliest_start.len(), self.task_no.len());
for i in 0..self.task_no.len() {
let idx = self.node_index(i);
let ect = earliest_start[i] + durations[i];
self.nodes[idx].total_durations = durations[i];
self.nodes[idx].earliest_completion = ect;
self.nodes[idx].total_durations_gray = durations[i];
self.nodes[idx].earliest_completion_gray = ect;
}
for i in (0..self.leaves_start_idx).rev() {
self.update_internal_node(i);
}
}
fn initialize(&mut self, task_sorted_by_earliest_start: &[usize]) {
self.task_no.copy_from_slice(task_sorted_by_earliest_start);
for i in 0..self.task_no.len() {
self.node_index_offset[self.task_no[i]] = i;
}
}
fn left_child(i: usize) -> usize {
(i << 1) + 1
}
pub(crate) fn new(tasks_no: usize) -> Self {
let tree_size = (1 << (33 - (tasks_no as i32 - 1).leading_zeros())) - 1;
OmegaThetaTree {
nodes: vec![
OmegaThetaTreeNode {
total_durations: 0,
earliest_completion: i64::MIN,
total_durations_gray: 0,
earliest_completion_gray: i64::MIN,
};
tree_size
],
leaves_start_idx: tree_size / 2,
node_index_offset: (0..tasks_no).collect(),
task_no: (0..tasks_no).collect(),
}
}
fn node_index(&self, i: usize) -> usize {
assert!(i < self.task_no.len());
self.leaves_start_idx + self.node_index_offset[i]
}
fn parent(i: usize) -> usize {
debug_assert_ne!(i, 0);
(i - 1) >> 1
}
fn recursive_update(&mut self, i: usize) {
if i == 0 {
return;
}
let parent = Self::parent(i);
self.update_internal_node(parent);
self.recursive_update(parent);
}
fn remove_task(&mut self, task_no: usize) -> bool {
assert!(task_no < self.task_no.len());
let idx = self.node_index(task_no);
if self.nodes[idx].total_durations == 0 && self.nodes[idx].total_durations_gray == 0 {
false
} else {
self.nodes[idx].total_durations = 0;
self.nodes[idx].earliest_completion = i64::MIN;
self.nodes[idx].total_durations_gray = 0;
self.nodes[idx].earliest_completion_gray = i64::MIN;
self.recursive_update(idx);
true
}
}
fn right_child(i: usize) -> usize {
(i << 1) + 2
}
fn root(&self) -> &OmegaThetaTreeNode {
&self.nodes[0]
}
fn update_internal_node(&mut self, i: usize) {
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;
let right_total_durations_gray = self.nodes[right_child].total_durations_gray;
let left_earliest_completion = self.nodes[left_child].earliest_completion;
let right_earliest_completion = self.nodes[right_child].earliest_completion;
let left_earliest_completion_gray = self.nodes[left_child].earliest_completion_gray;
let right_earliest_completion_gray = self.nodes[right_child].earliest_completion_gray;
self.nodes[i].total_durations = left_total_durations + right_total_durations;
self.nodes[i].earliest_completion =
right_earliest_completion.max(left_earliest_completion + right_total_durations);
self.nodes[i].total_durations_gray = (left_total_durations_gray + right_total_durations)
.max(left_total_durations + right_total_durations_gray);
self.nodes[i].earliest_completion_gray = right_earliest_completion_gray.max(
(left_earliest_completion + right_total_durations_gray)
.max(left_earliest_completion_gray + right_total_durations),
);
}
}
#[cfg(test)]
mod tests {
use expect_test::expect;
use tracing_test::traced_test;
use crate::{
constraints::disjunctive::DisjunctivePropagator,
solver::{LiteralStrategy, Solver},
};
#[test]
#[traced_test]
fn test_disjunctive_strict_propagator() {
for (edge_finding, not_last, detectable_precedence) in
itertools::iproduct!([true, false], [true, false], [true, false])
{
let mut slv = Solver::default();
let a = slv
.new_int_decision(0..=4)
.order_literals(LiteralStrategy::Eager)
.view();
let b = slv
.new_int_decision(0..=4)
.order_literals(LiteralStrategy::Eager)
.view();
let c = slv
.new_int_decision(0..=4)
.order_literals(LiteralStrategy::Eager)
.view();
let durations = vec![2, 3, 1];
DisjunctivePropagator::post(
&mut slv,
vec![a, b, c],
durations.clone(),
edge_finding,
not_last,
detectable_precedence,
);
DisjunctivePropagator::post(
&mut slv,
[a, b, c]
.iter()
.zip(durations.iter())
.map(|(v, d)| -*v + (7 - d))
.collect(),
durations.clone(),
edge_finding,
not_last,
detectable_precedence,
);
slv.expect_solutions(
&[a, b, c],
expect![[r#"
0, 3, 2
0, 4, 2
0, 4, 3
1, 3, 0
1, 4, 0
1, 4, 3
2, 4, 0
2, 4, 1
4, 0, 3
4, 1, 0"#]],
);
}
}
}