use std::cmp::min;
use std::rc::Rc;
use pumpkin_core::asserts::pumpkin_assert_extreme;
use pumpkin_core::proof::InferenceCode;
use pumpkin_core::propagation::Domains;
use pumpkin_core::propagation::EnqueueDecision;
use pumpkin_core::propagation::PropagationContext;
use pumpkin_core::propagation::ReadDomains;
use pumpkin_core::state::PropagationStatusCP;
use pumpkin_core::variables::IntegerVariable;
use crate::cumulative::CumulativeParameters;
use crate::cumulative::ResourceProfile;
use crate::cumulative::Task;
use crate::cumulative::UpdatableStructures;
use crate::cumulative::UpdatedTaskInfo;
use crate::propagators::cumulative::time_table::propagation_handler::CumulativePropagationHandler;
pub(crate) struct ShouldEnqueueResult<Var> {
pub(crate) decision: EnqueueDecision,
pub(crate) update: Option<UpdatedTaskInfo<Var>>,
}
pub(crate) fn should_enqueue<Var: IntegerVariable + 'static>(
parameters: &CumulativeParameters<Var>,
updatable_structures: &UpdatableStructures<Var>,
updated_task: &Rc<Task<Var>>,
mut context: Domains,
empty_time_table: bool,
) -> ShouldEnqueueResult<Var> {
pumpkin_assert_extreme!(
context.lower_bound(&updated_task.start_variable)
> updatable_structures.get_stored_lower_bound(updated_task)
|| updatable_structures.get_stored_upper_bound(updated_task)
>= context.upper_bound(&updated_task.start_variable),
"Either the stored lower-bound was larger than or equal to the actual lower bound or the upper-bound was smaller than or equal to the actual upper-bound\nThis either indicates that the propagator subscribed to events other than lower-bound and upper-bound updates or the stored bounds were not managed properly"
);
let mut result = ShouldEnqueueResult {
decision: EnqueueDecision::Skip,
update: None,
};
let old_lower_bound = updatable_structures.get_stored_lower_bound(updated_task);
let old_upper_bound = updatable_structures.get_stored_upper_bound(updated_task);
if old_lower_bound == context.lower_bound(&updated_task.start_variable)
&& old_upper_bound == context.upper_bound(&updated_task.start_variable)
{
return result;
}
if has_mandatory_part(context.reborrow(), updated_task) {
result.update = Some(UpdatedTaskInfo {
task: Rc::clone(updated_task),
old_lower_bound,
old_upper_bound,
new_lower_bound: context.lower_bound(&updated_task.start_variable),
new_upper_bound: context.upper_bound(&updated_task.start_variable),
});
}
result.decision = if parameters.options.allow_holes_in_domain {
if updatable_structures.has_updates() || result.update.is_some() {
EnqueueDecision::Enqueue
} else {
EnqueueDecision::Skip
}
} else {
if !empty_time_table || updatable_structures.has_updates() || result.update.is_some() {
EnqueueDecision::Enqueue
} else {
EnqueueDecision::Skip
}
};
result
}
pub(crate) fn has_mandatory_part<Var: IntegerVariable + 'static>(
context: Domains,
task: &Rc<Task<Var>>,
) -> bool {
context.upper_bound(&task.start_variable)
< context.lower_bound(&task.start_variable) + task.processing_time
}
pub(crate) fn has_mandatory_part_in_interval<Var: IntegerVariable + 'static>(
context: Domains,
task: &Rc<Task<Var>>,
start: i32,
end: i32,
) -> bool {
let (lower_bound, upper_bound) = (
context.lower_bound(&task.start_variable),
context.upper_bound(&task.start_variable),
);
(upper_bound < (lower_bound + task.processing_time))
&& has_overlap_with_interval(upper_bound, lower_bound + task.processing_time, start, end)
}
pub(crate) fn task_has_overlap_with_interval<Var: IntegerVariable + 'static>(
context: Domains,
task: &Rc<Task<Var>>,
start: i32,
end: i32,
) -> bool {
let (lower_bound, upper_bound) = (
context.lower_bound(&task.start_variable),
context.upper_bound(&task.start_variable) + task.processing_time,
); has_overlap_with_interval(lower_bound, upper_bound, start, end)
}
pub(crate) fn has_overlap_with_interval(
lower_bound: i32,
upper_bound: i32,
start: i32,
end: i32,
) -> bool {
start < upper_bound && lower_bound <= end
}
fn debug_check_whether_profiles_are_maximal_and_sorted<'a, Var: IntegerVariable + 'static>(
time_table: impl Iterator<Item = &'a ResourceProfile<Var>> + Clone,
) -> bool {
let collected_time_table = time_table.clone().collect::<Vec<_>>();
let sorted_profiles = collected_time_table.is_empty()
|| (0..collected_time_table.len() - 1).all(|profile_index| {
collected_time_table[profile_index].end < collected_time_table[profile_index + 1].start
});
if !sorted_profiles {
eprintln!("The provided time-table was not ordered according to start/end times");
}
let non_overlapping_profiles = collected_time_table.is_empty()
|| (0..collected_time_table.len()).all(|profile_index| {
(0..collected_time_table.len()).all(|other_profile_index| {
let current_profile = collected_time_table[profile_index];
let other_profile = collected_time_table[other_profile_index];
profile_index == other_profile_index
|| !has_overlap_with_interval(
current_profile.start,
current_profile.end + 1,
other_profile.start,
other_profile.end,
)
})
});
if !non_overlapping_profiles {
eprintln!("There was overlap between profiles in the provided time-table");
}
sorted_profiles && non_overlapping_profiles
}
pub(crate) fn propagate_based_on_timetable<'a, Var: IntegerVariable + 'static>(
context: &mut PropagationContext,
inference_code: &InferenceCode,
time_table: impl Iterator<Item = &'a ResourceProfile<Var>> + Clone,
parameters: &CumulativeParameters<Var>,
updatable_structures: &mut UpdatableStructures<Var>,
) -> PropagationStatusCP {
pumpkin_assert_extreme!(
debug_check_whether_profiles_are_maximal_and_sorted(time_table.clone()),
"The provided time-table did not adhere to the invariants"
);
pumpkin_assert_extreme!(
updatable_structures
.get_unfixed_tasks()
.all(|unfixed_task| !context.is_fixed(&unfixed_task.start_variable)),
"All of the unfixed tasks should not be fixed at this point"
);
pumpkin_assert_extreme!(
updatable_structures
.get_fixed_tasks()
.all(|fixed_task| context.is_fixed(&fixed_task.start_variable)),
"All of the fixed tasks should be fixed at this point"
);
if parameters.options.generate_sequence {
propagate_sequence_of_profiles(
context,
inference_code,
time_table,
updatable_structures,
parameters,
)?;
} else {
propagate_single_profiles(
context,
inference_code,
time_table,
updatable_structures,
parameters,
)?;
}
Ok(())
}
fn propagate_single_profiles<'a, Var: IntegerVariable + 'static>(
context: &mut PropagationContext,
inference_code: &InferenceCode,
time_table: impl Iterator<Item = &'a ResourceProfile<Var>> + Clone,
updatable_structures: &mut UpdatableStructures<Var>,
parameters: &CumulativeParameters<Var>,
) -> PropagationStatusCP {
let mut propagation_handler = CumulativePropagationHandler::new(
parameters.options.explanation_type,
inference_code.clone(),
);
'profile_loop: for profile in time_table {
propagation_handler.next_profile();
let mut task_index = 0;
while task_index < updatable_structures.number_of_unfixed_tasks() {
let task = updatable_structures.get_unfixed_task_at_index(task_index);
if context.is_fixed(&task.start_variable) {
updatable_structures.temporarily_remove_task_from_unfixed(&task);
if updatable_structures.has_no_unfixed_tasks() {
break 'profile_loop;
}
continue;
}
if profile.start > context.upper_bound(&task.start_variable) + task.processing_time {
updatable_structures.temporarily_remove_task_from_unfixed(&task);
if updatable_structures.has_no_unfixed_tasks() {
break 'profile_loop;
}
continue;
}
task_index += 1;
if lower_bound_can_be_propagated_by_profile(
context.domains(),
&task,
profile,
parameters.capacity,
) {
let result = propagation_handler.propagate_lower_bound_with_explanations(
context,
profile,
&task,
parameters.capacity,
);
if result.is_err() {
updatable_structures.restore_temporarily_removed();
result?;
}
}
if upper_bound_can_be_propagated_by_profile(
context.domains(),
&task,
profile,
parameters.capacity,
) {
let result = propagation_handler.propagate_upper_bound_with_explanations(
context,
profile,
&task,
parameters.capacity,
);
if result.is_err() {
updatable_structures.restore_temporarily_removed();
result?;
}
}
if parameters.options.allow_holes_in_domain
&& can_be_updated_by_profile(context.domains(), &task, profile, parameters.capacity)
{
let result = propagation_handler.propagate_holes_in_domain(
context,
profile,
&task,
parameters.capacity,
);
if result.is_err() {
updatable_structures.restore_temporarily_removed();
result?;
}
}
}
}
updatable_structures.restore_temporarily_removed();
Ok(())
}
fn propagate_sequence_of_profiles<'a, Var: IntegerVariable + 'static>(
context: &mut PropagationContext,
inference_code: &InferenceCode,
time_table: impl Iterator<Item = &'a ResourceProfile<Var>> + Clone,
updatable_structures: &mut UpdatableStructures<Var>,
parameters: &CumulativeParameters<Var>,
) -> PropagationStatusCP {
let mut profile_buffer = Vec::default();
let time_table = time_table.collect::<Vec<_>>();
if time_table.is_empty() {
return Ok(());
}
let mut propagation_handler = CumulativePropagationHandler::new(
parameters.options.explanation_type,
inference_code.clone(),
);
for task in updatable_structures.get_unfixed_tasks() {
if context.is_fixed(&task.start_variable) {
continue;
}
sweep_forward(
task,
&mut propagation_handler,
context,
&time_table,
parameters,
&mut profile_buffer,
)?;
sweep_backward(
task,
&mut propagation_handler,
context,
&time_table,
parameters,
&mut profile_buffer,
)?;
if parameters.options.allow_holes_in_domain {
let lower_bound_index = time_table.partition_point(|profile| {
profile.start < context.lower_bound(&task.start_variable)
});
let upper_bound_index = time_table.partition_point(|profile| {
profile.start < context.upper_bound(&task.start_variable) + task.processing_time
});
for profile in &time_table[lower_bound_index..upper_bound_index] {
propagation_handler.next_profile();
if can_be_updated_by_profile(context.domains(), task, profile, parameters.capacity)
{
propagation_handler.propagate_holes_in_domain(
context,
profile,
task,
parameters.capacity,
)?;
}
}
}
}
Ok(())
}
fn sweep_forward<'a, Var: IntegerVariable + 'static>(
task: &Rc<Task<Var>>,
propagation_handler: &mut CumulativePropagationHandler,
context: &mut PropagationContext,
time_table: &[&'a ResourceProfile<Var>],
parameters: &CumulativeParameters<Var>,
profile_buffer: &mut Vec<&'a ResourceProfile<Var>>,
) -> PropagationStatusCP {
let mut profile_index = time_table
.partition_point(|profile| profile.end < context.lower_bound(&task.start_variable));
'lower_bound_profile_loop: while profile_index < time_table.len() {
let profile = time_table[profile_index];
if profile.start > context.lower_bound(&task.start_variable) + task.processing_time {
break 'lower_bound_profile_loop;
}
propagation_handler.next_profile();
if lower_bound_can_be_propagated_by_profile(
context.domains(),
task,
profile,
parameters.capacity,
) {
find_profiles_which_propagate_lower_bound(
profile_index,
time_table,
context.domains(),
task,
parameters.capacity,
profile_buffer,
);
propagation_handler.propagate_chain_of_lower_bounds_with_explanations(
context,
profile_buffer,
task,
parameters.capacity,
)?;
break 'lower_bound_profile_loop;
}
profile_index += 1;
}
Ok(())
}
fn sweep_backward<'a, Var: IntegerVariable + 'static>(
task: &Rc<Task<Var>>,
propagation_handler: &mut CumulativePropagationHandler,
context: &mut PropagationContext,
time_table: &[&'a ResourceProfile<Var>],
parameters: &CumulativeParameters<Var>,
profile_buffer: &mut Vec<&'a ResourceProfile<Var>>,
) -> PropagationStatusCP {
let mut profile_index = min(
time_table.partition_point(|profile| {
profile.start < context.upper_bound(&task.start_variable) + task.processing_time
}),
time_table.len() - 1,
);
'upper_bound_profile_loop: loop {
let profile = time_table[profile_index];
if profile.end < context.upper_bound(&task.start_variable) {
break 'upper_bound_profile_loop;
}
propagation_handler.next_profile();
if upper_bound_can_be_propagated_by_profile(
context.domains(),
task,
profile,
parameters.capacity,
) {
find_profiles_which_propagate_upper_bound(
profile_index,
time_table,
context.domains(),
task,
parameters.capacity,
profile_buffer,
);
propagation_handler.propagate_chain_of_upper_bounds_with_explanations(
context,
profile_buffer,
task,
parameters.capacity,
)?;
break 'upper_bound_profile_loop;
}
if profile_index == 0 {
break 'upper_bound_profile_loop;
}
profile_index -= 1;
}
Ok(())
}
fn find_profiles_which_propagate_lower_bound<'a, Var: IntegerVariable + 'static>(
profile_index: usize,
time_table: &[&'a ResourceProfile<Var>],
mut context: Domains,
task: &Rc<Task<Var>>,
capacity: i32,
profile_buffer: &mut Vec<&'a ResourceProfile<Var>>,
) {
profile_buffer.clear();
profile_buffer.push(time_table[profile_index]);
let mut last_propagating_index = profile_index;
let mut current_index = profile_index + 1;
while current_index < time_table.len() {
let next_profile = time_table[current_index];
if next_profile.start - time_table[last_propagating_index].end >= task.processing_time {
break;
}
if overflows_capacity_and_is_not_part_of_profile(
context.reborrow(),
task,
next_profile,
capacity,
) {
last_propagating_index = current_index;
profile_buffer.push(time_table[current_index])
}
current_index += 1;
}
}
fn find_profiles_which_propagate_upper_bound<'a, Var: IntegerVariable + 'static>(
profile_index: usize,
time_table: &[&'a ResourceProfile<Var>],
mut context: Domains,
task: &Rc<Task<Var>>,
capacity: i32,
profile_buffer: &mut Vec<&'a ResourceProfile<Var>>,
) {
profile_buffer.clear();
profile_buffer.push(time_table[profile_index]);
if profile_index == 0 {
return;
}
let mut last_propagating = profile_index;
let mut current_index = profile_index - 1;
loop {
let previous_profile = time_table[current_index];
if time_table[last_propagating].start - previous_profile.end >= task.processing_time {
break;
}
if overflows_capacity_and_is_not_part_of_profile(
context.reborrow(),
task,
previous_profile,
capacity,
) {
last_propagating = current_index;
profile_buffer.push(time_table[current_index]);
}
if current_index == 0 {
break;
} else {
current_index -= 1;
}
}
profile_buffer.reverse();
}
fn lower_bound_can_be_propagated_by_profile<Var: IntegerVariable + 'static>(
mut context: Domains,
task: &Rc<Task<Var>>,
profile: &ResourceProfile<Var>,
capacity: i32,
) -> bool {
can_be_updated_by_profile(context.reborrow(), task, profile, capacity)
&& (context.lower_bound(&task.start_variable) + task.processing_time) > profile.start
&& context.lower_bound(&task.start_variable) <= profile.end
}
fn upper_bound_can_be_propagated_by_profile<Var: IntegerVariable + 'static>(
mut context: Domains,
task: &Rc<Task<Var>>,
profile: &ResourceProfile<Var>,
capacity: i32,
) -> bool {
can_be_updated_by_profile(context.reborrow(), task, profile, capacity)
&& (context.upper_bound(&task.start_variable) + task.processing_time) > profile.start
&& context.upper_bound(&task.start_variable) <= profile.end
}
fn can_be_updated_by_profile<Var: IntegerVariable + 'static>(
mut context: Domains,
task: &Rc<Task<Var>>,
profile: &ResourceProfile<Var>,
capacity: i32,
) -> bool {
overflows_capacity_and_is_not_part_of_profile(context.reborrow(), task, profile, capacity)
&& task_has_overlap_with_interval(context.reborrow(), task, profile.start, profile.end)
}
fn overflows_capacity_and_is_not_part_of_profile<Var: IntegerVariable + 'static>(
context: Domains,
task: &Rc<Task<Var>>,
profile: &ResourceProfile<Var>,
capacity: i32,
) -> bool {
profile.height + task.resource_usage > capacity
&& !has_mandatory_part_in_interval(context, task, profile.start, profile.end)
}
pub(crate) fn insert_update<Var: IntegerVariable + 'static>(
updated_task: &Rc<Task<Var>>,
updatable_structures: &mut UpdatableStructures<Var>,
potential_update: Option<UpdatedTaskInfo<Var>>,
) {
if let Some(update) = potential_update {
updatable_structures.task_has_been_updated(updated_task);
updatable_structures.insert_update_for_task(updated_task, update);
}
}
pub(crate) fn backtrack_update<Var: IntegerVariable + 'static>(
context: Domains,
updatable_structures: &mut UpdatableStructures<Var>,
updated_task: &Rc<Task<Var>>,
) {
let lower_bound_equal_to_stored = updatable_structures.get_stored_lower_bound(updated_task)
== context.lower_bound(&updated_task.start_variable);
let upper_bound_equal_to_stored = updatable_structures.get_stored_upper_bound(updated_task)
== context.upper_bound(&updated_task.start_variable);
let previously_did_not_have_mandatory_part = updatable_structures
.get_stored_upper_bound(updated_task)
>= updatable_structures.get_stored_lower_bound(updated_task) + updated_task.processing_time;
if (lower_bound_equal_to_stored && upper_bound_equal_to_stored)
|| previously_did_not_have_mandatory_part
{
return;
}
updatable_structures.task_has_been_updated(updated_task);
updatable_structures.insert_update_for_task(
updated_task,
UpdatedTaskInfo {
task: Rc::clone(updated_task),
old_lower_bound: updatable_structures.get_stored_lower_bound(updated_task),
old_upper_bound: updatable_structures.get_stored_upper_bound(updated_task),
new_lower_bound: context.lower_bound(&updated_task.start_variable),
new_upper_bound: context.upper_bound(&updated_task.start_variable),
},
);
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use pumpkin_core::propagation::LocalId;
use pumpkin_core::state::State;
use super::find_profiles_which_propagate_lower_bound;
use crate::cumulative::ResourceProfile;
use crate::cumulative::Task;
use crate::propagators::cumulative::time_table::time_table_util::find_profiles_which_propagate_upper_bound;
#[test]
fn test_finding_last_index_lower_bound() {
let mut state = State::default();
let x = state.new_interval_variable(0, 10, None);
let y = state.new_interval_variable(5, 5, None);
let z = state.new_interval_variable(8, 8, None);
let time_table = [
&ResourceProfile {
start: 5,
end: 6,
profile_tasks: vec![Rc::new(Task {
start_variable: y,
processing_time: 2,
resource_usage: 1,
id: LocalId::from(1),
})],
height: 1,
},
&ResourceProfile {
start: 8,
end: 8,
profile_tasks: vec![Rc::new(Task {
start_variable: z,
processing_time: 1,
resource_usage: 1,
id: LocalId::from(2),
})],
height: 1,
},
];
let mut profile_buffer = vec![];
find_profiles_which_propagate_lower_bound(
0,
&time_table,
state.get_domains(),
&Rc::new(Task {
start_variable: x,
processing_time: 6,
resource_usage: 1,
id: LocalId::from(0),
}),
1,
&mut profile_buffer,
);
assert_eq!(profile_buffer.len(), 2);
}
#[test]
fn test_finding_last_index_upper_bound() {
let mut state = State::default();
let x = state.new_interval_variable(7, 7, None);
let y = state.new_interval_variable(5, 5, None);
let z = state.new_interval_variable(8, 8, None);
let time_table = [
&ResourceProfile {
start: 5,
end: 6,
profile_tasks: vec![Rc::new(Task {
start_variable: y,
processing_time: 2,
resource_usage: 1,
id: LocalId::from(1),
})],
height: 1,
},
&ResourceProfile {
start: 8,
end: 8,
profile_tasks: vec![Rc::new(Task {
start_variable: z,
processing_time: 1,
resource_usage: 1,
id: LocalId::from(2),
})],
height: 1,
},
];
let mut profile_buffer = vec![];
find_profiles_which_propagate_upper_bound(
1,
&time_table,
state.get_domains(),
&Rc::new(Task {
start_variable: x,
processing_time: 6,
resource_usage: 1,
id: LocalId::from(0),
}),
1,
&mut profile_buffer,
);
assert_eq!(profile_buffer.len(), 2);
}
}